diff --git a/.gemini/skills/offload/scripts/clean.ts b/.gemini/skills/offload/scripts/clean.ts index 451178119f..c3b2c576e2 100644 --- a/.gemini/skills/offload/scripts/clean.ts +++ b/.gemini/skills/offload/scripts/clean.ts @@ -1,7 +1,7 @@ /** * Universal Offload Cleanup (Local) * - * Cleans up tmux sessions and workspace on the GCE worker. + * Surgical or full cleanup of sessions and worktrees on the GCE worker. */ import { spawnSync } from 'child_process'; import path from 'path'; @@ -22,7 +22,10 @@ async function confirm(question: string): Promise { }); } -export async function runCleanup() { +export async function runCleanup(args: string[]) { + const prNumber = args[0]; + const action = args[1]; + const settingsPath = path.join(REPO_ROOT, '.gemini/settings.json'); if (!fs.existsSync(settingsPath)) { console.error('โŒ Settings not found. Run "npm run offload:setup" first.'); @@ -37,32 +40,49 @@ export async function runCleanup() { return 1; } - const { projectId, zone } = config; - const targetVM = `gcli-offload-${process.env.USER || 'mattkorwel'}`; + const { remoteHost } = config; - console.log(`๐Ÿงน Starting cleanup for ${targetVM}...`); + if (prNumber && action) { + const sessionName = `offload-${prNumber}-${action}`; + const worktreePath = `~/dev/worktrees/${sessionName}`; + + console.log(`๐Ÿงน Surgically removing session and worktree for ${prNumber}-${action}...`); + + // Kill specific tmux session + spawnSync(`ssh ${remoteHost} "tmux kill-session -t ${sessionName} 2>/dev/null"`, { shell: true }); + + // Remove specific worktree + spawnSync(`ssh ${remoteHost} "cd ~/dev/main && git worktree remove -f ${worktreePath} 2>/dev/null"`, { shell: true }); + spawnSync(`ssh ${remoteHost} "cd ~/dev/main && git worktree prune"`, { shell: true }); + + console.log(`โœ… Cleaned up ${prNumber}-${action}.`); + return 0; + } + + // --- Bulk Cleanup (Old Behavior) --- + console.log(`๐Ÿงน Starting BULK cleanup on ${remoteHost}...`); // 1. Standard Cleanup - console.log(' - Killing remote tmux sessions...'); + console.log(' - Killing ALL remote tmux sessions...'); spawnSync(`ssh ${remoteHost} "tmux kill-server"`, { shell: true }); - console.log(' - Cleaning up Git Worktrees...'); + console.log(' - Cleaning up ALL Git Worktrees...'); spawnSync(`ssh ${remoteHost} "cd ~/dev/main && git worktree prune"`, { shell: true }); spawnSync(`ssh ${remoteHost} "rm -rf ~/dev/worktrees/*"`, { shell: true }); console.log('โœ… Remote environment cleared.'); // 2. Full Wipe Option - const shouldWipe = await confirm('\nWould you like to COMPLETELY wipe the remote workspace directory?'); + const shouldWipe = await confirm('\nWould you like to COMPLETELY wipe the remote workspace (main clone)?'); if (shouldWipe) { - console.log(`๐Ÿ”ฅ Wiping ~/.offload/workspace...`); - spawnSync(`gcloud compute ssh ${targetVM} --project ${projectId} --zone ${zone} --command "rm -rf ~/.offload/workspace && mkdir -p ~/.offload/workspace"`, { stdio: 'inherit', shell: true }); - console.log('โœ… Remote workspace wiped.'); + console.log(`๐Ÿ”ฅ Wiping ~/dev/main...`); + spawnSync(`ssh ${remoteHost} "rm -rf ~/dev/main && mkdir -p ~/dev/main"`, { stdio: 'inherit', shell: true }); + console.log('โœ… Remote hub wiped. You will need to run npm run offload:setup again.'); } return 0; } if (import.meta.url === `file://${process.argv[1]}`) { - runCleanup().catch(console.error); + runCleanup(process.argv.slice(2)).catch(console.error); } diff --git a/.gemini/skills/offload/scripts/fleet.ts b/.gemini/skills/offload/scripts/fleet.ts index 6984f8a9a3..755cba2c61 100644 --- a/.gemini/skills/offload/scripts/fleet.ts +++ b/.gemini/skills/offload/scripts/fleet.ts @@ -41,29 +41,47 @@ async function provisionWorker() { return; } - console.log(`๐Ÿš€ Provisioning container-native offload worker: ${name}...`); + console.log(`๐Ÿš€ Provisioning high-performance container worker: ${name}...`); console.log(` - Image: ${imageUri}`); + console.log(` - Disk: 200GB (High Performance)`); + // Use a startup script to run the container. This is the modern replacement + // for the deprecated create-with-container agent. + const startupScript = `#!/bin/bash + # Install Docker + apt-get update && apt-get install -y docker.io + + # Authenticate to Artifact Registry + # (The VM Service Account must have Artifact Registry Reader permissions) + gcloud auth configure-docker us-docker.pkg.dev --quiet + + # Pull and Run the maintainer container + docker pull ${imageUri} + docker run -d --name gemini-sandbox --restart always \\ + -v /home/$(whoami)/dev:/home/node/dev:rw \\ + -v /home/$(whoami)/.gemini:/home/node/.gemini:rw \\ + -v /home/$(whoami)/.offload:/home/node/.offload:rw \\ + ${imageUri} /bin/bash -c "while true; do sleep 1000; done" + `; + const result = spawnSync('gcloud', [ - 'compute', 'instances', 'create-with-container', name, + 'compute', 'instances', 'create', name, '--project', PROJECT_ID, '--zone', zone, '--machine-type', 'n2-standard-8', - '--container-image', imageUri, - '--container-name', 'gemini-sandbox', - '--container-restart-policy', 'always', - '--container-mount-host-path', 'host-path=/home/$(whoami)/dev,mount-path=/home/node/dev,mode=rw', - '--container-mount-host-path', 'host-path=/home/$(whoami)/.gemini,mount-path=/home/node/.gemini,mode=rw', - '--container-mount-host-path', 'host-path=/home/$(whoami)/.offload,mount-path=/home/node/.offload,mode=rw', - '--boot-disk-size', '50GB', + '--image-family', 'ubuntu-2204-lts', + '--image-project', 'ubuntu-os-cloud', + '--boot-disk-size', '200GB', + '--boot-disk-type', 'pd-balanced', + '--metadata', `startup-script=${startupScript}`, '--labels', `owner=${USER.replace(/[^a-z0-9_-]/g, '_')},type=offload-worker`, '--tags', `gcli-offload-${USER}`, '--scopes', 'https://www.googleapis.com/auth/cloud-platform' ], { stdio: 'inherit', shell: true }); if (result.status === 0) { - console.log(`\nโœ… Container worker ${name} is being provisioned.`); - console.log(`๐Ÿ‘‰ Access is managed via 'gcloud compute ssh --container'.`); + console.log(`\nโœ… Worker ${name} is being provisioned.`); + console.log(`๐Ÿ‘‰ Container 'gemini-sandbox' will start automatically once booted.`); } } diff --git a/.gemini/skills/offload/scripts/status.ts b/.gemini/skills/offload/scripts/status.ts index f1541ff95a..ae18a9284f 100644 --- a/.gemini/skills/offload/scripts/status.ts +++ b/.gemini/skills/offload/scripts/status.ts @@ -55,9 +55,10 @@ function getStatus() { console.log(`${pr.padEnd(10)} | ${action.padEnd(10)} | ${state.padEnd(12)} | ${id.padEnd(25)}`); if (state === '๐Ÿƒ RUNNING') { - console.log(` โ””โ”€ Attach: npm run offload:attach ${pr} ${action} [--local]`); - console.log(` โ””โ”€ Logs: npm run offload:logs ${pr} ${action}`); + console.log(` โ”œโ”€ Attach: npm run offload:attach ${pr} ${action} [--local]`); + console.log(` โ”œโ”€ Logs: npm run offload:logs ${pr} ${action}`); } + console.log(` โ””โ”€ Remove: npm run offload:remove ${pr} ${action}`); }); console.log(''.padEnd(100, '-')); } diff --git a/package.json b/package.json index 98c9379233..c094066517 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "offload:status": "npm run offload:fleet status", "offload:attach": "tsx .gemini/skills/offload/scripts/attach.ts", "offload:logs": "tsx .gemini/skills/offload/scripts/logs.ts", + "offload:remove": "tsx .gemini/skills/offload/scripts/clean.ts", "pre-commit": "node scripts/pre-commit.js" }, "overrides": {