@achieveai/hitl-mcp-server 1.2.0 → 2.1.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.
Files changed (58) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +131 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config.d.ts +19 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +54 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/git-context.d.ts +7 -0
  10. package/dist/git-context.d.ts.map +1 -0
  11. package/dist/git-context.js +29 -0
  12. package/dist/git-context.js.map +1 -0
  13. package/dist/mcp-server.d.ts +3 -0
  14. package/dist/mcp-server.d.ts.map +1 -0
  15. package/dist/mcp-server.js +237 -0
  16. package/dist/mcp-server.js.map +1 -0
  17. package/dist/ntfy-transport.d.ts +41 -0
  18. package/dist/ntfy-transport.d.ts.map +1 -0
  19. package/dist/ntfy-transport.js +150 -0
  20. package/dist/ntfy-transport.js.map +1 -0
  21. package/dist/setup.d.ts +43 -0
  22. package/dist/setup.d.ts.map +1 -0
  23. package/dist/setup.js +184 -0
  24. package/dist/setup.js.map +1 -0
  25. package/package.json +61 -63
  26. package/scripts/postinstall.js +33 -0
  27. package/LICENSE +0 -20
  28. package/README.md +0 -422
  29. package/config/claude-desktop.json +0 -11
  30. package/config/cursor-mcp.json +0 -17
  31. package/config/vscode-mcp.json +0 -17
  32. package/dist/__tests__/dialog-manager.test.d.ts +0 -2
  33. package/dist/__tests__/dialog-manager.test.d.ts.map +0 -1
  34. package/dist/__tests__/dialog-manager.test.js +0 -140
  35. package/dist/__tests__/dialog-manager.test.js.map +0 -1
  36. package/dist/dialog-manager.d.ts +0 -37
  37. package/dist/dialog-manager.d.ts.map +0 -1
  38. package/dist/dialog-manager.js +0 -644
  39. package/dist/dialog-manager.js.map +0 -1
  40. package/dist/dialog-manager.test.d.ts +0 -2
  41. package/dist/dialog-manager.test.d.ts.map +0 -1
  42. package/dist/dialog-manager.test.js +0 -156
  43. package/dist/dialog-manager.test.js.map +0 -1
  44. package/dist/index.d.ts +0 -3
  45. package/dist/index.d.ts.map +0 -1
  46. package/dist/index.js +0 -222
  47. package/dist/index.js.map +0 -1
  48. package/dist/test-client.d.ts +0 -3
  49. package/dist/test-client.d.ts.map +0 -1
  50. package/dist/test-client.js +0 -125
  51. package/dist/test-client.js.map +0 -1
  52. package/dist/test-dialog-manager.d.ts +0 -2
  53. package/dist/test-dialog-manager.d.ts.map +0 -1
  54. package/dist/test-dialog-manager.js +0 -156
  55. package/dist/test-dialog-manager.js.map +0 -1
  56. package/example-usage.md +0 -223
  57. package/mcp.json +0 -152
  58. package/sounds/notification.wav +0 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Transport layer for communicating with ntfy.sh.
