claude-code-workflow 6.2.7 → 6.3.0

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 (208) hide show
  1. package/.claude/CLAUDE.md +16 -1
  2. package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
  3. package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
  4. package/.claude/workflows/cli-tools-usage.md +14 -24
  5. package/.codex/AGENTS.md +51 -1
  6. package/.codex/prompts/compact.md +378 -0
  7. package/.gemini/GEMINI.md +57 -20
  8. package/ccw/dist/cli.d.ts.map +1 -1
  9. package/ccw/dist/cli.js +21 -8
  10. package/ccw/dist/cli.js.map +1 -1
  11. package/ccw/dist/commands/cli.d.ts +2 -0
  12. package/ccw/dist/commands/cli.d.ts.map +1 -1
  13. package/ccw/dist/commands/cli.js +129 -8
  14. package/ccw/dist/commands/cli.js.map +1 -1
  15. package/ccw/dist/commands/hook.d.ts.map +1 -1
  16. package/ccw/dist/commands/hook.js +3 -2
  17. package/ccw/dist/commands/hook.js.map +1 -1
  18. package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
  19. package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
  20. package/ccw/dist/config/litellm-api-config-manager.js +770 -0
  21. package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
  22. package/ccw/dist/config/provider-models.d.ts +73 -0
  23. package/ccw/dist/config/provider-models.d.ts.map +1 -0
  24. package/ccw/dist/config/provider-models.js +172 -0
  25. package/ccw/dist/config/provider-models.js.map +1 -0
  26. package/ccw/dist/core/cache-manager.d.ts.map +1 -1
  27. package/ccw/dist/core/cache-manager.js +3 -5
  28. package/ccw/dist/core/cache-manager.js.map +1 -1
  29. package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
  30. package/ccw/dist/core/dashboard-generator.js +3 -1
  31. package/ccw/dist/core/dashboard-generator.js.map +1 -1
  32. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
  33. package/ccw/dist/core/routes/cli-routes.js +169 -0
  34. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  35. package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
  36. package/ccw/dist/core/routes/codexlens-routes.js +234 -18
  37. package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
  38. package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
  39. package/ccw/dist/core/routes/hooks-routes.js +30 -32
  40. package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
  41. package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
  42. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
  43. package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
  44. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
  45. package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
  46. package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
  47. package/ccw/dist/core/routes/litellm-routes.js +85 -0
  48. package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
  49. package/ccw/dist/core/routes/mcp-routes.js +2 -2
  50. package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
  51. package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
  52. package/ccw/dist/core/routes/status-routes.js +39 -0
  53. package/ccw/dist/core/routes/status-routes.js.map +1 -1
  54. package/ccw/dist/core/routes/system-routes.js +1 -1
  55. package/ccw/dist/core/routes/system-routes.js.map +1 -1
  56. package/ccw/dist/core/server.d.ts.map +1 -1
  57. package/ccw/dist/core/server.js +15 -1
  58. package/ccw/dist/core/server.js.map +1 -1
  59. package/ccw/dist/mcp-server/index.js +1 -1
  60. package/ccw/dist/mcp-server/index.js.map +1 -1
  61. package/ccw/dist/tools/claude-cli-tools.d.ts +82 -0
  62. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
  63. package/ccw/dist/tools/claude-cli-tools.js +216 -0
  64. package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
  65. package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
  66. package/ccw/dist/tools/cli-executor.js +76 -14
  67. package/ccw/dist/tools/cli-executor.js.map +1 -1
  68. package/ccw/dist/tools/codex-lens.d.ts +9 -2
  69. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  70. package/ccw/dist/tools/codex-lens.js +114 -9
  71. package/ccw/dist/tools/codex-lens.js.map +1 -1
  72. package/ccw/dist/tools/context-cache-store.d.ts +136 -0
  73. package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
  74. package/ccw/dist/tools/context-cache-store.js +256 -0
  75. package/ccw/dist/tools/context-cache-store.js.map +1 -0
  76. package/ccw/dist/tools/context-cache.d.ts +56 -0
  77. package/ccw/dist/tools/context-cache.d.ts.map +1 -0
  78. package/ccw/dist/tools/context-cache.js +294 -0
  79. package/ccw/dist/tools/context-cache.js.map +1 -0
  80. package/ccw/dist/tools/core-memory.d.ts.map +1 -1
  81. package/ccw/dist/tools/core-memory.js +33 -19
  82. package/ccw/dist/tools/core-memory.js.map +1 -1
  83. package/ccw/dist/tools/index.d.ts.map +1 -1
  84. package/ccw/dist/tools/index.js +2 -0
  85. package/ccw/dist/tools/index.js.map +1 -1
  86. package/ccw/dist/tools/litellm-client.d.ts +85 -0
  87. package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
  88. package/ccw/dist/tools/litellm-client.js +188 -0
  89. package/ccw/dist/tools/litellm-client.js.map +1 -0
  90. package/ccw/dist/tools/litellm-executor.d.ts +34 -0
  91. package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
  92. package/ccw/dist/tools/litellm-executor.js +192 -0
  93. package/ccw/dist/tools/litellm-executor.js.map +1 -0
  94. package/ccw/dist/tools/pattern-parser.d.ts +55 -0
  95. package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
  96. package/ccw/dist/tools/pattern-parser.js +237 -0
  97. package/ccw/dist/tools/pattern-parser.js.map +1 -0
  98. package/ccw/dist/tools/smart-search.d.ts +1 -0
  99. package/ccw/dist/tools/smart-search.d.ts.map +1 -1
  100. package/ccw/dist/tools/smart-search.js +117 -41
  101. package/ccw/dist/tools/smart-search.js.map +1 -1
  102. package/ccw/dist/types/litellm-api-config.d.ts +294 -0
  103. package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
  104. package/ccw/dist/types/litellm-api-config.js +8 -0
  105. package/ccw/dist/types/litellm-api-config.js.map +1 -0
  106. package/ccw/src/cli.ts +258 -244
  107. package/ccw/src/commands/cli.ts +153 -9
  108. package/ccw/src/commands/hook.ts +3 -2
  109. package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
  110. package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
  111. package/ccw/src/config/provider-models.ts +222 -0
  112. package/ccw/src/core/cache-manager.ts +292 -294
  113. package/ccw/src/core/dashboard-generator.ts +3 -1
  114. package/ccw/src/core/routes/cli-routes.ts +192 -0
  115. package/ccw/src/core/routes/codexlens-routes.ts +241 -19
  116. package/ccw/src/core/routes/hooks-routes.ts +399 -405
  117. package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
  118. package/ccw/src/core/routes/litellm-routes.ts +107 -0
  119. package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
  120. package/ccw/src/core/routes/status-routes.ts +51 -0
  121. package/ccw/src/core/routes/system-routes.ts +1 -1
  122. package/ccw/src/core/server.ts +15 -1
  123. package/ccw/src/mcp-server/index.ts +1 -1
  124. package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
  125. package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
  126. package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
  127. package/ccw/src/templates/dashboard-js/components/cli-status.js +323 -9
  128. package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
  129. package/ccw/src/templates/dashboard-js/i18n.js +583 -1
  130. package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
  131. package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -24
  132. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
  133. package/ccw/src/templates/dashboard.html +840 -831
  134. package/ccw/src/tools/claude-cli-tools.ts +300 -0
  135. package/ccw/src/tools/cli-executor.ts +83 -14
  136. package/ccw/src/tools/codex-lens.ts +146 -9
  137. package/ccw/src/tools/context-cache-store.ts +368 -0
  138. package/ccw/src/tools/context-cache.ts +393 -0
  139. package/ccw/src/tools/core-memory.ts +33 -19
  140. package/ccw/src/tools/index.ts +2 -0
  141. package/ccw/src/tools/litellm-client.ts +246 -0
  142. package/ccw/src/tools/litellm-executor.ts +241 -0
  143. package/ccw/src/tools/pattern-parser.ts +329 -0
  144. package/ccw/src/tools/smart-search.ts +142 -41
  145. package/ccw/src/types/litellm-api-config.ts +402 -0
  146. package/ccw-litellm/README.md +180 -0
  147. package/ccw-litellm/pyproject.toml +35 -0
  148. package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
  149. package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
  151. package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
  152. package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
  153. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
  154. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
  155. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
  156. package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
  157. package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
  158. package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
  159. package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
  160. package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
  161. package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
  162. package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
  163. package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
  164. package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
  165. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
  166. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
  167. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
  168. package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
  169. package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
  170. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  171. package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
  172. package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
  173. package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
  174. package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
  175. package/codex-lens/src/codexlens/cli/commands.py +378 -23
  176. package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
  177. package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
  178. package/codex-lens/src/codexlens/cli/output.py +12 -1
  179. package/codex-lens/src/codexlens/config.py +93 -0
  180. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  181. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  182. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  183. package/codex-lens/src/codexlens/search/chain_search.py +6 -2
  184. package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
  185. package/codex-lens/src/codexlens/search/ranking.py +1 -1
  186. package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
  187. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  188. package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
  189. package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
  190. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  191. package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
  192. package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
  193. package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
  194. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  195. package/codex-lens/src/codexlens/semantic/base.py +61 -0
  196. package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
  197. package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
  198. package/codex-lens/src/codexlens/semantic/factory.py +98 -0
  199. package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
  200. package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
  201. package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
  202. package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
  203. package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
  204. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
  205. package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
  206. package/package.json +15 -5
  207. package/.codex/prompts.zip +0 -0
  208. package/ccw/package.json +0 -65
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Claude CLI Tools Configuration Manager
3
+ * Manages .claude/cli-tools.json with fallback:
4
+ * 1. Project workspace: {projectDir}/.claude/cli-tools.json (priority)
5
+ * 2. Global: ~/.claude/cli-tools.json (fallback)
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as os from 'os';
10
+
11
+ // ========== Types ==========
12
+
13
+ export interface ClaudeCliTool {
14
+ enabled: boolean;
15
+ isBuiltin: boolean;
16
+ command: string;
17
+ description: string;
18
+ }
19
+
20
+ export interface ClaudeCacheSettings {
21
+ injectionMode: 'auto' | 'manual' | 'disabled';
22
+ defaultPrefix: string;
23
+ defaultSuffix: string;
24
+ }
25
+
26
+ export interface ClaudeCliToolsConfig {
27
+ $schema?: string;
28
+ version: string;
29
+ tools: Record<string, ClaudeCliTool>;
30
+ customEndpoints: Array<{
31
+ id: string;
32
+ name: string;
33
+ enabled: boolean;
34
+ }>;
35
+ defaultTool: string;
36
+ settings: {
37
+ promptFormat: 'plain' | 'yaml' | 'json';
38
+ smartContext: {
39
+ enabled: boolean;
40
+ maxFiles: number;
41
+ };
42
+ nativeResume: boolean;
43
+ recursiveQuery: boolean;
44
+ cache: ClaudeCacheSettings;
45
+ };
46
+ }
47
+
48
+ // ========== Default Config ==========
49
+
50
+ const DEFAULT_CONFIG: ClaudeCliToolsConfig = {
51
+ version: '1.0.0',
52
+ tools: {
53
+ gemini: {
54
+ enabled: true,
55
+ isBuiltin: true,
56
+ command: 'gemini',
57
+ description: 'Google AI for code analysis'
58
+ },
59
+ qwen: {
60
+ enabled: true,
61
+ isBuiltin: true,
62
+ command: 'qwen',
63
+ description: 'Alibaba AI assistant'
64
+ },
65
+ codex: {
66
+ enabled: true,
67
+ isBuiltin: true,
68
+ command: 'codex',
69
+ description: 'OpenAI code generation'
70
+ },
71
+ claude: {
72
+ enabled: true,
73
+ isBuiltin: true,
74
+ command: 'claude',
75
+ description: 'Anthropic AI assistant'
76
+ }
77
+ },
78
+ customEndpoints: [],
79
+ defaultTool: 'gemini',
80
+ settings: {
81
+ promptFormat: 'plain',
82
+ smartContext: {
83
+ enabled: false,
84
+ maxFiles: 10
85
+ },
86
+ nativeResume: true,
87
+ recursiveQuery: true,
88
+ cache: {
89
+ injectionMode: 'auto',
90
+ defaultPrefix: '',
91
+ defaultSuffix: ''
92
+ }
93
+ }
94
+ };
95
+
96
+ // ========== Helper Functions ==========
97
+
98
+ function getProjectConfigPath(projectDir: string): string {
99
+ return path.join(projectDir, '.claude', 'cli-tools.json');
100
+ }
101
+
102
+ function getGlobalConfigPath(): string {
103
+ return path.join(os.homedir(), '.claude', 'cli-tools.json');
104
+ }
105
+
106
+ /**
107
+ * Resolve config path with fallback:
108
+ * 1. Project: {projectDir}/.claude/cli-tools.json
109
+ * 2. Global: ~/.claude/cli-tools.json
110
+ * Returns { path, source } where source is 'project' | 'global' | 'default'
111
+ */
112
+ function resolveConfigPath(projectDir: string): { path: string; source: 'project' | 'global' | 'default' } {
113
+ const projectPath = getProjectConfigPath(projectDir);
114
+ if (fs.existsSync(projectPath)) {
115
+ return { path: projectPath, source: 'project' };
116
+ }
117
+
118
+ const globalPath = getGlobalConfigPath();
119
+ if (fs.existsSync(globalPath)) {
120
+ return { path: globalPath, source: 'global' };
121
+ }
122
+
123
+ return { path: projectPath, source: 'default' };
124
+ }
125
+
126
+ function ensureClaudeDir(projectDir: string): void {
127
+ const claudeDir = path.join(projectDir, '.claude');
128
+ if (!fs.existsSync(claudeDir)) {
129
+ fs.mkdirSync(claudeDir, { recursive: true });
130
+ }
131
+ }
132
+
133
+ // ========== Main Functions ==========
134
+
135
+ /**
136
+ * Load CLI tools configuration with fallback:
137
+ * 1. Project: {projectDir}/.claude/cli-tools.json
138
+ * 2. Global: ~/.claude/cli-tools.json
139
+ * 3. Default config
140
+ */
141
+ export function loadClaudeCliTools(projectDir: string): ClaudeCliToolsConfig & { _source?: string } {
142
+ const resolved = resolveConfigPath(projectDir);
143
+
144
+ try {
145
+ if (resolved.source === 'default') {
146
+ // No config file found, return defaults
147
+ return { ...DEFAULT_CONFIG, _source: 'default' };
148
+ }
149
+
150
+ const content = fs.readFileSync(resolved.path, 'utf-8');
151
+ const parsed = JSON.parse(content) as Partial<ClaudeCliToolsConfig>;
152
+
153
+ // Merge with defaults
154
+ const config = {
155
+ ...DEFAULT_CONFIG,
156
+ ...parsed,
157
+ tools: { ...DEFAULT_CONFIG.tools, ...(parsed.tools || {}) },
158
+ settings: {
159
+ ...DEFAULT_CONFIG.settings,
160
+ ...(parsed.settings || {}),
161
+ smartContext: {
162
+ ...DEFAULT_CONFIG.settings.smartContext,
163
+ ...(parsed.settings?.smartContext || {})
164
+ },
165
+ cache: {
166
+ ...DEFAULT_CONFIG.settings.cache,
167
+ ...(parsed.settings?.cache || {})
168
+ }
169
+ },
170
+ _source: resolved.source
171
+ };
172
+
173
+ console.log(`[claude-cli-tools] Loaded config from ${resolved.source}: ${resolved.path}`);
174
+ return config;
175
+ } catch (err) {
176
+ console.error('[claude-cli-tools] Error loading config:', err);
177
+ return { ...DEFAULT_CONFIG, _source: 'default' };
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Save CLI tools configuration to project .claude/cli-tools.json
183
+ * Always saves to project directory (not global)
184
+ */
185
+ export function saveClaudeCliTools(projectDir: string, config: ClaudeCliToolsConfig & { _source?: string }): void {
186
+ ensureClaudeDir(projectDir);
187
+ const configPath = getProjectConfigPath(projectDir);
188
+
189
+ // Remove internal _source field before saving
190
+ const { _source, ...configToSave } = config;
191
+
192
+ try {
193
+ fs.writeFileSync(configPath, JSON.stringify(configToSave, null, 2), 'utf-8');
194
+ console.log(`[claude-cli-tools] Saved config to project: ${configPath}`);
195
+ } catch (err) {
196
+ console.error('[claude-cli-tools] Error saving config:', err);
197
+ throw new Error(`Failed to save CLI tools config: ${err}`);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Update enabled status for a specific tool
203
+ */
204
+ export function updateClaudeToolEnabled(
205
+ projectDir: string,
206
+ toolName: string,
207
+ enabled: boolean
208
+ ): ClaudeCliToolsConfig {
209
+ const config = loadClaudeCliTools(projectDir);
210
+
211
+ if (config.tools[toolName]) {
212
+ config.tools[toolName].enabled = enabled;
213
+ saveClaudeCliTools(projectDir, config);
214
+ }
215
+
216
+ return config;
217
+ }
218
+
219
+ /**
220
+ * Update cache settings
221
+ */
222
+ export function updateClaudeCacheSettings(
223
+ projectDir: string,
224
+ cacheSettings: Partial<ClaudeCacheSettings>
225
+ ): ClaudeCliToolsConfig {
226
+ const config = loadClaudeCliTools(projectDir);
227
+
228
+ config.settings.cache = {
229
+ ...config.settings.cache,
230
+ ...cacheSettings
231
+ };
232
+
233
+ saveClaudeCliTools(projectDir, config);
234
+ return config;
235
+ }
236
+
237
+ /**
238
+ * Update default tool
239
+ */
240
+ export function updateClaudeDefaultTool(
241
+ projectDir: string,
242
+ defaultTool: string
243
+ ): ClaudeCliToolsConfig {
244
+ const config = loadClaudeCliTools(projectDir);
245
+ config.defaultTool = defaultTool;
246
+ saveClaudeCliTools(projectDir, config);
247
+ return config;
248
+ }
249
+
250
+ /**
251
+ * Add custom endpoint
252
+ */
253
+ export function addClaudeCustomEndpoint(
254
+ projectDir: string,
255
+ endpoint: { id: string; name: string; enabled: boolean }
256
+ ): ClaudeCliToolsConfig {
257
+ const config = loadClaudeCliTools(projectDir);
258
+
259
+ // Check if endpoint already exists
260
+ const existingIndex = config.customEndpoints.findIndex(e => e.id === endpoint.id);
261
+ if (existingIndex >= 0) {
262
+ config.customEndpoints[existingIndex] = endpoint;
263
+ } else {
264
+ config.customEndpoints.push(endpoint);
265
+ }
266
+
267
+ saveClaudeCliTools(projectDir, config);
268
+ return config;
269
+ }
270
+
271
+ /**
272
+ * Remove custom endpoint
273
+ */
274
+ export function removeClaudeCustomEndpoint(
275
+ projectDir: string,
276
+ endpointId: string
277
+ ): ClaudeCliToolsConfig {
278
+ const config = loadClaudeCliTools(projectDir);
279
+ config.customEndpoints = config.customEndpoints.filter(e => e.id !== endpointId);
280
+ saveClaudeCliTools(projectDir, config);
281
+ return config;
282
+ }
283
+
284
+ /**
285
+ * Get config source info
286
+ */
287
+ export function getClaudeCliToolsInfo(projectDir: string): {
288
+ projectPath: string;
289
+ globalPath: string;
290
+ activePath: string;
291
+ source: 'project' | 'global' | 'default';
292
+ } {
293
+ const resolved = resolveConfigPath(projectDir);
294
+ return {
295
+ projectPath: getProjectConfigPath(projectDir),
296
+ globalPath: getGlobalConfigPath(),
297
+ activePath: resolved.path,
298
+ source: resolved.source
299
+ };
300
+ }
@@ -10,6 +10,10 @@ import { spawn, ChildProcess } from 'child_process';
10
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, readdirSync, statSync } from 'fs';
11
11
  import { join, relative } from 'path';
12
12
 
13
+ // LiteLLM integration
14
+ import { executeLiteLLMEndpoint } from './litellm-executor.js';
15
+ import { findEndpointById } from '../config/litellm-api-config-manager.js';
16
+
13
17
  // Native resume support
14
18
  import {
15
19
  trackNewSession,
@@ -63,7 +67,7 @@ const ParamsSchema = z.object({
63
67
  model: z.string().optional(),
64
68
  cd: z.string().optional(),
65
69
  includeDirs: z.string().optional(),
66
- timeout: z.number().default(300000),
70
+ timeout: z.number().default(0), // 0 = no internal timeout, controlled by external caller (e.g., bash timeout)
67
71
  resume: z.union([z.boolean(), z.string()]).optional(), // true = last, string = single ID or comma-separated IDs
68
72
  id: z.string().optional(), // Custom execution ID (e.g., IMPL-001-step1)
69
73
  noNative: z.boolean().optional(), // Force prompt concatenation instead of native resume
@@ -592,6 +596,66 @@ async function executeCliTool(
592
596
  const workingDir = cd || process.cwd();
593
597
  ensureHistoryDir(workingDir); // Ensure history directory exists
594
598
 
599
+ // NEW: Check if model is a custom LiteLLM endpoint ID
600
+ if (model && !['gemini', 'qwen', 'codex'].includes(tool)) {
601
+ const endpoint = findEndpointById(workingDir, model);
602
+ if (endpoint) {
603
+ // Route to LiteLLM executor
604
+ if (onOutput) {
605
+ onOutput({ type: 'stderr', data: `[Routing to LiteLLM endpoint: ${model}]\n` });
606
+ }
607
+
608
+ const result = await executeLiteLLMEndpoint({
609
+ prompt,
610
+ endpointId: model,
611
+ baseDir: workingDir,
612
+ cwd: cd,
613
+ includeDirs: includeDirs ? includeDirs.split(',').map(d => d.trim()) : undefined,
614
+ enableCache: true,
615
+ onOutput: onOutput || undefined,
616
+ });
617
+
618
+ // Convert LiteLLM result to ExecutionOutput format
619
+ const startTime = Date.now();
620
+ const endTime = Date.now();
621
+ const duration = endTime - startTime;
622
+
623
+ const execution: ExecutionRecord = {
624
+ id: customId || `${Date.now()}-litellm`,
625
+ timestamp: new Date(startTime).toISOString(),
626
+ tool: 'litellm',
627
+ model: result.model,
628
+ mode,
629
+ prompt,
630
+ status: result.success ? 'success' : 'error',
631
+ exit_code: result.success ? 0 : 1,
632
+ duration_ms: duration,
633
+ output: {
634
+ stdout: result.output,
635
+ stderr: result.error || '',
636
+ truncated: false,
637
+ },
638
+ };
639
+
640
+ const conversation = convertToConversation(execution);
641
+
642
+ // Try to save to history
643
+ try {
644
+ saveConversation(workingDir, conversation);
645
+ } catch (err) {
646
+ console.error('[CLI Executor] Failed to save LiteLLM history:', (err as Error).message);
647
+ }
648
+
649
+ return {
650
+ success: result.success,
651
+ execution,
652
+ conversation,
653
+ stdout: result.output,
654
+ stderr: result.error || '',
655
+ };
656
+ }
657
+ }
658
+
595
659
  // Get SQLite store for native session lookup
596
660
  const store = await getSqliteStore(workingDir);
597
661
 
@@ -994,19 +1058,24 @@ async function executeCliTool(
994
1058
  reject(new Error(`Failed to spawn ${tool}: ${error.message}`));
995
1059
  });
996
1060
 
997
- // Timeout handling
998
- const timeoutId = setTimeout(() => {
999
- timedOut = true;
1000
- child.kill('SIGTERM');
1001
- setTimeout(() => {
1002
- if (!child.killed) {
1003
- child.kill('SIGKILL');
1004
- }
1005
- }, 5000);
1006
- }, timeout);
1061
+ // Timeout handling (timeout=0 disables internal timeout, controlled by external caller)
1062
+ let timeoutId: NodeJS.Timeout | null = null;
1063
+ if (timeout > 0) {
1064
+ timeoutId = setTimeout(() => {
1065
+ timedOut = true;
1066
+ child.kill('SIGTERM');
1067
+ setTimeout(() => {
1068
+ if (!child.killed) {
1069
+ child.kill('SIGKILL');
1070
+ }
1071
+ }, 5000);
1072
+ }, timeout);
1073
+ }
1007
1074
 
1008
1075
  child.on('close', () => {
1009
- clearTimeout(timeoutId);
1076
+ if (timeoutId) {
1077
+ clearTimeout(timeoutId);
1078
+ }
1010
1079
  });
1011
1080
  });
1012
1081
  }
@@ -1051,8 +1120,8 @@ Modes:
1051
1120
  },
1052
1121
  timeout: {
1053
1122
  type: 'number',
1054
- description: 'Timeout in milliseconds (default: 300000 = 5 minutes)',
1055
- default: 300000
1123
+ description: 'Timeout in milliseconds (default: 0 = disabled, controlled by external caller)',
1124
+ default: 0
1056
1125
  }
1057
1126
  },
1058
1127
  required: ['tool', 'prompt']
@@ -33,6 +33,14 @@ const VENV_PYTHON =
33
33
  let bootstrapChecked = false;
34
34
  let bootstrapReady = false;
35
35
 
36
+ // Venv status cache with TTL
37
+ interface VenvStatusCache {
38
+ status: ReadyStatus;
39
+ timestamp: number;
40
+ }
41
+ let venvStatusCache: VenvStatusCache | null = null;
42
+ const VENV_STATUS_TTL = 5 * 60 * 1000; // 5 minutes TTL
43
+
36
44
  // Track running indexing process for cancellation
37
45
  let currentIndexingProcess: ReturnType<typeof spawn> | null = null;
38
46
  let currentIndexingAborted = false;
@@ -77,6 +85,7 @@ interface SemanticStatus {
77
85
  backend?: string;
78
86
  accelerator?: string;
79
87
  providers?: string[];
88
+ litellmAvailable?: boolean;
80
89
  error?: string;
81
90
  }
82
91
 
@@ -115,6 +124,13 @@ interface ProgressInfo {
115
124
  totalFiles?: number;
116
125
  }
117
126
 
127
+ /**
128
+ * Clear venv status cache (call after install/uninstall operations)
129
+ */
130
+ function clearVenvStatusCache(): void {
131
+ venvStatusCache = null;
132
+ }
133
+
118
134
  /**
119
135
  * Detect available Python 3 executable
120
136
  * @returns Python executable command
@@ -137,17 +153,27 @@ function getSystemPython(): string {
137
153
 
138
154
  /**
139
155
  * Check if CodexLens venv exists and has required packages
156
+ * @param force - Force refresh cache (default: false)
140
157
  * @returns Ready status
141
158
  */
142
- async function checkVenvStatus(): Promise<ReadyStatus> {
159
+ async function checkVenvStatus(force = false): Promise<ReadyStatus> {
160
+ // Use cached result if available and not expired
161
+ if (!force && venvStatusCache && (Date.now() - venvStatusCache.timestamp < VENV_STATUS_TTL)) {
162
+ return venvStatusCache.status;
163
+ }
164
+
143
165
  // Check venv exists
144
166
  if (!existsSync(CODEXLENS_VENV)) {
145
- return { ready: false, error: 'Venv not found' };
167
+ const result = { ready: false, error: 'Venv not found' };
168
+ venvStatusCache = { status: result, timestamp: Date.now() };
169
+ return result;
146
170
  }
147
171
 
148
172
  // Check python executable exists
149
173
  if (!existsSync(VENV_PYTHON)) {
150
- return { ready: false, error: 'Python executable not found in venv' };
174
+ const result = { ready: false, error: 'Python executable not found in venv' };
175
+ venvStatusCache = { status: result, timestamp: Date.now() };
176
+ return result;
151
177
  }
152
178
 
153
179
  // Check codexlens is importable
@@ -168,15 +194,21 @@ async function checkVenvStatus(): Promise<ReadyStatus> {
168
194
  });
169
195
 
170
196
  child.on('close', (code) => {
197
+ let result: ReadyStatus;
171
198
  if (code === 0) {
172
- resolve({ ready: true, version: stdout.trim() });
199
+ result = { ready: true, version: stdout.trim() };
173
200
  } else {
174
- resolve({ ready: false, error: `CodexLens not installed: ${stderr}` });
201
+ result = { ready: false, error: `CodexLens not installed: ${stderr}` };
175
202
  }
203
+ // Cache the result
204
+ venvStatusCache = { status: result, timestamp: Date.now() };
205
+ resolve(result);
176
206
  });
177
207
 
178
208
  child.on('error', (err) => {
179
- resolve({ ready: false, error: `Failed to check venv: ${err.message}` });
209
+ const result = { ready: false, error: `Failed to check venv: ${err.message}` };
210
+ venvStatusCache = { status: result, timestamp: Date.now() };
211
+ resolve(result);
180
212
  });
181
213
  });
