Tomas Mirchev 9fb08d035f
Some checks failed
test / unit (push) Has been cancelled
test / e2e (push) Has been cancelled
update
2026-05-18 04:05:46 +03:00
2026-05-14 16:19:15 +03:00
2026-05-14 16:19:15 +03:00
2026-05-18 03:14:15 +03:00
2026-05-14 16:19:15 +03:00
2026-05-18 04:05:46 +03:00
2026-05-18 04:05:46 +03:00
2026-05-14 13:58:45 +03:00
2026-05-18 03:14:15 +03:00
2026-05-18 03:14:15 +03:00
2026-05-14 13:14:38 +03:00

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):

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:

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):

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:

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:

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:

flow-<version>-python.tar.gz
flow-<version>-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:

PYTHON=/path/to/python3 FLOW_INSTALL_ROOT=~/.flow FLOW_BIN_DIR=~/bin ./install.sh

Release build:

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

# 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.

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:

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

_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:

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:

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

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:

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.

Description
No description provided
Readme 689 KiB
Languages
Python 98.5%
Makefile 0.7%
Shell 0.5%
Dockerfile 0.3%