diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e9953a6..1dc1478 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,6 +29,7 @@ include: - stripe-action - smtp-action - github-action + - slack-action .rust-actions: - cron-action diff --git a/README.md b/README.md index 32adaf6..e7841ba 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ The "central" place for all actions — a monorepo containing integrations provi | [GLS](docs/Actions/GLS/overview.md) | GLS ShipIT integration for creating and managing shipments | | [Stripe](actions/stripe-action/README.md) | Stripe payments integration for managing customers, payment intents and refunds | | SMTP | Send emails (with attachments) through any SMTP server via nodemailer | +| [Slack](actions/slack-action/README.md) | Slack integration for posting messages, reacting to Slack events and driving channel/user operations | + ## ENV All actions share the following base environment variables, which connect them to the Hercules/Aquila infrastructure: diff --git a/actions/slack-action/.gitignore b/actions/slack-action/.gitignore new file mode 100644 index 0000000..ddc3c2a --- /dev/null +++ b/actions/slack-action/.gitignore @@ -0,0 +1,3 @@ +.env +node_modules/ +dist/ diff --git a/actions/slack-action/Dockerfile b/actions/slack-action/Dockerfile new file mode 100644 index 0000000..8d9f6c7 --- /dev/null +++ b/actions/slack-action/Dockerfile @@ -0,0 +1,13 @@ +FROM node:24.10.0-alpine + +WORKDIR /app + +COPY package.json package-lock.json* ./ + +RUN npm ci + +COPY . . + +RUN npm run build + +CMD ["npm", "run", "start"] diff --git a/actions/slack-action/README.md b/actions/slack-action/README.md new file mode 100644 index 0000000..b6395f0 --- /dev/null +++ b/actions/slack-action/README.md @@ -0,0 +1,146 @@ +# Slack Action + +Slack integration for the **Hercules** automation platform — the canonical +"notify a human / capture a human decision" step in a flow. + +Messages, threads, reactions, files and channel/user operations go out through +the Slack Web API; inbound Slack events arrive as `Rest` webhook flow types, the +same pattern the `shopify-action` and `stripe-action` use. + +## Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `slackPostMessage` | `(channelId: TEXT, text: TEXT, blocks?: LIST): SLACK_MESSAGE` | Post a message to a channel, private group or DM. | +| `slackReplyInThread` | `(channelId: TEXT, threadTs: TEXT, text: TEXT, blocks?: LIST): SLACK_MESSAGE` | Reply in an existing thread. | +| `slackUpdateMessage` | `(channelId: TEXT, ts: TEXT, text: TEXT, blocks?: LIST): SLACK_MESSAGE` | Replace the content of a message the app posted. | +| `slackDeleteMessage` | `(channelId: TEXT, ts: TEXT): BOOLEAN` | Delete a message the app posted. | +| `slackAddReaction` | `(channelId: TEXT, ts: TEXT, emoji: TEXT): BOOLEAN` | Add an emoji reaction to a message. | +| `slackSendDirectMessage` | `(userId: TEXT, text: TEXT, blocks?: LIST): SLACK_MESSAGE` | Open (or reuse) an IM channel with a user, then post into it. | +| `slackUploadFile` | `(channelId: TEXT, filename: TEXT, content: TEXT, title?: TEXT): SLACK_FILE` | Upload a text file and share it into a channel. | +| `slackCreateChannel` | `(name: TEXT, isPrivate?: BOOLEAN): SLACK_CHANNEL` | Create a public or private channel. | +| `slackInviteToChannel` | `(channelId: TEXT, userIds: LIST): SLACK_CHANNEL` | Invite up to 1000 users to a channel. | +| `slackGetUserByEmail` | `(email: TEXT): SLACK_USER` | Look a workspace member up by email address. | +| `slackSetChannelTopic` | `(channelId: TEXT, topic: TEXT): SLACK_CHANNEL` | Set a channel's topic. | +| `slackVerifyRequestSignature` | `(signature: TEXT, timestamp: TEXT, rawBody: TEXT): BOOLEAN` | Verify that an inbound request really came from Slack. See [Verifying inbound requests](#verifying-inbound-requests). | + +### Block Kit utils + +Two builders assemble [Block Kit](https://docs.slack.dev/block-kit/) payloads for +the `blocks` argument of the message functions, mirroring the `create*` utils of +the `gls-action`: + +| Function | Signature | Description | +|----------|-----------|-------------| +| `slackCreateTextSection` | `(text: TEXT, markdown?: BOOLEAN, blockId?: TEXT): SLACK_BLOCK` | A `section` block holding one text object. Slack markdown by default. | +| `slackCreateBlock` | `(type: TEXT, text?: TEXT, fields?: LIST, imageUrl?: TEXT, altText?: TEXT, blockId?: TEXT): SLACK_BLOCK` | Any other block type — `divider`, `header`, `image`, `context`, or a `section` with fields. | + +Only the arguments a block type actually uses are sent, so `slackCreateBlock("divider")` +produces a bare divider. `header` text is rendered literally, because Slack +rejects a `mrkdwn` text object there. + +## Events + +Slack pushes events over HTTP via its [Events API](https://docs.slack.dev/apis/events-api). +Each event below is a `Rest` flow type: it exposes an HTTP endpoint, so paste its +URL into **Event Subscriptions → Request URL** in your Slack app and subscribe to +the matching bot event. + +| Event | Slack event | Payload | +|-------|-------------|---------| +| `SlackMessagePosted` | `message.channels` | `SlackMessagePostedEventPayload` | +| `SlackAppMentioned` | `app_mention` | `SlackAppMentionEventPayload` | +| `SlackReactionAdded` | `reaction_added` | `SlackReactionAddedEventPayload` | +| `SlackChannelCreated` | `channel_created` | `SlackChannelCreatedEventPayload` | +| `SlackSlashCommand` | slash command invocation | `SlackSlashCommandPayload` | + +The four Events API events arrive as `application/json`, wrapped in the shared +`event_callback` envelope (`{ team_id, event_id, event: { … } }`); a slash command +arrives as `application/x-www-form-urlencoded` with flat fields and no envelope. +Both defaults are pre-set on the events, together with `POST`. + +### Filtering by channel or command + +Slack delivers one subscription to one Request URL, so an endpoint receives every +message of every channel the app is in — the event itself cannot narrow that +down. Filter inside the flow instead: compare `event.channel` +(`SlackMessagePosted`), `event.item.channel` (`SlackReactionAdded`) or `command` +(`SlackSlashCommand`) against the value you care about and stop early otherwise. + +### Responding to Slack + +Slack expects a `2xx` within three seconds and retries otherwise, so respond with +the `Respond` function of the `rest-action` (`rest::control::respond`) before doing +slow work: + +- **URL verification** — when you first save the Request URL, Slack posts + `{ "type": "url_verification", "challenge": "…" }` and expects the `challenge` + echoed back. +- **Slash commands** — respond with a body to reply immediately, or respond empty + and post to the payload's `response_url` for up to 30 minutes afterwards. + +### Verifying inbound requests + +Slack signs each request with an `X-Slack-Signature` header: an HMAC-SHA256 over +`v0::` keyed with the signing secret. +`slackVerifyRequestSignature` recomputes it and additionally rejects timestamps +older than five minutes as replays. + +> **Known limitation:** Slack signs the exact bytes it sent, and the `Rest` flow +> type hands the flow an already parsed `payload` — re-serializing it does not +> reproduce those bytes. Until the REST adapter exposes the raw body, the function +> can only be used where the flow has the unparsed body available. Slack does not +> send a Bearer token either, so the event's `http_auth` setting cannot stand in +> for it. Treat these endpoints as publicly reachable and keep that in mind for +> what a flow does with an unverified payload. + +## Data types + +One class per file in `src/data_types/`, each a hand-written `zod` schema +registered with `@Identifier` / `@Name` / `@Schema`: + +| Data type | Description | +|-----------|-------------| +| `SLACK_MESSAGE` | A posted message. `ts` identifies it within its channel and doubles as a thread timestamp. | +| `SLACK_CHANNEL` | A conversation: public or private channel, DM or group DM. | +| `SLACK_USER` | A workspace member, including their profile. | +| `SLACK_FILE` | A file hosted by Slack. | +| `SLACK_REACTION` | An emoji reaction and the item it points at. | +| `SLACK_BLOCK` | A single Block Kit block. | + +Slack's block and element catalogue keeps growing, so `SLACK_BLOCK` types the +fields shared by the common blocks and leaves `accessory` and `elements` open for +raw Block Kit objects. + +The five event payload schemas live next to them; the four Events API ones share +the envelope built by `src/data_types/slackEventEnvelope.ts`. + +## Configuration + +| Config | Required | Env default | Description | +|--------|----------|-------------|-------------| +| `bot_token` | yes | `SLACK_BOT_TOKEN` | Bot user OAuth token (`xoxb-…`), the Bearer credential for the Web API. Found under *OAuth & Permissions*. | +| `signing_secret` | yes | `SLACK_SIGNING_SECRET` | Signing secret used by `slackVerifyRequestSignature`. Found under *Basic Information*. | +| `app_token` | no | `SLACK_APP_TOKEN` | App-level token (`xapp-…`), only needed when running the Slack app in Socket Mode instead of exposing public HTTP endpoints. | + +Beyond these the action uses the shared Hercules variables (`HERCULES_AUTH_TOKEN`, +`HERCULES_AQUILA_URL`, `HERCULES_ACTION_ID`, `HERCULES_SDK_VERSION`) documented in +the [repository README](../../README.md). + +### Slack app scopes + +The bot token needs the scopes for the functions and events a flow actually uses: + +| Scope | Used by | +|-------|---------| +| `chat:write` | `slackPostMessage`, `slackReplyInThread`, `slackUpdateMessage`, `slackDeleteMessage`, `slackSendDirectMessage` | +| `reactions:write` | `slackAddReaction` | +| `files:write` | `slackUploadFile` | +| `channels:manage`, `groups:write` | `slackCreateChannel`, `slackInviteToChannel`, `slackSetChannelTopic` | +| `im:write` | `slackSendDirectMessage` | +| `users:read`, `users:read.email` | `slackGetUserByEmail` | +| `channels:history`, `app_mentions:read`, `reactions:read`, `channels:read` | the corresponding events | + +All API calls go through the official +[`@slack/web-api`](https://www.npmjs.com/package/@slack/web-api) SDK, which +handles authentication, retries and rate limit backoff. diff --git a/actions/slack-action/manifest.json b/actions/slack-action/manifest.json new file mode 100644 index 0000000..da65260 --- /dev/null +++ b/actions/slack-action/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "slack", + "identifier": "slack-action", + "description": "Slack integration: post and update messages, react to Slack events, and drive channel, user and file operations via the Slack Web API.", + "deployment": { + "docker": { + "image": "ghcr.io/code0-tech/centaurus/ci-builds/slack-action" + } + }, + "dependencies": [ + "rest-action" + ] +} diff --git a/actions/slack-action/package-lock.json b/actions/slack-action/package-lock.json new file mode 100644 index 0000000..cde8b32 --- /dev/null +++ b/actions/slack-action/package-lock.json @@ -0,0 +1,3822 @@ +{ + "name": "@code0-tech/slack-action", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@code0-tech/slack-action", + "version": "1.0.0", + "dependencies": { + "@code0-tech/hercules": "^1.6.0", + "@slack/web-api": "^7.9.1" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.0.0", + "typescript": "^5.9.3", + "vite": "^8.0.3", + "vitest": "^4.1.10" + } + }, + "node_modules/@clack/core": { + "version": "1.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.0.0-alpha.4.tgz", + "integrity": "sha512-VCtU+vjyKPMSakVrB9q1bOnXN7QW/w4+YQDQCOF59GrzydW+169i0fVx/qzRRXJgt8KGj/pZZ/JxXroFZIDByg==", + "license": "MIT", + "peer": true, + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "1.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.0.0-alpha.4.tgz", + "integrity": "sha512-KnmtDF2xQGoI5AlBme9akHtvCRV0RKAARUXHBQO2tMwnY8B08/4zPWigT7uLK25UPrMCEqnyQPkKRjNdhPbf8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@clack/core": "1.0.0-alpha.4", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@code0-tech/hercules": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@code0-tech/hercules/-/hercules-1.7.2.tgz", + "integrity": "sha512-xRfolLdKOWC+oiNa7XRo8EeQSylwgq6XkcLhB0YocU8XeEc7qSaHWKJSNnhIDLOmAZzpa7OmpxZj6YFeweUqJg==", + "license": "ISC", + "bin": { + "hercules": "bin/hercules.js" + }, + "peerDependencies": { + "@code0-tech/tucana": "0.0.83", + "@grpc/grpc-js": "^1.14.3", + "@protobuf-ts/grpc-backend": "^2.11.1", + "@protobuf-ts/grpc-transport": "^2.11.1", + "@protobuf-ts/runtime": "^2.11.1", + "@protobuf-ts/runtime-rpc": "^2.11.1", + "reflect-metadata": "^0.2.2", + "ts-morph": "^28.0.0", + "ts-to-zod": "^5.1.0", + "zod": "^4.4.3", + "zod-to-ts": "^2.1.0" + } + }, + "node_modules/@code0-tech/tucana": { + "version": "0.0.83", + "resolved": "https://registry.npmjs.org/@code0-tech/tucana/-/tucana-0.0.83.tgz", + "integrity": "sha512-scBOjgf3oIQF8pUlskqPHqfzPvgCJgG1+/tt9RAKWZ1IdCdSqTRG768uEVeTjpuryiGRiO0p8r4bCXNEQNRtRg==", + "license": "MIT", + "peer": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "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", + "peer": true, + "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", + "peer": true, + "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/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "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", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@oclif/core": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.14.0.tgz", + "integrity": "sha512-QCJIZoVJxV7jywgVAUlsQwl3dr3Sa21kfi5z9KrYolbexWJUtFSEtMoNiBWojD05mYycLnB50gp/VxlGdaOCRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.3", + "ejs": "^6", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "lilconfig": "^3.1.3", + "minimatch": "^10.2.5", + "semver": "^7.8.1", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.17", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0", + "wsl-utils": "^0.4.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.149.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz", + "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@protobuf-ts/grpc-backend": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/grpc-backend/-/grpc-backend-2.11.1.tgz", + "integrity": "sha512-llAkJ03SlR/+p70YBTMOAQqdkXWghsTWwlPuGXC+Hj99hDQq6fa2CoVePPqbcn7A7/aLBcPG/9Ag3yqlltldxw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1", + "@protobuf-ts/runtime-rpc": "^2.11.1" + }, + "peerDependencies": { + "@grpc/grpc-js": "^1.8.22" + } + }, + "node_modules/@protobuf-ts/grpc-transport": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/grpc-transport/-/grpc-transport-2.11.1.tgz", + "integrity": "sha512-l6wrcFffY+tuNnuyrNCkRM8hDIsAZVLA8Mn7PKdVyYxITosYh60qW663p9kL6TWXYuDCL3oxH8ih3vLKTDyhtg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1", + "@protobuf-ts/runtime-rpc": "^2.11.1" + }, + "peerDependencies": { + "@grpc/grpc-js": "^1.6.0" + } + }, + "node_modules/@protobuf-ts/runtime": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.11.1.tgz", + "integrity": "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true + }, + "node_modules/@protobuf-ts/runtime-rpc": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz", + "integrity": "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1" + } + }, + "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", + "peer": true + }, + "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", + "peer": true + }, + "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", + "peer": true + }, + "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", + "peer": true + }, + "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", + "peer": true, + "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", + "peer": true + }, + "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", + "peer": true + }, + "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", + "peer": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz", + "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz", + "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz", + "integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz", + "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz", + "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz", + "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz", + "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz", + "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz", + "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz", + "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz", + "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz", + "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz", + "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz", + "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz", + "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@slack/logger": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.1.tgz", + "integrity": "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==", + "license": "MIT", + "dependencies": { + "@types/node": ">=18" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.22.0.tgz", + "integrity": "sha512-sZ9lIgJhPX2qft/tKWiklFlc0o1FWeI7QtciZJfW1+ErH1eGGHvOZ8e73sleTCFEFJp1q/R0WeS8Oa7AsiDprg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.19.0.tgz", + "integrity": "sha512-ItjyjEZml+LDH8CjcCLRLJHh7VZtevPKExrRN3l5KWyBliyDnGAeoO4Y+K+fFBmRpKLYVPgqWMX4THldv2HVtA==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.1", + "@slack/types": "^2.21.0", + "@types/node": ">=18", + "@types/retry": "0.12.0", + "axios": "^1.16.0", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.4", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ts-morph/common": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.29.0.tgz", + "integrity": "sha512-35oUmphHbJvQ/+UTwFNme/t2p3FoKiGJ5auTjjpNTop2dyREspirjMy82PLSC1pnDJ8ah1GU98hwpVt64YXQsg==", + "license": "MIT", + "peer": true, + "dependencies": { + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1", + "tinyglobby": "^0.2.14" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.2.tgz", + "integrity": "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@typescript/vfs": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.4.tgz", + "integrity": "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.3" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "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/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "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/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "license": "MIT", + "peer": true, + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "peer": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "license": "MIT", + "peer": true, + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT", + "peer": true + }, + "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", + "peer": true, + "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", + "peer": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT", + "peer": true + }, + "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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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/ejs": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-6.0.1.tgz", + "integrity": "sha512-UaaM14yby8U3k02ihS1Bmj5Kz2d7CCQM1scxpgs4Mhkq8F1wR2gl3+Ts4h5Ne4Mnt7M9m4Dw7jsuMr3+xO4vZA==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.12.18" + } + }, + "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", + "peer": true + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "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.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "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/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", + "peer": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "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/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/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", + "peer": true, + "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/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/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "license": "MIT", + "peer": true, + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT", + "peer": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT", + "peer": true + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "peer": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT", + "peer": true + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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", + "peer": true + }, + "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/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/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "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/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT", + "peer": true + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "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/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "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.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "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-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0", + "peer": true + }, + "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", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "peer": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT", + "peer": true + }, + "node_modules/rolldown": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz", + "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.149.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.8", + "@rolldown/binding-android-arm64": "1.2.8", + "@rolldown/binding-darwin-arm64": "1.2.8", + "@rolldown/binding-darwin-x64": "1.2.8", + "@rolldown/binding-freebsd-x64": "1.2.8", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", + "@rolldown/binding-linux-arm64-gnu": "1.2.8", + "@rolldown/binding-linux-arm64-musl": "1.2.8", + "@rolldown/binding-linux-ppc64-gnu": "1.2.8", + "@rolldown/binding-linux-s390x-gnu": "1.2.8", + "@rolldown/binding-linux-x64-gnu": "1.2.8", + "@rolldown/binding-linux-x64-musl": "1.2.8", + "@rolldown/binding-openharmony-arm64": "1.2.8", + "@rolldown/binding-win32-arm64-msvc": "1.2.8", + "@rolldown/binding-win32-x64-msvc": "1.2.8" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT", + "peer": true + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "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==", + "dev": true, + "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/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "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", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/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", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/text-camel-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-camel-case/-/text-camel-case-1.2.11.tgz", + "integrity": "sha512-2ZsM/gOlB1tyza+8lGLvs6gtPuZ9qEYuKPa+gwo38m65wkY4k323SK4hT7ku8r5wIKyspUYIWSk1aB9/Jjxr7A==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-pascal-case": "^1.2.11" + } + }, + "node_modules/text-capital-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-capital-case/-/text-capital-case-1.2.11.tgz", + "integrity": "sha512-30A7B7+VUvevEmPE0xWK1Z2z0ncl/JTjSUBLfjpoXrkwuPpmNTVbjHShRTN3cX9GIuZn/P3jvR+TO9JiTZcl8A==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11", + "text-upper-case-first": "^1.2.11" + } + }, + "node_modules/text-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-case/-/text-case-1.2.11.tgz", + "integrity": "sha512-LbdWNQeuXWbfav+pxBxvaefkziffMYeSA53BHp52cgJa9rjiC0dkjum9AKrH8iQWoQJ4InGPSGexLeerGFaZ1Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-camel-case": "^1.2.11", + "text-capital-case": "^1.2.11", + "text-constant-case": "^1.2.11", + "text-dot-case": "^1.2.11", + "text-header-case": "^1.2.11", + "text-is-lower-case": "^1.2.11", + "text-is-upper-case": "^1.2.11", + "text-kebab-case": "^1.2.11", + "text-lower-case": "^1.2.11", + "text-lower-case-first": "^1.2.11", + "text-no-case": "^1.2.11", + "text-param-case": "^1.2.11", + "text-pascal-case": "^1.2.11", + "text-path-case": "^1.2.11", + "text-sentence-case": "^1.2.11", + "text-snake-case": "^1.2.11", + "text-swap-case": "^1.2.11", + "text-title-case": "^1.2.11", + "text-upper-case": "^1.2.11", + "text-upper-case-first": "^1.2.11" + } + }, + "node_modules/text-constant-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-constant-case/-/text-constant-case-1.2.11.tgz", + "integrity": "sha512-XnTBILsa7UpMWncUCchqIybZlg15FUcrlyNaWIJ8ybPy54qcoN513EXFswueyizuAgyJFXPCwwSFbSji6kw/Uw==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11", + "text-upper-case": "^1.2.11" + } + }, + "node_modules/text-dot-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-dot-case/-/text-dot-case-1.2.11.tgz", + "integrity": "sha512-7SLKiT45KZO0qad0+p+GvC0+F+6pZ851HJcTcBJiSF88HsK/e1qErlGLtVBT6hkTHIaAj48WfSyQr4lZRv1xJQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11" + } + }, + "node_modules/text-header-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-header-case/-/text-header-case-1.2.11.tgz", + "integrity": "sha512-7OBHd2g7X+aH6rXMC3cANFh6yvhXjXkyumw2NaRwJRIk343pP2e1SQCTCfowPDmmi8wkZVqz1fdWNq5LwvcBOQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-capital-case": "^1.2.11" + } + }, + "node_modules/text-is-lower-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-is-lower-case/-/text-is-lower-case-1.2.11.tgz", + "integrity": "sha512-dBqPAkNmX7eTM7ZbS3D/UBCQ5i9EXt5tujF2wIGGbZ1+aN8bY7Qda4mDpxgd6Hbzf/z10uQWRNzupl99wFQ8CQ==", + "license": "MIT", + "peer": true + }, + "node_modules/text-is-upper-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-is-upper-case/-/text-is-upper-case-1.2.11.tgz", + "integrity": "sha512-MZeUIYEYfKZ2FSeg0vnHCH4mHXLgGzes+iz2K+4BYnhnkEa2svKA1nNjQAqTUiVNHOPqPCuzmUr1LsyQZ73uyA==", + "license": "MIT", + "peer": true + }, + "node_modules/text-kebab-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-kebab-case/-/text-kebab-case-1.2.11.tgz", + "integrity": "sha512-RIg9iN6VwH+JrX9dFdm1nd1efPGR9LjNc0CiQz496sQETeKGkDEzxES/ZzxbkerrAL2DFEMGdLXckzDz1OEDBQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11" + } + }, + "node_modules/text-lower-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-lower-case/-/text-lower-case-1.2.11.tgz", + "integrity": "sha512-txTy6y0y8M23Lhf0mk8WcvXTqlf4OQ3AGnDsRB6o3uMNfIa0CJDol2s1PdKNa63rt5B2277zkZCCn6Xeq//big==", + "license": "MIT", + "peer": true + }, + "node_modules/text-lower-case-first": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-lower-case-first/-/text-lower-case-first-1.2.11.tgz", + "integrity": "sha512-QR483XLyuyIpq8tKu1ds3Q1jfsgfaa/p9rtoQKHe6Rv5ah9ic/SUzTGN0MQ7UIS9APADd8SUPn5TTh1Z2/ACyg==", + "license": "MIT", + "peer": true + }, + "node_modules/text-no-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-no-case/-/text-no-case-1.2.11.tgz", + "integrity": "sha512-wazS7FEq0Ct3aJzeE8MEMcSs0eW4+/X/fwdotv/rG66bLS+g1T0pa0gUsbBGjjLFs191AIXVIry+bYE0uaaBBQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-lower-case": "^1.2.11" + } + }, + "node_modules/text-param-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-param-case/-/text-param-case-1.2.11.tgz", + "integrity": "sha512-3EMMAMLSz/mJXOnATNnrS+dZAvghpq09VhOVYDOkUnbm5zlYc6iU5AZOKVDpiAVVllQ9P1h5IKVZzsEYrdIRGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-dot-case": "^1.2.11" + } + }, + "node_modules/text-pascal-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-pascal-case/-/text-pascal-case-1.2.11.tgz", + "integrity": "sha512-BNhQ1O/g/Q4dH5gPyLIJLDLDknl2dipBwV629ScsiZCKJaCLGXYhTXp23rp9Htg3O5OSSsiU3mqDKq+pBmwTSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11" + } + }, + "node_modules/text-path-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-path-case/-/text-path-case-1.2.11.tgz", + "integrity": "sha512-FsJU4BmMdtLtmnBK/XRJPqTwLF8yFiTEClHjxlQjSAG5Xt9R4p6D1WNaM1CI2dG5Lr4rsFM4jiVC620m0AsRbw==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-dot-case": "^1.2.11" + } + }, + "node_modules/text-sentence-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-sentence-case/-/text-sentence-case-1.2.11.tgz", + "integrity": "sha512-ApiVsvdLy+Wb8x7mZRVuoy8VO12jJ22G2djVM3ZZbUhXVIkqGgHxmiXRwdhRPoWGojK9n53m7jviJgBVNdRn+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11", + "text-upper-case-first": "^1.2.11" + } + }, + "node_modules/text-snake-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-snake-case/-/text-snake-case-1.2.11.tgz", + "integrity": "sha512-NOEQvjyyVABB41SS8dUx423Y6hWS+Z4TrAAJg1xzCkOD3q9y0JtdJjCvCA1FWI8oDu+HiIOp/N446uDM8j54XQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-dot-case": "^1.2.11" + } + }, + "node_modules/text-swap-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-swap-case/-/text-swap-case-1.2.11.tgz", + "integrity": "sha512-PBmC5xvZdDZ4suikydpeXH0s4JV2XHelMj9/OEXEbA3oLpdV2A+B4BspVDWVw7C2Gi5eCareqk/7EE8I1/WwgQ==", + "license": "MIT", + "peer": true + }, + "node_modules/text-title-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-title-case/-/text-title-case-1.2.11.tgz", + "integrity": "sha512-V1GZy0XlqdkYUQm0tqm1jqtYlXJqFVMreBCTUReOaz8d/JbozTSpZrcakIeV8+1bN7LsvfhPFhA5zREiax6YIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "text-no-case": "^1.2.11", + "text-upper-case-first": "^1.2.11" + } + }, + "node_modules/text-upper-case": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-upper-case/-/text-upper-case-1.2.11.tgz", + "integrity": "sha512-BfTL7yB1YIRlVGNdZUvno013hOq2cRs07fDR2ApppOXRDuKrEmsLDEY82xXlDzQHELp0jexqkI+NeyPIl6MtMw==", + "license": "MIT", + "peer": true + }, + "node_modules/text-upper-case-first": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/text-upper-case-first/-/text-upper-case-first-1.2.11.tgz", + "integrity": "sha512-vgfbwKo8TEJbRsapR9LWWvIJRnv8u9aXVa6cyYOAQQmurCx54Cnt59x5fKdiq+hFaBJ51AbzCgMpbP3p65/pHQ==", + "license": "MIT", + "peer": true + }, + "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": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ts-morph": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-28.0.0.tgz", + "integrity": "sha512-Wp3tnZ2bzwxyTZMtgWVzXDfm7lB1Drz+y9DmmYH/L702PQhPyVrp3pkou3yIz4qjS14GY9kcpmLiOOMvl8oG1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ts-morph/common": "~0.29.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/ts-to-zod": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ts-to-zod/-/ts-to-zod-5.1.0.tgz", + "integrity": "sha512-giqqlvRHunlJqG9tBL/KAO3wWIVZGF//mZiWLKm/fdQnKnz4EN2mtiK5cugN9slytBkdMEXQIaLvMzIScbhhFw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@clack/prompts": "1.0.0-alpha.4", + "@oclif/core": "^4.5.4", + "@typescript/vfs": "^1.5.0", + "chokidar": "^4.0.3", + "listr2": "^9.0.4", + "slash": "^5.1.0", + "text-case": "^1.2.4", + "tslib": "^2.3.1", + "tsutils": "^3.21.0", + "typescript": "^5.2.2", + "zod": "^4.1.5" + }, + "bin": { + "ts-to-zod": "bin/run" + } + }, + "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", + "peer": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD", + "peer": true + }, + "node_modules/tsx": { + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", + "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-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "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/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "license": "MIT", + "peer": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT", + "peer": true + }, + "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", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/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", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wsl-utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.4.0.tgz", + "integrity": "sha512-9YmF+2sFEd+T7TkwlmE337F0IVzfDvDknhtpBQxxXzEOfgPphGlFYpyx0cTuCIFj8/p+sqwBYAeGxOMNSzPPDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "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", + "peer": true, + "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", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.2.tgz", + "integrity": "sha512-lh5RCAGFa1Cm2hjtNwLQhSs/AsqdWnTQaBER9fEwN/88pSh7KOtJavtBx/0VlkN/uFd61SwYmljLMDAsHlvzBQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-ts": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-2.1.0.tgz", + "integrity": "sha512-jZP1GokTqR99FLmtGU+B9acjD9u/R++b++Vxe1sWpBQDd82/9/j5LyLZZ7/Oy3h2nxcz5NihikgX4D0hhdu3+g==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "typescript": "^5 || ^6", + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/actions/slack-action/package.json b/actions/slack-action/package.json new file mode 100644 index 0000000..f397e1a --- /dev/null +++ b/actions/slack-action/package.json @@ -0,0 +1,25 @@ +{ + "name": "@code0-tech/slack-action", + "version": "1.0.0", + "private": true, + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "typecheck": "tsc --noEmit", + "build": "vite build", + "test": "vitest run", + "start": "node dist/index.js" + }, + "dependencies": { + "@code0-tech/hercules": "^1.6.0", + "@slack/web-api": "^7.9.1" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.0.0", + "typescript": "^5.9.3", + "vite": "^8.0.3", + "vitest": "^4.1.10" + } +} diff --git a/actions/slack-action/src/data_types/slackAppMentionEventPayload.ts b/actions/slack-action/src/data_types/slackAppMentionEventPayload.ts new file mode 100644 index 0000000..8c90c53 --- /dev/null +++ b/actions/slack-action/src/data_types/slackAppMentionEventPayload.ts @@ -0,0 +1,28 @@ +import { Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; +import { SlackBlockSchema } from "./slackBlock.js"; +import { slackEventEnvelope } from "./slackEventEnvelope.js"; + +/** + * Payload of an `app_mention` event: the bot user was @-mentioned in a message. + * See https://docs.slack.dev/reference/events/app_mention + */ +export const SlackAppMentionEventPayloadSchema = slackEventEnvelope( + z.object({ + type: z.string().describe("The event type. Always `app_mention` for this event."), + channel: z.string().describe("The ID of the channel the bot was mentioned in."), + user: z.string().nullish().describe("The ID of the user who mentioned the bot."), + bot_id: z.string().nullish().describe("The ID of the bot that posted the message, if any."), + text: z.string().nullish().describe("The full message text, including the `<@BOTID>` mention."), + ts: z.string().describe("The message timestamp. Pass it as the thread timestamp to reply in a thread."), + thread_ts: z.string().nullish().describe("The timestamp of the parent message when the mention is a threaded reply."), + event_ts: z.string().nullish().describe("The timestamp at which the event was dispatched."), + team: z.string().nullish().describe("The ID of the workspace the message belongs to."), + blocks: z.array(SlackBlockSchema).nullish().describe("The Block Kit blocks that make up the message body."), + }) +); + +@Identifier("SlackAppMentionEventPayload") +@Name({ code: "en-US", content: "SlackAppMentionEventPayload" }) +@Schema(SlackAppMentionEventPayloadSchema) +export class SlackAppMentionEventPayload {} diff --git a/actions/slack-action/src/data_types/slackBlock.ts b/actions/slack-action/src/data_types/slackBlock.ts new file mode 100644 index 0000000..e7365cf --- /dev/null +++ b/actions/slack-action/src/data_types/slackBlock.ts @@ -0,0 +1,39 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +/** + * A Block Kit composition object holding text, either as Slack markdown + * (`mrkdwn`) or as literal text (`plain_text`). + * See https://docs.slack.dev/reference/block-kit/composition-objects/text-object + */ +export const SlackTextObjectSchema = z.object({ + type: z.enum(["mrkdwn", "plain_text"]).describe("How the text is rendered: mrkdwn for Slack markdown, plain_text for literal text."), + text: z.string().max(3000).describe("The text content. Max 3000 characters."), + emoji: z.boolean().nullish().describe("Whether to escape emoji into their colon form. Only valid for plain_text."), + verbatim: z.boolean().nullish().describe("Whether to skip auto-linking of URLs and channel names. Only valid for mrkdwn."), +}); +export type SlackTextObject = z.infer; + +/** + * A single Block Kit block. Slack defines a large, growing set of block types, + * so only the fields shared by the common ones are typed; anything else can be + * passed through `accessory` and `elements`. + * See https://docs.slack.dev/reference/block-kit/blocks + */ +export const SlackBlockSchema = z.object({ + type: z.string().describe("The block type, e.g. section, divider, header, image, actions or context."), + block_id: z.string().max(255).nullish().describe("A unique identifier for the block, echoed back in interaction payloads."), + text: SlackTextObjectSchema.nullish().describe("The block's primary text object. Used by section and header blocks."), + fields: z.array(SlackTextObjectSchema).max(10).nullish().describe("Up to 10 text objects rendered as a two column layout. Used by section blocks."), + accessory: z.unknown().nullish().describe("An interactive element rendered next to the block's text, as a raw Block Kit element object."), + elements: z.array(z.unknown()).nullish().describe("The block's interactive or context elements, as raw Block Kit element objects."), + image_url: z.string().nullish().describe("The URL of the image to display. Used by image blocks."), + alt_text: z.string().nullish().describe("A plain text summary of the image, used by assistive technology."), +}); +export type SlackBlock = z.infer; + +@Identifier("SLACK_BLOCK") +@Name({ code: "en-US", content: "Slack block" }) +@DisplayMessage({ code: "en-US", content: "Slack block" }) +@Schema(SlackBlockSchema) +export class SlackBlockDataType {} diff --git a/actions/slack-action/src/data_types/slackChannel.ts b/actions/slack-action/src/data_types/slackChannel.ts new file mode 100644 index 0000000..1ee488a --- /dev/null +++ b/actions/slack-action/src/data_types/slackChannel.ts @@ -0,0 +1,44 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +/** + * A Slack conversation: a public or private channel, a DM (`is_im`) or a group + * DM (`is_mpim`). + * See https://docs.slack.dev/reference/objects/conversation-object + */ +export const SlackChannelSchema = z.object({ + id: z.string().describe("The channel ID, e.g. C0123456789."), + name: z.string().nullish().describe("The channel name without the leading #."), + is_channel: z.boolean().nullish().describe("Whether the conversation is a channel."), + is_group: z.boolean().nullish().describe("Whether the conversation is a legacy private group."), + is_im: z.boolean().nullish().describe("Whether the conversation is a direct message."), + is_mpim: z.boolean().nullish().describe("Whether the conversation is a group direct message."), + is_private: z.boolean().nullish().describe("Whether the channel is private."), + is_archived: z.boolean().nullish().describe("Whether the channel has been archived."), + created: z.number().nullish().describe("The Unix timestamp at which the channel was created."), + creator: z.string().nullish().describe("The ID of the user who created the channel."), + num_members: z.number().nullish().describe("The number of members in the channel."), + topic: z + .object({ + value: z.string().nullish().describe("The channel topic."), + creator: z.string().nullish().describe("The ID of the user who set the topic."), + last_set: z.number().nullish().describe("The Unix timestamp at which the topic was last set."), + }) + .nullish() + .describe("The channel topic."), + purpose: z + .object({ + value: z.string().nullish().describe("The channel purpose."), + creator: z.string().nullish().describe("The ID of the user who set the purpose."), + last_set: z.number().nullish().describe("The Unix timestamp at which the purpose was last set."), + }) + .nullish() + .describe("The channel purpose."), +}); +export type SlackChannel = z.infer; + +@Identifier("SLACK_CHANNEL") +@Name({ code: "en-US", content: "Slack channel" }) +@DisplayMessage({ code: "en-US", content: "Slack channel" }) +@Schema(SlackChannelSchema) +export class SlackChannelDataType {} diff --git a/actions/slack-action/src/data_types/slackChannelCreatedEventPayload.ts b/actions/slack-action/src/data_types/slackChannelCreatedEventPayload.ts new file mode 100644 index 0000000..02adf9f --- /dev/null +++ b/actions/slack-action/src/data_types/slackChannelCreatedEventPayload.ts @@ -0,0 +1,30 @@ +import { Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; +import { slackEventEnvelope } from "./slackEventEnvelope.js"; + +/** + * Payload of a `channel_created` event: a new public channel was created in the + * workspace. Only a slim channel stub is delivered — call the Slack API if the + * flow needs the full conversation object. + * See https://docs.slack.dev/reference/events/channel_created + */ +export const SlackChannelCreatedEventPayloadSchema = slackEventEnvelope( + z.object({ + type: z.string().describe("The event type. Always `channel_created` for this event."), + channel: z + .object({ + id: z.string().describe("The ID of the newly created channel."), + name: z.string().nullish().describe("The channel name without the leading #."), + created: z.number().nullish().describe("The Unix timestamp at which the channel was created."), + creator: z.string().nullish().describe("The ID of the user who created the channel."), + is_channel: z.boolean().nullish().describe("Whether the conversation is a channel."), + }) + .describe("The channel that was created."), + event_ts: z.string().nullish().describe("The timestamp at which the event was dispatched."), + }) +); + +@Identifier("SlackChannelCreatedEventPayload") +@Name({ code: "en-US", content: "SlackChannelCreatedEventPayload" }) +@Schema(SlackChannelCreatedEventPayloadSchema) +export class SlackChannelCreatedEventPayload {} diff --git a/actions/slack-action/src/data_types/slackEventEnvelope.ts b/actions/slack-action/src/data_types/slackEventEnvelope.ts new file mode 100644 index 0000000..330a8da --- /dev/null +++ b/actions/slack-action/src/data_types/slackEventEnvelope.ts @@ -0,0 +1,20 @@ +import { z } from "zod"; + +/** + * Every Events API delivery arrives wrapped in the same `event_callback` + * envelope; only the inner `event` object differs per event type. This builder + * keeps the envelope in one place so each payload file only has to describe its + * own event. + * See https://docs.slack.dev/apis/events-api#event-type-structure + */ +export const slackEventEnvelope = (event: T) => + z.object({ + token: z.string().nullish().describe("The deprecated verification token. Validate the request signature instead."), + team_id: z.string().nullish().describe("The ID of the workspace the event happened in."), + api_app_id: z.string().nullish().describe("The ID of the Slack app the event was delivered to."), + type: z.string().describe("The envelope type. Always `event_callback` for Events API deliveries."), + event_id: z.string().nullish().describe("A unique identifier for this delivery, usable for deduplication."), + event_time: z.number().nullish().describe("The Unix timestamp at which the event happened."), + event_context: z.string().nullish().describe("An opaque string identifying the event's subscription context."), + event, + }); diff --git a/actions/slack-action/src/data_types/slackFile.ts b/actions/slack-action/src/data_types/slackFile.ts new file mode 100644 index 0000000..c99cef0 --- /dev/null +++ b/actions/slack-action/src/data_types/slackFile.ts @@ -0,0 +1,34 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +/** + * A file hosted by Slack, as returned by the external upload flow behind + * `slackUploadFile`. + * See https://docs.slack.dev/reference/objects/file-object + */ +export const SlackFileSchema = z.object({ + id: z.string().describe("The file ID, e.g. F0123456789."), + name: z.string().nullish().describe("The file name including its extension."), + title: z.string().nullish().describe("The title shown for the file in Slack."), + mimetype: z.string().nullish().describe("The MIME type Slack detected for the file."), + filetype: z.string().nullish().describe("Slack's short file type, e.g. png or javascript."), + pretty_type: z.string().nullish().describe("A human readable form of the file type, e.g. PNG."), + user: z.string().nullish().describe("The ID of the user who uploaded the file."), + size: z.number().nullish().describe("The file size in bytes."), + created: z.number().nullish().describe("The Unix timestamp at which the file was created."), + timestamp: z.number().nullish().describe("The Unix timestamp at which the file was uploaded."), + mode: z.string().nullish().describe("How the file is stored, e.g. hosted, external or snippet."), + is_external: z.boolean().nullish().describe("Whether the file is hosted outside of Slack."), + is_public: z.boolean().nullish().describe("Whether the file is publicly shared within the workspace."), + permalink: z.string().nullish().describe("A permanent link to the file in Slack."), + permalink_public: z.string().nullish().describe("A public link to the file, if public sharing is enabled."), + url_private: z.string().nullish().describe("An authenticated URL to download the file content."), + channels: z.array(z.string()).nullish().describe("The IDs of the public channels the file was shared to."), +}); +export type SlackFile = z.infer; + +@Identifier("SLACK_FILE") +@Name({ code: "en-US", content: "Slack file" }) +@DisplayMessage({ code: "en-US", content: "Slack file" }) +@Schema(SlackFileSchema) +export class SlackFileDataType {} diff --git a/actions/slack-action/src/data_types/slackMessage.ts b/actions/slack-action/src/data_types/slackMessage.ts new file mode 100644 index 0000000..82d7d92 --- /dev/null +++ b/actions/slack-action/src/data_types/slackMessage.ts @@ -0,0 +1,32 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; +import { SlackBlockSchema } from "./slackBlock.js"; + +/** + * A message as returned by the Slack Web API (chat.postMessage, chat.update, …). + * `ts` is the message timestamp and doubles as its identifier within a channel; + * pass it back as `threadTs` to reply in a thread. + * See https://docs.slack.dev/reference/methods/chat.postmessage + */ +export const SlackMessageSchema = z.object({ + ok: z.boolean().nullish().describe("Whether the Slack API call succeeded."), + channel: z.string().nullish().describe("The ID of the channel the message was posted to."), + ts: z.string().nullish().describe("The message timestamp. Identifies the message within its channel."), + thread_ts: z.string().nullish().describe("The timestamp of the parent message when this message is a threaded reply."), + text: z.string().nullish().describe("The fallback text of the message."), + user: z.string().nullish().describe("The ID of the user the message was posted as."), + bot_id: z.string().nullish().describe("The ID of the bot the message was posted as."), + app_id: z.string().nullish().describe("The ID of the Slack app the message was posted by."), + team: z.string().nullish().describe("The ID of the workspace the message belongs to."), + type: z.string().nullish().describe("The message type, usually `message`."), + subtype: z.string().nullish().describe("The message subtype, e.g. `bot_message` or `channel_join`."), + permalink: z.string().nullish().describe("A permanent link to the message."), + blocks: z.array(SlackBlockSchema).nullish().describe("The Block Kit blocks that make up the message body."), +}); +export type SlackMessage = z.infer; + +@Identifier("SLACK_MESSAGE") +@Name({ code: "en-US", content: "Slack message" }) +@DisplayMessage({ code: "en-US", content: "Slack message" }) +@Schema(SlackMessageSchema) +export class SlackMessageDataType {} diff --git a/actions/slack-action/src/data_types/slackMessagePostedEventPayload.ts b/actions/slack-action/src/data_types/slackMessagePostedEventPayload.ts new file mode 100644 index 0000000..27a8f78 --- /dev/null +++ b/actions/slack-action/src/data_types/slackMessagePostedEventPayload.ts @@ -0,0 +1,31 @@ +import { Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; +import { SlackBlockSchema } from "./slackBlock.js"; +import { slackEventEnvelope } from "./slackEventEnvelope.js"; + +/** + * Payload of a `message.channels` event: a message was posted in a public + * channel the app is a member of. + * See https://docs.slack.dev/reference/events/message + */ +export const SlackMessagePostedEventPayloadSchema = slackEventEnvelope( + z.object({ + type: z.string().describe("The event type. Always `message` for this event."), + subtype: z.string().nullish().describe("The message subtype, e.g. `bot_message` or `message_changed`. Absent for plain user messages."), + channel: z.string().describe("The ID of the channel the message was posted in."), + channel_type: z.string().nullish().describe("The kind of channel, e.g. `channel`, `group`, `im` or `mpim`."), + user: z.string().nullish().describe("The ID of the user who posted the message."), + bot_id: z.string().nullish().describe("The ID of the bot that posted the message, if any."), + text: z.string().nullish().describe("The message text."), + ts: z.string().describe("The message timestamp. Pass it as the thread timestamp to reply in a thread."), + thread_ts: z.string().nullish().describe("The timestamp of the parent message when this message is a threaded reply."), + event_ts: z.string().nullish().describe("The timestamp at which the event was dispatched."), + team: z.string().nullish().describe("The ID of the workspace the message belongs to."), + blocks: z.array(SlackBlockSchema).nullish().describe("The Block Kit blocks that make up the message body."), + }) +); + +@Identifier("SlackMessagePostedEventPayload") +@Name({ code: "en-US", content: "SlackMessagePostedEventPayload" }) +@Schema(SlackMessagePostedEventPayloadSchema) +export class SlackMessagePostedEventPayload {} diff --git a/actions/slack-action/src/data_types/slackReaction.ts b/actions/slack-action/src/data_types/slackReaction.ts new file mode 100644 index 0000000..1256ce7 --- /dev/null +++ b/actions/slack-action/src/data_types/slackReaction.ts @@ -0,0 +1,32 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +/** + * An emoji reaction on a message, identified by the message it points at + * (`item`) and the emoji name without the surrounding colons. + * See https://docs.slack.dev/reference/methods/reactions.add + */ +export const SlackReactionSchema = z.object({ + name: z.string().describe("The emoji name without the surrounding colons, e.g. thumbsup."), + user: z.string().nullish().describe("The ID of the user who added the reaction."), + item_user: z.string().nullish().describe("The ID of the user whose item was reacted to."), + count: z.number().nullish().describe("How many users reacted with this emoji."), + users: z.array(z.string()).nullish().describe("The IDs of the users who reacted with this emoji."), + item: z + .object({ + type: z.string().nullish().describe("The type of the item reacted to, usually message."), + channel: z.string().nullish().describe("The ID of the channel holding the item."), + ts: z.string().nullish().describe("The timestamp of the message reacted to."), + file: z.string().nullish().describe("The ID of the file reacted to."), + }) + .nullish() + .describe("The item the reaction was added to."), + event_ts: z.string().nullish().describe("The timestamp at which the reaction event occurred."), +}); +export type SlackReaction = z.infer; + +@Identifier("SLACK_REACTION") +@Name({ code: "en-US", content: "Slack reaction" }) +@DisplayMessage({ code: "en-US", content: "Slack reaction" }) +@Schema(SlackReactionSchema) +export class SlackReactionDataType {} diff --git a/actions/slack-action/src/data_types/slackReactionAddedEventPayload.ts b/actions/slack-action/src/data_types/slackReactionAddedEventPayload.ts new file mode 100644 index 0000000..4c73848 --- /dev/null +++ b/actions/slack-action/src/data_types/slackReactionAddedEventPayload.ts @@ -0,0 +1,32 @@ +import { Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; +import { slackEventEnvelope } from "./slackEventEnvelope.js"; + +/** + * Payload of a `reaction_added` event: a member added an emoji reaction to a + * message or file. This is the event behind approval and reaction-role flows — + * match on `event.reaction` to decide what a reaction means. + * See https://docs.slack.dev/reference/events/reaction_added + */ +export const SlackReactionAddedEventPayloadSchema = slackEventEnvelope( + z.object({ + type: z.string().describe("The event type. Always `reaction_added` for this event."), + user: z.string().describe("The ID of the user who added the reaction."), + reaction: z.string().describe("The emoji name without the surrounding colons, e.g. white_check_mark."), + item_user: z.string().nullish().describe("The ID of the user who created the item that was reacted to."), + item: z + .object({ + type: z.string().describe("The type of the item reacted to, e.g. `message` or `file`."), + channel: z.string().nullish().describe("The ID of the channel holding the item."), + ts: z.string().nullish().describe("The timestamp of the message reacted to."), + file: z.string().nullish().describe("The ID of the file reacted to."), + }) + .describe("The item the reaction was added to."), + event_ts: z.string().nullish().describe("The timestamp at which the event was dispatched."), + }) +); + +@Identifier("SlackReactionAddedEventPayload") +@Name({ code: "en-US", content: "SlackReactionAddedEventPayload" }) +@Schema(SlackReactionAddedEventPayloadSchema) +export class SlackReactionAddedEventPayload {} diff --git a/actions/slack-action/src/data_types/slackSlashCommandPayload.ts b/actions/slack-action/src/data_types/slackSlashCommandPayload.ts new file mode 100644 index 0000000..b7795df --- /dev/null +++ b/actions/slack-action/src/data_types/slackSlashCommandPayload.ts @@ -0,0 +1,31 @@ +import { Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +/** + * Payload of a slash command invocation. Unlike Events API deliveries this is + * not wrapped in an `event_callback` envelope: Slack posts the fields below as + * `application/x-www-form-urlencoded`. + * See https://docs.slack.dev/interactivity/implementing-slash-commands + */ +export const SlackSlashCommandPayloadSchema = z.object({ + token: z.string().nullish().describe("The deprecated verification token. Validate the request signature instead."), + command: z.string().describe("The invoked command including its leading slash, e.g. /deploy."), + text: z.string().nullish().describe("Everything the user typed after the command name."), + team_id: z.string().nullish().describe("The ID of the workspace the command was invoked in."), + team_domain: z.string().nullish().describe("The subdomain of the workspace the command was invoked in."), + enterprise_id: z.string().nullish().describe("The ID of the Enterprise Grid organization, if any."), + enterprise_name: z.string().nullish().describe("The name of the Enterprise Grid organization, if any."), + channel_id: z.string().nullish().describe("The ID of the channel the command was invoked in."), + channel_name: z.string().nullish().describe("The name of the channel the command was invoked in."), + user_id: z.string().nullish().describe("The ID of the user who invoked the command."), + user_name: z.string().nullish().describe("The handle of the user who invoked the command."), + api_app_id: z.string().nullish().describe("The ID of the Slack app the command belongs to."), + is_enterprise_install: z.string().nullish().describe("Whether the app is installed org-wide, as the string `true` or `false`."), + response_url: z.string().nullish().describe("A URL that accepts delayed responses for up to 30 minutes after the invocation."), + trigger_id: z.string().nullish().describe("A short-lived ID usable to open a modal in response to the command."), +}); + +@Identifier("SlackSlashCommandPayload") +@Name({ code: "en-US", content: "SlackSlashCommandPayload" }) +@Schema(SlackSlashCommandPayloadSchema) +export class SlackSlashCommandPayload {} diff --git a/actions/slack-action/src/data_types/slackUser.ts b/actions/slack-action/src/data_types/slackUser.ts new file mode 100644 index 0000000..809684e --- /dev/null +++ b/actions/slack-action/src/data_types/slackUser.ts @@ -0,0 +1,38 @@ +import { DisplayMessage, Identifier, Name, Schema } from "@code0-tech/hercules"; +import { z } from "zod"; + +/** + * A Slack workspace member. + * See https://docs.slack.dev/reference/objects/user-object + */ +export const SlackUserSchema = z.object({ + id: z.string().describe("The user ID, e.g. U0123456789."), + team_id: z.string().nullish().describe("The ID of the workspace the user belongs to."), + name: z.string().nullish().describe("The user's workspace handle without the leading @."), + real_name: z.string().nullish().describe("The user's full name."), + tz: z.string().nullish().describe("The user's timezone, e.g. Europe/Berlin."), + is_admin: z.boolean().nullish().describe("Whether the user is a workspace admin."), + is_owner: z.boolean().nullish().describe("Whether the user is the workspace owner."), + is_bot: z.boolean().nullish().describe("Whether the user is a bot."), + deleted: z.boolean().nullish().describe("Whether the user account has been deactivated."), + profile: z + .object({ + real_name: z.string().nullish().describe("The user's full name."), + display_name: z.string().nullish().describe("The name the user chose to display."), + email: z.string().nullish().describe("The user's email address. Requires the users:read.email scope."), + title: z.string().nullish().describe("The user's job title."), + phone: z.string().nullish().describe("The user's phone number."), + status_text: z.string().nullish().describe("The user's custom status text."), + status_emoji: z.string().nullish().describe("The emoji of the user's custom status."), + image_512: z.string().nullish().describe("The URL of the user's 512x512 profile picture."), + }) + .nullish() + .describe("The user's profile."), +}); +export type SlackUser = z.infer; + +@Identifier("SLACK_USER") +@Name({ code: "en-US", content: "Slack user" }) +@DisplayMessage({ code: "en-US", content: "Slack user" }) +@Schema(SlackUserSchema) +export class SlackUserDataType {} diff --git a/actions/slack-action/src/events/slackAppMentioned.ts b/actions/slack-action/src/events/slackAppMentioned.ts new file mode 100644 index 0000000..39563cc --- /dev/null +++ b/actions/slack-action/src/events/slackAppMentioned.ts @@ -0,0 +1,33 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + EventSetting, + Identifier, + Name, + Rest, + Signature, +} from "@code0-tech/hercules"; + +@Identifier("SlackAppMentioned") +@DisplayIcon("simple:slack") +@Name({code: "en-US", content: "Slack app mentioned"}) +@Description({code: "en-US", content: "Triggered when the bot user is @-mentioned in a message (app_mention)."}) +@DisplayMessage({code: "en-US", content: "Slack app mentioned on ${http_url}"}) +@Signature("(http_schema: HTTP_SCHEMA, http_url: HTTP_URL, http_method: HTTP_METHOD, http_auth: A, http_auth_value: REST_AUTH_VALUE, input_schema?: SlackAppMentionEventPayload): REST_ADAPTER_INPUT") +@EventSetting({ + identifier: "input_schema", + hidden: true +}) +@EventSetting({ + identifier: "http_method", + hidden: true, + defaultValue: "POST" +}) +@EventSetting({ + identifier: "http_schema", + hidden: true, + defaultValue: "application/json" +}) +export class SlackAppMentioned extends Rest { +} diff --git a/actions/slack-action/src/events/slackChannelCreated.ts b/actions/slack-action/src/events/slackChannelCreated.ts new file mode 100644 index 0000000..d2e9554 --- /dev/null +++ b/actions/slack-action/src/events/slackChannelCreated.ts @@ -0,0 +1,33 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + EventSetting, + Identifier, + Name, + Rest, + Signature, +} from "@code0-tech/hercules"; + +@Identifier("SlackChannelCreated") +@DisplayIcon("simple:slack") +@Name({code: "en-US", content: "Slack channel created"}) +@Description({code: "en-US", content: "Triggered when a new public channel is created in the workspace (channel_created)."}) +@DisplayMessage({code: "en-US", content: "Slack channel created on ${http_url}"}) +@Signature("(http_schema: HTTP_SCHEMA, http_url: HTTP_URL, http_method: HTTP_METHOD, http_auth: A, http_auth_value: REST_AUTH_VALUE, input_schema?: SlackChannelCreatedEventPayload): REST_ADAPTER_INPUT") +@EventSetting({ + identifier: "input_schema", + hidden: true +}) +@EventSetting({ + identifier: "http_method", + hidden: true, + defaultValue: "POST" +}) +@EventSetting({ + identifier: "http_schema", + hidden: true, + defaultValue: "application/json" +}) +export class SlackChannelCreated extends Rest { +} diff --git a/actions/slack-action/src/events/slackMessagePosted.ts b/actions/slack-action/src/events/slackMessagePosted.ts new file mode 100644 index 0000000..8ba3777 --- /dev/null +++ b/actions/slack-action/src/events/slackMessagePosted.ts @@ -0,0 +1,33 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + EventSetting, + Identifier, + Name, + Rest, + Signature, +} from "@code0-tech/hercules"; + +@Identifier("SlackMessagePosted") +@DisplayIcon("simple:slack") +@Name({code: "en-US", content: "Slack message posted"}) +@Description({code: "en-US", content: "Triggered when a message is posted in a public channel the Slack app is a member of (message.channels). Compare event.channel against the channel you want to watch."}) +@DisplayMessage({code: "en-US", content: "Slack message posted on ${http_url}"}) +@Signature("(http_schema: HTTP_SCHEMA, http_url: HTTP_URL, http_method: HTTP_METHOD, http_auth: A, http_auth_value: REST_AUTH_VALUE, input_schema?: SlackMessagePostedEventPayload): REST_ADAPTER_INPUT") +@EventSetting({ + identifier: "input_schema", + hidden: true +}) +@EventSetting({ + identifier: "http_method", + hidden: true, + defaultValue: "POST" +}) +@EventSetting({ + identifier: "http_schema", + hidden: true, + defaultValue: "application/json" +}) +export class SlackMessagePosted extends Rest { +} diff --git a/actions/slack-action/src/events/slackReactionAdded.ts b/actions/slack-action/src/events/slackReactionAdded.ts new file mode 100644 index 0000000..4193dec --- /dev/null +++ b/actions/slack-action/src/events/slackReactionAdded.ts @@ -0,0 +1,33 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + EventSetting, + Identifier, + Name, + Rest, + Signature, +} from "@code0-tech/hercules"; + +@Identifier("SlackReactionAdded") +@DisplayIcon("simple:slack") +@Name({code: "en-US", content: "Slack reaction added"}) +@Description({code: "en-US", content: "Triggered when a member adds an emoji reaction to a message or file (reaction_added). Use it to drive approval and reaction-role flows."}) +@DisplayMessage({code: "en-US", content: "Slack reaction added on ${http_url}"}) +@Signature("(http_schema: HTTP_SCHEMA, http_url: HTTP_URL, http_method: HTTP_METHOD, http_auth: A, http_auth_value: REST_AUTH_VALUE, input_schema?: SlackReactionAddedEventPayload): REST_ADAPTER_INPUT") +@EventSetting({ + identifier: "input_schema", + hidden: true +}) +@EventSetting({ + identifier: "http_method", + hidden: true, + defaultValue: "POST" +}) +@EventSetting({ + identifier: "http_schema", + hidden: true, + defaultValue: "application/json" +}) +export class SlackReactionAdded extends Rest { +} diff --git a/actions/slack-action/src/events/slackSlashCommand.ts b/actions/slack-action/src/events/slackSlashCommand.ts new file mode 100644 index 0000000..d9c22fb --- /dev/null +++ b/actions/slack-action/src/events/slackSlashCommand.ts @@ -0,0 +1,33 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + EventSetting, + Identifier, + Name, + Rest, + Signature, +} from "@code0-tech/hercules"; + +@Identifier("SlackSlashCommand") +@DisplayIcon("simple:slack") +@Name({code: "en-US", content: "Slack slash command"}) +@Description({code: "en-US", content: "Triggered when a registered slash command is invoked. Compare event.command against the command you registered for this endpoint."}) +@DisplayMessage({code: "en-US", content: "Slack slash command on ${http_url}"}) +@Signature("(http_schema: HTTP_SCHEMA, http_url: HTTP_URL, http_method: HTTP_METHOD, http_auth: A, http_auth_value: REST_AUTH_VALUE, input_schema?: SlackSlashCommandPayload): REST_ADAPTER_INPUT") +@EventSetting({ + identifier: "input_schema", + hidden: true +}) +@EventSetting({ + identifier: "http_method", + hidden: true, + defaultValue: "POST" +}) +@EventSetting({ + identifier: "http_schema", + hidden: true, + defaultValue: "application/x-www-form-urlencoded" +}) +export class SlackSlashCommand extends Rest { +} diff --git a/actions/slack-action/src/functions/addReactionFunction.ts b/actions/slack-action/src/functions/addReactionFunction.ts new file mode 100644 index 0000000..c514cb3 --- /dev/null +++ b/actions/slack-action/src/functions/addReactionFunction.ts @@ -0,0 +1,50 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toRuntimeError } from "../helpers.js"; + +@Identifier("slackAddReaction") +@DisplayIcon("simple:slack") +@Signature("(channelId: TEXT, ts: TEXT, emoji: TEXT): BOOLEAN") +@Name({ code: "en-US", content: "Add reaction" }) +@DisplayMessage({ code: "en-US", content: "Add :${emoji}: to Slack message ${ts}" }) +@Documentation({ + code: "en-US", + content: + "Adds an emoji reaction to a message via `reactions.add` and returns whether Slack accepted it.\nGive the emoji name without the surrounding colons, e.g. `white_check_mark`. Use it to acknowledge a message a flow has handled.", +}) +@Description({ code: "en-US", content: "Adds an emoji reaction to a Slack message." }) +@Parameter({ + runtimeName: "channelId", + name: [{ code: "en-US", content: "Channel ID" }], + description: [{ code: "en-US", content: "The ID of the channel holding the message." }], +}) +@Parameter({ + runtimeName: "ts", + name: [{ code: "en-US", content: "Message timestamp" }], + description: [{ code: "en-US", content: "The `ts` of the message to react to." }], +}) +@Parameter({ + runtimeName: "emoji", + name: [{ code: "en-US", content: "Emoji" }], + description: [{ code: "en-US", content: "The emoji name without the surrounding colons, e.g. white_check_mark." }], +}) +export class AddReactionFunction { + async run(context: FunctionContext, channelId: string, ts: string, emoji: string): Promise { + const client = getSlackClient(context); + try { + const result = await client.reactions.add({ channel: channelId, timestamp: ts, name: emoji }); + return result.ok === true; + } catch (error) { + throw toRuntimeError("SLACK_ADD_REACTION_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/createChannelFunction.ts b/actions/slack-action/src/functions/createChannelFunction.ts new file mode 100644 index 0000000..b481ee9 --- /dev/null +++ b/actions/slack-action/src/functions/createChannelFunction.ts @@ -0,0 +1,56 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + RuntimeError, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toRuntimeError } from "../helpers.js"; +import { SlackChannel, SlackChannelSchema } from "../data_types/slackChannel.js"; + +@Identifier("slackCreateChannel") +@DisplayIcon("simple:slack") +@Signature("(name: TEXT, isPrivate?: BOOLEAN): SLACK_CHANNEL") +@Name({ code: "en-US", content: "Create channel" }) +@DisplayMessage({ code: "en-US", content: "Create Slack channel #${name}" }) +@Documentation({ + code: "en-US", + content: + "Creates a channel via `conversations.create`.\nSlack channel names are lowercase, max 80 characters, and may only contain letters, digits, hyphens and underscores. The creating app becomes a member, so it can post right away.", +}) +@Description({ code: "en-US", content: "Creates a Slack channel." }) +@Parameter({ + runtimeName: "name", + name: [{ code: "en-US", content: "Name" }], + description: [ + { + code: "en-US", + content: "The channel name without the leading #. Lowercase, max 80 characters, letters, digits, hyphens and underscores only.", + }, + ], +}) +@Parameter({ + runtimeName: "isPrivate", + name: [{ code: "en-US", content: "Private" }], + description: [{ code: "en-US", content: "Whether to create a private channel. Defaults to a public channel." }], + optional: true, +}) +export class CreateChannelFunction { + async run(context: FunctionContext, name: string, isPrivate?: boolean): Promise { + const client = getSlackClient(context); + try { + const result = await client.conversations.create({ name, is_private: isPrivate ?? false }); + if (!result.channel) { + throw new RuntimeError("SLACK_CREATE_CHANNEL_FAILED", `Slack did not return a channel for ${name}.`); + } + return SlackChannelSchema.parse(result.channel); + } catch (error) { + throw toRuntimeError("SLACK_CREATE_CHANNEL_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/deleteMessageFunction.ts b/actions/slack-action/src/functions/deleteMessageFunction.ts new file mode 100644 index 0000000..9dad133 --- /dev/null +++ b/actions/slack-action/src/functions/deleteMessageFunction.ts @@ -0,0 +1,45 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toRuntimeError } from "../helpers.js"; + +@Identifier("slackDeleteMessage") +@DisplayIcon("simple:slack") +@Signature("(channelId: TEXT, ts: TEXT): BOOLEAN") +@Name({ code: "en-US", content: "Delete message" }) +@DisplayMessage({ code: "en-US", content: "Delete Slack message ${ts}" }) +@Documentation({ + code: "en-US", + content: + "Deletes a message via `chat.delete` and returns whether Slack accepted the deletion.\nA bot token can only delete messages the app itself posted.", +}) +@Description({ code: "en-US", content: "Deletes a Slack message the app posted." }) +@Parameter({ + runtimeName: "channelId", + name: [{ code: "en-US", content: "Channel ID" }], + description: [{ code: "en-US", content: "The ID of the channel holding the message." }], +}) +@Parameter({ + runtimeName: "ts", + name: [{ code: "en-US", content: "Message timestamp" }], + description: [{ code: "en-US", content: "The `ts` of the message to delete." }], +}) +export class DeleteMessageFunction { + async run(context: FunctionContext, channelId: string, ts: string): Promise { + const client = getSlackClient(context); + try { + const result = await client.chat.delete({ channel: channelId, ts }); + return result.ok === true; + } catch (error) { + throw toRuntimeError("SLACK_DELETE_MESSAGE_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/getUserByEmailFunction.ts b/actions/slack-action/src/functions/getUserByEmailFunction.ts new file mode 100644 index 0000000..8dce572 --- /dev/null +++ b/actions/slack-action/src/functions/getUserByEmailFunction.ts @@ -0,0 +1,45 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + RuntimeError, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toRuntimeError } from "../helpers.js"; +import { SlackUser, SlackUserSchema } from "../data_types/slackUser.js"; + +@Identifier("slackGetUserByEmail") +@DisplayIcon("simple:slack") +@Signature("(email: TEXT): SLACK_USER") +@Name({ code: "en-US", content: "Get user by email" }) +@DisplayMessage({ code: "en-US", content: "Look up Slack user ${email}" }) +@Documentation({ + code: "en-US", + content: + "Looks a workspace member up by email address via `users.lookupByEmail`.\nThis is the bridge from records in other systems (a Shopify customer, a support ticket assignee) to a Slack user ID usable with `slackSendDirectMessage` or `slackInviteToChannel`. Requires the `users:read.email` scope.", +}) +@Description({ code: "en-US", content: "Looks up a Slack user by their email address." }) +@Parameter({ + runtimeName: "email", + name: [{ code: "en-US", content: "Email" }], + description: [{ code: "en-US", content: "The email address of the workspace member to look up." }], +}) +export class GetUserByEmailFunction { + async run(context: FunctionContext, email: string): Promise { + const client = getSlackClient(context); + try { + const result = await client.users.lookupByEmail({ email }); + if (!result.user) { + throw new RuntimeError("SLACK_GET_USER_BY_EMAIL_FAILED", `Slack did not return a user for ${email}.`); + } + return SlackUserSchema.parse(result.user); + } catch (error) { + throw toRuntimeError("SLACK_GET_USER_BY_EMAIL_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/inviteToChannelFunction.ts b/actions/slack-action/src/functions/inviteToChannelFunction.ts new file mode 100644 index 0000000..0f9c259 --- /dev/null +++ b/actions/slack-action/src/functions/inviteToChannelFunction.ts @@ -0,0 +1,58 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + RuntimeError, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toRuntimeError } from "../helpers.js"; +import { SlackChannel, SlackChannelSchema } from "../data_types/slackChannel.js"; + +@Identifier("slackInviteToChannel") +@DisplayIcon("simple:slack") +@Signature("(channelId: TEXT, userIds: LIST): SLACK_CHANNEL") +@Name({ code: "en-US", content: "Invite to channel" }) +@DisplayMessage({ code: "en-US", content: "Invite users to Slack channel ${channelId}" }) +@Documentation({ + code: "en-US", + content: + "Invites up to 1000 users to a channel via `conversations.invite` and returns the updated channel.\nSlack fails the whole call if any single user cannot be invited, e.g. because they are already a member.", +}) +@Description({ code: "en-US", content: "Invites users to a Slack channel." }) +@Parameter({ + runtimeName: "channelId", + name: [{ code: "en-US", content: "Channel ID" }], + description: [{ code: "en-US", content: "The ID of the channel to invite into." }], +}) +@Parameter({ + runtimeName: "userIds", + name: [{ code: "en-US", content: "User IDs" }], + description: [{ code: "en-US", content: "The IDs of the users to invite. Up to 1000 per call." }], +}) +export class InviteToChannelFunction { + async run(context: FunctionContext, channelId: string, userIds: string[]): Promise { + const client = getSlackClient(context); + if (userIds.length === 0) { + throw new RuntimeError("SLACK_INVITE_TO_CHANNEL_FAILED", "No user IDs provided to invite."); + } + + try { + // conversations.invite takes the users as one comma separated value. + const result = await client.conversations.invite({ channel: channelId, users: userIds.join(",") }); + if (!result.channel) { + throw new RuntimeError( + "SLACK_INVITE_TO_CHANNEL_FAILED", + `Slack did not return a channel for ${channelId}.` + ); + } + return SlackChannelSchema.parse(result.channel); + } catch (error) { + throw toRuntimeError("SLACK_INVITE_TO_CHANNEL_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/postMessageFunction.ts b/actions/slack-action/src/functions/postMessageFunction.ts new file mode 100644 index 0000000..38ef837 --- /dev/null +++ b/actions/slack-action/src/functions/postMessageFunction.ts @@ -0,0 +1,62 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toBlockArgument, toRuntimeError } from "../helpers.js"; +import { SlackBlock } from "../data_types/slackBlock.js"; +import { SlackMessage, SlackMessageSchema } from "../data_types/slackMessage.js"; + +@Identifier("slackPostMessage") +@DisplayIcon("simple:slack") +@Signature("(channelId: TEXT, text: TEXT, blocks?: LIST): SLACK_MESSAGE") +@Name({ code: "en-US", content: "Post message" }) +@DisplayMessage({ code: "en-US", content: "Post Slack message to ${channelId}" }) +@Documentation({ + code: "en-US", + content: + "Posts a message to a Slack channel, private group or DM via `chat.postMessage`.\nThe bot must be a member of the channel. `text` is also used as the notification and accessibility fallback when `blocks` are provided.", +}) +@Description({ code: "en-US", content: "Posts a message to a Slack channel." }) +@Parameter({ + runtimeName: "channelId", + name: [{ code: "en-US", content: "Channel ID" }], + description: [{ code: "en-US", content: "The ID of the channel, private group or DM to post to (e.g. C0123456789)." }], +}) +@Parameter({ + runtimeName: "text", + name: [{ code: "en-US", content: "Text" }], + description: [ + { + code: "en-US", + content: "The message text. Used as the notification and accessibility fallback when blocks are provided.", + }, + ], +}) +@Parameter({ + runtimeName: "blocks", + name: [{ code: "en-US", content: "Blocks" }], + description: [{ code: "en-US", content: "Block Kit blocks making up the message body. Build them with slackCreateTextSection or slackCreateBlock." }], + optional: true, +}) +export class PostMessageFunction { + async run(context: FunctionContext, channelId: string, text: string, blocks?: SlackBlock[]): Promise { + const client = getSlackClient(context); + try { + const result = await client.chat.postMessage({ + channel: channelId, + text, + ...toBlockArgument(blocks), + }); + return SlackMessageSchema.parse({ ...result.message, ok: result.ok, channel: result.channel, ts: result.ts }); + } catch (error) { + throw toRuntimeError("SLACK_POST_MESSAGE_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/replyInThreadFunction.ts b/actions/slack-action/src/functions/replyInThreadFunction.ts new file mode 100644 index 0000000..362ebd0 --- /dev/null +++ b/actions/slack-action/src/functions/replyInThreadFunction.ts @@ -0,0 +1,74 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toBlockArgument, toRuntimeError } from "../helpers.js"; +import { SlackBlock } from "../data_types/slackBlock.js"; +import { SlackMessage, SlackMessageSchema } from "../data_types/slackMessage.js"; + +@Identifier("slackReplyInThread") +@DisplayIcon("simple:slack") +@Signature("(channelId: TEXT, threadTs: TEXT, text: TEXT, blocks?: LIST): SLACK_MESSAGE") +@Name({ code: "en-US", content: "Reply in thread" }) +@DisplayMessage({ code: "en-US", content: "Reply in Slack thread ${threadTs}" }) +@Documentation({ + code: "en-US", + content: + "Posts a message as a threaded reply to an existing message.\n`threadTs` is the `ts` of the parent message — take it from a SlackMessagePosted or SlackAppMentioned event, or from the result of a previous slackPostMessage.", +}) +@Description({ code: "en-US", content: "Posts a reply in an existing Slack thread." }) +@Parameter({ + runtimeName: "channelId", + name: [{ code: "en-US", content: "Channel ID" }], + description: [{ code: "en-US", content: "The ID of the channel holding the parent message." }], +}) +@Parameter({ + runtimeName: "threadTs", + name: [{ code: "en-US", content: "Thread timestamp" }], + description: [{ code: "en-US", content: "The `ts` of the parent message to reply to." }], +}) +@Parameter({ + runtimeName: "text", + name: [{ code: "en-US", content: "Text" }], + description: [ + { + code: "en-US", + content: "The reply text. Used as the notification and accessibility fallback when blocks are provided.", + }, + ], +}) +@Parameter({ + runtimeName: "blocks", + name: [{ code: "en-US", content: "Blocks" }], + description: [{ code: "en-US", content: "Block Kit blocks making up the reply body." }], + optional: true, +}) +export class ReplyInThreadFunction { + async run( + context: FunctionContext, + channelId: string, + threadTs: string, + text: string, + blocks?: SlackBlock[] + ): Promise { + const client = getSlackClient(context); + try { + const result = await client.chat.postMessage({ + channel: channelId, + thread_ts: threadTs, + text, + ...toBlockArgument(blocks), + }); + return SlackMessageSchema.parse({ ...result.message, ok: result.ok, channel: result.channel, ts: result.ts }); + } catch (error) { + throw toRuntimeError("SLACK_REPLY_IN_THREAD_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/sendDirectMessageFunction.ts b/actions/slack-action/src/functions/sendDirectMessageFunction.ts new file mode 100644 index 0000000..0f0f635 --- /dev/null +++ b/actions/slack-action/src/functions/sendDirectMessageFunction.ts @@ -0,0 +1,72 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + RuntimeError, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toBlockArgument, toRuntimeError } from "../helpers.js"; +import { SlackBlock } from "../data_types/slackBlock.js"; +import { SlackMessage, SlackMessageSchema } from "../data_types/slackMessage.js"; + +@Identifier("slackSendDirectMessage") +@DisplayIcon("simple:slack") +@Signature("(userId: TEXT, text: TEXT, blocks?: LIST): SLACK_MESSAGE") +@Name({ code: "en-US", content: "Send direct message" }) +@DisplayMessage({ code: "en-US", content: "Send Slack DM to ${userId}" }) +@Documentation({ + code: "en-US", + content: + "Sends a direct message to a single user. Opens (or reuses) the IM channel between the bot and the user via `conversations.open`, then posts into it.\nLook the user up by email first with `slackGetUserByEmail` if the flow only knows an address.", +}) +@Description({ code: "en-US", content: "Sends a direct message to a Slack user." }) +@Parameter({ + runtimeName: "userId", + name: [{ code: "en-US", content: "User ID" }], + description: [{ code: "en-US", content: "The ID of the user to message (e.g. U0123456789)." }], +}) +@Parameter({ + runtimeName: "text", + name: [{ code: "en-US", content: "Text" }], + description: [ + { + code: "en-US", + content: "The message text. Used as the notification and accessibility fallback when blocks are provided.", + }, + ], +}) +@Parameter({ + runtimeName: "blocks", + name: [{ code: "en-US", content: "Blocks" }], + description: [{ code: "en-US", content: "Block Kit blocks making up the message body." }], + optional: true, +}) +export class SendDirectMessageFunction { + async run(context: FunctionContext, userId: string, text: string, blocks?: SlackBlock[]): Promise { + const client = getSlackClient(context); + try { + const conversation = await client.conversations.open({ users: userId }); + const channelId = conversation.channel?.id; + if (!channelId) { + throw new RuntimeError( + "SLACK_SEND_DIRECT_MESSAGE_FAILED", + `Slack did not return an IM channel for user ${userId}.` + ); + } + + const result = await client.chat.postMessage({ + channel: channelId, + text, + ...toBlockArgument(blocks), + }); + return SlackMessageSchema.parse({ ...result.message, ok: result.ok, channel: result.channel, ts: result.ts }); + } catch (error) { + throw toRuntimeError("SLACK_SEND_DIRECT_MESSAGE_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/setChannelTopicFunction.ts b/actions/slack-action/src/functions/setChannelTopicFunction.ts new file mode 100644 index 0000000..8b810c2 --- /dev/null +++ b/actions/slack-action/src/functions/setChannelTopicFunction.ts @@ -0,0 +1,53 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + RuntimeError, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toRuntimeError } from "../helpers.js"; +import { SlackChannel, SlackChannelSchema } from "../data_types/slackChannel.js"; + +@Identifier("slackSetChannelTopic") +@DisplayIcon("simple:slack") +@Signature("(channelId: TEXT, topic: TEXT): SLACK_CHANNEL") +@Name({ code: "en-US", content: "Set channel topic" }) +@DisplayMessage({ code: "en-US", content: "Set topic of Slack channel ${channelId}" }) +@Documentation({ + code: "en-US", + content: + "Sets a channel's topic via `conversations.setTopic` and returns the updated channel.\nThe topic is capped at 250 characters and setting it posts a visible notice into the channel.", +}) +@Description({ code: "en-US", content: "Sets the topic of a Slack channel." }) +@Parameter({ + runtimeName: "channelId", + name: [{ code: "en-US", content: "Channel ID" }], + description: [{ code: "en-US", content: "The ID of the channel whose topic to set." }], +}) +@Parameter({ + runtimeName: "topic", + name: [{ code: "en-US", content: "Topic" }], + description: [{ code: "en-US", content: "The new channel topic. Max 250 characters." }], +}) +export class SetChannelTopicFunction { + async run(context: FunctionContext, channelId: string, topic: string): Promise { + const client = getSlackClient(context); + try { + const result = await client.conversations.setTopic({ channel: channelId, topic }); + if (!result.channel) { + throw new RuntimeError( + "SLACK_SET_CHANNEL_TOPIC_FAILED", + `Slack did not return a channel for ${channelId}.` + ); + } + return SlackChannelSchema.parse(result.channel); + } catch (error) { + throw toRuntimeError("SLACK_SET_CHANNEL_TOPIC_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/updateMessageFunction.ts b/actions/slack-action/src/functions/updateMessageFunction.ts new file mode 100644 index 0000000..a42ea09 --- /dev/null +++ b/actions/slack-action/src/functions/updateMessageFunction.ts @@ -0,0 +1,69 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toBlockArgument, toRuntimeError } from "../helpers.js"; +import { SlackBlock } from "../data_types/slackBlock.js"; +import { SlackMessage, SlackMessageSchema } from "../data_types/slackMessage.js"; + +@Identifier("slackUpdateMessage") +@DisplayIcon("simple:slack") +@Signature("(channelId: TEXT, ts: TEXT, text: TEXT, blocks?: LIST): SLACK_MESSAGE") +@Name({ code: "en-US", content: "Update message" }) +@DisplayMessage({ code: "en-US", content: "Update Slack message ${ts}" }) +@Documentation({ + code: "en-US", + content: + "Replaces the content of a message the app posted, via `chat.update`.\nUse it to turn an approval prompt into its outcome, or to keep a long running status message current instead of posting again.", +}) +@Description({ code: "en-US", content: "Updates an existing Slack message." }) +@Parameter({ + runtimeName: "channelId", + name: [{ code: "en-US", content: "Channel ID" }], + description: [{ code: "en-US", content: "The ID of the channel holding the message." }], +}) +@Parameter({ + runtimeName: "ts", + name: [{ code: "en-US", content: "Message timestamp" }], + description: [{ code: "en-US", content: "The `ts` of the message to update." }], +}) +@Parameter({ + runtimeName: "text", + name: [{ code: "en-US", content: "Text" }], + description: [{ code: "en-US", content: "The new message text. Replaces the previous text entirely." }], +}) +@Parameter({ + runtimeName: "blocks", + name: [{ code: "en-US", content: "Blocks" }], + description: [{ code: "en-US", content: "The new Block Kit blocks. Replace the previous blocks entirely." }], + optional: true, +}) +export class UpdateMessageFunction { + async run( + context: FunctionContext, + channelId: string, + ts: string, + text: string, + blocks?: SlackBlock[] + ): Promise { + const client = getSlackClient(context); + try { + const result = await client.chat.update({ + channel: channelId, + ts, + text, + ...toBlockArgument(blocks), + }); + return SlackMessageSchema.parse({ ...result.message, ok: result.ok, channel: result.channel, ts: result.ts }); + } catch (error) { + throw toRuntimeError("SLACK_UPDATE_MESSAGE_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/uploadFileFunction.ts b/actions/slack-action/src/functions/uploadFileFunction.ts new file mode 100644 index 0000000..f938be6 --- /dev/null +++ b/actions/slack-action/src/functions/uploadFileFunction.ts @@ -0,0 +1,75 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + RuntimeError, + Signature, +} from "@code0-tech/hercules"; +import { getSlackClient, toRuntimeError } from "../helpers.js"; +import { SlackFile, SlackFileSchema } from "../data_types/slackFile.js"; + +@Identifier("slackUploadFile") +@DisplayIcon("simple:slack") +@Signature("(channelId: TEXT, filename: TEXT, content: TEXT, title?: TEXT): SLACK_FILE") +@Name({ code: "en-US", content: "Upload file" }) +@DisplayMessage({ code: "en-US", content: "Upload ${filename} to Slack channel ${channelId}" }) +@Documentation({ + code: "en-US", + content: + "Uploads a text file and shares it into a channel, using Slack's external upload flow (`files.getUploadURLExternal` + `files.completeUploadExternal`).\nThe file extension in `filename` determines how Slack renders the content, e.g. `report.csv` or `trace.log`.", +}) +@Description({ code: "en-US", content: "Uploads a file and shares it into a Slack channel." }) +@Parameter({ + runtimeName: "channelId", + name: [{ code: "en-US", content: "Channel ID" }], + description: [{ code: "en-US", content: "The ID of the channel to share the file into." }], +}) +@Parameter({ + runtimeName: "filename", + name: [{ code: "en-US", content: "File name" }], + description: [{ code: "en-US", content: "The file name including its extension, e.g. report.csv." }], +}) +@Parameter({ + runtimeName: "content", + name: [{ code: "en-US", content: "Content" }], + description: [{ code: "en-US", content: "The file content as text." }], +}) +@Parameter({ + runtimeName: "title", + name: [{ code: "en-US", content: "Title" }], + description: [{ code: "en-US", content: "The title shown for the file in Slack. Defaults to the file name." }], + optional: true, +}) +export class UploadFileFunction { + async run( + context: FunctionContext, + channelId: string, + filename: string, + content: string, + title?: string + ): Promise { + const client = getSlackClient(context); + try { + const result = await client.filesUploadV2({ + channel_id: channelId, + filename, + content, + title: title ?? filename, + }); + // filesUploadV2 batches uploads, so a single file arrives as the first + // file of the first completion response. + const file = result.files?.[0]?.files?.[0]; + if (!file) { + throw new RuntimeError("SLACK_UPLOAD_FILE_FAILED", `Slack did not return a file for ${filename}.`); + } + return SlackFileSchema.parse(file); + } catch (error) { + throw toRuntimeError("SLACK_UPLOAD_FILE_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/functions/utils/createBlockFunction.ts b/actions/slack-action/src/functions/utils/createBlockFunction.ts new file mode 100644 index 0000000..12da060 --- /dev/null +++ b/actions/slack-action/src/functions/utils/createBlockFunction.ts @@ -0,0 +1,81 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { SlackBlock, SlackTextObject } from "../../data_types/slackBlock.js"; + +@Identifier("slackCreateBlock") +@DisplayIcon("simple:slack") +@Signature("(type: TEXT, text?: TEXT, fields?: LIST, imageUrl?: TEXT, altText?: TEXT, blockId?: TEXT): SLACK_BLOCK") +@Name({ code: "en-US", content: "Create Slack block" }) +@DisplayMessage({ code: "en-US", content: "Create Slack ${type} block" }) +@Documentation({ + code: "en-US", + content: + "Creates a Block Kit block (`SLACK_BLOCK`) of any type, for the blocks `slackCreateTextSection` does not cover.\nOnly the arguments a block type actually uses are included: `divider` needs none, `header` and `section` take `text`, `section` can take up to 10 `fields` rendered as a two column layout, and `image` takes `imageUrl` plus `altText`. All text is rendered as Slack markdown, except in `header` blocks, which Slack renders literally.", +}) +@Description({ code: "en-US", content: "Creates a Slack Block Kit block of a given type." }) +@Parameter({ + runtimeName: "type", + name: [{ code: "en-US", content: "Type" }], + description: [{ code: "en-US", content: "The block type, e.g. section, divider, header, image or context." }], +}) +@Parameter({ + runtimeName: "text", + name: [{ code: "en-US", content: "Text" }], + description: [{ code: "en-US", content: "The block's primary text. Used by section, header and context blocks." }], + optional: true, +}) +@Parameter({ + runtimeName: "fields", + name: [{ code: "en-US", content: "Fields" }], + description: [{ code: "en-US", content: "Up to 10 texts rendered as a two column layout. Used by section blocks." }], + optional: true, +}) +@Parameter({ + runtimeName: "imageUrl", + name: [{ code: "en-US", content: "Image URL" }], + description: [{ code: "en-US", content: "The URL of the image to display. Used by image blocks." }], + optional: true, +}) +@Parameter({ + runtimeName: "altText", + name: [{ code: "en-US", content: "Alt text" }], + description: [{ code: "en-US", content: "A plain text summary of the image, used by assistive technology." }], + optional: true, +}) +@Parameter({ + runtimeName: "blockId", + name: [{ code: "en-US", content: "Block ID" }], + description: [{ code: "en-US", content: "A unique identifier for the block, echoed back in interaction payloads." }], + optional: true, +}) +export class CreateBlockFunction { + run( + _context: unknown, + type: string, + text?: string, + fields?: string[], + imageUrl?: string, + altText?: string, + blockId?: string + ): SlackBlock { + // Slack renders header blocks literally and rejects a mrkdwn text object there. + const textType: SlackTextObject["type"] = type === "header" ? "plain_text" : "mrkdwn"; + + return { + type, + block_id: blockId, + text: text !== undefined ? { type: textType, text } : undefined, + fields: fields && fields.length > 0 ? fields.map((field) => ({ type: textType, text: field })) : undefined, + image_url: imageUrl, + alt_text: altText, + }; + } +} diff --git a/actions/slack-action/src/functions/utils/createTextSectionFunction.ts b/actions/slack-action/src/functions/utils/createTextSectionFunction.ts new file mode 100644 index 0000000..216f958 --- /dev/null +++ b/actions/slack-action/src/functions/utils/createTextSectionFunction.ts @@ -0,0 +1,57 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { SlackBlock } from "../../data_types/slackBlock.js"; + +@Identifier("slackCreateTextSection") +@DisplayIcon("simple:slack") +@Signature("(text: TEXT, markdown?: BOOLEAN, blockId?: TEXT): SLACK_BLOCK") +@Name({ code: "en-US", content: "Create Slack text section" }) +@DisplayMessage({ code: "en-US", content: "Create Slack text section" }) +@Documentation({ + code: "en-US", + content: + "Creates a Block Kit `section` block (`SLACK_BLOCK`) holding a single text object — the workhorse block for message bodies.\nBy default the text is rendered as Slack markdown (`mrkdwn`), so `*bold*`, `_italic_`, `<@U123>` mentions and `` work. Set `markdown` to false to render the text literally.", +}) +@Description({ code: "en-US", content: "Creates a Slack Block Kit section block containing text." }) +@Parameter({ + runtimeName: "text", + name: [{ code: "en-US", content: "Text" }], + description: [{ code: "en-US", content: "The section text. Max 3000 characters." }], +}) +@Parameter({ + runtimeName: "markdown", + name: [{ code: "en-US", content: "Markdown" }], + description: [ + { + code: "en-US", + content: "Whether to render the text as Slack markdown. Defaults to true; set to false to render it literally.", + }, + ], + optional: true, +}) +@Parameter({ + runtimeName: "blockId", + name: [{ code: "en-US", content: "Block ID" }], + description: [{ code: "en-US", content: "A unique identifier for the block, echoed back in interaction payloads." }], + optional: true, +}) +export class CreateTextSectionFunction { + run(_context: unknown, text: string, markdown?: boolean, blockId?: string): SlackBlock { + return { + type: "section", + block_id: blockId, + text: { + type: markdown === false ? "plain_text" : "mrkdwn", + text, + }, + }; + } +} diff --git a/actions/slack-action/src/functions/verifyRequestSignatureFunction.ts b/actions/slack-action/src/functions/verifyRequestSignatureFunction.ts new file mode 100644 index 0000000..d489182 --- /dev/null +++ b/actions/slack-action/src/functions/verifyRequestSignatureFunction.ts @@ -0,0 +1,48 @@ +import { + Description, + DisplayIcon, + DisplayMessage, + Documentation, + FunctionContext, + Identifier, + Name, + Parameter, + Signature, +} from "@code0-tech/hercules"; +import { toRuntimeError, verifyRequestSignature } from "../helpers.js"; + +@Identifier("slackVerifyRequestSignature") +@DisplayIcon("simple:slack") +@Signature("(signature: TEXT, timestamp: TEXT, rawBody: TEXT): BOOLEAN") +@Name({ code: "en-US", content: "Verify request signature" }) +@DisplayMessage({ code: "en-US", content: "Verify Slack request signature" }) +@Documentation({ + code: "en-US", + content: + "Verifies that an inbound Slack request really came from Slack, by recomputing the `X-Slack-Signature` HMAC over `v0::` with the configured signing secret. Requests whose `X-Slack-Request-Timestamp` is more than five minutes old are rejected as replays.\nSlack signs the exact bytes it sent, so `rawBody` must be the unparsed request body — a re-serialized payload will not reproduce the signature. See the action README for how this interacts with the webhook events.", +}) +@Description({ code: "en-US", content: "Verifies the signature of an inbound Slack request." }) +@Parameter({ + runtimeName: "signature", + name: [{ code: "en-US", content: "Signature" }], + description: [{ code: "en-US", content: "The value of the request's X-Slack-Signature header (v0=...)." }], +}) +@Parameter({ + runtimeName: "timestamp", + name: [{ code: "en-US", content: "Timestamp" }], + description: [{ code: "en-US", content: "The value of the request's X-Slack-Request-Timestamp header." }], +}) +@Parameter({ + runtimeName: "rawBody", + name: [{ code: "en-US", content: "Raw body" }], + description: [{ code: "en-US", content: "The unparsed request body exactly as Slack sent it." }], +}) +export class VerifyRequestSignatureFunction { + run(context: FunctionContext, signature: string, timestamp: string, rawBody: string): boolean { + try { + return verifyRequestSignature(context, signature, timestamp, rawBody); + } catch (error) { + throw toRuntimeError("SLACK_VERIFY_REQUEST_SIGNATURE_FAILED", error); + } + } +} diff --git a/actions/slack-action/src/helpers.ts b/actions/slack-action/src/helpers.ts new file mode 100644 index 0000000..900d140 --- /dev/null +++ b/actions/slack-action/src/helpers.ts @@ -0,0 +1,106 @@ +import { createHmac, timingSafeEqual } from "node:crypto"; +import { FunctionContext, RuntimeError } from "@code0-tech/hercules"; +import { ErrorCode, WebClient, type CodedError } from "@slack/web-api"; +import { SlackBlock } from "./data_types/slackBlock.js"; + +/** + * Builds (and caches) a Slack Web API client from the action configuration. + * + * The client is cached per bot token so repeated function invocations reuse the + * same instance. The official `@slack/web-api` SDK handles authentication, + * retries and rate limit backoff for us. + */ +let cached: { token: string; client: WebClient } | null = null; + +export function getSlackClient(context: FunctionContext): WebClient { + const botToken = context.matchedConfig.findConfig("bot_token") as string; + if (!botToken) { + throw new RuntimeError( + "SLACK_MISSING_BOT_TOKEN", + "No Slack bot token configured. Set the 'bot_token' config to your xoxb-... token." + ); + } + + if (cached && cached.token === botToken) { + return cached.client; + } + + const client = new WebClient(botToken); + cached = { token: botToken, client }; + return client; +} + +/** + * Maps an error thrown by the Slack SDK (or anything else) into a Hercules + * RuntimeError with a stable code and a human readable message. Slack reports + * API-level failures as a platform error carrying a machine readable + * `data.error` such as `channel_not_found` or `not_in_channel`. + */ +export function toRuntimeError(code: string, error: unknown): RuntimeError { + if (error instanceof RuntimeError) { + return error; + } + + const coded = error as Partial & { data?: { error?: string }; retryAfter?: number }; + if (coded?.code === ErrorCode.PlatformError && coded.data?.error) { + return new RuntimeError(code, `Slack API error: ${coded.data.error}`); + } + if (coded?.code === ErrorCode.RateLimitedError) { + return new RuntimeError(code, `Slack rate limited the request. Retry after ${coded.retryAfter ?? "?"}s.`); + } + if (error instanceof Error) { + return new RuntimeError(code, error.message); + } + return new RuntimeError(code, "An unexpected error occurred while calling the Slack API."); +} + +/** + * Block Kit blocks travel through flows as SLACK_BLOCK values. Slack rejects an + * empty `blocks` array, so an omitted or empty list is sent as no blocks at all. + */ +export function toBlockArgument(blocks?: SlackBlock[] | null): { blocks?: SlackBlock[] } { + return blocks && blocks.length > 0 ? { blocks } : {}; +} + +/** Requests older than this are rejected by {@link verifyRequestSignature} as replays. */ +const SIGNATURE_MAX_AGE_SECONDS = 60 * 5; + +/** + * Verifies the `X-Slack-Signature` / `X-Slack-Request-Timestamp` header pair of + * an inbound Slack request against the configured signing secret. + * + * Slack signs the exact bytes it sent, so `rawBody` must be the unparsed request + * body — re-serializing a parsed payload will not reproduce the same signature. + * See https://docs.slack.dev/authentication/verifying-requests-from-slack + */ +export function verifyRequestSignature( + context: FunctionContext, + signature: string, + timestamp: string, + rawBody: string +): boolean { + const signingSecret = context.matchedConfig.findConfig("signing_secret") as string; + if (!signingSecret) { + throw new RuntimeError( + "SLACK_MISSING_SIGNING_SECRET", + "No Slack signing secret configured. Set the 'signing_secret' config to verify inbound requests." + ); + } + + const requestTime = Number(timestamp); + if (!Number.isFinite(requestTime)) { + return false; + } + // Reject stale timestamps so a captured request cannot be replayed later. + if (Math.abs(Math.floor(Date.now() / 1000) - requestTime) > SIGNATURE_MAX_AGE_SECONDS) { + return false; + } + + const expected = `v0=${createHmac("sha256", signingSecret).update(`v0:${timestamp}:${rawBody}`).digest("hex")}`; + const expectedBuffer = Buffer.from(expected, "utf8"); + const actualBuffer = Buffer.from(signature, "utf8"); + if (expectedBuffer.length !== actualBuffer.length) { + return false; + } + return timingSafeEqual(expectedBuffer, actualBuffer); +} diff --git a/actions/slack-action/src/index.ts b/actions/slack-action/src/index.ts new file mode 100644 index 0000000..4834e8b --- /dev/null +++ b/actions/slack-action/src/index.ts @@ -0,0 +1,150 @@ +import "reflect-metadata"; +import { Action, CodeZeroEvent } from "@code0-tech/hercules"; + +import { SlackBlockDataType } from "./data_types/slackBlock.js"; +import { SlackChannelDataType } from "./data_types/slackChannel.js"; +import { SlackFileDataType } from "./data_types/slackFile.js"; +import { SlackMessageDataType } from "./data_types/slackMessage.js"; +import { SlackReactionDataType } from "./data_types/slackReaction.js"; +import { SlackUserDataType } from "./data_types/slackUser.js"; +import { SlackAppMentionEventPayload } from "./data_types/slackAppMentionEventPayload.js"; +import { SlackChannelCreatedEventPayload } from "./data_types/slackChannelCreatedEventPayload.js"; +import { SlackMessagePostedEventPayload } from "./data_types/slackMessagePostedEventPayload.js"; +import { SlackReactionAddedEventPayload } from "./data_types/slackReactionAddedEventPayload.js"; +import { SlackSlashCommandPayload } from "./data_types/slackSlashCommandPayload.js"; + +import { AddReactionFunction } from "./functions/addReactionFunction.js"; +import { CreateChannelFunction } from "./functions/createChannelFunction.js"; +import { DeleteMessageFunction } from "./functions/deleteMessageFunction.js"; +import { GetUserByEmailFunction } from "./functions/getUserByEmailFunction.js"; +import { InviteToChannelFunction } from "./functions/inviteToChannelFunction.js"; +import { PostMessageFunction } from "./functions/postMessageFunction.js"; +import { ReplyInThreadFunction } from "./functions/replyInThreadFunction.js"; +import { SendDirectMessageFunction } from "./functions/sendDirectMessageFunction.js"; +import { SetChannelTopicFunction } from "./functions/setChannelTopicFunction.js"; +import { UpdateMessageFunction } from "./functions/updateMessageFunction.js"; +import { UploadFileFunction } from "./functions/uploadFileFunction.js"; +import { VerifyRequestSignatureFunction } from "./functions/verifyRequestSignatureFunction.js"; + +import { CreateBlockFunction } from "./functions/utils/createBlockFunction.js"; +import { CreateTextSectionFunction } from "./functions/utils/createTextSectionFunction.js"; + +import { SlackAppMentioned } from "./events/slackAppMentioned.js"; +import { SlackChannelCreated } from "./events/slackChannelCreated.js"; +import { SlackMessagePosted } from "./events/slackMessagePosted.js"; +import { SlackReactionAdded } from "./events/slackReactionAdded.js"; +import { SlackSlashCommand } from "./events/slackSlashCommand.js"; + +const action = new Action( + process.env.ACTION_ID ?? "slack-action", + process.env.VERSION ?? "1.0.0", + process.env.AQUILA_URL ?? "127.0.0.1:8081", + "code0-tech", + "simple:slack", + "Slack integration: post and update messages, react to Slack events, and drive channel, user and file operations via the Slack Web API.", + [{ code: "en-US", content: "Slack" }], + [ + { + identifier: "bot_token", + type: "TEXT", + defaultValue: process.env.SLACK_BOT_TOKEN ?? "", + name: [{ code: "en-US", content: "Bot token" }], + description: [ + { + code: "en-US", + content: + "The bot user OAuth token (xoxb-...) used as the Bearer credential for the Slack Web API. Find it under OAuth & Permissions in your Slack app.", + }, + ], + linkedDataTypes: ["TEXT"], + }, + { + identifier: "signing_secret", + type: "TEXT", + defaultValue: process.env.SLACK_SIGNING_SECRET ?? "", + name: [{ code: "en-US", content: "Signing secret" }], + description: [ + { + code: "en-US", + content: + "The Slack signing secret used by slackVerifyRequestSignature to validate the X-Slack-Signature and X-Slack-Request-Timestamp headers of inbound requests. Find it under Basic Information in your Slack app.", + }, + ], + linkedDataTypes: ["TEXT"], + }, + { + identifier: "app_token", + type: "TEXT", + defaultValue: process.env.SLACK_APP_TOKEN ?? "", + optional: true, + name: [{ code: "en-US", content: "App token" }], + description: [ + { + code: "en-US", + content: + "An optional app-level token (xapp-...), only needed if you run the Slack app in Socket Mode instead of exposing the webhook events as public HTTP endpoints.", + }, + ], + linkedDataTypes: ["TEXT"], + }, + ] +); + +action.registerDataTypeClass(SlackBlockDataType); +action.registerDataTypeClass(SlackChannelDataType); +action.registerDataTypeClass(SlackFileDataType); +action.registerDataTypeClass(SlackMessageDataType); +action.registerDataTypeClass(SlackReactionDataType); +action.registerDataTypeClass(SlackUserDataType); +action.registerDataTypeClass(SlackAppMentionEventPayload); +action.registerDataTypeClass(SlackChannelCreatedEventPayload); +action.registerDataTypeClass(SlackMessagePostedEventPayload); +action.registerDataTypeClass(SlackReactionAddedEventPayload); +action.registerDataTypeClass(SlackSlashCommandPayload); + +action.registerRuntimeFunction(PostMessageFunction); +action.registerRuntimeFunction(ReplyInThreadFunction); +action.registerRuntimeFunction(UpdateMessageFunction); +action.registerRuntimeFunction(DeleteMessageFunction); +action.registerRuntimeFunction(AddReactionFunction); +action.registerRuntimeFunction(SendDirectMessageFunction); +action.registerRuntimeFunction(UploadFileFunction); +action.registerRuntimeFunction(CreateChannelFunction); +action.registerRuntimeFunction(InviteToChannelFunction); +action.registerRuntimeFunction(GetUserByEmailFunction); +action.registerRuntimeFunction(SetChannelTopicFunction); +action.registerRuntimeFunction(VerifyRequestSignatureFunction); + +action.registerRuntimeFunction(CreateBlockFunction); +action.registerRuntimeFunction(CreateTextSectionFunction); + +action.registerEventClass(SlackMessagePosted); +action.registerEventClass(SlackAppMentioned); +action.registerEventClass(SlackReactionAdded); +action.registerEventClass(SlackChannelCreated); +action.registerEventClass(SlackSlashCommand); + +action.on(CodeZeroEvent.connected, () => { + console.log("Connected to aquila"); +}); + +action.on(CodeZeroEvent.error, (error: Error) => { + console.error("Stream error:", error.message); + console.log("Attempting to reconnect in 5s..."); + setTimeout(() => { + action.connect(process.env.AUTH_TOKEN ?? "your_auth_token_here").catch((err: Error) => { + action.emit(CodeZeroEvent.error, err); + }); + }, 5000); +}); + +action.connect(process.env.AUTH_TOKEN ?? "your_auth_token_here").catch((err: Error) => { + action.emit(CodeZeroEvent.error, err); +}); + +action.on(CodeZeroEvent.moduleUpdated, (message: any) => { + console.dir(message, { depth: null }); + console.dir(action.configs.values(), { depth: null }); +}); + +export { action }; diff --git a/actions/slack-action/test/index.test.ts b/actions/slack-action/test/index.test.ts new file mode 100644 index 0000000..ba536bc --- /dev/null +++ b/actions/slack-action/test/index.test.ts @@ -0,0 +1,237 @@ +import "reflect-metadata"; +import { createHmac } from "node:crypto"; +import { FunctionContext } from "@code0-tech/hercules"; +import { describe, expect, it } from "vitest"; +import { SlackMessageSchema } from "../src/data_types/slackMessage.js"; +import { SlackSlashCommandPayloadSchema } from "../src/data_types/slackSlashCommandPayload.js"; +import { SlackReactionAddedEventPayloadSchema } from "../src/data_types/slackReactionAddedEventPayload.js"; +import { toBlockArgument, verifyRequestSignature } from "../src/helpers.js"; +import { CreateBlockFunction } from "../src/functions/utils/createBlockFunction.js"; +import { CreateTextSectionFunction } from "../src/functions/utils/createTextSectionFunction.js"; + +const SIGNING_SECRET = "example-signing-secret"; + +/** A FunctionContext exposing just the configs the helpers read. */ +const contextWith = (configs: Record): FunctionContext => + ({ + projectId: 1, + executionId: "test", + matchedConfig: { + projectId: 1, + configValues: Object.entries(configs).map( + ([identifier, value]) => ({ identifier, value }), + ), + findConfig: (identifier: string) => configs[identifier], + }, + executeFlow: async () => undefined, + }) as unknown as FunctionContext; + +describe("SlackMessageSchema", () => { + it("parses a chat.postMessage response", () => { + const parsed = SlackMessageSchema.parse({ + ok: true, + channel: "C0EXAMPLE", + ts: "1503435956.000247", + text: "Deployment finished", + bot_id: "B0EXAMPLE", + type: "message", + }); + + expect(parsed.channel).toEqual("C0EXAMPLE"); + expect(parsed.ts).toEqual("1503435956.000247"); + }); +}); + +describe("SlackReactionAddedEventPayloadSchema", () => { + it("parses a reaction_added delivery and keeps the reacted item", () => { + const parsed = SlackReactionAddedEventPayloadSchema.parse({ + type: "event_callback", + team_id: "T0EXAMPLE", + event_id: "Ev0EXAMPLE", + event_time: 1234567890, + event: { + type: "reaction_added", + user: "U0EXAMPLE", + reaction: "white_check_mark", + item: { + type: "message", + channel: "C0EXAMPLE", + ts: "1360782400.498405", + }, + event_ts: "1360782804.083113", + }, + }); + + expect(parsed.event.reaction).toEqual("white_check_mark"); + expect(parsed.event.item.ts).toEqual("1360782400.498405"); + }); + + it("rejects a delivery without the inner event", () => { + expect(() => + SlackReactionAddedEventPayloadSchema.parse({ + type: "event_callback", + }), + ).toThrow(); + }); +}); + +describe("SlackSlashCommandPayloadSchema", () => { + it("parses a form encoded slash command invocation", () => { + const parsed = SlackSlashCommandPayloadSchema.parse({ + command: "/deploy", + text: "staging", + channel_id: "C0EXAMPLE", + user_id: "U0EXAMPLE", + response_url: + "https://hooks.slack.com/commands/T0EXAMPLE/000/example", + }); + + expect(parsed.command).toEqual("/deploy"); + expect(parsed.text).toEqual("staging"); + }); + + it("requires a command", () => { + expect(() => + SlackSlashCommandPayloadSchema.parse({ text: "staging" }), + ).toThrow(); + }); +}); + +describe("block builders", () => { + it("builds a mrkdwn section by default and a plain_text one on request", () => { + const builder = new CreateTextSectionFunction(); + + expect(builder.run(undefined, "*bold*")).toEqual({ + type: "section", + block_id: undefined, + text: { type: "mrkdwn", text: "*bold*" }, + }); + expect(builder.run(undefined, "*not bold*", false).text?.type).toEqual( + "plain_text", + ); + }); + + it("renders header text literally and omits unused arguments", () => { + const builder = new CreateBlockFunction(); + + expect(builder.run(undefined, "header", "Release 1.2.0").text).toEqual({ + type: "plain_text", + text: "Release 1.2.0", + }); + expect(builder.run(undefined, "divider")).toEqual({ + type: "divider", + block_id: undefined, + text: undefined, + fields: undefined, + image_url: undefined, + alt_text: undefined, + }); + }); + + it("turns field texts into text objects", () => { + const block = new CreateBlockFunction().run( + undefined, + "section", + undefined, + ["*Env*\nstaging", "*By*\n<@U0E>"], + ); + + expect(block.fields).toEqual([ + { type: "mrkdwn", text: "*Env*\nstaging" }, + { type: "mrkdwn", text: "*By*\n<@U0E>" }, + ]); + }); +}); + +describe("toBlockArgument", () => { + it("omits blocks entirely when there are none, because Slack rejects an empty array", () => { + expect(toBlockArgument()).toEqual({}); + expect(toBlockArgument([])).toEqual({}); + expect(toBlockArgument([{ type: "divider" }])).toEqual({ + blocks: [{ type: "divider" }], + }); + }); +}); + +describe("verifyRequestSignature", () => { + const sign = (timestamp: string, body: string, secret = SIGNING_SECRET) => + `v0=${createHmac("sha256", secret).update(`v0:${timestamp}:${body}`).digest("hex")}`; + + const context = contextWith({ + bot_token: "xoxb-example", + signing_secret: SIGNING_SECRET, + }); + const body = "token=example&command=%2Fdeploy&text=staging"; + + it("accepts a signature Slack would have produced", () => { + const timestamp = String(Math.floor(Date.now() / 1000)); + expect( + verifyRequestSignature( + context, + sign(timestamp, body), + timestamp, + body, + ), + ).toBe(true); + }); + + it("rejects a signature made with a different secret", () => { + const timestamp = String(Math.floor(Date.now() / 1000)); + expect( + verifyRequestSignature( + context, + sign(timestamp, body, "other-secret"), + timestamp, + body, + ), + ).toBe(false); + }); + + it("rejects a tampered body", () => { + const timestamp = String(Math.floor(Date.now() / 1000)); + const signature = sign(timestamp, body); + expect( + verifyRequestSignature( + context, + signature, + timestamp, + `${body}&extra=1`, + ), + ).toBe(false); + }); + + it("rejects a replayed request older than five minutes", () => { + const timestamp = String(Math.floor(Date.now() / 1000) - 60 * 6); + expect( + verifyRequestSignature( + context, + sign(timestamp, body), + timestamp, + body, + ), + ).toBe(false); + }); + + it("rejects a non numeric timestamp", () => { + expect( + verifyRequestSignature( + context, + sign("not-a-time", body), + "not-a-time", + body, + ), + ).toBe(false); + }); + + it("fails loudly when no signing secret is configured", () => { + const timestamp = String(Math.floor(Date.now() / 1000)); + expect(() => + verifyRequestSignature( + contextWith({ bot_token: "xoxb-example" }), + sign(timestamp, body), + timestamp, + body, + ), + ).toThrow(/signing secret/i); + }); +}); diff --git a/actions/slack-action/tsconfig.json b/actions/slack-action/tsconfig.json new file mode 100644 index 0000000..d013e69 --- /dev/null +++ b/actions/slack-action/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Bundler", + "declaration": true, + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "baseUrl": ".", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "types": ["node", "vite/client"] + }, + "include": [ + "src/**/*", + "test/**/*", + "vite.config.ts" + ] +} \ No newline at end of file diff --git a/actions/slack-action/vite.config.ts b/actions/slack-action/vite.config.ts new file mode 100644 index 0000000..cc312df --- /dev/null +++ b/actions/slack-action/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; + +export default defineConfig({ + build: { + target: "node18", + ssr: resolve(__dirname, 'src/index.ts'), + rollupOptions: { + external: (id) => + [ + 'fs', + 'path', + 'typescript', + '@code0-tech/hercules', + '@slack/web-api' + ].includes(id) || id.startsWith('node:') + } + } +});