Skip to main content

Vercel provider connection (hosted TypeScript service)

This is the canonical live demo path for Keycli right now. It is the safest honest way to prove that the hosted control plane can really mutate infrastructure:
  • single provider
  • preview environment
  • no deploy step
  • low risk under current policy

What is live now

  • ✅ store a Vercel ProviderConnection
  • ✅ test/validate that connection from the hosted API
  • ✅ surface workspace-scoped live readiness through authenticated GET /v1/capabilities
  • ✅ upsert/rotate Vercel env vars through the Vercel API
  • ✅ redeploy the latest Vercel deployment when a plan includes a deploy step
  • ✅ participate in mixed-provider execution when every provider in the plan is connected and supported

What falls back to simulation

If the workspace does not have a valid connected Vercel ProviderConnection, Keycli stays honest and returns:
  • plan.execution.mode: "local-provider-simulation"
  • a reason such as:
    • missing_vercel_connection
    • connection_needs_reauth
    • connection_invalid

Configure a Vercel connection

Start the hosted API:
npm run api:hosted
Bootstrap a workspace and capture the owner token:
BOOTSTRAP=$(curl -s -X POST http://localhost:8788/v1/bootstrap \
  -H 'content-type: application/json' \
  -d '{"workspaceName":"vercel-demo","ownerName":"ricco"}')

export KEYCLI_TOKEN=$(printf '%s' "$BOOTSTRAP" | node -e 'process.stdin.on("data",d=>{const x=JSON.parse(d); console.log(x.token.token)})')
Create the Vercel connection. Recommended: use an env-backed secret ref so Keycli never stores the raw token in the API object. If this token should only be used for one project, bind it explicitly with scope.
export VERCEL_TOKEN=...

curl -s -X POST http://localhost:8788/v1/connections \
  -H "authorization: Bearer $KEYCLI_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "provider": "vercel",
    "label": "main",
    "authMethod": "api_token",
    "credentialRef": "env:VERCEL_TOKEN",
    "scope": {
      "provider": "vercel",
      "project": "your-project"
    }
  }'

Test the connection

This validates the token and, if you pass projectIdOrName, confirms project access for the live path.
export KEYCLI_CONNECTION_ID=conn_...
export KEYCLI_VERCEL_PROJECT=your-project

curl -s -X POST "http://localhost:8788/v1/connections/$KEYCLI_CONNECTION_ID/test" \
  -H "authorization: Bearer $KEYCLI_TOKEN" \
  -H 'content-type: application/json' \
  -d "{\"projectIdOrName\":\"$KEYCLI_VERCEL_PROJECT\"}"
Expected success shape:
{
  "test": {
    "ok": true,
    "status": "connected",
    "liveExecutionAvailable": true,
    "reason": "vercel_project_access_confirmed"
  }
}
If the connection is project-scoped and you test a different project, Keycli returns reason: "vercel_scope_mismatch" and leaves live execution unavailable for that target.

Inspect workspace readiness

curl -s http://localhost:8788/v1/capabilities \
  -H "authorization: Bearer $KEYCLI_TOKEN"
Relevant response fragment:
{
  "capabilities": {
    "workspace": {
      "liveExecution": {
        "vercel": {
          "supported": true,
          "configured": true,
          "available": true,
          "reason": "connected",
          "scope": "vercel project your-project",
          "connectionScope": {
            "provider": "vercel",
            "project": "your-project"
          }
        }
      }
    }
  }
}

Canonical live demo: preview-only Vercel flow

One-command version

Terminal 1:
npm run api:hosted
Terminal 2:
export VERCEL_TOKEN=...
export KEYCLI_VERCEL_PROJECT=your-project
npm run demo:vercel:preview
That script:
  • bootstraps a workspace
  • issues an agent token
  • creates the Vercel connection
  • tests it against your project
  • prints workspace readiness
  • creates a preview-only Vercel plan
  • verifies the plan is in provider-api mode
  • applies it and waits for the run to finish

Manual curl flow

Create an agent token:
AGENT=$(curl -s -X POST http://localhost:8788/v1/tokens \
  -H "authorization: Bearer $KEYCLI_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"principalKind":"agent","principalName":"codex","label":"vercel-preview-demo"}')

export KEYCLI_AGENT_TOKEN=$(printf '%s' "$AGENT" | node -e 'process.stdin.on("data",d=>{const x=JSON.parse(d); console.log(x.token)})')
Create the plan:
PLAN=$(curl -s -X POST http://localhost:8788/v1/plans \
  -H "authorization: Bearer $KEYCLI_AGENT_TOKEN" \
  -H 'content-type: application/json' \
  -H 'idempotency-key: vercel-preview-demo-1' \
  -d "{
    \"action\": {
      \"action\": \"rotate_secret\",
      \"secretName\": \"KEYCLI_PREVIEW_TEST\",
      \"targets\": [
        { \"provider\": \"vercel\", \"project\": \"$KEYCLI_VERCEL_PROJECT\", \"environment\": \"preview\" }
      ],
      \"deployAfter\": false
    },
    \"value\": \"keycli_preview_demo_value\",
    \"requestChannel\": \"api\"
  }")

export PLAN_ID=$(printf '%s' "$PLAN" | node -e 'process.stdin.on("data",d=>{const x=JSON.parse(d); console.log(x.plan.id)})')
printf '%s\n' "$PLAN"
Expected properties:
  • plan.risk.level: "low"
  • response nextAction: "apply_plan"
  • plan.execution.mode: "provider-api"
  • plan.execution.reason: "vercel_connection_ready"
Apply it:
curl -s -X POST "http://localhost:8788/v1/plans/$PLAN_ID/apply" \
  -H "authorization: Bearer $KEYCLI_AGENT_TOKEN" \
  -H 'content-type: application/json' \
  -d '{}'

Deploy step behavior

When deployAfter: true, Keycli adds a vercel:deploy step. In provider-api mode this performs a real Vercel redeploy by:
  • resolving the project
  • looking up the latest deployment
  • requesting a redeploy
  • fetching best-effort deployment status
That path is real, but the recommended morning demo remains the preview-only no-deploy flow above because it is more repeatable and lower risk.

Safety / redaction

  • Secrets are retrieved via SecretStore.getSecret(ref) at apply/test time.
  • Secret values are not stored in plan diffs, run events, or audit logs.
  • Connection reads return redacted credentialRef values.