feat: add CLI entry point, command modules, and zsh completion

- CLI with context detection, config merging, VM blocking
- Command modules: dotfiles, packages, setup, remote, dev, projects
- Zsh completion with declarative command/subcommand/flag structure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 05:06:31 +02:00
parent f79154d86f
commit 6ea23e02df
15 changed files with 466 additions and 1717 deletions

View File

@@ -1,49 +1,32 @@
"""Tests for CLI routing and command registration."""
"""Tests for CLI."""
import os
import subprocess
import sys
from unittest.mock import patch
import pytest
def _clean_env():
"""Return env dict without DF_* variables that trigger enter's guard."""
env = {k: v for k, v in os.environ.items() if not k.startswith("DF_")}
env["FLOW_SKIP_SUDO_REFRESH"] = "1"
return env
def test_version():
def test_version_flag():
"""Test --version flag works."""
result = subprocess.run(
[sys.executable, "-m", "flow", "--version"],
capture_output=True, text=True,
)
assert result.returncode == 0
assert "0.1.0" in result.stdout
assert "flow" in result.stdout
def test_help():
def test_help_flag():
"""Test --help shows commands."""
result = subprocess.run(
[sys.executable, "-m", "flow", "--help"],
capture_output=True, text=True,
)
assert result.returncode == 0
assert "enter" in result.stdout
assert "dev" in result.stdout
assert "dotfiles" in result.stdout
assert "bootstrap" in result.stdout
assert "package" in result.stdout
assert "sync" in result.stdout
assert "completion" in result.stdout
def test_enter_help():
result = subprocess.run(
[sys.executable, "-m", "flow", "enter", "--help"],
capture_output=True, text=True,
)
assert result.returncode == 0
assert "target" in result.stdout
assert "--dry-run" in result.stdout
assert "packages" in result.stdout
assert "setup" in result.stdout
def test_dotfiles_help():
@@ -52,136 +35,14 @@ def test_dotfiles_help():
capture_output=True, text=True,
)
assert result.returncode == 0
assert "init" in result.stdout
assert "link" in result.stdout
assert "unlink" in result.stdout
assert "undo" in result.stdout
assert "status" in result.stdout
assert "sync" in result.stdout
assert "repo" in result.stdout
def test_dotfiles_help_without_sudo_in_path():
env = _clean_env()
env["PATH"] = os.path.dirname(sys.executable)
def test_packages_help():
result = subprocess.run(
[sys.executable, "-m", "flow", "dotfiles", "--help"],
capture_output=True,
text=True,
env=env,
)
assert result.returncode == 0
assert "dotfiles" in result.stdout
def test_bootstrap_help():
result = subprocess.run(
[sys.executable, "-m", "flow", "bootstrap", "--help"],
capture_output=True, text=True,
)
assert result.returncode == 0
assert "run" in result.stdout
assert "list" in result.stdout
assert "show" in result.stdout
assert "packages" in result.stdout
def test_package_help():
result = subprocess.run(
[sys.executable, "-m", "flow", "package", "--help"],
[sys.executable, "-m", "flow", "packages", "--help"],
capture_output=True, text=True,
)
assert result.returncode == 0
assert "install" in result.stdout
assert "list" in result.stdout
assert "remove" in result.stdout
def test_sync_help():
result = subprocess.run(
[sys.executable, "-m", "flow", "sync", "--help"],
capture_output=True, text=True,
)
assert result.returncode == 0
assert "check" in result.stdout
assert "fetch" in result.stdout
assert "summary" in result.stdout
def test_dev_help():
result = subprocess.run(
[sys.executable, "-m", "flow", "dev", "--help"],
capture_output=True, text=True,
)
assert result.returncode == 0
assert "create" in result.stdout
assert "exec" in result.stdout
assert "connect" in result.stdout
assert "list" in result.stdout
assert "stop" in result.stdout
assert "remove" in result.stdout
assert "respawn" in result.stdout
def test_enter_dry_run():
result = subprocess.run(
[sys.executable, "-m", "flow", "enter", "--dry-run", "personal@orb"],
capture_output=True, text=True, env=_clean_env(),
)
assert result.returncode == 0
assert "ssh" in result.stdout
assert "personal.orb" in result.stdout
assert "tmux" in result.stdout
def test_enter_dry_run_no_tmux():
result = subprocess.run(
[sys.executable, "-m", "flow", "enter", "--dry-run", "--no-tmux", "personal@orb"],
capture_output=True, text=True, env=_clean_env(),
)
assert result.returncode == 0
assert "ssh" in result.stdout
assert "tmux" not in result.stdout
def test_enter_dry_run_with_user():
result = subprocess.run(
[sys.executable, "-m", "flow", "enter", "--dry-run", "root@personal@orb"],
capture_output=True, text=True, env=_clean_env(),
)
assert result.returncode == 0
assert "root@personal.orb" in result.stdout
def test_enter_dry_run_shows_terminfo_hint_for_ghostty():
env = _clean_env()
env["TERM"] = "xterm-ghostty"
result = subprocess.run(
[sys.executable, "-m", "flow", "enter", "--dry-run", "personal@orb"],
capture_output=True, text=True, env=env,
)
assert result.returncode == 0
assert "flow will not install or modify terminfo" in result.stdout
assert "infocmp -x xterm-ghostty | ssh" in result.stdout
def test_aliases():
"""Test that command aliases work."""
for alias, cmd in [("dot", "dotfiles"), ("pkg", "package"), ("setup", "bootstrap")]:
result = subprocess.run(
[sys.executable, "-m", "flow", alias, "--help"],
capture_output=True, text=True,
)
assert result.returncode == 0, f"Alias '{alias}' failed"
def test_dev_remove_alias():
result = subprocess.run(
[sys.executable, "-m", "flow", "dev", "rm", "--help"],
capture_output=True, text=True,
)
assert result.returncode == 0