Encrypt and store secret ENV vars in the repo with dotenvx (#114)

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
Co-authored-by: John Richmond <5629+jr@users.noreply.github.com>
This commit is contained in:
Chris Estreich 2025-06-26 10:54:39 -07:00 committed by GitHub
parent 680a96fc9f
commit 5edb2f8bb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
74 changed files with 1032 additions and 965 deletions

View file

@ -1,31 +0,0 @@
# docker compose build base
FROM node:20-slim AS base
# Install pnpm
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# Install common system packages
RUN apt update && \
apt install -y \
curl \
git \
vim \
jq \
netcat-openbsd \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release \
wget \
gpg \
gh \
&& rm -rf /var/lib/apt/lists/*
# Install Docker cli
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt update && apt install -y docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*

View file

@ -1,12 +1,41 @@
# docker compose build roomote-api
# docker compose build roomote-api && docker compose up roomote-api
FROM roo-code-cloud-base AS base
FROM node:20-slim AS base
# Install pnpm
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# Install common system packages
RUN apt update && \
apt install -y \
curl \
git \
vim \
jq \
netcat-openbsd \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release \
wget \
gpg \
gh \
&& rm -rf /var/lib/apt/lists/*
# Install Docker cli
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt update && apt install -y docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /roo
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .env.* ./
COPY packages/config-eslint/package.json ./packages/config-eslint/
COPY packages/config-typescript/package.json ./packages/config-typescript/
COPY packages/env/package.json ./packages/env/
COPY packages/db/package.json ./packages/db/
COPY packages/ipc/package.json ./packages/ipc/
COPY apps/roomote/package.json ./apps/roomote/
@ -15,13 +44,14 @@ RUN pnpm install
COPY packages/config-eslint ./packages/config-eslint/
COPY packages/config-typescript ./packages/config-typescript/
COPY packages/env ./packages/env/
COPY packages/db ./packages/db/
COPY packages/ipc ./packages/ipc/
COPY apps/roomote ./apps/roomote/
WORKDIR /roo/apps/roomote
RUN node_modules/.bin/next build
RUN npx dotenvx run -f ../../.env.${APP_ENV:-development} -- node_modules/.bin/next build
EXPOSE 3001
ENV NODE_ENV=production
CMD ["node_modules/.bin/next", "start", "--port", "3001"]
CMD ["sh", "-c", "npx dotenvx run -f ../../.env.${APP_ENV:-development} -- node_modules/.bin/next start --port 3001"]

View file

@ -1,12 +1,41 @@
# docker compose build roomote-controller
# docker compose build roomote-controller && docker compose up roomote-controller
FROM roo-code-cloud-base AS base
FROM node:20-slim AS base
# Install pnpm
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# Install common system packages
RUN apt update && \
apt install -y \
curl \
git \
vim \
jq \
netcat-openbsd \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release \
wget \
gpg \
gh \
&& rm -rf /var/lib/apt/lists/*
# Install Docker cli
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt update && apt install -y docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /roo
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .env.* ./
COPY packages/config-eslint/package.json ./packages/config-eslint/
COPY packages/config-typescript/package.json ./packages/config-typescript/
COPY packages/env/package.json ./packages/env/
COPY packages/db/package.json ./packages/db/
COPY packages/ipc/package.json ./packages/ipc/
COPY apps/roomote/package.json ./apps/roomote/
@ -15,11 +44,13 @@ RUN pnpm install
COPY packages/config-eslint ./packages/config-eslint/
COPY packages/config-typescript ./packages/config-typescript/
COPY packages/env ./packages/env/
COPY packages/db ./packages/db/
COPY packages/ipc ./packages/ipc/
COPY apps/roomote ./apps/roomote/
WORKDIR /roo/apps/roomote
ENV NODE_ENV=production
ENV HOST_EXECUTION_METHOD=docker
CMD ["pnpm", "controller"]
CMD ["sh", "-c", "npx dotenvx run -f ../../.env.${APP_ENV:-development} -- tsx src/lib/controller.ts"]

View file

@ -1,22 +1,52 @@
# docker compose build roomote-dashboard
# docker compose build roomote-dashboard && docker compose up roomote-dashboard
FROM roo-code-cloud-base AS base
FROM node:20-slim AS base
# Install pnpm
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# Install common system packages
RUN apt update && \
apt install -y \
curl \
git \
vim \
jq \
netcat-openbsd \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release \
wget \
gpg \
gh \
&& rm -rf /var/lib/apt/lists/*
# Install Docker cli
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt update && apt install -y docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /roo
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .env.* ./
COPY packages/config-eslint/package.json ./packages/config-eslint/
COPY packages/config-typescript/package.json ./packages/config-typescript/
COPY packages/env/package.json ./packages/env/
COPY apps/roomote-dashboard/package.json ./apps/roomote-dashboard/
RUN pnpm install
COPY packages/config-eslint ./packages/config-eslint/
COPY packages/config-typescript ./packages/config-typescript/
COPY packages/env ./packages/env/
COPY apps/roomote-dashboard ./apps/roomote-dashboard/
WORKDIR /roo/apps/roomote-dashboard
EXPOSE 3002
ENV NODE_ENV=production
CMD ["pnpm", "dev"]
CMD ["sh", "-c", "npx dotenvx run -f ../../.env.${APP_ENV:-development} -- tsx src/index.ts"]

View file

@ -1,7 +1,34 @@
# docker compose build roomote-worker
# Note: Requires $GH_TOKEN to be set as build argument.
# docker compose build roomote-worker && docker compose up roomote-worker
FROM roo-code-cloud-base AS base
FROM node:20-slim AS base
# Install pnpm
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# Install common system packages
RUN apt update && \
apt install -y \
curl \
git \
vim \
jq \
netcat-openbsd \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release \
wget \
gpg \
gh \
&& rm -rf /var/lib/apt/lists/*
# Install Docker cli
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt update && apt install -y docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*
# Install additional worker-specific packages
RUN apt update && \
@ -39,11 +66,12 @@ WORKDIR /roo/repos/Roo-Code
RUN gh repo set-default RooCodeInc/Roo-Code
RUN pnpm install
# Install dependencies
WORKDIR /roo
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json .env.* ./
COPY packages/config-eslint/package.json ./packages/config-eslint/
COPY packages/config-typescript/package.json ./packages/config-typescript/
COPY packages/env/package.json ./packages/env/
COPY packages/db/package.json ./packages/db/
COPY packages/ipc/package.json ./packages/ipc/
COPY apps/roomote/package.json ./apps/roomote/
@ -52,11 +80,13 @@ RUN pnpm install
COPY packages/config-eslint ./packages/config-eslint/
COPY packages/config-typescript ./packages/config-typescript/
COPY packages/env ./packages/env/
COPY packages/db ./packages/db/
COPY packages/ipc ./packages/ipc/
COPY apps/roomote ./apps/roomote/
WORKDIR /roo/apps/roomote
ENV NODE_ENV=production
ENV HOST_EXECUTION_METHOD=docker
CMD ["pnpm", "worker"]
CMD ["sh", "-c", "npx dotenvx run -f ../../.env.${APP_ENV:-development} -- tsx src/lib/controller.ts"]

View file

@ -28,3 +28,6 @@ knip.json
# next.js
**/.next/
.vercel
# env
.env.keys

0
.env
View file

24
.env.development Normal file
View file

@ -0,0 +1,24 @@
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY_DEVELOPMENT="022703b50cc07889c89fc1425f6a780fc2cd813976eb5c9ed710687e550a0e1083"
# .env.development
DATABASE_URL=encrypted:BHEnYqWD6ebRKQM5nSA9ETE9BERgb1KghegFptV61hZSoF267Iv1wXbJnMRSqvF8ZIMkr2FSlx3YhGRX+fmo82xEZdtiyy58PCVVHsPWaliX03TFqrEY8KyKRtkX8zbNccTf+rEtQ9Gq0TzGlhKp0fWe22IprYdMKJpmF8pJmFjCf91SbmpnUwuUuo93UI/dGOCUkNi+Uw==
REDIS_URL=encrypted:BOG9yPmE7Gx/5tqBwICkJGmsd/SyfsRhCtzFhZSz2D8bZdmR+AcJVoDlBSG2PPxbtSEPrm1Bv51vmN5pArO5+vm+Uu9IPaUzSA6/5IDGgw0MsFiSTcmkQq8KQHXyRArrm+bwSMbvyG9tzdNpRbgjkh6vvg==
CLICKHOUSE_URL=encrypted:BJ9IPsZpsMW7rNYj89ZDfNAtpeAIIFmRcruWSgOTIsx6/ou3layaIwU/l5Nv5PEMqOWVZSWIU4+D6AEVprxCm9QzILWoBirEQRXBFMHAd5h0DgfRdDANB4/N6VyGKWJUPWLYbuOjs3qcJ54exM5mICapG0wgG/GlIEOyvAojuw==
CLICKHOUSE_PASSWORD=encrypted:BJ4Fdj7rqLD/7ElnUW2c5vuq/30mfXKTZBpxeUc1/kOSx5HzQZWgzSKam95l9pnP+EXuVgdofHwmsj2P9CN/+fm2OpdNwSG4yhFQkn27kj2nsaNiK/eyNchnprU0dlOnJUiYlcyENtAa
CLERK_SECRET_KEY=encrypted:BOd1VAy8MDjf1xmeiN3/ez4TDwmSDgvpLkA27q+lIFlLAc953bwA4aUlU1FlDuoiVoouQJnn+1lLNHqkZ9l7ZInDB3fHGJYQYOHKY2563kOj24GBC5lNWRr2I8xU+Q2OuXSlTddYflHedqwZvhNOA9GCXd8kdxRqQ3xYRmxvj0XS1jUhvxmaBwFiGbZx+zd+Lspj
GH_WEBHOOK_SECRET=encrypted:BG5ptaSJzmurbp1p1QTSl2Iyl6OQT5ONbjW4tbxQAdgUi+iOMHeFISIsRIFVCsLGKNDE9yOfV/NTzGfwwlG5s7xkvYffIIIRtZLr049ImgLHr14wXNrU+BQkU4QNHhA6P7GiQ+wfGocn28uJxtEirpSizhdSrfkusyYIt+iCTd2C
GH_TOKEN=encrypted:BGQvKArqcFk+y2UmBmzUZVBo6cNtDH20i0kHiqoGDRqAoFUJkc2hEumU6ukK+OSoATTLSAtRxfjwNLf9VTjHaGkRn4jExqNhvMCDLQqP8nTQ6NWvssvfb9e8RY4KAjTqXBBK1sCNfSWN647y+9pcTho4SMpmpb18clhuBkT0TlF7lyyckVE5aeM=
OPENROUTER_API_KEY=encrypted:BA3bYeSEXFA0jnw3lmzLKz53ekkGr0K6sDLN6+nFGsa0TdJrYoKI5IcRPCMWuXIoLR1dlRi61eziWMQt0e2MgLU8zcR+zKif/2twlIzFlTU7OtFSwC1DPknxAG0pf3e2LSTDiLU4hRKgYbdJn8coA3rf/YY9NRVlmJV1gDLb9KVL89IWgT+fEn3Tzh383dSolr1ec/9RyAYVGpIYTg4O57OzQ9TaZp6InWs=
SLACK_API_TOKEN=encrypted:BI5T2ZFY35FaOfSzPT8qQyUdhJs+fmL6wOFp8cVd4hM0xcSJ8va+vk1CCpltNrk25uXCd8D+fQnITb3G+L/1z1X8eqZVobqRwxO39wTcqVv1d9AQSLxAmYcEoAEWowiXQei2qS+AMcBSjZhDfURbdOfYo1+XHj0NdfspvsB1YwC+3CN3iZC/TSVBkzcQDy82Db541HnfFxhIQg==

22
.env.preview Normal file
View file

@ -0,0 +1,22 @@
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY_PREVIEW="03daea73b84e36000e1ecd63e339e6a49793b3212a48929896d0e7d8a8c94b0525"
# .env.preview
DATABASE_URL=encrypted:BOJ23F02TQxTqP7t+e1ViPBO13fODlyM9Nhb0cuEQRHjc8cQmL8e5kU8+pGKSBD4Skh03cHp327GrM3cQLfu7F20zFIazrXv6dg4urA6umlgZJpyc+N8GodZ+BUCKWBf554gcouQAp/OJN0CZgOlboP3982dCxMfUKdx3nPZVVH7QoNmfk5tIwfHTUYqGMuN7KIdjUopVsaGvAxpqz7X53RkbnOpH2AAZDFzsdeLNV6/PIhjzuIh
REDIS_URL=encrypted:BOFDUP0rzhc/kIMljZ1QG4zOQROGFOApVU8kswfA/cJy09K103SS8V2t6wrWyWu3ZgwmBfNm3gQnrm8aP8ZNfFpTrm/V3azkeV+huzXLV81mkbu0zioO5f5OrPzvR5TVOz5+cQFVQdWzwGS/ZXDoLalmvIzPZ0u3nmFPRyMT9EJM8yGb59kF6E8OdfPFzO/Cw3K4xJtalufR2k+C5FZLC8AR6TFVUgQ+jlxSu66Zd5tAVGWk3QxAinn+kd41cqzENkKf0rBm1dG/DLZ00ggI
CLICKHOUSE_URL=encrypted:BFauYbJKvW/D63ZYffeTG8EEmb7icwBJkKobendgtsn9C1mG7AkAhmcr23pJSRjjhFAiNfxZyn92mWXzZFBbulhTgFo461Ppz/y4oq/8bvLv1WKSZHG9E0BrQLx4QYHX+KymDX6pabvUftKpbQq3TnRPDjtdlYPEjxTTXCBYM7gjKsWCzp8pp6tXCrtfSqzbXB5xy0A2Rw==
CLICKHOUSE_PASSWORD=encrypted:BCZQ1MN8NoNoPVF3WxcIJLjSY5euH47/CVL4ESwAk0f6OZ6lstKFW4AaPrg5Wdd3uf9oPzcYM/dwawqhQ7bqYdtx76cBYbrtmHrirBWfvTlzX5knaV1E1xFE03jPvDGAAkK8RRU6UTQKC2lUq48=
CLERK_SECRET_KEY=encrypted:BIJlh3p2P0zzTxgP9AwYarPeT3DsHs44M4fTP+ZQfDWwjtZw4QVbNMh2NBKbSOO/bH1XTVOFPZzy7N/NXIygmqeSJwdVLwAanb0eJKq2dNbwN0MAVsPTgL0U4t5A+9ytLRcdYeivoFzQ49wL1J6D38cdYSTKXQwcO3GDCRycylBZdOOzHbP8mE/y46d4RkuK7nay
GH_WEBHOOK_SECRET=encrypted:BBVpX03MCeoEzrJbeP+bdWzB2bkDQ9VYMhZiRFckoBjN8foBb5oSI8YbyPKkRPCycfSTRirCddLzvNY/Q1Joe7pvynYubY0hW6YnbmVEaC6UYP27rGcWdJStMNfCI9OXvYaV765bjbmfBh59j+6FBTHzWuTS+xy84eRpcrzNiYjZ
GH_TOKEN=encrypted:BMB+HR14pdhTojK7exBUDaWpiQGDWziDR5cdUMM/I24MpkdCtZrvHsf+uTc/gTSnP6uGzOHc1xQAQG3OOGxA6IBls13Gv8wvVDOUdPm0NoyFSGMAqE7HbBi6GFSuNstuRNy92QJo86ZWH7La8llJXCNZ3juzFESD/rfAD1odhBkzBVDXfPdDonM=
OPENROUTER_API_KEY=encrypted:BDoXCMz3fV5DXa89cwd77/pZhw6tVIw96LvtyZEFwFQtQV1Qk+ZmtQoObiKZkfI4y1ZvpOSL2ivXfS7YY8QtLnl8uEG550FHq6s0FZsVdua+SlmoUhJOK0zV341GadCG3bc5aSMB5Wha6ml51CJzy/AKud4U4Zi3eXaj+nMxeXF8wtnHsw51JQ3F7N9VnYklyrb5FEin2SVovg3TPZk40saP8Q/YyDyU8e8=
SLACK_API_TOKEN=encrypted:BFmTkof1HoUg2UTgpm2wWdv5ZqVuldkgU5xLhGjgGFqRG7sOnAF5QdUVAfjV+890FbGF/BxBcb7bJsjMdf6+krq5U8p8Il2lucELdg1BoWisGcoy1+OCYoLxWm4RDrI/zovC09vaSnIdgIpz4T/4sNRbY5U8r7KaKXSCDPp2rCTUAC9Fc5cODlurbnMbfmPaFJIxKktqm5rfXg==

22
.env.production Normal file
View file

@ -0,0 +1,22 @@
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY_PRODUCTION="0220845c470bea52e1a524ffb098188cd9560b3610032586efdd7580cd3bf5798f"
# .env.production
DATABASE_URL=encrypted:BMAmRVLIX9odtXaqSTZwduXFizp16qjO/vmXKfrsAlLW1jrkgWpltAl6AkVpACQehua2oopJPeTISffC+qRhEHOw/kkn94ePuMrldS9Z5V6XlbT6eXJJIRVO81S1yFqipzDj00m3HwlvxlT4y912MPAfwApdzx888Tu7Dix6UODVSyiqEHfXc0/WNKo3ly1NDjT/VjIxcIIqRCXMYkTc7thLBfY5KltajL+U4MOOpPLK7lzRDd1v
REDIS_URL=encrypted:BIsj6USwbqPSaFIoun8Gii1x/n2JTmpTEvMIKbN82noNRg0Wt1xJ8GEbrNB0UXxZ3Q7p/Ugf7zUu79MqsF2Nnkh5PkXxBJku0L4JNb2eoWZdiKHFaHVf5HAFG6yyIQtKVHWjS82BOA/QQMiZkgZ2VVYwB6zvrDNkIuI6tGCVdRkAm9grvPjgHDFc6ggd0o4OotBoK5mN0UdF5Jl+ILALNyZAsc8mvVJmtJSLvFWbjRe4bM14usNRGkq48ekuoyndt6g/3AzIvlUL4dsrd7TJosVWEpA=
CLICKHOUSE_URL=encrypted:BNN8odgd5JZkV1lxYKejudbFTMClmNzerqnNTT1ALZxVH6en86Kn4kv2nb1ykFtp+NSED84BLBwrd2UdclIuaHpKreDtgVBnND34St2e+Coo52XJyvCFdgJ5E8KlDEPEukn6Ju+/oJORzhEUBUclPnrtQjOwIufEjojt9jZQ8tq6Hf/Y5c6CNgY+OeMjq5tjf6EX5uIs4g==
CLICKHOUSE_PASSWORD=encrypted:BGDdYw0Se+gkPbdpqzbrWq40r+B4s1iW4CGS61Ybq8eZzKs+b9KjD3fBqI3Z8n5mETSLheNFaYsanFj+1IzsdOz0t8YpapXmZAZyCDWLBmXrh49vlf+hhfrgdC1L/JO4H5wL/d01g+iDuuwJZz0=
CLERK_SECRET_KEY=encrypted:BDV4gzZvpKtTMWjY/5eTjuq99u13Ukr941xK4kvMvmKNEvf18znpa8vXDS9VZhKG/Od1Sp1axCv5YEDq7x9u3YE+hcS5NrldE6uOqbTwzO6HLYvHdiT/PKDiEmUmnW8p4GrS+MqaNlY9JNie/TSUzebLzvN5SzWIZfiiLXoHLFuTtrvoJ9NU2GZWjtSwXT0ndCkW
GH_WEBHOOK_SECRET=encrypted:BAoBNKhYwOuEAVJTBdEhmM2E+P9EiJJO1Zuf6v3xkDziWVIgTkjy0HPgN3+v4HauJCcQSzRBojd99QoyhTARmNGM55V3S9ErBWO/aS1rVJbWhRq25xHGBZU/r36a5kbpYA9t8u4oQ1O0b0E9U/xGmukqqrbWAZmi6GLjkcFiARAQ
GH_TOKEN=encrypted:BN93qO9v+VfEZFaIdRwyhwR2CPM0rw1QUE9SSm/qjIT6W9XnL4Gg6EStB9zfldMIA4oD+MEQqsfllXCIcpjNHfUlbeFeM+Ma9a1lhP5oD4B5pKlZW+qVk3Ne5Yr/c0okgyzGUp2RzWENIyDvxubMevBczZ6Uo36yjqlvhUcRYhfRB2wWx7Y4I8Q=
OPENROUTER_API_KEY=encrypted:BLBANC6AI/i5gHIeiBaY5lOogEEahxSLAvsxUpk6Z1FXtfrIEj/6EKA/CE1LzHOJEZNIXHTtlb41ONGQWuRFtgDcvbhSthZ5XZB4c9dcdPz0cxEU4RfWSRwjQxYZ8OMs1oLdr7NV8oR6IYwJ43lVfuePBYit7zX125Mp/25R7Kl50oSVREHnlfhtOMTOTQqLI7LutgGvQiDym1Ji1BFDaTBd4rTrkoymYxo=
SLACK_API_TOKEN=encrypted:BHrN/c76f9fFC6XlzfgRnrb2NXK1jlGrq5XbSqk0FM+t4BaLFGILW4RruQRnbBVVm4dGNVCP395H0rHXAQsFd25nPYbBUXiHY42Ap2jBfy3xVqm3dWhapffv4+MiTMdEJvpVY+yyxJwNAu2RFEEnI5I4UpogpY7yMFvHyzV2Jym3/9pJ1svQpCPqAzz79SXRLDQq8w1b7LmQhA==

21
.env.test Normal file
View file

@ -0,0 +1,21 @@
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY_TEST="02852f7005fb38145aa0d7a0a356158ba31dffeb6cb0280aa9671ee9a7673ddc94"
# .env.test
DATABASE_URL=encrypted:BBzcyQFl877jbvtrWEoYrvA+AX1xRsVPyjQwlL/5yUyFsVFN/CXVbHFooQPzesG46YemJNkYzWGebMDWNZNbPc09rAROipuDwOvipkb6MS9E2IRUuQqUDVtnvlCuDD0VK+7492RpKfrFB73owT83EiQw/XJt+FMFSIW+FK09fE3mIDAOIeNdfBPwZoOiaAeVTQ==
REDIS_URL=encrypted:BFF5ijYrLHtSlP9lxZVKsXB6RLV2dXJFzFxpk3+mVp0aA8r2Ez7g6nW7PWivmDxJjlCLg1nA27pR/Gzezg4GLXbqwDY9zV+e7Z7P46QHVvToRKDmGAuOV4CsKGTURHjLVof4K156CIu0ltyEUy7bhJuXtYqgoik=
CLICKHOUSE_URL=encrypted:BH8OZoeYgQo7Gc0MgfTSM03qQR6qdR6/cx8t25vHOv9+SOLnddFcle/WbtnzXvUsiLGNO3/IKlwx5WRBqzfDwSnbnPend+1kRZT1GO4qZp+JXWGY7bqQExplzA+YUrMj2NQEqPg3xORK7EkHTRLJ0CZYghKSAw==
CLICKHOUSE_PASSWORD=encrypted:BKa4hWylvhPLPG+MpqIdAfyNAYZY1DzxMVHxIqb294W9Ec/V7TV2Reb68mZIKsFHJ7931cbEX7rgq5qIHHedJzW9t43GsX6k2SIdF4LFMIvYBEVhdRk4Yeu1aQpZ+pxpqtAyLMEOl/Mg
CLERK_SECRET_KEY=encrypted:BGDuYROAkWIQ6nS75BFIqFlNm1nuCU8o9AOODPSs3+yxoLv8X3G0UGDI5V8xFxvN0lNgK2FBTG3GqkbEOeVZAQHXEzEJGsIQZ7Yb+/WMw6PR6CQEuvX/oKqlrNiLsyhS2St4DICSJERdR8foHA==
GH_WEBHOOK_SECRET=encrypted:BPqZwnjqQE1EXxEZhAlM5lHSMseL3blLLB2XsK50/1k77AUTtRBWHY7V/1KN5CvYocieNVury1L7t8zuxyr4vk0ZIcZZsvsLYzWu/4CiIieHc1/oh0HqjG6ZCl86EwCscPEVBgQ=
GH_TOKEN=encrypted:BGefstTA/5VjgS/e9iy7TCvM64YaKDDgp3SSr6IUGZfWrfqAhyMZe5VmxyNlNEV/UeodnnNEzT+mMtwq1HG1PWDnxl9gepXyJfulWIIx2x7SzaUfChQFXCjLKQ/AwZunnZ1HmjSreD1J
OPENROUTER_API_KEY=encrypted:BJHyYNaUsHiU6By54yUA2lG48W6NFuafGOr6Sc4XDopmTB9WulF1LbWrP0hiHY+GcWNhb3JR6V4XSOoqQDjNPza65uJCVOob1bWdC1vVoLBjxLev19OUI53jCi0kQVuNYz7P7zI5/YTWk0noSDU=
SLACK_API_TOKEN=encrypted:BDkskSYEb9SYQLpS0LyyOMgS7pVOyha9agMrS15iQUBoeULTzXbfGxEc+w3oEUa7mFyuRW+gHpo1jqEYicc2Nm9xXawDFsfhoTgXkhUdDlAVsJY/jctWtw97Hb9qLvigRrJWyx7ha5wUMg==

View file

@ -5,10 +5,13 @@ on:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
env:
NODE_VERSION: 20.19.2
PNPM_VERSION: 10.12.1
DOTENV_PRIVATE_KEY_TEST: ${{ secrets.DOTENV_PRIVATE_KEY_TEST }}
DOTENV_PRIVATE_KEY_DEVELOPMENT: ${{ secrets.DOTENV_PRIVATE_KEY_DEVELOPMENT }}
jobs:
build:
@ -31,6 +34,8 @@ jobs:
run: pnpm install
- name: Build workspaces
run: pnpm build
env:
DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }}
test:
runs-on: ubuntu-latest
@ -132,7 +137,7 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Migrate production database
run: npx drizzle-kit migrate
run: pnpm db:production:migrate
working-directory: packages/db
env:
DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }}
DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }}

