Clean action runtime project state

This commit is contained in:
2026-05-14 13:58:45 +03:00
parent b05d3589b7
commit 4ce98d0ff1
27 changed files with 711 additions and 4772 deletions

246
README.md
View File

@@ -1,27 +1,58 @@
# flow
CLI for managing development environments: dotfiles, packages, dev containers, remote targets, and system bootstrap.
Action-centered CLI for managing a development machine: dotfiles, packages,
setup profiles, dev containers, remote targets, project git status, and shell
completion.
## Quick start
## Quick Start
From the repository:
```bash
make build && make install-local # installs to ~/.local/bin/flow
flow setup run linux-work # bootstrap a machine
flow dotfiles link # symlink dotfiles
flow dev create api -i tm0/node # spin up a dev container
flow dev attach api # attach via tmux
uv sync --locked --extra dev --extra build
uv run flow --help
uv run flow --version
```
## Commands
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
```
Install the CLI as a local uv tool:
```bash
uv tool install --force .
flow --help
```
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/` is the uv-managed local environment. `venv/` is legacy
local clutter and should not be used.
## 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] # pull -> $EDITOR -> commit+push
flow dotfiles edit PACKAGE [--no-commit]
# Dotfiles repos (unified: dotfiles repo + module repos)
# Dotfiles and module repos
flow dotfiles repos list
flow dotfiles repos status [--repo NAME]
flow dotfiles repos pull [--repo NAME] [--dry-run]
@@ -32,41 +63,43 @@ flow packages install [NAMES...] [--profile NAME] [--dry-run]
flow packages remove NAMES... [--dry-run]
flow packages list [--all]
# Bootstrap
flow setup run [PROFILE|--profile NAME] [--dry-run] [--var KEY=VALUE]
flow setup show PROFILE # preview profile steps
# Setup/bootstrap
flow setup list
flow setup show PROFILE
flow setup run [PROFILE|--profile NAME] [--dry-run] [--var KEY=VALUE]
# Remote targets
flow remote enter TARGET [--dry-run] # ssh + tmux into a remote target
flow remote list
flow remote enter TARGET [--user USER] [--namespace NAME] [--platform NAME] [--session NAME] [--no-tmux] [--dry-run]
flow enter TARGET
# Dev containers (docker or podman)
flow dev create NAME -i IMAGE [-p PROJECT] [--dry-run]
flow dev attach NAME # tmux session into container
flow dev exec NAME [-- CMD...] # run a command in container
flow dev enter NAME # interactive shell
# 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 [-f]
flow dev respawn NAME # restart all tmux panes
flow dev remove NAME [--force]
flow dev respawn NAME
flow dev list
# Projects
flow projects check [--fetch] # scan ~/projects for dirty repos
flow projects fetch # fetch all project remotes
flow projects summary # quick overview
flow projects check [--fetch]
flow projects fetch
flow projects summary
flow sync
# Shell completion
flow completion zsh # print zsh completion script
flow completion install-zsh # install to ~/.zsh/completions
flow completion zsh
flow completion install-zsh [--dir DIR] [--rc FILE] [--no-rc]
# Global flags
flow --version
flow --quiet # suppress info output
flow --no-color # disable colored output
flow --quiet
flow --no-color
```
### Aliases
Aliases:
- `dotfiles` -> `dot`
- `dotfiles repos` -> `dotfiles repo`
@@ -79,75 +112,40 @@ flow --no-color # disable colored output
## Configuration
Loaded from `~/.config/flow/config.yaml`, merged with dotfiles repo overlay at `~/.local/share/flow/dotfiles/_shared/flow/.config/flow/`.
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-runtime: auto # auto | docker | podman | podman-rootful
container-registry: registry.example.com
container-tag: latest
tmux-session: main
tmux-session: default
targets:
personal@orb: personal.orb
work@ec2:
host: work.ec2.internal
host: work.internal
identity: ~/.ssh/id_work
```
### Container runtime
`container-runtime` controls which engine `flow dev` uses:
- `auto` -- detect docker or podman from PATH (default)
- `docker` -- force docker
- `podman` -- force podman, prefer rootless socket
- `podman-rootful` -- force podman, prefer rootful socket (`/run/podman/podman.sock`)
When using podman, `flow dev create` automatically adds `--security-opt label=disable` for engine socket access. The socket is always mounted to `/var/run/docker.sock` inside the container (Podman's Docker-compatible API).
## Dotfiles layout
```
_shared/ # Linked for all profiles
zsh/
.zshrc
nvim/
.config/nvim/
_module.yaml # External git module
.local/bin/
nvim-wrapper
dns/
_root/ # Absolute path marker (needs sudo)
etc/hosts
linux-work/ # Profile-specific layer
i3/
.config/i3/config
```
### External modules
`_module.yaml` inside a package mounts an external git repo at that location:
```yaml
source: github:org/nvim-config
ref:
branch: main
```
Modules are regular git clones managed by flow (not git submodules). They appear alongside the dotfiles repo in `flow dotfiles repos list` and are pulled/pushed with the same commands.
## Manifest
Packages and profiles defined in YAML files under the flow config directory.
Packages and setup profiles are YAML manifest data loaded from the same config
directories:
```yaml
packages:
@@ -163,45 +161,95 @@ packages:
version: "0.10.4"
platform-map:
linux-x64: nvim-linux-x86_64.tar.gz
install:
bin: [bin/nvim]
profiles:
linux-work:
os: linux
hostname: dev-box
locale: en_US.UTF-8
shell: zsh
packages:
- fd
- binary/neovim
ssh-keys:
- path: ~/.ssh/id_ed25519
type: ed25519
runcmd:
- echo "setup complete"
- mkdir -p ~/projects
```
See `example/README.md` for a complete runnable dotfiles/setup fixture.
## Dotfiles Layout
```text
_shared/
zsh/
.zshrc
nvim/
.config/nvim/
_module.yaml
system/
_root/
etc/hostname
linux-work/
i3/
.config/i3/config
```
`_shared/` applies to every profile. Profile directories add or override package
sets. `_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 an action-centered runtime:
Flow uses a single execution boundary:
- **cli** parses Typer command arguments and calls app use-cases.
- **app** resolves config/state, builds `ActionPlan` objects for executor-managed work, and keeps only explicit interactive boundaries outside the executor.
- **domain** modules keep planning and resolution logic pure with frozen dataclasses.
- **actions** are the execution boundary: `DomainAction` records domain intent, expansion converts it to `PrimitiveAction`, and `ActionExecutor` handles dry-run rendering, audit logging, rollback, and dispatch.
- **adapters** provide runtime primitives through `SystemRuntime`: `CommandRunner`, `FileSystem`, `GitClient`, `TmuxClient`, `ContainerRuntime`, download, and archive adapters.
```text
cli -> app -> domain -> actions -> adapters
```
Action audit records are appended to `actions.jsonl` under the relevant flow state directory.
- `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.
## Security
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.
- Rejects root invocation
- `_root/` dotfile paths require sudo for linking
- Package post-install hooks run without sudo by default
Action audit records are append-only JSONL files under the relevant state
directory, normally `~/.local/state/flow/actions.jsonl`.
## Development
```bash
make deps # create .venv + install deps
.venv/bin/python -m pytest tests/ -v --ignore=tests/e2e
FLOW_RUN_E2E=1 .venv/bin/python -m pytest tests/e2e/ -v # requires docker or podman
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 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
uv build
uv run pyinstaller --noconfirm --clean --onefile --name flow --paths src src/flow/__main__.py
```