ask-user-question-plus 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -3,212 +3,200 @@
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
  [![npm version](https://img.shields.io/npm/v/ask-user-question-plus.svg)](https://www.npmjs.com/package/ask-user-question-plus)
5
5
 
6
- 一个功能强大的 MCP (Model Context Protocol) 服务器,通过精美的 TUI 风格 Web 界面让 LLM(如 Claude)向用户提问。
6
+ A MCP server (Streamable HTTP) for asking user questions via a web interface, inspired by the Claude Code AskUserQuestion Tool.
7
7
 
8
- ![TUI Interface](./assets/screenshot.png)
8
+ > 🌟 Supports Claude Code, Codex, and Gemini CLI!
9
+
10
+ ![Interface Screenshot](./assets/screenshot.png)
11
+
12
+ <div align="center">
13
+
14
+ [**English**](#english) | [**中文**](#chinese)
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ <a id="chinese"></a>
9
21
 
10
22
  ## ✨ 特性
11
23
 
12
- - **🎨 TUI 风格界面**:浏览器中呈现像素级完美的类终端外观(暗/浅色主题)
13
- - **⌨️ 全键盘操作**:支持方向键、Tab、Space、Enter 快捷键
14
- - **📊 丰富交互**:单选、多选、推荐选项、自定义输入(Other)
15
- - **🔄 实时通信**:基于 WebSocket 的实时消息传递
16
- - **⏱️ 超时保护**:可配置超时(默认 10 分钟)
17
- - **📈 可扩展**:支持 1-20 个问题,每个问题选项数量不限
24
+ - **🎨 精美界面**:Claude Code AskUserQuestions 风格样式,支持暗/浅色主题适配
25
+ - **⌨️ 全键盘操作**:支持方向键、Tab、Space、Enter 快捷键,操作流畅
26
+ - **📊 丰富交互**:支持单选、多选、推荐选项标记以及自定义输入(Other)
27
+ - **🔄 实时通信**:基于 WebSocket 的实时消息传递,响应迅速
28
+ - **⏱️ 超时保护**:可配置会话超时时间(默认 10 分钟)
29
+ - **📈 高度可扩展**:支持 1-20 个问题,每个问题选项数量不限
18
30
 
19
- ### Claude Code AskUserQuestion Tool vs AskUserQuestionPlus
31
+ ## 🚀 部署
20
32
 
21
- | 对比项 | AskUserQuestion (内置) | ask-user-question-plus (MCP) |
22
- | ------------ | ---------------------- | ---------------------------- |
23
- | 问题数量限制 | 1-4 个 | 1-20 个 |
24
- | 选项数量限制 | 2-4 个 | 无限制 |
33
+ ### 启动参数
25
34
 
26
- ## 📦 安装
35
+ | 参数 | 说明 | 默认值 | 示例 |
36
+ | :----------------- | :----------- | :--------------- | :----------------- |
37
+ | `--timeout=<毫秒>` | 会话超时时间 | 600000 (10 分钟) | `--timeout=300000` |
38
+ | `--port=<端口>` | 监听端口 | 3456 | `--port=8080` |
27
39
 
28
- ### 方式 1:直接安装(推荐)
40
+ ### 前台运行
29
41
 
30
42
  ```bash
31
- npm install -g ask-user-question-plus
43
+ npx ask-user-question-plus
44
+
45
+ # 自定义端口和超时
46
+ npx ask-user-question-plus -- --port=3456 --timeout=600000
32
47
  ```
33
48
 
34
- ### 方式 2:从源码构建
49
+ ### 后台运行(推荐)
50
+
51
+ #### 使用 pm2
52
+
53
+ > 需先安装 pm2: `npm install -g pm2`
35
54
 
36
55
  ```bash
37
- git clone https://github.com/JoJoJotarou/AskUserQuestionPlus.git
38
- cd AskUserQuestionPlus
39
- npm install
40
- npm run build
56
+ pm2 start "npx ask-user-question-plus" --name ask-user-question-plus-service
57
+
58
+ # 带参数启动
59
+ pm2 start "npx ask-user-question-plus -- --port=3456 --timeout=600000" --name ask-user-question-plus-service
60
+
61
+ # 停止服务
62
+ pm2 stop ask-user-question-plus-service
63
+
64
+ # Windows
65
+ pm2 start cmd --name ask-user-question-plus-service -- /c npx ask-user-question-plus
41
66
  ```
42
67
 
43
- ## 🚀 配置
68
+ #### 使用 Docker
44
69
 
45
- 将此服务器添加到你的 MCP 客户端配置文件(Claude Desktop 的 `claude_desktop_config.json` 或 Claude Code 配置)。
70
+ ```bash
71
+ docker compose up -d
72
+ ```
46
73
 
47
- ### Claude Desktop 配置
74
+ ## ⚙️ 配置
48
75
 
49
- **基础配置:**
76
+ ### Claude Code
50
77
 
51
- ```json
52
- {
53
- "mcpServers": {
54
- "ask-user-question-plus": {
55
- "command": "npx",
56
- "args": ["ask-user-question-plus"]
57
- }
58
- }
59
- }
78
+ ```bash
79
+ claude mcp add -s user -t http AskUserQuestionPlus http://localhost:3456/mcp
60
80
  ```
61
81
 
62
- **自定义超时(毫秒):**
82
+ ### Codex
63
83
 
64
- ```json
65
- {
66
- "mcpServers": {
67
- "ask-user-question-plus": {
68
- "command": "npx",
69
- "args": ["ask-user-question-plus", "--timeout=300000"]
70
- }
71
- }
72
- }
84
+ 在 `~/.codex/config.toml` 中添加配置:
85
+
86
+ ```toml
87
+ experimental_use_rmcp_client = true
88
+
89
+ [mcp_servers.AskUserQuestionPlus]
90
+ url = "http://localhost:3456/mcp/"
73
91
  ```
74
92
 
75
- ### 启动参数
93
+ 或者使用命令行:
76
94
 
77
- | 参数 | 说明 | 默认值 | 示例 |
78
- | ------------------ | -------------- | ---------------- | ------------------ |
79
- | `--timeout=<毫秒>` | 会话超时时间 | 600000 (10 分钟) | `--timeout=300000` |
80
- | `--port=<端口>` | WebSocket 端口 | 3456 | `--port=8080` |
95
+ `~/.codex/config.toml` 中添加配置:
81
96
 
82
- ## 📖 使用方法
97
+ ```toml
98
+ experimental_use_rmcp_client = true
99
+ ```
100
+
101
+ ```bash
102
+ codex mcp add AskUserQuestionsPlus --url http://localhost:3456/mcp/
103
+ ```
104
+
105
+ ### Gemini CLI
106
+
107
+ ```bash
108
+ gemini mcp add AskUserQuestionPlus http://localhost:3456/mcp -s user --transport http
109
+ ```
110
+
111
+ > **⚠️ 注意**:如果遇到 `[Error: Unrecognized key(s) in object: 'type']` 错误 (参见 [issue #15449](https://github.com/google-gemini/gemini-cli/issues/15449)):
112
+ >
113
+ > - 请在 `~/.gemini/settings.json` 中删除 `type` 字段
114
+ > - 或等待 gemini cli `v0.23.0` 发布
115
+
116
+ ## 🕹️ 使用方法
83
117
 
84
118
  ### 基本流程
85
119
 
86
- 1. 启动与 Claude 的对话
87
- 2. Claude 使用工具:`使用 ask_user_question_plus 工具问我 3 个问题`
88
- 3. 浏览器自动打开问卷界面
89
- 4. 使用键盘(方向键、Space、Enter)回答问题
90
- 5. 提交后浏览器标签页自动关闭
120
+ 1. 启动与 Claude Code/Codex/Gemini 的对话。
121
+ 2. 模型调用工具:例如 `使用 ask_user_question_plus 工具问我 3 个问题`。
122
+ 3. 浏览器自动打开问卷界面。
123
+ 4. 使用键盘(方向键、Space、Enter)或鼠标回答问题。
124
+ 5. 提交后浏览器标签页自动关闭,结果返回给模型。
91
125
 
92
126
  ### 键盘快捷键
93
127
 
94
- | 快捷键 | 功能 |
95
- | ------------------ | ----------------------------- |
96
- | `↑` `↓` | 在选项之间移动 / 滚动问题内容 |
97
- | `←` `→` / `Tab` | 在问题标签页之间切换 |
98
- | `Space` | 选中/取消选中当前选项 |
99
- | `Cmd/Ctrl + Enter` | 进入审查模式 / 提交答案 |
128
+ | 快捷键 | 功能 |
129
+ | :----------------- | :------------------------ |
130
+ | `↑` `↓` | 在选项之间移动 / 滚动页面 |
131
+ | `←` `→` / `Tab` | 在不同问题标签页之间切换 |
132
+ | `Space` / `Enter` | 选中/取消选中当前选项 |
133
+ | `Cmd/Ctrl + Enter` | 进入提交 Tab / 提交答案 |
100
134
 
101
- ## 🔧 工具 API
135
+ ## 🔧 工具定义 (Tools)
102
136
 
103
- ### MCP 工具:`ask_user_question_plus`
137
+ ### `ask_user_question_plus`
104
138
 
105
- **输入 Schema:**
139
+ **输入 Schema (Input):**
106
140
 
107
141
  ```typescript
108
142
  {
109
143
  questions: [
110
144
  {
111
145
  id: string; // 问题唯一标识
112
- header: string; // 标签标题(≤12 字符推荐)
113
- text: string; // 问题文本
114
- type: "single" | "multiple"; // 单选或多选
146
+ header: string; // 标签标题(推荐 ≤12 字符)
147
+ text: string; // 问题正文
148
+ type: "single" | "multiple"; // 问题类型:单选或多选
115
149
  options: [
116
150
  {
117
151
  value: string; // 选项值
118
- label: string; // 选项标签
152
+ label: string; // 选项显示文本
119
153
  description?: string; // 选项描述(可选)
120
- recommended?: boolean;// 是否推荐(可选)
154
+ recommended?: boolean;// 是否标记为推荐(可选)
121
155
  }
122
156
  ]
123
157
  }
124
- ] // 最少 1 个,最多 20 个问题
158
+ ] // 限制:1-20 个问题
125
159
  }
126
160
  ```
127
161
 
128
- **输出 Schema:**
162
+ **输出 Schema (Output):**
129
163
 
130
164
  ```typescript
131
165
  {
132
- [questionId: string]: string | string[] // 单选为字符串,多选为数组
166
+ "answers": {
167
+ "questionId1": {"value" : "", "other":""},
168
+ "questionId2": {"value" : "", "other":""},
169
+ }
133
170
  }
134
171
  ```
135
172
 
136
- > **💡 提示**:前端会自动为每个问题添加 "Other (自定义输入)" 选项,无需手动指定。
173
+ > **💡 提示**:前端界面会自动为每个问题追加 "Other (自定义输入)" 选项。
137
174
 
138
- ### 使用示例
175
+ ## 🛠️ 开发指南
139
176
 
140
- **单选问题:**
177
+ ### 克隆项目
141
178
 
142
- ```json
143
- {
144
- "questions": [
145
- {
146
- "id": "framework",
147
- "header": "Framework",
148
- "text": "选择你的前端框架:",
149
- "type": "single",
150
- "options": [
151
- {
152
- "value": "react",
153
- "label": "React",
154
- "description": "用于构建用户界面的 JavaScript 库",
155
- "recommended": true
156
- },
157
- {
158
- "value": "vue",
159
- "label": "Vue",
160
- "description": "渐进式 JavaScript 框架"
161
- }
162
- ]
163
- }
164
- ]
165
- }
179
+ ```bash
180
+ git clone https://github.com/JoJoJotarou/AskUserQuestionPlus.git && cd AskUserQuestionPlus
166
181
  ```
167
182
 
168
- **多选问题:**
183
+ ### 安装依赖
169
184
 
170
- ```json
171
- {
172
- "questions": [
173
- {
174
- "id": "features",
175
- "header": "Features",
176
- "text": "你需要哪些功能?(可多选)",
177
- "type": "multiple",
178
- "options": [
179
- {
180
- "value": "auth",
181
- "label": "用户认证",
182
- "description": "登录、注册、密码重置"
183
- },
184
- {
185
- "value": "api",
186
- "label": "REST API",
187
- "description": "RESTful API 端点"
188
- },
189
- {
190
- "value": "db",
191
- "label": "数据库",
192
- "description": "PostgreSQL/MySQL 支持"
193
- }
194
- ]
195
- }
196
- ]
197
- }
185
+ ```bash
186
+ npm install
198
187
  ```
199
188
 
200
- ## 🛠️ 开发
201
-
202
- ### 运行开发模式
189
+ ### 开发模式
203
190
 
204
191
  ```bash
205
192
  npm run dev
206
193
  ```
207
194
 
208
- ### 构建项目
195
+ ### 构建
209
196
 
210
197
  ```bash
211
198
  npm run build
199
+ # npm start
212
200
  ```
213
201
 
214
202
  ### 目录结构
@@ -217,48 +205,220 @@ npm run build
217
205
  ask-user-question-plus/
218
206
  ├── src/
219
207
  │ ├── index.ts # 主入口文件
220
- │ ├── ws-service.ts # WebSocket 服务
221
- │ └── mcp-server.ts # MCP 服务器逻辑
208
+ │ ├── ws-service.ts # WebSocket 服务逻辑
209
+ │ └── mcp-server.ts # MCP 服务器定义
222
210
  ├── public/
223
- │ └── index.html # TUI 前端界面
224
- ├── dist/ # 编译输出目录
225
- └── package.json # 项目配置
211
+ │ └── index.html # 前端 Web 界面
212
+ ├── dist/ # 编译输出
213
+ └── package.json
226
214
  ```
227
215
 
228
- ### 技术栈
216
+ ## 🤝 贡献
217
+
218
+ 欢迎提交 [Issues](https://github.com/JoJoJotarou/AskUserQuestionPlus/issues) 或 Pull Requests 来改进这个项目。
229
219
 
230
- - **后端**:Node.js 20+, TypeScript, Express, ws, @modelcontextprotocol/sdk, Zod
231
- - **前端**:原生 HTML/CSS/JavaScript(单文件 1184 行)
220
+ ## 📄 许可证
221
+
222
+ [MIT License](LICENSE)
232
223
 
233
- ## 🐳 Docker 部署
224
+ ## 🔗 相关链接
225
+
226
+ - [Model Context Protocol](https://modelcontextprotocol.io/)
227
+ - [Codex Support for streamable HTTP MCP](https://github.com/openai/codex/pull/4317)
228
+
229
+ ---
230
+
231
+ <a id="english"></a>
232
+
233
+ ## ✨ Features
234
+
235
+ - **🎨 Beautiful Interface**: Replicates the Claude Code AskUserQuestions style with support for Dark/Light themes.
236
+ - **⌨️ Full Keyboard Control**: Seamless navigation using Arrow keys, Tab, Space, and Enter.
237
+ - **📊 Rich Interactions**: Supports single choice, multiple choice, recommended options, and custom input ("Other").
238
+ - **🔄 Real-time Communication**: WebSocket-based messaging for instant feedback.
239
+ - **⏱️ Timeout Protection**: Configurable session timeout (Default: 10 minutes).
240
+ - **📈 Scalable**: Supports 1 to 20 questions with unlimited options per question.
241
+
242
+ ## 🚀 Deployment
243
+
244
+ ### Startup Parameters
245
+
246
+ | Argument | Description | Default | Example |
247
+ | :--------------- | :------------------------------ | :--------------- | :----------------- |
248
+ | `--timeout=<ms>` | Session timeout in milliseconds | 600000 (10 mins) | `--timeout=300000` |
249
+ | `--port=<port>` | Server port | 3456 | `--port=8080` |
250
+
251
+ ### Foreground
234
252
 
235
253
  ```bash
236
- # 使用 Docker Compose
237
- docker-compose up -d
254
+ npx ask-user-question-plus
238
255
 
239
- # 查看日志
240
- docker-compose logs -f
256
+ # Custom port and timeout
257
+ npx ask-user-question-plus -- --port=3456 --timeout=600000
258
+ ```
241
259
 
242
- # 停止服务
243
- docker-compose down
260
+ ### Background (Recommended)
261
+
262
+ #### Using pm2
263
+
264
+ > Install pm2 first: `npm install -g pm2`
265
+
266
+ ```bash
267
+ pm2 start "npx ask-user-question-plus" --name ask-user-question-plus-service
268
+
269
+ # With arguments
270
+ pm2 start "npx ask-user-question-plus -- --port=3456 --timeout=600000" --name ask-user-question-plus-service
271
+
272
+ # Stop service
273
+ pm2 stop ask-user-question-plus-service
274
+
275
+ # Windows
276
+ pm2 start cmd --name ask-user-question-plus-service -- /c npx ask-user-question-plu
244
277
  ```
245
278
 
246
- ## 🤝 贡献
279
+ #### Using Docker
247
280
 
248
- 欢迎贡献!请提交 [Issues](https://github.com/JoJoJotarou/AskUserQuestionPlus/issues) 或 Pull Requests。
281
+ ```bash
282
+ docker compose up -d
283
+ ```
249
284
 
250
- ## 📄 许可证
285
+ ## ⚙️ Configuration
251
286
 
252
- [MIT License](LICENSE)
287
+ ### Claude Code
253
288
 
254
- ## 🔗 相关链接
289
+ ```bash
290
+ claude mcp add -s user -t http AskUserQuestionPlus http://localhost:3456/mcp
291
+ ```
255
292
 
256
- - [GitHub 仓库](https://github.com/JoJoJotarou/AskUserQuestionPlus)
257
- - [npm 包](https://www.npmjs.com/package/ask-user-question-plus)
258
- - [问题反馈](https://github.com/JoJoJotarou/AskUserQuestionPlus/issues)
259
- - [Model Context Protocol](https://modelcontextprotocol.io/)
260
- - [Claude Code](https://github.com/anthropics/claude-code)
293
+ ### Codex
261
294
 
262
- ---
295
+ Add to `~/.codex/config.toml`:
296
+
297
+ ```toml
298
+ experimental_use_rmcp_client = true
299
+
300
+ [mcp_servers.AskUserQuestionPlus]
301
+ url = "http://localhost:3456/mcp/"
302
+ ```
303
+
304
+ Or via CLI:
305
+
306
+ Add to `~/.codex/config.toml` first:
307
+
308
+ ```toml
309
+ experimental_use_rmcp_client = true
310
+ ```
311
+
312
+ ```bash
313
+ codex mcp add AskUserQuestionsPlus --url http://localhost:3456/mcp/
314
+ ```
315
+
316
+ ### Gemini CLI
317
+
318
+ ```bash
319
+ gemini mcp add AskUserQuestionPlus http://localhost:3456/mcp -s user --transport http
320
+ ```
321
+
322
+ > **⚠️ Note**: If you encounter `[Error: Unrecognized key(s) in object: 'type']` ([Issue #15449](https://github.com/google-gemini/gemini-cli/issues/15449)):
323
+ >
324
+ > - Remove the `type` field in `~/.gemini/settings.json`.
325
+ > - Or wait for gemini cli `v0.23.0`.
326
+
327
+ ## 🕹️ Usage
328
+
329
+ ### Basic Workflow
330
+
331
+ 1. Start a conversation with Claude, Codex, or Gemini.
332
+ 2. Ask the model to use the tool: e.g., "Use ask_user_question_plus to ask me 3 questions."
333
+ 3. A browser tab will automatically open with the questionnaire.
334
+ 4. Answer using your keyboard (Arrows, Space, Enter) or mouse.
335
+ 5. Upon submission, the tab closes, and answers are sent back to the model.
336
+
337
+ ### Keyboard Shortcuts
338
+
339
+ | Shortcut | Function |
340
+ | :----------------- | :---------------------------- |
341
+ | `↑` `↓` | Move between options / Scroll |
342
+ | `←` `→` / `Tab` | Switch between question tabs |
343
+ | `Space` / `Enter` | Select/Deselect option |
344
+ | `Cmd/Ctrl + Enter` | Submit Tab / Submit answers |
345
+
346
+ ## 🔧 Tools Definition
347
+
348
+ ### `ask_user_question_plus`
349
+
350
+ **Input Schema:**
351
+
352
+ ```typescript
353
+ {
354
+ questions: [
355
+ {
356
+ id: string; // Unique identifier
357
+ header: string; // Tab header (Rec. ≤12 chars)
358
+ text: string; // Question text
359
+ type: "single" | "multiple"; // Question type
360
+ options: [
361
+ {
362
+ value: string; // Option value
363
+ label: string; // Option label
364
+ description?: string; // Option description (optional)
365
+ recommended?: boolean;// Recommended flag (optional)
366
+ }
367
+ ]
368
+ }
369
+ ] // Limit: 1-20 questions
370
+ }
371
+ ```
372
+
373
+ **Output Schema:**
374
+
375
+ ```json
376
+ {
377
+ "answers": {
378
+ "questionId1": { "value": "", "other": "" },
379
+ "questionId2": { "value": "", "other": "" }
380
+ }
381
+ }
382
+ ```
383
+
384
+ > **💡 Tip**: The "Other" (custom input) option is automatically added by the frontend.
385
+
386
+ ## 🛠️ Development
387
+
388
+ ### Clone
389
+
390
+ ```bash
391
+ git clone https://github.com/JoJoJotarou/AskUserQuestionPlus.git && cd AskUserQuestionPlus
392
+ ```
393
+
394
+ ### Install Dependencies
263
395
 
264
- **注意**:此工具需要浏览器支持,确保系统可以自动打开默认浏览器。
396
+ ```bash
397
+ npm install
398
+ ```
399
+
400
+ ### Run Dev Mode
401
+
402
+ ```bash
403
+ npm run dev
404
+ ```
405
+
406
+ ### Build
407
+
408
+ ```bash
409
+ npm run build
410
+ # npm start
411
+ ```
412
+
413
+ ## 🤝 Contribution
414
+
415
+ Contributions are welcome! Please submit [Issues](https://github.com/JoJoJotarou/AskUserQuestionPlus/issues) or Pull Requests.
416
+
417
+ ## 📄 License
418
+
419
+ [MIT License](LICENSE)
420
+
421
+ ## 🔗 Related Links
422
+
423
+ - [Model Context Protocol](https://modelcontextprotocol.io/)
424
+ - [Codex Support for streamable HTTP MCP](https://github.com/openai/codex/pull/4317)
@@ -22,37 +22,37 @@ export class MCPServerService {
22
22
  }
23
23
  registerTools(server) {
24
24
  server.registerTool("ask_user_question_plus", {
25
- description: "一个基于 MCP 的用户交互工具,用于向用户提出结构化问题并收集反馈。该工具支持单选、多选类型的问题,允许标记推荐选项,并支持任意数量的问题与答案选项。前端会自动为每个问题添加 'Other'(自定义输入)选项,适用于复杂配置与决策流程。",
25
+ description: "An MCP-based user interaction tool for asking structured questions and collecting feedback. It supports single-choice and multiple-choice questions, marking recommended options, and flexible numbers of questions and options. An 'Other' (custom input) option is automatically added to each question, making it suitable for complex configuration and decision-making workflows.",
26
26
  inputSchema: {
27
27
  questions: z
28
28
  .array(z.object({
29
- id: z.string().describe("问题唯一标识"),
29
+ id: z.string().describe("Unique identifier for the question"),
30
30
  header: z
31
31
  .string()
32
32
  .max(12)
33
- .describe("问题标题(最多 12 个字符)"),
34
- text: z.string().describe("问题文本"),
33
+ .describe("Short header for the question tab (max 12 chars)"),
34
+ text: z.string().describe("Main text of the question"),
35
35
  type: z
36
36
  .enum(["single", "multiple"])
37
- .describe("问题类型:single(单选)或 multiple(多选)"),
37
+ .describe("Question type: 'single' or 'multiple'"),
38
38
  options: z
39
39
  .array(z.object({
40
- value: z.string().describe("选项值"),
41
- label: z.string().describe("选项标签"),
40
+ value: z.string().describe("Technical value of the option"),
41
+ label: z.string().describe("Display label for the option"),
42
42
  description: z
43
43
  .string()
44
44
  .optional()
45
- .describe("选项描述(可选)"),
45
+ .describe("Additional description for the option"),
46
46
  recommended: z
47
47
  .boolean()
48
48
  .optional()
49
- .describe("是否为推荐选项(可选)"),
49
+ .describe("Whether this is a recommended option"),
50
50
  }))
51
- .describe("选项列表(不限数量)。注意:前端会自动添加 'Other'(自定义输入)选项,用户可自定义输入。"),
51
+ .describe("List of options. Note: An 'Other' option is automatically added for custom user input."),
52
52
  }))
53
53
  .min(1)
54
54
  .max(20)
55
- .describe("问题列表(最多可支持 20 个问题)"),
55
+ .describe("List of questions (up to 20 items)"),
56
56
  },
57
57
  }, async ({ questions }) => {
58
58
  console.error("[MCP] Tool invoked: ask_user_question_plus");
@@ -39,6 +39,11 @@ export class WebSocketService {
39
39
  clearTimeout(session.timeout);
40
40
  session.timeout = null;
41
41
  }
42
+ // Send session meta first so the client can start countdown when it receives NEW_QUESTION.
43
+ this.sendMessage(ws, {
44
+ type: "SESSION_META",
45
+ payload: { timeoutMs: this.sessionTimeoutMs },
46
+ });
42
47
  // Send questions to client
43
48
  this.sendMessage(ws, {
44
49
  type: "NEW_QUESTION",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ask-user-question-plus",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A TUI-style MCP server for asking user questions via a web interface.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,6 +11,7 @@
11
11
  "build": "tsc",
12
12
  "start": "node dist/index.js",
13
13
  "dev": "tsx src/index.ts",
14
+ "test": "echo 'No tests yet' && exit 0",
14
15
  "prepublishOnly": "npm run build"
15
16
  },
16
17
  "keywords": [
package/public/index.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="zh-CN">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -545,7 +545,7 @@
545
545
 
546
546
  <!-- GitHub Star -->
547
547
  <a
548
- href="https://github.com/changjunjie/ask-user-question-plus"
548
+ href="https://github.com/JoJoJotarou/AskUserQuestionPlus"
549
549
  target="_blank"
550
550
  class="icon-btn"
551
551
  title="Star on GitHub"
@@ -575,10 +575,10 @@
575
575
  <div class="footer">
576
576
  <div class="key-hints">
577
577
  <div class="key-hint-item">
578
- <span class="key-tag">↑↓</span> 选项/滚动
578
+ <span class="key-tag">↑↓</span> Select/Scroll
579
579
  </div>
580
580
  <div class="key-hint-item">
581
- <span class="key-tag">← →/Tab</span> 切换问题
581
+ <span class="key-tag">← →/Tab</span> Switch
582
582
  </div>
583
583
  <div class="key-hint-item">
584
584
  <span class="key-tag">Enter/Space</span>
@@ -586,7 +586,7 @@
586
586
  </div>
587
587
  </div>
588
588
  <button class="submit-btn" id="submit-all" disabled>
589
- Go Submit
589
+ Review & Submit
590
590
  <span class="submit-shortcut" id="submit-shortcut-text">⌘+Enter</span>
591
591
  </button>
592
592
  </div>
@@ -601,11 +601,57 @@
601
601
  let activeReviewIndex = 0;
602
602
  let shouldFocusOther = false;
603
603
  let ws = null;
604
+ let sessionTimeoutMs = null;
605
+ let countdownInterval = null;
606
+ let countdownEndsAt = null;
607
+ let hasReceivedQuestions = false;
604
608
  let sessionId =
605
609
  new URLSearchParams(window.location.search).get("sessionId") ||
606
610
  "default";
607
611
  const isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
608
612
 
613
+ function formatMmSs(ms) {
614
+ const totalSeconds = Math.max(0, Math.floor(ms / 1000));
615
+ const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, "0");
616
+ const seconds = String(totalSeconds % 60).padStart(2, "0");
617
+ return `${minutes}:${seconds}`;
618
+ }
619
+
620
+ function setSessionInfo(statusText) {
621
+ document.getElementById("session-info").textContent = statusText;
622
+ }
623
+
624
+ function setSessionInfoConnected() {
625
+ const base = `Session: ${sessionId} (Connected)`;
626
+ if (countdownEndsAt) {
627
+ const remaining = countdownEndsAt - Date.now();
628
+ setSessionInfo(`${base} | Timeout in ${formatMmSs(remaining)}`);
629
+ } else {
630
+ setSessionInfo(base);
631
+ }
632
+ }
633
+
634
+ function stopCountdown() {
635
+ if (countdownInterval) {
636
+ clearInterval(countdownInterval);
637
+ countdownInterval = null;
638
+ }
639
+ countdownEndsAt = null;
640
+ }
641
+
642
+ function startCountdown(timeoutMs) {
643
+ stopCountdown();
644
+ countdownEndsAt = Date.now() + timeoutMs;
645
+
646
+ // Tick immediately to avoid showing stale text for up to 1s.
647
+ setSessionInfoConnected();
648
+ countdownInterval = setInterval(() => {
649
+ const remaining = countdownEndsAt - Date.now();
650
+ setSessionInfoConnected();
651
+ if (remaining <= 0) stopCountdown();
652
+ }, 1000);
653
+ }
654
+
609
655
  // --- WebSocket Logic ---
610
656
  function connectWS() {
611
657
  // Robust WS URL construction
@@ -628,9 +674,7 @@
628
674
 
629
675
  ws.onopen = () => {
630
676
  console.error("WS Open");
631
- document.getElementById(
632
- "session-info"
633
- ).textContent = `Session: ${sessionId} (Connected)`;
677
+ setSessionInfoConnected();
634
678
  document.getElementById("session-info").style.color =
635
679
  "var(--green-color)";
636
680
  ws.send(JSON.stringify({ type: "GET_STATE" }));
@@ -649,6 +693,19 @@
649
693
  try {
650
694
  const message = JSON.parse(event.data);
651
695
 
696
+ if (message.type === "SESSION_META" && message.payload) {
697
+ sessionTimeoutMs =
698
+ typeof message.payload.timeoutMs === "number"
699
+ ? message.payload.timeoutMs
700
+ : null;
701
+
702
+ // For B: start countdown when we have received the questions.
703
+ if (hasReceivedQuestions && sessionTimeoutMs) {
704
+ startCountdown(sessionTimeoutMs);
705
+ }
706
+ return;
707
+ }
708
+
652
709
  if (
653
710
  message.type === "SYNC_STATE" ||
654
711
  message.type === "NEW_QUESTION"
@@ -660,6 +717,8 @@
660
717
 
661
718
  if (questions && questions.length > 0) {
662
719
  QUESTIONS_DATA = questions;
720
+ hasReceivedQuestions = true;
721
+ if (sessionTimeoutMs) startCountdown(sessionTimeoutMs);
663
722
  initAnswers();
664
723
  activeQuestionIndex = 0;
665
724
  activeOptionIndex = 0;
@@ -675,6 +734,7 @@
675
734
  };
676
735
 
677
736
  ws.onclose = () => {
737
+ stopCountdown();
678
738
  document.getElementById(
679
739
  "session-info"
680
740
  ).textContent = `Disconnected. Reconnecting...`;
@@ -799,7 +859,7 @@
799
859
  ${opt.label}
800
860
  ${
801
861
  opt.recommended
802
- ? '<span class="recommended-tag">(推荐)</span>'
862
+ ? '<span class="recommended-tag">(Recommended)</span>'
803
863
  : ""
804
864
  }
805
865
  </div>
@@ -833,7 +893,7 @@
833
893
  <div class="question-header">
834
894
  <span class="q-text">${q.text}${
835
895
  q.type === "multiple"
836
- ? ' <span style="color:var(--dim-color); font-size:0.8em">(可多选)</span>'
896
+ ? ' <span style="color:var(--dim-color); font-size:0.8em">(Multi-select)</span>'
837
897
  : ""
838
898
  }</span>
839
899
  </div>
@@ -875,7 +935,7 @@
875
935
 
876
936
  if (q.type === "single") {
877
937
  if (!ans.value) {
878
- error = "未选择答案";
938
+ error = "No answer selected";
879
939
  } else {
880
940
  const opt = [
881
941
  ...q.options,
@@ -885,7 +945,7 @@
885
945
  displayValue = opt.label;
886
946
  if (opt.isOther) {
887
947
  if (!ans.otherText.trim()) {
888
- error = "请填写自定义内容";
948
+ error = "Please provide custom input";
889
949
  displayValue += ": [Empty]";
890
950
  } else {
891
951
  displayValue += `: ${ans.otherText}`;
@@ -895,7 +955,7 @@
895
955
  }
896
956
  } else {
897
957
  if (!ans.values || ans.values.length === 0) {
898
- error = "请至少选择一项";
958
+ error = "Please select at least one option";
899
959
  } else {
900
960
  displayValue = ans.values
901
961
  .map((v) => {
@@ -907,7 +967,7 @@
907
967
  let label = opt.label;
908
968
  if (opt.isOther) {
909
969
  if (!ans.otherText.trim()) {
910
- error = "请填写自定义内容";
970
+ error = "Please provide custom input";
911
971
  label += ": [Empty]";
912
972
  } else {
913
973
  label += `: ${ans.otherText}`;
@@ -939,7 +999,7 @@
939
999
  container.innerHTML = `
940
1000
  <div class="question-block">
941
1001
  <div class="question-header">
942
- <span class="q-text">请检查您的回答,确认无误后提交!</span>
1002
+ <span class="q-text">Please review your answers before submitting!</span>
943
1003
  </div>
944
1004
  ${summaryHtml}
945
1005
  </div>
@@ -961,13 +1021,13 @@
961
1021
  const shortcutText = isMac ? "⌘+Enter" : "Ctrl+Enter";
962
1022
 
963
1023
  if (isReview) {
964
- submitBtn.innerHTML = `Submt Answers <span class="submit-shortcut" id="submit-shortcut-text">${shortcutText}</span>`;
1024
+ submitBtn.innerHTML = `Submit Answers <span class="submit-shortcut" id="submit-shortcut-text">${shortcutText}</span>`;
965
1025
  submitBtn.disabled = !isValid;
966
- if (spaceHint) spaceHint.textContent = "跳转";
1026
+ if (spaceHint) spaceHint.textContent = "Jump";
967
1027
  } else {
968
- submitBtn.innerHTML = `Go Submit <span class="submit-shortcut" id="submit-shortcut-text">${shortcutText}</span>`;
1028
+ submitBtn.innerHTML = `Review & Submit <span class="submit-shortcut" id="submit-shortcut-text">${shortcutText}</span>`;
969
1029
  submitBtn.disabled = QUESTIONS_DATA.length === 0;
970
- if (spaceHint) spaceHint.textContent = "选择";
1030
+ if (spaceHint) spaceHint.textContent = "Select";
971
1031
  }
972
1032
  }
973
1033
 
@@ -1091,7 +1151,7 @@
1091
1151
  submitBtn.disabled = true;
1092
1152
 
1093
1153
  setTimeout(() => {
1094
- alert("提交成功!即将关闭...");
1154
+ alert("Submitted successfully! Closing...");
1095
1155
  submitBtn.textContent = "DONE";
1096
1156
  window.close();
1097
1157
  document.body.innerHTML =