gitnexus 1.6.8-rc.24 → 1.6.8-rc.26

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 (49) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/help-i18n.js +5 -0
  3. package/dist/cli/i18n/en.d.ts +5 -1
  4. package/dist/cli/i18n/en.js +5 -1
  5. package/dist/cli/i18n/resources.d.ts +9 -1
  6. package/dist/cli/i18n/zh-CN.d.ts +4 -0
  7. package/dist/cli/i18n/zh-CN.js +5 -1
  8. package/dist/cli/index.js +10 -1
  9. package/dist/cli/mcp.d.ts +6 -1
  10. package/dist/cli/mcp.js +26 -1
  11. package/dist/cli/setup.d.ts +3 -1
  12. package/dist/cli/setup.js +67 -17
  13. package/dist/mcp/http-transport.d.ts +134 -0
  14. package/dist/mcp/http-transport.js +503 -0
  15. package/dist/server/api.js +30 -28
  16. package/dist/server/mcp-http.d.ts +5 -6
  17. package/dist/server/mcp-http.js +9 -82
  18. package/dist/server/middleware.d.ts +34 -5
  19. package/dist/server/middleware.js +82 -17
  20. package/dist/server/private-ip.d.ts +1 -0
  21. package/dist/server/private-ip.js +15 -0
  22. package/package.json +1 -1
  23. package/web/assets/{agent-ay4LD70X.js → agent-CJmGaI8F.js} +1 -1
  24. package/web/assets/{architectureDiagram-UL44E2DR-Dc-viYhd.js → architectureDiagram-UL44E2DR-D7Nzv8Z7.js} +1 -1
  25. package/web/assets/{chunk-LCXTWHL2-4rpojOyj.js → chunk-LCXTWHL2-x5GBtTcn.js} +1 -1
  26. package/web/assets/{chunk-RG4AUYOV-BnOy944n.js → chunk-RG4AUYOV-5yz_7HGK.js} +1 -1
  27. package/web/assets/{classDiagram-KGZ6W3CR-Bsgpy98Q.js → classDiagram-KGZ6W3CR-Ce7th0vG.js} +1 -1
  28. package/web/assets/{classDiagram-v2-72OJOZXJ-D5atDGjc.js → classDiagram-v2-72OJOZXJ-Cc0N-4aA.js} +1 -1
  29. package/web/assets/{diagram-3NCE3AQN-Cz1OEMVi.js → diagram-3NCE3AQN-6wUsUD7U.js} +1 -1
  30. package/web/assets/{diagram-GF46GFSD-CWYwzfP8.js → diagram-GF46GFSD-Disbhf7W.js} +1 -1
  31. package/web/assets/{diagram-QXG6HAR7-DtBObr8L.js → diagram-QXG6HAR7-BeFCG-ob.js} +1 -1
  32. package/web/assets/{diagram-WEQXMOUZ-BUV44Ov_.js → diagram-WEQXMOUZ-Bd6Dgwiv.js} +1 -1
  33. package/web/assets/{erDiagram-L5TCEMPS-Ds9s-sRF.js → erDiagram-L5TCEMPS-sHox0Zi_.js} +1 -1
  34. package/web/assets/{flowDiagram-H6V6AXG4-CupStHQb.js → flowDiagram-H6V6AXG4-C0y_PyUc.js} +1 -1
  35. package/web/assets/{index-Do8AE5yF.js → index-C_FZ3Bqu.js} +6 -6
  36. package/web/assets/{infoDiagram-3YFTVSEB-DPTXSbgu.js → infoDiagram-3YFTVSEB-K8pFihzq.js} +1 -1
  37. package/web/assets/{ishikawaDiagram-BNXS4ZKH-CeK-Qhet.js → ishikawaDiagram-BNXS4ZKH-DnMJCWwI.js} +1 -1
  38. package/web/assets/{kanban-definition-75IXJCU3-D4tx6QQ-.js → kanban-definition-75IXJCU3-D-4EXdJe.js} +1 -1
  39. package/web/assets/{mindmap-definition-2TDM6QVE-Bcto_kGa.js → mindmap-definition-2TDM6QVE-CC0qeqmA.js} +1 -1
  40. package/web/assets/{pieDiagram-CU6KROY3-DPIeBTG6.js → pieDiagram-CU6KROY3-D-53o4Ll.js} +1 -1
  41. package/web/assets/{requirementDiagram-JXO7QTGE-DDGXjVKO.js → requirementDiagram-JXO7QTGE-gNmDM8qN.js} +1 -1
  42. package/web/assets/{sequenceDiagram-VS2MUI6T-BzDqpcVW.js → sequenceDiagram-VS2MUI6T-CxcTKXw1.js} +1 -1
  43. package/web/assets/{stateDiagram-7D4R322I-Bx__als3.js → stateDiagram-7D4R322I-CjnhCRbL.js} +1 -1
  44. package/web/assets/{stateDiagram-v2-36443NZ5-QqOh2yO-.js → stateDiagram-v2-36443NZ5-C5HPpkcW.js} +1 -1
  45. package/web/assets/{timeline-definition-O6YCAMPW-C1eogTOG.js → timeline-definition-O6YCAMPW-BmDoRniN.js} +1 -1
  46. package/web/assets/{vennDiagram-MWXL3ELB-D20F4rSW.js → vennDiagram-MWXL3ELB-S9b_NlgG.js} +1 -1
  47. package/web/assets/{wardleyDiagram-CUQ6CDDI-BeBHg7ST.js → wardleyDiagram-CUQ6CDDI-Bbs9z_r0.js} +1 -1
  48. package/web/assets/{xychartDiagram-N2JHSOCM-CdOWeoNI.js → xychartDiagram-N2JHSOCM-FyWVcm04.js} +1 -1
  49. package/web/index.html +1 -1
