# DshFileMenu

[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![npm version](https://img.shields.io/npm/v/dsh-filemenu.svg)](https://www.npmjs.com/package/dsh-filemenu)
[![powered by dsh](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness)
![platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
![GitHub release](https://img.shields.io/github/v/release/ltsone9/dsh-filemenu)
![GitHub stars](https://img.shields.io/github/stars/ltsone9/dsh-filemenu)

[English](./README.md) | **中文**

为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)（DSH）Web 界面添加右键菜单的插件。

对话中产出的文件、行内文件引用、侧边栏的**工作区（项目）行**与**会话行**——一键打开、在系统文件管理器中显示、用检测到的编辑器打开、或复制路径。菜单项参考了最新 Codex 桌面端的文件右键操作惯例。

以**动态 Cordis 插件**形态交付——纯增量，不替换任何官方 UI 席位，插件停止时自动清理全部副作用。

---

## ✨ 功能特性

- **文件右键菜单** —— 右键以下任意目标：
  - 对话"产物"行的文件 chip；
  - 助手消息里的行内代码文件引用；
  - 工具卡片上的文件路径链接（尽力而为）；
  - 助手纯文本里的绝对路径（例如 `D:\\dayu\\XMDJ\\frontend\\voter-web\\dist`）；
  - → **打开** · **打开所在文件夹** · **在编辑器中打开** ▸ · **用其他程序打开** · **复制路径** · **复制相对路径**
- **工作区（项目）右键菜单** —— 右键侧边栏工作区行：
  - **打开所在目录** · **打开**（切换到该工作区的会话）· **复制路径**
- **会话右键菜单** —— 右键侧边栏会话行：
  - **打开所在目录** · **打开**（切换到该会话）· **重命名** · **分叉** · **归档** · **复制路径**
- **在编辑器中打开** —— 二级子菜单，自动检测 Host 上的编辑器：VS Code、Cursor、Windsurf、VSCodium、Sublime Text、Notepad++、Typora、HBuilderX、Zed、IntelliJ IDEA、WebStorm、PyCharm、Rider、CLion、Neovim、Vim、gedit。
- **用其他程序打开** —— Windows 系统"打开方式"对话框。
- **主题自适应** —— 浅色模式跟随 Harness 主题 token；深色模式使用接近纯黑的背景 + 接近纯白的文字与线条。
- **严格跟随鼠标位置** —— 单列排布，贴边自动避让，不会超出视口；每个操作均带内联 SVG 图标。

## 📦 安装

### 环境要求

- 运行中的 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web 界面。
- 具备原生打开器的宿主系统（Windows、macOS 或带桌面的 Linux）。Host 报告 `canOpenPath` 时 OS 相关菜单项自动启用（无头 Linux Host 会自动隐藏这些项）。

### 方式 A —— 作为动态插件快速安装（无需重启）

> DshFileMenu 以**动态 Cordis 插件**交付：两个纯 JavaScript 文件，一个 Host 半区、一个 Client 半区。动态插件在**会话内**定义并激活，立即生效。

1. 获取两个插件文件：

   - [`plugin/host.js`](./plugin/host.js) —— Host 半区
   - [`plugin/client.js`](./plugin/client.js) —— Client 半区

   （也可直接下载 raw 地址：[`host.js`](https://raw.githubusercontent.com/ltsone9/dsh-filemenu/main/plugin/host.js) 和 [`client.js`](https://raw.githubusercontent.com/ltsone9/dsh-filemenu/main/plugin/client.js)。）

2. 打开 DSH Web 界面中的任意会话，让 Agent 安装它：

   > 请安装 dsh-filemenu：用 `cordis_define`（`kind: new`，`idPrefix: file`）创建插件，`code.host` 填入 `plugin/host.js` 的完整内容，`code.client` 填入 `plugin/client.js` 的完整内容，然后 `cordis_run`（`mode: run`）激活。

3. **批准首次激活。** Client 半区会在 Run 卡中请求授权——点击**允许**（双勾可同时信任未来版本）。未批准时插件会停留在 `awaiting-approval`。

4. 完成。右键产物文件、工作区行或会话行即可验证。

> **注意：** 动态插件是**进程级**的。Harness 重启后需要重新定义并运行（方式 B 可让插件常驻）。

### 方式 B —— 常驻安装（官方 CLI 路线）✅ v1.1.6

DshFileMenu 已改为标准包形态（`dsh.bundle` / `dsh.client` 声明），可以作为**常驻 profile 插件**安装，重启后依然存在：

```sh
# 在仓库目录执行
dsh plugin --profile <profile> add .
```

> **`<profile>` 选哪个？** DSH **桌面版**运行的是 `desktop` profile → 用 `--profile desktop`；独立的 Web 服务部署跑 `dsh web`，用 `web` profile → `--profile web`。可查看 `~/.dsh/profiles/` 确认你实际使用的 profile。

**安装步骤（按顺序）：**

1. **把包装进 profile**：`dsh plugin --profile desktop add .` —— 它会在 profile 目录里执行 `pnpm add`，并因为包的 `package.json` 声明了 `dsh.bundle`（`cordis.patch.yml` 组合行）而**自动挂载**；浏览器半区通过 `dsh.client` 声明被发现（`/plugins/dsh-filemenu/client.js`）。
2. **重启 Harness 服务** —— Host 组合需要重新加载，Web shell 需要重建 client bundle。桌面版重启应用（或后端）即可。
3. **验证** —— 重启后在 Web 界面右键产物文件 / 工作区行 / 会话行。
4. 后续维护用同一命令：`dsh plugin --profile desktop remove dsh-filemenu`、`update dsh-filemenu`、`why dsh-filemenu`（任意 pnpm 参数都会被原样转发）。

> **已发布到 npm**（`dsh-filemenu@1.1.6`）——`dsh plugin --profile desktop add dsh-filemenu` 可直接从 registry 安装；本地目录（`add .`）同样可用。前置条件：`PATH` 里有 `pnpm`（CLI 会转发给它）。

如果你从源码运行 Harness，也可以使用 patch 覆盖层（见官方教程 [你的第一个插件](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/index.zh.md)）：

```sh
pnpm dsh web --patch ./cordis.patch.yml
```

参考：[官方插件文档](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/index.zh.md)、[deepseek-harness-plugins 插件合集](https://github.com/linxiecoder/deepseek-harness-plugins)、[dsh-market 插件市场](https://github.com/2BingLing/dsh-market)。

## 🆕 最近更新

### v1.1.6

- 新增助手纯文本绝对路径右键识别：优先识别鼠标下的路径，回退时只接受当前文本块中的唯一路径。

### v1.1.5

- 移除侧边栏会话右键菜单中的**置顶/取消置顶**功能。

### v1.1.4

- 所有一级菜单操作与编辑器二级菜单行增加内联 SVG 图标。

### v1.1.3

- 侧边栏会话右键菜单新增**重命名、分叉、归档**；归档在调用现有安全 `archiveSession` API 前会确认。

### v1.1.2

- 扩充编辑器检测：新增 Typora、HBuilderX、Zed、IntelliJ IDEA、WebStorm、PyCharm、Rider、CLion、Neovim、Vim。
- 同步完善中英文文档：可直接 npm 安装、补充最近改动说明。

### v1.1.1

- 修复常驻版 Host 启动时序：声明 `inject: ['webServer']`，确保等待 Web 服务就绪后再注册 `/dsh-filemenu/rpc`。
- 改进常驻版 RPC 错误提示，区分 HTTP 错误、空响应和无效 JSON。

### v1.1.0

- 增加常驻包形态：`dsh.bundle`、`dsh.client`、`cordis.patch.yml`、标准 `__ModuleLoader__` Client bundle 与 Host HTTP RPC。
- 发布 `dsh-filemenu@1.1.0` 到 npm。

## 🖱️ 使用说明

右键目标即可，菜单严格出现在鼠标位置。

| 目标 | 菜单项 |
| --- | --- |
| 产物 chip / 行内引用 / 工具卡链接 | 打开 · 打开所在文件夹 · 在编辑器中打开 ▸（检测到的编辑器）· 用其他程序打开 · 复制路径 · 复制相对路径 |
| 侧边栏工作区（项目）行 | 打开所在目录 · 打开 · 复制路径 |
| 侧边栏会话行 | 打开所在目录 · 打开 · 复制路径 |

平台行为：

- **Windows** —— "打开所在文件夹"会在资源管理器中**选中该文件**（`explorer /select,<path>`）；"打开"使用 `Invoke-Item`；"用其他程序打开"调起系统对话框（`rundll32 shell32.dll,OpenAs_RunDLL`）。
- **macOS** —— 显示用 `open -R`；打开用 `open`。
- **Linux** —— 显示/打开用 `xdg-open`（仅桌面环境）。
- "用其他程序打开"**仅 Windows**，其他平台自动隐藏。
- Host 无法原生打开路径时（`canOpenPath = false`，如远程 Web 访问或无头 Linux），OS 相关菜单项全部置灰；复制类功能不受影响。

## ⚙️ 工作原理

```
┌────────────────────────── Client（浏览器）──────────────────────────┐
│ shell.overlay 条目承载菜单；document 捕获阶段 contextmenu 监听器       │
│ 通过稳定 DOM 标记识别目标                                             │
│ （data-produced-files-row / code button[title] /                     │
│   data-dsh-workspace-drop-target + role=treeitem 行）                 │
│ 工作区/会话目标通过实时 sessions/workspaces 快照解析。                  │
└───────────────┬──────────────────────────────────────────────┬────────┘
                │ host.call（动态）/ fetch（打包 RPC）           │ 客户端服务
                ▼                                               ▼
┌────────────────────────── Host（Node）────────────────────────┐
│ fs 服务解析路径；subprocess 服务启动原生命令；                   │
│ 平台由 processPath 形态推断（Host 沙箱没有 process 全局）。      │
└────────────────────────────────────────────────────────────────┘
```

Host RPC：

| 动作 | 用途 | 典型命令 |
| --- | --- | --- |
| `open` | 用默认应用打开 | `powershell Invoke-Item` / `open` / `xdg-open` |
| `reveal` | 在系统文件管理器中显示 | `explorer.exe /select,` / `open -R` / `xdg-open` |
| `editors` | 检测已安装编辑器 + 上报 Host 平台 | PATH 解析 + 安装路径探测 |
| `openWith` | 用检测到的编辑器打开文件 | `<编辑器可执行文件> <路径>` |
| `openWithDialog` | Windows"打开方式"对话框 | `rundll32 shell32.dll,OpenAs_RunDLL` |

传输通道随安装形态而异：**动态版**（快速安装）用 `harness.handle`（包私有 RPC）；**打包版**用 HTTP RPC 端点（`POST /dsh-filemenu/rpc`，注册在 `webServer` 服务上，客户端用 `fetch` 调用）。两者分发到同一组动作。

编辑器检测通过 `subprocess.resolveExecutable` 解析 CLI 名称（Windows 上感知 PATHEXT，能找到 `code.cmd` 等），把 `.cmd`/`.bat` bin 脚本反推回其安装目录中的真实 GUI 可执行文件，并回退探测常见绝对安装路径。

## 🔧 配置

- **添加自定义编辑器**：编辑 [`plugin/host.js`](./plugin/host.js)，并同步修改 [`lib/index.js`](./lib/index.js) 中的 `EDITOR_SPECS` 表——每个 spec 含 `commands`（PATH 可解析的命令名）、`exeNames`（`bin/` 脚本旁的 GUI 可执行文件）、`installs`（绝对安装路径）。
- **菜单文案**：编辑 [`plugin/client.js`](./plugin/client.js) 中的 `zh` / `en` 字典。
- **主题**：浅色模式使用 Harness alias token；深色配色在 [`plugin/client.js`](./plugin/client.js) 的 `MENU_CSS`（`data-fm-theme="dark"` 块）中。

## 🚑 故障排查

| 现象 | 排查 |
| --- | --- |
| 右键没有反应 | 插件是否为 `running`（`cordis_inspect_self`）？首次 Client 激活是否已批准？尝试刷新页面。确认目标是受支持表面（产物 chip / 行内引用 / 工作区行 / 会话行）。 |
| OS 相关菜单项置灰 | `canOpenPath = false`——远程 Web 访问或无头 Host 无法原生打开路径。 |
| 菜单出现红色错误 | 阅读菜单内显示的错误信息（如 `sessions service unavailable`）；若报错涉及旧字段名，请升级到最新版本。 |
| 编辑器子菜单为空 | 编辑器不在 PATH 上也不在被探测的安装位置——把它加入 `EDITOR_SPECS`。 |
| 菜单不跟随鼠标 | 请使用最新版本；早期版本缺少 `position: fixed` 样式。 |

## ❓ FAQ

- **会替换任何官方 UI 吗？** 不会。DshFileMenu 只新增一个 `shell.overlay` 条目；所有官方 Slot（`tool.call.toolview`、`conversation.chat.turnTail`、侧边栏浏览器等）均未改动。
- **需要重启 Harness 吗？** 不需要——动态插件流程立即生效，停止时自动清理。
- **为什么动态插件重启后不见了？** 动态插件按设计是进程级的。请用方式 B 的常驻 npm 包（`dsh plugin --profile desktop add dsh-filemenu`），或重新定义动态副本。
- **支持哪些平台？** Windows 为主；macOS / Linux 在具备原生打开器时可用。"用其他程序打开"仅 Windows。
- **侧边栏行如何解析？** 工作区/会话目标按显示标题匹配实时快照（重名时取最近更新），分组视图下按所属工作区收敛候选。

## 🛠️ 开发

```
dsh-filemenu/
├── README.md            English documentation
├── README.zh-CN.md      中文文档
├── LICENSE              MIT license
├── package.json         package metadata（dsh.bundle / dsh.client 声明）
├── cordis.patch.yml     `dsh plugin add` 插入的组合行
├── lib/
│   ├── index.js         Host 半区 —— 常驻 profile 插件（webServer RPC）
│   └── client.js        Client 半区 —— 标准 __ModuleLoader__ Web bundle
└── plugin/
    ├── host.js          Host 半区 —— 动态快速安装副本（code.host）
    └── client.js        Client 半区 —— 动态快速安装副本（code.client）
```

- `plugin/` 是**动态快速安装**副本：返回 Cordis 插件的纯 JavaScript 函数体——不涉及 TypeScript、JSX 或打包器。
- `lib/` 是**打包版**半区（方式 B 使用）。`lib/client.js` 是标准 Web bundle（`window.__ModuleLoader__.load`、`require('react')`、手动注入样式、`fetch` RPC）。
- 改动行为时两个副本要保持同步（动态版用 `harness.handle` / `host.call`；打包版走 HTTP RPC 端点）。

在 DSH 会话中加载 `cordis-plugin-development` skill 可查阅这里用到的完整 API（slots、harness、styles、theme）。

欢迎通过 Issue 或 PR 参与贡献。

## 🗺️ 路线图

- [x] 文件右键菜单（打开 / 显示 / 编辑器子菜单 / 用其他程序打开 / 复制路径）
- [x] 侧边栏工作区与会话右键菜单
- [x] 深色/浅色主题自适应
- [x] 常驻 npm 包形态（方式 B 的 `dsh plugin add` 安装）—— v1.1.0
- [x] 发布到 npm（`dsh-filemenu@1.1.6`）
- [ ] 截图与动图演示
- [ ] 从 Harness 设置读取用户自定义编辑器路径

## 📜 许可证

[MIT](./LICENSE) © 2026 [ltsone9](https://github.com/ltsone9)
