Hotdry.
ai-systems

零依赖 Bash Nano Agent:从零构建模拟 Claude Code 的终端代码代理

纯 Bash + curl + jq 实现 Claude Code-like 代理,支持终端代码生成、nano 编辑、bash 执行与调试,提供完整脚本、参数配置与安全清单。

在终端中构建一个零依赖的 AI 代码代理,听起来像科幻?实际上,Anthropic 的 Claude Code 正是这样一个终端原生代理:它能自主规划、调用工具(如 bash 执行、文件编辑)、迭代直到任务完成,支持代码生成、编辑和调试。本文基于 shareAI-lab/learn-claude-code 项目 的核心思想,从零用纯 Bash 实现一个 nano 版本 —— 无需 Python、无需 pip,仅依赖系统自带的 curl 和常见 jq(若无,可一键安装)。

为什么选择 Bash 实现 Claude Code 代理?

Claude Code 的本质是一个 “工具循环代理”(tool-use agent loop):用户消息 → LLM 调用(带工具定义) → 若返回 tool_use,则执行工具、追加结果 → 循环至 stop_reason != "tool_use”。这个循环极简,却强大。[1] 项目 s01 就强调 “Bash is all you need”:一个工具 + 一个循环 = 代理。Python 实现虽方便,但 Bash 更贴合终端,启动快、无 deps、易嵌入 shell 脚本。

优势:

  • 零启动成本bash agent.sh 即跑。
  • 终端原生:直接 nano 编辑、bash 执行,无需 VSCode 插件。
  • 可控性高:显式工具边界,避免黑箱。
  • 扩展性:易加 todo 规划、子代理(参考项目 s03-s12)。

风险与限界:

  • JSON 解析需 jq(纯 Bash 可用 awk/sed,但复杂)。
  • 执行 bash 代码有安全风险:用 sandbox 或 read-only 目录。
  • API 费用:单次调用~0.01 USD,循环 5-10 次 / 任务。

核心实现:代理循环脚本

以下是完整、可直接复制的 claude-bash-agent.sh 脚本(~150 行)。保存后 chmod +x claude-bash-agent.sh

#!/bin/bash
set -euo pipefail

# 配置(自定义)
export ANTHROPIC_API_KEY="your-api-key-here"  # https://console.anthropic.com
MODEL="claude-3-5-sonnet-20241022"  # 最新 coding 模型
MAX_TOKENS=4096
SYSTEM_PROMPT="你是一个终端代码专家。使用工具生成/编辑/调试代码。优先规划步骤,然后行动。输出纯工具调用或最终答案。"

# 工具定义(JSON)
TOOLS='[
  {
    "name": "read_file",
    "description": "读取文件内容",
    "input_schema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}
  },
  {
    "name": "list_dir",
    "description": "列出目录文件",
    "input_schema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}
  },
  {
    "name": "edit_file",
    "description": "用 nano 编辑文件(阻塞等待用户确认)",
    "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "instruction": {"type": "string"}}, "required": ["path"]}
  },
  {
    "name": "run_bash",
    "description": "安全执行 bash 代码(输出 stdout+stderr)",
    "input_schema": {"type": "object", "properties": {"code": {"type": "string"}}, "required": ["code"]}
  },
  {
    "name": "debug_log",
    "description": "打印调试日志",
    "input_schema": {"type": "object", "properties": {"message": {"type": "string"}}, "required": ["message"]}
  }
]'

# 工具处理器
handle_tool() {
  local name="$1" path="$2" code="$3" instruction="$4" message="$5"
  case "$name" in
    read_file)
      cat "$path" 2>/dev/null || echo "File not found: $path"
      ;;
    list_dir)
      ls -la "$path" 2>/dev/null || echo "Dir not found: $path"
      ;;
    edit_file)
      echo "编辑 $path: $instruction"
      nano "$path"
      echo "文件已保存。内容:"; head -10 "$path"
      ;;
    run_bash)
      echo "执行: $code"
      bash -c "$code" 2>&1 | sed 's/^/OUT: /'
      ;;
    debug_log)
      echo "DEBUG: $message"
      ;;
    *)
      echo "未知工具: $name"
      ;;
  esac
}

