vibeman 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +5 -7
- package/dist/runtime/api/.tsbuildinfo +1 -1
- package/dist/runtime/api/agent/agent-service.d.ts +18 -19
- package/dist/runtime/api/agent/agent-service.js +61 -58
- package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +2 -2
- package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +25 -36
- package/dist/runtime/api/agent/ai-providers/codex-cli-provider.d.ts +2 -0
- package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +109 -43
- package/dist/runtime/api/agent/ai-providers/types.d.ts +2 -0
- package/dist/runtime/api/agent/codex-cli-provider.test.js +83 -1
- package/dist/runtime/api/agent/parsers.d.ts +1 -0
- package/dist/runtime/api/agent/parsers.js +75 -8
- package/dist/runtime/api/agent/prompt-service.d.ts +14 -1
- package/dist/runtime/api/agent/prompt-service.js +123 -14
- package/dist/runtime/api/agent/prompt-service.test.js +230 -0
- package/dist/runtime/api/agent/routing-policy.d.ts +25 -42
- package/dist/runtime/api/agent/routing-policy.js +82 -132
- package/dist/runtime/api/agent/routing-policy.test.js +63 -0
- package/dist/runtime/api/api/routers/ai.d.ts +19 -7
- package/dist/runtime/api/api/routers/ai.js +9 -23
- package/dist/runtime/api/api/routers/executions.d.ts +4 -4
- package/dist/runtime/api/api/routers/executions.js +12 -21
- package/dist/runtime/api/api/routers/provider-config.d.ts +165 -0
- package/dist/runtime/api/api/routers/provider-config.js +252 -0
- package/dist/runtime/api/api/routers/tasks.d.ts +9 -9
- package/dist/runtime/api/api/routers/workflows.d.ts +23 -16
- package/dist/runtime/api/api/routers/workflows.js +30 -27
- package/dist/runtime/api/api/routers/worktrees.d.ts +4 -5
- package/dist/runtime/api/api/routers/worktrees.js +11 -11
- package/dist/runtime/api/api/trpc.d.ts +16 -16
- package/dist/runtime/api/index.js +2 -10
- package/dist/runtime/api/lib/local-config.d.ts +245 -0
- package/dist/runtime/api/lib/local-config.js +288 -0
- package/dist/runtime/api/lib/provider-detection.d.ts +59 -0
- package/dist/runtime/api/lib/provider-detection.js +244 -0
- package/dist/runtime/api/lib/server/bootstrap.d.ts +38 -0
- package/dist/runtime/api/lib/server/bootstrap.js +197 -0
- package/dist/runtime/api/lib/server/project-root.js +24 -1
- package/dist/runtime/api/lib/trpc/server.d.ts +143 -30
- package/dist/runtime/api/lib/trpc/server.js +8 -8
- package/dist/runtime/api/lib/trpc/ws-server.js +2 -2
- package/dist/runtime/api/router.d.ts +144 -31
- package/dist/runtime/api/router.js +9 -31
- package/dist/runtime/api/settings-service.js +51 -1
- package/dist/runtime/api/types/index.d.ts +8 -1
- package/dist/runtime/api/types/settings.d.ts +15 -2
- package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +8 -3
- package/dist/runtime/api/workflows/vibing-orchestrator.js +214 -184
- package/dist/runtime/web/.next/BUILD_ID +1 -1
- package/dist/runtime/web/.next/app-build-manifest.json +19 -12
- package/dist/runtime/web/.next/app-path-routes-manifest.json +2 -1
- package/dist/runtime/web/.next/build-manifest.json +2 -2
- package/dist/runtime/web/.next/prerender-manifest.json +10 -10
- package/dist/runtime/web/.next/routes-manifest.json +8 -0
- package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js +1 -0
- package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js.nft.json +1 -0
- package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route_client-reference-manifest.js +1 -0
- package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/_not-found.html +2 -2
- package/dist/runtime/web/.next/server/app/_not-found.rsc +5 -5
- package/dist/runtime/web/.next/server/app/api/health/route.js +1 -1
- package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js +1 -1
- package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/api/upload/route.js +1 -1
- package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app/index.html +2 -2
- package/dist/runtime/web/.next/server/app/index.rsc +6 -6
- package/dist/runtime/web/.next/server/app/page.js +21 -21
- package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/runtime/web/.next/server/app-paths-manifest.json +2 -1
- package/dist/runtime/web/.next/server/chunks/458.js +1 -1
- package/dist/runtime/web/.next/server/pages/404.html +2 -2
- package/dist/runtime/web/.next/server/pages/500.html +1 -1
- package/dist/runtime/web/.next/server/pages-manifest.json +1 -1
- package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -1
- package/dist/runtime/web/.next/static/5_15u1WQCxN1_eHZpldCv/_buildManifest.js +1 -0
- package/dist/runtime/web/.next/static/chunks/{277-0142a939f08738c3.js → 823-6f371a6e829adbba.js} +1 -1
- package/dist/runtime/web/.next/static/chunks/app/.vibeman/assets/images/[...path]/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/health/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/api/upload/route-751c9265a65409e5.js +1 -0
- package/dist/runtime/web/.next/static/chunks/app/{layout-dc0cfd29075b2160.js → layout-8435322f09fd0975.js} +1 -1
- package/dist/runtime/web/.next/static/chunks/app/page-9fe7d75095b4ccec.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -1
- package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +0 -26
- package/dist/runtime/api/lib/image-paste-drop-extension.js +0 -125
- package/dist/runtime/api/lib/markdown-utils.d.ts +0 -8
- package/dist/runtime/api/lib/markdown-utils.js +0 -282
- package/dist/runtime/api/lib/markdown-utils.test.js +0 -348
- package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +0 -27
- package/dist/runtime/api/lib/tiptap-utils.d.ts +0 -130
- package/dist/runtime/api/lib/tiptap-utils.js +0 -327
- package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +0 -1
- package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +0 -1
- /package/dist/runtime/api/{lib/markdown-utils.test.d.ts → agent/prompt-service.test.d.ts} +0 -0
- /package/dist/runtime/api/{lib/tiptap-utils.clamp-selection.test.d.ts → agent/routing-policy.test.d.ts} +0 -0
- /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → 5_15u1WQCxN1_eHZpldCv}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Configuration Manager
|
|
3
|
+
* Handles machine-specific configuration that should not be committed to git
|
|
4
|
+
*/
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { log } from './logger.js';
|
|
10
|
+
import { getProjectRoot } from './server/project-root.js';
|
|
11
|
+
// Schema for local configuration
|
|
12
|
+
const LocalConfigSchema = z.object({
|
|
13
|
+
providers: z
|
|
14
|
+
.object({
|
|
15
|
+
'claude-code': z
|
|
16
|
+
.object({
|
|
17
|
+
binPath: z.string().optional(),
|
|
18
|
+
detectedPath: z.string().optional(),
|
|
19
|
+
lastDetected: z.string().optional(), // ISO date
|
|
20
|
+
detectionMethod: z.enum(['explicit', 'PATH', 'common-location']).optional(),
|
|
21
|
+
})
|
|
22
|
+
.optional(),
|
|
23
|
+
codex: z
|
|
24
|
+
.object({
|
|
25
|
+
binPath: z.string().optional(),
|
|
26
|
+
detectedPath: z.string().optional(),
|
|
27
|
+
lastDetected: z.string().optional(),
|
|
28
|
+
detectionMethod: z.enum(['explicit', 'PATH', 'common-location']).optional(),
|
|
29
|
+
})
|
|
30
|
+
.optional(),
|
|
31
|
+
})
|
|
32
|
+
.optional(),
|
|
33
|
+
cache: z
|
|
34
|
+
.object({
|
|
35
|
+
lastProviderValidation: z.string().optional(), // ISO date
|
|
36
|
+
validationResults: z
|
|
37
|
+
.record(z.object({
|
|
38
|
+
available: z.boolean(),
|
|
39
|
+
error: z.string().optional(),
|
|
40
|
+
version: z.string().optional(),
|
|
41
|
+
checkedAt: z.string(), // ISO date
|
|
42
|
+
}))
|
|
43
|
+
.optional(),
|
|
44
|
+
})
|
|
45
|
+
.optional(),
|
|
46
|
+
});
|
|
47
|
+
// Default local configuration
|
|
48
|
+
const DEFAULT_LOCAL_CONFIG = {
|
|
49
|
+
providers: {},
|
|
50
|
+
cache: {},
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Local Configuration Service
|
|
54
|
+
* Manages machine-specific configuration stored in local-only directory
|
|
55
|
+
*/
|
|
56
|
+
export class LocalConfigService extends EventEmitter {
|
|
57
|
+
constructor(options = {}) {
|
|
58
|
+
super();
|
|
59
|
+
this.config = { ...DEFAULT_LOCAL_CONFIG };
|
|
60
|
+
this.initialized = false;
|
|
61
|
+
const configDir = options.configDir ?? '.vibeman/.local';
|
|
62
|
+
const configFileName = options.configFileName ?? 'local-config.json';
|
|
63
|
+
// Store in project-local directory that should be gitignored
|
|
64
|
+
const projectRoot = getProjectRoot();
|
|
65
|
+
this.configPath = path.join(projectRoot, configDir, configFileName);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Initialize the local config service
|
|
69
|
+
*/
|
|
70
|
+
async initialize() {
|
|
71
|
+
if (this.initialized)
|
|
72
|
+
return;
|
|
73
|
+
try {
|
|
74
|
+
await this.ensureConfigDirectory();
|
|
75
|
+
await this.loadConfig();
|
|
76
|
+
this.initialized = true;
|
|
77
|
+
log.info('Local config service initialized', { configPath: this.configPath }, 'local-config');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
log.error('Failed to initialize local config service', error, 'local-config');
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get the full local configuration
|
|
86
|
+
*/
|
|
87
|
+
getConfig() {
|
|
88
|
+
return { ...this.config };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get provider-specific configuration
|
|
92
|
+
*/
|
|
93
|
+
getProviderConfig(provider) {
|
|
94
|
+
return this.config.providers?.[provider];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Set provider binary path and detection info
|
|
98
|
+
*/
|
|
99
|
+
async setProviderPath(provider, binPath, detectionMethod = 'explicit') {
|
|
100
|
+
if (!this.config.providers) {
|
|
101
|
+
this.config.providers = {};
|
|
102
|
+
}
|
|
103
|
+
if (!this.config.providers[provider]) {
|
|
104
|
+
this.config.providers[provider] = {};
|
|
105
|
+
}
|
|
106
|
+
this.config.providers[provider].binPath = binPath;
|
|
107
|
+
this.config.providers[provider].detectedPath = binPath;
|
|
108
|
+
this.config.providers[provider].lastDetected = new Date().toISOString();
|
|
109
|
+
this.config.providers[provider].detectionMethod = detectionMethod;
|
|
110
|
+
await this.saveConfig();
|
|
111
|
+
this.emit('providerPathChanged', { provider, binPath, detectionMethod });
|
|
112
|
+
}
|
|
113
|
+
async updateDetectedProvider(provider, detectedPath, detectionMethod = 'PATH') {
|
|
114
|
+
if (!this.config.providers) {
|
|
115
|
+
this.config.providers = {};
|
|
116
|
+
}
|
|
117
|
+
if (!this.config.providers[provider]) {
|
|
118
|
+
this.config.providers[provider] = {};
|
|
119
|
+
}
|
|
120
|
+
this.config.providers[provider].detectedPath = detectedPath;
|
|
121
|
+
this.config.providers[provider].lastDetected = new Date().toISOString();
|
|
122
|
+
this.config.providers[provider].detectionMethod = detectionMethod;
|
|
123
|
+
await this.saveConfig();
|
|
124
|
+
this.emit('providerPathDetected', { provider, detectedPath, detectionMethod });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Clear provider configuration
|
|
128
|
+
*/
|
|
129
|
+
async clearProviderPath(provider) {
|
|
130
|
+
if (this.config.providers?.[provider]) {
|
|
131
|
+
delete this.config.providers[provider];
|
|
132
|
+
await this.saveConfig();
|
|
133
|
+
this.emit('providerPathCleared', { provider });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Cache provider validation results
|
|
138
|
+
*/
|
|
139
|
+
async cacheValidationResult(provider, result) {
|
|
140
|
+
if (!this.config.cache) {
|
|
141
|
+
this.config.cache = {};
|
|
142
|
+
}
|
|
143
|
+
if (!this.config.cache.validationResults) {
|
|
144
|
+
this.config.cache.validationResults = {};
|
|
145
|
+
}
|
|
146
|
+
this.config.cache.validationResults[provider] = {
|
|
147
|
+
...result,
|
|
148
|
+
checkedAt: new Date().toISOString(),
|
|
149
|
+
};
|
|
150
|
+
this.config.cache.lastProviderValidation = new Date().toISOString();
|
|
151
|
+
await this.saveConfig();
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get cached validation result
|
|
155
|
+
*/
|
|
156
|
+
getCachedValidationResult(provider) {
|
|
157
|
+
return this.config.cache?.validationResults?.[provider];
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Check if cached validation is still fresh (within last 5 minutes)
|
|
161
|
+
*/
|
|
162
|
+
isCachedValidationFresh(provider) {
|
|
163
|
+
const cached = this.getCachedValidationResult(provider);
|
|
164
|
+
if (!cached)
|
|
165
|
+
return false;
|
|
166
|
+
const cacheAge = Date.now() - new Date(cached.checkedAt).getTime();
|
|
167
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
168
|
+
return cacheAge < CACHE_TTL;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Clear all cached validation results
|
|
172
|
+
*/
|
|
173
|
+
async clearValidationCache() {
|
|
174
|
+
if (this.config.cache) {
|
|
175
|
+
this.config.cache.validationResults = {};
|
|
176
|
+
this.config.cache.lastProviderValidation = undefined;
|
|
177
|
+
await this.saveConfig();
|
|
178
|
+
this.emit('validationCacheCleared');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get configuration file information
|
|
183
|
+
*/
|
|
184
|
+
async getConfigInfo() {
|
|
185
|
+
const exists = await this.fileExists(this.configPath);
|
|
186
|
+
let size;
|
|
187
|
+
if (exists) {
|
|
188
|
+
try {
|
|
189
|
+
const stats = await fs.stat(this.configPath);
|
|
190
|
+
size = stats.size;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Ignore errors getting file size
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { path: this.configPath, exists, size };
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Export configuration for debugging/backup
|
|
200
|
+
*/
|
|
201
|
+
async exportConfig() {
|
|
202
|
+
return JSON.stringify(this.config, null, 2);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Import configuration from JSON string
|
|
206
|
+
*/
|
|
207
|
+
async importConfig(configJson) {
|
|
208
|
+
try {
|
|
209
|
+
const parsed = JSON.parse(configJson);
|
|
210
|
+
const validated = LocalConfigSchema.parse(parsed);
|
|
211
|
+
this.config = validated;
|
|
212
|
+
await this.saveConfig();
|
|
213
|
+
this.emit('configImported', this.config);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
throw new Error(`Invalid config format: ${error instanceof Error ? error.message : String(error)}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Private methods
|
|
220
|
+
async ensureConfigDirectory() {
|
|
221
|
+
const configDir = path.dirname(this.configPath);
|
|
222
|
+
try {
|
|
223
|
+
await fs.access(configDir);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
227
|
+
log.info('Created local config directory', { configDir }, 'local-config');
|
|
228
|
+
// Create .gitignore to ensure this directory is not committed
|
|
229
|
+
const gitignorePath = path.join(configDir, '.gitignore');
|
|
230
|
+
try {
|
|
231
|
+
await fs.writeFile(gitignorePath, '# Local configuration - do not commit\n*\n!.gitignore\n');
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
log.warn('Failed to create .gitignore in local config directory', error, 'local-config');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async loadConfig() {
|
|
239
|
+
try {
|
|
240
|
+
await fs.access(this.configPath);
|
|
241
|
+
const data = await fs.readFile(this.configPath, 'utf-8');
|
|
242
|
+
const parsed = JSON.parse(data);
|
|
243
|
+
// Validate and use parsed config
|
|
244
|
+
const validated = LocalConfigSchema.parse(parsed);
|
|
245
|
+
this.config = this.mergeWithDefaults(validated);
|
|
246
|
+
log.debug('Local config loaded from file', { configPath: this.configPath }, 'local-config');
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
log.debug('Local config file not found or invalid, using defaults', { error: String(error) }, 'local-config');
|
|
250
|
+
this.config = { ...DEFAULT_LOCAL_CONFIG };
|
|
251
|
+
await this.saveConfig();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async saveConfig() {
|
|
255
|
+
const data = JSON.stringify(this.config, null, 2);
|
|
256
|
+
await fs.writeFile(this.configPath, data, 'utf-8');
|
|
257
|
+
log.debug('Local config saved to file', { configPath: this.configPath }, 'local-config');
|
|
258
|
+
}
|
|
259
|
+
async fileExists(filePath) {
|
|
260
|
+
try {
|
|
261
|
+
await fs.access(filePath);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
mergeWithDefaults(config) {
|
|
269
|
+
return {
|
|
270
|
+
...DEFAULT_LOCAL_CONFIG,
|
|
271
|
+
...config,
|
|
272
|
+
providers: {
|
|
273
|
+
...DEFAULT_LOCAL_CONFIG.providers,
|
|
274
|
+
...config.providers,
|
|
275
|
+
},
|
|
276
|
+
cache: {
|
|
277
|
+
...DEFAULT_LOCAL_CONFIG.cache,
|
|
278
|
+
...config.cache,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Singleton instance
|
|
284
|
+
let localConfigService = null;
|
|
285
|
+
export function getLocalConfigService(config) {
|
|
286
|
+
localConfigService ?? (localConfigService = new LocalConfigService(config));
|
|
287
|
+
return localConfigService;
|
|
288
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Provider Detection Service
|
|
3
|
+
* Robust detection logic for AI provider CLI tools with multiple fallback strategies
|
|
4
|
+
*/
|
|
5
|
+
export interface DetectionResult {
|
|
6
|
+
found: boolean;
|
|
7
|
+
path?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
method?: 'explicit' | 'PATH' | 'common-location';
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ProviderInfo {
|
|
13
|
+
name: string;
|
|
14
|
+
command: string;
|
|
15
|
+
versionArgsList?: string[][];
|
|
16
|
+
versionRegex: RegExp;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Provider definitions for detection
|
|
20
|
+
*/
|
|
21
|
+
export declare const PROVIDER_DEFINITIONS: Record<string, ProviderInfo>;
|
|
22
|
+
/**
|
|
23
|
+
* Enhanced Provider Detection Service
|
|
24
|
+
*/
|
|
25
|
+
export declare class ProviderDetectionService {
|
|
26
|
+
private localConfig;
|
|
27
|
+
constructor();
|
|
28
|
+
/**
|
|
29
|
+
* Detect a specific provider with all fallback strategies
|
|
30
|
+
*/
|
|
31
|
+
detectProvider(providerId: string): Promise<DetectionResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Detect all configured providers
|
|
34
|
+
*/
|
|
35
|
+
detectAllProviders(): Promise<Record<string, DetectionResult>>;
|
|
36
|
+
/**
|
|
37
|
+
* Validate a specific binary path
|
|
38
|
+
*/
|
|
39
|
+
validateBinaryPath(binPath: string, provider: ProviderInfo, method?: DetectionResult['method']): Promise<DetectionResult>;
|
|
40
|
+
/**
|
|
41
|
+
* Set explicit provider path and cache it
|
|
42
|
+
*/
|
|
43
|
+
setProviderPath(providerId: string, binPath: string): Promise<DetectionResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Clear cached provider configuration
|
|
46
|
+
*/
|
|
47
|
+
clearProviderPath(providerId: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Get cached detection results if still fresh
|
|
50
|
+
*/
|
|
51
|
+
getCachedResult(providerId: string): Promise<DetectionResult | null>;
|
|
52
|
+
private checkExplicitConfig;
|
|
53
|
+
private checkSystemCommand;
|
|
54
|
+
private getVersion;
|
|
55
|
+
private resolveCommand;
|
|
56
|
+
private toLocalConfigKey;
|
|
57
|
+
private cacheDetectionResult;
|
|
58
|
+
}
|
|
59
|
+
export declare function getProviderDetectionService(): ProviderDetectionService;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Provider Detection Service
|
|
3
|
+
* Robust detection logic for AI provider CLI tools with multiple fallback strategies
|
|
4
|
+
*/
|
|
5
|
+
import { exec, execFile } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import which from 'which';
|
|
9
|
+
import { log } from './logger.js';
|
|
10
|
+
import { getLocalConfigService } from './local-config.js';
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
/**
|
|
14
|
+
* Provider definitions for detection
|
|
15
|
+
*/
|
|
16
|
+
export const PROVIDER_DEFINITIONS = {
|
|
17
|
+
'claude-code': {
|
|
18
|
+
name: 'Claude Code',
|
|
19
|
+
command: 'claude',
|
|
20
|
+
versionArgsList: [['--version'], ['-v']],
|
|
21
|
+
versionRegex: /(\d+\.\d+\.\d+)/,
|
|
22
|
+
},
|
|
23
|
+
codex: {
|
|
24
|
+
name: 'Codex CLI',
|
|
25
|
+
command: 'codex',
|
|
26
|
+
versionArgsList: [['--version'], ['-v']],
|
|
27
|
+
versionRegex: /(\d+\.\d+\.\d+)/,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Enhanced Provider Detection Service
|
|
32
|
+
*/
|
|
33
|
+
export class ProviderDetectionService {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.localConfig = getLocalConfigService();
|
|
36
|
+
// Ensure local config is initialized
|
|
37
|
+
this.localConfig.initialize().catch((error) => {
|
|
38
|
+
log.error('Failed to initialize local config for provider detection', error, 'provider-detection');
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Detect a specific provider with all fallback strategies
|
|
43
|
+
*/
|
|
44
|
+
async detectProvider(providerId) {
|
|
45
|
+
const provider = PROVIDER_DEFINITIONS[providerId];
|
|
46
|
+
if (!provider) {
|
|
47
|
+
return {
|
|
48
|
+
found: false,
|
|
49
|
+
error: `Unknown provider: ${providerId}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
log.debug(`Detecting provider: ${provider.name}`, { providerId }, 'provider-detection');
|
|
53
|
+
// Strategy 1: Check explicit configuration from local config
|
|
54
|
+
const explicitResult = await this.checkExplicitConfig(providerId, provider);
|
|
55
|
+
if (explicitResult.found) {
|
|
56
|
+
log.info(`Provider found via explicit config: ${provider.name}`, explicitResult, 'provider-detection');
|
|
57
|
+
return explicitResult;
|
|
58
|
+
}
|
|
59
|
+
// Strategy 2: Resolve via system command lookup
|
|
60
|
+
const systemResult = await this.checkSystemCommand(provider);
|
|
61
|
+
if (systemResult.found) {
|
|
62
|
+
log.info(`Provider found on system PATH: ${provider.name}`, systemResult, 'provider-detection');
|
|
63
|
+
await this.cacheDetectionResult(providerId, systemResult);
|
|
64
|
+
return systemResult;
|
|
65
|
+
}
|
|
66
|
+
// All strategies failed
|
|
67
|
+
const result = {
|
|
68
|
+
found: false,
|
|
69
|
+
error: `${provider.name} not found via explicit config or system PATH`,
|
|
70
|
+
};
|
|
71
|
+
log.warn(`Provider not found: ${provider.name}`, result, 'provider-detection');
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Detect all configured providers
|
|
76
|
+
*/
|
|
77
|
+
async detectAllProviders() {
|
|
78
|
+
const results = {};
|
|
79
|
+
for (const providerId of Object.keys(PROVIDER_DEFINITIONS)) {
|
|
80
|
+
try {
|
|
81
|
+
results[providerId] = await this.detectProvider(providerId);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
results[providerId] = {
|
|
85
|
+
found: false,
|
|
86
|
+
error: `Detection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return results;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Validate a specific binary path
|
|
94
|
+
*/
|
|
95
|
+
async validateBinaryPath(binPath, provider, method = 'explicit') {
|
|
96
|
+
try {
|
|
97
|
+
// Check if file exists and is executable
|
|
98
|
+
const stats = await fs.stat(binPath);
|
|
99
|
+
if (!stats.isFile()) {
|
|
100
|
+
return {
|
|
101
|
+
found: false,
|
|
102
|
+
error: `Path is not a file: ${binPath}`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Try to execute version command
|
|
106
|
+
const version = await this.getVersion(binPath, provider);
|
|
107
|
+
return {
|
|
108
|
+
found: true,
|
|
109
|
+
path: binPath,
|
|
110
|
+
version,
|
|
111
|
+
method,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
found: false,
|
|
117
|
+
path: binPath,
|
|
118
|
+
error: `Validation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Set explicit provider path and cache it
|
|
124
|
+
*/
|
|
125
|
+
async setProviderPath(providerId, binPath) {
|
|
126
|
+
const provider = PROVIDER_DEFINITIONS[providerId];
|
|
127
|
+
if (!provider) {
|
|
128
|
+
return {
|
|
129
|
+
found: false,
|
|
130
|
+
error: `Unknown provider: ${providerId}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const result = await this.validateBinaryPath(binPath, provider);
|
|
134
|
+
if (result.found) {
|
|
135
|
+
const localId = this.toLocalConfigKey(providerId);
|
|
136
|
+
await this.localConfig.setProviderPath(localId, binPath, 'explicit');
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Clear cached provider configuration
|
|
142
|
+
*/
|
|
143
|
+
async clearProviderPath(providerId) {
|
|
144
|
+
const localId = this.toLocalConfigKey(providerId);
|
|
145
|
+
await this.localConfig.clearProviderPath(localId);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get cached detection results if still fresh
|
|
149
|
+
*/
|
|
150
|
+
async getCachedResult(providerId) {
|
|
151
|
+
if (this.localConfig.isCachedValidationFresh(providerId)) {
|
|
152
|
+
const cached = this.localConfig.getCachedValidationResult(providerId);
|
|
153
|
+
if (cached) {
|
|
154
|
+
const localId = this.toLocalConfigKey(providerId);
|
|
155
|
+
return {
|
|
156
|
+
found: cached.available,
|
|
157
|
+
path: cached.available
|
|
158
|
+
? this.localConfig.getProviderConfig(localId)?.detectedPath
|
|
159
|
+
: undefined,
|
|
160
|
+
version: cached.version,
|
|
161
|
+
error: cached.error,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
// Private detection strategies
|
|
168
|
+
async checkExplicitConfig(providerId, provider) {
|
|
169
|
+
const localId = this.toLocalConfigKey(providerId);
|
|
170
|
+
const config = this.localConfig.getProviderConfig(localId);
|
|
171
|
+
if (!config?.binPath) {
|
|
172
|
+
return { found: false };
|
|
173
|
+
}
|
|
174
|
+
return this.validateBinaryPath(config.binPath, provider, 'explicit');
|
|
175
|
+
}
|
|
176
|
+
async checkSystemCommand(provider) {
|
|
177
|
+
const resolvedPath = await this.resolveCommand(provider.command);
|
|
178
|
+
if (!resolvedPath) {
|
|
179
|
+
return { found: false };
|
|
180
|
+
}
|
|
181
|
+
return this.validateBinaryPath(resolvedPath, provider, 'PATH');
|
|
182
|
+
}
|
|
183
|
+
async getVersion(binPath, provider) {
|
|
184
|
+
const argSets = provider.versionArgsList ?? [['--version']];
|
|
185
|
+
for (const args of argSets) {
|
|
186
|
+
try {
|
|
187
|
+
const { stdout, stderr } = await execFileAsync(binPath, args, { timeout: 10000 });
|
|
188
|
+
const output = (stdout + stderr).trim();
|
|
189
|
+
const match = output.match(provider.versionRegex);
|
|
190
|
+
if (match?.[1]) {
|
|
191
|
+
return match[1];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
log.debug(`Version command failed for ${binPath}`, { args, error }, 'provider-detection');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
async resolveCommand(command) {
|
|
201
|
+
try {
|
|
202
|
+
return await which(command);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
log.debug(`which lookup failed for ${command}`, error, 'provider-detection');
|
|
206
|
+
}
|
|
207
|
+
if (process.platform !== 'win32') {
|
|
208
|
+
try {
|
|
209
|
+
const { stdout } = await execAsync(`command -v ${command}`, { timeout: 5000 });
|
|
210
|
+
const resolved = stdout.trim().split('\n')[0];
|
|
211
|
+
return resolved ? resolved : null;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
log.debug(`command -v lookup failed for ${command}`, error, 'provider-detection');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
toLocalConfigKey(providerId) {
|
|
220
|
+
return providerId === 'claude-code' ? 'claude-code' : 'codex';
|
|
221
|
+
}
|
|
222
|
+
async cacheDetectionResult(providerId, result) {
|
|
223
|
+
if (result.found && result.path) {
|
|
224
|
+
const localId = this.toLocalConfigKey(providerId);
|
|
225
|
+
if (result.method === 'explicit') {
|
|
226
|
+
await this.localConfig.setProviderPath(localId, result.path, 'explicit');
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
await this.localConfig.updateDetectedProvider(localId, result.path, result.method || 'PATH');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
await this.localConfig.cacheValidationResult(providerId, {
|
|
233
|
+
available: result.found,
|
|
234
|
+
error: result.error,
|
|
235
|
+
version: result.version,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Singleton instance
|
|
240
|
+
let providerDetectionService = null;
|
|
241
|
+
export function getProviderDetectionService() {
|
|
242
|
+
providerDetectionService ?? (providerDetectionService = new ProviderDetectionService());
|
|
243
|
+
return providerDetectionService;
|
|
244
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Bootstrap - Async-first service initialization
|
|
3
|
+
*
|
|
4
|
+
* Provides ordered initialization of core services before router creation,
|
|
5
|
+
* ensuring all dependencies are ready for synchronous access during runtime.
|
|
6
|
+
*/
|
|
7
|
+
import { getSettingsService } from '../../settings-service.js';
|
|
8
|
+
import { TaskService } from '../../tasks/task-service.js';
|
|
9
|
+
import { AgentService } from '../../agent/agent-service.js';
|
|
10
|
+
import { GitService } from '../../vcs/git-service.js';
|
|
11
|
+
import { WorktreeService } from '../../vcs/worktree-service.js';
|
|
12
|
+
import { VibingOrchestrator } from '../../workflows/vibing-orchestrator.js';
|
|
13
|
+
/**
|
|
14
|
+
* Initialize core services in proper dependency order.
|
|
15
|
+
* Must be called before any service getters or router creation.
|
|
16
|
+
*/
|
|
17
|
+
export declare function bootstrap(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Ready-only sync getters - throw if called before bootstrap
|
|
20
|
+
*/
|
|
21
|
+
export declare function getBootstrappedSettingsService(): ReturnType<typeof getSettingsService>;
|
|
22
|
+
export declare function getBootstrappedTaskService(): TaskService;
|
|
23
|
+
export declare function getBootstrappedAgentService(): AgentService;
|
|
24
|
+
export declare function getBootstrappedGitService(): GitService;
|
|
25
|
+
export declare function getBootstrappedWorktreeService(): WorktreeService;
|
|
26
|
+
export declare function getBootstrappedVibingOrchestrator(): VibingOrchestrator;
|
|
27
|
+
/**
|
|
28
|
+
* Get bootstrap completion promise for diagnostics/tests
|
|
29
|
+
*/
|
|
30
|
+
export declare function getBootstrapReady(): Promise<void> | null;
|
|
31
|
+
/**
|
|
32
|
+
* Check if bootstrap has completed
|
|
33
|
+
*/
|
|
34
|
+
export declare function isBootstrapComplete(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Test bootstrap function that initializes against a temp workspace
|
|
37
|
+
*/
|
|
38
|
+
export declare function testBootstrap(tempWorkspace?: string): Promise<void>;
|