折腾笔记[48]-通过WriteFile和ReadFile间接调用Shell

摘要

在Windows上配置kimi-cli通过WriteFile和ReadFile间接调用Shell, 解决Tool Runtime Error问题.

声明

本文人类为第一作者, 龙虾为通讯作者.本文有AI生成内容.

kimi-shell-skill技能

README.md

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Shell Proxy Skill</title>
</head>
<body>
<h1>Shell Proxy Skill</h1>
<p>通过 <code>shell_proxy.py</code> 代理脚本,实现 <strong>Kimi</strong> 与系统 Shell 的持久化交互,解决受限环境下无法直接使用 Shell 工具的问题。</p>
<blockquote>
<p><strong>适用场景</strong>:Kimi 的 <code>Shell</code> 工具不可用(如 <code>[WinError 2] 系统找不到指定的文件</code>),需要通过文件管道间接执行系统命令。</p>
</blockquote>
<hr>
<h2>架构</h2>
<pre><code>┌─────────┐  WriteFile    ┌─────────────────┐  执行命令    ┌──────────┐
│  Kimi   │ ────────────→ │ shell_proxy.py  │ ─────────→ │  Shell   │
│ (Agent) │   cmd_in.txt  │   (用户启动)     │            │(CMD/      │
│         │ ←──────────── │                 │ ←───────── │ PowerShell│
└─────────┘  ReadFile     └─────────────────┘  返回结果   └──────────┘
              cmd_out.txt
</code></pre>
<hr>
<h2>使用步骤</h2>
<h3>第一步:用户启动代理</h3>
<p>在终端中切换到工作目录,运行代理脚本:</p>
<pre><code class="language-bash"># 示例:在 tests 目录启动代理
PS W:\...\CudaSharp\tests&gt; python .\kimi-shell-skill\scripts\shell_proxy.py
</code></pre>
<p>代理启动后会显示关键信息:</p>
<pre><code>[shell_proxy] Shell代理已启动
[shell_proxy] 使用的Shell: C:\Windows\System32\cmd.exe
[shell_proxy] 命令输入文件: W:\...\CudaSharp\tests\cmd_in.txt      ← Kimi 写入这里
[shell_proxy] 结果输出文件: W:\...\CudaSharp\tests\cmd_out.txt     ← Kimi 读取这里
[shell_proxy] 轮询间隔: 0.5s
[shell_proxy] 等待命令中...
</code></pre>
<blockquote>
<p><strong>重要</strong>:记录 <code>命令输入文件</code> 和 <code>结果输出文件</code> 的<strong>绝对路径</strong>,Kimi 需要用到。</p>
</blockquote>
<hr>
<h3>第二步:Kimi 发送命令</h3>
<p>Kimi 通过 <code>WriteFile</code> 向代理的<strong>命令输入文件</strong>(<code>cmd_in.txt</code>)写入 JSON 格式的命令:</p>
<pre><code class="language-json">{"command": "dir"}
</code></pre>
<p><strong>命令格式</strong>:</p>
<table>
<thead>
<tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>command</code></td><td>string</td><td>是</td><td>要执行的 Shell 命令</td></tr>
<tr><td><code>timeout</code></td><td>int</td><td>否</td><td>超时秒数,默认 30</td></tr>
<tr><td><code>work_dir</code></td><td>string</td><td>否</td><td>执行前切换的工作目录</td></tr>
</tbody>
</table>
<p><strong>示例命令</strong>:</p>
<pre><code class="language-json">// 查看当前目录(CMD)
{"command": "dir"}

// 查看当前目录(PowerShell)
{"command": "Get-ChildItem"}

// 创建文件夹
{"command": "mkdir hello"}

// 切换目录后执行
{"command": "dir", "work_dir": "C:\\Users"}

// 设置超时
{"command": "timeout /t 5", "timeout": 10}
</code></pre>
<blockquote>
<p><strong>注意</strong>:命令语法必须与代理使用的 Shell 匹配。代理启动日志会显示 <code>使用的Shell</code>:</p>
<ul>
<li><code>cmd.exe</code> → 使用 <code>dir</code>, <code>cd</code>, <code>mkdir</code> 等 CMD 命令</li>
<li><code>powershell.exe</code> → 使用 <code>Get-Location</code>, <code>New-Item</code> 等 PowerShell 命令</li>
</ul>
</blockquote>
<hr>
<h3>第三步:代理自动执行</h3>
<p>代理以 0.5 秒为间隔轮询 <code>cmd_in.txt</code>:</p>
<ol>
<li><strong>检测到文件</strong> → 读取 JSON 命令</li>
<li><strong>执行命令</strong> → 调用系统 Shell</li>
<li><strong>写入结果</strong> → 将结果写入 <code>cmd_out.txt</code></li>
<li><strong>删除命令文件</strong> → 清理 <code>cmd_in.txt</code></li>
</ol>
<p>此过程<strong>完全自动</strong>,无需用户干预。</p>
<hr>
<h3>第四步:Kimi 读取结果</h3>
<p>Kimi 通过 <code>ReadFile</code> 从代理的<strong>结果输出文件</strong>(<code>cmd_out.txt</code>)读取 JSON 结果:</p>
<pre><code class="language-json">{
  "status": "success",
  "stdout": " 驱动器 W 中的卷没有标签。\n ...",
  "stderr": "",
  "exit_code": 0,
  "timeout": false,
  "command": "dir"
}
</code></pre>
<p><strong>响应字段</strong>:</p>
<table>
<thead>
<tr><th>字段</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>status</code></td><td><code>success</code> / <code>error</code></td></tr>
<tr><td><code>stdout</code></td><td>标准输出内容</td></tr>
<tr><td><code>stderr</code></td><td>标准错误内容</td></tr>
<tr><td><code>exit_code</code></td><td>退出码(<code>0</code> 表示成功,<code>-1</code> 表示超时或异常)</td></tr>
<tr><td><code>timeout</code></td><td>是否超时</td></tr>
<tr><td><code>command</code></td><td>原始命令</td></tr>
</tbody>
</table>
<hr>
<h2>完整交互示例</h2>
<h3>场景:在 <code>W:\...\CudaSharp\tests</code> 目录执行 <code>dir</code> 命令</h3>
<h4>用户操作</h4>
<pre><code class="language-bash"># 1. 打开终端,进入工作目录
PS W:\...\CudaSharp\tests&gt; python .\kimi-shell-skill\scripts\shell_proxy.py

# 2. 代理启动,显示文件路径
[shell_proxy] 命令输入文件: W:\...\CudaSharp\tests\cmd_in.txt
[shell_proxy] 结果输出文件: W:\...\CudaSharp\tests\cmd_out.txt
[shell_proxy] 等待命令中...

# 3. 保持终端运行,等待 Kimi 交互...
</code></pre>
<h4>Kimi 操作</h4>
<pre><code class="language-python"># 1. 写入命令到代理的命令输入文件
WriteFile(path="W:\...\CudaSharp\tests\cmd_in.txt", content='{"command": "dir"}\n')

# 2. 等待代理执行(约 0.5~1 秒)

# 3. 从结果输出文件读取结果
ReadFile(path="W:\...\CudaSharp\tests\cmd_out.txt")
# 返回:
# {
#   "status": "success",
#   "stdout": " 驱动器 W 中的卷没有标签。\n ...",
#   "stderr": "",
#   "exit_code": 0,
#   "timeout": false,
#   "command": "dir"
# }
</code></pre>
<hr>
<h2>使用注意事项</h2>
<h3>⚠️ 文件路径必须与代理工作目录一致</h3>
<p>代理启动时会打印实际轮询的文件路径,<strong>Kimi 必须将 <code>cmd_in.txt</code> 写入代理工作目录下</strong>,而非脚本所在目录。</p>
<p><strong>示例</strong>:</p>
<pre><code># 代理在 tests 目录启动
PS W:\...\CudaSharp\tests&gt; python .\kimi-shell-skill\scripts\shell_proxy.py
[shell_proxy] 命令输入文件: W:\...\CudaSharp\tests\cmd_in.txt   ← 代理轮询这里

# ❌ 错误:写入脚本同级目录
WriteFile → kimi-shell-skill/cmd_in.txt

# ✅ 正确:写入代理工作目录
WriteFile → W:\...\CudaSharp\tests\cmd_in.txt
</code></pre>
<h3>⚠️ 命令语法必须与 Shell 类型匹配</h3>
<p>代理会自动检测可用 Shell,启动日志会显示实际使用的 Shell:</p>
<ul>
<li><code>cmd.exe</code> → 仅支持 CMD 语法(<code>dir</code>, <code>cd</code>, <code>mkdir</code>, <code>type</code> 等)</li>
<li><code>powershell.exe</code> / <code>pwsh.exe</code> → 支持 PowerShell 语法(<code>Get-Location</code>, <code>New-Item</code> 等)</li>
</ul>
<p><strong>示例</strong>:</p>
<pre><code>[shell_proxy] 使用的Shell: C:\Windows\System32\cmd.exe   ← CMD 环境
# 此时应使用:  {"command": "mkdir hello"}
# 而非:        {"command": "New-Item -ItemType Directory hello"}
</code></pre>
<h3>⚠️ 命令文件会被自动删除</h3>
<p>代理读取 <code>cmd_in.txt</code> 后会<strong>立即删除</strong>该文件。如果文件一直存在,说明代理未检测到(路径错误或代理未启动)。</p>
<h3>⚠️ 代理需要保持运行</h3>
<p>代理是一个<strong>长期运行</strong>的进程,用户终端需要保持打开。如果代理被关闭,Kimi 将无法再执行命令。</p>
<hr>
<h2>环境变量配置</h2>
<table>
<thead>
<tr><th>变量名</th><th>默认值</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>SHELL_PROXY_IN</code></td><td><code>cmd_in.txt</code></td><td>命令输入文件路径</td></tr>
<tr><td><code>SHELL_PROXY_OUT</code></td><td><code>cmd_out.txt</code></td><td>结果输出文件路径</td></tr>
<tr><td><code>SHELL_PROXY_POLL</code></td><td><code>0.5</code></td><td>轮询间隔(秒)</td></tr>
</tbody>
</table>
<p><strong>自定义示例</strong>:</p>
<pre><code class="language-bash">set SHELL_PROXY_IN=my_cmd.txt
set SHELL_PROXY_OUT=my_result.txt
set SHELL_PROXY_POLL=1.0
python scripts/shell_proxy.py
</code></pre>
<hr>
<h2>跨平台支持</h2>
<p>脚本会自动检测可用的 Shell:</p>
<table>
<thead>
<tr><th>平台</th><th>检测顺序</th></tr>
</thead>
<tbody>
<tr><td><strong>Windows</strong></td><td><code>pwsh.exe</code> → <code>powershell.exe</code> → <code>cmd.exe</code></td></tr>
<tr><td><strong>Linux</strong></td><td><code>bash</code> → <code>sh</code></td></tr>
<tr><td><strong>macOS</strong></td><td><code>bash</code> → <code>sh</code> → <code>zsh</code></td></tr>
</tbody>
</table>
<hr>
<h2>常见问题</h2>
<h3>Q1: 代理显示 "等待命令中..." 但命令不执行</h3>
<p><strong>原因</strong>:<code>cmd_in.txt</code> 没有写入代理工作目录。</p>
<p><strong>解决</strong>:查看代理启动日志中的 <code>命令输入文件</code> 绝对路径,确保 Kimi 的 <code>WriteFile</code> 目标路径与之完全一致。</p>
<h3>Q2: 命令执行报错 "不是内部或外部命令"</h3>
<p><strong>原因</strong>:代理使用的是 <code>cmd.exe</code>,但命令用了 PowerShell 语法。</p>
<p><strong>解决</strong>:根据代理启动日志中的 <code>使用的Shell</code> 选择对应语法。</p>
<h3>Q3: 如何指定代理使用 PowerShell?</h3>
<p><strong>解决</strong>:在启动代理前确保 <code>powershell.exe</code> 在 PATH 中:</p>
<pre><code class="language-bash">set PATH=%PATH%;C:\Windows\System32\WindowsPowerShell\v1.0
python scripts/shell_proxy.py
</code></pre>
<h3>Q4: 中文显示乱码</h3>
<p><strong>解决</strong>:脚本已自动根据 Shell 类型选择编码(CMD 用 GBK,PowerShell 用 UTF-8)。如仍有问题,请检查系统区域设置。</p>
<hr>
<h2>文件说明</h2>
<table>
<thead>
<tr><th>文件</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>scripts/shell_proxy.py</code></td><td>Shell 代理主脚本</td></tr>
<tr><td><code>README.md</code></td><td>本说明文档(用户 + Kimi 双角色指南)</td></tr>
<tr><td><code>SKILL.md</code></td><td>Skill 技术文档</td></tr>
<tr><td><code>cmd_in.txt</code></td><td>命令输入文件(运行时由 Kimi 写入)</td></tr>
<tr><td><code>cmd_out.txt</code></td><td>结果输出文件(运行时由代理写入)</td></tr>
</tbody>
</table>
</body>
</html>

SKILL.md

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Shell Proxy Skill</title>
</head>
<body>
<h1>Shell Proxy Skill</h1>
<p>通过 <code>shell_proxy.py</code> 代理脚本,实现 Kimi 与系统 Shell 的持久化交互。</p>
<blockquote>
<p><strong>注意</strong>:当前环境无法直接使用 <code>Shell</code> 工具执行命令(<code>[WinError 2] 系统找不到指定的文件</code>),因此采用<strong>文件管道</strong>方式间接操作 Shell。</p>
</blockquote>
<hr>
<h2>架构</h2>
<pre><code>┌─────────┐  WriteFile    ┌─────────────────┐  转发命令    ┌──────────┐
│  Kimi   │ ────────────→ │ shell_proxy.py  │ ─────────→ │  Shell   │
│ (Agent) │   cmd_in.txt  │   (用户启动)     │            │(PowerShell│
│         │ ←──────────── │                 │ ←───────── │ /Bash/...)│
└─────────┘  ReadFile     └─────────────────┘  返回结果   └──────────┘
              cmd_out.txt
</code></pre>
<p><strong>工作流程:</strong></p>
<ol>
<li><strong>用户启动代理</strong>:<code>python scripts/shell_proxy.py</code></li>
<li><strong>Kimi 写命令</strong>:向 <code>cmd_in.txt</code> 写入 JSON 命令</li>
<li><strong>代理自动执行</strong>:检测到 <code>cmd_in.txt</code> 后读取、执行、删除命令文件</li>
<li><strong>Kimi 读结果</strong>:从 <code>cmd_out.txt</code> 读取 JSON 结果</li>
</ol>
<hr>
<h2>使用注意事项</h2>
<h3>⚠️ 命令文件路径必须与代理工作目录一致</h3>
<p>代理启动时会打印实际轮询的文件路径,<strong>Kimi 必须将 <code>cmd_in.txt</code> 写入代理工作目录下</strong>,而非脚本所在目录。</p>
<p><strong>示例:</strong></p>
<pre><code># 代理在 tests 目录启动
PS W:\...\CudaSharp\tests&gt; python .\kimi-shell-skill\scripts\shell_proxy.py
[shell_proxy] 命令输入文件: W:\...\CudaSharp\tests\cmd_in.txt   ← 代理轮询这里

# ❌ 错误:写入脚本同级目录
WriteFile → kimi-shell-skill/cmd_in.txt

# ✅ 正确:写入代理工作目录
WriteFile → cmd_in.txt  (即 W:\...\CudaSharp\tests\cmd_in.txt)
</code></pre>
<h3>⚠️ 代理使用的 Shell 取决于环境</h3>
<p>代理会自动检测可用 Shell,启动日志会显示实际使用的 Shell:</p>
<ul>
<li><code>powershell.exe</code> / <code>pwsh.exe</code> → 支持 PowerShell 语法(<code>Get-Location</code>, <code>mkdir</code> 等)</li>
<li><code>cmd.exe</code> → 仅支持 CMD 语法(<code>cd</code>, <code>dir</code>, <code>mkdir</code> 等)</li>
</ul>
<p><strong>示例:</strong></p>
<pre><code>[shell_proxy] 使用的Shell: C:\Windows\System32\cmd.exe   ← CMD 环境
# 此时应使用:  {"command": "mkdir hello"}
# 而非:        {"command": "New-Item -ItemType Directory hello"}
</code></pre>
<h3>⚠️ 命令文件会被自动删除</h3>
<p>代理读取 <code>cmd_in.txt</code> 后会<strong>立即删除</strong>该文件。如果文件一直存在,说明代理未检测到(路径错误或代理未启动)。</p>
<hr>
<h2>快速开始</h2>
<h3>1. 用户启动代理</h3>
<p>在终端中运行:</p>
<pre><code class="language-bash">python scripts/shell_proxy.py
</code></pre>
<p>代理启动后会显示:</p>
<pre><code>[shell_proxy] Shell代理已启动
[shell_proxy] 使用的Shell: C:\Windows\System32\cmd.exe
[shell_proxy] 命令输入文件: W:\...\CudaSharp\tests\cmd_in.txt
[shell_proxy] 结果输出文件: W:\...\CudaSharp\tests\cmd_out.txt
[shell_proxy] 轮询间隔: 0.5s
[shell_proxy] 等待命令中...
</code></pre>
<blockquote>
<p><strong>注意 <code>命令输入文件</code> 的绝对路径</strong>,Kimi 必须将命令写入该路径。</p>
</blockquote>
<h3>2. Kimi 发送命令</h3>
<p>Kimi 通过 <code>WriteFile</code> 向代理工作目录下的 <code>cmd_in.txt</code> 写入 JSON 命令:</p>
<pre><code class="language-json">{"command": "mkdir hello"}
</code></pre>
<p><strong>命令格式说明:</strong></p>
<table>
<thead>
<tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>command</code></td><td>string</td><td>是</td><td>要执行的 Shell 命令</td></tr>
<tr><td><code>timeout</code></td><td>int</td><td>否</td><td>超时秒数,默认 30</td></tr>
<tr><td><code>work_dir</code></td><td>string</td><td>否</td><td>执行前切换的工作目录</td></tr>
</tbody>
</table>
<h3>3. Kimi 读取结果</h3>
<p>代理执行完成后,向 <code>cmd_out.txt</code> 写入 JSON 结果:</p>
<pre><code class="language-json">{
  "status": "success",
  "stdout": "",
  "stderr": "",
  "exit_code": 0,
  "timeout": false,
  "command": "mkdir hello"
}
</code></pre>
<p><strong>响应字段说明:</strong></p>
<table>
<thead>
<tr><th>字段</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>status</code></td><td><code>success</code> / <code>error</code></td></tr>
<tr><td><code>stdout</code></td><td>标准输出内容</td></tr>
<tr><td><code>stderr</code></td><td>标准错误内容</td></tr>
<tr><td><code>exit_code</code></td><td>退出码(<code>-1</code> 表示超时)</td></tr>
<tr><td><code>timeout</code></td><td>是否超时</td></tr>
<tr><td><code>command</code></td><td>原始命令</td></tr>
</tbody>
</table>
<hr>
<h2>使用示例</h2>
<h3>示例 1:创建文件夹</h3>
<p><strong>Kimi 写入</strong> (<code>cmd_in.txt</code>):</p>
<pre><code class="language-json">{"command": "mkdir hello"}
</code></pre>
<p><strong>代理执行后,Kimi 读取</strong> (<code>cmd_out.txt</code>):</p>
<pre><code class="language-json">{
  "status": "success",
  "stdout": "",
  "stderr": "",
  "exit_code": 0,
  "timeout": false,
  "command": "mkdir hello"
}
</code></pre>
<h3>示例 2:查看当前目录(CMD)</h3>
<p><strong>Kimi 写入</strong> (<code>cmd_in.txt</code>):</p>
<pre><code class="language-json">{"command": "cd"}
</code></pre>
<h3>示例 3:查看当前目录(PowerShell)</h3>
<p><strong>Kimi 写入</strong> (<code>cmd_in.txt</code>):</p>
<pre><code class="language-json">{"command": "Get-Location"}
</code></pre>
<h3>示例 4:切换目录后执行</h3>
<p><strong>Kimi 写入</strong> (<code>cmd_in.txt</code>):</p>
<pre><code class="language-json">{"command": "dir", "work_dir": "C:\\Users"}
</code></pre>
<h3>示例 5:设置超时</h3>
<p><strong>Kimi 写入</strong> (<code>cmd_in.txt</code>):</p>
<pre><code class="language-json">{"command": "timeout /t 5", "timeout": 10}
</code></pre>
<hr>
<h2>环境变量配置</h2>
<table>
<thead>
<tr><th>变量名</th><th>默认值</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>SHELL_PROXY_IN</code></td><td><code>cmd_in.txt</code></td><td>命令输入文件路径</td></tr>
<tr><td><code>SHELL_PROXY_OUT</code></td><td><code>cmd_out.txt</code></td><td>结果输出文件路径</td></tr>
<tr><td><code>SHELL_PROXY_POLL</code></td><td><code>0.5</code></td><td>轮询间隔(秒)</td></tr>
</tbody>
</table>
<p><strong>自定义示例:</strong></p>
<pre><code class="language-bash">set SHELL_PROXY_IN=my_cmd.txt
set SHELL_PROXY_OUT=my_result.txt
set SHELL_PROXY_POLL=1.0
python scripts/shell_proxy.py
</code></pre>
<hr>
<h2>跨平台支持</h2>
<p>脚本会自动检测可用的 Shell:</p>
<table>
<thead>
<tr><th>平台</th><th>检测顺序</th></tr>
</thead>
<tbody>
<tr><td><strong>Windows</strong></td><td><code>pwsh.exe</code> → <code>powershell.exe</code> → <code>cmd.exe</code></td></tr>
<tr><td><strong>Linux</strong></td><td><code>bash</code> → <code>sh</code></td></tr>
<tr><td><strong>macOS</strong></td><td><code>bash</code> → <code>sh</code> → <code>zsh</code></td></tr>
</tbody>
</table>
<hr>
<h2>关键特性</h2>
<table>
<thead>
<tr><th>特性</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><strong>文件管道</strong></td><td>通过文件而非 stdin/stdout 交互,适配受限环境</td></tr>
<tr><td><strong>持久化 Shell</strong></td><td>Shell 进程保持运行,支持状态保持(变量、目录等)</td></tr>
<tr><td><strong>并发安全</strong></td><td>使用线程锁确保命令顺序执行</td></tr>
<tr><td><strong>超时控制</strong></td><td>默认 30 秒超时,防止命令挂起</td></tr>
<tr><td><strong>UTF-8 支持</strong></td><td>全程 UTF-8 编码,支持中文</td></tr>
<tr><td><strong>异步输出</strong></td><td>独立线程读取 stdout/stderr,避免阻塞</td></tr>
<tr><td><strong>标记分隔</strong></td><td>使用 UUID 标记区分命令边界</td></tr>
<tr><td><strong>自动检测 Shell</strong></td><td>根据平台自动选择可用的 Shell</td></tr>
<tr><td><strong>自动清理</strong></td><td>执行后自动删除命令文件</td></tr>
</tbody>
</table>
<hr>
<h2>常见问题</h2>
<h3>Q1: 代理显示 "等待命令中..." 但命令不执行</h3>
<p><strong>原因</strong>:<code>cmd_in.txt</code> 没有写入代理工作目录。</p>
<p><strong>解决</strong>:查看代理启动日志中的 <code>命令输入文件</code> 绝对路径,确保 Kimi 的 <code>WriteFile</code> 目标路径与之完全一致。</p>
<h3>Q2: 命令执行报错 "不是内部或外部命令"</h3>
<p><strong>原因</strong>:代理使用的是 <code>cmd.exe</code>,但命令用了 PowerShell 语法。</p>
<p><strong>解决</strong>:根据代理启动日志中的 <code>使用的Shell</code> 选择对应语法:</p>
<ul>
<li><code>cmd.exe</code> → 用 <code>dir</code>, <code>cd</code>, <code>mkdir</code>, <code>type</code> 等 CMD 命令</li>
<li><code>powershell.exe</code> → 用 <code>Get-ChildItem</code>, <code>Set-Location</code>, <code>New-Item</code> 等 PowerShell 命令</li>
</ul>
<h3>Q3: 如何指定代理使用 PowerShell?</h3>
<p><strong>解决</strong>:在启动代理前确保 <code>powershell.exe</code> 在 PATH 中,或在启动时指定:</p>
<pre><code class="language-bash"># Windows: 将 PowerShell 路径加入 PATH
set PATH=%PATH%;C:\Windows\System32\WindowsPowerShell\v1.0
python scripts/shell_proxy.py
</code></pre>
<hr>
<h2>文件说明</h2>
<table>
<thead>
<tr><th>文件</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>scripts/shell_proxy.py</code></td><td>Shell 代理主脚本</td></tr>
<tr><td><code>SKILL.md</code></td><td>本说明文档</td></tr>
<tr><td><code>cmd_in.txt</code></td><td>命令输入文件(运行时由 Kimi 写入)</td></tr>
<tr><td><code>cmd_out.txt</code></td><td>结果输出文件(运行时由代理写入)</td></tr>
</tbody>
</table>
</body>
</html>

scripts/shell_proxy.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
shell_proxy.py - Shell命令代理(文件管道版)
功能: 通过文件管道与Kimi交互,转发命令到Shell并返回结果
架构: Kimi写命令文件 → shell_proxy.py读取 → 执行Shell → 写结果文件 → Kimi读取

用法:
    1. 用户启动代理:  python scripts/shell_proxy.py
    2. Kimi写入命令:  向 cmd_in.txt 写入 JSON 命令
    3. 代理自动执行:  检测到 cmd_in.txt 后读取、执行、删除
    4. Kimi读取结果:  从 cmd_out.txt 读取 JSON 结果

命令文件格式 (cmd_in.txt):
    {"command": "mkdir hello", "timeout": 30, "work_dir": null}

结果文件格式 (cmd_out.txt):
    {"status": "success", "stdout": "", "stderr": "", "exit_code": 0, ...}
"""

import sys
import subprocess
import json
import os
import time
import shutil


# 默认管道文件路径(可配置)
CMD_IN_FILE = os.environ.get("SHELL_PROXY_IN", "cmd_in.txt")
CMD_OUT_FILE = os.environ.get("SHELL_PROXY_OUT", "cmd_out.txt")
POLL_INTERVAL = float(os.environ.get("SHELL_PROXY_POLL", "0.5"))  # 轮询间隔秒数


class ShellProxy:
    def __init__(self):
        self.shell_cmd = self._detect_shell()

    def _detect_shell(self):
        """自动检测可用的Shell"""
        if os.name == 'nt' or sys.platform == 'win32':
            for cmd in ['pwsh.exe', 'powershell.exe', 'cmd.exe']:
                path = shutil.which(cmd)
                if path:
                    return path
            return 'cmd.exe'
        else:
            for cmd in ['bash', 'sh', 'zsh']:
                path = shutil.which(cmd)
                if path:
                    return path
            return '/bin/sh'

    def _is_powershell(self):
        """判断是否使用 PowerShell"""
        shell = self.shell_cmd.lower()
        return 'powershell' in shell or 'pwsh' in shell

    def _is_cmd(self):
        """判断是否使用 CMD"""
        return 'cmd.exe' in self.shell_cmd.lower()

    def _build_command(self, command):
        """根据Shell类型构建命令行"""
        if self._is_powershell():
            return [self.shell_cmd, "-NoProfile", "-Command", command]
        elif self._is_cmd():
            return [self.shell_cmd, "/C", command]
        else:
            return [self.shell_cmd, "-c", command]

    def _get_encoding(self):
        """获取当前系统适用的编码"""
        if os.name == 'nt' or sys.platform == 'win32':
            # Windows CMD 默认使用 GBK (cp936),PowerShell 可用 UTF-8
            if self._is_cmd():
                return 'gbk'
            return 'utf-8'
        return 'utf-8'

    def _exec_command(self, command, timeout=30, work_dir=None):
        """执行单条命令并获取输出"""
        try:
            encoding = self._get_encoding()
            kwargs = {
                'capture_output': True,
                'text': True,
                'encoding': encoding,
                'errors': 'replace',  # 遇到编码错误时用 � 替换,避免崩溃
                'timeout': timeout,
            }
            if work_dir and os.path.isdir(work_dir):
                kwargs['cwd'] = work_dir

            # Windows 隐藏窗口
            if os.name == 'nt' or sys.platform == 'win32':
                startupinfo = subprocess.STARTUPINFO()
                startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
                kwargs['startupinfo'] = startupinfo

            cmdline = self._build_command(command)
            result = subprocess.run(cmdline, **kwargs)

            return {
                "stdout": result.stdout,
                "stderr": result.stderr,
                "exit_code": result.returncode,
                "timeout": False
            }
        except subprocess.TimeoutExpired:
            return {
                "stdout": "",
                "stderr": f"命令执行超时(>{timeout}秒)",
                "exit_code": -1,
                "timeout": True
            }
        except Exception as e:
            return {
                "stdout": "",
                "stderr": str(e),
                "exit_code": -1,
                "timeout": False
            }

    def _write_result(self, data):
        """向结果文件写入JSON格式的输出"""
        try:
            with open(CMD_OUT_FILE, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"[shell_proxy] 写入结果文件失败: {e}", file=sys.stderr, flush=True)

    def _read_command(self):
        """从命令文件读取JSON命令"""
        try:
            with open(CMD_IN_FILE, 'r', encoding='utf-8') as f:
                content = f.read().strip()
            if not content:
                return None
            try:
                return json.loads(content)
            except json.JSONDecodeError:
                return {"command": content, "timeout": 30, "work_dir": None}
        except Exception:
            return None

    def run(self):
        """主循环:轮询命令文件,执行后写入结果文件"""
        print(f"[shell_proxy] Shell代理已启动")
        print(f"[shell_proxy] 使用的Shell: {self.shell_cmd}")
        print(f"[shell_proxy] 命令输入文件: {os.path.abspath(CMD_IN_FILE)}")
        print(f"[shell_proxy] 结果输出文件: {os.path.abspath(CMD_OUT_FILE)}")
        print(f"[shell_proxy] 轮询间隔: {POLL_INTERVAL}s")
        print(f"[shell_proxy] 等待命令中...")

        while True:
            try:
                # 检查命令文件是否存在
                if os.path.exists(CMD_IN_FILE):
                    cmd_data = self._read_command()
                    if cmd_data:
                        command = cmd_data.get("command", "")
                        timeout = cmd_data.get("timeout", 30)
                        work_dir = cmd_data.get("work_dir", None)

                        if not command:
                            self._write_result({"status": "error", "message": "空命令"})
                        else:
                            # 执行命令
                            result = self._exec_command(command, timeout, work_dir)

                            response = {
                                "status": "success" if result["exit_code"] == 0 and not result["timeout"] else "error",
                                "stdout": result["stdout"],
                                "stderr": result["stderr"],
                                "exit_code": result["exit_code"],
                                "timeout": result["timeout"],
                                "command": command
                            }
                            self._write_result(response)

                    # 删除已处理的命令文件
                    try:
                        os.remove(CMD_IN_FILE)
                    except Exception:
                        pass

                time.sleep(POLL_INTERVAL)

            except KeyboardInterrupt:
                print("\n[shell_proxy] 收到中断信号,正在退出...")
                break
            except Exception as e:
                self._write_result({"status": "error", "message": f"执行异常: {str(e)}"})
                try:
                    os.remove(CMD_IN_FILE)
                except Exception:
                    pass


if __name__ == "__main__":
    proxy = ShellProxy()
    try:
        proxy.run()
    except Exception as e:
        print(f"[shell_proxy] 致命错误: {e}", file=sys.stderr)
        sys.exit(1)

运行效果

  1. 运行目录生成cmd_in.txtcmd_out.txt
  2. 智能体通过WriteFile和ReadFile间接操作Shell(PowerShell).
posted @ 2026-04-18 10:52  qsBye  阅读(10)  评论(0)    收藏  举报