@veewo/gitnexus 1.3.4 → 1.3.6

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Graph-powered code intelligence for AI agents.** Index any codebase into a knowledge graph, then query it via MCP or CLI.
4
4
 
5
- Works with **Cursor**, **Claude Code**, **Windsurf**, **Cline**, **OpenCode**, and any MCP-compatible tool.
5
+ Works with **Cursor**, **Claude Code**, **Codex**, **Windsurf**, **Cline**, **OpenCode**, and any MCP-compatible tool.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/gitnexus.svg)](https://www.npmjs.com/package/gitnexus)
8
8
  [![License: PolyForm Noncommercial](https://img.shields.io/badge/License-PolyForm%20Noncommercial-blue.svg)](https://polyformproject.org/licenses/noncommercial/1.0.0/)
@@ -22,11 +22,13 @@ AI coding tools don't understand your codebase structure. They edit a function w
22
22
  npx gitnexus analyze
23
23
  ```
24
24
 
25
- That's it. This indexes the codebase, installs agent skills, registers Claude Code hooks, and creates `AGENTS.md` / `CLAUDE.md` context files all in one command.
25
+ That's it. This indexes the codebase, updates `AGENTS.md` / `CLAUDE.md` context files, and (when using project scope) installs repo-local agent skills.
26
26
 
27
- To configure MCP for your editor, run `npx gitnexus setup` once or set it up manually below.
27
+ To configure MCP + skills, run `npx gitnexus setup` once (default global mode), or use `npx gitnexus setup --scope project` for project-local mode.
28
28
 
29
- `gitnexus setup` auto-detects your editors and writes the correct global MCP config. You only need to run it once.
29
+ `gitnexus setup` supports two scopes:
30
+ - `global` (default): configures global editor MCP + installs global skills
31
+ - `project`: writes repo-local `.mcp.json` + installs repo-local skills
30
32
 
31
33
  ## Team Deployment and Distribution
32
34
 
@@ -43,6 +45,7 @@ Key links:
43
45
  |--------|-----|--------|---------------------|---------|
44
46
  | **Claude Code** | Yes | Yes | Yes (PreToolUse) | **Full** |
45
47
  | **Cursor** | Yes | Yes | — | MCP + Skills |
48
+ | **Codex** | Yes | Yes | — | MCP + Skills |
46
49
  | **Windsurf** | Yes | — | — | MCP |
47
50
  | **OpenCode** | Yes | Yes | — | MCP + Skills |
48
51
 
@@ -94,6 +97,12 @@ Add to `~/.config/opencode/config.json`:
94
97
  }
95
98
  ```
96
99
 
100
+ ### Codex
101
+
102
+ ```bash
103
+ codex mcp add gitnexus -- npx -y gitnexus@latest mcp
104
+ ```
105
+
97
106
  ## How It Works
98
107
 
99
108
  GitNexus builds a complete knowledge graph of your codebase through a multi-phase indexing pipeline:
@@ -145,7 +154,8 @@ Your AI agent gets these tools automatically:
145
154
  ## CLI Commands
146
155
 
147
156
  ```bash
148
- gitnexus setup # Configure MCP for your editors (one-time)
157
+ gitnexus setup # Default: global MCP + global skills
158
+ gitnexus setup --scope project # Project-local MCP + project-local skills
149
159
  gitnexus analyze [path] # Index a repository (or update stale index)
150
160
  gitnexus analyze --force # Force full re-index
151
161
  gitnexus analyze --embeddings # Enable semantic embeddings (off by default)
@@ -205,9 +215,10 @@ GitNexus ships with skill files that teach AI agents how to use the tools effect
205
215
 
206
216
  Installation rules:
207
217
 
208
- - `gitnexus analyze` installs repo-local skills to `.agents/skills/gitnexus/` and updates `AGENTS.md` / `CLAUDE.md`.
209
- - `gitnexus setup` installs global skills to `~/.agents/skills/gitnexus/`.
210
- - If needed, create editor-specific symlinks yourself (for example map `.claude/skills/gitnexus` to `~/.agents/skills/gitnexus`).
218
+ - `gitnexus setup` controls skill scope:
219
+ - default `global`: installs to `~/.agents/skills/gitnexus/`
220
+ - `--scope project`: installs to `.agents/skills/gitnexus/` in current repo
221
+ - `gitnexus analyze` always updates `AGENTS.md` / `CLAUDE.md`; skill install follows configured setup scope.
211
222
 
212
223
  ## Requirements
213
224
 
@@ -2,9 +2,10 @@
2
2
  * AI Context Generator
3
3
  *
4
4
  * Creates AGENTS.md and CLAUDE.md with full inline GitNexus context.
5
- * AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Cline, etc.
5
+ * AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, etc.
6
6
  * CLAUDE.md is for Claude Code which only reads that file.
7
7
  */
8
+ type SkillScope = 'project' | 'global';
8
9
  interface RepoStats {
9
10
  files?: number;
10
11
  nodes?: number;
@@ -16,7 +17,9 @@ interface RepoStats {
16
17
  /**
17
18
  * Generate AI context files after indexing
18
19
  */
19
- export declare function generateAIContextFiles(repoPath: string, _storagePath: string, projectName: string, stats: RepoStats): Promise<{
20
+ export declare function generateAIContextFiles(repoPath: string, _storagePath: string, projectName: string, stats: RepoStats, options?: {
21
+ skillScope?: SkillScope;
22
+ }): Promise<{
20
23
  files: string[];
21
24
  }>;
22
25
  export {};
@@ -2,7 +2,7 @@
2
2
  * AI Context Generator
3
3
  *
4
4
  * Creates AGENTS.md and CLAUDE.md with full inline GitNexus context.
5
- * AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Cline, etc.
5
+ * AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, etc.
6
6
  * CLAUDE.md is for Claude Code which only reads that file.
7
7
  */
8
8
  import fs from 'fs/promises';
@@ -23,7 +23,10 @@ const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';
23
23
  * - One-line quick start (read context resource) gives agents an entry point
24
24
  * - Tools/Resources sections are labeled "Reference" — agents treat them as lookup, not workflow
25
25
  */
26
- function generateGitNexusContent(projectName, stats) {
26
+ function generateGitNexusContent(projectName, stats, skillScope) {
27
+ const skillRoot = skillScope === 'global'
28
+ ? '~/.agents/skills/gitnexus'
29
+ : '.agents/skills/gitnexus';
27
30
  return `${GITNEXUS_START_MARKER}
28
31
  # GitNexus MCP
29
32
 
@@ -41,12 +44,12 @@ This project is indexed by GitNexus as **${projectName}** (${stats.nodes || 0} s
41
44
 
42
45
  | Task | Read this skill file |
43
46
  |------|---------------------|
44
- | Understand architecture / "How does X work?" | \`.agents/skills/gitnexus/gitnexus-exploring/SKILL.md\` |
45
- | Blast radius / "What breaks if I change X?" | \`.agents/skills/gitnexus/gitnexus-impact-analysis/SKILL.md\` |
46
- | Trace bugs / "Why is X failing?" | \`.agents/skills/gitnexus/gitnexus-debugging/SKILL.md\` |
47
- | Rename / extract / split / refactor | \`.agents/skills/gitnexus/gitnexus-refactoring/SKILL.md\` |
48
- | Tools, resources, schema reference | \`.agents/skills/gitnexus/gitnexus-guide/SKILL.md\` |
49
- | Index, status, clean, wiki CLI commands | \`.agents/skills/gitnexus/gitnexus-cli/SKILL.md\` |
47
+ | Understand architecture / "How does X work?" | \`${skillRoot}/gitnexus-exploring/SKILL.md\` |
48
+ | Blast radius / "What breaks if I change X?" | \`${skillRoot}/gitnexus-impact-analysis/SKILL.md\` |
49
+ | Trace bugs / "Why is X failing?" | \`${skillRoot}/gitnexus-debugging/SKILL.md\` |
50
+ | Rename / extract / split / refactor | \`${skillRoot}/gitnexus-refactoring/SKILL.md\` |
51
+ | Tools, resources, schema reference | \`${skillRoot}/gitnexus-guide/SKILL.md\` |
52
+ | Index, status, clean, wiki CLI commands | \`${skillRoot}/gitnexus-cli/SKILL.md\` |
50
53
 
51
54
  ${GITNEXUS_END_MARKER}`;
52
55
  }
@@ -164,10 +167,11 @@ Use GitNexus tools to accomplish this task.
164
167
  /**
165
168
  * Generate AI context files after indexing
166
169
  */
167
- export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats) {
168
- const content = generateGitNexusContent(projectName, stats);
170
+ export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats, options) {
171
+ const skillScope = options?.skillScope === 'global' ? 'global' : 'project';
172
+ const content = generateGitNexusContent(projectName, stats, skillScope);
169
173
  const createdFiles = [];
170
- // Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Cline, etc.)
174
+ // Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Codex, Cline, etc.)
171
175
  const agentsPath = path.join(repoPath, 'AGENTS.md');
172
176
  const agentsResult = await upsertGitNexusSection(agentsPath, content);
173
177
  createdFiles.push(`AGENTS.md (${agentsResult})`);
@@ -175,10 +179,12 @@ export async function generateAIContextFiles(repoPath, _storagePath, projectName
175
179
  const claudePath = path.join(repoPath, 'CLAUDE.md');
176
180
  const claudeResult = await upsertGitNexusSection(claudePath, content);
177
181
  createdFiles.push(`CLAUDE.md (${claudeResult})`);
178
- // Install skills to .agents/skills/gitnexus/
179
- const installedSkills = await installSkills(repoPath);
180
- if (installedSkills.length > 0) {
181
- createdFiles.push(`.agents/skills/gitnexus/ (${installedSkills.length} skills)`);
182
+ // Install repo-local skills only when project scope is selected.
183
+ if (skillScope === 'project') {
184
+ const installedSkills = await installSkills(repoPath);
185
+ if (installedSkills.length > 0) {
186
+ createdFiles.push(`.agents/skills/gitnexus/ (${installedSkills.length} skills)`);
187
+ }
182
188
  }
183
189
  return { files: createdFiles };
184
190
  }
@@ -11,7 +11,7 @@ test('generateAIContextFiles installs repo skills under .agents/skills/gitnexus'
11
11
  nodes: 1,
12
12
  edges: 2,
13
13
  processes: 3,
14
- });
14
+ }, { skillScope: 'project' });
15
15
  const agentsPath = path.join(repoPath, 'AGENTS.md');
16
16
  const claudePath = path.join(repoPath, 'CLAUDE.md');
17
17
  const skillPath = path.join(repoPath, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
@@ -28,3 +28,25 @@ test('generateAIContextFiles installs repo skills under .agents/skills/gitnexus'
28
28
  await fs.rm(repoPath, { recursive: true, force: true });
29
29
  }
30
30
  });