5
.gitignore vendored
View file

@ -9,9 +9,8 @@ node_modules
Thumbs.db
# .env
.env*.local
.env*.preview
.env*.production
.env.keys
.env.local
# turbo
.turbo

View file

@ -5,6 +5,7 @@
"csstools.postcss",
"bradlc.vscode-tailwindcss",
"vitest.explorer",
"yoavbls.pretty-ts-errors"
"yoavbls.pretty-ts-errors",
"tamasfe.even-better-toml"
]
}

View file

@ -5,7 +5,7 @@
"scripts": {
"lint": "eslint src --ext=ts --max-warnings=0",
"check-types": "tsc --noEmit",
"dev": "tsx src/index.ts",
"dev": "dotenvx run -f ../../.env.development -- tsx src/index.ts",
"clean": "rimraf .turbo"
},
"dependencies": {

View file

@ -6,7 +6,11 @@ import { createBullBoard } from '@bull-board/api';
import express from 'express';
import type { Express, Request, Response } from 'express';
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6380', {
if (!process.env.REDIS_URL) {
throw new Error('REDIS_URL is not set');
}
const redis = new Redis(process.env.REDIS_URL, {
maxRetriesPerRequest: null,
});

View file

@ -1,5 +1,5 @@
{
"extends": "@roo-code-cloud/config-typescript/base.json",
"include": ["src/**/*.ts"],
"include": ["src"],
"exclude": ["node_modules"]
}

View file

@ -1,9 +0,0 @@
DATABASE_URL=postgres://postgres:password@postgres:5432/development
REDIS_URL=redis://localhost:6379
GH_WEBHOOK_SECRET=your-webhook-secret-here
GH_TOKEN=your-token-here
OPENROUTER_API_KEY=sk-or-v1-...
SLACK_API_TOKEN=xoxb-...

View file

@ -1,7 +1,7 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
serverExternalPackages: ['postgres', 'ioredis', 'bullmq'],
serverExternalPackages: ['ioredis', 'bullmq'],
};
export default nextConfig;

