claude-code-workflow 6.3.24 → 6.3.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 (75) hide show
  1. package/.claude/commands/issue/discover-by-prompt.md +764 -0
  2. package/.claude/skills/text-formatter/SKILL.md +196 -0
  3. package/.claude/skills/text-formatter/phases/01-input-collection.md +111 -0
  4. package/.claude/skills/text-formatter/phases/02-content-analysis.md +248 -0
  5. package/.claude/skills/text-formatter/phases/03-format-transform.md +245 -0
  6. package/.claude/skills/text-formatter/phases/04-output-preview.md +183 -0
  7. package/.claude/skills/text-formatter/specs/callout-types.md +293 -0
  8. package/.claude/skills/text-formatter/specs/element-mapping.md +226 -0
  9. package/.claude/skills/text-formatter/specs/format-rules.md +273 -0
  10. package/.claude/skills/text-formatter/templates/bbcode-template.md +350 -0
  11. package/ccw/dist/core/routes/help-routes.d.ts.map +1 -1
  12. package/ccw/dist/core/routes/help-routes.js +43 -7
  13. package/ccw/dist/core/routes/help-routes.js.map +1 -1
  14. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
  15. package/ccw/dist/core/routes/litellm-api-routes.js +31 -5
  16. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
  17. package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -1
  18. package/ccw/dist/core/routes/memory-routes.js +73 -0
  19. package/ccw/dist/core/routes/memory-routes.js.map +1 -1
  20. package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
  21. package/ccw/dist/core/routes/status-routes.js +36 -4
  22. package/ccw/dist/core/routes/status-routes.js.map +1 -1
  23. package/ccw/dist/core/server.d.ts.map +1 -1
  24. package/ccw/dist/core/server.js +58 -0
  25. package/ccw/dist/core/server.js.map +1 -1
  26. package/ccw/dist/core/services/api-key-tester.d.ts.map +1 -1
  27. package/ccw/dist/core/services/api-key-tester.js +8 -3
  28. package/ccw/dist/core/services/api-key-tester.js.map +1 -1
  29. package/ccw/dist/tools/claude-cli-tools.d.ts +7 -0
  30. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  31. package/ccw/dist/tools/claude-cli-tools.js +11 -1
  32. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  33. package/ccw/dist/tools/cli-executor-core.d.ts +11 -0
  34. package/ccw/dist/tools/cli-executor-core.d.ts.map +1 -1
  35. package/ccw/dist/tools/cli-executor-core.js +89 -2
  36. package/ccw/dist/tools/cli-executor-core.js.map +1 -1
  37. package/ccw/dist/tools/codex-lens.d.ts +2 -1
  38. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  39. package/ccw/dist/tools/codex-lens.js +51 -8
  40. package/ccw/dist/tools/codex-lens.js.map +1 -1
  41. package/ccw/dist/tools/index.d.ts.map +1 -1
  42. package/ccw/dist/tools/index.js +2 -0
  43. package/ccw/dist/tools/index.js.map +1 -1
  44. package/ccw/dist/tools/litellm-client.d.ts +6 -0
  45. package/ccw/dist/tools/litellm-client.d.ts.map +1 -1
  46. package/ccw/dist/tools/litellm-client.js +22 -1
  47. package/ccw/dist/tools/litellm-client.js.map +1 -1
  48. package/ccw/dist/tools/litellm-executor.js +2 -2
  49. package/ccw/dist/tools/litellm-executor.js.map +1 -1
  50. package/ccw/dist/tools/memory-update-queue.d.ts +172 -0
  51. package/ccw/dist/tools/memory-update-queue.d.ts.map +1 -0
  52. package/ccw/dist/tools/memory-update-queue.js +431 -0
  53. package/ccw/dist/tools/memory-update-queue.js.map +1 -0
  54. package/ccw/src/core/routes/help-routes.ts +46 -7
  55. package/ccw/src/core/routes/litellm-api-routes.ts +35 -4
  56. package/ccw/src/core/routes/memory-routes.ts +84 -0
  57. package/ccw/src/core/routes/status-routes.ts +39 -4
  58. package/ccw/src/core/server.ts +62 -0
  59. package/ccw/src/core/services/api-key-tester.ts +9 -3
  60. package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +45 -0
  61. package/ccw/src/templates/dashboard-js/components/cli-status.js +36 -5
  62. package/ccw/src/templates/dashboard-js/components/hook-manager.js +42 -81
  63. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +170 -28
  64. package/ccw/src/templates/dashboard-js/components/notifications.js +14 -4
  65. package/ccw/src/templates/dashboard-js/i18n.js +26 -0
  66. package/ccw/src/templates/dashboard-js/views/cli-manager.js +72 -2
  67. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +11 -1
  68. package/ccw/src/tools/claude-cli-tools.ts +17 -1
  69. package/ccw/src/tools/cli-executor-core.ts +103 -2
  70. package/ccw/src/tools/codex-lens.ts +63 -8
  71. package/ccw/src/tools/index.ts +2 -0
  72. package/ccw/src/tools/litellm-client.ts +25 -3
  73. package/ccw/src/tools/litellm-executor.ts +2 -2
  74. package/ccw/src/tools/memory-update-queue.js +499 -0
  75. package/package.json +91 -91
