# <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Huge Icons by Hugeicons - undefined --><g fill="none" stroke="currentColor" stroke-linecap="round"><path stroke-width="1.5" d="M16.093 13C17.23 12.098 18 10.85 18 9.47C18 6.72 14.945 2 12 2C9.054 2 6 6.72 6 9.47c0 1.38.77 2.628 1.908 3.53"/><path stroke-linejoin="round" stroke-width="2" d="M14.012 10h-.01m-3.99 0h-.01"/><path stroke-linejoin="round" stroke-width="1.5" d="M13.5 14c-.484 4 .4 5.714 3.5 8m-6.5-8c.484 4-.4 5.714-3.5 8m6.5-8c1 2 3.58 4.5 5.547 4.5c1.969 0 2.953-1.231 2.953-2.75S20.898 13 19.54 13m-9.04 1c-1 2-3.58 4.5-5.548 4.5S2 17.269 2 15.75S3.102 13 4.46 13"/></g></svg> PULSOS CLI
[](https://github.com/Vivallo04/pulsos-cli/actions/workflows/ci.yml)
[](LICENSE)
Cross-platform deployment monitoring CLI. Track deployments across GitHub Actions, Railway, and Vercel from a single terminal.
## What is Pulsos
Pulsos is a terminal tool that gives you a unified view of deployments across GitHub Actions, Railway, and Vercel. It runs as a live TUI dashboard, as a persistent background daemon with a native system-tray icon, or as one-shot CLI output. The correlation engine matches deployment events across platforms by commit SHA and timestamp heuristics, so you can see how a single commit flows through your CI/CD pipeline.
## Features
- **Live TUI** with 5 tabs: Unified, Platform, Health, Settings, Logs
- **Cross-platform correlation** — matches events by SHA and timestamp proximity
- **Health scoring** per project (weighted: GitHub 40%, Railway 35%, Vercel 25%)
- **DORA metrics** — Deployment Frequency, Lead Time, Change Failure Rate, Time to Restore
- **Real-time telemetry** — Railway container metrics (CPU/RAM) and blackbox endpoint pings (TTFB/uptime)
- **Background daemon** — `pulsos daemon start` runs a persistent process with an Axum SSE server and native tray icon (macOS / Windows)
- **Auto-discovery** of repos, projects, and services via `repos sync`
- **Saved views** with filters for project, platform, branch, and status
- **ETag caching** with rate-limit-aware adaptive polling
- **Credential storage** — OS keyring with file fallback (`~/.config/pulsos/credentials.toml`)
- **Output formats** — table, compact, JSON
- **Shell completions** for bash, zsh, fish, powershell, elvish
## Install
### From crates.io
```sh
cargo install pulsos-cli
```
### From source
```sh
git clone https://github.com/Vivallo04/pulsos-cli.git
cd pulsos-cli
cargo install --path crates/pulsos-cli
```
### Homebrew (macOS / Linux)
```sh
brew install Vivallo04/tap/pulsos
```
### Requirements
- Rust 1.75+
## Quick Start
```sh
# 1. Authenticate with your platforms
pulsos auth github
pulsos auth railway
pulsos auth vercel
# 2. Discover and track your repos/projects
pulsos repos sync
# 3. Launch the live TUI dashboard
pulsos status --watch
# Or get a one-shot table in your terminal
pulsos status --once
# Or run as a persistent background daemon
pulsos daemon start
```
**What happens at each step:**
1. `pulsos auth <platform>` prompts for a token and stores it in your OS keyring (falls back to `~/.config/pulsos/credentials.toml`).
2. `pulsos repos sync` queries each authenticated platform, discovers available repos/projects/services, lets you pick which ones to track, and auto-generates correlations.
3. `pulsos status` fetches recent deployments from all tracked resources, correlates them, and displays the result. With `--watch` it launches the live TUI; without flags it auto-detects (TUI if interactive terminal, one-shot otherwise).
4. `pulsos daemon start` detaches the daemon to the background. The TUI automatically connects to the daemon's SSE stream when one is running, saving API quota.
## Authentication
### Token Resolution Order
Pulsos resolves tokens in this priority order:
1. **Environment variables** — checked first
2. **OS keyring** — via `keyring` crate (macOS Keychain, Windows Credential Manager, Linux Secret Service)
3. **CLI config detection** — reads tokens from `gh`, `railway`, and `vercel` CLI config files
### Environment Variables
| Platform | Variables |
|----------|-----------|
| GitHub | `GH_TOKEN`, `GITHUB_TOKEN` |
| Railway | `RAILWAY_TOKEN`, `RAILWAY_API_TOKEN` |
| Vercel | `VERCEL_TOKEN` |
### Interactive Setup
```sh
pulsos auth github # Authenticate with GitHub
pulsos auth railway # Authenticate with Railway
pulsos auth vercel # Authenticate with Vercel
```
Each command prompts for a token, validates it against the platform API, and stores it.
### Non-Interactive / CI
```sh
# Store tokens in one shot (CI mode)
pulsos auth --ci --github-token=ghp_xxx --railway-token=xxx --vercel-token=xxx
# Check which tokens are resolved
pulsos auth status
# Re-validate stored tokens; re-prompts if invalid
pulsos auth refresh
# Remove stored tokens
pulsos auth logout
pulsos auth logout --platform github
```
### Token Detection Toggles
You can disable automatic detection of specific sources in `config.toml`:
```toml
[auth.token_detection]
detect_gh_cli = true
detect_railway_cli = true
detect_vercel_cli = true
detect_env_vars = true
```
## Configuration
Config file location: `~/.config/pulsos/config.toml`
Run `pulsos config path` to print the exact path, or `pulsos config edit` to open it in `$EDITOR`.
### Full Example
```toml
[auth]
github_host = "github.mycompany.com" # Default: "github.com"
[auth.token_detection]
detect_gh_cli = true
detect_railway_cli = false
detect_vercel_cli = true
detect_env_vars = true
[[github.organizations]]
name = "myorg"
include_patterns = ["api-*"]
exclude_patterns = ["*-legacy"]
auto_discover = true
[[railway.workspaces]]
name = "lambda-prod"
include_projects = ["my-saas-api"]
default_environment = "production" # Default: "production"
[[vercel.teams]]
name = "lambda"
include_projects = ["my-saas-web"]
include_preview_deployments = true
[[correlations]]
name = "my-saas"
github_repo = "myorg/my-saas"
railway_project = "my-saas-api"
vercel_project = "my-saas-web"
[correlations.branch_mapping]
main = "production"
develop = "staging"
[[views]]
name = "production"
description = "Production systems"
projects = ["my-saas", "api-core"]
platforms = ["github", "railway", "vercel"]
branch_filter = "main"
status_filter = ["success", "failure"]
refresh_interval = 5
[[groups]]
name = "backend"
resources = ["api-core", "auth-service"]
[tui]
theme = "dark" # "dark" or "light"
fps = 10 # Render frames per second
refresh_interval = 5 # Seconds between API polls
default_tab = "unified" # "unified", "platform", "health", "settings", "logs"
show_sparklines = true
unicode = "auto" # "auto", "always", "never"
[cache]
max_size_mb = 100
```
### Config Sections
| Section | Purpose |
|---------|---------|
| `[auth]` | GitHub Enterprise host, token detection toggles |
| `[[github.organizations]]` | Org name, include/exclude repo patterns |
| `[[railway.workspaces]]` | Workspace name, include/exclude projects, default environment |
| `[[vercel.teams]]` | Team name, include projects, preview deployment toggle |
| `[[correlations]]` | Link a GitHub repo, Railway project, and Vercel project under one name |
| `[[views]]` | Named filter presets (projects, platforms, branch, status) |
| `[[groups]]` | Named collections of resources for UI-level grouping |
| `[tui]` | Theme, FPS, refresh interval, default tab, sparklines |
| `[cache]` | Max cache size in MB |
## CLI Commands
### Status
```sh
pulsos # Default: show deployment status
pulsos status # Same as above
pulsos status --watch # Live TUI mode
pulsos status --once # Force one-shot output
pulsos status --platform github # Filter by platform
pulsos status --view production # Use a saved view
pulsos status --branch main # Filter by branch
pulsos status --format json # JSON output
pulsos status --format compact # Compact output
```
### Auth
```sh
pulsos auth github # Authenticate with GitHub
pulsos auth railway # Authenticate with Railway
pulsos auth vercel # Authenticate with Vercel
pulsos auth github --token ghp_xxx # Non-interactive with token
pulsos auth status # Check auth status across platforms
pulsos auth refresh # Re-validate tokens, re-prompt if invalid
pulsos auth refresh --platform github # Refresh a specific platform
pulsos auth logout # Remove tokens (prompts for platform)
pulsos auth logout --platform github # Remove a specific platform token
pulsos auth --ci --github-token=xxx # CI mode: store tokens non-interactively
pulsos auth --from-env # Only check env vars (skip keyring/interactive)
```
### Repos
```sh
pulsos repos sync # Discover, select, and save (all platforms)
pulsos repos list # Show tracked repos/projects
pulsos repos add github:org/repo # Add a resource
pulsos repos remove github:org/repo # Remove a resource
pulsos repos correlate my-saas # Edit correlations for a project
pulsos repos groups list # List resource groups
pulsos repos groups create mygroup -- github:org/repo railway:project
pulsos repos groups delete mygroup # Delete a group
pulsos repos verify # Check access permissions for tracked resources
```
### Views
```sh
pulsos views # List all views (default)
pulsos views list # List all views
pulsos views show production # Display view details
pulsos views create # Create a view interactively
pulsos views edit production # Edit a view interactively
pulsos views delete production # Delete a view
pulsos views templates # List built-in templates
pulsos views validate production # Validate view projects against correlations
pulsos views export production -o view.json # Export to JSON
pulsos views import view.json # Import from JSON
```
### Config
```sh
pulsos config # Print current config as TOML (default)
pulsos config show # Print current config as TOML
pulsos config path # Print config file path
pulsos config edit # Open config in $EDITOR
pulsos config wizard # Run interactive platform setup wizard
```
### Daemon
```sh
pulsos daemon run # Run the daemon in the foreground (owns main thread for tray icon)
pulsos daemon start # Start the daemon as a detached background process
pulsos daemon stop # Stop the running daemon (sends SIGTERM via PID file)
pulsos daemon status # Print daemon running state and port
```
**Daemon architecture:**
- `daemon run` starts an Axum SSE server on an ephemeral port, writes the port to `~/.config/pulsos/daemon.port`, generates a random 64-char bearer token at `~/.config/pulsos/daemon.token` (mode 0600 on Unix), and (on macOS/Windows) shows a native tray icon in the menu bar.
- `daemon start` re-execs `daemon run` as a detached process (stdin/stdout/stderr closed).
- `daemon stop` reads `~/.config/pulsos/daemon.pid` and sends SIGTERM (Unix) or `taskkill /F` (Windows), then removes `daemon.pid`, `daemon.port`, and `daemon.token`.
- `daemon status` checks the `/health` endpoint of the running daemon.
- When a daemon is running, `pulsos status --watch` connects to its bearer-auth-protected SSE stream (`/api/stream`) instead of polling platform APIs directly, conserving rate-limit budget. The `/health` endpoint remains public (used by `daemon status`).
- The tray icon shows three states: neutral (all OK), syncing (blue), alert (red — endpoint down or deployment failure on main/master).
- Desktop notifications fire on endpoint state transitions (up→down, down→up) on macOS and Windows.
### Other
```sh
pulsos doctor # Run diagnostics (system, auth, connectivity, cache)
pulsos completions bash # Generate shell completions
pulsos completions zsh
pulsos completions fish
pulsos completions powershell
pulsos completions elvish
```
### Global Flags
| Flag | Description |
|------|-------------|
| `--format <table\|compact\|json>` | Output format (default: table) |
| `--no-color` | Disable color output |
| `--verbose` | Show debug information |
| `--config <path>` | Custom config file path |
## TUI Keyboard Shortcuts
| Key | Action |
|-----|--------|
| `1`–`5` | Switch to tab (Unified, Platform, Health, Settings, Logs) |
| `Tab` / `Shift+Tab` | Cycle tabs forward / backward |
| `j` / `k` or `↑` / `↓` | Navigate rows |
| `/` | Enter search mode |
| `Enter` | Apply search (in search mode) |
| `Esc` | Exit search mode / cancel |
| `r` | Force refresh |
| `s` | Cycle sort order on Unified tab (by time / by platform) |
| `q` / `Ctrl+C` | Quit |
### Platform Tab
| Key | Action |
|-----|--------|
| `←` / `→` | Switch provider subtab (GitHub / Railway / Vercel) |
| `g` | Switch to GitHub subtab |
| `w` | Switch to Railway subtab |
| `v` | Switch to Vercel subtab |
| `d` | Toggle GitHub job/step details panel (GitHub subtab only) |
### Platform Tab — Details Panel (GitHub)
| Key | Action |
|-----|--------|
| `↑` / `↓` or `j` / `k` | Navigate tree / scroll right panel |
| `→` / `Enter` | Expand job or open right panel |
| `←` | Collapse job or focus left tree |
| `PageDown` / `PageUp` | Scroll right panel by 20 lines |
| `Ctrl+D` / `Ctrl+U` | Scroll right panel by 10 lines |
| `Esc` | Close details panel |
### Logs Tab
| Key | Action |
|-----|--------|
| `f` | Cycle log level filter (ALL → ERR → WARN → INFO → ALL) |
| `c` | Copy selected log message to clipboard |
### Settings Tab
| Key | Action |
|-----|--------|
| `t` | Enter token for selected platform |
| `T` | Enter token (force override even with env token) |
| `v` | Validate selected platform |
| `x` | Remove stored token |
| `o` / `Enter` | Start onboarding / discovery flow |
## Project Architecture
```
pulsos-cli/
├── crates/
│ ├── pulsos-core/ # Library crate
│ │ └── src/
│ │ ├── platform/ # GitHub, Railway, Vercel API clients + retry helper
│ │ ├── correlation/ # Event matching engine (SHA + timestamp heuristic)
│ │ ├── domain/ # DeploymentEvent, CorrelatedEvent, DoraMetrics,
│ │ │ # ResourceMetrics, EndpointHealth, health scoring
│ │ ├── auth/ # Credential resolution, KeyringStore, FileCredentialStore, FallbackStore
│ │ ├── config/ # TOML config loading and saving
│ │ ├── cache/ # sled-based ETag cache
│ │ ├── health/ # Platform health checks (PlatformHealthReport) + PingEngine
│ │ ├── scheduler/ # Polling budget and adaptive scheduler
│ │ ├── analytics/ # DoraCalculator
│ │ └── sync/ # Auto-correlation builder (build_correlations, merge)
│ ├── pulsos-cli/ # Binary crate
│ │ ├── assets/ # Embedded tray icon PNGs (neutral, sync, alert — 16x16)
│ │ └── src/
│ │ ├── main.rs # CLI entry point (clap); daemon run owns main thread for tray
│ │ ├── commands/ # Command handlers: status, auth, repos, views, config, doctor, daemon
│ │ │ ├── daemon.rs # pulsos daemon [run|start|stop|status]
│ │ │ ├── wizard.rs # First-run config wizard (auth + discovery + re-check)
│ │ │ └── ui/ # Full-screen interactive prompt layer (ScreenSession)
│ │ ├── daemon/ # Background daemon subsystem
│ │ │ ├── engine.rs # Wraps run_poller; broadcasts DataSnapshot via SSE channel
│ │ │ ├── server.rs # Axum SSE server (/api/stream, /health); writes daemon.port
│ │ │ ├── tray.rs # Native tray icon (macOS/Windows only; tray-icon + tao + muda)
│ │ │ └── notify.rs # Desktop notifications on endpoint state transitions
│ │ ├── tui/ # ratatui live TUI (5 tabs: Unified, Platform, Health, Settings, Logs)
│ │ │ ├── poll.rs # Background poller; connects to SSE if daemon is running
│ │ │ ├── actions.rs # ActionRequest/ActionResult async channel for settings
│ │ │ └── widgets/ # Per-tab rendering components + log_buffer, settings_flow
│ │ └── output/ # Table, compact, JSON formatters
│ └── pulsos-test/ # Test helpers and builders (EventBuilder, mock servers)
└── Cargo.toml # Workspace root
```
### Daemon File Locations
| File | Path | Notes |
|------|------|-------|
| Port file | `~/.config/pulsos/daemon.port` | Written by SSE server on bind; mode 0600 on Unix |
| PID file | `~/.config/pulsos/daemon.pid` | Written by `daemon run` on startup |
| Token file | `~/.config/pulsos/daemon.token` | 64-char random hex bearer token; mode 0600 on Unix; removed on stop |
| Credentials | `~/.config/pulsos/credentials.toml` | File-based fallback; mode 0600 on Unix |
| Config | `~/.config/pulsos/config.toml` | Main TOML config |
### Polling Intervals (TUI / Poller)
| Platform | Interval | Notes |
|----------|----------|-------|
| GitHub | 30s (adaptive) | Backs off to 60s/120s as rate limit is consumed |
| Railway | 15s | |
| Vercel | 15s | |
| Health checks | 30s | Per-platform token + connectivity readiness |
| Container metrics | 30s | Railway CPU/RAM via GraphQL |
| Endpoint pings | 8s | Blackbox TTFB/uptime; does not consume platform quota |
## Development
### Prerequisites
- Rust 1.75+
### Build
```sh
cargo build --workspace
```
### Test
```sh
cargo test --workspace
```
### Lint
```sh
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
```
### Run Locally
```sh
cargo run --bin pulsos -- status
cargo run --bin pulsos -- status --watch
cargo run --bin pulsos -- doctor
cargo run --bin pulsos -- daemon run
```
## License
MIT
---
Last Updated: 2026-02-23