diff --git a/check_coolify_logs.js b/check_coolify_logs.js deleted file mode 100644 index f0df1c7..0000000 --- a/check_coolify_logs.js +++ /dev/null @@ -1,36 +0,0 @@ -const DFS_LOGIN = process.env.COOLIFY_API_TOKEN; - -async function checkDeployment() { - const url = `${process.env.COOLIFY_URL}/api/v1/deployments?resource_uuid=y4cscsc8s08c8808go0448s0&per_page=1`; - console.log(`Pinging Coolify API at ${url}...`); - - const response = await fetch(url, { - headers: { - "Authorization": `Bearer ${DFS_LOGIN}` - } - }); - - const data = await response.json(); - if (data && data.length > 0) { - const deploy = data[0]; - console.log(`\nDeployment UUID: ${deploy.deployment_uuid}`); - console.log(`Status: ${deploy.status}`); - console.log(`Created At: ${deploy.created_at}`); - - // Parse the logs array to see what it's currently doing - try { - const logs = JSON.parse(deploy.logs); - if (logs && logs.length > 0) { - console.log("\nLast 5 log lines from the build container:"); - logs.slice(-5).forEach(log => { - console.log(`[${log.timestamp}] ${log.type}: ${log.output.substring(0, 100)}`); - }); - } - } catch(e) { - console.log("Could not parse logs array."); - } - } else { - console.log("No deployments found."); - } -} -checkDeployment().catch(console.error); diff --git a/deploy_error.txt b/deploy_error.txt deleted file mode 100644 index 2e38d90..0000000 --- a/deploy_error.txt +++ /dev/null @@ -1 +0,0 @@ -[{"command":null,"output":"Docker 27.0.3 with BuildKit and Buildx detected on deployment server (localhost).","type":"stdout","timestamp":"2026-05-16T21:57:35.692039Z","hidden":false,"batch":1},{"command":null,"output":"Starting deployment of https:\/\/git.vibnai.com\/mark\/vibn-frontend.git:main to localhost.","type":"stdout","timestamp":"2026-05-16T21:57:35.738938Z","hidden":false,"batch":1,"order":2},{"command":null,"output":"Preparing container with helper image: ghcr.io\/coollabsio\/coolify-helper:1.0.13","type":"stdout","timestamp":"2026-05-16T21:57:36.487209Z","hidden":false,"batch":1,"order":3},{"command":"docker stop -t 30 iy53yujv0jkvol25maud6x85","output":"Error response from daemon: No such container: iy53yujv0jkvol25maud6x85","type":"stderr","timestamp":"2026-05-16T21:57:36.770916Z","hidden":true,"batch":1,"order":4},{"command":"docker run -d --network 'coolify' --name iy53yujv0jkvol25maud6x85 --rm -v \/var\/run\/docker.sock:\/var\/run\/docker.sock ghcr.io\/coollabsio\/coolify-helper:1.0.13","output":"b2d82459da497b707bc188557355be86e6ad1c39da582616da9d378ef6628ff0","type":"stdout","timestamp":"2026-05-16T21:57:37.284114Z","hidden":true,"batch":2,"order":5},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p 22 -o Port=22 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git ls-remote https:\/\/git.vibnai.com\/mark\/vibn-frontend.git refs\/heads\/main'","output":"814815af82be69ad6cf3c0e14c76e6aef316631e\trefs\/heads\/main","type":"stdout","timestamp":"2026-05-16T21:57:40.117959Z","hidden":true,"batch":3,"order":6},{"command":null,"output":"----------------------------------------","type":"stdout","timestamp":"2026-05-16T21:57:40.176981Z","hidden":false,"batch":1,"order":7},{"command":null,"output":"Importing https:\/\/git.vibnai.com\/mark\/vibn-frontend.git:main (commit sha 814815af82be69ad6cf3c0e14c76e6aef316631e) to \/artifacts\/iy53yujv0jkvol25maud6x85.","type":"stdout","timestamp":"2026-05-16T21:57:40.217750Z","hidden":false,"batch":1,"order":8},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Cloning into '\/artifacts\/iy53yujv0jkvol25maud6x85'...","type":"stderr","timestamp":"2026-05-16T21:57:41.048081Z","hidden":true,"batch":4,"order":9},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Updating files: 85% (1506\/1754)","type":"stderr","timestamp":"2026-05-16T21:57:46.420662Z","hidden":true,"batch":4,"order":10},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Updating files: 86% (1509\/1754)","type":"stderr","timestamp":"2026-05-16T21:57:46.445366Z","hidden":true,"batch":4,"order":11},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Updating files: 87% (1526\/1754)","type":"stderr","timestamp":"2026-05-16T21:57:46.456866Z","hidden":true,"batch":4,"order":12},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Updating files: 88% (1544\/1754)\rUpdating files: 89% (1562\/1754)\rUpdating files: 90% (1579\/1754)\rUpdating files: 91% (1597\/1754)","type":"stderr","timestamp":"2026-05-16T21:57:46.492382Z","hidden":true,"batch":4,"order":13},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Updating files: 92% (1614\/1754)\rUpdating files: 93% (1632\/1754)\rUpdating files: 94% (1649\/1754)","type":"stderr","timestamp":"2026-05-16T21:57:46.497986Z","hidden":true,"batch":4,"order":14},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Updating files: 95% (1667\/1754)","type":"stderr","timestamp":"2026-05-16T21:57:46.506559Z","hidden":true,"batch":4,"order":15},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Updating files: 96% (1684\/1754)","type":"stderr","timestamp":"2026-05-16T21:57:46.522218Z","hidden":true,"batch":4,"order":16},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Updating files: 97% (1702\/1754)\rUpdating files: 98% (1719\/1754)","type":"stderr","timestamp":"2026-05-16T21:57:46.546454Z","hidden":true,"batch":4,"order":17},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"Updating files: 99% (1737\/1754)\rUpdating files: 100% (1754\/1754)\rUpdating files: 100% (1754\/1754), done.","type":"stderr","timestamp":"2026-05-16T21:57:46.559301Z","hidden":true,"batch":4,"order":18},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'git clone --depth=1 --recurse-submodules --shallow-submodules -b '\\''main'\\'' '\\''https:\/\/git.vibnai.com\/mark\/vibn-frontend.git'\\'' '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git fetch --depth=1 origin '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' && git -c advice.detachedHead=false checkout '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' >\/dev\/null 2>&1 && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && if [ -f .gitmodules ]; then sed -i \"s#git@\\(.*\\):#https:\/\/\\1\/#g\" '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\''\/.gitmodules || true && git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git submodule update --init --recursive --depth=1; fi && cd '\\''\/artifacts\/iy53yujv0jkvol25maud6x85'\\'' && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=\/dev\/null\" git lfs pull'","output":"From https:\/\/git.vibnai.com\/mark\/vibn-frontend\n * branch 814815af82be69ad6cf3c0e14c76e6aef316631e -> FETCH_HEAD","type":"stderr","timestamp":"2026-05-16T21:57:46.811295Z","hidden":true,"batch":4,"order":19},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'cd \/artifacts\/iy53yujv0jkvol25maud6x85 && git log -1 '\\''814815af82be69ad6cf3c0e14c76e6aef316631e'\\'' --pretty=%B'","output":"fix(deploy): install openssl in base docker image to fix prisma client initialization error during build phase","type":"stdout","timestamp":"2026-05-16T21:57:48.606485Z","hidden":true,"batch":6,"order":20},{"command":null,"output":"Image not found (y4cscsc8s08c8808go0448s0:814815af82be69ad6cf3c0e14c76e6aef316631e). Building new image.","type":"stdout","timestamp":"2026-05-16T21:57:48.851625Z","hidden":false,"batch":1,"order":21},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'cat \/artifacts\/iy53yujv0jkvol25maud6x85\/Dockerfile'","output":"cat: can't open '\/artifacts\/iy53yujv0jkvol25maud6x85\/Dockerfile': No such file or directory","type":"stderr","timestamp":"2026-05-16T21:57:50.719280Z","hidden":true,"batch":10,"order":22},{"command":null,"output":"Creating build-time .env file in \/artifacts (outside Docker context).","type":"stdout","timestamp":"2026-05-16T21:57:52.183793Z","hidden":true,"batch":1,"order":23},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'cat \/artifacts\/iy53yujv0jkvol25maud6x85\/Dockerfile'","output":"cat: can't open '\/artifacts\/iy53yujv0jkvol25maud6x85\/Dockerfile': No such file or directory","type":"stderr","timestamp":"2026-05-16T21:57:53.768951Z","hidden":true,"batch":13,"order":24},{"command":null,"output":"----------------------------------------","type":"stdout","timestamp":"2026-05-16T21:57:53.784223Z","hidden":false,"batch":1,"order":25},{"command":null,"output":"Building docker image started.","type":"stdout","timestamp":"2026-05-16T21:57:53.798083Z","hidden":false,"batch":1,"order":26},{"command":null,"output":"To check the current progress, click on Show Debug Logs.","type":"stdout","timestamp":"2026-05-16T21:57:53.812181Z","hidden":false,"batch":1,"order":27},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'cat \/artifacts\/build.sh'","output":"cd \/artifacts\/iy53yujv0jkvol25maud6x85 && set -a && source \/artifacts\/build-time.env && set +a && DOCKER_BUILDKIT=1 docker build --add-host coolify:10.0.1.11 --add-host coolify-db:10.0.1.3 --add-host coolify-realtime:10.0.1.9 --add-host coolify-redis:10.0.1.2 --add-host kggs4ogckc0w8ggwkkk88kck:10.0.1.13 --add-host kggs4ogckc0w8ggwkkk88kck-proxy:10.0.1.6 --add-host mh20hmj0h7pg9ftt0upo8s8p:10.0.1.24 --add-host p4dpjwv9p188h3y21c4xgiwy:10.0.1.21 --add-host q8i3lfauirs97awl4pieqbme:10.0.1.16 --add-host qckwo4g8gs8kw08gkgc0ss0g:10.0.1.8 --add-host qckwo4g8gs8kw08gkgc0ss0g-proxy:10.0.1.20 --add-host rlwvhmh25r7n2g2pbzygjlms:10.0.1.30 --add-host vibn-dev-dm3hqkyjknucuehfmqb75627:10.0.1.14 --add-host vibn-dev-lbhz4nd7wllowjlnwm1tmhu3:10.0.1.10 --add-host yoscw0og00gkgcsgswoskc8c:10.0.1.17 --add-host yoscw0og00gkgcsgswoskc8c-proxy:10.0.1.7 --add-host zlxavhunxe6vit057ntt7imx:10.0.1.27 --network host -f \/artifacts\/iy53yujv0jkvol25maud6x85\/Dockerfile --progress plain -t y4cscsc8s08c8808go0448s0:814815af82be69ad6cf3c0e14c76e6aef316631e --build-arg COOLIFY_URL --build-arg COOLIFY_BRANCH --build-arg COOLIFY_RESOURCE_UUID --build-arg OPENSRS_API_KEY_LIVE --build-arg GITEA_API_TOKEN --build-arg NEXTAUTH_URL --build-arg COOLIFY_SERVER_UUID --build-arg GITEA_WEBHOOK_SECRET --build-arg AGENT_RUNNER_SECRET --build-arg GEMINI_MODEL --build-arg NEXTAUTH_SECRET --build-arg NEXT_PUBLIC_APP_URL --build-arg GOOGLE_SERVICE_ACCOUNT_KEY_B64 --build-arg GITEA_API_URL --build-arg GITEA_ADMIN_USER --build-arg GOOGLE_CLIENT_SECRET --build-arg AGENT_RUNNER_URL --build-arg ADMIN_MIGRATE_SECRET --build-arg GOOGLE_API_KEY --build-arg COOLIFY_API_TOKEN --build-arg OPENSRS_RESELLER_USERNAME --build-arg OPENSRS_API_KEY_TEST --build-arg OPENSRS_PORT --build-arg OPENSRS_MODE --build-arg OPENSRS_CURRENCY --build-arg GITHUB_CLIENT_ID --build-arg GITHUB_CLIENT_SECRET --build-arg COOLIFY_SSH_HOST --build-arg COOLIFY_SSH_PORT --build-arg COOLIFY_SSH_USER --build-arg COOLIFY_SSH_PRIVATE_KEY_B64 --build-arg GCP_PROJECT_ID --build-arg GOOGLE_CLIENT_ID --build-arg OPENSRS_HOST_LIVE --build-arg SENTRY_AUTH_TOKEN --build-arg GITEA_USERNAME --build-arg NEXT_PUBLIC_SENTRY_DSN --build-arg INFRA_HEALTH_SECRET --build-arg DATABASE_URL --build-arg OPENSRS_HOST_TEST --build-arg VIBN_SECRETS_KEY --build-arg COOLIFY_BUILD_SECRETS_HASH=8139843e870e13b929b1a153d03a63ab44957aa859be24b76062228914d14f84 --build-arg 'COOLIFY_URL' --build-arg 'COOLIFY_BRANCH' --build-arg 'COOLIFY_RESOURCE_UUID' \/artifacts\/iy53yujv0jkvol25maud6x85","type":"stdout","timestamp":"2026-05-16T21:57:54.749901Z","hidden":true,"batch":14,"order":28},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'bash \/artifacts\/build.sh'","output":"#0 building with \"default\" instance using docker driver\n\n#1 [internal] load build definition from Dockerfile\n#1 transferring dockerfile: 2B 0.0s done\n#1 DONE 0.1s","type":"stderr","timestamp":"2026-05-16T21:57:56.039046Z","hidden":true,"batch":14,"order":29},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'bash \/artifacts\/build.sh'","output":"ERROR: failed to build: failed to solve: failed to read dockerfile: open Dockerfile: no such file or directory","type":"stderr","timestamp":"2026-05-16T21:57:56.132031Z","hidden":true,"batch":14,"order":30},{"command":"docker exec iy53yujv0jkvol25maud6x85 bash -c 'bash \/artifacts\/build.sh'","output":"exit status 1","type":"stderr","timestamp":"2026-05-16T21:57:56.144084Z","hidden":true,"batch":14,"order":31},{"command":null,"output":"========================================","type":"stderr","timestamp":"2026-05-16T21:57:56.248895Z","hidden":false,"batch":1,"order":32},{"command":null,"output":"Deployment failed: Command execution failed (exit code 1): docker exec iy53yujv0jkvol25maud6x85 bash -c 'bash \/artifacts\/build.sh'\nError: #0 building with \"default\" instance using docker driver\n\n#1 [internal] load build definition from Dockerfile\n#1 transferring dockerfile: 2B 0.0s done\n#1 DONE 0.1s\nERROR: failed to build: failed to solve: failed to read dockerfile: open Dockerfile: no such file or directory\nexit status 1","type":"stderr","timestamp":"2026-05-16T21:57:56.259239Z","hidden":false,"batch":1,"order":33},{"command":null,"output":"Error type: App\\Exceptions\\DeploymentException","type":"stderr","timestamp":"2026-05-16T21:57:56.271042Z","hidden":true,"batch":1,"order":34},{"command":null,"output":"Error code: 0","type":"stderr","timestamp":"2026-05-16T21:57:56.295409Z","hidden":true,"batch":1,"order":35},{"command":null,"output":"Location: \/var\/www\/html\/app\/Traits\/ExecuteRemoteCommand.php:242","type":"stderr","timestamp":"2026-05-16T21:57:56.323687Z","hidden":true,"batch":1,"order":36},{"command":null,"output":"Stack trace (first 5 lines):","type":"stderr","timestamp":"2026-05-16T21:57:56.348272Z","hidden":true,"batch":1,"order":37},{"command":null,"output":"#0 \/var\/www\/html\/app\/Traits\/ExecuteRemoteCommand.php(106): App\\Jobs\\ApplicationDeploymentJob->executeCommandWithProcess()","type":"stderr","timestamp":"2026-05-16T21:57:56.386159Z","hidden":true,"batch":1,"order":38},{"command":null,"output":"#1 \/var\/www\/html\/vendor\/laravel\/framework\/src\/Illuminate\/Collections\/Traits\/EnumeratesValues.php(275): App\\Jobs\\ApplicationDeploymentJob->{closure:App\\Traits\\ExecuteRemoteCommand::execute_remote_command():72}()","type":"stderr","timestamp":"2026-05-16T21:57:56.404446Z","hidden":true,"batch":1,"order":39},{"command":null,"output":"#2 \/var\/www\/html\/app\/Traits\/ExecuteRemoteCommand.php(72): Illuminate\\Support\\Collection->each()","type":"stderr","timestamp":"2026-05-16T21:57:56.415183Z","hidden":true,"batch":1,"order":40},{"command":null,"output":"#3 \/var\/www\/html\/app\/Jobs\/ApplicationDeploymentJob.php(3290): App\\Jobs\\ApplicationDeploymentJob->execute_remote_command()","type":"stderr","timestamp":"2026-05-16T21:57:56.426721Z","hidden":true,"batch":1,"order":41},{"command":null,"output":"#4 \/var\/www\/html\/app\/Jobs\/ApplicationDeploymentJob.php(898): App\\Jobs\\ApplicationDeploymentJob->build_image()","type":"stderr","timestamp":"2026-05-16T21:57:56.452359Z","hidden":true,"batch":1,"order":42},{"command":null,"output":"========================================","type":"stderr","timestamp":"2026-05-16T21:57:56.466279Z","hidden":false,"batch":1,"order":43},{"command":null,"output":"Deployment failed. Removing the new version of your application.","type":"stderr","timestamp":"2026-05-16T21:57:56.489343Z","hidden":false,"batch":1,"order":44},{"command":null,"output":"Gracefully shutting down build container: iy53yujv0jkvol25maud6x85","type":"stdout","timestamp":"2026-05-16T21:57:57.505440Z","hidden":false,"batch":1,"order":45},{"command":"docker stop -t 30 iy53yujv0jkvol25maud6x85","output":"iy53yujv0jkvol25maud6x85","type":"stdout","timestamp":"2026-05-16T21:57:58.427355Z","hidden":true,"batch":17,"order":46}] diff --git a/grep_out.txt b/grep_out.txt deleted file mode 100644 index 84a9f99..0000000 --- a/grep_out.txt +++ /dev/null @@ -1,28 +0,0 @@ -vibn-frontend/.next/server/chunks/lib_68058895._.js.map:6: {"offset": {"line": 1, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/lib/design-kits/registry.ts","turbopack:///[project]/lib/design-kits/types.ts","turbopack:///[project]/lib/integrations/sentry.ts","turbopack:///[project]/lib/ai/gemini-chat.ts","turbopack:///[project]/lib/ai/openai-compatible-chat.ts","turbopack:///[project]/lib/ai/vibn-chat-model.ts","turbopack:///[project]/lib/design-kits/for-ai.ts","turbopack:///[project]/lib/design-kits/resolve.ts"],"sourcesContent":["import type { StarterKitDefinition } from \"./types\";\n\n/**\n * Visual presets + UI foundation hints for codegen.\n *\n * Popular stacks (shadcn/ui, MUI, HeroUI, Untitled UI) are **different\n * products** — distinct installs, APIs, and licensing. Presets that share\n * `uiFoundation: \"agnostic\"` are token themes only; the agent should map\n * tokens onto whatever framework already exists unless greenfield.\n *\n * Mood presets at the bottom mirror legacy DESIGN_FEELS in\n * `preview-assist-ui/src/App.jsx`.\n */\nexport const STARTER_KITS: StarterKitDefinition[] = [\n {\n id: \"shadcn-neutral\",\n name: \"shadcn / Neutral SaaS\",\n tagline: \"Slate neutrals · minimal chrome · Tailwind-friendly\",\n defaults: {\n accentHex: \"#6366f1\",\n radiusMdPx: 8,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\"],\n uiFoundation: \"shadcn-ui\",\n foundationNotesForAi:\n \"Prefer shadcn/ui patterns when adding UI (Tailwind + Radix primitives; copy components into the repo). Map resolved accent/radius to CSS variables and Tailwind theme as shadcn expects.\",\n },\n {\n id: \"mui-material\",\n name: \"MUI (Material)\",\n tagline:\n \"Enterprise components · Material Design · dashboards & CRM shells\",\n defaults: {\n accentHex: \"#1976d2\",\n radiusMdPx: 4,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"mui-material\",\n foundationNotesForAi:\n \"Prefer MUI (@mui/material) when scaffolding new screens unless the repo already uses something else. Map accent and corners via createTheme (palette.primary, shape.borderRadius); avoid mixing MUI with a second heavy kit without user consent.\",\n },\n {\n id: \"heroui-next\",\n name: \"HeroUI\",\n tagline: \"Next.js-optimized · React Aria · Tailwind primitives\",\n defaults: {\n accentHex: \"#006fee\",\n radiusMdPx: 12,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"heroui\",\n foundationNotesForAi:\n \"Prefer HeroUI (@heroui/react) on Next.js greenfield; Tailwind-based with React Aria. Align semantic colors with tokens below via Tailwind/HeroUI theme config.\",\n },\n {\n id: \"untitled-ui\",\n name: \"Untitled UI\",\n tagline: \"Professional SaaS density · React Aria · Tailwind\",\n defaults: {\n accentHex: \"#444ce7\",\n radiusMdPx: 8,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"untitled-ui\",\n foundationNotesForAi:\n \"Untitled UI is a commercial kit — confirm the user has a license before importing paid sections from Figma handoff. When cleared, follow Untitled UI + React Aria + Tailwind patterns; map tokens to their spacing/color scales.\",\n },\n {\n id: \"twenty-crm\",\n name: \"Twenty CRM UI Kit\",\n tagline: \"CRM density · indigo accent · Radix-aligned semantics\",\n defaults: {\n accentHex: \"#5f6bf5\",\n radiusMdPx: 8,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Twenty-inspired tokens only — do not assume or vendor Twenty OSS unless the user asks. If greenfield with Tailwind, shadcn/ui-class primitives fit well; if repo uses MUI/HeroUI, map tokens there instead.\",\n },\n {\n id: \"flyonui\",\n name: \"FlyonUI\",\n tagline: \"Tailwind + daisyUI semantics + Preline JS\",\n defaults: {\n accentHex: \"#4f46e5\",\n radiusMdPx: 8,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Prefer FlyonUI semantics (daisyUI class names + Preline JS for interactivity) when scaffolding new screens. 800+ copy-paste components available. Use semantic classes like 'btn btn-primary' instead of raw utility classes where possible.\",\n },\n {\n id: \"warm-minimal\",\n name: \"Warm minimal\",\n tagline: \"Soft stone grays · rounded surfaces · editorial spacing\",\n defaults: {\n accentHex: \"#c2410c\",\n radiusMdPx: 10,\n fontPreset: \"system\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Warm SaaS mood — tokens only. Respect whichever UI stack exists; prefer centralized CSS/Tailwind variables for neutrals and accent.\",\n },\n {\n id: \"bold-confident\",\n name: \"Bold & confident\",\n tagline: \"High contrast · vivid accent — Stripe / Vercel energy\",\n defaults: {\n accentHex: \"#f43f5e\",\n radiusMdPx: 8,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Bold marketing/product mood — tokens only. Map accent into existing theme; avoid bolting on a new framework if one is already present.\",\n },\n {\n id: \"fresh-modern\",\n name: \"Fresh & modern\",\n tagline: \"Crisp green accent · friendly surfaces\",\n defaults: {\n accentHex: \"#22c55e\",\n radiusMdPx: 10,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Fresh palette mood — tokens only. Align greens with brand accent variables without prescribing a specific component vendor.\",\n },\n {\n id: \"electric-vivid\",\n name: \"Electric & vivid\",\n tagline: \"Purple creative tooling — Figma / Framer mood\",\n defaults: {\n accentHex: \"#8b5cf6\",\n radiusMdPx: 8,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Creative-tool mood — tokens only. Prefer mapping into existing DS before introducing another library.\",\n },\n {\n id: \"luxury-refined\",\n name: \"Premium & refined\",\n tagline: \"Gold accent · tight radius · editorial polish\",\n defaults: {\n accentHex: \"#d4a853\",\n radiusMdPx: 6,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Premium editorial mood — tokens only. Dark chrome often pairs with subtle borders; respect existing stack.\",\n },\n {\n id: \"anthropic-claude\",\n name: \"Anthropic / Claude\",\n tagline: \"Warm terracotta accent · clean editorial layout · serif headers\",\n defaults: {\n accentHex: \"#d97757\",\n radiusMdPx: 8,\n fontPreset: \"system\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Claude Design System — Editorial, warm terracotta accent (#d97757), off-white backgrounds (#f9f8f6). Use serif fonts (like Recoleta or a strong serif fallback) for primary headers and clean sans-serif for body. Buttons are rounded, surfaces are separated by subtle hairlines instead of heavy shadows. Read github.com/VoltAgent/awesome-claude-design/blob/main/design-systems/claude/DESIGN.md for specific token semantics.\",\n },\n {\n id: \"stripe-fintech\",\n name: \"Stripe / Fintech\",\n tagline: \"Signature purple gradients · weight-300 elegance · crisp utility\",\n defaults: {\n accentHex: \"#635bff\",\n radiusMdPx: 6,\n fontPreset: \"inter\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Stripe Design System — Crisp precision. Signature blurple accent (#635bff). Use incredibly soft, large shadows (0 50px 100px -20px rgba(50,50,93,0.25)) and crisp borders. Typography is tightly tracked, favoring font-weight 400 and 500. Gradients should be used sparingly but impactfully on active states. See github.com/VoltAgent/awesome-claude-design/blob/main/design-systems/stripe/DESIGN.md.\",\n },\n {\n id: \"vercel-dev\",\n name: \"Vercel / Developer\",\n tagline: \"Black and white precision · Geist font · maximum contrast\",\n defaults: {\n accentHex: \"#000000\",\n radiusMdPx: 6,\n fontPreset: \"system\",\n density: \"compact\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Vercel Design System — Monochrome, high-contrast, developer-oriented. Use Geist or Geist Mono. The primary accent is solid black (#000) on white, or white on black in dark mode. Extremely sparse use of color, only for status (success, error, warning). Layouts are boxy, defined by 1px solid #eaeaea borders. See github.com/VoltAgent/awesome-claude-design/blob/main/design-systems/vercel/DESIGN.md.\",\n },\n {\n id: \"linear-app\",\n name: \"Linear / Productivity\",\n tagline: \"Ultra-minimal · precise dark UI · purple glow\",\n defaults: {\n accentHex: \"#5e6ad2\",\n radiusMdPx: 8,\n fontPreset: \"system\",\n density: \"compact\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\", \"density\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Linear Design System — Dark mode first. Very subtle, single-pixel inner borders (box-shadow inset) on surfaces to give them depth. Primary accent is a muted indigo/purple. Backgrounds are deep gray (#111214) rather than pure black. Typography is highly structured, slightly smaller (13px/14px base) for density. See github.com/VoltAgent/awesome-claude-design/blob/main/design-systems/linear.app/DESIGN.md.\",\n },\n {\n id: \"airbnb-marketplace\",\n name: \"Airbnb / Marketplace\",\n tagline: \"Warm coral accent · photography-driven · large rounded UI\",\n defaults: {\n accentHex: \"#ff385c\",\n radiusMdPx: 12,\n fontPreset: \"system\",\n density: \"comfortable\",\n },\n customizeFields: [\"accent\", \"radius\", \"font\"],\n uiFoundation: \"agnostic\",\n foundationNotesForAi:\n \"Airbnb Design System — Friendly, consumer-focused. Large touch targets, heavy border-radius (12px+), and a stark contrast between pure white backgrounds and the vibrant 'Rausch' coral accent (#ff385c). Minimal borders, using white space and soft, dispersed shadows for elevation. See github.com/VoltAgent/awesome-claude-design/blob/main/design-systems/airbnb/DESIGN.md.\",\n },\n];\n\nexport function getStarterKit(id: string): StarterKitDefinition | undefined {\n return STARTER_KITS.find((k) => k.id === id);\n}\n","/**\n * Project-scoped design kit selection + customization (starter presets).\n * Persisted under fs_projects.data.designKit — see /api/projects/[id]/design-kit.\n */\n\nexport type DesignKitDensity = \"compact\" | \"comfortable\";\n\nexport type DesignKitFontPreset = \"inter\" | \"system\";\n\n/**\n * Which component stack this preset targets when scaffolding or extending UI.\n * Distinct from visual tokens — shadcn, MUI, HeroUI, and Untitled are different\n * products (install model, APIs, licensing).\n */\nexport type UiFoundationId =\n | \"shadcn-ui\"\n | \"mui-material\"\n | \"heroui\"\n | \"untitled-ui\"\n | \"agnostic\";\n\nexport const UI_FOUNDATION_LABELS: Record = {\n \"shadcn-ui\": \"shadcn/ui · Radix · Tailwind\",\n \"mui-material\": \"MUI (Material UI)\",\n heroui: \"HeroUI · Next.js\",\n \"untitled-ui\": \"Untitled UI · React Aria · Tailwind\",\n agnostic: \"Tokens only — match your existing stack\",\n};\n\n/** User-authored deltas applied on top of a starter kit's defaults. */\nexport interface DesignKitOverrides {\n accentHex?: string;\n /** Mid-step radius in px; xs/sm/xl derived from this */\n radiusMdPx?: number;\n fontPreset?: DesignKitFontPreset;\n density?: DesignKitDensity;\n}\n\nexport interface StarterKitDefinition {\n id: string;\n name: string;\n tagline: string;\n defaults: DesignKitOverrides;\n /** Customize panel fields shown for this starter */\n customizeFields: Array<\"accent\" | \"radius\" | \"font\" | \"density\">;\n uiFoundation: UiFoundationId;\n /** Short guidance for the coding agent (stack, licensing, when not to add libs). */\n foundationNotesForAi: string;\n}\n\n/** Persisted JSON shape */\nexport interface DesignKitPersisted {\n kitId: string;\n /** Overrides keyed by kit id so switching kits preserves each setup */\n perKit: Record;\n}\n\nexport const DEFAULT_DESIGN_KIT_ID = \"twenty-crm\";\n","/**\n * Sentry-as-product integration.\n *\n * Provisions a Sentry project per Vibn project under the shared\n * `vibnai` Sentry org, then makes its DSN + auth token available\n * to any Coolify app deployed for that project. Real-user errors\n * from the user's deployed app land in Sentry; AI tools (Stage 3)\n * read the issue feed back into chat (Stage 4).\n *\n * Design choices:\n * - **Idempotent.** Looks up existing Sentry projects by slug\n * before creating, so reruns are safe.\n * - **Shared org token.** The same `SENTRY_AUTH_TOKEN` we use to\n * upload source maps for vibn-frontend is reused across every\n * user project — it has org-write scope. This means we DON'T\n * need per-project auth tokens, just per-project DSNs.\n * - **Slug convention.** `vibn-{workspace}-{projectSlug}`. Avoids\n * collisions across workspaces and stays under Sentry's\n * 50-char project slug limit. Clamped if longer.\n * - **Soft failure.** If Sentry provisioning fails (rate limit,\n * network, token revoked) we log and continue. The Vibn\n * project still works without Sentry; we'll lazily retry on\n * the next deploy.\n *\n * See SENTRY_AS_PRODUCT.md for the full proposal context.\n */\n\nimport { query } from '@/lib/db-postgres';\nimport { upsertApplicationEnv } from '@/lib/coolify';\n\nconst SENTRY_API_BASE = 'https://de.sentry.io/api/0';\nconst SENTRY_ORG_SLUG = 'vibnai';\nconst SENTRY_TEAM_SLUG = 'vibnai'; // Default team name matches org slug for personal/single-team setups.\n\nexport interface SentryProjectInfo {\n /** Sentry project slug, e.g. `vibn-mark-account-checkout-app`. */\n slug: string;\n /** Public DSN for runtime error capture. NEXT_PUBLIC_SENTRY_DSN. */\n dsn: string;\n /** When this row was created/updated. */\n provisionedAt: string;\n}\n\nexport interface ProvisionInput {\n projectId: string;\n /** Vibn workspace slug, e.g. \"mark-account\". */\n workspaceSlug: string;\n /** Vibn project slug, e.g. \"checkout-app\". */\n projectSlug: string;\n /** Display name shown in the Sentry UI. */\n projectName: string;\n}\n\n/**\n * Returns the Sentry project for a given Vibn project, creating\n * it if missing. Persists the result to fs_projects.data.sentry\n * so subsequent calls hit the cache instead of Sentry's API.\n *\n * Returns null if Sentry provisioning fails — caller should log\n * and continue.\n */\nexport async function ensureSentryProject(\n input: ProvisionInput,\n): Promise {\n const authToken = process.env.SENTRY_AUTH_TOKEN;\n if (!authToken) {\n console.warn('[sentry] SENTRY_AUTH_TOKEN missing — skipping provisioning');\n return null;\n }\n\n // 1. Fast path: already provisioned for this Vibn project.\n const cached = await loadSentryFromProject(input.projectId);\n if (cached) return cached;\n\n // 2. Build the deterministic slug. Sentry caps at 50 chars and\n // only allows lowercase a–z, 0–9, dashes.\n const slug = buildSentrySlug(input.workspaceSlug, input.projectSlug);\n\n try {\n // 3. Try to look up existing Sentry project (handles the case\n // where fs_projects was wiped but Sentry still has the project).\n const existing = await fetchSentryProject(slug, authToken);\n if (existing) {\n const info = await materialize(input.projectId, existing, authToken);\n if (info) return info;\n }\n\n // 4. Create fresh.\n const created = await createSentryProject({\n slug,\n name: input.projectName.slice(0, 50),\n authToken,\n });\n if (!created) return null;\n\n return await materialize(input.projectId, created, authToken);\n } catch (err) {\n console.error('[sentry] ensureSentryProject failed', err);\n return null;\n }\n}\n\n/**\n * Sets the standard Sentry env vars on a Coolify application\n * so that its next build inlines the DSN and uploads source maps.\n * Idempotent — safe to call on every apps.create. Soft-fails on\n * any per-key error so a single failed upsert doesn't block deploy.\n *\n * Env vars set:\n * - NEXT_PUBLIC_SENTRY_DSN — public DSN for runtime capture\n * - SENTRY_AUTH_TOKEN — shared org token for source map upload\n *\n * Both are marked Coolify-default (is_buildtime=true is_runtime=true);\n * the public DSN must be present at build time so Next.js inlines\n * it into the client bundle.\n */\nexport async function applySentryEnvToCoolifyApp(\n coolifyAppUuid: string,\n projectId: string,\n): Promise {\n const sentry = await loadSentryFromProject(projectId);\n if (!sentry) return; // Project not yet provisioned in Sentry — Stage 1 will retry.\n\n const authToken = process.env.SENTRY_AUTH_TOKEN;\n if (!authToken) return;\n\n const envs: Array<[string, string]> = [\n ['NEXT_PUBLIC_SENTRY_DSN', sentry.dsn],\n ['SENTRY_AUTH_TOKEN', authToken],\n ];\n\n for (const [key, value] of envs) {\n try {\n await upsertApplicationEnv(coolifyAppUuid, { key, value });\n } catch (err) {\n console.warn(`[sentry] upsert ${key} on ${coolifyAppUuid} failed`, err);\n }\n }\n}\n\n/**\n * Returns the Sentry project info for a Vibn project if already\n * provisioned, else null. Read-only — does NOT call Sentry's API.\n */\nexport async function loadSentryFromProject(\n projectId: string,\n): Promise {\n const rows = await query<{ data: any }>(\n `SELECT data FROM fs_projects WHERE id = $1 LIMIT 1`,\n [projectId],\n );\n const sentry = rows[0]?.data?.sentry;\n if (sentry?.slug && sentry?.dsn && sentry?.provisionedAt) {\n return sentry as SentryProjectInfo;\n }\n return null;\n}\n\n// ──────────────────────────────────────────────────────────────────\n// Issue feed — Stage 3 MCP tools read these\n// ──────────────────────────────────────────────────────────────────\n\nexport interface SentryIssueSummary {\n id: string;\n title: string;\n /** \"error\" | \"fatal\" | \"warning\" | \"info\" | \"debug\" */\n level: string;\n /** Total event count (lifetime). */\n count: number;\n /** ISO of most recent occurrence. */\n lastSeen: string;\n /** ISO of first occurrence. */\n firstSeen: string;\n /** \"unresolved\" | \"resolved\" | \"ignored\" — we filter to unresolved. */\n status: string;\n /** Best-effort culprit string from Sentry, e.g. \"GET /api/checkout\". */\n culprit: string | null;\n /** Direct URL to the issue in Sentry's UI. */\n permalink: string | null;\n}\n\nexport interface SentryEventDetail {\n /** Sentry event ID (the canonical \"fingerprint\" of this occurrence). */\n eventId: string;\n /** Most recent event's render of the stack trace (top frames first). */\n stackFrames: Array<{\n function: string | null;\n filename: string | null;\n lineno: number | null;\n colno: number | null;\n /** Source code surrounding the line if Sentry has source maps. */\n contextLine: string | null;\n }>;\n /** User-context tag (email/id/username/ip), if Sentry captured one. */\n user: { email?: string; id?: string; username?: string; ipAddress?: string } | null;\n /** Request method + URL if this was an HTTP-facing error. */\n request: { method?: string; url?: string } | null;\n /** Truncated breadcrumbs (last 20) — clicks, fetches, navigations. */\n breadcrumbs: Array<{\n type: string | null;\n category: string | null;\n message: string | null;\n timestamp: string | null;\n }>;\n /** Direct URL to a Session Replay if the user had one recorded. */\n replayUrl: string | null;\n}\n\n/**\n * List recent unresolved issues for a Vibn project's Sentry project.\n * Returns [] if Sentry isn't provisioned yet — caller should treat\n * that as \"no errors\", which is functionally correct (no Sentry =\n * no error capture path).\n */\nexport async function listRecentSentryIssues(\n projectId: string,\n options?: { limit?: number; sinceHours?: number },\n): Promise {\n const sentry = await loadSentryFromProject(projectId);\n if (!sentry) return [];\n\n const authToken = process.env.SENTRY_AUTH_TOKEN;\n if (!authToken) return [];\n\n const limit = clampLimit(options?.limit ?? 10);\n const sinceHours = options?.sinceHours ?? 24;\n const statsPeriod = `${sinceHours}h`;\n\n const url = new URL(\n `${SENTRY_API_BASE}/projects/${SENTRY_ORG_SLUG}/${sentry.slug}/issues/`,\n );\n url.searchParams.set('limit', String(limit));\n url.searchParams.set('query', 'is:unresolved');\n url.searchParams.set('statsPeriod', statsPeriod);\n url.searchParams.set('sort', 'date');\n\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${authToken}` },\n });\n if (!res.ok) {\n console.warn(\n `[sentry] listRecentSentryIssues ${res.status}: ${await res.text()}`,\n );\n return [];\n }\n const raw = (await res.json()) as Array>;\n return raw.map((i) => ({\n id: String(i.id),\n title: String(i.title ?? '(untitled)'),\n level: String(i.level ?? 'error'),\n count: Number(i.count ?? 0),\n lastSeen: String(i.lastSeen ?? ''),\n firstSeen: String(i.firstSeen ?? ''),\n status: String(i.status ?? 'unresolved'),\n culprit: i.culprit ? String(i.culprit) : null,\n permalink: i.permalink ? String(i.permalink) : null,\n }));\n}\n\n/**\n * Fetch the latest event for an issue, with stack trace,\n * breadcrumbs, user, request, and Session Replay link if any.\n * Returns null if the issue isn't found or Sentry isn't ready.\n */\nexport async function getSentryIssueDetail(\n projectId: string,\n issueId: string,\n): Promise {\n const sentry = await loadSentryFromProject(projectId);\n if (!sentry) return null;\n const authToken = process.env.SENTRY_AUTH_TOKEN;\n if (!authToken) return null;\n\n const res = await fetch(\n `${SENTRY_API_BASE}/issues/${encodeURIComponent(issueId)}/events/latest/`,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n );\n if (!res.ok) {\n console.warn(\n `[sentry] getSentryIssueDetail ${res.status}: ${await res.text()}`,\n );\n return null;\n }\n const e = (await res.json()) as Record;\n\n // Sentry stores frames in a few different places depending on\n // platform. The exception entry has the most reliable shape.\n const exceptionEntry = (e.entries || []).find(\n (entry: any) => entry?.type === 'exception',\n );\n const firstException = exceptionEntry?.data?.values?.[0];\n const frames =\n (firstException?.stacktrace?.frames as Array> | undefined) ?? [];\n // Sentry returns frames in oldest-first order; reverse so top of\n // stack (the line that actually threw) is first — which is what\n // every developer scans first.\n const stackFrames = [...frames].reverse().slice(0, 12).map((f) => ({\n function: f.function ?? null,\n filename: f.filename ?? null,\n lineno: typeof f.lineno === 'number' ? f.lineno : null,\n colno: typeof f.colno === 'number' ? f.colno : null,\n contextLine: f.contextLine ?? null,\n }));\n\n const breadcrumbsEntry = (e.entries || []).find(\n (entry: any) => entry?.type === 'breadcrumbs',\n );\n const breadcrumbs =\n ((breadcrumbsEntry?.data?.values as Array> | undefined) ?? [])\n .slice(-20)\n .map((b) => ({\n type: b.type ?? null,\n category: b.category ?? null,\n message: b.message ?? null,\n timestamp: b.timestamp ?? null,\n }));\n\n const requestEntry = (e.entries || []).find(\n (entry: any) => entry?.type === 'request',\n );\n const request = requestEntry?.data\n ? {\n method: requestEntry.data.method ?? undefined,\n url: requestEntry.data.url ?? undefined,\n }\n : null;\n\n // Session Replay: Sentry attaches a `replayId` tag on events that\n // have one. Build the canonical UI URL.\n const replayId = (e.tags as Array> | undefined)?.find(\n (t) => t.key === 'replayId',\n )?.value;\n const replayUrl = replayId\n ? `https://${SENTRY_ORG_SLUG}.sentry.io/replays/${replayId}/`\n : null;\n\n return {\n eventId: String(e.eventID ?? e.id ?? ''),\n stackFrames,\n user: e.user ? sanitizeUser(e.user) : null,\n request,\n breadcrumbs,\n replayUrl,\n };\n}\n\n/**\n * Mark an issue resolved. Used by the AI after it ships a fix and\n * verifies the bug no longer fires (e.g. via tests, log watch, or\n * explicit user confirmation).\n */\nexport async function resolveSentryIssue(\n projectId: string,\n issueId: string,\n): Promise {\n const sentry = await loadSentryFromProject(projectId);\n if (!sentry) return false;\n const authToken = process.env.SENTRY_AUTH_TOKEN;\n if (!authToken) return false;\n\n const res = await fetch(\n `${SENTRY_API_BASE}/issues/${encodeURIComponent(issueId)}/`,\n {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ status: 'resolved' }),\n },\n );\n return res.ok;\n}\n\nfunction clampLimit(n: number): number {\n if (!Number.isFinite(n)) return 10;\n return Math.max(1, Math.min(50, Math.floor(n)));\n}\n\nfunction sanitizeUser(u: Record): SentryEventDetail['user'] {\n return {\n email: u.email ?? undefined,\n id: u.id ?? undefined,\n username: u.username ?? undefined,\n ipAddress: u.ip_address ?? u.ipAddress ?? undefined,\n };\n}\n\n// ──────────────────────────────────────────────────────────────────\n// Internal helpers\n// ──────────────────────────────────────────────────────────────────\n\nfunction buildSentrySlug(workspaceSlug: string, projectSlug: string): string {\n const raw = `vibn-${workspaceSlug}-${projectSlug}`\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n // Sentry: 50 char max. Truncate from the end (preserves the\n // `vibn-{workspace}` prefix which keeps slugs distinguishable\n // across workspaces).\n return raw.slice(0, 50);\n}\n\ninterface SentryApiProject {\n slug: string;\n name: string;\n id: string;\n}\n\nasync function fetchSentryProject(\n slug: string,\n authToken: string,\n): Promise {\n const res = await fetch(\n `${SENTRY_API_BASE}/projects/${SENTRY_ORG_SLUG}/${slug}/`,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n );\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `Sentry GET project failed: ${res.status} ${await res.text()}`,\n );\n }\n return (await res.json()) as SentryApiProject;\n}\n\nasync function createSentryProject(input: {\n slug: string;\n name: string;\n authToken: string;\n}): Promise {\n const res = await fetch(\n `${SENTRY_API_BASE}/teams/${SENTRY_ORG_SLUG}/${SENTRY_TEAM_SLUG}/projects/`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${input.authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: input.name,\n slug: input.slug,\n platform: 'javascript-nextjs', // Most common case; doesn't gate which SDK actually sends events.\n }),\n },\n );\n if (!res.ok) {\n const text = await res.text();\n // 409 = slug already exists in another team; we tried the GET\n // path first, so this is genuinely a race or a slug conflict.\n if (res.status === 409) {\n console.warn(`[sentry] slug ${input.slug} taken — retry GET`);\n return await fetchSentryProject(input.slug, input.authToken);\n }\n throw new Error(`Sentry POST project failed: ${res.status} ${text}`);\n }\n return (await res.json()) as SentryApiProject;\n}\n\ninterface SentryClientKey {\n dsn: { public: string };\n isActive: boolean;\n}\n\nasync function fetchProjectDsn(\n slug: string,\n authToken: string,\n): Promise {\n const res = await fetch(\n `${SENTRY_API_BASE}/projects/${SENTRY_ORG_SLUG}/${slug}/keys/`,\n {\n headers: { Authorization: `Bearer ${authToken}` },\n },\n );\n if (!res.ok) {\n throw new Error(\n `Sentry GET keys failed: ${res.status} ${await res.text()}`,\n );\n }\n const keys = (await res.json()) as SentryClientKey[];\n // Sentry auto-creates a \"Default\" key on project creation; pick\n // the first active one. Multiple keys exist in pro setups but\n // we always want the live one.\n const active = keys.find((k) => k.isActive) ?? keys[0];\n return active?.dsn?.public ?? null;\n}\n\nasync function materialize(\n projectId: string,\n apiProj: SentryApiProject,\n authToken: string,\n): Promise {\n const dsn = await fetchProjectDsn(apiProj.slug, authToken);\n if (!dsn) {\n console.error(`[sentry] no DSN returned for ${apiProj.slug}`);\n return null;\n }\n\n const info: SentryProjectInfo = {\n slug: apiProj.slug,\n dsn,\n provisionedAt: new Date().toISOString(),\n };\n\n await query(\n `UPDATE fs_projects\n SET data = jsonb_set(COALESCE(data, '{}'::jsonb), '{sentry}', $2::jsonb, true),\n updated_at = NOW()\n WHERE id = $1`,\n [projectId, JSON.stringify(info)],\n );\n\n return info;\n}\n","/**\n * Gemini 3.1 Pro chat client with tool-calling support.\n *\n * Architecture:\n * - Tool-calling rounds use generateContent (non-streaming) so we always\n * get the complete response including thought_signature. Thinking models\n * (2.5+, 3.x) require this field to be echoed back in functionResponse\n * and it is not reliably present in individual SSE chunks.\n * - Final text-only response uses streamGenerateContent for good UX.\n */\n\nconst GEMINI_API_KEY = process.env.GOOGLE_API_KEY || \"\";\nconst GEMINI_MODEL = process.env.VIBN_CHAT_MODEL || \"gemini-3.1-pro-preview\";\nconst GEMINI_BASE_URL = \"https://generativelanguage.googleapis.com/v1beta\";\n\nexport interface ChatMessage {\n role: \"user\" | \"assistant\" | \"tool\";\n content: string;\n toolCalls?: ToolCall[];\n toolCallId?: string;\n toolName?: string;\n thoughtSignature?: string;\n}\n\nexport interface ToolCall {\n id: string;\n name: string;\n args: Record;\n /** Must be echoed back in functionResponse for Gemini thinking models */\n thoughtSignature?: string;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n parameters: Record;\n}\n\nexport interface ChatChunk {\n type: \"text\" | \"thinking\" | \"tool_call\" | \"done\" | \"error\";\n text?: string;\n toolCall?: ToolCall;\n error?: string;\n}\n\n/** Convert our ChatMessage[] to Gemini's contents[] format */\nfunction toGeminiContents(messages: ChatMessage[]) {\n const contents: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n contents.push({ role: \"user\", parts: [{ text: msg.content }] });\n } else if (msg.role === \"assistant\") {\n const parts: any[] = [];\n if (msg.content) parts.push({ text: msg.content });\n if (msg.toolCalls?.length) {\n for (const tc of msg.toolCalls) {\n // thoughtSignature is a SIBLING of functionCall in the part object,\n // not nested inside it. See: ai.google.dev/gemini-api/docs/thought-signatures\n const part: any = {\n functionCall: { name: tc.name, args: tc.args, id: tc.id },\n };\n if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature;\n parts.push(part);\n }\n }\n if (parts.length) contents.push({ role: \"model\", parts });\n } else if (msg.role === \"tool\") {\n const part = {\n functionResponse: {\n name: msg.toolName || \"unknown\",\n id: msg.toolCallId,\n response: { content: msg.content },\n },\n };\n const last = contents[contents.length - 1];\n if (last?.role === \"user\") {\n last.parts.push(part);\n } else {\n contents.push({ role: \"user\", parts: [part] });\n }\n }\n }\n return contents;\n}\n\nfunction toGeminiFunctions(tools: ToolDefinition[]) {\n if (!tools.length) return undefined;\n return [\n {\n functionDeclarations: tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n })),\n },\n ];\n}\n\nfunction buildBody(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n /**\n * Ask Gemini to return its thought summaries as parts marked\n * `thought: true`. We pay for thinking tokens regardless; this just\n * makes them visible so the UI can show \"Reading server.js…\",\n * \"Shipping to production…\" between tool calls instead of leaving\n * the user staring at a silent tool tray. Defaults to true.\n */\n includeThoughts?: boolean;\n}) {\n const body: any = {\n contents: toGeminiContents(opts.messages),\n systemInstruction: { parts: [{ text: opts.systemPrompt }] },\n generationConfig: {\n temperature: opts.temperature ?? 0.7,\n maxOutputTokens: 8192,\n thinkingConfig: { includeThoughts: opts.includeThoughts ?? true },\n },\n };\n const fns = toGeminiFunctions(opts.tools ?? []);\n if (fns) body.tools = fns;\n return body;\n}\n\n/**\n * Non-streaming call — used for tool-calling rounds.\n * Returns complete response with thought_signature guaranteed.\n */\nexport async function callGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n includeThoughts?: boolean;\n}): Promise<{\n text: string;\n /** First-person reasoning narration; meant for a \"thinking\" UI panel, not the main bubble. */\n thoughts: string;\n toolCalls: ToolCall[];\n finishReason?: string;\n error?: string;\n}> {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n return {\n text: \"\",\n thoughts: \"\",\n toolCalls: [],\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n\n const data = await res.json().catch(() => ({}));\n if (!res.ok) {\n const msg = data?.error?.message || JSON.stringify(data).slice(0, 200);\n return {\n text: \"\",\n thoughts: \"\",\n toolCalls: [],\n error: `Gemini API error ${res.status}: ${msg}`,\n };\n }\n\n const cand = data?.candidates?.[0];\n const parts: any[] = cand?.content?.parts ?? [];\n let text = \"\";\n let thoughts = \"\";\n const toolCalls: ToolCall[] = [];\n\n for (const part of parts) {\n if (part.text) {\n // CRITICAL: Gemini tags reasoning parts with `thought: true`. If\n // we lump them into `text` they leak into the chat bubble as if\n // they were prose for the user — which is the opposite of what\n // the user wants. Keep them in their own bucket so the route\n // can stream them as a separate SSE event type.\n if (part.thought) thoughts += part.text;\n else text += part.text;\n }\n if (part.functionCall) {\n toolCalls.push({\n id:\n part.functionCall.id ||\n `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name: part.functionCall.name,\n args: part.functionCall.args ?? {},\n // thoughtSignature is a SIBLING of functionCall in the part, not inside it\n thoughtSignature: part.thoughtSignature,\n });\n }\n }\n\n return { text, thoughts, toolCalls, finishReason: cand?.finishReason };\n}\n\n/**\n * Streaming call — used for the final text-only response.\n * Yields ChatChunk objects.\n */\nexport async function* streamGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n}): AsyncGenerator {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:streamGenerateContent?key=${GEMINI_API_KEY}&alt=sse`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n yield {\n type: \"error\",\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n return;\n }\n\n if (!res.ok) {\n const errText = await res.text().catch(() => \"\");\n yield {\n type: \"error\",\n error: `Gemini API error ${res.status}: ${errText.slice(0, 300)}`,\n };\n return;\n }\n\n const reader = res.body?.getReader();\n if (!reader) {\n yield { type: \"error\", error: \"No response body\" };\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n const data = line.slice(6).trim();\n if (!data || data === \"[DONE]\") continue;\n let chunk: any;\n try {\n chunk = JSON.parse(data);\n } catch {\n continue;\n }\n const parts = chunk?.candidates?.[0]?.content?.parts ?? [];\n for (const part of parts) {\n if (part.text) {\n yield part.thought\n ? { type: \"thinking\", text: part.text }\n : { type: \"text\", text: part.text };\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n yield { type: \"done\" };\n}\n","/**\n * OpenAI Chat Completions-compatible backend (DeepSeek, etc.).\n *\n * DeepSeek: base URL + `/chat/completions`, Bearer key — see\n * https://api-docs.deepseek.com/\n *\n * Tool schemas in Vibn are authored for Gemini (uppercase type enums).\n * We normalize them to JSON Schema before sending.\n */\n\nimport type { ChatMessage, ToolCall, ToolDefinition } from \"./gemini-chat\";\n\nconst DEFAULT_CHAT_URL = \"https://api.deepseek.com/chat/completions\";\n\nfunction resolveApiKey(): string {\n return (\n process.env.DEEPSEEK_API_KEY?.trim() ||\n process.env.VIBN_OPENAI_COMPATIBLE_API_KEY?.trim() ||\n \"\"\n );\n}\n\nfunction resolveChatUrl(): string {\n const raw = process.env.VIBN_OPENAI_COMPATIBLE_CHAT_URL?.trim();\n if (raw) return raw.replace(/\\/$/, \"\");\n const base = process.env.VIBN_OPENAI_COMPATIBLE_BASE_URL?.trim().replace(\n /\\/$/,\n \"\",\n );\n if (!base) return DEFAULT_CHAT_URL;\n if (base.endsWith(\"/chat/completions\")) return base;\n return `${base}/chat/completions`;\n}\n\nfunction resolveModel(): string {\n return (\n process.env.VIBN_OPENAI_COMPATIBLE_MODEL?.trim() ||\n process.env.DEEPSEEK_MODEL?.trim() ||\n \"deepseek-chat\"\n );\n}\n\n/** Gemini API Catalog-style schema → OpenAI JSON Schema */\nfunction geminiStyleToJsonSchema(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node))\n return node;\n const n = node as Record;\n const out: Record = {};\n\n for (const [key, val] of Object.entries(n)) {\n if (key === \"type\" && typeof val === \"string\") {\n const map: Record = {\n OBJECT: \"object\",\n STRING: \"string\",\n NUMBER: \"number\",\n INTEGER: \"integer\",\n BOOLEAN: \"boolean\",\n ARRAY: \"array\",\n };\n const upper = val.toUpperCase();\n out.type = map[upper] ?? val.toLowerCase();\n continue;\n }\n if (\n key === \"properties\" &&\n val &&\n typeof val === \"object\" &&\n !Array.isArray(val)\n ) {\n out.properties = Object.fromEntries(\n Object.entries(val as object).map(([k, v]) => [\n k,\n geminiStyleToJsonSchema(v),\n ]),\n );\n continue;\n }\n if (key === \"items\") {\n out.items = geminiStyleToJsonSchema(val);\n continue;\n }\n out[key] =\n val && typeof val === \"object\" && !Array.isArray(val)\n ? geminiStyleToJsonSchema(val)\n : val;\n }\n return out;\n}\n\nfunction toOpenAiTools(\n tools: ToolDefinition[] | undefined,\n): object[] | undefined {\n if (!tools?.length) return undefined;\n return tools.map((t) => ({\n type: \"function\",\n function: {\n name: t.name,\n description: t.description,\n parameters: geminiStyleToJsonSchema(t.parameters) as Record<\n string,\n unknown\n >,\n },\n }));\n}\n\n/**\n * OpenAI Chat Completions forbid `user`/`assistant` between an assistant\n * `tool_calls` block and the matching `tool` replies. Gemini-oriented code\n * may inject recovery `user` rows between individual tool results — move\n * those users to immediately after all tool rows for that assistant turn.\n */\nfunction reorderMessagesForOpenAiToolPairs(\n messages: ChatMessage[],\n): ChatMessage[] {\n const result: ChatMessage[] = [];\n let i = 0;\n while (i < messages.length) {\n const m = messages[i]!;\n if (m.role !== \"assistant\" || !m.toolCalls?.length) {\n result.push(m);\n i++;\n continue;\n }\n\n const expectedIds = m.toolCalls.map((tc) => tc.id);\n const pending = new Set(expectedIds);\n result.push(m);\n i++;\n\n const toolById = new Map();\n const bufferedUsers: ChatMessage[] = [];\n\n while (i < messages.length && pending.size > 0) {\n const n = messages[i]!;\n if (n.role === \"tool\" && n.toolCallId && pending.has(n.toolCallId)) {\n toolById.set(n.toolCallId, n);\n pending.delete(n.toolCallId);\n i++;\n continue;\n }\n if (n.role === \"user\") {\n bufferedUsers.push(n);\n i++;\n continue;\n }\n break;\n }\n\n for (const id of expectedIds) {\n const t = toolById.get(id);\n if (t) result.push(t);\n }\n result.push(...bufferedUsers);\n }\n return result;\n}\n\nfunction toOpenAiMessages(\n systemPrompt: string,\n messages: ChatMessage[],\n): object[] {\n const normalized = reorderMessagesForOpenAiToolPairs(messages);\n const out: object[] = [{ role: \"system\", content: systemPrompt }];\n for (const m of normalized) {\n if (m.role === \"user\") {\n out.push({ role: \"user\", content: m.content });\n } else if (m.role === \"assistant\") {\n const hasTools = Boolean(m.toolCalls?.length);\n const text = typeof m.content === \"string\" ? m.content.trim() : \"\";\n const msg: Record = {\n role: \"assistant\",\n content: text.length > 0 ? m.content : hasTools ? null : \"\",\n };\n if (hasTools && m.toolCalls) {\n msg.tool_calls = m.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\",\n function: {\n name: tc.name,\n arguments: JSON.stringify(tc.args ?? {}),\n },\n }));\n }\n out.push(msg);\n } else if (m.role === \"tool\") {\n const body =\n typeof m.content === \"string\"\n ? m.content\n : JSON.stringify(m.content ?? \"\");\n out.push({\n role: \"tool\",\n tool_call_id: m.toolCallId ?? \"\",\n content: body.length > 0 ? body : \"(empty)\",\n });\n }\n }\n return out;\n}\n\nfunction parseAssistantMessage(message: Record | undefined): {\n text: string;\n thoughts: string;\n toolCalls: ToolCall[];\n} {\n const rawText = typeof message?.content === \"string\" ? message.content : \"\";\n const thoughts =\n typeof message?.reasoning_content === \"string\"\n ? message.reasoning_content\n : typeof (message as { reasoning?: string })?.reasoning === \"string\"\n ? (message as { reasoning: string }).reasoning\n : \"\";\n\n const stripTags = (s: string) =>\n s\n .replace(/[\\s\\S]*?<\\/tool_calls>/g, \"\")\n .replace(/[\\s\\S]*?<\\/think>/g, \"\")\n .trim();\n\n // DeepSeek separates thinking from speaking — during tool loops it\n // often puts everything in reasoning_content and leaves content empty.\n // When that happens, surface the reasoning as the user-visible text\n // so the user isn't staring at silent tool pills.\n const text = stripTags(rawText || thoughts);\n const toolCalls: ToolCall[] = [];\n const rawCalls = message?.tool_calls;\n if (Array.isArray(rawCalls)) {\n for (const c of rawCalls) {\n const call = c as Record;\n if (call.type !== \"function\") continue;\n const fn = call.function as Record | undefined;\n const name = typeof fn?.name === \"string\" ? fn.name : \"\";\n const id =\n typeof call.id === \"string\"\n ? call.id\n : `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n let args: Record = {};\n const argStr = typeof fn?.arguments === \"string\" ? fn.arguments : \"{}\";\n try {\n args = JSON.parse(argStr || \"{}\") as Record;\n } catch {\n args = {};\n }\n if (name) toolCalls.push({ id, name, args });\n }\n }\n return { text, thoughts, toolCalls };\n}\n\n/**\n * Non-streaming chat + tool calls — mirrors {@link callGeminiChat} return shape.\n */\nexport async function callOpenAiCompatibleChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n /** Unused for OpenAI-compat; kept for call-site symmetry */\n includeThoughts?: boolean;\n}): Promise<{\n text: string;\n thoughts: string;\n toolCalls: ToolCall[];\n finishReason?: string;\n error?: string;\n}> {\n const apiKey = resolveApiKey();\n if (!apiKey) {\n return {\n text: \"\",\n thoughts: \"\",\n toolCalls: [],\n error:\n \"No API key: set DEEPSEEK_API_KEY or VIBN_OPENAI_COMPATIBLE_API_KEY for OpenAI-compatible chat.\",\n };\n }\n\n const url = resolveChatUrl();\n const model = resolveModel();\n const tools = toOpenAiTools(opts.tools);\n const oaiMessages = toOpenAiMessages(opts.systemPrompt, opts.messages);\n const body: Record = {\n model,\n messages: oaiMessages,\n temperature: opts.temperature ?? 0.7,\n max_tokens: 8192,\n stream: false,\n };\n if (tools?.length) body.tools = tools;\n\n // ── Request logging (DeepSeek 400 debug) ──────────────────────────────\n const msgSummary = oaiMessages.map((m: any) => ({\n role: m.role,\n has_tool_calls:\n m.role === \"assistant\" ? Boolean(m.tool_calls?.length) : undefined,\n tool_calls_ids:\n m.role === \"assistant\" && m.tool_calls?.length\n ? m.tool_calls.map((tc: any) => tc.id)\n : undefined,\n tool_call_id: m.role === \"tool\" ? m.tool_call_id : undefined,\n content_len: typeof m.content === \"string\" ? m.content.length : 0,\n }));\n console.error(\n \"[deepseek] request\",\n JSON.stringify({\n url,\n model,\n msg_count: oaiMessages.length,\n has_tools: Boolean(tools?.length),\n tool_count: tools?.length ?? 0,\n msg_summary: msgSummary,\n last_5_roles: msgSummary.slice(-5).map((m: any) => m.role),\n }),\n );\n // ───────────────────────────────────────────────────────────────────────\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n } catch (e) {\n return {\n text: \"\",\n thoughts: \"\",\n toolCalls: [],\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n\n const data = (await res.json().catch(() => ({}))) as Record;\n if (!res.ok) {\n // ── Error logging (DeepSeek 400 debug) ───────────────────────────────\n console.error(\n \"[deepseek] error response\",\n JSON.stringify(\n {\n status: res.status,\n status_text: res.statusText,\n headers: Object.fromEntries(res.headers.entries()),\n body: data,\n // include the last few messages sent so we can see the exact\n // pattern that triggered the error\n last_5_sent: msgSummary.slice(-5),\n },\n null,\n 2,\n ),\n );\n // ─────────────────────────────────────────────────────────────────────\n const errObj = data?.error as Record | undefined;\n const msg =\n (typeof errObj?.message === \"string\" && errObj.message) ||\n JSON.stringify(data).slice(0, 280);\n return {\n text: \"\",\n thoughts: \"\",\n toolCalls: [],\n error: `Chat API error ${res.status}: ${msg}`,\n };\n }\n\n const choice = (data.choices as Record[] | undefined)?.[0];\n const message = choice?.message as Record | undefined;\n const { text, thoughts, toolCalls } = parseAssistantMessage(message);\n const finishReason =\n typeof choice?.finish_reason === \"string\"\n ? choice.finish_reason\n : undefined;\n\n return { text, thoughts, toolCalls, finishReason };\n}\n","/**\n * Routes workspace AI chat to Gemini or an OpenAI-compatible API (e.g. DeepSeek).\n *\n * Env:\n * VIBN_CHAT_PROVIDER=gemini | deepseek | openai_compatible\n *\n * Default: gemini (requires GOOGLE_API_KEY / studio key + VIBN_CHAT_MODEL).\n *\n * DeepSeek / OpenAI-compat:\n * DEEPSEEK_API_KEY (or VIBN_OPENAI_COMPATIBLE_API_KEY)\n * Optional: VIBN_OPENAI_COMPATIBLE_CHAT_URL (default https://api.deepseek.com/chat/completions)\n * Optional: VIBN_OPENAI_COMPATIBLE_MODEL (default deepseek-chat)\n */\n\nimport type { ChatMessage, ToolDefinition } from './gemini-chat';\nimport { callGeminiChat } from './gemini-chat';\nimport { callOpenAiCompatibleChat } from './openai-compatible-chat';\n\nexport type VibnChatCallOpts = {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n includeThoughts?: boolean;\n};\n\nexport async function callVibnChat(opts: VibnChatCallOpts) {\n const p = (process.env.VIBN_CHAT_PROVIDER || 'gemini').toLowerCase().trim();\n if (p === 'deepseek' || p === 'openai_compatible') {\n return callOpenAiCompatibleChat(opts);\n }\n return callGeminiChat(opts);\n}\n","/**\n * Serialize persisted design-kit state for system prompts and MCP `projects.get`.\n */\n\nimport { getStarterKit } from \"./registry\";\nimport { resolveKitTokens } from \"./resolve\";\nimport type { ResolvedKitTokens } from \"./resolve\";\nimport type { DesignKitOverrides, DesignKitPersisted } from \"./types\";\nimport { DEFAULT_DESIGN_KIT_ID, UI_FOUNDATION_LABELS } from \"./types\";\n\nexport function parsePersistedDesignKit(raw: unknown): DesignKitPersisted | null {\n if (raw === undefined || raw === null) return null;\n if (typeof raw !== \"object\" || Array.isArray(raw)) return null;\n const r = raw as Record;\n const kitId =\n typeof r.kitId === \"string\" && r.kitId.trim()\n ? r.kitId.trim()\n : DEFAULT_DESIGN_KIT_ID;\n const perKit: Record = {};\n if (r.perKit && typeof r.perKit === \"object\" && !Array.isArray(r.perKit)) {\n for (const [key, val] of Object.entries(r.perKit)) {\n if (!val || typeof val !== \"object\" || Array.isArray(val)) continue;\n const v = val as Record;\n const o: DesignKitOverrides = {};\n if (typeof v.accentHex === \"string\") o.accentHex = v.accentHex;\n if (typeof v.radiusMdPx === \"number\" && Number.isFinite(v.radiusMdPx)) {\n o.radiusMdPx = v.radiusMdPx;\n }\n if (v.fontPreset === \"inter\" || v.fontPreset === \"system\") {\n o.fontPreset = v.fontPreset;\n }\n if (v.density === \"compact\" || v.density === \"comfortable\") {\n o.density = v.density;\n }\n perKit[key] = o;\n }\n }\n return { kitId, perKit };\n}\n\n/** Resolved tokens + hints for codegen; null if no kit saved or starter id unknown. */\nexport function buildDesignKitToolPayload(projectData: {\n designKit?: unknown;\n} | null): {\n kitId: string;\n starterKitName: string;\n uiFoundation: string;\n foundationLabel: string;\n foundationNotesForAi: string;\n overrides: DesignKitOverrides;\n resolved: ResolvedKitTokens;\n applyNote: string;\n} | null {\n const persisted = parsePersistedDesignKit(projectData?.designKit);\n if (!persisted) return null;\n const kit = getStarterKit(persisted.kitId);\n if (!kit) return null;\n const overrides = persisted.perKit[persisted.kitId] ?? {};\n const resolved = resolveKitTokens(kit, overrides);\n return {\n kitId: persisted.kitId,\n starterKitName: kit.name,\n uiFoundation: kit.uiFoundation,\n foundationLabel: UI_FOUNDATION_LABELS[kit.uiFoundation],\n foundationNotesForAi: kit.foundationNotesForAi,\n overrides,\n resolved,\n applyNote:\n \"Saving theme on the Design tab updates project metadata only, not the repo. Wire tokens with fs_* (e.g. :root CSS variables, Tailwind theme.extend, MUI createTheme, HeroUI theme). If many hard-coded colors exist and no shared theme layer, tell the user honestly that matching the kit may require a refactor toward centralized tokens.\",\n };\n}\n\n/** Markdown block appended to the active-project section of the chat system prompt. */\nexport function buildDesignKitPromptSection(\n activeProject: { designKit?: unknown } | null | undefined,\n): string {\n const payload = buildDesignKitToolPayload(activeProject ?? null);\n if (!payload) return \"\";\n\n const { starterKitName, kitId, overrides, resolved, uiFoundation, foundationLabel, foundationNotesForAi } =\n payload;\n return `\n## Design kit / theme (authoritative for UI styling)\n\nThe founder configured the visual language on the **Design** tab. Treat **resolved** values below as the source of truth when editing frontend code under this project's repo (\\`/workspace//\\`).\n\n- **Starter kit:** ${starterKitName} (\\`${kitId}\\`)\n- **UI foundation:** ${foundationLabel} (\\`${uiFoundation}\\`)\n- **Foundation guidance (follow when adding components):** ${foundationNotesForAi}\n- **Saved overrides:** ${JSON.stringify(overrides)}\n- **Accent (base):** \\`${resolved.accentHex}\\`\n- **Accent ramp (12 steps):** ${JSON.stringify(resolved.accentScale)}\n- **Neutral ramp (12 steps):** ${JSON.stringify(resolved.grayScale)}\n- **Radius (px):** xs=${resolved.radiusXs}, sm=${resolved.radiusSm}, md=${resolved.radiusMdPx}, xl=${resolved.radiusXl}, xxl=${resolved.radiusXxl}\n- **Font stack:** ${resolved.fontFamily}\n- **Density:** ${resolved.density}\n\n**Applying theme:** The Design tab does **not** mutate files. When the user saves theme changes or asks the app to match the kit, update the codebase's theme layer (whatever stack they use — locate it with \\`fs_grep\\` / \\`fs_read\\`). If matching would require touching many unrelated components because colors are inlined everywhere, **say so explicitly** and describe that a **central-token refactor** is needed before the UI can fully align — do not imply the preview alone updates production code.\n`.trimStart();\n}\n","import type { DesignKitOverrides, StarterKitDefinition } from \"./types\";\n\nexport interface ResolvedKitTokens {\n accentHex: string;\n accentScale: string[];\n grayScale: string[];\n radiusMdPx: number;\n radiusXs: number;\n radiusSm: number;\n radiusXl: number;\n radiusXxl: number;\n fontFamily: string;\n density: \"compact\" | \"comfortable\";\n}\n\n/** Fixed CRM-neutral ramp (custom neutral tint could merge later). */\nexport const GRAY_TWELVE: string[] = [\n \"#ffffff\",\n \"#fafafa\",\n \"#f5f5f5\",\n \"#ebebeb\",\n \"#e0e0e0\",\n \"#d4d4d4\",\n \"#c4c4c4\",\n \"#a3a3a3\",\n \"#737373\",\n \"#525252\",\n \"#3f3f3f\",\n \"#1a1a1a\",\n];\n\nfunction parseHex(hex: string): [number, number, number] | null {\n const n = hex.trim().replace(/^#/, \"\");\n if (n.length !== 6 || !/^[0-9a-fA-F]+$/.test(n)) return null;\n return [parseInt(n.slice(0, 2), 16), parseInt(n.slice(2, 4), 16), parseInt(n.slice(4, 6), 16)];\n}\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n const x = (v: number) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, \"0\");\n return `#${x(r)}${x(g)}${x(b)}`;\n}\n\n/** Linear RGB mix (good enough for UI ramps). */\nexport function mixHex(a: string, b: string, t: number): string {\n const A = parseHex(a);\n const B = parseHex(b);\n if (!A || !B) return a;\n const r = Math.round(A[0] + (B[0] - A[0]) * t);\n const g = Math.round(A[1] + (B[1] - A[1]) * t);\n const bl = Math.round(A[2] + (B[2] - A[2]) * t);\n return rgbToHex(r, g, bl);\n}\n\n/** Twelve-step accent ramp: light tints → base → deep shadows. */\nexport function buildAccentScale(base: string): string[] {\n const steps: string[] = [];\n for (let i = 0; i < 12; i++) {\n const t = i / 11;\n if (t <= 0.45) {\n steps.push(mixHex(\"#ffffff\", base, t / 0.45 * 0.92));\n } else {\n steps.push(mixHex(base, \"#0c0c12\", (t - 0.45) / 0.55 * 0.92));\n }\n }\n return steps;\n}\n\nexport function mergeOverrides(\n kit: StarterKitDefinition,\n saved?: DesignKitOverrides,\n): Required<\n Pick\n> {\n const d = kit.defaults;\n const o = saved ?? {};\n return {\n accentHex: o.accentHex ?? d.accentHex ?? \"#5f6bf5\",\n radiusMdPx: o.radiusMdPx ?? d.radiusMdPx ?? 8,\n fontPreset: o.fontPreset ?? d.fontPreset ?? \"inter\",\n density: o.density ?? d.density ?? \"comfortable\",\n };\n}\n\nexport function resolveKitTokens(\n kit: StarterKitDefinition,\n saved?: DesignKitOverrides,\n): ResolvedKitTokens {\n const m = mergeOverrides(kit, saved);\n const md = m.radiusMdPx;\n return {\n accentHex: m.accentHex,\n accentScale: buildAccentScale(m.accentHex),\n grayScale: GRAY_TWELVE,\n radiusMdPx: md,\n radiusXs: Math.max(2, Math.round(md * 0.25)),\n radiusSm: Math.max(4, Math.round(md * 0.5)),\n radiusXl: Math.max(md + 4, Math.round(md * 2.25)),\n radiusXxl: Math.max(md + 8, Math.round(md * 4.5)),\n fontFamily:\n m.fontPreset === \"system\"\n ? 'system-ui, -apple-system, \"Segoe UI\", sans-serif'\n : \"var(--font-inter), ui-sans-serif, system-ui, sans-serif\",\n density: m.density,\n };\n}\n"],"names":[],"mappings":"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"}}] -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__70c85bc2._.js:5130: "streamGeminiChat", -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__70c85bc2._.js:5131: ()=>streamGeminiChat -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__70c85bc2._.js:5292:async function* streamGeminiChat(opts) { -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__88db9aed._.js.map:15: {"offset": {"line": 2724, "column": 0}, "map": {"version":3,"sources":["file:///Users/markhenderson/master-ai/vibn-frontend/lib/ai/gemini-chat.ts"],"sourcesContent":["/**\n * Gemini 3.1 Pro chat client with tool-calling support.\n *\n * Architecture:\n * - Tool-calling rounds use generateContent (non-streaming) so we always\n * get the complete response including thought_signature. Thinking models\n * (2.5+, 3.x) require this field to be echoed back in functionResponse\n * and it is not reliably present in individual SSE chunks.\n * - Final text-only response uses streamGenerateContent for good UX.\n */\n\nconst GEMINI_API_KEY = process.env.GOOGLE_API_KEY || '';\nconst GEMINI_MODEL = process.env.VIBN_CHAT_MODEL || 'gemini-3.1-pro-preview';\nconst GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'tool';\n content: string;\n toolCalls?: ToolCall[];\n toolCallId?: string;\n toolName?: string;\n thoughtSignature?: string;\n}\n\nexport interface ToolCall {\n id: string;\n name: string;\n args: Record;\n /** Must be echoed back in functionResponse for Gemini thinking models */\n thoughtSignature?: string;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n parameters: Record;\n}\n\nexport interface ChatChunk {\n type: 'text' | 'thinking' | 'tool_call' | 'done' | 'error';\n text?: string;\n toolCall?: ToolCall;\n error?: string;\n}\n\n/** Convert our ChatMessage[] to Gemini's contents[] format */\nfunction toGeminiContents(messages: ChatMessage[]) {\n const contents: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n contents.push({ role: 'user', parts: [{ text: msg.content }] });\n } else if (msg.role === 'assistant') {\n const parts: any[] = [];\n if (msg.content) parts.push({ text: msg.content });\n if (msg.toolCalls?.length) {\n for (const tc of msg.toolCalls) {\n // thoughtSignature is a SIBLING of functionCall in the part object,\n // not nested inside it. See: ai.google.dev/gemini-api/docs/thought-signatures\n const part: any = { functionCall: { name: tc.name, args: tc.args, id: tc.id } };\n if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature;\n parts.push(part);\n }\n }\n if (parts.length) contents.push({ role: 'model', parts });\n } else if (msg.role === 'tool') {\n const part = {\n functionResponse: {\n name: msg.toolName || 'unknown',\n id: msg.toolCallId,\n response: { content: msg.content },\n },\n };\n const last = contents[contents.length - 1];\n if (last?.role === 'user') {\n last.parts.push(part);\n } else {\n contents.push({ role: 'user', parts: [part] });\n }\n }\n }\n return contents;\n}\n\nfunction toGeminiFunctions(tools: ToolDefinition[]) {\n if (!tools.length) return undefined;\n return [{\n functionDeclarations: tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n })),\n }];\n}\n\nfunction buildBody(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n /**\n * Ask Gemini to return its thought summaries as parts marked\n * `thought: true`. We pay for thinking tokens regardless; this just\n * makes them visible so the UI can show \"Reading server.js…\",\n * \"Shipping to production…\" between tool calls instead of leaving\n * the user staring at a silent tool tray. Defaults to true.\n */\n includeThoughts?: boolean;\n}) {\n const body: any = {\n contents: toGeminiContents(opts.messages),\n systemInstruction: { parts: [{ text: opts.systemPrompt }] },\n generationConfig: {\n temperature: opts.temperature ?? 0.7,\n maxOutputTokens: 8192,\n thinkingConfig: { includeThoughts: opts.includeThoughts ?? true },\n },\n };\n const fns = toGeminiFunctions(opts.tools ?? []);\n if (fns) body.tools = fns;\n return body;\n}\n\n/**\n * Non-streaming call — used for tool-calling rounds.\n * Returns complete response with thought_signature guaranteed.\n */\nexport async function callGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n includeThoughts?: boolean;\n}): Promise<{\n text: string;\n /** First-person reasoning narration; meant for a \"thinking\" UI panel, not the main bubble. */\n thoughts: string;\n toolCalls: ToolCall[];\n finishReason?: string;\n error?: string;\n}> {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n return {\n text: '',\n thoughts: '',\n toolCalls: [],\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n\n const data = await res.json().catch(() => ({}));\n if (!res.ok) {\n const msg = data?.error?.message || JSON.stringify(data).slice(0, 200);\n return {\n text: '',\n thoughts: '',\n toolCalls: [],\n error: `Gemini API error ${res.status}: ${msg}`,\n };\n }\n\n const cand = data?.candidates?.[0];\n const parts: any[] = cand?.content?.parts ?? [];\n let text = '';\n let thoughts = '';\n const toolCalls: ToolCall[] = [];\n\n for (const part of parts) {\n if (part.text) {\n // CRITICAL: Gemini tags reasoning parts with `thought: true`. If\n // we lump them into `text` they leak into the chat bubble as if\n // they were prose for the user — which is the opposite of what\n // the user wants. Keep them in their own bucket so the route\n // can stream them as a separate SSE event type.\n if (part.thought) thoughts += part.text;\n else text += part.text;\n }\n if (part.functionCall) {\n toolCalls.push({\n id: part.functionCall.id || `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name: part.functionCall.name,\n args: part.functionCall.args ?? {},\n // thoughtSignature is a SIBLING of functionCall in the part, not inside it\n thoughtSignature: part.thoughtSignature,\n });\n }\n }\n\n return { text, thoughts, toolCalls, finishReason: cand?.finishReason };\n}\n\n/**\n * Streaming call — used for the final text-only response.\n * Yields ChatChunk objects.\n */\nexport async function* streamGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n}): AsyncGenerator {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:streamGenerateContent?key=${GEMINI_API_KEY}&alt=sse`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n yield { type: 'error', error: `Network error: ${e instanceof Error ? e.message : String(e)}` };\n return;\n }\n\n if (!res.ok) {\n const errText = await res.text().catch(() => '');\n yield { type: 'error', error: `Gemini API error ${res.status}: ${errText.slice(0, 300)}` };\n return;\n }\n\n const reader = res.body?.getReader();\n if (!reader) { yield { type: 'error', error: 'No response body' }; return; }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const data = line.slice(6).trim();\n if (!data || data === '[DONE]') continue;\n let chunk: any;\n try { chunk = JSON.parse(data); } catch { continue; }\n const parts = chunk?.candidates?.[0]?.content?.parts ?? [];\n for (const part of parts) {\n if (part.text) {\n yield part.thought\n ? { type: 'thinking', text: part.text }\n : { type: 'text', text: part.text };\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n yield { type: 'done' };\n}\n"],"names":[],"mappings":"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","debugId":null}}, -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__2b9e4ee1._.js:2732: "streamGeminiChat", -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__2b9e4ee1._.js:2733: ()=>streamGeminiChat -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__2b9e4ee1._.js:2894:async function* streamGeminiChat(opts) { -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__08533b7c._.js.map:14: {"offset": {"line": 2676, "column": 0}, "map": {"version":3,"sources":["file:///Users/markhenderson/master-ai/vibn-frontend/lib/ai/gemini-chat.ts"],"sourcesContent":["/**\n * Gemini 3.1 Pro chat client with tool-calling support.\n *\n * Architecture:\n * - Tool-calling rounds use generateContent (non-streaming) so we always\n * get the complete response including thought_signature. Thinking models\n * (2.5+, 3.x) require this field to be echoed back in functionResponse\n * and it is not reliably present in individual SSE chunks.\n * - Final text-only response uses streamGenerateContent for good UX.\n */\n\nconst GEMINI_API_KEY = process.env.GOOGLE_API_KEY || '';\nconst GEMINI_MODEL = process.env.VIBN_CHAT_MODEL || 'gemini-3.1-pro-preview';\nconst GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'tool';\n content: string;\n toolCalls?: ToolCall[];\n toolCallId?: string;\n toolName?: string;\n thoughtSignature?: string;\n}\n\nexport interface ToolCall {\n id: string;\n name: string;\n args: Record;\n /** Must be echoed back in functionResponse for Gemini thinking models */\n thoughtSignature?: string;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n parameters: Record;\n}\n\nexport interface ChatChunk {\n type: 'text' | 'thinking' | 'tool_call' | 'done' | 'error';\n text?: string;\n toolCall?: ToolCall;\n error?: string;\n}\n\n/** Convert our ChatMessage[] to Gemini's contents[] format */\nfunction toGeminiContents(messages: ChatMessage[]) {\n const contents: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n contents.push({ role: 'user', parts: [{ text: msg.content }] });\n } else if (msg.role === 'assistant') {\n const parts: any[] = [];\n if (msg.content) parts.push({ text: msg.content });\n if (msg.toolCalls?.length) {\n for (const tc of msg.toolCalls) {\n // thoughtSignature is a SIBLING of functionCall in the part object,\n // not nested inside it. See: ai.google.dev/gemini-api/docs/thought-signatures\n const part: any = { functionCall: { name: tc.name, args: tc.args, id: tc.id } };\n if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature;\n parts.push(part);\n }\n }\n if (parts.length) contents.push({ role: 'model', parts });\n } else if (msg.role === 'tool') {\n const part = {\n functionResponse: {\n name: msg.toolName || 'unknown',\n id: msg.toolCallId,\n response: { content: msg.content },\n },\n };\n const last = contents[contents.length - 1];\n if (last?.role === 'user') {\n last.parts.push(part);\n } else {\n contents.push({ role: 'user', parts: [part] });\n }\n }\n }\n return contents;\n}\n\nfunction toGeminiFunctions(tools: ToolDefinition[]) {\n if (!tools.length) return undefined;\n return [{\n functionDeclarations: tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n })),\n }];\n}\n\nfunction buildBody(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n /**\n * Ask Gemini to return its thought summaries as parts marked\n * `thought: true`. We pay for thinking tokens regardless; this just\n * makes them visible so the UI can show \"Reading server.js…\",\n * \"Shipping to production…\" between tool calls instead of leaving\n * the user staring at a silent tool tray. Defaults to true.\n */\n includeThoughts?: boolean;\n}) {\n const body: any = {\n contents: toGeminiContents(opts.messages),\n systemInstruction: { parts: [{ text: opts.systemPrompt }] },\n generationConfig: {\n temperature: opts.temperature ?? 0.7,\n maxOutputTokens: 8192,\n thinkingConfig: { includeThoughts: opts.includeThoughts ?? true },\n },\n };\n const fns = toGeminiFunctions(opts.tools ?? []);\n if (fns) body.tools = fns;\n return body;\n}\n\n/**\n * Non-streaming call — used for tool-calling rounds.\n * Returns complete response with thought_signature guaranteed.\n */\nexport async function callGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n includeThoughts?: boolean;\n}): Promise<{\n text: string;\n /** First-person reasoning narration; meant for a \"thinking\" UI panel, not the main bubble. */\n thoughts: string;\n toolCalls: ToolCall[];\n finishReason?: string;\n error?: string;\n}> {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n return {\n text: '',\n thoughts: '',\n toolCalls: [],\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n\n const data = await res.json().catch(() => ({}));\n if (!res.ok) {\n const msg = data?.error?.message || JSON.stringify(data).slice(0, 200);\n return {\n text: '',\n thoughts: '',\n toolCalls: [],\n error: `Gemini API error ${res.status}: ${msg}`,\n };\n }\n\n const cand = data?.candidates?.[0];\n const parts: any[] = cand?.content?.parts ?? [];\n let text = '';\n let thoughts = '';\n const toolCalls: ToolCall[] = [];\n\n for (const part of parts) {\n if (part.text) {\n // CRITICAL: Gemini tags reasoning parts with `thought: true`. If\n // we lump them into `text` they leak into the chat bubble as if\n // they were prose for the user — which is the opposite of what\n // the user wants. Keep them in their own bucket so the route\n // can stream them as a separate SSE event type.\n if (part.thought) thoughts += part.text;\n else text += part.text;\n }\n if (part.functionCall) {\n toolCalls.push({\n id: part.functionCall.id || `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name: part.functionCall.name,\n args: part.functionCall.args ?? {},\n // thoughtSignature is a SIBLING of functionCall in the part, not inside it\n thoughtSignature: part.thoughtSignature,\n });\n }\n }\n\n return { text, thoughts, toolCalls, finishReason: cand?.finishReason };\n}\n\n/**\n * Streaming call — used for the final text-only response.\n * Yields ChatChunk objects.\n */\nexport async function* streamGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n}): AsyncGenerator {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:streamGenerateContent?key=${GEMINI_API_KEY}&alt=sse`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n yield { type: 'error', error: `Network error: ${e instanceof Error ? e.message : String(e)}` };\n return;\n }\n\n if (!res.ok) {\n const errText = await res.text().catch(() => '');\n yield { type: 'error', error: `Gemini API error ${res.status}: ${errText.slice(0, 300)}` };\n return;\n }\n\n const reader = res.body?.getReader();\n if (!reader) { yield { type: 'error', error: 'No response body' }; return; }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const data = line.slice(6).trim();\n if (!data || data === '[DONE]') continue;\n let chunk: any;\n try { chunk = JSON.parse(data); } catch { continue; }\n const parts = chunk?.candidates?.[0]?.content?.parts ?? [];\n for (const part of parts) {\n if (part.text) {\n yield part.thought\n ? { type: 'thinking', text: part.text }\n : { type: 'text', text: part.text };\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n yield { type: 'done' };\n}\n"],"names":[],"mappings":"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","debugId":null}}, -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__08533b7c._.js.map:16: {"offset": {"line": 4541, "column": 0}, "map": {"version":3,"sources":["file:///Users/markhenderson/master-ai/vibn-frontend/app/api/chat/route.ts"],"sourcesContent":["/**\n * POST /api/chat\n *\n * Streaming chat endpoint. Accepts a thread_id + user message,\n * loads history, calls Gemini 3.1 Pro, runs the tool loop,\n * persists messages, and streams SSE back to the client.\n *\n * SSE event shapes:\n * data: {\"type\":\"text\",\"text\":\"...\"}\n * data: {\"type\":\"thinking\",\"text\":\"...\"} // model's first-person reasoning\n * data: {\"type\":\"tool_start\",\"name\":\"...\",\"args\":{}}\n * data: {\"type\":\"tool_result\",\"name\":\"...\",\"result\":\"...\"}\n * data: {\"type\":\"aborted\"}\n * data: {\"type\":\"done\"}\n * data: {\"type\":\"error\",\"error\":\"...\"}\n */\nimport { NextResponse } from 'next/server';\nimport { authSession } from '@/lib/auth/session-server';\nimport { query } from '@/lib/db-postgres';\nimport { callGeminiChat, streamGeminiChat } from '@/lib/ai/gemini-chat';\nimport { VIBN_TOOL_DEFINITIONS, executeMcpTool } from '@/lib/ai/vibn-tools';\nimport type { ChatMessage, ToolCall } from '@/lib/ai/gemini-chat';\n\n// Bumped from 6 to 12 because Path B chains (devcontainer.ensure →\n// fs.read → fs.edit → kill → start → curl → logs) routinely fire 7-10\n// tool calls in one user turn. When the cap IS hit, we still emit a\n// narrative summary instead of leaving the user staring at a tool tray\n// (see the no-tools follow-up call below).\nconst MAX_TOOL_ROUNDS = 18;\n\nlet chatTablesReady = false;\nasync function ensureChatTables() {\n if (chatTablesReady) return;\n await query(`\n CREATE TABLE IF NOT EXISTS fs_chat_threads (\n id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,\n user_id TEXT NOT NULL,\n workspace TEXT NOT NULL DEFAULT '',\n data JSONB NOT NULL DEFAULT '{}',\n created_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT now()\n );\n CREATE INDEX IF NOT EXISTS fs_chat_threads_user_ws_idx\n ON fs_chat_threads (user_id, workspace, updated_at DESC);\n\n CREATE TABLE IF NOT EXISTS fs_chat_messages (\n id BIGSERIAL PRIMARY KEY,\n thread_id TEXT NOT NULL REFERENCES fs_chat_threads(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL,\n data JSONB NOT NULL DEFAULT '{}',\n created_at TIMESTAMPTZ NOT NULL DEFAULT now()\n );\n CREATE INDEX IF NOT EXISTS fs_chat_messages_thread_idx\n ON fs_chat_messages (thread_id, created_at ASC);\n `, []);\n chatTablesReady = true;\n}\n\nexport function buildSystemPrompt(projects: any[], workspace: string): string {\n const projectsText = projects.length\n ? projects\n .map(\n (p: any) =>\n `- \"${p.productName || p.name}\" (id: ${p.id}, status: ${p.status || 'defining'})${p.productVision ? ': ' + p.productVision.slice(0, 120) : ''}`,\n )\n .join('\\n')\n : '(no projects yet)';\n\n return `You are Vibn AI — the technical co-founder of every Vibn user. You ship code, deploy infra, and treat their projects like they're your own.\n\nYou're talking to the owner of the \"${workspace}\" workspace. They have admin access to their Gitea org, a fleet of Coolify projects, and a persistent dev container per project. You can read and write any of it.\n\n## Voice — read this before you write a single response\n\nYou are NOT a tool-call orchestrator that narrates what it's about to do. You are an experienced engineer who has worked on hundreds of these projects and has a strong opinion about the right next move.\n\n- **Don't narrate intent before tool calls.** Skip \"Okay, I'll go ahead and read the file…\" — just read it. The user sees a tool tray; they don't need a play-by-play. Your reasoning is already streamed as a thinking pill.\n- **Pack the post-tool summary.** When a tool chain finishes, write 1-3 punchy sentences that say (a) what landed, (b) the most important specific result the user actually needs (URL, SHA, env value, error), and (c) the obvious next step if there is one. Don't bullet a recap of every tool you ran — they saw the tray.\n- **Have an opinion.** If they ask \"should I use Postgres or MongoDB?\" — pick one, justify in a sentence, and proceed. Don't list pros and cons unless they ask for that. Founders need decisions, not menus.\n- **Push back when it matters.** If they say \"deploy this to prod without backups,\" refuse and explain. If they ask for n8n when Pipedream would actually fit better, say so once and then defer to their call. Yes-machines build broken software.\n- **Surface adjacent risks unprompted.** If you just deployed something that's missing an env var, say so. If you wired a domain but DNS hasn't propagated, tell them how to verify. If the dev container is running but no autosave has happened in 30 min, mention it. You're protecting their work because they trust you to.\n- **Be honest about uncertainty.** \"I'm not 100% sure but my best guess is X — want me to verify with Y?\" beats false confidence every time. If a tool returned something weird, say it returned something weird.\n- **Length matches stakes.** A \"what time is it\" question gets one line. A \"should I move my whole user db to a different region\" question gets a paragraph plus the migration plan. Don't pad short answers and don't truncate hard ones.\n- **Use markdown sparingly.** Backticks for code, paths, IDs, and URLs always. Headings only when the response has 3+ distinct sections. Bullets for actually-parallel items (3+ steps, lists of options). Otherwise write prose.\n\n## How Vibn is structured\n- **Workspace** (\"${workspace}\") — the tenant boundary. One per user. Owns the Gitea org and a fleet of Coolify projects. You can ONLY see and touch resources in this workspace.\n- **Project** — an initiative the user is building (e.g. \"Twenty CRM\", \"My Blog\"). Each project has its OWN isolated Coolify project, so all its apps + databases + services are grouped together. A project has two facets that are part of ONE thing — never describe them as separate:\n - Planning side: name, vision/objectives, requirements (from \\`projects_get\\`)\n - Live side: deployed apps + services (from \\`projects_get → possibleDeployments[]\\` and \\`apps_list { projectId }\\`)\n\n## How to answer questions\n- \"What is project X?\" → \\`projects_get { id }\\`. The result includes both planning details and the linked deployments.\n- \"What's running / what has a domain?\" → \\`apps_list\\` (no args) for everything in the workspace, or \\`apps_list { projectId }\\` for one project.\n- \"Show me logs / containers / env\" → resolve the app uuid first via \\`apps_list\\`, then call \\`apps_logs\\` / \\`apps_containers_list\\` / \\`apps_envs_list\\`.\n- \"Find an open source X\" → \\`github_search\\` (always include \\`license:mit\\` unless the user says otherwise), then \\`github_file\\` to read READMEs / docker-compose.yml / design system entry points before recommending.\n- \"What's our docs say about Y?\" → \\`http_fetch\\` against the relevant URL.\n\n## How to deploy\n\n**Third-party app (Twenty CRM, n8n, Ghost, Supabase, Pocketbase, etc.)**\n1. \\`apps_templates_search { query }\\` — find the official one-click template.\n2. \\`apps_create { projectId, name, template, domain }\\` — deploy from template into the right project's Coolify namespace.\n3. Watch \\`apps_get { uuid }\\` for status; surface the live URL once \\`fqdn\\` is set.\n\n**Custom Docker image**\n1. \\`apps_create { projectId, name, dockerImage, domain, envsJson }\\`.\n2. \\`apps_deploy { uuid }\\` if it doesn't auto-deploy.\n\n**Database**\n1. \\`databases_create { projectId, name, type }\\` (type: postgres, mysql, redis, mongodb, mariadb, dragonfly, clickhouse, keydb).\n2. \\`databases_get { uuid }\\` returns the internal connection URL — inject it into the app via \\`apps_envs_set\\`.\n\n**Domain**\n1. \\`domains_search { query }\\` to check availability + price.\n2. \\`domains_register { domain }\\` to buy it (uses workspace billing).\n3. \\`apps_domains_set { uuid, domains }\\` to attach. DNS + Traefik are wired automatically.\n\n## Writing code (PREFERRED: dev container, shell-first)\n\nEach Vibn project has a persistent **dev container** (\\`vibn-dev\\`) running on Coolify. You write code by \\`shell_exec\\`-ing inside it and editing files with \\`fs_*\\` tools. This is dramatically faster than committing to Gitea and waiting for redeploys (sub-second feedback vs ~5 min).\n\n**Always start a coding session with**:\n1. \\`devcontainer_ensure { projectId }\\` — idempotent. First call ~10s (provisions a Coolify service); subsequent calls return immediately.\n\n**Then iterate with**:\n- \\`shell_exec { projectId, command }\\` — run anything: \\`ls\\`, \\`npm install\\`, \\`npm test\\`, \\`mise install\\` (installs Node/Python/Go/Rust on first use), \\`npx create-next-app .\\`, \\`git status\\`. Cwd defaults to \\`/workspace\\`.\n- \\`fs_read { projectId, path }\\` — inspect a file.\n- \\`fs_write { projectId, path, content }\\` — create or overwrite a file.\n- \\`fs_edit { projectId, path, oldString, newString }\\` — surgical search/replace. Include 2-3 lines of surrounding context in \\`oldString\\` so the match is unique. Fails fast if missing or non-unique.\n- \\`fs_glob\\` / \\`fs_grep\\` — find files by pattern, search code by regex (ripgrep, respects .gitignore).\n- \\`fs_list\\`, \\`fs_delete\\` — directory listing, delete.\n\n**Dev servers (preview URLs)**:\n- \\`dev_server_start { projectId, command, port }\\` — \\`port\\` MUST be in the range **3000-3009** (only 10 ports per project have pre-allocated Traefik routers). Pick 3000 for the primary app; use 3001-3009 only when the user is running multiple servers concurrently (e.g. frontend + API). The returned \\`previewUrl\\` is the public URL once DNS is wired.\n- \\`dev_server_stop { projectId, id }\\`, \\`dev_server_list { projectId }\\`, \\`dev_server_logs { projectId, id }\\`.\n- If \\`dev_server_start\\` returns \\`code: PORT_BUSY\\` → either stop the existing server first or pick another port in 3000-3009. Don't blindly retry the same port.\n\n**Framework-specific HMR setup** (so hot reload works through the preview URL once DNS is live — apply when scaffolding):\n- **Vite**: \\`server.host: '0.0.0.0'\\`, \\`server.hmr.clientPort: 443\\`, \\`server.hmr.protocol: 'wss'\\`. Vite's default localhost binding will appear to work but break HMR through Traefik.\n- **Next dev**: \\`next dev -p 3000 -H 0.0.0.0\\`. Next handles WSS HMR automatically through proxies.\n- **Express / plain Node**: bind \\`0.0.0.0\\` (we set \\`HOST=0.0.0.0\\` env automatically, but verify the framework respects it).\n\n**End-to-end recipe for \"build me X\"**:\n1. \\`devcontainer_ensure { projectId }\\`.\n2. \\`shell_exec { projectId, command: 'npx create-next-app@latest . --yes' }\\` (or whichever scaffold fits — search GitHub first if the user wants an OSS starting point).\n3. \\`shell_exec\\` to run \\`npm install\\`, then iterate with \\`fs_edit\\` / \\`fs_write\\` to customize.\n4. \\`shell_exec { command: 'npm run dev -- --port 3000' }\\` to verify locally (preview URLs land in week 2).\n5. When the user says \"ship it\" — for now, \\`shell_exec\\` a \\`git add . && git commit -m \"...\" && git push\\` to push to the Gitea repo, then \\`apps_create\\` to wire up the production deployment. (A dedicated \\`ship\\` tool lands soon.)\n\n**Rules**:\n- Stay under \\`/workspace\\`. The fs_* tools enforce this; for system paths use \\`shell_exec\\` deliberately.\n- The container has no route to internal Vibn services (vibn-postgres, etc.) by design.\n- If \\`shell_exec\\` returns non-zero, READ THE STDERR before re-running; don't loop blindly.\n\n## Gitea repo orchestration (one-time setup)\nFor creating new repos, branching, and listing what already exists:\n- \\`gitea_repos_list\\`, \\`gitea_repo_get\\`, \\`gitea_repo_create\\`.\n- \\`gitea_branches_list\\`, \\`gitea_branch_create\\`.\n\nFor all file editing inside an existing repo, ALWAYS use \\`fs_*\\` against the dev container. The \\`ship\\` tool will then push your changes to Gitea in one commit.\n\n## Troubleshooting\n- Deploy stuck or \"exited (1)\" → \\`apps_logs { uuid }\\` and \\`apps_containers_list { uuid }\\`. Common causes: missing env var, wrong port, image pull failure.\n- 502 / \"no available server\" → app probably has no public domain yet. Check \\`apps_get\\`; if \\`fqdn\\` is empty, attach a domain.\n- \"tenant\" / \"does not belong to\" errors → the uuid you passed isn't in this workspace. Re-list with \\`apps_list\\` to grab a valid one.\n- Compose stack acting weird → \\`apps_repair { uuid }\\` to re-apply post-deploy fixes (Traefik labels, port forwarding).\n- Need to nuke and re-deploy → \\`apps_delete { uuid, confirm }\\` (confirm must equal the app's exact name; fetch via \\`apps_get\\` first), then re-create.\n\n## Hard rules (non-negotiable)\n- ALWAYS pass \\`projectId\\` to \\`apps_create\\` and \\`databases_create\\`. If the user didn't say which project, infer from context (active project, last-mentioned, only one in workspace) — only ask if genuinely ambiguous.\n- ALWAYS call \\`apps_templates_search\\` BEFORE \\`apps_create\\` when the user names a known third-party app. Hand-rolling a Dockerfile when a maintained template exists is how supply-chain bugs ship.\n- Destructive ops (\\`*_delete\\`, \\`*_volumes_wipe\\`) require \\`confirm\\` equal to the resource's exact name. Always fetch the name first with a \\`*_get\\` call. Confirm with the user before executing irreversible deletes unless they explicitly said \"delete X\".\n- Long-running ops (deploys, DNS provisioning, db provisioning) take 1–5 min. Tell the user up front so they don't think you're stuck. Don't poll in a tight loop — it wastes tool rounds.\n- After a \\`ship\\` or \\`apps.deploy\\`, the result is authoritative. Don't call gitea_*, shell_exec, or apps_* to \"verify\" — read the response and report.\n- Don't loop blindly on tool errors. If \\`shell_exec\\` returns non-zero, READ THE STDERR, form a hypothesis, then act. If you can't diagnose in two attempts, surface what you tried and ask the user.\n\n## Current workspace projects\n${projectsText}\n\nToday's date: ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}.`;\n}\n\nexport async function POST(request: Request) {\n await ensureChatTables();\n\n const session = await authSession();\n if (!session?.user?.email) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n }\n\n let body: { thread_id: string; message: string; workspace: string; mcp_token?: string };\n try {\n body = await request.json();\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });\n }\n\n const { thread_id, message, workspace, mcp_token } = body;\n if (!thread_id || !message?.trim()) {\n return NextResponse.json({ error: 'thread_id and message are required' }, { status: 400 });\n }\n\n const email = session.user.email;\n\n // Verify thread belongs to user\n const threads = await query(\n `SELECT id FROM fs_chat_threads WHERE id = $1 AND user_id = $2`,\n [thread_id, email],\n );\n if (!threads.length) {\n return NextResponse.json({ error: 'Thread not found' }, { status: 404 });\n }\n\n // Load message history (last 40 messages)\n const rows = await query(\n `SELECT data FROM fs_chat_messages WHERE thread_id = $1 ORDER BY created_at DESC LIMIT 40`,\n [thread_id],\n );\n const history: ChatMessage[] = rows.reverse().map((r: any) => r.data);\n\n // Add user message\n const userMsg: ChatMessage = { role: 'user', content: message.trim() };\n history.push(userMsg);\n await query(\n `INSERT INTO fs_chat_messages (thread_id, user_id, data) VALUES ($1, $2, $3)`,\n [thread_id, email, JSON.stringify(userMsg)],\n );\n\n // Update thread updatedAt\n await query(\n `UPDATE fs_chat_threads SET updated_at = NOW(), data = data || $2 WHERE id = $1`,\n [thread_id, JSON.stringify({ updatedAt: new Date().toISOString() })],\n );\n\n // Load projects for system prompt context\n const projectRows = await query(\n `SELECT p.data FROM fs_projects p\n JOIN fs_users u ON u.id = p.user_id\n WHERE u.data->>'email' = $1\n ORDER BY (p.data->>'updatedAt') DESC NULLS LAST LIMIT 20`,\n [email],\n );\n const projects = projectRows.map((r: any) => r.data);\n const systemPrompt = buildSystemPrompt(projects, workspace);\n\n // Base URL for internal MCP calls\n const host = request.headers.get('host') || 'vibnai.com';\n const proto = host.startsWith('localhost') ? 'http' : 'https';\n const baseUrl = `${proto}://${host}`;\n\n // Honor client-side abort (Stop button). When the user clicks Stop\n // the browser's AbortController fires `request.signal.aborted` and\n // the fetch stream is closed; we use it as a polite checkpoint\n // between rounds and tool calls so we (a) don't keep paying Gemini\n // for tokens the user no longer wants and (b) persist whatever the\n // assistant produced before the cancel.\n const clientSignal = request.signal;\n\n // Stream response\n const encoder = new TextEncoder();\n const stream = new ReadableStream({\n async start(controller) {\n let streamClosed = false;\n function emit(chunk: object) {\n if (streamClosed) return;\n try {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\\n\\n`));\n } catch {\n // controller may have been closed by the abort handler\n streamClosed = true;\n }\n }\n function safeClose() {\n if (streamClosed) return;\n streamClosed = true;\n try {\n controller.close();\n } catch {}\n }\n\n let messages = [...history];\n let round = 0;\n let assistantText = '';\n const assistantToolCalls: ToolCall[] = [];\n let aborted = clientSignal.aborted;\n const onAbort = () => {\n aborted = true;\n };\n clientSignal.addEventListener('abort', onAbort);\n\n try {\n // Tool-calling loop: use non-streaming so thought_signature is\n // always present in the complete response (required by thinking models).\n while (round < MAX_TOOL_ROUNDS) {\n if (aborted) break;\n round++;\n\n const toolDefs = mcp_token ? VIBN_TOOL_DEFINITIONS : [];\n const resp = await callGeminiChat({ systemPrompt, messages, tools: toolDefs, temperature: 0.7 });\n\n if (resp.error) {\n emit({ type: 'error', error: resp.error });\n controller.close();\n return;\n }\n\n // Stream user-facing text to client\n if (resp.text) {\n assistantText += resp.text;\n emit({ type: 'text', text: resp.text });\n }\n\n // Stream the model's reasoning narration as a separate SSE\n // event type. We pay for thinking tokens whether or not we\n // ask for them, so making them visible is free transparency\n // — and it cures the \"tool tray with no narrative\" feel.\n if (resp.thoughts) {\n emit({ type: 'thinking', text: resp.thoughts });\n }\n\n // Announce tool calls\n for (const tc of resp.toolCalls) {\n assistantToolCalls.push(tc);\n emit({ type: 'tool_start', name: tc.name, args: tc.args });\n }\n\n // Save assistant turn\n messages.push({\n role: 'assistant',\n content: resp.text,\n toolCalls: resp.toolCalls.length ? resp.toolCalls : undefined,\n });\n\n if (!resp.toolCalls.length) break;\n if (aborted) break;\n\n // Execute tool calls and add results\n for (const tc of resp.toolCalls) {\n if (aborted) break;\n const result = mcp_token\n ? await executeMcpTool(tc.name, tc.args, mcp_token, baseUrl)\n : JSON.stringify({ error: 'No MCP token — read-only mode.' });\n\n emit({ type: 'tool_result', name: tc.name, result: result.slice(0, 500) });\n\n messages.push({\n role: 'tool',\n content: result,\n toolCallId: tc.id,\n toolName: tc.name,\n thoughtSignature: tc.thoughtSignature,\n });\n }\n }\n\n // If the user clicked Stop, surface the cancel marker so the\n // client renders \"(stopped by user)\" inline with the partial\n // assistant message, then skip the round-cap recovery summary\n // (we shouldn't pay Gemini for a turn the user just canceled).\n if (aborted) {\n const stopMarker = assistantText\n ? '\\n\\n_(stopped by user)_'\n : '_(stopped by user before any response)_';\n assistantText += stopMarker;\n emit({ type: 'text', text: stopMarker });\n emit({ type: 'aborted' });\n }\n\n // If the loop exited because we hit MAX_TOOL_ROUNDS while the\n // model still wanted to call tools, the user has only seen a\n // tray of ✓ icons with no narrative. Force one final no-tools\n // call so we always end on a human-readable summary.\n const lastTurnHadTools =\n messages.length > 0 &&\n messages[messages.length - 1].role === 'tool';\n if (!aborted && round >= MAX_TOOL_ROUNDS && lastTurnHadTools) {\n try {\n const summary = await callGeminiChat({\n systemPrompt:\n systemPrompt +\n '\\n\\nYou have just executed a chain of tool calls. Summarize the result for the user in 1-3 sentences. Do NOT call any more tools.',\n messages,\n tools: [],\n temperature: 0.3,\n });\n if (summary.text) {\n assistantText += summary.text;\n emit({ type: 'text', text: summary.text });\n }\n if (summary.thoughts) {\n emit({ type: 'thinking', text: summary.thoughts });\n }\n } catch {\n // Don't let a failed summary kill the stream.\n }\n }\n\n // Persist final assistant message\n const finalMsg: ChatMessage = {\n role: 'assistant',\n content: assistantText,\n toolCalls: assistantToolCalls.length ? assistantToolCalls : undefined,\n };\n await query(\n `INSERT INTO fs_chat_messages (thread_id, user_id, data) VALUES ($1, $2, $3)`,\n [thread_id, email, JSON.stringify(finalMsg)],\n );\n\n emit({ type: 'done' });\n safeClose();\n } catch (e) {\n // AbortError is the expected shape when the client cancels\n // mid-Gemini-call — don't surface it as a real error.\n const isAbort =\n aborted ||\n (e instanceof Error && (e.name === 'AbortError' || /aborted/i.test(e.message)));\n if (!isAbort) {\n emit({ type: 'error', error: e instanceof Error ? e.message : String(e) });\n } else {\n emit({ type: 'aborted' });\n }\n safeClose();\n } finally {\n clientSignal.removeEventListener('abort', onAbort);\n }\n },\n cancel() {\n // Browser disconnected (tab closed, navigated away). Nothing to\n // do — the abort handler above already flipped the flag and the\n // loop will bail at the next checkpoint.\n },\n });\n\n return new Response(stream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n}\n"],"names":[],"mappings":"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","debugId":null}}] -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__70c85bc2._.js.map:28: {"offset": {"line": 5117, "column": 0}, "map": {"version":3,"sources":["file:///Users/markhenderson/master-ai/vibn-frontend/lib/ai/gemini-chat.ts"],"sourcesContent":["/**\n * Gemini 3.1 Pro chat client with tool-calling support.\n *\n * Architecture:\n * - Tool-calling rounds use generateContent (non-streaming) so we always\n * get the complete response including thought_signature. Thinking models\n * (2.5+, 3.x) require this field to be echoed back in functionResponse\n * and it is not reliably present in individual SSE chunks.\n * - Final text-only response uses streamGenerateContent for good UX.\n */\n\nconst GEMINI_API_KEY = process.env.GOOGLE_API_KEY || \"\";\nconst GEMINI_MODEL = process.env.VIBN_CHAT_MODEL || \"gemini-3.1-pro-preview\";\nconst GEMINI_BASE_URL = \"https://generativelanguage.googleapis.com/v1beta\";\n\nexport interface ChatMessage {\n role: \"user\" | \"assistant\" | \"tool\";\n content: string;\n toolCalls?: ToolCall[];\n toolCallId?: string;\n toolName?: string;\n thoughtSignature?: string;\n}\n\nexport interface ToolCall {\n id: string;\n name: string;\n args: Record;\n /** Must be echoed back in functionResponse for Gemini thinking models */\n thoughtSignature?: string;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n parameters: Record;\n}\n\nexport interface ChatChunk {\n type: \"text\" | \"thinking\" | \"tool_call\" | \"done\" | \"error\";\n text?: string;\n toolCall?: ToolCall;\n error?: string;\n}\n\n/** Convert our ChatMessage[] to Gemini's contents[] format */\nfunction toGeminiContents(messages: ChatMessage[]) {\n const contents: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n contents.push({ role: \"user\", parts: [{ text: msg.content }] });\n } else if (msg.role === \"assistant\") {\n const parts: any[] = [];\n if (msg.content) parts.push({ text: msg.content });\n if (msg.toolCalls?.length) {\n for (const tc of msg.toolCalls) {\n // thoughtSignature is a SIBLING of functionCall in the part object,\n // not nested inside it. See: ai.google.dev/gemini-api/docs/thought-signatures\n const part: any = {\n functionCall: { name: tc.name, args: tc.args, id: tc.id },\n };\n if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature;\n parts.push(part);\n }\n }\n if (parts.length) contents.push({ role: \"model\", parts });\n } else if (msg.role === \"tool\") {\n const part = {\n functionResponse: {\n name: msg.toolName || \"unknown\",\n id: msg.toolCallId,\n response: { content: msg.content },\n },\n };\n const last = contents[contents.length - 1];\n if (last?.role === \"user\") {\n last.parts.push(part);\n } else {\n contents.push({ role: \"user\", parts: [part] });\n }\n }\n }\n return contents;\n}\n\nfunction toGeminiFunctions(tools: ToolDefinition[]) {\n if (!tools.length) return undefined;\n return [\n {\n functionDeclarations: tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n })),\n },\n ];\n}\n\nfunction buildBody(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n /**\n * Ask Gemini to return its thought summaries as parts marked\n * `thought: true`. We pay for thinking tokens regardless; this just\n * makes them visible so the UI can show \"Reading server.js…\",\n * \"Shipping to production…\" between tool calls instead of leaving\n * the user staring at a silent tool tray. Defaults to true.\n */\n includeThoughts?: boolean;\n}) {\n const body: any = {\n contents: toGeminiContents(opts.messages),\n systemInstruction: { parts: [{ text: opts.systemPrompt }] },\n generationConfig: {\n temperature: opts.temperature ?? 0.7,\n maxOutputTokens: 8192,\n thinkingConfig: { includeThoughts: opts.includeThoughts ?? true },\n },\n };\n const fns = toGeminiFunctions(opts.tools ?? []);\n if (fns) body.tools = fns;\n return body;\n}\n\n/**\n * Non-streaming call — used for tool-calling rounds.\n * Returns complete response with thought_signature guaranteed.\n */\nexport async function callGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n includeThoughts?: boolean;\n}): Promise<{\n text: string;\n /** First-person reasoning narration; meant for a \"thinking\" UI panel, not the main bubble. */\n thoughts: string;\n toolCalls: ToolCall[];\n finishReason?: string;\n error?: string;\n}> {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n return {\n text: \"\",\n thoughts: \"\",\n toolCalls: [],\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n\n const data = await res.json().catch(() => ({}));\n if (!res.ok) {\n const msg = data?.error?.message || JSON.stringify(data).slice(0, 200);\n return {\n text: \"\",\n thoughts: \"\",\n toolCalls: [],\n error: `Gemini API error ${res.status}: ${msg}`,\n };\n }\n\n const cand = data?.candidates?.[0];\n const parts: any[] = cand?.content?.parts ?? [];\n let text = \"\";\n let thoughts = \"\";\n const toolCalls: ToolCall[] = [];\n\n for (const part of parts) {\n if (part.text) {\n // CRITICAL: Gemini tags reasoning parts with `thought: true`. If\n // we lump them into `text` they leak into the chat bubble as if\n // they were prose for the user — which is the opposite of what\n // the user wants. Keep them in their own bucket so the route\n // can stream them as a separate SSE event type.\n if (part.thought) thoughts += part.text;\n else text += part.text;\n }\n if (part.functionCall) {\n toolCalls.push({\n id:\n part.functionCall.id ||\n `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name: part.functionCall.name,\n args: part.functionCall.args ?? {},\n // thoughtSignature is a SIBLING of functionCall in the part, not inside it\n thoughtSignature: part.thoughtSignature,\n });\n }\n }\n\n return { text, thoughts, toolCalls, finishReason: cand?.finishReason };\n}\n\n/**\n * Streaming call — used for the final text-only response.\n * Yields ChatChunk objects.\n */\nexport async function* streamGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n}): AsyncGenerator {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:streamGenerateContent?key=${GEMINI_API_KEY}&alt=sse`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n yield {\n type: \"error\",\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n return;\n }\n\n if (!res.ok) {\n const errText = await res.text().catch(() => \"\");\n yield {\n type: \"error\",\n error: `Gemini API error ${res.status}: ${errText.slice(0, 300)}`,\n };\n return;\n }\n\n const reader = res.body?.getReader();\n if (!reader) {\n yield { type: \"error\", error: \"No response body\" };\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n const data = line.slice(6).trim();\n if (!data || data === \"[DONE]\") continue;\n let chunk: any;\n try {\n chunk = JSON.parse(data);\n } catch {\n continue;\n }\n const parts = chunk?.candidates?.[0]?.content?.parts ?? [];\n for (const part of parts) {\n if (part.text) {\n yield part.thought\n ? { type: \"thinking\", text: part.text }\n : { type: \"text\", text: part.text };\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n yield { type: \"done\" };\n}\n"],"names":[],"mappings":"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","debugId":null}}, -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__88db9aed._.js:2737: "streamGeminiChat", -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__88db9aed._.js:2738: ()=>streamGeminiChat -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__88db9aed._.js:2899:async function* streamGeminiChat(opts) { -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__47ad00d5._.js.map:15: {"offset": {"line": 2719, "column": 0}, "map": {"version":3,"sources":["file:///Users/markhenderson/master-ai/vibn-frontend/lib/ai/gemini-chat.ts"],"sourcesContent":["/**\n * Gemini 3.1 Pro chat client with tool-calling support.\n *\n * Architecture:\n * - Tool-calling rounds use generateContent (non-streaming) so we always\n * get the complete response including thought_signature. Thinking models\n * (2.5+, 3.x) require this field to be echoed back in functionResponse\n * and it is not reliably present in individual SSE chunks.\n * - Final text-only response uses streamGenerateContent for good UX.\n */\n\nconst GEMINI_API_KEY = process.env.GOOGLE_API_KEY || '';\nconst GEMINI_MODEL = process.env.VIBN_CHAT_MODEL || 'gemini-3.1-pro-preview';\nconst GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'tool';\n content: string;\n toolCalls?: ToolCall[];\n toolCallId?: string;\n toolName?: string;\n thoughtSignature?: string;\n}\n\nexport interface ToolCall {\n id: string;\n name: string;\n args: Record;\n /** Must be echoed back in functionResponse for Gemini thinking models */\n thoughtSignature?: string;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n parameters: Record;\n}\n\nexport interface ChatChunk {\n type: 'text' | 'thinking' | 'tool_call' | 'done' | 'error';\n text?: string;\n toolCall?: ToolCall;\n error?: string;\n}\n\n/** Convert our ChatMessage[] to Gemini's contents[] format */\nfunction toGeminiContents(messages: ChatMessage[]) {\n const contents: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n contents.push({ role: 'user', parts: [{ text: msg.content }] });\n } else if (msg.role === 'assistant') {\n const parts: any[] = [];\n if (msg.content) parts.push({ text: msg.content });\n if (msg.toolCalls?.length) {\n for (const tc of msg.toolCalls) {\n // thoughtSignature is a SIBLING of functionCall in the part object,\n // not nested inside it. See: ai.google.dev/gemini-api/docs/thought-signatures\n const part: any = { functionCall: { name: tc.name, args: tc.args, id: tc.id } };\n if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature;\n parts.push(part);\n }\n }\n if (parts.length) contents.push({ role: 'model', parts });\n } else if (msg.role === 'tool') {\n const part = {\n functionResponse: {\n name: msg.toolName || 'unknown',\n id: msg.toolCallId,\n response: { content: msg.content },\n },\n };\n const last = contents[contents.length - 1];\n if (last?.role === 'user') {\n last.parts.push(part);\n } else {\n contents.push({ role: 'user', parts: [part] });\n }\n }\n }\n return contents;\n}\n\nfunction toGeminiFunctions(tools: ToolDefinition[]) {\n if (!tools.length) return undefined;\n return [{\n functionDeclarations: tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n })),\n }];\n}\n\nfunction buildBody(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n /**\n * Ask Gemini to return its thought summaries as parts marked\n * `thought: true`. We pay for thinking tokens regardless; this just\n * makes them visible so the UI can show \"Reading server.js…\",\n * \"Shipping to production…\" between tool calls instead of leaving\n * the user staring at a silent tool tray. Defaults to true.\n */\n includeThoughts?: boolean;\n}) {\n const body: any = {\n contents: toGeminiContents(opts.messages),\n systemInstruction: { parts: [{ text: opts.systemPrompt }] },\n generationConfig: {\n temperature: opts.temperature ?? 0.7,\n maxOutputTokens: 8192,\n thinkingConfig: { includeThoughts: opts.includeThoughts ?? true },\n },\n };\n const fns = toGeminiFunctions(opts.tools ?? []);\n if (fns) body.tools = fns;\n return body;\n}\n\n/**\n * Non-streaming call — used for tool-calling rounds.\n * Returns complete response with thought_signature guaranteed.\n */\nexport async function callGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n includeThoughts?: boolean;\n}): Promise<{\n text: string;\n /** First-person reasoning narration; meant for a \"thinking\" UI panel, not the main bubble. */\n thoughts: string;\n toolCalls: ToolCall[];\n finishReason?: string;\n error?: string;\n}> {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n return {\n text: '',\n thoughts: '',\n toolCalls: [],\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n\n const data = await res.json().catch(() => ({}));\n if (!res.ok) {\n const msg = data?.error?.message || JSON.stringify(data).slice(0, 200);\n return {\n text: '',\n thoughts: '',\n toolCalls: [],\n error: `Gemini API error ${res.status}: ${msg}`,\n };\n }\n\n const cand = data?.candidates?.[0];\n const parts: any[] = cand?.content?.parts ?? [];\n let text = '';\n let thoughts = '';\n const toolCalls: ToolCall[] = [];\n\n for (const part of parts) {\n if (part.text) {\n // CRITICAL: Gemini tags reasoning parts with `thought: true`. If\n // we lump them into `text` they leak into the chat bubble as if\n // they were prose for the user — which is the opposite of what\n // the user wants. Keep them in their own bucket so the route\n // can stream them as a separate SSE event type.\n if (part.thought) thoughts += part.text;\n else text += part.text;\n }\n if (part.functionCall) {\n toolCalls.push({\n id: part.functionCall.id || `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name: part.functionCall.name,\n args: part.functionCall.args ?? {},\n // thoughtSignature is a SIBLING of functionCall in the part, not inside it\n thoughtSignature: part.thoughtSignature,\n });\n }\n }\n\n return { text, thoughts, toolCalls, finishReason: cand?.finishReason };\n}\n\n/**\n * Streaming call — used for the final text-only response.\n * Yields ChatChunk objects.\n */\nexport async function* streamGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n}): AsyncGenerator {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:streamGenerateContent?key=${GEMINI_API_KEY}&alt=sse`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n yield { type: 'error', error: `Network error: ${e instanceof Error ? e.message : String(e)}` };\n return;\n }\n\n if (!res.ok) {\n const errText = await res.text().catch(() => '');\n yield { type: 'error', error: `Gemini API error ${res.status}: ${errText.slice(0, 300)}` };\n return;\n }\n\n const reader = res.body?.getReader();\n if (!reader) { yield { type: 'error', error: 'No response body' }; return; }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const data = line.slice(6).trim();\n if (!data || data === '[DONE]') continue;\n let chunk: any;\n try { chunk = JSON.parse(data); } catch { continue; }\n const parts = chunk?.candidates?.[0]?.content?.parts ?? [];\n for (const part of parts) {\n if (part.text) {\n yield part.thought\n ? { type: 'thinking', text: part.text }\n : { type: 'text', text: part.text };\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n yield { type: 'done' };\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;CASC;;;;;;AAED,MAAM,iBAAiB,QAAQ,GAAG,CAAC,cAAc,IAAI;AACrD,MAAM,eAAe,QAAQ,GAAG,CAAC,eAAe,IAAI;AACpD,MAAM,kBAAkB;AAgCxB,4DAA4D,GAC5D,SAAS,iBAAiB,QAAuB;IAC/C,MAAM,WAAkB,EAAE;IAE1B,KAAK,MAAM,OAAO,SAAU;QAC1B,IAAI,IAAI,IAAI,KAAK,QAAQ;YACvB,SAAS,IAAI,CAAC;gBAAE,MAAM;gBAAQ,OAAO;oBAAC;wBAAE,MAAM,IAAI,OAAO;oBAAC;iBAAE;YAAC;QAC/D,OAAO,IAAI,IAAI,IAAI,KAAK,aAAa;YACnC,MAAM,QAAe,EAAE;YACvB,IAAI,IAAI,OAAO,EAAE,MAAM,IAAI,CAAC;gBAAE,MAAM,IAAI,OAAO;YAAC;YAChD,IAAI,IAAI,SAAS,EAAE,QAAQ;gBACzB,KAAK,MAAM,MAAM,IAAI,SAAS,CAAE;oBAC9B,oEAAoE;oBACpE,8EAA8E;oBAC9E,MAAM,OAAY;wBAAE,cAAc;4BAAE,MAAM,GAAG,IAAI;4BAAE,MAAM,GAAG,IAAI;4BAAE,IAAI,GAAG,EAAE;wBAAC;oBAAE;oBAC9E,IAAI,GAAG,gBAAgB,EAAE,KAAK,gBAAgB,GAAG,GAAG,gBAAgB;oBACpE,MAAM,IAAI,CAAC;gBACb;YACF;YACA,IAAI,MAAM,MAAM,EAAE,SAAS,IAAI,CAAC;gBAAE,MAAM;gBAAS;YAAM;QACzD,OAAO,IAAI,IAAI,IAAI,KAAK,QAAQ;YAC9B,MAAM,OAAO;gBACX,kBAAkB;oBAChB,MAAM,IAAI,QAAQ,IAAI;oBACtB,IAAI,IAAI,UAAU;oBAClB,UAAU;wBAAE,SAAS,IAAI,OAAO;oBAAC;gBACnC;YACF;YACA,MAAM,OAAO,QAAQ,CAAC,SAAS,MAAM,GAAG,EAAE;YAC1C,IAAI,MAAM,SAAS,QAAQ;gBACzB,KAAK,KAAK,CAAC,IAAI,CAAC;YAClB,OAAO;gBACL,SAAS,IAAI,CAAC;oBAAE,MAAM;oBAAQ,OAAO;wBAAC;qBAAK;gBAAC;YAC9C;QACF;IACF;IACA,OAAO;AACT;AAEA,SAAS,kBAAkB,KAAuB;IAChD,IAAI,CAAC,MAAM,MAAM,EAAE,OAAO;IAC1B,OAAO;QAAC;YACN,sBAAsB,MAAM,GAAG,CAAC,CAAC,IAAM,CAAC;oBACtC,MAAM,EAAE,IAAI;oBACZ,aAAa,EAAE,WAAW;oBAC1B,YAAY,EAAE,UAAU;gBAC1B,CAAC;QACH;KAAE;AACJ;AAEA,SAAS,UAAU,IAalB;IACC,MAAM,OAAY;QAChB,UAAU,iBAAiB,KAAK,QAAQ;QACxC,mBAAmB;YAAE,OAAO;gBAAC;oBAAE,MAAM,KAAK,YAAY;gBAAC;aAAE;QAAC;QAC1D,kBAAkB;YAChB,aAAa,KAAK,WAAW,IAAI;YACjC,iBAAiB;YACjB,gBAAgB;gBAAE,iBAAiB,KAAK,eAAe,IAAI;YAAK;QAClE;IACF;IACA,MAAM,MAAM,kBAAkB,KAAK,KAAK,IAAI,EAAE;IAC9C,IAAI,KAAK,KAAK,KAAK,GAAG;IACtB,OAAO;AACT;AAMO,eAAe,eAAe,IAMpC;IAQC,MAAM,MAAM,GAAG,gBAAgB,QAAQ,EAAE,aAAa,qBAAqB,EAAE,gBAAgB;IAE7F,IAAI;IACJ,IAAI;QACF,MAAM,MAAM,MAAM,KAAK;YACrB,QAAQ;YACR,SAAS;gBAAE,gBAAgB;YAAmB;YAC9C,MAAM,KAAK,SAAS,CAAC,UAAU;QACjC;IACF,EAAE,OAAO,GAAG;QACV,OAAO;YACL,MAAM;YACN,UAAU;YACV,WAAW,EAAE;YACb,OAAO,CAAC,eAAe,EAAE,aAAa,QAAQ,EAAE,OAAO,GAAG,OAAO,IAAI;QACvE;IACF;IAEA,MAAM,OAAO,MAAM,IAAI,IAAI,GAAG,KAAK,CAAC,IAAM,CAAC,CAAC,CAAC;IAC7C,IAAI,CAAC,IAAI,EAAE,EAAE;QACX,MAAM,MAAM,MAAM,OAAO,WAAW,KAAK,SAAS,CAAC,MAAM,KAAK,CAAC,GAAG;QAClE,OAAO;YACL,MAAM;YACN,UAAU;YACV,WAAW,EAAE;YACb,OAAO,CAAC,iBAAiB,EAAE,IAAI,MAAM,CAAC,EAAE,EAAE,KAAK;QACjD;IACF;IAEA,MAAM,OAAO,MAAM,YAAY,CAAC,EAAE;IAClC,MAAM,QAAe,MAAM,SAAS,SAAS,EAAE;IAC/C,IAAI,OAAO;IACX,IAAI,WAAW;IACf,MAAM,YAAwB,EAAE;IAEhC,KAAK,MAAM,QAAQ,MAAO;QACxB,IAAI,KAAK,IAAI,EAAE;YACb,iEAAiE;YACjE,gEAAgE;YAChE,+DAA+D;YAC/D,6DAA6D;YAC7D,gDAAgD;YAChD,IAAI,KAAK,OAAO,EAAE,YAAY,KAAK,IAAI;iBAClC,QAAQ,KAAK,IAAI;QACxB;QACA,IAAI,KAAK,YAAY,EAAE;YACrB,UAAU,IAAI,CAAC;gBACb,IAAI,KAAK,YAAY,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,GAAG,GAAG,CAAC,EAAE,KAAK,MAAM,GAAG,QAAQ,CAAC,IAAI,KAAK,CAAC,IAAI;gBACrF,MAAM,KAAK,YAAY,CAAC,IAAI;gBAC5B,MAAM,KAAK,YAAY,CAAC,IAAI,IAAI,CAAC;gBACjC,2EAA2E;gBAC3E,kBAAkB,KAAK,gBAAgB;YACzC;QACF;IACF;IAEA,OAAO;QAAE;QAAM;QAAU;QAAW,cAAc,MAAM;IAAa;AACvE;AAMO,gBAAgB,iBAAiB,IAKvC;IACC,MAAM,MAAM,GAAG,gBAAgB,QAAQ,EAAE,aAAa,2BAA2B,EAAE,eAAe,QAAQ,CAAC;IAE3G,IAAI;IACJ,IAAI;QACF,MAAM,MAAM,MAAM,KAAK;YACrB,QAAQ;YACR,SAAS;gBAAE,gBAAgB;YAAmB;YAC9C,MAAM,KAAK,SAAS,CAAC,UAAU;QACjC;IACF,EAAE,OAAO,GAAG;QACV,MAAM;YAAE,MAAM;YAAS,OAAO,CAAC,eAAe,EAAE,aAAa,QAAQ,EAAE,OAAO,GAAG,OAAO,IAAI;QAAC;QAC7F;IACF;IAEA,IAAI,CAAC,IAAI,EAAE,EAAE;QACX,MAAM,UAAU,MAAM,IAAI,IAAI,GAAG,KAAK,CAAC,IAAM;QAC7C,MAAM;YAAE,MAAM;YAAS,OAAO,CAAC,iBAAiB,EAAE,IAAI,MAAM,CAAC,EAAE,EAAE,QAAQ,KAAK,CAAC,GAAG,MAAM;QAAC;QACzF;IACF;IAEA,MAAM,SAAS,IAAI,IAAI,EAAE;IACzB,IAAI,CAAC,QAAQ;QAAE,MAAM;YAAE,MAAM;YAAS,OAAO;QAAmB;QAAG;IAAQ;IAE3E,MAAM,UAAU,IAAI;IACpB,IAAI,SAAS;IAEb,IAAI;QACF,MAAO,KAAM;YACX,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,IAAI;YACzC,IAAI,MAAM;YACV,UAAU,QAAQ,MAAM,CAAC,OAAO;gBAAE,QAAQ;YAAK;YAC/C,MAAM,QAAQ,OAAO,KAAK,CAAC;YAC3B,SAAS,MAAM,GAAG,MAAM;YAExB,KAAK,MAAM,QAAQ,MAAO;gBACxB,IAAI,CAAC,KAAK,UAAU,CAAC,WAAW;gBAChC,MAAM,OAAO,KAAK,KAAK,CAAC,GAAG,IAAI;gBAC/B,IAAI,CAAC,QAAQ,SAAS,UAAU;gBAChC,IAAI;gBACJ,IAAI;oBAAE,QAAQ,KAAK,KAAK,CAAC;gBAAO,EAAE,OAAM;oBAAE;gBAAU;gBACpD,MAAM,QAAQ,OAAO,YAAY,CAAC,EAAE,EAAE,SAAS,SAAS,EAAE;gBAC1D,KAAK,MAAM,QAAQ,MAAO;oBACxB,IAAI,KAAK,IAAI,EAAE;wBACb,MAAM,KAAK,OAAO,GACd;4BAAE,MAAM;4BAAY,MAAM,KAAK,IAAI;wBAAC,IACpC;4BAAE,MAAM;4BAAQ,MAAM,KAAK,IAAI;wBAAC;oBACtC;gBACF;YACF;QACF;IACF,SAAU;QACR,OAAO,WAAW;IACpB;IAEA,MAAM;QAAE,MAAM;IAAO;AACvB","debugId":null}}, -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__574f5573._.js.map:15: {"offset": {"line": 2738, "column": 0}, "map": {"version":3,"sources":["file:///Users/markhenderson/master-ai/vibn-frontend/lib/ai/gemini-chat.ts"],"sourcesContent":["/**\n * Gemini 3.1 Pro chat client with tool-calling support.\n *\n * Architecture:\n * - Tool-calling rounds use generateContent (non-streaming) so we always\n * get the complete response including thought_signature. Thinking models\n * (2.5+, 3.x) require this field to be echoed back in functionResponse\n * and it is not reliably present in individual SSE chunks.\n * - Final text-only response uses streamGenerateContent for good UX.\n */\n\nconst GEMINI_API_KEY = process.env.GOOGLE_API_KEY || \"\";\nconst GEMINI_MODEL = process.env.VIBN_CHAT_MODEL || \"gemini-3.1-pro-preview\";\nconst GEMINI_BASE_URL = \"https://generativelanguage.googleapis.com/v1beta\";\n\nexport interface ChatMessage {\n role: \"user\" | \"assistant\" | \"tool\";\n content: string;\n toolCalls?: ToolCall[];\n toolCallId?: string;\n toolName?: string;\n thoughtSignature?: string;\n}\n\nexport interface ToolCall {\n id: string;\n name: string;\n args: Record;\n /** Must be echoed back in functionResponse for Gemini thinking models */\n thoughtSignature?: string;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n parameters: Record;\n}\n\nexport interface ChatChunk {\n type: \"text\" | \"thinking\" | \"tool_call\" | \"done\" | \"error\";\n text?: string;\n toolCall?: ToolCall;\n error?: string;\n}\n\n/** Convert our ChatMessage[] to Gemini's contents[] format */\nfunction toGeminiContents(messages: ChatMessage[]) {\n const contents: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n contents.push({ role: \"user\", parts: [{ text: msg.content }] });\n } else if (msg.role === \"assistant\") {\n const parts: any[] = [];\n if (msg.content) parts.push({ text: msg.content });\n if (msg.toolCalls?.length) {\n for (const tc of msg.toolCalls) {\n // thoughtSignature is a SIBLING of functionCall in the part object,\n // not nested inside it. See: ai.google.dev/gemini-api/docs/thought-signatures\n const part: any = {\n functionCall: { name: tc.name, args: tc.args, id: tc.id },\n };\n if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature;\n parts.push(part);\n }\n }\n if (parts.length) contents.push({ role: \"model\", parts });\n } else if (msg.role === \"tool\") {\n const part = {\n functionResponse: {\n name: msg.toolName || \"unknown\",\n id: msg.toolCallId,\n response: { content: msg.content },\n },\n };\n const last = contents[contents.length - 1];\n if (last?.role === \"user\") {\n last.parts.push(part);\n } else {\n contents.push({ role: \"user\", parts: [part] });\n }\n }\n }\n return contents;\n}\n\nfunction toGeminiFunctions(tools: ToolDefinition[]) {\n if (!tools.length) return undefined;\n return [\n {\n functionDeclarations: tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n })),\n },\n ];\n}\n\nfunction buildBody(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n /**\n * Ask Gemini to return its thought summaries as parts marked\n * `thought: true`. We pay for thinking tokens regardless; this just\n * makes them visible so the UI can show \"Reading server.js…\",\n * \"Shipping to production…\" between tool calls instead of leaving\n * the user staring at a silent tool tray. Defaults to true.\n */\n includeThoughts?: boolean;\n}) {\n const body: any = {\n contents: toGeminiContents(opts.messages),\n systemInstruction: { parts: [{ text: opts.systemPrompt }] },\n generationConfig: {\n temperature: opts.temperature ?? 0.7,\n maxOutputTokens: 8192,\n thinkingConfig: { includeThoughts: opts.includeThoughts ?? true },\n },\n };\n const fns = toGeminiFunctions(opts.tools ?? []);\n if (fns) body.tools = fns;\n return body;\n}\n\n/**\n * Non-streaming call — used for tool-calling rounds.\n * Returns complete response with thought_signature guaranteed.\n */\nexport async function callGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n includeThoughts?: boolean;\n}): Promise<{\n text: string;\n /** First-person reasoning narration; meant for a \"thinking\" UI panel, not the main bubble. */\n thoughts: string;\n toolCalls: ToolCall[];\n finishReason?: string;\n error?: string;\n}> {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n return {\n text: \"\",\n thoughts: \"\",\n toolCalls: [],\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n\n const data = await res.json().catch(() => ({}));\n if (!res.ok) {\n const msg = data?.error?.message || JSON.stringify(data).slice(0, 200);\n return {\n text: \"\",\n thoughts: \"\",\n toolCalls: [],\n error: `Gemini API error ${res.status}: ${msg}`,\n };\n }\n\n const cand = data?.candidates?.[0];\n const parts: any[] = cand?.content?.parts ?? [];\n let text = \"\";\n let thoughts = \"\";\n const toolCalls: ToolCall[] = [];\n\n for (const part of parts) {\n if (part.text) {\n // CRITICAL: Gemini tags reasoning parts with `thought: true`. If\n // we lump them into `text` they leak into the chat bubble as if\n // they were prose for the user — which is the opposite of what\n // the user wants. Keep them in their own bucket so the route\n // can stream them as a separate SSE event type.\n if (part.thought) thoughts += part.text;\n else text += part.text;\n }\n if (part.functionCall) {\n toolCalls.push({\n id:\n part.functionCall.id ||\n `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name: part.functionCall.name,\n args: part.functionCall.args ?? {},\n // thoughtSignature is a SIBLING of functionCall in the part, not inside it\n thoughtSignature: part.thoughtSignature,\n });\n }\n }\n\n return { text, thoughts, toolCalls, finishReason: cand?.finishReason };\n}\n\n/**\n * Streaming call — used for the final text-only response.\n * Yields ChatChunk objects.\n */\nexport async function* streamGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n}): AsyncGenerator {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:streamGenerateContent?key=${GEMINI_API_KEY}&alt=sse`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n yield {\n type: \"error\",\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n return;\n }\n\n if (!res.ok) {\n const errText = await res.text().catch(() => \"\");\n yield {\n type: \"error\",\n error: `Gemini API error ${res.status}: ${errText.slice(0, 300)}`,\n };\n return;\n }\n\n const reader = res.body?.getReader();\n if (!reader) {\n yield { type: \"error\", error: \"No response body\" };\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n const data = line.slice(6).trim();\n if (!data || data === \"[DONE]\") continue;\n let chunk: any;\n try {\n chunk = JSON.parse(data);\n } catch {\n continue;\n }\n const parts = chunk?.candidates?.[0]?.content?.parts ?? [];\n for (const part of parts) {\n if (part.text) {\n yield part.thought\n ? { type: \"thinking\", text: part.text }\n : { type: \"text\", text: part.text };\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n yield { type: \"done\" };\n}\n"],"names":[],"mappings":"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","debugId":null}}, -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__574f5573._.js:2751: "streamGeminiChat", -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__574f5573._.js:2752: ()=>streamGeminiChat -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__574f5573._.js:2913:async function* streamGeminiChat(opts) { -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__08533b7c._.js:2689: "streamGeminiChat", -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__08533b7c._.js:2690: ()=>streamGeminiChat -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__08533b7c._.js:2851:async function* streamGeminiChat(opts) { -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__47ad00d5._.js:2732: "streamGeminiChat", -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__47ad00d5._.js:2733: ()=>streamGeminiChat -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__47ad00d5._.js:2894:async function* streamGeminiChat(opts) { -vibn-frontend/.next/dev/server/chunks/[root-of-the-server]__2b9e4ee1._.js.map:15: {"offset": {"line": 2719, "column": 0}, "map": {"version":3,"sources":["file:///Users/markhenderson/master-ai/vibn-frontend/lib/ai/gemini-chat.ts"],"sourcesContent":["/**\n * Gemini 3.1 Pro chat client with tool-calling support.\n *\n * Architecture:\n * - Tool-calling rounds use generateContent (non-streaming) so we always\n * get the complete response including thought_signature. Thinking models\n * (2.5+, 3.x) require this field to be echoed back in functionResponse\n * and it is not reliably present in individual SSE chunks.\n * - Final text-only response uses streamGenerateContent for good UX.\n */\n\nconst GEMINI_API_KEY = process.env.GOOGLE_API_KEY || '';\nconst GEMINI_MODEL = process.env.VIBN_CHAT_MODEL || 'gemini-3.1-pro-preview';\nconst GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'tool';\n content: string;\n toolCalls?: ToolCall[];\n toolCallId?: string;\n toolName?: string;\n thoughtSignature?: string;\n}\n\nexport interface ToolCall {\n id: string;\n name: string;\n args: Record;\n /** Must be echoed back in functionResponse for Gemini thinking models */\n thoughtSignature?: string;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n parameters: Record;\n}\n\nexport interface ChatChunk {\n type: 'text' | 'thinking' | 'tool_call' | 'done' | 'error';\n text?: string;\n toolCall?: ToolCall;\n error?: string;\n}\n\n/** Convert our ChatMessage[] to Gemini's contents[] format */\nfunction toGeminiContents(messages: ChatMessage[]) {\n const contents: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n contents.push({ role: 'user', parts: [{ text: msg.content }] });\n } else if (msg.role === 'assistant') {\n const parts: any[] = [];\n if (msg.content) parts.push({ text: msg.content });\n if (msg.toolCalls?.length) {\n for (const tc of msg.toolCalls) {\n // thoughtSignature is a SIBLING of functionCall in the part object,\n // not nested inside it. See: ai.google.dev/gemini-api/docs/thought-signatures\n const part: any = { functionCall: { name: tc.name, args: tc.args, id: tc.id } };\n if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature;\n parts.push(part);\n }\n }\n if (parts.length) contents.push({ role: 'model', parts });\n } else if (msg.role === 'tool') {\n const part = {\n functionResponse: {\n name: msg.toolName || 'unknown',\n id: msg.toolCallId,\n response: { content: msg.content },\n },\n };\n const last = contents[contents.length - 1];\n if (last?.role === 'user') {\n last.parts.push(part);\n } else {\n contents.push({ role: 'user', parts: [part] });\n }\n }\n }\n return contents;\n}\n\nfunction toGeminiFunctions(tools: ToolDefinition[]) {\n if (!tools.length) return undefined;\n return [{\n functionDeclarations: tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n })),\n }];\n}\n\nfunction buildBody(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n /**\n * Ask Gemini to return its thought summaries as parts marked\n * `thought: true`. We pay for thinking tokens regardless; this just\n * makes them visible so the UI can show \"Reading server.js…\",\n * \"Shipping to production…\" between tool calls instead of leaving\n * the user staring at a silent tool tray. Defaults to true.\n */\n includeThoughts?: boolean;\n}) {\n const body: any = {\n contents: toGeminiContents(opts.messages),\n systemInstruction: { parts: [{ text: opts.systemPrompt }] },\n generationConfig: {\n temperature: opts.temperature ?? 0.7,\n maxOutputTokens: 8192,\n thinkingConfig: { includeThoughts: opts.includeThoughts ?? true },\n },\n };\n const fns = toGeminiFunctions(opts.tools ?? []);\n if (fns) body.tools = fns;\n return body;\n}\n\n/**\n * Non-streaming call — used for tool-calling rounds.\n * Returns complete response with thought_signature guaranteed.\n */\nexport async function callGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n includeThoughts?: boolean;\n}): Promise<{\n text: string;\n /** First-person reasoning narration; meant for a \"thinking\" UI panel, not the main bubble. */\n thoughts: string;\n toolCalls: ToolCall[];\n finishReason?: string;\n error?: string;\n}> {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n return {\n text: '',\n thoughts: '',\n toolCalls: [],\n error: `Network error: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n\n const data = await res.json().catch(() => ({}));\n if (!res.ok) {\n const msg = data?.error?.message || JSON.stringify(data).slice(0, 200);\n return {\n text: '',\n thoughts: '',\n toolCalls: [],\n error: `Gemini API error ${res.status}: ${msg}`,\n };\n }\n\n const cand = data?.candidates?.[0];\n const parts: any[] = cand?.content?.parts ?? [];\n let text = '';\n let thoughts = '';\n const toolCalls: ToolCall[] = [];\n\n for (const part of parts) {\n if (part.text) {\n // CRITICAL: Gemini tags reasoning parts with `thought: true`. If\n // we lump them into `text` they leak into the chat bubble as if\n // they were prose for the user — which is the opposite of what\n // the user wants. Keep them in their own bucket so the route\n // can stream them as a separate SSE event type.\n if (part.thought) thoughts += part.text;\n else text += part.text;\n }\n if (part.functionCall) {\n toolCalls.push({\n id: part.functionCall.id || `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n name: part.functionCall.name,\n args: part.functionCall.args ?? {},\n // thoughtSignature is a SIBLING of functionCall in the part, not inside it\n thoughtSignature: part.thoughtSignature,\n });\n }\n }\n\n return { text, thoughts, toolCalls, finishReason: cand?.finishReason };\n}\n\n/**\n * Streaming call — used for the final text-only response.\n * Yields ChatChunk objects.\n */\nexport async function* streamGeminiChat(opts: {\n systemPrompt: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n temperature?: number;\n}): AsyncGenerator {\n const url = `${GEMINI_BASE_URL}/models/${GEMINI_MODEL}:streamGenerateContent?key=${GEMINI_API_KEY}&alt=sse`;\n\n let res: Response;\n try {\n res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(buildBody(opts)),\n });\n } catch (e) {\n yield { type: 'error', error: `Network error: ${e instanceof Error ? e.message : String(e)}` };\n return;\n }\n\n if (!res.ok) {\n const errText = await res.text().catch(() => '');\n yield { type: 'error', error: `Gemini API error ${res.status}: ${errText.slice(0, 300)}` };\n return;\n }\n\n const reader = res.body?.getReader();\n if (!reader) { yield { type: 'error', error: 'No response body' }; return; }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const data = line.slice(6).trim();\n if (!data || data === '[DONE]') continue;\n let chunk: any;\n try { chunk = JSON.parse(data); } catch { continue; }\n const parts = chunk?.candidates?.[0]?.content?.parts ?? [];\n for (const part of parts) {\n if (part.text) {\n yield part.thought\n ? { type: 'thinking', text: part.text }\n : { type: 'text', text: part.text };\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n yield { type: 'done' };\n}\n"],"names":[],"mappings":"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","debugId":null}}, -vibn-frontend/## User:590:### async function* streamGeminiChat( ) › const url › L216-226 -vibn-frontend/lib/ai/gemini-chat.ts:211:export async function* streamGeminiChat(opts: { diff --git a/latest_deploy.txt b/latest_deploy.txt deleted file mode 100644 index 2f27266..0000000 --- a/latest_deploy.txt +++ /dev/null @@ -1 +0,0 @@ -[{"command":null,"output":"Docker 27.0.3 with BuildKit and Buildx detected on deployment server (localhost).","type":"stdout","timestamp":"2026-05-17T20:48:41.186732Z","hidden":false,"batch":1},{"command":null,"output":"Starting deployment of https:\/\/git.vibnai.com\/mark\/vibn-frontend.git:main to localhost.","type":"stdout","timestamp":"2026-05-17T20:48:41.236729Z","hidden":false,"batch":1,"order":2}] diff --git a/mcp-zed-adapter.js b/mcp-zed-adapter.js deleted file mode 100755 index 51827e1..0000000 --- a/mcp-zed-adapter.js +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env node - -/** - * Vibn MCP Adapter for Zed - * - * This script runs locally as a standard stdio MCP server for Zed. - * It intercepts tool calls and forwards them over HTTP to the Vibn API - * using the proprietary format that Vibn expects. - */ - -const { Server } = require("@modelcontextprotocol/sdk/server/index.js"); -const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js"); -const { CallToolRequestSchema, ListToolsRequestSchema } = require("@modelcontextprotocol/sdk/types.js"); - -// Configuration from environment variables -const VIBN_API_URL = process.env.VIBN_API_URL || "https://vibnai.com/api/mcp"; -const VIBN_API_KEY = process.env.VIBN_API_KEY; - -if (!VIBN_API_KEY) { - console.error("Missing VIBN_API_KEY environment variable."); - process.exit(1); -} - -// Create the MCP server -const server = new Server( - { - name: "vibn-zed-adapter", - version: "1.0.0", - }, - { - capabilities: { - tools: {}, - }, - } -); - -let cachedTools = null; - -// Fetch the tool list from the Vibn API -async function fetchTools() { - if (cachedTools) return cachedTools; - - try { - const response = await fetch(VIBN_API_URL, { - method: "GET", - headers: { - Authorization: \`Bearer \${VIBN_API_KEY}\`, - }, - }); - - if (!response.ok) { - throw new Error(\`Failed to fetch tools: \${response.statusText}\`); - } - - const data = await response.json(); - - // We need to fetch the actual tool schemas since the GET endpoint - // only returns a string array of tool names. - // For this adapter, we define the schema passthrough. - - // Note: Since Vibn's GET /api/mcp only returns an array of names: - // "available": ["workspace.describe", "gitea.credentials", ...] - // We will construct basic MCP tool definitions for them. - // In a production scenario, you would expose the JSON schemas on the GET endpoint. - - const available = data.capabilities?.tools?.available || []; - - cachedTools = available.map(name => ({ - name: name, - description: \`Vibn workspace tool: \${name}\`, - inputSchema: { - type: "object", - properties: { - // Accept any parameters generically for the proxy - projectId: { type: "string", description: "Vibn project ID" }, - }, - additionalProperties: true - } - })); - - return cachedTools; - } catch (error) { - console.error("Error fetching tools:", error); - return []; - } -} - -// Handle List Tools Request -server.setRequestHandler(ListToolsRequestSchema, async () => { - const tools = await fetchTools(); - return { - tools: tools, - }; -}); - -// Handle Call Tool Request -server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - try { - // Forward the request to Vibn API - const response = await fetch(VIBN_API_URL, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: \`Bearer \${VIBN_API_KEY}\`, - }, - body: JSON.stringify({ - tool: name, // Vibn's expected format - params: args || {}, - }), - }); - - const data = await response.json(); - - if (!response.ok) { - return { - content: [ - { - type: "text", - text: \`Error (\${response.status}): \${JSON.stringify(data.error || data)}\`, - }, - ], - isError: true, - }; - } - - return { - content: [ - { - type: "text", - text: JSON.stringify(data.result || data, null, 2), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: "text", - text: \`Internal Adapter Error: \${error.message}\`, - }, - ], - isError: true, - }; - } -}); - -// Start the server -async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Vibn Zed MCP Adapter running on stdio"); -} - -main().catch((error) => { - console.error("Fatal error:", error); - process.exit(1); -}); diff --git a/patch_stripe_keys.js b/patch_stripe_keys.js deleted file mode 100644 index 1d8c3ec..0000000 --- a/patch_stripe_keys.js +++ /dev/null @@ -1,17 +0,0 @@ -const fs = require('fs'); - -const envPath = 'vibn-frontend/.env.local'; - -let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : ''; - -if (!content.includes('STRIPE_CLIENT_ID')) { - content += `\nSTRIPE_CLIENT_ID=ca_UTuWw2qE8wFLNlWOL7T1v0H5GdB6BtDw\n`; - fs.writeFileSync(envPath, content); - console.log("✅ Successfully added STRIPE_CLIENT_ID to .env.local!"); -} else { - // Replace it just in case - content = content.replace(/STRIPE_CLIENT_ID=.*/, 'STRIPE_CLIENT_ID=ca_UTuWw2qE8wFLNlWOL7T1v0H5GdB6BtDw'); - fs.writeFileSync(envPath, content); - console.log("✅ Successfully updated STRIPE_CLIENT_ID in .env.local!"); -} - diff --git a/patch_thought_sig.ts b/patch_thought_sig.ts deleted file mode 100644 index 3718055..0000000 --- a/patch_thought_sig.ts +++ /dev/null @@ -1,40 +0,0 @@ -const fs = require('fs'); - -const path = 'vibn-frontend/lib/ai/gemini-chat.ts'; -let code = fs.readFileSync(path, 'utf8'); - -// The issue is in `toGeminiContents()`. -// For `gemini-3.1-pro-preview` thinking models, `thoughtSignature` MUST be passed back -// as a sibling to `functionCall` in the `parts` array for assistant responses, -// OR inside the `functionResponse` for tool responses (depending on API version). -// Also we need to extract it from the response. - -code = code.replace( - ` const part: any = { - functionCall: { name: tc.name, args: tc.args }, - }; - parts.push(part);`, - ` const part: any = { - functionCall: { name: tc.name, args: tc.args }, - }; - if (tc.thoughtSignature) { - part.thoughtSignature = tc.thoughtSignature; - } - parts.push(part);` -); - -code = code.replace( - ` toolCalls.push({ - id: \`tc-\${Date.now()}-\${Math.random().toString(36).slice(2)}\`, - name: part.functionCall.name, - args: part.functionCall.args as Record ?? {}, - });`, - ` toolCalls.push({ - id: \`tc-\${Date.now()}-\${Math.random().toString(36).slice(2)}\`, - name: part.functionCall.name, - args: part.functionCall.args as Record ?? {}, - thoughtSignature: (part as any).thoughtSignature, - });` -); - -fs.writeFileSync(path, code); diff --git a/vibn-frontend/.firebase/hosting.Lm5leHQvc3RhdGlj.cache b/vibn-frontend/.firebase/hosting.Lm5leHQvc3RhdGlj.cache deleted file mode 100644 index 2c8b081..0000000 --- a/vibn-frontend/.firebase/hosting.Lm5leHQvc3RhdGlj.cache +++ /dev/null @@ -1,86 +0,0 @@ -media/8a480f0b521d4e75-s.8e0177b5.woff2,1764292945234,cccaf7bf72117d313a9afc3a475289a19b9fcd0f735c9b2e4ded6cba08a517a0 -media/7178b3e590c64307-s.b97b3418.woff2,1764292945325,2b719e95831c9d92a31ebbe512bbd87cc76501765edb9b6ca4734cc5a2becb94 -media/4fa387ec64143e14-s.c1fdd6c2.woff2,1764292945447,7cdf2599fa32a0a3edc7d4126b5c8f5233d62799ddb46552ca6890389ba7d9c1 -debHSXN92soAOPU1HZgYF/_ssgManifest.js,1764292948075,02dbc1aeab6ef0a6ff2ff9a1643158cf9bb38929945eaa343a3627dee9ba6778 -debHSXN92soAOPU1HZgYF/_clientMiddlewareManifest.json,1764292945829,6731668a37f6a3ed10d77860e21c7ba693c377a061571ffa58564d1d699c1798 -debHSXN92soAOPU1HZgYF/_buildManifest.js,1764292945829,615bff88115b95e28f767491701e61ecffa7e94106127d229aa17a5905c320ed -chunks/f9cb1844dfa45255.js,1764292945340,01c00f2411e0e3e097e7018d72b70b2b51875d8d225caaf29640ce25a2a544f6 -chunks/f7f1f72136b370ca.js,1764292945327,01a104bc08fcc0604c0ec1a806fca89dcf30e910b371b53deef33ca6265eecde -chunks/ebe72e5be9ee5ad9.js,1764292945628,daae931739a356f539b4ad0ca419f1eb5b4bb3778cbb61dd7caf61a1d9865f2a -chunks/f5e1eb514e39cc88.js,1764292944956,e4565dc0670c37ec0f447d2b5b70d452743abe10a95fed3845c1cce4abb48b4c -chunks/f2800e0af697fdd0.js,1764292945328,6ce375e87966595708eadaf758ada294509c58dfac5ed8c59f70f616721c00c3 -chunks/e4262adb08a1c2bf.js,1764292945289,d4460e79d3dc59fc127276a44923f64f9a58a4265dabfec5271f2558d9fd2bc6 -chunks/turbopack-9ca93d673567d695.js,1764292945039,8192a0d1a0740e6d887b97a7403a2a7f247ef214e8920cab730ff87fed58437b -chunks/ddebd270303d8e52.js,1764292945615,8c3623313303c5d58f40c3d5a8507a7f26b878129e1a322a4c49cc1abeb27a22 -chunks/e3204003115c48b3.js,1764292945452,4f1d81d5502dc30584b94916d1192cca4ad31abc0c9ec90a41b8a086d2ad36d8 -chunks/f44a1b0e13fe130e.js,1764292945521,bab4ad594f82e40e230885b71ad2e66bbdcc9adf0a06e5dabbb9881b3f090bfd -chunks/d8fb8fcccb9ed575.js,1764292945641,88dc104238c97d623ed03c0457896bf0e5576f0e555b0521429514968254db72 -chunks/d53fe979bca22d91.js,1764292945292,7e0b70993217191be5b9c1cbcb8eae8e93f77d2ffc8b8c47260dddf45531b0bf -chunks/e9d7f43cc4a2ffde.js,1764292944977,f3b75db76ea5f0e0897a6643c7eac52ef942c66e06c9e923d8adf0842b6102df -chunks/c8f249a29afd3371.js,1764292945174,cf90afbb172a0e61b4bb1eba8b903cdf008a5b5dcbb22fbeaad4bdf99a1aa1a7 -chunks/d37715a4848800df.js,1764292945614,3c838423e38372ac0fbf00a2d2a665bd128b7b3bb282fcc3faddbf119821529c -chunks/cc3b7acd0b8a8ed0.js,1764292945378,63c43444cf37250c0265200f90da944fe7cb30cbf4712ca78320313cc28ae2c0 -chunks/e930ad9e05eaf62d.js,1764292944935,59ebe62acad73a7571001179dfdb02a87205e2b822ca22262e97b8a6582fe5cb -media/favicon.0b3bf435.ico,1764292945309,04614fc32690cb60b39e472119b7f7aa91d88eaeb8511a7489f8cbe1552e6e59 -chunks/b7d3d522b141a153.js,1764292945341,2f7e530895f432df1e996fe86ed3299a10f7ba8585e1b0d9d206f93cc228bb18 -chunks/b2d11888d122e656.js,1764292945620,f318502788773401c1f1f95699d1d3b62a6e021a53f7e2bff8fdd9a2c6cee3b7 -chunks/b297c493e9d2a547.js,1764292945333,562b883b63a596c2de501948be11263522bf0ba4c16cf2e779c6cd74cad7dcf9 -chunks/b678db9b7a6233a6.js,1764292945451,5930f7e3b147761c765cf03570bf989a4376adc16294f8e7f63041b910145c21 -chunks/aee0a3aab75c6656.js,1764292945645,74ef1a854f40fccfc42849f0eff252e9a9a099dac0fb4afa98b17e3aa773d361 -chunks/a65fa752112154b2.js,1764292945316,695a77ee322b9e0e9597b24b6efd71d653f3651aa579b479f73bf36b93c4211d -chunks/9c2f2a94801db6cd.js,1764292945267,6bde7c92d8ae55b0bdb2f9cc508671a69314ff01c824d16ffae15b41203123a3 -chunks/8f12ba4a400d0818.js,1764292945640,9d69d3faa9752f8dcc197c2e62ea5c9a843e2431de85570ec152260f0606a64e -chunks/9d593509176d2bdc.js,1764292945182,58f2e77ebd69f17e0e850f3dff502266bac2f51f6a66e067459ee2344c4c1823 -chunks/8f647170168e8688.js,1764292945436,6d91c1f674b325e03565f00bb0ee8f017c247a836b170067b7271c4184a76368 -chunks/8269db69d7104eaf.js,1764292945289,46b9e7e465ed39ef4b9030e292173ca01e4141f67d7290d9a8df5cb8cfdb94a4 -chunks/a3751053cf95bf66.js,1764292944936,a1864f7575f28ae1495943736f439149ff7ca641c48f352a27b8241e1c5b1895 -chunks/7f6ce89234677f07.js,1764292945596,d028183aa6747e759e6e2e2f94012efeb3c01edd9eab81cffa463ca884daa9fb -chunks/7a5de61b06aada33.js,1764292945448,4de9756e37dc8195f5d3ab68b7d70a1228b3ef0c14fa376f7d4a41bdfdde1a4b -chunks/7e6878fd487d3e54.js,1764292945013,f13804b8190486b356af32ba87000b503316d82c8dce6a5bca9e65e39189605d -chunks/b72884fc3dd51b08.js,1764292944972,83adbd329c23f79df34e0bd3d9d52f2f8f888c6d6a3ff45698a67e90a3b1e485 -media/bbc41e54d2fcbd21-s.799d8ef8.woff2,1764292945043,396955195c54144bce504511dd89d0c74a3f6b453d73823073be1a2cbe00e6de -chunks/c4e22d55290821bf.js,1764292945037,27669ee06cf5e963c5a0e12977384e4c2894b9befa9bc0d13ccc4ecc3dc2d43b -media/caa3a2e1cccd8315-s.p.853070df.woff2,1764292945300,d38dd3d36107934ef290b2449c29728caa7bcceeb4750b0a2bec2042fac4c601 -media/797e433ab948586e-s.p.dbea232f.woff2,1764292945245,d4a2afa79a272709433753cffe4f64c13e37ae2fdfa1ded22b38c83f978b78a4 -chunks/8decf5edbe5dd12a.js,1764292945026,a9150cd9cf27455e4190ae18a8db7fca07bc34a3a8b689d1d5cb3524b13c0880 -chunks/ef5b2d67ab809f64.css,1764292945059,90165d549a46ffb7036763cb03adfd897952fc93c25201d746605436ef5ea46b -chunks/d0f2cbc50b9d061e.js,1764292945736,3c74944275ad985170b8011410d2a5ccc6c6026cf0d4c96951b128dfb8661833 -chunks/767a4a5f6aabf6e2.js,1764292945172,bbbb2624994ac119eb5bc7a692eb7b97d3ffba0d4d8160a406aee89a81c212a8 -chunks/74c6d21fb44e33e9.js,1764292945162,7a886154420672f0f8395bdf535bd17f52063a455ede9987e3b6ea7d5bc91479 -chunks/78680bef0dd9c8e5.js,1764292945077,ec8a5a1356c00f3dbb68f2a7655a6e7ba711212a4f5ced7994a82331eb94c32a -chunks/67c396666365a0f8.js,1764292945315,63772f51138d6a11e29d5f270f1c2c7de60f1cb1218c0d5a51488272d01f265e -chunks/6c4b3aa006ad826c.js,1764292945616,523294bac6d6a537fc1dbfe5287ae46897908edc7e56b0fbac214b9ac3d4d6cc -chunks/6eca49992d798c82.js,1764292944887,12d6b568615b35975b20c73435dcdc7c11a27c92ca224cae23ddd1d5461c3544 -chunks/74e1fd68a7e0896e.js,1764292944962,05e56d1e6e1fca0c5ed8e0957cf019f861c764e7221f33d0ef52116238f814bc -chunks/5e558e7e27d2aa84.js,1764292945458,5f105cc0e1577cc8ebb66e71f6c5b8e564dd34db3a3542ada5f302e8a23e3b58 -chunks/58f8c6398723d54a.js,1764292945344,8673d8f45265a904abbae8551f71c5a5521c0725b93da73661b93af6a6583991 -chunks/5d0b6a3739039b40.js,1764292944980,bd68dfe5373212a24cb45220c93568159d5341b3198997a613fb6fd193880f98 -chunks/5db6f063644758f9.js,1764292944959,c80f1a857f8d76e5634e745e8bd7d8994e78eb33961668431cb366851cb16d5e -chunks/523ae041bd709184.js,1764292945042,ef06a0e01cf2cc8cf4883811ef022a392f335d1d38e9a989cf62bae079deea60 -chunks/424c4036add0df26.js,1764292945340,b3e7b7f805682f0b0436474ecded19ccce5b30dcb76a8495d8bee88eede11be0 -chunks/3d3465d604d848a9.js,1764292945339,92abaf6ffd0e336dee0fdfcf6fb6243aac311a212e834594b3e7de146e5d3f6e -chunks/334c0b45eeee04d3.js,1764292945574,874d6b35af5399e08a2cb7cc1b45cec6a7929205ae6371ef800dd924c20bc293 -chunks/3cf25d104286385c.js,1764292944988,7529dca0fda4234018cdbe2a24db01affdbf5bbe7ca6f97da46d3c1fa97ea8d4 -chunks/3e4ff1ad25a3aee4.js,1764292945339,234650a4582a73748c2f6e6d0ddc8a9aa3eb664cae80464126a35aa95f295615 -chunks/2fd974473265b3b8.js,1764292945207,fc5a8fab93123a055f6f1ca470cbb2ef213e4f075658f13038f265533887cdb8 -chunks/36fc507596e706a4.js,1764292944943,0f382cd8c81102e98bd414ca12d0e1951449a8dcaf734435e973e4e030b6a920 -chunks/211e6519dff5166f.js,1764292945000,388888d4e8a2df019664930f161a592dd7c676134ae9a2760abab30f10a1c12c -chunks/1f21d91f935fa2f4.js,1764292945414,3bcfd338aac600b69f6c50d060739432e3c8de64fb22ef7db4e2fbc88d35199f -chunks/483a049d7197220c.js,1764292944995,8f6f500d3b55f867e389a55ffc2a5808a01f75422e2bae4bc07e231d9f70d6f5 -chunks/1f6d845154a92f55.js,1764292945060,b9ac0f84700799143de73d09457b4973bb43f4ee0bf57ff742ef83491eef2aa5 -chunks/1f3d32af4b7e9fce.js,1764292944988,b589322566402033b3b58dd2ddba216b1819f82a8643a08877c4402c89de8cb7 -chunks/1ad9158bace97ad1.js,1764292945642,9ea54650308e293190ed8a7d7ef942e1029cc2cda2d2b03c1759fcd9b175c4e4 -chunks/0924dac1a36a5d4f.js,1764292945341,a823c1c585aea754343d4947d1c35350eec6544b9772486515756ec252992cb8 -chunks/22798aa879c2d479.js,1764292945062,a84d48fd0cb3fa97a0689f059806866fc2fe685e4c13b61b936bb13b7b729dc2 -chunks/01de74e34c8191ad.js,1764292945439,0f86a0b77fcffaa64a1869842351812295bca22dcdccf7a89616a0fe4a812848 -chunks/13c76ac4c576ebea.js,1764292945064,864fb695e806c8fd95eab7ff98fdf24c7fcab284d2eec9a2b030edea5ccd04a1 -chunks/a6dad97d9634a72d.js,1764292945738,bea630d9824beca22855271c757404b58bb7b410c52a5e7d58d69ff26d9ddd0b -chunks/055808b7b4395593.js,1764292945729,5a06bd47cb2c83a7b4363f3d7b02796224575ffce0dc51fb66157df312ea6239 -chunks/051191fc7c032fe7.js,1764292945459,5d675199d64b330bc32091e0d807a5d807c559ebbeb535d3b81e46d9ac0beba4 -chunks/5ba52f526366ce3d.js,1764292945252,94e2ba60b4d2d276adc47cc684fe3b41b7130b438a22f13fb03240cd3079b9c1 -chunks/0839f6c03dd07402.js,1764292945458,9b607c0411e2db92fea878d5d9450aceae119f0bb4f86eef757b7f012e353ba7 -chunks/02cfabe42ac75354.js,1764292945726,edd9cc50162ed881e6f63569e677d1f330cf09a782c74dd2af3183ac20cf23ed -chunks/770045fdeaa29947.js,1764292945315,fbff4e91fe383eb226ec79f5d7e557d3f1f798d724a8cd0da9e2606ecab997d5 -chunks/da99455a9bebb11a.js,1764292945437,96396c6b5792f967eb51bfdb0a59b2c10ef6c9cac6bab6ec346832219504f8c5 -chunks/667df385421d23bd.js,1764292945329,0f4ba7a21ad0772c31d49384e853c4f5cde23a277a76e93e14c6ffa8a4b00f1d -media/icon.69668ad2.png,1764292945757,a9312b012897c18eb945ecc474445181c063a6ff2363fefdd295bca3e7f17a70 diff --git a/vibn-frontend/.firebase/hosting.cHVibGlj.cache b/vibn-frontend/.firebase/hosting.cHVibGlj.cache deleted file mode 100644 index 4069802..0000000 --- a/vibn-frontend/.firebase/hosting.cHVibGlj.cache +++ /dev/null @@ -1,10 +0,0 @@ -window.svg,1762902124964,11deaca6eadbb148caace8a5fe4a67353112de0afc5da83005d4797e403ab4f1 -vobn-favicon.png,1763082657981,9051755f781b64be5155a8ef6b1846afae7ed12a942ce1fca217cde1fe0a4f09 -vibn-sqaure-black-logo.png,1763083818226,2cd39bf33b13110575f3a2b02b4558c5dd157d96506783526d11988a01dbe249 -vibn-logo-circle.png,1763083818571,b24c20ee6505547a3cd03a492681b281bf49c8e95eed78872e87581b5218eee2 -vibn-black-circle-logo.png,1763083818322,a9312b012897c18eb945ecc474445181c063a6ff2363fefdd295bca3e7f17a70 -vibn-2-logo.png,1763081817627,475fcbd3e4fa36dc5c63191220b5090ae90ae36d74d968240af25464829286fa -vercel.svg,1762902124964,9a61e768442ba3450026d0d69421315044931cbffaf8f6019f856ea82dd91e4e -next.svg,1762902124964,33c5c6ad1d08bb69d8026289530e377b4d6e2a96f24562e209fd1e1e9ccee64a -globe.svg,1762902124964,ffe166407c928caa4d1640e2786d3385468043b3b9e6ea2282d4a3e370b3bc23 -file.svg,1762902124964,154a8c2948836a88c695a789045bc44cc74c3d8958d5785a531d26324bc42cb1 diff --git a/vibn-frontend/.firebaserc b/vibn-frontend/.firebaserc deleted file mode 100644 index a0cc1bb..0000000 --- a/vibn-frontend/.firebaserc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "projects": { - "default": "gen-lang-client-0980079410" - } -} - diff --git a/vibn-frontend/app/(onboarding)/onboarding/onboarding-build.tsx b/vibn-frontend/app/(onboarding)/onboarding/onboarding-build.tsx new file mode 100644 index 0000000..dfba298 --- /dev/null +++ b/vibn-frontend/app/(onboarding)/onboarding/onboarding-build.tsx @@ -0,0 +1,447 @@ +import React, { useState, useEffect, useRef, useMemo, useCallback } from "react"; +import { WizardTop, WizardBody, WizardQ, LANE_LABELS } from "./onboarding-primitives"; +// Build + Ready screens. The build screen shows the terminal stream + a live +// preview stencil; ready is a quiet confirmation page with the workspace URL. + +// ── Per-path build plans ─────────────────────────────────────────────────── +function buildPlanFor(path, data) { + const common = [ + { line: "vibn init — reading brief", ms: 600 }, + { line: "↳ provisioning workspace", ms: 700 }, + { line: "↳ wiring auth (email + Google)", ms: 800 }, + { line: "↳ minting database & seed schema", ms: 700 }, + ]; + + if (path === "entrepreneur") { + return [ + ...common, + { line: `↳ generating landing page · vibe "${data.vibe || "warm"}"`, ms: 900 }, + { line: `↳ writing copy aimed at "${(data.audience || "your audience").slice(0, 40)}"`, ms: 800 }, + { line: "↳ wiring email capture + Stripe payment link", ms: 700 }, + { line: "↳ scaffolding admin: subscribers, sales, comments", ms: 800 }, + { line: `↳ tuning launch plan for goal: ${data.goal || "first_customer"}`, ms: 700 }, + { line: "↳ publishing preview → " + workspaceUrlFor(path, data), ms: 900 }, + { line: "ready.", ms: 400, ok: true }, + ]; + } + if (path === "owner") { + return [ + ...common, + { line: `↳ modelling ${data.biz || "small business"} for "${data.bizName || "your business"}"`, ms: 800 }, + { line: `↳ importing your stack (${(data.tools || []).length} tools)`, ms: 800 }, + { line: `↳ building module: ${labelFor(data.firstThing)}`, ms: 1000 }, + { line: "↳ generating customer + job records (10 sample)", ms: 700 }, + { line: "↳ scheduling daily ops view + weekly report", ms: 700 }, + { line: `↳ wiring savings tracker · est. $${(data.spend || 0)}/mo replaced`, ms: 800 }, + { line: "↳ publishing preview → " + workspaceUrlFor(path, data), ms: 900 }, + { line: "ready.", ms: 400, ok: true }, + ]; + } + return [ + ...common, + { line: `↳ branding workspace for "${data.clientName || "client"}"`, ms: 800 }, + { line: `↳ scaffolding scope (${(data.scope || []).length} modules)`, ms: 1000 }, + ...(data.scope || []).slice(0, 4).map((s) => ({ line: ` • ${s}`, ms: 350 })), + { line: "↳ generating handoff document + invoice template", ms: 700 }, + { line: `↳ setting deploy target: ${data.handoff || "subdomain"}`, ms: 700 }, + { line: "↳ publishing preview → " + workspaceUrlFor(path, data), ms: 900 }, + { line: "ready.", ms: 400, ok: true }, + ]; +} + +function labelFor(id) { + const map = { + booking: "Bookings & scheduling", + invoicing: "Quotes & invoices", + customers: "Customer portal", + inventory: "Inventory & orders", + team: "Team & dispatch", + marketing: "Marketing site", + }; + return map[id] || "your first workflow"; +} + +function workspaceUrlFor(path, data) { + const seed = + (path === "owner" && data.bizName) || + (path === "consultant" && data.clientName) || + (path === "entrepreneur" && (data.audience || data.idea)) || + "your-workspace"; + const slug = String(seed).toLowerCase() + .replace(/[^a-z0-9\s-]/g, "") + .trim() + .split(/\s+/) + .slice(0, 3) + .join("-") + .slice(0, 28) || "your-workspace"; + return `${slug}.vibn.app`; +} + +const BUILD_BIZ_LABEL = { + service: "Trades / services", + retail: "Retail", + food: "Food & drink", + appointments: "Appointments", + events: "Events / hospitality", + other: "Small business", +}; + +// ── Build screen ─────────────────────────────────────────────────────────── +export function BuildScreen({ path, data, onBack, onClose, onOpen }) { + const plan = React.useMemo(() => buildPlanFor(path, data), [path, data]); + const [lineIdx, setLineIdx] = React.useState(0); + const [done, setDone] = React.useState(false); + const logRef = React.useRef(null); + + React.useEffect(() => { + if (lineIdx >= plan.length) { setDone(true); return undefined; } + const t = setTimeout(() => setLineIdx(lineIdx + 1), plan[lineIdx].ms); + return () => clearTimeout(t); + }, [lineIdx, plan]); + + React.useEffect(() => { + if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight; + }, [lineIdx]); + + const url = workspaceUrlFor(path, data); + const lane = LANE_LABELS[path]; + const previewTitle = + path === "owner" ? (data.bizName || "Your business") : + path === "consultant" ? (data.clientName || "Your client") : + "Your launch page"; + const previewSub = + path === "owner" ? `${BUILD_BIZ_LABEL[data.biz] || "Small business"} · ${labelFor(data.firstThing)}` : + path === "consultant" ? (data.industry || "Project") : + (data.audience || "An idea worth building").slice(0, 64); + + const pct = done ? 1 : lineIdx / plan.length; + + return ( + <> + +
+
+ + + + +
+ {/* terminal */} +
+
+ + + + vibn build — {url} + {!done && live} +
+
+ {plan.slice(0, lineIdx + 1).map((p, i) => { + const isCurrent = i === lineIdx && !done; + const isOk = p.ok && i <= lineIdx; + const cls = isOk ? "l-ok" : isCurrent ? "l-current" : "l-done"; + return
{p.line}
; + })} + {!done &&
+
+ + {/* preview */} +
+
+ + + + + + https://{url} +
+
+
+
+ {previewTitle} +
+
+ {previewSub} +
+
+
+
+
+
+
+
+
+
+
+ +
+ + {done ? "build complete" : <>Building {Math.min(lineIdx, plan.length)}/{plan.length}} + + {done && ( + + )} +
+
+
+ + ); +} + +// ── Ready screen ─────────────────────────────────────────────────────────── +export function ReadyScreen({ path, data, onClose, onOpenChat }) { + const url = workspaceUrlFor(path, data); + const summary = summaryFor(path, data); + + return ( + <> + + +
+ + + +
+ + + +
+
+ + Workspace URL + + + {url} + +
+
+ {summary.map((row, i) => ( +
+ + {row.label} + + {row.value} +
+ ))} +
+
+ +
+ Back to home + +
+
+ + ); +} + +function summaryFor(path, data) { + if (path === "owner") { + return [ + { label: "Business", value: `${data.bizName || "Untitled"} · ${BUILD_BIZ_LABEL[data.biz] || "Small business"}` }, + { label: "Replacing", value: `${(data.tools || []).length} tools · ~$${data.spend || 0}/mo` }, + { label: "First fix", value: labelFor(data.firstThing) }, + { label: "Team", value: `${data.team || 1} · ${(data.customers || 0).toLocaleString()} cust/mo` }, + ]; + } + if (path === "consultant") { + return [ + { label: "Client", value: `${data.clientName || "Untitled"} · ${data.industry || ""}` }, + { label: "Scope", value: `${(data.scope || []).length} modules` }, + { label: "Brief", value: (data.brief || "").slice(0, 60) + ((data.brief || "").length > 60 ? "…" : "") }, + { label: "Handoff", value: data.handoff || "subdomain" }, + ]; + } + return [ + { label: "Building", value: (data.idea || "").slice(0, 64) + ((data.idea || "").length > 64 ? "…" : "") }, + { label: "Audience", value: (data.audience || "").slice(0, 64) }, + { label: "Goal", value: data.goal || "first_customer" }, + { label: "Vibe", value: data.vibe || "warm" }, + ]; +} + + diff --git a/vibn-frontend/app/(onboarding)/onboarding/onboarding-consultant.tsx b/vibn-frontend/app/(onboarding)/onboarding/onboarding-consultant.tsx new file mode 100644 index 0000000..43546f7 --- /dev/null +++ b/vibn-frontend/app/(onboarding)/onboarding/onboarding-consultant.tsx @@ -0,0 +1,296 @@ +import React, { useState, useEffect, useRef, useMemo, useCallback } from "react"; +import { WizardTop, WizardBody, WizardQ, WizardFooter, Label, LANE_LABELS, PresetGroup, Field } from "./onboarding-primitives"; +// Consultant path — 4 steps for freelancers building for a client. + +const CONS_TOTAL = 4; +const CONS_STEP_NAMES = ["Client", "Brief", "Scope", "Handoff"]; + +export function ConsClient({ clientName, industry, contact, onChange }) { + return ( + <> + + + onChange({ clientName: e.target.value })} + autoFocus + /> + + + onChange({ industry: e.target.value })} + /> + + + onChange({ contact: e.target.value })} + /> + + + ); +} + +const BRIEF_TEMPLATES = [ + { id: "quote_tool", label: "Quote tool", + body: "Customers request a quote with a few photos and a project description. The team reviews, sends a polished PDF, customer signs and pays a deposit online." }, + { id: "booking", label: "Booking system", + body: "Customers see real availability, book a service window, and get reminders. The team has a daily view of jobs with addresses and contact info." }, + { id: "portal", label: "Customer portal", + body: "Logged-in customers see past jobs, invoices, documents, and can message the business. The business sees a single page per customer." }, + { id: "internal", label: "Internal ops", + body: "Replace the spreadsheets the team is currently using. CRUD on jobs/customers, simple reports, role-based access, export to accounting." }, +]; + +function ConsBrief({ brief, onChange }) { + return ( + <> + + +
+ {BRIEF_TEMPLATES.map((t) => ( + + ))} +
+
+ +