@@ -6,6 +6,9 @@
6
6
  import { z } from 'zod';
7
7
  import type { ToolSchema, ToolResult } from '../types/tool.js';
8
8
  import { spawn, ChildProcess } from 'child_process';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import * as os from 'os';
9
12
  import { validatePath } from '../utils/path-resolver.js';
10
13
  import { escapeWindowsArg } from '../utils/shell-escape.js';
11
14
  import { buildCommand, checkToolAvailability, clearToolCache, debugLog, errorLog, type NativeResumeConfig, type ToolAvailability } from './cli-executor-utils.js';
@@ -82,7 +85,73 @@ import { findEndpointById } from '../config/litellm-api-config-manager.js';
82
85
 
83
86
  // CLI Settings (CLI封装) integration
84
87
  import { loadEndpointSettings, getSettingsFilePath, findEndpoint } from '../config/cli-settings-manager.js';
85
- import { loadClaudeCliTools } from './claude-cli-tools.js';
88
+ import { loadClaudeCliTools, getToolConfig } from './claude-cli-tools.js';
89
+
90
+ /**
91
+ * Parse .env file content into key-value pairs
92
+ * Supports: KEY=value, KEY="value", KEY='value', comments (#), empty lines
93
+ */
94
+ function parseEnvFile(content: string): Record<string, string> {
95
+ const env: Record<string, string> = {};
96
+ const lines = content.split(/\r?\n/);
97
+
98
+ for (const line of lines) {
99
+ // Skip empty lines and comments
100
+ const trimmed = line.trim();
101
+ if (!trimmed || trimmed.startsWith('#')) continue;
102
+
103
+ // Find first = sign
104
+ const eqIndex = trimmed.indexOf('=');
105
+ if (eqIndex === -1) continue;
106
+
107
+ const key = trimmed.substring(0, eqIndex).trim();
108
+ let value = trimmed.substring(eqIndex + 1).trim();
109
+
110
+ // Remove surrounding quotes if present
111
+ if ((value.startsWith('"') && value.endsWith('"')) ||
112
+ (value.startsWith("'") && value.endsWith("'"))) {
113
+ value = value.slice(1, -1);
114
+ }
115
+
116
+ if (key) {
117
+ env[key] = value;
118
+ }
119
+ }
120
+
121
+ return env;
122
+ }
123
+
124
+ /**
125
+ * Load environment variables from .env file
126
+ * Supports ~ for home directory expansion
127
+ */
128
+ function loadEnvFile(envFilePath: string): Record<string, string> {
129
+ try {
130
+ // Expand ~ to home directory
131
+ let resolvedPath = envFilePath;
132
+ if (resolvedPath.startsWith('~')) {
133
+ resolvedPath = path.join(os.homedir(), resolvedPath.slice(1));
134
+ }
135
+
136
+ // Resolve relative paths
137
+ if (!path.isAbsolute(resolvedPath)) {
138
+ resolvedPath = path.resolve(resolvedPath);
139
+ }
140
+
141
+ if (!fs.existsSync(resolvedPath)) {
142
+ debugLog('ENV_FILE', `Env file not found: ${resolvedPath}`);
143
+ return {};
144
+ }
145
+
146
+ const content = fs.readFileSync(resolvedPath, 'utf-8');
147
+ const envVars = parseEnvFile(content);
148
+ debugLog('ENV_FILE', `Loaded ${Object.keys(envVars).length} env vars from ${resolvedPath}`);
149
+ return envVars;
150
+ } catch (err) {
151
+ errorLog('ENV_FILE', `Failed to load env file: ${envFilePath}`, err as Error);
152
+ return {};
153
+ }
154
+ }
86
155
 
