Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 23 additions & 25 deletions .dev.vars.example
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
# Copy this to .dev.vars and fill in your values
# .dev.vars is gitignored and used by wrangler dev

# AI Provider (at least one required)
ANTHROPIC_API_KEY=sk-ant-...
# OPENAI_API_KEY=sk-...

# Cloudflare AI Gateway (alternative to direct provider keys)
# CLOUDFLARE_AI_GATEWAY_API_KEY=your-provider-api-key
# CF_AI_GATEWAY_ACCOUNT_ID=your-account-id
# CF_AI_GATEWAY_GATEWAY_ID=your-gateway-id
# CF_AI_GATEWAY_MODEL=workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast

# Legacy AI Gateway (still supported)
# AI_GATEWAY_API_KEY=your-key
# AI_GATEWAY_BASE_URL=https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/anthropic

# Local development mode - skips Cloudflare Access auth and bypasses device pairing
# ──────────────────────────────────────────────
# Mode A: Quick dev (skip login, straight to chat)
# ──────────────────────────────────────────────
DEV_MODE=true

# ──────────────────────────────────────────────
# Mode B: Full Poe login flow (paste API key, get chat)
# Comment out DEV_MODE and uncomment the session secrets below.
# ──────────────────────────────────────────────
# DEV_MODE=true

# E2E test mode - skips Cloudflare Access auth but keeps device pairing enabled
# Use this for automated tests that need to test the real pairing flow
# E2E_TEST_MODE=true
# SESSION_SECRET=dev-session-secret-32-chars-minimum
# ENCRYPTION_SECRET=dev-encrypt-secret-32-chars-minimum

# Enable debug routes at /debug/* (optional)
# DEBUG_ROUTES=true

# Optional - set a fixed token instead of auto-generated
# Gateway token (any string works for local dev)
MOLTBOT_GATEWAY_TOKEN=dev-token-change-in-prod

# Cloudflare Access configuration for /_admin and /api routes
# Required for admin UI and device pairing API in production
# CF_ACCESS_TEAM_DOMAIN=myteam.cloudflareaccess.com
# CF_ACCESS_AUD=your-application-audience-tag
# AI Provider (optional - users bring their own Poe keys in production)
# For local dev without Poe, set one of these:
# ANTHROPIC_API_KEY=sk-ant-...
# OPENAI_API_KEY=sk-...

# Cloudflare AI Gateway (alternative to direct provider keys)
# CLOUDFLARE_AI_GATEWAY_API_KEY=your-provider-api-key
# CF_AI_GATEWAY_ACCOUNT_ID=your-account-id
# CF_AI_GATEWAY_GATEWAY_ID=your-gateway-id
# CF_AI_GATEWAY_MODEL=workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast

# Optional chat channels
# TELEGRAM_BOT_TOKEN=optional
# DISCORD_BOT_TOKEN=optional

# CDP (Chrome DevTools Protocol) configuration for browser automation
# CDP (Chrome DevTools Protocol) for browser automation
# CDP_SECRET=shared-secret-for-cdp-auth
# WORKER_URL=https://your-worker.example.com
67 changes: 67 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Claude Code Instructions

Guidelines for Claude Code when working on this codebase.

## Project

PoeClaw — a Cloudflare Worker that runs OpenClaw AI assistant in a Cloudflare Sandbox container. See [README.md](./README.md) for user-facing docs, [AGENTS.md](./AGENTS.md) for technical agent instructions.

## Philosophy

Read [`docs/PHILOSOPHY.md`](./docs/PHILOSOPHY.md) before making changes. It defines our layered defense strategy for quality:

- **Layer 0: CI Guardrails** — lint, format, typecheck must pass. No `--no-verify`.
- **Layer 1: Types** — minimize `as any`, use exhaustive pattern matching, share types across server/client.
- **Layer 2: Unit Tests** — test pure logic and security boundaries. Don't test route registration or HTML templates.
- **Layer 3: Property Tests** — use for combinatorial config (`buildEnvVars`), security code, classification logic.
- **Layer 4: Contract Tests** — define API boundary promises before implementing.
- **Layer 5: Integration Tests** — real dependencies for boundary behavior. Mocks are necessary lies.
- **Layer 6: Deploy Safety** — health checks and post-deploy verification.

### Key Principles

1. **Test behavior, not implementation.** Tests verify *what* code promises, not *how* it works.
2. **Security code gets the full treatment.** Auth, credentials, secrets — no shortcuts.
3. **Mocks are necessary lies.** Use for fast feedback on pure logic. Don't trust for boundary behavior.
4. **Pragmatism is not laziness.** Use the Severity x Detectability matrix: test where severity is high and detectability is low.

## Commands

Use `make` as the primary interface. Run `make help` to see all targets.

```bash
make check # Run ALL checks: typecheck, lint, format, test (use before completing work)
make test # Run tests (vitest)
make test-watch # Watch mode
make typecheck # TypeScript strict check
make lint # Run linter (oxlint)
make fix # Auto-fix: lint + format
make build # Build worker + client
make deploy # Build and deploy to Cloudflare
make dev # Clean start: kill stale containers, clear state, run dev server
make clean # Kill sandbox containers and remove wrangler state
make status # Show sandbox containers, images, and listening ports
```

<details>
<summary>npm equivalents (for reference)</summary>

```bash
npm test # Run tests
npm run test:watch # Watch mode
npm run typecheck # TypeScript strict check
npm run lint # oxlint
npm run format:check # oxfmt
npm run build # Build worker + client
npm run deploy # Build and deploy to Cloudflare
```
</details>

## Working Here

