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,770 @@
1
+ /**
2
+ * LiteLLM API Configuration Manager
3
+ * Manages provider credentials, custom endpoints, and cache settings
4
+ */
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
6
+ import { homedir } from 'os';
7
+ import { join } from 'path';
8
+ import { GlobalPaths, ensureStorageDir } from './storage-paths.js';
9
+ /**
10
+ * Default configuration
11
+ */
12
+ function getDefaultConfig() {
13
+ return {
14
+ version: 1,
15
+ providers: [],
16
+ endpoints: [],
17
+ globalCacheSettings: {
18
+ enabled: true,
19
+ cacheDir: '~/.ccw/cache/context',
20
+ maxTotalSizeMB: 100,
21
+ },
22
+ };
23
+ }
24
+ /**
25
+ * Get config file path (global, shared across all projects)
26
+ */
27
+ function getConfigPath(_baseDir) {
28
+ const configDir = GlobalPaths.config();
29
+ ensureStorageDir(configDir);
30
+ return join(configDir, 'litellm-api-config.json');
31
+ }
32
+ /**
33
+ * Load configuration from file
34
+ */
35
+ export function loadLiteLLMApiConfig(baseDir) {
36
+ const configPath = getConfigPath(baseDir);
37
+ if (!existsSync(configPath)) {
38
+ return getDefaultConfig();
39
+ }
40
+ try {
41
+ const content = readFileSync(configPath, 'utf-8');
42
+ return JSON.parse(content);
43
+ }
44
+ catch (error) {
45
+ console.error('[LiteLLM Config] Failed to load config:', error);
46
+ return getDefaultConfig();
47
+ }
48
+ }
49
+ /**
50
+ * Save configuration to file
51
+ */
52
+ function saveConfig(baseDir, config) {
53
+ const configPath = getConfigPath(baseDir);
54
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
55
+ }
56
+ /**
57
+ * Resolve environment variables in API key
58
+ * Supports ${ENV_VAR} syntax
59
+ */
60
+ export function resolveEnvVar(value) {
61
+ if (!value)
62
+ return value;
63
+ const envVarMatch = value.match(/^\$\{(.+)\}$/);
64
+ if (envVarMatch) {
65
+ const envVarName = envVarMatch[1];
66
+ return process.env[envVarName] || '';
67
+ }
68
+ return value;
69
+ }
70
+ // ===========================
71
+ // Provider Management
72
+ // ===========================
73
+ /**
74
+ * Get all providers
75
+ */
76
+ export function getAllProviders(baseDir) {
77
+ const config = loadLiteLLMApiConfig(baseDir);
78
+ return config.providers;
79
+ }
80
+ /**
81
+ * Get provider by ID
82
+ */
83
+ export function getProvider(baseDir, providerId) {
84
+ const config = loadLiteLLMApiConfig(baseDir);
85
+ return config.providers.find((p) => p.id === providerId) || null;
86
+ }
87
+ /**
88
+ * Get provider with resolved environment variables
89
+ */
90
+ export function getProviderWithResolvedEnvVars(baseDir, providerId) {
91
+ const provider = getProvider(baseDir, providerId);
92
+ if (!provider)
93
+ return null;
94
+ return {
95
+ ...provider,
96
+ resolvedApiKey: resolveEnvVar(provider.apiKey),
97
+ };
98
+ }
99
+ /**
100
+ * Add new provider
101
+ */
102
+ export function addProvider(baseDir, providerData) {
103
+ const config = loadLiteLLMApiConfig(baseDir);
104
+ const provider = {
105
+ ...providerData,
106
+ id: `${providerData.type}-${Date.now()}`,
107
+ createdAt: new Date().toISOString(),
108
+ updatedAt: new Date().toISOString(),
109
+ };
110
+ config.providers.push(provider);
111
+ saveConfig(baseDir, config);
112
+ return provider;
113
+ }
114
+ /**
115
+ * Update provider
116
+ */
117
+ export function updateProvider(baseDir, providerId, updates) {
118
+ const config = loadLiteLLMApiConfig(baseDir);
119
+ const providerIndex = config.providers.findIndex((p) => p.id === providerId);
120
+ if (providerIndex === -1) {
121
+ throw new Error(`Provider not found: ${providerId}`);
122
+ }
123
+ config.providers[providerIndex] = {
124
+ ...config.providers[providerIndex],
125
+ ...updates,
126
+ updatedAt: new Date().toISOString(),
127
+ };
128
+ saveConfig(baseDir, config);
129
+ return config.providers[providerIndex];
130
+ }
131
+ /**
132
+ * Delete provider
133
+ */
134
+ export function deleteProvider(baseDir, providerId) {
135
+ const config = loadLiteLLMApiConfig(baseDir);
136
+ const initialLength = config.providers.length;
137
+ config.providers = config.providers.filter((p) => p.id !== providerId);
138
+ if (config.providers.length === initialLength) {
139
+ return false;
140
+ }
141
+ // Also remove endpoints using this provider
142
+ config.endpoints = config.endpoints.filter((e) => e.providerId !== providerId);
143
+ saveConfig(baseDir, config);
144
+ return true;
145
+ }
146
+ // ===========================
147
+ // Endpoint Management
148
+ // ===========================
149
+ /**
150
+ * Get all endpoints
151
+ */
152
+ export function getAllEndpoints(baseDir) {
153
+ const config = loadLiteLLMApiConfig(baseDir);
154
+ return config.endpoints;
155
+ }
156
+ /**
157
+ * Get endpoint by ID
158
+ */
159
+ export function getEndpoint(baseDir, endpointId) {
160
+ const config = loadLiteLLMApiConfig(baseDir);
161
+ return config.endpoints.find((e) => e.id === endpointId) || null;
162
+ }
163
+ /**
164
+ * Find endpoint by ID (alias for getEndpoint)
165
+ */
166
+ export function findEndpointById(baseDir, endpointId) {
167
+ return getEndpoint(baseDir, endpointId);
168
+ }
169
+ /**
170
+ * Add new endpoint
171
+ */
172
+ export function addEndpoint(baseDir, endpointData) {
173
+ const config = loadLiteLLMApiConfig(baseDir);
174
+ // Check if ID already exists
175
+ if (config.endpoints.some((e) => e.id === endpointData.id)) {
176
+ throw new Error(`Endpoint ID already exists: ${endpointData.id}`);
177
+ }
178
+ // Verify provider exists
179
+ if (!config.providers.find((p) => p.id === endpointData.providerId)) {
180
+ throw new Error(`Provider not found: ${endpointData.providerId}`);
181
+ }
182
+ const endpoint = {
183
+ ...endpointData,
184
+ createdAt: new Date().toISOString(),
185
+ updatedAt: new Date().toISOString(),
186
+ };
187
+ config.endpoints.push(endpoint);
188
+ saveConfig(baseDir, config);
189
+ return endpoint;
190
+ }
191
+ /**
192
+ * Update endpoint
193
+ */
194
+ export function updateEndpoint(baseDir, endpointId, updates) {
195
+ const config = loadLiteLLMApiConfig(baseDir);
196
+ const endpointIndex = config.endpoints.findIndex((e) => e.id === endpointId);
197
+ if (endpointIndex === -1) {
198
+ throw new Error(`Endpoint not found: ${endpointId}`);
199
+ }
200
+ // Verify provider exists if updating providerId
201
+ if (updates.providerId && !config.providers.find((p) => p.id === updates.providerId)) {
202
+ throw new Error(`Provider not found: ${updates.providerId}`);
203
+ }
204
+ config.endpoints[endpointIndex] = {
205
+ ...config.endpoints[endpointIndex],
206
+ ...updates,
207
+ updatedAt: new Date().toISOString(),
208
+ };
209
+ saveConfig(baseDir, config);
210
+ return config.endpoints[endpointIndex];
211
+ }
212
+ /**
213
+ * Delete endpoint
214
+ */
215
+ export function deleteEndpoint(baseDir, endpointId) {
216
+ const config = loadLiteLLMApiConfig(baseDir);
217
+ const initialLength = config.endpoints.length;
218
+ config.endpoints = config.endpoints.filter((e) => e.id !== endpointId);
219
+ if (config.endpoints.length === initialLength) {
220
+ return false;
221
+ }
222
+ // Clear default endpoint if deleted
223
+ if (config.defaultEndpoint === endpointId) {
224
+ delete config.defaultEndpoint;
225
+ }
226
+ saveConfig(baseDir, config);
227
+ return true;
228
+ }
229
+ // ===========================
230
+ // Default Endpoint Management
231
+ // ===========================
232
+ /**
233
+ * Get default endpoint
234
+ */
235
+ export function getDefaultEndpoint(baseDir) {
236
+ const config = loadLiteLLMApiConfig(baseDir);
237
+ return config.defaultEndpoint;
238
+ }
239
+ /**
240
+ * Set default endpoint
241
+ */
242
+ export function setDefaultEndpoint(baseDir, endpointId) {
243
+ const config = loadLiteLLMApiConfig(baseDir);
244
+ if (endpointId) {
245
+ // Verify endpoint exists
246
+ if (!config.endpoints.find((e) => e.id === endpointId)) {
247
+ throw new Error(`Endpoint not found: ${endpointId}`);
248
+ }
249
+ config.defaultEndpoint = endpointId;
250
+ }
251
+ else {
252
+ delete config.defaultEndpoint;
253
+ }
254
+ saveConfig(baseDir, config);
255
+ }
256
+ // ===========================
257
+ // Cache Settings Management
258
+ // ===========================
259
+ /**
260
+ * Get global cache settings
261
+ */
262
+ export function getGlobalCacheSettings(baseDir) {
263
+ const config = loadLiteLLMApiConfig(baseDir);
264
+ return config.globalCacheSettings;
265
+ }
266
+ /**
267
+ * Update global cache settings
268
+ */
269
+ export function updateGlobalCacheSettings(baseDir, settings) {
270
+ const config = loadLiteLLMApiConfig(baseDir);
271
+ config.globalCacheSettings = {
272
+ ...config.globalCacheSettings,
273
+ ...settings,
274
+ };
275
+ saveConfig(baseDir, config);
276
+ }
277
+ // ===========================
278
+ // CodexLens Embedding Rotation Management
279
+ // ===========================
280
+ /**
281
+ * Get CodexLens embedding rotation config
282
+ */
283
+ export function getCodexLensEmbeddingRotation(baseDir) {
284
+ const config = loadLiteLLMApiConfig(baseDir);
285
+ return config.codexlensEmbeddingRotation;
286
+ }
287
+ /**
288
+ * Update CodexLens embedding rotation config
289
+ * Also triggers sync to CodexLens settings.json
290
+ */
291
+ export function updateCodexLensEmbeddingRotation(baseDir, rotationConfig) {
292
+ const config = loadLiteLLMApiConfig(baseDir);
293
+ if (rotationConfig) {
294
+ config.codexlensEmbeddingRotation = rotationConfig;
295
+ }
296
+ else {
297
+ delete config.codexlensEmbeddingRotation;
298
+ }
299
+ saveConfig(baseDir, config);
300
+ // Auto-sync to CodexLens settings.json
301
+ const syncResult = syncCodexLensConfig(baseDir);
302
+ return { syncResult };
303
+ }
304
+ /**
305
+ * Get all enabled embedding providers with their API keys for rotation
306
+ * This aggregates all providers that have embedding models configured
307
+ */
308
+ export function getEmbeddingProvidersForRotation(baseDir) {
309
+ const config = loadLiteLLMApiConfig(baseDir);
310
+ const result = [];
311
+ for (const provider of config.providers) {
312
+ if (!provider.enabled)
313
+ continue;
314
+ // Check if provider has embedding models
315
+ const embeddingModels = (provider.embeddingModels || [])
316
+ .filter(m => m.enabled)
317
+ .map(m => ({
318
+ modelId: m.id,
319
+ modelName: m.name,
320
+ dimensions: m.capabilities?.embeddingDimension || 1536,
321
+ }));
322
+ if (embeddingModels.length === 0)
323
+ continue;
324
+ // Get API keys (single key or multiple from apiKeys array)
325
+ const apiKeys = [];
326
+ if (provider.apiKeys && provider.apiKeys.length > 0) {
327
+ // Use multi-key configuration
328
+ for (const keyEntry of provider.apiKeys) {
329
+ apiKeys.push({
330
+ keyId: keyEntry.id,
331
+ keyLabel: keyEntry.label || keyEntry.id,
332
+ enabled: keyEntry.enabled,
333
+ });
334
+ }
335
+ }
336
+ else if (provider.apiKey) {
337
+ // Single key fallback
338
+ apiKeys.push({
339
+ keyId: 'default',
340
+ keyLabel: 'Default Key',
341
+ enabled: true,
342
+ });
343
+ }
344
+ result.push({
345
+ providerId: provider.id,
346
+ providerName: provider.name,
347
+ apiBase: provider.apiBase || getDefaultApiBaseForType(provider.type),
348
+ embeddingModels,
349
+ apiKeys,
350
+ });
351
+ }
352
+ return result;
353
+ }
354
+ /**
355
+ * Generate rotation endpoints for ccw_litellm
356
+ * Creates endpoint list from rotation config for parallel embedding
357
+ * Supports both legacy codexlensEmbeddingRotation and new embeddingPoolConfig
358
+ */
359
+ export function generateRotationEndpoints(baseDir) {
360
+ const config = loadLiteLLMApiConfig(baseDir);
361
+ // Prefer embeddingPoolConfig, fallback to codexlensEmbeddingRotation for backward compatibility
362
+ const poolConfig = config.embeddingPoolConfig;
363
+ const rotationConfig = config.codexlensEmbeddingRotation;
364
+ // Check if new poolConfig is enabled
365
+ if (poolConfig && poolConfig.enabled) {
366
+ return generateEndpointsFromPool(baseDir, poolConfig, config);
367
+ }
368
+ // Fallback to legacy rotation config
369
+ if (rotationConfig && rotationConfig.enabled) {
370
+ return generateEndpointsFromLegacyRotation(baseDir, rotationConfig, config);
371
+ }
372
+ return [];
373
+ }
374
+ /**
375
+ * Generate endpoints from new embeddingPoolConfig (with auto-discovery support)
376
+ */
377
+ function generateEndpointsFromPool(baseDir, poolConfig, config) {
378
+ const endpoints = [];
379
+ if (poolConfig.autoDiscover) {
380
+ // Auto-discover all providers offering targetModel
381
+ const discovered = discoverProvidersForModel(baseDir, poolConfig.targetModel);
382
+ const excludedIds = new Set(poolConfig.excludedProviderIds || []);
383
+ for (const disc of discovered) {
384
+ // Skip excluded providers
385
+ if (excludedIds.has(disc.providerId))
386
+ continue;
387
+ // Find the provider config
388
+ const provider = config.providers.find(p => p.id === disc.providerId);
389
+ if (!provider || !provider.enabled)
390
+ continue;
391
+ // Find the embedding model
392
+ const embeddingModel = provider.embeddingModels?.find(m => m.id === disc.modelId);
393
+ if (!embeddingModel || !embeddingModel.enabled)
394
+ continue;
395
+ // Get API base (model-specific or provider default)
396
+ const apiBase = embeddingModel.endpointSettings?.baseUrl ||
397
+ provider.apiBase ||
398
+ getDefaultApiBaseForType(provider.type);
399
+ // Get API keys to use
400
+ let keysToUse = [];
401
+ if (provider.apiKeys && provider.apiKeys.length > 0) {
402
+ // Use all enabled keys
403
+ keysToUse = provider.apiKeys
404
+ .filter(k => k.enabled)
405
+ .map(k => ({ id: k.id, key: k.key, label: k.label || k.id }));
406
+ }
407
+ else if (provider.apiKey) {
408
+ // Single key fallback
409
+ keysToUse = [{ id: 'default', key: provider.apiKey, label: 'Default' }];
410
+ }
411
+ // Create endpoint for each key
412
+ for (const keyInfo of keysToUse) {
413
+ endpoints.push({
414
+ name: `${provider.name}-${keyInfo.label}`,
415
+ api_key: resolveEnvVar(keyInfo.key),
416
+ api_base: apiBase,
417
+ model: embeddingModel.name,
418
+ weight: 1.0, // Default weight for auto-discovered providers
419
+ max_concurrent: poolConfig.defaultMaxConcurrentPerKey,
420
+ });
421
+ }
422
+ }
423
+ }
424
+ return endpoints;
425
+ }
426
+ /**
427
+ * Generate endpoints from legacy codexlensEmbeddingRotation config
428
+ */
429
+ function generateEndpointsFromLegacyRotation(baseDir, rotationConfig, config) {
430
+ const endpoints = [];
431
+ for (const rotationProvider of rotationConfig.providers) {
432
+ if (!rotationProvider.enabled)
433
+ continue;
434
+ // Find the provider config
435
+ const provider = config.providers.find(p => p.id === rotationProvider.providerId);
436
+ if (!provider || !provider.enabled)
437
+ continue;
438
+ // Find the embedding model
439
+ const embeddingModel = provider.embeddingModels?.find(m => m.id === rotationProvider.modelId);
440
+ if (!embeddingModel || !embeddingModel.enabled)
441
+ continue;
442
+ // Get API base (model-specific or provider default)
443
+ const apiBase = embeddingModel.endpointSettings?.baseUrl ||
444
+ provider.apiBase ||
445
+ getDefaultApiBaseForType(provider.type);
446
+ // Get API keys to use
447
+ let keysToUse = [];
448
+ if (provider.apiKeys && provider.apiKeys.length > 0) {
449
+ if (rotationProvider.useAllKeys) {
450
+ // Use all enabled keys
451
+ keysToUse = provider.apiKeys
452
+ .filter(k => k.enabled)
453
+ .map(k => ({ id: k.id, key: k.key, label: k.label || k.id }));
454
+ }
455
+ else if (rotationProvider.selectedKeyIds && rotationProvider.selectedKeyIds.length > 0) {
456
+ // Use only selected keys
457
+ keysToUse = provider.apiKeys
458
+ .filter(k => k.enabled && rotationProvider.selectedKeyIds.includes(k.id))
459
+ .map(k => ({ id: k.id, key: k.key, label: k.label || k.id }));
460
+ }
461
+ }
462
+ else if (provider.apiKey) {
463
+ // Single key fallback
464
+ keysToUse = [{ id: 'default', key: provider.apiKey, label: 'Default' }];
465
+ }
466
+ // Create endpoint for each key
467
+ for (const keyInfo of keysToUse) {
468
+ endpoints.push({
469
+ name: `${provider.name}-${keyInfo.label}`,
470
+ api_key: resolveEnvVar(keyInfo.key),
471
+ api_base: apiBase,
472
+ model: embeddingModel.name,
473
+ weight: rotationProvider.weight,
474
+ max_concurrent: rotationProvider.maxConcurrentPerKey,
475
+ });
476
+ }
477
+ }
478
+ return endpoints;
479
+ }
480
+ /**
481
+ * Sync CodexLens settings with CCW API config
482
+ * Writes rotation endpoints to ~/.codexlens/settings.json
483
+ * This enables the Python backend to use UI-configured rotation
484
+ * Supports both new embeddingPoolConfig and legacy codexlensEmbeddingRotation
485
+ */
486
+ export function syncCodexLensConfig(baseDir) {
487
+ try {
488
+ const config = loadLiteLLMApiConfig(baseDir);
489
+ // Prefer embeddingPoolConfig, fallback to codexlensEmbeddingRotation
490
+ const poolConfig = config.embeddingPoolConfig;
491
+ const rotationConfig = config.codexlensEmbeddingRotation;
492
+ // Get CodexLens settings path
493
+ const codexlensDir = join(homedir(), '.codexlens');
494
+ const settingsPath = join(codexlensDir, 'settings.json');
495
+ // Ensure directory exists
496
+ if (!existsSync(codexlensDir)) {
497
+ mkdirSync(codexlensDir, { recursive: true });
498
+ }
499
+ // Load existing settings or create new
500
+ let settings = {};
501
+ if (existsSync(settingsPath)) {
502
+ try {
503
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
504
+ }
505
+ catch {
506
+ settings = {};
507
+ }
508
+ }
509
+ // Check if either config is enabled
510
+ const isPoolEnabled = poolConfig && poolConfig.enabled;
511
+ const isRotationEnabled = rotationConfig && rotationConfig.enabled;
512
+ // If neither is enabled, remove rotation endpoints and return
513
+ if (!isPoolEnabled && !isRotationEnabled) {
514
+ if (settings.litellm_rotation_endpoints) {
515
+ delete settings.litellm_rotation_endpoints;
516
+ delete settings.litellm_rotation_strategy;
517
+ delete settings.litellm_rotation_cooldown;
518
+ delete settings.litellm_target_model;
519
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
520
+ }
521
+ return { success: true, message: 'Rotation disabled, cleared endpoints', endpointCount: 0 };
522
+ }
523
+ // Generate rotation endpoints (function handles priority internally)
524
+ const endpoints = generateRotationEndpoints(baseDir);
525
+ if (endpoints.length === 0) {
526
+ return { success: false, message: 'No valid endpoints generated from rotation config' };
527
+ }
528
+ // Update settings with rotation config (use poolConfig if available)
529
+ settings.litellm_rotation_endpoints = endpoints;
530
+ if (isPoolEnabled) {
531
+ settings.litellm_rotation_strategy = poolConfig.strategy;
532
+ settings.litellm_rotation_cooldown = poolConfig.defaultCooldown;
533
+ settings.litellm_target_model = poolConfig.targetModel;
534
+ }
535
+ else {
536
+ settings.litellm_rotation_strategy = rotationConfig.strategy;
537
+ settings.litellm_rotation_cooldown = rotationConfig.defaultCooldown;
538
+ settings.litellm_target_model = rotationConfig.targetModel;
539
+ }
540
+ // Write updated settings
541
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
542
+ return {
543
+ success: true,
544
+ message: `Synced ${endpoints.length} rotation endpoints to CodexLens`,
545
+ endpointCount: endpoints.length,
546
+ };
547
+ }
548
+ catch (error) {
549
+ const errorMessage = error instanceof Error ? error.message : String(error);
550
+ console.error('[LiteLLM Config] Failed to sync CodexLens config:', errorMessage);
551
+ return { success: false, message: `Sync failed: ${errorMessage}` };
552
+ }
553
+ }
554
+ // ===========================
555
+ // Embedding Pool Management (Generic, with Auto-Discovery)
556
+ // ===========================
557
+ /**
558
+ * Get embedding pool config
559
+ */
560
+ export function getEmbeddingPoolConfig(baseDir) {
561
+ const config = loadLiteLLMApiConfig(baseDir);
562
+ return config.embeddingPoolConfig;
563
+ }
564
+ /**
565
+ * Update embedding pool config
566
+ * Also triggers sync to CodexLens settings.json if enabled
567
+ */
568
+ export function updateEmbeddingPoolConfig(baseDir, poolConfig) {
569
+ const config = loadLiteLLMApiConfig(baseDir);
570
+ if (poolConfig) {
571
+ config.embeddingPoolConfig = poolConfig;
572
+ }
573
+ else {
574
+ delete config.embeddingPoolConfig;
575
+ }
576
+ saveConfig(baseDir, config);
577
+ // Auto-sync to CodexLens settings.json
578
+ const syncResult = syncCodexLensConfig(baseDir);
579
+ return { syncResult };
580
+ }
581
+ /**
582
+ * Discover all providers that offer a specific embedding model
583
+ * Returns list of {providerId, providerName, modelId, modelName, apiKeys[]}
584
+ */
585
+ export function discoverProvidersForModel(baseDir, targetModel) {
586
+ const config = loadLiteLLMApiConfig(baseDir);
587
+ const result = [];
588
+ for (const provider of config.providers) {
589
+ if (!provider.enabled)
590
+ continue;
591
+ // Check if provider has embedding models matching targetModel
592
+ const matchingModels = (provider.embeddingModels || []).filter(m => m.enabled && (m.id === targetModel || m.name === targetModel));
593
+ if (matchingModels.length === 0)
594
+ continue;
595
+ // Get API keys (single key or multiple from apiKeys array)
596
+ const apiKeys = [];
597
+ if (provider.apiKeys && provider.apiKeys.length > 0) {
598
+ // Use multi-key configuration
599
+ for (const keyEntry of provider.apiKeys) {
600
+ apiKeys.push({
601
+ keyId: keyEntry.id,
602
+ keyLabel: keyEntry.label || keyEntry.id,
603
+ enabled: keyEntry.enabled,
604
+ });
605
+ }
606
+ }
607
+ else if (provider.apiKey) {
608
+ // Single key fallback
609
+ apiKeys.push({
610
+ keyId: 'default',
611
+ keyLabel: 'Default Key',
612
+ enabled: true,
613
+ });
614
+ }
615
+ // Add each matching model
616
+ for (const model of matchingModels) {
617
+ result.push({
618
+ providerId: provider.id,
619
+ providerName: provider.name,
620
+ modelId: model.id,
621
+ modelName: model.name,
622
+ apiKeys,
623
+ });
624
+ }
625
+ }
626
+ return result;
627
+ }
628
+ // ===========================
629
+ // YAML Config Generation for ccw_litellm
630
+ // ===========================
631
+ /**
632
+ * Convert UI config (JSON) to ccw_litellm config (YAML format object)
633
+ * This allows CodexLens to use UI-configured providers
634
+ */
635
+ export function generateLiteLLMYamlConfig(baseDir) {
636
+ const config = loadLiteLLMApiConfig(baseDir);
637
+ // Build providers object
638
+ const providers = {};
639
+ for (const provider of config.providers) {
640
+ if (!provider.enabled)
641
+ continue;
642
+ providers[provider.id] = {
643
+ api_key: provider.apiKey,
644
+ api_base: provider.apiBase || getDefaultApiBaseForType(provider.type),
645
+ };
646
+ }
647
+ // Build embedding_models object from providers' embeddingModels
648
+ const embeddingModels = {};
649
+ for (const provider of config.providers) {
650
+ if (!provider.enabled || !provider.embeddingModels)
651
+ continue;
652
+ for (const model of provider.embeddingModels) {
653
+ if (!model.enabled)
654
+ continue;
655
+ embeddingModels[model.id] = {
656
+ provider: provider.id,
657
+ model: model.name,
658
+ dimensions: model.capabilities?.embeddingDimension || 1536,
659
+ // Use model-specific base URL if set, otherwise use provider's
660
+ ...(model.endpointSettings?.baseUrl && {
661
+ api_base: model.endpointSettings.baseUrl,
662
+ }),
663
+ };
664
+ }
665
+ }
666
+ // Build llm_models object from providers' llmModels
667
+ const llmModels = {};
668
+ for (const provider of config.providers) {
669
+ if (!provider.enabled || !provider.llmModels)
670
+ continue;
671
+ for (const model of provider.llmModels) {
672
+ if (!model.enabled)
673
+ continue;
674
+ llmModels[model.id] = {
675
+ provider: provider.id,
676
+ model: model.name,
677
+ ...(model.endpointSettings?.baseUrl && {
678
+ api_base: model.endpointSettings.baseUrl,
679
+ }),
680
+ };
681
+ }
682
+ }
683
+ // Find default provider
684
+ const defaultProvider = config.providers.find((p) => p.enabled)?.id || 'openai';
685
+ return {
686
+ version: 1,
687
+ default_provider: defaultProvider,
688
+ providers,
689
+ embedding_models: Object.keys(embeddingModels).length > 0 ? embeddingModels : {
690
+ default: {
691
+ provider: defaultProvider,
692
+ model: 'text-embedding-3-small',
693
+ dimensions: 1536,
694
+ },
695
+ },
696
+ llm_models: Object.keys(llmModels).length > 0 ? llmModels : {
697
+ default: {
698
+ provider: defaultProvider,
699
+ model: 'gpt-4',
700
+ },
701
+ },
702
+ };
703
+ }
704
+ /**
705
+ * Get default API base URL for provider type
706
+ */
707
+ function getDefaultApiBaseForType(type) {
708
+ const defaults = {
709
+ openai: 'https://api.openai.com/v1',
710
+ anthropic: 'https://api.anthropic.com/v1',
711
+ custom: 'https://api.example.com/v1',
712
+ };
713
+ return defaults[type] || 'https://api.openai.com/v1';
714
+ }
715
+ /**
716
+ * Save ccw_litellm YAML config file
717
+ * Writes to ~/.ccw/config/litellm-config.yaml
718
+ */
719
+ export function saveLiteLLMYamlConfig(baseDir) {
720
+ const yamlConfig = generateLiteLLMYamlConfig(baseDir);
721
+ // Convert to YAML manually (simple format)
722
+ const yamlContent = objectToYaml(yamlConfig);
723
+ // Write to ~/.ccw/config/litellm-config.yaml
724
+ const homePath = process.env.HOME || process.env.USERPROFILE || '';
725
+ const yamlPath = join(homePath, '.ccw', 'config', 'litellm-config.yaml');
726
+ // Ensure directory exists
727
+ const configDir = join(homePath, '.ccw', 'config');
728
+ ensureStorageDir(configDir);
729
+ writeFileSync(yamlPath, yamlContent, 'utf-8');
730
+ return yamlPath;
731
+ }
732
+ /**
733
+ * Simple object to YAML converter
734
+ */
735
+ function objectToYaml(obj, indent = 0) {
736
+ const spaces = ' '.repeat(indent);
737
+ if (obj === null || obj === undefined) {
738
+ return 'null';
739
+ }
740
+ if (typeof obj === 'string') {
741
+ // Quote strings that contain special characters
742
+ if (obj.includes(':') || obj.includes('#') || obj.includes('\n') || obj.startsWith('$')) {
743
+ return `"${obj.replace(/"/g, '\\"')}"`;
744
+ }
745
+ return obj;
746
+ }
747
+ if (typeof obj === 'number' || typeof obj === 'boolean') {
748
+ return String(obj);
749
+ }
750
+ if (Array.isArray(obj)) {
751
+ if (obj.length === 0)
752
+ return '[]';
753
+ return obj.map((item) => `${spaces}- ${objectToYaml(item, indent + 1).trimStart()}`).join('\n');
754
+ }
755
+ if (typeof obj === 'object') {
756
+ const entries = Object.entries(obj);
757
+ if (entries.length === 0)
758
+ return '{}';
759
+ return entries
760
+ .map(([key, value]) => {
761
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
762
+ return `${spaces}${key}:\n${objectToYaml(value, indent + 1)}`;
763
+ }
764
+ return `${spaces}${key}: ${objectToYaml(value, indent)}`;
765
+ })
766
+ .join('\n');
767
+ }
768
+ return String(obj);
769
+ }
770
+ //# sourceMappingURL=litellm-api-config-manager.js.map