Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "Cloud AI Workspaces",
"name": "Simple Agent Manager",
"image": "mcr.microsoft.com/devcontainers/typescript-node:24-bookworm",
// Ensure Claude CLI uses a config directory within the workspace
"containerEnv": {
Expand Down
4 changes: 2 additions & 2 deletions .devcontainer/setup.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
# Post-create setup script for Cloud AI Workspaces devcontainer
# Post-create setup script for Simple Agent Manager devcontainer
set -e

echo "=== Installing Claude ==="
Expand All @@ -14,7 +14,7 @@ claude mcp add context7 npx -- -y @upstash/context7-mcp
npm install -g happy-coder


echo "=== Setting up Cloud AI Workspaces development environment ==="
echo "=== Setting up Simple Agent Manager development environment ==="

# Install project dependencies
echo "Installing project dependencies..."
Expand Down
14 changes: 7 additions & 7 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Templates Status:
Follow-up TODOs: None
-->

# Cloud AI Coding Workspaces Constitution
# Simple Agent Manager Constitution

## Core Principles

Expand Down Expand Up @@ -189,7 +189,7 @@ Complexity is the enemy. Every abstraction, pattern, and dependency MUST justify
### Repository Structure

```
cloud-ai-workspaces/
simple-agent-manager/
├── apps/
│ ├── web/ # Control plane UI (Cloudflare Pages)
│ └── api/ # Worker API (Cloudflare Workers + Hono)
Expand Down Expand Up @@ -307,17 +307,17 @@ Consistent naming enables identification and automation:

| Resource Type | Pattern | Example |
|---------------|---------|---------|
| Workers | `{project}-{env}` | `cloud-ai-workspaces-staging` |
| KV Namespaces | `{project}-{env}-{purpose}` | `cloud-ai-workspaces-prod-sessions` |
| R2 Buckets | `{project}-{env}-{purpose}` | `cloud-ai-workspaces-prod-backups` |
| D1 Databases | `{project}-{env}` | `cloud-ai-workspaces-staging` |
| Workers | `{project}-{env}` | `simple-agent-manager-staging` |
| KV Namespaces | `{project}-{env}-{purpose}` | `simple-agent-manager-prod-sessions` |
| R2 Buckets | `{project}-{env}-{purpose}` | `simple-agent-manager-prod-backups` |
| D1 Databases | `{project}-{env}` | `simple-agent-manager-staging` |
| DNS Records | `*.{vm-id}.vm.{domain}` | `*.abc123.vm.example.com` |
| Hetzner VMs | `ws-{workspace-id}` | `ws-abc123` |

**Rules:**
- All names lowercase with hyphens (no underscores or camelCase)
- Include environment in name for clarity
- VM labels include `managed-by: cloud-ai-workspaces` for filtering
- VM labels include `managed-by: simple-agent-manager` for filtering

### Cloud-Init Scripts

Expand Down
27 changes: 20 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ No database is required for the MVP.
### Package Dependencies

```
@cloud-ai-workspaces/shared
@simple-agent-manager/shared
@cloud-ai-workspaces/providers
@simple-agent-manager/providers
@cloud-ai-workspaces/api
@simple-agent-manager/api
@cloud-ai-workspaces/web
@simple-agent-manager/web
```

Build order matters: shared → providers → api/web
Expand All @@ -42,6 +42,19 @@ Build order matters: shared → providers → api/web
- Use Miniflare for Worker integration tests
- Critical paths require >90% coverage

### Documentation & File Naming

When creating documentation or implementation notes:

- **Location**: Never put documentation files in package roots
- Ephemeral working notes (implementation summaries, checklists): `docs/notes/`
- Permanent documentation (guides, architecture): `docs/`
- Feature specs and design docs: `specs/<feature>/`
- **Naming**: Use kebab-case for all markdown files
- Good: `phase8-implementation-summary.md`, `idle-detection-design.md`
- Bad: `PHASE8_IMPLEMENTATION_SUMMARY.md`, `IdleDetectionDesign.md`
- **Exceptions**: Only `README.md`, `LICENSE`, `CONTRIBUTING.md`, `CHANGELOG.md` use UPPER_CASE

### Error Handling

All API errors should follow this format:
Expand Down Expand Up @@ -139,9 +152,9 @@ export const WorkspaceCard: FC<Props> = ({ workspace }) => {

Run builds in dependency order:
```bash
pnpm --filter @cloud-ai-workspaces/shared build
pnpm --filter @cloud-ai-workspaces/providers build
pnpm --filter @cloud-ai-workspaces/api build
pnpm --filter @simple-agent-manager/shared build
pnpm --filter @simple-agent-manager/providers build
pnpm --filter @simple-agent-manager/api build
```

### Test Failures
Expand Down
24 changes: 16 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Cloud AI Coding Workspaces
# Simple Agent Manager (SAM)

A serverless platform to spin up AI coding agent environments on-demand with zero ongoing cost.

## Project Overview

This is a monorepo containing a Cloudflare-based platform for managing ephemeral Claude Code workspaces. Users can create cloud VMs with Claude Code pre-installed from any git repository, access them via a web-based interface (CloudCLI), and have them automatically terminate when idle.
This is a monorepo containing a Cloudflare-based platform for managing ephemeral Claude Code workspaces. Users can create cloud VMs with Claude Code pre-installed from any git repository, access them via a web-based interface, and have them automatically terminate when idle.

## Tech Stack

Expand All @@ -25,7 +25,10 @@ apps/

packages/
├── shared/ # Shared types and utilities
└── providers/ # Cloud provider abstraction (Hetzner)
├── providers/ # Cloud provider abstraction (Hetzner)
├── terminal/ # Shared terminal component (@simple-agent-manager/terminal)
├── cloud-init/ # Cloud-init template generator
└── vm-agent/ # Go VM agent (PTY, WebSocket, idle detection)

scripts/
└── vm/ # VM-side scripts (cloud-init, idle detection)
Expand Down Expand Up @@ -66,11 +69,13 @@ pnpm format

## API Endpoints

- `POST /vms` - Create workspace
- `GET /vms` - List workspaces
- `GET /vms/:id` - Get workspace details
- `DELETE /vms/:id` - Stop workspace
- `POST /vms/:id/cleanup` - Cleanup callback (called by VM)
- `POST /api/workspaces` - Create workspace
- `GET /api/workspaces` - List user's workspaces
- `GET /api/workspaces/:id` - Get workspace details
- `DELETE /api/workspaces/:id` - Stop workspace
- `POST /api/workspaces/:id/heartbeat` - VM heartbeat with idle detection
- `POST /api/bootstrap/:token` - Redeem one-time bootstrap token (VM startup)
- `POST /api/terminal/:workspaceId/token` - Get terminal WebSocket token

## Environment Variables

Expand All @@ -89,8 +94,11 @@ See `.env.example` for required configuration:
- TypeScript 5.x + BetterAuth + Drizzle ORM + jose (API), React + Vite + TailwindCSS + xterm.js (Web) (003-browser-terminal-saas)
- Go 1.22+ + creack/pty + gorilla/websocket + golang-jwt (VM Agent) (003-browser-terminal-saas)
- Cloudflare D1 (SQLite) + KV (sessions) + R2 (binaries) (003-browser-terminal-saas)
- TypeScript 5.x (API, Web, packages) + Go 1.22+ (VM Agent) + Hono (API), React + Vite (Web), xterm.js (Terminal), Drizzle ORM (Database) (004-mvp-hardening)
- Cloudflare D1 (workspaces), Cloudflare KV (sessions, bootstrap tokens) (004-mvp-hardening)

## Recent Changes
- 004-mvp-hardening: Secure bootstrap tokens, workspace ownership validation, provisioning timeouts, shared terminal package, WebSocket reconnection, idle deadline tracking
- 003-browser-terminal-saas: Added multi-tenant SaaS with GitHub OAuth, VM Agent (Go), browser terminal
- 002-local-mock-mode: Added local mock mode with devcontainers CLI
- 001-mvp: Added TypeScript 5.x + Hono (API), React + Vite (UI), Cloudflare Workers
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Contributing to Cloud AI Workspaces
# Contributing to Simple Agent Manager

Thank you for your interest in contributing! This document provides guidelines for contributing to the project.

## Getting Started

1. Fork the repository
2. Clone your fork: `git clone https://github.com/your-username/cloud-ai-workspaces.git`
2. Clone your fork: `git clone https://github.com/your-username/simple-agent-manager.git`
3. Install dependencies: `pnpm install`
4. Create a branch: `git checkout -b feature/your-feature`

Expand Down Expand Up @@ -77,7 +77,7 @@ docs/
pnpm test

# Run tests for a specific package
pnpm --filter @cloud-ai-workspaces/api test
pnpm --filter @simple-agent-manager/api test

# Run with coverage
pnpm test:coverage
Expand All @@ -88,7 +88,7 @@ pnpm test:coverage
Integration tests use mocked APIs. No real cloud resources are used.

```bash
pnpm --filter @cloud-ai-workspaces/api test
pnpm --filter @simple-agent-manager/api test
```

## Adding a New Feature
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2025 Cloud AI Workspaces Contributors
Copyright (c) 2025 Simple Agent Manager Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
43 changes: 36 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<p align="center">
<img src="docs/assets/logo.svg" alt="Cloud AI Workspaces" width="400" />
<img src="docs/assets/logo.svg" alt="Simple Agent Manager" width="400" />
</p>

<p align="center">
<strong>Spin up AI coding environments on-demand. Zero cost when idle.</strong>
<strong>Simple Agent Manager (SAM) - Spin up AI coding environments on-demand. Zero cost when idle.</strong>
</p>

<p align="center">
Expand All @@ -20,13 +20,13 @@

---

Cloud AI Workspaces is a serverless platform for creating ephemeral cloud development environments optimized for [Claude Code](https://www.anthropic.com/claude-code). Point it at any GitHub repository and get a fully configured workspace with Claude Code pre-installed—accessible from your browser in minutes.
Simple Agent Manager (SAM) is a serverless platform for creating ephemeral cloud development environments optimized for [Claude Code](https://www.anthropic.com/claude-code). Point it at any GitHub repository and get a fully configured workspace with Claude Code pre-installed—accessible from your browser in minutes.

Think **GitHub Codespaces, but built for AI-assisted development** and with automatic shutdown to eliminate surprise bills.

## Why Cloud AI Workspaces?
## Why Simple Agent Manager?

| | GitHub Codespaces | Cloud AI Workspaces |
| | GitHub Codespaces | Simple Agent Manager |
|---|---|---|
| **Cost** | $0.18–$0.36/hour | ~$0.07–$0.15/hour |
| **Idle shutdown** | Manual or 30min timeout | Automatic with AI-aware detection |
Expand Down Expand Up @@ -67,8 +67,8 @@ Think **GitHub Codespaces, but built for AI-assisted development** and with auto

```bash
# Clone the repository
git clone https://github.com/YOUR_ORG/cloud-ai-workspaces.git
cd cloud-ai-workspaces
git clone https://github.com/YOUR_ORG/simple-agent-manager.git
cd simple-agent-manager

# Install dependencies
pnpm install
Expand Down Expand Up @@ -202,6 +202,7 @@ packages/
├── shared/ # Shared types and validation
├── providers/ # Cloud provider abstraction
├── cloud-init/ # VM cloud-init template generation
├── terminal/ # Shared terminal component (xterm.js + WebSocket)
└── vm-agent/ # Go agent for WebSocket terminal + idle detection

scripts/
Expand Down Expand Up @@ -263,8 +264,36 @@ docs/ # Documentation
| `/api/agent/version` | `GET` | Get current agent version |
| `/api/agent/install-script` | `GET` | Get VM agent install script |

### Bootstrap (VM Credential Delivery)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/bootstrap/:token` | `POST` | Redeem one-time bootstrap token for credentials |

Authentication is session-based via cookies (BetterAuth + GitHub OAuth).

## Security

### Secure Credential Delivery (Bootstrap Tokens)

VMs receive credentials securely using one-time bootstrap tokens:

1. **Workspace creation**: API generates a one-time bootstrap token stored in KV with 5-minute TTL
2. **Cloud-init**: VM receives only the bootstrap URL (no embedded secrets)
3. **VM startup**: VM agent calls `POST /api/bootstrap/:token` to redeem credentials
4. **Token invalidation**: Token is deleted immediately after first use

This ensures:
- No sensitive tokens in cloud-init user data (visible in Hetzner console)
- Single-use tokens prevent replay attacks
- Short TTL limits exposure window

### Workspace Access Control

All workspace operations validate ownership to prevent IDOR attacks:
- Non-owners receive `404 Not Found` (not `403 Forbidden`) to prevent information disclosure
- Workspace lists are filtered by authenticated user
- Terminal WebSocket tokens are scoped to workspace owner

## Use Cases

### Instant Prototyping
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Roadmap

This document outlines the planned development phases for Cloud AI Workspaces.
This document outlines the planned development phases for Simple Agent Manager (SAM).

## Complete: MVP (Phase 1)

Expand Down
8 changes: 4 additions & 4 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "@cloud-ai-workspaces/api",
"name": "@simple-agent-manager/api",
"version": "0.1.0",
"private": true,
"description": "Cloudflare Worker API for Cloud AI Workspaces",
"description": "Cloudflare Worker API for Simple Agent Manager",
"type": "module",
"main": "dist/index.js",
"scripts": {
Expand All @@ -17,8 +17,8 @@
"deploy:staging": "wrangler deploy --env staging"
},
"dependencies": {
"@cloud-ai-workspaces/providers": "workspace:*",
"@cloud-ai-workspaces/shared": "workspace:*",
"@simple-agent-manager/providers": "workspace:*",
"@simple-agent-manager/shared": "workspace:*",
"@workspace/cloud-init": "workspace:*",
"@hono/node-server": "^1.19.9",
"better-auth": "^1.0.0",
Expand Down
5 changes: 5 additions & 0 deletions apps/api/src/db/migrations/0001_mvp_hardening.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- MVP Hardening: Add shutdown_deadline for idle tracking
-- Migration: 0001_mvp_hardening.sql

-- Add shutdown_deadline column for predictable idle shutdown (US5)
ALTER TABLE workspaces ADD COLUMN shutdown_deadline TEXT;
1 change: 1 addition & 0 deletions apps/api/src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const workspaces = sqliteTable('workspaces', {
dnsRecordId: text('dns_record_id'),
lastActivityAt: text('last_activity_at'),
errorMessage: text('error_message'),
shutdownDeadline: text('shutdown_deadline'),
createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
updatedAt: text('updated_at').notNull().default(sql`CURRENT_TIMESTAMP`),
});
Expand Down
25 changes: 24 additions & 1 deletion apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { githubRoutes } from './routes/github';
import { workspacesRoutes } from './routes/workspaces';
import { terminalRoutes } from './routes/terminal';
import { agentRoutes } from './routes/agent';
import { bootstrapRoutes } from './routes/bootstrap';
import { checkProvisioningTimeouts } from './services/timeout';

// Cloudflare bindings type
export interface Env {
Expand Down Expand Up @@ -74,6 +76,7 @@ app.route('/api/github', githubRoutes);
app.route('/api/workspaces', workspacesRoutes);
app.route('/api/terminal', terminalRoutes);
app.route('/api/agent', agentRoutes);
app.route('/api/bootstrap', bootstrapRoutes);

// 404 handler
app.notFound((c) => {
Expand All @@ -83,4 +86,24 @@ app.notFound((c) => {
}, 404);
});

export default app;
// Export handler with scheduled (cron) support
export default {
fetch: app.fetch,

/**
* Scheduled (cron) handler for background tasks.
* Runs every 5 minutes (configured in wrangler.toml).
*/
async scheduled(
_controller: ScheduledController,
env: Env,
_ctx: ExecutionContext
): Promise<void> {
console.log('Cron triggered:', new Date().toISOString());

// Check for stuck provisioning workspaces
const timedOut = await checkProvisioningTimeouts(env.DATABASE);

console.log(`Cron completed: ${timedOut} workspace(s) timed out`);
},
};
2 changes: 1 addition & 1 deletion apps/api/src/lib/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Context } from 'hono';
import type { ApiError } from '@cloud-ai-workspaces/shared';
import type { ApiError } from '@simple-agent-manager/shared';

/**
* Standard error codes
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/middleware/error.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Context, Next } from 'hono';
import type { ApiError } from '@cloud-ai-workspaces/shared';
import type { ApiError } from '@simple-agent-manager/shared';

/**
* Custom error class for API errors with status codes.
Expand Down Expand Up @@ -61,7 +61,7 @@
console.error('Request error:', err);

if (err instanceof AppError) {
return c.json(err.toJSON(), err.statusCode as any);

Check warning on line 64 in apps/api/src/middleware/error.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
}

// Handle unknown errors
Expand Down
Loading