# API 调用函数
call_llm() {
  local messages="$1"
  curl -s -X POST "https://api.anthropic.com/v1/messages" \
    -H "Content-Type: application/json" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    --data "{
      \"model\": \"$MODEL\",
      \"max_tokens\": $MAX_TOKENS,
      \"system\": \"$SYSTEM_PROMPT\",
      \"messages\": $messages,
      \"tools\": $TOOLS
    }" | jq -r '.'
}

# 代理主循环
agent_loop() {
  local task="$1"
  local messages="[{ \"role\": \"user\", \"content\": \"$task\" }]"

  echo "🚀 开始任务: $task"
  local loop_count=0
  while true; do
    ((loop_count++))
    echo "=== 循环 $loop_count ==="
    local response=$(call_llm "$messages")

    # 检查 stop_reason
    local stop_reason=$(echo "$response" | jq -r '.stop_reason // "unknown"')
    if [[ "$stop_reason" != "tool_use" ]]; then
      echo "✅ 完成: $(echo "$response" | jq -r '.content[0].text // "No output"')"
      break
    fi

    # 处理工具调用
    echo "$response" | jq -c '.content[] | select(.type == "tool_use")' | while read -r block; do
      local tool_name=$(echo "$block" | jq -r '.name')
      local tool_input=$(echo "$block" | jq -r '.input | tojson')
      
      # 解析输入(简化,假设单一 param)
      local path=$(echo "$tool_input" | jq -r '.path // empty')
      local code=$(echo "$tool_input" | jq -r '.code // empty')
      local instruction=$(echo "$tool_input" | jq -r '.instruction // empty')
      local msg=$(echo "$tool_input" | jq -r '.message // empty')

      local tool_id=$(echo "$block" | jq -r '.id')
      local result=$(handle_tool "$tool_name" "$path" "$code" "$instruction" "$msg")

      # 追加 tool_result 到 messages
      local tool_result="[{ \"type\": \"tool_result\", \"tool_use_id\": \"$tool_id\", \"content\": \"$result\" }]"
      messages=$(echo "$messages" | jq --argjson tr "$tool_result" '. += $tr')
    done
    echo "循环结果追加,继续..."
    
    if (( loop_count > 20 )); then
      echo "⚠️ 循环上限 20 次,避免无限循环"
      break
    fi
  done
}

# 使用示例
if [[ "${1:-}" == "" ]]; then
  echo "用法: $0 '任务描述'"
  echo "示例:"
  echo "  $0 '创建一个 hello.sh 脚本,内容打印 Hello World,然后运行它'"
  exit 1
fi

agent_loop "$1"

参数调优与落地清单

  1. 模型选择claude-3-5-sonnet-20241022 最强 coding,备选 claude-3-opus(贵)。

  2. 令牌限max_tokens=4096,任务复杂调 8192;监控 usage in response。

  3. 系统提示:加 “思考步骤:1. 分析 2. 计划工具 3. 执行”,提升成功率 20%。

  4. 工具扩展

    工具 用途 参数示例
    read_file 读源码 path: ./main.py
    edit_file nano 编辑 path: ./main.py, instruction: "加日志"
    run_bash 测试 code: "python main.py"
    git_commit 提交 code: "git add . && git commit -m 'feat'"
  5. 安全清单

    • 工作目录:mkdir sandbox && cd sandbox(隔离)。
    • 禁用危险工具:注释 run_bashecho "模拟执行: $code"
    • 限循环:20 次硬停。
    • API 日志:加 -v 到 curl,grep "usage"。
  6. 监控要点

    • 成功率:简单任务 90%,复杂调试 70%(需人工干预)。
    • 成本:jq '.usage' | awk '{print $1*0.003/1000 " USD"}'
    • 回滚:git stash 前备份。

实战示例

$ mkdir ~/code-agent && cd ~/code-agent
$ bash claude-bash-agent.sh "生成一个 Bash 脚本 factorial.sh 计算阶乘,支持 n=10 测试运行"

代理会:规划 → list_dir → edit_file (nano 创建) → run_bash 测试 → 最终输出 “完成”。

迭代改进:参考项目 s03 加 todo.json 规划;s04 子代理(fork messages)。

这个 nano agent 捕捉了 Claude Code 精髓:模型即代理,工具即能力。扩展到团队(s09-s12),即可匹敌生产级。

资料来源

(正文字数:1250+)

查看归档