gitnexus 1.6.6-rc.147 → 1.6.6-rc.149

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.
@@ -156,7 +156,7 @@ export declare const en: {
156
156
  readonly 'help.option.clean.all': "Clean all indexed repos";
157
157
  readonly 'help.option.clean.lbugSidecars': "Clean quarantined LadybugDB missing-shadow WAL sidecars";
158
158
  readonly 'help.option.wiki.force': "Force full regeneration even if up to date";
159
- readonly 'help.option.wiki.provider': "LLM provider: openai, openrouter, azure, custom, cursor, claude, or codex (default: openai)";
159
+ readonly 'help.option.wiki.provider': "LLM provider: openai, openrouter, azure, custom, cursor, claude, codex, or opencode (default: openai)";
160
160
  readonly 'help.option.wiki.model': "LLM model or Azure deployment name (default: minimax/minimax-m2.5)";
161
161
  readonly 'help.option.wiki.baseUrl': "LLM API base URL. Azure v1: https://{resource}.openai.azure.com/openai/v1";
162
162
  readonly 'help.option.wiki.apiKey': "LLM API key or Azure api-key (saved to ~/.gitnexus/config.json)";
@@ -156,7 +156,7 @@ export const en = {
156
156
  'help.option.clean.all': 'Clean all indexed repos',
157
157
  'help.option.clean.lbugSidecars': 'Clean quarantined LadybugDB missing-shadow WAL sidecars',
158
158
  'help.option.wiki.force': 'Force full regeneration even if up to date',
159
- 'help.option.wiki.provider': 'LLM provider: openai, openrouter, azure, custom, cursor, claude, or codex (default: openai)',
159
+ 'help.option.wiki.provider': 'LLM provider: openai, openrouter, azure, custom, cursor, claude, codex, or opencode (default: openai)',
160
160
  'help.option.wiki.model': 'LLM model or Azure deployment name (default: minimax/minimax-m2.5)',
161
161
  'help.option.wiki.baseUrl': 'LLM API base URL. Azure v1: https://{resource}.openai.azure.com/openai/v1',
162
162
  'help.option.wiki.apiKey': 'LLM API key or Azure api-key (saved to ~/.gitnexus/config.json)',
@@ -157,7 +157,7 @@ export declare const cliResources: {
157
157
  readonly 'help.option.clean.all': "Clean all indexed repos";
158
158
  readonly 'help.option.clean.lbugSidecars': "Clean quarantined LadybugDB missing-shadow WAL sidecars";
159
159
  readonly 'help.option.wiki.force': "Force full regeneration even if up to date";
160
- readonly 'help.option.wiki.provider': "LLM provider: openai, openrouter, azure, custom, cursor, claude, or codex (default: openai)";
160
+ readonly 'help.option.wiki.provider': "LLM provider: openai, openrouter, azure, custom, cursor, claude, codex, or opencode (default: openai)";
161
161
  readonly 'help.option.wiki.model': "LLM model or Azure deployment name (default: minimax/minimax-m2.5)";
162
162
  readonly 'help.option.wiki.baseUrl': "LLM API base URL. Azure v1: https://{resource}.openai.azure.com/openai/v1";
163
163
  readonly 'help.option.wiki.apiKey': "LLM API key or Azure api-key (saved to ~/.gitnexus/config.json)";
@@ -156,7 +156,7 @@ export const zhCN = {
156
156
  'help.option.clean.all': '清理所有已索引仓库',
157
157
  'help.option.clean.lbugSidecars': '清理已隔离的 LadybugDB missing-shadow WAL sidecar',
158
158
  'help.option.wiki.force': '即使已是最新也强制完整重新生成',
159
- 'help.option.wiki.provider': 'LLM 提供商:openai、openrouter、azure、custom、cursor、claude 或 codex(默认:openai)',
159
+ 'help.option.wiki.provider': 'LLM 提供商:openai、openrouter、azure、custom、cursor、claude、codex 或 opencode(默认:openai)',
160
160
  'help.option.wiki.model': 'LLM 模型或 Azure deployment 名称(默认:minimax/minimax-m2.5)',
161
161
  'help.option.wiki.baseUrl': 'LLM API base URL。Azure v1:https://{resource}.openai.azure.com/openai/v1',
162
162
  'help.option.wiki.apiKey': 'LLM API key 或 Azure api-key(保存到 ~/.gitnexus/config.json)',
package/dist/cli/index.js CHANGED
@@ -96,7 +96,7 @@ program
96
96
  .command('wiki [path]')
97
97
  .description('Generate repository wiki from knowledge graph')
98
98
  .option('-f, --force', 'Force full regeneration even if up to date')
99
- .option('--provider <provider>', 'LLM provider: openai, openrouter, azure, custom, cursor, claude, or codex (default: openai)')
99
+ .option('--provider <provider>', 'LLM provider: openai, openrouter, azure, custom, cursor, claude, codex, or opencode (default: openai)')
100
100
  .option('--model <model>', 'LLM model or Azure deployment name (default: minimax/minimax-m2.5)')
101
101
  .option('--base-url <url>', 'LLM API base URL. Azure v1: https://{resource}.openai.azure.com/openai/v1')
102
102
  .option('--api-key <key>', 'LLM API key or Azure api-key (saved to ~/.gitnexus/config.json)')
package/dist/cli/wiki.js CHANGED
@@ -29,14 +29,21 @@ function parsePositiveIntegerOption(value, flag, multiplier = 1) {
29
29
  return parsed;
30
30
  }
31
31
  function isLocalProvider(provider) {
32
- return provider === 'cursor' || provider === 'claude' || provider === 'codex';
32
+ return (provider === 'cursor' ||
33
+ provider === 'claude' ||
34
+ provider === 'codex' ||
35
+ provider === 'opencode');
33
36
  }
34
37
  function localModelConfigKey(provider) {
35
38
  if (provider === 'cursor')
36
39
  return 'cursorModel';
37
40
  if (provider === 'claude')
38
41
  return 'claudeModel';
39
- return 'codexModel';
42
+ if (provider === 'codex')
43
+ return 'codexModel';
44
+ if (provider === 'opencode')
45
+ return 'opencodeModel';
46
+ throw new Error(`Unsupported local provider: ${provider}`);
40
47
  }
41
48
  /**
42
49
  * Prompt the user for input via stdin.
@@ -212,7 +219,7 @@ const wikiCommandImpl = async (inputPath, options) => {
212
219
  if (!llmConfig.apiKey && !isLocalProvider(llmConfig.provider)) {
213
220
  console.log(' Error: No LLM API key found.');
214
221
  console.log(' Set OPENAI_API_KEY or GITNEXUS_API_KEY environment variable,');
215
- console.log(' or pass --api-key <key>, or use --provider cursor|claude|codex.\n');
222
+ console.log(' or pass --api-key <key>, or use --provider cursor|claude|codex|opencode.\n');
216
223
  process.exitCode = 1;
217
224
  return;
218
225
  }
@@ -220,11 +227,12 @@ const wikiCommandImpl = async (inputPath, options) => {
220
227
  }
221
228
  else {
222
229
  console.log(" No LLM configured. Let's set it up.\n");
223
- console.log(' Supports OpenAI, OpenRouter, Azure, any OpenAI-compatible API, Cursor CLI, Claude CLI, or Codex CLI.\n');
230
+ console.log(' Supports OpenAI, OpenRouter, Azure, any OpenAI-compatible API, Cursor CLI, Claude CLI, Codex CLI, or OpenCode CLI.\n');
224
231
  // Check if local agent CLIs are available.
225
232
  const hasCursor = detectCursorCLI();
226
233
  const hasClaude = detectLocalCLI('claude');
227
234
  const hasCodex = detectLocalCLI('codex');
235
+ const hasOpenCode = detectLocalCLI('opencode');
228
236
  const localChoices = [];
229
237
  // Provider selection
230
238
  console.log(' [1] OpenAI (api.openai.com)');
@@ -256,6 +264,14 @@ const wikiCommandImpl = async (inputPath, options) => {
256
264
  });
257
265
  console.log(` [${choice}] Codex CLI (local, uses your Codex login)`);
258
266
  }
267
+ if (hasOpenCode) {
268
+ const choice = String(nextChoice++);
269
+ localChoices.push({
270
+ choice,
271
+ provider: 'opencode',
272
+ });
273
+ console.log(` [${choice}] OpenCode CLI (local, uses your OpenCode login/config)`);
274
+ }
259
275
  console.log('');
260
276
  const maxChoice = String(nextChoice - 1);
261
277
  const choice = await prompt(` Select provider (1/${maxChoice}): `);
@@ -133,11 +133,34 @@ const RUST_SCOPE_QUERY = `
133
133
  name: (identifier) @reference.name)) @reference.call.free
134
134
 
135
135
  ;; References — constructor calls (struct literal)
136
- ;; Covers bare names (Foo {}), scoped (foo::bar::Baz {}), and turbofish
137
- ;; (Foo::<T> {}) — the name: field resolves to the trailing identifier
138
- ;; in all cases through tree-sitter-rust's grammar.
136
+ ;; tree-sitter-rust gives struct_expression.name one of three node types
137
+ ;; (type_identifier | scoped_type_identifier | generic_type_with_turbofish);
138
+ ;; the turbofish form additionally nests either a type_identifier or a
139
+ ;; scoped_identifier. We enumerate all four shapes below so the capture is
140
+ ;; always the trailing identifier (resolved scope-aware), not the full path:
141
+ ;; bare Foo {}
142
+ ;; scoped foo::bar::Baz {}
143
+ ;; turbofish Foo::<T> {}
144
+ ;; scoped+turbofish foo::Bar::<T> {}
139
145
  (struct_expression
140
- name: (_) @reference.name) @reference.call.constructor
146
+ name: (type_identifier) @reference.name) @reference.call.constructor
147
+
148
+ ;; Scoped struct (foo::bar::Baz {})
149
+ (struct_expression
150
+ name: (scoped_type_identifier
151
+ name: (type_identifier) @reference.name)) @reference.call.constructor
152
+
153
+ ;; Turbofish struct (Foo::<T> {})
154
+ (struct_expression
155
+ name: (generic_type_with_turbofish
156
+ type: (type_identifier) @reference.name)) @reference.call.constructor
157
+
158
+ ;; Scoped + turbofish struct (foo::Bar::<T> {}) — the turbofish wraps a
159
+ ;; scoped_identifier whose tail is an identifier (not a type_identifier).
160
+ (struct_expression
161
+ name: (generic_type_with_turbofish
162
+ type: (scoped_identifier
163
+ name: (identifier) @reference.name))) @reference.call.constructor
141
164
 
142
165
  ;; References — macro invocations (disjoint namespace from functions)
143
166
  ;; Resolved via MacroRegistry → Macro defs only (never fn of the same name).
@@ -17,7 +17,7 @@ import { generateHTMLViewer } from './html-viewer.js';
17
17
  import { sanitizeMermaidMarkdown } from './mermaid-sanitizer.js';
18
18
  import { callLLM, estimateTokens, } from './llm-client.js';
19
19
  import { callCursorLLM, resolveCursorConfig } from './cursor-client.js';
20
- import { callClaudeLLM, callCodexLLM, resolveLocalCLIConfig } from './local-cli-client.js';
20
+ import { callClaudeLLM, callCodexLLM, callOpenCodeLLM, resolveLocalCLIConfig, } from './local-cli-client.js';
21
21
  import { GROUPING_SYSTEM_PROMPT, GROUPING_USER_PROMPT, MODULE_SYSTEM_PROMPT, MODULE_USER_PROMPT, PARENT_SYSTEM_PROMPT, PARENT_USER_PROMPT, OVERVIEW_SYSTEM_PROMPT, OVERVIEW_USER_PROMPT, fillTemplate, formatFileListForGrouping, formatDirectoryTree, formatCallEdges, formatProcesses, } from './prompts.js';
22
22
  import { shouldIgnorePath } from '../../config/ignore-service.js';
23
23
  // ─── Constants ────────────────────────────────────────────────────────
@@ -123,15 +123,23 @@ export class WikiGenerator {
123
123
  });
124
124
  return callCursorLLM(prompt, cursorConfig, systemPrompt, options);
125
125
  }
126
- if (this.llmConfig.provider === 'claude' || this.llmConfig.provider === 'codex') {
126
+ if (this.llmConfig.provider === 'claude' ||
127
+ this.llmConfig.provider === 'codex' ||
128
+ this.llmConfig.provider === 'opencode') {
127
129
  const localConfig = resolveLocalCLIConfig({
128
130
  model: this.llmConfig.model,
129
131
  workingDirectory: this.repoPath,
130
132
  requestTimeoutMs: this.llmConfig.requestTimeoutMs,
131
133
  });
132
- return this.llmConfig.provider === 'claude'
133
- ? callClaudeLLM(prompt, localConfig, systemPrompt, options)
134
- : callCodexLLM(prompt, localConfig, systemPrompt, options);
134
+ if (this.llmConfig.provider === 'claude') {
135
+ return callClaudeLLM(prompt, localConfig, systemPrompt, options);
136
+ }
137
+ if (this.llmConfig.provider === 'codex') {
138
+ return callCodexLLM(prompt, localConfig, systemPrompt, options);
139
+ }
140
+ if (this.llmConfig.provider === 'opencode') {
141
+ return callOpenCodeLLM(prompt, localConfig, systemPrompt, options);
142
+ }
135
143
  }
136
144
  return callLLM(prompt, this.llmConfig, systemPrompt, options);
137
145
  }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Config priority: CLI flags > env vars > defaults
8
8
  */
9
- export type LLMProvider = 'openai' | 'openrouter' | 'azure' | 'custom' | 'cursor' | 'claude' | 'codex';
9
+ export type LLMProvider = 'openai' | 'openrouter' | 'azure' | 'custom' | 'cursor' | 'claude' | 'codex' | 'opencode';
10
10
  export interface LLMConfig {
11
11
  apiKey: string;
12
12
  baseUrl: string;
@@ -16,8 +16,13 @@ export async function resolveLLMConfig(overrides) {
16
16
  ? savedConfig.claudeModel
17
17
  : savedProvider === 'codex'
18
18
  ? savedConfig.codexModel
19
- : undefined;
20
- const localProvider = savedProvider === 'cursor' || savedProvider === 'claude' || savedProvider === 'codex';
19
+ : savedProvider === 'opencode'
20
+ ? savedConfig.opencodeModel
21
+ : undefined;
22
+ const localProvider = savedProvider === 'cursor' ||
23
+ savedProvider === 'claude' ||
24
+ savedProvider === 'codex' ||
25
+ savedProvider === 'opencode';
21
26
  const apiKey = overrides?.apiKey ||
22
27
  process.env.GITNEXUS_API_KEY ||
23
28
  process.env.OPENAI_API_KEY ||
@@ -5,7 +5,7 @@
5
5
  * an OpenAI-compatible HTTP API.
6
6
  */
7
7
  import type { LLMResponse, CallLLMOptions } from './llm-client.js';
8
- export type LocalAgentProvider = 'claude' | 'codex';
8
+ export type LocalAgentProvider = 'claude' | 'codex' | 'opencode';
9
9
  export interface LocalCLIConfig {
10
10
  model?: string;
11
11
  workingDirectory?: string;
@@ -15,3 +15,4 @@ export declare function detectLocalCLI(provider: LocalAgentProvider): string | n
15
15
  export declare function resolveLocalCLIConfig(overrides?: Partial<LocalCLIConfig>): LocalCLIConfig;
16
16
  export declare function callClaudeLLM(prompt: string, config: LocalCLIConfig, systemPrompt?: string, options?: CallLLMOptions): Promise<LLMResponse>;
17
17
  export declare function callCodexLLM(prompt: string, config: LocalCLIConfig, systemPrompt?: string, options?: CallLLMOptions): Promise<LLMResponse>;
18
+ export declare function callOpenCodeLLM(prompt: string, config: LocalCLIConfig, systemPrompt?: string, options?: CallLLMOptions): Promise<LLMResponse>;
@@ -14,6 +14,7 @@ import { logger } from '../logger.js';
14
14
  const COMMANDS = {
15
15
  claude: 'claude',
16
16
  codex: 'codex',
17
+ opencode: 'opencode',
17
18
  };
18
19
  function killChildTree(child) {
19
20
  if (process.platform === 'win32' && child.pid !== undefined) {
@@ -121,6 +122,66 @@ export async function callCodexLLM(prompt, config, systemPrompt, options) {
121
122
  await fs.rm(outputDir, { recursive: true, force: true }).catch(() => undefined);
122
123
  }
123
124
  }
125
+ function parseOpenCodeEventStream(output) {
126
+ const lines = output
127
+ .split(/\r?\n/)
128
+ .map((line) => line.trim())
129
+ .filter(Boolean);
130
+ const textParts = [];
131
+ for (const line of lines) {
132
+ let event;
133
+ try {
134
+ event = JSON.parse(line);
135
+ }
136
+ catch {
137
+ continue;
138
+ }
139
+ if (event.type === 'error') {
140
+ const message = event.error?.data?.message ||
141
+ event.error?.name ||
142
+ event.message ||
143
+ event.part?.text ||
144
+ line;
145
+ throw new Error(`OpenCode CLI returned error event: ${message}`);
146
+ }
147
+ if (event.type === 'text' && typeof event.part?.text === 'string') {
148
+ textParts.push(event.part.text);
149
+ }
150
+ }
151
+ const content = textParts.join('').trim();
152
+ if (!content) {
153
+ throw new Error('OpenCode CLI returned no text output');
154
+ }
155
+ return content;
156
+ }
157
+ function buildChildEnv(provider) {
158
+ const env = {
159
+ ...process.env,
160
+ CI: '1',
161
+ };
162
+ if (provider === 'opencode') {
163
+ delete env.OPENCODE_SERVER_PASSWORD;
164
+ delete env.OPENCODE_SERVER_USERNAME;
165
+ }
166
+ return env;
167
+ }
168
+ export async function callOpenCodeLLM(prompt, config, systemPrompt, options) {
169
+ const commandInfo = getDetectedCommand('opencode');
170
+ if (!commandInfo) {
171
+ throw new Error('OpenCode CLI not found. Install OpenCode CLI and ensure `opencode` is on PATH.');
172
+ }
173
+ const workingDirectory = config.workingDirectory || process.cwd();
174
+ const fullPrompt = systemPrompt ? `${systemPrompt}\n\n---\n\n${prompt}` : prompt;
175
+ // OpenCode does not expose a Codex-style read-only sandbox / no-tools flag,
176
+ // so we rely on its non-interactive permission model and tolerate any
177
+ // non-JSON stdout warnings in the parser.
178
+ const args = ['run', '--format', 'json', '--dir', workingDirectory];
179
+ if (config.model) {
180
+ args.push('--model', config.model);
181
+ }
182
+ const response = await runLocalCLI('opencode', commandInfo, args, config, fullPrompt, options);
183
+ return { content: parseOpenCodeEventStream(response.content) };
184
+ }
124
185
  function runLocalCLI(provider, commandInfo, args, config, stdinText, options) {
125
186
  const finalArgs = [...commandInfo.argsPrefix, ...args];
126
187
  verboseLog(provider, 'Spawning:', commandInfo.command, maskPromptArgs(provider, finalArgs).join(' '));
@@ -137,10 +198,7 @@ function runLocalCLI(provider, commandInfo, args, config, stdinText, options) {
137
198
  cwd: config.workingDirectory || process.cwd(),
138
199
  stdio: ['pipe', 'pipe', 'pipe'],
139
200
  windowsHide: true,
140
- env: {
141
- ...process.env,
142
- CI: '1',
143
- },
201
+ env: buildChildEnv(provider),
144
202
  });
145
203
  verboseLog(provider, 'Process spawned with PID:', child.pid);
146
204
  let stdout = '';
@@ -413,10 +413,11 @@ export interface CLIConfig {
413
413
  apiKey?: string;
414
414
  model?: string;
415
415
  baseUrl?: string;
416
- provider?: 'openai' | 'openrouter' | 'azure' | 'custom' | 'cursor' | 'claude' | 'codex';
416
+ provider?: 'openai' | 'openrouter' | 'azure' | 'custom' | 'cursor' | 'claude' | 'codex' | 'opencode';
417
417
  cursorModel?: string;
418
418
  claudeModel?: string;
419
419
  codexModel?: string;
420
+ opencodeModel?: string;
420
421
  /** Azure api-version query param (e.g. '2024-10-21'). Only used when provider is 'azure'. */
421
422
  apiVersion?: string;
422
423
  /** Set true when the deployment is a reasoning model (o1, o3, o4-mini). Auto-detected for OpenAI; must be set for Azure deployments. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.147",
3
+ "version": "1.6.6-rc.149",
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",