# dsh-plugin-gitbash [![CI](https://github.com/Jinsight-gif/dsh-plugin-gitbash/actions/workflows/ci.yml/badge.svg)](https://github.com/Jinsight-gif/dsh-plugin-gitbash/actions/workflows/ci.yml) 在 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)中运行 **Windows 主机侧 Git for Windows Bash** 命令的 Cordis 插件。 一个零运行时依赖的自包含插件,向模型注册一个工具:`gitbash`。典型场景: - 操作 Windows 原生仓库的 git(git-bash 里的 git,而不是 WSL 里的)。 - 在 WSL 里跑着 harness,却需要执行 Windows 可执行文件、原生 Windows 路径(`D:\...`、`C:\...`)、或者不带 cmd/PowerShell 的 Windows 脚本。 - 任何必须触达 Windows 侧的操作,不必离开 agent 会话。 > [English](README.md) ## 工作原理 每次调用都会启动一个全新的 Git Bash 进程: ``` -c '' ``` `stdin` 置为 `ignore`(fd 0 接到 `/dev/null`),因此 shell 必然以非交互方式运行并在执行结束后退出,不会卡在交互提示符上。每次调用都是全新进程:cwd、环境变量、shell 函数等状态**不会**在调用之间保留。 **`command` 内部的路径语义**:被启动的是 *Windows* 的 Git Bash,所以里面要写 git-bash / Windows 路径(`/d/tools`、`D:/tools`、`C:\...`)。WSL 的 `/mnt/...` 路径在 command 内部**无效**。 **`workdir` 语义**:`workdir` 参数(缺省为会话工作目录)会被归一化——`/mnt/X/...`、`/X/...`、`X:\...` 三种写法都会映射为 `/mnt/X/...`,再由 WSL interop 转成原生 Windows 目录。无法映射的 WSL 独有路径(如 `/home/...`)不会让调用失败,而是在会话工作目录中启动(WSL interop 下表现为 `//wsl.localhost/...`)。 ## 环境要求 - DSH profile 运行在: - **WSL / Linux**:Windows 驱动器已挂载,且装有 Git for Windows;或 - **原生 Windows**:装有 Git for Windows。 - harness 主机能访问 `bash.exe`(见下方路径解析)。 ## 安装 ### 从 npm(发布后) ```sh dsh plugin --profile add dsh-plugin-gitbash ``` `dsh plugin` 会把包装进 profile;因为包声明了 `dsh.bundle`,它会**自动追加**到 profile 的 bundle 栈(`dsh.profile.bundles`)。重启 profile(`dsh web` / `dsh --profile `)即挂载 `gitbash` 工具。 ### 直接从 GitHub 安装(npm 发布前) ```sh dsh plugin --profile add https://github.com//dsh-plugin-gitbash ``` 生产环境建议锁定不可变 commit(社区审核惯例): ```sh dsh plugin --profile add https://github.com//dsh-plugin-gitbash# ``` ### 手动改 profile 清单 在 profile 的 `package.json` `dependencies` 里加依赖,并把 `dsh-plugin-gitbash` 追加到 `dsh.profile.bundles`,然后执行 `dsh plugin --profile install`。 ### Agent preset 行(免安装、直接放文件) 插件刻意只依赖 Node 内建模块 `node:fs`,所以也可以作为 preset 组合里的相对路径行直接使用,无需安装任何包。把 `lib/index.js`(或改名 `gitbash.mjs`)拷进你的 preset 目录,然后加一行: ```yaml # agent.cordis.yml - id: tool-gitbash name: ./index.js # 或你拷贝后的文件名 ``` ### 验证 ```sh node test/smoke.mjs ``` 用本机真实的 Git Bash 跑真实插件,输出 `SMOKE: ALL PASS`;主机上没有 git-bash 时优雅跳过(exit 0)。 ## 配置 bundle 行(或 preset 行)的 config 字段: | 键 | 类型 | 默认 | 含义 | | --- | --- | --- | --- | | `gitBashPath` | string | — | `bash.exe` 的显式路径。直接信任,不再探测。 | | `autoDetect` | boolean | `true` | 未显式配置时,搜索常见安装位置。 | | `timeoutMs` | number | `120000` | 每次调用的默认超时;单次调用可用 `timeoutMs` 参数覆盖。 | | `maxOutputChars` | number | `65536` | stdout/stderr 每条流尾部截断到该字符数(完整捕获仍在收集器/spill 中)。 | 解析优先级:`config.gitBashPath` → 环境变量 `DSH_GITBASH_PATH` → 自动探测(除非 `autoDetect: false`)。**自愈**:挂载时没解析到路径也没关系,每次调用都会重新解析——之后装了 Git for Windows 或挂了新盘,无需重启 DSH 即可生效。 自动探测的候选位置: - **Windows 主机**:`%ProgramFiles%\Git\bin\bash.exe`、`%ProgramFiles(x86)%\Git\bin\bash.exe`,最后退化为 PATH 里的 `bash`。 - **WSL / POSIX**:`/mnt//Program Files/Git/bin/bash.exe` 及 `(x86)` 变体(逐个盘符检查)。 显式路径示例(行配置): ```yaml - id: tool-gitbash name: 'dsh-plugin-gitbash' config: gitBashPath: '/mnt/e/Program Files/Git/bin/bash.exe' ``` 不想改配置?直接用环境变量: ```sh export DSH_GITBASH_PATH='/mnt/e/Program Files/Git/bin/bash.exe' ``` ## 排障 - **`No Git for Windows bash.exe found` / `Git Bash not found at ...`** —— 安装 Git for Windows,确认驱动器已挂载,然后设置 `gitBashPath` 或 `DSH_GITBASH_PATH`。 - **命令卡住不返回** —— git-bash 的 stdin 来自 `/dev/null`,交互式提示符按设计无法阻塞它;真正耗时的命令调大 `timeoutMs` 即可,进程始终可被终止。 - **`workdir` 没生效** —— WSL 独有路径(如 `/home/...`)无法作为 Windows 进程的工作目录,调用会在会话工作目录启动,提示信息会说明原因。要用 `/mnt/X/...`、`/X/...` 或 `X:\...` 写法控制。 - **command 内部报 WSL 路径错误** —— 在 `command` 里你处于 *Windows* git-bash:写 `/d/...`、`D:/...`,绝不要写 `/mnt/d/...`。 - **状态在调用间丢失** —— 这是设计使然:每次都是新进程;需要状态时把命令串在一条里(`cd /d/tools && git status`)。 ## 目录结构 ``` lib/index.js Cordis 插件本体(零运行时依赖) cordis.patch.yml dsh.bundle patch —— 插入工具行 dsh.plugin.json DSH 插件清单(工具发现元数据) test/smoke.mjs 独立 smoke 测试(Linux + Windows CI 双跑) .github/workflows CI:Linux 语法/分支测试 + Windows 真实 git-bash 测试 ``` ## 许可 MIT —— 见 [LICENSE](LICENSE)。