feat(workspaces): implement unified scripts/workspaces.ts entry point and simplify commands

This commit is contained in:
mkorwel
2026-03-19 00:21:41 -07:00
parent 0df26997b4
commit 7d11fdeb8c
3 changed files with 134 additions and 31 deletions

View File

@@ -1,51 +1,75 @@
# Gemini Workspaces: Maintainer Onboarding
Gemini Workspaces allow you to offload heavy tasks (PR reviews, agentic fixes, full builds) to a high-performance GCP worker. It uses a **Unified Data Disk** architecture to ensure your work persists even if the VM is deleted or recreated.
Gemini Workspaces allow you to offload heavy tasks (PR reviews, agentic fixes,
full builds) to a high-performance GCP worker. It uses a **Unified Data Disk**
architecture to ensure your work persists even if the VM is deleted or
recreated.
## 1. Local Prerequisites
Before starting, ensure you have:
* **GCloud CLI**: Authenticated (`gcloud auth login`).
* **GitHub CLI**: Authenticated (`gh auth login`).
* **Project Access**: A GCP Project ID where you have `Editor` or `Compute Admin` roles.
- **GCloud CLI**: Authenticated (`gcloud auth login`).
- **GitHub CLI**: Authenticated (`gh auth login`).
- **Project Access**: A GCP Project ID where you have `Editor` or
`Compute Admin` roles.
## 2. Initialization
Run the setup script using `npx tsx` to ensure all dependencies are available:
Run the setup script using the unified workspaces entry point:
```bash
npx tsx .gemini/skills/workspaces/scripts/setup.ts
npx tsx scripts/workspaces.ts setup
```
**What happens during setup:**
1. **Auth Discovery**: It will detect your `GEMINI_API_KEY` (from `~/.env`) and `GH_TOKEN`.
1. **Auth Discovery**: It will detect your `GEMINI_API_KEY` (from `~/.env`) and
`GH_TOKEN`.
2. **Project Choice**: You will be prompted for your GCP Project and Zone.
3. **Infrastructure Check**: It verifies if your worker (`gcli-workspace-<user>`) exists.
4. **SSH Magic**: It generates a local `.gemini/workspaces/ssh_config` for seamless access.
3. **Infrastructure Check**: It verifies if your worker
(`gcli-workspace-<user>`) exists.
4. **SSH Magic**: It generates a local `.gemini/workspaces/ssh_config` for
seamless access.
## 3. Provisioning
If the setup informs you that the worker was not found, provision it:
```bash
npx tsx .gemini/skills/workspaces/scripts/fleet.ts provision
npx tsx scripts/workspaces.ts fleet provision
```
*This creates a VM with a 10GB Boot Disk and a 200GB Data Disk. Initialization takes ~1 minute.*
_This creates a VM with a 10GB Boot Disk and a 200GB Data Disk. Initialization
takes ~1 minute._
## 4. Finalizing Remote Setup
Run the setup script one last time to clone the repo and sync credentials:
```bash
npx tsx .gemini/skills/workspaces/scripts/setup.ts
npx tsx scripts/workspaces.ts setup
```
*When you see "ALL SYSTEMS GO!", your workspace is ready.*
_When you see "ALL SYSTEMS GO!", your workspace is ready._
## 5. Daily Usage
Once initialized, you can launch tasks directly through `npm`:
* **Review a PR**: `npm run workspace <PR_NUMBER> review`
* **Fix a PR**: `npm run workspace <PR_NUMBER> fix`
* **Check Status**: `npx tsx .gemini/skills/workspaces/scripts/status.ts`
* **Stop Worker**: `npx tsx .gemini/skills/workspaces/scripts/fleet.ts stop` (Recommended when finished to save cost).
Once initialized, you can launch tasks directly through `npm` or the entry
point:
- **Review a PR**: `npm run workspace <PR_NUMBER> review`
- **Launch a Shell**: `npm run workspace:shell <ID>`
- **Check Status**: `npm run workspace:status`
- **Cleanup All**: `npm run workspace:clean-all`
- **Kill Task**: `npm run workspace:kill <PR> <action>`
- **Stop Worker**: `npx tsx scripts/workspaces.ts fleet stop` (Recommended when
finished to save cost).
## Troubleshooting
* **Permission Denied (Docker)**: The orchestrator handles this by using `sudo docker` internally.
* **Dubious Ownership**: The system automatically adds `/mnt/disks/data/main` to Git's safe directory list.
* **Missing tsx**: Always prefer `npx tsx` when running scripts manually.
- **Permission Denied (Docker)**: The orchestrator handles this by using
`sudo docker` internally.
- **Dubious Ownership**: The system automatically adds `/mnt/disks/data/main` to
Git's safe directory list.
- **Missing tsx**: Always prefer `npx tsx` when running scripts manually.

