diff --git a/src/ai-claude/NOTES.md b/src/ai-claude/NOTES.md new file mode 100644 index 0000000..e46e1c5 --- /dev/null +++ b/src/ai-claude/NOTES.md @@ -0,0 +1,71 @@ +## Claude Code AI Feature + +This feature installs [Anthropic's Claude Code](https://code.claude.com/docs/en/vs-code) CLI and VS Code extension with support for DuploCloud AI skills. + +### What's Included + +- **Claude Code CLI** - `@anthropic-ai/claude-code` npm package installed globally +- **VS Code Extension** - [Claude Code extension](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code) automatically installed +- **Skills Support** - Automatic installation of skills from [duplocloud/ai-ops](https://github.com/duplocloud/ai-ops) + +### Usage in devcontainer.json + +```json +{ + "features": { + "ghcr.io/duplocloud/devcontainers/ai-claude:1": { + "skills": "tf-module,api-design" + } + } +} +``` + +### Options + +- `skills` - Comma-separated list of skills to install (default: "") + +### Skills Installation + +Skills are installed to `~/.claude/skills/` during container creation. You can specify skills in two ways: + +1. **Feature option**: Set the `skills` option in your devcontainer.json +2. **Environment variable**: Set `DUPLO_AI_SKILLS` in your devcontainer.json + +Both sources are merged, so you can have a common set via environment variable and feature-specific additions via the option. + +### Example: Installing Multiple Skills + +```json +{ + "containerEnv": { + "DUPLO_AI_SKILLS": "tf-module,api-design" + }, + "features": { + "ghcr.io/duplocloud/devcontainers/ai-claude:1": { + "skills": "code-review" + } + } +} +``` + +This configuration installs all three skills: `tf-module`, `api-design`, and `code-review`. + +### Skill Location + +Claude Code discovers skills from multiple locations: +- **User skills**: `~/.claude/skills/` (installed by this feature) +- **Project skills**: `.claude/skills/` (commit to version control) +- **Plugin skills**: Provided by extensions + +For more details on how Claude Code uses skills, see the [Skills documentation](https://code.claude.com/docs/en/skills). + +### Available Skills + +Skills are published in the [duplocloud/ai-ops releases](https://github.com/duplocloud/ai-ops/releases). + +## References + +- [Claude Code Documentation](https://code.claude.com/docs/en/vs-code) +- [Claude Code VS Code Extension](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code) +- [Claude Code Skills Guide](https://code.claude.com/docs/en/skills) +- [Agent Skills Standard](https://agentskills.io/) diff --git a/src/ai-claude/devcontainer-feature.json b/src/ai-claude/devcontainer-feature.json new file mode 100644 index 0000000..7c11229 --- /dev/null +++ b/src/ai-claude/devcontainer-feature.json @@ -0,0 +1,23 @@ +{ + "id": "ai-claude", + "version": "1.0.0", + "name": "Claude Code AI", + "description": "Anthropic's Claude Code CLI with VS Code integration and skills support", + "options": { + "skills": { + "type": "string", + "default": "", + "description": "Comma-separated list of skills to install (e.g., 'tf-module,api-design')" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "anthropic.claude-code" + ] + } + }, + "dependsOn": { + "ghcr.io/duplocloud/devcontainers/ai:latest": {} + } +} diff --git a/src/ai-claude/install.sh b/src/ai-claude/install.sh new file mode 100644 index 0000000..572dca8 --- /dev/null +++ b/src/ai-claude/install.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -e + +echo "Installing Claude Code AI..." + +# Install Claude Code CLI globally +echo "Installing @anthropic-ai/claude-code..." +npm install -g @anthropic-ai/claude-code + +# Verify installation +if command -v claude-code &> /dev/null; then + echo "✓ Claude Code CLI installed successfully" + claude-code --version || true +else + echo "⚠ Claude Code CLI installation could not be verified" +fi + +# Copy on-create script to shared location +FEATURE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cp "${FEATURE_DIR}/on-create.sh" /usr/local/share/ai-claude-on-create.sh +chmod +x /usr/local/share/ai-claude-on-create.sh + +# Save feature options to config file for use during onCreate lifecycle hook +mkdir -p /usr/local/etc +cat < /usr/local/etc/ai-claude-feature.conf +SKILLS="${SKILLS:-}" +EOF +chmod 644 /usr/local/etc/ai-claude-feature.conf + +echo "Claude Code AI installed successfully!" +echo "" +echo "Note: Skills will be installed during container creation (on-create hook)" diff --git a/src/ai-claude/on-create.sh b/src/ai-claude/on-create.sh new file mode 100644 index 0000000..1e1a994 --- /dev/null +++ b/src/ai-claude/on-create.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -e + +echo "Installing Claude Code skills..." + +# Use devcontainer environment variables with fallbacks +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" +USER_NAME="${_REMOTE_USER:-$(whoami)}" + +# Load feature configuration +if [ -f /usr/local/etc/ai-claude-feature.conf ]; then + source /usr/local/etc/ai-claude-feature.conf +fi + +# Skills directory for Claude Code (user scope) +SKILLS_DIR="${USER_HOME}/.claude/skills" +mkdir -p "${USER_HOME}/.claude" "${SKILLS_DIR}" + +# Get skills from option +FEATURE_SKILLS="${SKILLS:-}" + +# Get skills from environment variable +ENV_SKILLS="${DUPLO_AI_SKILLS:-}" + +# Merge skills from both sources +ALL_SKILLS="" +if [ -n "${FEATURE_SKILLS}" ]; then + ALL_SKILLS="${FEATURE_SKILLS}" +fi +if [ -n "${ENV_SKILLS}" ]; then + if [ -n "${ALL_SKILLS}" ]; then + ALL_SKILLS="${ALL_SKILLS},${ENV_SKILLS}" + else + ALL_SKILLS="${ENV_SKILLS}" + fi +fi + +# If no skills specified, exit early +if [ -z "${ALL_SKILLS}" ]; then + echo "No skills specified. Skipping skill installation." + echo "To install skills, set 'skills' option or DUPLO_AI_SKILLS environment variable." + exit 0 +fi + +# Convert comma-separated list to array +IFS=',' read -ra SKILL_ARRAY <<< "${ALL_SKILLS}" + +# Install each skill +INSTALLED_COUNT=0 +FAILED_COUNT=0 + +for SKILL in "${SKILL_ARRAY[@]}"; do + # Trim whitespace + SKILL="$(echo "${SKILL}" | xargs)" + + if [ -z "${SKILL}" ]; then + continue + fi + + echo "" + echo "Installing skill: ${SKILL}" + + if duplo-skills --dir "${SKILLS_DIR}" --skill "${SKILL}"; then + ((INSTALLED_COUNT++)) + else + echo "⚠ Failed to install skill: ${SKILL}" + ((FAILED_COUNT++)) + fi +done + +echo "" +echo "========================================" +echo "Skills installation complete" +echo "========================================" +echo "Installed: ${INSTALLED_COUNT}" +echo "Failed: ${FAILED_COUNT}" +echo "Location: ${SKILLS_DIR}" +echo "" + +if [ ${INSTALLED_COUNT} -gt 0 ]; then + echo "✓ Claude Code skills are ready to use" +fi diff --git a/src/ai-codex/NOTES.md b/src/ai-codex/NOTES.md new file mode 100644 index 0000000..2ec788f --- /dev/null +++ b/src/ai-codex/NOTES.md @@ -0,0 +1,85 @@ +## OpenAI Codex AI Feature + +This feature installs [OpenAI's Codex CLI](https://developers.openai.com/codex/skills/) with VS Code integration and support for DuploCloud AI skills. + +### What's Included + +- **Codex CLI** - `@openai/codex` npm package installed globally +- **VS Code Extension** - [ChatGPT extension](https://marketplace.visualstudio.com/items?itemName=openai.chatgpt) automatically installed +- **Skills Support** - Automatic installation of skills from [duplocloud/ai-ops](https://github.com/duplocloud/ai-ops) + +### Usage in devcontainer.json + +```json +{ + "features": { + "ghcr.io/duplocloud/devcontainers/ai-codex:1": { + "skills": "tf-module,api-design" + } + } +} +``` + +### Options + +- `skills` - Comma-separated list of skills to install (default: "") + +### Skills Installation + +Skills are installed to `$CODEX_HOME/skills/` (defaults to `~/.codex/skills/`) during container creation. You can specify skills in two ways: + +1. **Feature option**: Set the `skills` option in your devcontainer.json +2. **Environment variable**: Set `DUPLO_AI_SKILLS` in your devcontainer.json + +Both sources are merged, so you can have a common set via environment variable and feature-specific additions via the option. + +### Example: Installing Multiple Skills + +```json +{ + "containerEnv": { + "DUPLO_AI_SKILLS": "tf-module,api-design" + }, + "features": { + "ghcr.io/duplocloud/devcontainers/ai-codex:1": { + "skills": "code-review" + } + } +} +``` + +This configuration installs all three skills: `tf-module`, `api-design`, and `code-review`. + +### Skill Location + +Codex discovers skills from multiple scopes (in order of precedence): +- **Repo (CWD)**: `$CWD/.codex/skills` - Current working directory +- **Repo (Parent)**: `$CWD/../.codex/skills` - Parent folders +- **Repo (Root)**: `$REPO_ROOT/.codex/skills` - Git repository root +- **User**: `$CODEX_HOME/skills` (installed by this feature) +- **Admin**: `/etc/codex/skills` - System-wide +- **System**: Bundled with Codex + +For more details on how Codex uses skills, see the [Agent Skills documentation](https://developers.openai.com/codex/skills/). + +### Managing Skills + +Skills can be enabled/disabled in `~/.codex/config.toml`: + +```toml +[[skills.config]] +path = "/path/to/skill" +enabled = false +``` + +### Available Skills + +Skills are published in the [duplocloud/ai-ops releases](https://github.com/duplocloud/ai-ops/releases). + +## References + +- [Codex Overview](https://developers.openai.com/codex) +- [ChatGPT VS Code Extension](https://marketplace.visualstudio.com/items?itemName=openai.chatgpt) +- [Codex Skills Guide](https://developers.openai.com/codex/skills/) +- [Codex IDE Integration](https://developers.openai.com/codex/ide/) +- [Agent Skills Standard](https://agentskills.io/) diff --git a/src/ai-codex/devcontainer-feature.json b/src/ai-codex/devcontainer-feature.json new file mode 100644 index 0000000..c49d318 --- /dev/null +++ b/src/ai-codex/devcontainer-feature.json @@ -0,0 +1,23 @@ +{ + "id": "ai-codex", + "version": "1.0.0", + "name": "OpenAI Codex AI", + "description": "OpenAI's Codex CLI with VS Code integration and skills support", + "options": { + "skills": { + "type": "string", + "default": "", + "description": "Comma-separated list of skills to install (e.g., 'tf-module,api-design')" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "openai.chatgpt" + ] + } + }, + "dependsOn": { + "ghcr.io/duplocloud/devcontainers/ai:latest": {} + } +} diff --git a/src/ai-codex/install.sh b/src/ai-codex/install.sh new file mode 100644 index 0000000..ba9d1b1 --- /dev/null +++ b/src/ai-codex/install.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -e + +echo "Installing OpenAI Codex AI..." + +# Install Codex CLI globally +echo "Installing @openai/codex..." +npm install -g @openai/codex + +# Verify installation +if command -v codex &> /dev/null; then + echo "✓ Codex CLI installed successfully" + codex --version || true +else + echo "⚠ Codex CLI installation could not be verified" +fi + +# Copy on-create script to shared location +FEATURE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cp "${FEATURE_DIR}/on-create.sh" /usr/local/share/ai-codex-on-create.sh +chmod +x /usr/local/share/ai-codex-on-create.sh + +# Save feature options to config file for use during onCreate lifecycle hook +mkdir -p /usr/local/etc +cat < /usr/local/etc/ai-codex-feature.conf +SKILLS="${SKILLS:-}" +EOF +chmod 644 /usr/local/etc/ai-codex-feature.conf + +echo "OpenAI Codex AI installed successfully!" +echo "" +echo "Note: Skills will be installed during container creation (on-create hook)" diff --git a/src/ai-codex/on-create.sh b/src/ai-codex/on-create.sh new file mode 100644 index 0000000..a51d5dd --- /dev/null +++ b/src/ai-codex/on-create.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -e + +echo "Installing Codex skills..." + +# Use devcontainer environment variables with fallbacks +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" +USER_NAME="${_REMOTE_USER:-$(whoami)}" + +# Load feature configuration +if [ -f /usr/local/etc/ai-codex-feature.conf ]; then + source /usr/local/etc/ai-codex-feature.conf +fi + +# Skills directory for Codex (user scope) +# Default CODEX_HOME to ~/.codex if not set +CODEX_HOME="${CODEX_HOME:-${USER_HOME}/.codex}" +SKILLS_DIR="${CODEX_HOME}/skills" +mkdir -p "${CODEX_HOME}" "${SKILLS_DIR}" + +# Get skills from option +FEATURE_SKILLS="${SKILLS:-}" + +# Get skills from environment variable +ENV_SKILLS="${DUPLO_AI_SKILLS:-}" + +# Merge skills from both sources +ALL_SKILLS="" +if [ -n "${FEATURE_SKILLS}" ]; then + ALL_SKILLS="${FEATURE_SKILLS}" +fi +if [ -n "${ENV_SKILLS}" ]; then + if [ -n "${ALL_SKILLS}" ]; then + ALL_SKILLS="${ALL_SKILLS},${ENV_SKILLS}" + else + ALL_SKILLS="${ENV_SKILLS}" + fi +fi + +# If no skills specified, exit early +if [ -z "${ALL_SKILLS}" ]; then + echo "No skills specified. Skipping skill installation." + echo "To install skills, set 'skills' option or DUPLO_AI_SKILLS environment variable." + exit 0 +fi + +# Convert comma-separated list to array +IFS=',' read -ra SKILL_ARRAY <<< "${ALL_SKILLS}" + +# Install each skill +INSTALLED_COUNT=0 +FAILED_COUNT=0 + +for SKILL in "${SKILL_ARRAY[@]}"; do + # Trim whitespace + SKILL="$(echo "${SKILL}" | xargs)" + + if [ -z "${SKILL}" ]; then + continue + fi + + echo "" + echo "Installing skill: ${SKILL}" + + if duplo-skills --dir "${SKILLS_DIR}" --skill "${SKILL}"; then + ((INSTALLED_COUNT++)) + else + echo "⚠ Failed to install skill: ${SKILL}" + ((FAILED_COUNT++)) + fi +done + +echo "" +echo "========================================" +echo "Skills installation complete" +echo "========================================" +echo "Installed: ${INSTALLED_COUNT}" +echo "Failed: ${FAILED_COUNT}" +echo "Location: ${SKILLS_DIR}" +echo "" + +if [ ${INSTALLED_COUNT} -gt 0 ]; then + echo "✓ Codex skills are ready to use" +fi diff --git a/src/ai-gemini/NOTES.md b/src/ai-gemini/NOTES.md new file mode 100644 index 0000000..47e8fa2 --- /dev/null +++ b/src/ai-gemini/NOTES.md @@ -0,0 +1,81 @@ +## Gemini CLI AI Feature + +This feature installs [Google's Gemini CLI](https://geminicli.com/docs/cli/skills/) with VS Code integration and support for DuploCloud AI skills. + +### What's Included + +- **Gemini CLI** - `@google/gemini-cli` npm package installed globally +- **VS Code Extension** - [Gemini Code Assist extension](https://marketplace.visualstudio.com/items?itemName=Google.geminicodeassist) automatically installed +- **Skills Support** - Automatic installation of skills from [duplocloud/ai-ops](https://github.com/duplocloud/ai-ops) + +### Usage in devcontainer.json + +```json +{ + "features": { + "ghcr.io/duplocloud/devcontainers/ai-gemini:1": { + "skills": "tf-module,api-design" + } + } +} +``` + +### Options + +- `skills` - Comma-separated list of skills to install (default: "") + +### Skills Installation + +Skills are installed to `~/.gemini/skills/` during container creation. You can specify skills in two ways: + +1. **Feature option**: Set the `skills` option in your devcontainer.json +2. **Environment variable**: Set `DUPLO_AI_SKILLS` in your devcontainer.json + +Both sources are merged, so you can have a common set via environment variable and feature-specific additions via the option. + +### Example: Installing Multiple Skills + +```json +{ + "containerEnv": { + "DUPLO_AI_SKILLS": "tf-module,api-design" + }, + "features": { + "ghcr.io/duplocloud/devcontainers/ai-gemini:1": { + "skills": "code-review" + } + } +} +``` + +This configuration installs all three skills: `tf-module`, `api-design`, and `code-review`. + +### Skill Location + +Gemini CLI discovers skills from multiple tiers: +- **User skills**: `~/.gemini/skills/` (installed by this feature) +- **Workspace skills**: `.gemini/skills/` (commit to version control) +- **Extension skills**: Provided by installed extensions + +Precedence: Workspace > User > Extension + +For more details on how Gemini CLI uses skills, see the [Agent Skills documentation](https://geminicli.com/docs/cli/skills/). + +### Managing Skills + +In the Gemini CLI interactive session: +- `/skills list` - View all discovered skills +- `/skills disable ` - Disable a specific skill +- `/skills enable ` - Re-enable a disabled skill +- `/skills reload` - Refresh the skills list + +### Available Skills + +Skills are published in the [duplocloud/ai-ops releases](https://github.com/duplocloud/ai-ops/releases). + +## References + +- [Gemini CLI Documentation](https://geminicli.com/docs/) +- [Gemini Code Assist VS Code Extension](https://marketplace.visualstudio.com/items?itemName=Google.geminicodeassist) +- [Gemini CLI Skills Guide](https://geminicli.com/docs/cli/skills/) +- [Agent Skills Standard](https://agentskills.io/) diff --git a/src/ai-gemini/devcontainer-feature.json b/src/ai-gemini/devcontainer-feature.json new file mode 100644 index 0000000..3cbccea --- /dev/null +++ b/src/ai-gemini/devcontainer-feature.json @@ -0,0 +1,23 @@ +{ + "id": "ai-gemini", + "version": "1.0.0", + "name": "Gemini CLI AI", + "description": "Google's Gemini CLI with VS Code integration and skills support", + "options": { + "skills": { + "type": "string", + "default": "", + "description": "Comma-separated list of skills to install (e.g., 'tf-module,api-design')" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "Google.geminicodeassist" + ] + } + }, + "dependsOn": { + "ghcr.io/duplocloud/devcontainers/ai:latest": {} + } +} diff --git a/src/ai-gemini/install.sh b/src/ai-gemini/install.sh new file mode 100644 index 0000000..ba89c01 --- /dev/null +++ b/src/ai-gemini/install.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -e + +echo "Installing Gemini CLI AI..." + +# Install Gemini CLI globally +echo "Installing @google/gemini-cli..." +npm install -g @google/gemini-cli + +# Verify installation +if command -v gemini &> /dev/null; then + echo "✓ Gemini CLI installed successfully" + gemini --version || true +else + echo "⚠ Gemini CLI installation could not be verified" +fi + +# Copy on-create script to shared location +FEATURE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cp "${FEATURE_DIR}/on-create.sh" /usr/local/share/ai-gemini-on-create.sh +chmod +x /usr/local/share/ai-gemini-on-create.sh + +# Save feature options to config file for use during onCreate lifecycle hook +mkdir -p /usr/local/etc +cat < /usr/local/etc/ai-gemini-feature.conf +SKILLS="${SKILLS:-}" +EOF +chmod 644 /usr/local/etc/ai-gemini-feature.conf + +echo "Gemini CLI AI installed successfully!" +echo "" +echo "Note: Skills will be installed during container creation (on-create hook)" diff --git a/src/ai-gemini/on-create.sh b/src/ai-gemini/on-create.sh new file mode 100644 index 0000000..4a2455c --- /dev/null +++ b/src/ai-gemini/on-create.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -e + +echo "Installing Gemini CLI skills..." + +# Use devcontainer environment variables with fallbacks +USER_HOME="${_REMOTE_USER_HOME:-$HOME}" +USER_NAME="${_REMOTE_USER:-$(whoami)}" + +# Load feature configuration +if [ -f /usr/local/etc/ai-gemini-feature.conf ]; then + source /usr/local/etc/ai-gemini-feature.conf +fi + +# Skills directory for Gemini CLI (user scope) +SKILLS_DIR="${USER_HOME}/.gemini/skills" +mkdir -p "${USER_HOME}/.gemini" "${SKILLS_DIR}" + +# Get skills from option +FEATURE_SKILLS="${SKILLS:-}" + +# Get skills from environment variable +ENV_SKILLS="${DUPLO_AI_SKILLS:-}" + +# Merge skills from both sources +ALL_SKILLS="" +if [ -n "${FEATURE_SKILLS}" ]; then + ALL_SKILLS="${FEATURE_SKILLS}" +fi +if [ -n "${ENV_SKILLS}" ]; then + if [ -n "${ALL_SKILLS}" ]; then + ALL_SKILLS="${ALL_SKILLS},${ENV_SKILLS}" + else + ALL_SKILLS="${ENV_SKILLS}" + fi +fi + +# If no skills specified, exit early +if [ -z "${ALL_SKILLS}" ]; then + echo "No skills specified. Skipping skill installation." + echo "To install skills, set 'skills' option or DUPLO_AI_SKILLS environment variable." + exit 0 +fi + +# Convert comma-separated list to array +IFS=',' read -ra SKILL_ARRAY <<< "${ALL_SKILLS}" + +# Install each skill +INSTALLED_COUNT=0 +FAILED_COUNT=0 + +for SKILL in "${SKILL_ARRAY[@]}"; do + # Trim whitespace + SKILL="$(echo "${SKILL}" | xargs)" + + if [ -z "${SKILL}" ]; then + continue + fi + + echo "" + echo "Installing skill: ${SKILL}" + + if duplo-skills --dir "${SKILLS_DIR}" --skill "${SKILL}"; then + ((INSTALLED_COUNT++)) + else + echo "⚠ Failed to install skill: ${SKILL}" + ((FAILED_COUNT++)) + fi +done + +echo "" +echo "========================================" +echo "Skills installation complete" +echo "========================================" +echo "Installed: ${INSTALLED_COUNT}" +echo "Failed: ${FAILED_COUNT}" +echo "Location: ${SKILLS_DIR}" +echo "" + +if [ ${INSTALLED_COUNT} -gt 0 ]; then + echo "✓ Gemini CLI skills are ready to use" +fi diff --git a/src/ai/NOTES.md b/src/ai/NOTES.md new file mode 100644 index 0000000..633d4a8 --- /dev/null +++ b/src/ai/NOTES.md @@ -0,0 +1,42 @@ +## AI Base Feature + +This feature provides the foundation for all AI CLI tools in DuploCloud devcontainers. It handles Node.js installation and provides the `duplo-skills` CLI for downloading skills from the [duplocloud/ai-ops](https://github.com/duplocloud/ai-ops) repository. + +### What's Included + +- **Node.js Installation**: Automatically checks for and installs Node.js if not present + - First tries to use nvm if available + - Falls back to apt package manager + - Compatible with [stu-bell/devcontainer-features/node](https://github.com/stu-bell/devcontainer-features/tree/main/src/node) + +- **duplo-skills CLI**: Global command for downloading AI skills + - Downloads `.skill` files from GitHub releases + - Verifies SHA256 checksums automatically + - Supports version pinning via `DUPLO_SKILLS_VERSION` + +### Usage + +This feature is typically used as a dependency for AI provider features (claude, gemini, codex). You don't need to use it directly unless you want to download skills manually. + +### Manual Skill Installation + +```bash +# Download a skill to any directory +duplo-skills --dir ~/.claude/skills --skill tf-module + +# Pin to a specific version +DUPLO_SKILLS_VERSION=v0.0.2 duplo-skills --dir ~/.gemini/skills --skill api-design +``` + +### Available Skills + +Skills are published in the [duplocloud/ai-ops releases](https://github.com/duplocloud/ai-ops/releases). Each release contains multiple `.skill` files with corresponding `.sha256` checksums. + +### Environment Variables + +- `DUPLO_SKILLS_VERSION` - Specify which release version to download from (default: "latest") + +## References + +- [duplocloud/ai-ops Repository](https://github.com/duplocloud/ai-ops) +- [Agent Skills Standard](https://agentskills.io/) diff --git a/src/ai/devcontainer-feature.json b/src/ai/devcontainer-feature.json new file mode 100644 index 0000000..aa4c258 --- /dev/null +++ b/src/ai/devcontainer-feature.json @@ -0,0 +1,10 @@ +{ + "id": "ai", + "version": "1.0.0", + "name": "AI Base", + "description": "Base dependencies for AI CLI tools including Node.js and skills downloader", + "options": {}, + "installsAfter": [ + "ghcr.io/devcontainers/features/node" + ] +} diff --git a/src/ai/install.sh b/src/ai/install.sh new file mode 100644 index 0000000..4649212 --- /dev/null +++ b/src/ai/install.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -e + +echo "Installing AI base dependencies..." + +# Get the directory where this script lives +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Install Node.js if not already present +bash "${SCRIPT_DIR}/scripts/install-nodejs.sh" + +# Copy duplo-skills package to a temp location for npm install +DUPLO_SKILLS_DIR="${SCRIPT_DIR}/scripts/duplo-skills" +TEMP_INSTALL_DIR="/tmp/duplo-skills-install" + +rm -rf "${TEMP_INSTALL_DIR}" +mkdir -p "${TEMP_INSTALL_DIR}" +cp -r "${DUPLO_SKILLS_DIR}"/* "${TEMP_INSTALL_DIR}/" + +# Install duplo-skills globally as root +cd "${TEMP_INSTALL_DIR}" +npm install -g . + +# Cleanup +rm -rf "${TEMP_INSTALL_DIR}" + +# Verify installation +if command -v duplo-skills &> /dev/null; then + echo "✓ duplo-skills installed successfully" + duplo-skills --version +else + echo "⚠ duplo-skills installation could not be verified" +fi + +echo "AI base dependencies installed successfully!" diff --git a/src/ai/scripts/duplo-skills/README.md b/src/ai/scripts/duplo-skills/README.md new file mode 100644 index 0000000..20254c5 --- /dev/null +++ b/src/ai/scripts/duplo-skills/README.md @@ -0,0 +1,42 @@ +# duplo-skills + +Downloads and verifies skills from the [duplocloud/ai-ops](https://github.com/duplocloud/ai-ops) repository. + +## Usage + +```bash +duplo-skills --dir --skill +``` + +## Options + +- `--dir ` - Directory to install the skill (required) +- `--skill ` - Name of the skill to download (required) +- `--version` - Show version +- `--help` - Show help message + +## Environment Variables + +- `DUPLO_SKILLS_VERSION` - Version to download (default: "latest") + +## Examples + +```bash +# Download the latest tf-module skill to Claude Code +duplo-skills --dir ~/.claude/skills --skill tf-module + +# Download a specific version +DUPLO_SKILLS_VERSION=v0.0.2 duplo-skills --dir ~/.gemini/skills --skill api-design + +# Download to Codex +duplo-skills --dir ~/.codex/skills --skill tf-module +``` + +## How it works + +1. Queries GitHub API for the specified release (latest by default) +2. Downloads the `.skill` file for the requested skill +3. Verifies SHA256 checksum if available +4. Installs to the specified directory + +Skills are packaged as `.skill` files in the [duplocloud/ai-ops releases](https://github.com/duplocloud/ai-ops/releases). diff --git a/src/ai/scripts/duplo-skills/index.js b/src/ai/scripts/duplo-skills/index.js new file mode 100644 index 0000000..b1ab2f5 --- /dev/null +++ b/src/ai/scripts/duplo-skills/index.js @@ -0,0 +1,185 @@ +#!/usr/bin/env node + +const https = require('https'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const REPO_OWNER = 'duplocloud'; +const REPO_NAME = 'ai-ops'; +const GITHUB_API = 'https://api.github.com'; + +function showUsage() { + console.log(` +Usage: duplo-skills --dir --skill [options] + +Options: + --dir Directory to install the skill (required) + --skill Name of the skill to download (required) + --version Show version + --help Show this help message + +Environment Variables: + DUPLO_SKILLS_VERSION Version to download (default: "latest") + +Example: + duplo-skills --dir ~/.claude/skills --skill tf-module +`); +} + +function showVersion() { + const pkg = require('./package.json'); + console.log(`duplo-skills v${pkg.version}`); +} + +function parseArgs() { + const args = process.argv.slice(2); + const parsed = {}; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === '--help' || arg === '-h') { + showUsage(); + process.exit(0); + } + + if (arg === '--version' || arg === '-v') { + showVersion(); + process.exit(0); + } + + if (arg === '--dir' && i + 1 < args.length) { + parsed.dir = args[++i]; + } + + if (arg === '--skill' && i + 1 < args.length) { + parsed.skill = args[++i]; + } + } + + return parsed; +} + +function httpsGet(url) { + return new Promise((resolve, reject) => { + https.get(url, { + headers: { + 'User-Agent': 'duplo-skills' + } + }, (res) => { + if (res.statusCode === 302 || res.statusCode === 301) { + // Follow redirect + return resolve(httpsGet(res.headers.location)); + } + + if (res.statusCode !== 200) { + return reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`)); + } + + const chunks = []; + res.on('data', (chunk) => chunks.push(chunk)); + res.on('end', () => resolve(Buffer.concat(chunks))); + res.on('error', reject); + }).on('error', reject); + }); +} + +async function getLatestRelease() { + const url = `${GITHUB_API}/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`; + console.log(`Fetching latest release from ${REPO_OWNER}/${REPO_NAME}...`); + + const data = await httpsGet(url); + return JSON.parse(data.toString()); +} + +async function getRelease(version) { + if (version === 'latest') { + return await getLatestRelease(); + } + + // Specific version + const url = `${GITHUB_API}/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${version}`; + console.log(`Fetching release ${version} from ${REPO_OWNER}/${REPO_NAME}...`); + + const data = await httpsGet(url); + return JSON.parse(data.toString()); +} + +function calculateChecksum(buffer) { + return crypto.createHash('sha256').update(buffer).digest('hex'); +} + +function findAssetWithChecksum(assets, skillName) { + const skillAsset = assets.find(a => a.name === `${skillName}.skill`); + const checksumAsset = assets.find(a => a.name === `${skillName}.skill.sha256`); + + return { skillAsset, checksumAsset }; +} + +async function downloadSkill(installDir, skillName) { + const version = process.env.DUPLO_SKILLS_VERSION || 'latest'; + + try { + // Get release info + const release = await getRelease(version); + console.log(`Found release: ${release.tag_name}`); + + // Find the skill asset and checksum + const { skillAsset, checksumAsset } = findAssetWithChecksum(release.assets, skillName); + + if (!skillAsset) { + throw new Error(`Skill "${skillName}.skill" not found in release ${release.tag_name}`); + } + + console.log(`Downloading ${skillAsset.name}...`); + const skillData = await httpsGet(skillAsset.browser_download_url); + + // Verify checksum if available + if (checksumAsset) { + console.log(`Verifying checksum...`); + const expectedChecksum = await httpsGet(checksumAsset.browser_download_url); + const expectedHash = expectedChecksum.toString().trim().split(/\s+/)[0]; + const actualHash = calculateChecksum(skillData); + + if (expectedHash !== actualHash) { + throw new Error(`Checksum mismatch!\n Expected: ${expectedHash}\n Got: ${actualHash}`); + } + console.log(`✓ Checksum verified`); + } else { + console.log(`⚠ No checksum available for verification`); + } + + // Ensure install directory exists + const fullPath = path.resolve(installDir); + if (!fs.existsSync(fullPath)) { + fs.mkdirSync(fullPath, { recursive: true }); + } + + // Write the skill file + const skillPath = path.join(fullPath, `${skillName}.skill`); + fs.writeFileSync(skillPath, skillData); + console.log(`✓ Skill installed to: ${skillPath}`); + + return skillPath; + + } catch (error) { + console.error(`Error downloading skill: ${error.message}`); + process.exit(1); + } +} + +// Main execution +async function main() { + const args = parseArgs(); + + if (!args.dir || !args.skill) { + console.error('Error: --dir and --skill are required\n'); + showUsage(); + process.exit(1); + } + + await downloadSkill(args.dir, args.skill); +} + +main(); diff --git a/src/ai/scripts/duplo-skills/package.json b/src/ai/scripts/duplo-skills/package.json new file mode 100644 index 0000000..a498032 --- /dev/null +++ b/src/ai/scripts/duplo-skills/package.json @@ -0,0 +1,15 @@ +{ + "name": "@duplocloud/duplo-skills", + "version": "1.0.0", + "description": "Download and verify skills from duplocloud/ai-ops releases", + "bin": { + "duplo-skills": "./index.js" + }, + "keywords": [ + "duplocloud", + "skills", + "ai" + ], + "author": "DuploCloud", + "license": "MIT" +} diff --git a/src/ai/scripts/install-nodejs.sh b/src/ai/scripts/install-nodejs.sh new file mode 100644 index 0000000..27ebd33 --- /dev/null +++ b/src/ai/scripts/install-nodejs.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -e + +echo "Checking Node.js installation..." + +# Check if node is already installed +if command -v node &> /dev/null; then + NODE_VERSION=$(node --version) + echo "✓ Node.js already installed: ${NODE_VERSION}" + exit 0 +fi + +echo "Node.js not found, attempting installation..." + +# Try to use nvm if available +if [ -s "${NVM_DIR:-$HOME/.nvm}/nvm.sh" ]; then + echo "Found nvm, loading it..." + # shellcheck disable=SC1091 + source "${NVM_DIR:-$HOME/.nvm}/nvm.sh" + + if command -v nvm &> /dev/null; then + echo "Installing Node.js LTS via nvm..." + nvm install --lts + nvm use --lts + echo "✓ Node.js installed via nvm" + node --version + exit 0 + fi +fi + +# Fallback to apt package manager +if command -v apt-get &> /dev/null; then + echo "Installing Node.js via apt..." + export DEBIAN_FRONTEND=noninteractive + + apt-get update + apt-get install -y nodejs npm + + echo "✓ Node.js installed via apt" + node --version + exit 0 +fi + +# If we got here, we couldn't install Node.js +echo "ERROR: Could not install Node.js. Neither nvm nor apt-get are available." +exit 1 diff --git a/test/ai-claude/scenarios.json b/test/ai-claude/scenarios.json new file mode 100644 index 0000000..1bf3e51 --- /dev/null +++ b/test/ai-claude/scenarios.json @@ -0,0 +1,10 @@ +{ + "with_skills": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ai-claude": { + "skills": "tf-module" + } + } + } +} diff --git a/test/ai-claude/test.sh b/test/ai-claude/test.sh new file mode 100644 index 0000000..0c406c6 --- /dev/null +++ b/test/ai-claude/test.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# Import test library +# shellcheck disable=SC1091 +source dev-container-features-test-lib + +# Test that base AI feature is installed (Node.js and duplo-skills) +check "node installed" node --version +check "duplo-skills installed" command -v duplo-skills + +# Test that Claude Code CLI is installed +check "claude-code installed" command -v claude-code + +# Test that npm has claude-code package +check "claude-code npm package" npm list -g @anthropic-ai/claude-code + +# Test that skills directory exists +check "claude skills dir exists" test -d "${HOME}/.claude/skills" + +# Report results +reportResults diff --git a/test/ai-claude/with_skills.sh b/test/ai-claude/with_skills.sh new file mode 100644 index 0000000..66f6d85 --- /dev/null +++ b/test/ai-claude/with_skills.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# Import test library +# shellcheck disable=SC1091 +source dev-container-features-test-lib + +# Test that base AI feature is installed (Node.js and duplo-skills) +check "node installed" node --version +check "duplo-skills installed" command -v duplo-skills + +# Test that Claude Code CLI is installed +check "claude-code installed" command -v claude-code + +# Test that skills directory exists +check "claude skills dir exists" test -d "${HOME}/.claude/skills" + +# Test that tf-module skill was installed +check "tf-module skill installed" test -f "${HOME}/.claude/skills/tf-module.skill" + +# Report results +reportResults diff --git a/test/ai-codex/scenarios.json b/test/ai-codex/scenarios.json new file mode 100644 index 0000000..37cdbcb --- /dev/null +++ b/test/ai-codex/scenarios.json @@ -0,0 +1,10 @@ +{ + "with_skills": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ai-codex": { + "skills": "tf-module" + } + } + } +} diff --git a/test/ai-codex/test.sh b/test/ai-codex/test.sh new file mode 100644 index 0000000..6cf13f2 --- /dev/null +++ b/test/ai-codex/test.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# Import test library +# shellcheck disable=SC1091 +source dev-container-features-test-lib + +# Test that base AI feature is installed (Node.js and duplo-skills) +check "node installed" node --version +check "duplo-skills installed" command -v duplo-skills + +# Test that Codex CLI is installed +check "codex installed" command -v codex + +# Test that npm has codex package +check "codex npm package" npm list -g @openai/codex + +# Test that skills directory exists (using default CODEX_HOME) +check "codex skills dir exists" test -d "${HOME}/.codex/skills" + +# Report results +reportResults diff --git a/test/ai-codex/with_skills.sh b/test/ai-codex/with_skills.sh new file mode 100644 index 0000000..d333250 --- /dev/null +++ b/test/ai-codex/with_skills.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# Import test library +# shellcheck disable=SC1091 +source dev-container-features-test-lib + +# Test that base AI feature is installed (Node.js and duplo-skills) +check "node installed" node --version +check "duplo-skills installed" command -v duplo-skills + +# Test that Codex CLI is installed +check "codex installed" command -v codex + +# Test that skills directory exists +check "codex skills dir exists" test -d "${HOME}/.codex/skills" + +# Test that tf-module skill was installed +check "tf-module skill installed" test -f "${HOME}/.codex/skills/tf-module.skill" + +# Report results +reportResults diff --git a/test/ai-gemini/scenarios.json b/test/ai-gemini/scenarios.json new file mode 100644 index 0000000..aca60e6 --- /dev/null +++ b/test/ai-gemini/scenarios.json @@ -0,0 +1,10 @@ +{ + "with_skills": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ai-gemini": { + "skills": "tf-module" + } + } + } +} diff --git a/test/ai-gemini/test.sh b/test/ai-gemini/test.sh new file mode 100644 index 0000000..3aa2781 --- /dev/null +++ b/test/ai-gemini/test.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# Import test library +# shellcheck disable=SC1091 +source dev-container-features-test-lib + +# Test that base AI feature is installed (Node.js and duplo-skills) +check "node installed" node --version +check "duplo-skills installed" command -v duplo-skills + +# Test that Gemini CLI is installed +check "gemini installed" command -v gemini + +# Test that npm has gemini-cli package +check "gemini npm package" npm list -g @google/gemini-cli + +# Test that skills directory exists +check "gemini skills dir exists" test -d "${HOME}/.gemini/skills" + +# Report results +reportResults diff --git a/test/ai-gemini/with_skills.sh b/test/ai-gemini/with_skills.sh new file mode 100644 index 0000000..3d663c3 --- /dev/null +++ b/test/ai-gemini/with_skills.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# Import test library +# shellcheck disable=SC1091 +source dev-container-features-test-lib + +# Test that base AI feature is installed (Node.js and duplo-skills) +check "node installed" node --version +check "duplo-skills installed" command -v duplo-skills + +# Test that Gemini CLI is installed +check "gemini installed" command -v gemini + +# Test that skills directory exists +check "gemini skills dir exists" test -d "${HOME}/.gemini/skills" + +# Test that tf-module skill was installed +check "tf-module skill installed" test -f "${HOME}/.gemini/skills/tf-module.skill" + +# Report results +reportResults diff --git a/test/ai/test.sh b/test/ai/test.sh new file mode 100644 index 0000000..777becf --- /dev/null +++ b/test/ai/test.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +# Import test library +# shellcheck disable=SC1091 +source dev-container-features-test-lib + +# Test that Node.js is installed +check "node installed" node --version + +# Test that npm is installed +check "npm installed" npm --version + +# Test that duplo-skills is installed +check "duplo-skills installed" command -v duplo-skills + +# Test that duplo-skills shows help +check "duplo-skills help" duplo-skills --help + +# Test that duplo-skills shows version +check "duplo-skills version" duplo-skills --version + +# Report results +reportResults