@enfyra/mcp-server 0.1.13 → 0.1.15

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 (71) hide show
  1. package/README.md +14 -1
  2. package/dist/index.d.ts +5 -0
  3. package/{src/index.mjs → dist/index.js} +8 -10
  4. package/dist/index.js.map +1 -0
  5. package/dist/lib/auth.d.ts +34 -0
  6. package/dist/lib/auth.js +161 -0
  7. package/dist/lib/auth.js.map +1 -0
  8. package/dist/lib/config-local.d.ts +1 -0
  9. package/dist/lib/config-local.js +719 -0
  10. package/dist/lib/config-local.js.map +1 -0
  11. package/dist/lib/extension-search-tools.d.ts +59 -0
  12. package/dist/lib/extension-search-tools.js +483 -0
  13. package/dist/lib/extension-search-tools.js.map +1 -0
  14. package/dist/lib/fetch.d.ts +28 -0
  15. package/dist/lib/fetch.js +106 -0
  16. package/dist/lib/fetch.js.map +1 -0
  17. package/dist/lib/mcp-examples.d.ts +99 -0
  18. package/dist/lib/mcp-examples.js +2285 -0
  19. package/dist/lib/mcp-examples.js.map +1 -0
  20. package/dist/lib/mcp-instructions.d.ts +11 -0
  21. package/dist/lib/mcp-instructions.js +78 -0
  22. package/dist/lib/mcp-instructions.js.map +1 -0
  23. package/dist/lib/mutation-guards.d.ts +33 -0
  24. package/dist/lib/mutation-guards.js +106 -0
  25. package/dist/lib/mutation-guards.js.map +1 -0
  26. package/dist/lib/platform-operation-tools.d.ts +12 -0
  27. package/dist/lib/platform-operation-tools.js +2304 -0
  28. package/dist/lib/platform-operation-tools.js.map +1 -0
  29. package/dist/lib/required-knowledge.d.ts +32 -0
  30. package/dist/lib/required-knowledge.js +181 -0
  31. package/dist/lib/required-knowledge.js.map +1 -0
  32. package/dist/lib/response-format.d.ts +7 -0
  33. package/dist/lib/response-format.js +179 -0
  34. package/dist/lib/response-format.js.map +1 -0
  35. package/dist/lib/route-guards.d.ts +1 -0
  36. package/dist/lib/route-guards.js +19 -0
  37. package/dist/lib/route-guards.js.map +1 -0
  38. package/dist/lib/route-permission-tools.d.ts +91 -0
  39. package/dist/lib/route-permission-tools.js +151 -0
  40. package/dist/lib/route-permission-tools.js.map +1 -0
  41. package/dist/lib/source-artifacts.d.ts +27 -0
  42. package/dist/lib/source-artifacts.js +82 -0
  43. package/dist/lib/source-artifacts.js.map +1 -0
  44. package/dist/lib/table-tools.d.ts +62 -0
  45. package/dist/lib/table-tools.js +774 -0
  46. package/dist/lib/table-tools.js.map +1 -0
  47. package/dist/lib/tool-routing.d.ts +297 -0
  48. package/dist/lib/tool-routing.js +585 -0
  49. package/dist/lib/tool-routing.js.map +1 -0
  50. package/dist/lib/types.d.ts +17 -0
  51. package/dist/lib/types.js +2 -0
  52. package/dist/lib/types.js.map +1 -0
  53. package/dist/mcp-server-entry.d.ts +4 -0
  54. package/dist/mcp-server-entry.js +2787 -0
  55. package/dist/mcp-server-entry.js.map +1 -0
  56. package/package.json +17 -10
  57. package/src/lib/auth.js +0 -179
  58. package/src/lib/config-local.mjs +0 -718
  59. package/src/lib/fetch.js +0 -111
  60. package/src/lib/mcp-examples.js +0 -2289
  61. package/src/lib/mcp-instructions.js +0 -80
  62. package/src/lib/mutation-guards.js +0 -118
  63. package/src/lib/platform-operation-tools.js +0 -2616
  64. package/src/lib/required-knowledge.js +0 -188
  65. package/src/lib/response-format.js +0 -187
  66. package/src/lib/route-guards.js +0 -24
  67. package/src/lib/route-permission-tools.js +0 -160
  68. package/src/lib/source-artifacts.js +0 -82
  69. package/src/lib/table-tools.js +0 -907
  70. package/src/lib/tool-routing.js +0 -589
  71. package/src/mcp-server-entry.mjs +0 -3177
