197 lines
5.7 KiB
Python
197 lines
5.7 KiB
Python
"""Install commands."""
|
|
|
|
import shlex
|
|
from pathlib import Path
|
|
|
|
import typer
|
|
|
|
from cli_tools.helpers import append_bashrc_section, run
|
|
|
|
app = typer.Typer(
|
|
help="Install tools and configure the environment.", no_args_is_help=True
|
|
)
|
|
|
|
APT_PACKAGES = [
|
|
"git",
|
|
"curl",
|
|
"micro",
|
|
"mc",
|
|
"detox",
|
|
"tree",
|
|
"ripgrep",
|
|
"fd-find",
|
|
"btop",
|
|
]
|
|
|
|
|
|
@app.command()
|
|
def claude():
|
|
"""Install Claude Code via the official install script."""
|
|
run("curl -fsSL https://claude.ai/install.sh | bash")
|
|
|
|
|
|
@app.command()
|
|
def copilot():
|
|
"""Install GitHub Copilot via the official install script."""
|
|
run("curl -fsSL https://gh.io/copilot-install | bash")
|
|
|
|
|
|
@app.command("apt-packages")
|
|
def apt_packages():
|
|
"""Update apt cache and install base packages."""
|
|
run("sudo apt-get update")
|
|
run(f"sudo apt-get install -y {' '.join(APT_PACKAGES)}")
|
|
|
|
|
|
@app.command()
|
|
def docker():
|
|
"""Install Docker and add current user to docker group."""
|
|
import getpass
|
|
|
|
run(
|
|
"command -v docker >/dev/null 2>&1 || curl -fsSL https://get.docker.com | sudo sh"
|
|
)
|
|
run(f"sudo usermod -aG docker {getpass.getuser()}")
|
|
|
|
|
|
@app.command()
|
|
def uv():
|
|
"""Install uv for the current user."""
|
|
run("test -f ~/.local/bin/uv || curl -LsSf https://astral.sh/uv/install.sh | sh")
|
|
|
|
|
|
@app.command()
|
|
def ccusage():
|
|
"""Install ccusage for monitoring code complexity."""
|
|
run("npm install -g ccusage")
|
|
|
|
@app.command()
|
|
def fzf():
|
|
"""Install fzf from git and bat for preview."""
|
|
run("sudo apt-get remove -y fzf 2>/dev/null || true")
|
|
run("sudo apt-get install -y bat")
|
|
run(
|
|
"test -d ~/.fzf || git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf"
|
|
)
|
|
run("~/.fzf/install --all")
|
|
|
|
|
|
@app.command()
|
|
def zoxide():
|
|
"""Install zoxide and configure .bashrc."""
|
|
run("sudo apt install -y zoxide")
|
|
append_bashrc_section("zoxide", 'eval "$(zoxide init bash)"')
|
|
|
|
|
|
@app.command()
|
|
def helpers():
|
|
"""Symlink bash_helpers.sh and aliases.sh into home directory."""
|
|
repo = Path(__file__).resolve().parents[2]
|
|
bash_helpers = repo / "scripts" / "bash_helpers.sh"
|
|
aliases = repo / "scripts" / "aliases.sh"
|
|
|
|
if not bash_helpers.is_file() or not aliases.is_file():
|
|
typer.secho(
|
|
"This command must be run from the checked-out cli-tools repo "
|
|
"(for example with `uv run cli-tools install helpers`).",
|
|
fg=typer.colors.RED,
|
|
err=True,
|
|
)
|
|
raise typer.Exit(code=1)
|
|
|
|
run(f"ln -sf {shlex.quote(str(bash_helpers))} ~/.bash_helpers.sh")
|
|
run(f"ln -sf {shlex.quote(str(aliases))} ~/.aliases.sh")
|
|
append_bashrc_section("bash_helpers", "source ~/.bash_helpers.sh")
|
|
|
|
|
|
@app.command()
|
|
def lazygit():
|
|
"""Install lazygit (terminal UI for git)."""
|
|
run(
|
|
"command -v lazygit >/dev/null 2>&1 || ("
|
|
"LAZYGIT_VERSION=$(curl -s https://api.github.com/repos/jesseduffield/lazygit/releases/latest"
|
|
" | grep '\"tag_name\"' | cut -d'\"' -f4 | sed 's/v//') && "
|
|
"curl -Lo /tmp/lazygit.tar.gz"
|
|
" https://github.com/jesseduffield/lazygit/releases/latest/download/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz && "
|
|
"tar -xf /tmp/lazygit.tar.gz -C /tmp lazygit && "
|
|
"sudo install /tmp/lazygit /usr/local/bin)"
|
|
)
|
|
|
|
|
|
@app.command()
|
|
def eza():
|
|
"""Install eza (modern ls replacement) via official deb repo."""
|
|
run("sudo mkdir -p /etc/apt/keyrings")
|
|
run(
|
|
"wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc"
|
|
" | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg"
|
|
)
|
|
run(
|
|
"echo 'deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main'"
|
|
" | sudo tee /etc/apt/sources.list.d/gierens.list"
|
|
)
|
|
run(
|
|
"sudo chmod 644 /etc/apt/keyrings/gierens.gpg /etc/apt/sources.list.d/gierens.list"
|
|
)
|
|
run("sudo apt update && sudo apt install -y eza")
|
|
|
|
|
|
@app.command("ai-skills")
|
|
def ai_skills():
|
|
"""Symlink Claude Code config, skills, agents, and Copilot skills globally."""
|
|
repo = Path(__file__).resolve().parents[2]
|
|
claude_src = repo / "ai-tools" / "claude"
|
|
skills_src = repo / "ai-tools" / "skills"
|
|
agents_src = repo / "ai-tools" / "agents"
|
|
|
|
claude_home = Path.home() / ".claude"
|
|
claude_skills = Path.home() / ".claude" / "skills"
|
|
claude_agents = Path.home() / ".claude" / "agents"
|
|
github_skills = Path.home() / ".github" / "skills"
|
|
|
|
claude_home.mkdir(parents=True, exist_ok=True)
|
|
for src in sorted(claude_src.iterdir()):
|
|
dest = claude_home / src.name
|
|
run(f"ln -sfn {shlex.quote(str(src))} {shlex.quote(str(dest))}")
|
|
|
|
claude_skills.mkdir(parents=True, exist_ok=True)
|
|
for skill_dir in sorted(skills_src.iterdir()):
|
|
if skill_dir.is_dir() and (skill_dir / "SKILL.md").exists():
|
|
dest = claude_skills / skill_dir.name
|
|
run(f"ln -sfn {shlex.quote(str(skill_dir))} {shlex.quote(str(dest))}")
|
|
|
|
claude_agents.mkdir(parents=True, exist_ok=True)
|
|
for agent_file in sorted(agents_src.glob("*.md")):
|
|
dest = claude_agents / agent_file.name
|
|
run(f"ln -sf {shlex.quote(str(agent_file))} {shlex.quote(str(dest))}")
|
|
|
|
github_skills.mkdir(parents=True, exist_ok=True)
|
|
for skill_dir in sorted(claude_skills.iterdir()):
|
|
if skill_dir.is_dir():
|
|
dest = github_skills / skill_dir.name
|
|
run(f"ln -sfn {shlex.quote(str(skill_dir))} {shlex.quote(str(dest))}")
|
|
|
|
|
|
|
|
|
|
|
|
@app.command()
|
|
def core():
|
|
"""Install essential tools."""
|
|
apt_packages()
|
|
docker()
|
|
uv()
|
|
fzf()
|
|
zoxide()
|
|
lazygit()
|
|
eza()
|
|
helpers()
|
|
|
|
@app.command()
|
|
def ai_tools():
|
|
"""Install AI tools and configure skills."""
|
|
claude()
|
|
copilot()
|
|
ai_skills()
|
|
ccusage()
|