31
+ test('generateAIContextFiles with global scope skips repo skill install', async () => {
32
+ const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-ai-context-global-'));
33
+ try {
34
+ const result = await generateAIContextFiles(repoPath, '', 'demo-repo', {
35
+ nodes: 1,
36
+ edges: 2,
37
+ processes: 3,
38
+ }, { skillScope: 'global' });
39
+ const agentsPath = path.join(repoPath, 'AGENTS.md');
40
+ const claudePath = path.join(repoPath, 'CLAUDE.md');
41
+ const localSkillsDir = path.join(repoPath, '.agents', 'skills', 'gitnexus');
42
+ const agentsContent = await fs.readFile(agentsPath, 'utf-8');
43
+ const claudeContent = await fs.readFile(claudePath, 'utf-8');
44
+ assert.match(agentsContent, /~\/\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
45
+ assert.match(claudeContent, /~\/\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
46
+ assert.ok(!result.files.some((entry) => entry.includes('.agents/skills/gitnexus/')), 'did not expect repo-local skills in generated file summary');
47
+ await assert.rejects(fs.access(localSkillsDir));
48
+ }
49
+ finally {
50
+ await fs.rm(repoPath, { recursive: true, force: true });
51
+ }
52
+ });
@@ -13,7 +13,7 @@ import { initKuzu, loadGraphToKuzu, getKuzuStats, executeQuery, executeWithReuse
13
13
  // loaded when embeddings are not requested. This avoids crashes on Node
14
14
  // versions whose ABI is not yet supported by the native binary (#89).
15
15
  // disposeEmbedder intentionally not called — ONNX Runtime segfaults on cleanup (see #38)
16
- import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath } from '../storage/repo-manager.js';
16
+ import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath, loadCLIConfig } from '../storage/repo-manager.js';
17
17
  import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
18
18
  import { generateAIContextFiles } from './ai-context.js';
19
19
  import fs from 'fs/promises';
@@ -312,6 +312,8 @@ export const analyzeCommand = async (inputPath, options) => {
312
312
  communities: pipelineResult.communityResult?.stats.totalCommunities,
313
313
  clusters: aggregatedClusterCount,
314
314
  processes: pipelineResult.processResult?.stats.totalProcesses,
315
+ }, {
316
+ skillScope: ((await loadCLIConfig()).setupScope === 'global') ? 'global' : 'project',
315
317
  });