@@ -0,0 +1,719 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { createInterface } from 'node:readline/promises';
3
+ import { emitKeypressEvents } from 'node:readline';
4
+ import { stdin as input, stdout as output, cwd } from 'node:process';
5
+ import { dirname, join } from 'node:path';
6
+ const SERVER_KEY = 'enfyra';
7
+ const MCP_PACKAGE_SPEC = '@enfyra/mcp-server@latest';
8
+ const forceColor = process.env.FORCE_COLOR != null && process.env.FORCE_COLOR !== '0';
9
+ const canStyle = forceColor || (output.isTTY && process.env.NO_COLOR == null);
10
+ const style = {
11
+ bold: (value) => canStyle ? `\x1B[1m${value}\x1B[22m` : value,
12
+ dim: (value) => canStyle ? `\x1B[2m${value}\x1B[22m` : value,
13
+ cyan: (value) => canStyle ? `\x1B[36m${value}\x1B[39m` : value,
14
+ green: (value) => canStyle ? `\x1B[32m${value}\x1B[39m` : value,
15
+ magenta: (value) => canStyle ? `\x1B[35m${value}\x1B[39m` : value,
16
+ blue: (value) => canStyle ? `\x1B[34m${value}\x1B[39m` : value,
17
+ yellow: (value) => canStyle ? `\x1B[33m${value}\x1B[39m` : value,
18
+ underline: (value) => canStyle ? `\x1B[4m${value}\x1B[24m` : value,
19
+ inverse: (value) => canStyle ? `\x1B[7m${value}\x1B[27m` : value,
20
+ };
21
+ const clients = {
22
+ codex: {
23
+ label: 'Codex',
24
+ path: './.codex/config.toml',
25
+ color: style.green,
26
+ },
27
+ claude: {
28
+ label: 'Claude Code',
29
+ path: './.mcp.json',
30
+ color: style.magenta,
31
+ },
32
+ cursor: {
33
+ label: 'Cursor',
34
+ path: './.cursor/mcp.json',
35
+ color: style.cyan,
36
+ },
37
+ vscode: {
38
+ label: 'VS Code / Copilot',
39
+ path: './.vscode/mcp.json',
40
+ color: style.blue,
41
+ },
42
+ antigravity: {
43
+ label: 'Antigravity',
44
+ path: './.agents/mcp_config.json',
45
+ color: style.yellow,
46
+ },
47
+ };
48
+ function statusIcon(kind) {
49
+ if (kind === 'success')
50
+ return canStyle ? style.green('✓') : 'Done';
51
+ if (kind === 'warn')
52
+ return canStyle ? style.yellow('!') : 'Warning';
53
+ return canStyle ? style.cyan('•') : '-';
54
+ }
55
+ function isCancelError(error) {
56
+ const item = error;
57
+ return item?.code === 'ABORT_ERR' || (item?.message || '') === 'Cancelled';
58
+ }
59
+ function exitCancelled() {
60
+ console.log('\nCancelled.');
61
+ process.exit(130);
62
+ }
63
+ function printHelp() {
64
+ console.log(`${style.bold('Enfyra MCP config')}
65
+ ${style.dim('Write project-local MCP client config for Enfyra.')}
66
+
67
+ ${style.bold('Usage')}
68
+ npx @enfyra/mcp-server@latest config [options]
69
+
70
+ ${style.bold('Supported clients')}
71
+ Codex ./.codex/config.toml
72
+ Claude Code ./.mcp.json
73
+ Cursor ./.cursor/mcp.json
74
+ VS Code ./.vscode/mcp.json
75
+ Antigravity ./.agents/mcp_config.json
76
+
77
+ ${style.bold('Options')}
78
+ --app-url <url> Enfyra app/admin URL, for example https://demo.enfyra.io
79
+ --api-token, -t <secret> ENFYRA_API_TOKEN
80
+ --reconfig Always choose target again in interactive mode and replace the old enfyra config for that target
81
+ --yes Non-interactive: no prompts (CI / scripts); use CLI, env, existing file, then defaults
82
+
83
+ ${style.bold('Client selection')}
84
+ Non-interactive default is all supported clients. In a TTY with no target flags, choose with ↑/↓.
85
+
86
+ --claude-code, --claude, --claude-only Only ./.mcp.json (Claude Code project scope)
87
+ --cursor, --cursor-only Only ./.cursor/mcp.json (Cursor)
88
+ --codex, --codex-only Only ./.codex/config.toml (Codex project scope)
89
+ --vscode, --copilot, --vscode-only Only ./.vscode/mcp.json (VS Code / Copilot)
90
+ --antigravity, --antigravity-only Only ./.agents/mcp_config.json (Antigravity)
91
+ Passing multiple target flags writes each selected target.
92
+
93
+ -h, --help Show this help
94
+
95
+ ${style.bold('Interactive mode')}
96
+ Choose Codex, Claude Code, Cursor, VS Code, Antigravity, or all clients; then enter ENFYRA_APP_URL and ENFYRA_API_TOKEN.
97
+ Existing Enfyra config and environment variables are used as defaults. Re-run anytime to update.
98
+
99
+ ${style.bold('Examples')}
100
+ npx @enfyra/mcp-server@latest config
101
+ npx @enfyra/mcp-server@latest config --yes
102
+ npx @enfyra/mcp-server@latest config --codex --cursor
103
+ npx @enfyra/mcp-server@latest config --vscode
104
+ npx @enfyra/mcp-server@latest config --antigravity
105
+ npx @enfyra/mcp-server@latest config --claude-code
106
+ npx @enfyra/mcp-server@latest config --reconfig
107
+ npx @enfyra/mcp-server@latest config --app-url http://localhost:3000 -t 'efy_pat_...'
108
+ ENFYRA_APP_URL=https://demo.enfyra.io ENFYRA_API_TOKEN=efy_pat_... npx @enfyra/mcp-server@latest config --yes
109
+ `);
110
+ }
111
+ function parseArgs(argv) {
112
+ const out = {
113
+ appUrl: undefined,
114
+ apiToken: undefined,
115
+ claude: true,
116
+ cursor: true,
117
+ codex: true,
118
+ help: false,
119
+ yes: false,
120
+ reconfig: false,
121
+ vscode: true,
122
+ antigravity: true,
123
+ targetExplicit: false,
124
+ };
125
+ let pickClaude = false;
126
+ let pickCursor = false;
127
+ let pickCodex = false;
128
+ let pickVscode = false;
129
+ let pickAntigravity = false;
130
+ for (let i = 0; i < argv.length; i += 1) {
131
+ const a = argv[i];
132
+ const next = () => {
133
+ const v = argv[i + 1];
134
+ if (v == null)
135
+ throw new Error(`Missing value after ${a}`);
136
+ i += 1;
137
+ return v;
138
+ };
139
+ if (a === '--help' || a === '-h')
140
+ out.help = true;
141
+ else if (a === 'help')
142
+ out.help = true;
143
+ else if (a === '--yes')
144
+ out.yes = true;
145
+ else if (a === '--reconfig')
146
+ out.reconfig = true;
147
+ else if (a === '--app-url')
148
+ out.appUrl = next();
149
+ else if (a === '--api-url' || a === '-a') {
150
+ throw new Error(`${a} is no longer supported for setup; use --app-url instead`);
151
+ }
152
+ else if (a === '--api-token' || a === '-t')
153
+ out.apiToken = next();
154
+ else if (a === '--email' || a === '-e' || a === '--password' || a === '-p') {
155
+ throw new Error(`${a} is no longer supported; use --api-token instead`);
156
+ }
157
+ else if (a === '--claude-only' || a === '--claude-code' || a === '--claude')
158
+ pickClaude = true;
159
+ else if (a === '--cursor-only' || a === '--cursor')
160
+ pickCursor = true;
161
+ else if (a === '--codex-only' || a === '--codex')
162
+ pickCodex = true;
163
+ else if (a === '--vscode-only' || a === '--vscode' || a === '--copilot')
164
+ pickVscode = true;
165
+ else if (a === '--antigravity-only' || a === '--antigravity')
166
+ pickAntigravity = true;
167
+ else
168
+ throw new Error(`Unknown argument: ${a}`);
169
+ }
170
+ out.targetExplicit = pickClaude || pickCursor || pickCodex || pickVscode || pickAntigravity;
171
+ if (out.targetExplicit) {
172
+ out.claude = pickClaude;
173
+ out.cursor = pickCursor;
174
+ out.codex = pickCodex;
175
+ out.vscode = pickVscode;
176
+ out.antigravity = pickAntigravity;
177
+ }
178
+ return out;
179
+ }
180
+ function buildServerEntry(apiUrl, apiToken) {
181
+ return {
182
+ command: 'npx',
183
+ args: ['-y', MCP_PACKAGE_SPEC],
184
+ env: {
185
+ ENFYRA_API_URL: apiUrl,
186
+ ENFYRA_API_TOKEN: apiToken,
187
+ },
188
+ };
189
+ }
190
+ function normalizeAppUrl(appUrl) {
191
+ const raw = String(appUrl || '').trim().replace(/\/+$/, '');
192
+ if (!raw)
193
+ return '';
194
+ return raw.replace(/\/(?:api|enfyra)$/i, '') || raw;
195
+ }
196
+ function deriveApiUrlFromAppUrl(appUrl) {
197
+ const normalized = normalizeAppUrl(appUrl);
198
+ return normalized ? `${normalized}/api` : 'http://localhost:3000/api';
199
+ }
200
+ function deriveAppUrlFromApiUrl(apiUrl) {
201
+ return normalizeAppUrl(apiUrl);
202
+ }
203
+ function deriveMeUrl(appUrl) {
204
+ const normalized = normalizeAppUrl(appUrl);
205
+ return normalized ? `${normalized}/me` : '/me';
206
+ }
207
+ function resolveDefaultAppUrl(opts, existing) {
208
+ const appCandidate = opts.appUrl ?? process.env.ENFYRA_APP_URL;
209
+ if (appCandidate)
210
+ return normalizeAppUrl(appCandidate);
211
+ const apiCandidate = process.env.ENFYRA_API_URL ?? existing.apiUrl;
212
+ if (apiCandidate)
213
+ return deriveAppUrlFromApiUrl(apiCandidate);
214
+ return 'http://localhost:3000';
215
+ }
216
+ async function mergeMcpFile(absPath, serverEntry) {
217
+ let data = { mcpServers: {} };
218
+ try {
219
+ const raw = await readFile(absPath, 'utf8');
220
+ const parsed = JSON.parse(raw);
221
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
222
+ data = { ...parsed, mcpServers: parsed.mcpServers && typeof parsed.mcpServers === 'object' ? { ...parsed.mcpServers } : {} };
223
+ }
224
+ }
225
+ catch (e) {
226
+ if (e.code !== 'ENOENT')
227
+ throw e;
228
+ }
229
+ data.mcpServers = { ...data.mcpServers, [SERVER_KEY]: serverEntry };
230
+ const dir = dirname(absPath);
231
+ await mkdir(dir, { recursive: true });
232
+ await writeFile(absPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
233
+ }
234
+ async function mergeVscodeMcpFile(absPath, serverEntry) {
235
+ let data = { servers: {} };
236
+ try {
237
+ const raw = await readFile(absPath, 'utf8');
238
+ const parsed = JSON.parse(raw);
239
+ if (parsed && typeof parsed === 'object') {
240
+ data = {
241
+ ...parsed,
242
+ servers: parsed.servers && typeof parsed.servers === 'object' ? { ...parsed.servers } : {},
243
+ };
244
+ }
245
+ }
246
+ catch (e) {
247
+ if (e.code !== 'ENOENT')
248
+ throw e;
249
+ }
250
+ data.servers = {
251
+ ...data.servers,
252
+ [SERVER_KEY]: {
253
+ type: 'stdio',
254
+ ...serverEntry,
255
+ },
256
+ };
257
+ await mkdir(dirname(absPath), { recursive: true });
258
+ await writeFile(absPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
259
+ }
260
+ function tomlString(value) {
261
+ return JSON.stringify(String(value ?? ''));
262
+ }
263
+ function buildCodexTomlBlock(apiUrl, apiToken) {
264
+ return [
265
+ '[mcp_servers.enfyra]',
266
+ 'command = "npx"',
267
+ `args = ["-y", "${MCP_PACKAGE_SPEC}"]`,
268
+ '',
269
+ '[mcp_servers.enfyra.env]',
270
+ `ENFYRA_API_URL = ${tomlString(apiUrl)}`,
271
+ `ENFYRA_API_TOKEN = ${tomlString(apiToken)}`,
272
+ '',
273
+ ].join('\n');
274
+ }
275
+ async function mergeCodexConfig(absPath, apiUrl, apiToken) {
276
+ let raw = '';
277
+ try {
278
+ raw = await readFile(absPath, 'utf8');
279
+ }
280
+ catch (e) {
281
+ if (e.code !== 'ENOENT')
282
+ throw e;
283
+ }
284
+ const kept = [];
285
+ let skip = false;
286
+ for (const line of raw.split(/\r?\n/)) {
287
+ const header = line.match(/^\s*\[([^\]]+)\]\s*$/);
288
+ if (header) {
289
+ const section = header[1].trim();
290
+ skip = section === 'mcp_servers.enfyra' || section.startsWith('mcp_servers.enfyra.');
291
+ }
292
+ if (!skip)
293
+ kept.push(line);
294
+ }
295
+ const prefix = kept.join('\n').trimEnd();
296
+ const next = prefix ? `${prefix}\n\n${buildCodexTomlBlock(apiUrl, apiToken)}` : buildCodexTomlBlock(apiUrl, apiToken);
297
+ await mkdir(dirname(absPath), { recursive: true });
298
+ await writeFile(absPath, next, 'utf8');
299
+ }
300
+ function parseTomlString(value) {
301
+ const trimmed = String(value || '').trim();
302
+ if (!trimmed)
303
+ return '';
304
+ try {
305
+ return JSON.parse(trimmed);
306
+ }
307
+ catch {
308
+ return trimmed.replace(/^['"]|['"]$/g, '');
309
+ }
310
+ }
311
+ async function readCodexEnfyraEnv(absPath) {
312
+ try {
313
+ const raw = await readFile(absPath, 'utf8');
314
+ const values = { apiUrl: '', apiToken: '' };
315
+ let inEnv = false;
316
+ for (const line of raw.split(/\r?\n/)) {
317
+ const header = line.match(/^\s*\[([^\]]+)\]\s*$/);
318
+ if (header) {
319
+ inEnv = header[1].trim() === 'mcp_servers.enfyra.env';
320
+ continue;
321
+ }
322
+ if (!inEnv)
323
+ continue;
324
+ const pair = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
325
+ if (!pair)
326
+ continue;
327
+ const key = pair[1];
328
+ const value = parseTomlString(pair[2]);
329
+ if (key === 'ENFYRA_API_URL')
330
+ values.apiUrl = value;
331
+ if (key === 'ENFYRA_API_TOKEN')
332
+ values.apiToken = value;
333
+ }
334
+ if (values.apiUrl || values.apiToken)
335
+ return values;
336
+ }
337
+ catch {
338
+ /* */
339
+ }
340
+ return null;
341
+ }
342
+ function getCodexConfigPath(root) {
343
+ return join(root, '.codex', 'config.toml');
344
+ }
345
+ function getClaudeConfigPath(root) {
346
+ return join(root, '.mcp.json');
347
+ }
348
+ function getCursorConfigPath(root) {
349
+ return join(root, '.cursor', 'mcp.json');
350
+ }
351
+ function getVscodeConfigPath(root) {
352
+ return join(root, '.vscode', 'mcp.json');
353
+ }
354
+ function getAntigravityConfigPath(root) {
355
+ return join(root, '.agents', 'mcp_config.json');
356
+ }
357
+ function getClientPath(client, root) {
358
+ if (client === 'claude')
359
+ return getClaudeConfigPath(root);
360
+ if (client === 'cursor')
361
+ return getCursorConfigPath(root);
362
+ if (client === 'codex')
363
+ return getCodexConfigPath(root);
364
+ if (client === 'vscode')
365
+ return getVscodeConfigPath(root);
366
+ if (client === 'antigravity')
367
+ return getAntigravityConfigPath(root);
368
+ throw new Error(`Unknown MCP client: ${client}`);
369
+ }
370
+ async function readMcpServerEnv(absPath, serverRootKey) {
371
+ try {
372
+ const raw = await readFile(absPath, 'utf8');
373
+ const j = JSON.parse(raw);
374
+ const e = j?.[serverRootKey]?.[SERVER_KEY]?.env;
375
+ if (e && typeof e === 'object' && (e.ENFYRA_API_URL || e.ENFYRA_API_TOKEN)) {
376
+ return {
377
+ apiUrl: typeof e.ENFYRA_API_URL === 'string' ? e.ENFYRA_API_URL : '',
378
+ apiToken: typeof e.ENFYRA_API_TOKEN === 'string' ? e.ENFYRA_API_TOKEN : '',
379
+ };
380
+ }
381
+ }
382
+ catch {
383
+ /* */
384
+ }
385
+ return null;
386
+ }
387
+ async function loadExistingEnfyraEnv(root, readClaude, readCursor, readCodex, readVscode, readAntigravity) {
388
+ const paths = [];
389
+ if (readClaude)
390
+ paths.push({ path: getClaudeConfigPath(root), rootKey: 'mcpServers' });
391
+ if (readCursor)
392
+ paths.push({ path: getCursorConfigPath(root), rootKey: 'mcpServers' });
393
+ if (!readClaude && readCursor)
394
+ paths.push({ path: getClaudeConfigPath(root), rootKey: 'mcpServers' });
395
+ if (readVscode)
396
+ paths.push({ path: getVscodeConfigPath(root), rootKey: 'servers' });
397
+ if (readAntigravity)
398
+ paths.push({ path: getAntigravityConfigPath(root), rootKey: 'mcpServers' });
399
+ const seen = new Set();
400
+ for (const entry of paths) {
401
+ const key = `${entry.rootKey}:${entry.path}`;
402
+ if (seen.has(key))
403
+ continue;
404
+ seen.add(key);
405
+ const env = await readMcpServerEnv(entry.path, entry.rootKey);
406
+ if (env)
407
+ return env;
408
+ }
409
+ if (readCodex) {
410
+ const codex = await readCodexEnfyraEnv(getCodexConfigPath(root));
411
+ if (codex)
412
+ return codex;
413
+ }
414
+ return { apiUrl: '', apiToken: '' };
415
+ }
416
+ async function promptTargetChoice() {
417
+ const choices = [
418
+ {
419
+ client: 'codex',
420
+ value: { claude: false, cursor: false, codex: true, vscode: false, antigravity: false },
421
+ },
422
+ {
423
+ client: 'claude',
424
+ value: { claude: true, cursor: false, codex: false, vscode: false, antigravity: false },
425
+ },
426
+ {
427
+ client: 'cursor',
428
+ value: { claude: false, cursor: true, codex: false, vscode: false, antigravity: false },
429
+ },
430
+ {
431
+ client: 'vscode',
432
+ value: { claude: false, cursor: false, codex: false, vscode: true, antigravity: false },
433
+ },
434
+ {
435
+ client: 'antigravity',
436
+ value: { claude: false, cursor: false, codex: false, vscode: false, antigravity: true },
437
+ },
438
+ {
439
+ client: 'all',
440
+ value: { claude: true, cursor: true, codex: true, vscode: true, antigravity: true },
441
+ },
442
+ ];
443
+ if (input.setRawMode && output.isTTY) {
444
+ return promptTargetSelect(choices, 5);
445
+ }
446
+ const rl = createInterface({ input, output });
447
+ const line = (await rl.question('Where should Enfyra MCP config be written?\n'
448
+ + ' [1] Codex ./.codex/config.toml\n'
449
+ + ' [2] Claude Code ./.mcp.json\n'
450
+ + ' [3] Cursor ./.cursor/mcp.json\n'
451
+ + ' [4] VS Code ./.vscode/mcp.json\n'
452
+ + ' [5] Antigravity ./.agents/mcp_config.json\n'
453
+ + ' [6] All [default]\n'
454
+ + 'Choice [6]: ')).trim().toLowerCase();
455
+ await rl.close();
456
+ if (line === '' || line === '6' || line === 'all' || line === 'a') {
457
+ return { claude: true, cursor: true, codex: true, vscode: true, antigravity: true };
458
+ }
459
+ if (line === '1' || line === 'codex' || line === 'x') {
460
+ return { claude: false, cursor: false, codex: true, vscode: false, antigravity: false };
461
+ }
462
+ if (line === '2' || line === 'claude' || line === 'claude-code') {
463
+ return { claude: true, cursor: false, codex: false, vscode: false, antigravity: false };
464
+ }
465
+ if (line === '3' || line === 'cursor' || line === 'u') {
466
+ return { claude: false, cursor: true, codex: false, vscode: false, antigravity: false };
467
+ }
468
+ if (line === '4' || line === 'vscode' || line === 'vs-code' || line === 'copilot') {
469
+ return { claude: false, cursor: false, codex: false, vscode: true, antigravity: false };
470
+ }
471
+ if (line === '5' || line === 'antigravity') {
472
+ return { claude: false, cursor: false, codex: false, vscode: false, antigravity: true };
473
+ }
474
+ return { claude: true, cursor: true, codex: true, vscode: true, antigravity: true };
475
+ }
476
+ async function promptTargetSelect(choices, initialIndex = 0) {
477
+ let selected = Math.max(0, Math.min(initialIndex, choices.length - 1));
478
+ let renderedLines = 0;
479
+ const formatChoice = (choice, active) => {
480
+ const indicator = active ? style.cyan('◆') : style.dim('◇');
481
+ const accent = active ? style.cyan('│') : style.dim('│');
482
+ if (choice.client === 'all') {
483
+ const label = active ? style.bold(style.underline('All supported clients')) : 'All supported clients';
484
+ const paddedLabel = label + ' '.repeat(22 - 'All supported clients'.length);
485
+ const hint = active ? style.cyan('Codex + Claude Code + Cursor + VS Code + Antigravity') : style.dim('Codex + Claude Code + Cursor + VS Code + Antigravity');
486
+ return `${accent} ${indicator} ${paddedLabel} ${hint}`;
487
+ }
488
+ const meta = clients[choice.client];
489
+ const label = active ? style.bold(meta.color(meta.label)) : meta.color(meta.label);
490
+ const paddedLabel = label + ' '.repeat(Math.max(1, 22 - meta.label.length));
491
+ const path = active ? style.cyan(meta.path) : style.dim(meta.path);
492
+ return `${accent} ${indicator} ${paddedLabel} ${path}`;
493
+ };
494
+ const render = () => {
495
+ if (renderedLines > 0) {
496
+ output.write(`\x1B[${renderedLines}A\x1B[0J`);
497
+ }
498
+ const lines = [
499
+ `${style.cyan('◆')} ${style.bold('Enfyra MCP setup')}`,
500
+ `${style.dim('│')} ${style.dim('Choose where to write the project config.')}`,
501
+ style.dim('│'),
502
+ ...choices.map((choice, index) => formatChoice(choice, index === selected)),
503
+ style.dim('│'),
504
+ style.dim('Choose one client config, or write all supported project configs.'),
505
+ `${style.dim('└')} ${style.dim('Use ↑/↓ to move, Enter to select, Ctrl+C to cancel.')}`,
506
+ ];
507
+ renderedLines = lines.length;
508
+ output.write(`${lines.join('\n')}\n`);
509
+ };
510
+ return new Promise((resolve, reject) => {
511
+ const wasRaw = input.isRaw;
512
+ const cleanup = () => {
513
+ input.off('keypress', onKeypress);
514
+ if (!wasRaw)
515
+ input.setRawMode(false);
516
+ output.write('\x1B[?25h');
517
+ };
518
+ const finish = () => {
519
+ cleanup();
520
+ resolve(choices[selected].value);
521
+ };
522
+ const onKeypress = (_str, key = {}) => {
523
+ if (key.ctrl && key.name === 'c') {
524
+ cleanup();
525
+ output.write('\n');
526
+ reject(new Error('Cancelled'));
527
+ return;
528
+ }
529
+ if (key.name === 'up') {
530
+ selected = selected <= 0 ? choices.length - 1 : selected - 1;
531
+ render();
532
+ return;
533
+ }
534
+ if (key.name === 'down') {
535
+ selected = selected >= choices.length - 1 ? 0 : selected + 1;
536
+ render();
537
+ return;
538
+ }
539
+ if (key.name === 'return' || key.name === 'enter') {
540
+ finish();
541
+ }
542
+ };
543
+ emitKeypressEvents(input);
544
+ input.setRawMode(true);
545
+ input.resume();
546
+ output.write('\x1B[?25l');
547
+ input.on('keypress', onKeypress);
548
+ render();
549
+ });
550
+ }
551
+ async function promptConfig(opts, existing) {
552
+ let appUrl = opts.appUrl ? normalizeAppUrl(opts.appUrl) : '';
553
+ let apiToken = opts.apiToken;
554
+ if (appUrl && apiToken !== undefined) {
555
+ return { apiUrl: deriveApiUrlFromAppUrl(appUrl), apiToken };
556
+ }
557
+ const rl = createInterface({ input, output });
558
+ const q = async (msg) => {
559
+ try {
560
+ return await rl.question(msg);
561
+ }
562
+ catch (error) {
563
+ if (isCancelError(error)) {
564
+ throw new Error('Cancelled');
565
+ }
566
+ throw error;
567
+ }
568
+ };
569
+ const defaultAppUrl = resolveDefaultAppUrl(opts, existing);
570
+ if (!appUrl) {
571
+ console.log(`${style.cyan('◆')} ${style.bold('Connect to Enfyra')}`);
572
+ console.log(`${style.dim('│')} Enter the Enfyra app URL.`);
573
+ const line = (await q(`${style.dim('└')} ENFYRA_APP_URL ${style.dim(`[${defaultAppUrl}]`)}: `)).trim();
574
+ appUrl = normalizeAppUrl(line || defaultAppUrl);
575
+ }
576
+ const apiUrl = deriveApiUrlFromAppUrl(appUrl);
577
+ const defaultApiToken = opts.apiToken ?? process.env.ENFYRA_API_TOKEN ?? existing.apiToken ?? '';
578
+ if (apiToken === undefined) {
579
+ const hint = defaultApiToken ? ' (Enter = keep current)' : '';
580
+ const meUrl = deriveMeUrl(appUrl);
581
+ console.log('');
582
+ console.log(`${style.cyan('◆')} ${style.bold('API token')}`);
583
+ console.log(`${style.dim('│')} If you do not have a token yet, create one here: ${style.cyan(meUrl)}`);
584
+ const line = (await q(`${style.dim('└')} ENFYRA_API_TOKEN${hint}: `)).trim();
585
+ apiToken = line !== '' ? line : defaultApiToken;
586
+ }
587
+ await rl.close();
588
+ return { apiUrl, apiToken };
589
+ }
590
+ function resolveNonInteractive(opts, existing) {
591
+ const appUrl = resolveDefaultAppUrl(opts, existing);
592
+ const apiUrl = deriveApiUrlFromAppUrl(appUrl);
593
+ const apiToken = opts.apiToken ?? process.env.ENFYRA_API_TOKEN ?? existing.apiToken ?? '';
594
+ return { apiUrl, apiToken };
595
+ }
596
+ export async function runLocalConfig(argv) {
597
+ const onSigint = () => exitCancelled();
598
+ const onUnhandledRejection = (error) => {
599
+ if (isCancelError(error))
600
+ exitCancelled();
601
+ };
602
+ const onUncaughtException = (error) => {
603
+ if (isCancelError(error))
604
+ exitCancelled();
605
+ throw error;
606
+ };
607
+ process.once('SIGINT', onSigint);
608
+ process.once('unhandledRejection', onUnhandledRejection);
609
+ process.once('uncaughtException', onUncaughtException);
610
+ let opts;
611
+ try {
612
+ opts = parseArgs(argv);
613
+ }
614
+ catch (e) {
615
+ console.error(e.message || e);
616
+ printHelp();
617
+ process.exit(1);
618
+ return;
619
+ }
620
+ if (opts.help) {
621
+ printHelp();
622
+ return;
623
+ }
624
+ const root = cwd();
625
+ const usePrompt = !opts.yes && input.isTTY && output.isTTY;
626
+ let writeClaude = opts.claude;
627
+ let writeCursor = opts.cursor;
628
+ let writeCodex = opts.codex;
629
+ let writeVscode = opts.vscode;
630
+ let writeAntigravity = opts.antigravity;
631
+ if (usePrompt && (!opts.targetExplicit || opts.reconfig)) {
632
+ const t = await promptTargetChoice();
633
+ writeClaude = t.claude;
634
+ writeCursor = t.cursor;
635
+ writeCodex = t.codex;
636
+ writeVscode = t.vscode;
637
+ writeAntigravity = t.antigravity;
638
+ }
639
+ const existing = await loadExistingEnfyraEnv(root, writeClaude, writeCursor, writeCodex, writeVscode, writeAntigravity);
640
+ let apiUrl;
641
+ let apiToken;
642
+ try {
643
+ if (usePrompt) {
644
+ const resolved = await promptConfig(opts, existing);
645
+ apiUrl = resolved.apiUrl;
646
+ apiToken = resolved.apiToken;
647
+ }
648
+ else {
649
+ const resolved = resolveNonInteractive(opts, existing);
650
+ apiUrl = resolved.apiUrl;
651
+ apiToken = resolved.apiToken;
652
+ }
653
+ }
654
+ catch (error) {
655
+ if (isCancelError(error)) {
656
+ exitCancelled();
657
+ return;
658
+ }
659
+ throw error;
660
+ }
661
+ const serverEntry = buildServerEntry(apiUrl, apiToken);
662
+ const written = [];
663
+ if (writeCodex) {
664
+ const p = getClientPath('codex', root);
665
+ await mergeCodexConfig(p, apiUrl, apiToken);
666
+ written.push({ client: 'codex', path: p });
667
+ }
668
+ if (writeClaude) {
669
+ const p = getClientPath('claude', root);
670
+ await mergeMcpFile(p, serverEntry);
671
+ written.push({ client: 'claude', path: p });
672
+ }
673
+ if (writeCursor) {
674
+ const p = getClientPath('cursor', root);
675
+ await mergeMcpFile(p, serverEntry);
676
+ written.push({ client: 'cursor', path: p });
677
+ }
678
+ if (writeVscode) {
679
+ const p = getClientPath('vscode', root);
680
+ await mergeVscodeMcpFile(p, serverEntry);
681
+ written.push({ client: 'vscode', path: p });
682
+ }
683
+ if (writeAntigravity) {
684
+ const p = getClientPath('antigravity', root);
685
+ await mergeMcpFile(p, serverEntry);
686
+ written.push({ client: 'antigravity', path: p });
687
+ }
688
+ console.log(`${statusIcon('success')} ${style.bold(style.green('Enfyra MCP config updated'))} ${style.dim('(project)')}\n`);
689
+ for (const entry of written) {
690
+ const meta = clients[entry.client];
691
+ console.log(` ${style.cyan('•')} ${style.bold(meta.color(meta.label))}`);
692
+ console.log(` ${style.dim(entry.path)}`);
693
+ }
694
+ const selectedClients = new Set(written.map(entry => entry.client));
695
+ console.log(`\n${style.bold(style.blue('Next steps'))}`);
696
+ if (selectedClients.has('codex')) {
697
+ console.log(' - Codex: open this folder in a new Codex session and approve the project MCP config if prompted.');
698
+ }
699
+ if (selectedClients.has('claude')) {
700
+ console.log(' - Claude Code: open this folder; approve project MCP if prompted.');
701
+ }
702
+ if (selectedClients.has('cursor')) {
703
+ console.log(' - Cursor: restart Cursor or reload MCP, then confirm the server under Settings -> MCP.');
704
+ }
705
+ if (selectedClients.has('vscode')) {
706
+ console.log(' - VS Code / Copilot: run MCP: List Servers or reload the workspace, then start the Enfyra server if prompted.');
707
+ }
708
+ if (selectedClients.has('antigravity')) {
709
+ console.log(' - Antigravity: reopen the workspace or reload MCP servers so ./.agents/mcp_config.json is picked up.');
710
+ }
711
+ console.log(' - Re-run this command anytime to update the same Enfyra entries.');
712
+ if (!apiToken) {
713
+ console.log(`\n${statusIcon('warn')} ${style.yellow('ENFYRA_API_TOKEN is empty; tools will not authenticate until it is set.')}`);
714
+ }
715
+ process.off('SIGINT', onSigint);
716
+ process.off('unhandledRejection', onUnhandledRejection);
717
+ process.off('uncaughtException', onUncaughtException);
718
+ }
719
+ //# sourceMappingURL=config-local.js.map