package/README.md CHANGED
@@ -34,7 +34,7 @@ That's it. This indexes the codebase, installs agent skills, registers Claude Co
34
34
 
35
35
  To configure MCP for your editor, run `npx gitnexus setup` once — or set it up manually below.
36
36
 
37
- `gitnexus setup` auto-detects your editors and writes the correct global MCP config. You only need to run it once.
37
+ `gitnexus setup` auto-detects your editors and writes the correct global MCP config. You only need to run it once. To configure only selected integrations, pass `--coding-agent`/`-c` with a comma-separated list or repeat the option, for example `gitnexus setup -c cursor,codex`.
38
38
 
39
39
  ### Editor Support
40
40
 
@@ -158,7 +158,7 @@ Your AI agent gets these tools automatically:
158
158
  ## CLI Commands
159
159
 
160
160
  ```bash
161
- gitnexus setup # Configure MCP for your editors (one-time)
161
+ gitnexus setup # Configure MCP for detected editors (one-time; use -c to select)
162
162
  gitnexus uninstall # Preview removal of GitNexus MCP/skills/hooks (add --force to apply)
163
163
  gitnexus analyze [path] # Index a repository (or update stale index)
164
164
  gitnexus analyze --repair-fts # Fast path: rebuild/verify only FTS indexes on existing index data
@@ -42,6 +42,7 @@ const COMMAND_DESCRIPTION_KEYS = {
42
42
  };
43
43
  const OPTION_DESCRIPTION_KEYS = {
44
44
  '|-V, --version': 'help.option.version',
45
+ 'setup|-c, --coding-agent <agents>': 'help.option.setup.codingAgent',
45
46
  'analyze|-f, --force': 'help.option.analyze.force',
46
47
  'analyze|--repair-fts': 'help.option.analyze.repairFts',
47
48
  'analyze|--embeddings [limit]': 'help.option.analyze.embeddings',
@@ -65,6 +66,10 @@ const OPTION_DESCRIPTION_KEYS = {
65
66
  'analyze|--embedding-device <device>': 'help.option.analyze.embeddingDevice',
66
67
  'index|-f, --force': 'help.option.index.force',
67
68
  'index|--allow-non-git': 'help.option.index.allowNonGit',
69
+ 'mcp|--http': 'help.option.mcp.http',
70
+ 'mcp|-p, --port <port>': 'help.option.port',
71
+ 'mcp|--host <host>': 'help.option.mcp.host',
72
+ 'mcp|--auth-token <token>': 'help.option.mcp.authToken',
68
73
  'serve|-p, --port <port>': 'help.option.port',
69
74
  'serve|--host <host>': 'help.option.serve.host',
70
75
  'uninstall|-f, --force': 'help.option.uninstall.force',
@@ -112,7 +112,7 @@ export declare const en: {
112
112
  readonly 'help.command.analyze.description': "Index a repository (full analysis)";
113
113
  readonly 'help.command.index.description': "Register an existing .gitnexus/ folder into the global registry (no re-analysis needed)";
114
114
  readonly 'help.command.serve.description': "Start local HTTP server for web UI connection";
115
- readonly 'help.command.mcp.description': "Start MCP server (stdio) serves all indexed repos";
115
+ readonly 'help.command.mcp.description': "Start MCP server. Default: stdio. Use --http for a remote HTTP server (Streamable HTTP at POST /mcp + legacy SSE at GET /sse, POST /messages).";
116
116
  readonly 'help.command.list.description': "List all indexed repositories";
117
117
  readonly 'help.command.status.description': "Show index status for current repo";
118
118
  readonly 'help.command.doctor.description': "Show runtime platform capabilities and embedding configuration";
@@ -138,6 +138,7 @@ export declare const en: {
138
138
  readonly 'help.command.group.impact.description': "Cross-repo impact for a symbol in one member repo of a group";
139
139
  readonly 'help.command.group.query.description': "Search execution flows across all repos in a group";
140
140
  readonly 'help.command.group.contracts.description': "Inspect Contract Registry";
141
+ readonly 'help.option.setup.codingAgent': "Configure only these coding agents (comma-separated or repeatable)";
141
142
  readonly 'help.option.analyze.force': "Force full re-index even if up to date";
142
143
  readonly 'help.option.analyze.repairFts': "Repair/rebuild search FTS indexes without full re-analysis";
143
144
  readonly 'help.option.analyze.embeddings': "Enable embedding generation for semantic search (off by default). Optional [limit] overrides the 50,000-node safety cap; pass 0 to disable the cap entirely.";
@@ -163,6 +164,9 @@ export declare const en: {
163
164
  readonly 'help.option.index.allowNonGit': "Allow registering folders that are not Git repositories";
164
165
  readonly 'help.option.port': "Port number";
165
166
  readonly 'help.option.serve.host': "Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)";
167
+ readonly 'help.option.mcp.http': "Serve MCP over HTTP instead of stdio (for remote clients)";
168
+ readonly 'help.option.mcp.host': "HTTP bind address (only with --http). Default: 127.0.0.1 (loopback). Use 0.0.0.0 to expose to all interfaces.";
169
+ readonly 'help.option.mcp.authToken': "Require this bearer token in the Authorization header (only with --http); may also be set via the GITNEXUS_MCP_AUTH_TOKEN env var. Required for a non-loopback bind (--host 0.0.0.0/::), which otherwise refuses to start.";
166
170
  readonly 'help.option.force.confirmation': "Skip confirmation prompt";
167
171
  readonly 'help.option.uninstall.force': "Apply the changes (default is a dry-run preview)";
168
172
  readonly 'help.option.clean.all': "Clean all indexed repos";
@@ -112,7 +112,7 @@ export const en = {
112
112
  'help.command.analyze.description': 'Index a repository (full analysis)',
113
113
  'help.command.index.description': 'Register an existing .gitnexus/ folder into the global registry (no re-analysis needed)',
114
114
  'help.command.serve.description': 'Start local HTTP server for web UI connection',
115
- 'help.command.mcp.description': 'Start MCP server (stdio) serves all indexed repos',
115
+ 'help.command.mcp.description': 'Start MCP server. Default: stdio. Use --http for a remote HTTP server (Streamable HTTP at POST /mcp + legacy SSE at GET /sse, POST /messages).',
116
116
  'help.command.list.description': 'List all indexed repositories',
117
117
  'help.command.status.description': 'Show index status for current repo',
118
118
  'help.command.doctor.description': 'Show runtime platform capabilities and embedding configuration',
@@ -138,6 +138,7 @@ export const en = {
138
138
  'help.command.group.impact.description': 'Cross-repo impact for a symbol in one member repo of a group',
139
139
  'help.command.group.query.description': 'Search execution flows across all repos in a group',
140
140
  'help.command.group.contracts.description': 'Inspect Contract Registry',
141
+ 'help.option.setup.codingAgent': 'Configure only these coding agents (comma-separated or repeatable)',
141
142
  'help.option.analyze.force': 'Force full re-index even if up to date',
142
143
  'help.option.analyze.repairFts': 'Repair/rebuild search FTS indexes without full re-analysis',
143
144
  'help.option.analyze.embeddings': 'Enable embedding generation for semantic search (off by default). Optional [limit] overrides the 50,000-node safety cap; pass 0 to disable the cap entirely.',
@@ -163,6 +164,9 @@ export const en = {
163
164
  'help.option.index.allowNonGit': 'Allow registering folders that are not Git repositories',
164
165
  'help.option.port': 'Port number',
165
166
  'help.option.serve.host': 'Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)',
167
+ 'help.option.mcp.http': 'Serve MCP over HTTP instead of stdio (for remote clients)',
168
+ 'help.option.mcp.host': 'HTTP bind address (only with --http). Default: 127.0.0.1 (loopback). Use 0.0.0.0 to expose to all interfaces.',
169
+ 'help.option.mcp.authToken': 'Require this bearer token in the Authorization header (only with --http); may also be set via the GITNEXUS_MCP_AUTH_TOKEN env var. Required for a non-loopback bind (--host 0.0.0.0/::), which otherwise refuses to start.',
166
170
  'help.option.force.confirmation': 'Skip confirmation prompt',
167
171
  'help.option.uninstall.force': 'Apply the changes (default is a dry-run preview)',
168
172
  'help.option.clean.all': 'Clean all indexed repos',
@@ -113,7 +113,7 @@ export declare const cliResources: {
113
113
  readonly 'help.command.analyze.description': "Index a repository (full analysis)";
114
114
  readonly 'help.command.index.description': "Register an existing .gitnexus/ folder into the global registry (no re-analysis needed)";
115
115
  readonly 'help.command.serve.description': "Start local HTTP server for web UI connection";
116
- readonly 'help.command.mcp.description': "Start MCP server (stdio) serves all indexed repos";
116
+ readonly 'help.command.mcp.description': "Start MCP server. Default: stdio. Use --http for a remote HTTP server (Streamable HTTP at POST /mcp + legacy SSE at GET /sse, POST /messages).";
117
117
  readonly 'help.command.list.description': "List all indexed repositories";
118
118
  readonly 'help.command.status.description': "Show index status for current repo";
119
119
  readonly 'help.command.doctor.description': "Show runtime platform capabilities and embedding configuration";
@@ -139,6 +139,7 @@ export declare const cliResources: {
139
139
  readonly 'help.command.group.impact.description': "Cross-repo impact for a symbol in one member repo of a group";
140
140
  readonly 'help.command.group.query.description': "Search execution flows across all repos in a group";
141
141
  readonly 'help.command.group.contracts.description': "Inspect Contract Registry";
142
+ readonly 'help.option.setup.codingAgent': "Configure only these coding agents (comma-separated or repeatable)";
142
143
  readonly 'help.option.analyze.force': "Force full re-index even if up to date";
143
144
  readonly 'help.option.analyze.repairFts': "Repair/rebuild search FTS indexes without full re-analysis";
144
145
  readonly 'help.option.analyze.embeddings': "Enable embedding generation for semantic search (off by default). Optional [limit] overrides the 50,000-node safety cap; pass 0 to disable the cap entirely.";
@@ -164,6 +165,9 @@ export declare const cliResources: {
164
165
  readonly 'help.option.index.allowNonGit': "Allow registering folders that are not Git repositories";
165
166
  readonly 'help.option.port': "Port number";
166
167
  readonly 'help.option.serve.host': "Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)";
168
+ readonly 'help.option.mcp.http': "Serve MCP over HTTP instead of stdio (for remote clients)";
169
+ readonly 'help.option.mcp.host': "HTTP bind address (only with --http). Default: 127.0.0.1 (loopback). Use 0.0.0.0 to expose to all interfaces.";
170
+ readonly 'help.option.mcp.authToken': "Require this bearer token in the Authorization header (only with --http); may also be set via the GITNEXUS_MCP_AUTH_TOKEN env var. Required for a non-loopback bind (--host 0.0.0.0/::), which otherwise refuses to start.";
167
171
  readonly 'help.option.force.confirmation': "Skip confirmation prompt";
168
172
  readonly 'help.option.uninstall.force': "Apply the changes (default is a dry-run preview)";
169
173
  readonly 'help.option.clean.all': "Clean all indexed repos";
@@ -365,6 +369,7 @@ export declare const cliResources: {
365
369
  'help.command.group.impact.description': string;
366
370
  'help.command.group.query.description': string;
367
371
  'help.command.group.contracts.description': string;
372
+ 'help.option.setup.codingAgent': string;
368
373
  'help.option.analyze.force': string;
369
374
  'help.option.analyze.repairFts': string;
370
375
  'help.option.analyze.embeddings': string;
@@ -390,6 +395,9 @@ export declare const cliResources: {
390
395
  'help.option.index.allowNonGit': string;
391
396
  'help.option.port': string;
392
397
  'help.option.serve.host': string;
398
+ 'help.option.mcp.http': string;
399
+ 'help.option.mcp.host': string;
400
+ 'help.option.mcp.authToken': string;
393
401
  'help.option.force.confirmation': string;
394
402
  'help.option.uninstall.force': string;
395
403
  'help.option.clean.all': string;
@@ -138,6 +138,7 @@ export declare const zhCN: {
138
138
  'help.command.group.impact.description': string;
139
139
  'help.command.group.query.description': string;
140
140
  'help.command.group.contracts.description': string;
141
+ 'help.option.setup.codingAgent': string;
141
142
  'help.option.analyze.force': string;
142
143
  'help.option.analyze.repairFts': string;
143
144
  'help.option.analyze.embeddings': string;
@@ -163,6 +164,9 @@ export declare const zhCN: {
163
164
  'help.option.index.allowNonGit': string;
164
165
  'help.option.port': string;
165
166
  'help.option.serve.host': string;
167
+ 'help.option.mcp.http': string;
168
+ 'help.option.mcp.host': string;
169
+ 'help.option.mcp.authToken': string;
166
170
  'help.option.force.confirmation': string;
167
171
  'help.option.uninstall.force': string;
168
172
  'help.option.clean.all': string;
@@ -112,7 +112,7 @@ export const zhCN = {
112
112
  'help.command.analyze.description': '索引仓库(完整分析)',
113
113
  'help.command.index.description': '将现有 .gitnexus/ 文件夹注册到全局注册表(无需重新分析)',
114
114
  'help.command.serve.description': '启动供 Web UI 连接的本地 HTTP 服务器',
115
- 'help.command.mcp.description': '启动 MCP 服务器(stdio)— 提供所有已索引仓库',
115
+ 'help.command.mcp.description': '启动 MCP 服务器。默认为 stdio。使用 --http 启动远程 HTTP 服务器(Streamable HTTP: POST /mcp + 遗留 SSE: GET /sse, POST /messages)。',
116
116
  'help.command.list.description': '列出所有已索引仓库',
117
117
  'help.command.status.description': '显示当前仓库的索引状态',
118
118
  'help.command.doctor.description': '显示运行平台能力和嵌入配置',
@@ -138,6 +138,7 @@ export const zhCN = {
138
138
  'help.command.group.impact.description': '分析仓库组中某个成员仓库符号的跨仓库影响',
139
139
  'help.command.group.query.description': '跨仓库组所有仓库搜索执行流程',
140
140
  'help.command.group.contracts.description': '查看 Contract Registry',
141
+ 'help.option.setup.codingAgent': '仅配置这些编码代理(逗号分隔或重复传入)',
141
142
  'help.option.analyze.force': '即使已是最新也强制完整重建索引',
142
143
  'help.option.analyze.repairFts': '修复/重建搜索 FTS 索引,不执行完整重新分析',
143
144
  'help.option.analyze.embeddings': '启用语义搜索的嵌入生成(默认关闭)。可选 [limit] 覆盖 50,000 节点安全上限;传 0 可完全禁用上限。',
@@ -163,6 +164,9 @@ export const zhCN = {
163
164
  'help.option.index.allowNonGit': '允许注册非 Git 仓库文件夹',
164
165
  'help.option.port': '端口号',
165
166
  'help.option.serve.host': '绑定地址(默认:127.0.0.1;远程访问可用 0.0.0.0)',
167
+ 'help.option.mcp.http': '使用 HTTP 代替 stdio 提供 MCP 服务(适合远程客户端)',
168
+ 'help.option.mcp.host': 'HTTP 绑定地址(仅与 --http 搭配使用)。默认:127.0.0.1(回环)。使用 0.0.0.0 向所有接口开放。',
169
+ 'help.option.mcp.authToken': '要求 Authorization 头携带此 Bearer Token(仅与 --http 搭配使用);也可通过 GITNEXUS_MCP_AUTH_TOKEN 环境变量设置。非回环绑定(--host 0.0.0.0/::)时必填,否则拒绝启动。',
166
170
  'help.option.force.confirmation': '跳过确认提示',
167
171
  'help.option.uninstall.force': '应用更改(默认仅为预演预览)',
168
172
  'help.option.clean.all': '清理所有已索引仓库',
package/dist/cli/index.js CHANGED
@@ -10,10 +10,14 @@ import { t } from './i18n/index.js';
10
10
  const _require = createRequire(import.meta.url);
11
11
  const pkg = _require('../../package.json');
12
12
  const program = new Command();
13
+ function collectCodingAgents(value, previous) {
14
+ return [...(previous ?? []), ...value.split(',')];
15
+ }
13
16
  program.name('gitnexus').description('GitNexus local CLI and MCP server').version(pkg.version);
14
17
  program
15
18
  .command('setup')
16
19
  .description('One-time setup: configure MCP for Cursor, Claude Code, Antigravity, OpenCode, Codex')
20
+ .option('-c, --coding-agent <agents>', 'Configure only these coding agents (comma-separated or repeatable)', collectCodingAgents)
17
21
  .action(createLazyAction(() => import('./setup.js'), 'setupCommand'));
18
22
  program
19
23
  .command('uninstall')
@@ -75,7 +79,12 @@ program
75
79
  .action(createLbugLazyAction(() => import('./serve.js'), 'serveCommand'));
76
80
  program
77
81
  .command('mcp')
78
- .description('Start MCP server (stdio) serves all indexed repos')
82
+ .description('Start MCP server. Default: stdio. Use --http for a remote HTTP server ' +
83
+ '(Streamable HTTP at POST /mcp + legacy SSE at GET /sse, POST /messages).')
84
+ .option('--http', 'Serve MCP over HTTP instead of stdio (for remote clients)')
85
+ .option('-p, --port <port>', 'HTTP port (only with --http). Default: 3000', '3000')
86
+ .option('--host <host>', 'HTTP bind address (only with --http). Default: 127.0.0.1 (loopback). Use 0.0.0.0 to expose to all interfaces.', '127.0.0.1')
87
+ .option('--auth-token <token>', 'Require this bearer token in the Authorization header (only with --http); may also be set via the GITNEXUS_MCP_AUTH_TOKEN env var. Required for a non-loopback bind (--host 0.0.0.0/::), which otherwise refuses to start.')
79
88
  .action(createLbugLazyAction(() => import('./mcp.js'), 'mcpCommand'));
80
89
  program
81
90
  .command('list')
package/dist/cli/mcp.d.ts CHANGED
@@ -26,4 +26,9 @@
26
26
  * `mcpCommand` after the sentinel install. The regression test at
27
27
  * `gitnexus/test/integration/mcp/import-closure.test.ts` enforces this.
28
28
  */
29
- export declare const mcpCommand: () => Promise<void>;
29
+ export declare const mcpCommand: (options?: {
30
+ http?: boolean;
31
+ port?: string;
32
+ host?: string;
33
+ authToken?: string;
34
+ }) => Promise<void>;
package/dist/cli/mcp.js CHANGED
@@ -27,7 +27,7 @@
27
27
  * `gitnexus/test/integration/mcp/import-closure.test.ts` enforces this.
28
28
  */
29
29
  import { installGlobalStdoutSentinel } from '../mcp/stdio-context.js';
30
- export const mcpCommand = async () => {
30
+ export const mcpCommand = async (options) => {
31
31
  // Install the global stdout sentinel as the very first thing — before
32
32
  // ANY other module loads. The static-import closure above is leaf-only
33
33
  // (stdio-context → stdio-capture, zero non-`node:` deps), so this is
@@ -68,6 +68,31 @@ export const mcpCommand = async () => {
68
68
  else {
69
69
  logger.info({ repoCount: repos.length, repos: repos.map((r) => r.name) }, 'GitNexus: MCP server starting');
70
70
  }
71
+ // Start HTTP server or fall back to stdio (default).
72
+ if (options?.http) {
73
+ // Dynamically import the HTTP transport module AFTER the sentinel installs.
74
+ // http-transport.ts pulls in express/cors/MCP SDK HTTP transport; these must
75
+ // not load before installGlobalStdoutSentinel() runs (see module doc above).
76
+ const port = Number(options.port ?? 3000);
77
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
78
+ logger.error({ port: options.port }, `Invalid --port value: "${options.port ?? ''}". Must be an integer between 1 and 65535.`);
79
+ process.exit(1);
80
+ }
81
+ // Dynamic import keeps express/cors out of mcp.ts's static graph (stdio sentinel).
82
+ const { startMcpHttpServer, resolveAuthToken } = await import('../mcp/http-transport.js');
83
+ try {
84
+ await startMcpHttpServer(backend, {
85
+ port,
86
+ host: options.host ?? '127.0.0.1',
87
+ authToken: resolveAuthToken(options.authToken, process.env),
88
+ });
89
+ }
90
+ catch (err) {
91
+ logger.error({ err: err instanceof Error ? err.message : err }, 'Failed to start the MCP HTTP server');
92
+ process.exit(1);
93
+ }
94
+ return;
95
+ }
71
96
  // Start MCP server (serves all repos, discovers new ones lazily)
72
97
  await startMCPServer(backend);
73
98
  };
@@ -37,5 +37,7 @@ interface SetupResult {
37
37
  * install paths copy this same list from hooks/claude/ (the canonical source).
38
38
  */
39
39
  export declare function copyHookHelpers(srcDir: string, destDir: string, label: string, result: SetupResult): Promise<string[]>;
40
- export declare const setupCommand: () => Promise<void>;
40
+ export declare const setupCommand: (options?: {
41
+ codingAgent?: string[] | string;
42
+ }) => Promise<void>;
41
43
  export {};
package/dist/cli/setup.js CHANGED
@@ -61,6 +61,33 @@ export function formatHookCommand(hookPath, isWindows = process.platform === 'wi
61
61
  // changes this literal, the guard records an actionable error instead of
62
62
  // silently shipping a hook with an unresolved relative cliPath.
63
63
  const CLI_PATH_SOURCE_LITERAL = "let cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');";
64
+ const CODING_AGENT_IDS = {
65
+ cursor: 'cursor',
66
+ claude: 'claude',
67
+ antigravity: 'antigravity',
68
+ opencode: 'opencode',
69
+ codex: 'codex',
70
+ };
71
+ const SUPPORTED_CODING_AGENTS = Object.values(CODING_AGENT_IDS);
72
+ function selectedCodingAgents(values) {
73
+ if (values == null)
74
+ return new Set(SUPPORTED_CODING_AGENTS);
75
+ const rawValues = Array.isArray(values) ? values : [values];
76
+ const requested = rawValues
77
+ .flatMap((value) => value.split(','))
78
+ .map((value) => value.trim().toLowerCase())
79
+ .filter(Boolean);
80
+ const invalid = requested.filter((value) => !SUPPORTED_CODING_AGENTS.includes(value));
81
+ if (requested.length === 0 || invalid.length > 0) {
82
+ const detail = requested.length === 0
83
+ ? 'No coding agents were provided.'
84
+ : `Unknown: ${invalid.join(', ')}.`;
85
+ process.stderr.write(`${detail} Valid values: ${SUPPORTED_CODING_AGENTS.join(', ')}.\n`);
86
+ process.exitCode = 1;
87
+ return null;
88
+ }
89
+ return new Set(requested);
90
+ }
64
91
  /**
65
92
  * Resolve the absolute path to the `gitnexus` binary if it's installed
66
93
  * globally (or via npm -g / yarn global). Returns null when not found.
@@ -832,7 +859,11 @@ async function installCodexSkills(result) {
832
859
  }
833
860
  }
834
861
  // ─── Main command ──────────────────────────────────────────────────
835
- export const setupCommand = async () => {
862
+ export const setupCommand = async (options) => {
863
+ const explicitSelection = options?.codingAgent != null;
864
+ const selected = selectedCodingAgents(options?.codingAgent);
865
+ if (!selected)
866
+ return;
836
867
  console.log('');
837
868
  console.log(' GitNexus Setup');
838
869
  console.log(' ==============');
@@ -846,19 +877,31 @@ export const setupCommand = async () => {
846
877
  errors: [],
847
878
  };
848
879
  // Detect and configure each editor's MCP
849
- await setupCursor(result);
850
- await setupClaudeCode(result);
851
- await setupAntigravity(result);
852
- await setupOpenCode(result);
853
- await setupCodex(result);
880
+ if (selected.has('cursor'))
881
+ await setupCursor(result);
882
+ if (selected.has('claude'))
883
+ await setupClaudeCode(result);
884
+ if (selected.has('antigravity'))
885
+ await setupAntigravity(result);
886
+ if (selected.has('opencode'))
887
+ await setupOpenCode(result);
888
+ if (selected.has('codex'))
889
+ await setupCodex(result);
854
890
  // Install global skills for platforms that support them
855
- await installClaudeCodeSkills(result);
856
- await installClaudeCodeHooks(result);
857
- await installAntigravitySkills(result);
858
- await installAntigravityHooks(result);
859
- await installCursorSkills(result);
860
- await installOpenCodeSkills(result);
861
- await installCodexSkills(result);
891
+ if (selected.has('claude')) {
892
+ await installClaudeCodeSkills(result);
893
+ await installClaudeCodeHooks(result);
894
+ }
895
+ if (selected.has('antigravity')) {
896
+ await installAntigravitySkills(result);
897
+ await installAntigravityHooks(result);
898
+ }
899
+ if (selected.has('cursor'))
900
+ await installCursorSkills(result);
901
+ if (selected.has('opencode'))
902
+ await installOpenCodeSkills(result);
903
+ if (selected.has('codex'))
904
+ await installCodexSkills(result);
862
905
  // Print results
863
906
  if (result.configured.length > 0) {
864
907
  console.log(' Configured:');
@@ -884,10 +927,17 @@ export const setupCommand = async () => {
884
927
  console.log(' Summary:');
885
928
  console.log(` MCP configured for: ${result.configured.filter((c) => !c.includes('skills')).join(', ') || 'none'}`);
886
929
  console.log(` Skills installed to: ${result.configured.filter((c) => c.includes('skills')).length > 0 ? result.configured.filter((c) => c.includes('skills')).join(', ') : 'none'}`);
930
+ const configurationSucceeded = result.configured.length > 0;
931
+ if (explicitSelection && !configurationSucceeded) {
932
+ process.stderr.write('None of the explicitly selected coding agents were configured.\n');
933
+ process.exitCode = 1;
934
+ }
887
935
  console.log('');
888
- console.log(' Next steps:');
889
- console.log(' 1. cd into any git repo');
890
- console.log(' 2. Run: gitnexus analyze');
891
- console.log(' 3. Open the repo in your editor — MCP is ready!');
936
+ if (configurationSucceeded) {
937
+ console.log(' Next steps:');
938
+ console.log(' 1. cd into any git repo');
939
+ console.log(' 2. Run: gitnexus analyze');
940
+ console.log(' 3. Open the repo in your editor — MCP is ready!');
941
+ }
892
942
  console.log('');
893
943
  };
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Dedicated MCP HTTP server.
3
+ *
4
+ * Provides HTTP-based MCP transport supporting:
5
+ * - Modern Streamable HTTP: POST /mcp
6
+ * - Legacy SSE transport: GET /sse + POST /messages
7
+ *
8
+ * Started via `gitnexus mcp --http`.
9
+ * stdio remains the default mode for `gitnexus mcp` (no breaking change).
10
+ *
11
+ * Exports createStreamableHttpHandler and createSseHandlers so that
12
+ * server/mcp-http.ts (web-UI route mount) can reuse them without inverting
13
+ * the established server/ → mcp/ dependency direction.
14
+ *
15
+ * Security considerations:
16
+ * - Default binds to 127.0.0.1 (loopback only).
17
+ * - Use --auth-token to enable Bearer Token authentication.
18
+ * - Use --host 0.0.0.0 to expose to all interfaces (requires --auth-token — refuses to start otherwise).
19
+ * - CORS is restricted to loopback origins when no auth token is configured.
20
+ * - PNA (Private Network Access) header is emitted only in response to browser preflight requests.
21
+ */
22
+ import type { Server as HttpServer } from 'http';
23
+ import { type Request, type Response, type NextFunction } from 'express';
24
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
25
+ import type { LocalBackend } from './local/local-backend.js';
26
+ /** HTTP server configuration options. */
27
+ export interface McpHttpOptions {
28
+ /** Listening port. */
29
+ port: number;
30
+ /** Bind address (default: 127.0.0.1). */
31
+ host: string;
32
+ /** Bearer auth token (optional; no auth when omitted). */
33
+ authToken?: string;
34
+ }
35
+ /**
36
+ * Creates a Bearer Token authentication middleware.
37
+ *
38
+ * - When authToken is not set, all requests pass through.
39
+ * - When authToken is set, checks the Authorization: Bearer <token> header.
40
+ * - Uses constant-time comparison to prevent timing oracle attacks.
41
+ * - Returns a JSON-RPC formatted 401 on failure.
42
+ */
43
+ export declare function createAuthMiddleware(authToken?: string): (req: Request, res: Response, next: NextFunction) => void;
44
+ /**
45
+ * Returns true when an Origin should be allowed by the no-auth (loopback-only)
46
+ * CORS policy — i.e. it is absent (non-browser caller) or a loopback origin.
47
+ *
48
+ * WHATWG URL keeps the brackets on IPv6 literals
49
+ * (`new URL('http://[::1]/').hostname === '[::1]'`) and canonicalizes the
50
+ * IPv4-mapped loopback to `[::ffff:7f00:1]`; loopback IPv4 is the whole
51
+ * 127.0.0.0/8 block — so all of those forms are matched explicitly.
52
+ */
53
+ export declare function isLoopbackOrigin(origin: string | undefined): boolean;
54
+ /** True for the exact loopback bind addresses. */
55
+ export declare function isLoopbackHost(host: string): boolean;
56
+ /** True for any-interface wildcard binds, whose externally-used Host is unknowable. */
57
+ export declare function isWildcardHost(host: string): boolean;
58
+ /**
59
+ * Computes the SDK DNS-rebinding `allowedHosts` list (a Host-header allowlist) for a
60
+ * bind host/port, or `undefined` when protection should stay off.
61
+ *
62
+ * Wildcard binds (`0.0.0.0` / `::`) return `undefined` — the Host a client
63
+ * legitimately uses is unknowable, so the bearer token (required for non-loopback
64
+ * binds) is the control. Loopback binds allow all three loopback host forms
65
+ * (bare + `:port`); a specific host (e.g. `192.168.1.50`) allows that host
66
+ * (bare + `:port`), which is knowable and a free defence-in-depth win.
67
+ */
68
+ export declare function computeAllowedHosts(host: string, port: number): string[] | undefined;
69
+ /**
70
+ * Resolves the MCP HTTP bearer token from the `--auth-token` flag or the
71
+ * `GITNEXUS_MCP_AUTH_TOKEN` env var (the flag wins). An empty or whitespace-only
72
+ * value is treated as "no token" so a blank env var cannot silently disable auth
73
+ * (and slip past the non-loopback hard-fail).
74
+ */
75
+ export declare function resolveAuthToken(optToken: string | undefined, env: NodeJS.ProcessEnv): string | undefined;
76
+ /**
77
+ * Starts a periodic sweep that closes and evicts sessions idle longer than
78
+ * `ttlMs`, returning the (unref'd) timer. Shared by both transport factories to
79
+ * guard against network drops where the per-session onclose never fires.
80
+ */
81
+ export declare function startIdleSweep<T extends {
82
+ server: Server;
83
+ lastActivity: number;
84
+ }>(sessions: Map<string, T>, ttlMs: number, intervalMs: number): NodeJS.Timeout;
85
+ /**
86
+ * Creates a reusable StreamableHTTP request handler.
87
+ *
88
+ * Encapsulates the session map and request-dispatch logic as an independent
89
+ * factory, reused by both startMcpHttpServer (POST /mcp) and the web-UI server
90
+ * route mount in server/mcp-http.ts (/api/mcp).
91
+ */
92
+ export declare function createStreamableHttpHandler(backend: LocalBackend, opts?: {
93
+ createServer?: () => Server;
94
+ host?: string;
95
+ port?: number;
96
+ }): {
97
+ handler: (req: Request, res: Response) => Promise<void>;
98
+ cleanup: () => Promise<void>;
99
+ };
100
+ /**
101
+ * Creates legacy SSE transport handlers.
102
+ *
103
+ * GET /sse (or custom path) establishes the SSE stream;
104
+ * POST /messages (or custom path) receives client JSON-RPC messages.
105
+ *
106
+ * Includes the same idle-TTL eviction as createStreamableHttpHandler to prevent
107
+ * memory leaks when clients drop without closing the SSE connection cleanly.
108
+ *
109
+ * @param backend LocalBackend instance
110
+ * @param messagesPath Path clients POST messages to (default: '/messages')
111
+ */
112
+ export declare function createSseHandlers(backend: LocalBackend, messagesPath?: string, opts?: {
113
+ maxSessions?: number;
114
+ host?: string;
115
+ port?: number;
116
+ }): {
117
+ sseHandler: (req: Request, res: Response) => Promise<void>;
118
+ messageHandler: (req: Request, res: Response) => Promise<void>;
119
+ cleanup: () => Promise<void>;
120
+ };
121
+ /**
122
+ * Creates and starts the dedicated MCP HTTP server.
123
+ *
124
+ * Mounts the following routes:
125
+ * - GET /health — health check (no auth required; for orchestrators/probes)
126
+ * - POST /mcp — Streamable HTTP (modern clients)
127
+ * - GET /sse — legacy SSE stream (old clients)
128
+ * - POST /messages — legacy SSE message endpoint
129
+ *
130
+ * @param backend LocalBackend instance
131
+ * @param options Server configuration
132
+ * @returns The listening http.Server
133
+ */
134
+ export declare function startMcpHttpServer(backend: LocalBackend, options: McpHttpOptions): Promise<HttpServer>;