316
318
  await closeKuzu();
317
319
  // Note: we intentionally do NOT call disposeEmbedder() here.
package/dist/cli/index.js CHANGED
@@ -59,7 +59,8 @@ program
59
59
  .version(resolveCliVersion());
60
60
  program
61
61
  .command('setup')
62
- .description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode')
62
+ .description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode, Codex')
63
+ .option('--scope <scope>', 'Install target: global (default) or project')
63
64
  .action(setupCommand);
64
65
  program
65
66
  .command('analyze [path]')
@@ -5,4 +5,8 @@
5
5
  * Detects installed AI editors and writes the appropriate MCP config
6
6
  * so the GitNexus MCP server is available in all projects.
7
7
  */
8
- export declare const setupCommand: () => Promise<void>;
8
+ interface SetupOptions {
9
+ scope?: string;
10
+ }
11
+ export declare const setupCommand: (options?: SetupOptions) => Promise<void>;
12
+ export {};
package/dist/cli/setup.js CHANGED
@@ -8,10 +8,21 @@
8
8
  import fs from 'fs/promises';
9
9
  import path from 'path';
10
10
  import os from 'os';
11
+ import { execFile } from 'node:child_process';
12
+ import { promisify } from 'node:util';
11
13
  import { fileURLToPath } from 'url';
