mcp-interactive-choice 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 +97 -97
- package/bin/native-ui-darwin-arm64 +0 -0
- package/bin/native-ui-darwin-x64 +0 -0
- package/bin/native-ui-win32-arm64.exe +0 -0
- package/bin/native-ui-win32-ia32.exe +0 -0
- package/bin/native-ui-win32-x64.exe +0 -0
- package/dist/index.js +41 -11
- package/dist/index.test.js +38 -1
- package/package.json +37 -33
- package/dist/spawn.test.js +0 -39
- package/dist/ui +0 -262
package/README.md
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
# Interactive Choice MCP Server (Native UI)
|
|
2
|
-
|
|
3
|
-
A Model Context Protocol (MCP) server that allows AI agents to ask questions through a **native window** (built with Tauri), preventing context-breaking interruptions and providing a premium user experience.
|
|
4
|
-
|
|
5
|
-
## ✨ Features
|
|
6
|
-
|
|
7
|
-
- **Native Window**: A native window appears when the agent needs your input.
|
|
8
|
-
- **Markdown Support**: Detailed descriptions from the AI are rendered in markdown.
|
|
9
|
-
- **Cross-Platform**: Supports Windows and macOS.
|
|
10
|
-
|
|
11
|
-
## 🚀 Setup & Installation
|
|
12
|
-
|
|
13
|
-
### Option A: Run with npx (Recommended)
|
|
14
|
-
You can use the server directly via `npx` in your MCP client configuration:
|
|
15
|
-
|
|
16
|
-
```json
|
|
17
|
-
{
|
|
18
|
-
"mcpServers": {
|
|
19
|
-
"interactive-choice": {
|
|
20
|
-
"command": "npx",
|
|
21
|
-
"args": [
|
|
22
|
-
"-y",
|
|
23
|
-
"mcp-interactive-choice"
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Option B: Local Build
|
|
31
|
-
#### 1. Build Everything
|
|
32
|
-
From the project root:
|
|
33
|
-
```bash
|
|
34
|
-
npm install
|
|
35
|
-
npm run build
|
|
36
|
-
```
|
|
37
|
-
This will:
|
|
38
|
-
1. Build the frontend (Vite)
|
|
39
|
-
2. Build the Tauri binary (Rust)
|
|
40
|
-
3. Compile the MCP server (TypeScript)
|
|
41
|
-
4. Copy the binary to the `bin/` folder
|
|
42
|
-
|
|
43
|
-
#### 2. Register with your MCP Client
|
|
44
|
-
Update your configuration (e.g., `claude_desktop_config.json`):
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"mcpServers": {
|
|
49
|
-
"interactive-choice": {
|
|
50
|
-
"command": "node",
|
|
51
|
-
"args": [
|
|
52
|
-
"/path/to/mcp-interactive-choice/dist/index.js"
|
|
53
|
-
]
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## 🛠️ Tool: `ask_user`
|
|
60
|
-
|
|
61
|
-
The agent calls this tool when it needs a human decision.
|
|
62
|
-
|
|
63
|
-
**Arguments:**
|
|
64
|
-
- `title` (optional): Short headline for the question.
|
|
65
|
-
- `body` (optional): Detailed context in Markdown.
|
|
66
|
-
- `choices` (required): List of strings.
|
|
67
|
-
- `recommended` (optional): One of the strings from `choices` that the agent suggests.
|
|
68
|
-
- `allowCustom` (optional): Boolean to show a text box for custom input.
|
|
69
|
-
- `timeoutSec` (optional): How long to wait (in seconds) before the tool auto-fails.
|
|
70
|
-
|
|
71
|
-
**Response:**
|
|
72
|
-
- Returns the string value of the selected choice.
|
|
73
|
-
- Returns the custom text if provided.
|
|
74
|
-
- Returns `"user cancelled the selection"` if the window is closed manually.
|
|
75
|
-
|
|
76
|
-
## 🛠️ Development & Debugging
|
|
77
|
-
|
|
78
|
-
### UI Development (Hot Reloading)
|
|
79
|
-
To iterate on the UI with hot reloading:
|
|
80
|
-
1. Go to `packages/native-ui`.
|
|
81
|
-
2. Run `npm run tauri dev`.
|
|
82
|
-
|
|
83
|
-
**Running with CLI parameters (Windows/PowerShell):**
|
|
84
|
-
To test specific data during development, use the flag `--input`:
|
|
85
|
-
```powershell
|
|
86
|
-
npm run tauri dev -- -- -- --input '{"title":"Dev Test","choices":["A","B"]}'
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### 🔍 Testing with MCP Inspector
|
|
90
|
-
1. **Build Everything**: `npm run build` at the root.
|
|
91
|
-
2. **Run Inspector** from the project root:
|
|
92
|
-
```bash
|
|
93
|
-
npx -y @modelcontextprotocol/inspector node dist/index.js
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## 📝 License
|
|
97
|
-
MIT
|
|
1
|
+
# Interactive Choice MCP Server (Native UI)
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that allows AI agents to ask questions through a **native window** (built with Tauri), preventing context-breaking interruptions and providing a premium user experience.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- **Native Window**: A native window appears when the agent needs your input.
|
|
8
|
+
- **Markdown Support**: Detailed descriptions from the AI are rendered in markdown.
|
|
9
|
+
- **Cross-Platform**: Supports Windows and macOS.
|
|
10
|
+
|
|
11
|
+
## 🚀 Setup & Installation
|
|
12
|
+
|
|
13
|
+
### Option A: Run with npx (Recommended)
|
|
14
|
+
You can use the server directly via `npx` in your MCP client configuration:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"interactive-choice": {
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": [
|
|
22
|
+
"-y",
|
|
23
|
+
"mcp-interactive-choice"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Option B: Local Build
|
|
31
|
+
#### 1. Build Everything
|
|
32
|
+
From the project root:
|
|
33
|
+
```bash
|
|
34
|
+
npm install
|
|
35
|
+
npm run build
|
|
36
|
+
```
|
|
37
|
+
This will:
|
|
38
|
+
1. Build the frontend (Vite)
|
|
39
|
+
2. Build the Tauri binary (Rust)
|
|
40
|
+
3. Compile the MCP server (TypeScript)
|
|
41
|
+
4. Copy the binary to the `bin/` folder
|
|
42
|
+
|
|
43
|
+
#### 2. Register with your MCP Client
|
|
44
|
+
Update your configuration (e.g., `claude_desktop_config.json`):
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"interactive-choice": {
|
|
50
|
+
"command": "node",
|
|
51
|
+
"args": [
|
|
52
|
+
"/path/to/mcp-interactive-choice/dist/index.js"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 🛠️ Tool: `ask_user`
|
|
60
|
+
|
|
61
|
+
The agent calls this tool when it needs a human decision.
|
|
62
|
+
|
|
63
|
+
**Arguments:**
|
|
64
|
+
- `title` (optional): Short headline for the question.
|
|
65
|
+
- `body` (optional): Detailed context in Markdown.
|
|
66
|
+
- `choices` (required): List of strings.
|
|
67
|
+
- `recommended` (optional): One of the strings from `choices` that the agent suggests.
|
|
68
|
+
- `allowCustom` (optional): Boolean to show a text box for custom input.
|
|
69
|
+
- `timeoutSec` (optional): How long to wait (in seconds) before the tool auto-fails.
|
|
70
|
+
|
|
71
|
+
**Response:**
|
|
72
|
+
- Returns the string value of the selected choice.
|
|
73
|
+
- Returns the custom text if provided.
|
|
74
|
+
- Returns `"user cancelled the selection"` if the window is closed manually.
|
|
75
|
+
|
|
76
|
+
## 🛠️ Development & Debugging
|
|
77
|
+
|
|
78
|
+
### UI Development (Hot Reloading)
|
|
79
|
+
To iterate on the UI with hot reloading:
|
|
80
|
+
1. Go to `packages/native-ui`.
|
|
81
|
+
2. Run `npm run tauri dev`.
|
|
82
|
+
|
|
83
|
+
**Running with CLI parameters (Windows/PowerShell):**
|
|
84
|
+
To test specific data during development, use the flag `--input`:
|
|
85
|
+
```powershell
|
|
86
|
+
npm run tauri dev -- -- -- --input '{"title":"Dev Test","choices":["A","B"]}'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 🔍 Testing with MCP Inspector
|
|
90
|
+
1. **Build Everything**: `npm run build` at the root.
|
|
91
|
+
2. **Run Inspector** from the project root:
|
|
92
|
+
```bash
|
|
93
|
+
npx -y @modelcontextprotocol/inspector node dist/index.js
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 📝 License
|
|
97
|
+
MIT
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -11,8 +12,13 @@ const __dirname = path.dirname(__filename);
|
|
|
11
12
|
// CLI Arguments for the MCP server itself
|
|
12
13
|
const program = new Command();
|
|
13
14
|
program
|
|
15
|
+
.name("mcp-interactive-choice")
|
|
16
|
+
.description("MCP server for asking user interactive questions")
|
|
17
|
+
.version("1.0.2")
|
|
14
18
|
.option("--timeout <number>", "Default timeout in seconds", "60")
|
|
15
19
|
.option("--binary-path <string>", "Path to the native-ui binary")
|
|
20
|
+
.option("--stdio", "Ignored for compatibility")
|
|
21
|
+
.allowUnknownOption()
|
|
16
22
|
.parse(process.argv);
|
|
17
23
|
const options = program.opts();
|
|
18
24
|
const DEFAULT_TIMEOUT = parseInt(options.timeout);
|
|
@@ -54,13 +60,36 @@ export function resolveRecommendedIndex(choices, recommended) {
|
|
|
54
60
|
const target = recommended.trim();
|
|
55
61
|
const index = choices.findIndex(c => c.trim() === target);
|
|
56
62
|
if (index === -1) {
|
|
57
|
-
|
|
63
|
+
const message = `recommended choice "${recommended}" does not match any available choices. Available: ${choices.join(", ")}`;
|
|
64
|
+
const error = new McpError(ErrorCode.InvalidParams, message);
|
|
65
|
+
error.message = message; // Avoid double-prefixing in certain clients
|
|
66
|
+
throw error;
|
|
58
67
|
}
|
|
59
68
|
return index;
|
|
60
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Parses the stdout from the native-ui binary.
|
|
72
|
+
* @returns The user's selection or custom input.
|
|
73
|
+
* @throws Error if parsing fails.
|
|
74
|
+
*/
|
|
75
|
+
export function parseToolResult(stdoutData) {
|
|
76
|
+
const cleanedStdout = stdoutData.split('\n')
|
|
77
|
+
.filter(line => !line.trim().startsWith('DEBUG'))
|
|
78
|
+
.join('\n')
|
|
79
|
+
.trim();
|
|
80
|
+
if (!cleanedStdout)
|
|
81
|
+
return "user cancelled the selection";
|
|
82
|
+
try {
|
|
83
|
+
const result = JSON.parse(cleanedStdout);
|
|
84
|
+
return result.custom_input || result.choice || "user cancelled the selection";
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
throw new Error(`Error parsing result: ${stdoutData}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
61
90
|
const server = new Server({
|
|
62
91
|
name: "mcp-interactive-choice",
|
|
63
|
-
version: "1.0.
|
|
92
|
+
version: "1.0.2",
|
|
64
93
|
}, {
|
|
65
94
|
capabilities: {
|
|
66
95
|
tools: {},
|
|
@@ -141,19 +170,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
141
170
|
clearTimeout(timer);
|
|
142
171
|
if (code === 0) {
|
|
143
172
|
try {
|
|
144
|
-
const
|
|
145
|
-
.filter(line => !line.trim().startsWith('DEBUG'))
|
|
146
|
-
.join('\n')
|
|
147
|
-
.trim();
|
|
148
|
-
const result = JSON.parse(cleanedStdout);
|
|
149
|
-
const textResult = result.custom_input || result.choice || "No selection made";
|
|
173
|
+
const textResult = parseToolResult(stdoutData);
|
|
150
174
|
resolve({
|
|
151
175
|
content: [{ type: "text", text: textResult }],
|
|
152
176
|
});
|
|
153
177
|
}
|
|
154
178
|
catch (e) {
|
|
155
179
|
resolve({
|
|
156
|
-
content: [{ type: "text", text:
|
|
180
|
+
content: [{ type: "text", text: e.message }],
|
|
157
181
|
isError: true,
|
|
158
182
|
});
|
|
159
183
|
}
|
|
@@ -167,11 +191,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
167
191
|
});
|
|
168
192
|
child.on("error", (err) => {
|
|
169
193
|
clearTimeout(timer);
|
|
170
|
-
|
|
194
|
+
const message = `Failed to launch interactive window: ${err.message}`;
|
|
195
|
+
const error = new McpError(ErrorCode.InternalError, message);
|
|
196
|
+
error.message = message;
|
|
197
|
+
reject(error);
|
|
171
198
|
});
|
|
172
199
|
});
|
|
173
200
|
}
|
|
174
|
-
|
|
201
|
+
const message = `Tool not found: ${request.params.name}`;
|
|
202
|
+
const error = new McpError(ErrorCode.MethodNotFound, message);
|
|
203
|
+
error.message = message;
|
|
204
|
+
throw error;
|
|
175
205
|
});
|
|
176
206
|
async function main() {
|
|
177
207
|
const transport = new StdioServerTransport();
|
package/dist/index.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { resolveRecommendedIndex } from "./index.js";
|
|
2
|
+
import { resolveRecommendedIndex, parseToolResult } from "./index.js";
|
|
3
3
|
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
describe("resolveRecommendedIndex", () => {
|
|
5
5
|
const choices = ["Apple", "Banana", "Cherry"];
|
|
@@ -22,4 +22,41 @@ describe("resolveRecommendedIndex", () => {
|
|
|
22
22
|
expect(e.code).toBe(ErrorCode.InvalidParams);
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
|
+
it("should throw McpError with clean message (no double prefix)", () => {
|
|
26
|
+
let thrownError;
|
|
27
|
+
try {
|
|
28
|
+
resolveRecommendedIndex(choices, "Dragonfruit");
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
thrownError = e;
|
|
32
|
+
}
|
|
33
|
+
expect(thrownError).toBeDefined();
|
|
34
|
+
expect(thrownError.message).not.toContain("MCP error");
|
|
35
|
+
expect(thrownError.message).toBe('recommended choice "Dragonfruit" does not match any available choices. Available: Apple, Banana, Cherry');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("parseToolResult", () => {
|
|
39
|
+
it("should return the choice if provided", () => {
|
|
40
|
+
const stdout = '{"choice":"Apple","index":0,"custom_input":null}';
|
|
41
|
+
expect(parseToolResult(stdout)).toBe("Apple");
|
|
42
|
+
});
|
|
43
|
+
it("should return custom_input if provided", () => {
|
|
44
|
+
const stdout = '{"choice":null,"index":-1,"custom_input":"My Custom"}';
|
|
45
|
+
expect(parseToolResult(stdout)).toBe("My Custom");
|
|
46
|
+
});
|
|
47
|
+
it("should return cancellation message if choice and custom_input are null", () => {
|
|
48
|
+
const stdout = '{"choice":null,"index":-1,"custom_input":null}';
|
|
49
|
+
expect(parseToolResult(stdout)).toBe("user cancelled the selection");
|
|
50
|
+
});
|
|
51
|
+
it("should filter out DEBUG lines", () => {
|
|
52
|
+
const stdout = 'DEBUG: some log\n{"choice":"Banana","index":1,"custom_input":null}\nDEBUG: another log';
|
|
53
|
+
expect(parseToolResult(stdout)).toBe("Banana");
|
|
54
|
+
});
|
|
55
|
+
it("should return cancellation message for empty stdout", () => {
|
|
56
|
+
expect(parseToolResult("")).toBe("user cancelled the selection");
|
|
57
|
+
expect(parseToolResult(" \n ")).toBe("user cancelled the selection");
|
|
58
|
+
});
|
|
59
|
+
it("should throw for invalid JSON", () => {
|
|
60
|
+
expect(() => parseToolResult("not json")).toThrow("Error parsing result");
|
|
61
|
+
});
|
|
25
62
|
});
|
package/package.json
CHANGED
|
@@ -1,34 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mcp-interactive-choice",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "MCP server for interactive user questions with native
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"mcp-interactive-choice": "dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"bin",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"workspaces": [
|
|
16
|
-
"packages/*"
|
|
17
|
-
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build:server": "tsc",
|
|
20
|
-
"build:ui": "npm run build --workspace=native-ui",
|
|
21
|
-
"build": "npm run build:ui && npm run build:server && node scripts/copy-binaries.js",
|
|
22
|
-
"start": "node dist/index.js",
|
|
23
|
-
"test": "vitest run"
|
|
24
|
-
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"@modelcontextprotocol/sdk": "^1.0.3",
|
|
27
|
-
"commander": "^11.1.0"
|
|
28
|
-
},
|
|
29
|
-
"devDependencies": {
|
|
30
|
-
"@types/node": "^20.11.0",
|
|
31
|
-
"typescript": "^5.3.3",
|
|
32
|
-
"vitest": "^4.0.18"
|
|
33
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-interactive-choice",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A MCP server for interactive user questions with native UI",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-interactive-choice": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"bin",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"workspaces": [
|
|
16
|
+
"packages/*"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build:server": "tsc",
|
|
20
|
+
"build:ui": "npm run build --workspace=native-ui",
|
|
21
|
+
"build": "npm run build:ui && npm run build:server && node scripts/copy-binaries.js",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"test": "vitest run"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.0.3",
|
|
27
|
+
"commander": "^11.1.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.11.0",
|
|
31
|
+
"typescript": "^5.3.3",
|
|
32
|
+
"vitest": "^4.0.18"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/oovz/mcp-interactive-choice"
|
|
37
|
+
}
|
|
34
38
|
}
|
package/dist/spawn.test.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'vitest';
|
|
2
|
-
import { spawn } from 'child_process';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
8
|
-
describe('spawn native-ui', () => {
|
|
9
|
-
it('should be able to find and execute the native-ui binary with --help', async () => {
|
|
10
|
-
const platform = process.platform;
|
|
11
|
-
const exeSuffix = platform === 'win32' ? '.exe' : '';
|
|
12
|
-
// Check release first, then debug
|
|
13
|
-
const releasePath = path.resolve(__dirname, `../packages/native-ui/src-tauri/target/release/native-ui${exeSuffix}`);
|
|
14
|
-
const debugPath = path.resolve(__dirname, `../packages/native-ui/src-tauri/target/debug/native-ui${exeSuffix}`);
|
|
15
|
-
const binaryPath = fs.existsSync(releasePath) ? releasePath : debugPath;
|
|
16
|
-
if (!fs.existsSync(binaryPath)) {
|
|
17
|
-
console.warn(`Binary not found at ${binaryPath}, skipping execution test. Run 'npm run build' first.`);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const child = spawn(binaryPath, ['--help'], {
|
|
22
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
23
|
-
});
|
|
24
|
-
let stdout = '';
|
|
25
|
-
child.stdout.on('data', (data) => {
|
|
26
|
-
stdout += data.toString();
|
|
27
|
-
});
|
|
28
|
-
child.on('close', (code) => {
|
|
29
|
-
// Tauri apps return 0 for help usually, but some return non-zero if no event loop
|
|
30
|
-
// We just care that it lived long enough to print something or exit cleanly
|
|
31
|
-
console.log('Tauri help output:', stdout);
|
|
32
|
-
resolve(true);
|
|
33
|
-
});
|
|
34
|
-
child.on('error', (err) => {
|
|
35
|
-
reject(err);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
}, 10000); // 10s timeout
|
|
39
|
-
});
|
package/dist/ui
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>AI Needs Your Choice</title>
|
|
7
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
-
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&display=swap" rel="stylesheet">
|
|
10
|
-
<style>
|
|
11
|
-
:root {
|
|
12
|
-
--primary: #4f46e5;
|
|
13
|
-
--primary-hover: #4338ca;
|
|
14
|
-
--bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
|
|
15
|
-
--glass: rgba(255, 255, 255, 0.05);
|
|
16
|
-
--glass-border: rgba(255, 255, 255, 0.1);
|
|
17
|
-
--text-main: #f8fafc;
|
|
18
|
-
--text-dim: #94a3b8;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
* {
|
|
22
|
-
margin: 0;
|
|
23
|
-
padding: 0;
|
|
24
|
-
box-sizing: border-box;
|
|
25
|
-
font-family: 'Outfit', sans-serif;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
body {
|
|
29
|
-
min-height: 100vh;
|
|
30
|
-
display: flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-content: center;
|
|
33
|
-
background: var(--bg-gradient);
|
|
34
|
-
background-attachment: fixed;
|
|
35
|
-
color: var(--text-main);
|
|
36
|
-
overflow: hidden;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* Abstract blobs for aesthetics */
|
|
40
|
-
.blob {
|
|
41
|
-
position: absolute;
|
|
42
|
-
width: 500px;
|
|
43
|
-
height: 500px;
|
|
44
|
-
background: linear-gradient(180deg, rgba(79, 70, 229, 0.2) 0%, rgba(147, 51, 234, 0.1) 100%);
|
|
45
|
-
filter: blur(80px);
|
|
46
|
-
border-radius: 50%;
|
|
47
|
-
z-index: -1;
|
|
48
|
-
animation: move 20s infinite alternate;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.blob-1 { top: -100px; left: -100px; }
|
|
52
|
-
.blob-2 { bottom: -100px; right: -100px; animation-delay: -5s; }
|
|
53
|
-
|
|
54
|
-
@keyframes move {
|
|
55
|
-
from { transform: translate(0, 0) scale(1); }
|
|
56
|
-
to { transform: translate(50px, 50px) scale(1.1); }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.container {
|
|
60
|
-
width: 100%;
|
|
61
|
-
max-width: 500px;
|
|
62
|
-
padding: 2rem;
|
|
63
|
-
background: var(--glass);
|
|
64
|
-
backdrop-filter: blur(12px);
|
|
65
|
-
border: 1px solid var(--glass-border);
|
|
66
|
-
border-radius: 24px;
|
|
67
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
68
|
-
animation: fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
@keyframes fadeIn {
|
|
72
|
-
from { opacity: 0; transform: translateY(20px); }
|
|
73
|
-
to { opacity: 1; transform: translateY(0); }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.header {
|
|
77
|
-
margin-bottom: 2rem;
|
|
78
|
-
text-align: center;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.ai-indicator {
|
|
82
|
-
display: inline-flex;
|
|
83
|
-
align-items: center;
|
|
84
|
-
gap: 0.5rem;
|
|
85
|
-
background: rgba(79, 70, 229, 0.1);
|
|
86
|
-
padding: 0.5rem 1rem;
|
|
87
|
-
border-radius: 100px;
|
|
88
|
-
color: #818cf8;
|
|
89
|
-
font-size: 0.875rem;
|
|
90
|
-
font-weight: 600;
|
|
91
|
-
margin-bottom: 1rem;
|
|
92
|
-
border: 1px solid rgba(79, 70, 229, 0.2);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.pulse {
|
|
96
|
-
width: 8px;
|
|
97
|
-
height: 8px;
|
|
98
|
-
background: #818cf8;
|
|
99
|
-
border-radius: 50%;
|
|
100
|
-
animation: pulse 2s infinite;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
@keyframes pulse {
|
|
104
|
-
0% { transform: scale(1); opacity: 1; box-shadow: 0 0 0 0 rgba(129, 140, 248, 0.7); }
|
|
105
|
-
70% { transform: scale(1); opacity: 1; box-shadow: 0 0 0 10px rgba(129, 140, 248, 0); }
|
|
106
|
-
100% { transform: scale(1); opacity: 0; }
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
h1 {
|
|
110
|
-
font-size: 1.5rem;
|
|
111
|
-
font-weight: 600;
|
|
112
|
-
line-height: 1.4;
|
|
113
|
-
color: var(--text-main);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.choices {
|
|
117
|
-
display: flex;
|
|
118
|
-
flex-direction: column;
|
|
119
|
-
gap: 0.75rem;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
button {
|
|
123
|
-
width: 100%;
|
|
124
|
-
padding: 1.25rem;
|
|
125
|
-
background: rgba(255, 255, 255, 0.03);
|
|
126
|
-
border: 1px solid var(--glass-border);
|
|
127
|
-
border-radius: 16px;
|
|
128
|
-
color: var(--text-main);
|
|
129
|
-
font-size: 1rem;
|
|
130
|
-
font-weight: 400;
|
|
131
|
-
text-align: left;
|
|
132
|
-
cursor: pointer;
|
|
133
|
-
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
134
|
-
display: flex;
|
|
135
|
-
justify-content: space-between;
|
|
136
|
-
align-items: center;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
button:hover {
|
|
140
|
-
background: rgba(255, 255, 255, 0.08);
|
|
141
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
142
|
-
transform: translateX(4px);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
button:active {
|
|
146
|
-
transform: scale(0.98);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
button .arrow {
|
|
150
|
-
opacity: 0;
|
|
151
|
-
transition: 0.2s;
|
|
152
|
-
transform: translateX(-10px);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
button:hover .arrow {
|
|
156
|
-
opacity: 1;
|
|
157
|
-
transform: translateX(0);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.footer {
|
|
161
|
-
margin-top: 2rem;
|
|
162
|
-
text-align: center;
|
|
163
|
-
font-size: 0.75rem;
|
|
164
|
-
color: var(--text-dim);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.loading-overlay {
|
|
168
|
-
position: fixed;
|
|
169
|
-
inset: 0;
|
|
170
|
-
background: var(--bg-gradient);
|
|
171
|
-
display: flex;
|
|
172
|
-
align-items: center;
|
|
173
|
-
justify-content: center;
|
|
174
|
-
z-index: 100;
|
|
175
|
-
transition: opacity 0.5s ease;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.loading-overlay.hidden {
|
|
179
|
-
opacity: 0;
|
|
180
|
-
pointer-events: none;
|
|
181
|
-
}
|
|
182
|
-
</style>
|
|
183
|
-
</head>
|
|
184
|
-
<body>
|
|
185
|
-
<div class="blob blob-1"></div>
|
|
186
|
-
<div class="blob blob-2"></div>
|
|
187
|
-
|
|
188
|
-
<div id="loading" class="loading-overlay">
|
|
189
|
-
<div class="ai-indicator">
|
|
190
|
-
<div class="pulse"></div>
|
|
191
|
-
Connecting...
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
|
|
195
|
-
<div class="container">
|
|
196
|
-
<div class="header">
|
|
197
|
-
<div class="ai-indicator">
|
|
198
|
-
<div class="pulse"></div>
|
|
199
|
-
Agent Interaction
|
|
200
|
-
</div>
|
|
201
|
-
<h1 id="question">Please wait while the question loads...</h1>
|
|
202
|
-
</div>
|
|
203
|
-
|
|
204
|
-
<div id="choices" class="choices">
|
|
205
|
-
<!-- Buttons will be injected here -->
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<div class="footer">
|
|
209
|
-
Choice will be returned to the AI assistant immediately.
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
|
|
213
|
-
<script>
|
|
214
|
-
async function init() {
|
|
215
|
-
try {
|
|
216
|
-
const response = await fetch('/api/details');
|
|
217
|
-
const data = await response.json();
|
|
218
|
-
|
|
219
|
-
document.getElementById('question').textContent = data.question;
|
|
220
|
-
const choicesContainer = document.getElementById('choices');
|
|
221
|
-
|
|
222
|
-
data.choices.forEach(choice => {
|
|
223
|
-
const btn = document.createElement('button');
|
|
224
|
-
btn.innerHTML = `${choice} <span class="arrow">→</span>`;
|
|
225
|
-
btn.onclick = () => submit(choice);
|
|
226
|
-
choicesContainer.appendChild(btn);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
document.getElementById('loading').classList.add('hidden');
|
|
230
|
-
} catch (err) {
|
|
231
|
-
console.error('Failed to load details', err);
|
|
232
|
-
document.getElementById('question').textContent = "Error: Could not connect to MCP server.";
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async function submit(choice) {
|
|
237
|
-
try {
|
|
238
|
-
await fetch('/api/respond', {
|
|
239
|
-
method: 'POST',
|
|
240
|
-
headers: { 'Content-Type': 'application/json' },
|
|
241
|
-
body: JSON.stringify({ choice })
|
|
242
|
-
});
|
|
243
|
-
// Close window if possible
|
|
244
|
-
window.close();
|
|
245
|
-
// If window.close() is blocked (standard browser behavior), show success
|
|
246
|
-
document.querySelector('.container').innerHTML = `
|
|
247
|
-
<div style="text-align: center; padding: 2rem;">
|
|
248
|
-
<div class="ai-indicator" style="background: rgba(16, 185, 129, 0.1); color: #10b981; border-color: rgba(16, 185, 129, 0.2);">✓ Response Sent</div>
|
|
249
|
-
<h1 style="margin-top: 1rem;">Done!</h1>
|
|
250
|
-
<p style="color: var(--text-dim); margin-top: 0.5rem;">You can close this tab now.</p>
|
|
251
|
-
</div>
|
|
252
|
-
`;
|
|
253
|
-
} catch (err) {
|
|
254
|
-
console.error('Failed to submit response', err);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
init();
|
|
259
|
-
</script>
|
|
260
|
-
</body>
|
|
261
|
-
</html>
|
|
262
|
-
|