182
214
  }
@@ -198,8 +230,15 @@ async function checkSemanticStatus(): Promise<SemanticStatus> {
198
230
  import sys
199
231
  import json
200
232
  try:
201
- from codexlens.semantic import SEMANTIC_AVAILABLE, SEMANTIC_BACKEND
202
- result = {"available": SEMANTIC_AVAILABLE, "backend": SEMANTIC_BACKEND if SEMANTIC_AVAILABLE else None}
233
+ import codexlens.semantic as semantic
234
+ SEMANTIC_AVAILABLE = bool(getattr(semantic, "SEMANTIC_AVAILABLE", False))
235
+ SEMANTIC_BACKEND = getattr(semantic, "SEMANTIC_BACKEND", None)
236
+ LITELLM_AVAILABLE = bool(getattr(semantic, "LITELLM_AVAILABLE", False))
237
+ result = {
238
+ "available": SEMANTIC_AVAILABLE,
239
+ "backend": SEMANTIC_BACKEND if SEMANTIC_AVAILABLE else None,
240
+ "litellm_available": LITELLM_AVAILABLE,
241
+ }
203
242
 
204
243
  # Get ONNX providers for accelerator info
205
244
  try:
@@ -250,6 +289,7 @@ except Exception as e:
250
289
  backend: result.backend,
251
290
  accelerator: result.accelerator || 'CPU',
252
291
  providers: result.providers || [],
292
+ litellmAvailable: result.litellm_available || false,
253
293
  error: result.error
254
294
  });
