Skip to content
dsh.fish
Bundle

@temsmie/dsh-tool-keymouse

Windows key/mouse/screenshot/window automation (SendInput over a persistent helper) for the DeepSeek Harness

Source
msn233
License
MIT
Updated
Updated 15 days ago

Readme

# dsh-tool-keymouse

Windows 键鼠 + 截图 + 窗口自动化工具插件(DeepSeek Harness)。通过常驻 helper(`bin/helper-x64.exe`)走管道 + JSON 行协议调用 Win32 `SendInput` 模拟真实键盘/鼠标输入,用于桌面自动化:打开/唤起应用、登录、点击、输入、拖拽、滚动、截图观察、窗口定位/置顶。

helper 已 **DPI-aware**:`GetSystemMetrics`、截图、`mouse_move` 全部统一到物理像素(如 2560x1600)。模型给坐标 = 截图里看到的坐标 = 物理像素,无需缩放换算。

## 特性

- 键盘:`key_press` / `key_up` / `key_tap`,字母数字、空格回车、F1-F12、方向键、Ctrl/Alt/Shift、Tab、Esc 等。
- 鼠标:`mouse_press` / `mouse_up`(按住与松开)、`mouse_move`(绝对物理坐标)、`mouse_roll`(滚轮)。
- 截图:`screenshot` 抓主屏,另外回报抓图那一刻的前台窗口 hwnd/title(用来分清「人手切屏」和「操作失败」);`screenshot_window` 抓指定窗口(缺省前台窗口;优先 PrintWindow,被遮挡也能抓,返回的 mode 标明抓法;抓之前会确认窗口在渲染,最小化/隐藏的用 `SW_SHOWNOACTIVATE` 恢复显示且不抢前台,被 DWM 藏起来——切到别的虚拟桌面或应用被挂起——的直接报错)。两者都保存 PNG 并返回绝对路径与物理像素尺寸;看图能力取决于宿主环境(见「环境与能力边界」)。
- 窗口:`get_window` / `win_rect`(查位置)、`win_find`(按进程名找窗口)、`win_activate`(唤起,含最小化/托盘)、`win_minimize` / `win_restore` / `win_maximize`、`win_top` / `release_top`(置顶)。
- 清理:`release_all` 一键抬起所有按住项防卡键;`release_top` 取消所有置顶。
- 自带常驻 `keymouse` 说明书(runtime skill),模型通过 skill 工具读取使用规则。

## 工具清单(19)

| 工具 | 参数 | 说明 |
| --- | --- | --- |
| `screenshot()` | 无 | 截主屏 PNG,返回 path/width/height 与当时前台窗口 hwnd/title |
| `screenshot_window(hwnd?)` | hwnd? | 截指定窗口(缺省前台),返回 path/width/height/mode/hwnd/title/minimized/restored |
| `get_window()` | 无 | 当前前台窗口 hwnd/title/process/pid/x/y/w/h |
| `win_rect(hwnd?)` | hwnd? | 窗口位置与尺寸(缺省前台) |
| `win_find(process)` | process | 按进程名找窗口(返回 hwnd/visible,false=托盘) |
| `win_activate(hwnd)` | hwnd | 唤起/前置窗口,处理最小化+托盘 |
| `win_minimize(hwnd?)` | hwnd? | 最小化 |
| `win_restore(hwnd?)` | hwnd? | 还原 |
| `win_maximize(hwnd?)` | hwnd? | 最大化 |
| `win_top(hwnd, on)` | hwnd, on | 置顶/取消置顶 |
| `release_top()` | 无 | 取消所有本插件置顶 |
| `key_tap(key)` | key | 按下+立刻抬起 |
| `key_press(key)` | key | 只按下(组合键用) |
| `key_up(key)` | key | 抬起 |
| `mouse_press(button)` | button | 只按下鼠标按钮 |
| `mouse_up(button)` | button | 抬起鼠标按钮(`release_all` 会连按住的键一起抬) |
| `mouse_move(x,y)` | x, y | 绝对移动(物理像素) |
| `mouse_roll(up, amount?)` | up, amount? | 滚轮滚动 |
| `release_all()` | 无 | 统一抬起全部按住项 |

典型组合:`win_find('QQ')` → `win_activate(hwnd)` 唤起窗口(不点图标);`key_press('ctrl')` → `key_tap('s')` → `release_all`;`mouse_press('left')` → `mouse_move(...)` → `release_all`;`screenshot()` →(环境支持看图时)查看图像 → 再定位操作。

## 安装

本地目录方式(开发/内网):

```
dsh plugin --profile <name> add <path-to-dsh-keymouse>
```

npm 方式(发布后):

```
dsh plugin --profile <name> add @temsmie/dsh-tool-keymouse
```

桌面端(DeepSeek Harness 客户端 0.2.0):在插件管理面板里安装 `@temsmie/dsh-tool-keymouse`,装完**重启桌面端**才注册工具。

> 注意:DSH 加载的是 profile 目录里的那份副本(如 `%USERPROFILE%\.dsh\profiles\desktop\node_modules\@temsmie\dsh-tool-keymouse`),不是源码目录。改源码后必须重新发版/重装,改 D 盘那份不会生效。

## 兼容性

宿主范围写在 `package.json` 的 `peerDependencies` 里。DSH 在**安装时**和**profile 启动时**都会校验,不满足直接拒绝,不会静默加载。

