@metabob/minibob 0.1.2
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/ARCHITECTURE.md +255 -0
- package/CHANGELOG.md +112 -0
- package/README.md +380 -0
- package/bin/minibob.js +36 -0
- package/dist/acp-gossip.d.ts +72 -0
- package/dist/acp-gossip.d.ts.map +1 -0
- package/dist/acp-gossip.js +156 -0
- package/dist/acp-gossip.js.map +1 -0
- package/dist/acp.d.ts +62 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +292 -0
- package/dist/acp.js.map +1 -0
- package/dist/activity.d.ts +157 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/activity.js +518 -0
- package/dist/activity.js.map +1 -0
- package/dist/agent-runtime.d.ts +104 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/boredom.d.ts +125 -0
- package/dist/boredom.d.ts.map +1 -0
- package/dist/boredom.js +244 -0
- package/dist/boredom.js.map +1 -0
- package/dist/cli/acp-server.d.ts +23 -0
- package/dist/cli/acp-server.d.ts.map +1 -0
- package/dist/cli/burrow.d.ts +26 -0
- package/dist/cli/burrow.d.ts.map +1 -0
- package/dist/cli/doctor.d.ts +22 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/goal.d.ts +22 -0
- package/dist/cli/goal.d.ts.map +1 -0
- package/dist/cli/index.d.ts +47 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/instance-registry.d.ts +78 -0
- package/dist/cli/instance-registry.d.ts.map +1 -0
- package/dist/cli/observe.d.ts +35 -0
- package/dist/cli/observe.d.ts.map +1 -0
- package/dist/cli/vessel.d.ts +14 -0
- package/dist/cli/vessel.d.ts.map +1 -0
- package/dist/composition-observer.d.ts +96 -0
- package/dist/composition-observer.d.ts.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +128 -0
- package/dist/config.js.map +1 -0
- package/dist/docker/Dockerfile +35 -0
- package/dist/environment.d.ts +72 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +142 -0
- package/dist/environment.js.map +1 -0
- package/dist/goal-processor.d.ts +165 -0
- package/dist/goal-processor.d.ts.map +1 -0
- package/dist/helm/minibob-cluster/Chart.yaml +13 -0
- package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
- package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
- package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
- package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
- package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
- package/dist/helm/minibob-cluster/values-local.yaml +41 -0
- package/dist/helm/minibob-cluster/values-production.yaml +57 -0
- package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
- package/dist/helm/minibob-cluster/values.yaml +127 -0
- package/dist/improviser.d.ts +74 -0
- package/dist/improviser.d.ts.map +1 -0
- package/dist/impulse-filter.d.ts +74 -0
- package/dist/impulse-filter.d.ts.map +1 -0
- package/dist/impulse.d.ts +92 -0
- package/dist/impulse.d.ts.map +1 -0
- package/dist/impulse.js +234 -0
- package/dist/impulse.js.map +1 -0
- package/dist/lib.d.ts +29 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +18561 -0
- package/dist/lib.js.map +98 -0
- package/dist/lifecycle-hooks.d.ts +99 -0
- package/dist/lifecycle-hooks.d.ts.map +1 -0
- package/dist/lifecycle-hooks.js +135 -0
- package/dist/lifecycle-hooks.js.map +1 -0
- package/dist/llm.d.ts +31 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +349 -0
- package/dist/llm.js.map +1 -0
- package/dist/mcp-activity-bridge.d.ts +66 -0
- package/dist/mcp-activity-bridge.d.ts.map +1 -0
- package/dist/mcp-activity-bridge.js +126 -0
- package/dist/mcp-activity-bridge.js.map +1 -0
- package/dist/mcp.d.ts +216 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +292 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory-agent.d.ts +92 -0
- package/dist/memory-agent.d.ts.map +1 -0
- package/dist/memory-agent.js +277 -0
- package/dist/memory-agent.js.map +1 -0
- package/dist/runtime-mapping.d.ts +97 -0
- package/dist/runtime-mapping.d.ts.map +1 -0
- package/dist/search-first-executor.d.ts +113 -0
- package/dist/search-first-executor.d.ts.map +1 -0
- package/dist/session.d.ts +48 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/template-extractor.d.ts +9 -0
- package/dist/template-extractor.d.ts.map +1 -0
- package/dist/template-generator.d.ts +12 -0
- package/dist/template-generator.d.ts.map +1 -0
- package/dist/tools.d.ts +58 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +771 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +503 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/understanding/analyzer.d.ts +55 -0
- package/dist/understanding/analyzer.d.ts.map +1 -0
- package/dist/understanding/explorer.d.ts +73 -0
- package/dist/understanding/explorer.d.ts.map +1 -0
- package/dist/understanding/index.d.ts +7 -0
- package/dist/understanding/index.d.ts.map +1 -0
- package/dist/understanding/types.d.ts +136 -0
- package/dist/understanding/types.d.ts.map +1 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +106 -0
- package/dist/validation.js.map +1 -0
- package/dist/vessel-bootstrap.d.ts +190 -0
- package/dist/vessel-bootstrap.d.ts.map +1 -0
- package/dist/vessel-registry.d.ts +229 -0
- package/dist/vessel-registry.d.ts.map +1 -0
- package/index.ts +1329 -0
- package/package.json +54 -0
- package/src/acp-gossip.ts +193 -0
- package/src/acp.ts +362 -0
- package/src/activity.ts +1464 -0
- package/src/agent-runtime.ts +365 -0
- package/src/boredom.ts +423 -0
- package/src/cli/acp-server.ts +377 -0
- package/src/cli/burrow.ts +896 -0
- package/src/cli/doctor.ts +526 -0
- package/src/cli/goal.ts +224 -0
- package/src/cli/index.ts +147 -0
- package/src/cli/instance-registry.ts +271 -0
- package/src/cli/observe.ts +682 -0
- package/src/cli/vessel.ts +287 -0
- package/src/components/SystemOverview.tsx +331 -0
- package/src/composition-observer.ts +449 -0
- package/src/config.ts +172 -0
- package/src/environment.ts +167 -0
- package/src/goal-processor.ts +654 -0
- package/src/improviser.ts +591 -0
- package/src/impulse-filter.ts +273 -0
- package/src/impulse.ts +311 -0
- package/src/lib.ts +147 -0
- package/src/lifecycle-hooks.ts +181 -0
- package/src/llm.ts +434 -0
- package/src/mcp-activity-bridge.ts +158 -0
- package/src/mcp.ts +747 -0
- package/src/memory-agent.ts +316 -0
- package/src/runtime-mapping.ts +527 -0
- package/src/search-first-executor.ts +666 -0
- package/src/session.ts +141 -0
- package/src/template-extractor.ts +256 -0
- package/src/template-generator.ts +130 -0
- package/src/tools.ts +924 -0
- package/src/types.ts +497 -0
- package/src/understanding/analyzer.ts +354 -0
- package/src/understanding/explorer.ts +488 -0
- package/src/understanding/index.ts +27 -0
- package/src/understanding/types.ts +153 -0
- package/src/validation.ts +125 -0
- package/src/vessel-bootstrap.ts +440 -0
- package/src/vessel-registry.ts +621 -0
- package/templates/core/edit-file.json +85 -0
- package/templates/understanding/diagnose-problem.json +32 -0
- package/templates/understanding/explore-codebase-v2.json +57 -0
- package/templates/understanding/explore-codebase.json +37 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor Command
|
|
3
|
+
*
|
|
4
|
+
* Health check and configuration management for minibob.
|
|
5
|
+
*
|
|
6
|
+
* Checks:
|
|
7
|
+
* 1. Configuration - API key, config file, working directory
|
|
8
|
+
* 2. Backend - MCP endpoint reachability and health
|
|
9
|
+
* 3. Templates - Local templates directory and validity
|
|
10
|
+
* 4. Vessels - Configured vessels and their status
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { parseArgs, formatHelp, printSuccess, printWarning, printInfo, exitWithError, ensureMinibobHome } from './index'
|
|
14
|
+
import { loadConfig } from '../config'
|
|
15
|
+
|
|
16
|
+
export interface DoctorOptions {
|
|
17
|
+
fix?: boolean
|
|
18
|
+
verbose?: boolean
|
|
19
|
+
json?: boolean
|
|
20
|
+
check?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CheckResult {
|
|
24
|
+
name: string
|
|
25
|
+
status: 'ok' | 'warning' | 'error'
|
|
26
|
+
message: string
|
|
27
|
+
details?: string
|
|
28
|
+
fixable?: boolean
|
|
29
|
+
fixed?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run doctor command
|
|
34
|
+
*/
|
|
35
|
+
export async function doctor(args: string[]): Promise<void> {
|
|
36
|
+
const { flags, values } = parseArgs(args)
|
|
37
|
+
|
|
38
|
+
// Show help
|
|
39
|
+
if (flags.help || flags.h) {
|
|
40
|
+
console.log(formatHelp('doctor', 'Health check and configuration management', [
|
|
41
|
+
{ flag: '--fix', description: 'Attempt to fix issues' },
|
|
42
|
+
{ flag: '--verbose', description: 'Show detailed diagnostics' },
|
|
43
|
+
{ flag: '--json', description: 'Output as JSON' },
|
|
44
|
+
{ flag: '--check <item>', description: 'Check specific item (config, backend, templates, vessels)' },
|
|
45
|
+
{ flag: '--help, -h', description: 'Show this help message' },
|
|
46
|
+
]))
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const options: DoctorOptions = {
|
|
51
|
+
fix: flags.fix,
|
|
52
|
+
verbose: flags.verbose || flags.v,
|
|
53
|
+
json: flags.json,
|
|
54
|
+
check: values.check,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const results: CheckResult[] = []
|
|
58
|
+
|
|
59
|
+
// Determine which checks to run
|
|
60
|
+
const checksToRun = options.check
|
|
61
|
+
? [options.check]
|
|
62
|
+
: ['config', 'backend', 'templates', 'vessels']
|
|
63
|
+
|
|
64
|
+
for (const check of checksToRun) {
|
|
65
|
+
switch (check) {
|
|
66
|
+
case 'config':
|
|
67
|
+
results.push(...(await checkConfiguration(options)))
|
|
68
|
+
break
|
|
69
|
+
case 'backend':
|
|
70
|
+
results.push(...(await checkBackend(options)))
|
|
71
|
+
break
|
|
72
|
+
case 'templates':
|
|
73
|
+
results.push(...(await checkTemplates(options)))
|
|
74
|
+
break
|
|
75
|
+
case 'vessels':
|
|
76
|
+
results.push(...(await checkVessels(options)))
|
|
77
|
+
break
|
|
78
|
+
default:
|
|
79
|
+
exitWithError(`Unknown check: ${check}`)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Output results
|
|
84
|
+
if (options.json) {
|
|
85
|
+
console.log(JSON.stringify({ checks: results }, null, 2))
|
|
86
|
+
} else {
|
|
87
|
+
printResults(results, options.verbose)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Exit with error if any checks failed
|
|
91
|
+
const hasErrors = results.some((r) => r.status === 'error')
|
|
92
|
+
if (hasErrors) {
|
|
93
|
+
process.exit(1)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check configuration
|
|
99
|
+
*/
|
|
100
|
+
async function checkConfiguration(options: DoctorOptions): Promise<CheckResult[]> {
|
|
101
|
+
const results: CheckResult[] = []
|
|
102
|
+
|
|
103
|
+
// Check API key
|
|
104
|
+
const apiKey = process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY
|
|
105
|
+
|
|
106
|
+
if (!apiKey) {
|
|
107
|
+
results.push({
|
|
108
|
+
name: 'API Key',
|
|
109
|
+
status: 'error',
|
|
110
|
+
message: 'No API key found',
|
|
111
|
+
details: 'Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable',
|
|
112
|
+
fixable: false,
|
|
113
|
+
})
|
|
114
|
+
} else {
|
|
115
|
+
// Validate API key format
|
|
116
|
+
const isValidFormat =
|
|
117
|
+
apiKey.startsWith('sk-ant-') || apiKey.startsWith('sk-') || apiKey.length > 20
|
|
118
|
+
|
|
119
|
+
if (!isValidFormat) {
|
|
120
|
+
results.push({
|
|
121
|
+
name: 'API Key',
|
|
122
|
+
status: 'warning',
|
|
123
|
+
message: 'API key format looks unusual',
|
|
124
|
+
details: `Key starts with: ${apiKey.slice(0, 7)}...`,
|
|
125
|
+
})
|
|
126
|
+
} else {
|
|
127
|
+
results.push({
|
|
128
|
+
name: 'API Key',
|
|
129
|
+
status: 'ok',
|
|
130
|
+
message: `Configured (${apiKey.slice(0, 7)}...${apiKey.slice(-4)})`,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check config file
|
|
136
|
+
try {
|
|
137
|
+
const config = await loadConfig()
|
|
138
|
+
|
|
139
|
+
results.push({
|
|
140
|
+
name: 'Config File',
|
|
141
|
+
status: 'ok',
|
|
142
|
+
message: 'Configuration loaded',
|
|
143
|
+
details: options.verbose
|
|
144
|
+
? `Provider: ${config.provider}, Model: ${config.model}`
|
|
145
|
+
: undefined,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// Check working directory
|
|
149
|
+
const fs = await import('fs/promises')
|
|
150
|
+
try {
|
|
151
|
+
await fs.access(config.workingDirectory)
|
|
152
|
+
results.push({
|
|
153
|
+
name: 'Working Directory',
|
|
154
|
+
status: 'ok',
|
|
155
|
+
message: config.workingDirectory,
|
|
156
|
+
})
|
|
157
|
+
} catch {
|
|
158
|
+
results.push({
|
|
159
|
+
name: 'Working Directory',
|
|
160
|
+
status: 'error',
|
|
161
|
+
message: 'Directory not accessible',
|
|
162
|
+
details: config.workingDirectory,
|
|
163
|
+
fixable: options.fix,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
if (options.fix) {
|
|
167
|
+
try {
|
|
168
|
+
await fs.mkdir(config.workingDirectory, { recursive: true })
|
|
169
|
+
results[results.length - 1]!.fixed = true
|
|
170
|
+
results[results.length - 1]!.status = 'ok'
|
|
171
|
+
results[results.length - 1]!.message = 'Directory created'
|
|
172
|
+
} catch {
|
|
173
|
+
// Couldn't fix
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
results.push({
|
|
179
|
+
name: 'Config File',
|
|
180
|
+
status: 'warning',
|
|
181
|
+
message: 'No config file found (using defaults)',
|
|
182
|
+
fixable: options.fix,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
if (options.fix) {
|
|
186
|
+
try {
|
|
187
|
+
const defaultConfig = {
|
|
188
|
+
provider: 'anthropic',
|
|
189
|
+
model: 'claude-sonnet-4-20250514',
|
|
190
|
+
templatesDir: './templates',
|
|
191
|
+
autoCommit: false,
|
|
192
|
+
}
|
|
193
|
+
await Bun.write('minibob.json', JSON.stringify(defaultConfig, null, 2))
|
|
194
|
+
results[results.length - 1]!.fixed = true
|
|
195
|
+
results[results.length - 1]!.status = 'ok'
|
|
196
|
+
results[results.length - 1]!.message = 'Default config created'
|
|
197
|
+
} catch {
|
|
198
|
+
// Couldn't fix
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check minibob home directory
|
|
204
|
+
try {
|
|
205
|
+
const home = await ensureMinibobHome()
|
|
206
|
+
results.push({
|
|
207
|
+
name: 'Data Directory',
|
|
208
|
+
status: 'ok',
|
|
209
|
+
message: home,
|
|
210
|
+
})
|
|
211
|
+
} catch {
|
|
212
|
+
results.push({
|
|
213
|
+
name: 'Data Directory',
|
|
214
|
+
status: 'warning',
|
|
215
|
+
message: 'Could not create ~/.minibob directory',
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return results
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Check backend connectivity
|
|
224
|
+
*/
|
|
225
|
+
async function checkBackend(options: DoctorOptions): Promise<CheckResult[]> {
|
|
226
|
+
const results: CheckResult[] = []
|
|
227
|
+
|
|
228
|
+
const mcpEndpoint = process.env.MINIBOB_MCP_ENDPOINT || 'http://api.minibob.local'
|
|
229
|
+
|
|
230
|
+
// Check MCP endpoint
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetch(`${mcpEndpoint}/health`, {
|
|
233
|
+
signal: AbortSignal.timeout(5000),
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
if (response.ok) {
|
|
237
|
+
const health = (await response.json()) as { status?: string }
|
|
238
|
+
results.push({
|
|
239
|
+
name: 'MCP Backend',
|
|
240
|
+
status: 'ok',
|
|
241
|
+
message: `Connected to ${mcpEndpoint}`,
|
|
242
|
+
details: options.verbose ? `Health: ${health.status || 'ok'}` : undefined,
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Check templates endpoint
|
|
246
|
+
try {
|
|
247
|
+
const templatesResponse = await fetch(`${mcpEndpoint}/v2/activities/templates?limit=1`, {
|
|
248
|
+
signal: AbortSignal.timeout(5000),
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
if (templatesResponse.ok) {
|
|
252
|
+
const data = (await templatesResponse.json()) as { templates?: any[] }
|
|
253
|
+
results.push({
|
|
254
|
+
name: 'Activity Templates',
|
|
255
|
+
status: 'ok',
|
|
256
|
+
message: `Backend has ${data.templates?.length || 0}+ templates`,
|
|
257
|
+
})
|
|
258
|
+
} else {
|
|
259
|
+
results.push({
|
|
260
|
+
name: 'Activity Templates',
|
|
261
|
+
status: 'warning',
|
|
262
|
+
message: 'Could not fetch templates',
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
} catch {
|
|
266
|
+
results.push({
|
|
267
|
+
name: 'Activity Templates',
|
|
268
|
+
status: 'warning',
|
|
269
|
+
message: 'Templates endpoint unreachable',
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
results.push({
|
|
274
|
+
name: 'MCP Backend',
|
|
275
|
+
status: 'error',
|
|
276
|
+
message: `Backend returned ${response.status}`,
|
|
277
|
+
details: mcpEndpoint,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
results.push({
|
|
282
|
+
name: 'MCP Backend',
|
|
283
|
+
status: 'warning',
|
|
284
|
+
message: 'Backend unreachable (local mode enabled)',
|
|
285
|
+
details: mcpEndpoint,
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return results
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Check templates directory
|
|
294
|
+
*/
|
|
295
|
+
async function checkTemplates(options: DoctorOptions): Promise<CheckResult[]> {
|
|
296
|
+
const results: CheckResult[] = []
|
|
297
|
+
const fs = await import('fs/promises')
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const config = await loadConfig()
|
|
301
|
+
const templatesDir = config.templatesDir
|
|
302
|
+
|
|
303
|
+
// Check directory exists
|
|
304
|
+
try {
|
|
305
|
+
await fs.access(templatesDir)
|
|
306
|
+
} catch {
|
|
307
|
+
results.push({
|
|
308
|
+
name: 'Templates Directory',
|
|
309
|
+
status: 'warning',
|
|
310
|
+
message: 'Directory not found',
|
|
311
|
+
details: templatesDir,
|
|
312
|
+
fixable: options.fix,
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
if (options.fix) {
|
|
316
|
+
try {
|
|
317
|
+
await fs.mkdir(templatesDir, { recursive: true })
|
|
318
|
+
results[results.length - 1]!.fixed = true
|
|
319
|
+
results[results.length - 1]!.status = 'ok'
|
|
320
|
+
results[results.length - 1]!.message = 'Directory created'
|
|
321
|
+
} catch {
|
|
322
|
+
// Couldn't fix
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return results
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Count and validate templates
|
|
330
|
+
const glob = new Bun.Glob('**/*.json')
|
|
331
|
+
let validCount = 0
|
|
332
|
+
let invalidCount = 0
|
|
333
|
+
const invalidFiles: string[] = []
|
|
334
|
+
|
|
335
|
+
for await (const file of glob.scan({ cwd: templatesDir })) {
|
|
336
|
+
try {
|
|
337
|
+
const content = await Bun.file(`${templatesDir}/${file}`).json()
|
|
338
|
+
|
|
339
|
+
// Basic template validation
|
|
340
|
+
if (content.id && content.name && Array.isArray(content.tasks)) {
|
|
341
|
+
validCount++
|
|
342
|
+
} else {
|
|
343
|
+
invalidCount++
|
|
344
|
+
invalidFiles.push(file)
|
|
345
|
+
}
|
|
346
|
+
} catch {
|
|
347
|
+
invalidCount++
|
|
348
|
+
invalidFiles.push(file)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
results.push({
|
|
353
|
+
name: 'Templates Directory',
|
|
354
|
+
status: 'ok',
|
|
355
|
+
message: templatesDir,
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
if (validCount > 0) {
|
|
359
|
+
results.push({
|
|
360
|
+
name: 'Valid Templates',
|
|
361
|
+
status: 'ok',
|
|
362
|
+
message: `${validCount} template(s) found`,
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (invalidCount > 0) {
|
|
367
|
+
results.push({
|
|
368
|
+
name: 'Invalid Templates',
|
|
369
|
+
status: 'warning',
|
|
370
|
+
message: `${invalidCount} invalid template(s)`,
|
|
371
|
+
details: options.verbose ? invalidFiles.join(', ') : undefined,
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (validCount === 0 && invalidCount === 0) {
|
|
376
|
+
results.push({
|
|
377
|
+
name: 'Templates',
|
|
378
|
+
status: 'warning',
|
|
379
|
+
message: 'No templates found',
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
results.push({
|
|
384
|
+
name: 'Templates',
|
|
385
|
+
status: 'error',
|
|
386
|
+
message: 'Could not check templates',
|
|
387
|
+
details: error instanceof Error ? error.message : String(error),
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return results
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Check configured vessels
|
|
396
|
+
*/
|
|
397
|
+
async function checkVessels(options: DoctorOptions): Promise<CheckResult[]> {
|
|
398
|
+
const results: CheckResult[] = []
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const config = await loadConfig()
|
|
402
|
+
|
|
403
|
+
if (!config.vessels || Object.keys(config.vessels).length === 0) {
|
|
404
|
+
results.push({
|
|
405
|
+
name: 'Vessels',
|
|
406
|
+
status: 'warning',
|
|
407
|
+
message: 'No vessels configured',
|
|
408
|
+
})
|
|
409
|
+
return results
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
for (const [name, vesselConfig] of Object.entries(config.vessels)) {
|
|
413
|
+
if (vesselConfig.type === 'http' && vesselConfig.endpoint) {
|
|
414
|
+
try {
|
|
415
|
+
const response = await fetch(`${vesselConfig.endpoint}/health`, {
|
|
416
|
+
signal: AbortSignal.timeout(3000),
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
if (response.ok) {
|
|
420
|
+
results.push({
|
|
421
|
+
name: `Vessel: ${name}`,
|
|
422
|
+
status: 'ok',
|
|
423
|
+
message: 'Connected',
|
|
424
|
+
details: options.verbose ? vesselConfig.endpoint : undefined,
|
|
425
|
+
})
|
|
426
|
+
} else {
|
|
427
|
+
results.push({
|
|
428
|
+
name: `Vessel: ${name}`,
|
|
429
|
+
status: 'warning',
|
|
430
|
+
message: `Returned ${response.status}`,
|
|
431
|
+
details: vesselConfig.endpoint,
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
} catch {
|
|
435
|
+
results.push({
|
|
436
|
+
name: `Vessel: ${name}`,
|
|
437
|
+
status: 'warning',
|
|
438
|
+
message: 'Unreachable',
|
|
439
|
+
details: vesselConfig.endpoint,
|
|
440
|
+
})
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
results.push({
|
|
444
|
+
name: `Vessel: ${name}`,
|
|
445
|
+
status: 'ok',
|
|
446
|
+
message: `Type: ${vesselConfig.type}`,
|
|
447
|
+
details: options.verbose
|
|
448
|
+
? `Capabilities: ${vesselConfig.capabilities?.join(', ') || 'none'}`
|
|
449
|
+
: undefined,
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} catch (error) {
|
|
454
|
+
results.push({
|
|
455
|
+
name: 'Vessels',
|
|
456
|
+
status: 'error',
|
|
457
|
+
message: 'Could not check vessels',
|
|
458
|
+
details: error instanceof Error ? error.message : String(error),
|
|
459
|
+
})
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return results
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Print check results
|
|
467
|
+
*/
|
|
468
|
+
function printResults(results: CheckResult[], verbose?: boolean): void {
|
|
469
|
+
console.log('\nminibob doctor\n')
|
|
470
|
+
|
|
471
|
+
const statusIcon = {
|
|
472
|
+
ok: '✓',
|
|
473
|
+
warning: '⚠',
|
|
474
|
+
error: '✗',
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const statusColor = {
|
|
478
|
+
ok: '\x1b[32m',
|
|
479
|
+
warning: '\x1b[33m',
|
|
480
|
+
error: '\x1b[31m',
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const reset = '\x1b[0m'
|
|
484
|
+
|
|
485
|
+
for (const result of results) {
|
|
486
|
+
const icon = statusIcon[result.status]
|
|
487
|
+
const color = statusColor[result.status]
|
|
488
|
+
|
|
489
|
+
console.log(`${color}${icon}${reset} ${result.name}: ${result.message}`)
|
|
490
|
+
|
|
491
|
+
if (verbose && result.details) {
|
|
492
|
+
console.log(` ${result.details}`)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (result.fixed) {
|
|
496
|
+
console.log(` (fixed)`)
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Summary
|
|
501
|
+
const okCount = results.filter((r) => r.status === 'ok').length
|
|
502
|
+
const warningCount = results.filter((r) => r.status === 'warning').length
|
|
503
|
+
const errorCount = results.filter((r) => r.status === 'error').length
|
|
504
|
+
|
|
505
|
+
console.log('')
|
|
506
|
+
console.log(
|
|
507
|
+
`Summary: ${okCount} ok, ${warningCount} warnings, ${errorCount} errors`
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
if (errorCount === 0 && warningCount === 0) {
|
|
511
|
+
console.log('\n✓ All checks passed!\n')
|
|
512
|
+
} else if (errorCount > 0) {
|
|
513
|
+
console.log('\n✗ Some checks failed. Run with --fix to attempt repairs.\n')
|
|
514
|
+
} else {
|
|
515
|
+
console.log('\n⚠ Some warnings. System should still work.\n')
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// CLI entry point
|
|
520
|
+
if (import.meta.main) {
|
|
521
|
+
const args = process.argv.slice(2)
|
|
522
|
+
doctor(args).catch((error) => {
|
|
523
|
+
console.error('Doctor failed:', error)
|
|
524
|
+
process.exit(1)
|
|
525
|
+
})
|
|
526
|
+
}
|