技术教程 · 阅读约 11 分钟

把 Claude Code 包成桌面 App?我直接用 50 行 Python 实现了同款效果

把 Claude Code 包成桌面 App?我直接用 50 行 Python 实现了同款效果

很多人看到那个"把 Claude Code 包成桌面 App"的帖子都觉得很酷——不用装 CLI、不碰终端、发一句话它自己干活。

但仔细想想:你真正需要的是一个 App,还是"发一句话让 AI 自己改代码"这个能力本身?

后者其实 50 行 Python 就够了。而且你能完全控制它在做什么,不是黑盒。


核心思路

Claude 的 tool use(工具调用)功能允许模型主动调用你定义的函数。我们只需要给它三个工具:

  • read_file:读取文件内容
  • write_file:写入修改后的内容
  • list_files:列出目录结构

然后发一句"帮我把这个项目里所有 print 换成 logging",它会自己决定先看哪些文件、改哪里、写回去。


完整代码


import os

import json

from openai import OpenAI



# 用无量Api,国内直连 Claude,比官方省 60%

client = OpenAI(

    api_key=os.environ.get("WULIANG_API_KEY"),

    base_url="https://api2everything.xyz/v1"

)



# ── 工具定义 ──────────────────────────────────────────

tools = [

    {

        "type": "function",

        "function": {

            "name": "read_file",

            "description": "读取指定路径的文件内容",

            "parameters": {

                "type": "object",

                "properties": {

                    "path": {"type": "string", "description": "文件相对路径"}

                },

                "required": ["path"]

            }

        }

    },

    {

        "type": "function",

        "function": {

            "name": "write_file",

            "description": "将内容写入指定路径的文件(会覆盖原文件)",

            "parameters": {

                "type": "object",

                "properties": {

                    "path": {"type": "string"},

                    "content": {"type": "string"}

                },

                "required": ["path", "content"]

            }

        }

    },

    {

        "type": "function",

        "function": {

            "name": "list_files",

            "description": "列出目录下所有文件(递归)",

            "parameters": {

                "type": "object",

                "properties": {

                    "directory": {"type": "string", "description": "目录路径,默认 ."}

                },

                "required": []

            }

        }

    }

]



# ── 工具执行层 ────────────────────────────────────────

def execute_tool(name: str, args: dict) -> str:

    if name == "read_file":

        path = args["path"]

        if not os.path.exists(path):

            return f"错误:文件 {path} 不存在"

        with open(path, "r", encoding="utf-8") as f:

            return f.read()



    elif name == "write_file":

        path = args["path"]

        os.makedirs(os.path.dirname(path) or ".", exist_ok=True)

        with open(path, "w", encoding="utf-8") as f:

            f.write(args["content"])

        return f"已写入 {path}"



    elif name == "list_files":

        directory = args.get("directory", ".")

        result = []

        for root, dirs, files in os.walk(directory):

            # 跳过常见无关目录

            dirs[:] = [d for d in dirs if d not in {".git", "__pycache__", "node_modules", ".venv"}]

            for file in files:

                result.append(os.path.join(root, file))

        return "\n".join(result)



    return f"未知工具:{name}"



# ── Agent 主循环 ──────────────────────────────────────

def run_agent(task: str, model: str = "claude-sonnet-4-5"):

    messages = [

        {

            "role": "system",

            "content": "你是一个代码助手,可以读取和修改本地文件。操作前先用 list_files 了解项目结构,再用 read_file 读取相关文件,最后用 write_file 写入修改。每次修改后告知用户改了什么。"

        },

        {"role": "user", "content": task}

    ]



    print(f"\n任务:{task}\n{'─' * 50}")



    while True:

        response = client.chat.completions.create(

            model=model,

            messages=messages,

            tools=tools,

            tool_choice="auto"

        )



        msg = response.choices[0].message



        # 没有工具调用,说明任务完成

        if not msg.tool_calls:

            print(f"\nClaude:{msg.content}")

            break



        # 执行所有工具调用

        messages.append(msg)

        tool_results = []



        for call in msg.tool_calls:

            args = json.loads(call.function.arguments)

            print(f"  → 调用 {call.function.name}({args})")

            result = execute_tool(call.function.name, args)



            tool_results.append({

                "role": "tool",

                "tool_call_id": call.id,

                "content": result

            })



        messages.extend(tool_results)



# ── 入口 ─────────────────────────────────────────────

if __name__ == "__main__":

    import sys

    task = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "列出项目结构,然后告诉我有哪些 Python 文件"

    run_agent(task)


跑起来试试


# 安装依赖

pip install openai



# 设置 API Key(去无量Api注册,送1元余额)

export WULIANG_API_KEY="sk-xxx"



# 发任务

python agent.py "帮我检查所有 .py 文件,找出没有异常处理的 open() 调用,并加上 try/except"

实际运行效果大概是这样:


任务:帮我检查所有 .py 文件,找出没有异常处理的 open() 调用,并加上 try/except

──────────────────────────────────────────────────

  → 调用 list_files({'directory': '.'})

  → 调用 read_file({'path': './utils.py'})

  → 调用 read_file({'path': './main.py'})

  → 调用 write_file({'path': './utils.py', 'content': '...'})



Claude:已修改 utils.py 中第 23 行的 open() 调用,添加了 try/except FileNotFoundError 处理。main.py 中的 open() 已有异常处理,无需修改。


几个值得注意的细节

安全边界:这个 agent 有写文件权限,建议在 git 仓库里跑,改坏了能回滚。生产环境用之前加一个"写入前先备份"的逻辑。

模型选择:claude-sonnet-4-5 在工具调用上表现稳定,任务复杂时可以换 claude-opus-4-5,但 token 消耗会多不少。无量Api 上这两个模型都有,价格比官方低 60% 以上。

循环上限:真实使用时建议加一个最大循环次数(比如 20 轮),防止模型陷入死循环一直调工具。

并行工具调用:Claude 支持一次返回多个 tool_call,上面的代码已经处理了这种情况(for call in msg.tool_calls),不用担心。


扩展方向

加上 run_command 工具(执行 shell 命令),就能让它自己跑测试、看报错、再改代码,形成完整的 agentic 循环——这基本就是 Claude Code 的核心逻辑了。

感兴趣的话评论区说一声,下篇可以写加入测试循环的完整版本。


关于 API 费用:文中代码用的是 无量Api,国内直连不需要梯子,支持 Claude / GPT / Gemini / DeepSeek 等 300+ 模型,注册送 ¥1 余额,SVIP 价格是官方的 7.7 折。改一行 base_url 就能接入,和官方 SDK 完全兼容。

觉得有用的话点个赞,有问题直接评论区问。