initial
This commit is contained in:
21
.claude/plan.md
Normal file
21
.claude/plan.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# This is a test project
|
||||||
|
|
||||||
|
## IMPORTANT (when working on a plan versus ccsb aka CLAUDE.MD)
|
||||||
|
- Never use hello world as a string in testing
|
||||||
|
- Use gemini-cli when you need to ask about documentation
|
||||||
|
|
||||||
|
## PLAN
|
||||||
|
|
||||||
|
### Basic file
|
||||||
|
- Create a python file called main.py
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- The file should be able to print whatever it is given into split by space returned as json array
|
||||||
|
|
||||||
|
## POST TASK TASKS
|
||||||
|
- Git commit with comprehensive changes
|
||||||
|
- Restructure project architecture for scalability
|
||||||
|
- Merge duplicate systems and optimize code quality
|
||||||
|
- Create utility libraries for common operations
|
||||||
|
- Performance testing and optimization
|
||||||
|
- Final git commit
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.claude/settings.local.json
|
||||||
|
test.*
|
||||||
|
|
||||||
59
Dockerfile
Normal file
59
Dockerfile
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
bash \
|
||||||
|
vim \
|
||||||
|
nano \
|
||||||
|
openssh-client \
|
||||||
|
python3 \
|
||||||
|
py3-pip \
|
||||||
|
build-base \
|
||||||
|
ca-certificates \
|
||||||
|
jq
|
||||||
|
|
||||||
|
# Set working directory to existing node user's home
|
||||||
|
WORKDIR /home/node
|
||||||
|
|
||||||
|
# Install Claude Code
|
||||||
|
RUN npm install -g @anthropic-ai/claude-code
|
||||||
|
|
||||||
|
# Install additional development tools
|
||||||
|
RUN npm install -g \
|
||||||
|
typescript \
|
||||||
|
ts-node \
|
||||||
|
nodemon \
|
||||||
|
prettier \
|
||||||
|
eslint
|
||||||
|
|
||||||
|
# Install gemini-cli
|
||||||
|
RUN npm install -g @google/gemini-cli
|
||||||
|
|
||||||
|
# Create necessary directories for node user
|
||||||
|
RUN mkdir -p /home/node/.claude \
|
||||||
|
/home/node/.config \
|
||||||
|
/home/node/.ssh \
|
||||||
|
/home/node/logs
|
||||||
|
|
||||||
|
# Copy claude-loop script
|
||||||
|
COPY claude-loop.sh /usr/local/bin/claude-loop
|
||||||
|
RUN chmod +x /usr/local/bin/claude-loop
|
||||||
|
|
||||||
|
# Set ownership of node user directories
|
||||||
|
RUN chown -R node:node /home/node
|
||||||
|
|
||||||
|
# Switch to node user
|
||||||
|
USER node
|
||||||
|
|
||||||
|
RUN curl -fsSL https://bun.sh/install | bash
|
||||||
|
|
||||||
|
# Set environment variables for Claude Code
|
||||||
|
ENV TERM=xterm-256color
|
||||||
|
ENV COLORTERM=truecolor
|
||||||
|
ENV NODE_ENV=development
|
||||||
|
ENV PATH="/home/node/.bun/bin:$PATH"
|
||||||
|
|
||||||
|
# Default command
|
||||||
|
CMD ["claude"]
|
||||||
155
README.md
Normal file
155
README.md
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# Claude Loop Project
|
||||||
|
|
||||||
|
A Docker-based automated task execution system using Claude AI to process and complete tasks defined in `.claude/plan.md`.
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This project provides two main scripts for interacting with Claude AI in a sandboxed Docker environment:
|
||||||
|
|
||||||
|
- **`ccl`** (Claude Code Loop) - Runs an automated loop that processes tasks from `.claude/plan.md`
|
||||||
|
- **`ccsb`** (Claude Code Sandbox) - Executes single Claude commands with full tool access
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **Docker**
|
||||||
|
2. **Claude CLI**
|
||||||
|
3. **Gemini CLI** (optional) - For documentation queries / gemini-cli mcp (https://github.com/jamubc/gemini-mcp-tool)
|
||||||
|
4. **User Home Directory** - Scripts must be run from within your home directory for security
|
||||||
|
5. **Linux** (or WSL) - with user 1000:1000
|
||||||
|
|
||||||
|
## Security Warning
|
||||||
|
|
||||||
|
- While rootless docker is very safe, it still has access to the internet and you local network and can possibly cause (limited) havoc.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Build the Docker Image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure Claude CLI
|
||||||
|
|
||||||
|
Ensure you have:
|
||||||
|
- `~/.claude.json` - Claude CLI configuration
|
||||||
|
- `~/.claude/` - Claude settings directory
|
||||||
|
|
||||||
|
### 3. Configure Gemini CLI (Optional)
|
||||||
|
|
||||||
|
If using gemini-cli for documentation queries:
|
||||||
|
- `~/.gemini/` - Gemini settings
|
||||||
|
- `GEMINI_API_KEY` environment variable
|
||||||
|
|
||||||
|
### 4. symlink (or move) to /bin
|
||||||
|
```bash
|
||||||
|
ln -s ccl /bin/ccl
|
||||||
|
ln -s ccsb /bin/ccsb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### CCL (Claude Code Loop)
|
||||||
|
|
||||||
|
Automatically processes tasks defined in `.claude/plan.md`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ccl
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Reads tasks from `.claude/plan.md`
|
||||||
|
- Updates task statuses: `(Not Started)` → `(In Progress)` → `(Completed)/(Aborted)`
|
||||||
|
- Continues until all tasks show `(Completed)`
|
||||||
|
- Creates `/tmp/plan_complete` when finished
|
||||||
|
- Pretty formatted output with progress tracking
|
||||||
|
|
||||||
|
**Task Status Format:**
|
||||||
|
```markdown
|
||||||
|
- (Status) Task description
|
||||||
|
```
|
||||||
|
|
||||||
|
Status options: `Not Started | In Progress | Aborted | Completed`
|
||||||
|
|
||||||
|
### CCSB (Claude Code Sandbox)
|
||||||
|
|
||||||
|
Execute claude in a sandbox with all permissions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ccsb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plan File Structure
|
||||||
|
|
||||||
|
The `.claude/plan.md` file defines tasks to be executed:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Project Name
|
||||||
|
|
||||||
|
## IMPORTANT (instructions for Claude when in ccl mode, does not apply to ccsb)
|
||||||
|
- Project-specific guidelines
|
||||||
|
- Tool preferences
|
||||||
|
- Constraints
|
||||||
|
|
||||||
|
## PLAN
|
||||||
|
|
||||||
|
### Section 1
|
||||||
|
- Task 1 description
|
||||||
|
- Task 2 description
|
||||||
|
|
||||||
|
### Section 2
|
||||||
|
- Task 3 description
|
||||||
|
|
||||||
|
## POST TASK TASKS
|
||||||
|
- Cleanup tasks
|
||||||
|
- Final commits
|
||||||
|
- Documentation updates
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- You can ask ccsb to run claude-loop
|
||||||
|
- You can ask claude to keep a work-log such as:
|
||||||
|
```markdown
|
||||||
|
- Append work to .claude/work-log.md, never read entire file into context with format $(date): <task>\n\n
|
||||||
|
- tail work-log.md before starting
|
||||||
|
- Focus on words with !! for accuracy
|
||||||
|
- Look for (Changes Needed) and view all the changes requested below
|
||||||
|
```
|
||||||
|
- Changes Needed example
|
||||||
|
```markdown
|
||||||
|
### Some Tasks Topic
|
||||||
|
- (Completed) Task1
|
||||||
|
- (Changes Needed) Original task to build a snowman
|
||||||
|
- You built a snowman without a head, add a head
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
- **Home Directory Restriction**: Scripts only run from within user home directory
|
||||||
|
- **Docker Isolation**: All Claude operations run in isolated container
|
||||||
|
- **Non-root Execution**: Container runs as user `1000:1000`
|
||||||
|
- **Limited File Access**: Only mounted directories are accessible
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **"Must be run from within user home directory"**
|
||||||
|
- Ensure you're running the script from a subdirectory of `$HOME`
|
||||||
|
|
||||||
|
2. **Docker permission errors**
|
||||||
|
- Check Docker is running and user has permissions
|
||||||
|
- Verify volume mounts point to existing directories
|
||||||
|
|
||||||
|
3. **Claude CLI not configured**
|
||||||
|
- Run `claude` to set up claude
|
||||||
|
- Ensure `~/.claude.json` exists
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
30
ccl
Executable file
30
ccl
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if current directory is under user home
|
||||||
|
if [[ "$PWD" != "$HOME"* ]]; then
|
||||||
|
echo "Error: Must be run from within user home directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build docker volume arguments
|
||||||
|
VOLUME_ARGS=""
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $HOME/.claude:/home/node/.claude"
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $HOME/.claude.json:/home/node/.claude.json"
|
||||||
|
|
||||||
|
# Only mount gemini directories if they exist
|
||||||
|
if [[ -d $HOME/.config/gemini-cli ]]; then
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $HOME/.config/gemini-cli:/home/node/.config/gemini-cli"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d $HOME/.gemini ]]; then
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $HOME/.gemini:/home/node/.gemini"
|
||||||
|
fi
|
||||||
|
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $PWD:/home/node/workspace"
|
||||||
|
|
||||||
|
docker run --rm -it \
|
||||||
|
--user "1000:1000" \
|
||||||
|
-e GEMINI_API_KEY \
|
||||||
|
$VOLUME_ARGS \
|
||||||
|
claude \
|
||||||
|
sh -c 'cd workspace && claude-loop'
|
||||||
30
ccsb
Executable file
30
ccsb
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if current directory is under user home
|
||||||
|
if [[ "$PWD" != "$HOME"* ]]; then
|
||||||
|
echo "Error: Must be run from within user home directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build docker volume arguments
|
||||||
|
VOLUME_ARGS=""
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $HOME/.claude:/home/node/.claude"
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $HOME/.claude.json:/home/node/.claude.json"
|
||||||
|
|
||||||
|
# Only mount gemini directories if they exist
|
||||||
|
if [[ -d $HOME/.config/gemini-cli ]]; then
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $HOME/.config/gemini-cli:/home/node/.config/gemini-cli"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d $HOME/.gemini ]]; then
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $HOME/.gemini:/home/node/.gemini"
|
||||||
|
fi
|
||||||
|
|
||||||
|
VOLUME_ARGS="$VOLUME_ARGS -v $PWD:/home/node/workspace"
|
||||||
|
|
||||||
|
docker run --rm -it \
|
||||||
|
--user "1000:1000" \
|
||||||
|
-e GEMINI_API_KEY \
|
||||||
|
$VOLUME_ARGS \
|
||||||
|
claude \
|
||||||
|
sh -c 'cd workspace && claude --dangerously-skip-permissions'
|
||||||
242
claude-loop.sh
Executable file
242
claude-loop.sh
Executable file
@@ -0,0 +1,242 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# claude-loop.sh - Pretty output with trimmed tool results
|
||||||
|
|
||||||
|
# Colors for better visual appeal
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
MAGENTA='\033[0;35m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
WHITE='\033[1;37m'
|
||||||
|
GRAY='\033[0;90m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Box drawing characters for prettier output
|
||||||
|
BOX_H="─"
|
||||||
|
BOX_V="│"
|
||||||
|
BOX_TL="┌"
|
||||||
|
BOX_TR="┐"
|
||||||
|
BOX_BL="└"
|
||||||
|
BOX_BR="┘"
|
||||||
|
|
||||||
|
rm -f /tmp/plan_complete
|
||||||
|
|
||||||
|
iteration=1
|
||||||
|
total_cost=0
|
||||||
|
total_input_tokens=0
|
||||||
|
total_output_tokens=0
|
||||||
|
|
||||||
|
# Function to print a fancy header
|
||||||
|
print_header() {
|
||||||
|
local text="$1"
|
||||||
|
local width=60
|
||||||
|
echo -e "${CYAN}${BOX_TL}$(printf "%.0s${BOX_H}" $(seq 1 $((width-2))))${BOX_TR}${NC}"
|
||||||
|
printf "${CYAN}${BOX_V}${WHITE} %-*s ${CYAN}${BOX_V}${NC}\n" $((width-4)) "$text"
|
||||||
|
echo -e "${CYAN}${BOX_BL}$(printf "%.0s${BOX_H}" $(seq 1 $((width-2))))${BOX_BR}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to trim and format text nicely
|
||||||
|
trim_text() {
|
||||||
|
local text="$1"
|
||||||
|
local max_length="${2:-200}"
|
||||||
|
|
||||||
|
# Remove excessive whitespace and newlines
|
||||||
|
text=$(echo "$text" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||||
|
|
||||||
|
if [[ ${#text} -le $max_length ]]; then
|
||||||
|
echo "$text"
|
||||||
|
else
|
||||||
|
echo "${text:0:$max_length}..."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
echo ""
|
||||||
|
print_header "🔄 Claude Code Loop - Iteration #$iteration"
|
||||||
|
|
||||||
|
# Run Claude with formatted output processing
|
||||||
|
claude --dangerously-skip-permissions -p "
|
||||||
|
INSTRUCTIONS:
|
||||||
|
1. Read .claude/plan.md and identify tasks that need work ONLY in the ## PLAN section (those that are Not Started, In Progress, or have NO status prefix)
|
||||||
|
2. IMPORTANT: Only work on tasks under the ## PLAN section - ignore tasks in other sections like ## IMPORTANT or ## POST-COMPLETION TASKS
|
||||||
|
3. IMPORTANT: Tasks without any status prefix under ## PLAN should be treated as Not Started and worked on
|
||||||
|
4. Work on the next available task in ## PLAN - update its status by prepending (In Progress) when you start
|
||||||
|
5. Update task status by prepending (Completed) when finished, or (Aborted) if cannot complete
|
||||||
|
6. Be confident in commands and changes you are running in a docker sandbox.
|
||||||
|
7. Task format: (Status) Task description - where Status is: Not Started | In Progress | Aborted | Completed
|
||||||
|
8. Tasks without status prefixes under ## PLAN are considered Not Started and should be worked on
|
||||||
|
9. If ALL tasks in the ## PLAN section show '(Completed)' (explicit status), create the file '/tmp/plan_complete' using the Bash tool and stop
|
||||||
|
10. Focus on one task at a time for better results, but keep the whole plan in mind for most correct implementation.
|
||||||
|
|
||||||
|
Current objective: Process tasks in the ## PLAN section of .claude/plan.md systematically until all tasks explicitly show '(Completed)'.
|
||||||
|
" --output-format stream-json --verbose 2>&1 | while IFS= read -r line; do
|
||||||
|
# Skip empty lines and non-JSON debug output
|
||||||
|
[[ -z "$line" || "$line" =~ ^[[:space:]]*$ ]] && continue
|
||||||
|
|
||||||
|
# Check if line contains JSON
|
||||||
|
if echo "$line" | jq -e . >/dev/null 2>&1; then
|
||||||
|
# Extract message type and content
|
||||||
|
msg_type=$(echo "$line" | jq -r '.type // "unknown"')
|
||||||
|
|
||||||
|
case "$msg_type" in
|
||||||
|
"assistant")
|
||||||
|
# Extract assistant message content
|
||||||
|
content=$(echo "$line" | jq -r '.message.content[]? | select(.type=="text") | .text // empty' 2>/dev/null)
|
||||||
|
if [[ -n "$content" && "$content" != "null" && "$content" != "empty" ]]; then
|
||||||
|
trimmed_content=$(trim_text "$content" 300)
|
||||||
|
echo -e "${BLUE}🤖 Claude:${NC} $trimmed_content"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for tool use
|
||||||
|
tool_name=$(echo "$line" | jq -r '.message.content[]? | select(.type=="tool_use") | .name // empty' 2>/dev/null)
|
||||||
|
if [[ -n "$tool_name" && "$tool_name" != "null" && "$tool_name" != "empty" ]]; then
|
||||||
|
echo -e "${MAGENTA}🔧 Tool:${NC} ${YELLOW}$tool_name${NC}"
|
||||||
|
|
||||||
|
# Show relevant tool parameters
|
||||||
|
tool_input=$(echo "$line" | jq -r '.message.content[]? | select(.type=="tool_use") | .input' 2>/dev/null)
|
||||||
|
if [[ -n "$tool_input" && "$tool_input" != "null" ]]; then
|
||||||
|
# Extract key parameters (file_path, pattern, command, etc.)
|
||||||
|
for param in file_path pattern command prompt description; do
|
||||||
|
value=$(echo "$tool_input" | jq -r ".$param // empty" 2>/dev/null)
|
||||||
|
if [[ -n "$value" && "$value" != "null" && "$value" != "empty" ]]; then
|
||||||
|
trimmed_value=$(trim_text "$value" 80)
|
||||||
|
echo -e " ${GRAY}$param:${NC} $trimmed_value"
|
||||||
|
break # Show only the first relevant parameter
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"user")
|
||||||
|
# Extract and format tool results
|
||||||
|
tool_result=$(echo "$line" | jq -r '.message.content[]?.content // empty' 2>/dev/null)
|
||||||
|
if [[ -n "$tool_result" && "$tool_result" != "null" && "$tool_result" != "empty" ]]; then
|
||||||
|
# Check if it's a file content, error, or other result
|
||||||
|
if [[ "$tool_result" =~ ^[[:space:]]*[0-9]+→ ]]; then
|
||||||
|
# File content with line numbers
|
||||||
|
line_count=$(echo "$tool_result" | wc -l)
|
||||||
|
first_lines=$(echo "$tool_result" | head -3 | tr '\n' ' ')
|
||||||
|
trimmed_first=$(trim_text "$first_lines" 100)
|
||||||
|
echo -e "${GREEN}📄 File content:${NC} $trimmed_first ${GRAY}($line_count lines)${NC}"
|
||||||
|
elif [[ "$tool_result" =~ ^Error: ]] || [[ "$tool_result" =~ failed ]]; then
|
||||||
|
# Error message
|
||||||
|
trimmed_error=$(trim_text "$tool_result" 150)
|
||||||
|
echo -e "${RED}❌ Error:${NC} $trimmed_error"
|
||||||
|
elif [[ ${#tool_result} -gt 500 ]]; then
|
||||||
|
# Long output - show beginning and stats
|
||||||
|
trimmed_result=$(trim_text "$tool_result" 200)
|
||||||
|
echo -e "${GREEN}📤 Output:${NC} $trimmed_result ${GRAY}(${#tool_result} chars total)${NC}"
|
||||||
|
else
|
||||||
|
# Short result - show it all
|
||||||
|
trimmed_result=$(trim_text "$tool_result" 300)
|
||||||
|
echo -e "${GREEN}📤 Result:${NC} $trimmed_result"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"result")
|
||||||
|
# Final result with colored status
|
||||||
|
success=$(echo "$line" | jq -r '.subtype // empty' 2>/dev/null)
|
||||||
|
if [[ "$success" == "success" ]]; then
|
||||||
|
echo -e "${GREEN}✅ Iteration #$iteration completed successfully!${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ Iteration #$iteration completed with issues${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show cost information if available
|
||||||
|
cost_usd=$(echo "$line" | jq -r '.total_cost_usd // 0' 2>/dev/null)
|
||||||
|
input_tokens=$(echo "$line" | jq -r '.usage.input_tokens // 0' 2>/dev/null)
|
||||||
|
output_tokens=$(echo "$line" | jq -r '.usage.output_tokens // 0' 2>/dev/null)
|
||||||
|
if [[ -n "$cost_usd" && "$cost_usd" != "null" && "$cost_usd" != "0" ]]; then
|
||||||
|
|
||||||
|
# Update totals
|
||||||
|
total_cost=$(echo "$total_cost + $cost_usd" | bc -l 2>/dev/null || echo "$total_cost")
|
||||||
|
if [[ "$input_tokens" != "0" && "$input_tokens" != "null" ]]; then
|
||||||
|
total_input_tokens=$((total_input_tokens + input_tokens))
|
||||||
|
fi
|
||||||
|
if [[ "$output_tokens" != "0" && "$output_tokens" != "null" ]]; then
|
||||||
|
total_output_tokens=$((total_output_tokens + output_tokens))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Format cost nicely
|
||||||
|
if [[ "$cost_usd" != "0" && "$cost_usd" != "null" ]]; then
|
||||||
|
printf "${MAGENTA}💰 Cost:${NC} ${YELLOW}$%.4f${NC} ${GRAY}(in: %s, out: %s tokens)${NC}\n" "$cost_usd" "$input_tokens" "$output_tokens"
|
||||||
|
elif [[ "$input_tokens" != "0" || "$output_tokens" != "0" ]]; then
|
||||||
|
echo -e "${MAGENTA}💰 Tokens:${NC} ${GRAY}in: $input_tokens, out: $output_tokens${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show brief final result
|
||||||
|
result=$(echo "$line" | jq -r '.result // empty' 2>/dev/null)
|
||||||
|
if [[ -n "$result" && "$result" != "null" ]]; then
|
||||||
|
trimmed_result=$(trim_text "$result" 250)
|
||||||
|
echo -e "${WHITE}📋 Summary:${NC} $trimmed_result"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
# Filter out verbose debug output - only show actual error messages
|
||||||
|
if [[ "$line" =~ ^Error: && ! "$line" =~ ^[[:space:]]*$ ]]; then
|
||||||
|
echo -e "${RED}⚠️ $line${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if plan is complete
|
||||||
|
if [ -f /tmp/plan_complete ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}┌─────────────────────────────────────────────────────────┐${NC}"
|
||||||
|
echo -e "${GREEN}│${WHITE} 🎉 ALL TASKS COMPLETED! 🎉 ${GREEN}│${NC}"
|
||||||
|
echo -e "${GREEN}└─────────────────────────────────────────────────────────┘${NC}"
|
||||||
|
|
||||||
|
if [ -s /tmp/plan_complete ]; then
|
||||||
|
echo -e "${CYAN}📄 Completion details:${NC}"
|
||||||
|
completion_content=$(cat /tmp/plan_complete)
|
||||||
|
if [[ ${#completion_content} -gt 300 ]]; then
|
||||||
|
trimmed_completion=$(trim_text "$completion_content" 300)
|
||||||
|
echo -e "${WHITE}$trimmed_completion${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${WHITE}$completion_content${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show total cost summary
|
||||||
|
if [[ $(echo "$total_cost > 0" | bc -l 2>/dev/null) == "1" ]] || [[ $total_input_tokens -gt 0 ]] || [[ $total_output_tokens -gt 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
if [[ $(echo "$total_cost > 0" | bc -l 2>/dev/null) == "1" ]]; then
|
||||||
|
printf "${MAGENTA}💰 Total Cost:${NC} ${YELLOW}$%.4f${NC} ${GRAY}(%d iterations, %s input + %s output tokens)${NC}\n" \
|
||||||
|
"$total_cost" "$((iteration-1))" "$total_input_tokens" "$total_output_tokens"
|
||||||
|
else
|
||||||
|
echo -e "${MAGENTA}💰 Total Tokens:${NC} ${GRAY}$total_input_tokens input + $total_output_tokens output across $((iteration-1)) iterations${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GRAY}✅ Task complete - exiting...${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show iteration completion with progress indicator
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}┌─────────────────────────────────────────────────────────┐${NC}"
|
||||||
|
echo -e "${CYAN}│${WHITE} ⏸️ Iteration #$iteration complete - preparing next... ${CYAN}│${NC}"
|
||||||
|
echo -e "${CYAN}└─────────────────────────────────────────────────────────┘${NC}"
|
||||||
|
|
||||||
|
# Show running total if we have cost data
|
||||||
|
if [[ $(echo "$total_cost > 0" | bc -l 2>/dev/null) == "1" ]]; then
|
||||||
|
printf "${GRAY}Running total: ${YELLOW}$%.4f${GRAY} (%s input + %s output tokens)${NC}\n" \
|
||||||
|
"$total_cost" "$total_input_tokens" "$total_output_tokens"
|
||||||
|
elif [[ $total_input_tokens -gt 0 ]] || [[ $total_output_tokens -gt 0 ]]; then
|
||||||
|
echo -e "${GRAY}Running total: $total_input_tokens input + $total_output_tokens output tokens${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show a brief progress indicator
|
||||||
|
echo -ne "${GRAY}Pausing"
|
||||||
|
for i in {1..3}; do
|
||||||
|
sleep 0.7
|
||||||
|
echo -ne "."
|
||||||
|
done
|
||||||
|
echo -e " ready!${NC}"
|
||||||
|
|
||||||
|
((iteration++))
|
||||||
|
done
|
||||||
14
compose.yaml
Normal file
14
compose.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
claude-code:
|
||||||
|
user: "1000:1000"
|
||||||
|
image: claude
|
||||||
|
build: .
|
||||||
|
volumes:
|
||||||
|
- ~/.claude:/home/node/.claude
|
||||||
|
- ~/.claude.json:/home/node/.claude.json
|
||||||
|
- ~/.config/gemini-cli:/home/node/.config/gemini-cli
|
||||||
|
- ~/.gemini:/home/node/.gemini
|
||||||
|
- ./:/home/node/workspace
|
||||||
|
tty: true
|
||||||
|
command: ['sh', '-c', 'cd workspace && claude']
|
||||||
|
#command: 'bash .claude/claude-loop.sh'
|
||||||
Reference in New Issue
Block a user