折腾笔记[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> 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> 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> 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> 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)
运行效果
- 运行目录生成
cmd_in.txt和cmd_out.txt - 智能体通过WriteFile和ReadFile间接操作Shell(PowerShell).

在Windows上配置kimi-cli通过WriteFile和ReadFile间接调用Shell, 解决`Tool Runtime Error`问题.
浙公网安备 33010602011771号