feat: Add agent preview integration with auto-teardown and simplified commands

- Add agentId label to preview containers (cw.agent-id) for tracking
- Add startForAgent/stopByAgentId methods to PreviewManager
- Auto-teardown: previews torn down on agent:stopped event
- Conditional preview prompt injection for execute/refine/discuss agents
- Agent-simplified CLI: cw preview start/stop --agent <id>
- cw preview setup command with --auto mode for guided config generation
- hasPreviewConfig hint on cw project register output
- New tRPC procedures: startPreviewForAgent, stopPreviewByAgent
This commit is contained in:
Lukas May
2026-03-05 15:39:15 +01:00
parent 66605da30d
commit ebe186bd5e
10 changed files with 381 additions and 22 deletions

View File

@@ -1038,6 +1038,11 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
console.log(`Registered project: ${project.id}`);
console.log(` Name: ${project.name}`);
console.log(` URL: ${project.url}`);
if (project.hasPreviewConfig) {
console.log(' Preview: .cw-preview.yml detected — preview deployments ready');
} else {
console.log(' Preview: No .cw-preview.yml found. Run `cw preview setup` for instructions.');
}
} catch (error) {
console.error('Failed to register project:', (error as Error).message);
process.exit(1);
@@ -1335,23 +1340,36 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
.description('Manage Docker-based preview deployments');
// cw preview start --initiative <id> --project <id> --branch <branch> [--phase <id>]
// cw preview start --agent <id> (agent-simplified: server resolves everything)
previewCommand
.command('start')
.description('Start a preview deployment')
.requiredOption('--initiative <id>', 'Initiative ID')
.requiredOption('--project <id>', 'Project ID')
.requiredOption('--branch <branch>', 'Branch to deploy')
.option('--initiative <id>', 'Initiative ID')
.option('--project <id>', 'Project ID')
.option('--branch <branch>', 'Branch to deploy')
.option('--phase <id>', 'Phase ID')
.action(async (options: { initiative: string; project: string; branch: string; phase?: string }) => {
.option('--agent <id>', 'Agent ID (server resolves initiative/project/branch)')
.action(async (options: { initiative?: string; project?: string; branch?: string; phase?: string; agent?: string }) => {
try {
const client = createDefaultTrpcClient();
console.log('Starting preview deployment...');
const preview = await client.startPreview.mutate({
initiativeId: options.initiative,
projectId: options.project,
branch: options.branch,
phaseId: options.phase,
});
let preview;
if (options.agent) {
preview = await client.startPreviewForAgent.mutate({ agentId: options.agent });
} else {
if (!options.initiative || !options.project || !options.branch) {
console.error('Either --agent or all of --initiative, --project, --branch are required');
process.exit(1);
}
preview = await client.startPreview.mutate({
initiativeId: options.initiative,
projectId: options.project,
branch: options.branch,
phaseId: options.phase,
});
}
console.log(`Preview started: ${preview.id}`);
console.log(` URL: ${preview.url}`);
console.log(` Branch: ${preview.branch}`);
@@ -1365,14 +1383,24 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
});
// cw preview stop <previewId>
// cw preview stop --agent <id>
previewCommand
.command('stop <previewId>')
.command('stop [previewId]')
.description('Stop a preview deployment')
.action(async (previewId: string) => {
.option('--agent <id>', 'Stop preview by agent ID')
.action(async (previewId: string | undefined, options: { agent?: string }) => {
try {
const client = createDefaultTrpcClient();
await client.stopPreview.mutate({ previewId });
console.log(`Preview '${previewId}' stopped`);
if (options.agent) {
await client.stopPreviewByAgent.mutate({ agentId: options.agent });
console.log(`Previews for agent '${options.agent}' stopped`);
} else if (previewId) {
await client.stopPreview.mutate({ previewId });
console.log(`Preview '${previewId}' stopped`);
} else {
console.error('Either <previewId> or --agent <id> is required');
process.exit(1);
}
} catch (error) {
console.error('Failed to stop preview:', (error as Error).message);
process.exit(1);
@@ -1434,6 +1462,138 @@ export function createCli(serverHandler?: (port?: number) => Promise<void>): Com
}
});
// cw preview setup [--auto --project <id>]
previewCommand
.command('setup')
.description('Show preview deployment setup instructions')
.option('--auto', 'Auto-create initiative and spawn agent to set up preview config')
.option('--project <id>', 'Project ID (required with --auto)')
.option('--provider <name>', 'Agent provider (optional, with --auto)')
.action(async (options: { auto?: boolean; project?: string; provider?: string }) => {
if (!options.auto) {
console.log(`Preview Deployment Setup
========================
Prerequisites:
- Docker installed and running
- Project registered in Codewalkers (cw project register)
Step 1: Add .cw-preview.yml to your project root
Minimal (single service with Dockerfile):
version: 1
services:
app:
build: .
port: 3000
Multi-service (frontend + API + database):
version: 1
services:
frontend:
build:
context: .
dockerfile: apps/web/Dockerfile
port: 3000
route: /
healthcheck:
path: /
backend:
build:
context: .
dockerfile: packages/api/Dockerfile
port: 8080
route: /api
healthcheck:
path: /health
db:
image: postgres:16-alpine
internal: true
env:
POSTGRES_PASSWORD: preview
Step 2: Commit .cw-preview.yml to your repo
Step 3: Start a preview
cw preview start --initiative <id> --project <id> --branch <branch>
Without .cw-preview.yml, previews auto-discover:
1. docker-compose.yml / compose.yml
2. Dockerfile (single service, port 3000)
Full reference: docs/preview.md`);
return;
}
// --auto mode
if (!options.project) {
console.error('--project <id> is required with --auto');
process.exit(1);
}
try {
const client = createDefaultTrpcClient();
// Look up project
const projects = await client.listProjects.query();
const project = projects.find((p: { id: string }) => p.id === options.project);
if (!project) {
console.error(`Project '${options.project}' not found`);
process.exit(1);
}
// Create initiative
const initiative = await client.createInitiative.mutate({
name: `Preview setup: ${project.name}`,
projectIds: [project.id],
});
// Create root page with setup guide
const rootPage = await client.getRootPage.query({ initiativeId: initiative.id });
const { markdownToTiptapJson } = await import('../agent/markdown-to-tiptap.js');
const setupMarkdown = `# Preview Setup for ${project.name}
Analyze the project structure and create a \`.cw-preview.yml\` configuration file for Docker-based preview deployments.
## What to do
1. Examine the project's build system, frameworks, and service architecture
2. Identify all services (frontend, backend, database, etc.)
3. Create a \`.cw-preview.yml\` at the project root with appropriate service definitions
4. Include dev mode configuration for hot-reload support
5. Add healthcheck endpoints where applicable
6. Commit the file to the repository
## Reference
See the Codewalkers documentation for .cw-preview.yml format and options.`;
await client.updatePage.mutate({
id: rootPage.id,
content: JSON.stringify(markdownToTiptapJson(setupMarkdown)),
});
// Spawn refine agent
const spawnInput: { initiativeId: string; instruction: string; provider?: string } = {
initiativeId: initiative.id,
instruction: 'Analyze the project and create a .cw-preview.yml for preview deployments. Follow the setup guide in the initiative pages.',
};
if (options.provider) {
spawnInput.provider = options.provider;
}
const agent = await client.spawnArchitectRefine.mutate(spawnInput);
console.log(`Created initiative: ${initiative.id}`);
console.log(`Spawned agent: ${agent.id} (${agent.name})`);
} catch (error) {
console.error('Failed to set up preview:', (error as Error).message);
process.exit(1);
}
});
// =========================================================================
// Inter-agent conversation commands
// =========================================================================