- Read [AGENTS.md](./AGENTS.md) for project structure, patterns, and common tasks.
- Read [CONTRIBUTING.md](./CONTRIBUTING.md) for contribution rules and AI policy.
- Tests are colocated: `foo.ts` → `foo.test.ts` in the same directory.
- Use the existing mock infrastructure in `src/test-utils.ts` for new tests.
- When adding a test, include a threat-model comment: what failure mode does it catch?
- Run `make check` before considering work complete.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ We welcome contributions, but with a few short rules:

- **You cannot be offended if we close a PR** or otherwise decide not to merge your work. We're maintaining Moltworker for (many, many) others, and we're ultimately the ones that have to maintain the code. This is especially true if we believe your PR to be AI driven without any human-in-the-loop review or explanation. Not all ideas or work makes it. If it's critical to your workflow, you can fork it!

- **Demonstrate that you've tested your work** - whether via manual testing, automated tests, or a mix of both. You may be quizzed here.
- **Demonstrate that you've tested your work** - run `make check` (typecheck, lint, format, tests) before submitting. You may be quizzed here.

## AI Contributions

Expand Down
122 changes: 122 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
.DEFAULT_GOAL := help
.PHONY: help dev dev-fast clean nuke build \
check test test-watch test-cov typecheck lint fix \
deploy logs secret secrets status \
docker-build docker-shell docker-logs docker-prune

# ──────────────────────────────────────────────
# Help
# ──────────────────────────────────────────────

help: ## Show all available commands
@echo "Usage: make <target>"
@echo ""
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) \
| awk -F ':.*## ' '{ printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 }'

# ──────────────────────────────────────────────
# Core Dev Workflow
# ──────────────────────────────────────────────

dev: clean ## Clean start: kill stale containers, clear state, run dev server
@npx wrangler dev

dev-fast: ## Skip cleanup, just run wrangler dev
@npx wrangler dev

clean: ## Kill sandbox containers and remove wrangler state
@echo "Stopping sandbox containers..."
@-docker ps -q --filter ancestor=cloudflare-dev/sandbox | xargs -r docker kill 2>/dev/null
@-docker ps -q --filter name=sandbox | xargs -r docker kill 2>/dev/null
@-docker ps -q | xargs -r -I{} sh -c 'docker inspect --format="{{.Id}} {{.Config.Image}}" {} | grep sandbox | cut -d" " -f1' | xargs -r docker kill 2>/dev/null
@echo "Removing wrangler state..."
@rm -rf .wrangler/state
@echo "Clean complete."

nuke: clean ## Full reset: clean + remove dist, node_modules, images, reinstall
@echo "Removing build artifacts..."
@rm -rf dist/
@echo "Removing node_modules..."
@rm -rf node_modules/
@echo "Removing sandbox docker images..."
@-docker images --format '{{.Repository}}:{{.Tag}} {{.ID}}' | grep sandbox | awk '{print $$2}' | xargs -r docker rmi -f 2>/dev/null
@echo "Reinstalling dependencies..."
@npm install
@echo "Nuke complete."

build: ## Build worker + client
@npx vite build

# ──────────────────────────────────────────────
# Quality & Testing
# ──────────────────────────────────────────────

check: typecheck lint fix-format-check test ## Run ALL checks: typecheck, lint, format-check, test (mirrors CI)

fix-format-check:
@npx oxfmt --check src/

test: ## Run tests
@npx vitest run

test-watch: ## Run tests in watch mode
@npx vitest

test-cov: ## Run tests with coverage
@npx vitest run --coverage

typecheck: ## TypeScript strict check
@npx tsc --noEmit

lint: ## Run linter
@npx oxlint src/

fix: ## Auto-fix: lint + format
@npx oxlint --fix src/
@npx oxfmt --write src/

# ──────────────────────────────────────────────
# Deployment & Ops
# ──────────────────────────────────────────────

deploy: build ## Build and deploy to Cloudflare
@npx wrangler deploy

logs: ## Tail production logs
@npx wrangler tail

secret: ## Set a secret (usage: make secret KEY=FOO VALUE=bar)
@echo "$(VALUE)" | npx wrangler secret put $(KEY)

secrets: ## List all secrets
@npx wrangler secret list

status: ## Show sandbox containers, images, and listening ports
@echo "=== Sandbox Containers ==="
@docker ps --filter name=sandbox --format 'table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}' 2>/dev/null || true
@echo ""
@echo "=== Sandbox Images ==="
@docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' | grep -E 'REPOSITORY|sandbox' || true
@echo ""
@echo "=== Listening Ports (8787, 5173, 18789) ==="
@lsof -iTCP:8787 -iTCP:5173 -iTCP:18789 -sTCP:LISTEN -P -n 2>/dev/null || echo " (none)"

# ──────────────────────────────────────────────
# Docker / Container Helpers
# ──────────────────────────────────────────────

docker-build: ## Force rebuild sandbox image (no cache)
@docker build --no-cache -t cloudflare-dev/sandbox:local .

docker-shell: ## Shell into running sandbox container
@docker exec -it $$(docker ps --filter name=sandbox -q | head -1) bash

docker-logs: ## Tail logs from running sandbox container
@docker logs -f $$(docker ps --filter name=sandbox -q | head -1)

docker-prune: ## Remove old/dangling sandbox images
@echo "Removing dangling images..."
@-docker image prune -f
@echo "Removing old sandbox images..."
@-docker images --format '{{.Repository}}:{{.Tag}} {{.ID}}' | grep sandbox | awk '{print $$2}' | xargs -r docker rmi -f 2>/dev/null
@echo "Prune complete."
Loading