View File

@@ -64,16 +64,16 @@
"telemetry": "node scripts/telemetry.js",
"check:lockfile": "node scripts/check-lockfile.js",
"clean": "node scripts/clean.js",
"workspace": "tsx .gemini/skills/workspaces/scripts/orchestrator.ts",
"workspace:setup": "tsx .gemini/skills/workspaces/scripts/setup.ts",
"workspace:shell": "tsx .gemini/skills/workspaces/scripts/orchestrator.ts shell",
"workspace:check": "tsx .gemini/skills/workspaces/scripts/check.ts",
"workspace:clean-all": "tsx .gemini/skills/workspaces/scripts/clean.ts",
"workspace:kill": "tsx .gemini/skills/workspaces/scripts/clean.ts",
"workspace:fleet": "tsx .gemini/skills/workspaces/scripts/fleet.ts",
"workspace:status": "tsx .gemini/skills/workspaces/scripts/status.ts",
"workspace:attach": "tsx .gemini/skills/workspaces/scripts/attach.ts",
"workspace:logs": "tsx .gemini/skills/workspaces/scripts/logs.ts",
"workspace": "tsx ./scripts/workspaces.ts",
"workspace:setup": "tsx ./scripts/workspaces.ts setup",
"workspace:shell": "tsx ./scripts/workspaces.ts shell",
"workspace:check": "tsx ./scripts/workspaces.ts check",
"workspace:clean-all": "tsx ./scripts/workspaces.ts clean-all",
"workspace:kill": "tsx ./scripts/workspaces.ts kill",
"workspace:fleet": "tsx ./scripts/workspaces.ts fleet",
"workspace:status": "tsx ./scripts/workspaces.ts status",
"workspace:attach": "tsx ./scripts/workspaces.ts attach",
"workspace:logs": "tsx ./scripts/workspaces.ts logs",
"pre-commit": "node scripts/pre-commit.js"
},
"overrides": {

79
scripts/workspaces.ts Executable file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/env npx tsx
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { spawnSync } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..');
const commands: Record<string, string> = {
setup: '.gemini/skills/workspaces/scripts/setup.ts',
shell: '.gemini/skills/workspaces/scripts/orchestrator.ts shell',
check: '.gemini/skills/workspaces/scripts/check.ts',
'clean-all': '.gemini/skills/workspaces/scripts/clean.ts',
kill: '.gemini/skills/workspaces/scripts/clean.ts',
fleet: '.gemini/skills/workspaces/scripts/fleet.ts',
status: '.gemini/skills/workspaces/scripts/status.ts',
attach: '.gemini/skills/workspaces/scripts/attach.ts',
logs: '.gemini/skills/workspaces/scripts/logs.ts',
};
function printUsage() {
console.log('Gemini Workspaces Management CLI');
console.log(
'\nUsage: scripts/workspaces.ts <command> [args] [--open foreground|tab|window]',
);
console.log('\nCommands:');
console.log(
' setup Initialize or reconfigure your remote worker',
);
console.log(' <pr-number> [action] Launch a PR task (review, fix, ready)');
console.log(' shell [id] Open an ad-hoc interactive session');
console.log(' status See worker and session overview');
console.log(' check <pr-number> Deep-dive into PR logs');
console.log(' kill <pr-number> <act> Surgical removal of a task');
console.log(' clean-all Full remote cleanup');
console.log(' fleet <action> Manage VM life cycle (stop, provision)');
process.exit(1);
}
async function main() {
const args = process.argv.slice(2);
const cmd = args[0];
if (!cmd || cmd === '--help' || cmd === '-h') {
printUsage();
}
let scriptPath = commands[cmd];
let finalArgs = args.slice(1);
// Default: If it's a number, it's a PR orchestrator task
if (!scriptPath && /^\d+$/.test(cmd)) {
scriptPath = '.gemini/skills/workspaces/scripts/orchestrator.ts';
finalArgs = args; // Pass the PR number as the first arg
}
if (!scriptPath) {
console.error(`❌ Unknown command: ${cmd}`);
printUsage();
}
const [realScript, ...internalArgs] = scriptPath.split(' ');
const fullScriptPath = path.join(REPO_ROOT, realScript);
const result = spawnSync(
'npx',
['tsx', fullScriptPath, ...internalArgs, ...finalArgs],
{ stdio: 'inherit' },
);
process.exit(result.status ?? 0);
}
main().catch(console.error);