From 0dc96dc444c1bf48ba2dd664788a0f943df9d9c6 Mon Sep 17 00:00:00 2001 From: ninan-versioning <269335075+ninan-versioning@users.noreply.github.com> Date: Thu, 25 Jun 2026 03:43:02 +0000 Subject: [PATCH] feat(connectors/google-ads): first-class Google Ads primitives + MCP/HTTP-SSE + Temporal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Performance Ingest source #1 for the Thompson Sampling loop. Mirrors connectors/shopify exactly (primitives/, mcp/, temporal/; HTTP/SSE + bearer auth; Temporal idempotency). Four primitives: 1. ingestPerformance() — GAQL campaign/ad_group/ad metrics → one JSONL/day of typed PerformanceObservation (roas = conversions_value/cost). Dedup by (date, campaign_id, ad_group_id, ad_id). Shopify join via shoploop.gads_campaign_id. 2. MCP (7 tools on :8788) — list_campaigns, get_campaign, get_performance (daily/weekly), get_search_terms, get_change_history; pause/resume gated behind SHOPLOOP_GADS_WRITE_ENABLED. 3. recordPerformanceForBandit() — enqueues observations to BANDIT_QUEUE_PATH; Thompson Sampling consumer stubbed with a clear TODO. 4. Temporal — IngestGoogleAdsPerformanceWorkflow (hourly, last 25h overlap) + PauseUnderperformingCampaignWorkflow (no-op behind write flag). Idempotency id gads:::. CLI worker --once for dev. Cost: ~$0/mo incremental (Basic Access free, JSONL <$1, co-located on Shopify t4g.small). Co-Authored-By: Claude Opus 4.7 --- README.md | 16 + connectors/google-ads/.env.example | 13 + connectors/google-ads/.gitignore | 6 + connectors/google-ads/DESIGN.md | 71 + connectors/google-ads/README.md | 148 + connectors/google-ads/bin/cli.js | 2 + .../contracts/performance_observation.v1.json | 80 + connectors/google-ads/package-lock.json | 6567 +++++++++++++++++ connectors/google-ads/package.json | 53 + connectors/google-ads/src/bandit/record.ts | 45 + connectors/google-ads/src/cli.ts | 73 + connectors/google-ads/src/client.ts | 280 + connectors/google-ads/src/config.ts | 58 + connectors/google-ads/src/index.ts | 78 + connectors/google-ads/src/mcp/server.ts | 36 + .../google-ads/src/mcp/tools/get_campaign.ts | 20 + .../src/mcp/tools/get_change_history.ts | 20 + .../src/mcp/tools/get_performance.ts | 29 + .../src/mcp/tools/get_search_terms.ts | 25 + connectors/google-ads/src/mcp/tools/index.ts | 21 + .../src/mcp/tools/list_campaigns.ts | 19 + .../src/mcp/tools/pause_campaign.ts | 17 + .../src/mcp/tools/resume_campaign.ts | 17 + connectors/google-ads/src/mcp/tools/tool.ts | 31 + connectors/google-ads/src/mcp/transport.ts | 94 + connectors/google-ads/src/observe.ts | 29 + .../src/primitives/bandit_feedback.ts | 7 + .../src/primitives/ingest_performance.ts | 93 + connectors/google-ads/src/primitives/join.ts | 45 + .../google-ads/src/primitives/mcp_tools.ts | 7 + connectors/google-ads/src/rollup.ts | 43 + .../google-ads/src/temporal/activities.ts | 70 + .../google-ads/src/temporal/dispatch.ts | 106 + connectors/google-ads/src/temporal/worker.ts | 21 + .../google-ads/src/temporal/workflows.ts | 42 + connectors/google-ads/src/types.ts | 148 + .../test/fixtures/varitea_gads_response.json | 935 +++ connectors/google-ads/test/helpers.ts | 83 + connectors/google-ads/test/mcp.test.ts | 153 + connectors/google-ads/test/primitives.test.ts | 162 + connectors/google-ads/test/workflow.test.ts | 72 + connectors/google-ads/tsconfig.build.json | 9 + connectors/google-ads/tsconfig.json | 22 + connectors/google-ads/vitest.config.ts | 9 + 44 files changed, 9875 insertions(+) create mode 100644 connectors/google-ads/.env.example create mode 100644 connectors/google-ads/.gitignore create mode 100644 connectors/google-ads/DESIGN.md create mode 100644 connectors/google-ads/README.md create mode 100644 connectors/google-ads/bin/cli.js create mode 100644 connectors/google-ads/contracts/performance_observation.v1.json create mode 100644 connectors/google-ads/package-lock.json create mode 100644 connectors/google-ads/package.json create mode 100644 connectors/google-ads/src/bandit/record.ts create mode 100644 connectors/google-ads/src/cli.ts create mode 100644 connectors/google-ads/src/client.ts create mode 100644 connectors/google-ads/src/config.ts create mode 100644 connectors/google-ads/src/index.ts create mode 100644 connectors/google-ads/src/mcp/server.ts create mode 100644 connectors/google-ads/src/mcp/tools/get_campaign.ts create mode 100644 connectors/google-ads/src/mcp/tools/get_change_history.ts create mode 100644 connectors/google-ads/src/mcp/tools/get_performance.ts create mode 100644 connectors/google-ads/src/mcp/tools/get_search_terms.ts create mode 100644 connectors/google-ads/src/mcp/tools/index.ts create mode 100644 connectors/google-ads/src/mcp/tools/list_campaigns.ts create mode 100644 connectors/google-ads/src/mcp/tools/pause_campaign.ts create mode 100644 connectors/google-ads/src/mcp/tools/resume_campaign.ts create mode 100644 connectors/google-ads/src/mcp/tools/tool.ts create mode 100644 connectors/google-ads/src/mcp/transport.ts create mode 100644 connectors/google-ads/src/observe.ts create mode 100644 connectors/google-ads/src/primitives/bandit_feedback.ts create mode 100644 connectors/google-ads/src/primitives/ingest_performance.ts create mode 100644 connectors/google-ads/src/primitives/join.ts create mode 100644 connectors/google-ads/src/primitives/mcp_tools.ts create mode 100644 connectors/google-ads/src/rollup.ts create mode 100644 connectors/google-ads/src/temporal/activities.ts create mode 100644 connectors/google-ads/src/temporal/dispatch.ts create mode 100644 connectors/google-ads/src/temporal/worker.ts create mode 100644 connectors/google-ads/src/temporal/workflows.ts create mode 100644 connectors/google-ads/src/types.ts create mode 100644 connectors/google-ads/test/fixtures/varitea_gads_response.json create mode 100644 connectors/google-ads/test/helpers.ts create mode 100644 connectors/google-ads/test/mcp.test.ts create mode 100644 connectors/google-ads/test/primitives.test.ts create mode 100644 connectors/google-ads/test/workflow.test.ts create mode 100644 connectors/google-ads/tsconfig.build.json create mode 100644 connectors/google-ads/tsconfig.json create mode 100644 connectors/google-ads/vitest.config.ts diff --git a/README.md b/README.md index a991730..b3d5cbd 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,22 @@ | [`shoploop-shopify`](../shoploop-shopify) | Shopify app · theme extensions · 3D configurator | | [`shoploop-growth`](../shoploop-growth) | Google · Meta · Postiz · lifecycle · funnel analytics | +## Connectors + +First-class, MCP-shaped, Temporal-driven connectors live under [`connectors/`](./connectors). +Each is the same shape — `primitives/` · `mcp/` (HTTP/SSE + bearer auth) · `temporal/` over a +single internal core. Not thin REST wrappers. + +| Connector | Role in the loop | MCP port | Status | +|---|---|---|---| +| [`@shoploop/connector-shopify`](./connectors/shopify) | Catalog → `product_twin.usda` → scene composer (left half of the loop) | `:8787` | live | +| [`@shoploop/connector-google-ads`](./connectors/google-ads) | Performance Ingest #1 — ROAS/CTR/CAC → `PerformanceObservation` → Thompson Sampling → `brand_rules.json` | `:8788` | live | + +``` +Shopify catalog → product_twin.usda → render pack → Distribution +→ Google Ads Performance Ingest → Thompson Sampling → brand_rules.json (loop closes) +``` + ## Conversions - **[Request Demo →](https://shoploop.ai/demo)** diff --git a/connectors/google-ads/.env.example b/connectors/google-ads/.env.example new file mode 100644 index 0000000..1d0b982 --- /dev/null +++ b/connectors/google-ads/.env.example @@ -0,0 +1,13 @@ +GOOGLE_ADS_DEVELOPER_TOKEN= +GOOGLE_ADS_CLIENT_ID= +GOOGLE_ADS_CLIENT_SECRET= +GOOGLE_ADS_REFRESH_TOKEN= +GOOGLE_ADS_LOGIN_CUSTOMER_ID= +GOOGLE_ADS_API_VERSION=v17 +SHOPLOOP_GADS_WRITE_ENABLED=false +BANDIT_QUEUE_PATH=./var/bandit/queue.jsonl +SHOPLOOP_MCP_TOKEN= +SHOPLOOP_GADS_MCP_PORT=8788 +TEMPORAL_ADDRESS=localhost:7233 +TEMPORAL_NAMESPACE=shoploop +PERF_OUT_DIR=./var/shoploop/perf diff --git a/connectors/google-ads/.gitignore b/connectors/google-ads/.gitignore new file mode 100644 index 0000000..f2f5f9b --- /dev/null +++ b/connectors/google-ads/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +*.tsbuildinfo +.env +.env.local +var/ diff --git a/connectors/google-ads/DESIGN.md b/connectors/google-ads/DESIGN.md new file mode 100644 index 0000000..a680e33 --- /dev/null +++ b/connectors/google-ads/DESIGN.md @@ -0,0 +1,71 @@ +# DESIGN — connectors/google-ads + +Companion to the root [`design.md`](../../design.md) and [`AGENTS.md`](../../AGENTS.md), and a +sibling of [`connectors/shopify/DESIGN.md`](../shopify/DESIGN.md). This doc covers only what is +specific to the Google Ads → performance-ingest connector. + +## Position in the architecture loop + +``` +Distribution (Meta / TikTok / Klaviyo) +→ [THIS] Performance Ingest (ROAS, CTR, CAC) ← Google Ads is source #1 +→ Thompson Sampling +→ brand_rules.json (loop closes) +``` + +The Shopify connector owns the *left* half of the loop (catalog → USD scene graph → render +pack). This connector owns the *right* half's **entry point**: turning channel performance into a +typed reward signal **before any bandit math exists**. + +## The bandit loop (one paragraph) + +Performance signal in, `brand_rules.json` arm update out, render pack regenerates. Each +`PerformanceObservation` carries a stable arm key (`campaign_id`, joinable to a product → +scene preset via the `shoploop.gads_campaign_id` Shopify metafield) and a reward +(`metrics.roas = conversions_value / cost`). The Thompson Sampling consumer treats each arm as a +Beta(α,β) posterior, updates it from observed ROAS vs spend, and rewrites the winning-scene bias +in `brand_rules.json` so the next render pack regenerates toward the arm with the highest sampled +ROAS. **The bandit is the moat; this connector is its input layer** — so the producer side is +locked to the contract and the consumer is stubbed (`recordPerformanceForBandit` just enqueues to +`BANDIT_QUEUE_PATH`). A real implementation drops in on the consumer side without touching this +connector. + +## Single internal core + +`client.ts` (`google-ads-api`, GAQL, API v17) is the only place Ads logic lives. The CLI, the MCP +tools, and the Temporal activities are thin adapters over the `GoogleAdsClient` interface — which +is trivially mocked by `FixtureClient` in tests, so a manual `ingest` and a scheduled workflow run +identical code. + +## Why MCP-shaped, not a REST wrapper + +A REST wrapper is commodity. The value is the first-class surface: campaigns, performance, search +terms, and change history are addressable by an agent (Claude/Cursor, Open Employee, Agentframe) +over the same HTTP/SSE + bearer pattern as the Shopify connector — on `:8788` so the two MCP +servers co-locate on one box. Write tools (`pause_campaign`, `resume_campaign`) exist but are +gated behind `SHOPLOOP_GADS_WRITE_ENABLED`, default off. + +## Idempotency + +Hourly schedule. Workflow id = `gads:::` with +`WorkflowIdReusePolicy=REJECT_DUPLICATE`, so duplicate/retried fires within an hour collapse to one +execution. The ingest itself queries a 25h window (overlap) and dedupes by +`(date, campaign_id, ad_group_id, ad_id)` — belt and suspenders against gaps and double-counting. + +## Contract (referenced, not vendored long-term) + +`performance_observation.v1.json` lives in [`contracts/`](./contracts) for now; its eventual home +is `open-imagine/contracts/performance_observation.v1.json`. It is producer-agnostic +(`source ∈ {google-ads, meta, tiktok, klaviyo}`) so Meta/TikTok/Klaviyo ingest sources emit the +same shape into the same bandit. + +## Cost discipline + +CPU-only, no GPU. Varitea dogfood (1 customer, ~3 campaigns): API free (Basic Access), JSONL +storage <$1/mo, co-located on the Shopify `t4g.small` → ~$0/mo incremental. + +## Boundaries + +- No creative authoring / asset uploads — outbound push is a later PR. +- No render/image code → `open-imagine`. +- No bandit math here — producer side only. diff --git a/connectors/google-ads/README.md b/connectors/google-ads/README.md new file mode 100644 index 0000000..97ce75f --- /dev/null +++ b/connectors/google-ads/README.md @@ -0,0 +1,148 @@ +# @shoploop/connector-google-ads + +First-class **Google Ads → `PerformanceObservation`** primitives. Not a thin REST wrapper — +MCP-shaped, agent-callable, Temporal-driven. This is **Performance Ingest source #1**: the input +layer for the Thompson Sampling bandit that updates `brand_rules.json`. + +``` +Distribution (Meta / TikTok / Klaviyo) → [THIS] Performance Ingest (ROAS, CTR, CAC) +→ Thompson Sampling → brand_rules.json → render pack regenerates +``` + +Google Ads is the first source wired in — **Drink Varitea** already runs Google Ads. Mirrors +[`connectors/shopify`](../shopify) exactly: same `primitives/`, `mcp/`, `temporal/` layout, same +HTTP/SSE transport, same bearer auth, same Temporal idempotency convention. + +> Contract referenced (vendored here until promoted): +> `open-imagine/contracts/performance_observation.v1.json` — see [`contracts/`](./contracts). +> +> **Ingest + signal only.** No creative authoring / asset uploads — outbound push is a later PR. + +## The four primitives + +| # | Primitive | Entry | +|---|-----------|-------| +| 1 | **Performance ingest** → one JSONL/day of `PerformanceObservation` | `ingestPerformance()` / `shoploop-google-ads ingest` | +| 2 | **MCP** read/write (7 tools, HTTP/SSE on `:8788`) | `shoploop-google-ads mcp` → `:8788/sse` | +| 3 | **Brand-rules feedback hook** (enqueue reward signal) | `recordPerformanceForBandit()` | +| 4 | **Scheduled ingest workflow** (hourly, idempotent) | `shoploop-google-ads worker` | + +All four call the **same internal core** (`client.ts`) — importable from +`@shoploop/connector-google-ads`, callable from MCP, triggerable from Temporal. No duplication. + +## Quickstart — Varitea dogfood path + +```bash +cd connectors/google-ads +npm install +cp .env.example .env # fill in the GOOGLE_ADS_* OAuth2 creds +``` + +```bash +# .env essentials (OAuth2 refresh-token flow; Basic Access tier covers Varitea) +GOOGLE_ADS_DEVELOPER_TOKEN=... +GOOGLE_ADS_CLIENT_ID=... +GOOGLE_ADS_CLIENT_SECRET=... +GOOGLE_ADS_REFRESH_TOKEN=... +GOOGLE_ADS_LOGIN_CUSTOMER_ID=5128873230 # Varitea manager/customer id +GOOGLE_ADS_API_VERSION=v17 +SHOPLOOP_GADS_WRITE_ENABLED=false # write tools OFF by default +BANDIT_QUEUE_PATH=./var/bandit/queue.jsonl +PERF_OUT_DIR=./var/shoploop/perf +``` + +### 1. Ingest performance + +```bash +shoploop-google-ads ingest --customer 5128873230 --since 2026-06-12 --out ./var/shoploop/perf +# --until defaults to today; --since defaults to 7 days ago +``` + +**Expected output:** one JSONL per UTC day under `./var/shoploop/perf//YYYY-MM-DD.jsonl`. +Each line is a `PerformanceObservation` at campaign / ad_group / ad grain. For the Varitea Search +campaign (1 campaign, 2 ad groups, 5 ads) you get **8 observations/day** (1 + 2 + 5): + +```json +{"schema":"performance_observation.v1","source":"google-ads","customer_id":"5128873230","date":"2026-06-12","level":"campaign","campaign_id":"20451234567","campaign_name":"Varitea — Search — Brand + Loose Leaf","ad_group_id":null,"ad_group_name":null,"ad_id":null,"metrics":{"impressions":1529,"clicks":75,"conversions":8,"cost_micros":58264164,"cost":58.264164,"conversions_value":304,"ctr":0.0490,"average_cpc":0.7769,"roas":5.22},"shopify_product_gid":null,"observed_at":"2026-06-19T00:00:00.000Z"} +``` + +`roas = conversions_value / cost` is the reward signal. Rows dedupe by +`(date, campaign_id, ad_group_id, ad_id)`, so overlapping windows are safe to re-run. + +### 2. Join a campaign to its Shopify product + +The join lives on the Shopify side as the product metafield **`shoploop.gads_campaign_id`**. We do +**not** duplicate Shopify client code — inject the Shopify connector's `update_metafield` tool: + +```ts +import { linkCampaignToShopifyProduct } from "@shoploop/connector-google-ads"; +import { createShopifyClient } from "@shoploop/connector-shopify"; + +const shopify = createShopifyClient({ /* ... */ }); +await linkCampaignToShopifyProduct({ + productGid: "gid://shopify/Product/7700112233445", + campaignId: "20451234567", + updateMetafield: shopify.updateMetafield, // the canonical Shopify write path +}); +``` + +Read those metafields back into a `campaignId → productGid` map (`buildJoinMap`) and pass it as +`ingestPerformance({ shopifyJoin })` — every observation then carries `shopify_product_gid`, which +the bandit uses to find the right `brand_rules.json` arm. + +### 3. Boot the MCP server + +```bash +shoploop-google-ads mcp # Express + SSE on :8788 (/sse, /messages) +``` + +Bearer auth via `SHOPLOOP_MCP_TOKEN` (unset ⇒ open, dev only). CORS allowlist for Open Employee + +Agentframe origins. Tools: + +| Tool | Kind | +|------|------| +| `list_campaigns(customerId, status?)` | read | +| `get_campaign(customerId, campaignId)` | read | +| `get_performance(customerId, range, granularity)` — daily/weekly rollups | read | +| `get_search_terms(customerId, campaignId, range)` — negative-keyword harvesting | read | +| `get_change_history(customerId, range)` — what changed and when | read | +| `pause_campaign(customerId, campaignId)` | **write** | +| `resume_campaign(customerId, campaignId)` | **write** | + +Write tools are **refused** unless `SHOPLOOP_GADS_WRITE_ENABLED=true` (default off). + +### 4. Run the Temporal worker + +```bash +shoploop-google-ads worker # task queue: shoploop-google-ads +shoploop-google-ads worker --once --customer 5128873230 # dev: run hourly ingest once, on-demand +``` + +| Workflow | Trigger | Idempotency id | +|----------|---------|----------------| +| `IngestGoogleAdsPerformanceWorkflow(customerId)` | hourly; queries last 25h (overlap) | `gads::IngestGoogleAdsPerformanceWorkflow:` | +| `PauseUnderperformingCampaignWorkflow(customerId, campaignId, reason)` | bandit (later); no-op behind write flag | `gads::PauseUnderperformingCampaignWorkflow::` | + +The ingest workflow writes JSONL then flushes every observation onto the bandit queue +(`BANDIT_QUEUE_PATH`). + +## Develop + +```bash +npm run typecheck # tsc --noEmit (src + test) +npm test # vitest (primitives, mcp, workflow idempotency) +npm run build # emit dist/ (CLI bin → dist/cli.js) +``` + +## Cost (Varitea dogfood — 1 customer, ~3 campaigns) + +- API quota: **free** (Basic Access tier). +- Storage (JSONL ~5 MB/mo): **<$1**. +- Co-locates on the same `t4g.small` as the Shopify MCP → **$0 incremental compute**. +- **Total: ~$0/mo incremental.** + +## Boundaries + +- No creative authoring / asset uploads → later PR. +- No render/image code → `open-imagine`. +- The real Thompson Sampling update is **stubbed** — this connector is the producer side only. diff --git a/connectors/google-ads/bin/cli.js b/connectors/google-ads/bin/cli.js new file mode 100644 index 0000000..8fb1272 --- /dev/null +++ b/connectors/google-ads/bin/cli.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import "../dist/cli.js"; diff --git a/connectors/google-ads/contracts/performance_observation.v1.json b/connectors/google-ads/contracts/performance_observation.v1.json new file mode 100644 index 0000000..087007e --- /dev/null +++ b/connectors/google-ads/contracts/performance_observation.v1.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "performance_observation.v1", + "title": "PerformanceObservation", + "description": "One performance datapoint from a distribution channel for one entity (campaign/ad_group/ad) on one day. Producer-agnostic reward signal for the Thompson Sampling bandit that updates brand_rules.json arms. EVENTUAL HOME: open-imagine/contracts/performance_observation.v1.json — vendored here until promoted.", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "source", + "customer_id", + "date", + "level", + "campaign_id", + "metrics", + "observed_at" + ], + "properties": { + "schema": { "const": "performance_observation.v1" }, + "source": { + "type": "string", + "description": "Channel that produced this observation.", + "enum": ["google-ads", "meta", "tiktok", "klaviyo"] + }, + "customer_id": { + "type": "string", + "description": "Channel account id (Google Ads customer id, digits only, no dashes)." + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "description": "UTC calendar day this datapoint aggregates." + }, + "level": { + "type": "string", + "enum": ["campaign", "ad_group", "ad"], + "description": "Aggregation grain. Dedup key is (date, campaign_id, ad_group_id, ad_id)." + }, + "campaign_id": { "type": "string" }, + "campaign_name": { "type": "string" }, + "ad_group_id": { "type": ["string", "null"] }, + "ad_group_name": { "type": ["string", "null"] }, + "ad_id": { "type": ["string", "null"] }, + "metrics": { + "type": "object", + "additionalProperties": false, + "required": [ + "impressions", + "clicks", + "conversions", + "cost_micros", + "cost", + "conversions_value", + "ctr", + "average_cpc", + "roas" + ], + "properties": { + "impressions": { "type": "integer", "minimum": 0 }, + "clicks": { "type": "integer", "minimum": 0 }, + "conversions": { "type": "number", "minimum": 0 }, + "cost_micros": { "type": "integer", "minimum": 0 }, + "cost": { "type": "number", "minimum": 0, "description": "cost_micros / 1e6, channel currency." }, + "conversions_value": { "type": "number", "minimum": 0 }, + "ctr": { "type": "number", "minimum": 0, "description": "clicks / impressions." }, + "average_cpc": { "type": "number", "minimum": 0, "description": "cost / clicks." }, + "roas": { "type": "number", "minimum": 0, "description": "conversions_value / cost. THE reward signal for the bandit." } + } + }, + "shopify_product_gid": { + "type": ["string", "null"], + "description": "Joined Shopify product gid via the shoploop.gads_campaign_id metafield. Null until the join is wired." + }, + "observed_at": { + "type": "string", + "format": "date-time", + "description": "When this connector ingested the datapoint (ISO 8601)." + } + } +} diff --git a/connectors/google-ads/package-lock.json b/connectors/google-ads/package-lock.json new file mode 100644 index 0000000..b57954c --- /dev/null +++ b/connectors/google-ads/package-lock.json @@ -0,0 +1,6567 @@ +{ + "name": "@shoploop/connector-google-ads", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@shoploop/connector-google-ads", + "version": "0.1.0", + "license": "UNLICENSED", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.18.0", + "@temporalio/activity": "^1.11.0", + "@temporalio/client": "^1.11.0", + "@temporalio/worker": "^1.11.0", + "@temporalio/workflow": "^1.11.0", + "express": "^4.21.0", + "google-ads-api": "^17.1.0", + "zod": "^3.23.0" + }, + "bin": { + "shoploop-google-ads": "bin/cli.js" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.14.0", + "tsx": "^4.19.0", + "typescript": "^5.6.0", + "vitest": "^2.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "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/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.57.8", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.8.tgz", + "integrity": "sha512-YzVbwggV9452VCeHgo0bjsTaUt1O7JE0XpEsPar93nn/+RAwXk0mb1Y+f5EDJ3TRtRCFe+Ck5RuojdfB4jeHVw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.8", + "@jsonjoy.com/fs-node-utils": "4.57.8", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.57.8", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.8.tgz", + "integrity": "sha512-vmClyvCQMxgqz7uamDiGtRfp4MjzOznk3pcQjCxlIwJcw7TWeyr+bF30hI0x8NxdtNOGMg1pHM74VDIXOeyjuw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.8", + "@jsonjoy.com/fs-node-builtins": "4.57.8", + "@jsonjoy.com/fs-node-utils": "4.57.8", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.57.8", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.8.tgz", + "integrity": "sha512-IPEOlDYSnTDYpjQlQg2F8h+eqxKQN3sdbroI0WrteRiQZ462HzVpBo9ZZX485njz4nAacoe3fd4iDiIhk+k5Hg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.8", + "@jsonjoy.com/fs-node-builtins": "4.57.8", + "@jsonjoy.com/fs-node-utils": "4.57.8", + "@jsonjoy.com/fs-print": "4.57.8", + "@jsonjoy.com/fs-snapshot": "4.57.8", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.57.8", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.8.tgz", + "integrity": "sha512-mxXSXw8zZwRVakcjLqR2I/psy4gURFSASZS10kKJ2kJw05GC2nXGroGrWVHxwgkxXgQLsFQnB74QaLzsxzdL/w==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.57.8", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.8.tgz", + "integrity": "sha512-AWZcT/4+H+iDl4XCukbXrarvwEgOrf/prFI5/7eg4ix9FxqVsZysIDJd1Kjd+AjlCeHKHJOaRqjLd5HiGSCJEw==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.57.8", + "@jsonjoy.com/fs-node-builtins": "4.57.8", + "@jsonjoy.com/fs-node-utils": "4.57.8" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.57.8", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.8.tgz", + "integrity": "sha512-E/bJ7sQAb4pu9nbeJhbULU3WnqWrswte4N9Js/oHt7aHB746S8/XBqKlcbrqIgnD3095XluovNEZuu5ONT230g==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.8" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.57.8", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.8.tgz", + "integrity": "sha512-DfzhOBpmvNu5P/KSe4NNQaOnvNliTdcf0qrh/4EReErF/XUQXYkd0vZl/OiJCm/qjEEo8DWRstliw2/JNS84dA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.57.8", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.57.8", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.8.tgz", + "integrity": "sha512-L+eqKaWOHLDaiMv1dh/EWQ4hA+o6xAhWSumTo3Teg7OM18jU/KE13/e8Mfal+eAZ/pSl4wIhKHcDiwapJzC8Wg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.57.8", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "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.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz", + "integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.27" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.43", + "@swc/core-darwin-x64": "1.15.43", + "@swc/core-linux-arm-gnueabihf": "1.15.43", + "@swc/core-linux-arm64-gnu": "1.15.43", + "@swc/core-linux-arm64-musl": "1.15.43", + "@swc/core-linux-ppc64-gnu": "1.15.43", + "@swc/core-linux-s390x-gnu": "1.15.43", + "@swc/core-linux-x64-gnu": "1.15.43", + "@swc/core-linux-x64-musl": "1.15.43", + "@swc/core-win32-arm64-msvc": "1.15.43", + "@swc/core-win32-ia32-msvc": "1.15.43", + "@swc/core-win32-x64-msvc": "1.15.43" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz", + "integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz", + "integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz", + "integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz", + "integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz", + "integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz", + "integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz", + "integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz", + "integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz", + "integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz", + "integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz", + "integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz", + "integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz", + "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@temporalio/activity": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@temporalio/activity/-/activity-1.18.1.tgz", + "integrity": "sha512-Y24veV/dOSavnXK0YRifM/eoM+i7A814w2CMnlw8H9GYYIKMo71JMMq6CEJkhueVuMJxGb4Ajg+uvWUT/Ocebw==", + "license": "MIT", + "dependencies": { + "@temporalio/client": "1.18.1", + "@temporalio/common": "1.18.1" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/@temporalio/client": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@temporalio/client/-/client-1.18.1.tgz", + "integrity": "sha512-ToYL+qTCO8AE7IBsk1kmCZYh2Nu6e2W8QB+H5cKp2l8VUYWljNKXfb1aguGVN1gnCsBhbcu3s7dJmltLvlOcSw==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@temporalio/common": "1.18.1", + "@temporalio/proto": "1.18.1", + "abort-controller": "^3.0.0", + "long": "^5.2.3", + "nexus-rpc": "^0.0.2", + "uuid": "^11.1.0" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/@temporalio/common": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@temporalio/common/-/common-1.18.1.tgz", + "integrity": "sha512-2hYWRKmY8WLt/8Nkrx2tziGAoNcbHDWbQR8AGvJ2xcap5E3hTIQPiNDARUm0tyCEK9umnCQztndbDxzLcxUZeA==", + "license": "MIT", + "dependencies": { + "@temporalio/proto": "1.18.1", + "long": "^5.2.3", + "ms": "3.0.0-canary.1", + "nexus-rpc": "^0.0.2", + "proto3-json-serializer": "^2.0.0" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/@temporalio/core-bridge": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@temporalio/core-bridge/-/core-bridge-1.18.1.tgz", + "integrity": "sha512-iMTF6piD03YyjFFDjbmY+yGYptEtNtM4QEC8/8EUR36Ze6DkyqW1gll0VUyUCLpkhHhy4+XLBtqQ0bfpen+Csw==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@temporalio/common": "1.18.1" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/@temporalio/nexus": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@temporalio/nexus/-/nexus-1.18.1.tgz", + "integrity": "sha512-LNrB+41kTEV32fCRHboLOT3D8RRVzaI953iqiHEaz0tkVVQmYUzRVQpSID/UjWExWw1mbaIvpFmKkYvY0g2j2A==", + "license": "MIT", + "dependencies": { + "@temporalio/client": "1.18.1", + "@temporalio/common": "1.18.1", + "@temporalio/proto": "1.18.1", + "long": "^5.2.3", + "nexus-rpc": "^0.0.2" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/@temporalio/proto": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@temporalio/proto/-/proto-1.18.1.tgz", + "integrity": "sha512-pLa1JkmVwGdoFPCmb9DJ4P0z2mk3Zyu1VW6pXvJiYjt6W52Vm7YYi0caqfcSs/Wb/ZamdMbGqMcezftzVdzkYg==", + "license": "MIT", + "dependencies": { + "long": "^5.2.3", + "protobufjs": "7.5.8" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/@temporalio/proto/node_modules/protobufjs": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.8.tgz", + "integrity": "sha512-dvpCIeLPbXZS/Ete7yLaO7RenOdken2NHKykBXbsaGxZT0UTltcarBciw+A78SRQs9iMAAVpsYA+l8b1hTePIA==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.1", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@temporalio/worker": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@temporalio/worker/-/worker-1.18.1.tgz", + "integrity": "sha512-ydeTHhlZF4G9ye5YnyBNgZT8/7lpGdEaF4LwJLUsW33AtLiqUp5zkzwefktpn/vyHWmNWjDdacxZ3l8WgdN+0w==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.12.4", + "@swc/core": "^1.3.102", + "@temporalio/activity": "1.18.1", + "@temporalio/client": "1.18.1", + "@temporalio/common": "1.18.1", + "@temporalio/core-bridge": "1.18.1", + "@temporalio/nexus": "1.18.1", + "@temporalio/proto": "1.18.1", + "@temporalio/workflow": "1.18.1", + "heap-js": "^2.6.0", + "memfs": "^4.6.0", + "nexus-rpc": "^0.0.2", + "protobufjs": "^7.5.8", + "rxjs": "^7.8.1", + "source-map": "^0.7.4", + "source-map-loader": "^5.0.0", + "supports-color": "^8.1.1", + "swc-loader": "^0.2.3", + "unionfs": "^4.5.1", + "webpack": "^5.106.2" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/@temporalio/workflow": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@temporalio/workflow/-/workflow-1.18.1.tgz", + "integrity": "sha512-UabVy4/Jbg9IiQFKfHQn4DKhnAqczo/TkzP/j8lebH+oK6Gqq/sLaFwIWEdVB0hSMO99q/A85NJvUbkr4n0DCA==", + "license": "MIT", + "dependencies": { + "@temporalio/common": "1.18.1", + "@temporalio/proto": "1.18.1", + "nexus-rpc": "^0.0.2" + }, + "engines": { + "node": ">= 20.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "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/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "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/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "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/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "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/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "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/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "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/agent-base/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/agent-base/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/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "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/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "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/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/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "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/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "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/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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/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/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.378", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.378.tgz", + "integrity": "sha512-VinvOAuuPmdD1guEgGv5f2Qp7/vlfqOrUOMYNnOD4wj3pit8kRsQHzfIf6teyUGWo15Tg5+bOJaRunvyltpVWQ==", + "license": "ISC" + }, + "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/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/enhanced-resolve": { + "version": "5.24.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.1.tgz", + "integrity": "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.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-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "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.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "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/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.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/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "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/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "license": "Apache-2.0" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/form-data": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.6.tgz", + "integrity": "sha512-Ogz/E85h9tlfJzpI6TuFpGcHZFhLrb9Gw8wq9v40CxSCPnv7ahKr6Xgtkn0KYCDQJ8DNn5VoMO8EXr9V5PadyA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "license": "Unlicense" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "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": "4.3.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", + "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", + "license": "Apache-2.0", + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/google-ads-api": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/google-ads-api/-/google-ads-api-17.1.0.tgz", + "integrity": "sha512-dI8vtEb+Rkaxh1UudHyla2jw119ffKypvGOcaRXqh3mckk6mGVDkx04FdovVemBid5/cfJR8Hvb90gVU9hyZmw==", + "license": "MIT", + "dependencies": { + "@isaacs/ttlcache": "^1.2.2", + "google-ads-node": "14.0.0", + "google-auth-library": "^7.1.0", + "google-gax": "^4.4.1", + "long": "^4.0.0" + } + }, + "node_modules/google-ads-api/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, + "node_modules/google-ads-node": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/google-ads-node/-/google-ads-node-14.0.0.tgz", + "integrity": "sha512-rbgZkcaRSCQKnwRki4Jl2GY6Csd12a3ouX45xcK1ttFbJqtKrm8GPkctAIVnwAx0yVi8VMgpFWaEls8/i0SGJg==", + "license": "MIT", + "dependencies": { + "google-gax": "^4.4.1", + "lru-cache": "^10.2.0" + } + }, + "node_modules/google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "license": "Apache-2.0", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-auth-library/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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-gax/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/google-gax/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/google-gax/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/google-gax/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/google-gax/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/google-gax/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/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/google-gax/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/google-gax/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/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "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/google-p12-pem": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.4.tgz", + "integrity": "sha512-HHuHmkLgwjdmVRngf5+gSmpkyaRI6QmOg77J8tkNBHhNEI62sGHyw4/+UkgyZEI7h84NbWprXDJ+sa3xOYFvTg==", + "deprecated": "Package is no longer maintained", + "license": "MIT", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "license": "MIT", + "dependencies": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/heap-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/heap-js/-/heap-js-2.7.1.tgz", + "integrity": "sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/hono": { + "version": "4.12.27", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz", + "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/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/http-proxy-agent/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/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/https-proxy-agent/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/https-proxy-agent/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/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "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/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "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/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "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/loader-runner": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "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/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.57.8", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.8.tgz", + "integrity": "sha512-bApYhn8BLpFAnAQmFfEl/NPN+8qx5Ar3V4Qt3ek23mVwBEElzV7c6XoPkb/PCG8ZFpowCEpHcPwMFTwHS7tSMA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.8", + "@jsonjoy.com/fs-fsa": "4.57.8", + "@jsonjoy.com/fs-node": "4.57.8", + "@jsonjoy.com/fs-node-builtins": "4.57.8", + "@jsonjoy.com/fs-node-to-fsa": "4.57.8", + "@jsonjoy.com/fs-node-utils": "4.57.8", + "@jsonjoy.com/fs-print": "4.57.8", + "@jsonjoy.com/fs-snapshot": "4.57.8", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=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/ms": { + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "license": "MIT", + "engines": { + "node": ">=12.13" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/nexus-rpc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/nexus-rpc/-/nexus-rpc-0.0.2.tgz", + "integrity": "sha512-IWjIExdVYlmwXuzHdY/Q3lXCv1gbqoAXPazQhy2w4Xgtgha3H0OOujEESVPQcFUFMWm+pAk2gKnb57g8S41JZg==", + "license": "MIT", + "engines": { + "node": ">= 20.0.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/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.49", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.49.tgz", + "integrity": "sha512-f06bl1D+8ZDkn2oOQQKAh5/otFWqVnM1Q5oerA8Pex7UfT66Tx4IPHIqVVFKqFT3FUtaDstdgkM7yT7JWhqxfw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/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/router/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/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "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.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "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/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/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/swc-loader": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.7.tgz", + "integrity": "sha512-nwYWw3Fh9ame3Rtm7StS9SBLpHRRnYcK7bnpF3UKZmesAK0gw2/ADvlURFAINmPvKtDLzp+GBiP9yLoEjg6S9w==", + "license": "MIT", + "dependencies": { + "@swc/counter": "^0.1.3" + }, + "peerDependencies": { + "@swc/core": "^1.2.147", + "webpack": ">=2" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/terser": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", + "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/thingies": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.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/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unionfs": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/unionfs/-/unionfs-4.6.0.tgz", + "integrity": "sha512-fJAy3gTHjFi5S3TP5EGdjs/OUMFFvI/ady3T8qVuZfkv8Qi8prV/Q8BuFEgODJslhZTT2z2qdD2lGdee9qjEnA==", + "dependencies": { + "fs-monkey": "^1.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "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/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vite-node/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vitest/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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/webpack": { + "version": "5.107.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.107.2.tgz", + "integrity": "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.22.0", + "es-module-lexer": "^2.1.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "loader-runner": "^4.3.2", + "mime-db": "^1.54.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.5.0", + "watchpack": "^2.5.1", + "webpack-sources": "^3.5.0" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", + "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "license": "MIT" + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "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/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/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "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" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/connectors/google-ads/package.json b/connectors/google-ads/package.json new file mode 100644 index 0000000..4aab009 --- /dev/null +++ b/connectors/google-ads/package.json @@ -0,0 +1,53 @@ +{ + "name": "@shoploop/connector-google-ads", + "version": "0.1.0", + "description": "First-class Google Ads → PerformanceObservation ingest + MCP (HTTP/SSE) + Temporal. Performance Ingest source #1 for the Thompson Sampling loop.", + "license": "UNLICENSED", + "private": true, + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "bin": { + "shoploop-google-ads": "./bin/cli.js" + }, + "files": [ + "dist", + "bin", + "contracts" + ], + "scripts": { + "build": "tsc -p tsconfig.build.json", + "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "cli": "tsx src/cli.ts", + "mcp": "tsx src/cli.ts mcp", + "worker": "tsx src/cli.ts worker" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.18.0", + "@temporalio/activity": "^1.11.0", + "@temporalio/client": "^1.11.0", + "@temporalio/worker": "^1.11.0", + "@temporalio/workflow": "^1.11.0", + "express": "^4.21.0", + "google-ads-api": "^17.1.0", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.14.0", + "tsx": "^4.19.0", + "typescript": "^5.6.0", + "vitest": "^2.1.0" + }, + "engines": { + "node": ">=20" + } +} diff --git a/connectors/google-ads/src/bandit/record.ts b/connectors/google-ads/src/bandit/record.ts new file mode 100644 index 0000000..837a437 --- /dev/null +++ b/connectors/google-ads/src/bandit/record.ts @@ -0,0 +1,45 @@ +import { mkdir, appendFile } from "node:fs/promises"; +import { dirname } from "node:path"; +import { loadConfig } from "../config.js"; +import type { PerformanceObservation } from "../types.js"; + +// Primitive 3: brand-rules feedback hook. +// +// The producer side of the bandit loop. A PerformanceObservation carries everything a +// Thompson Sampling update needs: a stable arm key (campaign_id, joinable to a brand_rules +// arm via shopify_product_gid) and the reward (metrics.roas). We enqueue it durably and +// return — the consumer is intentionally decoupled. +// +// TODO: Thompson Sampling update — wire to brand_rules.json arms. +// Consume this queue, treat each campaign_id (joined → product → scene preset) as a bandit +// arm, update its Beta(α,β) posterior from metrics.roas (reward) vs spend (pulls), and +// rewrite the winning-scene bias in brand_rules.json so the next render pack regenerates +// toward the arm with the highest sampled ROAS. Nothing on THIS producer side changes when +// that lands — the observation shape is the contract. + +export interface RecordOptions { + queuePath?: string; // defaults to BANDIT_QUEUE_PATH +} + +export async function recordPerformanceForBandit( + observation: PerformanceObservation, + opts: RecordOptions = {}, +): Promise { + const queuePath = opts.queuePath ?? loadConfig().banditQueuePath; + await mkdir(dirname(queuePath), { recursive: true }); + await appendFile(queuePath, JSON.stringify(observation) + "\n", "utf8"); +} + +// Batch convenience used by the ingest workflow — same single-writer queue, no dedup here +// (the producer already deduped by observationKey before enqueuing). +export async function recordBatchForBandit( + observations: PerformanceObservation[], + opts: RecordOptions = {}, +): Promise { + if (observations.length === 0) return 0; + const queuePath = opts.queuePath ?? loadConfig().banditQueuePath; + await mkdir(dirname(queuePath), { recursive: true }); + const payload = observations.map((o) => JSON.stringify(o)).join("\n") + "\n"; + await appendFile(queuePath, payload, "utf8"); + return observations.length; +} diff --git a/connectors/google-ads/src/cli.ts b/connectors/google-ads/src/cli.ts new file mode 100644 index 0000000..02e0947 --- /dev/null +++ b/connectors/google-ads/src/cli.ts @@ -0,0 +1,73 @@ +import { loadConfig } from "./config.js"; +import { ingestPerformance } from "./primitives/ingest_performance.js"; +import { startMcpServer } from "./mcp/transport.js"; +import { runWorker } from "./temporal/worker.js"; +import { runIngestOnce } from "./temporal/dispatch.js"; + +function flag(argv: string[], name: string): string | undefined { + const i = argv.indexOf(`--${name}`); + return i >= 0 && i + 1 < argv.length ? argv[i + 1] : undefined; +} +function has(argv: string[], name: string): boolean { + return argv.includes(`--${name}`); +} + +const USAGE = `shoploop-google-ads + +Commands: + ingest --customer [--since ] [--until ] --out + Google Ads performance → JSONL PerformanceObservation + mcp Boot MCP HTTP/SSE server on :8788 + worker [--once --customer ] Boot Temporal worker (queue: shoploop-google-ads); + --once runs IngestGoogleAdsPerformanceWorkflow a single + time on-demand (dev) instead of serving the queue +`; + +async function main(): Promise { + const [, , command, ...rest] = process.argv; + + switch (command) { + case "ingest": { + const customerId = flag(rest, "customer") ?? process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID; + const out = flag(rest, "out") ?? process.env.PERF_OUT_DIR; + const since = flag(rest, "since") ?? new Date(Date.now() - 7 * 86400_000).toISOString().slice(0, 10); + const until = flag(rest, "until"); + if (!customerId || !out) { + console.error("ingest requires --customer and --out"); + process.exit(2); + } + const report = await ingestPerformance({ customerId, since, until, outDir: out }); + console.log(JSON.stringify(report, null, 2)); + console.log(`\n${report.observations} observations across ${report.days} day(s) → ${out}/${report.customerId}`); + break; + } + case "mcp": { + startMcpServer(); + break; + } + case "worker": { + if (has(rest, "once")) { + const customerId = flag(rest, "customer") ?? process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID; + if (!customerId) { + console.error("worker --once requires --customer "); + process.exit(2); + } + loadConfig(); // fail fast on missing temporal env + const { workflowId, result } = await runIngestOnce(customerId); + console.log(`ran ${workflowId}`); + console.log(JSON.stringify(result, null, 2)); + process.exit(0); + } + await runWorker(); + break; + } + default: + console.log(USAGE); + process.exit(command ? 2 : 0); + } +} + +main().catch((err) => { + console.error(err instanceof Error ? err.stack : String(err)); + process.exit(1); +}); diff --git a/connectors/google-ads/src/client.ts b/connectors/google-ads/src/client.ts new file mode 100644 index 0000000..1577656 --- /dev/null +++ b/connectors/google-ads/src/client.ts @@ -0,0 +1,280 @@ +import { GoogleAdsApi, enums } from "google-ads-api"; +import { + normalizeCustomerId, + type CampaignDetail, + type CampaignSummary, + type ChangeEvent, + type PerfRow, + type SearchTermRow, +} from "./types.js"; + +export interface DateRange { + since: string; // YYYY-MM-DD inclusive + until: string; // YYYY-MM-DD inclusive +} + +// The internal core surface. CLI, MCP tools, and Temporal activities all depend on THIS +// interface — a single source of truth, trivially mockable in tests via FixtureClient. +// customerId is a parameter (not fixed at construction): one manager login fans out to +// many client accounts. +export interface GoogleAdsClient { + listCampaigns(customerId: string, status?: string): Promise; + getCampaign(customerId: string, campaignId: string): Promise; + // All three grains (campaign/ad_group/ad) for the range, flattened to PerfRow[]. + queryPerformance(customerId: string, range: DateRange): Promise; + getSearchTerms(customerId: string, campaignId: string, range: DateRange): Promise; + getChangeHistory(customerId: string, range: DateRange): Promise; + // Write surface — gated behind SHOPLOOP_GADS_WRITE_ENABLED at the MCP/Temporal layer. + pauseCampaign(customerId: string, campaignId: string): Promise<{ resourceName: string }>; + resumeCampaign(customerId: string, campaignId: string): Promise<{ resourceName: string }>; +} + +export interface GoogleAdsClientOptions { + developerToken: string; + clientId: string; + clientSecret: string; + refreshToken: string; + loginCustomerId: string; +} + +type RawRow = Record; + +function s(v: unknown): string { + return v === undefined || v === null ? "" : String(v); +} +function n(v: unknown): number { + const x = Number(v); + return Number.isFinite(x) ? x : 0; +} + +// Google Ads returns enum fields as numeric values; map back to the readable name. +function enumName(e: Record, v: unknown): string { + if (typeof v === "string") return v; + for (const [k, val] of Object.entries(e)) if (val === v) return k; + return s(v); +} + +const PERF_METRICS = ` + metrics.impressions, + metrics.clicks, + metrics.conversions, + metrics.cost_micros, + metrics.conversions_value +`; + +export function mapCampaignRow(r: RawRow): PerfRow { + return { + level: "campaign", + date: s(r.segments?.date), + campaignId: s(r.campaign?.id), + campaignName: s(r.campaign?.name), + adGroupId: null, + adGroupName: null, + adId: null, + impressions: n(r.metrics?.impressions), + clicks: n(r.metrics?.clicks), + conversions: n(r.metrics?.conversions), + costMicros: n(r.metrics?.cost_micros), + conversionsValue: n(r.metrics?.conversions_value), + }; +} + +export function mapAdGroupRow(r: RawRow): PerfRow { + return { + level: "ad_group", + date: s(r.segments?.date), + campaignId: s(r.campaign?.id), + campaignName: s(r.campaign?.name), + adGroupId: s(r.ad_group?.id), + adGroupName: s(r.ad_group?.name), + adId: null, + impressions: n(r.metrics?.impressions), + clicks: n(r.metrics?.clicks), + conversions: n(r.metrics?.conversions), + costMicros: n(r.metrics?.cost_micros), + conversionsValue: n(r.metrics?.conversions_value), + }; +} + +export function mapAdRow(r: RawRow): PerfRow { + return { + level: "ad", + date: s(r.segments?.date), + campaignId: s(r.campaign?.id), + campaignName: s(r.campaign?.name), + adGroupId: s(r.ad_group?.id), + adGroupName: s(r.ad_group?.name), + adId: s(r.ad_group_ad?.ad?.id), + impressions: n(r.metrics?.impressions), + clicks: n(r.metrics?.clicks), + conversions: n(r.metrics?.conversions), + costMicros: n(r.metrics?.cost_micros), + conversionsValue: n(r.metrics?.conversions_value), + }; +} + +export function createGoogleAdsClient(opts: GoogleAdsClientOptions): GoogleAdsClient { + const api = new GoogleAdsApi({ + client_id: opts.clientId, + client_secret: opts.clientSecret, + developer_token: opts.developerToken, + }); + + const customers = new Map>(); + function customer(customerId: string) { + const cid = normalizeCustomerId(customerId); + let c = customers.get(cid); + if (!c) { + c = api.Customer({ + customer_id: cid, + refresh_token: opts.refreshToken, + login_customer_id: opts.loginCustomerId + ? normalizeCustomerId(opts.loginCustomerId) + : undefined, + }); + customers.set(cid, c); + } + return c; + } + + async function query(customerId: string, gaql: string): Promise { + return (await customer(customerId).query(gaql)) as RawRow[]; + } + + return { + async listCampaigns(customerId, status) { + const where = status ? ` WHERE campaign.status = '${status}'` : ""; + const rows = await query( + customerId, + `SELECT campaign.id, campaign.name, campaign.status, + campaign.advertising_channel_type, campaign.bidding_strategy_type + FROM campaign${where} + ORDER BY campaign.id`, + ); + return rows.map((r) => ({ + id: s(r.campaign?.id), + name: s(r.campaign?.name), + status: enumName(enums.CampaignStatus, r.campaign?.status), + channelType: enumName(enums.AdvertisingChannelType, r.campaign?.advertising_channel_type), + biddingStrategyType: enumName(enums.BiddingStrategyType, r.campaign?.bidding_strategy_type), + })); + }, + + async getCampaign(customerId, campaignId) { + const rows = await query( + customerId, + `SELECT campaign.id, campaign.name, campaign.status, + campaign.advertising_channel_type, campaign.bidding_strategy_type, + campaign.start_date, campaign.end_date, campaign_budget.amount_micros + FROM campaign + WHERE campaign.id = ${normalizeCustomerId(campaignId)} + LIMIT 1`, + ); + const r = rows[0]; + if (!r) return null; + return { + id: s(r.campaign?.id), + name: s(r.campaign?.name), + status: enumName(enums.CampaignStatus, r.campaign?.status), + channelType: enumName(enums.AdvertisingChannelType, r.campaign?.advertising_channel_type), + biddingStrategyType: enumName(enums.BiddingStrategyType, r.campaign?.bidding_strategy_type), + budgetMicros: n(r.campaign_budget?.amount_micros), + startDate: r.campaign?.start_date ? s(r.campaign.start_date) : null, + endDate: r.campaign?.end_date ? s(r.campaign.end_date) : null, + }; + }, + + async queryPerformance(customerId, range) { + const dates = `segments.date BETWEEN '${range.since}' AND '${range.until}'`; + const [campaignRows, adGroupRows, adRows] = await Promise.all([ + query( + customerId, + `SELECT campaign.id, campaign.name, segments.date, ${PERF_METRICS} + FROM campaign WHERE ${dates}`, + ), + query( + customerId, + `SELECT campaign.id, campaign.name, ad_group.id, ad_group.name, segments.date, ${PERF_METRICS} + FROM ad_group WHERE ${dates}`, + ), + query( + customerId, + `SELECT campaign.id, campaign.name, ad_group.id, ad_group.name, + ad_group_ad.ad.id, segments.date, ${PERF_METRICS} + FROM ad_group_ad WHERE ${dates}`, + ), + ]); + return [ + ...campaignRows.map(mapCampaignRow), + ...adGroupRows.map(mapAdGroupRow), + ...adRows.map(mapAdRow), + ]; + }, + + async getSearchTerms(customerId, campaignId, range) { + const rows = await query( + customerId, + `SELECT campaign.id, ad_group.id, search_term_view.search_term, + search_term_view.status, segments.search_term_match_type, ${PERF_METRICS} + FROM search_term_view + WHERE campaign.id = ${normalizeCustomerId(campaignId)} + AND segments.date BETWEEN '${range.since}' AND '${range.until}'`, + ); + return rows.map((r) => ({ + campaignId: s(r.campaign?.id), + adGroupId: s(r.ad_group?.id), + searchTerm: s(r.search_term_view?.search_term), + matchType: enumName(enums.SearchTermMatchType, r.segments?.search_term_match_type), + status: enumName(enums.SearchTermTargetingStatus, r.search_term_view?.status), + impressions: n(r.metrics?.impressions), + clicks: n(r.metrics?.clicks), + conversions: n(r.metrics?.conversions), + costMicros: n(r.metrics?.cost_micros), + })); + }, + + async getChangeHistory(customerId, range) { + const rows = await query( + customerId, + `SELECT change_event.change_date_time, change_event.change_resource_type, + change_event.change_resource_name, change_event.client_type, + change_event.user_email, change_event.resource_change_operation, + change_event.changed_fields + FROM change_event + WHERE change_event.change_date_time >= '${range.since} 00:00:00' + AND change_event.change_date_time <= '${range.until} 23:59:59' + ORDER BY change_event.change_date_time DESC + LIMIT 1000`, + ); + return rows.map((r) => ({ + changeDateTime: s(r.change_event?.change_date_time), + resourceType: enumName(enums.ChangeEventResourceType, r.change_event?.change_resource_type), + changeResourceName: s(r.change_event?.change_resource_name), + clientType: enumName(enums.ChangeClientType, r.change_event?.client_type), + userEmail: s(r.change_event?.user_email), + operation: enumName(enums.ResourceChangeOperation, r.change_event?.resource_change_operation), + changedFields: Array.isArray(r.change_event?.changed_fields?.paths) + ? r.change_event.changed_fields.paths.map(s) + : [], + })); + }, + + async pauseCampaign(customerId, campaignId) { + const cid = normalizeCustomerId(customerId); + const resourceName = `customers/${cid}/campaigns/${normalizeCustomerId(campaignId)}`; + await customer(customerId).campaigns.update([ + { resource_name: resourceName, status: enums.CampaignStatus.PAUSED }, + ]); + return { resourceName }; + }, + + async resumeCampaign(customerId, campaignId) { + const cid = normalizeCustomerId(customerId); + const resourceName = `customers/${cid}/campaigns/${normalizeCustomerId(campaignId)}`; + await customer(customerId).campaigns.update([ + { resource_name: resourceName, status: enums.CampaignStatus.ENABLED }, + ]); + return { resourceName }; + }, + }; +} diff --git a/connectors/google-ads/src/config.ts b/connectors/google-ads/src/config.ts new file mode 100644 index 0000000..7f917fa --- /dev/null +++ b/connectors/google-ads/src/config.ts @@ -0,0 +1,58 @@ +// Centralized env + secrets. Every entrypoint (CLI, MCP, worker) reads here. +// OAuth2 refresh-token flow; creds are env-driven. CONTRACT: performance_observation.v1. + +export interface GoogleAdsConnectorConfig { + developerToken: string; + clientId: string; + clientSecret: string; + refreshToken: string; + loginCustomerId: string; + apiVersion: string; + writeEnabled: boolean; + banditQueuePath: string; + mcpToken: string; + mcpPort: number; + temporalAddress: string; + temporalNamespace: string; + perfOutDir: string; +} + +function env(key: string, fallback?: string): string { + const v = process.env[key]; + if (v === undefined || v === "") { + if (fallback !== undefined) return fallback; + throw new Error(`missing required env: ${key}`); + } + return v; +} + +function optional(key: string, fallback: string): string { + const v = process.env[key]; + return v === undefined || v === "" ? fallback : v; +} + +// Lazy so importing the package never throws when env is absent (tests, type tooling). +export function loadConfig( + overrides: Partial = {}, +): GoogleAdsConnectorConfig { + return { + developerToken: overrides.developerToken ?? env("GOOGLE_ADS_DEVELOPER_TOKEN"), + clientId: overrides.clientId ?? env("GOOGLE_ADS_CLIENT_ID"), + clientSecret: overrides.clientSecret ?? env("GOOGLE_ADS_CLIENT_SECRET"), + refreshToken: overrides.refreshToken ?? env("GOOGLE_ADS_REFRESH_TOKEN"), + loginCustomerId: overrides.loginCustomerId ?? optional("GOOGLE_ADS_LOGIN_CUSTOMER_ID", ""), + apiVersion: overrides.apiVersion ?? optional("GOOGLE_ADS_API_VERSION", "v17"), + writeEnabled: + overrides.writeEnabled ?? + optional("SHOPLOOP_GADS_WRITE_ENABLED", "false").toLowerCase() === "true", + banditQueuePath: + overrides.banditQueuePath ?? optional("BANDIT_QUEUE_PATH", "./var/bandit/queue.jsonl"), + mcpToken: overrides.mcpToken ?? optional("SHOPLOOP_MCP_TOKEN", ""), + mcpPort: overrides.mcpPort ?? Number(optional("SHOPLOOP_GADS_MCP_PORT", "8788")), + temporalAddress: overrides.temporalAddress ?? optional("TEMPORAL_ADDRESS", "localhost:7233"), + temporalNamespace: overrides.temporalNamespace ?? optional("TEMPORAL_NAMESPACE", "shoploop"), + perfOutDir: overrides.perfOutDir ?? optional("PERF_OUT_DIR", "./var/shoploop/perf"), + }; +} + +export const TASK_QUEUE = "shoploop-google-ads"; diff --git a/connectors/google-ads/src/index.ts b/connectors/google-ads/src/index.ts new file mode 100644 index 0000000..c7fdd85 --- /dev/null +++ b/connectors/google-ads/src/index.ts @@ -0,0 +1,78 @@ +// @shoploop/connector-google-ads — first-class Google Ads → PerformanceObservation primitives. +// Performance Ingest source #1 for the Thompson Sampling loop that updates brand_rules.json. +// CONTRACT: open-imagine/contracts/performance_observation.v1.json + +// Config +export { loadConfig, TASK_QUEUE, type GoogleAdsConnectorConfig } from "./config.js"; + +// Types +export type { + CampaignSummary, + CampaignDetail, + PerfLevel, + PerfRow, + SearchTermRow, + ChangeEvent, + PerformanceMetrics, + PerformanceObservation, + IngestReport, + RollupBucket, +} from "./types.js"; +export { deriveMetrics, observationKey, microsToUnits, normalizeCustomerId, MICROS } from "./types.js"; + +// Client (single internal core) +export { + createGoogleAdsClient, + type GoogleAdsClient, + type GoogleAdsClientOptions, + type DateRange, +} from "./client.js"; + +// Observation + rollup +export { toObservation } from "./observe.js"; +export { rollupPerformance, weekStart, type Granularity } from "./rollup.js"; + +// Primitive 1: performance ingest +export { ingestPerformance, toDateOnly, type IngestPerformanceOptions } from "./primitives/ingest_performance.js"; +export { + linkCampaignToShopifyProduct, + buildJoinMap, + JOIN_NAMESPACE, + JOIN_KEY, + JOIN_TYPE, + type ShopifyMetafieldWriter, +} from "./primitives/join.js"; + +// Primitive 2: MCP +export { + createMcpServer, + buildApp, + startMcpServer, + ALL_TOOLS, + WriteDisabledError, + type ToolContext, + type ToolDef, + type ToolResult, +} from "./primitives/mcp_tools.js"; + +// Primitive 3: bandit feedback hook +export { + recordPerformanceForBandit, + recordBatchForBandit, + type RecordOptions, +} from "./primitives/bandit_feedback.js"; + +// Primitive 4: Temporal +export * as workflows from "./temporal/workflows.js"; +export { + hourBucket, + gadsWorkflowId, + dispatchHourlyIngest, + dispatchPause, + getTemporalClient, + clientStarter, + runIngestOnce, + type WorkflowStarter, + type IngestRunResult, +} from "./temporal/dispatch.js"; +export { runWorker } from "./temporal/worker.js"; diff --git a/connectors/google-ads/src/mcp/server.ts b/connectors/google-ads/src/mcp/server.ts new file mode 100644 index 0000000..4ad348d --- /dev/null +++ b/connectors/google-ads/src/mcp/server.ts @@ -0,0 +1,36 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { ALL_TOOLS, WriteDisabledError, type ToolContext } from "./tools/index.js"; + +export const MCP_SERVER_NAME = "shoploop-connector-google-ads"; +export const MCP_SERVER_VERSION = "0.1.0"; + +// Builds an McpServer with all 7 Google Ads tools registered against a shared core context. +// Used by both the HTTP/SSE transport and in-process tests. Write tools (pause/resume) are +// refused unless ctx.writeEnabled — the gate lives here so every transport inherits it. +export function createMcpServer(ctx: ToolContext): McpServer { + const server = new McpServer( + { name: MCP_SERVER_NAME, version: MCP_SERVER_VERSION }, + { capabilities: { tools: {} } }, + ); + + for (const tool of ALL_TOOLS) { + server.tool(tool.name, tool.description, tool.shape, async (args: Record) => { + try { + if (tool.write && !ctx.writeEnabled) throw new WriteDisabledError(tool.name); + const result = await tool.handler(ctx, args ?? {}); + const content: { type: "text"; text: string }[] = [ + { type: "text", text: JSON.stringify(result.data) }, + ]; + if (result.text) content.push({ type: "text", text: result.text }); + return { content }; + } catch (err) { + return { + isError: true, + content: [{ type: "text", text: `error: ${(err as Error).message}` }], + }; + } + }); + } + + return server; +} diff --git a/connectors/google-ads/src/mcp/tools/get_campaign.ts b/connectors/google-ads/src/mcp/tools/get_campaign.ts new file mode 100644 index 0000000..35640c7 --- /dev/null +++ b/connectors/google-ads/src/mcp/tools/get_campaign.ts @@ -0,0 +1,20 @@ +import { z } from "zod"; +import type { ToolDef } from "./tool.js"; + +export const getCampaign: ToolDef = { + name: "get_campaign", + description: + "Get one Google Ads campaign including budget, channel type, bidding strategy, and flight dates.", + shape: { + customerId: z.string().describe("Google Ads customer id"), + campaignId: z.string(), + }, + async handler(ctx, args) { + const campaign = await ctx.client.getCampaign( + args.customerId as string, + args.campaignId as string, + ); + if (!campaign) return { data: { found: false, campaignId: args.campaignId } }; + return { data: { found: true, campaign } }; + }, +}; diff --git a/connectors/google-ads/src/mcp/tools/get_change_history.ts b/connectors/google-ads/src/mcp/tools/get_change_history.ts new file mode 100644 index 0000000..3a48744 --- /dev/null +++ b/connectors/google-ads/src/mcp/tools/get_change_history.ts @@ -0,0 +1,20 @@ +import { z } from "zod"; +import type { ToolDef } from "./tool.js"; + +export const getChangeHistory: ToolDef = { + name: "get_change_history", + description: + "What changed in the account and when (change_event): resource type, operation, changed fields, who and which client. Useful for attributing performance shifts.", + shape: { + customerId: z.string().describe("Google Ads customer id"), + range: z.object({ + since: z.string().describe("YYYY-MM-DD inclusive"), + until: z.string().describe("YYYY-MM-DD inclusive"), + }), + }, + async handler(ctx, args) { + const range = args.range as { since: string; until: string }; + const events = await ctx.client.getChangeHistory(args.customerId as string, range); + return { data: { customerId: args.customerId, count: events.length, events } }; + }, +}; diff --git a/connectors/google-ads/src/mcp/tools/get_performance.ts b/connectors/google-ads/src/mcp/tools/get_performance.ts new file mode 100644 index 0000000..65fda6c --- /dev/null +++ b/connectors/google-ads/src/mcp/tools/get_performance.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import { rollupPerformance, type Granularity } from "../../rollup.js"; +import type { ToolDef } from "./tool.js"; + +export const getPerformance: ToolDef = { + name: "get_performance", + description: + "Daily or weekly performance rollup (impressions, clicks, conversions, cost, ctr, cpc, roas) for a customer over a date range. roas = conversions_value / cost.", + shape: { + customerId: z.string().describe("Google Ads customer id"), + range: z + .object({ + since: z.string().describe("YYYY-MM-DD inclusive"), + until: z.string().describe("YYYY-MM-DD inclusive"), + }) + .describe("date range"), + granularity: z.enum(["daily", "weekly"]).default("daily"), + }, + async handler(ctx, args) { + const range = args.range as { since: string; until: string }; + const granularity = (args.granularity as Granularity) ?? "daily"; + const rows = await ctx.client.queryPerformance(args.customerId as string, range); + const buckets = rollupPerformance(rows, granularity); + return { + data: { customerId: args.customerId, granularity, range, buckets }, + text: `${buckets.length} ${granularity} bucket(s) for ${args.customerId} over ${range.since}..${range.until}`, + }; + }, +}; diff --git a/connectors/google-ads/src/mcp/tools/get_search_terms.ts b/connectors/google-ads/src/mcp/tools/get_search_terms.ts new file mode 100644 index 0000000..9345e43 --- /dev/null +++ b/connectors/google-ads/src/mcp/tools/get_search_terms.ts @@ -0,0 +1,25 @@ +import { z } from "zod"; +import type { ToolDef } from "./tool.js"; + +export const getSearchTerms: ToolDef = { + name: "get_search_terms", + description: + "Search terms that triggered ads in a campaign over a date range — for negative-keyword harvesting. Returns term, match type, status, and spend/conversions.", + shape: { + customerId: z.string().describe("Google Ads customer id"), + campaignId: z.string(), + range: z.object({ + since: z.string().describe("YYYY-MM-DD inclusive"), + until: z.string().describe("YYYY-MM-DD inclusive"), + }), + }, + async handler(ctx, args) { + const range = args.range as { since: string; until: string }; + const terms = await ctx.client.getSearchTerms( + args.customerId as string, + args.campaignId as string, + range, + ); + return { data: { customerId: args.customerId, campaignId: args.campaignId, count: terms.length, terms } }; + }, +}; diff --git a/connectors/google-ads/src/mcp/tools/index.ts b/connectors/google-ads/src/mcp/tools/index.ts new file mode 100644 index 0000000..2b1ab9b --- /dev/null +++ b/connectors/google-ads/src/mcp/tools/index.ts @@ -0,0 +1,21 @@ +import type { ToolDef } from "./tool.js"; +import { listCampaigns } from "./list_campaigns.js"; +import { getCampaign } from "./get_campaign.js"; +import { getPerformance } from "./get_performance.js"; +import { getSearchTerms } from "./get_search_terms.js"; +import { getChangeHistory } from "./get_change_history.js"; +import { pauseCampaign } from "./pause_campaign.js"; +import { resumeCampaign } from "./resume_campaign.js"; + +export const ALL_TOOLS: ToolDef[] = [ + listCampaigns, + getCampaign, + getPerformance, + getSearchTerms, + getChangeHistory, + pauseCampaign, + resumeCampaign, +]; + +export type { ToolContext, ToolDef, ToolResult } from "./tool.js"; +export { WriteDisabledError } from "./tool.js"; diff --git a/connectors/google-ads/src/mcp/tools/list_campaigns.ts b/connectors/google-ads/src/mcp/tools/list_campaigns.ts new file mode 100644 index 0000000..c3f3cd3 --- /dev/null +++ b/connectors/google-ads/src/mcp/tools/list_campaigns.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; +import type { ToolDef } from "./tool.js"; + +export const listCampaigns: ToolDef = { + name: "list_campaigns", + description: + "List Google Ads campaigns for a customer, optionally filtered by status. Returns id, name, status, channel, bidding strategy.", + shape: { + customerId: z.string().describe("Google Ads customer id (digits, dashes ok)"), + status: z.enum(["ENABLED", "PAUSED", "REMOVED"]).optional(), + }, + async handler(ctx, args) { + const campaigns = await ctx.client.listCampaigns( + args.customerId as string, + args.status as string | undefined, + ); + return { data: { count: campaigns.length, campaigns } }; + }, +}; diff --git a/connectors/google-ads/src/mcp/tools/pause_campaign.ts b/connectors/google-ads/src/mcp/tools/pause_campaign.ts new file mode 100644 index 0000000..a77450a --- /dev/null +++ b/connectors/google-ads/src/mcp/tools/pause_campaign.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; +import type { ToolDef } from "./tool.js"; + +export const pauseCampaign: ToolDef = { + name: "pause_campaign", + description: + "Pause a campaign (write). Gated behind SHOPLOOP_GADS_WRITE_ENABLED=true; refused when the flag is off.", + write: true, + shape: { + customerId: z.string().describe("Google Ads customer id"), + campaignId: z.string(), + }, + async handler(ctx, args) { + const res = await ctx.client.pauseCampaign(args.customerId as string, args.campaignId as string); + return { data: { ok: true, status: "PAUSED", ...res } }; + }, +}; diff --git a/connectors/google-ads/src/mcp/tools/resume_campaign.ts b/connectors/google-ads/src/mcp/tools/resume_campaign.ts new file mode 100644 index 0000000..ba5f6af --- /dev/null +++ b/connectors/google-ads/src/mcp/tools/resume_campaign.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; +import type { ToolDef } from "./tool.js"; + +export const resumeCampaign: ToolDef = { + name: "resume_campaign", + description: + "Resume (enable) a campaign (write). Gated behind SHOPLOOP_GADS_WRITE_ENABLED=true; refused when the flag is off.", + write: true, + shape: { + customerId: z.string().describe("Google Ads customer id"), + campaignId: z.string(), + }, + async handler(ctx, args) { + const res = await ctx.client.resumeCampaign(args.customerId as string, args.campaignId as string); + return { data: { ok: true, status: "ENABLED", ...res } }; + }, +}; diff --git a/connectors/google-ads/src/mcp/tools/tool.ts b/connectors/google-ads/src/mcp/tools/tool.ts new file mode 100644 index 0000000..82721e0 --- /dev/null +++ b/connectors/google-ads/src/mcp/tools/tool.ts @@ -0,0 +1,31 @@ +import type { ZodRawShape } from "zod"; +import type { GoogleAdsClient } from "../../client.js"; + +export interface ToolContext { + client: GoogleAdsClient; + // Gate for write tools (pause/resume). Mirrors SHOPLOOP_GADS_WRITE_ENABLED. + writeEnabled: boolean; + banditQueuePath: string; +} + +export interface ToolResult { + data: unknown; // JSON-serializable payload + text?: string; // optional LLM-facing textual summary +} + +export interface ToolDef { + name: string; + description: string; + shape: ZodRawShape; + write?: boolean; // requires ctx.writeEnabled; refused otherwise + handler: (ctx: ToolContext, args: Record) => Promise; +} + +export class WriteDisabledError extends Error { + constructor(tool: string) { + super( + `write tool '${tool}' refused: set SHOPLOOP_GADS_WRITE_ENABLED=true to enable Google Ads mutations`, + ); + this.name = "WriteDisabledError"; + } +} diff --git a/connectors/google-ads/src/mcp/transport.ts b/connectors/google-ads/src/mcp/transport.ts new file mode 100644 index 0000000..444c599 --- /dev/null +++ b/connectors/google-ads/src/mcp/transport.ts @@ -0,0 +1,94 @@ +import express, { type Express, type NextFunction, type Request, type Response } from "express"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { createGoogleAdsClient } from "../client.js"; +import { loadConfig, type GoogleAdsConnectorConfig } from "../config.js"; +import { createMcpServer } from "./server.js"; +import type { ToolContext } from "./tools/index.js"; + +const CORS_ALLOWLIST = [ + "https://open-employee.local", + "https://agentframe.local", + "http://localhost:3000", +]; + +function bearerAuth(token: string) { + return (req: Request, res: Response, next: NextFunction) => { + if (!token) return next(); // unset token => open (dev only); set SHOPLOOP_MCP_TOKEN in prod + const header = req.header("authorization") ?? ""; + const provided = header.startsWith("Bearer ") ? header.slice(7) : ""; + if (provided !== token) { + res.status(401).json({ error: "unauthorized" }); + return; + } + next(); + }; +} + +function cors(req: Request, res: Response, next: NextFunction) { + const origin = req.header("origin"); + if (origin && CORS_ALLOWLIST.includes(origin)) { + res.header("Access-Control-Allow-Origin", origin); + res.header("Access-Control-Allow-Headers", "authorization, content-type"); + res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + } + if (req.method === "OPTIONS") { + res.sendStatus(204); + return; + } + next(); +} + +export function buildApp(cfg: GoogleAdsConnectorConfig): Express { + const app = express(); + app.use(cors); + + const ctx: ToolContext = { + client: createGoogleAdsClient({ + developerToken: cfg.developerToken, + clientId: cfg.clientId, + clientSecret: cfg.clientSecret, + refreshToken: cfg.refreshToken, + loginCustomerId: cfg.loginCustomerId, + }), + writeEnabled: cfg.writeEnabled, + banditQueuePath: cfg.banditQueuePath, + }; + + const transports = new Map(); + + app.get("/health", (_req, res) => + res.json({ ok: true, server: "shoploop-connector-google-ads", writeEnabled: cfg.writeEnabled }), + ); + + // SSE stream: client connects here, receives a sessionId, then POSTs to /messages. + app.get("/sse", bearerAuth(cfg.mcpToken), async (_req, res) => { + const transport = new SSEServerTransport("/messages", res); + transports.set(transport.sessionId, transport); + res.on("close", () => transports.delete(transport.sessionId)); + const server = createMcpServer(ctx); + await server.connect(transport); + }); + + app.post("/messages", bearerAuth(cfg.mcpToken), express.json(), async (req, res) => { + const sessionId = String(req.query.sessionId ?? ""); + const transport = transports.get(sessionId); + if (!transport) { + res.status(404).json({ error: "unknown sessionId" }); + return; + } + await transport.handlePostMessage(req, res, req.body); + }); + + return app; +} + +export function startMcpServer(overrides?: Partial): { port: number } { + const cfg = loadConfig(overrides); + const app = buildApp(cfg); + app.listen(cfg.mcpPort, () => { + console.log( + `[shoploop-google-ads] MCP HTTP/SSE on :${cfg.mcpPort} (/sse, /messages) write=${cfg.writeEnabled}`, + ); + }); + return { port: cfg.mcpPort }; +} diff --git a/connectors/google-ads/src/observe.ts b/connectors/google-ads/src/observe.ts new file mode 100644 index 0000000..2a784d8 --- /dev/null +++ b/connectors/google-ads/src/observe.ts @@ -0,0 +1,29 @@ +import { + deriveMetrics, + type PerfRow, + type PerformanceObservation, +} from "./types.js"; + +// PerfRow → typed PerformanceObservation. The single place raw counters become the +// producer-side reward signal. CONTRACT: open-imagine/contracts/performance_observation.v1.json +export function toObservation( + row: PerfRow, + customerId: string, + opts: { observedAt?: string; shopifyJoin?: Map } = {}, +): PerformanceObservation { + return { + schema: "performance_observation.v1", + source: "google-ads", + customer_id: customerId, + date: row.date, + level: row.level, + campaign_id: row.campaignId, + campaign_name: row.campaignName, + ad_group_id: row.adGroupId, + ad_group_name: row.adGroupName, + ad_id: row.adId, + metrics: deriveMetrics(row), + shopify_product_gid: opts.shopifyJoin?.get(row.campaignId) ?? null, + observed_at: opts.observedAt ?? new Date().toISOString(), + }; +} diff --git a/connectors/google-ads/src/primitives/bandit_feedback.ts b/connectors/google-ads/src/primitives/bandit_feedback.ts new file mode 100644 index 0000000..9cc02a9 --- /dev/null +++ b/connectors/google-ads/src/primitives/bandit_feedback.ts @@ -0,0 +1,7 @@ +// Primitive 3 barrel: brand-rules feedback hook. The producer side of the Thompson Sampling +// loop — emits typed PerformanceObservation onto a durable queue. Consumer is stubbed. +export { + recordPerformanceForBandit, + recordBatchForBandit, + type RecordOptions, +} from "../bandit/record.js"; diff --git a/connectors/google-ads/src/primitives/ingest_performance.ts b/connectors/google-ads/src/primitives/ingest_performance.ts new file mode 100644 index 0000000..5a0698c --- /dev/null +++ b/connectors/google-ads/src/primitives/ingest_performance.ts @@ -0,0 +1,93 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { createGoogleAdsClient, type DateRange, type GoogleAdsClient } from "../client.js"; +import { loadConfig } from "../config.js"; +import { toObservation } from "../observe.js"; +import { + normalizeCustomerId, + observationKey, + type IngestReport, + type PerformanceObservation, +} from "../types.js"; + +export interface IngestPerformanceOptions { + customerId: string; + since: string; // ISO datetime or YYYY-MM-DD (inclusive) + until?: string; // defaults to today (UTC) + outDir: string; + // injectable for tests; defaults to a live client from env (OAuth2 refresh token) + client?: GoogleAdsClient; + // campaign.id → shopify product gid, resolved from shoploop.gads_campaign_id metafields + shopifyJoin?: Map; + observedAt?: string; // pin for deterministic tests +} + +// YYYY-MM-DD (UTC) from an ISO datetime or an already-date string. +export function toDateOnly(v: string): string { + return v.length >= 10 ? v.slice(0, 10) : v; +} + +function todayUTC(): string { + return new Date().toISOString().slice(0, 10); +} + +// Primitive 1: Google Ads performance → one JSONL per day of PerformanceObservation. +// Dedupes by (date, campaign_id, ad_group_id, ad_id) so overlapping windows are safe. +// CONTRACT: open-imagine/contracts/performance_observation.v1.json +export async function ingestPerformance(opts: IngestPerformanceOptions): Promise { + const customerId = normalizeCustomerId(opts.customerId); + const since = toDateOnly(opts.since); + const until = toDateOnly(opts.until ?? todayUTC()); + const range: DateRange = { since, until }; + + const client = opts.client ?? defaultClient(); + const rows = await client.queryPerformance(customerId, range); + + // Dedup + group by date. + const seen = new Set(); + const byDate = new Map(); + const skipped: IngestReport["skipped"] = []; + + for (const row of rows) { + const obs = toObservation(row, customerId, { + observedAt: opts.observedAt, + shopifyJoin: opts.shopifyJoin, + }); + const key = observationKey(obs); + if (seen.has(key)) { + skipped.push({ key, reason: "duplicate (date, campaign_id, ad_group_id, ad_id)" }); + continue; + } + seen.add(key); + const bucket = byDate.get(obs.date) ?? []; + bucket.push(obs); + byDate.set(obs.date, bucket); + } + + const custDir = join(opts.outDir, customerId); + await mkdir(custDir, { recursive: true }); + + const files: IngestReport["files"] = []; + let observations = 0; + for (const date of [...byDate.keys()].sort()) { + const items = byDate.get(date)!; + const path = join(custDir, `${date}.jsonl`); + const body = items.map((o) => JSON.stringify(o)).join("\n") + "\n"; + await writeFile(path, body, "utf8"); + files.push({ date, path, count: items.length }); + observations += items.length; + } + + return { customerId, since, until, days: files.length, observations, files, skipped }; +} + +function defaultClient(): GoogleAdsClient { + const cfg = loadConfig(); + return createGoogleAdsClient({ + developerToken: cfg.developerToken, + clientId: cfg.clientId, + clientSecret: cfg.clientSecret, + refreshToken: cfg.refreshToken, + loginCustomerId: cfg.loginCustomerId, + }); +} diff --git a/connectors/google-ads/src/primitives/join.ts b/connectors/google-ads/src/primitives/join.ts new file mode 100644 index 0000000..5b84638 --- /dev/null +++ b/connectors/google-ads/src/primitives/join.ts @@ -0,0 +1,45 @@ +// Catalog join: Google Ads campaign.id ↔ Shopify product.id. +// +// The join lives on the Shopify side as a product metafield `shoploop.gads_campaign_id`. +// We do NOT duplicate Shopify client code here — the caller injects the Shopify connector's +// `update_metafield` tool (its MCP tool / ShopifyClient.updateMetafield). This keeps the two +// connectors decoupled while reusing the one canonical Shopify write path. + +export const JOIN_NAMESPACE = "shoploop"; +export const JOIN_KEY = "gads_campaign_id"; +export const JOIN_TYPE = "single_line_text_field"; + +// Shape of the Shopify connector's update_metafield (subset we need). Inject the real one. +export type ShopifyMetafieldWriter = (input: { + ownerId: string; + namespace: string; + key: string; + type: string; + value: string; +}) => Promise; + +// Write the gads_campaign_id metafield onto a Shopify product, establishing the join used +// by ingest's `shopifyJoin` map and, downstream, by the bandit to find the brand_rules arm. +export async function linkCampaignToShopifyProduct(args: { + productGid: string; + campaignId: string; + updateMetafield: ShopifyMetafieldWriter; +}): Promise { + await args.updateMetafield({ + ownerId: args.productGid, + namespace: JOIN_NAMESPACE, + key: JOIN_KEY, + type: JOIN_TYPE, + value: args.campaignId, + }); +} + +// Invert a productGid → campaignId map into the campaignId → productGid map ingest expects. +// Source it from Shopify metafields (read shoploop.gads_campaign_id across products). +export function buildJoinMap(productToCampaign: Record): Map { + const m = new Map(); + for (const [productGid, campaignId] of Object.entries(productToCampaign)) { + if (campaignId) m.set(campaignId, productGid); + } + return m; +} diff --git a/connectors/google-ads/src/primitives/mcp_tools.ts b/connectors/google-ads/src/primitives/mcp_tools.ts new file mode 100644 index 0000000..1647576 --- /dev/null +++ b/connectors/google-ads/src/primitives/mcp_tools.ts @@ -0,0 +1,7 @@ +// Primitive 2 barrel: MCP read/write surface. Same internal core (client.ts) as the CLI +// and Temporal paths — the MCP server simply exposes it over HTTP/SSE on :8788. +export { createMcpServer, MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../mcp/server.js"; +export { buildApp, startMcpServer } from "../mcp/transport.js"; +export { ALL_TOOLS } from "../mcp/tools/index.js"; +export type { ToolContext, ToolDef, ToolResult } from "../mcp/tools/index.js"; +export { WriteDisabledError } from "../mcp/tools/index.js"; diff --git a/connectors/google-ads/src/rollup.ts b/connectors/google-ads/src/rollup.ts new file mode 100644 index 0000000..044ba76 --- /dev/null +++ b/connectors/google-ads/src/rollup.ts @@ -0,0 +1,43 @@ +import { deriveMetrics, type PerfRow, type RollupBucket } from "./types.js"; + +export type Granularity = "daily" | "weekly"; + +// Monday-based ISO week start (YYYY-MM-DD) for a YYYY-MM-DD date. +export function weekStart(date: string): string { + const d = new Date(`${date}T00:00:00Z`); + const day = d.getUTCDay(); // 0=Sun..6=Sat + const delta = (day + 6) % 7; // days since Monday + d.setUTCDate(d.getUTCDate() - delta); + return d.toISOString().slice(0, 10); +} + +// Rolls campaign-level rows into daily or weekly buckets, summing counters and recomputing +// derived metrics (ctr/cpc/roas) from the sums — never averaging ratios. +export function rollupPerformance(rows: PerfRow[], granularity: Granularity): RollupBucket[] { + const acc = new Map< + string, + { impressions: number; clicks: number; conversions: number; costMicros: number; conversionsValue: number } + >(); + + for (const r of rows) { + if (r.level !== "campaign") continue; // rollups are campaign-grain + const key = granularity === "weekly" ? weekStart(r.date) : r.date; + const cur = acc.get(key) ?? { + impressions: 0, + clicks: 0, + conversions: 0, + costMicros: 0, + conversionsValue: 0, + }; + cur.impressions += r.impressions; + cur.clicks += r.clicks; + cur.conversions += r.conversions; + cur.costMicros += r.costMicros; + cur.conversionsValue += r.conversionsValue; + acc.set(key, cur); + } + + return [...acc.entries()] + .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) + .map(([bucket, sums]) => ({ bucket, metrics: deriveMetrics(sums) })); +} diff --git a/connectors/google-ads/src/temporal/activities.ts b/connectors/google-ads/src/temporal/activities.ts new file mode 100644 index 0000000..f2ed683 --- /dev/null +++ b/connectors/google-ads/src/temporal/activities.ts @@ -0,0 +1,70 @@ +import { readFile } from "node:fs/promises"; +import { createGoogleAdsClient } from "../client.js"; +import { loadConfig } from "../config.js"; +import { recordBatchForBandit } from "../bandit/record.js"; +import { ingestPerformance } from "../primitives/ingest_performance.js"; +import type { IngestReport, PerformanceObservation } from "../types.js"; + +function core() { + const cfg = loadConfig(); + const client = createGoogleAdsClient({ + developerToken: cfg.developerToken, + clientId: cfg.clientId, + clientSecret: cfg.clientSecret, + refreshToken: cfg.refreshToken, + loginCustomerId: cfg.loginCustomerId, + }); + return { cfg, client }; +} + +function sinceFromLookback(hours: number): string { + return new Date(Date.now() - hours * 3600_000).toISOString().slice(0, 10); +} + +// Pull the last `lookbackHours` of performance and write JSONL. Overlap (25h default) plus +// the (date,campaign,ad_group,ad) dedup makes re-runs within the hour safe. +export async function ingestRecentPerformance(input: { + customerId: string; + lookbackHours?: number; +}): Promise { + const { cfg, client } = core(); + return ingestPerformance({ + customerId: input.customerId, + since: sinceFromLookback(input.lookbackHours ?? 25), + outDir: cfg.perfOutDir, + client, + }); +} + +// Read the JSONL files ingest wrote and enqueue every observation for the bandit. Separate +// activity so a bandit-queue failure retries without re-querying the Ads API. +export async function flushObservationsToBandit(input: { + files: string[]; +}): Promise<{ enqueued: number }> { + const { cfg } = core(); + let enqueued = 0; + for (const path of input.files) { + const text = await readFile(path, "utf8"); + const observations: PerformanceObservation[] = text + .split("\n") + .filter((l) => l.trim().length > 0) + .map((l) => JSON.parse(l) as PerformanceObservation); + enqueued += await recordBatchForBandit(observations, { queuePath: cfg.banditQueuePath }); + } + return { enqueued }; +} + +// Write activity, gated. PauseUnderperformingCampaignWorkflow calls this; with the flag off +// it is a no-op (the bandit can dry-run end-to-end before any live mutation is enabled). +export async function pauseCampaignActivity(input: { + customerId: string; + campaignId: string; + reason: string; +}): Promise<{ paused: boolean; resourceName: string | null; reason: string }> { + const { cfg, client } = core(); + if (!cfg.writeEnabled) { + return { paused: false, resourceName: null, reason: `write disabled (${input.reason})` }; + } + const res = await client.pauseCampaign(input.customerId, input.campaignId); + return { paused: true, resourceName: res.resourceName, reason: input.reason }; +} diff --git a/connectors/google-ads/src/temporal/dispatch.ts b/connectors/google-ads/src/temporal/dispatch.ts new file mode 100644 index 0000000..586428d --- /dev/null +++ b/connectors/google-ads/src/temporal/dispatch.ts @@ -0,0 +1,106 @@ +import { Connection, Client, type WorkflowHandle } from "@temporalio/client"; +import { loadConfig, TASK_QUEUE, type GoogleAdsConnectorConfig } from "../config.js"; +import { + IngestGoogleAdsPerformanceWorkflow, + PauseUnderperformingCampaignWorkflow, +} from "./workflows.js"; + +// Hour bucket "YYYY-MM-DDTHH" (UTC). The hourly schedule fires once per bucket; the bucket +// in the workflow id collapses duplicate/retried fires within the same hour to one execution. +export function hourBucket(d: Date = new Date()): string { + return d.toISOString().slice(0, 13); +} + +// Idempotency: gads:::. +export function gadsWorkflowId(customerId: string, workflow: string, bucket: string): string { + return `gads:${customerId}:${workflow}:${bucket}`; +} + +// Minimal surface so dispatch can be unit-tested with a mock starter (REJECT_DUPLICATE). +export interface WorkflowStarter { + start( + workflow: string, + opts: { args: unknown[]; taskQueue: string; workflowId: string }, + ): Promise<{ workflowId: string }>; +} + +// Start the hourly ingest for a customer, deduped by hour bucket. Returns the (possibly +// pre-existing) workflow id. +export async function dispatchHourlyIngest( + starter: WorkflowStarter, + customerId: string, + at: Date = new Date(), +): Promise { + const id = gadsWorkflowId(customerId, IngestGoogleAdsPerformanceWorkflow.name, hourBucket(at)); + await starter.start(IngestGoogleAdsPerformanceWorkflow.name, { + args: [customerId], + taskQueue: TASK_QUEUE, + workflowId: id, + }); + return id; +} + +export async function dispatchPause( + starter: WorkflowStarter, + customerId: string, + campaignId: string, + reason: string, + at: Date = new Date(), +): Promise { + const id = gadsWorkflowId( + customerId, + `${PauseUnderperformingCampaignWorkflow.name}:${campaignId}`, + hourBucket(at), + ); + await starter.start(PauseUnderperformingCampaignWorkflow.name, { + args: [customerId, campaignId, reason], + taskQueue: TASK_QUEUE, + workflowId: id, + }); + return id; +} + +let cached: Client | null = null; + +export async function getTemporalClient(cfg?: GoogleAdsConnectorConfig): Promise { + if (cached) return cached; + const c = cfg ?? loadConfig(); + const connection = await Connection.connect({ address: c.temporalAddress }); + cached = new Client({ connection, namespace: c.temporalNamespace }); + return cached; +} + +// Adapts the real Temporal Client to WorkflowStarter. REJECT_DUPLICATE enforces hour-bucket +// idempotency: a second start with the same id is rejected, not duplicated. +export function clientStarter(client: Client): WorkflowStarter { + return { + async start(workflow, opts) { + const handle: WorkflowHandle = await client.workflow.start(workflow, { + args: opts.args, + taskQueue: opts.taskQueue, + workflowId: opts.workflowId, + workflowIdReusePolicy: "REJECT_DUPLICATE", + }); + return { workflowId: handle.workflowId }; + }, + }; +} + +// CLI dev path: run the hourly ingest once, on-demand, and await its result. +export async function runIngestOnce(customerId: string): Promise { + const client = await getTemporalClient(); + const id = gadsWorkflowId(customerId, IngestGoogleAdsPerformanceWorkflow.name, hourBucket()); + const handle = await client.workflow.start(IngestGoogleAdsPerformanceWorkflow, { + args: [customerId], + taskQueue: TASK_QUEUE, + workflowId: id, + workflowIdReusePolicy: "REJECT_DUPLICATE", + }); + const result = await handle.result(); + return { workflowId: id, result }; +} + +export interface IngestRunResult { + workflowId: string; + result: Awaited>; +} diff --git a/connectors/google-ads/src/temporal/worker.ts b/connectors/google-ads/src/temporal/worker.ts new file mode 100644 index 0000000..541fd4a --- /dev/null +++ b/connectors/google-ads/src/temporal/worker.ts @@ -0,0 +1,21 @@ +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +import { NativeConnection, Worker } from "@temporalio/worker"; +import { loadConfig, TASK_QUEUE } from "../config.js"; +import * as activities from "./activities.js"; + +// Separate process: `shoploop-google-ads worker`. Task queue: shoploop-google-ads. +export async function runWorker(): Promise { + const cfg = loadConfig(); + const connection = await NativeConnection.connect({ address: cfg.temporalAddress }); + const here = dirname(fileURLToPath(import.meta.url)); + const worker = await Worker.create({ + connection, + namespace: cfg.temporalNamespace, + taskQueue: TASK_QUEUE, + workflowsPath: join(here, "workflows.js"), + activities, + }); + console.log(`[shoploop-google-ads] worker on '${cfg.temporalNamespace}' queue '${TASK_QUEUE}'`); + await worker.run(); +} diff --git a/connectors/google-ads/src/temporal/workflows.ts b/connectors/google-ads/src/temporal/workflows.ts new file mode 100644 index 0000000..20018d0 --- /dev/null +++ b/connectors/google-ads/src/temporal/workflows.ts @@ -0,0 +1,42 @@ +import { proxyActivities } from "@temporalio/workflow"; +import type * as activities from "./activities.js"; + +const acts = proxyActivities({ + startToCloseTimeout: "5 minutes", + retry: { maximumAttempts: 5 }, +}); + +export interface IngestWorkflowResult { + customerId: string; + days: number; + observations: number; + enqueued: number; +} + +// Scheduled hourly. Queries the last 25h (overlap), writes one JSONL/day, then flushes every +// observation onto the bandit queue. Idempotent per hour via the workflow id (see dispatch). +export async function IngestGoogleAdsPerformanceWorkflow( + customerId: string, +): Promise { + const report = await acts.ingestRecentPerformance({ customerId, lookbackHours: 25 }); + const { enqueued } = await acts.flushObservationsToBandit({ + files: report.files.map((f) => f.path), + }); + return { customerId, days: report.days, observations: report.observations, enqueued }; +} + +export interface PauseResult { + paused: boolean; + resourceName: string | null; + reason: string; +} + +// Invoked by the bandit later (pause an arm that lost). For now a typed entry point with a +// no-op body behind SHOPLOOP_GADS_WRITE_ENABLED — the pause activity refuses when the flag is off. +export async function PauseUnderperformingCampaignWorkflow( + customerId: string, + campaignId: string, + reason: string, +): Promise { + return acts.pauseCampaignActivity({ customerId, campaignId, reason }); +} diff --git a/connectors/google-ads/src/types.ts b/connectors/google-ads/src/types.ts new file mode 100644 index 0000000..896c451 --- /dev/null +++ b/connectors/google-ads/src/types.ts @@ -0,0 +1,148 @@ +// Normalized Google Ads shapes. The google-ads-api client maps GAQL rows into these; +// ingest/MCP/temporal all consume this single shape (no duplication of field handling). +// CONTRACT: open-imagine/contracts/performance_observation.v1.json + +export interface CampaignSummary { + id: string; + name: string; + status: string; // ENABLED | PAUSED | REMOVED + channelType: string; // SEARCH | PERFORMANCE_MAX | ... + biddingStrategyType: string; +} + +export interface CampaignDetail extends CampaignSummary { + budgetMicros: number; + startDate: string | null; // YYYY-MM-DD + endDate: string | null; // YYYY-MM-DD +} + +export type PerfLevel = "campaign" | "ad_group" | "ad"; + +// One row of performance at a single grain for a single day. The flat shape every +// GAQL level (campaign/ad_group/ad_group_ad) maps into before becoming an observation. +export interface PerfRow { + level: PerfLevel; + date: string; // YYYY-MM-DD + campaignId: string; + campaignName: string; + adGroupId: string | null; + adGroupName: string | null; + adId: string | null; + impressions: number; + clicks: number; + conversions: number; + costMicros: number; + conversionsValue: number; +} + +export interface SearchTermRow { + campaignId: string; + adGroupId: string; + searchTerm: string; + matchType: string; // EXACT | PHRASE | BROAD | NEAR_EXACT | ... + status: string; // ADDED | EXCLUDED | NONE + impressions: number; + clicks: number; + conversions: number; + costMicros: number; +} + +export interface ChangeEvent { + changeDateTime: string; // ISO from Google Ads change_event.change_date_time + resourceType: string; // CAMPAIGN | AD_GROUP | AD_GROUP_CRITERION | ... + changeResourceName: string; + clientType: string; // GOOGLE_ADS_WEB_CLIENT | GOOGLE_ADS_API | ... + userEmail: string; + operation: string; // CREATE | UPDATE | REMOVE + changedFields: string[]; +} + +// The typed reward signal. Producer-agnostic so a real Thompson Sampling consumer can +// drop in without touching this connector. Mirrors performance_observation.v1.json. +export interface PerformanceMetrics { + impressions: number; + clicks: number; + conversions: number; + cost_micros: number; + cost: number; // cost_micros / 1e6 + conversions_value: number; + ctr: number; // clicks / impressions + average_cpc: number; // cost / clicks + roas: number; // conversions_value / cost — the reward +} + +export interface PerformanceObservation { + schema: "performance_observation.v1"; + source: "google-ads"; + customer_id: string; + date: string; // YYYY-MM-DD + level: PerfLevel; + campaign_id: string; + campaign_name: string; + ad_group_id: string | null; + ad_group_name: string | null; + ad_id: string | null; + metrics: PerformanceMetrics; + shopify_product_gid: string | null; // joined via shoploop.gads_campaign_id metafield + observed_at: string; // ISO +} + +export interface IngestReport { + customerId: string; + since: string; // YYYY-MM-DD + until: string; // YYYY-MM-DD + days: number; + observations: number; + files: { date: string; path: string; count: number }[]; + skipped: { key: string; reason: string }[]; +} + +// Daily/weekly rollup returned by get_performance. +export interface RollupBucket { + bucket: string; // YYYY-MM-DD (daily) or ISO week start YYYY-MM-DD (weekly) + metrics: PerformanceMetrics; +} + +export const MICROS = 1_000_000; + +export function microsToUnits(micros: number): number { + return micros / MICROS; +} + +// Derives ctr/cpc/roas from raw counters. Single source of truth for metric math so +// ingest and rollups never disagree. +export function deriveMetrics(raw: { + impressions: number; + clicks: number; + conversions: number; + costMicros: number; + conversionsValue: number; +}): PerformanceMetrics { + const cost = microsToUnits(raw.costMicros); + return { + impressions: raw.impressions, + clicks: raw.clicks, + conversions: raw.conversions, + cost_micros: raw.costMicros, + cost, + conversions_value: raw.conversionsValue, + ctr: raw.impressions > 0 ? raw.clicks / raw.impressions : 0, + average_cpc: raw.clicks > 0 ? cost / raw.clicks : 0, + roas: cost > 0 ? raw.conversionsValue / cost : 0, + }; +} + +// Dedup key per the contract: (date, campaign_id, ad_group_id, ad_id). +export function observationKey(o: { + date: string; + campaign_id: string; + ad_group_id: string | null; + ad_id: string | null; +}): string { + return `${o.date}|${o.campaign_id}|${o.ad_group_id ?? "-"}|${o.ad_id ?? "-"}`; +} + +// "1234-567-8901" / "123-456-7890" → "1234567890". Google Ads customer ids are digits. +export function normalizeCustomerId(id: string): string { + return id.replace(/[^0-9]/g, ""); +} diff --git a/connectors/google-ads/test/fixtures/varitea_gads_response.json b/connectors/google-ads/test/fixtures/varitea_gads_response.json new file mode 100644 index 0000000..6bbd80d --- /dev/null +++ b/connectors/google-ads/test/fixtures/varitea_gads_response.json @@ -0,0 +1,935 @@ +{ + "customerId": "5128873230", + "campaign": { + "id": "20451234567", + "name": "Varitea — Search — Brand + Loose Leaf", + "status": "ENABLED", + "channelType": "SEARCH", + "biddingStrategyType": "MAXIMIZE_CONVERSIONS", + "budgetMicros": 45000000, + "startDate": "2026-05-01", + "endDate": null + }, + "adGroups": [ + { + "id": "1583390011", + "name": "Brand", + "campaignId": "20451234567" + }, + { + "id": "1583390022", + "name": "Loose Leaf Tea", + "campaignId": "20451234567" + } + ], + "ads": [ + { + "id": "734500001", + "adGroupId": "1583390011", + "name": "Brand RSA — Drink Varitea" + }, + { + "id": "734500002", + "adGroupId": "1583390011", + "name": "Brand RSA — Varitea Official" + }, + { + "id": "734500003", + "adGroupId": "1583390022", + "name": "Loose Leaf RSA — Whole Leaf" + }, + { + "id": "734500004", + "adGroupId": "1583390022", + "name": "Loose Leaf RSA — Organic Origin" + }, + { + "id": "734500005", + "adGroupId": "1583390022", + "name": "Loose Leaf RSA — Steeped Slow" + } + ], + "dateRange": { + "since": "2026-06-12", + "until": "2026-06-18" + }, + "performance": [ + { + "level": "ad", + "date": "2026-06-12", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500001", + "impressions": 353, + "clicks": 22, + "conversions": 2, + "costMicros": 17208246, + "conversionsValue": 76 + }, + { + "level": "ad", + "date": "2026-06-13", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500001", + "impressions": 354, + "clicks": 23, + "conversions": 3, + "costMicros": 17990462, + "conversionsValue": 114 + }, + { + "level": "ad", + "date": "2026-06-14", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500001", + "impressions": 355, + "clicks": 23, + "conversions": 3, + "costMicros": 17990485, + "conversionsValue": 114 + }, + { + "level": "ad", + "date": "2026-06-15", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500001", + "impressions": 356, + "clicks": 23, + "conversions": 3, + "costMicros": 17990508, + "conversionsValue": 114 + }, + { + "level": "ad", + "date": "2026-06-16", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500001", + "impressions": 357, + "clicks": 24, + "conversions": 4, + "costMicros": 18772728, + "conversionsValue": 152 + }, + { + "level": "ad", + "date": "2026-06-17", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500001", + "impressions": 358, + "clicks": 24, + "conversions": 4, + "costMicros": 18772752, + "conversionsValue": 152 + }, + { + "level": "ad", + "date": "2026-06-18", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500001", + "impressions": 359, + "clicks": 25, + "conversions": 4, + "costMicros": 19554975, + "conversionsValue": 152 + }, + { + "level": "ad", + "date": "2026-06-12", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500002", + "impressions": 209, + "clicks": 9, + "conversions": 1, + "costMicros": 5677506, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-13", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500002", + "impressions": 210, + "clicks": 9, + "conversions": 2, + "costMicros": 5677515, + "conversionsValue": 76 + }, + { + "level": "ad", + "date": "2026-06-14", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500002", + "impressions": 211, + "clicks": 10, + "conversions": 1, + "costMicros": 6308360, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-15", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500002", + "impressions": 212, + "clicks": 10, + "conversions": 1, + "costMicros": 6308370, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-16", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500002", + "impressions": 213, + "clicks": 10, + "conversions": 1, + "costMicros": 6308380, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-17", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500002", + "impressions": 214, + "clicks": 10, + "conversions": 1, + "costMicros": 6308390, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-18", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": "734500002", + "impressions": 215, + "clicks": 11, + "conversions": 1, + "costMicros": 6939240, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-12", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500003", + "impressions": 421, + "clicks": 17, + "conversions": 2, + "costMicros": 10995107, + "conversionsValue": 76 + }, + { + "level": "ad", + "date": "2026-06-13", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500003", + "impressions": 422, + "clicks": 18, + "conversions": 3, + "costMicros": 11641896, + "conversionsValue": 114 + }, + { + "level": "ad", + "date": "2026-06-14", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500003", + "impressions": 423, + "clicks": 18, + "conversions": 3, + "costMicros": 11641914, + "conversionsValue": 114 + }, + { + "level": "ad", + "date": "2026-06-15", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500003", + "impressions": 424, + "clicks": 19, + "conversions": 3, + "costMicros": 12288706, + "conversionsValue": 114 + }, + { + "level": "ad", + "date": "2026-06-16", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500003", + "impressions": 425, + "clicks": 19, + "conversions": 3, + "costMicros": 12288725, + "conversionsValue": 114 + }, + { + "level": "ad", + "date": "2026-06-17", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500003", + "impressions": 426, + "clicks": 20, + "conversions": 1, + "costMicros": 12935520, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-18", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500003", + "impressions": 427, + "clicks": 20, + "conversions": 1, + "costMicros": 12935540, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-12", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500004", + "impressions": 277, + "clicks": 14, + "conversions": 1, + "costMicros": 12535768, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-13", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500004", + "impressions": 278, + "clicks": 15, + "conversions": 1, + "costMicros": 13431195, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-14", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500004", + "impressions": 279, + "clicks": 15, + "conversions": 1, + "costMicros": 13431210, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-15", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500004", + "impressions": 280, + "clicks": 15, + "conversions": 1, + "costMicros": 13431225, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-16", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500004", + "impressions": 281, + "clicks": 16, + "conversions": 2, + "costMicros": 14326656, + "conversionsValue": 76 + }, + { + "level": "ad", + "date": "2026-06-17", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500004", + "impressions": 282, + "clicks": 16, + "conversions": 2, + "costMicros": 14326672, + "conversionsValue": 76 + }, + { + "level": "ad", + "date": "2026-06-18", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500004", + "impressions": 283, + "clicks": 16, + "conversions": 2, + "costMicros": 14326688, + "conversionsValue": 76 + }, + { + "level": "ad", + "date": "2026-06-12", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500005", + "impressions": 269, + "clicks": 13, + "conversions": 2, + "costMicros": 11847537, + "conversionsValue": 76 + }, + { + "level": "ad", + "date": "2026-06-13", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500005", + "impressions": 270, + "clicks": 14, + "conversions": 2, + "costMicros": 12758900, + "conversionsValue": 76 + }, + { + "level": "ad", + "date": "2026-06-14", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500005", + "impressions": 271, + "clicks": 14, + "conversions": 2, + "costMicros": 12758914, + "conversionsValue": 76 + }, + { + "level": "ad", + "date": "2026-06-15", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500005", + "impressions": 272, + "clicks": 14, + "conversions": 1, + "costMicros": 12758928, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-16", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500005", + "impressions": 273, + "clicks": 14, + "conversions": 1, + "costMicros": 12758942, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-17", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500005", + "impressions": 274, + "clicks": 15, + "conversions": 1, + "costMicros": 13670310, + "conversionsValue": 38 + }, + { + "level": "ad", + "date": "2026-06-18", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": "734500005", + "impressions": 275, + "clicks": 15, + "conversions": 1, + "costMicros": 13670325, + "conversionsValue": 38 + }, + { + "level": "ad_group", + "date": "2026-06-12", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": null, + "impressions": 562, + "clicks": 31, + "conversions": 3, + "costMicros": 22885752, + "conversionsValue": 114 + }, + { + "level": "ad_group", + "date": "2026-06-13", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": null, + "impressions": 564, + "clicks": 32, + "conversions": 5, + "costMicros": 23667977, + "conversionsValue": 190 + }, + { + "level": "ad_group", + "date": "2026-06-14", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": null, + "impressions": 566, + "clicks": 33, + "conversions": 4, + "costMicros": 24298845, + "conversionsValue": 152 + }, + { + "level": "ad_group", + "date": "2026-06-15", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": null, + "impressions": 568, + "clicks": 33, + "conversions": 4, + "costMicros": 24298878, + "conversionsValue": 152 + }, + { + "level": "ad_group", + "date": "2026-06-16", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": null, + "impressions": 570, + "clicks": 34, + "conversions": 5, + "costMicros": 25081108, + "conversionsValue": 190 + }, + { + "level": "ad_group", + "date": "2026-06-17", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": null, + "impressions": 572, + "clicks": 34, + "conversions": 5, + "costMicros": 25081142, + "conversionsValue": 190 + }, + { + "level": "ad_group", + "date": "2026-06-18", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390011", + "adGroupName": "Brand", + "adId": null, + "impressions": 574, + "clicks": 36, + "conversions": 5, + "costMicros": 26494215, + "conversionsValue": 190 + }, + { + "level": "ad_group", + "date": "2026-06-12", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": null, + "impressions": 967, + "clicks": 44, + "conversions": 5, + "costMicros": 35378412, + "conversionsValue": 190 + }, + { + "level": "ad_group", + "date": "2026-06-13", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": null, + "impressions": 970, + "clicks": 47, + "conversions": 6, + "costMicros": 37831991, + "conversionsValue": 228 + }, + { + "level": "ad_group", + "date": "2026-06-14", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": null, + "impressions": 973, + "clicks": 47, + "conversions": 6, + "costMicros": 37832038, + "conversionsValue": 228 + }, + { + "level": "ad_group", + "date": "2026-06-15", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": null, + "impressions": 976, + "clicks": 48, + "conversions": 5, + "costMicros": 38478859, + "conversionsValue": 190 + }, + { + "level": "ad_group", + "date": "2026-06-16", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": null, + "impressions": 979, + "clicks": 49, + "conversions": 6, + "costMicros": 39374323, + "conversionsValue": 228 + }, + { + "level": "ad_group", + "date": "2026-06-17", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": null, + "impressions": 982, + "clicks": 51, + "conversions": 4, + "costMicros": 40932502, + "conversionsValue": 152 + }, + { + "level": "ad_group", + "date": "2026-06-18", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": "1583390022", + "adGroupName": "Loose Leaf Tea", + "adId": null, + "impressions": 985, + "clicks": 51, + "conversions": 4, + "costMicros": 40932553, + "conversionsValue": 152 + }, + { + "level": "campaign", + "date": "2026-06-12", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": null, + "adGroupName": null, + "adId": null, + "impressions": 1529, + "clicks": 75, + "conversions": 8, + "costMicros": 58264164, + "conversionsValue": 304 + }, + { + "level": "campaign", + "date": "2026-06-13", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": null, + "adGroupName": null, + "adId": null, + "impressions": 1534, + "clicks": 79, + "conversions": 11, + "costMicros": 61499968, + "conversionsValue": 418 + }, + { + "level": "campaign", + "date": "2026-06-14", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": null, + "adGroupName": null, + "adId": null, + "impressions": 1539, + "clicks": 80, + "conversions": 10, + "costMicros": 62130883, + "conversionsValue": 380 + }, + { + "level": "campaign", + "date": "2026-06-15", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": null, + "adGroupName": null, + "adId": null, + "impressions": 1544, + "clicks": 81, + "conversions": 9, + "costMicros": 62777737, + "conversionsValue": 342 + }, + { + "level": "campaign", + "date": "2026-06-16", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": null, + "adGroupName": null, + "adId": null, + "impressions": 1549, + "clicks": 83, + "conversions": 11, + "costMicros": 64455431, + "conversionsValue": 418 + }, + { + "level": "campaign", + "date": "2026-06-17", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": null, + "adGroupName": null, + "adId": null, + "impressions": 1554, + "clicks": 85, + "conversions": 9, + "costMicros": 66013644, + "conversionsValue": 342 + }, + { + "level": "campaign", + "date": "2026-06-18", + "campaignId": "20451234567", + "campaignName": "Varitea — Search — Brand + Loose Leaf", + "adGroupId": null, + "adGroupName": null, + "adId": null, + "impressions": 1559, + "clicks": 87, + "conversions": 9, + "costMicros": 67426768, + "conversionsValue": 342 + } + ], + "searchTerms": [ + { + "campaignId": "20451234567", + "adGroupId": "1583390011", + "searchTerm": "drink varitea", + "matchType": "EXACT", + "status": "ADDED", + "impressions": 412, + "clicks": 88, + "conversions": 14, + "costMicros": 41250000 + }, + { + "campaignId": "20451234567", + "adGroupId": "1583390011", + "searchTerm": "varitea tea brand", + "matchType": "PHRASE", + "status": "NONE", + "impressions": 96, + "clicks": 11, + "conversions": 1, + "costMicros": 7480000 + }, + { + "campaignId": "20451234567", + "adGroupId": "1583390022", + "searchTerm": "loose leaf tea organic", + "matchType": "BROAD", + "status": "NONE", + "impressions": 530, + "clicks": 47, + "conversions": 6, + "costMicros": 38900000 + }, + { + "campaignId": "20451234567", + "adGroupId": "1583390022", + "searchTerm": "whole leaf tea sampler", + "matchType": "PHRASE", + "status": "ADDED", + "impressions": 221, + "clicks": 19, + "conversions": 3, + "costMicros": 15600000 + }, + { + "campaignId": "20451234567", + "adGroupId": "1583390022", + "searchTerm": "cheap tea bags bulk", + "matchType": "BROAD", + "status": "NONE", + "impressions": 314, + "clicks": 22, + "conversions": 0, + "costMicros": 18700000 + } + ], + "changeHistory": [ + { + "changeDateTime": "2026-06-17 09:14:32", + "resourceType": "CAMPAIGN_BUDGET", + "changeResourceName": "customers/5128873230/campaignBudgets/9912", + "clientType": "GOOGLE_ADS_WEB_CLIENT", + "userEmail": "ops@drinkvaritea.com", + "operation": "UPDATE", + "changedFields": [ + "amount_micros" + ] + }, + { + "changeDateTime": "2026-06-15 13:02:10", + "resourceType": "AD_GROUP_CRITERION", + "changeResourceName": "customers/5128873230/adGroupCriteria/1583390022~3001", + "clientType": "GOOGLE_ADS_API", + "userEmail": "agent@shoploop.ai", + "operation": "CREATE", + "changedFields": [ + "negative", + "keyword.text" + ] + }, + { + "changeDateTime": "2026-06-13 18:45:55", + "resourceType": "AD_GROUP_AD", + "changeResourceName": "customers/5128873230/adGroupAds/1583390011~734500002", + "clientType": "GOOGLE_ADS_WEB_CLIENT", + "userEmail": "ops@drinkvaritea.com", + "operation": "UPDATE", + "changedFields": [ + "ad.responsive_search_ad.headlines" + ] + } + ] +} diff --git a/connectors/google-ads/test/helpers.ts b/connectors/google-ads/test/helpers.ts new file mode 100644 index 0000000..6fdc18f --- /dev/null +++ b/connectors/google-ads/test/helpers.ts @@ -0,0 +1,83 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +import type { DateRange, GoogleAdsClient } from "../src/client.js"; +import type { + CampaignDetail, + CampaignSummary, + ChangeEvent, + PerfRow, + SearchTermRow, +} from "../src/types.js"; + +export const HERE = dirname(fileURLToPath(import.meta.url)); + +export interface GadsFixture { + customerId: string; + campaign: CampaignDetail; + adGroups: { id: string; name: string; campaignId: string }[]; + ads: { id: string; adGroupId: string; name: string }[]; + dateRange: DateRange; + performance: PerfRow[]; + searchTerms: SearchTermRow[]; + changeHistory: ChangeEvent[]; +} + +export function loadFixture(): GadsFixture { + return JSON.parse(readFileSync(join(HERE, "fixtures", "varitea_gads_response.json"), "utf8")); +} + +function inRange(date: string, range: DateRange): boolean { + return date >= range.since && date <= range.until; +} + +// In-memory GoogleAdsClient backed by the fixture — no network, no OAuth. Mirrors the real +// interface so ingest / MCP / Temporal exercise identical code paths. +export class FixtureClient implements GoogleAdsClient { + public pauses: { customerId: string; campaignId: string }[] = []; + public resumes: { customerId: string; campaignId: string }[] = []; + + constructor(private fx: GadsFixture) {} + + async listCampaigns(_customerId: string, status?: string): Promise { + const c = this.fx.campaign; + const summary: CampaignSummary = { + id: c.id, + name: c.name, + status: c.status, + channelType: c.channelType, + biddingStrategyType: c.biddingStrategyType, + }; + return !status || status === c.status ? [summary] : []; + } + + async getCampaign(_customerId: string, campaignId: string): Promise { + return this.fx.campaign.id === campaignId ? this.fx.campaign : null; + } + + async queryPerformance(_customerId: string, range: DateRange): Promise { + return this.fx.performance.filter((r) => inRange(r.date, range)); + } + + async getSearchTerms( + _customerId: string, + campaignId: string, + _range: DateRange, + ): Promise { + return this.fx.searchTerms.filter((t) => t.campaignId === campaignId); + } + + async getChangeHistory(_customerId: string, range: DateRange): Promise { + return this.fx.changeHistory.filter((e) => inRange(e.changeDateTime.slice(0, 10), range)); + } + + async pauseCampaign(customerId: string, campaignId: string): Promise<{ resourceName: string }> { + this.pauses.push({ customerId, campaignId }); + return { resourceName: `customers/${customerId}/campaigns/${campaignId}` }; + } + + async resumeCampaign(customerId: string, campaignId: string): Promise<{ resourceName: string }> { + this.resumes.push({ customerId, campaignId }); + return { resourceName: `customers/${customerId}/campaigns/${campaignId}` }; + } +} diff --git a/connectors/google-ads/test/mcp.test.ts b/connectors/google-ads/test/mcp.test.ts new file mode 100644 index 0000000..b883d94 --- /dev/null +++ b/connectors/google-ads/test/mcp.test.ts @@ -0,0 +1,153 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { createMcpServer } from "../src/mcp/server.js"; +import type { ToolContext } from "../src/mcp/tools/index.js"; +import { FixtureClient, loadFixture } from "./helpers.js"; + +const CUSTOMER = "5128873230"; +const CAMPAIGN = "20451234567"; +const RANGE = { since: "2026-06-12", until: "2026-06-18" }; + +interface ContentItem { + type: string; + text: string; +} + +// Connect an MCP client to an in-proc server with the given write gate. +async function connect(writeEnabled: boolean) { + const ctx: ToolContext = { + client: new FixtureClient(loadFixture()), + writeEnabled, + banditQueuePath: "/tmp/ignored.jsonl", + }; + const server = createMcpServer(ctx); + const [clientT, serverT] = InMemoryTransport.createLinkedPair(); + const client = new Client({ name: "test", version: "0.0.0" }); + await Promise.all([server.connect(serverT), client.connect(clientT)]); + return client; +} + +async function call(client: Client, name: string, args: Record) { + const res = (await client.callTool({ name, arguments: args })) as { + content: ContentItem[]; + isError?: boolean; + }; + const texts = res.content.filter((c) => c.type === "text"); + const isError = res.isError ?? false; + return { + isError, + data: !isError && texts[0] ? JSON.parse(texts[0].text) : null, + errorText: isError ? texts[0]?.text : undefined, + text: texts[1]?.text, + }; +} + +let ro: Client; + +beforeAll(async () => { + ro = await connect(false); // write flag OFF +}); +afterAll(async () => { + await ro?.close(); +}); + +describe("MCP server (write disabled)", () => { + it("exposes all 7 Google Ads tools", async () => { + const tools = await ro.listTools(); + expect(tools.tools.map((t) => t.name).sort()).toEqual( + [ + "get_campaign", + "get_change_history", + "get_performance", + "get_search_terms", + "list_campaigns", + "pause_campaign", + "resume_campaign", + ].sort(), + ); + }); + + it("list_campaigns returns the Varitea campaign", async () => { + const { data } = await call(ro, "list_campaigns", { customerId: CUSTOMER }); + expect(data.count).toBe(1); + expect(data.campaigns[0].id).toBe(CAMPAIGN); + expect(data.campaigns[0].channelType).toBe("SEARCH"); + }); + + it("get_campaign returns budget + bidding strategy", async () => { + const { data } = await call(ro, "get_campaign", { customerId: CUSTOMER, campaignId: CAMPAIGN }); + expect(data.found).toBe(true); + expect(data.campaign.biddingStrategyType).toBe("MAXIMIZE_CONVERSIONS"); + expect(data.campaign.budgetMicros).toBe(45000000); + }); + + it("get_performance returns a 7-day daily rollup with derived roas", async () => { + const { data, text } = await call(ro, "get_performance", { + customerId: CUSTOMER, + range: RANGE, + granularity: "daily", + }); + expect(data.buckets).toHaveLength(7); + expect(text).toContain("7 daily bucket(s)"); + for (const b of data.buckets) { + expect(b.metrics.roas).toBeCloseTo(b.metrics.conversions_value / b.metrics.cost, 6); + } + }); + + it("get_performance weekly rollup buckets by Monday", async () => { + const { data } = await call(ro, "get_performance", { + customerId: CUSTOMER, + range: RANGE, + granularity: "weekly", + }); + expect(data.buckets.map((b: any) => b.bucket)).toEqual(["2026-06-08", "2026-06-15"]); + }); + + it("get_search_terms returns terms for negative-keyword harvesting", async () => { + const { data } = await call(ro, "get_search_terms", { + customerId: CUSTOMER, + campaignId: CAMPAIGN, + range: RANGE, + }); + expect(data.count).toBeGreaterThan(0); + expect(data.terms.some((t: any) => t.searchTerm === "cheap tea bags bulk")).toBe(true); + }); + + it("get_change_history returns change events in range", async () => { + const { data } = await call(ro, "get_change_history", { customerId: CUSTOMER, range: RANGE }); + expect(data.count).toBe(3); + expect(data.events[0].operation).toBeDefined(); + }); + + it("refuses pause_campaign when the write flag is off", async () => { + const { isError, errorText } = await call(ro, "pause_campaign", { + customerId: CUSTOMER, + campaignId: CAMPAIGN, + }); + expect(isError).toBe(true); + expect(errorText).toContain("SHOPLOOP_GADS_WRITE_ENABLED"); + }); + + it("refuses resume_campaign when the write flag is off", async () => { + const { isError } = await call(ro, "resume_campaign", { + customerId: CUSTOMER, + campaignId: CAMPAIGN, + }); + expect(isError).toBe(true); + }); +}); + +describe("MCP server (write enabled)", () => { + it("pause_campaign and resume_campaign succeed when SHOPLOOP_GADS_WRITE_ENABLED=true", async () => { + const rw = await connect(true); + const paused = await call(rw, "pause_campaign", { customerId: CUSTOMER, campaignId: CAMPAIGN }); + expect(paused.isError).toBe(false); + expect(paused.data.status).toBe("PAUSED"); + + const resumed = await call(rw, "resume_campaign", { customerId: CUSTOMER, campaignId: CAMPAIGN }); + expect(resumed.isError).toBe(false); + expect(resumed.data.status).toBe("ENABLED"); + await rw.close(); + }); +}); diff --git a/connectors/google-ads/test/primitives.test.ts b/connectors/google-ads/test/primitives.test.ts new file mode 100644 index 0000000..41df219 --- /dev/null +++ b/connectors/google-ads/test/primitives.test.ts @@ -0,0 +1,162 @@ +import { mkdtemp, readFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { ingestPerformance } from "../src/primitives/ingest_performance.js"; +import { recordPerformanceForBandit } from "../src/bandit/record.js"; +import { rollupPerformance, weekStart } from "../src/rollup.js"; +import { toObservation } from "../src/observe.js"; +import { buildJoinMap, linkCampaignToShopifyProduct } from "../src/primitives/join.js"; +import { deriveMetrics, observationKey, type PerformanceObservation } from "../src/types.js"; +import { FixtureClient, loadFixture } from "./helpers.js"; + +const CUSTOMER = "5128873230"; +const RANGE = { since: "2026-06-12", until: "2026-06-18" }; +const OBSERVED = "2099-01-01T00:00:00.000Z"; + +describe("ingestPerformance", () => { + it("writes one JSONL per day with all-level observations matching the contract shape", async () => { + const out = await mkdtemp(join(tmpdir(), "gads-")); + const client = new FixtureClient(loadFixture()); + const report = await ingestPerformance({ + customerId: CUSTOMER, + since: RANGE.since, + until: RANGE.until, + outDir: out, + client, + observedAt: OBSERVED, + }); + + // 7 days; per day = 1 campaign + 2 ad_group + 5 ad = 8 observations → 56 total. + expect(report.days).toBe(7); + expect(report.observations).toBe(56); + expect(report.files).toHaveLength(7); + expect(report.files[0].path).toContain(`${CUSTOMER}/2026-06-12.jsonl`); + expect(report.files[0].count).toBe(8); + + const firstDay = await readFile(report.files[0].path, "utf8"); + const lines = firstDay.trim().split("\n").map((l) => JSON.parse(l) as PerformanceObservation); + expect(lines).toHaveLength(8); + const levels = lines.map((o) => o.level).sort(); + expect(levels).toEqual(["ad", "ad", "ad", "ad", "ad", "ad_group", "ad_group", "campaign"]); + + const campaign = lines.find((o) => o.level === "campaign")!; + expect(campaign.schema).toBe("performance_observation.v1"); + expect(campaign.source).toBe("google-ads"); + expect(campaign.customer_id).toBe(CUSTOMER); + expect(campaign.date).toBe("2026-06-12"); + expect(campaign.observed_at).toBe(OBSERVED); + expect(campaign.shopify_product_gid).toBeNull(); + // roas = conversions_value / cost, derived not stored + expect(campaign.metrics.roas).toBeCloseTo( + campaign.metrics.conversions_value / campaign.metrics.cost, + 6, + ); + expect(campaign.metrics.cost).toBeCloseTo(campaign.metrics.cost_micros / 1_000_000, 6); + }); + + it("dedupes overlapping windows by (date, campaign_id, ad_group_id, ad_id)", async () => { + const out = await mkdtemp(join(tmpdir(), "gads-")); + const fx = loadFixture(); + // Duplicate every row — ingest must collapse them. + fx.performance = [...fx.performance, ...fx.performance]; + const report = await ingestPerformance({ + customerId: CUSTOMER, + since: RANGE.since, + until: RANGE.until, + outDir: out, + client: new FixtureClient(fx), + observedAt: OBSERVED, + }); + expect(report.observations).toBe(56); + expect(report.skipped.length).toBe(56); + }); + + it("joins campaign → shopify product gid when a join map is supplied", async () => { + const out = await mkdtemp(join(tmpdir(), "gads-")); + const join0 = buildJoinMap({ "gid://shopify/Product/7700112233445": "20451234567" }); + const report = await ingestPerformance({ + customerId: CUSTOMER, + since: RANGE.since, + until: RANGE.until, + outDir: out, + client: new FixtureClient(loadFixture()), + shopifyJoin: join0, + observedAt: OBSERVED, + }); + const day = await readFile(report.files[0].path, "utf8"); + const obs = JSON.parse(day.trim().split("\n")[0]) as PerformanceObservation; + expect(obs.shopify_product_gid).toBe("gid://shopify/Product/7700112233445"); + }); +}); + +describe("deriveMetrics", () => { + it("guards divide-by-zero", () => { + const m = deriveMetrics({ impressions: 0, clicks: 0, conversions: 0, costMicros: 0, conversionsValue: 0 }); + expect(m.ctr).toBe(0); + expect(m.average_cpc).toBe(0); + expect(m.roas).toBe(0); + }); +}); + +describe("rollupPerformance", () => { + it("rolls campaign-level rows to daily buckets summing counters", () => { + const rows = loadFixture().performance; + const daily = rollupPerformance(rows, "daily"); + expect(daily).toHaveLength(7); + const campaignRows = rows.filter((r) => r.level === "campaign"); + const totalClicks = campaignRows.reduce((a, r) => a + r.clicks, 0); + expect(daily.reduce((a, b) => a + b.metrics.clicks, 0)).toBe(totalClicks); + }); + + it("rolls into weekly buckets keyed by Monday", () => { + const rows = loadFixture().performance; + const weekly = rollupPerformance(rows, "weekly"); + // 2026-06-12..18 spans week of Mon 2026-06-08 and Mon 2026-06-15. + expect(weekly.map((w) => w.bucket)).toEqual(["2026-06-08", "2026-06-15"]); + expect(weekStart("2026-06-18")).toBe("2026-06-15"); + }); +}); + +describe("recordPerformanceForBandit", () => { + it("appends typed observations to the bandit queue jsonl", async () => { + const dir = await mkdtemp(join(tmpdir(), "bandit-")); + const queuePath = join(dir, "queue.jsonl"); + const row = loadFixture().performance.find((r) => r.level === "campaign")!; + const obs = toObservation(row, CUSTOMER, { observedAt: OBSERVED }); + await recordPerformanceForBandit(obs, { queuePath }); + await recordPerformanceForBandit(obs, { queuePath }); + const text = await readFile(queuePath, "utf8"); + const lines = text.trim().split("\n"); + expect(lines).toHaveLength(2); + expect((JSON.parse(lines[0]) as PerformanceObservation).campaign_id).toBe(row.campaignId); + }); +}); + +describe("linkCampaignToShopifyProduct", () => { + it("writes the shoploop.gads_campaign_id metafield via the injected Shopify writer", async () => { + const writes: any[] = []; + await linkCampaignToShopifyProduct({ + productGid: "gid://shopify/Product/7700112233445", + campaignId: "20451234567", + updateMetafield: async (input) => { + writes.push(input); + return input; + }, + }); + expect(writes[0]).toMatchObject({ + ownerId: "gid://shopify/Product/7700112233445", + namespace: "shoploop", + key: "gads_campaign_id", + value: "20451234567", + }); + }); +}); + +describe("observationKey", () => { + it("is stable across the dedup tuple", () => { + expect( + observationKey({ date: "2026-06-12", campaign_id: "1", ad_group_id: null, ad_id: null }), + ).toBe("2026-06-12|1|-|-"); + }); +}); diff --git a/connectors/google-ads/test/workflow.test.ts b/connectors/google-ads/test/workflow.test.ts new file mode 100644 index 0000000..736ddd9 --- /dev/null +++ b/connectors/google-ads/test/workflow.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from "vitest"; +import { + dispatchHourlyIngest, + dispatchPause, + gadsWorkflowId, + hourBucket, + type WorkflowStarter, +} from "../src/temporal/dispatch.js"; +import { TASK_QUEUE } from "../src/config.js"; + +const CUSTOMER = "5128873230"; + +// Mock starter enforcing WorkflowIdReusePolicy=REJECT_DUPLICATE: a second start with the same +// id is collapsed (no duplicate execution), exactly like Temporal. +function rejectDuplicateStarter() { + const started: { workflow: string; workflowId: string; args: unknown[]; taskQueue: string }[] = []; + const seen = new Set(); + const starter: WorkflowStarter = { + async start(workflow, opts) { + if (!seen.has(opts.workflowId)) { + seen.add(opts.workflowId); + started.push({ workflow, ...opts }); + } + return { workflowId: opts.workflowId }; + }, + }; + return { starter, started }; +} + +describe("hour-bucket idempotency", () => { + it("builds gads::: ids", () => { + const at = new Date("2026-06-25T14:37:09Z"); + expect(hourBucket(at)).toBe("2026-06-25T14"); + expect(gadsWorkflowId(CUSTOMER, "IngestGoogleAdsPerformanceWorkflow", hourBucket(at))).toBe( + "gads:5128873230:IngestGoogleAdsPerformanceWorkflow:2026-06-25T14", + ); + }); + + it("collapses repeated hourly fires within the same hour to one execution", async () => { + const { starter, started } = rejectDuplicateStarter(); + const at = new Date("2026-06-25T14:05:00Z"); + const later = new Date("2026-06-25T14:58:00Z"); // same hour bucket + + const id1 = await dispatchHourlyIngest(starter, CUSTOMER, at); + const id2 = await dispatchHourlyIngest(starter, CUSTOMER, later); + + expect(id1).toBe(id2); + expect(started).toHaveLength(1); + expect(started[0].taskQueue).toBe(TASK_QUEUE); + expect(started[0].args).toEqual([CUSTOMER]); + }); + + it("starts a fresh execution in the next hour bucket", async () => { + const { starter, started } = rejectDuplicateStarter(); + await dispatchHourlyIngest(starter, CUSTOMER, new Date("2026-06-25T14:05:00Z")); + await dispatchHourlyIngest(starter, CUSTOMER, new Date("2026-06-25T15:05:00Z")); + expect(started).toHaveLength(2); + }); + + it("namespaces pause workflows per campaign + hour", async () => { + const { starter, started } = rejectDuplicateStarter(); + const at = new Date("2026-06-25T14:05:00Z"); + const id = await dispatchPause(starter, CUSTOMER, "20451234567", "low roas", at); + expect(id).toBe( + "gads:5128873230:PauseUnderperformingCampaignWorkflow:20451234567:2026-06-25T14", + ); + // duplicate fire for the same campaign+hour collapses + await dispatchPause(starter, CUSTOMER, "20451234567", "low roas", at); + expect(started).toHaveLength(1); + expect(started[0].args).toEqual([CUSTOMER, "20451234567", "low roas"]); + }); +}); diff --git a/connectors/google-ads/tsconfig.build.json b/connectors/google-ads/tsconfig.build.json new file mode 100644 index 0000000..376ad7f --- /dev/null +++ b/connectors/google-ads/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "noEmit": false + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules", "test"] +} diff --git a/connectors/google-ads/tsconfig.json b/connectors/google-ads/tsconfig.json new file mode 100644 index 0000000..b55ba1e --- /dev/null +++ b/connectors/google-ads/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": ".", + "declaration": true, + "sourceMap": true, + "strict": true, + "noImplicitOverride": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "verbatimModuleSyntax": false, + "types": ["node"] + }, + "include": ["src/**/*.ts", "test/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/connectors/google-ads/vitest.config.ts b/connectors/google-ads/vitest.config.ts new file mode 100644 index 0000000..0388827 --- /dev/null +++ b/connectors/google-ads/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["test/**/*.test.ts"], + globals: false, + }, +});