# Git & GitHub Deployments — Locus Build

> Companion guide to [`SKILL.md`](./SKILL.md). Covers git push deploy and GitHub App integration.

## When To Load

Load this file only for git push deploys, GitHub integration, private repo access, or auto-deploy behavior.

## Table of Contents

- [Deploy via Git Push](#deploy-via-git-push)
- [GitHub Integration (Private Repos)](#github-integration-private-repos)
- [Agent Workflow: Git Push Deploy](#agent-workflow-git-push-deploy)

## Deploy via Git Push

Push code directly to Locus using a git remote — no GitHub required. Ideal for local development and AI agents.

```bash
# Add the Locus git remote (one-time setup)
git remote add locus https://x:YOUR_API_KEY@git.buildwithlocus.com/{workspaceId}/{projectId}.git

# Deploy with a push
git push locus main
```

The username (`x`) is ignored — only the password matters. Use either a `claw_` API key or a JWT token as the password.

### Authentication Options

**Option 1: API Key** (standard users with a `claw_` key)
```bash
git remote add locus https://x:YOUR_API_KEY@git.buildwithlocus.com/{workspaceId}/{projectId}.git
```

**Option 2: JWT Token** (x402/MPP users)

If you signed up via x402 or MPP and have a JWT token instead of a `claw_` API key, use it directly as the password:

```bash
git remote add locus https://x:YOUR_JWT_TOKEN@git.buildwithlocus.com/{workspaceId}/{projectId}.git
```

The git server detects the credential type automatically — API keys start with `claw_`, everything else is treated as a JWT.

JWT tokens expire after 30 days. If your push fails with "Token validation failed", refresh your token and update the remote:

```bash
NEW_TOKEN=$(curl -s -X POST https://api.buildwithlocus.com/v1/auth/refresh \
  -H "Authorization: Bearer $OLD_TOKEN" | jq -r '.token')

git remote set-url locus https://x:${NEW_TOKEN}@git.buildwithlocus.com/{workspaceId}/{projectId}.git
```

Use `GET /v1/git/remote-url` to get the correct git host. It returns `{remoteUrl, usage}` — `remoteUrl` is the workspace base URL (e.g., `https://git.buildwithlocus.com/{workspaceId}`), and `usage` shows the full remote format. Substitute your project ID and credential:

```
https://x:YOUR_API_KEY_OR_JWT@{gitHost}/{workspaceId}/{projectId}.git
```

**What happens on push:**
1. Code is archived and uploaded
2. All services in the project are deployed from the same source
3. Each service uses its configured `rootDir` to find its code within the archive
4. Build status and deployment IDs are echoed back to your terminal

**Example output:**
```
remote: ==============================
remote:   Locus Deploy via git push
remote: ==============================
remote:
remote: Branch: main
remote: Commit: e39d88ca34e2
remote:
remote: Archiving source code...
remote: Uploading source...
remote:
remote: Project: my-app
remote: Deployments triggered: 2
remote:
remote:   -> api [deploy_abc123]
remote:   -> web [deploy_def456]
remote:
remote: ==============================
remote:   Deploy complete
remote: ==============================
```

### Monorepo Support

For projects with multiple services in a single repo, set `rootDir` on each service to point to its subdirectory:

```bash
# Create services with different root directories
curl -s -X POST https://api.buildwithlocus.com/v1/services \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "'"$PROJECT_ID"'",
    "environmentId": "'"$ENV_ID"'",
    "name": "api",
    "source": { "type": "s3", "rootDir": "services/api" },
    "runtime": { "port": 8080 }
  }'

curl -s -X POST https://api.buildwithlocus.com/v1/services \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "'"$PROJECT_ID"'",
    "environmentId": "'"$ENV_ID"'",
    "name": "web",
    "source": { "type": "s3", "rootDir": "services/web" },
    "runtime": { "port": 8080 }
  }'
```

A single `git push locus main` uploads the entire repo once and triggers a deployment for each service, each building from its own `rootDir`.

**`.locusbuild` auto-detection:** If the repo contains a `.locusbuild` file at its root, Locus automatically detects it on push and creates any new services/addons defined in it before triggering deployments. See [monorepo.md](./monorepo.md) for the file format.

### Agent Workflow: Local Code with `.locusbuild`

When you have local code (no GitHub repo) and a `.locusbuild` file, use the manual setup + git push flow. Do NOT use `from-repo` or `from-locusbuild` — those require a real GitHub repository.

**Step 1: Create project + environment**

```bash
PROJECT_ID=$(curl -s -X POST https://api.buildwithlocus.com/v1/projects \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app"}' | jq -r '.id')

ENV_ID=$(curl -s -X POST https://api.buildwithlocus.com/v1/projects/$PROJECT_ID/environments \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "production", "type": "production"}' | jq -r '.id')
```

**Step 2: Create services with `source.type: "s3"` and `rootDir`**

Read your `.locusbuild` and create each service individually:

```bash
# For each service in .locusbuild
curl -s -X POST https://api.buildwithlocus.com/v1/services \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "'"$PROJECT_ID"'",
    "environmentId": "'"$ENV_ID"'",
    "name": "backend",
    "source": { "type": "s3", "rootDir": "backend" },
    "runtime": { "port": 8080 }
  }'
```

**Step 3: Provision addons BEFORE the first push**

Provision addons so the connection strings are available when the first deployment runs:

```bash
# Provision Postgres
ADDON_ID=$(curl -s -X POST https://api.buildwithlocus.com/v1/addons \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"'"$PROJECT_ID"'","environmentId":"'"$ENV_ID"'","type":"postgres","name":"main-db"}' \
  | jq -r '.id')

# Poll until available
while true; do
  STATUS=$(curl -s -H "Authorization: Bearer $TOKEN" \
    "https://api.buildwithlocus.com/v1/addons/$ADDON_ID" | jq -r '.status')
  [ "$STATUS" = "available" ] && break
  sleep 5
done
```

**Step 4: Add git remote and push**

```bash
WORKSPACE_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.buildwithlocus.com/v1/auth/whoami | jq -r '.workspaceId')

git remote add locus https://x:YOUR_API_KEY_OR_JWT@git.buildwithlocus.com/$WORKSPACE_ID/$PROJECT_ID.git
git push locus main
```

The push triggers deployments for all services. Each service builds from its `rootDir` within the pushed code. Services that need addon access must explicitly reference them via template syntax in their environment variables (e.g., `"DATABASE_URL": "${{db.DATABASE_URL}}"`).

### Finding Your Workspace and Project IDs

```bash
# Get your workspace ID
curl -s -H "Authorization: Bearer $TOKEN" https://api.buildwithlocus.com/v1/auth/whoami | jq -r '.workspaceId'

# List projects to find the project ID
curl -s -H "Authorization: Bearer $TOKEN" https://api.buildwithlocus.com/v1/projects | jq '.projects[] | {id, name}'
```

## GitHub Integration (Private Repos)

Locus supports deploying from private GitHub repositories via a GitHub App. When the app is installed on a user's GitHub account or organization, Locus generates short-lived tokens to clone private repos during builds.

### Agent Workflow: Deploying a Private Repo

**Step 1: Check if the repo is accessible**

Before creating a service, check whether Locus can access the repo:

```bash
ACCESS=$(curl -s -H "Authorization: Bearer $TOKEN" \
  "https://api.buildwithlocus.com/v1/github/repo-access?repo=my-org/private-repo")
echo $ACCESS | jq .
```

Response when accessible (public or GitHub App installed):
```json
{ "accessible": true, "installationId": 12345678 }
```

Response when not accessible:
```json
{
  "accessible": false,
  "installUrl": "https://github.com/apps/build-with-locus/installations/new",
  "message": "Repo \"my-org/private-repo\" is not accessible. Install the GitHub App to grant access."
}
```

**Step 2: If not accessible, direct the user to connect GitHub via the Locus dashboard**

**IMPORTANT:** Do NOT send the user to the raw GitHub App install URL. Always direct them to the Locus integrations page, which handles the full connection flow (installation, permissions, and linking to their workspace).

Tell the user:
```
The repo "my-org/private-repo" is private and Locus doesn't have access yet.

To connect your GitHub account, go to:
https://buildwithlocus.com/integrations

From there, connect GitHub and make sure to grant access to the "private-repo" repository.
Let me know once that's done and I'll continue with the deployment.
```

**Step 3: Proceed with normal service creation**

Once GitHub is connected via the integrations page, Locus automatically saves the installation. Create the service normally — Locus auto-detects the `installationId` and handles private repo cloning:

```bash
curl -s -X POST https://api.buildwithlocus.com/v1/services \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "'"$PROJECT_ID"'",
    "environmentId": "'"$ENV_ID"'",
    "name": "api",
    "source": { "type": "github", "repo": "my-org/private-repo", "branch": "main" },
    "runtime": { "port": 8080 }
  }'
```

### GitHub API Endpoints

| Action | Endpoint | Notes |
|--------|----------|-------|
| **Check repo access** | `GET /v1/github/repo-access?repo=owner/repo` | Returns `{accessible, installationId?}` or `{accessible: false, installUrl}` |
| **Get install URL** | `GET /v1/github/install-url` | Returns `{installUrl}` for the GitHub App |
| **List installations** | `GET /v1/github/installations` | Returns `{installations: [...]}` |
| **Save installation** | `POST /v1/github/installations` | `{installationId: number}` — call after GitHub redirect |
| **Remove installation** | `DELETE /v1/github/installations/:installationId` | Removes installation mapping |

### How It Works

1. User installs the Locus GitHub App on their account/org, selecting which repos to grant access to
2. Locus saves the installation ID linked to the user's workspace
3. When a service is created with a GitHub source, Locus checks the workspace's installations and auto-associates the correct one
4. At deploy time, Locus generates a short-lived GitHub installation token and passes it to the build system
5. The build system uses the token to clone the private repo via `https://x-access-token:{token}@github.com/...`

Tokens are short-lived (1 hour) and scoped to the repos the user granted access to.

### Auto-Deploy on GitHub Push

Services with `autoDeploy: true` and a GitHub source automatically deploy when code is pushed to the configured branch. No manual `POST /v1/deployments` call is needed — Locus receives the push event from GitHub and triggers a deployment for each matching service.

**How it works:**
1. GitHub sends a push webhook to Locus when code is pushed to any branch
2. Locus finds all services with `source.type: "github"`, matching `repo` and `branch`, and `autoDeploy: true`
3. Each matching service gets a new deployment with the pushed commit SHA
4. Suspended/delinquent services are skipped

**Enable auto-deploy on an existing service:**

```bash
curl -s -X PATCH https://api.buildwithlocus.com/v1/services/$SERVICE_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"autoDeploy": true}'
```

**Or set it during service creation:**

```bash
curl -s -X POST https://api.buildwithlocus.com/v1/services \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "'"$PROJECT_ID"'",
    "environmentId": "'"$ENV_ID"'",
    "name": "api",
    "source": { "type": "github", "repo": "my-org/my-repo", "branch": "main" },
    "autoDeploy": true,
    "runtime": { "port": 8080 }
  }'
```

Auto-deploys show `triggeredBy: "github:<username>"` in the deployment metadata to distinguish them from manual deploys. The GitHub App must be installed for private repos (see above).

## Agent Workflow: Git Push Deploy

**For AI agents:** Git push deploy involves two steps the human should know about — setting up the remote and pushing code. After the push, hand off to the deployment monitoring workflow in [SKILL.md](./SKILL.md).

### Communication During Setup

**Before adding the remote:**
```
Setting up a Locus git remote so you can deploy by pushing code directly.
```

**Before pushing:**
```
Pushing your code to Locus. This will upload the source and trigger a deployment for each service.
```

**After the push completes, share the deployment IDs from the push output:**
```
Code pushed! Deployments triggered:
  - api → deploy_abc123
  - web → deploy_def456

These builds will take 3-7 minutes each. I'll monitor them and let you know when they're live.
```

Then follow the deployment monitoring workflow in [deployment-workflows.md](./deployment-workflows.md) to poll and report completion.

### Auto-Deploy Status Checks

When enabling or checking auto-deploy on a GitHub-connected service:
```
Auto-deploy is now enabled for my-org/my-repo (main branch).
Any push to main will automatically trigger a deployment — no manual action needed.
```

When the human asks about a recent auto-deploy:
```
I can see a deployment was auto-triggered by GitHub push from user "alice" — let me check its status.
```
