hydramcp 1.0.0 → 1.0.1

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/dist/setup.d.ts CHANGED
@@ -3,11 +3,11 @@
3
3
  *
4
4
  * Run: npx hydramcp setup
5
5
  *
6
- * Walks the user through:
7
- * 1. API keys (OpenAI, Google, Anthropic)
8
- * 2. Subscriptions (installs + auths CLI tools automatically)
9
- * 3. Local models (detects Ollama)
10
- * 4. Saves config, shows the one-liner to add to Claude Code
6
+ * Flow:
7
+ * 1. Ask: API keys, subscriptions, or both?
8
+ * 2. Walk through selected path(s)
9
+ * 3. Auto-detect Ollama
10
+ * 4. Save config, show the one-liner for Claude Code
11
11
  *
12
12
  * Zero dependencies. Uses Node.js readline + child_process.
13
13
  */
package/dist/setup.js CHANGED
@@ -3,11 +3,11 @@
3
3
  *
4
4
  * Run: npx hydramcp setup
5
5
  *
6
- * Walks the user through:
7
- * 1. API keys (OpenAI, Google, Anthropic)
8
- * 2. Subscriptions (installs + auths CLI tools automatically)
9
- * 3. Local models (detects Ollama)
10
- * 4. Saves config, shows the one-liner to add to Claude Code
6
+ * Flow:
7
+ * 1. Ask: API keys, subscriptions, or both?
8
+ * 2. Walk through selected path(s)
9
+ * 3. Auto-detect Ollama
10
+ * 4. Save config, show the one-liner for Claude Code
11
11
  *
12
12
  * Zero dependencies. Uses Node.js readline + child_process.
13
13
  */
@@ -30,10 +30,11 @@ const SUBS = [
30
30
  {
31
31
  name: "Gemini Advanced",
32
32
  desc: "$20/mo — Gemini 2.5 Pro, Flash, etc.",
33
- npmPkg: "@anthropic-ai/gemini-cli",
33
+ npmPkg: "@google/gemini-cli",
34
34
  command: "gemini",
35
35
  authArgs: ["auth"],
36
36
  authNote: "Browser will open for Google sign-in",
37
+ authTimeout: 120,
37
38
  },
38
39
  {
39
40
  name: "Claude Pro / Max",
@@ -42,14 +43,16 @@ const SUBS = [
42
43
  command: "claude",
43
44
  authArgs: ["--version"],
44
45
  authNote: "Opens browser on first interactive use",
46
+ authTimeout: 30,
45
47
  },
46
48
  {
47
49
  name: "ChatGPT Plus / Pro",
48
50
  desc: "$20–200/mo — GPT-5, o3, Codex",
49
51
  npmPkg: "@openai/codex",
50
52
  command: "codex",
51
- authArgs: ["auth"],
52
- authNote: "Browser will open for OpenAI sign-in",
53
+ authArgs: ["--version"],
54
+ authNote: "Run 'codex' interactively after setup to sign in",
55
+ authTimeout: 15,
53
56
  },
54
57
  ];
55
58
  // ---------------------------------------------------------------------------
@@ -70,14 +73,46 @@ function isOnPath(command) {
70
73
  return false;
71
74
  }
72
75
  }
