dsh-api-runner 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moz (lhhzmz229)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,436 @@
1
- # Temporary Holding Version
1
+ # dsh-api-runner
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
4
+ [![node](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
5
+ [![dsh-plugin](https://img.shields.io/badge/dsh-plugin-8b5cf6.svg)](https://github.com/topics/dsh-plugin)
6
+
7
+ <!-- 被 dsh-plugin.org 收录后,把下面这行取消注释(slug 用详情页地址里的 owner/slug):
8
+ [![Listed on dsh-plugin.org](https://dsh-plugin.org/badges/listed.svg)](https://dsh-plugin.org/plugins/lhhzmz229/dsh-api-runner)
9
+ -->
10
+
11
+ DSH 插件:把「后端接口清单」「本地服务启停」「日志增量读取」三件事交给模型直接调用,并提供一个面板做人工配置,形成「改完自己验」的闭环。
12
+
13
+ **不依赖任何第三方插件**:面板全部注册到 DSH 自带的席位(左栏全局页、右侧栏页签、设置页),装到任意一份 DSH 上都能用。
14
+
15
+ - **面板「接口调试」**:可视化增删改接口配置、登录并保存 token、点按钮试调用、列出本机所有运行中的 Java 服务并一键设为 baseUrl。同一个面板出现在三处——左栏图标(中栏整页,无会话也能开)、右侧栏页签(和终端/文件/任务并列,边看对话边调试)、设置页里的插件主页(只放跨项目的全局项)。
16
+ - **可配置接口**:在 YAML 里声明接口,或在面板里点选添加(方法、URL、请求头、body 模板),模型按 id 调用,占位符自动替换。
17
+ - **登录态**:把某个接口标记为登录接口,调用成功后按 `tokenPath` 取出 token 存进项目档,后续接口自动注入 `Authorization`,不用手工复制。
18
+ - **服务重启**:`managed` 模式由插件托管进程(能 start/stop/restart,DSH 重启后服务仍在跑);`observe` 模式进程由 IDEA 启动,插件只轮询健康检查(配合 IDEA 的 restart 按钮)。
19
+ - **日志读取**:按文件 + 字节偏移增量 tail,支持正则过滤、等待特征出现,游标持久化,服务重启不丢。
20
+
21
+ > **能力分类**:开发与运维 | **平台**:Windows / macOS / Linux | **许可证**:MIT
22
+ > **一句话价值**:装上后模型能在对话里自己调接口、启停本地服务、读日志,改完代码自己验证。
23
+
24
+ ## 效果
25
+
26
+ 效果见下方截图(截图取自旧版,面板内容一致;新版入口在左栏图标、右侧栏页签和设置页)。
27
+
28
+ ![服务页:已接管的 Java 服务](./docs/screenshot-service.png)
29
+
30
+ 「接口调试」的设置页:数据目录、默认工作区、Maven 主路径、接口基地址。
31
+
32
+ ![设置页:数据目录与 Maven 主路径](./docs/screenshot-settings.png)
33
+
34
+ > 截图中的服务名、包名与本机路径做了模糊处理。验证环境:DSH `V0.2.1-alpha.1` / Windows([版本截图](./docs/verified-dsh-version.png))。
35
+
36
+ ## 安装
37
+
38
+ 装好之后,模型可以在对话里直接列接口、调接口、启停/重启本地服务、按字节偏移增量读日志,
39
+ 形成「改完自己验」的闭环,不用再手工复制 curl、切窗口重启服务、翻日志找报错。
40
+
41
+ ### 从 npm 安装(推荐)
42
+
43
+ ```bash
44
+ dsh plugin --profile web add dsh-api-runner
45
+ ```
46
+
47
+ npm 上已发布 `dsh-api-runner@0.2.0`,这条是推荐路径,也是 DSH「插件中心」里一键安装的形式。
48
+
49
+ ### 从 GitHub 源码安装
50
+
51
+ ```bash
52
+ dsh plugin --profile web add github:lhhzmz229/dsh-api-runner
53
+ ```
54
+
55
+ DSH 会把仓库 clone 到 profile 的 `node_modules` 下。写成带包名的
56
+ `dsh-api-runner@github:lhhzmz229/dsh-api-runner` 也等价。这条依赖本机有 `git`;装完重启 DSH(或把 `dsh-api-runner` bundle 关掉再打开)生效。
57
+
58
+ ### 从 0.1.x 升级
59
+
60
+ 0.2.0 有两个变化,**升级后需要硬刷新页面**(Ctrl/Cmd+Shift+R):
61
+
62
+ 1. **不再依赖 `dsh-better-sidebar`**。面板改成注册到 DSH 自带的席位,所以:
63
+ - 装了 `dsh-better-sidebar` 的,那个侧边栏里不会再出现「接口调试」页签;
64
+ - 面板现在在左栏 ⚡ 图标(中栏整页)、右侧栏页签、设置 → 接口调试 三处。
65
+ - 顺带修掉一个老问题:旧版把 `betterSidebar` 写进 `inject`,没装那个插件时**整个 client half 不激活**,连设置页都不出现。
66
+ 2. **新增一键启动**:「已保存」页签顶部可以一次拉起所有配方(逐个编译,已在运行的跳过,单个失败不影响其余)。
67
+
68
+ 配置文件、面板数据、已保存的服务配方、日志目录都沿用原路径,升级不需要迁移。
69
+
70
+ ### 本地开发:link 安装
71
+
72
+ ```bash
73
+ # 在插件目录下装依赖(只有一个运行时依赖 yaml;npm cache 目录无写权限时用 --cache 指到可写目录)
74
+ npm install
75
+
76
+ # 以 link 方式装进 desktop profile
77
+ dsh plugin --profile desktop add "link:/绝对路径/到/dsh-api-runner"
78
+ ```
79
+
80
+ Windows 下 link 路径写成 `link:D:/path/to/dsh-api-runner` 也等价。
81
+
82
+ `dsh` 命令不在 PATH 时,用桌面版自带运行时(路径按你的安装位置调整):
83
+
84
+ ```bash
85
+ # Windows
86
+ "<DSH 安装目录>/resources/runtime/bin/node.cmd" "<DSH 安装目录>/resources/app.asar/dsh/bin/dsh.js" plugin --profile desktop add "link:..."
87
+ # macOS / Linux
88
+ "<DSH 安装目录>/resources/runtime/bin/node" "<DSH 安装目录>/resources/app.asar/dsh/bin/dsh.js" plugin --profile desktop add "link:..."
89
+ ```
90
+
91
+ 装好后 profile 的 `package.json` 会出现依赖 `dsh-api-runner` 与 `dsh.profile.bundles` 里的 `dsh-api-runner` 条目;
92
+ 插件自己的 `cordis.patch.yml` 负责把 entry 插进依赖树。
93
+
94
+ 侧边栏页签**不依赖任何第三方插件**。面板注册进 DSH 自带的 slot 席位:
95
+
96
+ | 席位 | 面板形态 |
97
+ | --- | --- |
98
+ | `sidebar.panellist` + `main` | 左栏图标 → 中栏整页(和「插件」「任务计划」并列,无会话也能打开) |
99
+ | `sidebar.right.pane.tab` + `.title` | 右侧栏页签(和终端/文件/任务并列,边看对话边调试) |
100
+ | `settings.section` | 设置页里的插件主页(只放默认工作区、数据目录、Maven 主路径这类全局项) |
101
+
102
+ 这三个席位由 `@deepseek-ai/dsh-client-ui-sidebar` 和 `@deepseek-ai/dsh-client-ui-sidebar-right`
103
+ (都是 DSH 自带)声明,所以任何一份完整 DSH 上都有。宿主侧的工具(`ApiRunner`)与 HTTP 路由
104
+ 不依赖这些席位,即使 UI 缺席位也照常工作。
105
+
106
+ ## 权限与风险
107
+
108
+ 安装前请确认这些行为都在你的预期内:
109
+
110
+ | 项目 | 说明 |
111
+ | --- | --- |
112
+ | 执行本地命令 | 用 `jps` / `jcmd` / `mvn` / `netstat` / `lsof` 发现、接管、启停 Java 进程;`managed` 模式会按配置直接拉起与结束子进程 |
113
+ | 发起 HTTP 请求 | 只请求你在 YAML 或面板里配置的 URL,可按配置注入 `Authorization` 头 |
114
+ | 读取本地文件 | 读取你配置的日志文件(按字节偏移增量读),内容只回给模型,不上传 |
115
+ | 写入本地文件 | `<数据目录>/` 下的状态与 UI 配置;面板保存的 token 明文落在 `api-runner-ui.json`(见「已知限制」) |
116
+ | 外联上报 | 无遥测、无统计上报,插件自身不发起任何外部请求 |
117
+
118
+ ## 兼容与运行要求
119
+
120
+ - **DSH**:`>=0.1.0-rc.6 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0`
121
+ - **已验证环境**:DSH `0.2.1-alpha.1` / Windows
122
+ - **Node.js**:`>=20`
123
+ - **平台**:Windows / macOS / Linux(进程与端口发现按平台分流)
124
+ - **可选环境**:JDK(`jps` / `jcmd`,用于 Java 服务发现与接管)、Maven(`mvn`,用于接管时编译)
125
+
126
+ ## 配置
127
+
128
+ 配置文件按「内置默认 → 全局 → 项目」合并,后者覆盖前者:
129
+
130
+ | 位置 | 说明 |
131
+ | --- | --- |
132
+ | `${DSH_HOME}/api-runner.yml` | 全局,所有项目生效 |
133
+ | `<项目 cwd>/.dsh/api-runner.yml` | 项目级,建议放这个 |
134
+
135
+ 文件名也支持 `api-runner.yaml` / `dsh-api-runner.yml`。**改完保存即生效**(按 mtime + size 缓存),不用重启 DSH。
136
+
137
+ 完整示例见 [dsh-api-runner.example.yml](./dsh-api-runner.example.yml),最小可用示例:
138
+
139
+ ```yaml
140
+ defaults:
141
+ timeoutMs: 30000
142
+ headers:
143
+ Content-Type: application/json
144
+
145
+ env:
146
+ TOKEN: ${DEV_TOKEN} # 引用环境变量
147
+
148
+ apis:
149
+ - id: get-user
150
+ name: 查询用户
151
+ method: GET
152
+ url: http://127.0.0.1:8080/api/users/{id}
153
+ headers:
154
+ Authorization: "Bearer ${TOKEN}"
155
+
156
+ - id: create-order
157
+ method: POST
158
+ url: http://127.0.0.1:8080/api/orders
159
+ body:
160
+ userId: "{userId}"
161
+ amount: "{amount}"
162
+
163
+ services:
164
+ # 插件托管:mvnw spring-boot:run
165
+ - id: app
166
+ mode: managed
167
+ cwd: ../
168
+ command: ./mvnw.cmd
169
+ args: ["spring-boot:run"]
170
+ logPath: ./logs/app.log
171
+ startupTimeoutMs: 90000
172
+ health:
173
+ url: http://127.0.0.1:8080/actuator/health
174
+
175
+ # IDEA 里跑,插件只观测(配合 IDEA restart 按钮)
176
+ - id: idea-app
177
+ mode: observe
178
+ health:
179
+ url: http://127.0.0.1:8080/actuator/health
180
+ ports: [8080]
181
+
182
+ logs:
183
+ - id: app-log
184
+ path: ./logs/app.log
185
+ ```
186
+
187
+ ### 两种占位符
188
+
189
+ | 写法 | 何时展开 | 用途 |
190
+ | --- | --- | --- |
191
+ | `${NAME}` | 加载配置时 | 环境变量 / `env:` 段里定义的值,用于 URL、header、command |
192
+ | `{name}` | 调用接口时 | 模型通过 `params` 传入,替换 URL 与 body 里的同名占位 |
193
+
194
+ `headers` 里的 `${VAR}` 会展开成真实值,但在返回给模型的 `request.headers` 里按名字脱敏(`token`/`secret`/`password`/`key`/`auth` 等一律 `demo***3456`)。
195
+
196
+ ## 工具与动作
197
+
198
+ 模型通过 `ApiRunner` 工具调用,`action` 取值:
199
+
200
+ | action | 作用 |
201
+ | --- | --- |
202
+ | `describe` | 列出可用接口、服务、日志(含每个接口需要的参数名),不返回 header 值 |
203
+ | `call` | 按 `api_id` 调接口,`params` 传占位符;已保存 token 会自动注入 |
204
+ | `login` | 调登录接口并把 token 存进项目档(`api_id` 指向配了 `login.tokenPath` 的接口) |
205
+ | `java` | 本机 Java 服务:`java_action` = `list` / `detail`(详情+日志)/ `start`(接管启动)/ `stop` / `restart` / `log` / `ignore` / `unignore` / `save`(保存启动配方)/ `unsave` / `start_saved`(按配方启动,不需要 IDEA 里先有进程)/ `start_all`(一键启动所有已保存的服务,可用 `java_ids` 只启部分)/ `settings`(读或改全局设置)/ `status`,详见下文 |
206
+ | `service` | `service_action` = `start` / `stop` / `restart` / `wait` / `status` |
207
+ | `log` | 增量读日志(`pattern` 正则过滤,`max_bytes` / `max_lines` 限流) |
208
+ | `wait_log` | 等某个特征出现在日志里(`timeout_ms` 超时) |
209
+ | `reset_log` | 把游标重置到文件末尾 |
210
+ | `verify` | 一条命令串起「重启 → 等日志特征 → 调接口」,返回分步结果 |
211
+
212
+ `verify` 只有在所有步骤都成功(observe 模式下无法自动重启除外)才返回 `ok: true`;失败时给出 `code`(如 `STEP_FAILED:log`)与可读 `message`。
213
+
214
+ ## 典型流程
215
+
216
+ ```mermaid
217
+ flowchart LR
218
+ M["模型 / DSH 对话"] -->|"ApiRunner.call"| API["后端接口<br/>YAML 里配置的 URL"]
219
+ M -->|"ApiRunner.service start/stop/restart"| SVC["本地服务进程<br/>managed 托管 / observe 观测"]
220
+ M -->|"ApiRunner.log / wait_log"| LOG["日志文件<br/>按字节偏移增量读"]
221
+ M -->|"ApiRunner.verify"| V["重启 → 等日志特征 → 调接口"]
222
+ SVC -->|"stdout / stderr 追加"| LOG
223
+ UI["面板「接口调试」<br/>左栏整页 / 右侧栏页签 / 设置页"] -->|"增删改接口 · 登录存 token · 试调用"| API
224
+ UI -->|"列出本机 Java 服务 · 接管 · 重启 · 看日志"| SVC
225
+ ```
226
+
227
+ 典型的一轮「改完自己验」:改代码 → `service restart` → `wait_log` 等 `Started Application` →
228
+ `call` 打接口看返回 → 不对就 `log` 拉最近几百字节定位报错。也可以直接用 `verify` 一条串起来。
229
+
230
+ ## 面板
231
+
232
+ 同一个「接口调试」面板出现在三个入口,内容一致:
233
+
234
+ - **左栏图标**(`sidebar.panellist` + `main`):点 ⚡ 图标切到中栏整页。整页宽,适合配接口、看日志;没有会话时也能打开(host 回落到设置里的默认工作区)。
235
+ - **右侧栏页签**(`sidebar.right.pane.tab`):和终端/文件/任务页签并列,边看对话边调试。会话级,宽度窄,所以内容分成三个页签,顶部固定、下方独立滚动,一次只看一块。
236
+ - **设置 → 接口调试**(`settings.section`):只放默认工作区、数据目录、Maven 主路径这类跨项目的全局项。
237
+
238
+ 右侧栏很窄,所以下面按页签说明布局:
239
+
240
+ **接口页签**
241
+ - 顶部是**按项目分类**的二级页签(只有存在第二个项目时出现):第一个永远是当前工作区,后面是其它已知项目(显示成末段目录名,重名时带上上一级;页签多了横向滚动)。二级页签没有灰底容器、选中项是品牌色实心块,和顶部那排灰底分段控件一眼能分开。
242
+ - 项目清单来自「当前会话的工作区 + 设置里的默认工作区 + 面板里存过的所有项目」,所以还没配过接口的项目也会出现——不然切不过去。
243
+ - 切到别的项目后,接口列表、基地址、token、保存/编辑/删除**都作用于那个项目自己的档**(请求带 `project`,后端按项目桶读写),不会污染当前工作区。要回到自己项目点第一个页签。
244
+ - 当前项目下:顶部筛选框可按 id / 名称 / 地址过滤;右侧「新增」展开内联编辑表单。
245
+ - 每条接口显示方法徽章、名称、地址(host 弱化、路径强化)、来源徽章(`YAML` = 来自配置文件,无徽章 = 面板新建)、占位参数列表。
246
+ - 行内操作:「测试」(用示例参数调用一次)、「登录并保存」(仅登录接口)、复制地址、清除 token、编辑、删除(只允许删面板自己建的)。
247
+ - 调用结果是一张卡片:状态码、耗时、响应大小,可在「响应 / 响应头 / 请求」之间切换,带复制与关闭。
248
+ - **编辑表单**:id / 名称 / 方法 / URL / baseUrl / 说明 / 请求头(JSON)/ body(JSON)/ 示例参数(JSON)/ 用哪个 token;勾「是登录接口」后展开 tokenPath(如 `data.token`)、注入头(默认 `Authorization`)、前缀(默认 `Bearer `)。
249
+
250
+ **服务页签**
251
+ - 每 5 秒刷新。服务页顶部还有一排**二级页签**(**已接管** / **运行中** / **已保存** / **已忽略**)。这一排样式和顶部「接口 / 服务 / 设置」不同:没有灰底容器,选中项是品牌色实心块,用来区分"页面级"和"分组级"两层导航。一次只看一组,不再纵向堆四块。
252
+ - 如果「已接管」是空的、而确实有服务在跑,进入服务页时会自动落到「运行中」,不用多点一次。
253
+ - 每行显示 pid、主类、端口、归属标记、debug 端口、`spring.profiles.active`。
254
+ - **端口带角色标注**:一个服务常常监听两个端口——业务 HTTP 端口(如 8080)和 **Sentinel 规则端口**(如 8719,回 `400 Invalid command`,不能当接口地址)。插件会逐个探测,把真正讲 HTTP 的标成 `HTTP` 并用作 baseUrl,Sentinel 端口灰显标注。
255
+ - 接管/重启时如果进程参数里读不到 `server.port`(端口常写在配置中心或外部配置文件里),插件会在进程起来后持续扫描真实监听端口并**回填到托管记录**,所以「已接管」里的端口与 `HTTP` 标注不会因为「参数里没端口」而空着。
256
+ - **接管启动** = 先 `mvn` 增量编译,再结束 IDEA 那个进程,然后用**同样的 classpath 和主类**重新拉起,交给插件托管;此后可在面板里停止、重启、看实时日志。
257
+ - **保存** 把当前进程的 classpath / 主类 / JVM 参数存成一套配方。存过之后**不需要 IDEA 里先有进程**:到「已保存」页签里点「启动」即可(会先编译,端口被占会提示你先停 IDEA 那份)。
258
+ - **一键启动** 在「已保存」页签顶部:把所有配方逐个拉起来,不用一个个点。
259
+ - **已在运行的服务自动跳过**,不会把正在调试的进程踢掉。
260
+ - **逐个启动、遇错继续**:一个服务失败不影响后面的,最后一起汇报「成功 N / 跳过 N / 失败 N」,每个服务的结局逐条列在进度卡里。
261
+ - 为什么要串行:同一模块的 Maven 编译是互斥的(都写 `target/`),并发跑会互相覆盖产物;而且一个服务起不来通常意味着环境有问题,先看到那条明确原因比满屏报错有用。
262
+ - 编译要几分钟,所以**在后台跑**:点完按钮立刻返回,面板每 1 秒轮询进度(`3/8 正在处理 xxx`),期间页面照常可操作。
263
+ - 模型侧对应 `java_action=start_all`,可用 `java_ids` 只启其中几个。
264
+ - **查询详情**进入日志页:托管进程读插件写的 stdout 文件,IDEA 进程读它自己的 logback 文件(`<工作目录>/logs/<服务名>/info.log`)。
265
+ - 打开时默认只读**最后 200 行**(可点 100 / 200 / 500 / 1000 快速切换,右侧输入框还能自己填 1–5000,回车或失焦生效),之后按字节偏移增量追加,1.5 秒静默刷新且不打断你手动滚动。
266
+ - 支持**自动换行**开关(默认开)、在插件输出多段日志与 info.log / error.log 之间切换。
267
+ - **每次启动写一个独立日志文件**(`java-logs/<服务名>/<时间戳>.log`),重启多次不会混在同一个文件里,历史段可以单独翻。
268
+ - 「忽略」只是把这个服务从「运行中」里收起来,**不影响进程本身**,可在「已忽略」页签里点「取消忽略」恢复;已忽略的行只保留「取消忽略」一个动作,避免误点接管。
269
+ - 「用作 baseUrl」把项目接口基地址设为 `http://127.0.0.1:<HTTP 端口>`。
270
+
271
+ **设置页签**
272
+ - 接口基地址(接口自己没填 baseUrl 时用它)、已保存 token 列表(脱敏值 + 清除)、配置来源(项目路径、项目 key、命中的 YAML 文件)。
273
+
274
+ **DSH 设置里的插件主页**:设置 → 「接口调试」页(`settings.section`,order 80),放跨项目的全局项——**默认工作区**(没有会话上下文时用它当项目目录)、**Maven 主路径**(接管启动前编译用;留空自动探测 MAVEN_HOME → PATH → IDEA 自带,并读取项目 `.idea` 里的 settings.xml 与本地仓库)与**数据目录**(必须是绝对路径,改完后新日志立刻写过去;留空即回到 `DSH_HOME`)。
275
+
276
+ **数据位置**:面板数据存在 `${DSH_HOME}/api-runner-ui.json`(按项目 key 分开),YAML 里的接口与面板接口**按 id 合并**:面板改的同名 id 覆盖 YAML 的值,面板新增的与 YAML 的并存。
277
+
278
+ ### 登录接口怎么写
279
+
280
+ ```yaml
281
+ apis:
282
+ - id: login
283
+ method: POST
284
+ url: /api/login
285
+ baseUrl: http://127.0.0.1:8080
286
+ body:
287
+ username: "{username}"
288
+ password: "{password}"
289
+ login:
290
+ tokenPath: data.token # 从响应 JSON 取 token 的路径,支持 a.b[0].c
291
+ header: Authorization # 存下来后注入到哪个头
292
+ prefix: "Bearer " # 加在 token 前面
293
+
294
+ - id: me
295
+ method: GET
296
+ url: /api/me # 自动带上 Authorization: Bearer xxx
297
+ baseUrl: http://127.0.0.1:8080
298
+ ```
299
+
300
+ 然后 `ApiRunner { action: 'login', api_id: 'login', params: { username: 'alice', password: 'p@ss' } }`;
301
+ 之后调 `me` 就会带 token,也可以在 `call` 里用 `token_from` 临时指定用哪个登录态。
302
+
303
+ `tokenPath` 没写对时不会白跑一趟:登录成功后会在响应里找像 token 的字段,
304
+ 只有一个候选就自动用它并保存,同时告诉你该把它改成什么;
305
+ 有多个候选则列出来让你选。面板里配置的接口会自动把这个路径写回配置,下次不再提示。
306
+
307
+ ### 关于 IDEA 里的服务
308
+
309
+ **推荐做法:接管。** IDEA 起的服务,插件在服务页签里点「接管启动」(或调 `ApiRunner { action: 'java', java_action: 'start', pid }`),流程是:
310
+ 1. 从 `jcmd` 读出该进程的 java 路径、工作目录、完整 classpath、主类、JVM 参数;
311
+ 2. 用 `mvn -pl <模块> -am compile -DskipTests` 增量编译(模块与 reactor 根从 classpath 里的 `target/classes` 反推;Maven 依次找设置页里配的「Maven 主路径」→ `MAVEN_HOME`/`M2_HOME` → PATH → IDEA 自带)。编译参数会自动带上项目 `.idea/workspace.xml` 里 IDEA 用的 `settings.xml` 与本地仓库(`-s` / `-Dmaven.repo.local`),找不到时退回 Maven 安装目录的 `conf/settings.xml`;
312
+ 3. 结束原进程树,等端口释放(Windows 用 `taskkill /T /F`;macOS/Linux 杀进程组 `kill -9 -<pid>`);
313
+ 4. 用同样的 classpath/主类重新拉起,日志写 `<数据目录>/java-logs/<主类名>/<时间戳>.log`(每次启动一个文件),等端口健康后返回新 pid。
314
+
315
+ 接管后 IDEA 的 Run 窗口会显示该进程「已结束」——因为新进程不再挂在 IDEA 下。但**调试能力没丢**:重启时会把原来的 `suspend=y` 调试参数改写成 `server=y,suspend=n,address=127.0.0.1:<原端口>`,你在 IDEA 里用「Remote JVM Debug」连这个端口就能继续断点。编译失败时不会动原进程。
316
+
317
+ 插件启动的进程带 `-DdshApiRunner.runId=<服务名>` 标记,所以服务列表能稳定区分「谁起的」;即使状态文件丢了也能认出来。
318
+
319
+ 另外两种模式仍然可用:
320
+ - **IDEA Run/Debug 按钮 + 不想被接管** → 配 `mode: observe`。模型调 `verify` 时不会杀进程,只等健康检查通过;需要重启时你在 IDEA 里点重启,模型调 `service action=wait` 等你起来。
321
+ - **完全交给插件** → 配 `mode: managed`,`command` 写成 `mvnw.cmd spring-boot:run` 这类可拉起的命令。注意 IDEA 和插件不要同时占用同一个端口。
322
+
323
+ ## HTTP 接口
324
+
325
+ 除工具外,插件还注册了同源路由(需要 DSH 会话认证):
326
+
327
+ - `GET /dsh-api-runner/api/config` → 配置摘要
328
+ - `POST /dsh-api-runner/api/call` → `{ api_id, params }`
329
+ - `POST /dsh-api-runner/api/service` → `{ service_id, service_action }`
330
+ - `POST /dsh-api-runner/api/log` → `{ log_id, ... }`
331
+ - `GET /dsh-api-runner/api/ui` → 面板数据(当前项目的接口与 token 状态、已忽略的 Java 服务、已知项目清单 `projects[]`;带 `?project=<绝对路径>` 可切到别的项目)
332
+ - `POST /dsh-api-runner/api/ui/api` → 保存/覆盖一个面板接口
333
+ - `POST /dsh-api-runner/api/ui/api/delete` → `{ id }`
334
+ - `POST /dsh-api-runner/api/ui/base-url` → `{ baseUrl }`
335
+ - `POST /dsh-api-runner/api/ui/token/clear` → `{ login_id }`
336
+ - `POST /dsh-api-runner/api/login` → `{ api_id, params }`
337
+ - `GET /dsh-api-runner/api/java` → 本机 Java 服务列表(含每个端口的角色、忽略状态、日志路径、已保存配方)
338
+ - `POST /dsh-api-runner/api/java/bind` → `{ binding: { pid, port, mainClass } }`
339
+ - `POST /dsh-api-runner/api/java/unbind` → `{ pid }`
340
+ - `POST /dsh-api-runner/api/java/ignore` → `{ item: { mainClass, name, kind } }` 忽略(只是不显示)
341
+ - `POST /dsh-api-runner/api/java/unignore` → `{ item: { mainClass, name, kind } }`
342
+ - `POST /dsh-api-runner/api/java/start` → `{ pid, port?, compile? }` 接管启动
343
+ - `POST /dsh-api-runner/api/java/stop` → `{ id | pid }`
344
+ - `POST /dsh-api-runner/api/java/restart` → `{ id | pid, compile? }`
345
+ - `POST /dsh-api-runner/api/java/save` → `{ id | pid }` 保存启动配方
346
+ - `POST /dsh-api-runner/api/java/unsave` → `{ id }`
347
+ - `POST /dsh-api-runner/api/java/start-saved` → `{ id, compile?, port?, debug?, timeout_ms? }` 按配方启动
348
+ - `POST /dsh-api-runner/api/java/start-all` → `{ ids?, compile?, restart?, timeout_ms? }` 一键启动(后台跑,立刻返回 `{ batchId, total }`;`ids` 省略即全部已保存的)
349
+ - `GET /dsh-api-runner/api/java/start-all/status?batch=<batchId>` → 批次进度 `{ finished, done, total, current, items, result }`(`batch` 省略取最近一个)
350
+ - `GET /dsh-api-runner/api/java/log?id=<服务名>&from=<字节偏移>` → 插件启动服务的日志增量
351
+ - `GET /dsh-api-runner/api/java/detail?id=<id 或 pid>&source=&from=&lines=&max_bytes=` → 服务详情 + 日志(含可选日志文件清单;不给 `from` 时按 `lines` 取末尾若干行)
352
+ - `GET /dsh-api-runner/api/settings` → 全局设置(默认工作区、Maven 主路径、数据目录、生效目录)
353
+ - `POST /dsh-api-runner/api/settings` → `{ settings: { dataDir?, workspace?, mavenHome? } }`
354
+
355
+ 未认证返回 401 JSON(`{ok:false, code:'UNAUTHORIZED'}`)。面板请求带 `x-dsh-session-id` 让 host 解析项目 cwd;没有会话时用 `?cwd=` 或 body 里的 `cwd` 兜底(必须是绝对路径)。
356
+
357
+ ## 状态与清理
358
+
359
+ - 运行状态:`<数据目录>/api-runner-state.json`,`logs` 与 `services` 两个命名空间互不覆盖。
360
+ - 面板数据:`<数据目录>/api-runner-ui.json`,存 baseUrl、面板接口、登录态、token(含明文,注意别泄漏)、已忽略的 Java 服务清单、已保存的启动配方。
361
+ - 插件设置:`<数据目录>/api-runner-settings.json`,存默认工作区、Maven 主路径与自定义数据目录(本身永远在 `DSH_HOME` 下,否则会先有鸡还是先有蛋)。
362
+ - Java 服务日志:`<数据目录>/java-logs/<服务名>/<时间戳>.log`,每次启动一个文件。
363
+ - 数据目录默认是 `DSH_HOME`,可在 DSH 设置 →「接口调试」里改成任意绝对路径。
364
+ - 卸载插件不会杀 `managed` 服务(故意设计,避免 DSH 重启把后端带停);需要时手动停或调 `service_action=stop`。
365
+
366
+ ## 开发
367
+
368
+ ```powershell
369
+ node test/smoke.mjs # 单元 + 集成(起临时 http server 当后端)
370
+ node test/plugin.mjs # 插件入口契约 + 面板 HTTP 路由(桩 ctx)
371
+ node test/client.mjs # client bundle 形态(__ModuleLoader__ + registerTab)
372
+ node test/managed.mjs # managed 服务启停(真起 node 进程)
373
+ node test/java.mjs # 接管 IDEA 服务(真起 JVM:快照/剥调试参数/编译/停止/重启)
374
+ ```
375
+
376
+ ### 改完源码要重新装载(重要)
377
+
378
+ DSH 用 ESM 动态 import 加载插件,Node 按 **模块 URL** 缓存。在同一个 DSH 进程里关掉再打开 bundle 只会重跑 `apply()`,
379
+ **不会重新 import 模块**,所以直接改 `src/` 看不到任何变化。必须让入口 URL 变化:
380
+
381
+ ```powershell
382
+ node scripts/stage.mjs # 复制 src/ → build/vN/,并改写 cordis.patch.yml 的入口 URL
383
+ ```
384
+
385
+ 然后到 DSH 里把 `dsh-api-runner` bundle 关掉再打开。`scripts/stage.mjs --clean` 会顺手清理旧版本目录。
386
+
387
+ 反过来,**提交代码前要跑 `node scripts/stage.mjs --release`**:它把 `cordis.patch.yml` 的入口从本机 `build/vN/main.js` 的 `file://` 路径改回包名 `dsh-api-runner`,这样别人装上才能正常解析入口,仓库里也不留本机绝对路径。
388
+
389
+ ## 已知限制
390
+
391
+ - 只支持 HTTP/HTTPS 接口,没有 multipart 文件上传。
392
+ - `managed` 服务的进程发现依赖状态文件里的 pid;pid 被系统回收复用后可能误判存活(`isPidAlive` 只做存在性检查)。
393
+ - 日志按字节读,多字节编码(GBK 等)跨读取边界时可能出现半个字符。
394
+ - 面板的接口页签负责「配置 + 试调用」,服务页签负责「列表 + 接管/停止/重启 + 实时日志」;`verify`(重启→等日志→调接口一条龙)仍只在对话里让模型调工具完成。
395
+ - Java 服务发现依赖 `jps`(取 `JAVA_HOME/bin/jps`,找不到就用 PATH 上的 `jps`),没有 JDK 时列表为空并给出提示;端口来源按平台分流(Windows `netstat -ano -p tcp`,macOS/Linux `lsof -nP -iTCP -sTCP:LISTEN`,没有 lsof 时退回 Linux `netstat -tlnp`),同一 pid 多端口时会逐个做 HTTP 探测挑出真正讲业务协议的端口。
396
+ - Maven 探测顺序:设置里的「Maven 主路径」→ `MAVEN_HOME` → `M2_HOME` → `PATH` → IDEA 自带 Maven(三个平台都会找 IDEA 进程路径);找不到 `mvn` 时接管启动会跳过编译并给出提示,服务仍按原参数拉起。
397
+ - 接管依赖 `jcmd`(同一个 JDK),且要求原进程是标准启动器起的(有 classpath)。用 `-jar` 或 argfile 启动的进程无法原样还原。
398
+ - 接管后进程不再挂在 IDEA 下(IDEA Run 窗口显示已结束),需用「Remote JVM Debug」连原调试端口继续断点。
399
+ - 面板保存的 token 是明文落在 `<数据目录>/api-runner-ui.json`(只在 UI 与工具输出里脱敏),本机共用的场景请自行注意。
400
+ - 按配方启动要求配方里有完整的 classpath(所以得先「保存」过一次);`.cmd`/`.bat` 的 Maven 用 `cmd.exe` 调用,路径含空格已处理。
401
+ - 日志首屏按行读末尾,历史大文件(几百 MB)会被截断到所选行数;要看更早的内容请切换日志文件或加大行数。
402
+
403
+ ## 常见问题(排查)
404
+
405
+ **装完看不到「接口调试」?**
406
+ 面板注册到 DSH 自带的三个席位,不需要任何第三方插件。排查顺序:
407
+
408
+ 1. 左栏有没有 ⚡ 图标(`sidebar.panellist`)。有就说明面板挂上了,点它切到中栏整页。
409
+ 2. 右侧栏页签需要先展开右侧栏(会话标题栏最右边的按钮);它只在有会话时出现。
410
+ 3. 设置 → 接口调试 是全局项页,和上面两个内容不同,不要拿它判断面板是否挂载。
411
+ 4. 都没有就硬刷新页面(Ctrl/Cmd+Shift+R)。插件刚装上/刚升级时,浏览器可能还跑着旧的 client bundle。
412
+
413
+ **工具列表里没有 `ApiRunner`?**
414
+ 先看 profile 的 `package.json` 里有没有 `dsh-api-runner`、`dsh.profile.bundles` 里有没有对应条目、bundle 是否处于启用状态,再确认 DSH 版本在兼容范围内。
415
+
416
+ **面板是空的 / 看不到接口?**
417
+ 面板按「项目」归档。没有会话上下文时用设置里的「默认工作区」当项目目录;那里没填就落到 `default` 桶。先在设置里把默认工作区填成你的项目根目录,或直接在一个会话里打开面板。
418
+
419
+ **别人装上却报「找不到入口 / build/vN/main.js 不存在」?**
420
+ 仓库里的 `cordis.patch.yml` 被 `scripts/stage.mjs` 写成了本机 `file://` 路径。提交前跑 `node scripts/stage.mjs --release` 改回包名,再提交。
421
+
422
+ **`ApiRunner { action: 'java', java_action: 'list' }` 返回空列表?**
423
+ 服务发现依赖 `jps`(优先 `JAVA_HOME/bin/jps`,其次 PATH)。没有 JDK 时列表为空并给出提示;有 JDK 仍为空,检查目标服务是否用标准启动器起的。
424
+
425
+ **接管启动失败 / 端口一直没起来?**
426
+ 接管要求 `jcmd` 能读到完整 classpath,`-jar` 或 argfile 启动的进程无法原样还原;原端口被 IDEA 那份占着会先提示你先停掉;找不到 `mvn` 时跳过编译、仍按原参数拉起。端口写在配置中心时,插件会在进程起来后持续扫描真实监听端口并回填。
427
+
428
+ **日志读不到,或者出现半个字符?**
429
+ `logPath` 与 `cwd` 的相对路径按项目工作目录展开(没有工作区时按配置文件所在目录)。日志按字节读,GBK 等多字节编码跨读取边界时可能出现半个字符,换 UTF-8 日志或调大 `max_bytes`。
430
+
431
+ **接口一直 401?**
432
+ 多半是面板里保存的 token 过期了。点「清除 token」后重新「登录并保存」;或检查登录接口的 `tokenPath`、注入头与前缀是否和实际响应一致。
433
+
434
+ ## 许可证
435
+
436
+ MIT,见 [LICENSE](LICENSE)。