Initial claude skill with skills access
This commit is contained in:
106
SKILL.md
Normal file
106
SKILL.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
name: claude
|
||||||
|
description: Claude Code CLI with skills access for self-modification
|
||||||
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
|
vibestack:
|
||||||
|
main: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Claude Skill
|
||||||
|
|
||||||
|
[Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) with full access to the skills directory for autonomous agent capabilities.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Claude Code CLI installation
|
||||||
|
- HTTP API for chat and execution
|
||||||
|
- Access to `/skills` directory for self-modification
|
||||||
|
- System prompt with skills context
|
||||||
|
- Streaming responses via SSE
|
||||||
|
- Auto-registers with Caddy if present
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `CLAUDE_PORT` | HTTP API port | `8888` |
|
||||||
|
| `CLAUDE_DOMAIN` | Domain for Caddy auto-config | (none) |
|
||||||
|
| `ANTHROPIC_API_KEY` | Anthropic API key | (required) |
|
||||||
|
| `CLAUDE_CODE_OAUTH_TOKEN` | OAuth token (alternative to API key) | (optional) |
|
||||||
|
| `CLAUDE_MODEL` | Model to use | `claude-sonnet-4-20250514` |
|
||||||
|
| `CLAUDE_MAX_TURNS` | Max conversation turns | `10` |
|
||||||
|
| `SKILLS_DIR` | Skills directory | `/skills` |
|
||||||
|
|
||||||
|
## HTTP API
|
||||||
|
|
||||||
|
### Chat (Streaming)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -N http://localhost:8888/chat \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"message": "List all skills and their purpose"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execute (One-shot)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8888/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"prompt": "Create a new skill called hello-world that responds with Hello World"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8888/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
Claude has access to:
|
||||||
|
|
||||||
|
1. **Skills Directory** (`/skills/`)
|
||||||
|
- Read all skill configurations
|
||||||
|
- Modify existing skills
|
||||||
|
- Create new skills
|
||||||
|
- Push changes to Git
|
||||||
|
|
||||||
|
2. **System Tools**
|
||||||
|
- Bash commands
|
||||||
|
- File operations
|
||||||
|
- Git operations
|
||||||
|
- Network requests
|
||||||
|
|
||||||
|
3. **Other Skills**
|
||||||
|
- Query DuckDB
|
||||||
|
- Read logs from Loki
|
||||||
|
- Check metrics
|
||||||
|
|
||||||
|
## System Prompt
|
||||||
|
|
||||||
|
Claude receives context about:
|
||||||
|
- Available skills and their structure
|
||||||
|
- How to create/modify skills
|
||||||
|
- Git workflow for pushing changes
|
||||||
|
- API endpoints for other skills
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Claude runs with full system access
|
||||||
|
- Ensure proper authentication on the API endpoint
|
||||||
|
- Use `CLAUDE_DOMAIN` with Caddy for HTTPS
|
||||||
|
- Consider running behind authelia for protected access
|
||||||
|
|
||||||
|
## Self-Modification Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8888/execute \
|
||||||
|
-d '{"prompt": "Add a /hello endpoint to the caddy skill that returns a greeting"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude will:
|
||||||
|
1. Read the caddy skill files
|
||||||
|
2. Modify the necessary scripts
|
||||||
|
3. Commit and push changes
|
||||||
|
4. Report what was done
|
||||||
190
scripts/autorun.sh
Normal file
190
scripts/autorun.sh
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SKILLS_DIR="${SKILLS_DIR:-/skills}"
|
||||||
|
SKILL_DIR="$(dirname "$(dirname "$0")")"
|
||||||
|
|
||||||
|
# Install Node.js (required for Claude CLI)
|
||||||
|
install_node() {
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
echo "Node.js already installed: $(node --version)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing Node.js..."
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||||
|
apt-get install -y nodejs
|
||||||
|
|
||||||
|
echo "Node.js installed: $(node --version)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install Claude Code CLI
|
||||||
|
install_claude() {
|
||||||
|
if command -v claude &>/dev/null; then
|
||||||
|
echo "Claude CLI already installed: $(claude --version)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing Claude Code CLI..."
|
||||||
|
npm install -g @anthropic-ai/claude-code
|
||||||
|
|
||||||
|
echo "Claude CLI installed: $(claude --version)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install socat for HTTP server
|
||||||
|
install_deps() {
|
||||||
|
if command -v socat &>/dev/null && command -v jq &>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing dependencies..."
|
||||||
|
apt-get update && apt-get install -y socat jq
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate system prompt with skills context
|
||||||
|
generate_system_prompt() {
|
||||||
|
local prompt_file="$SKILL_DIR/prompts/system.md"
|
||||||
|
|
||||||
|
cat > "$prompt_file" << 'EOF'
|
||||||
|
# VibeStack Agent
|
||||||
|
|
||||||
|
You are an autonomous agent running inside a VibeStack skill-based container. You have full access to the system and can modify skills.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
- Skills directory: /skills
|
||||||
|
- Each skill is a separate Git repository
|
||||||
|
- Skills have: SKILL.md (metadata), scripts/autorun.sh, scripts/run.sh
|
||||||
|
|
||||||
|
## Available Skills
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# List available skills
|
||||||
|
for skill_dir in "$SKILLS_DIR"/*/; do
|
||||||
|
local skill_name=$(basename "$skill_dir")
|
||||||
|
local skill_md="$skill_dir/SKILL.md"
|
||||||
|
|
||||||
|
if [ -f "$skill_md" ]; then
|
||||||
|
local description=$(yq -r '.description // "No description"' < <(sed -n '/^---$/,/^---$/p' "$skill_md" | sed '1d;$d') 2>/dev/null || echo "No description")
|
||||||
|
echo "- **$skill_name**: $description" >> "$prompt_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cat >> "$prompt_file" << 'EOF'
|
||||||
|
|
||||||
|
## Creating/Modifying Skills
|
||||||
|
|
||||||
|
1. Skills are in /skills/{name}/
|
||||||
|
2. Edit files directly
|
||||||
|
3. Commit and push changes:
|
||||||
|
```bash
|
||||||
|
cd /skills/{name}
|
||||||
|
git add .
|
||||||
|
git commit -m "Description of changes"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
## Skill Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
skill-name/
|
||||||
|
├── SKILL.md # YAML frontmatter + documentation
|
||||||
|
├── scripts/
|
||||||
|
│ ├── autorun.sh # Setup/install (runs on skill load)
|
||||||
|
│ └── run.sh # Main entry point
|
||||||
|
└── config/ # Optional config files
|
||||||
|
```
|
||||||
|
|
||||||
|
## SKILL.md Format
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
name: skill-name
|
||||||
|
description: What it does
|
||||||
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
|
vibestack:
|
||||||
|
main: false
|
||||||
|
metrics-port: 8080 # If skill exposes metrics
|
||||||
|
requires: # Dependencies
|
||||||
|
- other-skill
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skill Name
|
||||||
|
|
||||||
|
Documentation...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other Skill APIs
|
||||||
|
|
||||||
|
- **DuckDB**: POST http://localhost:8432/query {"sql": "..."}
|
||||||
|
- **Loki**: GET http://localhost:3100/loki/api/v1/query?query={job="skills"}
|
||||||
|
- **Metrics**: GET http://localhost:9090/metrics
|
||||||
|
|
||||||
|
## Git Configuration
|
||||||
|
|
||||||
|
Git is configured with token auth. You can push to:
|
||||||
|
- https://git.vibe-overflow.com/azat/{skill-name}
|
||||||
|
|
||||||
|
To create a new skill repo, use the Gitea API or ask the user.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "System prompt generated: $prompt_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure Caddy if present
|
||||||
|
configure_caddy() {
|
||||||
|
local caddy_dir="$SKILLS_DIR/caddy"
|
||||||
|
local claude_port="${CLAUDE_PORT:-8888}"
|
||||||
|
local claude_domain="${CLAUDE_DOMAIN:-}"
|
||||||
|
|
||||||
|
if [ ! -d "$caddy_dir" ]; then
|
||||||
|
echo "Caddy not found - Claude API on port $claude_port"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Caddy detected - configuring reverse proxy..."
|
||||||
|
mkdir -p "$caddy_dir/snippets.d"
|
||||||
|
|
||||||
|
local snippet="$caddy_dir/snippets.d/claude.caddy"
|
||||||
|
|
||||||
|
if [ -n "$claude_domain" ]; then
|
||||||
|
cat > "$snippet" << EOF
|
||||||
|
# Auto-generated by claude skill
|
||||||
|
$claude_domain {
|
||||||
|
reverse_proxy localhost:$claude_port
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
echo "Caddy config: $claude_domain -> localhost:$claude_port"
|
||||||
|
else
|
||||||
|
cat > "$snippet" << EOF
|
||||||
|
# Auto-generated by claude skill
|
||||||
|
# Add to your site block:
|
||||||
|
# handle /claude/* {
|
||||||
|
# uri strip_prefix /claude
|
||||||
|
# reverse_proxy localhost:$claude_port
|
||||||
|
# }
|
||||||
|
EOF
|
||||||
|
echo "Caddy snippet created (manual config needed)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate authentication
|
||||||
|
check_auth() {
|
||||||
|
if [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then
|
||||||
|
echo "WARNING: No authentication configured!"
|
||||||
|
echo "Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN"
|
||||||
|
else
|
||||||
|
echo "Authentication configured"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_node
|
||||||
|
install_claude
|
||||||
|
install_deps
|
||||||
|
generate_system_prompt
|
||||||
|
configure_caddy
|
||||||
|
check_auth
|
||||||
|
|
||||||
|
echo "Claude setup complete"
|
||||||
175
scripts/run.sh
Normal file
175
scripts/run.sh
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SKILL_DIR="$(dirname "$(dirname "$0")")"
|
||||||
|
CLAUDE_PORT="${CLAUDE_PORT:-8888}"
|
||||||
|
CLAUDE_MODEL="${CLAUDE_MODEL:-claude-sonnet-4-20250514}"
|
||||||
|
CLAUDE_MAX_TURNS="${CLAUDE_MAX_TURNS:-10}"
|
||||||
|
SKILLS_DIR="${SKILLS_DIR:-/skills}"
|
||||||
|
SYSTEM_PROMPT_FILE="$SKILL_DIR/prompts/system.md"
|
||||||
|
|
||||||
|
# Create HTTP handler
|
||||||
|
create_handler() {
|
||||||
|
cat > /tmp/claude_handler.sh << 'HANDLER'
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SKILL_DIR="${SKILL_DIR}"
|
||||||
|
CLAUDE_MODEL="${CLAUDE_MODEL:-claude-sonnet-4-20250514}"
|
||||||
|
CLAUDE_MAX_TURNS="${CLAUDE_MAX_TURNS:-10}"
|
||||||
|
SKILLS_DIR="${SKILLS_DIR:-/skills}"
|
||||||
|
SYSTEM_PROMPT_FILE="${SYSTEM_PROMPT_FILE}"
|
||||||
|
|
||||||
|
# Read HTTP request
|
||||||
|
read -r request_line
|
||||||
|
method=$(echo "$request_line" | cut -d' ' -f1)
|
||||||
|
path=$(echo "$request_line" | cut -d' ' -f2)
|
||||||
|
|
||||||
|
# Read headers
|
||||||
|
content_length=0
|
||||||
|
while read -r header; do
|
||||||
|
header=$(echo "$header" | tr -d '\r')
|
||||||
|
[ -z "$header" ] && break
|
||||||
|
if [[ "$header" =~ ^[Cc]ontent-[Ll]ength:\ *([0-9]+) ]]; then
|
||||||
|
content_length="${BASH_REMATCH[1]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Read body
|
||||||
|
body=""
|
||||||
|
if [ "$content_length" -gt 0 ]; then
|
||||||
|
body=$(head -c "$content_length")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Response helper
|
||||||
|
send_response() {
|
||||||
|
local status="$1"
|
||||||
|
local content_type="$2"
|
||||||
|
local body="$3"
|
||||||
|
local body_length=${#body}
|
||||||
|
|
||||||
|
printf "HTTP/1.1 %s\r\n" "$status"
|
||||||
|
printf "Content-Type: %s\r\n" "$content_type"
|
||||||
|
printf "Content-Length: %d\r\n" "$body_length"
|
||||||
|
printf "Connection: close\r\n"
|
||||||
|
printf "\r\n"
|
||||||
|
printf "%s" "$body"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
if [ "$path" = "/health" ]; then
|
||||||
|
if command -v claude &>/dev/null; then
|
||||||
|
send_response "200 OK" "application/json" '{"status":"ok","claude":"available"}'
|
||||||
|
else
|
||||||
|
send_response "503 Service Unavailable" "application/json" '{"status":"error","claude":"not found"}'
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Execute endpoint (one-shot)
|
||||||
|
if [ "$path" = "/execute" ] && [ "$method" = "POST" ]; then
|
||||||
|
prompt=$(echo "$body" | jq -r '.prompt // empty')
|
||||||
|
|
||||||
|
if [ -z "$prompt" ]; then
|
||||||
|
send_response "400 Bad Request" "application/json" '{"error":"Missing prompt field"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build claude command
|
||||||
|
cd "$SKILLS_DIR"
|
||||||
|
|
||||||
|
# Run claude with system prompt
|
||||||
|
result=$(claude --print \
|
||||||
|
--model "$CLAUDE_MODEL" \
|
||||||
|
--max-turns "$CLAUDE_MAX_TURNS" \
|
||||||
|
--system-prompt "$(cat "$SYSTEM_PROMPT_FILE")" \
|
||||||
|
--allowedTools "Bash,Read,Write,Edit,Glob,Grep" \
|
||||||
|
"$prompt" 2>&1) || {
|
||||||
|
error_msg=$(echo "$result" | jq -Rs '.')
|
||||||
|
send_response "500 Internal Server Error" "application/json" "{\"success\":false,\"error\":$error_msg}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Return result
|
||||||
|
result_json=$(echo "$result" | jq -Rs '.')
|
||||||
|
send_response "200 OK" "application/json" "{\"success\":true,\"result\":$result_json}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Chat endpoint (simplified, non-streaming for now)
|
||||||
|
if [ "$path" = "/chat" ] && [ "$method" = "POST" ]; then
|
||||||
|
message=$(echo "$body" | jq -r '.message // empty')
|
||||||
|
session_id=$(echo "$body" | jq -r '.session_id // empty')
|
||||||
|
|
||||||
|
if [ -z "$message" ]; then
|
||||||
|
send_response "400 Bad Request" "application/json" '{"error":"Missing message field"}'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$SKILLS_DIR"
|
||||||
|
|
||||||
|
# Build command
|
||||||
|
claude_args=(
|
||||||
|
"--print"
|
||||||
|
"--model" "$CLAUDE_MODEL"
|
||||||
|
"--max-turns" "1"
|
||||||
|
"--system-prompt" "$(cat "$SYSTEM_PROMPT_FILE")"
|
||||||
|
"--allowedTools" "Bash,Read,Write,Edit,Glob,Grep"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Resume session if provided
|
||||||
|
if [ -n "$session_id" ]; then
|
||||||
|
claude_args+=("--resume" "$session_id")
|
||||||
|
fi
|
||||||
|
|
||||||
|
result=$(claude "${claude_args[@]}" "$message" 2>&1) || {
|
||||||
|
error_msg=$(echo "$result" | jq -Rs '.')
|
||||||
|
send_response "500 Internal Server Error" "application/json" "{\"success\":false,\"error\":$error_msg}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
result_json=$(echo "$result" | jq -Rs '.')
|
||||||
|
send_response "200 OK" "application/json" "{\"success\":true,\"response\":$result_json}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skills list
|
||||||
|
if [ "$path" = "/skills" ] && [ "$method" = "GET" ]; then
|
||||||
|
skills="[]"
|
||||||
|
for skill_dir in "$SKILLS_DIR"/*/; do
|
||||||
|
skill_name=$(basename "$skill_dir")
|
||||||
|
skill_md="$skill_dir/SKILL.md"
|
||||||
|
if [ -f "$skill_md" ]; then
|
||||||
|
desc=$(yq -r '.description // "No description"' < <(sed -n '/^---$/,/^---$/p' "$skill_md" | sed '1d;$d') 2>/dev/null || echo "")
|
||||||
|
skills=$(echo "$skills" | jq --arg name "$skill_name" --arg desc "$desc" '. + [{"name":$name,"description":$desc}]')
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
send_response "200 OK" "application/json" "$skills"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Not found
|
||||||
|
send_response "404 Not Found" "application/json" '{"error":"Endpoints: POST /execute, POST /chat, GET /skills, GET /health"}'
|
||||||
|
HANDLER
|
||||||
|
|
||||||
|
chmod +x /tmp/claude_handler.sh
|
||||||
|
|
||||||
|
# Export vars for handler
|
||||||
|
export SKILL_DIR
|
||||||
|
export CLAUDE_MODEL
|
||||||
|
export CLAUDE_MAX_TURNS
|
||||||
|
export SKILLS_DIR
|
||||||
|
export SYSTEM_PROMPT_FILE
|
||||||
|
}
|
||||||
|
|
||||||
|
# Serve HTTP API
|
||||||
|
serve_api() {
|
||||||
|
echo "Starting Claude HTTP API on port $CLAUDE_PORT..."
|
||||||
|
echo "Model: $CLAUDE_MODEL"
|
||||||
|
echo "Max turns: $CLAUDE_MAX_TURNS"
|
||||||
|
echo "Working directory: $SKILLS_DIR"
|
||||||
|
|
||||||
|
exec socat TCP-LISTEN:$CLAUDE_PORT,reuseaddr,fork EXEC:"/tmp/claude_handler.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_handler
|
||||||
|
serve_api
|
||||||
Reference in New Issue
Block a user