diff --git a/dev/cli/src/commands/env.ts b/dev/cli/src/commands/env.ts index 9ad741b11f..9064777e2d 100644 --- a/dev/cli/src/commands/env.ts +++ b/dev/cli/src/commands/env.ts @@ -6,12 +6,15 @@ import { join } from 'path'; export async function envCheck(root: string) { ui.header('Environment Variable Check'); + let allGood = true; + const envLocalPath = join(root, '.env.local'); const envLocalExists = await Bun.file(envLocalPath).exists(); if (envLocalExists) { ui.success('.env.local exists'); } else { ui.error('.env.local missing — run: vercel env pull'); + allGood = false; } const vercelProjectPath = join(root, '.vercel', 'project.json'); @@ -20,10 +23,10 @@ export async function envCheck(root: string) { ui.success('Vercel project linked'); } else { ui.warn('Vercel project not linked — run: vercel link --project kilocode-app'); + allGood = false; } const servicesWithEnv = services.filter(s => s.envFile); - let allGood = true; for (const svc of servicesWithEnv) { const examplePath = join(root, svc.dir, svc.envFile!); diff --git a/dev/cli/src/commands/logs.ts b/dev/cli/src/commands/logs.ts index 69e18bc639..68380a9ae9 100644 --- a/dev/cli/src/commands/logs.ts +++ b/dev/cli/src/commands/logs.ts @@ -18,12 +18,21 @@ export async function logs(args: string[], root: string) { return; } - if (svc.type === 'infra') { + // Only docker compose services support `docker compose logs`. + // Infra services like 'migrations' use non-docker commands (e.g. pnpm drizzle migrate) + // and have no persistent container to tail. + const dockerComposeServices = new Set(['postgres', 'redis']); + + if (svc.type === 'infra' && dockerComposeServices.has(svc.name)) { const proc = Bun.spawn( ['docker', 'compose', '-f', 'dev/docker-compose.yml', 'logs', '-f', svc.name], { stdout: 'inherit', stderr: 'inherit', cwd: root } ); await proc.exited; + } else if (svc.type === 'infra') { + ui.warn( + `"${svc.name}" is not a Docker Compose service — no logs to tail.\n It runs: ${svc.devCommand ?? 'n/a'}` + ); } else { ui.warn( `Log tailing for running dev servers is not yet supported.\n Start the service with 'pnpm kilo dev up ${name}' to see its output.` diff --git a/dev/cli/src/commands/status.ts b/dev/cli/src/commands/status.ts index ec681e2a4e..e4e84433a4 100644 --- a/dev/cli/src/commands/status.ts +++ b/dev/cli/src/commands/status.ts @@ -16,11 +16,42 @@ export async function status(root: string) { ); const portServices = services.filter(s => s.port && s.type !== 'infra'); + + // Group services by port to detect shared ports + const portToServices = new Map(); + for (const svc of portServices) { + const list = portToServices.get(svc.port!) ?? []; + list.push(svc.name); + portToServices.set(svc.port!, list); + } + + // Track ports we've already checked to avoid duplicate probes + const checkedPorts = new Set(); + for (const svc of portServices) { - const listening = await isPortListening(svc.port!); - console.log( - ` ${listening ? ui.green('●') : ui.dim('○')} ${svc.name.padEnd(12)} ${listening ? `port ${svc.port}` : ui.dim('not running')}` - ); + const port = svc.port!; + const sharesPort = portToServices.get(port)!; + + if (checkedPorts.has(port)) { + // Already reported for this port — skip duplicate probe + continue; + } + checkedPorts.add(port); + + const listening = await isPortListening(port); + + if (sharesPort.length > 1) { + // Multiple services claim this port — show them together with a note + const names = sharesPort.join(' / '); + const ambiguityNote = ui.dim('(shared port — cannot distinguish)'); + console.log( + ` ${listening ? ui.green('●') : ui.dim('○')} ${names.padEnd(24)} ${listening ? `port ${port} ${ambiguityNote}` : ui.dim('not running')}` + ); + } else { + console.log( + ` ${listening ? ui.green('●') : ui.dim('○')} ${svc.name.padEnd(12)} ${listening ? `port ${port}` : ui.dim('not running')}` + ); + } } console.log(); diff --git a/dev/cli/src/projects/auto-fix.ts b/dev/cli/src/projects/auto-fix.ts index 8e86e18461..822a9686aa 100644 --- a/dev/cli/src/projects/auto-fix.ts +++ b/dev/cli/src/projects/auto-fix.ts @@ -2,6 +2,7 @@ import type { ProjectDef } from './types'; import { spawnService, run } from '../utils/process'; import * as ui from '../utils/ui'; import { join } from 'path'; +import { mkdir } from 'fs/promises'; import { createHmac, randomUUID } from 'crypto'; const GENERIC_BODY = JSON.stringify( @@ -50,6 +51,7 @@ async function upCommand(args: string[], root: string): Promise { const skipRoot = args.includes('--no-root'); const logDir = join(root, 'dev', '.dev-logs', 'auto-fix'); + await mkdir(logDir, { recursive: true }); await Bun.write(join(logDir, '.gitkeep'), ''); ui.header('Kilo Cloud Dev Services — Auto Fix'); diff --git a/dev/cli/src/projects/code-review.ts b/dev/cli/src/projects/code-review.ts index 5212e66b40..6364dba4ce 100644 --- a/dev/cli/src/projects/code-review.ts +++ b/dev/cli/src/projects/code-review.ts @@ -2,6 +2,7 @@ import type { ProjectDef } from './types'; import { spawnService, run } from '../utils/process'; import * as ui from '../utils/ui'; import { join } from 'path'; +import { mkdir } from 'fs/promises'; import { createHmac, randomUUID } from 'crypto'; const GENERIC_BODY = JSON.stringify( @@ -48,6 +49,7 @@ async function upCommand(args: string[], root: string): Promise { const skipRoot = args.includes('--no-root'); const logDir = join(root, 'dev', '.dev-logs', 'review'); + await mkdir(logDir, { recursive: true }); await Bun.write(join(logDir, '.gitkeep'), ''); ui.header('Kilo Cloud Dev Services — Code Review');