12
- import { getGlobalDir } from '../storage/repo-manager.js';
14
+ import { getGlobalDir, loadCLIConfig, saveCLIConfig } from '../storage/repo-manager.js';
15
+ import { getGitRoot } from '../storage/git.js';
13
16
  const __filename = fileURLToPath(import.meta.url);
14
17
  const __dirname = path.dirname(__filename);
18
+ const execFileAsync = promisify(execFile);
19
+ function resolveSetupScope(rawScope) {
20
+ if (!rawScope || rawScope.trim() === '')
21
+ return 'global';
22
+ if (rawScope === 'global' || rawScope === 'project')
23
+ return rawScope;
24
+ throw new Error(`Invalid --scope value "${rawScope}". Use "global" or "project".`);
25
+ }
15
26
  /**
16
27
  * The MCP server entry for all editors.
17
28
  * On Windows, npx must be invoked via cmd /c since it's a .cmd script.
@@ -122,6 +133,18 @@ async function installGlobalAgentSkills(result) {
122
133
  result.errors.push(`Global agent skills: ${err.message}`);
123
134
  }
124
135
  }
136
+ async function installProjectAgentSkills(repoRoot, result) {
137
+ const skillsDir = path.join(repoRoot, '.agents', 'skills', 'gitnexus');
138
+ try {
139
+ const installed = await installSkillsTo(skillsDir);
140
+ if (installed.length > 0) {
141
+ result.configured.push(`Project agent skills (${installed.length} skills → ${path.relative(repoRoot, skillsDir)}/)`);
142
+ }
143
+ }
144
+ catch (err) {
145
+ result.errors.push(`Project agent skills: ${err.message}`);
146
+ }
147
+ }
125
148
  /**
126
149
  * Install GitNexus hooks to ~/.claude/settings.json for Claude Code.
127
150
  * Merges hook config without overwriting existing hooks.
@@ -195,6 +218,42 @@ async function setupOpenCode(result) {
195
218
  result.errors.push(`OpenCode: ${err.message}`);
196
219
  }
197
220
  }
221
+ async function setupCodex(result) {
222
+ const entry = getMcpEntry();
223
+ try {
224
+ await execFileAsync('codex', ['mcp', 'add', 'gitnexus', '--', entry.command, ...entry.args], { timeout: 15000 });
225
+ result.configured.push('Codex');
226
+ }
227
+ catch (err) {
228
+ if (err?.code === 'ENOENT') {
229
+ result.skipped.push('Codex (not installed)');
230
+ return;
231
+ }
232
+ result.errors.push(`Codex: ${err.message}`);
233
+ }
234
+ }
235
+ async function setupProjectMcp(repoRoot, result) {
236
+ const mcpPath = path.join(repoRoot, '.mcp.json');
237
+ try {
238
+ const existing = await readJsonFile(mcpPath);
239
+ const updated = mergeMcpConfig(existing);
240
+ await writeJsonFile(mcpPath, updated);
241
+ result.configured.push(`Project MCP (${path.relative(repoRoot, mcpPath)})`);
242
+ }
243
+ catch (err) {
244
+ result.errors.push(`Project MCP: ${err.message}`);
245
+ }
246
+ }
247
+ async function saveSetupScope(scope, result) {
248
+ try {
249
+ const existing = await loadCLIConfig();
250
+ await saveCLIConfig({ ...existing, setupScope: scope });
251
+ result.configured.push(`Default setup scope (${scope})`);
252
+ }
253
+ catch (err) {
254
+ result.errors.push(`Persist setup scope: ${err.message}`);
255
+ }
256
+ }
198
257
  // ─── Skill Installation ───────────────────────────────────────────
199
258
  const SKILL_NAMES = ['gitnexus-exploring', 'gitnexus-debugging', 'gitnexus-impact-analysis', 'gitnexus-refactoring', 'gitnexus-guide', 'gitnexus-cli'];
200
259
  /**
@@ -256,11 +315,20 @@ async function copyDirRecursive(src, dest) {
256
315
  }
257
316
  }
258
317
  // ─── Main command ──────────────────────────────────────────────────
259
- export const setupCommand = async () => {
318
+ export const setupCommand = async (options = {}) => {
260
319
  console.log('');
261
320
  console.log(' GitNexus Setup');
262
321
  console.log(' ==============');
263
322
  console.log('');
323
+ let scope;
324
+ try {
325
+ scope = resolveSetupScope(options.scope);
326
+ }
327
+ catch (err) {
328
+ console.log(` ${err?.message || String(err)}\n`);
329
+ process.exitCode = 1;
330
+ return;
331
+ }
264
332
  // Ensure global directory exists
265
333
  const globalDir = getGlobalDir();
266
334
  await fs.mkdir(globalDir, { recursive: true });
@@ -269,14 +337,28 @@ export const setupCommand = async () => {
269
337
  skipped: [],
270
338
  errors: [],
271
339
  };
272
- // Detect and configure each editor's MCP
273
- await setupCursor(result);
274
- await setupClaudeCode(result);
275
- await setupOpenCode(result);
276
- // Install shared global skills once
277
- await installGlobalAgentSkills(result);
278
- // Optional Claude-specific hooks
279
- await installClaudeCodeHooks(result);
340
+ if (scope === 'global') {
341
+ // Detect and configure each editor's MCP
342
+ await setupCursor(result);
343
+ await setupClaudeCode(result);
344
+ await setupOpenCode(result);
345
+ await setupCodex(result);
346
+ // Install shared global skills once
347
+ await installGlobalAgentSkills(result);
348
+ // Optional Claude-specific hooks
349
+ await installClaudeCodeHooks(result);
350
+ }
351
+ else {
352
+ const repoRoot = getGitRoot(process.cwd());
353
+ if (!repoRoot) {
354
+ console.log(' --scope project requires running inside a git repository\n');
355
+ process.exitCode = 1;
356
+ return;
357
+ }
358
+ await setupProjectMcp(repoRoot, result);
359
+ await installProjectAgentSkills(repoRoot, result);
360
+ }
361
+ await saveSetupScope(scope, result);
280
362
  // Print results
281
363
  if (result.configured.length > 0) {
282
364
  console.log(' Configured:');
@@ -300,6 +382,7 @@ export const setupCommand = async () => {
300
382
  }
301
383
  console.log('');
302
384
  console.log(' Summary:');
385
+ console.log(` Scope: ${scope}`);
303
386
  console.log(` MCP configured for: ${result.configured.filter(c => !c.includes('skills')).join(', ') || 'none'}`);
304
387
  console.log(` Skills installed to: ${result.configured.filter(c => c.includes('skills')).length > 0 ? result.configured.filter(c => c.includes('skills')).join(', ') : 'none'}`);
305
388
  console.log('');
@@ -7,11 +7,11 @@ import { execFile } from 'node:child_process';
7
7
  import { promisify } from 'node:util';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  const execFileAsync = promisify(execFile);
10
+ const here = path.dirname(fileURLToPath(import.meta.url));
11
+ const packageRoot = path.resolve(here, '..', '..');
12
+ const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
10
13
  test('setup installs global skills under ~/.agents/skills/gitnexus', async () => {
11
14
  const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
12
- const here = path.dirname(fileURLToPath(import.meta.url));
13
- const packageRoot = path.resolve(here, '..', '..');
14
- const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
15
15
  try {
16
16
  await execFileAsync(process.execPath, [cliPath, 'setup'], {
17
17
  cwd: packageRoot,
@@ -22,10 +22,103 @@ test('setup installs global skills under ~/.agents/skills/gitnexus', async () =>
22
22
  },
23
23
  });
24
24
  const skillPath = path.join(fakeHome, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
25
+ const configPath = path.join(fakeHome, '.gitnexus', 'config.json');
25
26
  await fs.access(skillPath);
27
+ const configRaw = await fs.readFile(configPath, 'utf-8');
28
+ const config = JSON.parse(configRaw);
29
+ assert.equal(config.setupScope, 'global');
26
30
  assert.ok(true);
27
31
  }
28
32
  finally {
29
33
  await fs.rm(fakeHome, { recursive: true, force: true });
30
34
  }
31
35
  });
36
+ test('setup configures Codex MCP when codex CLI is available', async () => {
37
+ const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
38
+ const fakeBin = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-bin-'));
39
+ const codexShimPath = path.join(fakeBin, process.platform === 'win32' ? 'codex.cmd' : 'codex');
40
+ const shimLogic = `
41
+ const fs = require('node:fs');
42
+ const path = require('node:path');
43
+ const args = process.argv.slice(2);
44
+ if (args[0] === 'mcp' && args[1] === 'add' && args[2] === 'gitnexus') {
45
+ const home = process.env.HOME || process.env.USERPROFILE;
46
+ const outputPath = path.join(home, '.codex', 'gitnexus-mcp-add.json');
47
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
48
+ fs.writeFileSync(outputPath, JSON.stringify({ args }, null, 2));
49
+ process.exit(0);
50
+ }
51
+ if (args[0] === '--version') {
52
+ process.stdout.write('codex-shim 0.0.0\\n');
53
+ process.exit(0);
54
+ }
55
+ process.exit(0);
56
+ `;
57
+ try {
58
+ if (process.platform === 'win32') {
59
+ const runnerPath = path.join(fakeBin, 'codex-shim.cjs');
60
+ await fs.writeFile(runnerPath, shimLogic, 'utf-8');
61
+ await fs.writeFile(codexShimPath, `@echo off\r\nnode "${runnerPath}" %*\r\n`, 'utf-8');
62
+ }
63
+ else {
64
+ await fs.writeFile(codexShimPath, `#!/usr/bin/env node\n${shimLogic}`, { mode: 0o755 });
65
+ }
66
+ await execFileAsync(process.execPath, [cliPath, 'setup'], {
67
+ cwd: packageRoot,
68
+ env: {
69
+ ...process.env,
70
+ HOME: fakeHome,
71
+ USERPROFILE: fakeHome,
72
+ PATH: `${fakeBin}${path.delimiter}${process.env.PATH || ''}`,
73
+ },
74
+ });
75
+ const outputPath = path.join(fakeHome, '.codex', 'gitnexus-mcp-add.json');
76
+ const raw = await fs.readFile(outputPath, 'utf-8');
77
+ const parsed = JSON.parse(raw);
78
+ assert.deepEqual(parsed.args.slice(0, 4), ['mcp', 'add', 'gitnexus', '--']);
79
+ assert.ok(parsed.args.includes('gitnexus@latest'));
80
+ assert.ok(parsed.args.includes('mcp'));
81
+ }
82
+ finally {
83
+ await fs.rm(fakeHome, { recursive: true, force: true });
84
+ await fs.rm(fakeBin, { recursive: true, force: true });
85
+ }
86
+ });
87
+ test('setup with --scope project writes local MCP and repo-local skills only', async () => {
88
+ const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
89
+ const fakeRepo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-repo-'));
90
+ try {
91
+ await execFileAsync('git', ['init'], {
92
+ cwd: fakeRepo,
93
+ env: {
94
+ ...process.env,
95
+ HOME: fakeHome,
96
+ USERPROFILE: fakeHome,
97
+ },
98
+ });
99
+ await execFileAsync(process.execPath, [cliPath, 'setup', '--scope', 'project'], {
100
+ cwd: fakeRepo,
101
+ env: {
102
+ ...process.env,
103
+ HOME: fakeHome,
104
+ USERPROFILE: fakeHome,
105
+ },
106
+ });
107
+ const projectMcpPath = path.join(fakeRepo, '.mcp.json');
108
+ const localSkillPath = path.join(fakeRepo, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
109
+ const globalSkillPath = path.join(fakeHome, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
110
+ const configPath = path.join(fakeHome, '.gitnexus', 'config.json');
111
+ const projectMcpRaw = await fs.readFile(projectMcpPath, 'utf-8');
112
+ const projectMcp = JSON.parse(projectMcpRaw);
113
+ const configRaw = await fs.readFile(configPath, 'utf-8');
114
+ const config = JSON.parse(configRaw);
115
+ assert.equal(projectMcp.mcpServers?.gitnexus?.command, 'npx');
116
+ await fs.access(localSkillPath);
117
+ await assert.rejects(fs.access(globalSkillPath));
118
+ assert.equal(config.setupScope, 'project');
119
+ }
120
+ finally {
121
+ await fs.rm(fakeHome, { recursive: true, force: true });
122
+ await fs.rm(fakeRepo, { recursive: true, force: true });
123
+ }
124
+ });
@@ -2,7 +2,7 @@
2
2
  * MCP Server (Multi-Repo)
3
3
  *
4
4
  * Model Context Protocol server that runs on stdio.
5
- * External AI tools (Cursor, Claude) spawn this process and
5
+ * External AI tools (Cursor, Claude, Codex, etc.) spawn this process and
6
6
  * communicate via stdin/stdout using the MCP protocol.
7
7
  *
8
8
  * Supports multiple indexed repositories via the global registry.
@@ -2,7 +2,7 @@
2
2
  * MCP Server (Multi-Repo)
3
3
  *
4
4
  * Model Context Protocol server that runs on stdio.
5
- * External AI tools (Cursor, Claude) spawn this process and
5
+ * External AI tools (Cursor, Claude, Codex, etc.) spawn this process and
6
6
  * communicate via stdin/stdout using the MCP protocol.
7
7
  *
8
8
  * Supports multiple indexed repositories via the global registry.
@@ -110,6 +110,7 @@ export interface CLIConfig {
110
110
  apiKey?: string;
111
111
  model?: string;
112
112
  baseUrl?: string;
113
+ setupScope?: 'global' | 'project';
113
114
  }
114
115
  /**
115
116
  * Get the path to the global CLI config file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veewo/gitnexus",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",