A powerful CLI tool for managing GitHub repository workspaces using git worktrees and optional Linear integration. Work on multiple features/tasks simultaneously, each in its own isolated workspace. Features an interactive TUI and support for repo config bundles for team onboarding.
- Interactive TUI: Beautiful terminal interface for managing projects and workspaces
- Git Worktrees: Work on multiple branches simultaneously without stashing
- Linear Integration: Create workspaces directly from Linear issues with automatic markdown documentation
- Smart Branch Management: Automatic detection of remote branches
- Workspace Status: Track uncommitted changes, stale workspaces, and more
- Custom Scripts: Convention-based scripts for setup, select, pre-setup, and removal phases
- Repo Config Bundles: Share onboarding configurations with your team, including scripts and setup steps
- Secure Secrets: Store sensitive values in OS keychain via Bun.secrets
The following tools must be installed and available in your PATH:
- GitHub CLI (
gh) - for listing repositories - Git - for worktree management
- jq - for JSON processing
GitHub Authentication: You must authenticate the GitHub CLI before using GitSpace:
gh auth login# npm
npm install -g gitspace
# bun
bun install -g gitspace
# pnpm
pnpm install -g gitspace
# yarn
yarn global add gitspace
# Verify installation
gssh --versionSimply run gssh with no arguments to launch the interactive TUI:
gsshThe TUI provides a two-panel interface:
- Left panel: Your projects
- Right panel: Workspaces in the selected project
Key Bindings:
| Key | Action |
|---|---|
Enter |
Select project / Open workspace |
Tab |
Switch between panels |
n |
New project / workspace |
d |
Delete selected item |
? |
Show help |
q |
Quit |
You can also use traditional CLI commands:
gssh project addSelect a GitHub repository, and GitSpace will:
- Clone the repository to
~/gitspace/<project-name>/base - Detect the default branch
- Run onboarding steps if a bundle is present
- Create project configuration
# Create a workspace from a Linear issue (if configured)
gssh workspace add --project my-project
# Or create a workspace with a custom name
gssh workspace add my-feature --project my-project# List workspaces in a project
gssh workspace list --project <project-name>
# Show context for a specific workspace
gssh workspace context --project my-project --workspace my-featureWhen GitSpace opens a workspace-scoped terminal session, it injects a space shell function (bash/zsh).
- Use
space ...for workspace operations without repeating--projectand--workspace gsshcommands are restricted in this mode to avoid cross-workspace mistakesgssh machine tmux ...is blocked inside workspace sessions
Examples:
space context --json
space review hunks src/app.ts --format json
space review add-hunk src/app.ts --index 1 --approve --body "Looks good"Repo config bundles allow repository owners to share onboarding configurations with their team. When someone clones a project that contains a bundle, they'll be guided through setup steps and have scripts automatically installed.
A bundle is a directory (typically .gitspace/) containing:
.gitspace/
├── bundle.json # Bundle manifest with onboarding steps
└── scripts/
├── pre/ # Deprecated: migrate scripts into ordered setup/
│ └── 01-copy-env.sh
├── setup/ # Scripts for setup runs (when bundle/value state changes)
│ └── 01-install-deps.sh
├── select/ # Scripts to run on each new terminal attach
│ └── 01-status.sh
└── remove/ # Scripts to run before workspace deletion
└── 01-cleanup.sh
{
"version": "1.0",
"name": "my-app-bundle",
"description": "Setup bundle for my-app",
"onboarding": [
{
"id": "welcome",
"type": "info",
"title": "Welcome",
"description": "Let's get you set up!"
},
{
"id": "node",
"type": "confirm",
"title": "Node.js",
"description": "Node.js 18+ is required",
"checkCommand": "node",
"installUrl": "https://nodejs.org"
},
{
"id": "api-key",
"type": "secret",
"title": "API Key",
"description": "Enter your API key",
"configKey": "apiKey"
},
{
"id": "team-name",
"type": "input",
"title": "Team Name",
"description": "Enter your team name",
"configKey": "teamName",
"defaultValue": "engineering"
}
]
}| Type | Purpose | Storage |
|---|---|---|
info |
Display information | N/A |
confirm |
Verify installation (can check command in PATH) | N/A |
secret |
Collect sensitive values (masked input) | OS Keychain |
input |
Collect plain text values | Project config |
Bundle values are passed to scripts as environment variables using the configured bundle keys:
<KEY>- Regular or secret value using the exactconfigKeyfrombundle.json<NORMALIZED_KEY>- Uppercase snake-case alias (for example,teamName->TEAM_NAME)
Example script:
#!/bin/bash
# .gitspace/scripts/select/01-status.sh
WORKSPACE_NAME=$1
REPOSITORY=$2
# Access bundle values
if [ -n "$TEAM_NAME" ]; then
echo "Welcome, $TEAM_NAME team!"
fi
# Access secrets (stored securely in OS keychain)
if [ -n "$API_KEY" ]; then
echo "API Key configured"
fiBundles can be loaded from:
- In-repo (automatic):
.gitspace/directory in the cloned repository - Local path:
gssh project add --bundle-path /path/to/bundle/ - Remote URL:
gssh project add --bundle-url https://example.com/bundle.zip
Launch the interactive terminal UI.
Add a new project from GitHub.
gssh project add [options]
Options:
--bundle-url <url> Load bundle from remote URL (zip archive)
--bundle-path <path> Load bundle from local directory
--skip-bundle Skip bundle detection and onboarding
--no-clone Create project structure without cloning
--org <org> Filter repos to specific organization
--linear-key <key> Provide Linear API key via flagCreate a new workspace in the current project.
gssh workspace add [workspace-name] --project <project-name> [options]
Options:
--branch <name> Specify different branch name from workspace name
--from <branch> Create from specific branch instead of base
--no-setup Skip setup commandsShow the resolved workspace context.
gssh workspace context --project <project-name> --workspace <workspace-name>Use --project on workspace commands to target a project.
List projects or workspaces.
gssh project list [options]
gssh workspace list --project <project-name> [options]
Options:
--json Output in JSON format
--verbose Show additional detailsRemove a workspace.
gssh workspace remove [workspace-name] --project <project-name> [options]
Options:
--force Skip confirmation prompts
--keep-branch Don't delete git branch when removing workspaceRemove a project.
gssh project remove [project-name] [options]
Options:
--force Skip confirmation promptsLocated at ~/gitspace/.config.json:
{
"currentProject": "my-app",
"projectsDir": "/Users/username/gitspace",
"defaultBaseBranch": "main",
"staleDays": 30
}Located at ~/gitspace/<project-name>/.config.json:
{
"name": "my-app",
"repository": "myorg/my-app",
"baseBranch": "main",
"linearApiKey": "lin_api_...",
"linearTeamKey": "ENG",
"bundleValues": {
"teamName": "engineering"
},
"bundleSecretKeys": ["apiKey"],
"appliedBundle": {
"name": "my-app-bundle",
"version": "1.0",
"source": "/path/to/bundle",
"appliedAt": "2025-01-01T00:00:00Z"
}
}bundleValues: Values collected from input steps during onboardingbundleSecretKeys: Keys of secrets stored in OS keychain (values are NOT stored in config)appliedBundle: Information about the bundle that was applied
GitSpace uses convention over configuration for custom scripts. Scripts live inside each workspace so they can vary by branch:
~/gitspace/<project-name>/workspaces/<workspace-name>/.gitspace/
└── scripts/
├── pre/ # Deprecated: run before setup (migrate to setup/)
├── setup/ # Run when setup state requires refresh
├── select/ # Run on each new terminal attach
└── remove/ # Run before workspace deletion
- Scripts must be executable (
chmod +x) - Scripts run alphabetically (use
01-,02-prefixes) - Working directory: The workspace directory
- Arguments:
$1= workspace name,$2= repository name - Environment: Bundle values available by key name (for example
REGION,PULUMI_ACCESS_TOKEN)
| Phase | When | Use Case |
|---|---|---|
pre/ |
Deprecated | Move scripts into ordered setup/ files |
setup/ |
When setup state changes | Install dependencies, initial build |
select/ |
Every new terminal attach | Git fetch, status checks |
remove/ |
Before deletion | Cleanup, notifications |
# Available in scripts (from bundle onboarding):
# <KEY> - Value by exact bundle config key name
# <NORMALIZED_KEY> - Uppercase snake-case alias (e.g. teamName -> TEAM_NAME)~/gitspace/
├── .config.json # Global configuration
├── <project-name>/
│ ├── .config.json # Project configuration
│ ├── base/ # Base repository clone
│ ├── workspaces/ # Git worktrees
│ │ └── <workspace-name>/
│ │ ├── gitspace.lock # Setup completion marker
│ │ ├── .prompt/ # Linear issue details (if applicable)
│ │ │ └── issue.md
│ │ └── .gitspace/
│ │ ├── bundle.json
│ │ └── scripts/ # Custom scripts (per worktree)
│ │ ├── pre/
│ │ ├── setup/
│ │ ├── select/
│ │ └── remove/
GitSpace provides secure remote terminal access with end-to-end encryption. Access your terminal sessions from anywhere via web browser or CLI.
The easiest way to get remote access is through gitspace.sh:
# 1. Initialize machine identity on your control host
gssh user identity init
gssh user identity show
# 2. Authenticate with gitspace.sh
gssh user auth login
# 3. Reserve your subdomain (e.g., yourname.gitspace.sh)
gssh user host reserve yourname
gssh user host status
# 4. Start serving
gssh machine serve start
gssh machine serve status
gssh status
# 5. Access from browser at https://yourname.gitspace.shIn hosted mode, this machine is your control node (owner): it runs the relay path, maintains access state, and is the place cloud-control state/secrets are managed.
For complete control, run your own relay:
# Terminal 1: Start relay server
gssh relay start --port 4480
# Terminal 2: Create relay-machine invite token
gssh invite relay-machine create --relay ws://localhost:4480/ws --machine-signing-key <BASE64_ED25519_PUB> --machine-key-exchange-key <BASE64_X25519_PUB> --label "My MacBook"
# Terminal 3: Initialize identity, enroll, and start serving
gssh user identity init
gssh machine enroll --invite "ws://localhost:4480/ws#<TOKEN>" --label "My MacBook"
gssh machine serve startWhen --relay is omitted, gssh machine serve start lets you choose from:
- local relay (
ws://127.0.0.1:4480/ws) if running - account relays (
*.gitspace.sh) discovered from your host config/account
gssh relay start always keeps the relay reachable locally. If account hosting is configured,
auto and hosted modes add a *.gitspace.sh tunnel on top of the same local relay instead of
replacing loopback access.
Every machine and client has a cryptographic identity (Ed25519 + X25519 keypair):
# Create machine identity (stored in OS keychain)
gssh user identity init
# View identity fingerprint
gssh user identity showRemote access is owner-only at runtime.
- Clients and machines must present device certificates derived from the same owner user root identity.
- There is no collaborator ACL grant path for relay or machine access.
Use root-signed invites for machine enrollment only:
# Create machine enrollment invite token
gssh invite relay-machine create --relay ws://localhost:4480/ws --machine-signing-key <BASE64_ED25519_PUB> --machine-key-exchange-key <BASE64_X25519_PUB>
# List/revoke enrollment invites
gssh invite list --relay ws://localhost:4480/ws
gssh invite revoke <invite-id> --relay ws://localhost:4480/ws# On another owner device: recover the same user root identity
gssh user identity recover
# Connect directly as owner
gssh client connect <machine-id>
# Browse machines on a relay
gssh client machines list --relay wss://relay.example.com| Command | Description |
|---|---|
gssh user auth login |
Authenticate with gitspace.sh (GitHub OAuth) |
gssh user auth logout |
Sign out of gitspace.sh |
gssh user host reserve <name> |
Reserve a subdomain on gitspace.sh |
gssh user host status |
Show hosting status |
gssh user identity init |
Create user root identity |
gssh user identity recover |
Recover identity from mnemonic |
gssh user identity show |
Display identity fingerprint |
gssh machine serve start --foreground |
Start machine daemon |
gssh machine serve start |
Start serve as background daemon |
gssh machine serve stop |
Stop background serve daemon |
gssh cloud status |
Show cloud control status on current control node |
gssh cloud list |
List cloud workspaces from control store |
gssh invite relay-machine create --relay <url> --machine-signing-key <k> --machine-key-exchange-key <k> |
Create machine enrollment invite |
gssh invite list --relay <url> |
List root-signed invites |
gssh invite revoke <invite-id> --relay <url> |
Revoke root-signed invite |
gssh client connect <target> |
Connect to remote machine |
gssh client machines list --relay <url> |
List accessible remote machines |
gssh status |
Show all daemon statuses |
For self-hosted relay servers:
| Command | Description |
|---|---|
gssh relay start |
Start relay server |
gssh relay stop |
Stop relay server |
gssh relay status |
Show relay server status |
gssh invite relay-machine create --relay <url> --machine-signing-key <k> --machine-key-exchange-key <k> |
Create machine enrollment invite |
gssh relay machines list |
List registered machines |
gssh relay machines revoke <machine-id> |
Revoke machine registration |
Manage terminal sessions:
| Command | Description |
|---|---|
gssh machine tmux start |
Start tmux-lite daemon |
gssh machine tmux stop |
Stop tmux-lite daemon |
gssh machine tmux list |
List sessions |
gssh machine tmux attach <id> |
Attach to session |
gssh machine tmux new |
Create new session |
gssh machine tmux kill <id> |
Kill session |
# Relay server
RELAY_PORT=4480 # Default relay port
RELAY_BIND=0.0.0.0 # Bind address
# gitspace.sh
GITSPACE_API_URL=https://api.gitspace.sh # API endpoint- E2E Encryption: All terminal I/O encrypted with AES-256-GCM
- X3DH Handshake: Forward-secret session key establishment
- Ed25519 Signatures: Cryptographic identity verification
- Zero-knowledge Relay: Relay cannot decrypt terminal content
See docs/GETTING-STARTED.md for detailed setup and docs/REMOTE-DESIGN.md for architecture.
Error: GitHub CLI is not authenticated
Solution: Run gh auth login and follow the prompts.
Solution: Install the missing dependencies using the provided URLs in the error message.
If expected bundle key environment variables are empty, ensure:
- You completed the onboarding secret steps
- Your OS keychain service is running (libsecret on Linux, Keychain on macOS)
# Install dependencies
bun install
# Development mode
bun run dev
# Type checking
bun run typecheck
# Run linter
bun run lintMIT
Contributions are welcome! Please feel free to submit a Pull Request.