87
156
  /**
88
157
  * Execute Claude CLI with custom settings file (CLI封装)
@@ -746,6 +815,19 @@ async function executeCliTool(
746
815
  const commandToSpawn = isWindows ? escapeWindowsArg(command) : command;
747
816
  const argsToSpawn = isWindows ? args.map(escapeWindowsArg) : args;
748
817
 
818
+ // Load custom environment variables from envFile if configured (for gemini/qwen)
819
+ const toolConfig = getToolConfig(workingDir, tool);
820
+ let customEnv: Record<string, string> = {};
821
+ if (toolConfig.envFile) {
822
+ customEnv = loadEnvFile(toolConfig.envFile);
823
+ }
824
+
825
+ // Merge custom env with process.env (custom env takes precedence)
826
+ const spawnEnv = {
827
+ ...process.env,
828
+ ...customEnv
829
+ };
830
+
749
831
  debugLog('SPAWN', `Spawning process`, {
750
832
  command,
751
833
  args,
@@ -754,13 +836,16 @@ async function executeCliTool(
754
836
  useStdin,
755
837
  platform: process.platform,
756
838
  fullCommand: `${command} ${args.join(' ')}`,
839
+ hasCustomEnv: Object.keys(customEnv).length > 0,
840
+ customEnvKeys: Object.keys(customEnv),
757
841
  ...(isWindows ? { escapedCommand: commandToSpawn, escapedArgs: argsToSpawn, escapedFullCommand: `${commandToSpawn} ${argsToSpawn.join(' ')}` } : {})
758
842
  });
759
843
 
760
844
  const child = spawn(commandToSpawn, argsToSpawn, {
761
845
  cwd: workingDir,
762
846
  shell: isWindows, // Enable shell on Windows for .cmd files
763
- stdio: [useStdin ? 'pipe' : 'ignore', 'pipe', 'pipe']
847
+ stdio: [useStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],
848
+ env: spawnEnv
764
849
  });
765
850
 
766
851
  // Track current child process for cleanup on interruption
@@ -1190,6 +1275,9 @@ export {
1190
1275
  * - api-endpoint: Check LiteLLM endpoint configuration exists
1191
1276
  */
1192
1277
  export async function getCliToolsStatus(): Promise<Record<string, ToolAvailability>> {
1278
+ const funcStart = Date.now();
1279
+ debugLog('PERF', 'getCliToolsStatus START');
1280
+
1193
1281
  // Default built-in tools
1194
1282
  const builtInTools = ['gemini', 'qwen', 'codex', 'claude', 'opencode'];
1195
1283
 
@@ -1202,6 +1290,7 @@ export async function getCliToolsStatus(): Promise<Record<string, ToolAvailabili
1202
1290
  }
1203
1291
  let toolsInfo: ToolInfo[] = builtInTools.map(name => ({ name, type: 'builtin' }));
1204
1292
 
