# Snuppy — Design Document
> GitHub is your package registry.
---
## What is Snuppy?
Snuppy is a command-line package manager for GitHub repositories. It lets you clone, build, install, and update tools from source — treating GitHub like apt or pacman treats their repositories.
The name comes from **Snuppy**, the world's first cloned dog (an Afghan Hound, cloned in South Korea in 2005). Snuppy clones repos. The metaphor runs deep.
The problem it solves: many developer tools live on GitHub but aren't available on apt/dnf/pacman, or only have outdated versions there. Today, keeping those tools updated means manually pulling, rebuilding, and replacing binaries — a tedious process with no good tooling. Snuppy manages that entire lifecycle.
---
## Core Concept
Snuppy is architecturally similar to traditional package managers (apt, pacman, Homebrew), adapted for GitHub as the source of truth:
1. **Registry/Index** — A community-maintained GitHub repository of recipes (one `.toml` file per tool). Snuppy fetches and caches this index locally.
2. **Local state database** — Tracks what's installed, at what version/commit, and where binaries live. Stored at `~/.config/snuppy/state.toml`.
3. **Recipe files** — One `pkg.toml` per tool, describing how to build and install it. Either pulled from the community registry or written by the user.
---
## Architecture
```
~/.config/snuppy/
state.toml # global state: installed packages, versions, paths
registry/ # locally cached community recipes
fzf.toml
ripgrep.toml
...
~/.managed-repos/
fzf/ # cloned repo
.pkg.toml # recipe (symlinked or copied from registry)
ripgrep/
.pkg.toml
...
~/.local/bin/ # installed binaries (default)
fzf
rg
...
```
---
## Recipe Format (`pkg.toml`)
Each recipe is a self-contained TOML file describing a single tool. It is written once and rarely changes unless the upstream project changes its build system.
```toml
[package]
name = "fzf"
description = "A command-line fuzzy finder"
github = "junegunn/fzf"
homepage = "https://github.com/junegunn/fzf"
[source]
default_branch = "master"
# Optional: track a specific branch instead of default
# branch = "dev"
[build]
# Ordered list of shell commands to build from source
steps = [
"make"
]
# Path to the binary after build, relative to repo root
output = "bin/fzf"
[release]
# Optional: download pre-built binary from GitHub Releases instead of building
# If present, Snuppy can use this as an alternative to building from source
enabled = true
# Asset name pattern — {version} and {arch} are substituted automatically
asset = "fzf-{version}-linux_{arch}.tar.gz"
# Path inside the archive where the binary lives
binary = "fzf"
[install]
# Where to place the binary. Defaults to global config if omitted.
destination = "~/.local/bin"
[dependencies]
# Optional: system packages required before building
# Snuppy will warn if these are missing (does not install them)
system = ["make", "golang"]
```
---
## Recipe Lookup Order
When a user runs `snuppy install junegunn/fzf`, Snuppy looks for a recipe in this order:
1. **Community registry** — checks the locally cached index for a matching recipe
2. **User's local recipes** — checks `~/.config/snuppy/recipes/` for a user-authored recipe
3. **Auto-detection** — inspects the cloned repo for known build system files (`Cargo.toml`, `go.mod`, `Makefile`, `CMakeLists.txt`) and attempts to infer build steps, warning the user that this is a best-effort guess
4. **Failure** — prompts the user to write a recipe, with a template and documentation link
---
## Commands
### `snuppy install <owner/repo>`
Clones the repository to `~/.managed-repos/<repo>`, finds or creates a recipe, builds the tool, and places the binary in the install destination.
```
snuppy install junegunn/fzf
snuppy install junegunn/fzf --release # download pre-built binary from Releases
snuppy install junegunn/fzf --tag # build from latest semver tag instead of HEAD
snuppy install junegunn/fzf --pin v0.54.0 # lock to a specific tag or commit
snuppy install junegunn/fzf --branch dev # track a specific branch
```
### `snuppy update`
Updates all managed packages. Pulls latest changes (or latest tag/release depending on flags/config), rebuilds, and swaps the binary atomically (old binary kept until new one is confirmed working, then replaced).
```
snuppy update # update all packages
snuppy update fzf # update a specific package
snuppy update --dry-run # show what would be updated without doing it
```
### `snuppy remove <name>`
Removes the installed binary and optionally the cloned repo.
```
snuppy remove fzf
snuppy remove fzf --keep-repo # remove binary but keep the cloned source
```
### `snuppy list`
Lists all managed packages with their current installed version/commit, install path, and tracking mode.
```
snuppy list
snuppy list --outdated # show only packages with updates available
```
### `snuppy sync`
Fetches and updates the local cache of the community recipe registry.
```
snuppy sync
```
### `snuppy info <name>`
Shows details about a package: recipe contents, installed version, last updated, binary location.
```
snuppy info fzf
```
---
## Version Tracking Modes
Each package can be in one of these modes (set at install time, overridable in recipe or config):
| `head` (default) | Always pull and build latest commit on default branch |
| `tag` | Track and build from latest semver tag |
| `release` | Download pre-built binary from GitHub Releases |
| `pin` | Locked to a specific commit or tag, never auto-updated |
---
## Update Safety
When `snuppy update` rebuilds a tool:
1. The new binary is built to a temporary path
2. If the build succeeds, the old binary is moved to a `.bak` path
3. The new binary is atomically moved into place
4. If the build fails, the old binary is untouched and an error is reported
This means a failed update never leaves the user without a working tool.
---
## Community Registry
The community registry is a public GitHub repository (e.g. `snuppy/registry`) containing one `pkg.toml` per tool, organized as:
```
registry/
f/
fzf.toml
r/
ripgrep.toml
rclone.toml
z/
zoxide.toml
...
```
Users can contribute recipes via pull request. Snuppy caches the registry locally and `snuppy sync` refreshes it. The local cache path is `~/.config/snuppy/registry/`.
In the future, the registry could include a signed index file to verify recipe integrity.
---
## Global Configuration
Located at `~/.config/snuppy/config.toml`:
```toml
[defaults]
install_destination = "~/.local/bin" # where binaries are placed
repos_directory = "~/.managed-repos" # where repos are cloned
[registry]
url = "https://github.com/snuppy/registry"
auto_sync = true # sync registry on install if cache is stale
sync_interval_hours = 24
[build]
show_output = true # stream build output to terminal
parallelism = 4 # max parallel builds during update
```
---
## State Database
Located at `~/.config/snuppy/state.toml`. Tracks all managed packages:
```toml
[packages.fzf]
github = "junegunn/fzf"
tracking = "head"
installed_commit = "abc1234"
installed_at = "2026-04-01T10:00:00Z"
updated_at = "2026-05-01T12:30:00Z"
binary_path = "~/.local/bin/fzf"
repo_path = "~/.managed-repos/fzf"
pinned = false
```
---
## Implementation Language & Stack
- **Language:** Rust
- **CLI framework:** `clap` (argument parsing, subcommands, flags)
- **HTTP/GitHub API:** `octocrab` or `reqwest` + GitHub REST API (for fetching releases, tags, repo metadata)
- **Git operations:** shell out to system `git`, or use `git2` (libgit2 bindings)
- **TOML parsing:** `toml` crate
- **Build execution:** shell out via `std::process::Command`, streaming stdout/stderr to terminal
- **Async runtime:** `tokio` (for parallel updates and HTTP requests)
---
## Out of Scope (v1)
- Installing system dependencies automatically (Snuppy warns, not installs)
- Supporting non-GitHub sources (GitLab, Sourcehut, etc.) — architecture allows it later
- Windows support — Linux and macOS only for v1
- A GUI or TUI
- Sandboxing or security verification of build steps (trust model: you trust the recipe)
---
## Future Ideas (v2+)
- **`snuppy submit`** — CLI command to open a PR to the community registry with a new recipe
- **GitLab / Sourcehut support** — generalize beyond GitHub
- **Binary signing verification** — for `release` mode installs
- **`snuppy doctor`** — checks for broken installs, missing binaries, stale repos
- **Shell completions** — auto-generate for bash/zsh/fish
- **Rollback command** — `snuppy rollback fzf` to restore the `.bak` binary
---
## Summary
Snuppy is apt, but GitHub is the repository. Recipes describe how to build a tool, the community maintains a registry of those recipes, and Snuppy handles the entire lifecycle: clone → build → install → update. It is opinionated about simplicity, safe about updates, and extensible toward a community ecosystem.