spin-cli
v0.1.2
Published
Beautiful interactive CLI for managing multiple dev services
Downloads
371
Maintainers
Readme
! This is an idea I've been playing around with. It works well, but it's in very early development. The API will likely not remain stable. Although please enjoy, and send me some feedback if you try it out <3.
spin-cli
A beautiful interactive TUI for managing multiple dev services. Start your entire development stack with a single command, view logs in real-time, and control everything with vim-style keyboard shortcuts.
Features
- Interactive TUI — Full-screen terminal interface with real-time log streaming
- Service Management — Start, stop, and restart shell commands or Docker containers
- Service Groups — Organize services into groups for common workflows (e.g.,
spin dev) - Dependencies — Define service dependencies with automatic startup ordering and env sharing
- Command Palette — Run ad-hoc commands with
:(like vim), with fuzzy search - Background Scripts — Minimize running commands to the background
- On-Demand Services — Only show services you're actively using; press
sto start others - PTY Support — Proper rendering for TUI programs like ngrok, htop, etc.
- MCP Integration — Built-in MCP server for AI assistants (Cursor, Claude Desktop)
- Vim Keybindings — Navigate with
j/k,g/G, scroll withCtrl+d/u - TypeScript Config — Type-safe configuration with IntelliSense support
Installation
npm install -g spin-cliOr with your preferred package manager:
pnpm add -g spin-cli
yarn global add spin-cliQuick Start
- Initialize spin in your project:
spin init- Edit
spin.config.tsto configure your services:
import { defineConfig, shell, docker } from "spin-cli";
export default defineConfig({
runnables: {
api: shell("npm run dev", {
cwd: "./api",
description: "API server",
readyWhen: (output) => output.includes("listening"),
}),
web: shell("npm run dev", {
cwd: "./web",
description: "Web frontend",
}),
postgres: docker("postgres:15", {
description: "PostgreSQL database",
ports: ["5432:5432"],
env: { POSTGRES_PASSWORD: "dev" },
}),
},
groups: {
dev: ["postgres", "api", "web"],
},
});Note: You can also import from
./.spin/cli(auto-generated byspin init), which re-exports fromspin-cli. Both work identically.
- Start your services:
spin # Start all services
spin dev # Start the "dev" group
spin api web # Start specific servicesConfiguration
Shell Commands
shell("npm run dev", {
cwd: "./packages/api", // Working directory
description: "API server", // Shown in TUI
env: { PORT: "3000" }, // Environment variables
dependsOn: ["postgres"], // Wait for dependencies
readyWhen: (
output, // Ready detection
) => output.includes("Listening"),
onReady: ({ output, setEnv }) => {
// Extract dynamic values and share with dependents
const port = output.match(/port (\d+)/)?.[1];
if (port) setEnv("API_PORT", port);
},
pty: false, // Use PTY for TUI programs (default: false)
});Docker Containers
docker("postgres:15", {
description: "PostgreSQL database",
ports: ["5432:5432"], // Port mappings
volumes: ["./data:/var/lib/postgresql/data"],
env: {
POSTGRES_USER: "dev",
POSTGRES_PASSWORD: "dev",
POSTGRES_DB: "app",
},
});Service Groups
groups: {
dev: ['postgres', 'redis', 'api', 'web'],
backend: ['postgres', 'redis', 'api', 'queue'],
infra: ['postgres', 'redis'],
}Dependencies
Services can depend on other services. Spin ensures dependencies are started first and waits for them to be ready before starting dependent services.
runnables: {
postgres: docker('postgres:15', {
readyWhen: (output) => output.includes('ready to accept connections'),
}),
api: shell('npm run dev', {
dependsOn: ['postgres'], // Waits for postgres to be ready
}),
}Keyboard Shortcuts
| Key | Action |
| -------------- | ------------------------------------------- |
| 1-9 | Switch to service by number |
| Tab | Next service/background script |
| Shift+Tab | Previous service/background script |
| j/k or ↓/↑ | Scroll down/up (3 lines) |
| Ctrl+d/u | Page down/up (half screen) |
| g/G | Go to top/bottom |
| f | Scroll to bottom and follow new output |
| : | Open command palette |
| r | Restart current service |
| s | Open service picker (start hidden services) |
| x | Stop current service |
| a | Start current service |
| R | Restart all visible services |
| b | View background scripts (when available) |
| ? | Show help |
| q | Quit |
Command Palette
Press : to open the command palette. You can:
- Run any shell command (e.g.,
:npm test) - Execute scripts from
package.jsonfiles - Run scripts from a configured scripts folder
- Search through command history
Commands run in a dedicated output view where you can:
- View output in real-time
- Minimize to background with
m - Cancel with
c - Rerun with
r - Close with
Escape
MCP Integration
Spin includes a built-in MCP (Model Context Protocol) server that allows AI assistants to interact with your services.
Automatic Setup
When you run spin init, spin automatically configures MCP for detected AI tools (Cursor, Claude Desktop).
Manual Setup
Add to your MCP configuration:
{
"mcpServers": {
"spin": {
"command": "spin",
"args": ["mcp"]
}
}
}MCP Commands
spin mcp # Start MCP server (stdio)
spin mcp install # Install MCP config to detected tools
spin mcp status # Show installation status
spin mcp update # Update .spin folder and install MCPAvailable MCP Tools
Always available:
- list_services — List all configured services with current status
- get_service_status — Get detailed status of a specific service
When spin TUI is running:
- get_logs — Get recent logs from a service (with configurable line count)
When spin TUI is not running:
- start_spin — Returns the command to start spin (with optional group parameter)
CLI Commands
spin # Start all services
spin <services...> # Start specific services or groups
spin list # List all services and groups
spin init # Initialize spin in your project
spin init --personal # Initialize for personal use (gitignored)
spin init --force # Overwrite existing configuration
spin mcp # Start MCP server
spin mcp install # Install MCP to detected AI tools
spin mcp status # Show MCP installation status
spin mcp update # Update .spin folder and install MCP
spin uninstall # Remove spin from your project
spin uninstall --force # Remove without confirmation
spin --help # Show help
spin --version # Show versionTeam Workflow
Spin is designed for team adoption:
- Commit
spin.config.ts— Share configuration with your team .spin/cli.tsis auto-generated — Each developer runsspinand the CLI file is generated automatically- MCP installs per-user — AI tool integrations are installed to user-specific config files
Team members just need to:
npm install -g spin-cli
spin # Auto-generates .spin/cli.ts and starts servicesAdvanced Configuration
Script Sources
Configure where the command palette finds scripts:
import { defineConfig, shell, packageScripts, scriptsFolder } from "spin-cli";
export default defineConfig({
runnables: {
/* ... */
},
scripts: [
// Include scripts from all package.json files
packageScripts(),
// Include scripts from a folder with a runner
scriptsFolder("./scripts", "bun", {
label: "scripts",
overrides: {
"migrate.ts": { confirm: true, description: "Run database migrations" },
},
}),
],
});The scriptsFolder() function requires a runner as the second argument:
// Simple runner (bun, node, bash, etc.)
scriptsFolder("./scripts", "bun");
// Docker runner - execute scripts inside a container
import { dockerContext } from "spin-cli";
scriptsFolder("./ops/scripts", dockerContext("ops-container"));
// Kubernetes runner - execute scripts inside a pod
import { kubernetes } from "spin-cli";
scriptsFolder(
"./k8s-scripts",
kubernetes({
selector: "app=api",
namespace: "staging",
runner: "bun run",
}),
);Shell Command Prefixes
Configure command prefixes that bypass fuzzy search and run immediately:
export default defineConfig({
runnables: {
/* ... */
},
// Commands starting with these prefixes run immediately without fuzzy search
shellCommands: [
"npm",
"pnpm",
"yarn",
"bun",
"git",
"docker",
"kubectl",
"make",
],
});PTY Support for TUI Programs
Some programs (like ngrok, htop, or interactive CLIs) require a pseudo-terminal to display properly. Enable PTY mode for these:
ngrok: shell("ngrok http 3000", {
pty: true, // Enables PTY for proper TUI rendering
readyWhen: (output) => output.includes("Forwarding"),
onReady: ({ output, setEnv }) => {
// Extract the ngrok URL and share with dependent services
const url = output.match(/https:\/\/[^\s]+\.ngrok[^\s]*/)?.[0];
if (url) setEnv("NGROK_URL", url);
},
});With pty: true, spin uses node-pty and xterm-headless to properly capture and render terminal escape codes.
Defaults
Configure default settings that apply to all runnables:
export default defineConfig({
runnables: {
/* ... */
},
defaults: {
// Environment variables applied to all runnables
env: {
NODE_ENV: "development",
},
// Maximum lines kept in each service's output buffer (default: 1000)
maxOutputLines: 500,
},
});Requirements
- Node.js >= 18.0.0
- Docker (optional, for Docker containers)
Development
# Install dependencies
pnpm install
# Run in watch mode
pnpm dev
# Build
pnpm build
# Run tests
pnpm test
# Type check
pnpm typecheck
# Lint
pnpm lintLicense
MIT