View file

@ -5,19 +5,22 @@
"scripts": {
"lint": "next lint --max-warnings 0",
"check-types": "tsc --noEmit",
"test": "vitest",
"dev": "next dev --port 3001",
"build": "next build",
"start": "next start --port 3001",
"clean": "rimraf .next .turbo",
"worker": "tsx src/lib/worker.ts",
"controller": "tsx src/lib/controller.ts"
"test": "dotenvx run -f ../../.env.test -- vitest",
"dev": "dotenvx run -f ../../.env.development -- next dev --turbopack --port 3001",
"build": "dotenvx run -f ../../.env.production -- next build",
"start": "dotenvx run -f ../../.env.production -- next start --port 3001",
"worker": "dotenvx run -f ../../.env.development -- tsx src/lib/worker.ts",
"controller": "dotenvx run -f ../../.env.development -- tsx src/lib/controller.ts",
"cli": "dotenvx run -f ../../.env.development -- tsx src/cli.ts",
"clean": "rimraf .next .turbo"
},
"dependencies": {
"@roo-code-cloud/db": "workspace:^",
"@roo-code-cloud/env": "workspace:^",
"@roo-code-cloud/ipc": "workspace:^",
"@roo-code/types": "^1.29.0",
"bullmq": "^5.37.0",
"cmd-ts": "^0.13.0",
"drizzle-orm": "^0.44.2",
"execa": "^9.6.0",
"ioredis": "^5.6.1",

View file

@ -1,12 +1,31 @@
import { NextResponse } from 'next/server';
import { db } from '@roo-code-cloud/db/server';
import { Env } from '@roo-code-cloud/env';
import { redis } from '@/lib';
export async function GET() {
try {
const services = { database: false, redis: false };
const hosts = { database: 'unknown', redis: 'unknown' };
try {
const databaseUrl = new URL(Env.DATABASE_URL);
hosts.database = databaseUrl.hostname;
} catch (error) {
console.error('Failed to parse DATABASE_URL:', error);
}
try {
const redisUrl = new URL(
process.env.REDIS_URL || 'redis://localhost:6379',
);
hosts.redis = redisUrl.hostname;
} catch (error) {
console.error('Failed to parse REDIS_URL:', error);
}
try {
await db.execute('SELECT 1');
@ -22,10 +41,11 @@ export async function GET() {
console.error('Redis health check failed:', error);
}
const allHealthy = Object.values(services).every(Boolean);
const isHealthy = Object.values(services).every(Boolean);
return NextResponse.json(
{ status: allHealthy ? 'ok' : 'error', services },
{ status: allHealthy ? 200 : 500 },
{ status: isHealthy ? 'ok' : 'error', services, hosts },
{ status: isHealthy ? 200 : 500 },
);
} catch (error) {
console.error('Health check error:', error);

View file

@ -1,9 +1,8 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { db, cloudJobs } from '@roo-code-cloud/db/server';
import { createJobSchema, db, cloudJobs } from '@roo-code-cloud/db/server';
import { createJobSchema } from '@/types';
import { enqueue } from '@/lib';
export async function POST(request: NextRequest) {

View file

@ -1,7 +1,7 @@
import { NextResponse } from 'next/server';
import { z } from 'zod';
import type { JobPayload } from '@/types';
import type { JobPayload } from '@roo-code-cloud/db';
import { createAndEnqueueJob } from './utils';

View file

@ -1,7 +1,7 @@
import { NextResponse } from 'next/server';
import { z } from 'zod';
import type { JobPayload } from '@/types';
import type { JobPayload } from '@roo-code-cloud/db';
import { createAndEnqueueJob } from './utils';

View file

@ -1,7 +1,7 @@
import { NextResponse } from 'next/server';
import { z } from 'zod';
import type { JobPayload } from '@/types';
import type { JobPayload } from '@roo-code-cloud/db';
import { createAndEnqueueJob } from './utils';

View file

@ -1,8 +1,12 @@
import { createHmac } from 'crypto';
import { db, cloudJobs } from '@roo-code-cloud/db/server';
import {
type JobType,
type JobPayload,
db,
cloudJobs,
} from '@roo-code-cloud/db/server';
import type { JobType, JobPayload } from '@/types';
import { enqueue } from '@/lib';
export function verifySignature(

311
apps/roomote/src/cli.ts Normal file
View file

@ -0,0 +1,311 @@
import * as path from 'path';
import * as os from 'node:os';
import {
command,
run,
string,
number,
option,
subcommands,
optional,
} from 'cmd-ts';
import type { JobPayload } from '@roo-code-cloud/db';
import {
fixGitHubIssue,
processIssueComment,
processPullRequestComment,
} from '@/lib/jobs';
import { runTask } from '@/lib/runTask';
import { Logger } from '@/lib/logger';
const logger = new Logger({
logDir: path.resolve(os.tmpdir(), 'logs'),
filename: 'cli.log',
tag: 'worker',
});
const fixIssueCommand = command({
name: 'fix-issue',
description: 'Fix a GitHub issue',
args: {
repo: option({
type: string,
long: 'repo',
short: 'r',
description: 'Repository name (e.g., owner/repo)',
}),
issue: option({
type: number,
long: 'issue',
short: 'i',
description: 'Issue number',
}),
title: option({
type: string,
long: 'title',
short: 't',
description: 'Issue title',
}),
body: option({
type: string,
long: 'body',
short: 'b',
description: 'Issue body/description',
}),
labels: option({
type: optional(string),
long: 'labels',
short: 'l',
description: 'Issue labels (comma-separated: -l "bug,enhancement")',
}),
},
handler: async ({ repo, issue, title, body, labels }) => {
try {
console.log('🔧 Fixing GitHub issue...');
const payload: JobPayload<'github.issue.fix'> = {
repo,
issue,
title,
body,
labels: labels ? labels.split(',').map((l) => l.trim()) : [],
};
const result = await fixGitHubIssue(payload);
console.log('✅ Issue fix completed successfully!');
console.log(`Repository: ${result.repo}`);
console.log(`Issue: #${result.issue}`);
console.log('Result:', result.result);
} catch (error) {
console.error('❌ Error fixing issue:', error);
process.exit(1);
}
},
});
const respondIssueCommentCommand = command({
name: 'respond-issue-comment',
description: 'Respond to a GitHub issue comment',
args: {
repo: option({
type: string,
long: 'repo',
short: 'r',
description: 'Repository name (e.g., owner/repo)',
}),
issueNumber: option({
type: number,
long: 'issue-number',
description: 'Issue number',
}),
issueTitle: option({
type: string,
long: 'issue-title',
description: 'Issue title',
}),
issueBody: option({
type: string,
long: 'issue-body',
description: 'Issue body',
}),
commentId: option({
type: number,
long: 'comment-id',
description: 'Comment ID',
}),
commentBody: option({
type: string,
long: 'comment-body',
description: 'Comment body',
}),
commentAuthor: option({
type: string,
long: 'comment-author',
description: 'Comment author',
}),
commentUrl: option({
type: string,
long: 'comment-url',
description: 'Comment URL',
}),
},
handler: async (args) => {
try {
console.log('💬 Responding to GitHub issue comment...');
const payload: JobPayload<'github.issue.comment.respond'> = {
repo: args.repo,
issueNumber: args.issueNumber,
issueTitle: args.issueTitle,
issueBody: args.issueBody,
commentId: args.commentId,
commentBody: args.commentBody,
commentAuthor: args.commentAuthor,
commentUrl: args.commentUrl,
};
const result = await processIssueComment(payload);
console.log('✅ Issue comment response completed successfully!');
console.log('Result:', result);
} catch (error) {
console.error('❌ Error responding to issue comment:', error);
process.exit(1);
}
},
});
const respondPrCommentCommand = command({
name: 'respond-pr-comment',
description: 'Respond to a GitHub PR comment',
args: {
repo: option({
type: string,
long: 'repo',
short: 'r',
description: 'Repository name (e.g., owner/repo)',
}),
prNumber: option({
type: number,
long: 'pr-number',
description: 'PR number',
}),
prTitle: option({
type: string,
long: 'pr-title',
description: 'PR title',
}),
prBody: option({
type: string,
long: 'pr-body',
description: 'PR body',
}),
prBranch: option({
type: string,
long: 'pr-branch',
description: 'PR branch',
}),
baseRef: option({
type: string,
long: 'base-ref',
description: 'Base reference',
}),
commentId: option({
type: number,
long: 'comment-id',
description: 'Comment ID',
}),
commentBody: option({
type: string,
long: 'comment-body',
description: 'Comment body',
}),
commentAuthor: option({
type: string,
long: 'comment-author',
description: 'Comment author',
}),
commentType: option({
type: string,
long: 'comment-type',
description: 'Comment type (issue_comment or review_comment)',
}),
commentUrl: option({
type: string,
long: 'comment-url',
description: 'Comment URL',
}),
},
handler: async (args) => {
try {
console.log('💬 Responding to GitHub PR comment...');
if (
args.commentType !== 'issue_comment' &&
args.commentType !== 'review_comment'
) {
throw new Error(
'Comment type must be either "issue_comment" or "review_comment"',
);
}
const payload: JobPayload<'github.pr.comment.respond'> = {
repo: args.repo,
prNumber: args.prNumber,
prTitle: args.prTitle,
prBody: args.prBody,
prBranch: args.prBranch,
baseRef: args.baseRef,
commentId: args.commentId,
commentBody: args.commentBody,
commentAuthor: args.commentAuthor,
commentType: args.commentType as 'issue_comment' | 'review_comment',
commentUrl: args.commentUrl,
};
const result = await processPullRequestComment(payload);
console.log('✅ PR comment response completed successfully!');
console.log('Result:', result);
} catch (error) {
console.error('❌ Error responding to PR comment:', error);
process.exit(1);
}
},
});
const promptCommand = command({
name: 'prompt',
description: 'Prompt',
args: {
text: option({
type: string,
long: 'text',
description: 'Text to test',
}),
mode: option({
type: string,
long: 'mode',
description: 'Mode to use: code, ask, architect',
}),
workspacePath: option({
type: string,
long: 'workspace-path',
description: 'Workspace path',
}),
},
handler: async ({ text, mode, workspacePath }) => {
if (!['code', 'ask', 'architect'].includes(mode)) {
throw new Error('Invalid mode');
}
console.log(`${text} (${mode}) in ${workspacePath}`);
await runTask({
jobType: 'test.prompt',
jobPayload: { text },
prompt: text,
logger,
notify: false,
workspacePath,
settings: { mode },
});
},
});
const app = subcommands({
name: 'roomote-cli',
description: 'Roomote CLI - Run jobs directly from the command line',
cmds: {
'fix-issue': fixIssueCommand,
'respond-issue-comment': respondIssueCommentCommand,
'respond-pr-comment': respondPrCommentCommand,
prompt: promptCommand,
},
});
run(app, process.argv.slice(2));

View file

@ -81,11 +81,7 @@ export class WorkerController {
`--name roomote-${workerId}`,
'--rm',
'--network roo-code-cloud_default',
'-e HOST_EXECUTION_METHOD=docker',
`-e GH_TOKEN=${process.env.GH_TOKEN}`,
`-e DATABASE_URL=${process.env.DATABASE_URL}`,
`-e REDIS_URL=${process.env.REDIS_URL}`,
`-e NODE_ENV=${process.env.NODE_ENV}`,
'-v /var/run/docker.sock:/var/run/docker.sock',
'-v /tmp/roomote:/var/log/roomote',
];

View file

@ -1,9 +1,15 @@
import { eq } from 'drizzle-orm';
import { Job } from 'bullmq';
import { db, cloudJobs, type UpdateCloudJob } from '@roo-code-cloud/db/server';
import type { JobType, JobStatus, JobParams, JobPayload } from '@/types';
import {
type JobType,
type JobStatus,
type JobParams,
type JobPayload,
type UpdateCloudJob,
db,
cloudJobs,
} from '@roo-code-cloud/db/server';
import { fixGitHubIssue } from './jobs/fixGitHubIssue';
import { processPullRequestComment } from './jobs/processPullRequestComment';
@ -26,7 +32,7 @@ export async function processJob<T extends JobType>({
payload as JobPayload<'github.issue.fix'>,
{
onTaskStarted: async (
slackThreadTs: string | null,
slackThreadTs: string | null | undefined,
_rooTaskId: string,
) => {
if (slackThreadTs) {
@ -48,7 +54,7 @@ export async function processJob<T extends JobType>({
payload as JobPayload<'github.issue.comment.respond'>,
{
onTaskStarted: async (
slackThreadTs: string | null,
slackThreadTs: string | null | undefined,
_rooTaskId: string,
) => {
if (slackThreadTs) {
@ -70,7 +76,7 @@ export async function processJob<T extends JobType>({
payload as JobPayload<'github.pr.comment.respond'>,
{
onTaskStarted: async (
slackThreadTs: string | null,
slackThreadTs: string | null | undefined,
_rooTaskId: string,
) => {
if (slackThreadTs) {

View file

@ -1,7 +1,7 @@
import * as path from 'path';
import * as os from 'node:os';
import type { JobType, JobPayload } from '@/types';
import type { JobType, JobPayload } from '@roo-code-cloud/db';
import { runTask, type RunTaskCallbacks } from '../runTask';
import { Logger } from '../logger';
@ -46,7 +46,6 @@ Your job isn't done until you've created a pull request. Try to solve any git is
jobType,
jobPayload,
prompt,
publish: async () => {},
logger: new Logger({
logDir: path.resolve(os.tmpdir(), 'logs'),
filename: 'worker.log',

View file

@ -0,0 +1,3 @@
export { fixGitHubIssue } from './fixGitHubIssue';
export { processIssueComment } from './processIssueComment';
export { processPullRequestComment } from './processPullRequestComment';

View file

@ -1,7 +1,7 @@
import * as path from 'path';
import * as os from 'node:os';
import type { JobType, JobPayload } from '@/types';
import type { JobType, JobPayload } from '@roo-code-cloud/db';
import { runTask, type RunTaskCallbacks } from '../runTask';
import { Logger } from '../logger';
@ -58,7 +58,6 @@ gh api repos/${jobPayload.repo}/issues/${jobPayload.issueNumber}/comments --meth
jobType,
jobPayload,
prompt,
publish: async () => {},
logger: new Logger({
logDir: path.resolve(os.tmpdir(), 'logs'),
filename: 'worker.log',

View file

@ -1,7 +1,7 @@
import * as path from 'path';
import * as os from 'node:os';
import type { JobType, JobPayload } from '@/types';
import type { JobType, JobPayload } from '@roo-code-cloud/db';
import { runTask, type RunTaskCallbacks } from '../runTask';
import { Logger } from '../logger';
@ -72,7 +72,6 @@ Do not create a new pull request - work directly on the existing PR branch.
jobType,
jobPayload,
prompt,
publish: async () => {},
logger: new Logger({
logDir: path.resolve(os.tmpdir(), 'logs'),
filename: 'worker.log',

View file

@ -1,6 +1,6 @@
import { Queue, Job } from 'bullmq';
import type { JobTypes, JobPayload, JobParams } from '@/types';
import type { JobTypes, JobPayload, JobParams } from '@roo-code-cloud/db';
import { redis } from './redis';

View file

@ -6,7 +6,7 @@ import pWaitFor from 'p-wait-for';
import { execa } from 'execa';
import {
type TaskEvent,
type RooCodeSettings,
TaskCommandName,
RooCodeEventName,
IpcMessageType,
@ -14,7 +14,7 @@ import {
} from '@roo-code/types';
import { IpcClient } from '@roo-code-cloud/ipc';
import type { JobPayload, JobType } from '@/types';
import type { JobPayload, JobType } from '@roo-code-cloud/db';
import { Logger } from './logger';
import { isDockerContainer } from './utils';
@ -31,12 +31,12 @@ class SubprocessTimeoutError extends Error {
export type RunTaskCallbacks = {
onTaskStarted?: (
slackThreadTs: string | null,
slackThreadTs: string | null | undefined,
rooTaskId: string,
) => Promise<void>;
onTaskAborted?: (slackThreadTs: string | null) => Promise<void>;
onTaskAborted?: (slackThreadTs: string | null | undefined) => Promise<void>;
onTaskCompleted?: (
slackThreadTs: string | null,
slackThreadTs: string | null | undefined,
success: boolean,
duration: number,
rooTaskId?: string,
@ -49,24 +49,28 @@ type RunTaskOptions<T extends JobType> = {
jobType: T;
jobPayload: JobPayload<T>;
prompt: string;
publish: (taskEvent: TaskEvent) => Promise<void>;
logger: Logger;
callbacks?: RunTaskCallbacks;
notify?: boolean;
workspacePath?: string;
settings?: RooCodeSettings;
};
export const runTask = async <T extends JobType>({
jobType,
jobPayload,
prompt,
publish,
logger,
callbacks,
notify = true,
workspacePath = '/roo/repos/Roo-Code',
settings = {},
}: RunTaskOptions<T>) => {
const workspacePath = '/roo/repos/Roo-Code'; // findGitRoot(process.cwd())
const ipcSocketPath = path.resolve(
os.tmpdir(),
`${crypto.randomUUID().slice(0, 8)}.sock`,
);
const env = { ROO_CODE_IPC_SOCKET_PATH: ipcSocketPath };
const controller = new AbortController();
const cancelSignal = controller.signal;
@ -124,8 +128,8 @@ export const runTask = async <T extends JobType>({
let rooTaskId: string | undefined;
let isClientDisconnected = false;
const slackNotifier = new SlackNotifier(logger);
let slackThreadTs: string | null = null;
const slackNotifier = notify ? new SlackNotifier(logger) : undefined;
let slackThreadTs: string | null | undefined = null;
const ignoreEvents: Record<'broadcast' | 'log', RooCodeEventName[]> = {
broadcast: [RooCodeEventName.Message],
@ -138,11 +142,6 @@ export const runTask = async <T extends JobType>({
client.on(IpcMessageType.TaskEvent, async (taskEvent) => {
const { eventName, payload } = taskEvent;
// Publish all events except for these to Redis.
if (!ignoreEvents.broadcast.includes(eventName)) {
await publish({ ...taskEvent });
}
// Log all events except for these.
// For message events we only log non-partial messages.
if (
@ -158,7 +157,7 @@ export const runTask = async <T extends JobType>({
rooTaskId = payload[0];
if (rooTaskId) {
slackThreadTs = await slackNotifier.postTaskStarted({
slackThreadTs = await slackNotifier?.postTaskStarted({
jobType,
jobPayload,
rooTaskId,
@ -174,7 +173,7 @@ export const runTask = async <T extends JobType>({
taskAbortedAt = Date.now();
if (slackThreadTs) {
await slackNotifier.postTaskUpdated(
await slackNotifier?.postTaskUpdated(
slackThreadTs,
'Task was aborted',
'warning',
@ -190,7 +189,7 @@ export const runTask = async <T extends JobType>({
taskFinishedAt = Date.now();
if (slackThreadTs) {
await slackNotifier.postTaskCompleted(
await slackNotifier?.postTaskCompleted(
slackThreadTs,
true,
taskFinishedAt - taskStartedAt,
@ -220,6 +219,7 @@ export const runTask = async <T extends JobType>({
configuration: {
...EVALS_SETTINGS,
openRouterApiKey: process.env.OPENROUTER_API_KEY,
...settings,
},
text: prompt,
newTab: true,
@ -239,7 +239,7 @@ export const runTask = async <T extends JobType>({
logger.error('time limit reached');
if (slackThreadTs) {
await slackNotifier.postTaskUpdated(
await slackNotifier?.postTaskUpdated(
slackThreadTs,
'Task timed out after 30 minutes',
'error',
@ -266,7 +266,7 @@ export const runTask = async <T extends JobType>({
logger.error('client disconnected before task finished');
if (slackThreadTs) {
await slackNotifier.postTaskUpdated(
await slackNotifier?.postTaskUpdated(
slackThreadTs,
'Client disconnected before task completion',
'error',

View file

@ -1,4 +1,5 @@
import { JobPayload, JobType } from '@/types';
import type { JobPayload, JobType } from '@roo-code-cloud/db';
import { Logger } from './logger';
export interface SlackMessage {

View file

@ -10,16 +10,12 @@ import { processJob } from './job';
// --interactive \
// --tty \
// --network roo-code-cloud_default \
// -e HOST_EXECUTION_METHOD=docker \
// -e GH_TOKEN=$GH_TOKEN \
// -e DATABASE_URL=postgres://postgres:password@postgres:5432/development \
// -e REDIS_URL=redis://redis:6379 \
// -e NODE_ENV=production \
// -v /var/run/docker.sock:/var/run/docker.sock \
// -v /tmp/roomote:/var/log/roomote \
// roomote-worker sh -c "bash"
async function processSingleJob() {
async function processNextJob() {
const worker = new Worker('roomote', undefined, {
autorun: false,
connection: redis,
@ -69,4 +65,4 @@ if (!process.env.GH_TOKEN) {
throw new Error('GH_TOKEN is not set');
}
processSingleJob();
processNextJob();

View file

@ -1,61 +0,0 @@
import { z } from 'zod';
export const createJobSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal('github.issue.fix'),
payload: z.object({
repo: z.string(),
issue: z.number(),
title: z.string(),
body: z.string(),
labels: z.array(z.string()).optional(),
}),
}),
z.object({
type: z.literal('github.issue.comment.respond'),
payload: z.object({
repo: z.string(),
issueNumber: z.number(),
issueTitle: z.string(),
issueBody: z.string(),
commentId: z.number(),
commentBody: z.string(),
commentAuthor: z.string(),
commentUrl: z.string(),
}),
}),
z.object({
type: z.literal('github.pr.comment.respond'),
payload: z.object({
repo: z.string(),
prNumber: z.number(),
prTitle: z.string(),
prBody: z.string(),
prBranch: z.string(),
baseRef: z.string(),
commentId: z.number(),
commentBody: z.string(),
commentAuthor: z.string(),
commentType: z.enum(['issue_comment', 'review_comment']),
commentUrl: z.string(),
}),
}),
]);
export type CreateJob = z.infer<typeof createJobSchema>;
export type JobTypes = {
[K in CreateJob['type']]: Extract<CreateJob, { type: K }>['payload'];
};
export type JobType = keyof JobTypes;
export type JobStatus = 'pending' | 'processing' | 'completed' | 'failed';
export type JobPayload<T extends JobType = JobType> = JobTypes[T];
export type JobParams<T extends JobType> = {
jobId: number;
type: T;
payload: JobPayload<T>;
};

View file

@ -1,24 +1,9 @@
# NOTE: Sensitive ENV vars are overridden with their real values in .env.local
# for local development and in the "Environment Variables" setting on Vercel
# for production (https://vercel.com/roo-code/roo-code-cloud/settings/environment-variables).
# Postgres
DATABASE_URL=postgres://foo:bar@baz:5432/jabberworky
# ClickHouse
CLICKHOUSE_URL=https://fake.url
CLICKHOUSE_PASSWORD=fake-password
# Clerk
CLERK_SECRET_KEY=fake-secret-key
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
NEXT_PUBLIC_CLERK_FRONTEND_API=https://fake.url
# https://clerk.com/docs/deployments/clerk-environment-variables#sign-in-and-sign-up-redirects
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXBpYy1jaGFtb2lzLTg1LmNsZXJrLmFjY291bnRzLmRldiQ
NEXT_PUBLIC_CLERK_FRONTEND_API=https://epic-chamois-85.clerk.accounts.dev
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL=/
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL=/authorized
NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL=/authorized
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/authorized
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/authorized
NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL=/
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/

View file

@ -1,3 +0,0 @@
DATABASE_URL=postgres://postgres:password@localhost:5432/development
CLICKHOUSE_URL=http://localhost:8123/default
CLICKHOUSE_PASSWORD=password

2
apps/web/.env.production Normal file
View file

@ -0,0 +1,2 @@
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_Y2xlcmsucm9vY29kZS5jb20k
NEXT_PUBLIC_CLERK_FRONTEND_API=https://clerk.roocode.com

View file

@ -1,3 +0,0 @@
DATABASE_URL=postgres://postgres:password@localhost:5432/test
CLICKHOUSE_URL=http://localhost:8123/default
CLICKHOUSE_PASSWORD=password

View file

@ -49,10 +49,8 @@ Create an `.env.local` file and fill out the required secrets, including:
- `CLICKHOUSE_URL`
- `CLICKHOUSE_PASSWORD`
- `CLERK_SECRET_KEY`
- `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`
- `NEXT_PUBLIC_CLERK_FRONTEND_API`
You can now start the Next.js app and [Spotlight](https://spotlightjs.com/) with:
You can now start the Next.js app with:
```sh
pnpm dev

View file

@ -1,14 +1,9 @@
import type { NextConfig } from 'next';
import withBundleAnalyzer from '@next/bundle-analyzer';
import { withSentryConfig } from '@sentry/nextjs';
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin();
const bundleAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});
const nextConfig: NextConfig = {
eslint: { dirs: ['.'] },
poweredByHeader: false,
@ -20,7 +15,7 @@ const nextConfig: NextConfig = {
// },
};
export default withSentryConfig(bundleAnalyzer(withNextIntl(nextConfig)), {
export default withSentryConfig(withNextIntl(nextConfig), {
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
// FIXME: Add your Sentry organization and project names

View file

@ -4,10 +4,10 @@
"scripts": {
"lint": "next lint --max-warnings 0",
"check-types": "tsc --noEmit",
"test": "dotenvx run -f .env.test -f .env -- vitest run",
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"test": "dotenvx run -f ../../.env.test -- vitest",
"dev": "dotenvx run -f ../../.env.development -- next dev --turbopack",
"build": "dotenvx run -f ../../.env.production -- next build",
"start": "dotenvx run -f ../../.env.production -- next start",
"clean": "rimraf .next .turbo out"
},
"dependencies": {
@ -16,7 +16,6 @@
"@clerk/themes": "^2.2.48",
"@clickhouse/client": "^1.11.1",
"@hookform/resolvers": "^5.0.1",
"@logtail/pino": "^0.5.5",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-dialog": "^1.1.14",
@ -32,9 +31,9 @@
"@radix-ui/react-toast": "^1.2.14",
"@radix-ui/react-tooltip": "^1.2.7",
"@roo-code-cloud/db": "workspace:^",
"@roo-code-cloud/env": "workspace:^",
"@roo-code/types": "^1.29.0",
"@sentry/nextjs": "^9.23.0",
"@t3-oss/env-nextjs": "^0.13.6",
"@tailwindcss/postcss": "^4.1.8",
"@tanstack/react-query": "^5.79.0",
"@tanstack/react-table": "^8.21.3",
@ -74,7 +73,6 @@
"devDependencies": {
"@clerk/testing": "^1.7.5",
"@eslint/js": "^9.27.0",
"@next/bundle-analyzer": "^15.3.3",
"@next/eslint-plugin-next": "^15.3.3",
"@roo-code-cloud/config-eslint": "workspace:^",
"@roo-code-cloud/config-typescript": "workspace:^",

View file

@ -3,8 +3,10 @@
import { NextRequest } from 'next/server';
import { auth } from '@clerk/nextjs/server';
import { Env } from '@roo-code-cloud/env';
import { type AuthResult, type ApiAuthResult, isOrgRole } from '@/types';
import { Env, logger } from '@/lib/server';
import { logger } from '@/lib/server';
// import {
// type AgentTokenPayload,
// validateAgentToken,

View file

@ -9,9 +9,6 @@ export async function register() {
// Sentry DSN
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Enable Spotlight in development
spotlight: process.env.NODE_ENV === 'development',
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
@ -30,9 +27,6 @@ export async function register() {
// Sentry DSN
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Enable Spotlight in development
spotlight: process.env.NODE_ENV === 'development',
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,

View file

@ -2,6 +2,12 @@
import jwt from 'jsonwebtoken';
vi.mock('@roo-code-cloud/env', () => ({
Env: {
CLERK_SECRET_KEY: 'test-secret-key-for-jwt-testing',
},
}));
import {
getAgentToken,
refreshAgentTokenIfNeeded,
@ -10,14 +16,9 @@ import {
} from '../agent-auth';
describe('agent-auth', () => {
const originalEnv = process.env;
const testSecret = 'test-secret-key-for-jwt-testing';
beforeEach(() => {
// Reset environment variables
process.env = { ...originalEnv };
process.env.CLERK_SECRET_KEY = testSecret;
// Mock Date.now() to return a consistent timestamp
vi.useFakeTimers();
vi.setSystemTime(new Date('2023-01-01T00:00:00Z'));
@ -25,7 +26,6 @@ describe('agent-auth', () => {
afterEach(() => {
vi.useRealTimers();
process.env = originalEnv;
});
describe('getAgentToken', () => {
@ -51,7 +51,13 @@ describe('agent-auth', () => {
});
it('should use fallback secret when CLERK_SECRET_KEY is not set', async () => {
delete process.env.CLERK_SECRET_KEY;
// Import the mocked module to modify it
const { Env } = await import('@roo-code-cloud/env');
const mockedEnv = vi.mocked(Env);
// Update the mock to use fallback secret
mockedEnv.CLERK_SECRET_KEY = 'fallback-secret';
const token = await getAgentToken('agent-123', 'org-456');
// Token should still be valid with fallback secret
@ -61,6 +67,9 @@ describe('agent-auth', () => {
// Verify it was signed with the fallback secret
const decoded = jwt.verify(token, 'fallback-secret') as AgentTokenPayload;
expect(decoded.sub).toBe('agent-123');
// Reset back to test secret
mockedEnv.CLERK_SECRET_KEY = testSecret;
});
it('should set correct expiration time (15 minutes)', async () => {
@ -240,7 +249,13 @@ describe('agent-auth', () => {
});
it('should use fallback secret when CLERK_SECRET_KEY is not set', async () => {
delete process.env.CLERK_SECRET_KEY;
// Import the mocked module to modify it
const { Env } = await import('@roo-code-cloud/env');
const mockedEnv = vi.mocked(Env);
// Update the mock to use fallback secret
mockedEnv.CLERK_SECRET_KEY = 'fallback-secret';
const token = jwt.sign(validPayload, 'fallback-secret', {
algorithm: 'HS256',
});
@ -248,6 +263,9 @@ describe('agent-auth', () => {
const result = await validateAgentToken(token);
expect(result).toEqual(validPayload);
// Reset back to test secret
mockedEnv.CLERK_SECRET_KEY = testSecret;
});
it('should throw error for invalid JWT signature', async () => {

View file

@ -1,6 +1,8 @@
import jwt from 'jsonwebtoken';
import { z } from 'zod';
import { Env } from '@roo-code-cloud/env';
const agentTokenPayloadSchema = z.object({
sub: z.string().min(1, 'Subject (sub) is required'),
org_id: z.string().min(1, 'Organization ID is required'),
@ -24,7 +26,7 @@ export async function getAgentToken(agentId: string, orgId: string) {
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 15 * 60, // 15 minutes
},
process.env.CLERK_SECRET_KEY || 'fallback-secret',
Env.CLERK_SECRET_KEY,
{ algorithm: 'HS256' },
);
}
@ -58,11 +60,9 @@ export async function validateAgentToken(
token: string,
): Promise<AgentTokenPayload> {
try {
const rawPayload = jwt.verify(
token,
process.env.CLERK_SECRET_KEY || 'fallback-secret',
{ algorithms: ['HS256'] },
);
const rawPayload = jwt.verify(token, Env.CLERK_SECRET_KEY, {
algorithms: ['HS256'],
});
const parseResult = agentTokenPayloadSchema.safeParse(rawPayload);

View file

@ -1,6 +1,6 @@
import { createClient } from '@clickhouse/client';
import { Env } from './env';
import { Env } from '@roo-code-cloud/env';
export const analytics = createClient({
url: Env.CLICKHOUSE_URL,

View file

@ -1,47 +0,0 @@
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const Env = createEnv({
server: {
CLERK_SECRET_KEY: z.string().min(1),
DATABASE_URL: z.string(),
LOGTAIL_SOURCE_TOKEN: z.string().optional(),
CLICKHOUSE_URL: z.string().min(1),
CLICKHOUSE_PASSWORD: z.string().min(1),
},
client: {
NEXT_PUBLIC_APP_URL: z.string().optional(),
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
NEXT_PUBLIC_CLERK_SIGN_IN_URL: z.string().min(1),
NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL: z.string().min(1),
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL: z.string().min(1),
NEXT_PUBLIC_CLERK_SIGN_UP_URL: z.string().min(1),
NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL: z.string().min(1),
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL: z.string().min(1),
},
shared: {
NODE_ENV: z.enum(['test', 'development', 'production']).optional(),
},
// You need to destructure all the keys manually.
runtimeEnv: {
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
DATABASE_URL: process.env.DATABASE_URL,
LOGTAIL_SOURCE_TOKEN: process.env.LOGTAIL_SOURCE_TOKEN,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
NEXT_PUBLIC_CLERK_SIGN_IN_URL: process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL,
NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL:
process.env.NEXT_PUBLIC_CLERK_SIGN_IN_FORCE_REDIRECT_URL,
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL:
process.env.NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL,
NEXT_PUBLIC_CLERK_SIGN_UP_URL: process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL,
NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL:
process.env.NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL,
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL:
process.env.NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL,
NODE_ENV: process.env.NODE_ENV,
CLICKHOUSE_URL: process.env.CLICKHOUSE_URL,
CLICKHOUSE_PASSWORD: process.env.CLICKHOUSE_PASSWORD,
},
});

View file

@ -1,4 +1,3 @@
export { Env } from './env';
export { logger } from './logger';
export { handleError } from './api';
export { analytics } from './analytics';

View file

@ -1,17 +1,6 @@
import pino, { type DestinationStream } from 'pino';
import pretty from 'pino-pretty';
import logtail from '@logtail/pino';
import { Env } from './env';
const stream: DestinationStream = Env.LOGTAIL_SOURCE_TOKEN
? pino.multistream([
await logtail({
sourceToken: Env.LOGTAIL_SOURCE_TOKEN,
options: { sendLogsToBetterStack: true },
}),
{ stream: pretty() }, // Prints logs to the console.
])
: pretty({ colorize: true });
const stream: DestinationStream = pretty({ colorize: true });
export const logger = pino({ base: undefined }, stream);

View file

@ -1,12 +1,6 @@
name: roo-code-cloud
services:
base:
build:
context: .
dockerfile: .docker/Dockerfile.base
image: roo-code-cloud-base
postgres:
container_name: roo-code-cloud-postgres
image: postgres:17.5
@ -67,7 +61,7 @@ services:
dockerfile: .docker/Dockerfile.roomote-dashboard
image: roo-code-cloud-roomote-dashboard
env_file:
- .env
- .env.keys
ports:
- "3002:3002"
depends_on:
@ -81,7 +75,7 @@ services:
dockerfile: .docker/Dockerfile.roomote-api
image: roo-code-cloud-roomote-api
env_file:
- .env
- .env.keys
ports:
- "3001:3001"
volumes:
@ -97,11 +91,9 @@ services:
build:
context: .
dockerfile: .docker/Dockerfile.roomote-controller
args:
- GH_TOKEN=${GH_TOKEN}
image: roo-code-cloud-roomote-controller
env_file:
- .env
- .env.keys
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /tmp/roomote/controller:/var/log/roomote
@ -116,11 +108,9 @@ services:
build:
context: .
dockerfile: .docker/Dockerfile.roomote-worker
args:
- GH_TOKEN=${GH_TOKEN}
image: roomote-worker
env_file:
- .env
- .env.keys
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /tmp/roomote/worker:/var/log/roomote

21
fly.roomote-api.toml Normal file
View file

@ -0,0 +1,21 @@
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
# Deploy with: `fly deploy --config fly.roomote-api.toml`
app = "roomote-api"
primary_region = "sjc" # See `fly platform regions`
[env]
APP_ENV = "production"
[build]
dockerfile = ".docker/Dockerfile.roomote-api"
ignorefile = ".dockerignore"
[http_service]
internal_port = 3001
force_https = true
min_machines_running = 0
[[vm]]
size = "performance-1x" # See `fly platform vm-sizes`

View file

@ -0,0 +1,16 @@
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
# Deploy with: `fly deploy --config fly.roomote-controller.toml`
app = "roomote-controller"
primary_region = "sjc" # See `fly platform regions`
[env]
APP_ENV = "production"
[build]
dockerfile = ".docker/Dockerfile.roomote-controller"
ignorefile = ".dockerignore"
[[vm]]
size = "performance-2x" # See `fly platform vm-sizes`

View file

@ -0,0 +1,21 @@
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
# Deploy with: `fly deploy --config fly.roomote-dashboard.toml`
app = "roomote-dashboard"
primary_region = "sjc" # See `fly platform regions`
[env]
APP_ENV = "production"
[build]
dockerfile = ".docker/Dockerfile.roomote-dashboard"
ignorefile = ".dockerignore"
[http_service]
internal_port = 3002
force_https = true
min_machines_running = 0
[[vm]]
size = "performance-1x" # See `fly platform vm-sizes`

View file

@ -8,15 +8,22 @@
"test": "turbo test --log-order grouped --output-logs new-only",
"build": "turbo build --log-order grouped --output-logs new-only",
"clean": "turbo clean --log-order grouped --output-logs new-only && rimraf .turbo",
"db:up": "docker compose up postgres clickhouse -d --wait && pnpm --filter @roo-code-cloud/db db:migrate && pnpm --filter @roo-code-cloud/db db:test:push --force",
"db:down": "docker compose down postgres clickhouse",
"db:up": "docker compose up postgres clickhouse redis -d --wait && pnpm --filter @roo-code-cloud/db db:migrate && pnpm --filter @roo-code-cloud/db db:test:push --force",
"db:down": "docker compose down postgres clickhouse redis",
"db:generate": "pnpm --filter @roo-code-cloud/db db:generate",
"db:migrate": "pnpm --filter @roo-code-cloud/db db:migrate",
"db:reset": "pnpm db:down && rimraf .docker/data .docker/logs && pnpm db:up",
"services:build": "docker compose build base && docker compose build roomote-api roomote-dashboard roomote-controller roomote-worker",
"services:up": "docker compose up redis roomote-dashboard roomote-api -d --wait",
"services:down": "docker compose down redis roomote-dashboard roomote-api",
"tunnel": "ngrok http 3001 --domain cte.ngrok.dev"
"services:build": "docker compose build --build-arg GH_TOKEN=$(npx dotenvx get GH_TOKEN -f .env.development) roomote-dashboard roomote-api roomote-controller roomote-worker",
"services:up": "docker compose up roomote-dashboard roomote-api -d --wait",
"services:down": "docker compose down roomote-dashboard roomote-api",
"tunnel": "ngrok http 3001 --domain cte.ngrok.dev",
"env:decrypt:test": "dotenvx decrypt -f .env.test --stdout",
"env:decrypt:development": "dotenvx decrypt -f .env.development --stdout",
"env:decrypt:preview": "dotenvx decrypt -f .env.preview --stdout",
"env:decrypt:production": "dotenvx decrypt -f .env.production --stdout",
"deploy:roomote-dashboard": "fly deploy --config fly.roomote-dashboard.toml",
"deploy:roomote-api": "fly deploy --config fly.roomote-api.toml",
"deploy:roomote-controller": "fly deploy --config fly.roomote-controller.toml"
},
"devDependencies": {
"@dotenvx/dotenvx": "^1.44.2",

View file

@ -1,3 +0,0 @@
DATABASE_URL=postgres://postgres:password@localhost:5432/development
CLICKHOUSE_URL=http://localhost:8123/default
CLICKHOUSE_PASSWORD=password

View file

@ -1,3 +0,0 @@
DATABASE_URL=postgres://postgres:password@localhost:5432/test
CLICKHOUSE_URL=http://localhost:8123/default
CLICKHOUSE_PASSWORD=password

View file

@ -1,10 +1,12 @@
import { defineConfig } from 'drizzle-kit';
import { Env } from '@roo-code-cloud/env';
export default defineConfig({
schema: './src/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
url: Env.DATABASE_URL,
},
});

View file

@ -19,13 +19,17 @@
"lint": "eslint src --ext=ts --max-warnings=0",
"check-types": "tsc --noEmit",
"clean": "rimraf .turbo",
"drizzle-kit": "tsx node_modules/drizzle-kit/bin.cjs",
"db:generate": "dotenvx run -f .env.development -- drizzle-kit generate",
"db:migrate": "dotenvx run -f .env.development -- drizzle-kit migrate",
"db:push": "dotenvx run -f .env.development -- drizzle-kit push",
"db:test:push": "dotenvx run -f .env.test -- drizzle-kit push"
"drizzle-kit:test": "NODE_ENV=test dotenvx run -f ../../.env.test -- tsx node_modules/drizzle-kit/bin.cjs",
"drizzle-kit:development": "NODE_ENV=development dotenvx run -f ../../.env.development -- tsx node_modules/drizzle-kit/bin.cjs",
"drizzle-kit:production": "NODE_ENV=production dotenvx run -f ../../.env.production -- tsx node_modules/drizzle-kit/bin.cjs",
"db:generate": "pnpm drizzle-kit:development generate",
"db:migrate": "pnpm drizzle-kit:development migrate",
"db:push": "pnpm drizzle-kit:development push",
"db:test:push": "pnpm drizzle-kit:test push",
"db:production:migrate": "pnpm drizzle-kit:production migrate"
},
"dependencies": {
"@roo-code-cloud/env": "workspace:^",
"@roo-code/types": "^1.29.0",
"drizzle-orm": "^0.44.2",
"postgres": "^3.4.7",

View file

@ -1,18 +1,19 @@
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import { Env } from '@roo-code-cloud/env';
import * as schema from './schema';
const client = postgres(process.env.DATABASE_URL!, { prepare: false });
const client = postgres(Env.DATABASE_URL, { prepare: false });
const db = drizzle({ client, schema });
if (process.env.NODE_ENV === 'test') {
if (
!process.env.DATABASE_URL!.includes('test') ||
!process.env.DATABASE_URL!.includes('localhost')
) {
throw new Error('DATABASE_URL is not a test database');
}
if (
Env.NODE_ENV === 'test' &&
(!Env.DATABASE_URL.includes('test') ||
!Env.DATABASE_URL.includes('localhost'))
) {
throw new Error('DATABASE_URL is not a test database');
}
const disconnect = async () => client.end();

View file

@ -122,6 +122,12 @@ export const createJobSchema = z.discriminatedUnion('type', [
commentUrl: z.string(),
}),
}),
z.object({
type: z.literal('test.prompt'),
payload: z.object({
text: z.string(),
}),
}),
]);
export type CreateJob = z.infer<typeof createJobSchema>;
@ -129,7 +135,6 @@ export type CreateJob = z.infer<typeof createJobSchema>;
export type JobTypes = {
[K in CreateJob['type']]: Extract<CreateJob, { type: K }>['payload'];
};
/**
* JobType, JobStatus, JobPayload, JobParams
*/

4
packages/env/eslint.config.mjs vendored Normal file
View file

@ -0,0 +1,4 @@
import { config } from "@roo-code-cloud/config-eslint/base"
/** @type {import("eslint").Linter.Config} */
export default [...config]

23
packages/env/package.json vendored Normal file
View file

@ -0,0 +1,23 @@
{
"name": "@roo-code-cloud/env",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"lint": "eslint src --ext=ts --max-warnings=0",
"check-types": "tsc --noEmit",
"test": "dotenvx run -f ../../.env.test -- vitest",
"clean": "rimraf .turbo"
},
"dependencies": {
"@roo-code/types": "^1.26.0",
"@t3-oss/env-nextjs": "^0.13.6",
"zod": "^3.25.41"
},
"devDependencies": {
"@roo-code-cloud/config-eslint": "workspace:^",
"@roo-code-cloud/config-typescript": "workspace:^",
"@types/node": "^24.0.3",
"vitest": "^3.2.4"
}
}

View file

@ -0,0 +1,9 @@
import { Env } from '../index';
describe('Env', () => {
it('should be defined', () => {
expect(Env.NODE_ENV).toBeDefined();
expect(Env.DATABASE_URL).toContain('postgres://');
expect(Env.DATABASE_URL).toContain('test');
});
});

25
packages/env/src/index.ts vendored Normal file
View file

@ -0,0 +1,25 @@
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const Env = createEnv({
shared: {
NODE_ENV: z.enum(['test', 'development', 'production']),
},
server: {
DATABASE_URL: z.string(),
REDIS_URL: z.string(),
CLICKHOUSE_URL: z.string().min(1),
CLICKHOUSE_PASSWORD: z.string().min(1),
CLERK_SECRET_KEY: z.string().min(1),
},
client: {},
// You need to destructure all the keys manually.
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
DATABASE_URL: process.env.DATABASE_URL,
REDIS_URL: process.env.REDIS_URL,
CLICKHOUSE_URL: process.env.CLICKHOUSE_URL,
CLICKHOUSE_PASSWORD: process.env.CLICKHOUSE_PASSWORD,
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
},
});

8
packages/env/tsconfig.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"extends": "@roo-code-cloud/config-typescript/base.json",
"compilerOptions": {
"types": ["vitest/globals"]
},
"include": ["src", "scripts"],
"exclude": ["node_modules"]
}

9
packages/env/vitest.config.ts vendored Normal file
View file

@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
watch: false,
reporters: ['dot'],
},
});

446
pnpm-lock.yaml generated
View file

@ -42,6 +42,9 @@ importers:
'@roo-code-cloud/db':
specifier: workspace:^
version: link:../../packages/db
'@roo-code-cloud/env':
specifier: workspace:^
version: link:../../packages/env
'@roo-code-cloud/ipc':
specifier: workspace:^
version: link:../../packages/ipc
@ -51,6 +54,9 @@ importers:
bullmq:
specifier: ^5.37.0
version: 5.54.3
cmd-ts:
specifier: ^0.13.0
version: 0.13.0
drizzle-orm:
specifier: ^0.44.2
version: 0.44.2(@electric-sql/pglite@0.3.0)(@libsql/client-wasm@0.15.5)(@opentelemetry/api@1.9.0)(@types/pg@8.15.2)(pg@8.15.6)(postgres@3.4.7)
@ -152,9 +158,6 @@ importers:
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.0.1(react-hook-form@7.56.4(react@19.1.0))
'@logtail/pino':
specifier: ^0.5.5
version: 0.5.5(pino@9.7.0)
'@radix-ui/react-accordion':
specifier: ^1.2.11
version: 1.2.11(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -200,15 +203,15 @@ importers:
'@roo-code-cloud/db':
specifier: workspace:^
version: link:../../packages/db
'@roo-code-cloud/env':
specifier: workspace:^
version: link:../../packages/env
'@roo-code/types':
specifier: ^1.29.0
version: 1.29.0
'@sentry/nextjs':
specifier: ^9.23.0
version: 9.23.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.3)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)
'@t3-oss/env-nextjs':
specifier: ^0.13.6
version: 0.13.6(arktype@2.1.20)(typescript@5.8.3)(zod@3.25.41)
'@tailwindcss/postcss':
specifier: ^4.1.8
version: 4.1.8
@ -321,9 +324,6 @@ importers:
'@eslint/js':
specifier: ^9.27.0
version: 9.27.0
'@next/bundle-analyzer':
specifier: ^15.3.3
version: 15.3.3
'@next/eslint-plugin-next':
specifier: ^15.3.3
version: 15.3.3
@ -434,6 +434,9 @@ importers:
packages/db:
dependencies:
'@roo-code-cloud/env':
specifier: workspace:^
version: link:../env
'@roo-code/types':
specifier: ^1.29.0
version: 1.29.0
@ -460,6 +463,31 @@ importers:
specifier: ^0.31.1
version: 0.31.1
packages/env:
dependencies:
'@roo-code/types':
specifier: ^1.26.0
version: 1.29.0
'@t3-oss/env-nextjs':
specifier: ^0.13.6
version: 0.13.6(arktype@2.1.20)(typescript@5.8.3)(zod@3.25.41)
zod:
specifier: ^3.25.41
version: 3.25.41
devDependencies:
'@roo-code-cloud/config-eslint':
specifier: workspace:^
version: link:../config-eslint
'@roo-code-cloud/config-typescript':
specifier: workspace:^
version: link:../config-typescript
'@types/node':
specifier: ^24.0.3
version: 24.0.3
vitest:
specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
packages/ipc:
dependencies:
'@roo-code/types':
@ -721,10 +749,6 @@ packages:
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
engines: {node: '>=18'}
'@discoveryjs/json-ext@0.5.7':
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'}
'@dotenvx/dotenvx@1.44.2':
resolution: {integrity: sha512-2C44+G2dch4cB6zw7+oGQ9VcFQuuVhc5xOzfVvY7iUEj2PRhiVMIB6SpNMK1V5TvpdqrAqCYFjclK18Mh9vwNQ==}
hasBin: true
@ -1290,27 +1314,6 @@ packages:
'@libsql/core@0.15.9':
resolution: {integrity: sha512-4OVdeAmuaCUq5hYT8NNn0nxlO9AcA/eTjXfUZ+QK8MT3Dz7Z76m73x7KxjU6I64WyXX98dauVH2b9XM+d84npw==}
'@logtail/core@0.5.4':
resolution: {integrity: sha512-UlbxvUg5MDroqk6gEhtfKuuBShrvD/VaXl3T3jGU1U6I19Y1OwfB7e681LvH7RbsCAnOJFziM/7rMQib/CJTdg==}
'@logtail/node@0.5.5':
resolution: {integrity: sha512-hSZDjl2wFRxe/+pxSlYNSpm0V3a2ZsiWqBifvtCBHTriwrNkd0LjQtedRlrjoBBumVxIMGdDOCPayAIbcux5YQ==}
'@logtail/pino@0.5.5':
resolution: {integrity: sha512-RNGWSWmqXHmchWm0MZS69JfnEAULck2uTrbh0/rZDBhnsZjsUTXmviDsA+zfjLr6Y702CmPLwx7ctlhHK7pbEw==}
peerDependencies:
pino: ^7.0.0 || ^8.0.0 || ^9.0.0
'@logtail/tools@0.5.4':
resolution: {integrity: sha512-S0PtfWOVVgvb36q8mLksMw910mO2TahDaqIYnUM/ZEYX+AwqKRGov4zt/X33zHRzCFVpSKoZFv7bFs8TBie/pQ==}
'@logtail/types@0.5.3':
resolution: {integrity: sha512-5uKf1xQ7wRqSW0lYIwDpXdXcFHxPKH5EM22MoEjwTpd8suymOYuEdx9qCCHCYey4uOhPq7x6OkZf86bTO83JsA==}
'@msgpack/msgpack@2.8.0':
resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==}
engines: {node: '>= 10'}
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
cpu: [arm64]
@ -1341,9 +1344,6 @@ packages:
cpu: [x64]
os: [win32]
'@next/bundle-analyzer@15.3.3':
resolution: {integrity: sha512-9gddnjACK6yOa5IkmeFyzcwZh2rscsb6ZspTd7tymPYKQM96fJuKjn9HrRtPNKiMm7ExKNadAJqREmHdBgHZ9A==}
'@next/env@15.3.3':
resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==}
@ -1615,9 +1615,6 @@ packages:
engines: {node: '>=18'}
hasBin: true
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
'@prisma/instrumentation@6.8.2':
resolution: {integrity: sha512-5NCTbZjw7a+WIZ/ey6G8SY+YKcyM2zBF0hOT1muvqC9TbVtTCr5Qv3RL/2iNDOzLUHEvo4I1uEfioyfuNOGK8Q==}
peerDependencies:
@ -2294,9 +2291,6 @@ packages:
'@roo-code/types@1.29.0':
resolution: {integrity: sha512-bovFOCVQYuWnV2Cvdcx5tWp8aUpF0tEoxN0rS05hzzwdDeDxB6F6q5orKsQqghTS0yqv5pYclQLZRyAV7oBsbw==}
'@roo-code/types@1.28.0':
resolution: {integrity: sha512-kJAWDaY80BLPRxVa1Nz3Fuj7tvnSVMsRlpgNq3YXvFyQwWsui7DZz/AgX5qm+CwcAHXHBPmCUn5oV5XnEAEykw==}
'@schummar/icu-type-parser@1.21.5':
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
@ -2762,9 +2756,6 @@ packages:
'@types/shimmer@1.2.0':
resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==}
'@types/stack-trace@0.0.33':
resolution: {integrity: sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==}
'@types/tedious@4.0.14':
resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==}
@ -2923,10 +2914,6 @@ packages:
'@xtuc/long@4.2.2':
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
@ -2941,15 +2928,6 @@ packages:
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn-walk@8.3.4:
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
engines: {node: '>=0.4.0'}
acorn@8.14.1:
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
@ -3084,9 +3062,6 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@ -3116,9 +3091,6 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
bullmq@5.54.3:
resolution: {integrity: sha512-MVK2pOkB3hvrIcubwI8dS4qWHJLNKakKPpgRBTw91sIpPZArmvZ4t2hvryyEaJXJbAS/JHd6pKYOUd+RGRkWQQ==}
@ -3236,6 +3208,9 @@ packages:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
cmd-ts@0.13.0:
resolution: {integrity: sha512-nsnxf6wNIM/JAS7T/x/1JmbEsjH0a8tezXqqpaL0O6+eV0/aDEnRxwjxpu0VzDdRcaC1ixGSbRlUuf/IU59I4g==}
cmdk@1.1.1:
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
peerDependencies:
@ -3273,10 +3248,6 @@ packages:
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
commondir@1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
@ -3410,9 +3381,6 @@ packages:
dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
debounce@1.2.1:
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
@ -3476,6 +3444,9 @@ packages:
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
@ -3604,9 +3575,6 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@ -3822,10 +3790,6 @@ packages:
resolution: {integrity: sha512-2QiHxshejKgJrYMzSI9MEHrvhmzxBL+eLyiM5IiyjDBySkgwS2+tdtnO3gbx8pEisu/yOFCIhfCb63gCEu0yBQ==}
engines: {node: '>=13.0.0'}
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
@ -4080,10 +4044,6 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
gzip-size@6.0.0:
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
engines: {node: '>=10'}
hard-rejection@2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'}
@ -4138,9 +4098,6 @@ packages:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
@ -4180,9 +4137,6 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@ -4344,10 +4298,6 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
is-plain-object@5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
@ -4669,9 +4619,6 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
loupe@3.1.3:
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
loupe@3.1.4:
resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==}
@ -4916,10 +4863,6 @@ packages:
module-details-from-path@1.0.4:
resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==}
mrmime@2.0.1:
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
engines: {node: '>=10'}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@ -5105,10 +5048,6 @@ packages:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
opener@1.5.2:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@ -5273,9 +5212,6 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
pino-abstract-transport@1.2.0:
resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==}
pino-abstract-transport@2.0.0:
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
@ -5388,10 +5324,6 @@ packages:
process-warning@5.0.0:
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
progress@2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
@ -5543,10 +5475,6 @@ packages:
readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
readable-stream@4.7.0:
resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@ -5728,10 +5656,6 @@ packages:
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
engines: {node: '>= 18'}
serialize-error@8.1.0:
resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==}
engines: {node: '>=10'}
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
@ -5805,10 +5729,6 @@ packages:
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
sirv@2.0.4:
resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
engines: {node: '>= 10'}
slice-ansi@5.0.0:
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
engines: {node: '>=12'}
@ -5870,9 +5790,6 @@ packages:
stack-generator@2.0.10:
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@ -5952,9 +5869,6 @@ packages:
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
@ -6137,10 +6051,6 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
tough-cookie@5.1.2:
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
engines: {node: '>=16'}
@ -6231,10 +6141,6 @@ packages:
resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==}
engines: {node: '>=10'}
type-fest@0.20.2:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'}
type-fest@0.6.0:
resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==}
engines: {node: '>=8'}
@ -6497,11 +6403,6 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
webpack-bundle-analyzer@4.10.1:
resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
engines: {node: '>= 10.13.0'}
hasBin: true
webpack-sources@3.3.0:
resolution: {integrity: sha512-77R0RDmJfj9dyv5p3bM5pOHa+X8/ZkO9c7kpDstigkC4nIDobadsfSGCwB4bKhMVxqAok8tajaoR8rirM7+VFQ==}
engines: {node: '>=10.13.0'}
@ -6588,18 +6489,6 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@7.5.10:
resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
engines: {node: '>=8.3.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
ws@8.18.2:
resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==}
engines: {node: '>=10.0.0'}
@ -6975,8 +6864,6 @@ snapshots:
'@csstools/css-tokenizer@3.0.4': {}
'@discoveryjs/json-ext@0.5.7': {}
'@dotenvx/dotenvx@1.44.2':
dependencies:
commander: 11.1.0
@ -7394,36 +7281,6 @@ snapshots:
js-base64: 3.7.7
optional: true
'@logtail/core@0.5.4':
dependencies:
'@logtail/tools': 0.5.4
'@logtail/types': 0.5.3
serialize-error: 8.1.0
'@logtail/node@0.5.5':
dependencies:
'@logtail/core': 0.5.4
'@logtail/types': 0.5.3
'@msgpack/msgpack': 2.8.0
'@types/stack-trace': 0.0.33
minimatch: 9.0.5
stack-trace: 0.0.10
'@logtail/pino@0.5.5(pino@9.7.0)':
dependencies:
'@logtail/node': 0.5.5
'@logtail/types': 0.5.3
pino: 9.7.0
pino-abstract-transport: 1.2.0
'@logtail/tools@0.5.4':
dependencies:
'@logtail/types': 0.5.3
'@logtail/types@0.5.3': {}
'@msgpack/msgpack@2.8.0': {}
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
optional: true
@ -7442,13 +7299,6 @@ snapshots:
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
optional: true
'@next/bundle-analyzer@15.3.3':
dependencies:
webpack-bundle-analyzer: 4.10.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@next/env@15.3.3': {}
'@next/eslint-plugin-next@15.3.3':
@ -7746,8 +7596,6 @@ snapshots:
playwright: 1.52.0
optional: true
'@polka/url@1.0.0-next.29': {}
'@prisma/instrumentation@6.8.2(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
@ -8359,8 +8207,6 @@ snapshots:
'@roo-code/types@1.29.0': {}
'@roo-code/types@1.28.0': {}
'@schummar/icu-type-parser@1.21.5': {}
'@sec-ant/readable-stream@0.4.1': {}
@ -8850,7 +8696,7 @@ snapshots:
'@types/pg@8.15.2':
dependencies:
'@types/node': 22.15.32
pg-protocol: 1.10.0
pg-protocol: 1.10.2
pg-types: 4.0.2
'@types/pg@8.6.1':
@ -8884,8 +8730,6 @@ snapshots:
'@types/shimmer@1.2.0': {}
'@types/stack-trace@0.0.33': {}
'@types/tedious@4.0.14':
dependencies:
'@types/node': 22.15.32
@ -9032,6 +8876,14 @@ snapshots:
optionalDependencies:
vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
'@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
'@vitest/pretty-format@3.2.4':
dependencies:
tinyrainbow: 2.0.0
@ -9140,10 +8992,6 @@ snapshots:
'@xtuc/long@4.2.2': {}
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
accepts@2.0.0:
dependencies:
mime-types: 3.0.1
@ -9157,12 +9005,6 @@ snapshots:
dependencies:
acorn: 8.15.0
acorn-walk@8.3.4:
dependencies:
acorn: 8.15.0
acorn@8.14.1: {}
acorn@8.15.0: {}
agent-base@6.0.2:
@ -9308,8 +9150,6 @@ snapshots:
balanced-match@1.0.2: {}
base64-js@1.5.1: {}
binary-extensions@2.3.0: {}
body-parser@2.2.0:
@ -9350,11 +9190,6 @@ snapshots:
buffer-from@1.1.2: {}
buffer@6.0.3:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
bullmq@5.54.3:
dependencies:
cron-parser: 4.9.0
@ -9411,7 +9246,7 @@ snapshots:
assertion-error: 2.0.1
check-error: 2.1.1
deep-eql: 5.0.2
loupe: 3.1.3
loupe: 3.1.4
pathval: 2.0.0
chalk@3.0.0:
@ -9479,6 +9314,15 @@ snapshots:
cluster-key-slot@1.1.2: {}
cmd-ts@0.13.0:
dependencies:
chalk: 4.1.2
debug: 4.4.1
didyoumean: 1.2.2
strip-ansi: 6.0.1
transitivePeerDependencies:
- supports-color
cmdk@1.1.1(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.6)(react@19.1.0)
@ -9519,8 +9363,6 @@ snapshots:
commander@2.20.3: {}
commander@7.2.0: {}
commondir@1.0.1: {}
concat-map@0.0.1: {}
@ -9650,8 +9492,6 @@ snapshots:
dateformat@4.6.3: {}
debounce@1.2.1: {}
debug@4.4.1:
dependencies:
ms: 2.1.3
@ -9701,6 +9541,8 @@ snapshots:
dependencies:
dequal: 2.0.3
didyoumean@1.2.2: {}
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
@ -9749,8 +9591,6 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
duplexer@0.1.2: {}
eastasianwidth@0.2.0: {}
easy-stack@1.0.1: {}
@ -10101,8 +9941,6 @@ snapshots:
copyfiles: 2.4.1
strong-type: 0.1.6
event-target-shim@5.0.1: {}
eventemitter3@4.0.7: {}
eventemitter3@5.0.1: {}
@ -10397,10 +10235,6 @@ snapshots:
graphemer@1.4.0: {}
gzip-size@6.0.0:
dependencies:
duplexer: 0.1.2
hard-rejection@2.1.0: {}
has-bigints@1.1.0: {}
@ -10465,8 +10299,6 @@ snapshots:
dependencies:
whatwg-encoding: 3.1.1
html-escaper@2.0.2: {}
html-url-attributes@3.0.1: {}
http-errors@2.0.0:
@ -10510,8 +10342,6 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
ieee754@1.2.1: {}
ignore@5.3.2: {}
ignore@7.0.4: {}
@ -10676,8 +10506,6 @@ snapshots:
is-plain-obj@4.1.0: {}
is-plain-object@5.0.0: {}
is-potential-custom-element-name@1.0.1: {}
is-promise@4.0.0: {}
@ -10994,8 +10822,6 @@ snapshots:
dependencies:
js-tokens: 4.0.0
loupe@3.1.3: {}
loupe@3.1.4: {}
lower-case@2.0.2:
@ -11348,8 +11174,6 @@ snapshots:
module-details-from-path@1.0.4: {}
mrmime@2.0.1: {}
ms@2.1.3: {}
msgpackr-extract@3.0.3:
@ -11556,8 +11380,6 @@ snapshots:
dependencies:
mimic-function: 5.0.1
opener@1.5.2: {}
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@ -11673,8 +11495,7 @@ snapshots:
pg-protocol@1.10.0: {}
pg-protocol@1.10.2:
optional: true
pg-protocol@1.10.2: {}
pg-types@2.2.0:
dependencies:
@ -11718,11 +11539,6 @@ snapshots:
pidtree@0.6.0: {}
pino-abstract-transport@1.2.0:
dependencies:
readable-stream: 4.7.0
split2: 4.2.0
pino-abstract-transport@2.0.0:
dependencies:
split2: 4.2.0
@ -11834,8 +11650,6 @@ snapshots:
process-warning@5.0.0: {}
process@0.11.10: {}
progress@2.0.3: {}
prop-types@15.8.1:
@ -12018,14 +11832,6 @@ snapshots:
string_decoder: 1.1.1
util-deprecate: 1.0.2
readable-stream@4.7.0:
dependencies:
abort-controller: 3.0.0
buffer: 6.0.3
events: 3.3.0
process: 0.11.10
string_decoder: 1.3.0
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
@ -12288,10 +12094,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
serialize-error@8.1.0:
dependencies:
type-fest: 0.20.2
serialize-javascript@6.0.2:
dependencies:
randombytes: 2.1.0
@ -12409,12 +12211,6 @@ snapshots:
is-arrayish: 0.3.2
optional: true
sirv@2.0.4:
dependencies:
'@polka/url': 1.0.0-next.29
mrmime: 2.0.1
totalist: 3.0.1
slice-ansi@5.0.0:
dependencies:
ansi-styles: 6.2.1
@ -12478,8 +12274,6 @@ snapshots:
dependencies:
stackframe: 1.3.4
stack-trace@0.0.10: {}
stackback@0.0.2: {}
stackframe@1.3.4: {}
@ -12584,10 +12378,6 @@ snapshots:
dependencies:
safe-buffer: 5.1.2
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
stringify-entities@4.0.4:
dependencies:
character-entities-html4: 2.1.0
@ -12737,8 +12527,6 @@ snapshots:
toidentifier@1.0.1: {}
totalist@3.0.1: {}
tough-cookie@5.1.2:
dependencies:
tldts: 6.1.86
@ -12807,8 +12595,6 @@ snapshots:
type-fest@0.18.1: {}
type-fest@0.20.2: {}
type-fest@0.6.0: {}
type-fest@0.7.1: {}
@ -13073,6 +12859,27 @@ snapshots:
- tsx
- yaml
vite-node@3.2.4(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies:
cac: 6.7.14
debug: 4.4.1
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.27)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)):
dependencies:
debug: 4.4.1
@ -13135,6 +12942,23 @@ snapshots:
tsx: 4.20.3
yaml: 2.8.0
vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies:
esbuild: 0.25.5
fdir: 6.4.5(picomatch@4.0.2)
picomatch: 4.0.2
postcss: 8.5.4
rollup: 4.41.1
tinyglobby: 0.2.14
optionalDependencies:
'@types/node': 24.0.3
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.30.1
terser: 5.43.1
tsx: 4.20.3
yaml: 2.8.0
vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies:
'@types/chai': 5.2.2
@ -13264,6 +13088,49 @@ snapshots:
- tsx
- yaml
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
chai: 5.2.0
debug: 4.4.1
expect-type: 1.2.1
magic-string: 0.30.17
pathe: 2.0.3
picomatch: 4.0.2
std-env: 3.9.0
tinybench: 2.9.0
tinyexec: 0.3.2
tinyglobby: 0.2.14
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
vite-node: 3.2.4(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 24.0.3
jsdom: 26.1.0
transitivePeerDependencies:
- jiti
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
@ -13279,25 +13146,6 @@ snapshots:
webidl-conversions@7.0.0: {}
webpack-bundle-analyzer@4.10.1:
dependencies:
'@discoveryjs/json-ext': 0.5.7
acorn: 8.14.1
acorn-walk: 8.3.4
commander: 7.2.0
debounce: 1.2.1
escape-string-regexp: 4.0.0
gzip-size: 6.0.0
html-escaper: 2.0.2
is-plain-object: 5.0.0
opener: 1.5.2
picocolors: 1.1.1
sirv: 2.0.4
ws: 7.5.10
transitivePeerDependencies:
- bufferutil
- utf-8-validate
webpack-sources@3.3.0: {}
webpack-sources@3.3.3: {}
@ -13427,8 +13275,6 @@ snapshots:
wrappy@1.0.2: {}
ws@7.5.10: {}
ws@8.18.2: {}
xml-name-validator@5.0.0: {}

View file

@ -1,87 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// npx tsx scripts/find-missing-translations.ts
import fs from 'fs';
import path from 'path';
function extractKeys(obj: any, prefix = ''): string[] {
let keys: string[] = [];
for (const key in obj) {
const currentKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null) {
keys = [...keys, ...extractKeys(obj[key], currentKey)];
} else {
keys.push(currentKey);
}
}
return keys;
}
async function main() {
const rootDir = path.resolve(__dirname, '..');
const localesDir = path.join('src', 'i18n', 'locales');
const files = fs
.readdirSync(path.join(rootDir, localesDir))
.filter((file) => file.endsWith('.json'))
.map((file) => path.join(rootDir, localesDir, file));
const localeKeys: { [locale: string]: Set<string> } = {};
const allKeys = new Set<string>();
for (const file of files) {
const keys = extractKeys(JSON.parse(fs.readFileSync(file, 'utf8')));
const relativePath = path.join(localesDir, path.basename(file));
localeKeys[relativePath] = new Set(keys);
for (const key of keys) {
allKeys.add(key);
}
}
Object.entries(localeKeys).forEach(([locale, keys]) => {
const missingKeys = Array.from(allKeys).filter((key) => !keys.has(key));
if (missingKeys.length > 0) {
console.log(
`\n[${locale}] Missing ${missingKeys.length} translation(s):`,
);
const groupedMissing = new Map<string, string[]>();
for (const key of missingKeys) {
const parts = key.split('.');
if (parts.length >= 2) {
const namespace = parts[0];
const subKey = parts.slice(1).join('.');
if (namespace && subKey) {
if (!groupedMissing.has(namespace)) {
groupedMissing.set(namespace, []);
}
const group = groupedMissing.get(namespace);
if (group) {
group.push(subKey);
}
}
}
}
for (const [namespace, keys] of groupedMissing.entries()) {
console.log(`${namespace}`);
keys.forEach((key) => console.log(`\t${key}`));
}
} else {
console.log(`\n[${locale}] No missing translations`);
}
});
}
main();

View file

@ -1,221 +0,0 @@
// npx dotenvx run -f .env.development -f .env.local -- tsx scripts/test-agent-endpoint.ts [orgId] [agent] [baseUrl]
import postgres from 'postgres';
import { drizzle } from 'drizzle-orm/postgres-js';
import { eq } from 'drizzle-orm';
import crypto from 'crypto';
import { agents, users } from '@/db/schema';
import { getAgentToken } from '@/lib/server/agent-auth';
const client = postgres(process.env.DATABASE_URL!);
const db = drizzle(client);
interface TestConfig {
orgId: string;
agentName: string;
baseUrl: string;
}
async function createTestAgent(config: TestConfig): Promise<string> {
console.log('🤖 Creating test agent...');
try {
const existingAgent = await db
.select()
.from(agents)
.where(eq(agents.displayName, config.agentName))
.limit(1);
if (existingAgent.length > 0) {
console.log(`✅ Using existing agent: ${existingAgent[0]!.id}`);
return existingAgent[0]!.id;
}
const existingUsers = await db.select().from(users).limit(1);
let createdByUserId: string;
if (existingUsers.length > 0) {
createdByUserId = existingUsers[0]!.id;
console.log(`📋 Using existing user as creator: ${createdByUserId}`);
} else {
const testUserId = `user_${crypto.randomBytes(12).toString('hex')}`;
const testUserData = {
id: testUserId,
orgId: config.orgId,
orgRole: 'admin',
name: 'Test User',
email: 'test@example.com',
imageUrl: 'https://example.com/avatar.png',
entity: {},
};
console.log(`👤 Creating test user: ${testUserId}`);
const [newUser] = await db.insert(users).values(testUserData).returning();
createdByUserId = newUser!.id;
}
const agentId = `agent_${crypto.randomBytes(12).toString('hex')}`;
const agentData = {
id: agentId,
orgId: config.orgId,
displayName: config.agentName,
description: 'Test agent created by script',
createdByUserId: createdByUserId,
};
const [newAgent] = await db.insert(agents).values(agentData).returning();
if (!newAgent) {
throw new Error('Failed to create agent in database');
}
console.log(`✅ Created agent: ${newAgent.id}`);
return newAgent.id;
} catch (error) {
console.error('❌ Failed to create agent:', error);
throw error;
}
}
async function testEndpoint(token: string, baseUrl: string) {
console.log('🌐 Testing endpoint...');
const url = `${baseUrl}/api/me`;
console.log(`📡 Request URL: ${url}`);
try {
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'TestScript/1.0',
},
});
console.log(
`📡 Response status: ${response.status} ${response.statusText}`,
);
console.log(
`📡 Response content-type: ${response.headers.get('content-type')}`,
);
const contentType = response.headers.get('content-type');
const isJson = contentType && contentType.includes('application/json');
if (isJson) {
const responseData = await response.json();
if (response.ok) {
console.log('✅ Success! Response data:');
console.log(JSON.stringify(responseData, null, 2));
} else {
console.log('❌ Error response:');
console.log(JSON.stringify(responseData, null, 2));
}
} else {
const responseText = await response.text();
if (response.status === 404) {
console.log('❌ Endpoint not found (404)');
console.log('💡 This might mean:');
console.log(' - The server is not running');
console.log(' - The endpoint does not exist');
console.log(' - The API route is not implemented');
console.log(
`📄 Response preview: ${responseText.substring(0, 200)}...`,
);
} else {
console.log(`❌ Non-JSON response (${response.status})`);
console.log(`📄 Response: ${responseText.substring(0, 500)}...`);
}
}
} catch (error) {
console.error('❌ Failed to call endpoint:', error);
if (error instanceof Error && error.message.includes('ECONNREFUSED')) {
console.log(
'💡 Connection refused - is the server running on http://localhost:3000?',
);
}
throw error;
}
}
async function cleanup(agentId: string): Promise<void> {
console.log('🧹 Cleaning up test agent...');
try {
await db.delete(agents).where(eq(agents.id, agentId));
console.log('✅ Test agent cleaned up');
} catch (error) {
console.error('❌ Failed to cleanup agent:', error);
}
}
async function main() {
const config: TestConfig = {
orgId: process.argv[2] || 'org_2wbhchVXZMQl8OS1yt0mrDazCpW',
agentName: process.argv[3] || 'TestAgent',
baseUrl: process.argv[4] || 'http://localhost:3000',
};
console.log('🚀 Starting agent endpoint test...');
console.log(` Org ID: ${config.orgId}`);
console.log(` Agent Name: ${config.agentName}`);
console.log(` Base URL: ${config.baseUrl}`);
console.log('');
let agentId: string | null = null;
try {
agentId = await createTestAgent(config);
const token = await getAgentToken(agentId, config.orgId);
await testEndpoint(token, config.baseUrl);
console.log('');
console.log('🎉 Test completed successfully!');
} catch (error) {
console.error('');
console.error('💥 Test failed:', error);
process.exit(1);
} finally {
if (agentId) {
await cleanup(agentId);
}
await client.end();
}
}
// Print usage if help requested
if (process.argv.includes('--help') || process.argv.includes('-h')) {
console.log(`
Usage: tsx scripts/test-agent-endpoint.ts [orgId] [agentName] [baseUrl]
Arguments:
orgId - Organization ID to test with (default: org_default_test)
agentName - Name for the test agent (default: TestAgent)
baseUrl - Base URL of the API (default: http://localhost:3000)
Examples:
tsx scripts/test-agent-endpoint.ts
tsx scripts/test-agent-endpoint.ts org_123 MyTestAgent http://localhost:3000
tsx scripts/test-agent-endpoint.ts org_456 ProductionAgent https://api.example.com
Environment Variables Required:
DATABASE_URL - PostgreSQL connection string
CLERK_SECRET_KEY - Clerk secret key for JWT signing
`);
process.exit(0);
}
main().catch((error) => {
console.error('Unhandled error:', error);
process.exit(1);
});

View file

@ -1,5 +1,11 @@
{
"$schema": "https://turbo.build/schema.json",
"globalPassThroughEnv": [
"DOTENV_PRIVATE_KEY_TEST",
"DOTENV_PRIVATE_KEY_DEVELOPMENT",
"DOTENV_PRIVATE_KEY_PREVIEW",
"DOTENV_PRIVATE_KEY_PRODUCTION"
],
"tasks": {
"lint": {},
"check-types": {},