| 宿主包 | 声明范围 |
| --- | --- |
| `@deepseek-ai/dsh-tools` | `^0.1.1-rc.2 \|\| >=0.2.0-rc.1 <0.3.0-0` |
| `@deepseek-ai/dsh-skill` | 同上 |
| `@deepseek-ai/cordis` | `^4.0.1` |

覆盖两条线:

- npm 全局 CLI(`@deepseek-ai/dsh` 0.1.x,profile `web`)
- 桌面端 0.2.0(内置 `@deepseek-ai/dsh` 0.2.0-rc.2 runtime,profile `desktop`)

0.2.1 的改动只有两处:版本号、以及把 peer 范围从 `^0.1.1-rc.2` 放宽到覆盖 0.2.x。之所以必须改——`^0.1.1-rc.2` 的上界是 `<0.2.0`,桌面端 0.2.0-rc.2 落在范围外,安装会被判 `incompatible with dsh 0.2.0-rc.2` 而拒绝。插件代码本身不用改:`defineTool`、`output.schema/render`、`exec.signal`、`ctx.skills.register` 的签名在 0.1.5-rc.1 与 0.2.0-rc.2 之间一致,两个版本的 `.d.ts` 下 `tsc` 都通过,编译产物与 0.2.0 版逐字节相同。

## 目录结构

```
dsh-keymouse/
  package.json          # npm 包契约(peerDeps/files/exports)
  tsconfig.json
  src/
    index.ts            # 插件入口: apply 里 spawn helper + 注册工具 + 注册skill
    helper.cpp          # helper 源码(SendInput + JSON行协议 + 键位表 + 窗口/截图)
    skill-content.ts    # keymouse 说明书内容(注册为 runtime skill)
  skills/
    keymouse.md         # 说明书可读副本(与 skill-content 一致)
  bin/
    helper-x64.exe      # 预编译 helper(Windows x64)
  lib/                  # tsc 编译产物(index.js / skill-content.js / *.d.ts)
```

## helper 协议

helper 从 stdin 逐行读 JSON,每行一条指令,每条输出一行 `{"ok":true|false,"error":...}` 到 stdout。stdin 关闭即退出(退出前自动 `release_all`)。

指令(`get_window`/`win_*` 返回窗口字段,`capture` 返回 path/width/height 外加当时前台窗口 hwnd/title,`capture_window` 返回 path/width/height/hwnd/title/mode/minimized/restored):

```
{"cmd":"press","key":"space"}
{"cmd":"up","key":"space"}
{"cmd":"tap","key":"enter"}
{"cmd":"mpress","button":"left"}
{"cmd":"mup","button":"left"}
{"cmd":"mmove","x":800,"y":450}
{"cmd":"mroll","up":true,"amount":1}
{"cmd":"release_all"}
{"cmd":"ping"}
{"cmd":"capture"}
{"cmd":"capture_window","hwnd":123}
{"cmd":"get_window"}
{"cmd":"win_rect","hwnd":123}
{"cmd":"win_top","hwnd":123,"on":true}
{"cmd":"release_top"}
{"cmd":"win_state","hwnd":123,"action":"minimize|restore|maximize"}
{"cmd":"win_find","process":"QQ"}
{"cmd":"win_activate","hwnd":123}
```

## 重新编译 helper

```
g++ -O2 -std=c++17 src\helper.cpp -o bin\helper-x64.exe -luser32 -lwinmm -lgdiplus -lgdi32
```

## 重新编译插件(TypeScript -> lib)

首次需把本机 DSH 的 `@deepseek-ai` 依赖接(junction)到本项目 `node_modules`(`@deepseek-ai/dsh-tools` 等类型来自 DSH 安装目录):

```
cmd /c mklink /J "node_modules\@deepseek-ai" "C:\Users\<你的用户名>\AppData\Roaming\npm\node_modules\@deepseek-ai\dsh\node_modules\@deepseek-ai"
```

然后:

```
npm install
npx tsc
```

说明:该 junction 仅用于本机编译,不在发布白名单(`files`)内,不影响打包分发。`node_modules` 已 gitignore,`bin/helper-x64.exe`(预编译、按需分发)随包提交。

## 环境与能力边界

- 本插件只做三件事:模拟键鼠输入、截主屏、窗口查询/置顶。**不含识图能力**。
- `screenshot` 返回 PNG 绝对路径。能否看图由宿主环境决定:
  - 当前模型支持图像输入 → 用宿主自带的读图能力打开该路径即可。
  - 模型不支持图像输入 → 需环境另有视觉/OCR 能力:安装提供图像理解/OCR 的 DSH 插件,或换用支持图像输入的模型。
- 插件不依赖任何特定视觉插件或工具名;`skill` 说明书同样不假设某种识图方式存在,避免在缺该能力的环境里给出无效步骤。
- 想提升体验:用插件市场(`find_dsh_plugin`)搜 `vision` / `OCR` / `image` 等关键词安装相应插件;或直接在支持图像输入的模型下使用。
- 键鼠工具本身依赖 Windows 桌面会话(见下)。

## 平台

仅支持 Windows(helper 使用 Win32 `SendInput`/`GetSystemMetrics`/`EnumWindows`)。DPI-aware,主屏物理像素坐标基准,单屏。

## License

MIT

Install

dsh plugin --profile web add github:msn233/dsh-keymouse-use

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source