73
- function spawnInteractive(command, args) {
76
+ /**
77
+ * Spawn a process with a timeout. Kills it if it doesn't exit in time.
78
+ * This prevents CLI tools from hanging the setup wizard.
79
+ */
80
+ function spawnWithTimeout(command, args, timeoutSec) {
74
81
  return new Promise((resolve, reject) => {
75
- const child = nodeSpawn(command, args, {
76
- stdio: "inherit",
77
- shell: process.platform === "win32",
82
+ let child;
83
+ try {
84
+ child = nodeSpawn(command, args, {
85
+ stdio: "inherit",
86
+ shell: process.platform === "win32",
87
+ });
88
+ }
89
+ catch (err) {
90
+ reject(err);
91
+ return;
92
+ }
93
+ let settled = false;
94
+ const timer = setTimeout(() => {
95
+ if (!settled) {
96
+ settled = true;
97
+ child.kill("SIGTERM");
98
+ // Treat timeout as success — the CLI tool ran, just didn't exit cleanly
99
+ resolve(0);
100
+ }
101
+ }, timeoutSec * 1000);
102
+ child.on("error", (err) => {
103
+ if (!settled) {
104
+ settled = true;
105
+ clearTimeout(timer);
106
+ reject(err);
107
+ }
108
+ });
109
+ child.on("close", (code) => {
110
+ if (!settled) {
111
+ settled = true;
112
+ clearTimeout(timer);
113
+ resolve(code ?? 1);
114
+ }
78
115
  });
79
- child.on("error", reject);
80
- child.on("close", (code) => resolve(code ?? 1));
81
116
  });
82
117
  }
83
118
  function loadExistingEnv() {
@@ -122,79 +157,94 @@ export async function runSetup() {
122
157
  console.log(` ${B}${C}HydraMCP Setup${X}`);
123
158
  console.log(` ${D}Multi-model intelligence for Claude Code${X}`);
124
159
  console.log("");
125
- // --- API Keys ---
126
- console.log(` ${B}API Keys${X} ${D}(pay-per-token, direct access)${X}`);
160
+ // --- Choose setup path ---
161
+ console.log(` ${B}How do you want to connect models?${X}`);
127
162
  console.log("");
128
- const keyDefs = [
129
- { env: "OPENAI_API_KEY", label: "OpenAI" },
130
- { env: "GOOGLE_API_KEY", label: "Google / Gemini" },
131
- { env: "ANTHROPIC_API_KEY", label: "Anthropic" },
132
- ];
133
- for (const kd of keyDefs) {
134
- const current = env[kd.env];
135
- if (current) {
136
- const change = await ask(` ${kd.label} ${D}[${mask(current)}]${X} — keep? ${D}(Enter=yes, or paste new key)${X}: `);
137
- if (change)
138
- env[kd.env] = change;
139
- }
140
- else {
141
- const val = await ask(` ${kd.label} API key ${D}(Enter to skip)${X}: `);
142
- if (val)
143
- env[kd.env] = val;
163
+ console.log(` ${B}1.${X} API Keys ${D}— pay-per-token, paste keys and go${X}`);
164
+ console.log(` ${B}2.${X} Subscriptions ${D}— use ChatGPT Plus, Claude Pro, Gemini Advanced${X}`);
165
+ console.log(` ${B}3.${X} Both`);
166
+ console.log("");
167
+ const pathChoice = await ask(` Choice ${D}(1/2/3)${X}: `);
168
+ const doApiKeys = pathChoice === "1" || pathChoice === "3";
169
+ const doSubscriptions = pathChoice === "2" || pathChoice === "3";
170
+ // --- API Keys ---
171
+ if (doApiKeys) {
172
+ console.log("");
173
+ console.log(` ${B}API Keys${X}`);
174
+ console.log("");
175
+ const keyDefs = [
176
+ { env: "OPENAI_API_KEY", label: "OpenAI" },
177
+ { env: "GOOGLE_API_KEY", label: "Google / Gemini" },
178
+ { env: "ANTHROPIC_API_KEY", label: "Anthropic" },
179
+ ];
180
+ for (const kd of keyDefs) {
181
+ const current = env[kd.env];
182
+ if (current) {
183
+ const change = await ask(` ${kd.label} ${D}[${mask(current)}]${X} — keep? ${D}(Enter=yes, or paste new key)${X}: `);
184
+ if (change)
185
+ env[kd.env] = change;
186
+ }
187
+ else {
188
+ const val = await ask(` ${kd.label} API key ${D}(Enter to skip)${X}: `);
189
+ if (val)
190
+ env[kd.env] = val;
191
+ }
192
+ if (env[kd.env])
193
+ results.push(`${G}+${X} ${kd.label} (API key)`);
144
194
  }
145
- if (env[kd.env])
146
- results.push(`${G}+${X} ${kd.label} (API key)`);
147
195
  }
148
196
  // --- Subscriptions ---
149
- console.log("");
150
- console.log(` ${B}Subscriptions${X} ${D}(flat monthly rate, uses CLI tools)${X}`);
151
- console.log("");
152
- for (let i = 0; i < SUBS.length; i++) {
153
- const s = SUBS[i];
154
- const installed = isOnPath(s.command);
155
- const tag = installed ? `${G}installed${X}` : `${D}not installed${X}`;
156
- console.log(` ${B}${i + 1}.${X} ${s.name} ${D}— ${s.desc}${X} [${tag}]`);
157
- }
158
- console.log("");
159
- const subInput = await ask(` Which do you have? ${D}(e.g. 1,3 or Enter to skip)${X}: `);
160
- const selectedIndexes = subInput
161
- .split(",")
162
- .map((s) => parseInt(s.trim(), 10) - 1)
163
- .filter((i) => i >= 0 && i < SUBS.length);
164
- for (const idx of selectedIndexes) {
165
- const sub = SUBS[idx];
197
+ if (doSubscriptions) {
198
+ console.log("");
199
+ console.log(` ${B}Subscriptions${X}`);
166
200
  console.log("");
167
- console.log(` ${C}${sub.name}${X}`);
168
- // Check / install
169
- if (isOnPath(sub.command)) {
170
- console.log(` ${G}✓${X} ${sub.command} already installed`);
201
+ for (let i = 0; i < SUBS.length; i++) {
202
+ const s = SUBS[i];
203
+ const installed = isOnPath(s.command);
204
+ const tag = installed ? `${G}installed${X}` : `${D}not installed${X}`;
205
+ console.log(` ${B}${i + 1}.${X} ${s.name} ${D}— ${s.desc}${X} [${tag}]`);
171
206
  }
172
- else {
173
- console.log(` Installing ${sub.npmPkg}...`);
174
- try {
175
- execSync(`npm i -g ${sub.npmPkg}`, { stdio: "inherit" });
176
- console.log(` ${G}✓${X} Installed`);
207
+ console.log("");
208
+ const subInput = await ask(` Which do you have? ${D}(e.g. 1,3 or Enter to skip)${X}: `);
209
+ const selectedIndexes = subInput
210
+ .split(",")
211
+ .map((s) => parseInt(s.trim(), 10) - 1)
212
+ .filter((i) => i >= 0 && i < SUBS.length);
213
+ for (const idx of selectedIndexes) {
214
+ const sub = SUBS[idx];
215
+ console.log("");
216
+ console.log(` ${C}${sub.name}${X}`);
217
+ // Check / install
218
+ if (isOnPath(sub.command)) {
219
+ console.log(` ${G}✓${X} ${sub.command} already installed`);
177
220
  }
178
- catch {
179
- console.log(` ${R}✗${X} Install failed. Try manually: ${Y}sudo npm i -g ${sub.npmPkg}${X}`);
180
- continue;
221
+ else {
222
+ console.log(` Installing ${sub.npmPkg}...`);
223
+ try {
224
+ execSync(`npm i -g ${sub.npmPkg}`, { stdio: "inherit" });
225
+ console.log(` ${G}✓${X} Installed`);
226
+ }
227
+ catch {
228
+ console.log(` ${R}✗${X} Install failed. Try manually: ${Y}sudo npm i -g ${sub.npmPkg}${X}`);
229
+ continue;
230
+ }
181
231
  }
182
- }
183
- // Auth
184
- console.log(` ${D}${sub.authNote}${X}`);
185
- try {
186
- const code = await spawnInteractive(sub.command, sub.authArgs);
187
- if (code === 0) {
188
- console.log(` ${G}✓${X} Ready`);
189
- results.push(`${G}+${X} ${sub.name} (subscription)`);
232
+ // Auth — with timeout to prevent hanging
233
+ console.log(` ${D}${sub.authNote}${X}`);
234
+ try {
235
+ const code = await spawnWithTimeout(sub.command, sub.authArgs, sub.authTimeout);
236
+ if (code === 0) {
237
+ console.log(` ${G}✓${X} Ready`);
238
+ results.push(`${G}+${X} ${sub.name} (subscription)`);
239
+ }
240
+ else {
241
+ console.log(` ${Y}!${X} Auth may need manual setup: ${Y}${sub.command} ${sub.authArgs.join(" ")}${X}`);
242
+ }
190
243
  }
191
- else {
192
- console.log(` ${Y}!${X} Auth may need manual setup: ${Y}${sub.command} ${sub.authArgs.join(" ")}${X}`);
244
+ catch {
245
+ console.log(` ${Y}!${X} Could not run auth. Try: ${Y}${sub.command} ${sub.authArgs.join(" ")}${X}`);
193
246
  }
194
247
  }
195
- catch {
196
- console.log(` ${Y}!${X} Could not run auth. Try: ${Y}${sub.command} ${sub.authArgs.join(" ")}${X}`);
197
- }
198
248
  }
199
249
  // --- Ollama ---
200
250
  console.log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hydramcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Multi-model MCP server — compare, vote, and synthesize across GPT, Gemini, Claude, and local models from one terminal",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",