Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- name: Install pytest
run: python -m pip install --upgrade pip pytest
- name: Install package + pytest
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"

- name: Run tests
run: python -m pytest tests/ -v
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ PROJECT_INDEX.md
# Hindsight migration markers
.hindsight-migrate-declined

# Mycelium project tracker (local-only)
.mycelium/

# uv lockfile (not tracked for applications-as-libraries)
uv.lock

# Python
__pycache__/
*.pyc
Expand Down
Binary file removed .mycelium/mycelium.db
Binary file not shown.
272 changes: 54 additions & 218 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# bgo - Background Go
# bgo — Background Go

Lightweight, zero-dep background process manager inspired by pm2.
Detach any binary or script from your shell with one command.
Expand All @@ -7,264 +7,100 @@ Detach any binary or script from your shell with one command.
[![Python](https://img.shields.io/pypi/pyversions/bgo-cli.svg)](https://pypi.org/project/bgo-cli/)
[![License](https://img.shields.io/pypi/l/bgo-cli.svg)](LICENSE)

PyPI: https://pypi.org/project/bgo-cli/
PyPI: <https://pypi.org/project/bgo-cli/>

## Features

- 🚀 **Simple syntax** — `bgo <name> -- <command>`
- 🐧 **Unix-style aliases** — `bgo open` / `kill` / `rm` / `ls`
- 📊 **Status monitoring** — CPU, memory, uptime in plain / normal / fancy tables (auto-detect)
- 📝 **Log management** — stdout / stderr / watcher logs with follow mode
- 🔄 **Lifecycle** — start, stop, restart, restart-stopped, restart-last, resurrect
- 👁 **Watch mode** — auto-restart crashed processes with fast-crash backoff
- 🧹 **Auto-cleanup** — clean stopped procs; keep logs on delete if desired
- 🤖 **Scriptable** — `--json` output for any pipeline
- ⚡ **Zero runtime dependencies** — pure Python 3.9+

## Installation

### Recommended: `uv`

[`uv`](https://docs.astral.sh/uv/) installs `bgo-cli` into an isolated
environment and links the `bgo` command onto your PATH. No global
Python pollution, no virtualenv to manage.

```bash
uv tool install bgo-cli
```

Upgrade:
```bash
uv tool upgrade bgo-cli
```

Uninstall:
```bash
uv tool uninstall bgo-cli
```

### Alternatives

```bash
# pipx (also isolated, very similar to uv tool)
pipx install bgo-cli

# Plain pip (installs into the active environment)
pip install bgo-cli

# Install script — builds nothing, just copies the single-file script
./install.sh # /usr/local/bin (sudo)
./install.sh --local # ~/.local/bin (no sudo)
./install.sh --help # --force / --uninstall

# Or fully manual — bgo is a single file
cp bgo ~/.local/bin/ # or /usr/local/bin/
ln -s "$(pwd)/bgo" ~/.local/bin/bgo
```

### Verify
- **Simple syntax** — `bgo <name> -- <command>`
- **Unix-style aliases** — `bgo open` / `kill` / `rm` / `ls`
- **Status monitoring** — CPU, memory, uptime; plain / normal / fancy
tables with auto-detection
- **Log management** — stdout / stderr / watcher logs with follow mode
- **Lifecycle** — start, stop, restart, restart-stopped, restart-last,
resurrect
- **Watch mode** — auto-restart crashed processes with fast-crash backoff
- **Desktop notifications** on errored transitions
- **Login autostart** via systemd-user (Linux) or LaunchAgent (macOS)
- **Tray icon** (optional) — PySide6, gear+dot status indicator,
KDE/Hyprland/GNOME/macOS
- **Scriptable** — `--json` output for any pipeline
- **Zero runtime dependencies** in the core; one optional extra for the tray

## Quick start

```bash
bgo --help
bgo ls # no procs registered yet
```
uv tool install bgo-cli # see docs/installation.md for alternatives

## Quick Start

```bash
# Start a process
bgo myserver -- python3 -m http.server 8080
bgo open myserver -- python3 -m http.server 8080 # alias

# Check status
bgo status # full alias chain: status / ls / list
bgo ls

# Status detail for one proc (or: bgo <registered-name>)
bgo myserver

# View logs
bgo logs myserver
bgo logs myserver -f # follow (tail -f)
bgo follow myserver # alias for logs -f

# Stop / restart / delete
bgo stop myserver # or: bgo kill myserver
bgo logs myserver -f
bgo restart myserver
bgo delete myserver # or: bgo rm myserver
bgo rm myserver --keep-logs # preserve log files

# Bare `bgo` prints help; `bgo <unknown>` errors out (never silently spawns)
bgo
bgo stop myserver
```

## Commands

### Lifecycle
`bgo` with no args prints help. `bgo <unknown>` errors out rather than
silently spawning something.

| Command | Description |
|---|---|
| `bgo start <name> -- <cmd>` | Start a process (alias: `open`) |
| `bgo <name> -- <cmd>` | Shorthand for start |
| `bgo stop <name>` | Stop (SIGTERM, alias: `kill`) |
| `bgo stop <name> -f` | Force kill (SIGKILL) |
| `bgo restart <name>` | Restart; preserves watch state and counters |
| `bgo restart <name> --reset-counters` | Also zero `watch.restarts` |
| `bgo restart-stopped` | Pick stopped procs to restart (interactive) |
| `bgo restart-stopped --all` | Restart every stopped proc |
| `bgo restart-stopped <name>...` | Restart named stopped procs |
| `bgo restart-last` | Menu sorted most-recent-first |
| `bgo restart-last --all` | Restart all not-running procs in recent order |
| `bgo resurrect` | Restart all procs that were running before shutdown |
| `bgo delete <name>` | Remove proc + logs (alias: `rm`) |
| `bgo delete <name> --keep-logs` | Remove proc, keep logs |
| `bgo clean` | Drop all stopped procs from the list |
## Documentation

### Inspection
Long-form docs live in [`docs/`](docs/README.md):

| Command | Description |
| Topic | Where |
|---|---|
| `bgo status` | Process table (alias: `ls`, `list`) |
| `bgo status <name>` | Detail view for one proc |
| `bgo status -w` | Watch mode (auto-refresh every 2s) |
| `bgo status -w --interval N` | Custom refresh interval |
| `bgo status --json` | Machine-readable output |
| `bgo status --plain` | ASCII-only output (no color, no glyphs) |
| `bgo status --fancy` | Force Unicode box-drawing rendering |
| `bgo <registered-name>` | Shorthand for `bgo status <name>` |
| `bgo logs <name>` | Last 50 lines |
| `bgo logs <name> -f` | Follow logs |
| `bgo logs <name> -n 100` | Last 100 lines |
| `bgo logs <name> --stderr` | Only stderr |
| `bgo logs <name> --watcher` | Watcher event log |
| `bgo follow <name>` | Alias for `logs -f` (also: `tail`) |
| Install paths (uv / pipx / pip / install.sh / manual) | [docs/installation.md](docs/installation.md) |
| Full CLI reference | [docs/commands.md](docs/commands.md) |
| Watch mode (auto-restart + fast-crash policy) | [docs/watch-mode.md](docs/watch-mode.md) |
| Desktop notifications | [docs/notifications.md](docs/notifications.md) |
| Login autostart (systemd / launchd) | [docs/autostart.md](docs/autostart.md) |
| Tray icon (optional, PySide6) | [docs/tray.md](docs/tray.md) |
| Module architecture | [docs/architecture.md](docs/architecture.md) |
| FAQ | [docs/faq.md](docs/faq.md) |
| Contributing | [docs/contributing.md](docs/contributing.md) |

## Sample status table

### Watch mode

| Command | Description |
|---|---|
| `bgo start -w <name> -- <cmd>` | Start with watcher attached |
| `bgo -w <name> <cmd>` | Direct mode with watcher |
| `bgo watch <name>` | Attach watcher to a running proc |
| `bgo watch <name> --interval N --min-uptime N --on-fast-crash MODE` | Tune |
| `bgo unwatch <name>` | Detach watcher, keep proc |

## Examples

```bash
# Python HTTP server
bgo web -- python3 -m http.server 8080

# Node.js app with watcher
bgo -w api -- npm start

# Custom binary with working directory
bgo start dashboard --cwd /opt/app -- node server.js

# Inspect one proc
bgo web

# Scripted: stop every online proc via JSON
bgo status --json | python3 -c '
import json, sys, subprocess
for p in json.load(sys.stdin):
if p["status"] == "online":
subprocess.run(["bgo", "stop", p["name"]])
'
```

## Status Table

The table auto-detects terminal capabilities and picks the best rendering:

| Level | Trigger | What you get |
|---|---|---|
| `plain` | non-TTY (pipes, CI logs), `TERM=dumb`, or `--plain` | ASCII only, no color, no glyphs |
| `normal` | TTY without UTF-8 locale | ANSI color + ASCII rules |
| `fancy` | TTY + UTF-8 locale (default) | ANSI color + Unicode box-drawing |

Override with `--plain` / `--fancy` or `BGO_TABLE=plain|normal|fancy`.

Sample (fancy):
```
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ NAME STATUS PID CPU MEM UPTIME WATCH COMMAND ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ web ● online 12345 2.5 0.1 00:05 ✓ 0 python3 -m http.server 8080 ┃
┃ api ● online 12346 0.0 0.0 01:23 ✓ 3 node server.js ┃
┃ worker ○ stopped - - - - ⚠ errored python3 worker.py ┃
┃ batch ● online 12347 0.0 0.0 02:11 · ./batch ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Total: 4 | ● online: 3 | ○ stopped: 1
Total: 3 | ● online: 2 | ○ stopped: 1

⚠ 1 errored:
worker — 4 consecutive fast-crashes
bgo logs worker --watcher | bgo restart worker
```

CPU / MEM / uptime are pulled in a single batched `ps` call regardless of how many procs are running.

## Watch Mode

Watch mode runs a sidecar process per watched proc that polls the
target and restarts it on crash. State (restart count, error reason,
last stderr tail) is recorded in the proc JSON and surfaced via
`bgo status`.

### Quick start

```bash
bgo start -w myapi -- node server.js # start with watcher
bgo -w myapi node server.js # direct-mode variant
bgo watch myapi # attach to a running proc
bgo watch myapi --interval 5 --min-uptime 3 --on-fast-crash backoff
bgo unwatch myapi # detach, keep proc
bgo logs myapi --watcher # inspect events
```

### Fast-crash policy

If a process dies before `--min-uptime` (default 2s) it's a *fast crash*. Reaction depends on `--on-fast-crash`:

| Mode | Behavior |
|---|---|
| `backoff` (default) | Wait 2s, retry. Then 4s, then 8s. After 4 consecutive fast-crashes, mark `errored` and exit watcher. |
| `stop` | Mark `errored` on the first fast-crash. |
| `retry` | Retry indefinitely, capped at 8s backoff. |

When a proc enters `errored`:
- WATCH column shows `⚠ errored` (or `[!] errored` in plain).
- Status footer summarizes errored procs and hints at the recovery commands.
- `bgo status <name>` detail shows the error reason and last stderr tail.
- `bgo restart <name>` clears the errored flag and re-spawns the watcher. Restart counter is **preserved** by default — use `--reset-counters` to zero it.

### Tunables

| Flag | Default | Notes |
|---|---|---|
| `--interval N` | 3 | Poll interval after the initial uptime window |
| `--min-uptime N` | 2 | Crash threshold; sub-window polled at high frequency |
| `--on-fast-crash MODE` | `backoff` | One of `backoff`, `stop`, `retry` |
| `--reset` | off | `bgo watch` only — reset prior watch config to defaults |
Override the rendering with `--plain` / `--fancy` or
`BGO_TABLE=plain|normal|fancy`.

## Storage

- State: `~/.bgo/procs/<name>.json` — one file per process, written atomically (tmp + `os.replace`)
- Logs: `~/.bgo/logs/<name>.out.log`, `<name>.err.log`, `<name>.watcher.log`
- State: `~/.bgo/procs/<name>.json` — one file per process, atomic writes.
- Logs: `~/.bgo/logs/<name>.{out,err,watcher}.log`.

Full layout: [docs/architecture.md](docs/architecture.md#storage-layout).

## Testing

```bash
python3 -m pytest tests/ -v
uv run pytest tests/ -v
```

54 tests covering state I/O, atomic writes, command-shape detection, name derivation, liveness + zombie filtering, watch-config inheritance, and table rendering across all three levels.
169 tests covering state I/O, atomic writes, command-shape detection,
name derivation, liveness + zombie filtering, watch-config inheritance,
table rendering, desktop notifications, login autostart, and tray menu
construction.

## Requirements

- Python 3.9+
- Linux or macOS (zombie detection is platform-aware: `/proc` on Linux, `ps -o stat=` on macOS)
- Python 3.10+
- Linux or macOS
- Optional `[tray]` extra: PySide6 (Qt for Python, LGPL)

## License

MIT
[MIT](LICENSE).
Loading
Loading