255
295
  } catch {
@@ -263,6 +303,77 @@ except Exception as e:
263
303
  });
264
304
  }
265
305
 
306
+ /**
307
+ * Ensure LiteLLM embedder dependencies are available in the CodexLens venv.
308
+ * Installs ccw-litellm into the venv if needed.
309
+ */
310
+ async function ensureLiteLLMEmbedderReady(): Promise<BootstrapResult> {
311
+ // Ensure CodexLens venv exists and CodexLens is installed.
312
+ const readyStatus = await ensureReady();
313
+ if (!readyStatus.ready) {
314
+ return { success: false, error: readyStatus.error || 'CodexLens not ready' };
315
+ }
316
+
317
+ // Check if ccw_litellm can be imported
318
+ const importStatus = await new Promise<{ ok: boolean; error?: string }>((resolve) => {
319
+ const child = spawn(VENV_PYTHON, ['-c', 'import ccw_litellm; print("OK")'], {
320
+ stdio: ['ignore', 'pipe', 'pipe'],
321
+ timeout: 15000,
322
+ });
323
+
324
+ let stderr = '';
325
+ child.stderr.on('data', (data) => {
326
+ stderr += data.toString();
327
+ });
328
+
329
+ child.on('close', (code) => {
330
+ resolve({ ok: code === 0, error: stderr.trim() || undefined });
331
+ });
332
+
333
+ child.on('error', (err) => {
334
+ resolve({ ok: false, error: err.message });
335
+ });
336
+ });
337
+
338
+ if (importStatus.ok) {
339
+ return { success: true };
340
+ }
341
+
342
+ const pipPath =
343
+ process.platform === 'win32'
344
+ ? join(CODEXLENS_VENV, 'Scripts', 'pip.exe')
345
+ : join(CODEXLENS_VENV, 'bin', 'pip');
346
+
347
+ try {
348
+ console.log('[CodexLens] Installing ccw-litellm for LiteLLM embedding backend...');
349
+
350
+ const possiblePaths = [
351
+ join(process.cwd(), 'ccw-litellm'),
352
+ join(__dirname, '..', '..', '..', 'ccw-litellm'), // ccw/src/tools -> project root
353
+ join(homedir(), 'ccw-litellm'),
354
+ ];
355
+
356
+ let installed = false;
357
+ for (const localPath of possiblePaths) {
358
+ if (existsSync(join(localPath, 'pyproject.toml'))) {
359
+ console.log(`[CodexLens] Installing ccw-litellm from local path: ${localPath}`);
360
+ execSync(`"${pipPath}" install -e "${localPath}"`, { stdio: 'inherit' });
361
+ installed = true;
362
+ break;
363
+ }
364
+ }
365
+
366
+ if (!installed) {
367
+ console.log('[CodexLens] Installing ccw-litellm from PyPI...');
368
+ execSync(`"${pipPath}" install ccw-litellm`, { stdio: 'inherit' });
369
+ }
370
+
371
+ return { success: true };
372
+ } catch (err) {
373
+ return { success: false, error: `Failed to install ccw-litellm: ${(err as Error).message}` };
374
+ }
375
+ }
376
+
266
377
  /**
267
378
  * GPU acceleration mode for semantic search
268
379
  */
