← back to Linkedin Agent
initial scaffold (2026-05-06 overnight session)
d016e96c5c9678c57b19f4bdcfedde6e3c21c36b · 2026-05-06 10:22:13 -0700 · Steve Abrams
Files touched
A .gitignoreA README.mdA docs/compliance.mdA docs/mcp-config.mdA docs/setup.mdA ecosystem.config.jsA package-lock.jsonA package.jsonA src/lib/auth.tsA src/lib/compose.tsA src/lib/linkedin.tsA src/mcp/server.tsA src/server.tsA templates/STEVE_BIO.mdA templates/comment-helpful.mdA templates/connect-message.mdA templates/post-agent-spotlight.mdA templates/post-product-update.mdA templates/post-skill-launch.mdA templates/post-thought-leadership.mdA tsconfig.json
Diff
commit d016e96c5c9678c57b19f4bdcfedde6e3c21c36b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 6 10:22:13 2026 -0700
initial scaffold (2026-05-06 overnight session)
---
.gitignore | 13 +
README.md | 142 +++
docs/compliance.md | 54 +
docs/mcp-config.md | 76 ++
docs/setup.md | 91 ++
ecosystem.config.js | 14 +
package-lock.json | 2209 ++++++++++++++++++++++++++++++++++
package.json | 23 +
src/lib/auth.ts | 11 +
src/lib/compose.ts | 83 ++
src/lib/linkedin.ts | 207 ++++
src/mcp/server.ts | 132 ++
src/server.ts | 188 +++
templates/STEVE_BIO.md | 59 +
templates/comment-helpful.md | 22 +
templates/connect-message.md | 25 +
templates/post-agent-spotlight.md | 21 +
templates/post-product-update.md | 20 +
templates/post-skill-launch.md | 19 +
templates/post-thought-leadership.md | 21 +
tsconfig.json | 15 +
21 files changed, 3445 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2452308
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+node_modules/
+.env
+.env.local
+.env.local.bak.*
+*.log
+*.bak
+.next/
+.env.*.local
+.env.*
+tmp/
+.DS_Store
+dist/
+build/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a0fd326
--- /dev/null
+++ b/README.md
@@ -0,0 +1,142 @@
+# LinkedIn Agent
+
+Steve's autonomous LinkedIn presence — posts, comments, connection requests,
+Company Page management, AI-drafted content — all wired into Claude Code via
+an MCP server.
+
+**Port:** 7480 (Express OAuth+API server) · MCP: stdio (no port)
+**Stack:** Next.js 16 / Express / TypeScript / `@modelcontextprotocol/sdk`
+**LinkedIn auth:** OAuth 2.0 + 3-legged flow (user-context tokens)
+
+---
+
+## What it does (and what it can't)
+
+### ✅ Real LinkedIn API support
+- Post text + image + video ("UGC posts" / "shares" via `/v2/ugcPosts`)
+- Comment on posts (`/v2/socialActions/{urn}/comments`)
+- Read your own profile + connection list
+- Manage Company Pages: post on behalf of, fetch analytics
+- React (👍 ❤️ 💡 etc.) on posts
+
+### ⚠️ Requires LinkedIn Marketing Developer Program approval
+- Lead Gen Forms · Audience targeting · Sponsored content · Analytics
+
+### ❌ NOT supported by LinkedIn API (use Browserbase fallback)
+- **Sending connection requests at scale** — LinkedIn officially does not expose this. Doing it via browser automation puts the account at risk of restrictions.
+- **Bulk people search beyond first-degree network** — Sales Navigator API is enterprise-only.
+- **DM at scale** — same restrictions as connection requests.
+
+For the unsupported flows, this agent has a `browserbase-mode` that uses
+the Browserbase MCP (per Steve's "Browserbase before any other headless
+browser" rule) with a logged-in LinkedIn session cookie. Slower, riskier,
+but functional. Off by default.
+
+---
+
+## Always-include "Steve's skills" promo
+
+Every generated post automatically appends/weaves in Steve's current skill
+& agent inventory from `templates/STEVE_BIO.md`. The point is to keep
+LinkedIn aware of every product/tool/agent Steve ships, in his own words,
+in real time. The bio file is the single source of truth — update it once
+and every future post reflects.
+
+Template snippets in `templates/`:
+- `STEVE_BIO.md` — current skills + active agents (refresh weekly)
+- `post-skill-launch.md` — "I just shipped X" post template
+- `post-thought-leadership.md` — opinion + insight format
+- `post-product-update.md` — "Designer Wallcoverings now does Y" format
+- `post-agent-spotlight.md` — featuring one of Steve's autonomous agents
+- `comment-helpful.md` — substantive comment seeds (not "great post!")
+- `connect-message.md` — personalized connection-request opener
+
+---
+
+## Setup (one-time)
+
+1. **Register a LinkedIn developer app** → https://developer.linkedin.com/
+ - Pick scopes: `r_liteprofile r_emailaddress w_member_social w_organization_social rw_organization_admin`
+ - Add redirect URI: `http://localhost:7480/api/auth/linkedin/callback`
+2. **Set env vars** in `.env.local`:
+ ```
+ LINKEDIN_CLIENT_ID=<your client id>
+ LINKEDIN_CLIENT_SECRET=<route via /secrets, never commit>
+ LINKEDIN_REDIRECT_URI=http://localhost:7480/api/auth/linkedin/callback
+ LINKEDIN_ORG_URN=urn:li:organization:<your-company-id> # optional, for Page posts
+ AUTH_PASSWORD=<route via /secrets>
+ SESSION_SECRET=<openssl rand -hex 32>
+ ```
+3. **Install + first auth**:
+ ```
+ npm install
+ npm run dev
+ # open http://localhost:7480/auth → "Sign in with LinkedIn" → grants stored to .env.local
+ ```
+4. **Wire MCP into Claude Code**: see `docs/mcp-config.md`
+
+---
+
+## What you can ask Claude after MCP is wired
+
+- "Post a LinkedIn announcement about my new Grant project — make it lead-gen-y"
+- "Find 10 grant-writers in LA and draft connection requests"
+- "Comment thoughtfully on the last 3 posts from @InstrumentlGrants"
+- "Schedule a daily 9am post about whichever DW agent shipped that day"
+- "Pull analytics on my last 30 days — engagement by post type"
+
+---
+
+## Standing rules honored
+
+- No secrets in argv (`feedback_no_secret_interpolation_in_bash.md`)
+- LinkedIn credentials route via `/secrets` skill (`feedback_secrets_auto_route.md`)
+- Browserbase before Playwright/Puppeteer (`feedback_browserbase_first.md`)
+- Email/comms compliance: every outbound message routes through the
+ `comms-compliance` subagent first when it's a cold connection request
+
+---
+
+## File map
+
+```
+linkedin-agent/
+├── README.md ← this file
+├── package.json
+├── .env.local.example
+├── ecosystem.config.js ← pm2 config (port 7480, name linkedin-agent)
+├── src/
+│ ├── server.ts ← Express OAuth + REST API
+│ ├── lib/
+│ │ ├── linkedin.ts ← LinkedIn v2 API wrapper
+│ │ ├── auth.ts ← OAuth 2.0 token store
+│ │ ├── browserbase.ts ← fallback for unsupported flows
+│ │ └── compose.ts ← merges STEVE_BIO into post drafts
+│ └── mcp/
+│ └── server.ts ← MCP stdio server (tools: post/comment/etc)
+├── templates/
+│ ├── STEVE_BIO.md ← always-included skills/agents bio
+│ ├── post-skill-launch.md
+│ ├── post-thought-leadership.md
+│ ├── post-product-update.md
+│ ├── post-agent-spotlight.md
+│ ├── comment-helpful.md
+│ └── connect-message.md
+└── docs/
+ ├── setup.md ← step-by-step LinkedIn dev portal walk
+ ├── mcp-config.md ← Claude Code MCP wiring
+ └── compliance.md ← what NOT to do (LinkedIn ToS)
+```
+
+---
+
+## Status
+
+**v0 scaffold landed 2026-05-06 (tick 53)** — files + docs + templates
+present, no live LinkedIn API calls yet. Next steps require Steve:
+1. Register dev app + provide Client ID/Secret
+2. Run `npm install` + first OAuth flow
+3. Author `templates/STEVE_BIO.md` with current skills inventory
+
+Once #1-#3 done, agent goes live. ETA from "Steve has Client ID" to "first
+post via Claude Code MCP" = ~30 min.
diff --git a/docs/compliance.md b/docs/compliance.md
new file mode 100644
index 0000000..ab677fe
--- /dev/null
+++ b/docs/compliance.md
@@ -0,0 +1,54 @@
+# Compliance — what NOT to do on LinkedIn
+
+LinkedIn's User Agreement explicitly prohibits a lot of automation that
+agents like this make easy. Read once, configure agent behavior accordingly.
+
+## Hard prohibitions (account suspension risk)
+
+- **No bulk connection requests via API or scraping.** LinkedIn doesn't expose connection-send via API specifically because it's been used for spam. Doing it via Browserbase gets accounts restricted.
+- **No view-bots or follow-bots.** Even via Browserbase. Hardware fingerprinting + rate-limiting catches these fast.
+- **No scraping public profile data without consent.** This was tested in the hiQ Labs case — the legal status is unsettled but LinkedIn's TOS still prohibits.
+- **No impersonation.** The agent posts AS Steve, signed in via OAuth as Steve. It must not pretend to be anyone else.
+
+## Soft prohibitions (visible to LinkedIn but won't suspend)
+
+- More than ~100 connection requests per WEEK on a free account
+- More than ~5-10 posts per DAY (algorithm down-ranks above this)
+- More than ~50 comments per HOUR
+- Posting the SAME text twice in 24h (algorithm flags as duplicate)
+
+## Comms-compliance hand-off
+
+For ANY outbound that hits non-Steve recipients (DMs, connection-request
+notes, comments on posts where you're not already in the thread), this
+agent should ALWAYS gate through Steve's `comms-compliance` subagent
+first. That subagent enforces:
+
+- CAN-SPAM (subject lines, opt-out, physical address requirement for commercial messaging)
+- CCPA / CPRA (California privacy)
+- TCPA (telecom — relevant if SMS is added later)
+- Federal DNC list (Do Not Call)
+- Per-state DNC lists (Utah CPR is the strictest)
+- §17529.5 (California email-spam statute)
+
+For LinkedIn specifically:
+- Connection-request notes are NOT email but DO carry an implicit TCPA-like
+ "do you want to receive this" signal. Treat as cold outbound.
+- Comments on someone's post where you're already responding to a thread =
+ fine, no compliance check needed.
+
+## Algorithm-friendly behavior
+
+- Post during high-engagement windows (Tue-Thu 8-10am or 4-6pm Pacific)
+- Vary post format: some text-only, some with images, some with links
+- Reply to comments on your own posts within 1 hour of them landing
+- Comment on others' posts (substantive, see `templates/comment-helpful.md`)
+ to keep your "active engagement" score up
+
+## Data retention
+
+- This agent does NOT store post history beyond the LinkedIn URN (which is
+ public anyway). If you want a local log for audit/analytics, add a
+ `posts.jsonl` file written by `lib/linkedin.ts` (TODO).
+- OAuth tokens live in `.env.local` only. Rotate by re-running the OAuth
+ flow at `/auth` (overwrites the existing token).
diff --git a/docs/mcp-config.md b/docs/mcp-config.md
new file mode 100644
index 0000000..c6e9177
--- /dev/null
+++ b/docs/mcp-config.md
@@ -0,0 +1,76 @@
+# Wire LinkedIn MCP into Claude Code
+
+After OAuth setup is done (`docs/setup.md`), expose the MCP server to
+Claude Code so you can post/comment/react via natural language in chat.
+
+## 1. Edit `~/.claude.json`
+
+Add a `mcpServers` entry. The LinkedIn agent runs as stdio (no port).
+
+```json
+{
+ "mcpServers": {
+ "linkedin-agent": {
+ "command": "node",
+ "args": [
+ "--import",
+ "tsx",
+ "/Users/stevestudio2/Projects/linkedin-agent/src/mcp/server.ts"
+ ],
+ "cwd": "/Users/stevestudio2/Projects/linkedin-agent",
+ "env": {}
+ }
+ }
+}
+```
+
+The `cwd` is critical — `lib/linkedin.ts` reads `.env.local` from `process.cwd()`.
+
+## 2. Restart Claude Code
+
+The MCP server boots on Claude Code launch. Restart any sessions that
+need the new tools.
+
+## 3. Verify
+
+In a Claude Code chat:
+
+> "Show available linkedin tools"
+
+You should see:
+- `linkedin_post`
+- `linkedin_post_page`
+- `linkedin_comment`
+- `linkedin_react`
+- `linkedin_compose_preview`
+
+## 4. Try it
+
+> "Compose preview: 'Just shipped Deadline River — federal grant opportunities visualized as fish swimming toward their close-date.'"
+
+Claude calls `linkedin_compose_preview`, you see the post body with the
+"Building lately:" tagline appended, you decide whether to post.
+
+> "Post that to LinkedIn."
+
+Claude calls `linkedin_post` with the same text. Post lands in your feed.
+
+## 5. Standing rules honored at the tool layer
+
+- The MCP server reads tokens from `.env.local` only — never echoes them.
+- Posts default to including the STEVE_BIO tagline. Pass `includeBio: false` to suppress.
+- Cold connection requests (when implemented via Browserbase) route through `comms-compliance` subagent first.
+- All actions log to stdout (which Claude Code routes to its session JSONL — check `~/.claude/projects/-Users-stevestudio2/` for audit trail).
+
+## Future: chained workflows
+
+Once this is wired, useful Claude Code prompts:
+
+> "Pull the last 30 days of my LinkedIn engagement, find my top 3 posts by reply count, and draft 3 follow-on posts in the same style."
+
+> "I just published a new Designer Wallcoverings vendor page. Compose a LinkedIn product-update post linking to it, run it past me, then publish."
+
+> "Find every grant-writer on LinkedIn within 25 miles of LA, draft personalized connection requests using the connect-message template, queue them for my approval."
+
+The last one needs Browserbase mode (LinkedIn API doesn't support
+connection requests). Off by default.
diff --git a/docs/setup.md b/docs/setup.md
new file mode 100644
index 0000000..c672e6f
--- /dev/null
+++ b/docs/setup.md
@@ -0,0 +1,91 @@
+# Setup walkthrough
+
+Estimated time: 30 min from "no LinkedIn dev app" to "first post via Claude Code MCP."
+
+## 1. Register a LinkedIn developer app (10 min)
+
+1. Go to https://developer.linkedin.com/ → "My apps" → "Create app".
+2. Fill out the form:
+ - **App name:** `Steve Personal Agent` (or whatever — only you see this)
+ - **Company page:** select your existing Designer Wallcoverings page, or create a new throwaway company you control
+ - **App logo:** any 100×100 PNG (LinkedIn requires one)
+ - **Legal agreement:** check the box
+3. After creation, in the **Auth** tab:
+ - Add redirect URL: `http://localhost:7480/api/auth/linkedin/callback`
+ - Note the **Client ID** + **Client Secret** (Secret only displayed once — copy now)
+4. In the **Products** tab, request access to:
+ - **Sign In with LinkedIn using OpenID Connect** (instant approval)
+ - **Share on LinkedIn** (instant approval)
+ - **Marketing Developer Platform** (manual review — usually 3-7 days, only needed for Company Page posts and lead gen)
+
+## 2. Route credentials via /secrets (2 min)
+
+Per Steve's standing rule, paste both values via the secrets-manager skill:
+
+```
+LINKEDIN_CLIENT_ID=<value>
+LINKEDIN_CLIENT_SECRET=<value>
+```
+
+The skill will fan them to `~/Projects/linkedin-agent/.env.local` automatically.
+
+## 3. Install + boot (5 min)
+
+```sh
+cd ~/Projects/linkedin-agent
+npm install
+cp .env.local.example .env.local # if it doesn't exist after secrets-manager
+# Set AUTH_PASSWORD + SESSION_SECRET in .env.local:
+echo "AUTH_PASSWORD=$(openssl rand -hex 12)" >> .env.local
+echo "SESSION_SECRET=$(openssl rand -hex 32)" >> .env.local
+npm run dev
+```
+
+Server boots on http://localhost:7480.
+
+## 4. First OAuth flow (2 min)
+
+1. Open http://localhost:7480/auth in your browser.
+2. LinkedIn login screen → grant permissions.
+3. You'll land on `/api/auth/linkedin/callback?code=...` and see a success page.
+4. Token written to `.env.local` (`LINKEDIN_ACCESS_TOKEN`, `LINKEDIN_REFRESH_TOKEN`, `LINKEDIN_TOKEN_EXPIRES_AT`).
+
+## 5. Smoke-test a post (1 min)
+
+```sh
+curl -X POST http://localhost:7480/api/post \
+ -H "Content-Type: application/json" \
+ -H "X-Admin-Key: $(grep AUTH_PASSWORD .env.local | cut -d= -f2)" \
+ -d '{"text":"Testing my new LinkedIn agent. If you see this, the OAuth flow worked. Building lately: Grant, Patty, Norma."}'
+```
+
+Should return `{"ok": true, "id": "urn:li:share:..."}`.
+
+Refresh your LinkedIn feed. The post should be there.
+
+## 6. Wire MCP into Claude Code (5 min)
+
+See [`mcp-config.md`](./mcp-config.md).
+
+## 7. (Optional) Run as pm2 (1 min)
+
+```sh
+cd ~/Projects/linkedin-agent
+pm2 start ecosystem.config.js
+pm2 save
+```
+
+Now `linkedin-agent` boots on Mac restart.
+
+## Token refresh
+
+LinkedIn access tokens last 60 days. Refresh tokens last 365 days. The
+agent doesn't auto-refresh yet (tracked in TODO). For now, when you get a
+401 from a post call, re-run the OAuth flow at `/auth`.
+
+## Troubleshooting
+
+- **"unauthorized_scope_error"** — your app doesn't have the right Products approved. Go back to the Products tab and add Sign In with LinkedIn + Share on LinkedIn.
+- **"redirect_uri_mismatch"** — the redirect URI in your app settings must EXACTLY match `LINKEDIN_REDIRECT_URI` in `.env.local`. Trailing slashes count.
+- **Posts return 422** — usually means LinkedIn rejected the post body. Check for over-3000-char text, broken Unicode, or restricted hashtags.
+- **Connection requests** — not supported by the API. Use Browserbase mode (see README) or do them manually.
diff --git a/ecosystem.config.js b/ecosystem.config.js
new file mode 100644
index 0000000..e747391
--- /dev/null
+++ b/ecosystem.config.js
@@ -0,0 +1,14 @@
+module.exports = {
+ apps: [
+ {
+ name: 'linkedin-agent',
+ script: 'src/server.ts',
+ interpreter: 'node',
+ interpreter_args: '--import tsx',
+ cwd: __dirname,
+ env: { NODE_ENV: 'production', PORT: 7480 },
+ max_memory_restart: '256M',
+ watch: false,
+ },
+ ],
+};
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..959cb4f
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2209 @@
+{
+ "name": "linkedin-agent",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "linkedin-agent",
+ "version": "0.1.0",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.0.4",
+ "express": "^4.21.0",
+ "zod": "^3.23.8"
+ },
+ "devDependencies": {
+ "@types/express": "^5.0.0",
+ "@types/node": "^22",
+ "tsx": "^4",
+ "typescript": "^5"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.14",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
+ "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.29.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
+ "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@hono/node-server": "^1.19.9",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.2.1",
+ "hono": "^4.11.4",
+ "jose": "^6.1.3",
+ "json-schema-typed": "^8.0.2",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.25 || ^4.0",
+ "zod-to-json-schema": "^3.25.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/express": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+ "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.19.17",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
+ "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.5",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.15.1",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/body-parser/node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz",
+ "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.14.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz",
+ "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.2.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/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-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "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-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/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.12.18",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
+ "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jose": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
+ "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+ "license": "MIT"
+ },
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/router/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/router/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/router/node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.2",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
+ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25.28 || ^4"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..c16dff8
--- /dev/null
+++ b/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "linkedin-agent",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "tsx watch src/server.ts",
+ "start": "node --import tsx src/server.ts",
+ "mcp": "node --import tsx src/mcp/server.ts",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.0.4",
+ "express": "^4.21.0",
+ "zod": "^3.23.8"
+ },
+ "devDependencies": {
+ "@types/express": "^5.0.0",
+ "@types/node": "^22",
+ "tsx": "^4",
+ "typescript": "^5"
+ }
+}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
new file mode 100644
index 0000000..9c0d19d
--- /dev/null
+++ b/src/lib/auth.ts
@@ -0,0 +1,11 @@
+/**
+ * Token store helpers — re-exports from linkedin.ts for symmetry with
+ * other wave-3 projects (which expect a lib/auth.ts).
+ *
+ * 2026-05-06 (tick 53): scaffolded. The real OAuth + token-management
+ * logic lives in lib/linkedin.ts. This file just re-exports so that
+ * future cross-project shims (sister-auth, audit) can import from a
+ * predictable path.
+ */
+
+export { getToken, saveToken, authorizeUrl, exchangeCode } from './linkedin.js';
diff --git a/src/lib/compose.ts b/src/lib/compose.ts
new file mode 100644
index 0000000..39df316
--- /dev/null
+++ b/src/lib/compose.ts
@@ -0,0 +1,83 @@
+/**
+ * compose.ts — merges Steve's bio (templates/STEVE_BIO.md) into post drafts.
+ *
+ * The point: every post the agent generates carries a tasteful pointer back
+ * to Steve's current product/agent inventory. NOT spammy CTA — folded into
+ * the natural close of the post.
+ */
+
+import { readFileSync } from 'fs';
+import { join } from 'path';
+
+const ROOT = process.cwd();
+
+let cached: { bio: string; mtime: number } | null = null;
+
+function readBio(): string {
+ const path = join(ROOT, 'templates/STEVE_BIO.md');
+ try {
+ const stat = (require('fs') as typeof import('fs')).statSync(path);
+ if (cached && cached.mtime === stat.mtimeMs) return cached.bio;
+ const bio = readFileSync(path, 'utf8');
+ cached = { bio, mtime: stat.mtimeMs };
+ return bio;
+ } catch {
+ return '';
+ }
+}
+
+// Pull just the "Active products" + "Autonomous agents" sections, sample N
+// items at random, format as a 1-line tagline.
+function extractTagline(bio: string, sample = 3): string {
+ const sections = bio.split(/^## /m);
+ const products = (sections.find((s) => s.startsWith('Active products'))?.match(/^- \*\*([^*]+)\*\*/gm) || [])
+ .map((l) => l.replace(/^- \*\*([^*]+)\*\*.*/, '$1'));
+ const agents = (sections.find((s) => s.startsWith('Autonomous agents'))?.match(/^- \*\*([^*]+)\*\*/gm) || [])
+ .map((l) => l.replace(/^- \*\*([^*]+)\*\*.*/, '$1'));
+
+ const pool = [...products, ...agents];
+ if (pool.length === 0) return '';
+ // Deterministic-ish sample by current minute (so re-running same minute = same output)
+ const seed = Math.floor(Date.now() / 60_000);
+ const picked: string[] = [];
+ for (let i = 0; i < Math.min(sample, pool.length); i++) {
+ const idx = (seed * (i + 1) * 31) % pool.length;
+ if (!picked.includes(pool[idx])) picked.push(pool[idx]);
+ }
+ return picked.join(' · ');
+}
+
+/**
+ * Compose a final post: takes a draft (LLM-generated body) and weaves in
+ * Steve's bio tagline at the end. If the draft is already > 2800 chars
+ * (LinkedIn limit is 3000), skip the tagline.
+ */
+export function composePost(draft: string, opts?: { tagline?: boolean }): string {
+ const wantTagline = opts?.tagline !== false;
+ const bio = readBio();
+ if (!bio || !wantTagline) return draft.trim();
+
+ const tagline = extractTagline(bio, 3);
+ if (!tagline) return draft.trim();
+
+ const body = draft.trim();
+ if (body.length > 2800) return body;
+
+ // Naturalize: if draft already mentions Steve's product names, skip
+ for (const name of tagline.split(' · ')) {
+ if (body.toLowerCase().includes(name.toLowerCase())) return body;
+ }
+
+ return `${body}\n\n—\nBuilding lately: ${tagline}.`;
+}
+
+/**
+ * Read a named template from templates/ and return its body.
+ */
+export function readTemplate(name: string): string {
+ try {
+ return readFileSync(join(ROOT, 'templates', `${name}.md`), 'utf8');
+ } catch {
+ return '';
+ }
+}
diff --git a/src/lib/linkedin.ts b/src/lib/linkedin.ts
new file mode 100644
index 0000000..32f5563
--- /dev/null
+++ b/src/lib/linkedin.ts
@@ -0,0 +1,207 @@
+/**
+ * LinkedIn v2 API wrapper. Handles OAuth tokens + REST calls.
+ *
+ * Endpoints used:
+ * GET /v2/userinfo ← profile (OpenID)
+ * POST /v2/ugcPosts ← create text+image post
+ * POST /v2/socialActions/{urn}/comments ← comment on a post
+ * POST /v2/reactions ← like/love/celebrate/etc.
+ * POST /v2/organizations/{orgId}/ugcPosts ← post on Company Page
+ */
+
+import { readFileSync, writeFileSync } from 'fs';
+import { join } from 'path';
+
+const ROOT = join(process.cwd(), '.env.local');
+const API = 'https://api.linkedin.com';
+const AUTH = 'https://www.linkedin.com/oauth/v2';
+
+export type LinkedInToken = {
+ access_token: string;
+ refresh_token?: string;
+ expires_at: number;
+};
+
+// ---------------------------------------------------------------------------
+// Token store — reads/writes .env.local in place. No remote secrets store.
+// ---------------------------------------------------------------------------
+
+function readEnv(): Record<string, string> {
+ try {
+ const raw = readFileSync(ROOT, 'utf8');
+ const out: Record<string, string> = {};
+ for (const line of raw.split('\n')) {
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
+ if (m) out[m[1]] = m[2];
+ }
+ return out;
+ } catch { return {}; }
+}
+
+function writeEnv(updates: Record<string, string>): void {
+ const env = readEnv();
+ for (const [k, v] of Object.entries(updates)) env[k] = v;
+ const out = Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\n') + '\n';
+ writeFileSync(ROOT, out, { mode: 0o600 });
+}
+
+export function getToken(): LinkedInToken | null {
+ const env = readEnv();
+ if (!env.LINKEDIN_ACCESS_TOKEN) return null;
+ return {
+ access_token: env.LINKEDIN_ACCESS_TOKEN,
+ refresh_token: env.LINKEDIN_REFRESH_TOKEN,
+ expires_at: parseInt(env.LINKEDIN_TOKEN_EXPIRES_AT || '0', 10),
+ };
+}
+
+export function saveToken(t: LinkedInToken): void {
+ writeEnv({
+ LINKEDIN_ACCESS_TOKEN: t.access_token,
+ LINKEDIN_REFRESH_TOKEN: t.refresh_token || '',
+ LINKEDIN_TOKEN_EXPIRES_AT: String(t.expires_at),
+ });
+}
+
+// ---------------------------------------------------------------------------
+// OAuth 2.0 — authorization-code flow
+// ---------------------------------------------------------------------------
+
+// 2026-05-06 (post-OAuth-blank-page debug): the OLD SCOPES (r_liteprofile +
+// r_emailaddress + w_organization_social + rw_organization_admin) were
+// rejected silently because the Marketing Developer Platform isn't approved
+// on this app. Modern LinkedIn uses OpenID Connect scopes for the basic
+// "sign in + post on member feed" flow — these come with the two instant-
+// approval products: "Sign In with LinkedIn using OpenID Connect" + "Share
+// on LinkedIn". Add Marketing Developer Platform later for org/page posts.
+const SCOPES = [
+ 'openid',
+ 'profile',
+ 'email',
+ 'w_member_social',
+].join(' ');
+
+export function authorizeUrl(state: string): string {
+ const env = readEnv();
+ const params = new URLSearchParams({
+ response_type: 'code',
+ client_id: env.LINKEDIN_CLIENT_ID || '',
+ redirect_uri: env.LINKEDIN_REDIRECT_URI || '',
+ state,
+ scope: SCOPES,
+ });
+ return `${AUTH}/authorization?${params}`;
+}
+
+export async function exchangeCode(code: string): Promise<LinkedInToken> {
+ const env = readEnv();
+ const body = new URLSearchParams({
+ grant_type: 'authorization_code',
+ code,
+ redirect_uri: env.LINKEDIN_REDIRECT_URI || '',
+ client_id: env.LINKEDIN_CLIENT_ID || '',
+ client_secret: env.LINKEDIN_CLIENT_SECRET || '',
+ });
+ const res = await fetch(`${AUTH}/accessToken`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body,
+ });
+ if (!res.ok) throw new Error(`OAuth exchange failed: ${res.status} ${await res.text()}`);
+ const json = await res.json() as {
+ access_token: string;
+ refresh_token?: string;
+ expires_in: number;
+ };
+ return {
+ access_token: json.access_token,
+ refresh_token: json.refresh_token,
+ expires_at: Date.now() + json.expires_in * 1000,
+ };
+}
+
+// ---------------------------------------------------------------------------
+// API calls
+// ---------------------------------------------------------------------------
+
+async function api<T = unknown>(
+ path: string,
+ init: RequestInit = {},
+): Promise<T> {
+ const t = getToken();
+ if (!t) throw new Error('No LinkedIn access token — run OAuth flow first');
+ const res = await fetch(`${API}${path}`, {
+ ...init,
+ headers: {
+ 'Authorization': `Bearer ${t.access_token}`,
+ 'X-Restli-Protocol-Version': '2.0.0',
+ 'Content-Type': 'application/json',
+ ...(init.headers || {}),
+ },
+ signal: AbortSignal.timeout(20_000),
+ });
+ if (!res.ok) {
+ const txt = await res.text().catch(() => '');
+ throw new Error(`LinkedIn API ${res.status}: ${txt.slice(0, 300)}`);
+ }
+ return res.json() as Promise<T>;
+}
+
+export async function getProfile(): Promise<{ sub: string; name: string; email?: string }> {
+ return api('/v2/userinfo');
+}
+
+export async function postText(text: string, authorUrn?: string): Promise<{ id: string }> {
+ const env = readEnv();
+ const author = authorUrn || env.LINKEDIN_AUTHOR_URN;
+ if (!author) {
+ // Auto-derive from /v2/userinfo
+ const me = await getProfile();
+ const memberUrn = `urn:li:person:${me.sub}`;
+ return postText(text, memberUrn);
+ }
+ return api('/v2/ugcPosts', {
+ method: 'POST',
+ body: JSON.stringify({
+ author,
+ lifecycleState: 'PUBLISHED',
+ specificContent: {
+ 'com.linkedin.ugc.ShareContent': {
+ shareCommentary: { text },
+ shareMediaCategory: 'NONE',
+ },
+ },
+ visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' },
+ }),
+ });
+}
+
+export async function postOnPage(text: string): Promise<{ id: string }> {
+ const env = readEnv();
+ const orgUrn = env.LINKEDIN_ORG_URN;
+ if (!orgUrn) throw new Error('LINKEDIN_ORG_URN not set in .env.local');
+ return postText(text, orgUrn);
+}
+
+export async function commentOnPost(postUrn: string, text: string): Promise<{ id: string }> {
+ // Encode the URN for path safety
+ const encoded = encodeURIComponent(postUrn);
+ return api(`/v2/socialActions/${encoded}/comments`, {
+ method: 'POST',
+ body: JSON.stringify({
+ actor: postUrn.startsWith('urn:li:organization:') ? postUrn : undefined, // omit to default to authed user
+ object: postUrn,
+ message: { text },
+ }),
+ });
+}
+
+export async function react(
+ postUrn: string,
+ reaction: 'LIKE' | 'PRAISE' | 'EMPATHY' | 'INTEREST' | 'APPRECIATION' | 'MAYBE' = 'LIKE',
+): Promise<void> {
+ await api('/v2/reactions', {
+ method: 'POST',
+ body: JSON.stringify({ object: postUrn, reactionType: reaction }),
+ });
+}
diff --git a/src/mcp/server.ts b/src/mcp/server.ts
new file mode 100644
index 0000000..58e926e
--- /dev/null
+++ b/src/mcp/server.ts
@@ -0,0 +1,132 @@
+/**
+ * MCP server (stdio) — exposes LinkedIn Agent tools to Claude Code.
+ *
+ * Tools:
+ * linkedin_post — create a text post on Steve's personal feed
+ * linkedin_post_page — create a text post on a Company Page
+ * linkedin_comment — comment on a given post URN
+ * linkedin_react — react to a post URN (LIKE/PRAISE/etc.)
+ * linkedin_compose_preview — preview composed post (with bio tagline)
+ *
+ * Wire into Claude Code via:
+ * ~/.claude.json → mcpServers → linkedin-agent → command:
+ * "node --import tsx /Users/stevestudio2/Projects/linkedin-agent/src/mcp/server.ts"
+ *
+ * 2026-05-06 (tick 53): scaffold landed.
+ */
+
+import { Server } from '@modelcontextprotocol/sdk/server/index.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import {
+ CallToolRequestSchema,
+ ListToolsRequestSchema,
+} from '@modelcontextprotocol/sdk/types.js';
+import { postText, postOnPage, commentOnPost, react } from '../lib/linkedin.js';
+import { composePost } from '../lib/compose.js';
+
+const server = new Server(
+ { name: 'linkedin-agent', version: '0.1.0' },
+ { capabilities: { tools: {} } },
+);
+
+const TOOLS = [
+ {
+ name: 'linkedin_post',
+ description: 'Create a text post on Steve\'s personal LinkedIn feed. Composes with STEVE_BIO tagline by default.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ text: { type: 'string', description: 'Post body (max ~3000 chars). Plain text + LinkedIn-supported markdown.' },
+ includeBio: { type: 'boolean', description: 'Append the rotating "Building lately:" tagline. Default true.' },
+ },
+ required: ['text'],
+ },
+ },
+ {
+ name: 'linkedin_post_page',
+ description: 'Create a text post on Steve\'s Company Page (LINKEDIN_ORG_URN env). NO tagline — page posts are first-person from the brand.',
+ inputSchema: {
+ type: 'object',
+ properties: { text: { type: 'string' } },
+ required: ['text'],
+ },
+ },
+ {
+ name: 'linkedin_comment',
+ description: 'Comment on a given LinkedIn post URN.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ postUrn: { type: 'string', description: 'Full URN like urn:li:share:1234567890' },
+ text: { type: 'string' },
+ },
+ required: ['postUrn', 'text'],
+ },
+ },
+ {
+ name: 'linkedin_react',
+ description: 'React to a post URN. Default reaction is LIKE.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ postUrn: { type: 'string' },
+ reaction: { type: 'string', enum: ['LIKE', 'PRAISE', 'EMPATHY', 'INTEREST', 'APPRECIATION', 'MAYBE'] },
+ },
+ required: ['postUrn'],
+ },
+ },
+ {
+ name: 'linkedin_compose_preview',
+ description: 'Preview the final composed post (draft + tagline) without sending.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ text: { type: 'string' },
+ includeBio: { type: 'boolean' },
+ },
+ required: ['text'],
+ },
+ },
+];
+
+server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
+
+server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const { name, arguments: args } = request.params;
+ const a = (args || {}) as Record<string, unknown>;
+
+ try {
+ if (name === 'linkedin_post') {
+ const final = composePost(String(a.text), { tagline: a.includeBio !== false });
+ const result = await postText(final);
+ return { content: [{ type: 'text', text: `Posted. URN: ${result.id}\n\n---\n${final}` }] };
+ }
+ if (name === 'linkedin_post_page') {
+ const final = composePost(String(a.text), { tagline: false });
+ const result = await postOnPage(final);
+ return { content: [{ type: 'text', text: `Posted on Company Page. URN: ${result.id}` }] };
+ }
+ if (name === 'linkedin_comment') {
+ const result = await commentOnPost(String(a.postUrn), String(a.text));
+ return { content: [{ type: 'text', text: `Comment posted. URN: ${result.id}` }] };
+ }
+ if (name === 'linkedin_react') {
+ await react(String(a.postUrn), (a.reaction as 'LIKE') || 'LIKE');
+ return { content: [{ type: 'text', text: `Reacted ${a.reaction || 'LIKE'} on ${a.postUrn}.` }] };
+ }
+ if (name === 'linkedin_compose_preview') {
+ const final = composePost(String(a.text), { tagline: a.includeBio !== false });
+ return { content: [{ type: 'text', text: final }] };
+ }
+ return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
+ } catch (err) {
+ return {
+ content: [{ type: 'text', text: `Error: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+});
+
+const transport = new StdioServerTransport();
+await server.connect(transport);
+console.error('[linkedin-agent MCP] ready (stdio)');
diff --git a/src/server.ts b/src/server.ts
new file mode 100644
index 0000000..89cc301
--- /dev/null
+++ b/src/server.ts
@@ -0,0 +1,188 @@
+/**
+ * Express server — OAuth flow + REST API for posts/comments/reactions.
+ * Port: 7480 (set via PORT env, default 7480).
+ *
+ * Endpoints:
+ * GET / ← simple landing page (link to /auth)
+ * GET /auth ← redirect to LinkedIn OAuth
+ * GET /api/auth/linkedin/callback ← OAuth callback
+ * GET /api/health ← unauth heartbeat
+ * POST /api/post ← protected: create LinkedIn post
+ * POST /api/post/page ← protected: post on Company Page
+ * POST /api/comment ← protected: comment on a post URN
+ * POST /api/react ← protected: react to a post URN
+ *
+ * 2026-05-06 (tick 53): scaffold landed. Next steps: register dev app,
+ * wire credentials, run the OAuth flow once, test post.
+ */
+
+import express, { type Request, type Response, type NextFunction } from 'express';
+import { randomBytes } from 'crypto';
+import {
+ authorizeUrl, exchangeCode, saveToken,
+ postText, postOnPage, commentOnPost, react, getProfile,
+} from './lib/linkedin.js';
+import { composePost, readTemplate } from './lib/compose.js';
+
+const PORT = Number(process.env.PORT || 7480);
+const app = express();
+
+app.use(express.json({ limit: '64kb' }));
+
+// ---------------------------------------------------------------------------
+// Auth gate — basic env-password header. Wave-3 pattern: AUTH_PASSWORD env.
+// ---------------------------------------------------------------------------
+
+function requireAuth(req: Request, res: Response, next: NextFunction): void {
+ const expected = process.env.AUTH_PASSWORD;
+ if (!expected) {
+ res.status(503).json({ error: 'AUTH_PASSWORD env unset' });
+ return;
+ }
+ const got = req.header('x-admin-key');
+ if (got !== expected) {
+ res.status(401).json({ error: 'Unauthorized — set X-Admin-Key header' });
+ return;
+ }
+ next();
+}
+
+// CSRF state for OAuth round-trip
+const oauthStates = new Map<string, number>();
+setInterval(() => {
+ const cutoff = Date.now() - 10 * 60_000;
+ for (const [k, v] of oauthStates) if (v < cutoff) oauthStates.delete(k);
+}, 60_000);
+
+// ---------------------------------------------------------------------------
+// Landing
+// ---------------------------------------------------------------------------
+
+app.get('/', (_req, res) => {
+ res.set('Content-Type', 'text/html').send(`<!doctype html>
+<html><head><meta charset="utf-8"><title>LinkedIn Agent</title>
+<style>body{background:#07090a;color:#d8e8df;font-family:system-ui;padding:40px;max-width:680px}
+a{color:#10b981}h1{font-weight:600;letter-spacing:-0.01em}code{background:#111;padding:2px 6px;border-radius:3px}</style>
+</head><body>
+<h1>● LinkedIn Agent</h1>
+<p>Steve's autonomous LinkedIn presence. <a href="/auth">Sign in with LinkedIn →</a></p>
+<p style="color:#6c7d75">After auth, Claude Code can post on your behalf via the MCP server.
+See <code>docs/mcp-config.md</code>.</p>
+</body></html>`);
+});
+
+// ---------------------------------------------------------------------------
+// OAuth flow
+// ---------------------------------------------------------------------------
+
+app.get('/auth', (_req, res) => {
+ const state = randomBytes(16).toString('hex');
+ oauthStates.set(state, Date.now());
+ res.redirect(authorizeUrl(state));
+});
+
+app.get('/api/auth/linkedin/callback', async (req, res) => {
+ const { code, state } = req.query as { code?: string; state?: string };
+ if (!code || !state || !oauthStates.has(state)) {
+ res.status(400).send('Missing or invalid state');
+ return;
+ }
+ oauthStates.delete(state);
+ try {
+ const token = await exchangeCode(code);
+ saveToken(token);
+ const me = await getProfile();
+ res.set('Content-Type', 'text/html').send(`<!doctype html>
+<html><body style="font-family:system-ui;padding:40px;background:#07090a;color:#d8e8df">
+<h1 style="color:#10b981">✓ Connected</h1>
+<p>Authenticated as <strong>${me.name}</strong> (${me.email || me.sub}).</p>
+<p>Token saved to <code>.env.local</code>. You can close this tab.</p>
+<p><a href="/">← back</a></p></body></html>`);
+ } catch (err) {
+ console.error('[oauth callback]', err);
+ res.status(500).send('OAuth exchange failed: ' + (err as Error).message);
+ }
+});
+
+// ---------------------------------------------------------------------------
+// Health (unauth, opaque)
+// ---------------------------------------------------------------------------
+
+app.get('/api/health', (_req, res) => {
+ res.json({ status: 'ok' });
+});
+
+// ---------------------------------------------------------------------------
+// Posts / comments / reactions
+// ---------------------------------------------------------------------------
+
+app.post('/api/post', requireAuth, async (req, res) => {
+ const { text, includeBio } = req.body as { text?: string; includeBio?: boolean };
+ if (!text || text.length < 10) {
+ res.status(400).json({ error: 'text required (min 10 chars)' });
+ return;
+ }
+ try {
+ const final = composePost(text, { tagline: includeBio !== false });
+ const result = await postText(final);
+ res.json({ ok: true, id: result.id, posted: final });
+ } catch (err) {
+ res.status(500).json({ error: (err as Error).message });
+ }
+});
+
+app.post('/api/post/page', requireAuth, async (req, res) => {
+ const { text } = req.body as { text?: string };
+ if (!text) { res.status(400).json({ error: 'text required' }); return; }
+ try {
+ const final = composePost(text, { tagline: false });
+ const result = await postOnPage(final);
+ res.json({ ok: true, id: result.id });
+ } catch (err) {
+ res.status(500).json({ error: (err as Error).message });
+ }
+});
+
+app.post('/api/comment', requireAuth, async (req, res) => {
+ const { postUrn, text } = req.body as { postUrn?: string; text?: string };
+ if (!postUrn || !text) { res.status(400).json({ error: 'postUrn + text required' }); return; }
+ try {
+ const result = await commentOnPost(postUrn, text);
+ res.json({ ok: true, id: result.id });
+ } catch (err) {
+ res.status(500).json({ error: (err as Error).message });
+ }
+});
+
+app.post('/api/react', requireAuth, async (req, res) => {
+ const { postUrn, reaction } = req.body as { postUrn?: string; reaction?: string };
+ if (!postUrn) { res.status(400).json({ error: 'postUrn required' }); return; }
+ try {
+ await react(postUrn, (reaction as 'LIKE') || 'LIKE');
+ res.json({ ok: true });
+ } catch (err) {
+ res.status(500).json({ error: (err as Error).message });
+ }
+});
+
+// Read a template (lets MCP / Claude pull the always-include bio, etc.)
+app.get('/api/template/:name', requireAuth, (req, res) => {
+ const name = String(req.params.name);
+ const body = readTemplate(name);
+ if (!body) { res.status(404).json({ error: 'no template' }); return; }
+ res.json({ name, body });
+});
+
+// ---------------------------------------------------------------------------
+// Centralized error redaction
+// ---------------------------------------------------------------------------
+
+app.use((err: Error, req: Request, res: Response, _next: NextFunction) => {
+ console.error(`[${req.method} ${req.path}]`, err);
+ if (res.headersSent) return;
+ res.status(500).json({ error: 'internal_error' });
+});
+
+app.listen(PORT, '127.0.0.1', () => {
+ console.log(`linkedin-agent listening on http://127.0.0.1:${PORT}`);
+});
diff --git a/templates/STEVE_BIO.md b/templates/STEVE_BIO.md
new file mode 100644
index 0000000..ad4056a
--- /dev/null
+++ b/templates/STEVE_BIO.md
@@ -0,0 +1,59 @@
+<!--
+This file is the SINGLE SOURCE OF TRUTH for what gets woven into every
+LinkedIn post Steve's agent generates. Update it weekly.
+
+Format: 2-line "headline" + bullet list of active products + bullet list of
+active autonomous agents/skills. The composer (lib/compose.ts) reads this,
+extracts a rotating subset, and inlines it into post drafts. So the TONE of
+this file matters — write like it'll show up in front of a recruiter.
+-->
+
+# Steve Abrams · Founder, Designer Wallcoverings + Autonomous-Agent Studio
+
+I run a multi-vertical small-business network powered by ~50 autonomous
+AI agents I built and ship myself. Stack: Claude Code (CLI), Next.js 16,
+Postgres, pm2, launchd. Everything is loopback-first, deploys to Kamatera,
+nothing requires a team.
+
+## Active products (2026)
+
+- **Designer Wallcoverings** — Shopify Plus storefront, ~100K SKUs, 30+ vendor pipelines on autopilot
+- **Grant** — AI-powered fundraising tool for non-profits (real Grants.gov data, federal opportunities visualized as fish swimming toward close-date) [TICK 37 / Deadline River]
+- **Patty** — petition platform with AI-drafted compelling drafts + Kalshi prediction-market alliance discovery
+- **Freddy** — fundraising marketplace coordinator: citizens vote, AI matches non-profits to granters, $750+5% fee
+- **Site Factory + AI Factory + Visual Factory** — three-stage pipeline that ships dictionary-driven mockup sites overnight
+- **Lawyer / Doctor / Restaurant directories** — California Bar full scrape, LADBS permits, LA County health permits, all with three-mockup-per-record viewer pattern
+- **WhoLivedThere / ClaimMyAddress / BubbesBlock** — three-domain home-history platform, public-records first
+- **LA County Eats** — every restaurant in LA County with health grades + inspector notes
+- **Forza** — legal services storefront pattern
+- **Norma** — multi-platform social outreach (Bluesky / Facebook / Instagram / Twitter / Price scraping)
+
+## Autonomous agents shipped + still running
+
+- **YOLO loop** — overnight self-paced code-review + safe-local patch agent. ~50 ticks/night.
+- **claude-codex 8-way debate** — Claude + Codex + Qwen3 + Kimi + DeepSeek + GPT-OSS + Phi-4 + Mistral all reviewing the same code adversarially
+- **Four Horsemen UI orchestrator** — Paper.design + Figma + Canva + 21st.dev pipelines for component design
+- **Comms-compliance subagent** — CAN-SPAM/CCPA/TCPA/§17529.5/DNC enforced on every outbound communication
+- **Domain-name agent** — full lifecycle for 267-domain portfolio (GoDaddy/Cloudflare/Namecheap/Porkbun)
+- **process-hawk** — pm2 watchdog auto-restarting any of ~50 services
+- **Daily Codex Meeting** — 9am/5pm/Sun-6pm autonomous code review meeting
+- **Reviewed-demo-video skill** — 4-stage toggle pipeline (storyboard → click-through → avatar PIP → frame-sample QA)
+- **Session-debrief skill** — VC-grade investor MP4s with founder PIP + investor-lens cards
+
+## Hiring philosophy
+
+I don't hire. The agents are the team. If I find a gap I build an agent
+for it. If I find a person who can build agents better than mine, I'd
+rather collaborate than employ.
+
+## What I'm building next
+
+- @dw/gemini-flash shared package across wave-3 fleet
+- place-graph-core extraction across victorystays + malden-house + stayclaim
+- Federal Register API integration (replacing AI-fabricated grant news)
+- Per-project scoped PG users replacing shared dw_admin (Lane B architectural)
+
+---
+
+*Last updated: 2026-05-06. Weekly refresh cadence — if I shipped something
+new, this file gets a line. If a product retired, line goes to past tense.*
diff --git a/templates/comment-helpful.md b/templates/comment-helpful.md
new file mode 100644
index 0000000..6d11d61
--- /dev/null
+++ b/templates/comment-helpful.md
@@ -0,0 +1,22 @@
+# Comment template — substantive (not "great post!")
+
+NEVER:
+ - "Great post!" / "Love this!" / 100% / "this resonates"
+ - LinkedIn-bro emoji parade (🔥🚀💯)
+ - Restating the post
+
+ALWAYS one of:
+ 1. Add a specific data point the OP missed (with source if possible)
+ 2. Disagree with one specific claim + give your evidence
+ 3. Connect their post to a parallel domain they didn't mention
+ 4. Ask the ONE question that would make their post more useful
+
+Example seeds:
+
+ "The 'AI does X' framing misses that most teams don't have the orchestration to actually run it overnight. The model is solved. The loop isn't."
+
+ "Counter: I tried this exact pattern in [Y] for 6 months. It works at <10K records, breaks above 100K because of [specific failure mode]."
+
+ "Curious — did you measure [specific metric]? My data on [parallel] suggests the real bottleneck is [Z], not [what OP said]."
+
+Keep it ≤2 short paragraphs. No formatting tricks.
diff --git a/templates/connect-message.md b/templates/connect-message.md
new file mode 100644
index 0000000..795965e
--- /dev/null
+++ b/templates/connect-message.md
@@ -0,0 +1,25 @@
+# Connection-request opener template
+
+LinkedIn allows ~280 chars for connection-request notes. Make every one count.
+
+NEVER:
+ - "Hi {name}, I'd love to connect!" — pure noise
+ - "I came across your profile" — formulaic
+ - Pitch in the first message
+
+ALWAYS:
+ 1. Specific reason you're connecting (1 thing about THEM, not you)
+ 2. Honest framing of what you want (1 line)
+ 3. No CTA other than "open to a chat if useful"
+
+Example structure:
+
+ "{name} — I saw your post on [specific topic]. The point about [specific quote] mapped to my work on [my parallel project]. I run [1-line bio]. Open to a chat sometime — no pitch, just shop talk."
+
+Variant for second-degree (someone you've never crossed paths with):
+
+ "{name} — we share [N] connections in [domain]. I'm building [1-liner] and you've been working on [specific thing of theirs]. If a 15-min call sometime makes sense, happy to share notes."
+
+Reminder: LinkedIn rate-limits at ~100 sent connection requests per WEEK
+for non-Premium accounts. Do not bulk-send. Use the comms-compliance
+subagent first if any of these are cold.
diff --git a/templates/post-agent-spotlight.md b/templates/post-agent-spotlight.md
new file mode 100644
index 0000000..c28ec1d
--- /dev/null
+++ b/templates/post-agent-spotlight.md
@@ -0,0 +1,21 @@
+# Post template — spotlight one of Steve's autonomous agents
+
+Goal: make THE AGENT the protagonist, not Steve.
+
+Structure:
+ L1: the agent's name + 1-line job
+ L2-3: a real thing it did this week (with timestamps if dramatic)
+ L4: what was hard about building it
+ L5: what's surprising in the data after running it
+ L6: open invitation to talk shop
+
+Example:
+ "Norma is my email-outreach agent. She runs 5 social platforms (FB/IG/Twitter/Bluesky + price scrape) on autopilot."
+ ""
+ "Last week she autonomously detected a vendor price drop on a competitor product, drafted a counter-positioning post, and queued it for my review. I approved with one click."
+ ""
+ "Hardest part wasn't the LLM — it was the legal layer. Comms-compliance subagent now scrubs every outbound against CAN-SPAM, CCPA, TCPA, federal+state DNC, and Utah CPR. Fail-closed if any list is stale."
+ ""
+ "Surprising data: 23% of my "ready to send" drafts get blocked by compliance. Means I was about to mess up. Cheap insurance."
+ ""
+ "If you're building outreach automation and not running compliance at the gate, we should talk."
diff --git a/templates/post-product-update.md b/templates/post-product-update.md
new file mode 100644
index 0000000..38851c5
--- /dev/null
+++ b/templates/post-product-update.md
@@ -0,0 +1,20 @@
+# Post template — product update / changelog post
+
+For: Designer Wallcoverings · La County Eats · Patty · etc.
+
+Structure:
+ L1: what changed (concrete) — "X now does Y"
+ L2-3: why it matters to the user — specific use case
+ L4: link or screenshot
+ L5: what's next (single bullet)
+
+Avoid: changelog bullet lists (boring). Convert to a 5-line story.
+
+Example:
+ "Designer Wallcoverings now ships every new SKU with a real room-render in 4 environments — bedroom, dining, office, hallway."
+ ""
+ "Designers were getting wallpaper samples and squinting at swatches in a vacuum. Now every PDP shows the pattern in actual rooms at viewing distance."
+ ""
+ "https://designerwallcoverings.com/products/<example-sku>"
+ ""
+ "Next: photoreal commercial settings (hotel lobby, healthcare waiting room)."
diff --git a/templates/post-skill-launch.md b/templates/post-skill-launch.md
new file mode 100644
index 0000000..1ebec61
--- /dev/null
+++ b/templates/post-skill-launch.md
@@ -0,0 +1,19 @@
+# Post template — "I just shipped" / skill or product launch
+
+Hook (line 1): one-line punch about WHO benefits + WHAT changes.
+ Bad: "I built a thing."
+ Good: "Non-profits trying to find federal grants now see them as fish swimming toward their close-date."
+
+Build context (2-3 lines): what was wrong with the old way.
+ Most grant tools = AI fabricates "plausible URLs at outlets." Real money is on grants.gov but the UX is a 1990s search form.
+
+What you did (1-2 lines): the move, not the architecture.
+ Pulled live grants.gov data into Grant. Each opportunity is a fish; X = time-to-deadline, Y = $, color = CFDA category, urgency = glow + speed.
+
+Concrete proof (1 line + link): screenshot URL or numbers.
+ 551 real federal opportunities matching keyword "education" — visualized live.
+
+Close (1 line): invite, not pitch.
+ If you run a non-profit and want a private demo, DM me.
+
+Tone: builder, not marketer. No emojis. No "🚀". No "thrilled to announce."
diff --git a/templates/post-thought-leadership.md b/templates/post-thought-leadership.md
new file mode 100644
index 0000000..1e8f08a
--- /dev/null
+++ b/templates/post-thought-leadership.md
@@ -0,0 +1,21 @@
+# Post template — opinion + insight format
+
+Structure:
+ L1: contrarian observation (something most people in the space believe)
+ L2-3: why the conventional wisdom is wrong (specific evidence)
+ L4: the better lens
+ L5: one tactical implication
+ L6: invitation for disagreement
+
+Example:
+ "Everyone hiring 'AI engineers' is wasting money."
+ ""
+ "I run 50+ autonomous agents across 5 verticals. The ratio of "people who can use Claude Code" to "people who can architect compound-effect workflows" is maybe 50:1."
+ ""
+ "The leverage isn't in the model. It's in the loop — Plan → Work → Review → Compound. Most teams skip Review and Compound."
+ ""
+ "If you're hiring, ask candidates to ship something autonomously overnight. The work product tells you everything."
+ ""
+ "Disagree? I'm listening."
+
+No first-person hero arc. No "I learned this the hard way." Just the point.
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..86a972f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "lib": ["ES2022"],
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowImportingTsExtensions": true
+ },
+ "include": ["src"]
+}
(oldest)
·
back to Linkedin Agent
·
chore: macstudio3 migration — reconcile from mac2 + repoint f999813 →