
Security News
PolinRider: North Korea-Linked Supply Chain Campaign Expands Across Open Source Ecosystems
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.
CLI for managing GitHub workspaces with git worktrees and secure remote terminal access
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.
The following tools must be installed and available in your PATH:
gh) - for listing repositoriesGitHub 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 --version
Simply run gssh with no arguments to launch the interactive TUI:
gssh
The TUI provides a two-panel interface:
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 add
Select a GitHub repository, and GitSpace will:
~/gitspace/<project-name>/base# 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-feature
space)When GitSpace opens a workspace-scoped terminal session, it injects a space shell function (bash/zsh).
space ... for workspace operations without repeating --project and --workspacegssh commands are restricted in this mode to avoid cross-workspace mistakesgssh machine tmux ... is blocked inside workspace sessionsExamples:
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
bundle.json){
"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 exact configKey from bundle.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"
fi
Bundles can be loaded from:
.gitspace/ directory in the cloned repositorygssh project add --bundle-path /path/to/bundle/gssh project add --bundle-url https://example.com/bundle.zipgssh (TUI)Launch the interactive terminal UI.
gssh project addAdd 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 flag
gssh workspace add [workspace-name] --project <project-name>Create 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 commands
gssh workspace context --project <project-name> --workspace <workspace-name>Show the resolved workspace context.
gssh workspace context --project <project-name> --workspace <workspace-name>
Use --project on workspace commands to target a project.
gssh project list / gssh workspace list --project <project-name>List projects or workspaces.
gssh project list [options]
gssh workspace list --project <project-name> [options]
Options:
--json Output in JSON format
--verbose Show additional details
gssh workspace remove [workspace-name] --project <project-name>Remove 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 workspace
gssh project remove [project-name]Remove a project.
gssh project remove [project-name] [options]
Options:
--force Skip confirmation prompts
Located 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 appliedGitSpace 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
chmod +x)01-, 02- prefixes)$1 = workspace name, $2 = repository nameREGION, 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.sh
In 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 start
When --relay is omitted, gssh machine serve start lets you choose from:
ws://127.0.0.1:4480/ws) if running*.gitspace.sh) discovered from your host config/accountgssh 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 show
Remote access is owner-only at runtime.
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
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:
# Install dependencies
bun install
# Development mode
bun run dev
# Type checking
bun run typecheck
# Run linter
bun run lint
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
FAQs
CLI for managing GitHub workspaces with git worktrees and secure remote terminal access
The npm package gitspace receives a total of 88 weekly downloads. As such, gitspace popularity was classified as not popular.
We found that gitspace demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.