This document provides comprehensive information for AI assistants working on the Spaces CLI project.
Spaces is a powerful CLI tool for managing GitHub repository workspaces using git worktrees and optional Linear integration. It features both a TUI (Terminal User Interface) for interactive management and traditional CLI commands. Developers can work on multiple features/tasks simultaneously, each in its own isolated workspace.
Key Capabilities:
- Git worktrees for parallel branch development
- Interactive TUI for workspace management
- Linear issue integration for workspace creation
- Convention-based custom scripts (pre, setup, select phases)
- Project and workspace lifecycle management
- Subshell spawning for workspace context
-
Projects: Top-level containers for a GitHub repository
- Located at
~/spaces/<project-name>/ - Contains: base repo clone, workspaces, scripts, config
- Located at
-
Workspaces: Individual git worktrees for features/branches
- Located at
~/spaces/<project-name>/workspaces/<workspace-name>/ - Each workspace has its own branch
- Located at
-
TUI: Interactive terminal interface
- Two-panel layout (projects | workspaces)
- Built with OpenTUI
- Launches when running
spaceswith no arguments
-
Scripts: Convention-based automation
pre/: Run in terminal before shell (e.g., copy .env files)setup/: Run once on first workspace creation (e.g., npm install)select/: Run every time workspace is opened (e.g., git fetch)
~/spaces/
├── app/ # This CLI application
│ ├── src/
│ │ ├── index.ts # Entry point (TUI or CLI dispatch)
│ │ ├── commands/ # Command implementations
│ │ │ ├── add.ts # Add projects/workspaces
│ │ │ ├── switch.ts # Switch projects/workspaces
│ │ │ ├── list.ts # List projects/workspaces
│ │ │ └── remove.ts # Remove projects/workspaces
│ │ ├── tui/ # Terminal User Interface
│ │ │ ├── index.ts # TUI entry point
│ │ │ ├── app.ts # Main TUI application
│ │ │ └── state.ts # State management
│ │ ├── core/ # Core functionality
│ │ │ ├── config.ts # Configuration management
│ │ │ ├── git.ts # Git operations (worktrees, branches)
│ │ │ ├── github.ts # GitHub API (via gh CLI)
│ │ │ ├── shell.ts # Subshell spawning
│ │ │ └── linear.ts # Linear API integration
│ │ ├── utils/ # Utility functions
│ │ │ ├── deps.ts # Dependency checking
│ │ │ ├── logger.ts # Colored logging
│ │ │ ├── prompts.ts # User prompts (@inquirer/prompts)
│ │ │ ├── run-scripts.ts # Script discovery and execution
│ │ │ ├── sanitize.ts # String sanitization
│ │ │ └── workspace-state.ts # Workspace state tracking
│ │ └── types/ # TypeScript type definitions
│ │ ├── config.ts
│ │ ├── errors.ts
│ │ └── workspace.ts
│ └── dist/ # Built JavaScript (via tsup)
└── <project-name>/ # User projects
├── .config.json # Project configuration
├── base/ # Base repository clone
├── workspaces/ # Git worktrees
│ └── <workspace-name>/
│ └── .spaces-setup # Marker file (setup scripts completed)
└── scripts/ # Custom automation scripts
├── pre/ # Run before shell (terminal)
│ └── 01-copy-env.sh
├── setup/ # Run once (first time)
│ └── 01-install.sh
└── select/ # Run every time
└── 01-fetch.sh
The TUI launches when running spaces with no arguments.
┌─────────────────────────────────────────────────────────────┐
│ SPACES │
├─────────────────────────────────────────────────────────────┤
│ Projects │ Workspaces (my-app) │
│ ─────────────────│──────────────────────────────────────── │
│ > my-app │ > feature-login [main +2 -0] clean │
│ other-project │ fix-bug-123 [main +0 -1] 3 chg │
│ website │ refactor-auth [main +5 -0] clean │
│ │ │
├─────────────────────────────────────────────────────────────┤
│ [Enter] Open [Tab] Switch [q] Quit │
└─────────────────────────────────────────────────────────────┘
| Key | Action |
|---|---|
Enter |
Open workspace (runs scripts, drops into shell) |
Tab |
Switch focus between panels |
↑/↓ or j/k |
Navigate list |
q or Ctrl+C |
Quit |
When opening a workspace from TUI:
- TUI suspends rendering
- Runs pre/setup/select scripts as appropriate
- Spawns interactive shell with workspace as cwd
- On shell exit (Ctrl+D), TUI resumes
- Workspace list refreshes
{
"currentProject": "my-app",
"projectsDir": "/Users/username/spaces",
"defaultBaseBranch": "main",
"staleDays": 30
}{
"name": "my-app",
"repository": "myorg/my-app",
"baseBranch": "main",
"linearApiKey": "lin_api_...",
"linearTeamKey": "ENG",
"createdAt": "2025-10-06T12:00:00Z",
"lastAccessed": "2025-10-06T12:00:00Z"
}Scripts are discovered by file system convention, not listed in config:
- Place executable scripts in
~/spaces/<project>/scripts/{pre|setup|select}/ - Scripts execute alphabetically (use
01-,02-prefixes) - Scripts receive two arguments:
$1= workspace name,$2= repository - Scripts execute with workspace directory as cwd
Pre Scripts (scripts/pre/):
- Run in terminal before shell spawns
- Run once when workspace is created
- Use for: copying .env files, creating directories
Setup Scripts (scripts/setup/):
- Run in terminal
- Run once when workspace is created
- After completion,
.spaces-setupmarker is created - Use for: npm install, builds, database setup
Select Scripts (scripts/select/):
- Run in terminal
- Run every time workspace is opened (when setup already complete)
- Use for: git fetch, status checks
# Type check
bun run typecheck
# Build (creates dist/index.js)
bun run build
# Run in development
bun run dev <command>
# Run TUI directly
bun run src/index.ts
# Lint
bun run lint- TypeScript: ESM modules, strict mode
- Error Handling: Use typed errors from
types/errors.ts - Logging: Use
loggerfromutils/logger.ts(supports colors, levels) - User Prompts: Use functions from
utils/prompts.ts(selectItem, promptInput, promptConfirm, promptPassword)
- Create command file in
src/commands/ - Export function(s) from the command file
- Register command in
src/index.tsusing Commander.js - Follow existing patterns for error handling and logging
All worktree operations go through src/core/git.ts:
createWorktree()- Create new worktree and branchremoveWorktree()- Remove worktree and optionally delete branchgetWorktreeInfo()- Get status (branch, ahead/behind, uncommitted changes)checkRemoteBranch()- Check if branch exists on remote
Required system dependencies (checked at runtime):
- GitHub CLI (
gh) - with authentication - Git
- jq
- Node.js 18+ / Bun
Note: fzf is NOT required (we use @inquirer/prompts instead)
Scripts discovered via:
// src/utils/run-scripts.ts
export function discoverScripts(scriptsDir: string): string[] {
// Finds executable files (mode & 0o111 !== 0)
// Returns sorted array
}Scripts execute with:
- Terminal:
spawn()withstdio: 'inherit'andcwd: workspacePath
Marker file: ~/spaces/<project>/workspaces/<workspace>/.spaces-setup
- Created after setup scripts complete
- Prevents setup scripts from running again
- Triggers select scripts instead on subsequent opens
The openWorkspaceShell() function in src/core/shell.ts:
- Runs setup or select scripts based on marker file
- Marks setup complete if needed
- Spawns interactive shell with
SPACES_WORKSPACEenv var set
- Update
getScriptsPhaseDir()insrc/core/config.tsto accept new phase - Create directory in
createProject()function - Add logic in appropriate command (add.ts, switch.ts, etc.)
- Update README.md with phase documentation
- Update type in
src/types/config.ts(GlobalConfig or ProjectConfig) - Update default config creation functions
- Update
readGlobalConfig()orreadProjectConfig()to merge defaults - Update any command that uses the config
Use functions from src/utils/prompts.ts:
import { selectItem, promptInput, promptConfirm, promptPassword } from '../utils/prompts.js';
// Searchable list
const selected = await selectItem(items, 'Select an item:');
// Text input with validation
const name = await promptInput('Enter name:', {
validate: (input) => input.length > 0 || 'Name required'
});
// Confirmation
const confirmed = await promptConfirm('Continue?', true);
// Password
const apiKey = await promptPassword('Enter API key:');When making changes, test these workflows:
-
TUI Launch:
- Run
spaceswith no arguments - Verify: two-panel layout, navigation works, projects load
- Run
-
Project Creation:
spaces add project- Verify: base clone, config created, scripts directories exist
-
Workspace Creation:
spaces add <name>- Verify: worktree created, pre scripts run, setup scripts run, marker created
-
Workspace Switching:
spaces switch <name>- Verify: select scripts run, setup scripts NOT run
-
TUI Workspace Open:
- Press Enter on workspace in TUI
- Verify: shell opens, Ctrl+D returns to TUI
-
Edge Cases:
- Cancelled prompts (return null)
- Missing dependencies
- Non-existent workspaces/projects
- Remote branch conflicts
Use typed errors from src/types/errors.ts:
import { SpacesError, NoProjectError, WorkspaceExistsError } from '../types/errors.js';
// General error
throw new SpacesError('Error message', 'USER_ERROR', 1);
// Specific error types
throw new NoProjectError();
throw new WorkspaceExistsError('workspace-name');Error types:
USER_ERROR(exit code 1): User-facing issuesSYSTEM_ERROR(exit code 2): System/runtime issues
Runtime:
commander- CLI framework@inquirer/prompts- User prompts (search, input, confirm, password)@opentui/core- Terminal UI framework@linear/sdk- Linear API clientchalk- Terminal colorsora- Loading spinners
System Commands:
gh- GitHub CLI (list repos, check auth)git- Git operations (worktree, branch management)jq- JSON processing (used by some git operations)
-
No Inline Bash for Communication: Never use
echoor bash tools to communicate with users. Useloggerinstead. -
Always Read Before Edit: The Edit tool requires reading files first.
-
TUI Suspension: When opening a workspace from TUI, the renderer must be stopped before spawning shell and restarted after.
-
Script Permissions: Example scripts are created with
chmod 0o755(executable). -
Environment Variable:
SPACES_CURRENT_PROJECToverrides config'scurrentProject. -
Git Worktree Paths: Always use absolute paths, never relative.
-
Concurrent Workspaces: Multiple workspaces can exist for the same project, each on different branches.
Get project directory: getProjectDir(projectName)
Get workspace directory: join(getProjectWorkspacesDir(projectName), workspaceName)
Get script phase directory: getScriptsPhaseDir(projectName, 'pre' | 'setup' | 'select')
Check setup status: hasSetupBeenRun(workspacePath)
Mark setup complete: markSetupComplete(workspacePath)
Discover scripts: discoverScripts(scriptsDir)
Run scripts in terminal: runScriptsInTerminal(scriptsDir, workspacePath, workspaceName, repository)
Open workspace shell: openWorkspaceShell(workspacePath, projectName, repository, noSetup)
Launch TUI: launchTUI()
README.md: User-facing documentation CLAUDE.md: This file - development documentation package.json: Dependencies and scripts tsconfig.json: TypeScript configuration src/types/: TypeScript type definitions
Last Updated: 2025-12-16 CLI Version: 1.0.0 Runtime: Bun / Node.js 18+