diff --git a/.env.example b/.env.example
index f7514a4fd..24a70ac61 100644
--- a/.env.example
+++ b/.env.example
@@ -67,8 +67,8 @@ WORKER_DISALLOWED_DOMAINS=
# Public gateway URL (required for OAuth callbacks)
PUBLIC_GATEWAY_URL=https://community.lobu.ai
-# Owletto MCP URL (enables OAuth login, memory, and integrations)
-AUTH_MCP_URL=https://owletto.com/mcp
+# Owletto MCP URL (enables memory and integrations)
+MEMORY_URL=https://owletto.com/mcp
# System skills registry URL (points to config/system-skills.json)
# LOBU_SYSTEM_SKILLS_URL=file:///app/config/system-skills.json
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3bd38d481..8b06c04a6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -73,8 +73,5 @@ jobs:
- name: Build core package first
run: cd packages/core && bun run build
- - name: Generate gateway CSS
- run: cd packages/gateway && bun run generate:css
-
- name: Run TypeScript type check
run: bun run typecheck
diff --git a/README.md b/README.md
index c4e6cc843..7d89387cb 100644
--- a/README.md
+++ b/README.md
@@ -82,14 +82,14 @@ Every Lobu agent comes equipped with a suite of tools for autonomous execution a
| Feature | Description | Built-in Tools |
| :--- | :--- | :--- |
| **Autonomous Scheduling** | Schedule one-time or recurring execution via cron. | `ScheduleReminder`, `ListReminders`, `CancelReminder` |
-| **Human-in-the-Loop** | Pause for user input via buttons and resume when answered. | `AskUserQuestion`, `Configure` |
+| **Human-in-the-Loop** | Pause for user input via buttons and resume when answered. | `AskUserQuestion` |
| **Full Linux Toolbox** | Sandboxed shell access, file editing, and advanced search. | `bash`, `read`, `write`, `edit`, `grep`, `find`, `ls` |
| **Conversation Context** | Pull earlier thread messages when the user references prior work. | `GetChannelHistory` |
| **File & Media Delivery** | Share reports, charts, or generated voice messages. | `UploadUserFile`, `GenerateAudio` |
-| **Self-Expansion** | Search and dynamically install new skills or MCP servers. | `SearchSkills`, `InstallSkill` |
+| **Skills** | Extend agent capabilities via skills configured in lobu.toml or the admin settings page. | `lobu.toml`, Settings UI |
| **Connected APIs** | Access third-party APIs (GitHub, Google, etc.) through Owletto MCP tools with managed OAuth. | MCP tools via Owletto |
| **Managed MCP Proxy** | Securely connect to any MCP server with secret injection. | [MCP Proxy](docs/SECURITY.md#credentials) |
-| **Advanced Capabilities** | Extend agent abilities with web browsing, headless UI interaction, and specialized utilities via Nix packages or external MCP servers. | `bash` (Nix), `SearchSkills`, `InstallSkill` (MCP) |
+| **Advanced Capabilities** | Extend agent abilities with web browsing, headless UI interaction, and specialized utilities via Nix packages or external MCP servers. | `bash` (Nix), MCP servers |
### Popular MCP Integrations
Workers access third-party APIs through MCP servers. OAuth and credential management is handled by Owletto:
diff --git a/bun.lock b/bun.lock
index 3901cec76..defa70164 100644
--- a/bun.lock
+++ b/bun.lock
@@ -29,7 +29,6 @@
"open": "^10.1.0",
"ora": "^8.0.1",
"smol-toml": "^1.3.1",
- "yaml": "^2.3.4",
"zod": "^3.24.0",
},
"devDependencies": {
@@ -62,6 +61,7 @@
"version": "1.0.0",
"dependencies": {
"@chat-adapter/discord": "^4",
+ "@chat-adapter/gchat": "^4",
"@chat-adapter/slack": "^4",
"@chat-adapter/state-ioredis": "^4",
"@chat-adapter/teams": "^4",
@@ -81,23 +81,13 @@
"dotenv": "^17.2.1",
"hono": "^4.11.7",
"ioredis": "^5.4.1",
- "jose": "^6.0.11",
- "jsonwebtoken": "^9.0.2",
- "marked": "^12.0.0",
- "pino": "^9.1.0",
+ "smol-toml": "^1.3.1",
"yaml": "^2.7.1",
"zod": "^4.1.12",
},
"devDependencies": {
- "@preact/signals": "^2.0.0",
- "@tailwindcss/typography": "^0.5.19",
- "@tanstack/react-virtual": "^3.11.2",
"@types/dockerode": "^3.3.29",
- "@types/express": "^4.17.21",
- "@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20.0.0",
- "esbuild": "^0.24.0",
- "preact": "^10.25.0",
"typescript": "^5.8.3",
},
},
@@ -109,7 +99,6 @@
"@astrojs/sitemap": "^3.7.0",
"@astrojs/starlight": "^0.37.6",
"@preact/signals": "^1.3.1",
- "@scalar/astro": "^0.2.0",
"astro": "^5.18.0",
"preact": "^10.25.4",
"zod": "^3.25.76",
@@ -133,12 +122,12 @@
"@mariozechner/pi-agent-core": "^0.51.6",
"@mariozechner/pi-ai": "^0.51.6",
"@mariozechner/pi-coding-agent": "^0.51.6",
- "@modelcontextprotocol/sdk": "^1.17.4",
"@sentry/node": "^10.6.0",
"@sinclair/typebox": "^0.34.33",
"form-data": "^4.0.4",
"hono": "^4.11.7",
"just-bash": "^2.12.8",
+ "owletto": "^1.4.0",
"zod": "^3.24.1",
},
"devDependencies": {
@@ -342,6 +331,8 @@
"@chat-adapter/discord": ["@chat-adapter/discord@4.20.0", "", { "dependencies": { "@chat-adapter/shared": "4.20.0", "chat": "4.20.0", "discord-api-types": "^0.37.119", "discord-interactions": "^4.4.0", "discord.js": "^14.25.1" } }, "sha512-iCHoxOWmHJx3Z3haxZQJwd9oq2fN/mK+oieiJ0ttL1vuJ/BDi2tnvC2hXP3X+kS1yb2Iy5sK476HeAlMu9vwzw=="],
+ "@chat-adapter/gchat": ["@chat-adapter/gchat@4.23.0", "", { "dependencies": { "@chat-adapter/shared": "4.23.0", "@googleapis/chat": "^44.6.0", "@googleapis/workspaceevents": "^9.1.0", "chat": "4.23.0" } }, "sha512-4IQxu3bHDkCLLaBldB8jN68AGgJhMN9pNIovUWWzL3HRQ4yHADna8B3PsCQEzy2KjPXV85aG7/+5C+nm7P4rCA=="],
+
"@chat-adapter/shared": ["@chat-adapter/shared@4.20.0", "", { "dependencies": { "chat": "4.20.0" } }, "sha512-PaN3TADZUswD5VNV5qMS5M316PP9hp14lGHNa5SB8s7LN5PGXuXUroO0e7rjyo18i7jNKsalJUjpD/uoEg0u3Q=="],
"@chat-adapter/slack": ["@chat-adapter/slack@4.20.0", "", { "dependencies": { "@chat-adapter/shared": "4.20.0", "@slack/web-api": "^7.14.0", "chat": "4.20.0" } }, "sha512-pZruK/ndyXJcgwTQ7S9MjB31TE80REuMtcGGlZCX/iaPSZ2QYDhz2493MvtBiy2DY4dyNOk2eihsf1r9JzzhkQ=="],
@@ -378,57 +369,57 @@
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
- "@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
- "@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
"@expressive-code/core": ["@expressive-code/core@0.41.7", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-ck92uZYZ9Wba2zxkiZLsZGi9N54pMSAVdrI9uW3Oo9AtLglD5RmrdTwbYPCT2S/jC36JGB2i+pnQtBm/Ib2+dg=="],
@@ -440,6 +431,10 @@
"@google/genai": ["@google/genai@1.34.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.24.0" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-vu53UMPvjmb7PGzlYu6Tzxso8Dfhn+a7eQFaS2uNemVtDZKwzSpJ5+ikqBbXplF7RGB1STcVDqCkPvquiwb2sw=="],
+ "@googleapis/chat": ["@googleapis/chat@44.6.0", "", { "dependencies": { "googleapis-common": "^8.0.0" } }, "sha512-Bnqzev/bSTXSbE0/N2WS4Stnleo8j9bJJ1LkCBk1fXQnehcArVMv7q543rzPYU6MJql4D34On6diNGAuYtI9xQ=="],
+
+ "@googleapis/workspaceevents": ["@googleapis/workspaceevents@9.1.0", "", { "dependencies": { "googleapis-common": "^8.0.0" } }, "sha512-aJiMrTi/YyUUaaTO0tnhTHDYU+N9CTD3l3FSfe0yzEHQl7DEc+1LISgdK1o2nurvCtguBEumify5kTkr6Cg5eA=="],
+
"@grpc/grpc-js": ["@grpc/grpc-js@1.13.4", "", { "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg=="],
"@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="],
@@ -766,13 +761,11 @@
"@pagefind/windows-x64": ["@pagefind/windows-x64@1.4.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g=="],
- "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
-
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@preact/preset-vite": ["@preact/preset-vite@2.10.3", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@prefresh/vite": "^2.4.11", "@rollup/pluginutils": "^5.0.0", "babel-plugin-transform-hook-names": "^1.0.2", "debug": "^4.4.3", "picocolors": "^1.1.1", "vite-prerender-plugin": "^0.5.8" }, "peerDependencies": { "@babel/core": "7.x", "vite": "2.x || 3.x || 4.x || 5.x || 6.x || 7.x" } }, "sha512-1SiS+vFItpkNdBs7q585PSAIln0wBeBdcpJYbzPs1qipsb/FssnkUioNXuRsb8ZnU8YEQHr+3v8+/mzWSnTQmg=="],
- "@preact/signals": ["@preact/signals@2.8.1", "", { "dependencies": { "@preact/signals-core": "^1.13.0" }, "peerDependencies": { "preact": ">= 10.25.0 || >=11.0.0-0" } }, "sha512-wX6U0SpcCukZTJBs5ChljvBZb3XmYzA5gd4vKHgX8wZZKaQCo2WHtmThdLx+mcVvlBa5u3XShC7ffbETJD4BiQ=="],
+ "@preact/signals": ["@preact/signals@1.3.4", "", { "dependencies": { "@preact/signals-core": "^1.7.0" }, "peerDependencies": { "preact": "10.x" } }, "sha512-TPMkStdT0QpSc8FpB63aOwXoSiZyIrPsP9Uj347KopdS6olZdAYeeird/5FZv/M1Yc1ge5qstub2o8VDbvkT4g=="],
"@preact/signals-core": ["@preact/signals-core@1.13.0", "", {}, "sha512-slT6XeTCAbdql61GVLlGU4x7XHI7kCZV5Um5uhE4zLX4ApgiiXc0UYFvVOKq06xcovzp7p+61l68oPi563ARKg=="],
@@ -864,8 +857,6 @@
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
- "@scalar/astro": ["@scalar/astro@0.2.0", "", { "dependencies": { "@scalar/core": "0.4.0" }, "peerDependencies": { "astro": "^4.0.0 || ^5.0.0" } }, "sha512-OO93Ou3EkXqz8jUvAXR9CXZDH/teZGaUJDR627VBIMJtMIMDPNC7sT+8pIL0Mu8WZ6aXAYlP+KkXlvOFltRI8g=="],
-
"@scalar/core": ["@scalar/core@0.3.36", "", { "dependencies": { "@scalar/types": "0.6.1" } }, "sha512-gdgoF/XP2RkvhqGlI0l2MWTR/2522GPdaiQkWwS348Po8oCkJy2npxFuZbC2jtp6DIrWDrOD6qYgHssyzMmcrA=="],
"@scalar/helpers": ["@scalar/helpers@0.2.10", "", {}, "sha512-VS32setBEAGY9JifuDZKHIq8SUCUWLEfL1V+h3s5V4wcmE8OZVkzaJemsMq/YAM9e7gb9ZbkvJLL4zzEvPSrVg=="],
@@ -1026,14 +1017,8 @@
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="],
- "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="],
-
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="],
- "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.19", "", { "dependencies": { "@tanstack/virtual-core": "3.13.19" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-KzwmU1IbE0IvCZSm6OXkS+kRdrgW2c2P3Ho3NC+zZXWK6oObv/L+lcV/2VuJ+snVESRlMJ+w/fg4WXI/JzoNGQ=="],
-
- "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.19", "", {}, "sha512-/BMP7kNhzKOd7wnDeB8NrIRNLwkf5AhCYCvtfZV2GXWbBieFm/el0n6LOAXlTi6ZwHICSNnQcIxRCWHrLzDY+g=="],
-
"@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
@@ -1042,8 +1027,6 @@
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
- "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
-
"@types/bun": ["@types/bun@1.2.11", "", { "dependencies": { "bun-types": "1.2.11" } }, "sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw=="],
"@types/caseless": ["@types/caseless@0.12.5", "", {}, "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg=="],
@@ -1060,26 +1043,18 @@
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
- "@types/express": ["@types/express@4.17.23", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ=="],
-
- "@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.6", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A=="],
-
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
- "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
-
"@types/inquirer": ["@types/inquirer@9.0.9", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw=="],
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
- "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="],
+ "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw=="],
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
"@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
- "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
-
"@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
@@ -1094,10 +1069,6 @@
"@types/pg-pool": ["@types/pg-pool@2.0.6", "", { "dependencies": { "@types/pg": "*" } }, "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ=="],
- "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
-
- "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
-
"@types/request": ["@types/request@2.48.12", "", { "dependencies": { "@types/caseless": "*", "@types/node": "*", "@types/tough-cookie": "*", "form-data": "^2.5.0" } }, "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw=="],
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
@@ -1106,10 +1077,6 @@
"@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
- "@types/send": ["@types/send@0.17.5", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w=="],
-
- "@types/serve-static": ["@types/serve-static@1.15.8", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "*" } }, "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg=="],
-
"@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="],
"@types/ssh2": ["@types/ssh2@1.15.5", "", { "dependencies": { "@types/node": "^18.11.18" } }, "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ=="],
@@ -1192,8 +1159,6 @@
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
- "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
-
"aws-sign2": ["aws-sign2@0.7.0", "", {}, "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="],
"aws4": ["aws4@1.13.2", "", {}, "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="],
@@ -1314,6 +1279,8 @@
"ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
+ "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
+
"cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
@@ -1360,6 +1327,8 @@
"compressjs": ["compressjs@1.0.3", "", { "dependencies": { "amdefine": "~1.0.0", "commander": "~2.8.1" }, "bin": { "compressjs": "./bin/compressjs" } }, "sha512-jpKJjBTretQACTGLNuvnozP1JdP2ZLrjdGdBgk/tz1VfXlUcBhhSZW6vEsuThmeot/yjvSrPQKEgfF3X2Lpi8Q=="],
+ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
+
"constantinople": ["constantinople@4.0.1", "", { "dependencies": { "@babel/parser": "^7.6.0", "@babel/types": "^7.6.1" } }, "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw=="],
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
@@ -1518,7 +1487,7 @@
"esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
- "esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
+ "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@@ -1668,6 +1637,8 @@
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
+ "googleapis-common": ["googleapis-common@8.0.1", "", { "dependencies": { "extend": "^3.0.2", "gaxios": "^7.0.0-rc.4", "google-auth-library": "^10.1.0", "qs": "^6.7.0", "url-template": "^2.0.8" } }, "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A=="],
+
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
@@ -1818,7 +1789,7 @@
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
- "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
+ "is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="],
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
@@ -1840,7 +1811,7 @@
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
- "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
+ "jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
"js-stringify": ["js-stringify@1.0.2", "", {}, "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g=="],
@@ -2188,8 +2159,6 @@
"oidc-token-hash": ["oidc-token-hash@5.1.1", "", {}, "sha512-D7EmwxJV6DsEB6vOFLrBM2OzsVgQzgPWyHlV2OOAVj772n+WTXpudC9e9u5BVKQnYwaD30Ivhi9b+4UeBcGu9g=="],
- "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
-
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
@@ -2214,6 +2183,8 @@
"ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
+ "owletto": ["owletto@1.4.0", "", { "dependencies": { "citty": "^0.1.6" }, "peerDependencies": { "@owletto/sdk": "^1.4.0", "@owletto/worker": "^1.4.0" }, "optionalPeers": ["@owletto/sdk", "@owletto/worker"], "bin": { "owletto": "dist/bin.js" } }, "sha512-49TKRly3qnssf+GPbTiZVndGKpoB0gwTxjCNPII0AUl1Yj3rPtOX+5XEBYdzHTr23rBYSPLHpHa1O3NdpAMj/g=="],
+
"oxc-resolver": ["oxc-resolver@11.11.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.11.1", "@oxc-resolver/binding-android-arm64": "11.11.1", "@oxc-resolver/binding-darwin-arm64": "11.11.1", "@oxc-resolver/binding-darwin-x64": "11.11.1", "@oxc-resolver/binding-freebsd-x64": "11.11.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.11.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.11.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.11.1", "@oxc-resolver/binding-linux-arm64-musl": "11.11.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.11.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.11.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.11.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.11.1", "@oxc-resolver/binding-linux-x64-gnu": "11.11.1", "@oxc-resolver/binding-linux-x64-musl": "11.11.1", "@oxc-resolver/binding-wasm32-wasi": "11.11.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.11.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.11.1", "@oxc-resolver/binding-win32-x64-msvc": "11.11.1" } }, "sha512-4Z86u4xQAxl2IC1OAAdHjk/S9GNbE2ewALQVOpBk9F8NkfqXlglY6R2ts+HEgY/Q3T9m/H8W0G4id71muw/Nng=="],
"p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="],
@@ -2272,19 +2243,13 @@
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
- "pino": ["pino@9.14.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w=="],
-
- "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
-
- "pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="],
-
"pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="],
- "postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
+ "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
@@ -2302,8 +2267,6 @@
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
- "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
-
"promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
@@ -2354,8 +2317,6 @@
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
- "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
-
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
@@ -2366,16 +2327,10 @@
"re2js": ["re2js@1.2.2", "", {}, "sha512-xvy4uuynAZWg9SuHbg0lgQncOuK6wssLmbHs8L8+YRbWLKY8Pe1avaHjNaFLOjErq8Oh0HvwQRWqIOCRL7uDDw=="],
- "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
-
- "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
-
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
- "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
-
"recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="],
"recma-jsx": ["recma-jsx@1.0.1", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="],
@@ -2478,8 +2433,6 @@
"sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="],
- "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
-
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
@@ -2530,8 +2483,6 @@
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
- "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
-
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
@@ -2542,8 +2493,6 @@
"split-ca": ["split-ca@1.0.1", "", {}, "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ=="],
- "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
-
"sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
"sql.js": ["sql.js@1.14.1", "", {}, "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A=="],
@@ -2616,8 +2565,6 @@
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
- "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="],
-
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
@@ -2710,6 +2657,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+ "url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="],
+
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
@@ -2800,6 +2749,8 @@
"@astrojs/markdown-remark/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
+ "@astrojs/preact/@preact/signals": ["@preact/signals@2.8.1", "", { "dependencies": { "@preact/signals-core": "^1.13.0" }, "peerDependencies": { "preact": ">= 10.25.0 || >=11.0.0-0" } }, "sha512-wX6U0SpcCukZTJBs5ChljvBZb3XmYzA5gd4vKHgX8wZZKaQCo2WHtmThdLx+mcVvlBa5u3XShC7ffbETJD4BiQ=="],
+
"@astrojs/starlight/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
@@ -2814,6 +2765,10 @@
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+ "@chat-adapter/gchat/@chat-adapter/shared": ["@chat-adapter/shared@4.23.0", "", { "dependencies": { "chat": "4.23.0" } }, "sha512-IDHgrAi3Y8Qptl1kd8zmM6jxWX+lu0cq/uZiURv8jONufWHrVwVr19pN0YK52ASnRlLZkcaNYXoa+mLFGQ2tlw=="],
+
+ "@chat-adapter/gchat/chat": ["chat@4.23.0", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "remend": "^1.2.1", "unified": "^11.0.5" } }, "sha512-Gmw8yyDrrH9Vxs+TfxF7FoTENrCzJV4T0FiAh7APGcQPhMMYAhrpq66PKj8azhkUSEyaen8ujbneogoJWiY8vg=="],
+
"@discordjs/builders/discord-api-types": ["discord-api-types@0.38.42", "", {}, "sha512-qs1kya7S84r5RR8m9kgttywGrmmoHaRifU1askAoi+wkoSefLpZP6aGXusjNw5b0jD3zOg3LTwUa3Tf2iHIceQ=="],
"@discordjs/formatters/discord-api-types": ["discord-api-types@0.38.42", "", {}, "sha512-qs1kya7S84r5RR8m9kgttywGrmmoHaRifU1askAoi+wkoSefLpZP6aGXusjNw5b0jD3zOg3LTwUa3Tf2iHIceQ=="],
@@ -2842,12 +2797,8 @@
"@lobu/gateway/commander": ["commander@14.0.1", "", {}, "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A=="],
- "@lobu/gateway/marked": ["marked@12.0.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q=="],
-
"@lobu/gateway/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
- "@lobu/landing/@preact/signals": ["@preact/signals@1.3.4", "", { "dependencies": { "@preact/signals-core": "^1.7.0" }, "peerDependencies": { "preact": "10.x" } }, "sha512-TPMkStdT0QpSc8FpB63aOwXoSiZyIrPsP9Uj347KopdS6olZdAYeeird/5FZv/M1Yc1ge5qstub2o8VDbvkT4g=="],
-
"@mariozechner/pi-ai/zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="],
"@mariozechner/pi-coding-agent/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
@@ -2902,8 +2853,6 @@
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
- "@scalar/astro/@scalar/core": ["@scalar/core@0.4.0", "", { "dependencies": { "@scalar/types": "0.7.0" } }, "sha512-Zcl+V8oBxb0S7vR+Nro8J53GD/w/kSjuyX0UoT3r1sn0bUM3Buf4Ob44n3CSfluQzxmiMuZxfOROHqa9F+ckbg=="],
-
"@scalar/types/type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="],
"@scalar/types/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
@@ -2934,16 +2883,12 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
- "@types/body-parser/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
-
"@types/connect/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
"@types/docker-modem/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
"@types/dockerode/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
- "@types/express-serve-static-core/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
-
"@types/jsonwebtoken/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
"@types/mysql/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
@@ -2956,10 +2901,6 @@
"@types/sax/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
- "@types/send/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
-
- "@types/serve-static/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
-
"@types/ssh2/@types/node": ["@types/node@18.19.123", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg=="],
"@types/tedious/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
@@ -2972,8 +2913,6 @@
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
- "astro/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
-
"astro/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"babel-walk/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
@@ -2996,8 +2935,6 @@
"botframework-connector/@azure/msal-node": ["@azure/msal-node@2.16.3", "", { "dependencies": { "@azure/msal-common": "14.16.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-CO+SE4weOsfJf+C5LM8argzvotrXw252/ZU6SM2Tz63fEblhH1uuVaaO4ISYFuN4Q6BhTo7I3qIdi8ydUQCqhw=="],
- "botframework-connector/@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw=="],
-
"botframework-connector/axios": ["axios@1.12.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw=="],
"botframework-schema/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
@@ -3066,6 +3003,8 @@
"google-auth-library/jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
+ "googleapis-common/qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
+
"gtoken/jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
"har-validator/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
@@ -3082,8 +3021,6 @@
"jsonwebtoken/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
- "jstransformer/is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="],
-
"just-bash/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
"knip/smol-toml": ["smol-toml@1.4.2", "", {}, "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g=="],
@@ -3106,8 +3043,6 @@
"openapi3-ts/yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
- "openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
-
"ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"ora/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
@@ -3120,8 +3055,6 @@
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
- "postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
-
"proper-lockfile/retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
"protobufjs/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
@@ -3146,6 +3079,8 @@
"router/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
+ "router/is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
+
"send/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"send/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
@@ -3200,8 +3135,6 @@
"@prisma/instrumentation/@opentelemetry/instrumentation/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
- "@scalar/astro/@scalar/core/@scalar/types": ["@scalar/types@0.7.0", "", { "dependencies": { "@scalar/helpers": "0.3.0", "nanoid": "^5.1.6", "type-fest": "^5.3.1", "zod": "^4.3.5" } }, "sha512-IkG62M4ztmqkYNVhLpcswBojlQctbXLdkDa3UFsY8FfT7yfZ2LppjptycW9tWjD09ZQb4QAZ070FAUHmFRIS7w=="],
-
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
"@slack/web-api/p-queue/p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
@@ -3210,56 +3143,6 @@
"accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
- "astro/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
-
- "astro/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
-
- "astro/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
-
- "astro/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
-
- "astro/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
-
- "astro/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
-
- "astro/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
-
- "astro/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
-
- "astro/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
-
- "astro/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
-
- "astro/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
-
- "astro/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
-
- "astro/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
-
- "astro/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
-
- "astro/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
-
- "astro/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
-
- "astro/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
-
- "astro/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
-
- "astro/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
-
- "astro/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
-
- "astro/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
-
- "astro/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
-
- "astro/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
-
- "astro/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
-
- "astro/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
-
"botbuilder/@azure/msal-node/@azure/msal-common": ["@azure/msal-common@14.16.1", "", {}, "sha512-nyxsA6NA4SVKh5YyRpbSXiMr7oQbwark7JU9LMeg6tJYTSPyAGkdx61wPT4gyxZfxlSxMMEyAsWaubBlNyIa1w=="],
"botbuilder/@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
@@ -3272,8 +3155,6 @@
"botframework-connector/@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
- "botframework-connector/@types/jsonwebtoken/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="],
-
"botframework-connector/axios/follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
"botframework-connector/axios/form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
@@ -3386,12 +3267,6 @@
"@grpc/proto-loader/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
- "@scalar/astro/@scalar/core/@scalar/types/@scalar/helpers": ["@scalar/helpers@0.3.0", "", {}, "sha512-lhQdehgighJC+PiSTJbbggM/SM3UydcRQil6Cfp/M4l539qklIh35pt4eh1+H+5Esa03gHnJwhTHF3TwglSOJw=="],
-
- "@scalar/astro/@scalar/core/@scalar/types/type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="],
-
- "@scalar/astro/@scalar/core/@scalar/types/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
-
"inquirer/ora/cli-cursor/restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
diff --git a/charts/lobu/templates/gateway-deployment.yaml b/charts/lobu/templates/gateway-deployment.yaml
index 39653661a..05efcca79 100644
--- a/charts/lobu/templates/gateway-deployment.yaml
+++ b/charts/lobu/templates/gateway-deployment.yaml
@@ -219,10 +219,10 @@ spec:
value: "{{ .Values.gateway.anthropicProxy.baseUrl }}"
{{- end }}
- # External auth (Owletto OIDC)
- {{- if .Values.gateway.config.authMcpUrl }}
- - name: AUTH_MCP_URL
- value: "{{ .Values.gateway.config.authMcpUrl }}"
+ # Owletto memory URL
+ {{- if .Values.gateway.config.memoryUrl }}
+ - name: MEMORY_URL
+ value: "{{ .Values.gateway.config.memoryUrl }}"
{{- end }}
# Admin password (direct value override, not in sealed secrets)
diff --git a/charts/lobu/values-community.yaml b/charts/lobu/values-community.yaml
index d39c3f1cf..28bed2982 100644
--- a/charts/lobu/values-community.yaml
+++ b/charts/lobu/values-community.yaml
@@ -130,7 +130,7 @@ gateway:
config:
nodeEnv: "production"
logLevel: "INFO"
- authMcpUrl: "https://owletto.com/mcp"
+ memoryUrl: "https://owletto.com/mcp"
anthropicProxy:
enabled: true
baseUrl: ""
diff --git a/charts/lobu/values.yaml b/charts/lobu/values.yaml
index d74609e14..2e189fb9e 100644
--- a/charts/lobu/values.yaml
+++ b/charts/lobu/values.yaml
@@ -17,7 +17,6 @@ secrets:
encryptionKey: "" # Optional: 32-char key for AES-GCM at-rest encryption
anthropicApiKey: "" # Optional: Anthropic API key (recommended for prod runtime)
claudeCodeOAuthToken: "" # Required: Claude Code OAuth token
- whatsappCredentials: "" # Optional: Base64-encoded WhatsApp credentials JSON
telegramBotToken: "" # Optional: Telegram bot token (enables Telegram when set)
# Bitnami Sealed Secrets - for production use with Git-stored encrypted secrets
@@ -176,7 +175,7 @@ gateway:
config:
nodeEnv: "development"
logLevel: "DEBUG"
- authMcpUrl: "" # Optional: Owletto MCP URL for OAuth login, memory, and integrations (e.g., https://owletto.com/mcp)
+ memoryUrl: "" # Optional: Owletto MCP URL for memory and integrations (e.g., https://owletto.com/mcp)
# Anthropic API proxy configuration
anthropicProxy:
enabled: true # Set to true to enable the proxy
diff --git a/config/dependency-cruiser.config.cjs b/config/dependency-cruiser.config.cjs
deleted file mode 100644
index 736c1219b..000000000
--- a/config/dependency-cruiser.config.cjs
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Dependency-cruiser configuration to keep our workspace boundaries honest.
- * See https://github.com/sverweij/dependency-cruiser for details.
- * @type {import('dependency-cruiser').IConfiguration}
- */
-module.exports = {
- extends: ["dependency-cruiser/configs/recommended-warn-only"],
- forbidden: [
- {
- name: "core-must-stay-isolated",
- comment:
- "Core is the shared foundation; it must not depend on package-specific code.",
- severity: "error",
- from: {
- path: "^packages/core/src",
- },
- to: {
- path: "^packages/(gateway|worker|github)/src",
- },
- },
- {
- name: "worker-must-not-know-platforms",
- comment:
- "Worker stays platform-agnostic and should only rely on @lobu/core.",
- severity: "error",
- from: {
- path: "^packages/worker/src",
- },
- to: {
- path: "^packages/(gateway|github)/src",
- },
- },
- {
- name: "gateway-must-not-import-worker",
- comment:
- "Gateway keeps platform adapters separate; shared logic lives in @lobu/core.",
- severity: "error",
- from: {
- path: "^packages/gateway/src",
- },
- to: {
- path: "^packages/worker/src",
- },
- },
- ],
- options: {
- tsConfig: {
- fileName: "./tsconfig.json",
- },
- tsPreCompilationDeps: true,
- doNotFollow: {
- path: "node_modules",
- },
- exclude: {
- path: "node_modules|/dist/|__tests__|__mocks__|\\.(spec|test)\\.(ts|tsx)$|/docs/|integration-tests|examples|workspaces|charts|bin",
- },
- enhancedResolveOptions: {
- extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"],
- conditionNames: ["import", "default"],
- exportsFields: ["exports"],
- },
- },
-};
diff --git a/config/knip.ts b/config/knip.ts
deleted file mode 100644
index c5df728c9..000000000
--- a/config/knip.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { KnipConfig } from "knip";
-
-const config: KnipConfig = {
- entry: [
- "packages/*/src/**/*.{ts,tsx,js,jsx,cjs,mjs}",
- "packages/*/bin/**/*.{js,cjs,mjs}",
- ],
- ignore: [
- "**/dist/**",
- "charts/**",
- "integration-tests/**",
- "config/dependency-cruiser.config.cjs",
- "workspaces/**",
- "my-app/**",
- "docker/docker-compose*.yml",
- "scripts/**",
- ],
- ignoreBinaries: ["helm"],
-};
-
-export default config;
diff --git a/config/skill-registries.json b/config/skill-registries.json
deleted file mode 100644
index fba1095d9..000000000
--- a/config/skill-registries.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "registries": [
- {
- "id": "lobu",
- "type": "lobu",
- "apiUrl": "config/system-skills.json"
- },
- {
- "id": "clawhub",
- "type": "clawhub",
- "apiUrl": "https://wry-manatee-359.convex.site/api/v1"
- }
- ]
-}
diff --git a/config/system-skills.json b/config/system-skills.json
index 9ef4fd9fd..03132be1b 100644
--- a/config/system-skills.json
+++ b/config/system-skills.json
@@ -10,7 +10,7 @@
{
"id": "owletto",
"name": "Owletto",
- "url": "${env:AUTH_MCP_URL}",
+ "url": "${env:MEMORY_URL}",
"type": "sse"
}
]
diff --git a/docker/Dockerfile.gateway b/docker/Dockerfile.gateway
index 205b24ecb..a9f1a19c4 100644
--- a/docker/Dockerfile.gateway
+++ b/docker/Dockerfile.gateway
@@ -5,7 +5,7 @@ FROM node:20-alpine
RUN apk add --no-cache curl bash docker-cli xz shadow gcompat coreutils && \
curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash && \
chmod +x /usr/local/bin/bun
-ENV PATH="/usr/local/bin:${PATH}"
+ENV PATH="/app/node_modules/.bin:/app/packages/worker/node_modules/.bin:/usr/local/bin:${PATH}"
# Install Nix (single-user mode) for embedded deployment mode nix-shell support
RUN adduser -D -u 1001 nixuser && mkdir -p /nix && chown nixuser:nixuser /nix
@@ -43,20 +43,14 @@ COPY packages/gateway/ ./packages/gateway/
COPY packages/worker/ ./packages/worker/
# Copy CLI's mcp-servers.json (referenced by gateway's mcp-registry)
COPY packages/cli/src/mcp-servers.json ./packages/cli/src/mcp-servers.json
-# Copy system skills registry (provider catalog, integrations, MCP configs)
+# Copy system skills config (provider catalog, integrations, MCP configs)
COPY config/system-skills.json ./config/system-skills.json
-COPY config/skill-registries.json ./config/skill-registries.json
# Build core package dist (used by Node.js in production, Bun uses src/ via conditional exports)
WORKDIR /app/packages/core
RUN bunx tsc
-# Generate Tailwind CSS and page bundles
WORKDIR /app/packages/gateway
-RUN bun run generate:css && \
- bun run scripts/build-history.ts && \
- bun run scripts/build-agent.ts && \
- bun run scripts/build-agents.ts
WORKDIR /app
@@ -69,4 +63,4 @@ RUN chmod -R a+rX /app
# Run with bun for TypeScript support and hot reload in dev
ENTRYPOINT []
-CMD ["sh", "-lc", "cd /app/packages/gateway && bun run generate:css >/dev/null 2>&1 && bun run scripts/build-history.ts >/dev/null 2>&1 && bun run scripts/build-agent.ts >/dev/null 2>&1 && bun run scripts/build-agents.ts >/dev/null 2>&1 && cd /app && exec bun --watch packages/gateway/src/index.ts"]
+CMD ["sh", "-lc", "exec bun --watch /app/packages/gateway/src/index.ts"]
diff --git a/docker/Dockerfile.worker b/docker/Dockerfile.worker
index 39d3b62a7..b3fa062f5 100644
--- a/docker/Dockerfile.worker
+++ b/docker/Dockerfile.worker
@@ -3,6 +3,8 @@ FROM oven/bun:1.2.9
# Build argument to control dev/prod behavior
ARG NODE_ENV=production
+ENV PATH="/app/node_modules/.bin:/app/packages/worker/node_modules/.bin:/home/bun/.bun/bin:/root/.cargo/bin:${PATH}"
+
WORKDIR /app
# Install runtime dependencies including Node.js, Python, uv, Docker, and GitHub CLI
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 9bd4d9f9e..074ea0775 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -41,6 +41,8 @@ services:
QUEUE_URL: redis://redis:6379
# Host project path for worker bind mounts (Docker-in-Docker)
LOBU_DEV_PROJECT_PATH: ${PWD}
+ # Read-only workspace mount used for file-first agent config loading
+ LOBU_WORKSPACE_ROOT: /workspace/project
env_file:
- ../.env
volumes:
@@ -48,8 +50,8 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
# Mount .env so gateway can load it via dotenv (avoids ARG_MAX with large vars)
- ../.env:/app/.env:ro
- # Mount .lobu/ for agents manifest and config
- - ../.lobu:/app/.lobu:ro
+ # Mount workspace root so the gateway can read lobu.toml directly
+ - ..:/workspace/project:ro
# Mount integrations config for live editing
- ../config:/app/config:ro
# Mount source for hot reload (bun --watch detects changes)
diff --git a/examples/hr-assistant/README.md b/examples/hr-assistant/README.md
index 35125b72c..4f7c1f3e3 100644
--- a/examples/hr-assistant/README.md
+++ b/examples/hr-assistant/README.md
@@ -8,7 +8,7 @@ Make sure you have Docker CLI installed.
```bash
# Start the services
-lobu dev -d
+lobu run -d
# View logs
docker compose logs -f
diff --git a/examples/hr-assistant/TESTING.md b/examples/hr-assistant/TESTING.md
index 383ebaa0c..4d9ea2886 100644
--- a/examples/hr-assistant/TESTING.md
+++ b/examples/hr-assistant/TESTING.md
@@ -8,7 +8,7 @@ Send messages to your bot with optional file uploads.
### Endpoint
```
-POST http://localhost:8081/api/messaging/send
+POST http://localhost:8081/api/v1/agents/{agentId}/messages
```
### Authentication
@@ -24,7 +24,7 @@ The bot token must be provided in the `Authorization` header, not in the request
#### JSON Request (Simple Message)
```bash
-curl -X POST http://localhost:8081/api/messaging/send \
+curl -X POST http://localhost:8081/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
@@ -36,7 +36,7 @@ curl -X POST http://localhost:8081/api/messaging/send \
#### Multipart Request (With File Upload)
```bash
-curl -X POST http://localhost:8081/api/messaging/send \
+curl -X POST http://localhost:8081/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-F "platform=slack" \
-F "channel=C12345678" \
@@ -90,7 +90,7 @@ If you don't want to mention the bot, simply omit `@me` from your message.
### Example: Simple Text Message (with @me)
```bash
-curl -X POST http://localhost:8081/api/messaging/send \
+curl -X POST http://localhost:8081/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
@@ -103,7 +103,7 @@ curl -X POST http://localhost:8081/api/messaging/send \
### Example: Without Bot Mention
```bash
-curl -X POST http://localhost:8081/api/messaging/send \
+curl -X POST http://localhost:8081/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
@@ -116,7 +116,7 @@ curl -X POST http://localhost:8081/api/messaging/send \
### Example: Thread Reply
```bash
-curl -X POST http://localhost:8081/api/messaging/send \
+curl -X POST http://localhost:8081/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
@@ -130,7 +130,7 @@ curl -X POST http://localhost:8081/api/messaging/send \
### Example: Single File Upload
```bash
-curl -X POST http://localhost:8081/api/messaging/send \
+curl -X POST http://localhost:8081/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-F "platform=slack" \
-F "channel=dev-channel" \
@@ -141,7 +141,7 @@ curl -X POST http://localhost:8081/api/messaging/send \
### Example: Multiple File Upload
```bash
-curl -X POST http://localhost:8081/api/messaging/send \
+curl -X POST http://localhost:8081/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-F "platform=slack" \
-F "channel=dev-channel" \
@@ -187,7 +187,7 @@ Testing a full conversation:
```bash
# Step 1: Send initial message
-RESPONSE=$(curl -s -X POST http://localhost:8081/api/messaging/send \
+RESPONSE=$(curl -s -X POST http://localhost:8081/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
diff --git a/examples/hr-assistant/docker-compose.yml b/examples/hr-assistant/docker-compose.yml
index 811b9ec92..ffc85cf17 100644
--- a/examples/hr-assistant/docker-compose.yml
+++ b/examples/hr-assistant/docker-compose.yml
@@ -35,10 +35,10 @@ services:
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
WORKER_ALLOWED_DOMAINS: ${WORKER_ALLOWED_DOMAINS:-}
WORKER_DISALLOWED_DOMAINS: ${WORKER_DISALLOWED_DOMAINS:-}
- AUTH_MCP_URL: ${AUTH_MCP_URL:-}
- MEMORY_PLUGIN: ${MEMORY_PLUGIN:-owletto}
+ MEMORY_URL: ${MEMORY_URL:-}
+ LOBU_WORKSPACE_ROOT: /workspace/project
volumes:
- - ./.lobu:/app/.lobu
+ - .:/workspace/project:ro
networks:
- lobu-public
- lobu-internal
@@ -53,4 +53,3 @@ networks:
lobu-internal:
internal: true
driver: bridge
-
diff --git a/landing/.gitignore b/landing/.gitignore
deleted file mode 100644
index f06235c46..000000000
--- a/landing/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-node_modules
-dist
diff --git a/landing/bun.lock b/landing/bun.lock
deleted file mode 100644
index 8f82a9af5..000000000
--- a/landing/bun.lock
+++ /dev/null
@@ -1,270 +0,0 @@
-{
- "lockfileVersion": 1,
- "configVersion": 1,
- "workspaces": {
- "": {
- "name": "lobu-demo",
- "dependencies": {
- "framer-motion": "^11.15.0",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- },
- "devDependencies": {
- "@types/react": "^18.3.12",
- "@types/react-dom": "^18.3.1",
- "@vitejs/plugin-react": "^4.3.4",
- "typescript": "^5.6.3",
- "vite": "^6.0.0",
- },
- },
- },
- "packages": {
- "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
-
- "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
-
- "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
-
- "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
-
- "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
-
- "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
-
- "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
-
- "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
-
- "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
-
- "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
-
- "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
-
- "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
-
- "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
-
- "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
-
- "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
-
- "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
-
- "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
-
- "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
-
- "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
-
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
-
- "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
-
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
-
- "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
-
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
-
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
-
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
-
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
-
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
-
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
-
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
-
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
-
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
-
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
-
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
-
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
-
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
-
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
-
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
-
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
-
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
-
- "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
-
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
-
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
-
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
-
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
-
- "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
-
- "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
-
- "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
-
- "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
-
- "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
-
- "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
-
- "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
-
- "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
-
- "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
-
- "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
-
- "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
-
- "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
-
- "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
-
- "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
-
- "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
-
- "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
-
- "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
-
- "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
-
- "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
-
- "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
-
- "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
-
- "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
-
- "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
-
- "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
-
- "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
-
- "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
-
- "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
-
- "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
-
- "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
-
- "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
-
- "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
-
- "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
-
- "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
-
- "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
-
- "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
-
- "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
-
- "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
-
- "@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="],
-
- "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
-
- "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
-
- "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
-
- "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
-
- "caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
-
- "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
-
- "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
-
- "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
-
- "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
-
- "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
-
- "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
-
- "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
-
- "framer-motion": ["framer-motion@11.18.2", "", { "dependencies": { "motion-dom": "^11.18.1", "motion-utils": "^11.18.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w=="],
-
- "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
-
- "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
-
- "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
-
- "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
-
- "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
-
- "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
-
- "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
-
- "motion-dom": ["motion-dom@11.18.1", "", { "dependencies": { "motion-utils": "^11.18.1" } }, "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw=="],
-
- "motion-utils": ["motion-utils@11.18.1", "", {}, "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA=="],
-
- "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
-
- "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
-
- "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
-
- "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
-
- "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
-
- "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
-
- "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
-
- "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
-
- "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
-
- "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
-
- "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
-
- "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
- "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
-
- "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
-
- "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
-
- "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
-
- "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
-
- "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
-
- "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
- }
-}
diff --git a/landing/index.html b/landing/index.html
deleted file mode 100644
index 30a6a2ed3..000000000
--- a/landing/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
- Lobu - How It Works
-
-
-
-
-
-
-
-
-
diff --git a/landing/package.json b/landing/package.json
deleted file mode 100644
index 3f06cdda1..000000000
--- a/landing/package.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "lobu-landing",
- "private": true,
- "version": "0.0.1",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "tsc && vite build",
- "preview": "vite preview"
- },
- "dependencies": {
- "framer-motion": "^11.15.0",
- "react": "^18.3.1",
- "react-dom": "^18.3.1"
- },
- "devDependencies": {
- "@types/react": "^18.3.12",
- "@types/react-dom": "^18.3.1",
- "@vitejs/plugin-react": "^4.3.4",
- "typescript": "^5.6.3",
- "vite": "^6.0.0"
- }
-}
diff --git a/landing/src/App.tsx b/landing/src/App.tsx
deleted file mode 100644
index c74dc4064..000000000
--- a/landing/src/App.tsx
+++ /dev/null
@@ -1,199 +0,0 @@
-import type React from "react";
-import { useCallback, useEffect, useRef, useState } from "react";
-import { Diagram } from "./components/Diagram";
-import { PromptSwitcher } from "./components/PromptSwitcher";
-import { SlackPanel } from "./components/SlackPanel";
-import { StepIndicator } from "./components/StepIndicator";
-import { buildSteps, PROMPT_OPTIONS } from "./steps";
-import { colors, layout } from "./styles";
-
-const globalStyles = `
- * { margin: 0; padding: 0; box-sizing: border-box; }
- html, body, #root { height: 100%; }
- body { background: ${colors.bg}; color: ${colors.text}; }
- ::-webkit-scrollbar { width: 6px; }
- ::-webkit-scrollbar-track { background: transparent; }
- ::-webkit-scrollbar-thumb { background: ${colors.border}; border-radius: 3px; }
- ::-webkit-scrollbar-thumb:hover { background: ${colors.borderLight}; }
-`;
-
-const App: React.FC = () => {
- const [selectedPrompt, setSelectedPrompt] = useState(PROMPT_OPTIONS[0]);
- const [currentStepIndex, setCurrentStepIndex] = useState(0);
- const [isPlaying, setIsPlaying] = useState(true);
- const steps = buildSteps(selectedPrompt);
- const timerRef = useRef | null>(null);
-
- const clearTimer = useCallback(() => {
- if (timerRef.current) {
- clearTimeout(timerRef.current);
- timerRef.current = null;
- }
- }, []);
-
- // Auto-play timer
- useEffect(() => {
- clearTimer();
-
- if (!isPlaying) return;
-
- const currentStep = steps[currentStepIndex];
- timerRef.current = setTimeout(() => {
- setCurrentStepIndex((prev) => {
- if (prev >= steps.length - 1) {
- // Loop back to start
- return 0;
- }
- return prev + 1;
- });
- }, currentStep.duration);
-
- return clearTimer;
- }, [isPlaying, currentStepIndex, steps, clearTimer]);
-
- const handleStepClick = useCallback(
- (index: number) => {
- clearTimer();
- setCurrentStepIndex(index);
- setIsPlaying(false);
- },
- [clearTimer]
- );
-
- const handleTogglePlay = useCallback(() => {
- setIsPlaying((prev) => !prev);
- }, []);
-
- const handleReset = useCallback(() => {
- clearTimer();
- setCurrentStepIndex(0);
- setIsPlaying(true);
- }, [clearTimer]);
-
- const handlePromptChange = useCallback(
- (prompt: typeof selectedPrompt) => {
- clearTimer();
- setSelectedPrompt(prompt);
- setCurrentStepIndex(0);
- setIsPlaying(true);
- },
- [clearTimer]
- );
-
- const currentStep = steps[currentStepIndex];
-
- return (
- <>
-
-
- {/* Header */}
-
-
-
- L
-
-
-
- How Lobu Works
-
-
- Message processing flow
-
-
-
-
-
-
-
- {/* Main content */}
-
- {/* Diagram area */}
-
- {/* Step description overlay */}
-
-
-
- Step {currentStepIndex + 1}: {currentStep.title}
-
-
- {currentStep.description}
-
-
-
-
- {/* SVG Diagram */}
-
-
-
-
-
- {/* Slack chat panel */}
-
-
-
-
-
- {/* Bottom control bar */}
-
-
-
-
- >
- );
-};
-
-export default App;
diff --git a/landing/src/components/AnimatedPacket.tsx b/landing/src/components/AnimatedPacket.tsx
deleted file mode 100644
index 2d19a4d30..000000000
--- a/landing/src/components/AnimatedPacket.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { motion } from "framer-motion";
-import type React from "react";
-import { NODES } from "../nodes";
-
-interface AnimatedPacketProps {
- from: string;
- to: string;
- color: string;
- label?: string;
-}
-
-export const AnimatedPacket: React.FC = ({
- from,
- to,
- color,
- label,
-}) => {
- const fromNode = NODES.find((n) => n.id === from);
- const toNode = NODES.find((n) => n.id === to);
- if (!fromNode || !toNode) return null;
-
- // If from and to are not directly connected, route through gateway
- const isDirectConnection = from === "gateway" || to === "gateway";
-
- // For indirect routes (e.g., mcp → sandbox), we go through gateway
- let waypoints: Array<{ x: number; y: number }>;
-
- if (isDirectConnection) {
- waypoints = [
- { x: fromNode.x, y: fromNode.y },
- { x: toNode.x, y: toNode.y },
- ];
- } else {
- const gateway = NODES.find((n) => n.id === "gateway")!;
- waypoints = [
- { x: fromNode.x, y: fromNode.y },
- { x: gateway.x, y: gateway.y },
- { x: toNode.x, y: toNode.y },
- ];
- }
-
- const numWaypoints = waypoints.length;
- const duration = 0.8 * (numWaypoints - 1);
-
- // Compute midpoint for label positioning
- const midIdx = Math.floor(numWaypoints / 2);
- const midX = waypoints[midIdx].x;
- const midY = waypoints[midIdx].y;
-
- return (
-
- {/* Animated dot traveling the path */}
- w.x),
- cy: waypoints.map((w) => w.y),
- }}
- transition={{ duration, ease: "easeInOut" }}
- />
-
- {/* Fade in/out overlay */}
- w.x),
- cy: waypoints.map((w) => w.y),
- opacity: [0, 1, 0],
- }}
- transition={{
- duration,
- ease: "easeInOut",
- opacity: { duration, times: [0, 0.15, 1] },
- }}
- />
-
- {/* Trail effect */}
- w.x),
- cy: waypoints.map((w) => w.y),
- opacity: [0, 0.4, 0],
- }}
- transition={{
- duration,
- ease: "easeInOut",
- delay: 0.08,
- opacity: { duration, times: [0, 0.2, 1] },
- }}
- />
-
- {/* Label at midpoint */}
- {label && (
-
- {label}
-
- )}
-
- );
-};
diff --git a/landing/src/components/ConnectionLine.tsx b/landing/src/components/ConnectionLine.tsx
deleted file mode 100644
index 1cc5775a1..000000000
--- a/landing/src/components/ConnectionLine.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import type React from "react";
-import { NODES } from "../nodes";
-import { colors } from "../styles";
-
-interface ConnectionLineProps {
- from: string;
- to: string;
- isActive: boolean;
-}
-
-export const ConnectionLine: React.FC = ({
- from,
- to,
- isActive,
-}) => {
- const fromNode = NODES.find((n) => n.id === from);
- const toNode = NODES.find((n) => n.id === to);
- if (!fromNode || !toNode) return null;
-
- return (
-
- );
-};
diff --git a/landing/src/components/Diagram.tsx b/landing/src/components/Diagram.tsx
deleted file mode 100644
index d91a24acd..000000000
--- a/landing/src/components/Diagram.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { AnimatePresence } from "framer-motion";
-import type React from "react";
-import { CONNECTIONS, NODES } from "../nodes";
-import type { FlowStep, PromptOption } from "../types";
-import { AnimatedPacket } from "./AnimatedPacket";
-import { ConnectionLine } from "./ConnectionLine";
-import { DiagramNode } from "./DiagramNode";
-
-interface DiagramProps {
- currentStep: FlowStep;
- prompt: PromptOption;
-}
-
-export const Diagram: React.FC = ({ currentStep, prompt }) => {
- const activeNodes = new Set(currentStep.activeNodes);
-
- // Determine which connections are active
- const activeConnections = new Set();
- if (currentStep.packet) {
- const { from, to } = currentStep.packet;
- // Mark direct connection or connections through gateway
- for (const conn of CONNECTIONS) {
- if (
- (conn.from === from && conn.to === to) ||
- (conn.from === to && conn.to === from) ||
- // Indirect: from → gateway → to
- (conn.from === from && conn.to === "gateway") ||
- (conn.from === "gateway" && conn.to === to) ||
- (conn.to === from && conn.from === "gateway") ||
- (conn.to === "gateway" && conn.from === to)
- ) {
- activeConnections.add(`${conn.from}-${conn.to}`);
- }
- }
- }
-
- // Update MCP label dynamically based on selected prompt
- const nodesWithDynamicLabels = NODES.map((n) => {
- if (n.id === "mcp") {
- return { ...n, label: prompt.mcpTarget, sublabel: prompt.mcpDomain };
- }
- return n;
- });
-
- return (
-
- );
-};
diff --git a/landing/src/components/DiagramNode.tsx b/landing/src/components/DiagramNode.tsx
deleted file mode 100644
index f9e802efa..000000000
--- a/landing/src/components/DiagramNode.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-import { motion } from "framer-motion";
-import type React from "react";
-import { colors } from "../styles";
-import type { NodePosition } from "../types";
-import { NodeIcon } from "./NodeIcon";
-
-interface DiagramNodeProps {
- node: NodePosition;
- isActive: boolean;
- callout?: {
- text: string;
- type: "info" | "security" | "warning";
- };
-}
-
-const NODE_WIDTH = 140;
-const NODE_HEIGHT = 80;
-
-const calloutColors = {
- info: { bg: colors.accentDim, border: colors.accent, text: colors.accent },
- security: {
- bg: colors.greenDim,
- border: colors.green,
- text: colors.green,
- },
- warning: {
- bg: colors.yellowDim,
- border: colors.yellow,
- text: colors.yellow,
- },
-};
-
-export const DiagramNode: React.FC = ({
- node,
- isActive,
- callout,
-}) => {
- const nodeX = node.x - NODE_WIDTH / 2;
- const nodeY = node.y - NODE_HEIGHT / 2;
-
- return (
-
- {/* Node box */}
-
-
-
- {/* Icon */}
-
-
-
-
-
-
- {/* Label */}
-
- {node.label}
-
-
- {/* Sublabel */}
- {node.sublabel && (
-
- {node.sublabel}
-
- )}
-
-
- {/* Callout bubble */}
- {callout && (
-
-
-
- {callout.type === "security" && "🔒 "}
- {callout.type === "warning" && "⚠️ "}
- {callout.text}
-
-
-
- )}
-
- );
-};
diff --git a/landing/src/components/NodeIcon.tsx b/landing/src/components/NodeIcon.tsx
deleted file mode 100644
index 0305ba077..000000000
--- a/landing/src/components/NodeIcon.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-import type React from "react";
-
-interface NodeIconProps {
- type: string;
- size?: number;
-}
-
-export const NodeIcon: React.FC = ({ type, size = 28 }) => {
- const s = size;
- const color = "currentColor";
-
- switch (type) {
- case "slack":
- return (
-
- );
-
- case "gateway":
- return (
-
- );
-
- case "sandbox":
- return (
-
- );
-
- case "mcp":
- return (
-
- );
-
- case "llm":
- return (
-
- );
-
- default:
- return (
-
- );
- }
-};
diff --git a/landing/src/components/PromptSwitcher.tsx b/landing/src/components/PromptSwitcher.tsx
deleted file mode 100644
index cc6c7fd70..000000000
--- a/landing/src/components/PromptSwitcher.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { motion } from "framer-motion";
-import type React from "react";
-import { colors } from "../styles";
-import type { PromptOption } from "../types";
-
-interface PromptSwitcherProps {
- options: PromptOption[];
- selected: PromptOption;
- onSelect: (option: PromptOption) => void;
-}
-
-export const PromptSwitcher: React.FC = ({
- options,
- selected,
- onSelect,
-}) => {
- return (
-
- {options.map((option) => {
- const isSelected = option.label === selected.label;
- return (
- onSelect(option)}
- style={{
- padding: "6px 14px",
- borderRadius: 20,
- border: `1px solid ${isSelected ? colors.accent : colors.border}`,
- background: isSelected ? colors.accentDim : "transparent",
- color: isSelected ? colors.accent : colors.textSecondary,
- fontSize: 12,
- fontWeight: 500,
- cursor: "pointer",
- whiteSpace: "nowrap",
- fontFamily: "Inter, sans-serif",
- transition: "all 0.2s",
- }}
- >
- {option.label}
-
- );
- })}
-
- );
-};
diff --git a/landing/src/components/SlackPanel.tsx b/landing/src/components/SlackPanel.tsx
deleted file mode 100644
index 949a48455..000000000
--- a/landing/src/components/SlackPanel.tsx
+++ /dev/null
@@ -1,363 +0,0 @@
-import { AnimatePresence, motion } from "framer-motion";
-import type React from "react";
-import { useEffect, useRef, useState } from "react";
-import { colors } from "../styles";
-import type { FlowStep } from "../types";
-
-interface SlackMessage {
- id: string;
- type: "user" | "bot" | "system" | "permission";
- text: string;
- streaming?: boolean;
-}
-
-interface SlackPanelProps {
- steps: FlowStep[];
- currentStepIndex: number;
-}
-
-export const SlackPanel: React.FC = ({
- steps,
- currentStepIndex,
-}) => {
- const [messages, setMessages] = useState([]);
- const [streamedText, setStreamedText] = useState("");
- const [isStreaming, setIsStreaming] = useState(false);
- const scrollRef = useRef(null);
- const prevStepRef = useRef(-1);
-
- useEffect(() => {
- // Reset on prompt change (step 0 reached again)
- if (currentStepIndex === 0 && prevStepRef.current !== 0) {
- setMessages([]);
- setStreamedText("");
- setIsStreaming(false);
- }
- prevStepRef.current = currentStepIndex;
-
- // Collect all slack events up to current step
- const slackMessages: SlackMessage[] = [];
- for (let i = 0; i <= currentStepIndex; i++) {
- const step = steps[i];
- if (step.slackEvent && step.slackEvent.type !== "typing") {
- slackMessages.push({
- id: step.id,
- type: step.slackEvent.type,
- text: step.slackEvent.text,
- });
- }
- }
- setMessages(slackMessages);
-
- // Handle streaming for typing events
- const currentStep = steps[currentStepIndex];
- if (currentStep?.slackEvent?.streaming) {
- setIsStreaming(true);
- const fullText = currentStep.slackEvent.text;
- let charIndex = 0;
- setStreamedText("");
-
- const interval = setInterval(() => {
- charIndex += 2;
- if (charIndex >= fullText.length) {
- setStreamedText(fullText);
- setIsStreaming(false);
- clearInterval(interval);
- } else {
- setStreamedText(fullText.slice(0, charIndex));
- }
- }, 40);
-
- return () => clearInterval(interval);
- } else {
- setIsStreaming(false);
- setStreamedText("");
- }
- }, [currentStepIndex, steps]);
-
- useEffect(() => {
- if (scrollRef.current) {
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
- }
- }, []);
-
- return (
-
- {/* Slack header */}
-
-
- {/* Messages area */}
-
-
- {messages.map((msg) => (
-
- {msg.type === "user" && }
- {msg.type === "bot" && }
- {msg.type === "system" && }
- {msg.type === "permission" && (
-
- )}
-
- ))}
-
- {/* Streaming message */}
- {isStreaming && (
-
-
-
- )}
-
-
-
- {/* Input bar */}
-
-
- );
-};
-
-const UserMessage: React.FC<{ text: string }> = ({ text }) => (
-
-
- U
-
-
-
- You
- now
-
-
- {text}
-
-
-
-);
-
-const BotMessage: React.FC<{ text: string; isStreaming?: boolean }> = ({
- text,
- isStreaming,
-}) => (
-
-
- L
-
-
-
- Lobu
-
- APP
-
- now
-
-
- {renderMarkdownLight(text)}
- {isStreaming && (
-
- |
-
- )}
-
-
-
-);
-
-const SystemMessage: React.FC<{ text: string }> = ({ text }) => (
-
- {text}
-
-);
-
-const PermissionMessage: React.FC<{ text: string }> = ({ text }) => (
-
-
- Permission Required
-
-
- {text}
-
-
-
- Allow for 1 hour
-
-
-
-
-);
-
-function renderMarkdownLight(text: string): React.ReactNode {
- // Very simple bold markdown rendering
- const parts = text.split(/(\*\*[^*]+\*\*)/g);
- return parts.map((part, i) => {
- if (part.startsWith("**") && part.endsWith("**")) {
- return (
- // biome-ignore lint/suspicious/noArrayIndexKey: markdown parts are derived from static text and never reordered
-
- {part.slice(2, -2)}
-
- );
- }
- return part;
- });
-}
diff --git a/landing/src/components/StepIndicator.tsx b/landing/src/components/StepIndicator.tsx
deleted file mode 100644
index 894bf4175..000000000
--- a/landing/src/components/StepIndicator.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-import { motion } from "framer-motion";
-import type React from "react";
-import { colors } from "../styles";
-import type { FlowStep } from "../types";
-
-interface StepIndicatorProps {
- steps: FlowStep[];
- currentIndex: number;
- onStepClick: (index: number) => void;
- isPlaying: boolean;
- onTogglePlay: () => void;
- onReset: () => void;
-}
-
-export const StepIndicator: React.FC = ({
- steps,
- currentIndex,
- onStepClick,
- isPlaying,
- onTogglePlay,
- onReset,
-}) => {
- const currentStep = steps[currentIndex];
-
- return (
-
- {/* Play/Pause button */}
-
- {isPlaying ? (
-
- ) : (
-
- )}
-
-
- {/* Reset button */}
-
-
-
-
- {/* Step dots */}
-
- {steps.map((_, index) => (
- onStepClick(index)}
- style={{
- width: index === currentIndex ? 16 : 6,
- height: 6,
- borderRadius: 3,
- background:
- index < currentIndex
- ? colors.green
- : index === currentIndex
- ? colors.accent
- : colors.border,
- border: "none",
- cursor: "pointer",
- padding: 0,
- transition: "all 0.3s",
- }}
- whileHover={{ scale: 1.4 }}
- title={steps[index].title}
- />
- ))}
-
-
- {/* Current step info */}
-
-
- {currentStep.title}
-
-
- {currentIndex + 1}/{steps.length} — {currentStep.description}
-
-
-
- );
-};
diff --git a/landing/src/main.tsx b/landing/src/main.tsx
deleted file mode 100644
index f46c379cc..000000000
--- a/landing/src/main.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-import App from "./App";
-
-ReactDOM.createRoot(document.getElementById("root")!).render(
-
-
-
-);
diff --git a/landing/src/nodes.ts b/landing/src/nodes.ts
deleted file mode 100644
index bb0fd0067..000000000
--- a/landing/src/nodes.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { NodePosition } from "./types";
-
-// Layout: hub-spoke with Gateway in center
-// Slack on the left, Sandbox on the right, MCP top-right, LLM bottom-right
-export const NODES: NodePosition[] = [
- {
- id: "slack",
- label: "Slack",
- sublabel: "User Interface",
- x: 120,
- y: 260,
- icon: "slack",
- },
- {
- id: "gateway",
- label: "Gateway",
- sublabel: "Lobu Gateway",
- x: 400,
- y: 260,
- icon: "gateway",
- },
- {
- id: "sandbox",
- label: "Sandbox",
- sublabel: "Isolated Container",
- x: 680,
- y: 260,
- icon: "sandbox",
- },
- {
- id: "mcp",
- label: "MCP Server",
- sublabel: "External Service",
- x: 400,
- y: 80,
- icon: "mcp",
- },
- {
- id: "llm",
- label: "LLM API",
- sublabel: "Claude / OpenAI",
- x: 400,
- y: 440,
- icon: "llm",
- },
-];
-
-// Connection lines between nodes
-export const CONNECTIONS: Array<{ from: string; to: string }> = [
- { from: "slack", to: "gateway" },
- { from: "gateway", to: "sandbox" },
- { from: "gateway", to: "mcp" },
- { from: "gateway", to: "llm" },
-];
diff --git a/landing/src/steps.ts b/landing/src/steps.ts
deleted file mode 100644
index d71449dea..000000000
--- a/landing/src/steps.ts
+++ /dev/null
@@ -1,344 +0,0 @@
-import type { FlowStep, PromptOption } from "./types";
-
-export const PROMPT_OPTIONS: PromptOption[] = [
- {
- label: "Summarize my emails",
- prompt: "Summarize my emails from today",
- mcpTarget: "Gmail",
- mcpDomain: "gmail.com",
- },
- {
- label: "Update Jira ticket",
- prompt: "Update PROJ-123 status to done",
- mcpTarget: "Jira",
- mcpDomain: "jira.atlassian.com",
- },
- {
- label: "Check PR reviews",
- prompt: "Show my open PR reviews on GitHub",
- mcpTarget: "GitHub",
- mcpDomain: "github.com",
- },
-];
-
-export function buildSteps(prompt: PromptOption): FlowStep[] {
- return [
- {
- id: "user-types",
- title: "User sends message",
- description: `User types "${prompt.prompt}" in Slack`,
- activeNodes: ["slack"],
- slackEvent: {
- type: "user",
- text: prompt.prompt,
- },
- duration: 2000,
- },
- {
- id: "message-to-gateway",
- title: "Message reaches Gateway",
- description: "Slack delivers the event to the Lobu gateway",
- activeNodes: ["slack", "gateway"],
- packet: {
- from: "slack",
- to: "gateway",
- label: "event",
- color: "#4A9EFF",
- },
- duration: 1500,
- },
- {
- id: "check-sandbox",
- title: "Check sandbox status",
- description:
- "Gateway checks if a sandbox is already running for this user",
- activeNodes: ["gateway"],
- callout: {
- node: "gateway",
- text: "Is sandbox running? → No",
- type: "info",
- },
- duration: 1800,
- },
- {
- id: "check-snapshot",
- title: "Look up snapshot",
- description:
- "Gateway checks if a pre-built snapshot exists for the agent",
- activeNodes: ["gateway"],
- callout: {
- node: "gateway",
- text: "Snapshot exists? → No",
- type: "info",
- },
- duration: 1800,
- },
- {
- id: "create-snapshot",
- title: "Create snapshot",
- description:
- "Gateway creates a snapshot with the agent's tools, MCP config, and dependencies",
- activeNodes: ["gateway", "sandbox"],
- packet: {
- from: "gateway",
- to: "sandbox",
- label: "snapshot",
- color: "#F59E0B",
- },
- callout: {
- node: "sandbox",
- text: "Building snapshot...",
- type: "info",
- },
- duration: 2500,
- },
- {
- id: "start-container",
- title: "Start container",
- description:
- "Container launches from snapshot — isolated environment with no direct internet access",
- activeNodes: ["sandbox"],
- callout: {
- node: "sandbox",
- text: "Container running (network isolated)",
- type: "security",
- },
- slackEvent: {
- type: "bot",
- text: "Working on it...",
- },
- duration: 2000,
- },
- {
- id: "deliver-message",
- title: "Deliver prompt to sandbox",
- description: "Gateway sends the user's message to the sandbox runtime",
- activeNodes: ["gateway", "sandbox"],
- packet: {
- from: "gateway",
- to: "sandbox",
- label: "prompt",
- color: "#4A9EFF",
- },
- duration: 1500,
- },
- {
- id: "sandbox-runs",
- title: "Sandbox processes message",
- description:
- "OpenClaw runtime analyzes the prompt and determines it needs to fetch emails via MCP",
- activeNodes: ["sandbox"],
- callout: {
- node: "sandbox",
- text: `Need to call ${prompt.mcpTarget} via MCP`,
- type: "info",
- },
- duration: 2000,
- },
- {
- id: "mcp-call",
- title: "MCP proxy request",
- description:
- "Sandbox calls the gateway's MCP proxy endpoint — it does NOT have OAuth tokens",
- activeNodes: ["sandbox", "gateway"],
- packet: {
- from: "sandbox",
- to: "gateway",
- label: "MCP call",
- color: "#A855F7",
- },
- callout: {
- node: "sandbox",
- text: "No OAuth tokens in sandbox",
- type: "security",
- },
- duration: 2000,
- },
- {
- id: "enrich-token",
- title: "Gateway enriches with credentials",
- description:
- "Gateway attaches the user's OAuth token to the MCP request — sandbox never sees it",
- activeNodes: ["gateway"],
- callout: {
- node: "gateway",
- text: "Injecting OAuth token (hidden from sandbox)",
- type: "security",
- },
- duration: 2000,
- },
- {
- id: "domain-check",
- title: "Domain access check",
- description: `MCP needs to reach ${prompt.mcpDomain} — but it's not in the allowlist`,
- activeNodes: ["gateway", "mcp"],
- packet: {
- from: "gateway",
- to: "mcp",
- label: "blocked",
- color: "#EF4444",
- },
- callout: {
- node: "gateway",
- text: `${prompt.mcpDomain} not in allowlist!`,
- type: "warning",
- },
- duration: 2000,
- },
- {
- id: "permission-prompt",
- title: "User permission prompt",
- description: `User is asked in Slack to approve access to ${prompt.mcpDomain}`,
- activeNodes: ["gateway", "slack"],
- packet: {
- from: "gateway",
- to: "slack",
- label: "permission",
- color: "#F59E0B",
- },
- slackEvent: {
- type: "permission",
- text: `Allow access to ${prompt.mcpDomain} for 1 hour?`,
- },
- duration: 3000,
- },
- {
- id: "user-approves",
- title: "User approves access",
- description: `User clicks "Allow for 1 hour" — domain is temporarily whitelisted`,
- activeNodes: ["slack", "gateway"],
- packet: {
- from: "slack",
- to: "gateway",
- label: "approved",
- color: "#10B981",
- },
- slackEvent: {
- type: "system",
- text: `✓ ${prompt.mcpDomain} allowed for 1 hour`,
- },
- duration: 1800,
- },
- {
- id: "fetch-data",
- title: `Fetch from ${prompt.mcpTarget}`,
- description: `Gateway fetches data from ${prompt.mcpDomain} using the user's OAuth credentials`,
- activeNodes: ["gateway", "mcp"],
- packet: {
- from: "gateway",
- to: "mcp",
- label: "fetch",
- color: "#10B981",
- },
- duration: 2000,
- },
- {
- id: "data-to-sandbox",
- title: "Data returned to sandbox",
- description: `${prompt.mcpTarget} data flows back through gateway to the isolated sandbox`,
- activeNodes: ["mcp", "gateway", "sandbox"],
- packet: {
- from: "mcp",
- to: "sandbox",
- label: "data",
- color: "#10B981",
- },
- duration: 1800,
- },
- {
- id: "llm-request",
- title: "LLM API call",
- description:
- "Sandbox calls the gateway's LLM endpoint — it does NOT have API keys",
- activeNodes: ["sandbox", "gateway"],
- packet: {
- from: "sandbox",
- to: "gateway",
- label: "LLM request",
- color: "#8B5CF6",
- },
- callout: {
- node: "sandbox",
- text: "No LLM API keys in sandbox",
- type: "security",
- },
- duration: 2000,
- },
- {
- id: "llm-process",
- title: "LLM processes request",
- description:
- "Gateway forwards to LLM API with proper credentials, generates the response",
- activeNodes: ["gateway", "llm"],
- packet: {
- from: "gateway",
- to: "llm",
- label: "generate",
- color: "#8B5CF6",
- },
- duration: 2000,
- },
- {
- id: "llm-response",
- title: "LLM response returns",
- description: "Generated response flows back through gateway to sandbox",
- activeNodes: ["llm", "gateway", "sandbox"],
- packet: {
- from: "llm",
- to: "sandbox",
- label: "response",
- color: "#8B5CF6",
- },
- duration: 1500,
- },
- {
- id: "stream-result",
- title: "Stream result to user",
- description:
- "Sandbox sends the final output through gateway back to Slack, streamed in real-time",
- activeNodes: ["sandbox", "gateway", "slack"],
- packet: {
- from: "sandbox",
- to: "slack",
- label: "stream",
- color: "#10B981",
- },
- slackEvent: {
- type: "typing",
- text: getResponseText(prompt),
- streaming: true,
- },
- duration: 4000,
- },
- {
- id: "done",
- title: "Complete",
- description:
- "User sees the response in Slack — all processing happened in an isolated sandbox",
- activeNodes: ["slack"],
- slackEvent: {
- type: "bot",
- text: getResponseText(prompt),
- },
- callout: {
- node: "sandbox",
- text: "Sandbox stays warm for follow-ups",
- type: "info",
- },
- duration: 3000,
- },
- ];
-}
-
-function getResponseText(prompt: PromptOption): string {
- switch (prompt.mcpTarget) {
- case "Gmail":
- return "Here's your email summary for today:\n\n• **Design review** from Sarah — needs feedback by EOD\n• **Sprint planning** reminder — tomorrow 10am\n• **AWS billing alert** — usage up 12% this month\n\n3 emails require your attention.";
- case "Jira":
- return "Done! PROJ-123 has been updated:\n\n• Status: **In Progress** → **Done**\n• Resolution: Completed\n• Time logged: 2h 30m\n\nThe ticket is now closed.";
- case "GitHub":
- return "You have 3 open PR reviews:\n\n• **#142** feat: add OAuth flow — 2 comments pending\n• **#138** fix: rate limiting bug — approved, ready to merge\n• **#135** refactor: MCP proxy — 5 files changed\n\nPR #138 can be merged now.";
- default:
- return "Done!";
- }
-}
diff --git a/landing/src/styles.ts b/landing/src/styles.ts
deleted file mode 100644
index 3d1620f75..000000000
--- a/landing/src/styles.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { CSSProperties } from "react";
-
-export const colors = {
- bg: "#0F1117",
- bgSecondary: "#1A1D27",
- bgTertiary: "#232633",
- border: "#2D3148",
- borderLight: "#3D4260",
- text: "#E2E8F0",
- textSecondary: "#94A3B8",
- textMuted: "#64748B",
- accent: "#4A9EFF",
- accentDim: "rgba(74, 158, 255, 0.15)",
- green: "#10B981",
- greenDim: "rgba(16, 185, 129, 0.15)",
- red: "#EF4444",
- redDim: "rgba(239, 68, 68, 0.15)",
- yellow: "#F59E0B",
- yellowDim: "rgba(245, 158, 11, 0.15)",
- purple: "#A855F7",
- purpleDim: "rgba(168, 85, 247, 0.15)",
- slackBg: "#1A1D21",
- slackSidebar: "#19171D",
- slackHover: "#222529",
-};
-
-export const layout: Record = {
- container: {
- fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
- background: colors.bg,
- color: colors.text,
- minHeight: "100vh",
- display: "flex",
- flexDirection: "column",
- overflow: "hidden",
- },
- header: {
- padding: "20px 32px",
- borderBottom: `1px solid ${colors.border}`,
- display: "flex",
- alignItems: "center",
- justifyContent: "space-between",
- flexShrink: 0,
- },
- main: {
- display: "flex",
- flex: 1,
- overflow: "hidden",
- },
- diagramPanel: {
- flex: 1,
- position: "relative",
- display: "flex",
- flexDirection: "column",
- },
- slackPanel: {
- width: 380,
- borderLeft: `1px solid ${colors.border}`,
- display: "flex",
- flexDirection: "column",
- background: colors.slackBg,
- flexShrink: 0,
- },
- bottomBar: {
- borderTop: `1px solid ${colors.border}`,
- padding: "16px 32px",
- display: "flex",
- alignItems: "center",
- gap: 16,
- background: colors.bgSecondary,
- flexShrink: 0,
- },
-};
diff --git a/landing/src/types.ts b/landing/src/types.ts
deleted file mode 100644
index 48cb2143a..000000000
--- a/landing/src/types.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-export interface FlowStep {
- id: string;
- title: string;
- description: string;
- /** Which nodes are active/highlighted during this step */
- activeNodes: string[];
- /** Animated packet from → to */
- packet?: {
- from: string;
- to: string;
- label?: string;
- color?: string;
- };
- /** What appears in the Slack chat panel */
- slackEvent?: {
- type: "user" | "bot" | "system" | "typing" | "permission";
- text: string;
- streaming?: boolean;
- };
- /** Callout/annotation to show */
- callout?: {
- node: string;
- text: string;
- type: "info" | "security" | "warning";
- };
- /** Duration in ms for auto-play */
- duration: number;
-}
-
-export interface NodePosition {
- id: string;
- label: string;
- x: number;
- y: number;
- icon: string;
- sublabel?: string;
-}
-
-export interface PromptOption {
- label: string;
- prompt: string;
- mcpTarget: string;
- mcpDomain: string;
-}
diff --git a/landing/tsconfig.json b/landing/tsconfig.json
deleted file mode 100644
index 39a405b95..000000000
--- a/landing/tsconfig.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2020",
- "useDefineForClassFields": true,
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "skipLibCheck": true,
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "isolatedModules": true,
- "moduleDetection": "force",
- "noEmit": true,
- "jsx": "react-jsx",
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["src"]
-}
diff --git a/landing/vite.config.ts b/landing/vite.config.ts
deleted file mode 100644
index 75120e622..000000000
--- a/landing/vite.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import react from "@vitejs/plugin-react";
-import { defineConfig } from "vite";
-
-export default defineConfig({
- plugins: [react()],
- base: "./",
-});
diff --git a/packages/cli/package.json b/packages/cli/package.json
index b99d19dc1..2afbdde33 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -30,7 +30,6 @@
"open": "^10.1.0",
"ora": "^8.0.1",
"smol-toml": "^1.3.1",
- "yaml": "^2.3.4",
"zod": "^3.24.0"
},
"devDependencies": {
diff --git a/packages/cli/src/__tests__/login.test.ts b/packages/cli/src/__tests__/login.test.ts
deleted file mode 100644
index 1b35e016a..000000000
--- a/packages/cli/src/__tests__/login.test.ts
+++ /dev/null
@@ -1,229 +0,0 @@
-import {
- afterEach,
- beforeEach,
- describe,
- expect,
- mock,
- spyOn,
- test,
-} from "bun:test";
-
-describe("loginCommand", () => {
- const originalFetch = globalThis.fetch;
- let consoleLog: ReturnType;
-
- beforeEach(() => {
- mock.restore();
- consoleLog = spyOn(console, "log").mockImplementation(() => undefined);
- });
-
- afterEach(() => {
- globalThis.fetch = originalFetch;
- consoleLog.mockRestore();
- });
-
- test("uses device-first login when the gateway returns device mode", async () => {
- const saveCredentials = mock(async () => undefined);
- const loadCredentials = mock(async () => null);
- const openMock = mock(async () => undefined);
- const spinner = {
- start: mock(() => spinner),
- fail: mock(() => spinner),
- succeed: mock(() => spinner),
- };
-
- mock.module("open", () => ({
- default: openMock,
- }));
- mock.module("ora", () => ({
- default: mock(() => spinner),
- }));
- mock.module("../api/context.js", () => ({
- resolveContext: mock(async () => ({
- name: "dev",
- apiUrl: "https://lobu.example.com/api/v1",
- })),
- }));
- mock.module("../api/credentials.js", () => ({
- loadCredentials,
- saveCredentials,
- }));
-
- let calls = 0;
- globalThis.fetch = mock(async (input: string | URL | Request) => {
- const url = String(input);
- calls += 1;
-
- if (url.endsWith("/auth/cli/start")) {
- return new Response(
- JSON.stringify({
- mode: "device",
- deviceAuthId: "device-123",
- userCode: "ABCD-EFGH",
- verificationUri: "https://issuer.example.com/device",
- verificationUriComplete:
- "https://issuer.example.com/device?user_code=ABCD-EFGH",
- interval: 1,
- expiresAt: Date.now() + 10_000,
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- if (url.endsWith("/auth/cli/poll")) {
- return new Response(
- JSON.stringify({
- status: "complete",
- accessToken: "lobu-access-token",
- refreshToken: "lobu-refresh-token",
- expiresAt: Date.now() + 3600_000,
- user: {
- userId: "user-123",
- email: "user@example.com",
- name: "Example User",
- },
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- throw new Error(`Unexpected fetch: ${url}`);
- }) as unknown as typeof fetch;
-
- const { loginCommand } = await import(
- `../commands/login.ts?device=${Date.now()}`
- );
- await loginCommand({});
-
- expect(calls).toBe(2);
- expect(openMock).toHaveBeenCalledWith(
- "https://issuer.example.com/device?user_code=ABCD-EFGH"
- );
- expect(saveCredentials).toHaveBeenCalledTimes(1);
- expect(spinner.succeed).toHaveBeenCalledTimes(1);
- });
-
- test("falls back to browser login when the gateway returns browser mode", async () => {
- const saveCredentials = mock(async () => undefined);
- const loadCredentials = mock(async () => null);
- const openMock = mock(async () => undefined);
- const spinner = {
- start: mock(() => spinner),
- fail: mock(() => spinner),
- succeed: mock(() => spinner),
- };
-
- mock.module("open", () => ({
- default: openMock,
- }));
- mock.module("ora", () => ({
- default: mock(() => spinner),
- }));
- mock.module("../api/context.js", () => ({
- resolveContext: mock(async () => ({
- name: "prod",
- apiUrl: "https://lobu.example.com/api/v1",
- })),
- }));
- mock.module("../api/credentials.js", () => ({
- loadCredentials,
- saveCredentials,
- }));
-
- globalThis.fetch = mock(async (input: string | URL | Request) => {
- const url = String(input);
-
- if (url.endsWith("/auth/cli/start")) {
- return new Response(
- JSON.stringify({
- mode: "browser",
- requestId: "request-123",
- loginUrl: "https://lobu.example.com/login",
- pollIntervalMs: 1,
- expiresAt: Date.now() + 10_000,
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- if (url.endsWith("/auth/cli/poll")) {
- return new Response(
- JSON.stringify({
- status: "complete",
- accessToken: "lobu-access-token",
- refreshToken: "lobu-refresh-token",
- expiresAt: Date.now() + 3600_000,
- user: {
- userId: "user-456",
- email: "prod@example.com",
- name: "Prod User",
- },
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- throw new Error(`Unexpected fetch: ${url}`);
- }) as unknown as typeof fetch;
-
- const { loginCommand } = await import(
- `../commands/login.ts?browser=${Date.now()}`
- );
- await loginCommand({});
-
- expect(openMock).toHaveBeenCalledWith("https://lobu.example.com/login");
- expect(saveCredentials).toHaveBeenCalledTimes(1);
- expect(spinner.succeed).toHaveBeenCalledTimes(1);
- });
-
- test("uses the explicit admin-password fallback when requested", async () => {
- const saveCredentials = mock(async () => undefined);
- const loadCredentials = mock(async () => null);
- const promptMock = mock(async () => ({ password: "dev-secret" }));
-
- mock.module("inquirer", () => ({
- default: {
- prompt: promptMock,
- },
- }));
- mock.module("../api/context.js", () => ({
- resolveContext: mock(async () => ({
- name: "dev",
- apiUrl: "https://lobu.example.com/api/v1",
- })),
- }));
- mock.module("../api/credentials.js", () => ({
- loadCredentials,
- saveCredentials,
- }));
-
- globalThis.fetch = mock(async (input: string | URL | Request) => {
- const url = String(input);
- if (url.endsWith("/auth/cli/admin-login")) {
- return new Response(
- JSON.stringify({
- status: "complete",
- accessToken: "lobu-access-token",
- refreshToken: "lobu-refresh-token",
- expiresAt: Date.now() + 3600_000,
- user: {
- userId: "admin",
- name: "Admin (dev)",
- },
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- throw new Error(`Unexpected fetch: ${url}`);
- }) as unknown as typeof fetch;
-
- const { loginCommand } = await import(
- `../commands/login.ts?admin=${Date.now()}`
- );
- await loginCommand({ adminPassword: true });
-
- expect(promptMock).toHaveBeenCalledTimes(1);
- expect(saveCredentials).toHaveBeenCalledTimes(1);
- });
-});
diff --git a/packages/cli/src/api/client.ts b/packages/cli/src/api/client.ts
deleted file mode 100644
index ab5ce93a3..000000000
--- a/packages/cli/src/api/client.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { resolveContext } from "./context.js";
-import { getToken } from "./credentials.js";
-
-export interface ApiResponse {
- ok: boolean;
- data?: T;
- error?: string;
-}
-
-/**
- * HTTP client for community.lobu.ai API.
- * Stub for Phase 1 — most endpoints don't exist yet.
- */
-export async function apiRequest(
- path: string,
- options: RequestInit = {}
-): Promise> {
- const token = await getToken();
- const context = await resolveContext();
- const url = `${context.apiUrl}${path}`;
-
- const headers: Record = {
- "Content-Type": "application/json",
- "X-Lobu-Org": "default",
- ...(token ? { Authorization: `Bearer ${token}` } : {}),
- ...(options.headers as Record | undefined),
- };
-
- try {
- const response = await fetch(url, { ...options, headers });
- if (!response.ok) {
- const body = await response.text();
- return { ok: false, error: `${response.status}: ${body}` };
- }
- const data = (await response.json()) as T;
- return { ok: true, data };
- } catch (err) {
- return {
- ok: false,
- error: err instanceof Error ? err.message : String(err),
- };
- }
-}
diff --git a/packages/cli/src/api/context.ts b/packages/cli/src/api/context.ts
index 6da0ef54c..70ceb5007 100644
--- a/packages/cli/src/api/context.ts
+++ b/packages/cli/src/api/context.ts
@@ -4,7 +4,7 @@ import { join } from "node:path";
export const LOBU_CONFIG_DIR = join(homedir(), ".config", "lobu");
export const DEFAULT_CONTEXT_NAME = "community";
-export const DEFAULT_API_URL = "https://community.lobu.ai/api/v1";
+const DEFAULT_API_URL = "https://community.lobu.ai/api/v1";
const CONTEXTS_FILE = join(LOBU_CONFIG_DIR, "config.json");
@@ -38,9 +38,7 @@ export async function loadContextConfig(): Promise {
}
}
-export async function saveContextConfig(
- config: LobuContextConfig
-): Promise {
+async function saveContextConfig(config: LobuContextConfig): Promise {
await mkdir(LOBU_CONFIG_DIR, { recursive: true });
await writeFile(CONTEXTS_FILE, JSON.stringify(config, null, 2), {
mode: 0o600,
diff --git a/packages/cli/src/commands/chat.ts b/packages/cli/src/commands/chat.ts
index 96a58fd44..ff7d03276 100644
--- a/packages/cli/src/commands/chat.ts
+++ b/packages/cli/src/commands/chat.ts
@@ -8,19 +8,26 @@ import { renderMarkdown } from "../utils/markdown.js";
/**
* `lobu chat "prompt"` — send a prompt to an agent and stream the response.
*
- * Requires `lobu dev` running. Connects to the local gateway,
- * creates a session, sends the message, streams output, then exits.
+ * Without --user: API mode — creates a session, sends message, streams to terminal.
+ * With --user platform:id: Platform mode — sends through Telegram/Slack, response
+ * appears on the platform. Terminal shows the streamed response too.
*/
export async function chatCommand(
cwd: string,
prompt: string,
- options: { agent?: string; gateway?: string }
+ options: {
+ agent?: string;
+ gateway?: string;
+ user?: string;
+ thread?: string;
+ dryRun?: boolean;
+ new?: boolean;
+ }
): Promise {
const gatewayUrl = (
options.gateway ?? (await resolveGatewayUrl(cwd))
).replace(/\/$/, "");
- // Resolve auth token: CLI JWT (from `lobu login`) → ADMIN_PASSWORD env var
const authToken = (await getToken()) ?? process.env.ADMIN_PASSWORD;
if (!authToken) {
console.error(
@@ -31,12 +38,142 @@ export async function chatCommand(
process.exit(1);
}
- // Resolve agent ID from flag or first agent in lobu.toml (undefined = ephemeral)
const agentId = options.agent ?? (await resolveAgentId(cwd));
- // 1. Create agent session
- const createBody: Record = {};
- if (agentId) createBody.agentId = agentId;
+ // Parse --user flag: "telegram:12345" → { platform: "telegram", userId: "12345" }
+ const platformUser = options.user ? parsePlatformUser(options.user) : null;
+
+ if (platformUser) {
+ // Platform mode: route through Telegram/Slack
+ await sendViaPlatform(gatewayUrl, authToken, {
+ agentId,
+ platform: platformUser.platform,
+ userId: platformUser.userId,
+ message: prompt,
+ thread: options.thread,
+ });
+ } else {
+ // API mode: create session, send message, stream response
+ await sendViaApi(gatewayUrl, authToken, {
+ agentId,
+ message: prompt,
+ thread: options.thread,
+ dryRun: options.dryRun,
+ forceNew: options.new,
+ });
+ }
+}
+
+function parsePlatformUser(
+ user: string
+): { platform: string; userId: string } | null {
+ const colonIndex = user.indexOf(":");
+ if (colonIndex === -1) {
+ // No platform prefix — use as plain userId in API mode
+ return null;
+ }
+ return {
+ platform: user.slice(0, colonIndex),
+ userId: user.slice(colonIndex + 1),
+ };
+}
+
+/**
+ * Platform mode: send message through Telegram/Slack via /api/v1/agents/{agentId}/messages.
+ * The response appears on the platform AND streams to terminal via eventsUrl.
+ */
+async function sendViaPlatform(
+ gatewayUrl: string,
+ authToken: string,
+ opts: {
+ agentId?: string;
+ platform: string;
+ userId: string;
+ message: string;
+ thread?: string;
+ }
+): Promise {
+ const agentId = opts.agentId || `test-${opts.platform}`;
+ const body: Record = {
+ platform: opts.platform,
+ content: opts.message,
+ };
+
+ // Platform-specific routing
+ if (opts.platform === "telegram") {
+ body.telegram = { chatId: opts.userId };
+ } else if (opts.platform === "slack") {
+ body.slack = {
+ channel: opts.userId,
+ thread: opts.thread,
+ };
+ } else if (opts.platform === "discord") {
+ body.discord = { channelId: opts.userId };
+ }
+
+ const res = await fetch(
+ `${gatewayUrl}/api/v1/agents/${encodeURIComponent(agentId)}/messages`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${authToken}`,
+ },
+ body: JSON.stringify(body),
+ }
+ );
+
+ if (!res.ok) {
+ const resBody = await res.text().catch(() => "");
+ console.error(
+ chalk.red(`\n Failed to send message (${res.status}): ${resBody}\n`)
+ );
+ process.exit(1);
+ }
+
+ const result = (await res.json()) as {
+ success: boolean;
+ agentId?: string;
+ eventsUrl?: string;
+ queued?: boolean;
+ };
+
+ if (result.eventsUrl) {
+ // Stream the response from the agent
+ const sseUrl = result.eventsUrl.startsWith("http")
+ ? result.eventsUrl
+ : `${gatewayUrl}${result.eventsUrl}`;
+
+ const sseController = new AbortController();
+ await streamResponse(sseUrl, authToken, sseController);
+ } else {
+ console.log(
+ chalk.dim(
+ ` Message sent via ${opts.platform}. Response will appear on the platform.\n`
+ )
+ );
+ }
+}
+
+/**
+ * API mode: create session, send message, stream response to terminal.
+ */
+async function sendViaApi(
+ gatewayUrl: string,
+ authToken: string,
+ opts: {
+ agentId?: string;
+ message: string;
+ thread?: string;
+ dryRun?: boolean;
+ forceNew?: boolean;
+ }
+): Promise {
+ const createBody: Record = {};
+ if (opts.agentId) createBody.agentId = opts.agentId;
+ if (opts.thread) createBody.thread = opts.thread;
+ if (opts.dryRun) createBody.dryRun = true;
+ if (opts.forceNew) createBody.forceNew = true;
const createRes = await fetch(`${gatewayUrl}/api/v1/agents`, {
method: "POST",
@@ -68,23 +205,20 @@ export async function chatCommand(
token: string;
};
- // Build URLs from gateway flag (returned URLs may point to a public domain)
const base = `${gatewayUrl}/api/v1/agents/${session.agentId}`;
const sseUrl = `${base}/events`;
const messagesUrl = `${base}/messages`;
- // 2. Open SSE connection before sending message so we don't miss events
const sseController = new AbortController();
const streaming = streamResponse(sseUrl, session.token, sseController);
- // 3. Send the prompt
const msgRes = await fetch(messagesUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${session.token}`,
},
- body: JSON.stringify({ content: prompt }),
+ body: JSON.stringify({ content: opts.message }),
});
if (!msgRes.ok) {
@@ -96,28 +230,16 @@ export async function chatCommand(
process.exit(1);
}
- // 4. Wait for streaming to complete
await streaming;
}
async function resolveAgentId(cwd: string): Promise {
const result = await loadConfig(cwd);
- if (isLoadError(result)) {
- // No lobu.toml — use ephemeral agent
- return undefined;
- }
-
+ if (isLoadError(result)) return undefined;
const ids = Object.keys(result.config.agents);
- if (ids.length === 0) {
- return undefined;
- }
-
- return ids[0]!;
+ return ids[0];
}
-/**
- * Connect to SSE, print deltas to stdout, resolve on complete/error.
- */
async function streamResponse(
sseUrl: string,
token: string,
@@ -176,8 +298,7 @@ async function streamResponse(
console.error(`\n${renderMarkdown(data.content)}\n`);
}
controller.abort();
- process.exit(1);
- break;
+ return;
case "link-button":
case "question":
case "grant-request":
@@ -195,7 +316,7 @@ async function streamResponse(
chalk.red(`\n Agent error: ${String(data.error)}\n`)
);
controller.abort();
- process.exit(1);
+ return;
}
currentEvent = "";
@@ -205,7 +326,6 @@ async function streamResponse(
}
}
} catch (err: unknown) {
- // AbortError is expected when we call controller.abort() on complete
if (err instanceof Error && err.name === "AbortError") return;
throw err;
} finally {
diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts
index 7ada5382c..0632e0a72 100644
--- a/packages/cli/src/commands/dev.ts
+++ b/packages/cli/src/commands/dev.ts
@@ -1,24 +1,14 @@
import { spawn } from "node:child_process";
-import { mkdir, readFile, writeFile } from "node:fs/promises";
-import { basename, join, resolve } from "node:path";
+import { readFile, writeFile } from "node:fs/promises";
+import { basename, join } from "node:path";
import chalk from "chalk";
import ora from "ora";
-import { loadSkillsRegistry } from "../commands/skills/registry.js";
-import type {
- AgentManifestEntry,
- AgentsManifest,
-} from "../config/agents-manifest.js";
-import {
- isLoadError,
- loadAgentMarkdown,
- loadConfig,
- loadSkillFiles,
-} from "../config/loader.js";
-import type { AgentEntry } from "../config/schema.js";
+import { isLoadError, loadConfig } from "../config/loader.js";
/**
- * `lobu dev` — smart wrapper around `docker compose up`.
- * Reads lobu.toml, seeds .env + agents manifest, then passes all args through.
+ * `lobu run` — smart wrapper around `docker compose up`.
+ * Validates lobu.toml, seeds .env, then starts docker compose.
+ * The gateway reads lobu.toml directly from the mounted workspace.
*/
export async function devCommand(
cwd: string,
@@ -41,10 +31,7 @@ export async function devCommand(
const spinner = ora("Preparing local dev environment...").start();
try {
- const lobuDir = join(cwd, ".lobu");
- await mkdir(lobuDir, { recursive: true });
-
- // Parse .env first so we can resolve $VAR references in lobu.toml
+ // Parse .env to merge derived vars
const envPath = join(cwd, ".env");
let existingEnv = "";
try {
@@ -54,16 +41,15 @@ export async function devCommand(
}
const dotenvVars = parseEnvFile(existingEnv);
- const { manifest, envVars } = await buildManifest(
- cwd,
- config.agents,
- dotenvVars
- );
+ const agentCount = Object.keys(config.agents).length;
- await writeFile(
- join(lobuDir, "agents.json"),
- JSON.stringify(manifest, null, 2)
- );
+ // Derive env vars (compose project name from first agent or directory name)
+ const firstAgent = Object.values(config.agents)[0];
+ const envVars: Record = {
+ COMPOSE_PROJECT_NAME: firstAgent?.name
+ ? firstAgent.name.toLowerCase().replace(/\s+/g, "-")
+ : basename(cwd),
+ };
// Merge derived vars into existing .env (preserves comments and formatting)
await mergeEnvFile(envPath, existingEnv, envVars);
@@ -84,9 +70,7 @@ export async function devCommand(
const fallbackPort = dotenvVars.GATEWAY_PORT || "8080";
- console.log(
- chalk.cyan(`\n Starting ${manifest.agents.length} agent(s)...\n`)
- );
+ console.log(chalk.cyan(`\n Starting ${agentCount} agent(s)...\n`));
const explicitDetach =
passthroughArgs.includes("-d") || passthroughArgs.includes("--detach");
@@ -129,7 +113,7 @@ export async function devCommand(
const gatewayUrl = `http://localhost:${port}`;
console.log(chalk.green("\n Lobu is running!\n"));
- console.log(chalk.cyan(` Admin page: ${gatewayUrl}/agents`));
+ console.log(chalk.cyan(` API docs: ${gatewayUrl}/api/docs`));
console.log(chalk.dim(`\n Stop: docker compose down`));
if (explicitDetach) {
@@ -163,183 +147,6 @@ export async function devCommand(
}
}
-/**
- * Build agents manifest and merged env vars from [agents.*] config.
- */
-async function buildManifest(
- cwd: string,
- agents: Record,
- dotenvVars: Record
-): Promise<{ manifest: AgentsManifest; envVars: Record }> {
- const entries: AgentManifestEntry[] = [];
- const rootSkillsDir = join(cwd, "skills");
- const registrySkills = new Map(
- loadSkillsRegistry().map((skill) => [skill.id, skill])
- );
-
- for (const [agentId, agentConfig] of Object.entries(agents)) {
- const agentDir = resolve(cwd, agentConfig.dir);
- const markdown = await loadAgentMarkdown(agentDir);
- const skillFiles = await loadSkillFiles([
- rootSkillsDir,
- join(agentDir, "skills"),
- ]);
- const systemSkills = agentConfig.skills.enabled
- .map((skillId) => registrySkills.get(skillId))
- .filter((skill): skill is NonNullable => !!skill)
- .map((skill) => ({
- repo: `system/${skill.id}`,
- name: skill.name,
- description: skill.description,
- instructions: skill.instructions,
- enabled: true,
- system: true,
- content: "",
- mcpServers: skill.mcpServers?.map((mcp) => ({
- id: mcp.id,
- name: mcp.name,
- url: mcp.url,
- type: mcp.type,
- command: mcp.command,
- args: mcp.args,
- })),
- nixPackages: skill.nixPackages,
- permissions: skill.permissions,
- providers: skill.providers?.length ? [skill.id] : undefined,
- }));
- const localSkills = skillFiles.map((skillFile) => ({
- repo: `local/${skillFile.name}`,
- name: skillFile.name,
- content: skillFile.content,
- enabled: true,
- }));
-
- const entry: AgentManifestEntry = {
- agentId,
- name: agentConfig.name,
- description: agentConfig.description,
- settings: { ...markdown },
- };
-
- if (agentConfig.providers.length > 0) {
- entry.settings.installedProviders = agentConfig.providers.map((p) => ({
- providerId: p.id,
- }));
- entry.settings.modelSelection = { mode: "auto" };
- const providerModelPreferences = Object.fromEntries(
- agentConfig.providers
- .filter((provider) => !!provider.model?.trim())
- .map((provider) => [provider.id, provider.model!.trim()])
- );
- if (Object.keys(providerModelPreferences).length > 0) {
- entry.settings.providerModelPreferences = providerModelPreferences;
- }
- }
-
- if (systemSkills.length > 0 || localSkills.length > 0) {
- entry.settings.skillsConfig = {
- skills: [...systemSkills, ...localSkills],
- };
- }
-
- if (agentConfig.network) {
- entry.settings.networkConfig = {
- allowedDomains: agentConfig.network.allowed,
- deniedDomains: agentConfig.network.denied,
- };
- }
-
- if (agentConfig.worker?.nix_packages?.length) {
- entry.settings.nixConfig = {
- packages: agentConfig.worker.nix_packages,
- };
- }
-
- if (agentConfig.skills.mcp) {
- const mcpServers: Record = {};
- for (const [id, mcp] of Object.entries(agentConfig.skills.mcp)) {
- const mapped: Record = { ...mcp };
- if (mcp.oauth) {
- mapped.oauth = {
- authUrl: mcp.oauth.auth_url,
- tokenUrl: mcp.oauth.token_url,
- clientId: resolveEnvVar(mcp.oauth.client_id || "", dotenvVars),
- clientSecret: resolveEnvVar(
- mcp.oauth.client_secret || "",
- dotenvVars
- ),
- scopes: mcp.oauth.scopes,
- tokenEndpointAuthMethod: mcp.oauth.token_endpoint_auth_method,
- };
- }
- // Resolve env vars in MCP env block
- if (mcp.env) {
- mapped.env = Object.fromEntries(
- Object.entries(mcp.env).map(([k, v]) => [
- k,
- resolveEnvVar(v, dotenvVars),
- ])
- );
- }
- mcpServers[id] = mapped;
- }
- entry.settings.mcpServers = mcpServers;
- }
-
- // Resolve provider credentials from $ENV_VAR references
- const credentials = agentConfig.providers
- .filter((p) => p.key)
- .map((p) => ({
- providerId: p.id,
- key: resolveEnvVar(p.key!, dotenvVars),
- }))
- .filter((c) => c.key); // skip if env var not found
-
- if (credentials.length > 0) {
- entry.credentials = credentials;
- }
-
- // Resolve connection configs from $ENV_VAR references
- const connections = agentConfig.connections
- .map((conn) => ({
- type: conn.type,
- config: Object.fromEntries(
- Object.entries(conn.config).map(([k, v]) => [
- k,
- resolveEnvVar(v, dotenvVars),
- ])
- ),
- }))
- .filter((conn) => Object.values(conn.config).every((v) => v !== "")); // skip if any env var missing
-
- if (connections.length > 0) {
- entry.connections = connections;
- }
-
- entries.push(entry);
- }
-
- const envVars: Record = {
- COMPOSE_PROJECT_NAME: entries[0]?.name
- ? entries[0].name.toLowerCase().replace(/\s+/g, "-")
- : basename(cwd),
- };
-
- return { manifest: { version: 1, agents: entries }, envVars };
-}
-
-/**
- * Resolve a value that may be a $ENV_VAR reference.
- * Returns the resolved value, or empty string if the env var is not set.
- */
-function resolveEnvVar(value: string, envVars: Record): string {
- if (value.startsWith("$")) {
- const varName = value.slice(1);
- return envVars[varName] || process.env[varName] || "";
- }
- return value;
-}
-
function parseEnvFile(content: string): Record {
const vars: Record = {};
for (const line of content.split("\n")) {
diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts
index 6d8f5a054..4453b9bbe 100644
--- a/packages/cli/src/commands/init.ts
+++ b/packages/cli/src/commands/init.ts
@@ -297,63 +297,57 @@ export async function initCommand(
}
}
- // Auth provider (OAuth login for settings page + memory/integrations)
- const { authProvider } = await inquirer.prompt([
+ // Memory
+ const { memoryChoice } = await inquirer.prompt([
{
type: "list",
- name: "authProvider",
- message: "Auth provider (OAuth login, memory & integrations):",
+ name: "memoryChoice",
+ message: "Memory:",
choices: [
- { name: "Owletto (owletto.com)", value: "owletto" },
- { name: "Custom URL", value: "custom" },
- { name: "None (admin password only)", value: "none" },
+ { name: "None (filesystem memory)", value: "none" },
+ { name: "Owletto Cloud (owletto.com)", value: "owletto-cloud" },
+ {
+ name: "Owletto Local (runs alongside gateway)",
+ value: "owletto-local",
+ },
+ { name: "Custom Owletto URL", value: "owletto-custom" },
],
- default: "owletto",
+ default: "none",
},
]);
- const oauthSecrets: Array<{ envVar: string; value: string }> = [];
- if (authProvider === "owletto") {
- oauthSecrets.push({
- envVar: "AUTH_MCP_URL",
- value: "https://owletto.com/mcp",
+ const envSecrets: Array<{ envVar: string; value: string }> = [];
+ let includeOwlettoLocal = false;
+ let owlettoUrl = "";
+
+ if (memoryChoice === "owletto-cloud") {
+ owlettoUrl = "https://owletto.com/mcp";
+ envSecrets.push({ envVar: "MEMORY_URL", value: owlettoUrl });
+ } else if (memoryChoice === "owletto-local") {
+ includeOwlettoLocal = true;
+ owlettoUrl = "http://owletto:8787/mcp";
+ envSecrets.push({ envVar: "MEMORY_URL", value: owlettoUrl });
+ envSecrets.push({
+ envVar: "OWLETTO_AUTH_SECRET",
+ value: randomBytes(32).toString("hex"),
+ });
+ envSecrets.push({
+ envVar: "OWLETTO_DB_PASSWORD",
+ value: randomBytes(16).toString("hex"),
});
- } else if (authProvider === "custom") {
- const { customAuthMcpUrl } = await inquirer.prompt([
+ } else if (memoryChoice === "owletto-custom") {
+ const { customOwlettoUrl } = await inquirer.prompt([
{
type: "input",
- name: "customAuthMcpUrl",
- message: "Auth MCP URL:",
+ name: "customOwlettoUrl",
+ message: "Owletto MCP URL:",
+ validate: (v: string) => (v ? true : "URL is required"),
},
]);
- if (customAuthMcpUrl) {
- oauthSecrets.push({
- envVar: "AUTH_MCP_URL",
- value: customAuthMcpUrl,
- });
- }
- }
-
- // Memory plugin
- const { memoryPlugin } = await inquirer.prompt([
- {
- type: "list",
- name: "memoryPlugin",
- message: "Memory plugin:",
- choices: [
- { name: "Owletto (persistent, cross-conversation)", value: "owletto" },
- { name: "Native (filesystem-based)", value: "native" },
- ],
- default: authProvider === "owletto" ? "owletto" : "native",
- },
- ]);
-
- if (memoryPlugin !== "owletto") {
- oauthSecrets.push({
- envVar: "MEMORY_PLUGIN",
- value: memoryPlugin,
- });
+ owlettoUrl = customOwlettoUrl;
+ envSecrets.push({ envVar: "MEMORY_URL", value: owlettoUrl });
}
+ // "none" — no env var needed, gateway defaults to filesystem memory
// Compute network domains from selected policy
let allowedDomains: string;
@@ -395,8 +389,7 @@ export async function initCommand(
const spinner = ora("Creating Lobu project...").start();
try {
- // Create .lobu and data directories in project directory
- await mkdir(join(projectDir, ".lobu"), { recursive: true });
+ // Create data directory in project directory
await mkdir(join(projectDir, "data"), { recursive: true });
// Generate lobu.toml
@@ -450,7 +443,7 @@ export async function initCommand(
}
// Save OAuth secrets to .env
- for (const secret of oauthSecrets) {
+ for (const secret of envSecrets) {
await secretsSetCommand(projectDir, secret.envVar, secret.value);
}
@@ -517,6 +510,7 @@ export async function initCommand(
gatewayPort,
dockerfilePath: "./Dockerfile.worker",
deploymentMode: answers.deploymentMode,
+ includeOwlettoLocal,
});
await writeFile(join(projectDir, composeFilename), composeContent);
@@ -551,13 +545,30 @@ export async function initCommand(
console.log();
const gatewayUrl = `http://localhost:${gatewayPort}`;
+ if (owlettoUrl) {
+ const displayUrl = includeOwlettoLocal
+ ? "http://localhost:8787"
+ : owlettoUrl;
+ console.log(chalk.cyan(" Owletto:"));
+ console.log(chalk.dim(` ${displayUrl}\n`));
+ }
console.log(chalk.cyan(" 3. Start the services:"));
- console.log(chalk.dim(" lobu dev -d\n"));
- console.log(chalk.cyan(" 4. Open the admin page:"));
- console.log(chalk.dim(` ${gatewayUrl}/agents\n`));
- console.log(chalk.cyan(" 5. View logs:"));
+ console.log(chalk.dim(" lobu run -d\n"));
+ if (includeOwlettoLocal) {
+ console.log(chalk.cyan(" 4. Set up Owletto (first run):"));
+ console.log(
+ chalk.dim(" Visit http://localhost:8787 to create your account\n")
+ );
+ }
+ console.log(
+ chalk.cyan(` ${includeOwlettoLocal ? "5" : "4"}. Open the API docs:`)
+ );
+ console.log(chalk.dim(` ${gatewayUrl}/api/docs\n`));
+ console.log(chalk.cyan(` ${includeOwlettoLocal ? "6" : "5"}. View logs:`));
console.log(chalk.dim(" docker compose logs -f\n"));
- console.log(chalk.cyan(" 6. Stop the services:"));
+ console.log(
+ chalk.cyan(` ${includeOwlettoLocal ? "7" : "6"}. Stop the services:`)
+ );
console.log(chalk.dim(" docker compose down\n"));
} catch (error) {
spinner.fail("Failed to create project");
@@ -604,7 +615,7 @@ async function generateLobuToml(
);
} else {
lines.push(
- "# Add providers via the admin page or uncomment below:",
+ "# Add providers via the gateway configuration APIs or uncomment below:",
`# [[agents.${id}.providers]]`,
'# id = "anthropic"',
'# key = "$ANTHROPIC_API_KEY"'
@@ -624,7 +635,7 @@ async function generateLobuToml(
}
} else {
lines.push(
- "# Messaging platform (add via admin page or uncomment below):",
+ "# Messaging platform (add via the gateway configuration APIs or uncomment below):",
`# [[agents.${id}.connections]]`,
'# type = "telegram"',
`# [agents.${id}.connections.config]`,
@@ -675,8 +686,10 @@ function generateDockerCompose(options: {
gatewayPort: string;
dockerfilePath: string;
deploymentMode: "embedded" | "docker";
+ includeOwlettoLocal?: boolean;
}): string {
- const { projectName, gatewayPort, deploymentMode } = options;
+ const { projectName, gatewayPort, deploymentMode, includeOwlettoLocal } =
+ options;
const gatewayImage = `ghcr.io/lobu-ai/lobu-gateway:latest`;
const workerImage = `ghcr.io/lobu-ai/lobu-worker-base:latest`;
@@ -698,6 +711,63 @@ function generateDockerCompose(options: {
- "127.0.0.1:8118:8118" # HTTP proxy for workers`
: "";
+ const owlettoServices = includeOwlettoLocal
+ ? `
+ owletto-postgres:
+ image: pgvector/pgvector:pg17
+ environment:
+ POSTGRES_USER: owletto
+ POSTGRES_PASSWORD: \${OWLETTO_DB_PASSWORD}
+ POSTGRES_DB: owletto
+ volumes:
+ - owletto-pgdata:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U owletto -d owletto"]
+ interval: 10s
+ timeout: 3s
+ retries: 5
+ networks:
+ - lobu-internal
+ restart: unless-stopped
+
+ owletto:
+ image: ghcr.io/lobu-ai/owletto-app:latest
+ pull_policy: always
+ ports:
+ - "127.0.0.1:8787:8787"
+ environment:
+ DATABASE_URL: postgresql://owletto:\${OWLETTO_DB_PASSWORD}@owletto-postgres:5432/owletto
+ BETTER_AUTH_SECRET: \${OWLETTO_AUTH_SECRET}
+ PORT: "8787"
+ HOST: 0.0.0.0
+ networks:
+ - lobu-internal
+ depends_on:
+ owletto-postgres:
+ condition: service_healthy
+ healthcheck:
+ test: ["CMD-SHELL", "curl -f http://127.0.0.1:8787/health || exit 1"]
+ interval: 10s
+ timeout: 10s
+ retries: 10
+ start_period: 20s
+ restart: unless-stopped
+`
+ : "";
+
+ const owlettoDependsOn = includeOwlettoLocal
+ ? `
+ owletto:
+ condition: service_healthy`
+ : "";
+
+ const owlettoVolumes = includeOwlettoLocal
+ ? `
+volumes:
+ owletto-pgdata:
+`
+ : "";
+
return `# Generated by @lobu/cli
# Deployment mode: ${deploymentMode}
@@ -718,7 +788,7 @@ services:
networks:
- lobu-internal
restart: unless-stopped
-
+${owlettoServices}
gateway:
image: ${gatewayImage}
pull_policy: always
@@ -735,16 +805,16 @@ services:
ENCRYPTION_KEY: \${ENCRYPTION_KEY}
WORKER_ALLOWED_DOMAINS: \${WORKER_ALLOWED_DOMAINS:-}
WORKER_DISALLOWED_DOMAINS: \${WORKER_DISALLOWED_DOMAINS:-}
- AUTH_MCP_URL: \${AUTH_MCP_URL:-}
- MEMORY_PLUGIN: \${MEMORY_PLUGIN:-owletto}
+ MEMORY_URL: \${MEMORY_URL:-}
+ LOBU_WORKSPACE_ROOT: /workspace/project
volumes:${dockerSocketMount}
- - ./.lobu:/app/.lobu
+ - .:/workspace/project:ro
networks:
- lobu-public
- lobu-internal
depends_on:
redis:
- condition: service_healthy
+ condition: service_healthy${owlettoDependsOn}
restart: unless-stopped
networks:
@@ -753,6 +823,6 @@ networks:
lobu-internal:
internal: true
driver: bridge
-
+${owlettoVolumes}
`;
}
diff --git a/packages/cli/src/commands/launch.ts b/packages/cli/src/commands/launch.ts
deleted file mode 100644
index 850733713..000000000
--- a/packages/cli/src/commands/launch.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import chalk from "chalk";
-import { isLoadError, loadConfig } from "../config/loader.js";
-import { validateCommand } from "./validate.js";
-
-export async function launchCommand(
- cwd: string,
- options: { dryRun?: boolean; env?: string; message?: string }
-): Promise {
- const valid = await validateCommand(cwd);
- if (!valid) {
- process.exit(1);
- }
-
- const result = await loadConfig(cwd);
- if (isLoadError(result)) {
- process.exit(1);
- }
-
- const agentCount = Object.keys(result.config.agents).length;
- console.log(chalk.dim(` ${agentCount} agent(s) configured`));
-
- if (options.dryRun) {
- console.log(chalk.dim("\n Dry run — no changes applied.\n"));
- return;
- }
-
- console.log(chalk.bold.cyan("\n Lobu Cloud is in early access.\n"));
- console.log(chalk.bold(" Get started:"));
- console.log(
- ` Schedule a call ${chalk.cyan("https://cal.com/burakemre/lobu")}`
- );
- console.log(
- ` Self-host now ${chalk.cyan("https://lobu.ai/docs/deployment")}`
- );
- console.log(
- ` REST API docs ${chalk.cyan("https://community.lobu.ai/api/docs")}`
- );
- console.log(chalk.dim(`\n Run locally with: ${chalk.white("lobu dev")}\n`));
-}
diff --git a/packages/cli/src/commands/status.ts b/packages/cli/src/commands/status.ts
index 3914844fe..76631c77b 100644
--- a/packages/cli/src/commands/status.ts
+++ b/packages/cli/src/commands/status.ts
@@ -44,7 +44,7 @@ export async function statusCommand(cwd: string): Promise {
status = (await res.json()) as StatusResponse;
} catch {
console.log(chalk.yellow("\n Gateway not reachable."));
- console.log(chalk.dim(" Start with `lobu dev` to run your agents.\n"));
+ console.log(chalk.dim(" Start with `lobu run` to run your agents.\n"));
return;
}
diff --git a/packages/cli/src/config/agents-manifest.ts b/packages/cli/src/config/agents-manifest.ts
deleted file mode 100644
index 24ba2453b..000000000
--- a/packages/cli/src/config/agents-manifest.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * Agents manifest — written to .lobu/agents.json by the CLI,
- * read by the gateway on startup to seed agent settings into Redis.
- */
-// NOTE: Keep in sync with packages/gateway/src/services/agent-seeder.ts
-
-export interface AgentsManifest {
- version: 1;
- agents: AgentManifestEntry[];
-}
-
-export interface AgentManifestEntry {
- agentId: string;
- name: string;
- description?: string;
- settings: {
- identityMd?: string;
- soulMd?: string;
- userMd?: string;
- installedProviders?: Array<{
- providerId: string;
- }>;
- modelSelection?: {
- mode: "auto" | "pinned";
- pinnedModel?: string;
- };
- providerModelPreferences?: Record;
- nixConfig?: {
- packages?: string[];
- flakeUrl?: string;
- };
- skillsConfig?: {
- skills: Array<{
- repo: string;
- name: string;
- description?: string;
- instructions?: string;
- content: string;
- enabled: boolean;
- system?: boolean;
- mcpServers?: Array<{
- id: string;
- name?: string;
- url?: string;
- type?: string;
- command?: string;
- args?: string[];
- }>;
- nixPackages?: string[];
- permissions?: string[];
- providers?: string[];
- modelPreference?: string;
- thinkingLevel?: "off" | "low" | "medium" | "high";
- }>;
- };
- networkConfig?: {
- allowedDomains?: string[];
- deniedDomains?: string[];
- };
- mcpServers?: Record<
- string,
- {
- url?: string;
- command?: string;
- args?: string[];
- env?: Record;
- headers?: Record;
- oauth?: {
- authUrl: string;
- tokenUrl: string;
- clientId?: string;
- clientSecret?: string;
- scopes?: string[];
- tokenEndpointAuthMethod?: string;
- };
- }
- >;
- };
- credentials?: Array<{
- providerId: string;
- key: string;
- }>;
- connections?: Array<{
- type: string;
- config: Record;
- }>;
-}
diff --git a/packages/cli/src/config/loader.ts b/packages/cli/src/config/loader.ts
index 6d5988739..36601b6fd 100644
--- a/packages/cli/src/config/loader.ts
+++ b/packages/cli/src/config/loader.ts
@@ -1,5 +1,5 @@
-import { readdir, readFile } from "node:fs/promises";
-import { join, resolve } from "node:path";
+import { readFile } from "node:fs/promises";
+import { join } from "node:path";
import { parse as parseToml } from "smol-toml";
import { type LobuTomlConfig, lobuConfigSchema } from "./schema.js";
@@ -57,71 +57,3 @@ export function isLoadError(
): result is LoadError {
return "error" in result;
}
-
-/**
- * Read IDENTITY.md, SOUL.md, USER.md from a directory.
- * Returns content or undefined if file doesn't exist.
- */
-export async function loadAgentMarkdown(
- dir: string
-): Promise<{ identityMd?: string; soulMd?: string; userMd?: string }> {
- const result: { identityMd?: string; soulMd?: string; userMd?: string } = {};
-
- const files = [
- { path: "IDENTITY.md", key: "identityMd" as const },
- { path: "SOUL.md", key: "soulMd" as const },
- { path: "USER.md", key: "userMd" as const },
- ];
-
- for (const { path, key } of files) {
- try {
- const content = await readFile(join(dir, path), "utf-8");
- if (content.trim()) {
- result[key] = content.trim();
- }
- } catch {
- // File doesn't exist, skip
- }
- }
-
- return result;
-}
-
-/**
- * Scan directories for *.md skill files.
- * Later dirs override earlier dirs (agent-specific overrides shared).
- * Returns array of { name, content } where name is filename without extension.
- */
-export async function loadSkillFiles(
- dirs: string[]
-): Promise> {
- const skillMap = new Map();
-
- for (const dir of dirs) {
- const resolvedDir = resolve(dir);
- let entries: string[];
- try {
- entries = await readdir(resolvedDir);
- } catch {
- continue; // Directory doesn't exist, skip
- }
-
- for (const entry of entries) {
- if (!entry.endsWith(".md")) continue;
- const name = entry.slice(0, -3); // strip .md
- try {
- const content = await readFile(join(resolvedDir, entry), "utf-8");
- if (content.trim()) {
- skillMap.set(name, content.trim());
- }
- } catch {
- // Skip unreadable files
- }
- }
- }
-
- return Array.from(skillMap.entries()).map(([name, content]) => ({
- name,
- content,
- }));
-}
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index 8393642bf..b6a0b39e5 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -48,12 +48,26 @@ export async function runCli(
.command("chat ")
.description("Send a prompt to an agent and stream the response")
.option("-a, --agent ", "Agent ID (defaults to first in lobu.toml)")
+ .option("-u, --user ", "User ID to impersonate (e.g. telegram:12345)")
+ .option("-t, --thread ", "Thread/conversation ID for multi-turn")
.option(
"-g, --gateway ",
"Gateway URL (default: http://localhost:8080)"
)
+ .option("--dry-run", "Process without persisting history")
+ .option("--new", "Force new session (ignore existing)")
.action(
- async (prompt: string, options: { agent?: string; gateway?: string }) => {
+ async (
+ prompt: string,
+ options: {
+ agent?: string;
+ gateway?: string;
+ user?: string;
+ thread?: string;
+ dryRun?: boolean;
+ new?: boolean;
+ }
+ ) => {
const { chatCommand } = await import("./commands/chat.js");
await chatCommand(process.cwd(), prompt, options);
}
@@ -69,12 +83,12 @@ export async function runCli(
if (!valid) process.exit(1);
});
- // ─── dev ────────────────────────────────────────────────────────────
+ // ─── run ────────────────────────────────────────────────────────────
// Passthrough to docker compose up — all extra args forwarded directly.
- // lobu dev -d --build → docker compose up -d --build
+ // lobu run -d --build → docker compose up -d --build
program
- .command("dev")
- .description("Run agent locally (reads lobu.toml, then docker compose up)")
+ .command("run")
+ .description("Run agent stack (reads lobu.toml, then docker compose up)")
.allowUnknownOption(true)
.helpOption(false)
.action(async (_opts: unknown, cmd: Command) => {
@@ -82,20 +96,6 @@ export async function runCli(
await devCommand(process.cwd(), cmd.args);
});
- // ─── launch ─────────────────────────────────────────────────────────
- program
- .command("launch")
- .description("Launch agent to Lobu Cloud")
- .option("-e, --env ", "Target environment")
- .option("--dry-run", "Show what would change")
- .option("-m, --message ", "Deployment note")
- .action(
- async (options: { env?: string; dryRun?: boolean; message?: string }) => {
- const { launchCommand } = await import("./commands/launch.js");
- await launchCommand(process.cwd(), options);
- }
- );
-
// ─── login ──────────────────────────────────────────────────────────
program
.command("login")
diff --git a/packages/cli/src/mcp-servers.json b/packages/cli/src/mcp-servers.json
index f0492b975..865639c83 100644
--- a/packages/cli/src/mcp-servers.json
+++ b/packages/cli/src/mcp-servers.json
@@ -17,7 +17,7 @@
"responseType": "code"
}
},
- "setupInstructions": "1. Create a GitHub OAuth App at https://github.com/settings/developers\n2. Set Authorization callback URL to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Update .lobu/mcp.config.json with your Client ID\n5. Add GITHUB_CLIENT_SECRET to your .env file"
+ "setupInstructions": "1. Create a GitHub OAuth App at https://github.com/settings/developers\n2. Set Authorization callback URL to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Add GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET to your .env file"
},
{
"id": "playwright",
@@ -46,7 +46,7 @@
"responseType": "code"
}
},
- "setupInstructions": "1. Create a Figma App at https://www.figma.com/developers/apps\n2. Set Redirect URI to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Update .lobu/mcp.config.json with your Client ID\n5. Add FIGMA_CLIENT_SECRET to your .env file"
+ "setupInstructions": "1. Create a Figma App at https://www.figma.com/developers/apps\n2. Set Redirect URI to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Add FIGMA_CLIENT_ID and FIGMA_CLIENT_SECRET to your .env file"
},
{
"id": "notion",
@@ -65,7 +65,7 @@
"responseType": "code"
}
},
- "setupInstructions": "1. Create a Notion Integration at https://www.notion.so/my-integrations\n2. Set Redirect URI to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the OAuth Client ID and Secret\n4. Update .lobu/mcp.config.json with your Client ID\n5. Add NOTION_CLIENT_SECRET to your .env file"
+ "setupInstructions": "1. Create a Notion Integration at https://www.notion.so/my-integrations\n2. Set Redirect URI to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the OAuth Client ID and Secret\n4. Add NOTION_CLIENT_ID and NOTION_CLIENT_SECRET to your .env file"
},
{
"id": "firecrawl",
@@ -111,7 +111,7 @@
"responseType": "code"
}
},
- "setupInstructions": "1. Create an OAuth 2.0 app at https://developer.atlassian.com/console/myapps/\n2. Set Callback URL to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Secret\n4. Update .lobu/mcp.config.json with your Client ID\n5. Add JIRA_CLIENT_SECRET to your .env file"
+ "setupInstructions": "1. Create an OAuth 2.0 app at https://developer.atlassian.com/console/myapps/\n2. Set Callback URL to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Secret\n4. Add JIRA_CLIENT_ID and JIRA_CLIENT_SECRET to your .env file"
},
{
"id": "datadog",
@@ -144,7 +144,7 @@
"responseType": "code"
}
},
- "setupInstructions": "1. Create OAuth credentials at https://console.cloud.google.com/apis/credentials\n2. Set Authorized redirect URI to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Update .lobu/mcp.config.json with your Client ID\n5. Add GOOGLE_CLIENT_SECRET to your .env file"
+ "setupInstructions": "1. Create OAuth credentials at https://console.cloud.google.com/apis/credentials\n2. Set Authorized redirect URI to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Add GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to your .env file"
},
{
"id": "sentry",
@@ -172,7 +172,7 @@
"responseType": "code"
}
},
- "setupInstructions": "1. Create OAuth app at https://linear.app/settings/api\n2. Set Redirect URL to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Update .lobu/mcp.config.json with your Client ID\n5. Add LINEAR_CLIENT_SECRET to your .env file"
+ "setupInstructions": "1. Create OAuth app at https://linear.app/settings/api\n2. Set Redirect URL to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Add LINEAR_CLIENT_ID and LINEAR_CLIENT_SECRET to your .env file"
},
{
"id": "discord",
@@ -191,7 +191,7 @@
"responseType": "code"
}
},
- "setupInstructions": "1. Create Discord app at https://discord.com/developers/applications\n2. Set Redirect URL to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Update .lobu/mcp.config.json with your Client ID\n5. Add DISCORD_CLIENT_SECRET to your .env file"
+ "setupInstructions": "1. Create Discord app at https://discord.com/developers/applications\n2. Set Redirect URL to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Client ID and Client Secret\n4. Add DISCORD_CLIENT_ID and DISCORD_CLIENT_SECRET to your .env file"
},
{
"id": "gitlab",
@@ -210,7 +210,7 @@
"responseType": "code"
}
},
- "setupInstructions": "1. Create GitLab application at https://gitlab.com/-/profile/applications\n2. Set Redirect URI to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Application ID and Secret\n4. Update .lobu/mcp.config.json with your Client ID\n5. Add GITLAB_CLIENT_SECRET to your .env file"
+ "setupInstructions": "1. Create GitLab application at https://gitlab.com/-/profile/applications\n2. Set Redirect URI to: {PUBLIC_URL}/mcp/oauth/callback\n3. Copy the Application ID and Secret\n4. Add GITLAB_CLIENT_ID and GITLAB_CLIENT_SECRET to your .env file"
}
]
}
diff --git a/packages/cli/src/mcp-servers.ts b/packages/cli/src/mcp-servers.ts
deleted file mode 100644
index 50fdaed76..000000000
--- a/packages/cli/src/mcp-servers.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { readFileSync } from "node:fs";
-import { dirname, join } from "node:path";
-import { fileURLToPath } from "node:url";
-
-const __dirname = dirname(fileURLToPath(import.meta.url));
-
-export interface McpServerDefinition {
- id: string;
- name: string;
- description: string;
- type: "oauth" | "api-key" | "command" | "none";
- config: any;
- setupInstructions?: string;
-}
-
-// Load MCP servers from JSON file
-const mcpServersJson = readFileSync(
- join(__dirname, "mcp-servers.json"),
- "utf-8"
-);
-const mcpServersData = JSON.parse(mcpServersJson);
-
-export const MCP_SERVERS: McpServerDefinition[] = mcpServersData.servers;
-
-// Helper function to get OAuth servers
-// Helper function to generate env variable names
-export function getRequiredEnvVars(servers: McpServerDefinition[]): string[] {
- const envVars = new Set();
-
- servers.forEach((server) => {
- const configStr = JSON.stringify(server.config);
- // Extract all ${VARIABLE} and ${env:VARIABLE} patterns
- const matches = configStr.match(/\$\{(?:env:)?([A-Z_]+)\}/g) || [];
- matches.forEach((match) => {
- const varName = match.replace(/\$\{(?:env:)?([A-Z_]+)\}/, "$1");
- if (varName !== "PUBLIC_URL") {
- // PUBLIC_URL is handled separately
- envVars.add(varName);
- }
- });
- });
-
- return Array.from(envVars);
-}
diff --git a/packages/cli/src/templates/README.md.tmpl b/packages/cli/src/templates/README.md.tmpl
index 6aba598fb..1c818eb4b 100644
--- a/packages/cli/src/templates/README.md.tmpl
+++ b/packages/cli/src/templates/README.md.tmpl
@@ -8,7 +8,7 @@ Make sure you have Docker CLI installed.
```bash
# Start the services
-lobu dev -d
+lobu run -d
# View logs
docker compose logs -f
@@ -51,7 +51,7 @@ Edit `.env` to configure:
### Platform Connections
-Platforms (Slack, Telegram, Discord, WhatsApp, Teams) are configured via the admin page at `{PUBLIC_GATEWAY_URL}/agents`. No platform-specific env vars needed — just paste your bot token in the UI.
+Platforms (Slack, Telegram, Discord, WhatsApp, Teams) are configured through the gateway APIs. No platform-specific env vars are required in `docker-compose.yml`; manage connections against `{PUBLIC_GATEWAY_URL}/api/v1/connections` and related auth/config endpoints.
### Worker Customization
diff --git a/packages/cli/src/templates/TESTING.md.tmpl b/packages/cli/src/templates/TESTING.md.tmpl
index d197022fb..3dcd5583b 100644
--- a/packages/cli/src/templates/TESTING.md.tmpl
+++ b/packages/cli/src/templates/TESTING.md.tmpl
@@ -8,7 +8,7 @@ Send messages to your bot with optional file uploads.
### Endpoint
```
-POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send
+POST http://localhost:{{GATEWAY_PORT}}/api/v1/agents/{agentId}/messages
```
### Authentication
@@ -24,23 +24,23 @@ The bot token must be provided in the `Authorization` header, not in the request
#### JSON Request (Simple Message)
```bash
-curl -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
+curl -X POST http://localhost:{{GATEWAY_PORT}}/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
"platform": "slack",
"channel": "general",
- "message": "what is 2+2?"
+ "content": "what is 2+2?"
}'
```
#### Multipart Request (With File Upload)
```bash
-curl -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
+curl -X POST http://localhost:{{GATEWAY_PORT}}/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-F "platform=slack" \
-F "channel=C12345678" \
- -F "message=please review this file" \
+ -F "content=please review this file" \
-F "file=@/path/to/document.pdf"
```
@@ -50,7 +50,7 @@ curl -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
|-------|----------|-------------|
| `platform` | Yes | Platform name (currently: "slack") |
| `channel` | Yes | Channel ID (e.g., `C12345678`) or name (e.g., `general`, `#general`) |
-| `message` | Yes | Message text to send (use `@me` to mention the bot) |
+| `content` | Yes | Message text to send (use `@me` to mention the bot) |
| `threadId` | No | Thread ID to reply to (for thread continuity) |
| `files` | No | File attachments (multipart/form-data, up to 10 files) |
@@ -76,7 +76,7 @@ Use the `@me` placeholder to mention the bot in a platform-agnostic way:
```json
{
- "message": "@me what is 2+2?"
+ "content": "@me what is 2+2?"
}
```
@@ -90,39 +90,39 @@ If you don't want to mention the bot, simply omit `@me` from your message.
### Example: Simple Text Message (with @me)
```bash
-curl -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
+curl -X POST http://localhost:{{GATEWAY_PORT}}/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
"platform": "slack",
"channel": "general",
- "message": "@me what is 2+2?"
+ "content": "@me what is 2+2?"
}'
```
### Example: Without Bot Mention
```bash
-curl -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
+curl -X POST http://localhost:{{GATEWAY_PORT}}/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
"platform": "slack",
"channel": "general",
- "message": "just a regular message"
+ "content": "just a regular message"
}'
```
### Example: Thread Reply
```bash
-curl -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
+curl -X POST http://localhost:{{GATEWAY_PORT}}/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-H "Content-Type: application/json" \
-d '{
"platform": "slack",
"channel": "C12345678",
- "message": "tell me more about that",
+ "content": "tell me more about that",
"threadId": "1234567890.123456"
}'
```
@@ -130,22 +130,22 @@ curl -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
### Example: Single File Upload
```bash
-curl -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
+curl -X POST http://localhost:{{GATEWAY_PORT}}/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-F "platform=slack" \
-F "channel=dev-channel" \
- -F "message=@me analyze this CSV" \
+ -F "content=@me analyze this CSV" \
-F "files=@data.csv"
```
### Example: Multiple File Upload
```bash
-curl -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
+curl -X POST http://localhost:{{GATEWAY_PORT}}/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer xoxb-your-bot-token" \
-F "platform=slack" \
-F "channel=dev-channel" \
- -F "message=@me review these documents" \
+ -F "content=@me review these documents" \
-F "files=@document1.pdf" \
-F "files=@document2.pdf" \
-F "files=@spreadsheet.xlsx"
@@ -196,13 +196,13 @@ Testing a full conversation:
```bash
# Step 1: Send initial message
-RESPONSE=$(curl -s -X POST http://localhost:{{GATEWAY_PORT}}/api/messaging/send \
+RESPONSE=$(curl -s -X POST http://localhost:{{GATEWAY_PORT}}/api/v1/agents/{agentId}/messages \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"platform": "slack",
"channel": "test-channel",
- "message": "@me give me three options"
+ "content": "@me give me three options"
}')
THREAD_ID=$(echo $RESPONSE | jq -r '.threadId')
diff --git a/packages/cli/src/utils/config.ts b/packages/cli/src/utils/config.ts
deleted file mode 100644
index eb7a5b758..000000000
--- a/packages/cli/src/utils/config.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { constants } from "node:fs";
-import { access } from "node:fs/promises";
-import { join } from "node:path";
-
-export async function checkConfigExists(cwd: string): Promise {
- try {
- await access(join(cwd, ".lobu"), constants.F_OK);
- return true;
- } catch {
- return false;
- }
-}
diff --git a/packages/core/src/__tests__/fixtures/mock-redis.ts b/packages/core/src/__tests__/fixtures/mock-redis.ts
index 4ce7d9dde..0349988bb 100644
--- a/packages/core/src/__tests__/fixtures/mock-redis.ts
+++ b/packages/core/src/__tests__/fixtures/mock-redis.ts
@@ -155,6 +155,11 @@ export class MockRedisClient {
return set ? Array.from(set) : [];
}
+ async sismember(key: string, member: string): Promise {
+ const set = this.sets.get(key);
+ return set?.has(member) ? 1 : 0;
+ }
+
// --- List operations ---
async rpush(key: string, ...values: string[]): Promise {
diff --git a/packages/core/src/agent-policy.ts b/packages/core/src/agent-policy.ts
index f9118a1d9..925f57d55 100644
--- a/packages/core/src/agent-policy.ts
+++ b/packages/core/src/agent-policy.ts
@@ -29,22 +29,6 @@ export const CUSTOM_TOOL_METADATA: Record = {
description:
"List all pending reminders you have scheduled. Shows upcoming reminders with their schedule IDs and remaining time.",
},
- SearchSkills: {
- description:
- "Search for installable skills and MCP servers, or list installed capabilities. Pass a query to search available capabilities. Pass an empty query to list all installed skills and MCP servers.",
- },
- InstallSkill: {
- description:
- "Install or upgrade a skill or MCP server. Pass the id from SearchSkills results.",
- },
- InstallPackage: {
- description:
- "Request installation of system packages (nix). Sends approval buttons to the user. Stop and wait for approval after calling.",
- },
- RequestNetworkAccess: {
- description:
- "Request access to blocked domains. Sends inline approval buttons to the user. Stop and wait for approval after calling. Do NOT retry blocked requests — the domain is blocked at the network level.",
- },
GenerateImage: {
description:
"Generate an image from a text prompt and send it to the user. Use when the user asks for image generation, visual concepts, posters, illustrations, or edits that can be done from prompt instructions.",
@@ -136,39 +120,6 @@ export const TOOL_INTENT_RULES: ToolIntentRule[] = [
/\bevery\s+\d+\s*(minute|minutes|hour|hours|day|days|week|weeks)\b/i,
],
},
- {
- id: "package-installation",
- title: "System Package Installation",
- tools: ["InstallPackage"],
- instructionLines: [
- "If the user asks to install or update a system package, your first action must be InstallPackage.",
- "Do not run apt, brew, nix, or similar package installation commands directly.",
- "After calling InstallPackage, stop and wait for approval.",
- ],
- priority: 50,
- patterns: [
- /\binstall\b.*\b(package|dependency|dependencies|ffmpeg|imagemagick|curl|git)\b/i,
- /\b(upgrade|update|add)\b.*\b(package|dependency|dependencies)\b/i,
- /\b(ffmpeg|imagemagick)\b/i,
- /\b(apt|brew|nix(?:-shell)?|apk|yum)\b/i,
- ],
- },
- {
- id: "network-access",
- title: "Blocked Network Access",
- tools: ["RequestNetworkAccess"],
- instructionLines: [
- "If access to a domain is blocked or the user explicitly asks for network/domain access, use RequestNetworkAccess.",
- "Do not keep retrying blocked requests after a proxy denial.",
- "After calling RequestNetworkAccess, stop and wait for approval.",
- ],
- priority: 60,
- patterns: [
- /\b(request|need|grant|allow|whitelist|allowlist|unblock)\b.*\b(domain|network|access)\b/i,
- /\bblocked domain\b/i,
- /\bnetwork access\b/i,
- ],
- },
{
id: "image-generation",
title: "Image Generation",
@@ -183,19 +134,6 @@ export const TOOL_INTENT_RULES: ToolIntentRule[] = [
/\b(image|illustration|poster|logo|picture|photo|icon)\b.*\b(generate|create|make|draw|edit|design)\b/i,
],
},
- {
- id: "skills-discovery",
- title: "Skill and MCP Discovery",
- tools: ["SearchSkills", "InstallSkill"],
- instructionLines: [
- "If the user asks about adding capabilities, finding skills, or installing an MCP server, search with SearchSkills first.",
- "Use InstallSkill only after you have an id from SearchSkills.",
- ],
- priority: 80,
- patterns: [
- /\b(search|find|install|add)\b.*\b(skill|skills|mcp|mcp server|capability|capabilities)\b/i,
- ],
- },
];
export function getCustomToolDescription(name: string): string {
@@ -257,13 +195,13 @@ export function renderDetectedToolIntentRules(prompt: string): string {
export function buildUnconfiguredAgentNotice(settingsUrl?: string): string {
const settingsHint = settingsUrl
- ? ` Your settings page is: ${settingsUrl}`
+ ? ` Admin configuration URL: ${settingsUrl}`
: "";
return `## Agent Configuration Notice
Your identity, instructions, and user context (IDENTITY.md, SOUL.md, USER.md) are not configured yet.
-To configure your soul, ask your admin to open the settings page and fill in the Instructions section.${settingsHint}
+To configure your soul, ask your admin to update the agent instructions in the admin control plane.${settingsHint}
Until configured, behave as a helpful, concise AI assistant.`;
}
diff --git a/packages/core/src/agent-store.ts b/packages/core/src/agent-store.ts
index ac55ac4dc..af13ce7d8 100644
--- a/packages/core/src/agent-store.ts
+++ b/packages/core/src/agent-store.ts
@@ -1,9 +1,8 @@
/**
* AgentStore — unified interface for agent configuration storage.
*
- * Replaces 6 separate Redis-backed stores with a single abstraction.
* Implementations:
- * - RedisAgentStore (CLI mode, seeded from lobu.toml)
+ * - InMemoryAgentStore (default, populated from files or API)
* - Host-provided store (embedded mode, e.g. PostgresAgentStore in Owletto)
*/
@@ -14,7 +13,6 @@ import type {
McpServerConfig,
NetworkConfig,
NixConfig,
- RegistryEntry,
SkillsConfig,
ToolsConfig,
} from "./types";
@@ -37,7 +35,6 @@ export interface AgentSettings {
pluginsConfig?: PluginsConfig;
authProfiles?: AuthProfile[];
installedProviders?: InstalledProvider[];
- skillRegistries?: RegistryEntry[];
verboseLogging?: boolean;
templateAgentId?: string;
updatedAt: number;
@@ -125,6 +122,26 @@ export interface AgentConfigStore {
listSandboxes(connectionId: string): Promise;
}
+/**
+ * Find the first non-sandbox agent with installed providers configured.
+ * Used to pick a default template agent when creating ephemeral/API agents.
+ */
+export async function findTemplateAgentId(
+ store: Pick
+): Promise {
+ const agents = await store.listAgents();
+
+ for (const agent of agents) {
+ if (agent.parentConnectionId) continue;
+ const settings = await store.getSettings(agent.agentId);
+ if (settings?.installedProviders?.length) {
+ return agent.agentId;
+ }
+ }
+
+ return null;
+}
+
/**
* Platform wiring storage.
* Connections (Telegram, Slack, etc.) + channel bindings.
@@ -195,7 +212,7 @@ export interface AgentAccessStore {
/**
* Full storage interface — intersection of all sub-stores.
- * Implementations (RedisAgentStore, PostgresAgentStore) satisfy all 3.
+ * Implementations (InMemoryAgentStore, etc.) satisfy all 3.
* Hosts can provide individual sub-stores via GatewayOptions instead.
*/
export type AgentStore = AgentConfigStore &
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 5a0d017fa..752ee5e37 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -16,6 +16,7 @@ export type {
Grant,
StoredConnection,
} from "./agent-store";
+export { findTemplateAgentId } from "./agent-store";
export type { CommandContext, CommandDefinition } from "./command-registry";
// Command registry
export { CommandRegistry } from "./command-registry";
@@ -94,16 +95,6 @@ export type {
UserSuggestion,
} from "./types";
-// Platform constants
-export const SUPPORTED_PLATFORMS = [
- "telegram",
- "slack",
- "discord",
- "whatsapp",
- "teams",
-] as const;
-export type SupportedPlatform = (typeof SUPPORTED_PLATFORMS)[number];
-
// Agent Settings API response types (for UI consumers)
export type {
AgentConfigResponse,
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index d4ac070c3..266a6533d 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -137,8 +137,12 @@ export interface SkillConfig {
mcpServers?: SkillMcpServer[];
/** System packages declared by the skill (nix) */
nixPackages?: string[];
- /** Network domains the skill needs access to */
+ /** Network domains the skill needs access to (legacy flat list) */
permissions?: string[];
+ /** Network access policy declared by the skill */
+ networkConfig?: { allowedDomains?: string[]; deniedDomains?: string[] };
+ /** Tool permission policy declared by the skill */
+ toolPermissions?: { allow?: string[]; deny?: string[] };
/** AI providers the skill requires */
providers?: string[];
/** Preferred model for this skill (e.g., "anthropic/claude-opus-4") */
diff --git a/packages/gateway/package.json b/packages/gateway/package.json
index e87e63da5..263cd204b 100644
--- a/packages/gateway/package.json
+++ b/packages/gateway/package.json
@@ -13,15 +13,15 @@
}
},
"scripts": {
- "generate:css": "bun run scripts/generate-css.ts",
- "build": "bun run generate:css && bun run scripts/build-history.ts && bun run scripts/build-agent.ts && bun run scripts/build-agents.ts && tsc",
- "dev": "bun run generate:css && bun run scripts/build-history.ts && bun run scripts/build-agent.ts && bun run scripts/build-agents.ts && DEPLOYMENT_MODE=docker bun --watch src/index.ts -- --env ../../.env",
+ "build": "tsc",
+ "dev": "DEPLOYMENT_MODE=docker bun --watch src/index.ts -- --env ../../.env",
"start": "cd ../core && bun run build && cd ../gateway && bun src/index.ts",
"test": "bun test",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@chat-adapter/discord": "^4",
+ "@chat-adapter/gchat": "^4",
"@chat-adapter/slack": "^4",
"@chat-adapter/state-ioredis": "^4",
"@chat-adapter/teams": "^4",
@@ -41,23 +41,13 @@
"dotenv": "^17.2.1",
"hono": "^4.11.7",
"ioredis": "^5.4.1",
- "jose": "^6.0.11",
- "jsonwebtoken": "^9.0.2",
- "marked": "^12.0.0",
- "pino": "^9.1.0",
+ "smol-toml": "^1.3.1",
"yaml": "^2.7.1",
"zod": "^4.1.12"
},
"devDependencies": {
- "@preact/signals": "^2.0.0",
- "@tailwindcss/typography": "^0.5.19",
- "@tanstack/react-virtual": "^3.11.2",
"@types/dockerode": "^3.3.29",
- "@types/express": "^4.17.21",
- "@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20.0.0",
- "esbuild": "^0.24.0",
- "preact": "^10.25.0",
"typescript": "^5.8.3"
}
}
diff --git a/packages/gateway/scripts/build-agent.ts b/packages/gateway/scripts/build-agent.ts
deleted file mode 100644
index 407f8fa9a..000000000
--- a/packages/gateway/scripts/build-agent.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { writeFileSync } from "node:fs";
-import { resolve } from "node:path";
-import { build } from "esbuild";
-
-const gatewayDir = resolve(import.meta.dir, "..");
-
-async function main() {
- const result = await build({
- entryPoints: [resolve(gatewayDir, "src/routes/public/agent-page/app.tsx")],
- bundle: true,
- minify: true,
- format: "esm",
- target: ["es2020"],
- write: false,
- jsx: "automatic",
- jsxImportSource: "preact",
- alias: {
- react: "preact/compat",
- "react-dom": "preact/compat",
- },
- });
-
- const js = result.outputFiles?.[0]?.text || "";
-
- // Escape for embedding in a JS template literal
- const escaped = js
- .replace(/\\/g, "\\\\")
- .replace(/`/g, "\\`")
- .replace(/\$\{/g, "\\${");
-
- const output = `/**
- * Auto-generated Preact bundle for the agent page.
- * DO NOT EDIT — regenerated on every build.
- */
-export const agentPageJS = \`${escaped}\`;
-`;
-
- writeFileSync(
- resolve(gatewayDir, "src/routes/public/agent-page-bundle.ts"),
- output
- );
- console.log("Agent page JS bundle generated");
-}
-
-main().catch((err) => {
- console.error("Failed to build agent page:", err);
- process.exit(1);
-});
diff --git a/packages/gateway/scripts/build-agents.ts b/packages/gateway/scripts/build-agents.ts
deleted file mode 100644
index 56e95460e..000000000
--- a/packages/gateway/scripts/build-agents.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { writeFileSync } from "node:fs";
-import { resolve } from "node:path";
-import { build } from "esbuild";
-
-const gatewayDir = resolve(import.meta.dir, "..");
-
-async function main() {
- const result = await build({
- entryPoints: [resolve(gatewayDir, "src/routes/public/agents-page/app.tsx")],
- bundle: true,
- minify: true,
- format: "esm",
- target: ["es2020"],
- write: false,
- jsx: "automatic",
- jsxImportSource: "preact",
- alias: {
- react: "preact/compat",
- "react-dom": "preact/compat",
- },
- });
-
- const js = result.outputFiles?.[0]?.text || "";
-
- // Also write raw JS for fs.readFileSync usage (avoids bun require cache)
- writeFileSync(
- resolve(gatewayDir, "src/routes/public/agents-page-bundle.raw.js"),
- js
- );
- console.log("Agents page JS bundle generated");
-}
-
-main().catch((err) => {
- console.error("Failed to build agents page:", err);
- process.exit(1);
-});
diff --git a/packages/gateway/scripts/build-history.ts b/packages/gateway/scripts/build-history.ts
deleted file mode 100644
index dbdc1ba62..000000000
--- a/packages/gateway/scripts/build-history.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { writeFileSync } from "node:fs";
-import { resolve } from "node:path";
-import { build } from "esbuild";
-
-const gatewayDir = resolve(import.meta.dir, "..");
-
-async function main() {
- const result = await build({
- entryPoints: [
- resolve(gatewayDir, "src/routes/public/history-page/app.tsx"),
- ],
- bundle: true,
- minify: true,
- format: "esm",
- target: ["es2020"],
- write: false,
- jsx: "automatic",
- jsxImportSource: "preact",
- alias: {
- react: "preact/compat",
- "react-dom": "preact/compat",
- },
- });
-
- const js = result.outputFiles?.[0]?.text || "";
-
- // Escape for embedding in a JS template literal
- const escaped = js
- .replace(/\\/g, "\\\\")
- .replace(/`/g, "\\`")
- .replace(/\$\{/g, "\\${");
-
- const output = `/**
- * Auto-generated Preact bundle for the history page.
- * DO NOT EDIT — regenerated on every build.
- */
-export const historyPageJS = \`${escaped}\`;
-`;
-
- writeFileSync(
- resolve(gatewayDir, "src/routes/public/history-page-bundle.ts"),
- output
- );
- console.log("History page JS bundle generated");
-}
-
-main().catch((err) => {
- console.error("Failed to build history page:", err);
- process.exit(1);
-});
diff --git a/packages/gateway/scripts/generate-css.ts b/packages/gateway/scripts/generate-css.ts
deleted file mode 100644
index 1636baa21..000000000
--- a/packages/gateway/scripts/generate-css.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { execSync } from "node:child_process";
-import { writeFileSync } from "node:fs";
-import { resolve } from "node:path";
-
-const gatewayDir = resolve(import.meta.dir, "..");
-const css = execSync(
- "bunx tailwindcss@3 -c tailwind.config.js -i src/routes/public/settings-input.css --minify",
- { cwd: gatewayDir, encoding: "utf-8" }
-).trim();
-
-// Escape for embedding in a JS template literal:
-// - backslashes (e.g. Tailwind's `.w-3\.5` selector) must be doubled
-// - backticks and ${ must be escaped
-const escaped = css
- .replace(/\\/g, "\\\\")
- .replace(/`/g, "\\`")
- .replace(/\$\{/g, "\\${");
-
-const output = `/**
- * Auto-generated Tailwind CSS for all pages.
- * DO NOT EDIT — regenerated on every build/dev start.
- */
-export const pageCSS = \`
-${escaped}
-[x-cloak] { display: none !important; }\`;
-`;
-
-writeFileSync(resolve(gatewayDir, "src/routes/public/page-styles.ts"), output);
-console.log("Page CSS generated");
diff --git a/packages/gateway/src/__tests__/agent-config-routes.test.ts b/packages/gateway/src/__tests__/agent-config-routes.test.ts
index f8c88d6e1..960836836 100644
--- a/packages/gateway/src/__tests__/agent-config-routes.test.ts
+++ b/packages/gateway/src/__tests__/agent-config-routes.test.ts
@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { OpenAPIHono } from "@hono/zod-openapi";
+import { encrypt } from "@lobu/core";
import { MockRedisClient } from "@lobu/core/testing";
import { AgentMetadataStore } from "../auth/agent-metadata-store";
import { AgentSettingsStore } from "../auth/settings/agent-settings-store";
@@ -8,12 +9,16 @@ import { createAgentConfigRoutes } from "../routes/public/agent-config";
import { setAuthProvider } from "../routes/public/settings-auth";
describe("agent config routes", () => {
+ let originalEncryptionKey: string | undefined;
let redis: MockRedisClient;
let agentSettingsStore: AgentSettingsStore;
let agentMetadataStore: AgentMetadataStore;
let grantStore: GrantStore;
beforeEach(async () => {
+ originalEncryptionKey = process.env.ENCRYPTION_KEY;
+ process.env.ENCRYPTION_KEY =
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
redis = new MockRedisClient();
agentSettingsStore = new AgentSettingsStore(redis as any);
agentMetadataStore = new AgentMetadataStore(redis as any);
@@ -51,6 +56,11 @@ describe("agent config routes", () => {
});
afterEach(() => {
+ if (originalEncryptionKey !== undefined) {
+ process.env.ENCRYPTION_KEY = originalEncryptionKey;
+ } else {
+ delete process.env.ENCRYPTION_KEY;
+ }
setAuthProvider(null);
});
@@ -80,7 +90,12 @@ describe("agent config routes", () => {
"/api/v1/agents/:agentId/config",
createAgentConfigRoutes({
agentSettingsStore,
- agentMetadataStore,
+ agentConfigStore: {
+ getSettings: (agentId: string) =>
+ agentSettingsStore.getSettings(agentId),
+ getMetadata: (agentId: string) =>
+ agentMetadataStore.getMetadata(agentId),
+ },
grantStore,
scheduledWakeupService: scheduledWakeupService as any,
})
@@ -125,33 +140,115 @@ describe("agent config routes", () => {
expect(data.tools.schedules[0]?.scheduleId).toBe("schedule-1");
});
- test("POST /reset-section clears sandbox overrides and restores inheritance", async () => {
+ test("GET /config accepts direct query token auth", async () => {
+ const app = buildApp();
+ const token = encrypt(
+ JSON.stringify({
+ agentId: "telegram-1",
+ userId: "u1",
+ platform: "telegram",
+ exp: Date.now() + 60_000,
+ settingsMode: "user",
+ allowedScopes: ["view-model", "system-prompt", "permissions"],
+ })
+ );
+
+ const response = await app.request(
+ `/api/v1/agents/telegram-1/config?token=${encodeURIComponent(token)}`
+ );
+
+ expect(response.status).toBe(200);
+ const data = (await response.json()) as any;
+ expect(data.agentId).toBe("telegram-1");
+ expect(data.scope).toBe("sandbox");
+ });
+
+ test("GET /config keeps exact agent tokens read-only when settingsMode is missing", async () => {
+ const app = buildApp();
+ const token = encrypt(
+ JSON.stringify({
+ agentId: "telegram-1",
+ userId: "u1",
+ platform: "telegram",
+ exp: Date.now() + 60_000,
+ })
+ );
+
+ const response = await app.request(
+ `/api/v1/agents/telegram-1/config?token=${encodeURIComponent(token)}`
+ );
+
+ expect(response.status).toBe(200);
+ const data = (await response.json()) as any;
+ expect(data.sections.model.editable).toBe(false);
+ expect(data.sections["system-prompt"].editable).toBe(false);
+ });
+
+ test("GET /config rejects direct query token for the wrong agent", async () => {
+ const app = buildApp();
+ const token = encrypt(
+ JSON.stringify({
+ agentId: "template-agent",
+ userId: "u1",
+ platform: "telegram",
+ exp: Date.now() + 60_000,
+ settingsMode: "user",
+ })
+ );
+
+ const response = await app.request(
+ `/api/v1/agents/telegram-1/config?token=${encodeURIComponent(token)}`
+ );
+
+ expect(response.status).toBe(401);
+ });
+
+ test("GET /config reads effective settings from the settings store", async () => {
setAuthProvider(() => ({
agentId: "telegram-1",
userId: "u1",
platform: "telegram",
exp: Date.now() + 60_000,
settingsMode: "user",
- allowedScopes: ["system-prompt"],
+ allowedScopes: ["view-model", "system-prompt"],
}));
- const app = buildApp();
- const response = await app.request(
- "/api/v1/agents/telegram-1/config/reset-section",
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ section: "system-prompt" }),
- }
+ const app = new OpenAPIHono();
+ app.route(
+ "/api/v1/agents/:agentId/config",
+ createAgentConfigRoutes({
+ agentSettingsStore,
+ agentConfigStore: {
+ getSettings: async () => null,
+ getMetadata: (agentId: string) =>
+ agentMetadataStore.getMetadata(agentId),
+ },
+ })
);
+
+ const response = await app.request("/api/v1/agents/telegram-1/config");
+
expect(response.status).toBe(200);
+ const data = (await response.json()) as any;
+ expect(data.instructions.identity).toBe("Local identity");
+ expect(data.instructions.soul).toBe("Template soul");
+ expect(data.providers.order).toEqual(["chatgpt"]);
+ expect(data.templateAgentId).toBe("template-agent");
+ });
- const localSettings = await agentSettingsStore.getSettings("telegram-1");
- const effectiveSettings =
- await agentSettingsStore.getEffectiveSettings("telegram-1");
+ test("GET /config grants owners full access even when browser session has no settingsMode", async () => {
+ setAuthProvider(() => ({
+ userId: "u1",
+ platform: "telegram",
+ exp: Date.now() + 60_000,
+ }));
- expect(localSettings?.identityMd).toBeUndefined();
- expect(effectiveSettings?.identityMd).toBe("Template identity");
- expect(effectiveSettings?.soulMd).toBe("Template soul");
+ const app = buildApp();
+ const response = await app.request("/api/v1/agents/telegram-1/config");
+
+ expect(response.status).toBe(200);
+ const data = (await response.json()) as any;
+ expect(data.sections.model.editable).toBe(true);
+ expect(data.sections["system-prompt"].editable).toBe(true);
});
});
diff --git a/packages/gateway/src/__tests__/agent-history-routes.test.ts b/packages/gateway/src/__tests__/agent-history-routes.test.ts
new file mode 100644
index 000000000..914e9cac4
--- /dev/null
+++ b/packages/gateway/src/__tests__/agent-history-routes.test.ts
@@ -0,0 +1,72 @@
+import { afterEach, beforeEach, describe, expect, test } from "bun:test";
+import { Hono } from "hono";
+import { MockRedisClient } from "@lobu/core/testing";
+import { AgentMetadataStore } from "../auth/agent-metadata-store";
+import { UserAgentsStore } from "../auth/user-agents-store";
+import { createAgentHistoryRoutes } from "../routes/public/agent-history";
+import { setAuthProvider } from "../routes/public/settings-auth";
+
+describe("agent history routes", () => {
+ let redis: MockRedisClient;
+ let agentMetadataStore: AgentMetadataStore;
+ let userAgentsStore: UserAgentsStore;
+
+ beforeEach(async () => {
+ redis = new MockRedisClient();
+ agentMetadataStore = new AgentMetadataStore(redis as any);
+ userAgentsStore = new UserAgentsStore(redis as any);
+
+ await agentMetadataStore.createAgent(
+ "agent-1",
+ "Agent 1",
+ "external",
+ "u1"
+ );
+ await userAgentsStore.addAgent("external", "u1", "agent-1");
+ });
+
+ afterEach(() => {
+ setAuthProvider(null);
+ });
+
+ test("rejects sessions that do not own the requested agent", async () => {
+ setAuthProvider(() => ({
+ userId: "u2",
+ platform: "external",
+ exp: Date.now() + 60_000,
+ }));
+
+ const app = new Hono();
+ app.route(
+ "/api/v1/agents/:agentId/history",
+ createAgentHistoryRoutes({
+ connectionManager: {
+ getDeploymentsForAgent() {
+ return [];
+ },
+ getHttpUrl() {
+ return null;
+ },
+ } as any,
+ agentConfigStore: {
+ getMetadata: (agentId: string) =>
+ agentMetadataStore.getMetadata(agentId),
+ listSandboxes: async () => [],
+ },
+ userAgentsStore,
+ })
+ );
+
+ const response = await app.request(
+ "/api/v1/agents/agent-1/history/status",
+ {
+ headers: {
+ host: "localhost",
+ },
+ method: "GET",
+ }
+ );
+
+ expect(response.status).toBe(401);
+ });
+});
diff --git a/packages/gateway/src/__tests__/agent-routes.test.ts b/packages/gateway/src/__tests__/agent-routes.test.ts
new file mode 100644
index 000000000..10b9e33bc
--- /dev/null
+++ b/packages/gateway/src/__tests__/agent-routes.test.ts
@@ -0,0 +1,68 @@
+import { afterEach, beforeEach, describe, expect, test } from "bun:test";
+import { MockRedisClient } from "@lobu/core/testing";
+import { AgentMetadataStore } from "../auth/agent-metadata-store";
+import { AgentSettingsStore } from "../auth/settings/agent-settings-store";
+import { UserAgentsStore } from "../auth/user-agents-store";
+import { createAgentRoutes } from "../routes/public/agents";
+import { setAuthProvider } from "../routes/public/settings-auth";
+
+describe("agent routes", () => {
+ let redis: MockRedisClient;
+ let agentMetadataStore: AgentMetadataStore;
+ let agentSettingsStore: AgentSettingsStore;
+ let userAgentsStore: UserAgentsStore;
+
+ beforeEach(async () => {
+ redis = new MockRedisClient();
+ agentMetadataStore = new AgentMetadataStore(redis as any);
+ agentSettingsStore = new AgentSettingsStore(redis as any);
+ userAgentsStore = new UserAgentsStore(redis as any);
+
+ await agentMetadataStore.createAgent(
+ "agent-1",
+ "Agent 1",
+ "telegram",
+ "u1"
+ );
+ await userAgentsStore.addAgent("telegram", "u1", "agent-1");
+ });
+
+ afterEach(() => {
+ setAuthProvider(null);
+ });
+
+ test("lists agents for external browser sessions by owner userId", async () => {
+ setAuthProvider(() => ({
+ userId: "u1",
+ oauthUserId: "u1",
+ platform: "external",
+ exp: Date.now() + 60_000,
+ }));
+
+ const app = createAgentRoutes({
+ userAgentsStore,
+ agentMetadataStore,
+ agentSettingsStore,
+ channelBindingService: {
+ async getBinding() {
+ return null;
+ },
+ async createBinding() {
+ return true;
+ },
+ async listBindings() {
+ return [];
+ },
+ async deleteAllBindings() {
+ return 0;
+ },
+ } as any,
+ });
+
+ const response = await app.request("/");
+ expect(response.status).toBe(200);
+ const data = (await response.json()) as any;
+ expect(data.agents).toHaveLength(1);
+ expect(data.agents[0]?.agentId).toBe("agent-1");
+ });
+});
diff --git a/packages/gateway/src/__tests__/agent-schedules-routes.test.ts b/packages/gateway/src/__tests__/agent-schedules-routes.test.ts
new file mode 100644
index 000000000..bfd3d2f5e
--- /dev/null
+++ b/packages/gateway/src/__tests__/agent-schedules-routes.test.ts
@@ -0,0 +1,59 @@
+import { afterEach, beforeEach, describe, expect, test } from "bun:test";
+import { OpenAPIHono } from "@hono/zod-openapi";
+import { MockRedisClient } from "@lobu/core/testing";
+import { AgentMetadataStore } from "../auth/agent-metadata-store";
+import { UserAgentsStore } from "../auth/user-agents-store";
+import { createAgentSchedulesRoutes } from "../routes/public/agent-schedules";
+import { setAuthProvider } from "../routes/public/settings-auth";
+
+describe("agent schedules routes", () => {
+ let redis: MockRedisClient;
+ let agentMetadataStore: AgentMetadataStore;
+ let userAgentsStore: UserAgentsStore;
+
+ beforeEach(async () => {
+ redis = new MockRedisClient();
+ agentMetadataStore = new AgentMetadataStore(redis as any);
+ userAgentsStore = new UserAgentsStore(redis as any);
+
+ await agentMetadataStore.createAgent(
+ "agent-1",
+ "Agent 1",
+ "external",
+ "u1"
+ );
+ await userAgentsStore.addAgent("external", "u1", "agent-1");
+ });
+
+ afterEach(() => {
+ setAuthProvider(null);
+ });
+
+ test("rejects neutral sessions that do not own the agent", async () => {
+ setAuthProvider(() => ({
+ userId: "u2",
+ platform: "external",
+ exp: Date.now() + 60_000,
+ }));
+
+ const app = new OpenAPIHono();
+ app.route(
+ "/api/v1/agents/:agentId/schedules",
+ createAgentSchedulesRoutes({
+ scheduledWakeupService: {
+ async listPendingForAgent() {
+ return [];
+ },
+ } as any,
+ userAgentsStore,
+ agentMetadataStore: {
+ getMetadata: (agentId: string) =>
+ agentMetadataStore.getMetadata(agentId),
+ },
+ })
+ );
+
+ const response = await app.request("/api/v1/agents/agent-1/schedules");
+ expect(response.status).toBe(401);
+ });
+});
diff --git a/packages/gateway/src/__tests__/chat-response-bridge.test.ts b/packages/gateway/src/__tests__/chat-response-bridge.test.ts
index 85501e09f..600d7ab3b 100644
--- a/packages/gateway/src/__tests__/chat-response-bridge.test.ts
+++ b/packages/gateway/src/__tests__/chat-response-bridge.test.ts
@@ -57,7 +57,7 @@ describe("ChatResponseBridge.handleEphemeral", () => {
chatId: "123",
},
content:
- "Setup required: add OpenAI in settings before this bot can respond.\n\n[Open Agent Settings](https://example.com/agent?claim=abc123)",
+ "Setup required: add OpenAI in settings before this bot can respond.\n\n[Open Agent Settings](https://example.com/connect/claim?claim=abc123)",
});
expect(posts).toHaveLength(1);
@@ -65,7 +65,7 @@ describe("ChatResponseBridge.handleEphemeral", () => {
expect(posts[0]).toHaveProperty("card");
expect(posts[0]).toHaveProperty("fallbackText");
expect((posts[0] as { fallbackText: string }).fallbackText).toContain(
- "Open Agent Settings: https://example.com/agent?claim=abc123"
+ "Open Agent Settings: https://example.com/connect/claim?claim=abc123"
);
});
diff --git a/packages/gateway/src/__tests__/config-memory-plugins.test.ts b/packages/gateway/src/__tests__/config-memory-plugins.test.ts
new file mode 100644
index 000000000..81c328b3a
--- /dev/null
+++ b/packages/gateway/src/__tests__/config-memory-plugins.test.ts
@@ -0,0 +1,92 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import { buildMemoryPlugins } from "../config";
+
+const originalMemoryUrl = process.env.MEMORY_URL;
+const originalDispatcherServiceName = process.env.DISPATCHER_SERVICE_NAME;
+const originalKubernetesNamespace = process.env.KUBERNETES_NAMESPACE;
+
+afterEach(() => {
+ if (originalMemoryUrl === undefined) {
+ delete process.env.MEMORY_URL;
+ } else {
+ process.env.MEMORY_URL = originalMemoryUrl;
+ }
+
+ if (originalDispatcherServiceName === undefined) {
+ delete process.env.DISPATCHER_SERVICE_NAME;
+ } else {
+ process.env.DISPATCHER_SERVICE_NAME = originalDispatcherServiceName;
+ }
+
+ if (originalKubernetesNamespace === undefined) {
+ delete process.env.KUBERNETES_NAMESPACE;
+ } else {
+ process.env.KUBERNETES_NAMESPACE = originalKubernetesNamespace;
+ }
+});
+
+describe("buildMemoryPlugins", () => {
+ test("returns native memory when MEMORY_URL is unset and plugin exists", () => {
+ delete process.env.MEMORY_URL;
+
+ expect(buildMemoryPlugins({ hasNativeMemoryPlugin: true })).toEqual([
+ {
+ source: "@openclaw/native-memory",
+ slot: "memory",
+ enabled: true,
+ },
+ ]);
+ });
+
+ test("returns no plugin when MEMORY_URL is unset and native memory is unavailable", () => {
+ delete process.env.MEMORY_URL;
+
+ expect(buildMemoryPlugins({ hasNativeMemoryPlugin: false })).toEqual([]);
+ });
+
+ test("falls back to native memory when Owletto plugin is unavailable", () => {
+ process.env.MEMORY_URL = "https://memory.example.com";
+
+ expect(
+ buildMemoryPlugins({
+ hasOwlettoPlugin: false,
+ hasNativeMemoryPlugin: true,
+ })
+ ).toEqual([
+ {
+ source: "@openclaw/native-memory",
+ slot: "memory",
+ enabled: true,
+ },
+ ]);
+ });
+
+ test("returns no plugin when neither Owletto nor native memory plugin exists", () => {
+ process.env.MEMORY_URL = "https://memory.example.com";
+
+ expect(
+ buildMemoryPlugins({
+ hasOwlettoPlugin: false,
+ hasNativeMemoryPlugin: false,
+ })
+ ).toEqual([]);
+ });
+
+ test("uses Owletto plugin when installed and MEMORY_URL is set", () => {
+ process.env.MEMORY_URL = "https://memory.example.com";
+ process.env.DISPATCHER_SERVICE_NAME = "lobu-gateway";
+ process.env.KUBERNETES_NAMESPACE = "lobu";
+
+ expect(buildMemoryPlugins({ hasOwlettoPlugin: true })).toEqual([
+ {
+ source: "@lobu/owletto-openclaw",
+ slot: "memory",
+ enabled: true,
+ config: {
+ mcpUrl: "http://lobu-gateway.lobu.svc.cluster.local:8080/mcp/owletto",
+ gatewayAuthUrl: "http://lobu-gateway.lobu.svc.cluster.local:8080",
+ },
+ },
+ ]);
+ });
+});
diff --git a/packages/gateway/src/__tests__/config-request-store.test.ts b/packages/gateway/src/__tests__/config-request-store.test.ts
new file mode 100644
index 000000000..bd330f128
--- /dev/null
+++ b/packages/gateway/src/__tests__/config-request-store.test.ts
@@ -0,0 +1,127 @@
+import { describe, expect, mock, test } from "bun:test";
+import {
+ applyPendingConfigRequest,
+ buildConfigRequestText,
+} from "../interactions/config-request-store";
+
+describe("config-request-store", () => {
+ test("applies skills, mcps, nix packages, and grants", async () => {
+ const updateSettings = mock(() => Promise.resolve());
+ const grant = mock(() => Promise.resolve());
+ const agentSettingsStore = {
+ getSettings: mock(() =>
+ Promise.resolve({
+ nixConfig: { packages: ["git"] },
+ skillsConfig: {
+ skills: [
+ {
+ repo: "existing-skill",
+ name: "Existing Skill",
+ description: "Existing",
+ enabled: false,
+ },
+ ],
+ },
+ mcpServers: {
+ existing: { enabled: true, url: "https://existing.example.com" },
+ },
+ })
+ ),
+ updateSettings,
+ };
+ const grantStore = { grant };
+
+ await applyPendingConfigRequest(
+ agentSettingsStore as any,
+ grantStore as any,
+ {
+ agentId: "agent-1",
+ reason: "Install requested skill",
+ skills: [
+ {
+ repo: "existing-skill",
+ name: "Existing Skill",
+ description: "Updated",
+ },
+ {
+ repo: "new-skill",
+ name: "New Skill",
+ description: "New",
+ },
+ ],
+ mcpServers: [
+ {
+ id: "new-mcp",
+ name: "New MCP",
+ url: "https://mcp.example.com",
+ type: "sse",
+ },
+ ],
+ nixPackages: ["git", "ffmpeg"],
+ grants: ["api.example.com"],
+ }
+ );
+
+ expect(updateSettings).toHaveBeenCalledTimes(1);
+ const [, updates] = updateSettings.mock.calls[0]!;
+ expect(updates.skillsConfig.skills).toHaveLength(2);
+ expect(
+ updates.skillsConfig.skills.find(
+ (skill: any) => skill.repo === "existing-skill"
+ ).enabled
+ ).toBe(true);
+ expect(
+ updates.skillsConfig.skills.find(
+ (skill: any) => skill.repo === "new-skill"
+ ).name
+ ).toBe("New Skill");
+ expect(updates.mcpServers["new-mcp"]).toMatchObject({
+ enabled: true,
+ url: "https://mcp.example.com",
+ type: "sse",
+ });
+ expect(updates.nixConfig.packages).toEqual(["git", "ffmpeg"]);
+ expect(grant).toHaveBeenCalledWith("agent-1", "api.example.com", null);
+ });
+
+ test("skips settings writes when a request only grants permissions", async () => {
+ const updateSettings = mock(() => Promise.resolve());
+ const grant = mock(() => Promise.resolve());
+ const agentSettingsStore = {
+ getSettings: mock(() => Promise.resolve({})),
+ updateSettings,
+ };
+ const grantStore = { grant };
+
+ await applyPendingConfigRequest(
+ agentSettingsStore as any,
+ grantStore as any,
+ {
+ agentId: "agent-1",
+ reason: "Allow API access",
+ grants: ["api.example.com"],
+ }
+ );
+
+ expect(updateSettings).not.toHaveBeenCalled();
+ expect(grant).toHaveBeenCalledWith("agent-1", "api.example.com", null);
+ });
+
+ test("builds readable config request text", () => {
+ const text = buildConfigRequestText({
+ agentId: "agent-1",
+ reason: "Install skill",
+ message: "Needed for triage",
+ skills: [{ repo: "ops-triage", name: "Ops Triage" }],
+ nixPackages: ["ffmpeg"],
+ grants: ["api.example.com"],
+ providers: ["openai"],
+ });
+
+ expect(text).toContain("Configuration Change Request");
+ expect(text).toContain("Ops Triage");
+ expect(text).toContain("ffmpeg");
+ expect(text).toContain("api.example.com");
+ expect(text).toContain("openai");
+ });
+});
diff --git a/packages/gateway/src/__tests__/connection-routes.test.ts b/packages/gateway/src/__tests__/connection-routes.test.ts
new file mode 100644
index 000000000..c29582d9f
--- /dev/null
+++ b/packages/gateway/src/__tests__/connection-routes.test.ts
@@ -0,0 +1,144 @@
+import { afterEach, beforeEach, describe, expect, test } from "bun:test";
+import { MockRedisClient } from "@lobu/core/testing";
+import { AgentMetadataStore } from "../auth/agent-metadata-store";
+import { UserAgentsStore } from "../auth/user-agents-store";
+import { createConnectionCrudRoutes } from "../routes/public/connections";
+import { setAuthProvider } from "../routes/public/settings-auth";
+
+describe("connection routes", () => {
+ let redis: MockRedisClient;
+ let agentMetadataStore: AgentMetadataStore;
+ let userAgentsStore: UserAgentsStore;
+
+ beforeEach(async () => {
+ redis = new MockRedisClient();
+ agentMetadataStore = new AgentMetadataStore(redis as any);
+ userAgentsStore = new UserAgentsStore(redis as any);
+
+ await agentMetadataStore.createAgent(
+ "agent-1",
+ "Agent 1",
+ "telegram",
+ "u1"
+ );
+ await agentMetadataStore.createAgent(
+ "sandbox-1",
+ "Sandbox 1",
+ "telegram",
+ "u1",
+ {
+ parentConnectionId: "conn-1",
+ }
+ );
+ await userAgentsStore.addAgent("telegram", "u1", "agent-1");
+ });
+
+ afterEach(() => {
+ setAuthProvider(null);
+ });
+
+ function buildApp() {
+ return createConnectionCrudRoutes(
+ {
+ async listConnections(filters?: any) {
+ const connection = {
+ id: "conn-1",
+ platform: "telegram",
+ templateAgentId: "agent-1",
+ config: { platform: "telegram" },
+ settings: {},
+ metadata: {},
+ status: "active",
+ createdAt: 1,
+ updatedAt: 1,
+ };
+ if (
+ filters?.templateAgentId &&
+ filters.templateAgentId !== "agent-1"
+ ) {
+ return [];
+ }
+ return [connection];
+ },
+ async getConnection(id: string) {
+ if (id !== "conn-1") return null;
+ return {
+ id: "conn-1",
+ platform: "telegram",
+ templateAgentId: "agent-1",
+ config: { platform: "telegram" },
+ settings: {},
+ metadata: {},
+ status: "active",
+ createdAt: 1,
+ updatedAt: 1,
+ };
+ },
+ has() {
+ return true;
+ },
+ getServices() {
+ return {
+ getQueue() {
+ return {
+ getRedisClient() {
+ return redis;
+ },
+ };
+ },
+ };
+ },
+ } as any,
+ {
+ userAgentsStore,
+ agentMetadataStore: {
+ getMetadata: (agentId: string) =>
+ agentMetadataStore.getMetadata(agentId),
+ listSandboxes: (connectionId: string) =>
+ agentMetadataStore.listSandboxes(connectionId),
+ },
+ }
+ );
+ }
+
+ test("forbids non-admin sessions from listing all connections", async () => {
+ setAuthProvider(() => ({
+ userId: "u1",
+ platform: "telegram",
+ exp: Date.now() + 60_000,
+ }));
+
+ const response = await buildApp().request("/api/v1/connections");
+ expect(response.status).toBe(403);
+ });
+
+ test("allows external owner sessions to list connections for their agent", async () => {
+ setAuthProvider(() => ({
+ userId: "u1",
+ oauthUserId: "u1",
+ platform: "external",
+ exp: Date.now() + 60_000,
+ }));
+
+ const response = await buildApp().request(
+ "/api/v1/connections?templateAgentId=agent-1"
+ );
+ expect(response.status).toBe(200);
+ const data = (await response.json()) as any;
+ expect(data.connections).toHaveLength(1);
+ expect(data.connections[0]?.id).toBe("conn-1");
+ });
+
+ test("forbids sandbox listing when session cannot access the connection template agent", async () => {
+ setAuthProvider(() => ({
+ userId: "u2",
+ platform: "telegram",
+ exp: Date.now() + 60_000,
+ }));
+
+ const response = await buildApp().request(
+ "/api/v1/connections/conn-1/sandboxes"
+ );
+ expect(response.status).toBe(403);
+ });
+});
diff --git a/packages/gateway/src/__tests__/core-services-store-selection.test.ts b/packages/gateway/src/__tests__/core-services-store-selection.test.ts
new file mode 100644
index 000000000..59758bc67
--- /dev/null
+++ b/packages/gateway/src/__tests__/core-services-store-selection.test.ts
@@ -0,0 +1,92 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import type { GatewayConfig } from "../config";
+import { CoreServices } from "../services/core-services";
+import { MockMessageQueue } from "./setup";
+import {
+ RedisAgentAccessStore,
+ RedisAgentConfigStore,
+ RedisAgentConnectionStore,
+} from "../stores/redis-agent-store";
+
+function createGatewayConfig(
+ overrides?: Partial
+): GatewayConfig {
+ return {
+ agentDefaults: {},
+ sessionTimeoutMinutes: 5,
+ logLevel: "INFO",
+ queues: {
+ connectionString: "redis://test",
+ directMessage: "direct_message",
+ messageQueue: "message_queue",
+ retryLimit: 3,
+ retryDelay: 1,
+ expireInHours: 24,
+ },
+ anthropicProxy: {
+ enabled: true,
+ },
+ orchestration: {
+ deploymentMode: "docker",
+ queues: {
+ connectionString: "redis://test",
+ retryLimit: 3,
+ retryDelay: 1,
+ expireInSeconds: 3600,
+ },
+ worker: {
+ image: {
+ repository: "lobu-worker",
+ tag: "latest",
+ digest: "",
+ pullPolicy: "Always",
+ },
+ imagePullSecrets: [],
+ serviceAccountName: "lobu-worker",
+ runtimeClassName: "",
+ startupTimeoutSeconds: 90,
+ resources: {
+ requests: { cpu: "100m", memory: "256Mi" },
+ limits: { cpu: "1000m", memory: "2Gi" },
+ },
+ idleCleanupMinutes: 60,
+ maxDeployments: 100,
+ },
+ kubernetes: { namespace: "lobu" },
+ cleanup: {
+ initialDelayMs: 1000,
+ intervalMs: 60000,
+ veryOldDays: 7,
+ },
+ },
+ mcp: {
+ publicGatewayUrl: "http://localhost:8080",
+ internalGatewayUrl: "http://gateway:8080",
+ },
+ health: {
+ checkIntervalMs: 1000,
+ staleThresholdMs: 2000,
+ protectActiveWorkers: true,
+ },
+ ...overrides,
+ };
+}
+
+afterEach(() => {
+ delete process.env.LOBU_WORKSPACE_ROOT;
+});
+
+describe("CoreServices store selection", () => {
+ test("uses Redis-backed stores by default when no file-first config is present", async () => {
+ const coreServices = new CoreServices(createGatewayConfig());
+ (coreServices as any).queue = new MockMessageQueue();
+
+ await (coreServices as any).initializeSessionServices();
+
+ expect(coreServices.getConfigStore()).toBeInstanceOf(RedisAgentConfigStore);
+ expect(coreServices.getConnectionStore()).toBeInstanceOf(
+ RedisAgentConnectionStore
+ );
+ expect(coreServices.getAccessStore()).toBeInstanceOf(RedisAgentAccessStore);
+ });
+});
diff --git a/packages/gateway/src/__tests__/embedded-deployment.test.ts b/packages/gateway/src/__tests__/embedded-deployment.test.ts
index bb22c71cc..cbf704ec6 100644
--- a/packages/gateway/src/__tests__/embedded-deployment.test.ts
+++ b/packages/gateway/src/__tests__/embedded-deployment.test.ts
@@ -9,6 +9,7 @@ import {
} from "bun:test";
import { EventEmitter } from "node:events";
import fs from "node:fs";
+import path from "node:path";
import { ErrorCode, OrchestratorError } from "@lobu/core";
import type {
MessagePayload,
@@ -19,6 +20,7 @@ import type {
// Mock child_process.spawn to return a fake ChildProcess
// ---------------------------------------------------------------------------
const mockChildProcesses: EventEmitter[] = [];
+const mockSpawn = mock(() => createMockChildProcess());
function createMockChildProcess() {
const cp = new EventEmitter() as EventEmitter & {
@@ -45,7 +47,7 @@ function createMockChildProcess() {
}
mock.module("node:child_process", () => ({
- spawn: mock(() => createMockChildProcess()),
+ spawn: mockSpawn,
}));
// ---------------------------------------------------------------------------
@@ -117,6 +119,7 @@ describe("EmbeddedDeploymentManager", () => {
process.env.ENCRYPTION_KEY = TEST_ENCRYPTION_KEY;
manager = new EmbeddedDeploymentManager(TEST_CONFIG);
mockChildProcesses.length = 0;
+ mockSpawn.mockClear();
mkdirSyncSpy = spyOn(fs, "mkdirSync").mockReturnValue(undefined);
});
@@ -170,6 +173,7 @@ describe("EmbeddedDeploymentManager", () => {
await manager.createDeployment("worker-1", "user-1", "user-1", msg);
expect(mockChildProcesses).toHaveLength(1);
expect(mockChildProcesses[0]).toBeDefined();
+ expect(mockSpawn.mock.calls.at(-1)?.[0]).toBe(process.execPath);
});
test("createDeployment with different names returns multiple entries", async () => {
@@ -279,6 +283,20 @@ describe("EmbeddedDeploymentManager", () => {
expect((globalThis as any).__lobuEmbeddedBashOps).toBeUndefined();
});
+ test("prepends the worker bin directory to subprocess PATH", async () => {
+ const msg = createTestMessagePayload();
+ await manager.createDeployment("worker-1", "user-1", "user-1", msg);
+
+ const spawnCall = mockSpawn.mock.calls.at(-1);
+ expect(spawnCall).toBeDefined();
+
+ const spawnOptions = spawnCall?.[2] as
+ | { env?: Record }
+ | undefined;
+ const pathEntries = (spawnOptions?.env?.PATH || "").split(":");
+ expect(pathEntries).toContain(path.resolve("node_modules/.bin"));
+ });
+
test("child process exit removes worker from map", async () => {
const msg = createTestMessagePayload();
await manager.createDeployment("worker-1", "user-1", "user-1", msg);
diff --git a/packages/gateway/src/__tests__/instruction-service.test.ts b/packages/gateway/src/__tests__/instruction-service.test.ts
index b66487d23..551a2987a 100644
--- a/packages/gateway/src/__tests__/instruction-service.test.ts
+++ b/packages/gateway/src/__tests__/instruction-service.test.ts
@@ -20,7 +20,7 @@ describe("InstructionService", () => {
userId: "user-1",
workingDirectory: "/workspace/thread-1",
} as any,
- { settingsUrl: "http://localhost:8080/agents/agent-1" }
+ { settingsUrl: "http://localhost:8080/api/v1/agents/agent-1/config" }
);
expect(sessionContext.agentInstructions).toContain(
diff --git a/packages/gateway/src/__tests__/link-buttons.test.ts b/packages/gateway/src/__tests__/link-buttons.test.ts
index 7a5f692c0..6fafacae6 100644
--- a/packages/gateway/src/__tests__/link-buttons.test.ts
+++ b/packages/gateway/src/__tests__/link-buttons.test.ts
@@ -4,31 +4,33 @@ import { extractSettingsLinkButtons } from "../platform/link-buttons";
describe("extractSettingsLinkButtons", () => {
test("extracts settings link and replaces with label", () => {
const content =
- "Click [Open Settings](https://example.com/agent?claim=abc123) to continue";
+ "Click [Open Settings](https://example.com/connect/claim?claim=abc123) to continue";
const { processedContent, linkButtons } =
extractSettingsLinkButtons(content);
expect(linkButtons).toHaveLength(1);
expect(linkButtons[0]!.text).toBe("Open Settings");
- expect(linkButtons[0]!.url).toBe("https://example.com/agent?claim=abc123");
+ expect(linkButtons[0]!.url).toBe(
+ "https://example.com/connect/claim?claim=abc123"
+ );
expect(processedContent).toBe("Click Open Settings to continue");
expect(processedContent).not.toContain("https://");
});
test("extracts settings link with agent param", () => {
const content =
- "[Settings](https://example.com/agent?claim=abc&agent=agent-1)";
+ "[Settings](https://example.com/connect/claim?claim=abc&agent=agent-1)";
const { linkButtons } = extractSettingsLinkButtons(content);
expect(linkButtons).toHaveLength(1);
expect(linkButtons[0]!.url).toBe(
- "https://example.com/agent?claim=abc&agent=agent-1"
+ "https://example.com/connect/claim?claim=abc&agent=agent-1"
);
});
test("extracts multiple settings links", () => {
const content =
- "[First](https://a.com/agent?claim=1) and [Second](https://b.com/agent?claim=2)";
+ "[First](https://a.com/connect/claim?claim=1) and [Second](https://b.com/connect/claim?claim=2)";
const { processedContent, linkButtons } =
extractSettingsLinkButtons(content);
@@ -37,7 +39,8 @@ describe("extractSettingsLinkButtons", () => {
});
test("filters out localhost URLs", () => {
- const content = "[Settings](http://localhost:3000/agent?claim=token)";
+ const content =
+ "[Settings](http://localhost:3000/connect/claim?claim=token)";
const { processedContent, linkButtons } =
extractSettingsLinkButtons(content);
@@ -47,7 +50,7 @@ describe("extractSettingsLinkButtons", () => {
});
test("filters out 127.0.0.1 URLs", () => {
- const content = "[Settings](http://127.0.0.1/agent?claim=token)";
+ const content = "[Settings](http://127.0.0.1/connect/claim?claim=token)";
const { linkButtons } = extractSettingsLinkButtons(content);
expect(linkButtons).toHaveLength(0);
});
@@ -80,8 +83,8 @@ describe("extractSettingsLinkButtons", () => {
});
test("handles HTTP and HTTPS", () => {
- const httpContent = "[A](http://example.com/agent?claim=x)";
- const httpsContent = "[B](https://example.com/agent?claim=y)";
+ const httpContent = "[A](http://example.com/connect/claim?claim=x)";
+ const httpsContent = "[B](https://example.com/connect/claim?claim=y)";
const httpResult = extractSettingsLinkButtons(httpContent);
const httpsResult = extractSettingsLinkButtons(httpsContent);
@@ -92,10 +95,18 @@ describe("extractSettingsLinkButtons", () => {
test("mixed localhost and remote links only keeps remote", () => {
const content =
- "[Local](http://localhost/agent?claim=a) and [Remote](https://app.com/agent?claim=b)";
+ "[Local](http://localhost/connect/claim?claim=a) and [Remote](https://app.com/connect/claim?claim=b)";
const { linkButtons } = extractSettingsLinkButtons(content);
expect(linkButtons).toHaveLength(1);
expect(linkButtons[0]!.url).toContain("app.com");
});
+
+ test("keeps backward compatibility with legacy /agent claim links", () => {
+ const content = "[Legacy](https://example.com/agent?claim=legacy)";
+ const { linkButtons } = extractSettingsLinkButtons(content);
+
+ expect(linkButtons).toHaveLength(1);
+ expect(linkButtons[0]!.url).toBe("https://example.com/agent?claim=legacy");
+ });
});
diff --git a/packages/gateway/src/__tests__/lobu.test.ts b/packages/gateway/src/__tests__/lobu.test.ts
new file mode 100644
index 000000000..a400f3600
--- /dev/null
+++ b/packages/gateway/src/__tests__/lobu.test.ts
@@ -0,0 +1,32 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import { Lobu } from "../lobu";
+
+const originalMemoryUrl = process.env.MEMORY_URL;
+const originalAdminPassword = process.env.ADMIN_PASSWORD;
+
+afterEach(() => {
+ if (originalMemoryUrl === undefined) {
+ delete process.env.MEMORY_URL;
+ } else {
+ process.env.MEMORY_URL = originalMemoryUrl;
+ }
+
+ if (originalAdminPassword === undefined) {
+ delete process.env.ADMIN_PASSWORD;
+ } else {
+ process.env.ADMIN_PASSWORD = originalAdminPassword;
+ }
+});
+
+describe("Lobu", () => {
+ test("applies config.memory to the gateway environment", () => {
+ delete process.env.MEMORY_URL;
+
+ new Lobu({
+ redis: "redis://localhost:6379",
+ memory: "https://memory.example.com",
+ });
+
+ expect(process.env.MEMORY_URL).toBe("https://memory.example.com");
+ });
+});
diff --git a/packages/gateway/src/__tests__/message-handler-bridge.test.ts b/packages/gateway/src/__tests__/message-handler-bridge.test.ts
new file mode 100644
index 000000000..f0566209b
--- /dev/null
+++ b/packages/gateway/src/__tests__/message-handler-bridge.test.ts
@@ -0,0 +1,17 @@
+import { describe, expect, test } from "bun:test";
+import { isSenderAllowed } from "../connections/message-handler-bridge";
+
+describe("isSenderAllowed", () => {
+ test("allows everyone when allowFrom is not configured", () => {
+ expect(isSenderAllowed(undefined, "user-1")).toBe(true);
+ });
+
+ test("blocks everyone when allowFrom is an empty array", () => {
+ expect(isSenderAllowed([], "user-1")).toBe(false);
+ });
+
+ test("only allows listed users when allowFrom is configured", () => {
+ expect(isSenderAllowed(["user-1"], "user-1")).toBe(true);
+ expect(isSenderAllowed(["user-1"], "user-2")).toBe(false);
+ });
+});
diff --git a/packages/gateway/src/__tests__/oauth-templates.test.ts b/packages/gateway/src/__tests__/oauth-templates.test.ts
index 4b3c3238f..8885fe282 100644
--- a/packages/gateway/src/__tests__/oauth-templates.test.ts
+++ b/packages/gateway/src/__tests__/oauth-templates.test.ts
@@ -33,6 +33,7 @@ describe("OAuth template escaping", () => {
);
expect(html).not.toContain('">');
- expect(html).toContain("Open Settings");
+ expect(html).toContain(""><script>alert(1)</script>");
+ expect(html).toContain("Open Configuration");
});
});
diff --git a/packages/gateway/src/__tests__/routes/cli-auth.test.ts b/packages/gateway/src/__tests__/routes/cli-auth.test.ts
index 4273a6554..4b6720b76 100644
--- a/packages/gateway/src/__tests__/routes/cli-auth.test.ts
+++ b/packages/gateway/src/__tests__/routes/cli-auth.test.ts
@@ -1,6 +1,10 @@
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
+import { decrypt } from "@lobu/core";
import { MockRedisClient } from "../../../../core/src/__tests__/fixtures/mock-redis";
-import { createCliAuthRoutes } from "../../routes/public/cli-auth";
+import {
+ createCliAuthRoutes,
+ createConnectAuthRoutes,
+} from "../../routes/public/cli-auth";
describe("cli auth routes", () => {
let originalKey: string | undefined;
@@ -75,6 +79,79 @@ describe("cli auth routes", () => {
expect(body.loginUrl).toContain("/api/v1/auth/cli/session/login?request=");
});
+ test("GET /connect/oauth/login redirects into external browser auth", async () => {
+ const router = createConnectAuthRoutes({
+ queue: queue as any,
+ externalAuthClient: {
+ generateCodeVerifier: () => "code-verifier",
+ buildAuthUrl: mock(async (state: string, codeVerifier: string) => {
+ expect(state).toBeTruthy();
+ expect(codeVerifier).toBe("code-verifier");
+ return "https://issuer.example.com/oauth/authorize";
+ }),
+ } as any,
+ });
+
+ const res = await router.request(
+ "https://gateway.example.com/connect/oauth/login?returnUrl=%2Fdone"
+ );
+
+ expect(res.status).toBe(302);
+ expect(res.headers.get("location")).toBe(
+ "https://issuer.example.com/oauth/authorize"
+ );
+ });
+
+ test("GET /connect/oauth/callback sets a settings session and redirects back", async () => {
+ await redis.setex(
+ "cli:auth:connect:state-123",
+ 600,
+ JSON.stringify({
+ returnUrl: "/done",
+ codeVerifier: "code-verifier",
+ })
+ );
+
+ const router = createConnectAuthRoutes({
+ queue: queue as any,
+ externalAuthClient: {
+ exchangeCodeForToken: mock(async () => ({
+ accessToken: "provider-access-token",
+ refreshToken: "provider-refresh-token",
+ tokenType: "Bearer",
+ expiresAt: Date.now() + 3600_000,
+ scopes: ["profile:read"],
+ })),
+ fetchUserInfo: mock(async () => ({
+ sub: "user-123",
+ email: "user@example.com",
+ name: "Example User",
+ })),
+ } as any,
+ });
+
+ const res = await router.request(
+ "https://gateway.example.com/connect/oauth/callback?code=auth-code&state=state-123"
+ );
+
+ expect(res.status).toBe(302);
+ expect(res.headers.get("location")).toBe("/done");
+ expect(res.headers.get("set-cookie")).toContain("lobu_settings_session=");
+
+ const setCookie = res.headers.get("set-cookie");
+ const token = setCookie?.match(/lobu_settings_session=([^;]+)/)?.[1];
+ expect(token).toBeTruthy();
+
+ const payload = JSON.parse(decrypt(decodeURIComponent(token!))) as Record<
+ string,
+ unknown
+ >;
+ expect(payload.userId).toBe("user-123");
+ expect(payload.platform).toBe("external");
+ expect(payload.isAdmin).toBeUndefined();
+ expect(payload.settingsMode).toBeUndefined();
+ });
+
test("POST /cli/poll mints Lobu tokens after device auth completes", async () => {
await redis.setex(
"cli:auth:device:device-123",
diff --git a/packages/gateway/src/__tests__/settings-oauth-client.test.ts b/packages/gateway/src/__tests__/settings-oauth-client.test.ts
deleted file mode 100644
index 04ab66741..000000000
--- a/packages/gateway/src/__tests__/settings-oauth-client.test.ts
+++ /dev/null
@@ -1,358 +0,0 @@
-import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
-import { SettingsOAuthClient } from "../auth/settings/oauth-client";
-
-describe("SettingsOAuthClient", () => {
- const originalFetch = globalThis.fetch;
-
- beforeEach(() => {
- mock.restore();
- });
-
- afterEach(() => {
- globalThis.fetch = originalFetch;
- });
-
- test("dynamically registers a client and requests device grant support when available", async () => {
- const fetchMock = mock(
- async (input: string | URL | Request, init?: RequestInit) => {
- const url = String(input);
-
- if (url.endsWith("/.well-known/openid-configuration")) {
- return new Response(
- JSON.stringify({
- issuer: "https://issuer.example.com",
- authorization_endpoint:
- "https://issuer.example.com/oauth/authorize",
- token_endpoint: "https://issuer.example.com/oauth/token",
- registration_endpoint:
- "https://issuer.example.com/oauth/register",
- userinfo_endpoint: "https://issuer.example.com/oauth/userinfo",
- device_authorization_endpoint:
- "https://issuer.example.com/oauth/device_authorization",
- grant_types_supported: [
- "authorization_code",
- "refresh_token",
- "urn:ietf:params:oauth:grant-type:device_code",
- ],
- token_endpoint_auth_methods_supported: ["none"],
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- if (url.endsWith("/oauth/register")) {
- expect(init?.method).toBe("POST");
- const body = JSON.parse(String(init?.body)) as {
- grant_types?: string[];
- token_endpoint_auth_method?: string;
- };
- expect(body.grant_types).toContain(
- "urn:ietf:params:oauth:grant-type:device_code"
- );
- expect(body.token_endpoint_auth_method).toBe("none");
-
- return new Response(
- JSON.stringify({
- client_id: "dynamic-client-id",
- token_endpoint_auth_method: "none",
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- throw new Error(`Unexpected fetch: ${url}`);
- }
- );
-
- globalThis.fetch = fetchMock as typeof fetch;
-
- const cache = new Map();
- const client = new SettingsOAuthClient({
- issuerUrl: "https://issuer.example.com",
- redirectUri: "https://gateway.example.com/agent/oauth/callback",
- cacheStore: {
- get: async (key) => cache.get(key) ?? null,
- set: async (key, value) => {
- cache.set(key, value);
- },
- },
- });
-
- const capabilities = await client.getCapabilities();
- expect(capabilities).toEqual({ browser: true, device: true });
-
- const authUrl = await client.buildAuthUrl("state-123", "verifier-123");
- const parsed = new URL(authUrl);
-
- expect(parsed.origin + parsed.pathname).toBe(
- "https://issuer.example.com/oauth/authorize"
- );
- expect(parsed.searchParams.get("client_id")).toBe("dynamic-client-id");
- expect(parsed.searchParams.get("redirect_uri")).toBe(
- "https://gateway.example.com/agent/oauth/callback"
- );
- expect(cache.size).toBe(1);
- // Discovery is cached in-memory after first call, so only 2 fetches: discovery + registration
- expect(fetchMock).toHaveBeenCalledTimes(2);
- });
-
- test("reuses cached client credentials and falls back to browser-only when device flow is unavailable", async () => {
- const fetchMock = mock(async (input: string | URL | Request) => {
- const url = String(input);
- if (url.endsWith("/.well-known/openid-configuration")) {
- return new Response(
- JSON.stringify({
- issuer: "https://issuer.example.com",
- authorization_endpoint:
- "https://issuer.example.com/oauth/authorize",
- token_endpoint: "https://issuer.example.com/oauth/token",
- registration_endpoint: "https://issuer.example.com/oauth/register",
- userinfo_endpoint: "https://issuer.example.com/oauth/userinfo",
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- throw new Error(`Unexpected fetch: ${url}`);
- });
-
- globalThis.fetch = fetchMock as typeof fetch;
-
- const cache = new Map([
- [
- "external:auth:client:v3",
- JSON.stringify({
- client_id: "cached-client-id",
- token_endpoint_auth_method: "none",
- }),
- ],
- ]);
-
- const client = new SettingsOAuthClient({
- issuerUrl: "https://issuer.example.com",
- redirectUri: "https://gateway.example.com/agent/oauth/callback",
- cacheStore: {
- get: async (key) => cache.get(key) ?? null,
- set: async () => {
- throw new Error("should not write cache when already populated");
- },
- },
- });
-
- const capabilities = await client.getCapabilities();
- expect(capabilities).toEqual({ browser: true, device: false });
-
- const authUrl = await client.buildAuthUrl("state-123", "verifier-123");
- expect(new URL(authUrl).searchParams.get("client_id")).toBe(
- "cached-client-id"
- );
- // Discovery is cached in-memory after getCapabilities(), so buildAuthUrl() reuses it
- expect(fetchMock).toHaveBeenCalledTimes(1);
- });
-
- test("prefers path-relative discovery for AUTH_MCP_URL-style issuers", async () => {
- const fetchMock = mock(async (input: string | URL | Request) => {
- const url = String(input);
- expect(url).toBe(
- "https://issuer.example.com/mcp/.well-known/openid-configuration"
- );
-
- return new Response(
- JSON.stringify({
- authorization_endpoint: "https://issuer.example.com/oauth/authorize",
- token_endpoint: "https://issuer.example.com/oauth/token",
- userinfo_endpoint: "https://issuer.example.com/oauth/userinfo",
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- });
-
- globalThis.fetch = fetchMock as typeof fetch;
-
- const client = new SettingsOAuthClient({
- issuerUrl: "https://issuer.example.com/mcp",
- clientId: "static-client-id",
- redirectUri: "https://gateway.example.com/agent/oauth/callback",
- });
-
- const capabilities = await client.getCapabilities();
- expect(capabilities).toEqual({ browser: true, device: false });
- expect(fetchMock).toHaveBeenCalledTimes(1);
- });
-
- test("falls back to root discovery when path-relative discovery fails", async () => {
- const fetchMock = mock(async (input: string | URL | Request) => {
- const url = String(input);
-
- if (
- url ===
- "https://issuer.example.com/mcp/.well-known/openid-configuration"
- ) {
- return new Response("not json", {
- status: 200,
- headers: { "content-type": "text/html" },
- });
- }
-
- if (
- url === "https://issuer.example.com/.well-known/openid-configuration"
- ) {
- return new Response(
- JSON.stringify({
- authorization_endpoint:
- "https://issuer.example.com/oauth/authorize",
- token_endpoint: "https://issuer.example.com/oauth/token",
- userinfo_endpoint: "https://issuer.example.com/oauth/userinfo",
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- throw new Error(`Unexpected fetch: ${url}`);
- });
-
- globalThis.fetch = fetchMock as typeof fetch;
-
- const client = new SettingsOAuthClient({
- issuerUrl: "https://issuer.example.com/mcp",
- clientId: "static-client-id",
- redirectUri: "https://gateway.example.com/agent/oauth/callback",
- });
-
- const capabilities = await client.getCapabilities();
- expect(capabilities).toEqual({ browser: true, device: false });
- expect(fetchMock).toHaveBeenCalledTimes(2);
- });
-
- test("treats authorization_pending and slow_down as pending during device polling", async () => {
- let tokenPolls = 0;
- const fetchMock = mock(async (input: string | URL | Request) => {
- const url = String(input);
-
- if (url.endsWith("/.well-known/openid-configuration")) {
- return new Response(
- JSON.stringify({
- authorization_endpoint:
- "https://issuer.example.com/oauth/authorize",
- token_endpoint: "https://issuer.example.com/oauth/token",
- device_authorization_endpoint:
- "https://issuer.example.com/oauth/device_authorization",
- userinfo_endpoint: "https://issuer.example.com/oauth/userinfo",
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- if (url.endsWith("/oauth/token")) {
- tokenPolls += 1;
- return new Response(
- JSON.stringify({
- error: tokenPolls === 1 ? "authorization_pending" : "slow_down",
- }),
- { status: 400, headers: { "content-type": "application/json" } }
- );
- }
-
- throw new Error(`Unexpected fetch: ${url}`);
- });
-
- globalThis.fetch = fetchMock as typeof fetch;
-
- const client = new SettingsOAuthClient({
- issuerUrl: "https://issuer.example.com",
- clientId: "static-client-id",
- redirectUri: "https://gateway.example.com/agent/oauth/callback",
- });
-
- const pending = await client.pollDeviceAuthorization("device-1", 5);
- expect(pending).toEqual({ status: "pending", interval: 5 });
-
- const slowed = await client.pollDeviceAuthorization("device-1", 5);
- expect(slowed).toEqual({ status: "pending", interval: 10 });
- });
-
- test("returns device auth errors and fetches userinfo after successful device login", async () => {
- let tokenPolls = 0;
- const fetchMock = mock(async (input: string | URL | Request) => {
- const url = String(input);
-
- if (url.endsWith("/.well-known/openid-configuration")) {
- return new Response(
- JSON.stringify({
- authorization_endpoint:
- "https://issuer.example.com/oauth/authorize",
- token_endpoint: "https://issuer.example.com/oauth/token",
- device_authorization_endpoint:
- "https://issuer.example.com/oauth/device_authorization",
- userinfo_endpoint: "https://issuer.example.com/oauth/userinfo",
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- if (url.endsWith("/oauth/token")) {
- tokenPolls += 1;
- if (tokenPolls === 1) {
- return new Response(
- JSON.stringify({
- error: "expired_token",
- error_description: "This device code expired.",
- }),
- { status: 400, headers: { "content-type": "application/json" } }
- );
- }
-
- return new Response(
- JSON.stringify({
- access_token: "provider-access-token",
- token_type: "Bearer",
- expires_in: 3600,
- refresh_token: "provider-refresh-token",
- scope: "profile:read",
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- if (url.endsWith("/oauth/userinfo")) {
- return new Response(
- JSON.stringify({
- sub: "user-123",
- email: "user@example.com",
- name: "Example User",
- }),
- { status: 200, headers: { "content-type": "application/json" } }
- );
- }
-
- throw new Error(`Unexpected fetch: ${url}`);
- });
-
- globalThis.fetch = fetchMock as typeof fetch;
-
- const client = new SettingsOAuthClient({
- issuerUrl: "https://issuer.example.com",
- clientId: "static-client-id",
- redirectUri: "https://gateway.example.com/agent/oauth/callback",
- });
-
- const expired = await client.pollDeviceAuthorization("device-1", 5);
- expect(expired).toEqual({
- status: "error",
- error: "This device code expired.",
- errorCode: "expired_token",
- });
-
- const complete = await client.pollDeviceAuthorization("device-1", 5);
- expect(complete.status).toBe("complete");
- if (complete.status !== "complete") {
- throw new Error("Expected complete device auth result");
- }
- expect(complete.credentials.accessToken).toBe("provider-access-token");
- expect(complete.user).toEqual({
- sub: "user-123",
- email: "user@example.com",
- name: "Example User",
- });
- });
-});
diff --git a/packages/gateway/src/__tests__/skill-and-mcp-registry.test.ts b/packages/gateway/src/__tests__/skill-and-mcp-registry.test.ts
index 6ad983cd6..5a927895e 100644
--- a/packages/gateway/src/__tests__/skill-and-mcp-registry.test.ts
+++ b/packages/gateway/src/__tests__/skill-and-mcp-registry.test.ts
@@ -1,148 +1,7 @@
import { describe, expect, test } from "bun:test";
import { McpRegistryService } from "../services/mcp-registry";
-import {
- type SkillContent,
- type SkillRegistry,
- SkillRegistryCoordinator,
- type SkillRegistryResult,
-} from "../services/skill-registry";
import type { SystemConfigResolver } from "../services/system-config-resolver";
-function createMockRegistry(
- id: string,
- results: SkillRegistryResult[],
- skills: Record = {}
-): SkillRegistry {
- return {
- id,
- search: async (_query: string, limit: number) => results.slice(0, limit),
- fetch: async (skillId: string) => {
- const skill = skills[skillId];
- if (!skill) throw new Error(`Not found: ${skillId}`);
- return skill;
- },
- };
-}
-
-function createFailingRegistry(id: string): SkillRegistry {
- return {
- id,
- search: async () => {
- throw new Error("Registry unavailable");
- },
- fetch: async () => {
- throw new Error("Registry unavailable");
- },
- };
-}
-
-const skillA: SkillContent = {
- name: "Skill A",
- description: "First skill",
- content: "content-a",
-};
-
-const skillB: SkillContent = {
- name: "Skill B",
- description: "Second skill",
- content: "content-b",
-};
-
-describe("SkillRegistryCoordinator", () => {
- test("search aggregates results from multiple registries", async () => {
- const reg1 = createMockRegistry("r1", [
- { id: "s1", name: "Skill 1", source: "r1", score: 5 },
- ]);
- const reg2 = createMockRegistry("r2", [
- { id: "s2", name: "Skill 2", source: "r2", score: 3 },
- ]);
- const coordinator = new SkillRegistryCoordinator([reg1, reg2]);
-
- const results = await coordinator.search("test", 10);
- expect(results).toHaveLength(2);
- expect(results.map((r) => r.id)).toEqual(["s1", "s2"]);
- });
-
- test("search deduplicates by id, first registry wins", async () => {
- const reg1 = createMockRegistry("r1", [
- { id: "dup", name: "From R1", source: "r1", score: 1 },
- ]);
- const reg2 = createMockRegistry("r2", [
- { id: "dup", name: "From R2", source: "r2", score: 10 },
- ]);
- const coordinator = new SkillRegistryCoordinator([reg1, reg2]);
-
- const results = await coordinator.search("test", 10);
- expect(results).toHaveLength(1);
- expect(results[0].name).toBe("From R1");
- expect(results[0].source).toBe("r1");
- });
-
- test("search sorts by score descending", async () => {
- const reg1 = createMockRegistry("r1", [
- { id: "low", name: "Low", source: "r1", score: 1 },
- { id: "high", name: "High", source: "r1", score: 10 },
- { id: "mid", name: "Mid", source: "r1", score: 5 },
- ]);
- const coordinator = new SkillRegistryCoordinator([reg1]);
-
- const results = await coordinator.search("test", 10);
- expect(results.map((r) => r.id)).toEqual(["high", "mid", "low"]);
- });
-
- test("search respects limit", async () => {
- const reg1 = createMockRegistry("r1", [
- { id: "s1", name: "A", source: "r1", score: 3 },
- { id: "s2", name: "B", source: "r1", score: 2 },
- { id: "s3", name: "C", source: "r1", score: 1 },
- ]);
- const coordinator = new SkillRegistryCoordinator([reg1]);
-
- const results = await coordinator.search("test", 2);
- expect(results).toHaveLength(2);
- });
-
- test("search tolerates registry failures", async () => {
- const failing = createFailingRegistry("bad");
- const working = createMockRegistry("good", [
- { id: "s1", name: "Survivor", source: "good", score: 1 },
- ]);
- const coordinator = new SkillRegistryCoordinator([failing, working]);
-
- const results = await coordinator.search("test", 10);
- expect(results).toHaveLength(1);
- expect(results[0].id).toBe("s1");
- });
-
- test("fetch returns from first registry that has the skill", async () => {
- const reg1 = createMockRegistry("r1", [], { "skill-a": skillA });
- const reg2 = createMockRegistry("r2", [], { "skill-b": skillB });
- const coordinator = new SkillRegistryCoordinator([reg1, reg2]);
-
- const result = await coordinator.fetch("skill-a");
- expect(result.name).toBe("Skill A");
- });
-
- test("fetch falls through to next registry on failure", async () => {
- const failing = createFailingRegistry("bad");
- const working = createMockRegistry("good", [], { "skill-b": skillB });
- const coordinator = new SkillRegistryCoordinator([failing, working]);
-
- const result = await coordinator.fetch("skill-b");
- expect(result.name).toBe("Skill B");
- });
-
- test("fetch throws when skill not found in any registry", async () => {
- const reg1 = createMockRegistry("r1", [], {});
- const reg2 = createMockRegistry("r2", [], {});
- const coordinator = new SkillRegistryCoordinator([reg1, reg2]);
-
- expect(coordinator.fetch("nonexistent")).rejects.toThrow(
- 'Skill "nonexistent" not found in any registry'
- );
- });
-});
-
// --- McpRegistryService ---
function createMockResolver(
diff --git a/packages/gateway/src/api/index.ts b/packages/gateway/src/api/index.ts
index 24f89f9a5..b45b7536c 100644
--- a/packages/gateway/src/api/index.ts
+++ b/packages/gateway/src/api/index.ts
@@ -1,2 +1 @@
export { ApiPlatform, type ApiPlatformConfig } from "./platform";
-export { ApiResponseRenderer } from "./response-renderer";
diff --git a/packages/gateway/src/api/platform.ts b/packages/gateway/src/api/platform.ts
index 2662b2b90..fdce40293 100644
--- a/packages/gateway/src/api/platform.ts
+++ b/packages/gateway/src/api/platform.ts
@@ -97,6 +97,16 @@ export class ApiPlatform implements PlatformAdapter {
});
});
+ interactionService.on("config:requested", (event: any) => {
+ if (event.teamId !== "api") return;
+ broadcastToAgent(event.conversationId, "config-request", {
+ type: "config-request",
+ requestId: event.id,
+ text: event.text,
+ timestamp: Date.now(),
+ });
+ });
+
interactionService.on("suggestion:created", (event: any) => {
if (event.teamId !== "api") return;
broadcastToAgent(event.conversationId, "suggestion", {
diff --git a/packages/gateway/src/auth/admin-status-cache.ts b/packages/gateway/src/auth/admin-status-cache.ts
deleted file mode 100644
index e8b80f622..000000000
--- a/packages/gateway/src/auth/admin-status-cache.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { BaseRedisStore } from "@lobu/core";
-import type Redis from "ioredis";
-
-interface AdminStatusEntry {
- isAdmin: boolean;
- cachedAt: number;
-}
-
-/**
- * Cache admin status checks to avoid platform API rate limiting.
- * TTL: 5 minutes by default.
- *
- * Storage: admin_status:{platform}:{chatId}:{userId}
- */
-export class AdminStatusCache extends BaseRedisStore {
- private readonly CACHE_TTL_SECONDS = 300; // 5 minutes
-
- constructor(redis: Redis) {
- super({
- redis,
- keyPrefix: "admin_status",
- loggerName: "admin-status-cache",
- });
- }
-
- /**
- * Get cached admin status.
- * Returns null if not cached or expired.
- */
- async getStatus(
- platform: string,
- chatId: string,
- userId: string
- ): Promise {
- const key = this.buildKey(platform, chatId, userId);
- const cached = await this.get(key);
- if (!cached) return null;
- return cached.isAdmin;
- }
-
- /**
- * Cache admin status with TTL.
- */
- async setStatus(
- platform: string,
- chatId: string,
- userId: string,
- isAdmin: boolean
- ): Promise {
- const key = this.buildKey(platform, chatId, userId);
- await this.set(
- key,
- { isAdmin, cachedAt: Date.now() },
- this.CACHE_TTL_SECONDS
- );
- }
-}
diff --git a/packages/gateway/src/auth/api-auth-middleware.ts b/packages/gateway/src/auth/api-auth-middleware.ts
index 1664c299a..f074000a9 100644
--- a/packages/gateway/src/auth/api-auth-middleware.ts
+++ b/packages/gateway/src/auth/api-auth-middleware.ts
@@ -34,7 +34,7 @@ export function createApiAuthMiddleware(opts: {
if (identity) return next();
}
- // 3. Try external OAuth token (validated against AUTH_MCP_URL userinfo)
+ // 3. Try external OAuth token (validated against MEMORY_URL userinfo)
if (opts.externalAuthClient) {
try {
const userInfo = await opts.externalAuthClient.fetchUserInfo(token);
diff --git a/packages/gateway/src/auth/chatgpt/index.ts b/packages/gateway/src/auth/chatgpt/index.ts
index 4c0275e94..6e192434d 100644
--- a/packages/gateway/src/auth/chatgpt/index.ts
+++ b/packages/gateway/src/auth/chatgpt/index.ts
@@ -1,2 +1 @@
export { ChatGPTOAuthModule } from "./chatgpt-oauth-module";
-export { ChatGPTDeviceCodeClient } from "./device-code-client";
diff --git a/packages/gateway/src/auth/external/client.ts b/packages/gateway/src/auth/external/client.ts
index ef638bc52..2e90e3328 100644
--- a/packages/gateway/src/auth/external/client.ts
+++ b/packages/gateway/src/auth/external/client.ts
@@ -238,18 +238,18 @@ export class ExternalAuthClient {
}
static isConfigured(): boolean {
- return !!process.env.AUTH_MCP_URL;
+ return !!process.env.MEMORY_URL;
}
static fromEnv(
publicGatewayUrl: string,
cacheStore?: ExternalAuthConfig["cacheStore"]
): ExternalAuthClient | null {
- const authMcpUrl = process.env.AUTH_MCP_URL;
+ const authMcpUrl = process.env.MEMORY_URL;
if (!authMcpUrl) return null;
const issuerUrl = authMcpUrl.replace(/\/+$/, "");
- const callbackPath = "/agent/oauth/callback";
+ const callbackPath = "/connect/oauth/callback";
// Register redirect URIs for both the configured public URL and localhost
// so OAuth works regardless of how the user accesses the gateway
diff --git a/packages/gateway/src/auth/mcp/proxy.ts b/packages/gateway/src/auth/mcp/proxy.ts
index 89f291e28..7f69e8a0c 100644
--- a/packages/gateway/src/auth/mcp/proxy.ts
+++ b/packages/gateway/src/auth/mcp/proxy.ts
@@ -339,10 +339,15 @@ export class McpProxy {
private async handleListTools(c: Context): Promise {
const mcpId = c.req.param("mcpId");
+ if (!mcpId) return c.json({ error: "Missing MCP server id" }, 400);
const auth = authenticateRequest(c);
if (!auth) return c.json({ error: "Invalid authentication token" }, 401);
const agentId = auth.tokenData.agentId || auth.tokenData.userId;
+ const requesterUserId = auth.tokenData.userId;
+ if (!agentId || !requesterUserId) {
+ return c.json({ error: "Invalid authentication token" }, 401);
+ }
const httpServer = await this.configService.getHttpServer(mcpId, agentId);
if (!httpServer) {
return c.json({ error: `MCP server '${mcpId}' not found` }, 404);
@@ -368,7 +373,7 @@ export class McpProxy {
mcpId,
"POST",
jsonRpcBody,
- auth.tokenData.userId
+ requesterUserId
);
const data = (await response.json()) as JsonRpcResponse;
@@ -402,10 +407,17 @@ export class McpProxy {
private async handleCallTool(c: Context): Promise {
const mcpId = c.req.param("mcpId");
const toolName = c.req.param("toolName");
+ if (!mcpId || !toolName) {
+ return c.json({ error: "Missing MCP server id or tool name" }, 400);
+ }
const auth = authenticateRequest(c);
if (!auth) return c.json({ error: "Invalid authentication token" }, 401);
const agentId = auth.tokenData.agentId || auth.tokenData.userId;
+ const requesterUserId = auth.tokenData.userId;
+ if (!agentId || !requesterUserId) {
+ return c.json({ error: "Invalid authentication token" }, 401);
+ }
const httpServer = await this.configService.getHttpServer(mcpId, agentId);
if (!httpServer) {
return c.json({ error: `MCP server '${mcpId}' not found` }, 404);
@@ -434,7 +446,7 @@ export class McpProxy {
content: [
{
type: "text",
- text: `Tool call requires approval. Grant access via settings page for: ${mcpId} → ${toolName}`,
+ text: `Tool call requires approval. Request access approval in chat for: ${mcpId} → ${toolName}`,
},
],
isError: true,
@@ -466,15 +478,13 @@ export class McpProxy {
id: 1,
});
- const userId = auth.tokenData.userId;
-
let response = await this.sendUpstreamRequest(
httpServer,
agentId,
mcpId,
"POST",
jsonRpcBody,
- userId
+ requesterUserId
);
let data = (await response.json()) as JsonRpcResponse;
@@ -485,7 +495,12 @@ export class McpProxy {
mcpId,
toolName,
});
- await this.reinitializeSession(httpServer, agentId, mcpId, userId);
+ await this.reinitializeSession(
+ httpServer,
+ agentId,
+ mcpId,
+ requesterUserId
+ );
response = await this.sendUpstreamRequest(
httpServer,
@@ -493,7 +508,7 @@ export class McpProxy {
mcpId,
"POST",
jsonRpcBody,
- userId
+ requesterUserId
);
data = (await response.json()) as JsonRpcResponse;
}
@@ -513,7 +528,7 @@ export class McpProxy {
const autoAuthResult = await this.tryAutoDeviceAuth(
mcpId,
agentId,
- auth.tokenData.userId
+ requesterUserId
);
if (autoAuthResult) {
return c.json(
diff --git a/packages/gateway/src/auth/oauth-templates.ts b/packages/gateway/src/auth/oauth-templates.ts
index 3e395c133..18f3c682d 100644
--- a/packages/gateway/src/auth/oauth-templates.ts
+++ b/packages/gateway/src/auth/oauth-templates.ts
@@ -1,14 +1,3 @@
-/**
- * Format MCP ID into human-readable name
- * Example: "github-mcp" -> "Github Mcp"
- */
-export function formatMcpName(mcpId: string): string {
- return mcpId
- .split("-")
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
- .join(" ");
-}
-
/**
* HTML templates for OAuth flow
*/
@@ -36,7 +25,7 @@ function escapeHtml(value: string): string {
/**
* Render a success page that auto-closes the tab (for in-app browsers)
- * and provides a link to the settings page as fallback.
+ * and provides a fallback link to agent configuration when available.
*/
export function renderOAuthSuccessPage(
name: string,
@@ -105,7 +94,7 @@ export function renderOAuthSuccessPage(
${safeTitle}
${safeDescription.includes(safeName) ? safeDescription : `${safeDescription} ${safeName}`}
${safeDetails ? `${safeDetails}
` : ""}
- ${safeSettingsUrl ? `Open Settings` : ""}
+ ${safeSettingsUrl ? `Open Configuration` : ""}
${safeCloseNote}
` : "";
- })()}
-
-
-
-
-
-
-