Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,7 @@ repomix-output.xml
.claude/
.opencode/
release-manifest.json

# VIM files
.*.sw?
.*.un~
1 change: 1 addition & 0 deletions assets/oca_contributor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ uv = [
"odoo-addons-path"
]
system_packages = ["postgresql"]
skills = ["https://github.com/trobz/public-skills", "git@github.com:trobz/skills.git"]
17 changes: 17 additions & 0 deletions bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,28 @@ install_trobz_local() {
echo "trobz_local installed (CLI: tlc)"
}

# Install vercel-labs/skills (Claude Code skills)
install_vercel_skills() {
if ! command -v nvm &>/dev/null; then
echo "Installing nvm..."
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
fi
if ! command -v npx &>/dev/null; then
echo "Installing node and related commands..."
. ~/.nvm/nvm.sh && nvm install --lts
fi
echo "Installing vercel-labs/skills..."
npx -y skills > /dev/null
echo "vercel-labs/skills installed"
}

# Main execution
install_git
install_gh
install_uv
setup_github_ssh
install_trobz_local
install_vercel_skills

echo ""
echo "=== Bootstrap complete ==="
Expand All @@ -128,6 +144,7 @@ echo " ✓ git"
echo " ✓ gh (GitHub CLI)"
echo " ✓ uv"
echo " ✓ trobz_local (tlc command available)"
echo " ✓ nvm, node, and vercel-labs/skills (AI agent skills)"
echo ""
echo "Next steps:"
echo ""
Expand Down
1 change: 1 addition & 0 deletions trobz_local/assets/oca_contributor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ uv = [
"odoo-addons-path"
]
system_packages = ["postgresql"]
skills = ["https://github.com/trobz/public-skills", "git@github.com:trobz/skills.git"]
96 changes: 96 additions & 0 deletions trobz_local/installers.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,102 @@ def install_npm_packages(packages: list[str], dry_run: bool = False) -> list:
return run_tasks(tasks)


def _install_skill(progress: Progress, task_id: TaskID, skill: str, npx_path: str):
progress.update(task_id, description=f"Installing {skill}...", total=100, completed=0)

try:
subprocess.run( # noqa: S603
[npx_path, "-y", "skills", "add", "-g", "--all", skill],
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as e:
progress.update(task_id, description=f"[red]✗ Failed to install {skill}")
raise PackageInstallError(skill, e.stderr) from e

progress.update(task_id, description=f"✓ {skill} installed.", completed=100)


def install_agent_skills(skills: list[str], dry_run: bool = False) -> list:
if not skills:
return []

npx_path = shutil.which("npx")
if not npx_path:
typer.secho(
"Error: npx is not installed. Please install Node.js first.",
fg=typer.colors.RED,
)
return [TaskResult(name="npx-check", success=False, message="npx is not installed")]

if dry_run:
typer.echo("\n[Skills - would be installed via npx skills install]")
for skill in skills:
typer.echo(f" - {skill}")
return []

typer.secho("\n--- Installing Skills ---", fg=typer.colors.BLUE, bold=True)

tasks = []
for skill in skills:
tasks.append({
"name": skill,
"func": _install_skill,
"args": {"skill": skill, "npx_path": npx_path},
})

return run_tasks(tasks)


def _update_skill(progress: Progress, task_id: TaskID, skill: str, npx_path: str):
progress.update(task_id, description=f"Updating {skill}...", total=100, completed=0)

try:
subprocess.run( # noqa: S603
[npx_path, "-y", "skills", "update", skill],
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as e:
progress.update(task_id, description=f"[red]✗ Failed to update {skill}")
raise PackageInstallError(skill, e.stderr) from e

progress.update(task_id, description=f"✓ {skill} updated.", completed=100)


def update_agent_skills(skills: list[str], dry_run: bool = False) -> list:
if not skills:
return []

npx_path = shutil.which("npx")
if not npx_path:
typer.secho(
"Error: npx is not installed. Please install Node.js first.",
fg=typer.colors.RED,
)
return [TaskResult(name="npx-check", success=False, message="npx is not installed")]

if dry_run:
typer.echo("\n[Skills - would be updated via npx skills update]")
for skill in skills:
typer.echo(f" - {skill}")
return []

typer.secho("\n--- Updating Skills ---", fg=typer.colors.BLUE, bold=True)

tasks = []
for skill in skills:
tasks.append({
"name": skill,
"func": _update_skill,
"args": {"skill": skill, "npx_path": npx_path},
})

return run_tasks(tasks)


def _install_uv_tool(progress: Progress, task_id: TaskID, tool: str, uv_path: str):
progress.update(task_id, description=f"Installing {tool}...", total=100, completed=0)

Expand Down
52 changes: 52 additions & 0 deletions trobz_local/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
from .concurrency import TaskResult, run_tasks
from .doctor import CheckStatus, run_doctor
from .installers import (
install_agent_skills,
install_npm_packages,
install_scripts,
install_system_packages,
install_uv_tools,
setup_postgresql_repo,
update_agent_skills,
)
from .postgres import (
check_postgres_running,
Expand Down Expand Up @@ -443,6 +445,56 @@ def install_tools(
typer.secho("\n✓ All tools installed successfully.", fg=typer.colors.GREEN)


@app.command()
def install_skills(
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be installed without executing."),
):
"""Install AI agent skills specified in the skills field of config."""
config = get_config()
skills = config.get("tools", {}).get("skills", [])

if not skills:
code_root = get_code_root()
typer.echo(f"No skills found in config. Add skills to [tools] section in {code_root}/config.toml")
return

results = install_agent_skills(skills, dry_run)

if not dry_run:
failed = [r for r in results if not r.success]
if failed:
typer.secho("\n--- Some skill(s) installations failed ---", fg=typer.colors.RED)
for r in failed:
typer.secho(f"✗ {r.name}: {r.message}", fg=typer.colors.RED)
raise typer.Exit(code=1)
typer.secho("\n✓ All skills installed successfully.", fg=typer.colors.GREEN)


@app.command()
def update_skills(
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be updated without executing."),
):
"""Update AI agent skills specified in the skills field of config."""
config = get_config()
skills = config.get("tools", {}).get("skills", [])

if not skills:
code_root = get_code_root()
typer.echo(f"No skills found in config. Add skills to [tools] section in {code_root}/config.toml")
return

results = update_agent_skills(skills, dry_run)

if not dry_run:
failed = [r for r in results if not r.success]
if failed:
typer.secho("\n--- Some skill(s) updates failed ---", fg=typer.colors.RED)
for r in failed:
typer.secho(f"✗ {r.name}: {r.message}", fg=typer.colors.RED)
raise typer.Exit(code=1)
typer.secho("\n✓ All skills updated successfully.", fg=typer.colors.GREEN)


@app.command()
def create_venvs(ctx: typer.Context):
"""
Expand Down
1 change: 1 addition & 0 deletions trobz_local/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class ToolsConfig(BaseModel):
npm: list[str] = Field(default_factory=list)
script: list[ScriptItem] = Field(default_factory=list)
system_packages: list[str] = Field(default_factory=list)
skills: list[str] = Field(default_factory=list)

@field_validator("uv")
@classmethod
Expand Down
Loading