@@ -421,6 +532,17 @@ async function installSemantic(gpuMode: GpuMode = 'cpu'): Promise<BootstrapResul
421
532
 
422
533
  child.on('close', (code) => {
423
534
  if (code === 0) {
535
+ // IMPORTANT: fastembed installs onnxruntime (CPU) as dependency, which conflicts
536
+ // with onnxruntime-directml/gpu. Reinstall the GPU version to ensure it takes precedence.
537
+ if (gpuMode !== 'cpu') {
538
+ try {
539
+ console.log(`[CodexLens] Reinstalling ${onnxPackage} to ensure GPU provider works...`);
540
+ execSync(`"${pipPath}" install --force-reinstall ${onnxPackage}`, { stdio: 'pipe', timeout: 300000 });
541
+ console.log(`[CodexLens] ${onnxPackage} reinstalled successfully`);
542
+ } catch (e) {
543
+ console.warn(`[CodexLens] Warning: Failed to reinstall ${onnxPackage}: ${(e as Error).message}`);
544
+ }
545
+ }
424
546
  console.log(`[CodexLens] Semantic dependencies installed successfully (${gpuMode} mode)`);
425
547
  resolve({ success: true, message: `Installed with ${modeDescription}` });
426
548
  } else {
@@ -490,6 +612,8 @@ async function bootstrapVenv(): Promise<BootstrapResult> {
490
612
  execSync(`"${pipPath}" install codexlens`, { stdio: 'inherit' });
491
613
  }
492
614
 
615
+ // Clear cache after successful installation
616
+ clearVenvStatusCache();
493
617
  return { success: true };
494
618
  } catch (err) {
495
619
  return { success: false, error: `Failed to install codexlens: ${(err as Error).message}` };
@@ -1209,6 +1333,7 @@ async function uninstallCodexLens(): Promise<BootstrapResult> {
1209
1333
  // Reset bootstrap cache
1210
1334
  bootstrapChecked = false;
1211
1335
  bootstrapReady = false;
1336
+ clearVenvStatusCache();
1212
1337
 
1213
1338
  console.log('[CodexLens] CodexLens uninstalled successfully');
1214
1339
  return { success: true, message: 'CodexLens uninstalled successfully' };
@@ -1273,7 +1398,19 @@ function isIndexingInProgress(): boolean {
1273
1398
  export type { ProgressInfo, ExecuteOptions };
1274
1399
 
1275
1400
  // Export for direct usage
1276
- export { ensureReady, executeCodexLens, checkVenvStatus, bootstrapVenv, checkSemanticStatus, installSemantic, detectGpuSupport, uninstallCodexLens, cancelIndexing, isIndexingInProgress };
1401
+ export {
1402
+ ensureReady,
1403
+ executeCodexLens,
1404
+ checkVenvStatus,
1405
+ bootstrapVenv,
1406
+ checkSemanticStatus,
1407
+ ensureLiteLLMEmbedderReady,
1408
+ installSemantic,
1409
+ detectGpuSupport,
1410
+ uninstallCodexLens,
1411
+ cancelIndexing,
1412
+ isIndexingInProgress,
1413
+ };
1277
1414
  export type { GpuMode };
1278
1415
 
1279
1416
  // Backward-compatible export for tests