1293
+ const configLoadStart = Date.now();
1205
1294
  try {
1206
1295
  // Dynamic import to avoid circular dependencies
1207
1296
  const { loadClaudeCliTools } = await import('./claude-cli-tools.js');
@@ -1225,11 +1314,15 @@ export async function getCliToolsStatus(): Promise<Record<string, ToolAvailabili
1225
1314
  // Fallback to built-in tools if config load fails
1226
1315
  debugLog('cli-executor', `Using built-in tools (config load failed: ${(e as Error).message})`);
1227
1316
  }
1317
+ debugLog('PERF', `Config load: ${Date.now() - configLoadStart}ms, tools: ${toolsInfo.length}`);
1228
1318
 
1229
1319
  const results: Record<string, ToolAvailability> = {};
1320
+ const toolTimings: Record<string, number> = {};
1230
1321
 
1322
+ const checksStart = Date.now();
1231
1323
  await Promise.all(toolsInfo.map(async (toolInfo) => {
1232
1324
  const { name, type, enabled, id } = toolInfo;
1325
+ const toolStart = Date.now();
1233
1326
 
1234
1327
  // Check availability based on tool type
1235
1328
  if (type === 'cli-wrapper') {
@@ -1271,8 +1364,13 @@ export async function getCliToolsStatus(): Promise<Record<string, ToolAvailabili
1271
1364
  // For builtin: check system PATH availability
1272
1365
  results[name] = await checkToolAvailability(name);
1273
1366
  }
1367
+
1368
+ toolTimings[name] = Date.now() - toolStart;
1274
1369
  }));
1275
1370
 
1371
+ debugLog('PERF', `Tool checks: ${Date.now() - checksStart}ms | Individual: ${JSON.stringify(toolTimings)}`);
1372
+ debugLog('PERF', `getCliToolsStatus TOTAL: ${Date.now() - funcStart}ms`);
1373
+
1276
1374
  return results;
1277
1375
  }
1278
1376
 
@@ -1520,6 +1618,9 @@ export type { PromptFormat, ConcatOptions } from './cli-prompt-builder.js';
1520
1618
  // Export utility functions and tool definition for backward compatibility
1521
1619
  export { executeCliTool, checkToolAvailability, clearToolCache };
1522
1620
 
1621
+ // Export env file utilities for testing
1622
+ export { parseEnvFile, loadEnvFile };
1623
+
1523
1624
  // Export prompt concatenation utilities
1524
1625
  export { PromptConcatenator, createPromptConcatenator, buildPrompt, buildMultiTurnPrompt } from './cli-prompt-builder.js';
1525
1626
 
