# flow Action-centered CLI for managing a development machine: dotfiles, packages, setup profiles, dev containers, remote targets, project git status, and shell completion. ## Quick Start Install from a release tarball (no `uv`, no `pipx`): ```bash VERSION=0.1.0 REPO=OWNER/REPO curl -fsSL "https://github.com/${REPO}/releases/download/v${VERSION}/flow-${VERSION}-python.tar.gz" \ -o "/tmp/flow-${VERSION}-python.tar.gz" curl -fsSL "https://github.com/${REPO}/releases/download/v${VERSION}/flow-${VERSION}-python.tar.gz.sha256" \ -o "/tmp/flow-${VERSION}-python.tar.gz.sha256" sha256sum -c "/tmp/flow-${VERSION}-python.tar.gz.sha256" tar -xzf "/tmp/flow-${VERSION}-python.tar.gz" -C /tmp /tmp/flow-${VERSION}-python/install.sh ``` Alternative one-shot path with `curl` and shell: ```bash tmpdir="$(mktemp -d)" trap 'rm -rf "$tmpdir"' EXIT curl -fsSL "https://github.com/${REPO}/releases/download/v${VERSION}/flow-${VERSION}-python.tar.gz" \ | tar -xz -C "$tmpdir" sh "${tmpdir}/flow-${VERSION}-python/install.sh" ``` From the repository (development): ```bash uv sync --locked --extra dev --extra build uv run flow --help uv run flow --version ``` Initialize a dotfiles repo, link a profile, and preview setup: ```bash flow dotfiles init --repo git@github.com:you/dotfiles.git flow dotfiles link --profile linux-work --dry-run flow setup show linux-work flow setup run linux-work --dry-run ``` Build a standalone binary: ```bash make build ./dist/flow --help make install # installs dist/flow to ~/.local/bin/flow ``` `build/`, `dist/`, and `flow.spec` are generated by packaging/binary builds and are ignored. `.venv/` and `venv/` are local virtual environments and are also ignored. ## Distribution The primary release artifact is a Python tarball published from GitHub Releases: ```text flow--python.tar.gz flow--python.tar.gz.sha256 ``` The tarball includes `install.sh` plus the `flow` wheel. End users need `python3` with `venv`; they do not need `uv` or `pipx`. The installer uses `pip` inside the isolated venv to install `flow` and resolve Python dependencies. Default install locations: - app environment: `~/.local/share/flow/venv` - command shim: `~/.local/bin/flow` Overrides: ```bash PYTHON=/path/to/python3 FLOW_INSTALL_ROOT=~/.flow FLOW_BIN_DIR=~/bin ./install.sh ``` Release build: ```bash make release-package ``` Native one-file binaries remain optional convenience artifacts. They are platform-specific; the Python release tarball is the portable default. ## Command Surface ```bash # Dotfiles flow dotfiles init [--repo URL] flow dotfiles link [--profile NAME] [--dry-run] [--skip PKG...] flow dotfiles unlink [PACKAGES...] [--dry-run] flow dotfiles status [PACKAGES...] flow dotfiles edit PACKAGE [--no-commit] # Dotfiles and module repos flow dotfiles repos list flow dotfiles repos status [--repo NAME] flow dotfiles repos pull [--repo NAME] [--dry-run] flow dotfiles repos push [--repo NAME] [--dry-run] # Packages flow packages install [NAMES...] [--profile NAME] [--dry-run] flow packages remove NAMES... [--dry-run] flow packages list [--all] # Setup/bootstrap flow setup list flow setup show PROFILE flow setup run [PROFILE|--profile NAME] [--dry-run] [--var KEY=VALUE] # Remote targets flow remote list flow remote enter TARGET [--user USER] [--namespace NAME] [--platform NAME] [--session NAME] [--no-tmux] [--dry-run] # Dev containers flow dev create NAME --image IMAGE [--project PATH] [--dry-run] flow dev attach NAME flow dev exec NAME [CMD...] flow dev enter NAME flow dev stop NAME [--kill] flow dev remove NAME [--force] flow dev respawn NAME flow dev list # Projects flow projects check [--fetch] flow projects fetch flow projects summary flow projects sync # Shell completion flow completion zsh flow completion install-zsh [--dir DIR] [--rc FILE] [--no-rc] # Global flags flow --version flow --quiet flow --no-color ``` Aliases: - `dotfiles` -> `dot` - `dotfiles repos` -> `dotfiles repo` - `packages` -> `package`, `pkg` - `projects` -> `project` - `setup` -> `bootstrap`, `provision` - `dev attach` -> `dev connect` - `dev remove` -> `dev rm` ## Configuration Flow reads XDG paths: - config: `~/.config/flow` - data: `~/.local/share/flow` - state: `~/.local/state/flow` The main config lives at `~/.config/flow/config.yaml`. A dotfiles repo may also provide an overlay at `_shared/flow/.config/flow/`; after `dotfiles init`, that overlay is merged into config and manifest loading. ```yaml repository: url: git@github.com:you/dotfiles.git branch: main pull-before-edit: true paths: projects: ~/projects defaults: container-runtime: auto # auto | docker | podman | podman-rootful container-registry: registry.example.com container-tag: latest tmux-session: default targets: personal@orb: personal.orb work@ec2: host: work.internal identity: ~/.ssh/id_work ``` Packages and setup profiles are YAML manifest data loaded from the same config directories: ```yaml packages: - name: fd type: pkg sources: apt: fd-find brew: fd - name: neovim type: binary source: github:neovim/neovim version: "0.10.4" platform-map: linux-x64: nvim-linux-x86_64.tar.gz install: bin: [bin/nvim] profiles: linux-work: os: linux shell: zsh packages: - fd - binary/neovim runcmd: - mkdir -p ~/projects ``` See `example/README.md` for a complete runnable dotfiles/setup fixture. ## Dotfiles Layout ```text _shared/ _root/ etc/hostname zsh/ .zshrc nvim/ .config/nvim/ _module.yaml linux-work/ i3/ .config/i3/config ``` `_shared/` applies to every profile. Profile directories add or override package sets. A layer-level `_root/` directory, such as `_shared/_root/` or `linux-work/_root/`, marks absolute paths and plans sudo-backed link actions. External module repos are declared with `_module.yaml`: ```yaml source: github:org/nvim-config ref: branch: main ``` Modules are regular git clones managed by flow, not git submodules. The dotfiles repo and all module repos are operated through `flow dotfiles repos`. ## Architecture Flow uses a single execution boundary: ```text cli -> app -> domain -> actions -> adapters ``` - `src/flow/cli.py`: Typer command definitions and argument parsing only. - `src/flow/app/`: use-cases that load config/state and build action plans. - `src/flow/domain/`: pure dataclasses, parsers, resolvers, and planners. - `src/flow/actions/`: `ActionPlan`, domain-to-primitive expansion, execution, dry-run rendering, JSONL audit logs, and rollback. - `src/flow/adapters/`: filesystem, process, git, package-manager, download, archive, container, and tmux adapters. Use-cases do not directly mutate files or run shell commands for non-interactive work. They produce actions and pass them to `ActionExecutor`. Interactive handoffs such as attaching to tmux remain explicit boundaries. Action audit records are append-only JSONL files under the relevant state directory, normally `~/.local/state/flow/actions.jsonl`. ## Development ```bash make deps # uv sync --locked --extra build --extra dev make test # unit tests, excluding e2e make test-e2e # requires Docker or healthy Podman make check # tests plus CLI smoke checks make package # wheel and sdist in dist/ make release-package # installable GitHub Release tarball make build # PyInstaller binary in dist/flow make clean # remove build/test artifacts make distclean # also remove .venv/ and venv/ ``` Direct commands are equivalent: ```bash uv run pytest tests/ -q --ignore=tests/e2e FLOW_RUN_E2E=1 uv run pytest tests/e2e/ -v FLOW_RUN_E2E=1 uv run pytest tests/e2e/test_dotfiles_e2e.py::test_cli_paths_run_in_disposable_container -q FLOW_RUN_E2E=1 uv run pytest tests/e2e/test_dotfiles_e2e.py::test_dotfiles_init_and_link_in_container -q uv build uv run pyinstaller --noconfirm --clean --onefile --name flow --paths src src/flow/__main__.py ``` For local container-only command validation, run the e2e module directly with `FLOW_RUN_E2E=1`. That executes all commands inside a disposable container instance with isolated `HOME`/`XDG_*` and a read-only example repo mount, so it does not touch host dotfiles or global state.