@enfyra/mcp-server 0.0.27 → 0.0.28
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 +1 -1
- package/package.json +1 -1
- package/src/lib/config-local.mjs +85 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ From your **Enfyra project root**:
|
|
|
17
17
|
npx @enfyra/mcp-server config
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
- **Interactive (default in a terminal):** first asks **where** to write config —
|
|
20
|
+
- **Interactive (default in a terminal):** first asks **where** to write config with an arrow-key selector — Claude Code, Cursor, Codex, or all — unless you already passed target flags. Then prompts for `ENFYRA_API_URL`, `ENFYRA_EMAIL`, and `ENFYRA_PASSWORD` when missing. Press **Enter** to accept bracketed defaults from env or existing `enfyra` config. Password **Enter** keeps the current saved password when updating.
|
|
21
21
|
- **Re-run anytime** to update the same files; other entries under `mcpServers` are preserved.
|
|
22
22
|
- **Non-interactive** (CI / scripts): `npx @enfyra/mcp-server config --yes` plus optional `-a` / `-e` / `-p` and/or env vars.
|
|
23
23
|
- **One host only:** `--claude-code` / `--claude` / `--claude-only` → `./.mcp.json`. `--cursor` / `--cursor-only` → `./.cursor/mcp.json`. `--codex` / `--codex-only` → `~/.codex/config.toml`. Pass multiple target flags to write each selected host.
|
package/package.json
CHANGED
package/src/lib/config-local.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
2
|
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
3
4
|
import { stdin as input, stdout as output, cwd } from 'node:process';
|
|
4
5
|
import { dirname, join } from 'node:path';
|
|
5
6
|
import { homedir } from 'node:os';
|
|
@@ -23,14 +24,14 @@ Options:
|
|
|
23
24
|
--password, -p <secret> ENFYRA_PASSWORD
|
|
24
25
|
--reconfig Always choose target again in interactive mode and replace the old enfyra config for that target
|
|
25
26
|
--yes Non-interactive: no prompts (CI / scripts); use CLI, env, existing file, then defaults
|
|
26
|
-
Target — non-interactive default is all; with TTY and no target flags,
|
|
27
|
+
Target — non-interactive default is all; with TTY and no target flags, choose with ↑/↓:
|
|
27
28
|
--claude-code, --claude, --claude-only Only ./.mcp.json (Claude Code project scope)
|
|
28
29
|
--cursor, --cursor-only Only ./.cursor/mcp.json (Cursor)
|
|
29
30
|
--codex, --codex-only Only ~/.codex/config.toml (Codex)
|
|
30
31
|
Passing multiple target flags writes each selected target.
|
|
31
32
|
-h, --help Show this help
|
|
32
33
|
|
|
33
|
-
Interactive mode:
|
|
34
|
+
Interactive mode: lets you choose Claude Code / Cursor / Codex / all if you did not pass target flags; then asks for URL / email / password
|
|
34
35
|
when missing. Existing ./.mcp.json, ./.cursor/mcp.json, and ~/.codex/config.toml are used as defaults. Re-run to update.
|
|
35
36
|
|
|
36
37
|
Examples:
|
|
@@ -231,6 +232,28 @@ async function loadExistingEnfyraEnv(root, readClaude, readCursor, readCodex) {
|
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
async function promptTargetChoice() {
|
|
235
|
+
const choices = [
|
|
236
|
+
{
|
|
237
|
+
label: 'Claude Code — ./.mcp.json',
|
|
238
|
+
value: { claude: true, cursor: false, codex: false },
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
label: 'Cursor — ./.cursor/mcp.json',
|
|
242
|
+
value: { claude: false, cursor: true, codex: false },
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
label: 'Codex — ~/.codex/config.toml',
|
|
246
|
+
value: { claude: false, cursor: false, codex: true },
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
label: 'All',
|
|
250
|
+
value: { claude: true, cursor: true, codex: true },
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
if (input.setRawMode && output.isTTY) {
|
|
254
|
+
return promptTargetSelect(choices, 3);
|
|
255
|
+
}
|
|
256
|
+
|
|
234
257
|
const rl = createInterface({ input, output });
|
|
235
258
|
const line = (await rl.question(
|
|
236
259
|
'Where should Enfyra MCP config be written?\n'
|
|
@@ -256,6 +279,66 @@ async function promptTargetChoice() {
|
|
|
256
279
|
return { claude: true, cursor: true, codex: true };
|
|
257
280
|
}
|
|
258
281
|
|
|
282
|
+
async function promptTargetSelect(choices, initialIndex = 0) {
|
|
283
|
+
let selected = Math.max(0, Math.min(initialIndex, choices.length - 1));
|
|
284
|
+
let renderedLines = 0;
|
|
285
|
+
|
|
286
|
+
const render = () => {
|
|
287
|
+
if (renderedLines > 0) {
|
|
288
|
+
output.write(`\x1B[${renderedLines}A\x1B[0J`);
|
|
289
|
+
}
|
|
290
|
+
const lines = [
|
|
291
|
+
'Where should Enfyra MCP config be written?',
|
|
292
|
+
...choices.map((choice, index) => `${index === selected ? '›' : ' '} ${choice.label}`),
|
|
293
|
+
'',
|
|
294
|
+
'Use ↑/↓ to move, Enter to select.',
|
|
295
|
+
];
|
|
296
|
+
renderedLines = lines.length;
|
|
297
|
+
output.write(`${lines.join('\n')}\n`);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
return new Promise((resolve, reject) => {
|
|
301
|
+
const wasRaw = input.isRaw;
|
|
302
|
+
const cleanup = () => {
|
|
303
|
+
input.off('keypress', onKeypress);
|
|
304
|
+
if (!wasRaw) input.setRawMode(false);
|
|
305
|
+
output.write('\x1B[?25h');
|
|
306
|
+
};
|
|
307
|
+
const finish = () => {
|
|
308
|
+
cleanup();
|
|
309
|
+
resolve(choices[selected].value);
|
|
310
|
+
};
|
|
311
|
+
const onKeypress = (_str, key = {}) => {
|
|
312
|
+
if (key.ctrl && key.name === 'c') {
|
|
313
|
+
cleanup();
|
|
314
|
+
output.write('\n');
|
|
315
|
+
reject(new Error('Cancelled'));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (key.name === 'up') {
|
|
319
|
+
selected = selected <= 0 ? choices.length - 1 : selected - 1;
|
|
320
|
+
render();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (key.name === 'down') {
|
|
324
|
+
selected = selected >= choices.length - 1 ? 0 : selected + 1;
|
|
325
|
+
render();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
329
|
+
finish();
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
emitKeypressEvents(input);
|
|
334
|
+
input.setRawMode(true);
|
|
335
|
+
input.resume();
|
|
336
|
+
output.write('\x1B[?25l');
|
|
337
|
+
input.on('keypress', onKeypress);
|
|
338
|
+
render();
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
259
342
|
async function promptConfig(opts, existing) {
|
|
260
343
|
let apiUrl = opts.apiUrl;
|
|
261
344
|
let email = opts.email;
|