Install Agent Skills into all 3 AI CLIs, and use Bun instead of npm

Adds scripts/install-skills.sh: pulls this repo's extensions/ skill
bundles (SKILL.md-format) at workspace startup and installs them into
Claude Code (~/.claude/skills), GitHub Copilot CLI (~/.copilot/skills),
and Antigravity CLI (~/.gemini/config/skills). Every env gets the common
awesome-skills-plugin bundle; COBOL/3D/TTRPG additionally get their
matching skill(s) from custom-specialty-plugin via a per-template
SPECIALTY_SKILLS value.

Each template also installs Bun via a coder_script and hooks ~/.bun/bin
onto PATH in .bashrc (the bun.sh installer doesn't reliably do this in a
scripted shell). The CLI setup wizard now uses `bun install -g` instead
of `npm install -g` for GitHub Copilot CLI and Claude Code CLI.

All six templates re-validated with terraform init/validate against the
real coder-server container on octo-winsrv.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 11:41:09 +02:00
parent 1beaac6e4f
commit f6c432d21d
9 changed files with 393 additions and 23 deletions
+41 -14
View File
@@ -17,8 +17,9 @@ templates/
web/main.tf # Web Applications web/main.tf # Web Applications
scripts/ scripts/
cli-setup-wizard.sh # shared first-run wizard, see below cli-setup-wizard.sh # shared first-run wizard, see below
install-skills.sh # shared Agent Skills installer, see below
profile-templates/ # VS Code .code-profile exports, one per env profile-templates/ # VS Code .code-profile exports, one per env
extensions/ # Claude Code plugin/skill bundles (see note below) extensions/ # Agent Skills bundles, installed per env (see below)
``` ```
Each `templates/<env>/main.tf` is a complete, independent Coder template Each `templates/<env>/main.tf` is a complete, independent Coder template
@@ -57,30 +58,56 @@ the actual `.code-profile` file extension, so settings never applied. That
bug (plus the single shared container) is why "one container, many envs" bug (plus the single shared container) is why "one container, many envs"
never really worked. never really worked.
### Bun
Every template installs [Bun](https://bun.sh) via a `coder_script`
(`curl -fsSL https://bun.sh/install | bash`) and hooks `~/.bun/bin` onto
`PATH` in `~/.bashrc` (the installer doesn't reliably do this itself in a
non-interactive/scripted shell). The CLI setup wizard below uses
`bun install -g <pkg>` instead of `npm install -g <pkg>` for everything it
installs.
### CLI setup wizard ### CLI setup wizard
`scripts/cli-setup-wizard.sh` is dropped onto every workspace and hooked `scripts/cli-setup-wizard.sh` is dropped onto every workspace and hooked
into `~/.bashrc`. It runs in every new interactive terminal - until the user into `~/.bashrc`. It runs in every new interactive terminal - until the user
finishes it - and offers to install + log into: finishes it - and offers to install + log into:
- **GitHub Copilot CLI** (`npm install -g @github/copilot`, then `copilot login`) - **GitHub Copilot CLI** (`bun install -g @github/copilot`, then `copilot login`)
- **Google Antigravity CLI** (`curl -fsSL https://antigravity.google/cli/install.sh | bash`, binary `agy`) - **Google Antigravity CLI** (`curl -fsSL https://antigravity.google/cli/install.sh | bash`, binary `agy`)
- **Claude Code CLI** (`npm install -g @anthropic-ai/claude-code`, then `claude`) - **Claude Code CLI** (`bun install -g @anthropic-ai/claude-code`, then `claude`)
It does **not** ask about VS Code extensions, since those are handled by It does **not** ask about VS Code extensions, since those are handled by
Terraform (see above). Once the user confirms completion it writes a Terraform (see above). Once the user confirms completion it writes a
sentinel file (`~/.cache/coder-cli-wizard/done`) and stops prompting. It can sentinel file (`~/.cache/coder-cli-wizard/done`) and stops prompting. It can
always be re-run manually: `bash /opt/coder/cli-setup-wizard.sh --force`. always be re-run manually: `bash /opt/coder/cli-setup-wizard.sh --force`.
### `extensions/` directory ### `extensions/` directory -> Agent Skills
Heads up: `extensions/awesome-skills-plugin` and `extensions/custom-specialty-plugin` `extensions/awesome-skills-plugin` and `extensions/custom-specialty-plugin`
are **Claude Code plugin/skill bundles** (`plugin.json` + `SKILL.md` files), are **Agent Skills bundles** (`plugin.json` + `SKILL.md` files) - the old
not actual VS Code extension packages. The old root `main.tf` copied this root `main.tf` copied this folder straight into code-server's VS Code
folder straight into code-server's extensions directory, which would never extensions directory, which never worked since these aren't VS Code
have worked as VS Code extensions regardless of the container bug above. extension packages.
This rewrite doesn't wire `extensions/` into anything - it's left as-is
pending a decision on whether/how to install these as Claude Code plugins Each template now runs `scripts/install-skills.sh` (via a `coder_script`,
inside each workspace (e.g. `custom-specialty-plugin/skills/cobol-teacher` pulling a fresh zip of this repo from Gitea rather than embedding ~2.5MB
for the COBOL env, `openscad-parametric` for 3D, `ttrpg-lore-weaver` / into Terraform state) to install skills into all three AI CLIs' personal
`foundryvtt-modding` for TTRPG). skills directories:
| CLI | Skills directory |
| --- | --- |
| Claude Code | `~/.claude/skills/<name>/` |
| GitHub Copilot CLI | `~/.copilot/skills/<name>/` |
| Antigravity CLI | `~/.gemini/config/skills/<name>/` (per antigravity.google/docs/skills - some third-party docs disagree on this path, worth a spot-check on a live workspace) |
Every environment gets the full `extensions/awesome-skills-plugin/skills/*`
bundle. On top of that, whichever env has a matching entry in
`extensions/custom-specialty-plugin/skills/` gets it installed too, set via
the `SPECIALTY_SKILLS` env var passed to the script from each template's
`install_skills` `coder_script`:
- COBOL -> `cobol-teacher`
- 3D Printing -> `openscad-parametric`
- TTRPG -> `foundryvtt-modding` and `ttrpg-lore-weaver`
- Default / Python / Web -> none (no matching specialty skill exists yet)
+10 -9
View File
@@ -19,7 +19,8 @@ WIZARD_DONE_FILE="${HOME}/.cache/coder-cli-wizard/done"
FORCE=0 FORCE=0
[ "${1:-}" = "--force" ] && FORCE=1 [ "${1:-}" = "--force" ] && FORCE=1
export PATH="${HOME}/.local/bin:${PATH}" export BUN_INSTALL="${HOME}/.bun"
export PATH="${BUN_INSTALL}/bin:${HOME}/.local/bin:${PATH}"
# Only bother interactive shells with a real terminal attached, and only until # Only bother interactive shells with a real terminal attached, and only until
# the user marks the wizard as done. # the user marks the wizard as done.
@@ -53,16 +54,16 @@ echo "==================================================================="
# --- GitHub Copilot CLI --- # --- GitHub Copilot CLI ---
if command -v copilot >/dev/null 2>&1; then if command -v copilot >/dev/null 2>&1; then
echo "GitHub Copilot CLI already installed, skipping." echo "GitHub Copilot CLI already installed, skipping."
elif command -v npm >/dev/null 2>&1; then elif command -v bun >/dev/null 2>&1; then
if ask_yes_no "Install GitHub Copilot CLI and log in?"; then if ask_yes_no "Install GitHub Copilot CLI and log in?"; then
if npm install -g @github/copilot; then if bun install -g @github/copilot; then
copilot login || echo "Install succeeded but login didn't complete. Retry any time with: copilot login" copilot login || echo "Install succeeded but login didn't complete. Retry any time with: copilot login"
else else
echo "Copilot CLI install failed. Retry later with: npm install -g @github/copilot && copilot login" echo "Copilot CLI install failed. Retry later with: bun install -g @github/copilot && copilot login"
fi fi
fi fi
else else
echo "Skipping GitHub Copilot CLI: npm not found on this workspace image." echo "Skipping GitHub Copilot CLI: bun not found on this workspace image."
fi fi
# --- Google Antigravity CLI (agy) --- # --- Google Antigravity CLI (agy) ---
@@ -82,17 +83,17 @@ fi
# --- Claude Code CLI --- # --- Claude Code CLI ---
if command -v claude >/dev/null 2>&1; then if command -v claude >/dev/null 2>&1; then
echo "Claude Code CLI already installed, skipping." echo "Claude Code CLI already installed, skipping."
elif command -v npm >/dev/null 2>&1; then elif command -v bun >/dev/null 2>&1; then
if ask_yes_no "Install Claude Code CLI and log in?"; then if ask_yes_no "Install Claude Code CLI and log in?"; then
if npm install -g @anthropic-ai/claude-code; then if bun install -g @anthropic-ai/claude-code; then
echo "Launching 'claude' once to complete sign-in (use /login if not prompted; Ctrl+C to exit when done)..." echo "Launching 'claude' once to complete sign-in (use /login if not prompted; Ctrl+C to exit when done)..."
claude || echo "Sign-in didn't complete. Retry any time by running: claude" claude || echo "Sign-in didn't complete. Retry any time by running: claude"
else else
echo "Claude Code CLI install failed. Retry later with: npm install -g @anthropic-ai/claude-code" echo "Claude Code CLI install failed. Retry later with: bun install -g @anthropic-ai/claude-code"
fi fi
fi fi
else else
echo "Skipping Claude Code CLI: npm not found on this workspace image." echo "Skipping Claude Code CLI: bun not found on this workspace image."
fi fi
echo "" echo ""
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# Installs this repo's Agent Skills (extensions/{awesome-skills-plugin,
# custom-specialty-plugin}/skills/*, each a SKILL.md-based skill directory)
# into every AI CLI's personal skills directory:
#
# Claude Code CLI -> ~/.claude/skills/<name>/
# GitHub Copilot CLI -> ~/.copilot/skills/<name>/
# Antigravity CLI -> ~/.gemini/config/skills/<name>/ (per antigravity.google/docs/skills;
# worth a spot-check if agy doesn't pick these up, some third-party
# docs disagree on the exact path)
#
# Run once at workspace startup via coder_script. Pulls this repo fresh from
# Gitea rather than embedding ~2.5MB of skill files into Terraform state.
#
# Env vars:
# SPECIALTY_SKILLS - optional space-separated skill names from
# extensions/custom-specialty-plugin/skills/ to install in addition to
# the common awesome-skills-plugin bundle (every env gets that one).
set -e
REPO_ZIP_URL="https://git.octoturge.com/octoturge/Profiles-for-Coder/archive/main.zip"
ZIP_PATH="/tmp/coder-skills-src.zip"
WORK_DIR="/tmp/coder-skills-src"
rm -rf "$WORK_DIR" "$ZIP_PATH"
curl -fsSL "$REPO_ZIP_URL" -o "$ZIP_PATH"
mkdir -p "$WORK_DIR"
unzip -q -o "$ZIP_PATH" -d "$WORK_DIR"
INNER_DIR=$(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -type d | head -n1)
if [ -z "$INNER_DIR" ]; then
echo "install-skills: couldn't find extracted repo contents, skipping." >&2
rm -rf "$WORK_DIR" "$ZIP_PATH"
exit 0
fi
TARGET_DIRS=("$HOME/.claude/skills" "$HOME/.copilot/skills" "$HOME/.gemini/config/skills")
for dir in "${TARGET_DIRS[@]}"; do
mkdir -p "$dir"
done
# Common skill bundle, installed for every environment.
COMMON_SKILLS_SRC="$INNER_DIR/extensions/awesome-skills-plugin/skills"
if [ -d "$COMMON_SKILLS_SRC" ]; then
for dir in "${TARGET_DIRS[@]}"; do
cp -r "$COMMON_SKILLS_SRC/." "$dir/"
done
echo "install-skills: installed common skill bundle into ${TARGET_DIRS[*]}"
else
echo "install-skills: common skill bundle not found at $COMMON_SKILLS_SRC, skipping." >&2
fi
# Environment-specific specialty skills, if any were requested.
for skill in ${SPECIALTY_SKILLS:-}; do
SRC="$INNER_DIR/extensions/custom-specialty-plugin/skills/$skill"
if [ -d "$SRC" ]; then
for dir in "${TARGET_DIRS[@]}"; do
cp -r "$SRC" "$dir/$skill"
done
echo "install-skills: installed specialty skill '$skill'"
else
echo "install-skills: specialty skill '$skill' not found at $SRC, skipping." >&2
fi
done
rm -rf "$WORK_DIR" "$ZIP_PATH"
echo "install-skills: done."
+45
View File
@@ -267,3 +267,48 @@ resource "coder_script" "cli_setup_wizard" {
fi fi
EOT EOT
} }
# Installs Bun and uses it (instead of npm) for the CLI installs the wizard
# script runs. The installer doesn't reliably add ~/.bun/bin to PATH in
# non-interactive shells, so that's hooked into .bashrc explicitly here.
resource "coder_script" "install_bun" {
agent_id = coder_agent.main.id
display_name = "Install Bun"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
export BUN_INSTALL="$HOME/.bun"
if [ ! -x "$BUN_INSTALL/bin/bun" ]; then
curl -fsSL https://bun.sh/install | bash
fi
MARKER="# >>> coder bun path >>>"
if ! grep -qF "$MARKER" "$HOME/.bashrc" 2>/dev/null; then
{
echo ""
echo "$MARKER"
echo 'export BUN_INSTALL="$HOME/.bun"'
echo 'export PATH="$BUN_INSTALL/bin:$PATH"'
echo "# <<< coder bun path <<<"
} >> "$HOME/.bashrc"
fi
EOT
}
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
# Antigravity CLI's skills directories, plus the openscad-parametric skill
# from extensions/custom-specialty-plugin. See ../../scripts/install-skills.sh.
resource "coder_script" "install_skills" {
agent_id = coder_agent.main.id
display_name = "Install Agent Skills"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
mkdir -p /opt/coder
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="openscad-parametric" /opt/coder/install-skills.sh
EOT
}
+45
View File
@@ -267,3 +267,48 @@ resource "coder_script" "cli_setup_wizard" {
fi fi
EOT EOT
} }
# Installs Bun and uses it (instead of npm) for the CLI installs the wizard
# script runs. The installer doesn't reliably add ~/.bun/bin to PATH in
# non-interactive shells, so that's hooked into .bashrc explicitly here.
resource "coder_script" "install_bun" {
agent_id = coder_agent.main.id
display_name = "Install Bun"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
export BUN_INSTALL="$HOME/.bun"
if [ ! -x "$BUN_INSTALL/bin/bun" ]; then
curl -fsSL https://bun.sh/install | bash
fi
MARKER="# >>> coder bun path >>>"
if ! grep -qF "$MARKER" "$HOME/.bashrc" 2>/dev/null; then
{
echo ""
echo "$MARKER"
echo 'export BUN_INSTALL="$HOME/.bun"'
echo 'export PATH="$BUN_INSTALL/bin:$PATH"'
echo "# <<< coder bun path <<<"
} >> "$HOME/.bashrc"
fi
EOT
}
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
# Antigravity CLI's skills directories, plus the cobol-teacher skill from
# extensions/custom-specialty-plugin. See ../../scripts/install-skills.sh.
resource "coder_script" "install_skills" {
agent_id = coder_agent.main.id
display_name = "Install Agent Skills"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
mkdir -p /opt/coder
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="cobol-teacher" /opt/coder/install-skills.sh
EOT
}
+46
View File
@@ -267,3 +267,49 @@ resource "coder_script" "cli_setup_wizard" {
fi fi
EOT EOT
} }
# Installs Bun and uses it (instead of npm) for the CLI installs the wizard
# script runs. The installer doesn't reliably add ~/.bun/bin to PATH in
# non-interactive shells, so that's hooked into .bashrc explicitly here.
resource "coder_script" "install_bun" {
agent_id = coder_agent.main.id
display_name = "Install Bun"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
export BUN_INSTALL="$HOME/.bun"
if [ ! -x "$BUN_INSTALL/bin/bun" ]; then
curl -fsSL https://bun.sh/install | bash
fi
MARKER="# >>> coder bun path >>>"
if ! grep -qF "$MARKER" "$HOME/.bashrc" 2>/dev/null; then
{
echo ""
echo "$MARKER"
echo 'export BUN_INSTALL="$HOME/.bun"'
echo 'export PATH="$BUN_INSTALL/bin:$PATH"'
echo "# <<< coder bun path <<<"
} >> "$HOME/.bashrc"
fi
EOT
}
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
# Antigravity CLI's skills directories. See ../../scripts/install-skills.sh.
# Default has no matching entry in extensions/custom-specialty-plugin/skills,
# so it only gets the common awesome-skills-plugin bundle.
resource "coder_script" "install_skills" {
agent_id = coder_agent.main.id
display_name = "Install Agent Skills"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
mkdir -p /opt/coder
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="" /opt/coder/install-skills.sh
EOT
}
+46
View File
@@ -267,3 +267,49 @@ resource "coder_script" "cli_setup_wizard" {
fi fi
EOT EOT
} }
# Installs Bun and uses it (instead of npm) for the CLI installs the wizard
# script runs. The installer doesn't reliably add ~/.bun/bin to PATH in
# non-interactive shells, so that's hooked into .bashrc explicitly here.
resource "coder_script" "install_bun" {
agent_id = coder_agent.main.id
display_name = "Install Bun"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
export BUN_INSTALL="$HOME/.bun"
if [ ! -x "$BUN_INSTALL/bin/bun" ]; then
curl -fsSL https://bun.sh/install | bash
fi
MARKER="# >>> coder bun path >>>"
if ! grep -qF "$MARKER" "$HOME/.bashrc" 2>/dev/null; then
{
echo ""
echo "$MARKER"
echo 'export BUN_INSTALL="$HOME/.bun"'
echo 'export PATH="$BUN_INSTALL/bin:$PATH"'
echo "# <<< coder bun path <<<"
} >> "$HOME/.bashrc"
fi
EOT
}
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
# Antigravity CLI's skills directories. See ../../scripts/install-skills.sh.
# Python has no matching entry in extensions/custom-specialty-plugin/skills,
# so it only gets the common awesome-skills-plugin bundle.
resource "coder_script" "install_skills" {
agent_id = coder_agent.main.id
display_name = "Install Agent Skills"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
mkdir -p /opt/coder
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="" /opt/coder/install-skills.sh
EOT
}
+46
View File
@@ -267,3 +267,49 @@ resource "coder_script" "cli_setup_wizard" {
fi fi
EOT EOT
} }
# Installs Bun and uses it (instead of npm) for the CLI installs the wizard
# script runs. The installer doesn't reliably add ~/.bun/bin to PATH in
# non-interactive shells, so that's hooked into .bashrc explicitly here.
resource "coder_script" "install_bun" {
agent_id = coder_agent.main.id
display_name = "Install Bun"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
export BUN_INSTALL="$HOME/.bun"
if [ ! -x "$BUN_INSTALL/bin/bun" ]; then
curl -fsSL https://bun.sh/install | bash
fi
MARKER="# >>> coder bun path >>>"
if ! grep -qF "$MARKER" "$HOME/.bashrc" 2>/dev/null; then
{
echo ""
echo "$MARKER"
echo 'export BUN_INSTALL="$HOME/.bun"'
echo 'export PATH="$BUN_INSTALL/bin:$PATH"'
echo "# <<< coder bun path <<<"
} >> "$HOME/.bashrc"
fi
EOT
}
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
# Antigravity CLI's skills directories, plus the foundryvtt-modding and
# ttrpg-lore-weaver skills from extensions/custom-specialty-plugin.
# See ../../scripts/install-skills.sh.
resource "coder_script" "install_skills" {
agent_id = coder_agent.main.id
display_name = "Install Agent Skills"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
mkdir -p /opt/coder
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="foundryvtt-modding ttrpg-lore-weaver" /opt/coder/install-skills.sh
EOT
}
+46
View File
@@ -267,3 +267,49 @@ resource "coder_script" "cli_setup_wizard" {
fi fi
EOT EOT
} }
# Installs Bun and uses it (instead of npm) for the CLI installs the wizard
# script runs. The installer doesn't reliably add ~/.bun/bin to PATH in
# non-interactive shells, so that's hooked into .bashrc explicitly here.
resource "coder_script" "install_bun" {
agent_id = coder_agent.main.id
display_name = "Install Bun"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
export BUN_INSTALL="$HOME/.bun"
if [ ! -x "$BUN_INSTALL/bin/bun" ]; then
curl -fsSL https://bun.sh/install | bash
fi
MARKER="# >>> coder bun path >>>"
if ! grep -qF "$MARKER" "$HOME/.bashrc" 2>/dev/null; then
{
echo ""
echo "$MARKER"
echo 'export BUN_INSTALL="$HOME/.bun"'
echo 'export PATH="$BUN_INSTALL/bin:$PATH"'
echo "# <<< coder bun path <<<"
} >> "$HOME/.bashrc"
fi
EOT
}
# Installs this repo's Agent Skills into Claude Code, GitHub Copilot CLI, and
# Antigravity CLI's skills directories. See ../../scripts/install-skills.sh.
# Web has no matching entry in extensions/custom-specialty-plugin/skills,
# so it only gets the common awesome-skills-plugin bundle.
resource "coder_script" "install_skills" {
agent_id = coder_agent.main.id
display_name = "Install Agent Skills"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -e
mkdir -p /opt/coder
echo '${base64encode(file("${path.module}/../../scripts/install-skills.sh"))}' | base64 -d > /opt/coder/install-skills.sh
chmod +x /opt/coder/install-skills.sh
SPECIALTY_SKILLS="" /opt/coder/install-skills.sh
EOT
}