gitnexus 1.6.13-rc.75 → 1.6.13-rc.77

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
@@ -36,6 +36,8 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
36
36
 
37
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
+ For Claude Code, Cursor, and Codex, setup keeps an existing URL-based `gitnexus` MCP entry and its authentication settings. Skills are still installed; setup installs Claude Code and Codex hooks, while Cursor hooks require [manual installation](../gitnexus-cursor-integration/README.md#hook-install).
40
+
39
41
  ### Editor Support
40
42
 
41
43
  | Editor | MCP | Skills | Hooks (auto-augment) | Support |
@@ -398,7 +400,7 @@ An operation must name a protocol, either through its own `bindings` or through
398
400
 
399
401
  Like Actuator snapshots, documents are external to git freshness — replacing one moves no commit and dirties no file — so an enabled run always rebuilds, and the first later run without the option rebuilds once to remove document-derived evidence. There is no glob-based auto-discovery, and the option is unsupported with `--watch`.
400
402
 
401
- > **`gitnexus uninstall`** reverses `gitnexus setup` — it removes the GitNexus MCP entries, hooks, and skill directories it added to each detected editor. Skill directories are identified **by bundled gitnexus skill name** (e.g. `gitnexus-cli/`), so if you customized files inside an installed skill directory, back them up first. It is a dry-run preview by default and prints the exact paths it would remove; pass `--force` to apply. Per-repo indexes (`gitnexus clean --all`) and the global npm package (`npm uninstall -g gitnexus`) are left for you to remove.
403
+ > **`gitnexus uninstall`** removes the named GitNexus MCP entries, hooks, and skill directories from detected editors. This includes a URL-based MCP entry you configured yourself, even if setup preserved it. Skill directories are identified **by bundled gitnexus skill name** (e.g. `gitnexus-cli/`), so if you customized files inside an installed skill directory, back them up first. It is a dry-run preview by default and prints the exact paths it would remove; pass `--force` to apply. Per-repo indexes (`gitnexus clean --all`) and the global npm package (`npm uninstall -g gitnexus`) are left for you to remove.
402
404
 
403
405
  ## Remote Embeddings
404
406
 
@@ -42,7 +42,7 @@ export interface McpJsoncTarget {
42
42
  export interface CodexMcpTarget {
43
43
  id: 'codex';
44
44
  label: string;
45
- /** Absolute path to ~/.codex/config.toml. */
45
+ /** Absolute path to CODEX_HOME/config.toml (default ~/.codex/config.toml). */
46
46
  configFile: string;
47
47
  /** The TOML table header (without brackets) setup writes / uninstall strips. */
48
48
  tomlSection: string;
@@ -24,6 +24,9 @@ import path from 'path';
24
24
  * `process.env.HOME` before invoking sees the right locations.
25
25
  */
26
26
  export function getEditorTargets(home = os.homedir()) {
27
+ const codexHome = process.env.CODEX_HOME
28
+ ? path.resolve(process.env.CODEX_HOME)
29
+ : path.join(home, '.codex');
27
30
  const mcpJsonc = [
28
31
  {
29
32
  id: 'cursor',
@@ -89,7 +92,7 @@ export function getEditorTargets(home = os.homedir()) {
89
92
  const codex = {
90
93
  id: 'codex',
91
94
  label: 'Codex',
92
- configFile: path.join(home, '.codex', 'config.toml'),
95
+ configFile: path.join(codexHome, 'config.toml'),
93
96
  tomlSection: 'mcp_servers.gitnexus',
94
97
  };
95
98
  const skills = [
@@ -126,10 +129,10 @@ export function getEditorTargets(home = os.homedir()) {
126
129
  // Codex hooks use Claude Code's exact {hooks: {Event: [...]}} JSON shape
127
130
  // and hookSpecificOutput response contract, in a dedicated hooks.json
128
131
  // (https://developers.openai.com/codex/hooks).
129
- settingsFile: path.join(home, '.codex', 'hooks.json'),
132
+ settingsFile: path.join(codexHome, 'hooks.json'),
130
133
  events: ['PreToolUse', 'PostToolUse'],
131
134
  needle: 'gitnexus-hook',
132
- scriptDir: path.join(home, '.codex', 'hooks', 'gitnexus'),
135
+ scriptDir: path.join(codexHome, 'hooks', 'gitnexus'),
133
136
  },
134
137
  {
135
138
  id: 'antigravity',
package/dist/cli/setup.js CHANGED
@@ -11,7 +11,8 @@ import os from 'os';
11
11
  import { execFile, execFileSync } from 'child_process';
12
12
  import { promisify } from 'util';
13
13
  import { fileURLToPath } from 'url';
14
- import { parseTree, modify, applyEdits, parse as parseJsonc } from 'jsonc-parser';
14
+ import { parseTree, findNodeAtLocation, getNodeValue, modify, applyEdits, parse as parseJsonc, } from 'jsonc-parser';
15
+ import { parse as parseToml, TomlError } from 'smol-toml';
15
16
  import { packageVersion } from '../core/package-version.js';
16
17
  import { getGlobalDir } from '../storage/repo-manager.js';
17
18
  import { getEditorTargets, mcpTarget, skillTarget, hookTarget, detectIndentation, isEnoent, } from './editor-targets.js';
@@ -172,7 +173,7 @@ function getOpenCodeMcpEntry() {
172
173
  * Merge a key/value pair into a JSONC config file, preserving comments and formatting.
173
174
  * If the file is genuinely corrupt (not valid JSONC), leaves it untouched.
174
175
  */
175
- async function mergeJsoncFile(filePath, keyPath, value) {
176
+ async function mergeJsoncFile(filePath, keyPath, value, options) {
176
177
  let raw;
177
178
  try {
178
179
  raw = await fs.readFile(filePath, 'utf-8');
@@ -197,6 +198,13 @@ async function mergeJsoncFile(filePath, keyPath, value) {
197
198
  const parseErrors = [];
198
199
  const tree = parseTree(raw, parseErrors);
199
200
  if (tree && tree.type === 'object' && parseErrors.length === 0) {
201
+ const existingNode = options?.preserveHttpEntry ? findNodeAtLocation(tree, keyPath) : undefined;
202
+ const existingEntry = existingNode === undefined ? undefined : getNodeValue(existingNode);
203
+ if (existingEntry !== null &&
204
+ typeof existingEntry === 'object' &&
205
+ typeof existingEntry.url === 'string') {
206
+ return 'preserved-http';
207
+ }
200
208
  const formattingOptions = detectIndentation(raw);
201
209
  const edits = modify(raw, keyPath, value, { formattingOptions });
202
210
  const result = applyEdits(raw, edits);
@@ -255,8 +263,13 @@ async function setupCursor(result) {
255
263
  }
256
264
  const { file: mcpPath, keyPath } = mcpTarget('cursor');
257
265
  try {
258
- const ok = await mergeJsoncFile(mcpPath, keyPath, getMcpEntry());
259
- if (ok) {
266
+ const ok = await mergeJsoncFile(mcpPath, keyPath, getMcpEntry(), {
267
+ preserveHttpEntry: true,
268
+ });
269
+ if (ok === 'preserved-http') {
270
+ result.configured.push('Cursor (existing HTTP MCP entry kept)');
271
+ }
272
+ else if (ok) {
260
273
  result.configured.push('Cursor');
261
274
  }
262
275
  else {
@@ -276,8 +289,13 @@ async function setupClaudeCode(result) {
276
289
  // Claude Code stores MCP config in ~/.claude.json
277
290
  const { file: mcpPath, keyPath } = mcpTarget('claude');
278
291
  try {
279
- const ok = await mergeJsoncFile(mcpPath, keyPath, getMcpEntry());
280
- if (ok) {
292
+ const ok = await mergeJsoncFile(mcpPath, keyPath, getMcpEntry(), {
293
+ preserveHttpEntry: true,
294
+ });
295
+ if (ok === 'preserved-http') {
296
+ result.configured.push('Claude Code (existing HTTP MCP entry kept)');
297
+ }
298
+ else if (ok) {
281
299
  result.configured.push('Claude Code');
282
300
  }
283
301
  else {
@@ -907,12 +925,44 @@ async function upsertCodexConfigToml(configPath) {
907
925
  await fs.mkdir(path.dirname(configPath), { recursive: true });
908
926
  await fs.writeFile(configPath, `${nextContent.trimEnd()}\n`, 'utf-8');
909
927
  }
928
+ async function codexHasHttpMcpEntry(configPath) {
929
+ let raw;
930
+ try {
931
+ raw = await fs.readFile(configPath, 'utf-8');
932
+ }
933
+ catch (err) {
934
+ if (isEnoent(err))
935
+ return false;
936
+ throw err;
937
+ }
938
+ const config = parseToml(raw);
939
+ const servers = config.mcp_servers;
940
+ if (servers === null || typeof servers !== 'object')
941
+ return false;
942
+ const entry = servers.gitnexus;
943
+ return (entry !== null &&
944
+ typeof entry === 'object' &&
945
+ typeof entry.url === 'string');
946
+ }
910
947
  async function setupCodex(result) {
911
- const codexDir = path.join(os.homedir(), '.codex');
912
- if (!(await dirExists(codexDir))) {
948
+ const configPath = getEditorTargets().codex.configFile;
949
+ const codexDir = path.dirname(configPath);
950
+ if (!process.env.CODEX_HOME && !(await dirExists(codexDir))) {
913
951
  result.skipped.push('Codex (not installed)');
914
952
  return;
915
953
  }
954
+ try {
955
+ if (await codexHasHttpMcpEntry(configPath)) {
956
+ result.configured.push('Codex (existing HTTP MCP entry kept)');
957
+ return;
958
+ }
959
+ }
960
+ catch (err) {
961
+ result.errors.push(err instanceof TomlError
962
+ ? `Codex: invalid config.toml (line ${err.line}, column ${err.column}); MCP entry unchanged`
963
+ : `Codex: ${err instanceof Error ? err.message : String(err)}`);
964
+ return;
965
+ }
916
966
  try {
917
967
  const entry = getMcpEntry();
918
968
  await execFileAsync('codex', ['mcp', 'add', 'gitnexus', '--', entry.command, ...entry.args], {
@@ -926,9 +976,8 @@ async function setupCodex(result) {
926
976
  // Fallback for environments where `codex` binary isn't on PATH.
927
977
  }
928
978
  try {
929
- const configPath = getEditorTargets().codex.configFile;
930
979
  await upsertCodexConfigToml(configPath);
931
- result.configured.push('Codex (MCP added to ~/.codex/config.toml)');
980
+ result.configured.push('Codex (MCP added to active config.toml)');
932
981
  }
933
982
  catch (err) {
934
983
  result.errors.push(`Codex: ${err.message}`);
@@ -1110,7 +1159,7 @@ async function installOpenCodeSkills(result) {
1110
1159
  * Install global Codex skills to ~/.agents/skills/gitnexus/
1111
1160
  */
1112
1161
  async function installCodexSkills(result) {
1113
- const codexDir = path.join(os.homedir(), '.codex');
1162
+ const codexDir = path.dirname(getEditorTargets().codex.configFile);
1114
1163
  if (!(await dirExists(codexDir)))
1115
1164
  return;
1116
1165
  const skillsDir = skillTarget('codex').dir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.13-rc.75",
3
+ "version": "1.6.13-rc.77",
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",
@@ -52,6 +52,7 @@
52
52
  "serve": "tsx src/cli/index.ts serve",
53
53
  "dev": "tsx watch src/cli/index.ts",
54
54
  "test": "vitest run",
55
+ "typecheck:tests": "tsc --noEmit -p tsconfig.test.json",
55
56
  "test:unit": "vitest run test/unit",
56
57
  "pretest:integration": "node scripts/build.js",
57
58
  "test:integration": "vitest run test/integration",