Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"dockerDashComposeVersion": "v2"
},
"ghcr.io/dhoeric/features/hadolint:1": {},
"ghcr.io/guiyomh/features/just:0": {}
"ghcr.io/guiyomh/features/just:0": {},
"ghcr.io/gilmanlab/features/agents:1": {}
},
"postCreateCommand": ".devcontainer/post-create.sh",
"customizations": {
Expand Down
90 changes: 90 additions & 0 deletions .github/workflows/features.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Features

on:
pull_request:
paths:
- 'images/features/**'
- '.github/workflows/features.yml'
push:
branches:
- master
paths:
- 'images/features/**'
- '.github/workflows/features.yml'
tags:
- 'features/*/v*'

env:
REGISTRY: ghcr.io

jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Validate devcontainer-feature.json
run: |
for feature_dir in images/features/src/*/; do
if [ -f "${feature_dir}devcontainer-feature.json" ]; then
echo "Validating ${feature_dir}devcontainer-feature.json"
jq empty "${feature_dir}devcontainer-feature.json"
fi
done

- name: Lint install.sh scripts
run: |
find images/features/src -name "*.sh" -type f -exec shellcheck {} +

test:
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
feature: [agents]
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'

- name: Install devcontainer CLI
run: npm install -g @devcontainers/cli

- name: Test feature
run: |
devcontainer features test \
--features ${{ matrix.feature }} \
--base-image mcr.microsoft.com/devcontainers/base:ubuntu \
--project-folder ./images/features

publish:
needs: [lint, test]
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/features/'))
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Log in to Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Publish Features
uses: devcontainers/action@v1
with:
publish-features: true
base-path-to-features: ./images/features/src
generate-docs: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
84 changes: 84 additions & 0 deletions images/features/src/agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# AI Coding Agents (agents)

Installs AI coding agent CLIs: Claude Code, Gemini CLI, and Codex CLI.

## Usage

Add this feature to your `devcontainer.json`:

```json
{
"features": {
"ghcr.io/gilmanlab/features/agents:1": {}
}
}
```

**Note:** This feature requires Node.js/npm. Add the Node.js feature if not already present:

```json
{
"features": {
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/gilmanlab/features/agents:1": {}
}
}
```

## Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `claudeCode` | boolean | `true` | Install Claude Code CLI |
| `claudeCodeVersion` | string | `latest` | Version of Claude Code |
| `geminiCli` | boolean | `true` | Install Gemini CLI |
| `geminiCliVersion` | string | `latest` | Version of Gemini CLI |
| `codexCli` | boolean | `true` | Install Codex CLI |
| `codexCliVersion` | string | `latest` | Version of Codex CLI |

## Examples

### Install all agents (default)

```json
{
"features": {
"ghcr.io/gilmanlab/features/agents:1": {}
}
}
```

### Install only Claude Code

```json
{
"features": {
"ghcr.io/gilmanlab/features/agents:1": {
"geminiCli": false,
"codexCli": false
}
}
}
```

### Install specific versions

```json
{
"features": {
"ghcr.io/gilmanlab/features/agents:1": {
"claudeCodeVersion": "2.0.76",
"geminiCliVersion": "0.22.5",
"codexCliVersion": "0.77.0"
}
}
}
```

## Installed CLIs

| CLI | Package | Binary |
|-----|---------|--------|
| Claude Code | `@anthropic-ai/claude-code` | `claude` |
| Gemini CLI | `@google/gemini-cli` | `gemini` |
| Codex CLI | `@openai/codex` | `codex` |
52 changes: 52 additions & 0 deletions images/features/src/agents/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"id": "agents",
"version": "1.0.0",
"name": "AI Coding Agents",
"description": "Installs AI coding agent CLIs: Claude Code, Gemini CLI, and Codex CLI",
"documentationURL": "https://github.com/GilmanLab/headjack/tree/master/images/features/agents",
"keywords": [
"ai",
"agents",
"claude",
"gemini",
"codex",
"anthropic",
"google",
"openai"
],
"options": {
"claudeCode": {
"type": "boolean",
"default": true,
"description": "Install Claude Code CLI (@anthropic-ai/claude-code)"
},
"claudeCodeVersion": {
"type": "string",
"default": "latest",
"description": "Version of Claude Code to install"
},
"geminiCli": {
"type": "boolean",
"default": true,
"description": "Install Gemini CLI (@google/gemini-cli)"
},
"geminiCliVersion": {
"type": "string",
"default": "latest",
"description": "Version of Gemini CLI to install"
},
"codexCli": {
"type": "boolean",
"default": true,
"description": "Install Codex CLI (@openai/codex)"
},
"codexCliVersion": {
"type": "string",
"default": "latest",
"description": "Version of Codex CLI to install"
}
},
"installsAfter": [
"ghcr.io/devcontainers/features/node"
]
}
121 changes: 121 additions & 0 deletions images/features/src/agents/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/bin/bash
# AI Coding Agents Feature - install.sh
# Installs Claude Code, Gemini CLI, and Codex CLI via npm
set -e

# Feature options (devcontainer converts option names to uppercase)
CLAUDE_CODE="${CLAUDECODE:-true}"
CLAUDE_CODE_VERSION="${CLAUDECODEVERSION:-latest}"
GEMINI_CLI="${GEMINICLI:-true}"
GEMINI_CLI_VERSION="${GEMINICLIVERSION:-latest}"
CODEX_CLI="${CODEXCLI:-true}"
CODEX_CLI_VERSION="${CODEXCLIVERSION:-latest}"

echo "Installing AI Coding Agent CLIs..."

# Install Node.js if npm is not available
if ! command -v npm > /dev/null 2>&1; then
echo "Node.js not found. Installing..."

# Detect package manager and install Node.js
if command -v apt-get > /dev/null 2>&1; then
apt-get update
apt-get install -y curl ca-certificates gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" > /etc/apt/sources.list.d/nodesource.list
apt-get update
apt-get install -y nodejs
rm -rf /var/lib/apt/lists/*
elif command -v apk > /dev/null 2>&1; then
apk add --no-cache nodejs npm
elif command -v dnf > /dev/null 2>&1; then
dnf install -y nodejs npm
elif command -v yum > /dev/null 2>&1; then
curl -fsSL https://rpm.nodesource.com/setup_22.x | bash -
yum install -y nodejs
else
echo "Error: Could not install Node.js - unsupported package manager"
echo "Please add the Node.js feature manually:"
echo ' "ghcr.io/devcontainers/features/node:1": {}'
exit 1
fi

echo "Node.js installed successfully"
fi

echo "Using npm version: $(npm --version)"
echo "Using Node.js version: $(node --version)"

# Build list of packages to install
PACKAGES=""

if [ "${CLAUDE_CODE}" = "true" ]; then
if [ "${CLAUDE_CODE_VERSION}" = "latest" ]; then
PACKAGES="${PACKAGES} @anthropic-ai/claude-code"
else
PACKAGES="${PACKAGES} @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"
fi
echo "Will install Claude Code: ${CLAUDE_CODE_VERSION}"
fi

if [ "${GEMINI_CLI}" = "true" ]; then
if [ "${GEMINI_CLI_VERSION}" = "latest" ]; then
PACKAGES="${PACKAGES} @google/gemini-cli"
else
PACKAGES="${PACKAGES} @google/gemini-cli@${GEMINI_CLI_VERSION}"
fi
echo "Will install Gemini CLI: ${GEMINI_CLI_VERSION}"
fi

if [ "${CODEX_CLI}" = "true" ]; then
if [ "${CODEX_CLI_VERSION}" = "latest" ]; then
PACKAGES="${PACKAGES} @openai/codex"
else
PACKAGES="${PACKAGES} @openai/codex@${CODEX_CLI_VERSION}"
fi
echo "Will install Codex CLI: ${CODEX_CLI_VERSION}"
fi

# Install packages if any are enabled
if [ -n "${PACKAGES}" ]; then
echo "Installing:${PACKAGES}"
# shellcheck disable=SC2086
npm install -g ${PACKAGES}
echo "Installation complete."
else
echo "No agents selected for installation."
fi

# Verify installations
echo ""
echo "Verification:"
if [ "${CLAUDE_CODE}" = "true" ]; then
if command -v claude > /dev/null 2>&1; then
echo " Claude Code: installed"
else
echo " Claude Code: FAILED"
exit 1
fi
fi

if [ "${GEMINI_CLI}" = "true" ]; then
if command -v gemini > /dev/null 2>&1; then
echo " Gemini CLI: installed"
else
echo " Gemini CLI: FAILED"
exit 1
fi
fi

if [ "${CODEX_CLI}" = "true" ]; then
if command -v codex > /dev/null 2>&1; then
echo " Codex CLI: installed"
else
echo " Codex CLI: FAILED"
exit 1
fi
fi

echo ""
echo "AI Coding Agent CLIs installation finished."
19 changes: 19 additions & 0 deletions images/features/test/agents/scenarios.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"test-default": {
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/node:1": {},
"agents": {}
}
},
"test-claude-only": {
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/node:1": {},
"agents": {
"geminiCli": false,
"codexCli": false
}
}
}
}
Loading