diff --git a/.agents/skills/deploy-to-vercel/SKILL.md b/.agents/skills/deploy-to-vercel/SKILL.md new file mode 100644 index 00000000..a0251ce8 --- /dev/null +++ b/.agents/skills/deploy-to-vercel/SKILL.md @@ -0,0 +1,296 @@ +--- +name: deploy-to-vercel +description: Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment". +metadata: + author: vercel + version: "3.0.0" +--- + +# Deploy to Vercel + +Deploy any project to Vercel. **Always deploy as preview** (not production) unless the user explicitly asks for production. + +The goal is to get the user into the best long-term setup: their project linked to Vercel with git-push deploys. Every method below tries to move the user closer to that state. + +## Step 1: Gather Project State + +Run all four checks before deciding which method to use: + +```bash +# 1. Check for a git remote +git remote get-url origin 2>/dev/null + +# 2. Check if locally linked to a Vercel project (either file means linked) +cat .vercel/project.json 2>/dev/null || cat .vercel/repo.json 2>/dev/null + +# 3. Check if the Vercel CLI is installed and authenticated +vercel whoami 2>/dev/null + +# 4. List available teams (if authenticated) +vercel teams list --format json 2>/dev/null +``` + +### Team selection + +If the user belongs to multiple teams, present all available team slugs as a bulleted list and ask which one to deploy to. Once the user picks a team, proceed immediately to the next step — do not ask for additional confirmation. + +Pass the team slug via `--scope` on all subsequent CLI commands (`vercel deploy`, `vercel link`, `vercel inspect`, etc.): + +```bash +vercel deploy [path] -y --no-wait --scope +``` + +If the project is already linked (`.vercel/project.json` or `.vercel/repo.json` exists), the `orgId` in those files determines the team — no need to ask again. If there is only one team (or just a personal account), skip the prompt and use it directly. + +**About the `.vercel/` directory:** A linked project has either: +- `.vercel/project.json` — created by `vercel link` (single project linking). Contains `projectId` and `orgId`. +- `.vercel/repo.json` — created by `vercel link --repo` (repo-based linking). Contains `orgId`, `remoteName`, and a `projects` array mapping directories to Vercel project IDs. + +Either file means the project is linked. Check for both. + +**Do NOT** use `vercel project inspect`, `vercel ls`, or `vercel link` to detect state in an unlinked directory — without a `.vercel/` config, they will interactively prompt (or with `--yes`, silently link as a side-effect). Only `vercel whoami` is safe to run anywhere. + +## Step 2: Choose a Deploy Method + +### Linked (`.vercel/` exists) + has git remote → Git Push + +This is the ideal state. The project is linked and has git integration. + +1. **Ask the user before pushing.** Never push without explicit approval: + ``` + This project is connected to Vercel via git. I can commit and push to + trigger a deployment. Want me to proceed? + ``` + +2. **Commit and push:** + ```bash + git add . + git commit -m "deploy: " + git push + ``` + Vercel automatically builds from the push. Non-production branches get preview deployments; the production branch (usually `main`) gets a production deployment. + +3. **Retrieve the preview URL.** If the CLI is authenticated: + ```bash + sleep 5 + vercel ls --format json + ``` + The JSON output has a `deployments` array. Find the latest entry — its `url` field is the preview URL. + + If the CLI is not authenticated, tell the user to check the Vercel dashboard or the commit status checks on their git provider for the preview URL. + +--- + +### Linked (`.vercel/` exists) + no git remote → `vercel deploy` + +The project is linked but there's no git repo. Deploy directly with the CLI. + +```bash +vercel deploy [path] -y --no-wait +``` + +Use `--no-wait` so the CLI returns immediately with the deployment URL instead of blocking until the build finishes (builds can take a while). Then check on the deployment status with: + +```bash +vercel inspect +``` + +For production deploys (only if user explicitly asks): +```bash +vercel deploy [path] --prod -y --no-wait +``` + +--- + +### Not linked + CLI is authenticated → Link first, then deploy + +The CLI is working but the project isn't linked yet. This is the opportunity to get the user into the best state. + +1. **Ask the user which team to deploy to.** Present the team slugs from Step 1 as a bulleted list. If there's only one team (or just a personal account), skip this step. + +2. **Once a team is selected, proceed directly to linking.** Tell the user what will happen but do not ask for separate confirmation: + ``` + Linking this project to on Vercel. This will create a Vercel + project to deploy to and enable automatic deployments on future git pushes. + ``` + +3. **If a git remote exists**, use repo-based linking with the selected team scope: + ```bash + vercel link --repo --scope + ``` + This reads the git remote URL and matches it to existing Vercel projects that deploy from that repo. It creates `.vercel/repo.json`. This is much more reliable than `vercel link` (without `--repo`), which tries to match by directory name and often fails when the local folder and Vercel project are named differently. + + **If there is no git remote**, fall back to standard linking: + ```bash + vercel link --scope + ``` + This prompts the user to select or create a project. It creates `.vercel/project.json`. + +4. **Then deploy using the best available method:** + - If a git remote exists → commit and push (see git push method above) + - If no git remote → `vercel deploy [path] -y --no-wait --scope `, then `vercel inspect ` to check status + +--- + +### Not linked + CLI not authenticated → Install, auth, link, deploy + +The Vercel CLI isn't set up at all. + +1. **Install the CLI (if not already installed):** + ```bash + npm install -g vercel + ``` + +2. **Authenticate:** + ```bash + vercel login + ``` + The user completes auth in their browser. If running in a non-interactive environment where login is not possible, skip to the **no-auth fallback** below. + +3. **Ask which team to deploy to** — present team slugs from `vercel teams list --format json` as a bulleted list. If only one team / personal account, skip. Once selected, proceed immediately. + +4. **Link the project** with the selected team scope (use `--repo` if a git remote exists, plain `vercel link` otherwise): + ```bash + vercel link --repo --scope # if git remote exists + vercel link --scope # if no git remote + ``` + +5. **Deploy** using the best available method (git push if remote exists, otherwise `vercel deploy -y --no-wait --scope `, then `vercel inspect ` to check status). + +--- + +### No-Auth Fallback — claude.ai sandbox + +**When to use:** Last resort when the CLI can't be installed or authenticated in the claude.ai sandbox. This requires no authentication — it returns a **Preview URL** (live site) and a **Claim URL** (transfer to your Vercel account). + +```bash +bash /mnt/skills/user/deploy-to-vercel/resources/deploy.sh [path] +``` + +**Arguments:** +- `path` - Directory to deploy, or a `.tgz` file (defaults to current directory) + +**Examples:** +```bash +# Deploy current directory +bash /mnt/skills/user/deploy-to-vercel/resources/deploy.sh + +# Deploy specific project +bash /mnt/skills/user/deploy-to-vercel/resources/deploy.sh /path/to/project + +# Deploy existing tarball +bash /mnt/skills/user/deploy-to-vercel/resources/deploy.sh /path/to/project.tgz +``` + +The script auto-detects the framework from `package.json`, packages the project (excluding `node_modules`, `.git`, `.env`), uploads it, and waits for the build to complete. + +**Tell the user:** "Your deployment is ready at [previewUrl]. Claim it at [claimUrl] to manage your deployment." + +--- + +### No-Auth Fallback — Codex sandbox + +**When to use:** In the Codex sandbox where the CLI may not be authenticated. Codex runs in a sandboxed environment by default — try the CLI first, and fall back to the deploy script if auth fails. + +1. **Check whether the Vercel CLI is installed** (no escalation needed for this check): + ```bash + command -v vercel + ``` + +2. **If `vercel` is installed**, try deploying with the CLI: + ```bash + vercel deploy [path] -y --no-wait + ``` + +3. **If `vercel` is not installed, or the CLI fails with "No existing credentials found"**, use the fallback script: + ```bash + skill_dir="" + + # Deploy current directory + bash "$skill_dir/resources/deploy-codex.sh" + + # Deploy specific project + bash "$skill_dir/resources/deploy-codex.sh" /path/to/project + + # Deploy existing tarball + bash "$skill_dir/resources/deploy-codex.sh" /path/to/project.tgz + ``` + +The script handles framework detection, packaging, and deployment. It waits for the build to complete and returns JSON with `previewUrl` and `claimUrl`. + +**Tell the user:** "Your deployment is ready at [previewUrl]. Claim it at [claimUrl] to manage your deployment." + +**Escalated network access:** Only escalate the actual deploy command if sandboxing blocks the network call (`sandbox_permissions=require_escalated`). Do **not** escalate the `command -v vercel` check. + +--- + +## Agent-Specific Notes + +### Claude Code / terminal-based agents + +You have full shell access. Do NOT use the `/mnt/skills/` path. Follow the decision flow above using the CLI directly. + +For the no-auth fallback, run the deploy script from the skill's installed location: +```bash +bash ~/.claude/skills/deploy-to-vercel/resources/deploy.sh [path] +``` +The path may vary depending on where the user installed the skill. + +### Sandboxed environments (claude.ai) + +You likely cannot run `vercel login` or `git push`. Go directly to the **no-auth fallback — claude.ai sandbox**. + +### Codex + +Codex runs in a sandbox. Check if the CLI is available first, then fall back to the deploy script. Go to the **no-auth fallback — Codex sandbox**. + +--- + +## Output + +Always show the user the deployment URL. + +- **Git push:** Use `vercel ls --format json` to find the preview URL. If the CLI isn't authenticated, tell the user to check the Vercel dashboard or commit status checks. +- **CLI deploy:** Show the URL returned by `vercel deploy --no-wait`. Use `vercel inspect ` to check build status and report it to the user. +- **No-auth fallback:** Show both the preview URL and the claim URL: + ``` + Deployment successful! + + Preview URL: https://my-app-abc123.vercel.app + Claim URL: https://vercel.com/claim-deployment?code=... + + View your site at the Preview URL. + To transfer this deployment to your Vercel account, visit the Claim URL. + ``` + +**Do not** curl or fetch the deployed URL to verify it works. Just return the link. + +--- + +## Troubleshooting + +### Network Egress Error (claude.ai) + +If deployment fails due to network restrictions on claude.ai, tell the user: + +``` +Deployment failed due to network restrictions. To fix this: + +1. Go to https://claude.ai/settings/capabilities +2. Add *.vercel.com to the allowed domains +3. Try deploying again +``` + +### Escalated Network Access (Codex / sandboxed environments) + +If deployment fails due to network issues (timeouts, DNS errors, connection resets) in a sandboxed environment, rerun with escalated permissions (`sandbox_permissions=require_escalated`). Do not escalate the initial CLI availability check — only escalate the actual deploy command. + +Example guidance to the user: +``` +The deploy needs escalated network access to deploy to Vercel. I can rerun +the command with escalated permissions — want me to proceed? +``` + +### CLI Auth Failure + +If `vercel login` or `vercel deploy` fails with authentication errors, fall back to the no-auth deploy script (claude.ai or Codex variant, depending on the environment). diff --git a/.agents/skills/deploy-to-vercel/resources/deploy-codex.sh b/.agents/skills/deploy-to-vercel/resources/deploy-codex.sh new file mode 100644 index 00000000..af07d0fd --- /dev/null +++ b/.agents/skills/deploy-to-vercel/resources/deploy-codex.sh @@ -0,0 +1,301 @@ +#!/bin/bash + +# Vercel Deployment Script for Codex (via claimable deploy endpoint) +# Usage: ./deploy-codex.sh [project-path] +# Returns: JSON with previewUrl, claimUrl, deploymentId, projectId + +set -euo pipefail + +DEPLOY_ENDPOINT="https://codex-deploy-skills.vercel.sh/api/deploy" + +# Detect framework from package.json +detect_framework() { + local pkg_json="$1" + + if [ ! -f "$pkg_json" ]; then + echo "null" + return + fi + + local content=$(cat "$pkg_json") + + # Helper to check if a package exists in dependencies or devDependencies. + # Use exact matching by default, with a separate prefix matcher for scoped + # package families like "@remix-run/". + has_dep_exact() { + echo "$content" | grep -q "\"$1\"" + } + + has_dep_prefix() { + echo "$content" | grep -q "\"$1" + } + + # Order matters - check more specific frameworks first + + # Blitz + if has_dep_exact "blitz"; then echo "blitzjs"; return; fi + + # Next.js + if has_dep_exact "next"; then echo "nextjs"; return; fi + + # Gatsby + if has_dep_exact "gatsby"; then echo "gatsby"; return; fi + + # Remix + if has_dep_prefix "@remix-run/"; then echo "remix"; return; fi + + # React Router (v7 framework mode) + if has_dep_prefix "@react-router/"; then echo "react-router"; return; fi + + # TanStack Start + if has_dep_exact "@tanstack/start"; then echo "tanstack-start"; return; fi + + # Astro + if has_dep_exact "astro"; then echo "astro"; return; fi + + # Hydrogen (Shopify) + if has_dep_exact "@shopify/hydrogen"; then echo "hydrogen"; return; fi + + # SvelteKit + if has_dep_exact "@sveltejs/kit"; then echo "sveltekit-1"; return; fi + + # Svelte (standalone) + if has_dep_exact "svelte"; then echo "svelte"; return; fi + + # Nuxt + if has_dep_exact "nuxt"; then echo "nuxtjs"; return; fi + + # Vue with Vitepress + if has_dep_exact "vitepress"; then echo "vitepress"; return; fi + + # Vue with Vuepress + if has_dep_exact "vuepress"; then echo "vuepress"; return; fi + + # Gridsome + if has_dep_exact "gridsome"; then echo "gridsome"; return; fi + + # SolidStart + if has_dep_exact "@solidjs/start"; then echo "solidstart-1"; return; fi + + # Docusaurus + if has_dep_exact "@docusaurus/core"; then echo "docusaurus-2"; return; fi + + # RedwoodJS + if has_dep_prefix "@redwoodjs/"; then echo "redwoodjs"; return; fi + + # Hexo + if has_dep_exact "hexo"; then echo "hexo"; return; fi + + # Eleventy + if has_dep_exact "@11ty/eleventy"; then echo "eleventy"; return; fi + + # Angular / Ionic Angular + if has_dep_exact "@ionic/angular"; then echo "ionic-angular"; return; fi + if has_dep_exact "@angular/core"; then echo "angular"; return; fi + + # Ionic React + if has_dep_exact "@ionic/react"; then echo "ionic-react"; return; fi + + # Create React App + if has_dep_exact "react-scripts"; then echo "create-react-app"; return; fi + + # Ember + if has_dep_exact "ember-cli" || has_dep_exact "ember-source"; then echo "ember"; return; fi + + # Dojo + if has_dep_exact "@dojo/framework"; then echo "dojo"; return; fi + + # Polymer + if has_dep_prefix "@polymer/"; then echo "polymer"; return; fi + + # Preact + if has_dep_exact "preact"; then echo "preact"; return; fi + + # Stencil + if has_dep_exact "@stencil/core"; then echo "stencil"; return; fi + + # UmiJS + if has_dep_exact "umi"; then echo "umijs"; return; fi + + # Sapper (legacy Svelte) + if has_dep_exact "sapper"; then echo "sapper"; return; fi + + # Saber + if has_dep_exact "saber"; then echo "saber"; return; fi + + # Sanity + if has_dep_exact "sanity"; then echo "sanity-v3"; return; fi + if has_dep_prefix "@sanity/"; then echo "sanity"; return; fi + + # Storybook + if has_dep_prefix "@storybook/"; then echo "storybook"; return; fi + + # NestJS + if has_dep_exact "@nestjs/core"; then echo "nestjs"; return; fi + + # Elysia + if has_dep_exact "elysia"; then echo "elysia"; return; fi + + # Hono + if has_dep_exact "hono"; then echo "hono"; return; fi + + # Fastify + if has_dep_exact "fastify"; then echo "fastify"; return; fi + + # h3 + if has_dep_exact "h3"; then echo "h3"; return; fi + + # Nitro + if has_dep_exact "nitropack"; then echo "nitro"; return; fi + + # Express + if has_dep_exact "express"; then echo "express"; return; fi + + # Vite (generic - check last among JS frameworks) + if has_dep_exact "vite"; then echo "vite"; return; fi + + # Parcel + if has_dep_exact "parcel"; then echo "parcel"; return; fi + + # No framework detected + echo "null" +} + +# Parse arguments +INPUT_PATH="${1:-.}" + +# Create temp directory for packaging +TEMP_DIR=$(mktemp -d) +TARBALL="$TEMP_DIR/project.tgz" +STAGING_DIR="$TEMP_DIR/staging" +CLEANUP_TEMP=true + +cleanup() { + if [ "$CLEANUP_TEMP" = true ]; then + rm -rf "$TEMP_DIR" + fi +} +trap cleanup EXIT + +echo "Preparing deployment..." >&2 + +# Check if input is a .tgz file or a directory +FRAMEWORK="null" + +if [ -f "$INPUT_PATH" ] && [[ "$INPUT_PATH" == *.tgz ]]; then + # Input is already a tarball, use it directly + echo "Using provided tarball..." >&2 + TARBALL="$INPUT_PATH" + CLEANUP_TEMP=false + # Can't detect framework from tarball, leave as null +elif [ -d "$INPUT_PATH" ]; then + # Input is a directory, need to tar it + PROJECT_PATH=$(cd "$INPUT_PATH" && pwd) + + # Detect framework from package.json + FRAMEWORK=$(detect_framework "$PROJECT_PATH/package.json") + + # Stage files into a temporary directory to avoid mutating the source tree. + mkdir -p "$STAGING_DIR" + echo "Staging project files..." >&2 + tar -C "$PROJECT_PATH" \ + --exclude='node_modules' \ + --exclude='.git' \ + --exclude='.env' \ + --exclude='.env.*' \ + -cf - . | tar -C "$STAGING_DIR" -xf - + + # Check if this is a static HTML project (no package.json) + if [ ! -f "$PROJECT_PATH/package.json" ]; then + # Find HTML files in root + HTML_FILES=$(find "$STAGING_DIR" -maxdepth 1 -name "*.html" -type f) + HTML_COUNT=$(printf '%s\n' "$HTML_FILES" | sed '/^$/d' | wc -l | tr -d '[:space:]') + + # If there's exactly one HTML file and it's not index.html, rename it + if [ "$HTML_COUNT" -eq 1 ]; then + HTML_FILE=$(echo "$HTML_FILES" | head -1) + BASENAME=$(basename "$HTML_FILE") + if [ "$BASENAME" != "index.html" ]; then + echo "Renaming $BASENAME to index.html..." >&2 + mv "$HTML_FILE" "$STAGING_DIR/index.html" + fi + fi + fi + + # Create tarball from the staging directory + echo "Creating deployment package..." >&2 + tar -czf "$TARBALL" -C "$STAGING_DIR" . +else + echo "Error: Input must be a directory or a .tgz file" >&2 + exit 1 +fi + +if [ "$FRAMEWORK" != "null" ]; then + echo "Detected framework: $FRAMEWORK" >&2 +fi + +# Deploy +echo "Deploying..." >&2 +RESPONSE=$(curl -s -X POST "$DEPLOY_ENDPOINT" -F "file=@$TARBALL" -F "framework=$FRAMEWORK") + +# Check for error in response +if echo "$RESPONSE" | grep -q '"error"'; then + ERROR_MSG=$(echo "$RESPONSE" | grep -o '"error":"[^"]*"' | cut -d'"' -f4) + echo "Error: $ERROR_MSG" >&2 + exit 1 +fi + +# Extract URLs from response +PREVIEW_URL=$(echo "$RESPONSE" | grep -o '"previewUrl":"[^"]*"' | cut -d'"' -f4) +CLAIM_URL=$(echo "$RESPONSE" | grep -o '"claimUrl":"[^"]*"' | cut -d'"' -f4) + +if [ -z "$PREVIEW_URL" ]; then + echo "Error: Could not extract preview URL from response" >&2 + echo "$RESPONSE" >&2 + exit 1 +fi + +echo "Deployment started. Waiting for build to complete..." >&2 +echo "Preview URL: $PREVIEW_URL" >&2 + +# Poll the preview URL until it returns a non-5xx response (5xx = still building) +MAX_ATTEMPTS=60 # 5 minutes max (60 * 5 seconds) +ATTEMPT=0 + +while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$PREVIEW_URL") + + if [ "$HTTP_STATUS" -eq 200 ]; then + echo "" >&2 + echo "Deployment ready!" >&2 + break + elif [ "$HTTP_STATUS" -ge 500 ]; then + # 5xx means still building/deploying + echo "Building... (attempt $((ATTEMPT + 1))/$MAX_ATTEMPTS)" >&2 + sleep 5 + ATTEMPT=$((ATTEMPT + 1)) + elif [ "$HTTP_STATUS" -ge 400 ] && [ "$HTTP_STATUS" -lt 500 ]; then + # 4xx might be an error or the app itself returns 4xx - it's responding + echo "" >&2 + echo "Deployment ready (returned $HTTP_STATUS)!" >&2 + break + else + # Any other status, assume it's ready + echo "" >&2 + echo "Deployment ready!" >&2 + break + fi +done + +if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then + echo "" >&2 + echo "Warning: Timed out waiting for deployment, but it may still be building." >&2 +fi + +echo "" >&2 +echo "Preview URL: $PREVIEW_URL" >&2 +echo "Claim URL: $CLAIM_URL" >&2 +echo "" >&2 + +# Output JSON for programmatic use +echo "$RESPONSE" diff --git a/.agents/skills/deploy-to-vercel/resources/deploy.sh b/.agents/skills/deploy-to-vercel/resources/deploy.sh new file mode 100644 index 00000000..a458da63 --- /dev/null +++ b/.agents/skills/deploy-to-vercel/resources/deploy.sh @@ -0,0 +1,301 @@ +#!/bin/bash + +# Vercel Deployment Script (via claimable deploy endpoint) +# Usage: ./deploy.sh [project-path] +# Returns: JSON with previewUrl, claimUrl, deploymentId, projectId + +set -euo pipefail + +DEPLOY_ENDPOINT="https://claude-skills-deploy.vercel.com/api/deploy" + +# Detect framework from package.json +detect_framework() { + local pkg_json="$1" + + if [ ! -f "$pkg_json" ]; then + echo "null" + return + fi + + local content=$(cat "$pkg_json") + + # Helper to check if a package exists in dependencies or devDependencies. + # Use exact matching by default, with a separate prefix matcher for scoped + # package families like "@remix-run/". + has_dep_exact() { + echo "$content" | grep -q "\"$1\"" + } + + has_dep_prefix() { + echo "$content" | grep -q "\"$1" + } + + # Order matters - check more specific frameworks first + + # Blitz + if has_dep_exact "blitz"; then echo "blitzjs"; return; fi + + # Next.js + if has_dep_exact "next"; then echo "nextjs"; return; fi + + # Gatsby + if has_dep_exact "gatsby"; then echo "gatsby"; return; fi + + # Remix + if has_dep_prefix "@remix-run/"; then echo "remix"; return; fi + + # React Router (v7 framework mode) + if has_dep_prefix "@react-router/"; then echo "react-router"; return; fi + + # TanStack Start + if has_dep_exact "@tanstack/start"; then echo "tanstack-start"; return; fi + + # Astro + if has_dep_exact "astro"; then echo "astro"; return; fi + + # Hydrogen (Shopify) + if has_dep_exact "@shopify/hydrogen"; then echo "hydrogen"; return; fi + + # SvelteKit + if has_dep_exact "@sveltejs/kit"; then echo "sveltekit-1"; return; fi + + # Svelte (standalone) + if has_dep_exact "svelte"; then echo "svelte"; return; fi + + # Nuxt + if has_dep_exact "nuxt"; then echo "nuxtjs"; return; fi + + # Vue with Vitepress + if has_dep_exact "vitepress"; then echo "vitepress"; return; fi + + # Vue with Vuepress + if has_dep_exact "vuepress"; then echo "vuepress"; return; fi + + # Gridsome + if has_dep_exact "gridsome"; then echo "gridsome"; return; fi + + # SolidStart + if has_dep_exact "@solidjs/start"; then echo "solidstart-1"; return; fi + + # Docusaurus + if has_dep_exact "@docusaurus/core"; then echo "docusaurus-2"; return; fi + + # RedwoodJS + if has_dep_prefix "@redwoodjs/"; then echo "redwoodjs"; return; fi + + # Hexo + if has_dep_exact "hexo"; then echo "hexo"; return; fi + + # Eleventy + if has_dep_exact "@11ty/eleventy"; then echo "eleventy"; return; fi + + # Angular / Ionic Angular + if has_dep_exact "@ionic/angular"; then echo "ionic-angular"; return; fi + if has_dep_exact "@angular/core"; then echo "angular"; return; fi + + # Ionic React + if has_dep_exact "@ionic/react"; then echo "ionic-react"; return; fi + + # Create React App + if has_dep_exact "react-scripts"; then echo "create-react-app"; return; fi + + # Ember + if has_dep_exact "ember-cli" || has_dep_exact "ember-source"; then echo "ember"; return; fi + + # Dojo + if has_dep_exact "@dojo/framework"; then echo "dojo"; return; fi + + # Polymer + if has_dep_prefix "@polymer/"; then echo "polymer"; return; fi + + # Preact + if has_dep_exact "preact"; then echo "preact"; return; fi + + # Stencil + if has_dep_exact "@stencil/core"; then echo "stencil"; return; fi + + # UmiJS + if has_dep_exact "umi"; then echo "umijs"; return; fi + + # Sapper (legacy Svelte) + if has_dep_exact "sapper"; then echo "sapper"; return; fi + + # Saber + if has_dep_exact "saber"; then echo "saber"; return; fi + + # Sanity + if has_dep_exact "sanity"; then echo "sanity-v3"; return; fi + if has_dep_prefix "@sanity/"; then echo "sanity"; return; fi + + # Storybook + if has_dep_prefix "@storybook/"; then echo "storybook"; return; fi + + # NestJS + if has_dep_exact "@nestjs/core"; then echo "nestjs"; return; fi + + # Elysia + if has_dep_exact "elysia"; then echo "elysia"; return; fi + + # Hono + if has_dep_exact "hono"; then echo "hono"; return; fi + + # Fastify + if has_dep_exact "fastify"; then echo "fastify"; return; fi + + # h3 + if has_dep_exact "h3"; then echo "h3"; return; fi + + # Nitro + if has_dep_exact "nitropack"; then echo "nitro"; return; fi + + # Express + if has_dep_exact "express"; then echo "express"; return; fi + + # Vite (generic - check last among JS frameworks) + if has_dep_exact "vite"; then echo "vite"; return; fi + + # Parcel + if has_dep_exact "parcel"; then echo "parcel"; return; fi + + # No framework detected + echo "null" +} + +# Parse arguments +INPUT_PATH="${1:-.}" + +# Create temp directory for packaging +TEMP_DIR=$(mktemp -d) +TARBALL="$TEMP_DIR/project.tgz" +STAGING_DIR="$TEMP_DIR/staging" +CLEANUP_TEMP=true + +cleanup() { + if [ "$CLEANUP_TEMP" = true ]; then + rm -rf "$TEMP_DIR" + fi +} +trap cleanup EXIT + +echo "Preparing deployment..." >&2 + +# Check if input is a .tgz file or a directory +FRAMEWORK="null" + +if [ -f "$INPUT_PATH" ] && [[ "$INPUT_PATH" == *.tgz ]]; then + # Input is already a tarball, use it directly + echo "Using provided tarball..." >&2 + TARBALL="$INPUT_PATH" + CLEANUP_TEMP=false + # Can't detect framework from tarball, leave as null +elif [ -d "$INPUT_PATH" ]; then + # Input is a directory, need to tar it + PROJECT_PATH=$(cd "$INPUT_PATH" && pwd) + + # Detect framework from package.json + FRAMEWORK=$(detect_framework "$PROJECT_PATH/package.json") + + # Stage files into a temporary directory to avoid mutating the source tree. + mkdir -p "$STAGING_DIR" + echo "Staging project files..." >&2 + tar -C "$PROJECT_PATH" \ + --exclude='node_modules' \ + --exclude='.git' \ + --exclude='.env' \ + --exclude='.env.*' \ + -cf - . | tar -C "$STAGING_DIR" -xf - + + # Check if this is a static HTML project (no package.json) + if [ ! -f "$PROJECT_PATH/package.json" ]; then + # Find HTML files in root + HTML_FILES=$(find "$STAGING_DIR" -maxdepth 1 -name "*.html" -type f) + HTML_COUNT=$(printf '%s\n' "$HTML_FILES" | sed '/^$/d' | wc -l | tr -d '[:space:]') + + # If there's exactly one HTML file and it's not index.html, rename it + if [ "$HTML_COUNT" -eq 1 ]; then + HTML_FILE=$(echo "$HTML_FILES" | head -1) + BASENAME=$(basename "$HTML_FILE") + if [ "$BASENAME" != "index.html" ]; then + echo "Renaming $BASENAME to index.html..." >&2 + mv "$HTML_FILE" "$STAGING_DIR/index.html" + fi + fi + fi + + # Create tarball from the staging directory + echo "Creating deployment package..." >&2 + tar -czf "$TARBALL" -C "$STAGING_DIR" . +else + echo "Error: Input must be a directory or a .tgz file" >&2 + exit 1 +fi + +if [ "$FRAMEWORK" != "null" ]; then + echo "Detected framework: $FRAMEWORK" >&2 +fi + +# Deploy +echo "Deploying..." >&2 +RESPONSE=$(curl -s -X POST "$DEPLOY_ENDPOINT" -F "file=@$TARBALL" -F "framework=$FRAMEWORK") + +# Check for error in response +if echo "$RESPONSE" | grep -q '"error"'; then + ERROR_MSG=$(echo "$RESPONSE" | grep -o '"error":"[^"]*"' | cut -d'"' -f4) + echo "Error: $ERROR_MSG" >&2 + exit 1 +fi + +# Extract URLs from response +PREVIEW_URL=$(echo "$RESPONSE" | grep -o '"previewUrl":"[^"]*"' | cut -d'"' -f4) +CLAIM_URL=$(echo "$RESPONSE" | grep -o '"claimUrl":"[^"]*"' | cut -d'"' -f4) + +if [ -z "$PREVIEW_URL" ]; then + echo "Error: Could not extract preview URL from response" >&2 + echo "$RESPONSE" >&2 + exit 1 +fi + +echo "Deployment started. Waiting for build to complete..." >&2 +echo "Preview URL: $PREVIEW_URL" >&2 + +# Poll the preview URL until it returns a non-5xx response (5xx = still building) +MAX_ATTEMPTS=60 # 5 minutes max (60 * 5 seconds) +ATTEMPT=0 + +while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$PREVIEW_URL") + + if [ "$HTTP_STATUS" -eq 200 ]; then + echo "" >&2 + echo "Deployment ready!" >&2 + break + elif [ "$HTTP_STATUS" -ge 500 ]; then + # 5xx means still building/deploying + echo "Building... (attempt $((ATTEMPT + 1))/$MAX_ATTEMPTS)" >&2 + sleep 5 + ATTEMPT=$((ATTEMPT + 1)) + elif [ "$HTTP_STATUS" -ge 400 ] && [ "$HTTP_STATUS" -lt 500 ]; then + # 4xx might be an error or the app itself returns 4xx - it's responding + echo "" >&2 + echo "Deployment ready (returned $HTTP_STATUS)!" >&2 + break + else + # Any other status, assume it's ready + echo "" >&2 + echo "Deployment ready!" >&2 + break + fi +done + +if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then + echo "" >&2 + echo "Warning: Timed out waiting for deployment, but it may still be building." >&2 +fi + +echo "" >&2 +echo "Preview URL: $PREVIEW_URL" >&2 +echo "Claim URL: $CLAIM_URL" >&2 +echo "" >&2 + +# Output JSON for programmatic use +echo "$RESPONSE" diff --git a/.agents/skills/vercel-cli-with-tokens/SKILL.md b/.agents/skills/vercel-cli-with-tokens/SKILL.md new file mode 100644 index 00000000..814423d0 --- /dev/null +++ b/.agents/skills/vercel-cli-with-tokens/SKILL.md @@ -0,0 +1,353 @@ +--- +name: vercel-cli-with-tokens +description: Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel". +metadata: + author: vercel + version: "1.0.0" +--- + +# Vercel CLI with Tokens + +Deploy and manage projects on Vercel using the CLI with token-based authentication, without relying on `vercel login`. + +## Step 1: Locate the Vercel Token + +Before running any Vercel CLI commands, identify where the token is coming from. Work through these scenarios in order: + +### A) `VERCEL_TOKEN` is already set in the environment + +```bash +printenv VERCEL_TOKEN +``` + +If this returns a value, you're ready. Skip to Step 2. + +### B) Token is in a `.env` file under `VERCEL_TOKEN` + +```bash +grep '^VERCEL_TOKEN=' .env 2>/dev/null +``` + +If found, export it: + +```bash +export VERCEL_TOKEN=$(grep '^VERCEL_TOKEN=' .env | cut -d= -f2-) +``` + +### C) Token is in a `.env` file under a different name + +Look for any variable that looks like a Vercel token (Vercel tokens typically start with `vca_`): + +```bash +grep -i 'vercel' .env 2>/dev/null +``` + +Inspect the output to identify which variable holds the token, then export it as `VERCEL_TOKEN`: + +```bash +export VERCEL_TOKEN=$(grep '^=' .env | cut -d= -f2-) +``` + +### D) No token found — ask the user + +If none of the above yield a token, ask the user to provide one. They can create a Vercel access token at vercel.com/account/tokens. + +--- + +**Important:** Once `VERCEL_TOKEN` is exported as an environment variable, the Vercel CLI reads it natively — **do not pass it as a `--token` flag**. Putting secrets in command-line arguments exposes them in shell history and process listings. + +```bash +# Bad — token visible in shell history and process listings +vercel deploy --token "vca_abc123" + +# Good — CLI reads VERCEL_TOKEN from the environment +export VERCEL_TOKEN="vca_abc123" +vercel deploy +``` + +## Step 2: Locate the Project and Team + +Similarly, check for the project ID and team scope. These let the CLI target the right project without needing `vercel link`. + +```bash +# Check environment +printenv VERCEL_PROJECT_ID +printenv VERCEL_ORG_ID + +# Or check .env +grep -i 'vercel' .env 2>/dev/null +``` + +**If you have a project URL** (e.g. `https://vercel.com/my-team/my-project`), extract the team slug: + +```bash +# e.g. "my-team" from "https://vercel.com/my-team/my-project" +echo "$PROJECT_URL" | sed 's|https://vercel.com/||' | cut -d/ -f1 +``` + +**If you have both `VERCEL_ORG_ID` and `VERCEL_PROJECT_ID` in your environment**, export them — the CLI will use these automatically and skip any `.vercel/` directory: + +```bash +export VERCEL_ORG_ID="" +export VERCEL_PROJECT_ID="" +``` + +Note: `VERCEL_ORG_ID` and `VERCEL_PROJECT_ID` must be set together — setting only one causes an error. + +## CLI Setup + +Ensure the Vercel CLI is installed and up to date: + +```bash +npm install -g vercel +vercel --version +``` + +## Deploying a Project + +Always deploy as **preview** unless the user explicitly requests production. Choose a method based on what you have available. + +### Quick Deploy (have project ID — no linking needed) + +When `VERCEL_TOKEN` and `VERCEL_PROJECT_ID` are set in the environment, deploy directly: + +```bash +vercel deploy -y --no-wait +``` + +With a team scope (either via `VERCEL_ORG_ID` or `--scope`): + +```bash +vercel deploy --scope -y --no-wait +``` + +Production (only when explicitly requested): + +```bash +vercel deploy --prod --scope -y --no-wait +``` + +Check status: + +```bash +vercel inspect +``` + +### Full Deploy Flow (no project ID — need to link) + +Use this when you have a token and team but no pre-existing project ID. + +#### Check project state first + +```bash +# Does the project have a git remote? +git remote get-url origin 2>/dev/null + +# Is it already linked to a Vercel project? +cat .vercel/project.json 2>/dev/null || cat .vercel/repo.json 2>/dev/null +``` + +#### Link the project + +**With git remote (preferred):** + +```bash +vercel link --repo --scope -y +``` + +Reads the git remote and connects to the matching Vercel project. Creates `.vercel/repo.json`. More reliable than plain `vercel link`, which matches by directory name. + +**Without git remote:** + +```bash +vercel link --scope -y +``` + +Creates `.vercel/project.json`. + +**Link to a specific project by name:** + +```bash +vercel link --project --scope -y +``` + +If the project is already linked, check `orgId` in `.vercel/project.json` or `.vercel/repo.json` to verify it matches the intended team. + +#### Deploy after linking + +**A) Git Push Deploy — has git remote (preferred)** + +Git pushes trigger automatic Vercel deployments. + +1. **Ask the user before pushing.** Never push without explicit approval. +2. Commit and push: + ```bash + git add . + git commit -m "deploy: " + git push + ``` +3. Vercel builds automatically. Non-production branches get preview deployments. +4. Retrieve the deployment URL: + ```bash + sleep 5 + vercel ls --format json --scope + ``` + Find the latest entry in the `deployments` array. + +**B) CLI Deploy — no git remote** + +```bash +vercel deploy --scope -y --no-wait +``` + +Check status: + +```bash +vercel inspect +``` + +### Deploying from a Remote Repository (code not cloned locally) + +1. Clone the repository: + ```bash + git clone + cd + ``` +2. Link to Vercel: + ```bash + vercel link --repo --scope -y + ``` +3. Deploy via git push (if you have push access) or CLI deploy. + +### About `.vercel/` Directory + +A linked project has either: +- `.vercel/project.json` — from `vercel link`. Contains `projectId` and `orgId`. +- `.vercel/repo.json` — from `vercel link --repo`. Contains `orgId`, `remoteName`, and a `projects` map. + +Not needed when `VERCEL_ORG_ID` + `VERCEL_PROJECT_ID` are both set in the environment. + +**Do NOT** run `vercel project inspect` or `vercel link` in an unlinked directory to detect state — they will interactively prompt or silently link as a side-effect. `vercel ls` is safe (in an unlinked directory it defaults to showing all deployments for the scope). `vercel whoami` is safe anywhere. + +## Managing Environment Variables + +```bash +# Set for all environments +echo "value" | vercel env add VAR_NAME --scope + +# Set for a specific environment (production, preview, development) +echo "value" | vercel env add VAR_NAME production --scope + +# List environment variables +vercel env ls --scope + +# Pull env vars to local .env.local file +vercel env pull --scope + +# Remove a variable +vercel env rm VAR_NAME --scope -y +``` + +## Inspecting Deployments + +```bash +# List recent deployments +vercel ls --format json --scope + +# Inspect a specific deployment +vercel inspect + +# View build logs (requires Vercel CLI v35+) +vercel inspect --logs + +# View runtime request logs (follows live by default; add --no-follow for a one-shot snapshot) +vercel logs +``` + +## Managing Domains + +```bash +# List domains +vercel domains ls --scope + +# Add a domain to the project — linked or env-linked directory (1 arg) +vercel domains add --scope + +# Add a domain — unlinked directory (requires positional) +vercel domains add --scope +``` + +## Stripe Projects Plan Changes + +If this project is managed by Stripe Projects. **Ask the user before running any paid or destructive plan change** — upgrades bill a real card, downgrades remove seats. + +First run `stripe projects status --json` to confirm the Vercel resource's local name. The examples below assume the default (`vercel-plan`); substitute the actual name if it was renamed at `stripe projects add` time. + +- **Upgrade to Pro:** `stripe projects add vercel/pro` (or `stripe projects upgrade vercel-plan pro`) +- **Downgrade to Hobby:** `stripe projects downgrade vercel-plan hobby` + +### What Pro gives you + +- $20/month platform fee, includes $20/month of usage credit. +- Turbo build machines (30 vCPUs, 60 GB memory) by default for new projects — significantly faster builds than Hobby. +- 1 deploying seat + unlimited free Viewer seats (read-only collaborators, preview comments). +- Higher included allocations (1 TB Fast Data Transfer, 10M Edge Requests per month). +- Paid add-ons available: SAML SSO, HIPAA BAA, Flags Explorer, Observability Plus, Speed Insights, Web Analytics Plus. + +Full details: https://vercel.com/docs/plans/pro-plan + +## Working Agreement + +- **Never pass `VERCEL_TOKEN` as a `--token` flag.** Export it as an environment variable and let the CLI read it natively. +- **Check the environment for tokens before asking the user.** Look in the current env and `.env` files first. +- **Default to preview deployments.** Only deploy to production when explicitly asked. +- **Ask before pushing to git.** Never push commits without the user's approval. +- **Do not modify `.vercel/` files directly.** The CLI manages this directory. Reading them (e.g. to verify `orgId`) is fine. +- **Do not curl/fetch deployed URLs to verify.** Just return the link to the user. +- **Use `--format json`** when structured output will help with follow-up steps. +- **Use `-y`** on commands that prompt for confirmation to avoid interactive blocking. + +## Troubleshooting + +### Token not found + +Check the environment and any `.env` files present: + +```bash +printenv | grep -i vercel +grep -i vercel .env 2>/dev/null +``` + +### Authentication error + +If the CLI fails with `Authentication required`: +- The token may be expired or invalid. +- Verify: `vercel whoami` (uses `VERCEL_TOKEN` from environment). +- Ask the user for a fresh token. + +### Wrong team + +Verify the scope is correct: + +```bash +vercel whoami --scope +``` + +### Build failure + +Check the build logs: + +```bash +vercel inspect --logs +``` + +Common causes: +- Missing dependencies — ensure `package.json` is complete and committed. +- Missing environment variables — add with `vercel env add`. +- Framework misconfiguration — check `vercel.json`. Vercel auto-detects frameworks (Next.js, Remix, Vite, etc.) from `package.json`; override with `vercel.json` if detection is wrong. + +### CLI not installed + +```bash +npm install -g vercel +``` diff --git a/.agents/skills/vercel-composition-patterns/AGENTS.md b/.agents/skills/vercel-composition-patterns/AGENTS.md new file mode 100644 index 00000000..558bf9aa --- /dev/null +++ b/.agents/skills/vercel-composition-patterns/AGENTS.md @@ -0,0 +1,946 @@ +# React Composition Patterns + +**Version 1.0.0** +Engineering +January 2026 + +> **Note:** +> This document is mainly for agents and LLMs to follow when maintaining, +> generating, or refactoring React codebases using composition. Humans +> may also find it useful, but guidance here is optimized for automation +> and consistency by AI-assisted workflows. + +--- + +## Abstract + +Composition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier for both humans and AI agents to work with as they scale. + +--- + +## Table of Contents + +1. [Component Architecture](#1-component-architecture) — **HIGH** + - 1.1 [Avoid Boolean Prop Proliferation](#11-avoid-boolean-prop-proliferation) + - 1.2 [Use Compound Components](#12-use-compound-components) +2. [State Management](#2-state-management) — **MEDIUM** + - 2.1 [Decouple State Management from UI](#21-decouple-state-management-from-ui) + - 2.2 [Define Generic Context Interfaces for Dependency Injection](#22-define-generic-context-interfaces-for-dependency-injection) + - 2.3 [Lift State into Provider Components](#23-lift-state-into-provider-components) +3. [Implementation Patterns](#3-implementation-patterns) — **MEDIUM** + - 3.1 [Create Explicit Component Variants](#31-create-explicit-component-variants) + - 3.2 [Prefer Composing Children Over Render Props](#32-prefer-composing-children-over-render-props) +4. [React 19 APIs](#4-react-19-apis) — **MEDIUM** + - 4.1 [React 19 API Changes](#41-react-19-api-changes) + +--- + +## 1. Component Architecture + +**Impact: HIGH** + +Fundamental patterns for structuring components to avoid prop +proliferation and enable flexible composition. + +### 1.1 Avoid Boolean Prop Proliferation + +**Impact: CRITICAL (prevents unmaintainable component variants)** + +Don't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize + +component behavior. Each boolean doubles possible states and creates + +unmaintainable conditional logic. Use composition instead. + +**Incorrect: boolean props create exponential complexity** + +```tsx +function Composer({ + onSubmit, + isThread, + channelId, + isDMThread, + dmId, + isEditing, + isForwarding, +}: Props) { + return ( +
+
+ + {isDMThread ? ( + + ) : isThread ? ( + + ) : null} + {isEditing ? ( + + ) : isForwarding ? ( + + ) : ( + + )} +