@@ -49,6 +49,14 @@ interface VenvStatusCache {
49
49
  let venvStatusCache: VenvStatusCache | null = null;
50
50
  const VENV_STATUS_TTL = 5 * 60 * 1000; // 5 minutes TTL
51
51
 
52
+ // Semantic status cache with TTL (same as venv cache)
53
+ interface SemanticStatusCache {
54
+ status: SemanticStatus;
55
+ timestamp: number;
56
+ }
57
+ let semanticStatusCache: SemanticStatusCache | null = null;
58
+ const SEMANTIC_STATUS_TTL = 5 * 60 * 1000; // 5 minutes TTL
59
+
52
60
  // Track running indexing process for cancellation
53
61
  let currentIndexingProcess: ReturnType<typeof spawn> | null = null;
54
62
  let currentIndexingAborted = false;
@@ -147,8 +155,12 @@ function clearVenvStatusCache(): void {
147
155
  * @returns Ready status
148
156
  */
149
157
  async function checkVenvStatus(force = false): Promise<ReadyStatus> {
158
+ const funcStart = Date.now();
159
+ console.log('[PERF][CodexLens] checkVenvStatus START');
160
+
150
161
  // Use cached result if available and not expired
151
162
  if (!force && venvStatusCache && (Date.now() - venvStatusCache.timestamp < VENV_STATUS_TTL)) {
163
+ console.log(`[PERF][CodexLens] checkVenvStatus CACHE HIT: ${Date.now() - funcStart}ms`);
152
164
  return venvStatusCache.status;
153
165
  }
154
166
 
@@ -156,6 +168,7 @@ async function checkVenvStatus(force = false): Promise<ReadyStatus> {
156
168
  if (!existsSync(CODEXLENS_VENV)) {
157
169
  const result = { ready: false, error: 'Venv not found' };
158
170
  venvStatusCache = { status: result, timestamp: Date.now() };
171
+ console.log(`[PERF][CodexLens] checkVenvStatus (no venv): ${Date.now() - funcStart}ms`);
159
172
  return result;
160
173
  }
161
174
 
@@ -163,12 +176,16 @@ async function checkVenvStatus(force = false): Promise<ReadyStatus> {
163
176
  if (!existsSync(VENV_PYTHON)) {
164
177
  const result = { ready: false, error: 'Python executable not found in venv' };
165
178
  venvStatusCache = { status: result, timestamp: Date.now() };
179
+ console.log(`[PERF][CodexLens] checkVenvStatus (no python): ${Date.now() - funcStart}ms`);
166
180
  return result;
167
181
  }
168
182
 
169
- // Check codexlens is importable
183
+ // Check codexlens and core dependencies are importable
184
+ const spawnStart = Date.now();
185
+ console.log('[PERF][CodexLens] checkVenvStatus spawning Python...');
186
+
170
187
  return new Promise((resolve) => {
171
- const child = spawn(VENV_PYTHON, ['-c', 'import codexlens; print(codexlens.__version__)'], {
188
+ const child = spawn(VENV_PYTHON, ['-c', 'import codexlens; import watchdog; print(codexlens.__version__)'], {
172
189
  stdio: ['ignore', 'pipe', 'pipe'],
173
190
  timeout: 10000,
174
191
  });
@@ -192,29 +209,54 @@ async function checkVenvStatus(force = false): Promise<ReadyStatus> {
192
209
  }
193
210
  // Cache the result
194
211
  venvStatusCache = { status: result, timestamp: Date.now() };
212
+ console.log(`[PERF][CodexLens] checkVenvStatus Python spawn: ${Date.now() - spawnStart}ms | TOTAL: ${Date.now() - funcStart}ms | ready: ${result.ready}`);
195
213
  resolve(result);
196
214
  });
197
215
 
198
216
  child.on('error', (err) => {
199
217
  const result = { ready: false, error: `Failed to check venv: ${err.message}` };
200
218
  venvStatusCache = { status: result, timestamp: Date.now() };
219
+ console.log(`[PERF][CodexLens] checkVenvStatus ERROR: ${Date.now() - funcStart}ms`);
201
220
  resolve(result);
202
221
  });
203
222
  });
204
223
  }
205
224
 
225
+ /**
226
+ * Clear semantic status cache (call after install/uninstall operations)
227
+ */
228
+ function clearSemanticStatusCache(): void {
229
+ semanticStatusCache = null;
230
+ }
231
+
206
232
  /**
207
233
  * Check if semantic search dependencies are installed
234
+ * @param force - Force refresh cache (default: false)
208
235
  * @returns Semantic status
209
236
  */
210
- async function checkSemanticStatus(): Promise<SemanticStatus> {
237
+ async function checkSemanticStatus(force = false): Promise<SemanticStatus> {
238
+ const funcStart = Date.now();
239
+ console.log('[PERF][CodexLens] checkSemanticStatus START');
240
+
241
+ // Use cached result if available and not expired
242
+ if (!force && semanticStatusCache && (Date.now() - semanticStatusCache.timestamp < SEMANTIC_STATUS_TTL)) {
243
+ console.log(`[PERF][CodexLens] checkSemanticStatus CACHE HIT: ${Date.now() - funcStart}ms`);
244
+ return semanticStatusCache.status;
245
+ }
246
+
211
247
  // First check if CodexLens is installed
212
248
  const venvStatus = await checkVenvStatus();
213
249
  if (!venvStatus.ready) {
214
- return { available: false, error: 'CodexLens not installed' };
250
+ const result: SemanticStatus = { available: false, error: 'CodexLens not installed' };
251
+ semanticStatusCache = { status: result, timestamp: Date.now() };
252
+ console.log(`[PERF][CodexLens] checkSemanticStatus (no venv): ${Date.now() - funcStart}ms`);
253
+ return result;
215
254
  }
216
255
 
217
256
  // Check semantic module availability and accelerator info
257
+ const spawnStart = Date.now();
258
+ console.log('[PERF][CodexLens] checkSemanticStatus spawning Python...');
259
+
218
260
  return new Promise((resolve) => {
219
261
  const checkCode = `
220
262
  import sys
@@ -274,21 +316,31 @@ except Exception as e:
274
316
  const output = stdout.trim();
275
317
  try {
276
318
  const result = JSON.parse(output);
277
- resolve({
319
+ console.log(`[PERF][CodexLens] checkSemanticStatus Python spawn: ${Date.now() - spawnStart}ms | TOTAL: ${Date.now() - funcStart}ms | available: ${result.available}`);
320
+ const status: SemanticStatus = {
278
321
  available: result.available || false,
279
322
  backend: result.backend,
280
323
  accelerator: result.accelerator || 'CPU',
281
324
  providers: result.providers || [],
282
325
  litellmAvailable: result.litellm_available || false,
283
326
  error: result.error
284
- });
327
+ };
328
+ // Cache the result
329
+ semanticStatusCache = { status, timestamp: Date.now() };
330
+ resolve(status);
285
331
  } catch {
286
- resolve({ available: false, error: output || stderr || 'Unknown error' });
332
+ console.log(`[PERF][CodexLens] checkSemanticStatus PARSE ERROR: ${Date.now() - funcStart}ms`);
333
+ const errorStatus: SemanticStatus = { available: false, error: output || stderr || 'Unknown error' };
334
+ semanticStatusCache = { status: errorStatus, timestamp: Date.now() };
335
+ resolve(errorStatus);
287
336
  }
288
337
  });
289
338
 
290
339
  child.on('error', (err) => {
291
- resolve({ available: false, error: `Check failed: ${err.message}` });
340
+ console.log(`[PERF][CodexLens] checkSemanticStatus ERROR: ${Date.now() - funcStart}ms`);
341
+ const errorStatus: SemanticStatus = { available: false, error: `Check failed: ${err.message}` };
342
+ semanticStatusCache = { status: errorStatus, timestamp: Date.now() };
343
+ resolve(errorStatus);
292
344
  });
293
345
  });
294
346
  }
@@ -583,6 +635,7 @@ async function bootstrapWithUv(gpuMode: GpuMode = 'cpu'): Promise<BootstrapResul
583
635
 
584
636
  // Clear cache after successful installation
585
637
  clearVenvStatusCache();
638
+ clearSemanticStatusCache();
586
639
  console.log(`[CodexLens] Bootstrap with UV complete (${gpuMode} mode)`);
587
640
  return { success: true, message: `Installed with UV (${gpuMode} mode)` };
588
641
  }
@@ -878,6 +931,7 @@ async function bootstrapVenv(): Promise<BootstrapResult> {
878
931
 
879
932
  // Clear cache after successful installation
880
933
  clearVenvStatusCache();
934
+ clearSemanticStatusCache();
881
935
  return { success: true };
882
936
  } catch (err) {
883
937
  return { success: false, error: `Failed to install codexlens: ${(err as Error).message}` };
@@ -1631,6 +1685,7 @@ async function uninstallCodexLens(): Promise<BootstrapResult> {
1631
1685
  bootstrapChecked = false;
1632
1686
  bootstrapReady = false;
1633
1687
  clearVenvStatusCache();
1688
+ clearSemanticStatusCache();
1634
1689
 
1635
1690
  console.log('[CodexLens] CodexLens uninstalled successfully');
1636
1691
  return { success: true, message: 'CodexLens uninstalled successfully' };
@@ -30,6 +30,7 @@ import type { ProgressInfo } from './codex-lens.js';
30
30
  import { uiGeneratePreviewTool } from './ui-generate-preview.js';
31
31
  import { uiInstantiatePrototypesTool } from './ui-instantiate-prototypes.js';
32
32
  import { updateModuleClaudeTool } from './update-module-claude.js';
33
+ import { memoryQueueTool } from './memory-update-queue.js';
33
34
 
34
35
  interface LegacyTool {
35
36
  name: string;
@@ -366,6 +367,7 @@ registerTool(toLegacyTool(skillContextLoaderMod));
366
367
  registerTool(uiGeneratePreviewTool);
367
368
  registerTool(uiInstantiatePrototypesTool);
368
369
  registerTool(updateModuleClaudeTool);
370
+ registerTool(memoryQueueTool);
369
371
 
370
372
  // Export for external tool registration
371
373
  export { registerTool };
@@ -10,14 +10,36 @@
10
10
  */
11
11
 
12
12
  import { spawn } from 'child_process';
13
- import { promisify } from 'util';
13
+ import { existsSync } from 'fs';
14
+ import { join } from 'path';
15
+ import { homedir } from 'os';
14
16
 
15
17
  export interface LiteLLMConfig {
16
- pythonPath?: string; // Default 'python'
18
+ pythonPath?: string; // Default: CodexLens venv Python
17
19
  configPath?: string; // Configuration file path
18
20
  timeout?: number; // Default 60000ms
19
21
  }
20
22
 
23
+ // Platform-specific constants for CodexLens venv
24
+ const IS_WINDOWS = process.platform === 'win32';
25
+ const CODEXLENS_VENV = join(homedir(), '.codexlens', 'venv');
26
+ const VENV_BIN_DIR = IS_WINDOWS ? 'Scripts' : 'bin';
27
+ const PYTHON_EXECUTABLE = IS_WINDOWS ? 'python.exe' : 'python';
28
+
29
+ /**
30
+ * Get the Python path from CodexLens venv
31
+ * Falls back to system 'python' if venv doesn't exist
32
+ * @returns Path to Python executable
33
+ */
34
+ export function getCodexLensVenvPython(): string {
35
+ const venvPython = join(CODEXLENS_VENV, VENV_BIN_DIR, PYTHON_EXECUTABLE);
36
+ if (existsSync(venvPython)) {
37
+ return venvPython;
38
+ }
39
+ // Fallback to system Python if venv not available
40
+ return 'python';
41
+ }
42
+
21
43
  export interface ChatMessage {
22
44
  role: 'system' | 'user' | 'assistant';
23
45
  content: string;
@@ -51,7 +73,7 @@ export class LiteLLMClient {
51
73
  private timeout: number;
52
74
 
53
75
  constructor(config: LiteLLMConfig = {}) {
54
- this.pythonPath = config.pythonPath || 'python';
76
+ this.pythonPath = config.pythonPath || getCodexLensVenvPython();
55
77
  this.configPath = config.configPath;
56
78
  this.timeout = config.timeout || 60000;
57
79
  }
@@ -3,7 +3,7 @@
3
3
  * Integrates with context-cache for file packing and LiteLLM client for API calls
4
4
  */
5
5
 
6
- import { getLiteLLMClient } from './litellm-client.js';
6
+ import { getLiteLLMClient, getCodexLensVenvPython } from './litellm-client.js';
7
7
  import { handler as contextCacheHandler } from './context-cache.js';
8
8
  import {
9
9
  findEndpointById,
@@ -179,7 +179,7 @@ export async function executeLiteLLMEndpoint(
179
179
  }
180
180
 
181
181
  const client = getLiteLLMClient({
182
- pythonPath: 'python',
182
+ pythonPath: getCodexLensVenvPython(),
183
183
  timeout: 120000, // 2 minutes
184
184
  });
185
185