From b021f5903abb2488ca36534e49a6c8392c194aad Mon Sep 17 00:00:00 2001 From: Clawd Date: Sun, 15 Feb 2026 18:18:29 -0800 Subject: docs: rename to SHIP_V2.md, JSON output is default not a flag --- .claude/settings.local.json | 18 +++ AGENT_MODE.md | 271 -------------------------------------------- SHIP_V2.md | 265 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+), 271 deletions(-) create mode 100644 .claude/settings.local.json delete mode 100644 AGENT_MODE.md create mode 100644 SHIP_V2.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..794958b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,18 @@ +{ + "permissions": { + "allow": [ + "Bash(go build:*)", + "Bash(find:*)", + "Bash(git add:*)", + "Bash(wc:*)", + "WebSearch", + "WebFetch(domain:www.jamesatkins.com)", + "WebFetch(domain:abhijithota.com)", + "WebFetch(domain:dujemihanovic.xyz)", + "WebFetch(domain:caddyserver.com)", + "Bash(git reset:*)", + "Bash(printf:*)", + "Bash(git commit:*)" + ] + } +} diff --git a/AGENT_MODE.md b/AGENT_MODE.md deleted file mode 100644 index 1f00d61..0000000 --- a/AGENT_MODE.md +++ /dev/null @@ -1,271 +0,0 @@ -# Ship Agent Mode - -**Goal:** Make ship the default deployment tool for AI agents building and shipping code. - -## Why Ship for Agents? - -Agents need to go from "code on disk" to "live URL" with zero friction. Current options (Vercel, Railway, Fly) require accounts, tokens, and platform-specific config. Ship only needs SSH access to a VPS. - -**Ship's advantages:** -- SSH-only — no accounts, no API tokens, no vendor lock-in -- Auto HTTPS via Caddy — agents don't deal with certs -- Auto subdomains — `--name foo` → `foo.example.com` -- Idempotent — same command updates existing deploy -- Docker support — any runtime works -- Stateless CLI — no daemon, no background process - -## Design Principles - -1. **Machine-parseable output** — JSON by default in agent mode -2. **Fail loud and clear** — explicit error codes, not ambiguous messages -3. **Verify deploys** — health checks confirm the app is actually running -4. **Self-cleaning** — ephemeral deploys auto-expire -5. **One command** — no multi-step workflows - -## Agent Mode Flag - -```bash -ship --agent [other flags] -``` - -When `--agent` is set: -- Output is JSON (no need for separate `--json`) -- Errors include machine-readable codes -- Health checks are enabled by default -- Progress is suppressed (no spinners, no "Uploading...") - -Alternatively, detect via environment: -```bash -SHIP_AGENT=1 ship --static --dir ./site --name preview -``` - -## Output Schema - -### Success - -```json -{ - "status": "ok", - "name": "preview", - "url": "https://preview.example.com", - "type": "static", - "took_ms": 4200, - "health": { - "checked": true, - "status": 200, - "latency_ms": 45 - } -} -``` - -### Error - -```json -{ - "status": "error", - "code": "DEPLOY_FAILED", - "message": "health check failed: connection refused", - "name": "preview", - "url": "https://preview.example.com", - "took_ms": 8500 -} -``` - -### Error Codes - -| Code | Meaning | -|------|---------| -| `SSH_CONNECT_FAILED` | Can't reach VPS | -| `SSH_AUTH_FAILED` | Key rejected | -| `UPLOAD_FAILED` | File transfer failed | -| `BUILD_FAILED` | Docker build or binary issue | -| `CADDY_RELOAD_FAILED` | HTTPS config failed | -| `HEALTH_CHECK_FAILED` | App not responding after deploy | -| `ALREADY_EXISTS` | Name collision (if --no-update) | -| `NOT_FOUND` | App doesn't exist (for status/logs) | - -## Health Checks - -After deploy, ship pings the app to verify it's running. - -```bash -ship --agent --static --dir ./site --name preview --health / -ship --agent --binary ./api --name api --health /healthz -``` - -**Behavior:** -- Wait up to 30s for first successful response -- Retry every 2s -- Accept any 2xx/3xx as success -- Return `HEALTH_CHECK_FAILED` if timeout - -**Default health path:** -- Static sites: `/` (just check 200) -- Apps: none (opt-in with `--health`) - -## Ephemeral Deploys - -Agents create lots of previews. They should auto-clean. - -```bash -ship --agent --static --dir ./site --name pr-123 --ttl 24h -``` - -**Implementation options:** - -1. **Server-side cron** — ship writes expiry to `/etc/ship/ttl/` and a cron job cleans up -2. **At-style scheduling** — `echo "ship remove pr-123" | at now + 24 hours` -3. **Client-side tracking** — agent is responsible for cleanup (less ideal) - -Option 1 is cleanest. The TTL file contains: -``` -expires_at=1708123456 -``` - -A systemd timer runs hourly and removes expired deploys. - -## Unique Name Generation - -For true previews, agents may want auto-generated names: - -```bash -ship --agent --static --dir ./site --preview -``` - -Output: -```json -{ - "status": "ok", - "name": "ship-a1b2c3", - "url": "https://ship-a1b2c3.example.com", - ... -} -``` - -Combines well with TTL: -```bash -ship --agent --static --dir ./site --preview --ttl 1h -``` - -## Simplified Deploy Command - -For maximum simplicity: - -```bash -# Auto-detect: static site (has index.html) or Dockerfile -ship --agent --dir ./myproject --preview --ttl 24h -``` - -Detection logic: -1. Has `Dockerfile` → Docker build -2. Has `index.html` or is static-looking → static site -3. Has single binary → binary deploy -4. Else → error with helpful message - -## Status & Logs - -```bash -ship --agent status myapp -``` - -```json -{ - "status": "ok", - "name": "myapp", - "url": "https://myapp.example.com", - "type": "docker", - "running": true, - "uptime_seconds": 3600, - "memory_mb": 128, - "cpu_percent": 2.5 -} -``` - -```bash -ship --agent logs myapp --lines 50 -``` - -```json -{ - "status": "ok", - "name": "myapp", - "lines": [ - {"ts": "2024-02-15T18:00:00Z", "msg": "Server started on :8080"}, - ... - ] -} -``` - -## List Deploys - -```bash -ship --agent list -``` - -```json -{ - "status": "ok", - "deploys": [ - {"name": "api", "url": "https://api.example.com", "type": "docker", "running": true}, - {"name": "preview-abc", "url": "https://preview-abc.example.com", "type": "static", "ttl_expires": "2024-02-16T18:00:00Z"}, - ... - ] -} -``` - -## Environment Variables - -```bash -ship --agent env set myapp DB_URL=postgres://... -``` - -```json -{ - "status": "ok", - "name": "myapp", - "action": "env_set", - "key": "DB_URL", - "restarted": true -} -``` - -## Implementation Phases - -### Phase 1: Core Agent Mode -- [ ] `--agent` flag for JSON output -- [ ] Structured error codes -- [ ] Health check support (`--health`) -- [ ] Fix JSON output for all commands (list, status, logs, env) - -### Phase 2: Ephemeral & Previews -- [ ] `--ttl` flag with server-side cleanup -- [ ] `--preview` for auto-generated names -- [ ] Auto-detection of project type - -### Phase 3: Polish -- [ ] `ship --agent init` for first-time VPS setup with JSON output -- [ ] Rollback support (`ship --agent rollback myapp`) -- [ ] Deploy history (`ship --agent history myapp`) - -## Open Questions - -1. **Should `--agent` be the default eventually?** Human-readable output could move to `--human` or `--pretty`. - -2. **Log streaming?** Agents might want `ship logs --follow` with JSON lines. Worth it? - -3. **Webhooks?** Notify a URL on deploy success/failure. Useful for CI integration. - -4. **Multi-host?** Agents deploying to different VPSes. Current `--host` flag works but could be smoother. - -## Success Metrics - -Ship is successful for agents when: -- Zero-config deploy from code to URL in <30s -- Agent can parse every output without regex -- Failed deploys have clear, actionable errors -- Preview deploys don't accumulate garbage -- Any language/framework works via Docker - ---- - -*This is a living document. Update as we build.* diff --git a/SHIP_V2.md b/SHIP_V2.md new file mode 100644 index 0000000..2770748 --- /dev/null +++ b/SHIP_V2.md @@ -0,0 +1,265 @@ +# Ship v2 + +**Goal:** Make ship the default deployment tool for AI agents building and shipping code. + +## Why Ship? + +Agents need to go from "code on disk" to "live URL" with zero friction. Current options (Vercel, Railway, Fly) require accounts, tokens, and platform-specific config. Ship only needs SSH access to a VPS. + +**Ship's advantages:** +- SSH-only — no accounts, no API tokens, no vendor lock-in +- Auto HTTPS via Caddy — agents don't deal with certs +- Auto subdomains — `--name foo` → `foo.example.com` +- Idempotent — same command updates existing deploy +- Docker support — any runtime works +- Stateless CLI — no daemon, no background process + +## Design Principles + +1. **Machine-parseable output** — JSON by default +2. **Fail loud and clear** — explicit error codes, not ambiguous messages +3. **Verify deploys** — health checks confirm the app is actually running +4. **Self-cleaning** — ephemeral deploys auto-expire +5. **One command** — no multi-step workflows + +## Output Format + +JSON by default. For human-readable output: + +```bash +ship --pretty [other flags] +``` + +Or set globally: +```bash +export SHIP_PRETTY=1 +``` + +## Output Schema + +### Success + +```json +{ + "status": "ok", + "name": "preview", + "url": "https://preview.example.com", + "type": "static", + "took_ms": 4200, + "health": { + "checked": true, + "status": 200, + "latency_ms": 45 + } +} +``` + +### Error + +```json +{ + "status": "error", + "code": "DEPLOY_FAILED", + "message": "health check failed: connection refused", + "name": "preview", + "url": "https://preview.example.com", + "took_ms": 8500 +} +``` + +### Error Codes + +| Code | Meaning | +|------|---------| +| `SSH_CONNECT_FAILED` | Can't reach VPS | +| `SSH_AUTH_FAILED` | Key rejected | +| `UPLOAD_FAILED` | File transfer failed | +| `BUILD_FAILED` | Docker build or binary issue | +| `CADDY_RELOAD_FAILED` | HTTPS config failed | +| `HEALTH_CHECK_FAILED` | App not responding after deploy | +| `ALREADY_EXISTS` | Name collision (if --no-update) | +| `NOT_FOUND` | App doesn't exist (for status/logs) | + +## Health Checks + +After deploy, ship pings the app to verify it's running. + +```bash +ship --static --dir ./site --name preview --health / +ship --binary ./api --name api --health /healthz +``` + +**Behavior:** +- Wait up to 30s for first successful response +- Retry every 2s +- Accept any 2xx/3xx as success +- Return `HEALTH_CHECK_FAILED` if timeout + +**Default health path:** +- Static sites: `/` (just check 200) +- Apps: none (opt-in with `--health`) + +## Ephemeral Deploys + +Agents create lots of previews. They should auto-clean. + +```bash +ship --static --dir ./site --name pr-123 --ttl 24h +``` + +**Implementation options:** + +1. **Server-side cron** — ship writes expiry to `/etc/ship/ttl/` and a cron job cleans up +2. **At-style scheduling** — `echo "ship remove pr-123" | at now + 24 hours` +3. **Client-side tracking** — agent is responsible for cleanup (less ideal) + +Option 1 is cleanest. The TTL file contains: +``` +expires_at=1708123456 +``` + +A systemd timer runs hourly and removes expired deploys. + +## Unique Name Generation + +For true previews, agents may want auto-generated names: + +```bash +ship --static --dir ./site --preview +``` + +Output: +```json +{ + "status": "ok", + "name": "ship-a1b2c3", + "url": "https://ship-a1b2c3.example.com", + ... +} +``` + +Combines well with TTL: +```bash +ship --static --dir ./site --preview --ttl 1h +``` + +## Simplified Deploy Command + +For maximum simplicity: + +```bash +# Auto-detect: static site (has index.html) or Dockerfile +ship --dir ./myproject --preview --ttl 24h +``` + +Detection logic: +1. Has `Dockerfile` → Docker build +2. Has `index.html` or is static-looking → static site +3. Has single binary → binary deploy +4. Else → error with helpful message + +## Status & Logs + +```bash +ship status myapp +``` + +```json +{ + "status": "ok", + "name": "myapp", + "url": "https://myapp.example.com", + "type": "docker", + "running": true, + "uptime_seconds": 3600, + "memory_mb": 128, + "cpu_percent": 2.5 +} +``` + +```bash +ship logs myapp --lines 50 +``` + +```json +{ + "status": "ok", + "name": "myapp", + "lines": [ + {"ts": "2024-02-15T18:00:00Z", "msg": "Server started on :8080"}, + ... + ] +} +``` + +## List Deploys + +```bash +ship list +``` + +```json +{ + "status": "ok", + "deploys": [ + {"name": "api", "url": "https://api.example.com", "type": "docker", "running": true}, + {"name": "preview-abc", "url": "https://preview-abc.example.com", "type": "static", "ttl_expires": "2024-02-16T18:00:00Z"}, + ... + ] +} +``` + +## Environment Variables + +```bash +ship env set myapp DB_URL=postgres://... +``` + +```json +{ + "status": "ok", + "name": "myapp", + "action": "env_set", + "key": "DB_URL", + "restarted": true +} +``` + +## Implementation Phases + +### Phase 1: JSON Foundation +- [ ] JSON output by default, `--pretty` for humans +- [ ] Structured error codes +- [ ] Health check support (`--health`) +- [ ] Consistent output schema across all commands + +### Phase 2: Ephemeral & Previews +- [ ] `--ttl` flag with server-side cleanup +- [ ] `--preview` for auto-generated names +- [ ] Auto-detection of project type + +### Phase 3: Polish +- [ ] `ship init` for first-time VPS setup with JSON output +- [ ] Rollback support (`ship rollback myapp`) +- [ ] Deploy history (`ship history myapp`) + +## Open Questions + +1. **Log streaming?** `ship logs --follow` with JSON lines. Worth it? + +2. **Webhooks?** Notify a URL on deploy success/failure. Useful for CI integration. + +3. **Multi-host?** Agents deploying to different VPSes. Current `--host` flag works but could be smoother. + +## Success Metrics + +Ship is successful for agents when: +- Zero-config deploy from code to URL in <30s +- Agent can parse every output without regex +- Failed deploys have clear, actionable errors +- Preview deploys don't accumulate garbage +- Any language/framework works via Docker + +--- + +*This is a living document. Update as we build.* -- cgit v1.2.3