3
+ *
4
+ * - Publishes question messages (HTTP POST)
5
+ * - Subscribes for answer messages (SSE stream)
6
+ */
7
+ export class NtfyTransport {
8
+ config;
9
+ abortController = null;
10
+ constructor(config) {
11
+ this.config = config;
12
+ }
13
+ /** Full URL for the ntfy topic. */
14
+ get topicUrl() {
15
+ const base = this.config.ntfyUrl.replace(/\/+$/, '');
16
+ return `${base}/${this.config.topicId}`;
17
+ }
18
+ /**
19
+ * Publish a question message to the ntfy topic.
20
+ */
21
+ async publishQuestion(msg) {
22
+ const response = await fetch(this.topicUrl, {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/json' },
25
+ body: JSON.stringify(msg),
26
+ });
27
+ if (!response.ok) {
28
+ throw new Error(`Failed to publish question: ${response.status} ${response.statusText}`);
29
+ }
30
+ }
31
+ /**
32
+ * Subscribe and wait for an answer to a specific question.
33
+ * Opens an SSE connection and filters for answer messages matching questionId.
34
+ *
35
+ * @param questionId - The messageId of the question to wait for
36
+ * @param timeout - Timeout in ms (0 = no timeout)
37
+ * @returns The answer message
38
+ */
39
+ async waitForAnswer(questionId, timeout) {
40
+ return new Promise((resolve, reject) => {
41
+ this.abortController = new AbortController();
42
+ const { signal } = this.abortController;
43
+ let timer;
44
+ if (timeout && timeout > 0) {
45
+ timer = setTimeout(() => {
46
+ this.abortController?.abort();
47
+ reject(new Error('Dialog timeout'));
48
+ }, timeout);
49
+ }
50
+ const cleanup = () => {
51
+ if (timer)
52
+ clearTimeout(timer);
53
+ };
54
+ // Use ntfy's JSON stream endpoint — since=<now_unix> to only get future messages
55
+ const sinceTs = Math.floor(Date.now() / 1000);
56
+ const sseUrl = `${this.topicUrl}/json?since=${sinceTs}`;
57
+ this.startSSEListener(sseUrl, signal, (msg) => {
58
+ if (msg.type === 'answer' && msg.questionId === questionId) {
59
+ cleanup();
60
+ resolve(msg);
61
+ }
62
+ }).catch((err) => {
63
+ cleanup();
64
+ if (!signal.aborted) {
65
+ reject(err);
66
+ }
67
+ });
68
+ signal.addEventListener('abort', () => {
69
+ cleanup();
70
+ });
71
+ });
72
+ }
73
+ /**
74
+ * Publish an answer message to the ntfy topic (used by client app, exposed here for testing).
75
+ */
76
+ async publishAnswer(msg) {
77
+ const response = await fetch(this.topicUrl, {
78
+ method: 'POST',
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body: JSON.stringify(msg),
81
+ });
82
+ if (!response.ok) {
83
+ throw new Error(`Failed to publish answer: ${response.status} ${response.statusText}`);
84
+ }
85
+ }
86
+ /**
87
+ * Open a streaming connection to ntfy and invoke the callback for each parsed HITL message.
88
+ * Uses fetch streaming (works in Node 18+).
89
+ */
90
+ async startSSEListener(url, signal, onMessage) {
91
+ const response = await fetch(url, {
92
+ headers: { Accept: 'application/x-ndjson' },
93
+ signal,
94
+ });
95
+ if (!response.ok) {
96
+ throw new Error(`ntfy subscription failed: ${response.status} ${response.statusText}`);
97
+ }
98
+ const reader = response.body?.getReader();
99
+ if (!reader)
100
+ throw new Error('No response body from ntfy');
101
+ const decoder = new TextDecoder();
102
+ let buffer = '';
103
+ try {
104
+ while (true) {
105
+ const { done, value } = await reader.read();
106
+ if (done)
107
+ break;
108
+ buffer += decoder.decode(value, { stream: true });
109
+ const lines = buffer.split('\n');
110
+ buffer = lines.pop() ?? '';
111
+ for (const line of lines) {
112
+ const trimmed = line.trim();
113
+ if (!trimmed)
114
+ continue;
115
+ try {
116
+ const ntfyEvent = JSON.parse(trimmed);
117
+ // ntfy wraps messages — the actual payload is in the 'message' field
118
+ if (ntfyEvent.message) {
119
+ try {
120
+ const hitlMsg = JSON.parse(ntfyEvent.message);
121
+ if (hitlMsg.type === 'question' || hitlMsg.type === 'answer') {
122
+ onMessage(hitlMsg);
123
+ }
124
+ }
125
+ catch {
126
+ // Not a valid HITL message, ignore
127
+ }
128
+ }
129
+ }
130
+ catch {
131
+ // Not valid JSON, ignore
132
+ }
133
+ }
134
+ }
135
+ }
136
+ catch (err) {
137
+ if (err instanceof Error && err.name === 'AbortError')
138
+ return;
139
+ throw err;
140
+ }
141
+ }
142
+ /**
143
+ * Close any active subscriptions.
144
+ */
145
+ close() {
146
+ this.abortController?.abort();
147
+ this.abortController = null;
148
+ }
149
+ }
150
+ //# sourceMappingURL=ntfy-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ntfy-transport.js","sourceRoot":"","sources":["../src/ntfy-transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /** Result of a single setup step. */
2
+ export interface SetupStepResult {
3
+ step: string;
4
+ status: 'ok' | 'created' | 'launched' | 'already_running' | 'not_found' | 'error';
5
+ message: string;
6
+ }
7
+ /** Aggregated result of the full setup process. */
8
+ export interface SetupResult {
9
+ success: boolean;
10
+ steps: SetupStepResult[];
11
+ summary: string;
12
+ }
13
+ /**
14
+ * Check if a process with the given name is currently running.
15
+ * Uses `tasklist` on Windows and `pgrep` on Unix.
16
+ */
17
+ export declare function isProcessRunning(processName: string): boolean;
18
+ /**
19
+ * Search known locations for the HITL client binary.
20
+ * Priority: bundled with npm package > dev build > release build > ~/.hitl/
21
+ *
22
+ * @param serverDir - The directory where the compiled server JS lives (e.g. server/dist/)
23
+ */
24
+ export declare function findClientBinary(serverDir: string): string | null;
25
+ /**
26
+ * Launch the client binary as a fully detached background process.
27
+ */
28
+ export declare function launchClient(binaryPath: string): void;
29
+ /**
30
+ * Ensure the HITL client is running. Finds and launches it if needed.
31
+ * Logs to stderr only — safe to call from the hot path of ask_human.
32
+ */
33
+ export declare function ensureClientRunning(serverDir: string): void;
34
+ /**
35
+ * Perform the full HITL client setup:
36
+ * 1. Ensure ~/.hitl/config.json exists
37
+ * 2. Check if the client process is running
38
+ * 3. Find and launch the client binary if needed
39
+ *
40
+ * @param serverDir - The directory of the running server JS (used to resolve relative binary paths)
41
+ */
42
+ export declare function performSetup(serverDir: string): Promise<SetupResult>;
43
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAMA,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,UAAU,GAAG,iBAAiB,GAAG,WAAW,GAAG,OAAO,CAAC;IAClF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAkBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAe7D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0BjE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAMrD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAgB3D;AAqBD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAmD1E"}
package/dist/setup.js ADDED
@@ -0,0 +1,184 @@
1
+ import { existsSync, chmodSync } from 'fs';
2
+ import { execSync, spawn } from 'child_process';
3
+ import { homedir, arch } from 'os';
4
+ import path from 'path';
5
+ import { saveConfig, generateDefaultConfig, getConfigPath } from './config.js';
6
+ /**
7
+ * Get the platform-specific subdirectory name for bundled binaries.
8
+ * Maps Node.js platform/arch to the artifact names used in CI.
9
+ */
10
+ function getBundledPlatformDir() {
11
+ const platform = process.platform;
12
+ const cpuArch = arch();
13
+ if (platform === 'win32')
14
+ return 'windows-x64';
15
+ if (platform === 'linux')
16
+ return 'linux-x64';
17
+ if (platform === 'darwin') {
18
+ return cpuArch === 'arm64' ? 'macos-arm64' : 'macos-x64';
19
+ }
20
+ return 'unknown';
21
+ }
22
+ /**
23
+ * Check if a process with the given name is currently running.
24
+ * Uses `tasklist` on Windows and `pgrep` on Unix.
25
+ */
26
+ export function isProcessRunning(processName) {
27
+ try {
28
+ if (process.platform === 'win32') {
29
+ const output = execSync(`tasklist /FI "IMAGENAME eq ${processName}" /NH`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
30
+ return output.toLowerCase().includes(processName.toLowerCase());
31
+ }
32
+ else {
33
+ execSync(`pgrep -x ${processName}`, { stdio: ['pipe', 'pipe', 'pipe'] });
34
+ return true;
35
+ }
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ /**
42
+ * Search known locations for the HITL client binary.
43
+ * Priority: bundled with npm package > dev build > release build > ~/.hitl/
44
+ *
45
+ * @param serverDir - The directory where the compiled server JS lives (e.g. server/dist/)
46
+ */
47
+ export function findClientBinary(serverDir) {
48
+ const binaryName = process.platform === 'win32' ? 'hitl-client.exe' : 'hitl-client';
49
+ const platformDir = getBundledPlatformDir();
50
+ const candidates = [
51
+ // Bundled with npm package (dist/bin/{platform}/hitl-client)
52
+ path.resolve(serverDir, 'bin', platformDir, binaryName),
53
+ // Dev build (relative to server dist → repo root → client)
54
+ path.resolve(serverDir, '..', '..', 'client', 'src-tauri', 'target', 'debug', binaryName),
55
+ // Release build
56
+ path.resolve(serverDir, '..', '..', 'client', 'src-tauri', 'target', 'release', binaryName),
57
+ // Installed location in user home
58
+ path.join(homedir(), '.hitl', binaryName),
59
+ ];
60
+ for (const candidate of candidates) {
61
+ if (existsSync(candidate)) {
62
+ // Ensure executable permission on Unix
63
+ if (process.platform !== 'win32') {
64
+ try {
65
+ chmodSync(candidate, 0o755);
66
+ }
67
+ catch { /* best effort */ }
68
+ }
69
+ return candidate;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ /**
75
+ * Launch the client binary as a fully detached background process.
76
+ */
77
+ export function launchClient(binaryPath) {
78
+ const child = spawn(binaryPath, [], {
79
+ detached: true,
80
+ stdio: 'ignore',
81
+ });
82
+ child.unref();
83
+ }
84
+ /**
85
+ * Ensure the HITL client is running. Finds and launches it if needed.
86
+ * Logs to stderr only — safe to call from the hot path of ask_human.
87
+ */
88
+ export function ensureClientRunning(serverDir) {
89
+ const binaryName = process.platform === 'win32' ? 'hitl-client.exe' : 'hitl-client';
90
+ if (isProcessRunning(binaryName)) {
91
+ return;
92
+ }
93
+ const binaryPath = findClientBinary(serverDir);
94
+ if (binaryPath) {
95
+ try {
96
+ launchClient(binaryPath);
97
+ console.error(`Auto-launched HITL client from ${binaryPath}`);
98
+ }
99
+ catch (err) {
100
+ console.error(`Failed to auto-launch HITL client: ${err}`);
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Build a user-friendly "not found" message listing the paths that were searched.
106
+ */
107
+ function buildNotFoundMessage(serverDir) {
108
+ const binaryName = process.platform === 'win32' ? 'hitl-client.exe' : 'hitl-client';
109
+ const platformDir = getBundledPlatformDir();
110
+ return [
111
+ `HITL client binary not found. Searched locations:`,
112
+ ` - ${path.resolve(serverDir, 'bin', platformDir, binaryName)} (bundled)`,
113
+ ` - <repo>/client/src-tauri/target/debug/${binaryName}`,
114
+ ` - <repo>/client/src-tauri/target/release/${binaryName}`,
115
+ ` - ~/.hitl/${binaryName}`,
116
+ ``,
117
+ `Install the client from GitHub Releases:`,
118
+ ` https://github.com/achieveai/HumanInTheLoop/releases`,
119
+ `Or build from source: cd client && npm run build`,
120
+ ].join('\n');
121
+ }
122
+ /**
123
+ * Perform the full HITL client setup:
124
+ * 1. Ensure ~/.hitl/config.json exists
125
+ * 2. Check if the client process is running
126
+ * 3. Find and launch the client binary if needed
127
+ *
128
+ * @param serverDir - The directory of the running server JS (used to resolve relative binary paths)
129
+ */
130
+ export async function performSetup(serverDir) {
131
+ const steps = [];
132
+ let overallSuccess = true;
133
+ // Step 1: Ensure config exists
134
+ const configPath = getConfigPath();
135
+ if (existsSync(configPath)) {
136
+ steps.push({ step: 'config', status: 'ok', message: `Config already exists at ${configPath}` });
137
+ }
138
+ else {
139
+ try {
140
+ const config = generateDefaultConfig();
141
+ saveConfig(config);
142
+ steps.push({ step: 'config', status: 'created', message: `Created default config at ${configPath}` });
143
+ }
144
+ catch (error) {
145
+ overallSuccess = false;
146
+ steps.push({
147
+ step: 'config',
148
+ status: 'error',
149
+ message: `Failed to create config: ${error instanceof Error ? error.message : String(error)}`,
150
+ });
151
+ }
152
+ }
153
+ // Step 2: Check if client is already running
154
+ const binaryName = process.platform === 'win32' ? 'hitl-client.exe' : 'hitl-client';
155
+ if (isProcessRunning(binaryName)) {
156
+ steps.push({ step: 'client', status: 'already_running', message: 'HITL client is already running' });
157
+ return { success: overallSuccess, steps, summary: formatSummary(steps) };
158
+ }
159
+ // Step 3: Find and launch binary
160
+ const binaryPath = findClientBinary(serverDir);
161
+ if (!binaryPath) {
162
+ overallSuccess = false;
163
+ steps.push({ step: 'client', status: 'not_found', message: buildNotFoundMessage(serverDir) });
164
+ return { success: overallSuccess, steps, summary: formatSummary(steps) };
165
+ }
166
+ try {
167
+ launchClient(binaryPath);
168
+ steps.push({ step: 'client', status: 'launched', message: `Launched HITL client from ${binaryPath}` });
169
+ }
170
+ catch (error) {
171
+ overallSuccess = false;
172
+ steps.push({
173
+ step: 'client',
174
+ status: 'error',
175
+ message: `Failed to launch client: ${error instanceof Error ? error.message : String(error)}`,
176
+ });
177
+ }
178
+ return { success: overallSuccess, steps, summary: formatSummary(steps) };
179
+ }
180
+ /** Format step results into a human-readable summary. */
181
+ function formatSummary(steps) {
182
+ return steps.map((s) => `[${s.status.toUpperCase()}] ${s.step}: ${s.message}`).join('\n');
183
+ }
184
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,63 +1,61 @@
1
- {
2
- "name": "@achieveai/hitl-mcp-server",
3
- "version": "1.2.0",
4
- "description": "Human In The Loop MCP Server - Enables LLM agents to ask questions and get user feedback through interactive dialogs",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "hitl-mcp-server": "dist/index.js"
8
- },
9
- "type": "module",
10
- "files": [
11
- "dist/",
12
- "config/",
13
- "sounds/",
14
- "mcp.json",
15
- "README.md",
16
- "LICENSE",
17
- "example-usage.md"
18
- ],
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/achieveai/HumanInTheLoop.git"
22
- },
23
- "bugs": {
24
- "url": "https://github.com/achieveai/HumanInTheLoop/issues"
25
- },
26
- "homepage": "https://github.com/achieveai/HumanInTheLoop#readme",
27
- "engines": {
28
- "node": ">=18.0.0"
29
- },
30
- "scripts": {
31
- "build": "tsc",
32
- "dev": "tsx watch src/index.ts",
33
- "start": "node dist/index.js",
34
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
35
- "test:dialog": "tsx src/test-client.ts",
36
- "typecheck": "tsc --noEmit"
37
- },
38
- "keywords": [
39
- "mcp",
40
- "llm",
41
- "human-in-the-loop",
42
- "dialog",
43
- "interactive"
44
- ],
45
- "author": "MCQdb LLC <https://achieve.ai>",
46
- "license": "GPL-3.0",
47
- "dependencies": {
48
- "@modelcontextprotocol/sdk": "^1.0.0",
49
- "express": "^4.21.2",
50
- "open": "^10.1.0",
51
- "uuid": "^11.0.5"
52
- },
53
- "devDependencies": {
54
- "@types/express": "^5.0.0",
55
- "@types/jest": "^29.5.14",
56
- "@types/node": "^22.10.5",
57
- "@types/uuid": "^10.0.0",
58
- "jest": "^29.7.0",
59
- "ts-jest": "^29.2.6",
60
- "tsx": "^4.19.2",
61
- "typescript": "^5.7.3"
62
- }
63
- }
1
+ {
2
+ "name": "@achieveai/hitl-mcp-server",
3
+ "version": "2.1.0",
4
+ "description": "Human-in-the-Loop MCP Server enables LLM agents to ask questions across all your devices via ntfy.sh",
5
+ "type": "module",
6
+ "main": "./dist/mcp-server.js",
7
+ "bin": {
8
+ "hitl": "dist/cli.js",
9
+ "hitl-mcp-server": "dist/mcp-server.js"
10
+ },
11
+ "files": [
12
+ "dist/**/*.js",
13
+ "dist/**/*.d.ts",
14
+ "dist/**/*.map",
15
+ "dist/bin/**/*",
16
+ "scripts/postinstall.js",
17
+ "README.md"
18
+ ],
19
+ "keywords": [
20
+ "mcp",
21
+ "llm",
22
+ "human-in-the-loop",
23
+ "hitl",
24
+ "dialog",
25
+ "interactive",
26
+ "model-context-protocol",
27
+ "ntfy",
28
+ "notifications"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/achieveai/HumanInTheLoop.git"
33
+ },
34
+ "license": "MIT",
35
+ "scripts": {
36
+ "build": "tsc",
37
+ "start": "node dist/mcp-server.js",
38
+ "dev": "tsx src/index.ts",
39
+ "test": "jest --config jest.config.js",
40
+ "clean": "rimraf dist",
41
+ "postinstall": "node scripts/postinstall.js",
42
+ "prepublishOnly": "npm run build"
43
+ },
44
+ "dependencies": {
45
+ "@hitl/shared": "*",
46
+ "@modelcontextprotocol/sdk": "^1.0.0",
47
+ "eventsource": "^2.0.2",
48
+ "uuid": "^9.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/eventsource": "^1.1.15",
52
+ "@types/node": "^20.0.0",
53
+ "@types/uuid": "^9.0.0",
54
+ "typescript": "^5.3.0",
55
+ "tsx": "^4.0.0",
56
+ "rimraf": "^5.0.0",
57
+ "jest": "^29.7.0",
58
+ "@jest/globals": "^29.7.0",
59
+ "ts-jest": "^29.1.0"
60
+ }
61
+ }
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Postinstall script: ensure bundled client binary is executable on Unix platforms.
4
+
5
+ import { readdirSync, chmodSync, statSync } from 'fs';
6
+ import { join, dirname } from 'path';
7
+ import { fileURLToPath } from 'url';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const binDir = join(__dirname, '..', 'dist', 'bin');
11
+
12
+ if (process.platform === 'win32') {
13
+ process.exit(0);
14
+ }
15
+
16
+ try {
17
+ const platforms = readdirSync(binDir);
18
+ for (const platform of platforms) {
19
+ const platformDir = join(binDir, platform);
20
+ if (!statSync(platformDir).isDirectory()) continue;
21
+
22
+ const files = readdirSync(platformDir);
23
+ for (const file of files) {
24
+ if (file.startsWith('hitl-client')) {
25
+ const filePath = join(platformDir, file);
26
+ chmodSync(filePath, 0o755);
27
+ console.log(`postinstall: chmod +x ${filePath}`);
28
+ }
29
+ }
30
+ }
31
+ } catch {
32
+ // bin/ directory may not exist if binaries aren't bundled (dev install)
33
+ }
package/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- GNU GENERAL PUBLIC LICENSE
2
- Version 3, 29 June 2007
3
-
4
- Copyright (C) 2025 Human-in-the-Loop MCP Server Contributors
5
-
6
- This program is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- This program is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with this program. If not, see <https://www.gnu.org/licenses/>.
18
-
19
- For the full text of the GNU General Public License Version 3,
20
- please visit: https://www.gnu.org/licenses/gpl-3.0.txt