osagent-cli 0.1.12 → 0.1.14
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/package.json +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/ui/commands/agentsCommand.js +101 -6
- package/dist/src/ui/commands/agentsCommand.js.map +1 -1
- package/dist/src/ui/commands/doctorCommand.js +528 -77
- package/dist/src/ui/commands/doctorCommand.js.map +1 -1
- package/dist/src/ui/components/OpenAIKeyPrompt.js +48 -21
- package/dist/src/ui/components/OpenAIKeyPrompt.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.js +2 -1
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
- package/dist/src/ui/utils/terminalUI.d.ts +162 -0
- package/dist/src/ui/utils/terminalUI.js +285 -0
- package/dist/src/ui/utils/terminalUI.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -6,142 +6,449 @@
|
|
|
6
6
|
import { CommandKind } from './types.js';
|
|
7
7
|
import { t } from '../../i18n/index.js';
|
|
8
8
|
import { execSync } from 'child_process';
|
|
9
|
-
import { existsSync, readdirSync } from 'fs';
|
|
10
|
-
import { homedir } from 'os';
|
|
9
|
+
import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
10
|
+
import { homedir, platform, release, cpus, totalmem, freemem } from 'os';
|
|
11
11
|
import { join } from 'path';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
import { colors, colorize, statusIcon, createBox, divider, createTree, keyValue, sectionHeader, icons, formatBytes, box, } from '../utils/terminalUI.js';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// SYSTEM INFO
|
|
15
|
+
// ============================================================================
|
|
16
|
+
function getSystemInfo() {
|
|
17
|
+
let npmVersion = 'unknown';
|
|
18
|
+
try {
|
|
19
|
+
npmVersion = execSync('npm --version 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// ignore
|
|
23
|
+
}
|
|
24
|
+
const cpuInfo = cpus();
|
|
25
|
+
const cpuModel = cpuInfo.length > 0 ? cpuInfo[0].model : 'unknown';
|
|
26
|
+
return {
|
|
27
|
+
os: `${platform()} ${release()}`,
|
|
28
|
+
arch: process.arch,
|
|
29
|
+
nodeVersion: process.version,
|
|
30
|
+
npmVersion,
|
|
31
|
+
memory: `${formatBytes(freemem())} free / ${formatBytes(totalmem())} total`,
|
|
32
|
+
cpu: `${cpuModel} (${cpuInfo.length} cores)`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// VERSION CHECKS
|
|
37
|
+
// ============================================================================
|
|
15
38
|
function getCurrentVersion() {
|
|
16
39
|
try {
|
|
40
|
+
// First try to get from package.json in the install location
|
|
17
41
|
const pkg = execSync('npm list -g osagent --json 2>/dev/null', { encoding: 'utf-8' });
|
|
18
42
|
const parsed = JSON.parse(pkg);
|
|
19
43
|
return parsed.dependencies?.osagent?.version || 'unknown';
|
|
20
44
|
}
|
|
21
45
|
catch {
|
|
22
|
-
|
|
46
|
+
// Fallback to checking npm view
|
|
47
|
+
try {
|
|
48
|
+
return execSync('npm view osagent-cli version 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return 'unknown';
|
|
52
|
+
}
|
|
23
53
|
}
|
|
24
54
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Get the latest version from npm
|
|
27
|
-
*/
|
|
28
55
|
function getLatestVersion() {
|
|
29
56
|
try {
|
|
30
|
-
const result = execSync('npm view osagent version 2>/dev/null', { encoding: 'utf-8' });
|
|
57
|
+
const result = execSync('npm view osagent-cli version 2>/dev/null', { encoding: 'utf-8' });
|
|
31
58
|
return result.trim();
|
|
32
59
|
}
|
|
33
60
|
catch {
|
|
34
61
|
return 'unknown';
|
|
35
62
|
}
|
|
36
63
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// HEALTH CHECKS
|
|
66
|
+
// ============================================================================
|
|
40
67
|
function checkNodeVersion() {
|
|
41
68
|
const version = process.version;
|
|
42
69
|
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
43
70
|
if (major >= 20) {
|
|
44
|
-
return { name: 'Node.js', status: 'ok', message: `${version}
|
|
71
|
+
return { name: 'Node.js Runtime', status: 'ok', message: `${version}` };
|
|
45
72
|
}
|
|
46
73
|
else if (major >= 18) {
|
|
47
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
name: 'Node.js Runtime',
|
|
76
|
+
status: 'warning',
|
|
77
|
+
message: `${version} (upgrade recommended)`,
|
|
78
|
+
fix: 'Install Node.js 20+ for best performance',
|
|
79
|
+
};
|
|
48
80
|
}
|
|
49
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
name: 'Node.js Runtime',
|
|
83
|
+
status: 'error',
|
|
84
|
+
message: `${version} (unsupported)`,
|
|
85
|
+
fix: 'Install Node.js 20+ from https://nodejs.org',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function checkOllamaInstalled() {
|
|
89
|
+
try {
|
|
90
|
+
const result = execSync('ollama --version 2>&1', { encoding: 'utf-8', timeout: 5000 });
|
|
91
|
+
const version = result.match(/ollama version ([\d.]+)/)?.[1] || result.trim();
|
|
92
|
+
return { name: 'Ollama CLI', status: 'ok', message: `v${version}` };
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return {
|
|
96
|
+
name: 'Ollama CLI',
|
|
97
|
+
status: 'warning',
|
|
98
|
+
message: 'not installed',
|
|
99
|
+
fix: 'Install from https://ollama.com for local models',
|
|
100
|
+
autoFix: async () => {
|
|
101
|
+
if (platform() === 'darwin') {
|
|
102
|
+
try {
|
|
103
|
+
execSync('brew install ollama', { encoding: 'utf-8', stdio: 'pipe' });
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function checkOllamaRunning() {
|
|
116
|
+
try {
|
|
117
|
+
execSync('curl -s --max-time 2 http://localhost:11434/api/tags', { encoding: 'utf-8' });
|
|
118
|
+
return { name: 'Ollama Server', status: 'ok', message: 'running on :11434' };
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return {
|
|
122
|
+
name: 'Ollama Server',
|
|
123
|
+
status: 'warning',
|
|
124
|
+
message: 'not running',
|
|
125
|
+
fix: 'Run: ollama serve',
|
|
126
|
+
autoFix: async () => {
|
|
127
|
+
try {
|
|
128
|
+
// Start ollama in background using spawn
|
|
129
|
+
const { spawn } = await import('child_process');
|
|
130
|
+
const child = spawn('ollama', ['serve'], {
|
|
131
|
+
detached: true,
|
|
132
|
+
stdio: 'ignore',
|
|
133
|
+
});
|
|
134
|
+
child.unref();
|
|
135
|
+
// Give it a moment to start
|
|
136
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
137
|
+
// Check if it started
|
|
138
|
+
try {
|
|
139
|
+
execSync('curl -s --max-time 2 http://localhost:11434/api/tags', { encoding: 'utf-8' });
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function checkGitInstalled() {
|
|
154
|
+
try {
|
|
155
|
+
const result = execSync('git --version 2>&1', { encoding: 'utf-8', timeout: 5000 });
|
|
156
|
+
const version = result.match(/git version ([\d.]+)/)?.[1] || 'installed';
|
|
157
|
+
return { name: 'Git', status: 'ok', message: `v${version}` };
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return {
|
|
161
|
+
name: 'Git',
|
|
162
|
+
status: 'warning',
|
|
163
|
+
message: 'not installed',
|
|
164
|
+
fix: 'Install Git for version control features',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function checkAPIKeys() {
|
|
169
|
+
const keys = {
|
|
170
|
+
OLLAMA_API_KEY: !!process.env['OLLAMA_API_KEY'],
|
|
171
|
+
GROQ_API_KEY: !!process.env['GROQ_API_KEY'],
|
|
172
|
+
OPENAI_API_KEY: !!process.env['OPENAI_API_KEY'],
|
|
173
|
+
};
|
|
174
|
+
const found = Object.entries(keys).filter(([_, v]) => v).map(([k]) => k);
|
|
175
|
+
if (found.length > 0) {
|
|
176
|
+
return {
|
|
177
|
+
name: 'API Keys',
|
|
178
|
+
status: 'ok',
|
|
179
|
+
message: found.map(k => k.replace('_API_KEY', '')).join(', '),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
name: 'API Keys',
|
|
184
|
+
status: 'warning',
|
|
185
|
+
message: 'none configured',
|
|
186
|
+
fix: 'Set OLLAMA_API_KEY, GROQ_API_KEY, or OPENAI_API_KEY',
|
|
187
|
+
details: [
|
|
188
|
+
'export OLLAMA_API_KEY="your-key"',
|
|
189
|
+
'Or configure in ~/.osagent/settings.json',
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function checkSettings() {
|
|
194
|
+
const settingsPath = join(homedir(), '.osagent', 'settings.json');
|
|
195
|
+
if (existsSync(settingsPath)) {
|
|
196
|
+
try {
|
|
197
|
+
const content = readFileSync(settingsPath, 'utf-8');
|
|
198
|
+
const settings = JSON.parse(content);
|
|
199
|
+
const keys = Object.keys(settings);
|
|
200
|
+
return {
|
|
201
|
+
name: 'Settings File',
|
|
202
|
+
status: 'ok',
|
|
203
|
+
message: `${keys.length} settings configured`,
|
|
204
|
+
details: keys.slice(0, 5),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return {
|
|
209
|
+
name: 'Settings File',
|
|
210
|
+
status: 'error',
|
|
211
|
+
message: 'invalid JSON',
|
|
212
|
+
fix: 'Fix JSON syntax or delete ~/.osagent/settings.json',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
name: 'Settings File',
|
|
218
|
+
status: 'ok',
|
|
219
|
+
message: 'using defaults',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function checkSystemPrompt() {
|
|
223
|
+
const promptPath = join(homedir(), '.osagent', 'system.md');
|
|
224
|
+
if (existsSync(promptPath)) {
|
|
225
|
+
try {
|
|
226
|
+
const content = readFileSync(promptPath, 'utf-8');
|
|
227
|
+
const lines = content.split('\n').length;
|
|
228
|
+
return {
|
|
229
|
+
name: 'System Prompt',
|
|
230
|
+
status: 'ok',
|
|
231
|
+
message: `custom (${lines} lines)`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return {
|
|
236
|
+
name: 'System Prompt',
|
|
237
|
+
status: 'warning',
|
|
238
|
+
message: 'unreadable',
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return { name: 'System Prompt', status: 'ok', message: 'default' };
|
|
50
243
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Check for custom agents
|
|
53
|
-
*/
|
|
54
244
|
function checkCustomAgents() {
|
|
55
245
|
const agentsDir = join(homedir(), '.osagent', 'agents');
|
|
56
246
|
if (existsSync(agentsDir)) {
|
|
57
247
|
try {
|
|
58
248
|
const agents = readdirSync(agentsDir).filter(f => f.endsWith('.json') || f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
59
249
|
if (agents.length > 0) {
|
|
60
|
-
return {
|
|
250
|
+
return {
|
|
251
|
+
name: 'Custom Agents',
|
|
252
|
+
status: 'ok',
|
|
253
|
+
message: `${agents.length} agents`,
|
|
254
|
+
details: agents.slice(0, 5),
|
|
255
|
+
};
|
|
61
256
|
}
|
|
62
257
|
}
|
|
63
258
|
catch {
|
|
64
259
|
// ignore
|
|
65
260
|
}
|
|
66
261
|
}
|
|
67
|
-
return { name: 'Custom Agents', status: 'ok', message: '
|
|
262
|
+
return { name: 'Custom Agents', status: 'ok', message: 'none' };
|
|
68
263
|
}
|
|
69
|
-
/**
|
|
70
|
-
* Check for custom commands
|
|
71
|
-
*/
|
|
72
264
|
function checkCustomCommands() {
|
|
73
265
|
const commandsDir = join(homedir(), '.osagent', 'commands');
|
|
74
266
|
if (existsSync(commandsDir)) {
|
|
75
267
|
try {
|
|
76
|
-
const commands = readdirSync(commandsDir).filter(f => f.endsWith('.js') || f.endsWith('.ts'));
|
|
268
|
+
const commands = readdirSync(commandsDir).filter(f => f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.toml'));
|
|
77
269
|
if (commands.length > 0) {
|
|
78
|
-
return {
|
|
270
|
+
return {
|
|
271
|
+
name: 'Custom Commands',
|
|
272
|
+
status: 'ok',
|
|
273
|
+
message: `${commands.length} commands`,
|
|
274
|
+
details: commands.slice(0, 5),
|
|
275
|
+
};
|
|
79
276
|
}
|
|
80
277
|
}
|
|
81
278
|
catch {
|
|
82
279
|
// ignore
|
|
83
280
|
}
|
|
84
281
|
}
|
|
85
|
-
return { name: 'Custom Commands', status: 'ok', message: '
|
|
282
|
+
return { name: 'Custom Commands', status: 'ok', message: 'none' };
|
|
86
283
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
284
|
+
function checkMCPServers() {
|
|
285
|
+
const mcpPath = join(homedir(), '.osagent', 'mcp.json');
|
|
286
|
+
if (existsSync(mcpPath)) {
|
|
287
|
+
try {
|
|
288
|
+
const content = readFileSync(mcpPath, 'utf-8');
|
|
289
|
+
const mcp = JSON.parse(content);
|
|
290
|
+
const servers = Object.keys(mcp.mcpServers || {});
|
|
291
|
+
if (servers.length > 0) {
|
|
292
|
+
return {
|
|
293
|
+
name: 'MCP Servers',
|
|
294
|
+
status: 'ok',
|
|
295
|
+
message: `${servers.length} configured`,
|
|
296
|
+
details: servers.slice(0, 5),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return {
|
|
302
|
+
name: 'MCP Servers',
|
|
303
|
+
status: 'warning',
|
|
304
|
+
message: 'invalid config',
|
|
305
|
+
fix: 'Fix ~/.osagent/mcp.json syntax',
|
|
306
|
+
};
|
|
307
|
+
}
|
|
94
308
|
}
|
|
95
|
-
return { name: '
|
|
309
|
+
return { name: 'MCP Servers', status: 'ok', message: 'none' };
|
|
96
310
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
311
|
+
function checkDiskSpace() {
|
|
312
|
+
try {
|
|
313
|
+
if (platform() === 'darwin' || platform() === 'linux') {
|
|
314
|
+
const result = execSync('df -h ~ | tail -1', { encoding: 'utf-8' });
|
|
315
|
+
const parts = result.trim().split(/\s+/);
|
|
316
|
+
const available = parts[3];
|
|
317
|
+
const usePercent = parseInt(parts[4], 10);
|
|
318
|
+
if (usePercent > 90) {
|
|
319
|
+
return {
|
|
320
|
+
name: 'Disk Space',
|
|
321
|
+
status: 'warning',
|
|
322
|
+
message: `${available} free (${usePercent}% used)`,
|
|
323
|
+
fix: 'Free up disk space',
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
name: 'Disk Space',
|
|
328
|
+
status: 'ok',
|
|
329
|
+
message: `${available} free`,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
104
332
|
}
|
|
105
|
-
|
|
333
|
+
catch {
|
|
334
|
+
// ignore
|
|
335
|
+
}
|
|
336
|
+
return { name: 'Disk Space', status: 'ok', message: 'unknown' };
|
|
106
337
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
function
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// FORMATTERS
|
|
340
|
+
// ============================================================================
|
|
341
|
+
function formatReport(checks, systemInfo, currentVersion, latestVersion, autoFix = false) {
|
|
111
342
|
const lines = [];
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
lines.push('
|
|
343
|
+
const width = 60;
|
|
344
|
+
// Header
|
|
345
|
+
lines.push('');
|
|
346
|
+
const headerBox = createBox('OS Agent Doctor', width);
|
|
347
|
+
lines.push(headerBox.top);
|
|
348
|
+
lines.push(headerBox.line(centerPad(`${icons.doctor} System Health Report`, width - 4)));
|
|
349
|
+
lines.push(headerBox.bottom);
|
|
115
350
|
lines.push('');
|
|
116
351
|
// Version info
|
|
117
|
-
const updateAvailable =
|
|
118
|
-
|
|
352
|
+
const updateAvailable = currentVersion !== 'unknown' &&
|
|
353
|
+
latestVersion !== 'unknown' &&
|
|
354
|
+
currentVersion !== latestVersion;
|
|
355
|
+
lines.push(sectionHeader('Version', icons.package));
|
|
356
|
+
lines.push(divider(width));
|
|
357
|
+
lines.push(keyValue('Installed', currentVersion));
|
|
358
|
+
if (updateAvailable) {
|
|
359
|
+
lines.push(keyValue('Available', colorize(latestVersion, colors.green) + ' ' + colorize('(update available)', colors.yellow)));
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
lines.push(keyValue('Status', colorize('up to date', colors.green)));
|
|
363
|
+
}
|
|
364
|
+
lines.push('');
|
|
365
|
+
// System info
|
|
366
|
+
lines.push(sectionHeader('System', icons.terminal));
|
|
367
|
+
lines.push(divider(width));
|
|
368
|
+
lines.push(keyValue('OS', systemInfo.os));
|
|
369
|
+
lines.push(keyValue('Architecture', systemInfo.arch));
|
|
370
|
+
lines.push(keyValue('Node.js', systemInfo.nodeVersion));
|
|
371
|
+
lines.push(keyValue('npm', systemInfo.npmVersion));
|
|
372
|
+
lines.push(keyValue('Memory', systemInfo.memory));
|
|
119
373
|
lines.push('');
|
|
120
374
|
// Health checks
|
|
121
|
-
lines.push('Health Checks
|
|
375
|
+
lines.push(sectionHeader('Health Checks', icons.shield));
|
|
376
|
+
lines.push(divider(width));
|
|
377
|
+
let errorCount = 0;
|
|
378
|
+
let warningCount = 0;
|
|
379
|
+
const fixes = [];
|
|
122
380
|
for (const check of checks) {
|
|
123
|
-
const icon = check.status
|
|
124
|
-
|
|
381
|
+
const icon = statusIcon(check.status);
|
|
382
|
+
const statusColor = check.status === 'ok' ? colors.green :
|
|
383
|
+
check.status === 'warning' ? colors.yellow : colors.red;
|
|
384
|
+
lines.push(`${icon} ${colorize(check.name.padEnd(20), colors.bold)} ${colorize(check.message, statusColor)}`);
|
|
385
|
+
if (check.details && check.details.length > 0) {
|
|
386
|
+
for (const detail of check.details) {
|
|
387
|
+
lines.push(` ${colorize(box.pipe, colors.gray)} ${colorize(detail, colors.gray)}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (check.status === 'error')
|
|
391
|
+
errorCount++;
|
|
392
|
+
if (check.status === 'warning')
|
|
393
|
+
warningCount++;
|
|
125
394
|
if (check.fix) {
|
|
126
|
-
|
|
395
|
+
fixes.push(`${check.name}: ${check.fix}`);
|
|
127
396
|
}
|
|
128
397
|
}
|
|
129
398
|
lines.push('');
|
|
130
|
-
|
|
131
|
-
lines.push('
|
|
132
|
-
lines.push(
|
|
133
|
-
|
|
134
|
-
|
|
399
|
+
// Summary
|
|
400
|
+
lines.push(sectionHeader('Summary', icons.sparkle));
|
|
401
|
+
lines.push(divider(width));
|
|
402
|
+
const total = checks.length;
|
|
403
|
+
const passed = total - errorCount - warningCount;
|
|
404
|
+
if (errorCount === 0 && warningCount === 0) {
|
|
405
|
+
lines.push(colorize(`${icons.success} All ${total} checks passed!`, colors.green, colors.bold));
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
lines.push(`${colorize(`${passed}`, colors.green)} passed, ` +
|
|
409
|
+
`${colorize(`${warningCount}`, colors.yellow)} warnings, ` +
|
|
410
|
+
`${colorize(`${errorCount}`, colors.red)} errors`);
|
|
411
|
+
}
|
|
412
|
+
lines.push('');
|
|
413
|
+
// Recommended fixes
|
|
414
|
+
if (fixes.length > 0) {
|
|
415
|
+
lines.push(sectionHeader('Recommended Actions', icons.wrench));
|
|
416
|
+
lines.push(divider(width));
|
|
417
|
+
fixes.forEach((fix, i) => {
|
|
418
|
+
lines.push(` ${colorize(`${i + 1}.`, colors.cyan)} ${fix}`);
|
|
419
|
+
});
|
|
420
|
+
lines.push('');
|
|
421
|
+
if (!autoFix) {
|
|
422
|
+
lines.push(colorize('Run /doctor fix to auto-fix issues', colors.gray, colors.italic));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Quick actions
|
|
135
426
|
if (updateAvailable) {
|
|
136
427
|
lines.push('');
|
|
137
|
-
lines.push(
|
|
428
|
+
lines.push(colorize(`${icons.update} Update available! Run: /doctor update`, colors.cyan));
|
|
138
429
|
}
|
|
139
|
-
|
|
430
|
+
lines.push('');
|
|
431
|
+
lines.push(sectionHeader('Quick Commands', icons.lightning));
|
|
432
|
+
lines.push(divider(width));
|
|
433
|
+
lines.push(` ${colorize('/doctor update', colors.cyan)} - Update to latest version`);
|
|
434
|
+
lines.push(` ${colorize('/doctor fix', colors.cyan)} - Auto-fix issues`);
|
|
435
|
+
lines.push(` ${colorize('/doctor init', colors.cyan)} - Initialize config directory`);
|
|
436
|
+
lines.push('');
|
|
437
|
+
return lines;
|
|
438
|
+
}
|
|
439
|
+
function centerPad(text, width) {
|
|
440
|
+
const stripped = text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
441
|
+
const padding = Math.max(0, width - stripped.length);
|
|
442
|
+
const leftPad = Math.floor(padding / 2);
|
|
443
|
+
return ' '.repeat(leftPad) + text;
|
|
140
444
|
}
|
|
445
|
+
// ============================================================================
|
|
446
|
+
// COMMAND DEFINITION
|
|
447
|
+
// ============================================================================
|
|
141
448
|
export const doctorCommand = {
|
|
142
449
|
name: 'doctor',
|
|
143
450
|
get description() {
|
|
144
|
-
return t('Check system health,
|
|
451
|
+
return t('Check system health, run diagnostics, and auto-fix issues');
|
|
145
452
|
},
|
|
146
453
|
kind: CommandKind.BUILT_IN,
|
|
147
454
|
subCommands: [
|
|
@@ -152,58 +459,192 @@ export const doctorCommand = {
|
|
|
152
459
|
},
|
|
153
460
|
kind: CommandKind.BUILT_IN,
|
|
154
461
|
action: async () => {
|
|
462
|
+
const lines = [];
|
|
463
|
+
lines.push('');
|
|
464
|
+
lines.push(sectionHeader('Updating OS Agent...', icons.update));
|
|
465
|
+
lines.push(divider(50));
|
|
155
466
|
try {
|
|
156
|
-
|
|
467
|
+
lines.push(`${spinnerChar()} Checking current version...`);
|
|
468
|
+
const oldVersion = getCurrentVersion();
|
|
469
|
+
lines.push(`${spinnerChar()} Installing latest version...`);
|
|
470
|
+
execSync('npm install -g osagent@latest 2>&1', { encoding: 'utf-8', stdio: 'pipe' });
|
|
157
471
|
const newVersion = getLatestVersion();
|
|
472
|
+
lines.push('');
|
|
473
|
+
lines.push(colorize(`${icons.success} Updated successfully!`, colors.green, colors.bold));
|
|
474
|
+
lines.push('');
|
|
475
|
+
lines.push(keyValue('Previous', oldVersion));
|
|
476
|
+
lines.push(keyValue('Current', colorize(newVersion, colors.green)));
|
|
477
|
+
lines.push('');
|
|
478
|
+
lines.push(colorize('Restart osagent to use the new version.', colors.yellow));
|
|
158
479
|
return {
|
|
159
480
|
type: 'message',
|
|
160
481
|
messageType: 'info',
|
|
161
|
-
content:
|
|
482
|
+
content: lines.join('\n'),
|
|
162
483
|
};
|
|
163
484
|
}
|
|
164
|
-
catch (
|
|
485
|
+
catch (error) {
|
|
486
|
+
lines.push('');
|
|
487
|
+
lines.push(colorize(`${icons.error} Update failed`, colors.red, colors.bold));
|
|
488
|
+
lines.push('');
|
|
489
|
+
lines.push('Try manually:');
|
|
490
|
+
lines.push(colorize(' sudo npm install -g osagent@latest', colors.cyan));
|
|
165
491
|
return {
|
|
166
492
|
type: 'message',
|
|
167
493
|
messageType: 'error',
|
|
168
|
-
content:
|
|
494
|
+
content: lines.join('\n'),
|
|
169
495
|
};
|
|
170
496
|
}
|
|
171
497
|
},
|
|
172
498
|
},
|
|
499
|
+
{
|
|
500
|
+
name: 'fix',
|
|
501
|
+
get description() {
|
|
502
|
+
return t('Automatically fix issues where possible');
|
|
503
|
+
},
|
|
504
|
+
kind: CommandKind.BUILT_IN,
|
|
505
|
+
action: async () => {
|
|
506
|
+
const lines = [];
|
|
507
|
+
lines.push('');
|
|
508
|
+
lines.push(sectionHeader('Auto-fixing issues...', icons.wrench));
|
|
509
|
+
lines.push(divider(50));
|
|
510
|
+
const checks = [
|
|
511
|
+
checkNodeVersion(),
|
|
512
|
+
checkOllamaInstalled(),
|
|
513
|
+
checkOllamaRunning(),
|
|
514
|
+
checkGitInstalled(),
|
|
515
|
+
checkSettings(),
|
|
516
|
+
checkAPIKeys(),
|
|
517
|
+
];
|
|
518
|
+
let fixed = 0;
|
|
519
|
+
let failed = 0;
|
|
520
|
+
for (const check of checks) {
|
|
521
|
+
if (check.status !== 'ok' && check.autoFix) {
|
|
522
|
+
lines.push(`${spinnerChar()} Fixing: ${check.name}...`);
|
|
523
|
+
try {
|
|
524
|
+
const success = await check.autoFix();
|
|
525
|
+
if (success) {
|
|
526
|
+
lines.push(` ${statusIcon('ok')} ${check.name} fixed`);
|
|
527
|
+
fixed++;
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
lines.push(` ${statusIcon('warning')} ${check.name} - manual fix needed`);
|
|
531
|
+
if (check.fix) {
|
|
532
|
+
lines.push(` ${colorize(icons.arrow, colors.gray)} ${check.fix}`);
|
|
533
|
+
}
|
|
534
|
+
failed++;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
lines.push(` ${statusIcon('error')} ${check.name} - fix failed`);
|
|
539
|
+
failed++;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
else if (check.status !== 'ok' && check.fix) {
|
|
543
|
+
lines.push(`${statusIcon('warning')} ${check.name} - manual fix needed`);
|
|
544
|
+
lines.push(` ${colorize(icons.arrow, colors.gray)} ${check.fix}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Initialize directory structure if missing
|
|
548
|
+
const baseDir = join(homedir(), '.osagent');
|
|
549
|
+
const dirs = ['agents', 'commands', 'prompts', 'memory'];
|
|
550
|
+
for (const dir of dirs) {
|
|
551
|
+
const path = join(baseDir, dir);
|
|
552
|
+
if (!existsSync(path)) {
|
|
553
|
+
mkdirSync(path, { recursive: true });
|
|
554
|
+
lines.push(`${statusIcon('ok')} Created ${dir}/ directory`);
|
|
555
|
+
fixed++;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
lines.push('');
|
|
559
|
+
lines.push(divider(50));
|
|
560
|
+
if (fixed > 0) {
|
|
561
|
+
lines.push(colorize(`${icons.success} ${fixed} issues fixed`, colors.green));
|
|
562
|
+
}
|
|
563
|
+
if (failed > 0) {
|
|
564
|
+
lines.push(colorize(`${icons.warning} ${failed} issues need manual attention`, colors.yellow));
|
|
565
|
+
}
|
|
566
|
+
if (fixed === 0 && failed === 0) {
|
|
567
|
+
lines.push(colorize(`${icons.success} No issues to fix!`, colors.green));
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
type: 'message',
|
|
571
|
+
messageType: 'info',
|
|
572
|
+
content: lines.join('\n'),
|
|
573
|
+
};
|
|
574
|
+
},
|
|
575
|
+
},
|
|
173
576
|
{
|
|
174
577
|
name: 'init',
|
|
175
578
|
get description() {
|
|
176
|
-
return t('Initialize ~/.osagent directory structure');
|
|
579
|
+
return t('Initialize ~/.osagent directory with recommended structure');
|
|
177
580
|
},
|
|
178
581
|
kind: CommandKind.BUILT_IN,
|
|
179
582
|
action: async () => {
|
|
180
|
-
const
|
|
583
|
+
const lines = [];
|
|
584
|
+
lines.push('');
|
|
585
|
+
lines.push(sectionHeader('Initializing OS Agent...', icons.gear));
|
|
586
|
+
lines.push(divider(50));
|
|
181
587
|
const baseDir = join(homedir(), '.osagent');
|
|
182
|
-
const dirs = ['agents', 'commands', 'prompts'];
|
|
588
|
+
const dirs = ['agents', 'commands', 'prompts', 'memory', 'skills'];
|
|
183
589
|
try {
|
|
184
590
|
for (const dir of dirs) {
|
|
185
591
|
const path = join(baseDir, dir);
|
|
186
592
|
mkdirSync(path, { recursive: true });
|
|
593
|
+
lines.push(`${statusIcon('ok')} Created ${colorize(dir + '/', colors.cyan)}`);
|
|
187
594
|
}
|
|
188
|
-
// Create example agent
|
|
595
|
+
// Create example agent
|
|
189
596
|
const exampleAgent = {
|
|
190
597
|
name: 'example-agent',
|
|
191
598
|
description: 'An example custom agent',
|
|
192
599
|
systemPrompt: 'You are a helpful assistant specialized in...',
|
|
193
600
|
model: 'qwen3-coder:480b-cloud',
|
|
601
|
+
tier: 'specialist',
|
|
194
602
|
};
|
|
195
|
-
|
|
603
|
+
const agentPath = join(baseDir, 'agents', 'example-agent.json');
|
|
604
|
+
if (!existsSync(agentPath)) {
|
|
605
|
+
writeFileSync(agentPath, JSON.stringify(exampleAgent, null, 2));
|
|
606
|
+
lines.push(`${statusIcon('ok')} Created ${colorize('example-agent.json', colors.cyan)}`);
|
|
607
|
+
}
|
|
608
|
+
// Create example skill
|
|
609
|
+
const exampleSkill = {
|
|
610
|
+
name: 'example-skill',
|
|
611
|
+
description: 'An example custom skill',
|
|
612
|
+
triggers: [{ type: 'keyword', patterns: ['example', 'demo'] }],
|
|
613
|
+
steps: [
|
|
614
|
+
{ name: 'step1', prompt: 'First step...' },
|
|
615
|
+
{ name: 'step2', prompt: 'Second step...', dependsOn: ['step1'] },
|
|
616
|
+
],
|
|
617
|
+
};
|
|
618
|
+
const skillPath = join(baseDir, 'skills', 'example-skill.json');
|
|
619
|
+
if (!existsSync(skillPath)) {
|
|
620
|
+
writeFileSync(skillPath, JSON.stringify(exampleSkill, null, 2));
|
|
621
|
+
lines.push(`${statusIcon('ok')} Created ${colorize('example-skill.json', colors.cyan)}`);
|
|
622
|
+
}
|
|
623
|
+
lines.push('');
|
|
624
|
+
lines.push(divider(50));
|
|
625
|
+
lines.push(colorize(`${icons.success} Initialization complete!`, colors.green, colors.bold));
|
|
626
|
+
lines.push('');
|
|
627
|
+
lines.push(sectionHeader('Directory Structure', icons.folder));
|
|
628
|
+
lines.push(createTree([
|
|
629
|
+
{ label: colorize('~/.osagent/', colors.cyan), children: [
|
|
630
|
+
{ label: colorize('agents/', colors.blue) + ' - Custom AI agents' },
|
|
631
|
+
{ label: colorize('commands/', colors.blue) + ' - Custom slash commands' },
|
|
632
|
+
{ label: colorize('prompts/', colors.blue) + ' - Custom prompts' },
|
|
633
|
+
{ label: colorize('skills/', colors.blue) + ' - Custom skills/workflows' },
|
|
634
|
+
{ label: colorize('memory/', colors.blue) + ' - Memory storage' },
|
|
635
|
+
] },
|
|
636
|
+
]).join('\n'));
|
|
196
637
|
return {
|
|
197
638
|
type: 'message',
|
|
198
639
|
messageType: 'info',
|
|
199
|
-
content:
|
|
640
|
+
content: lines.join('\n'),
|
|
200
641
|
};
|
|
201
642
|
}
|
|
202
643
|
catch (error) {
|
|
203
644
|
return {
|
|
204
645
|
type: 'message',
|
|
205
646
|
messageType: 'error',
|
|
206
|
-
content:
|
|
647
|
+
content: `${icons.error} Failed to initialize: ${error}`,
|
|
207
648
|
};
|
|
208
649
|
}
|
|
209
650
|
},
|
|
@@ -212,19 +653,29 @@ export const doctorCommand = {
|
|
|
212
653
|
action: async () => {
|
|
213
654
|
const checks = [
|
|
214
655
|
checkNodeVersion(),
|
|
656
|
+
checkOllamaInstalled(),
|
|
657
|
+
checkOllamaRunning(),
|
|
658
|
+
checkGitInstalled(),
|
|
659
|
+
checkDiskSpace(),
|
|
660
|
+
checkAPIKeys(),
|
|
215
661
|
checkSettings(),
|
|
216
662
|
checkSystemPrompt(),
|
|
217
663
|
checkCustomAgents(),
|
|
218
664
|
checkCustomCommands(),
|
|
665
|
+
checkMCPServers(),
|
|
219
666
|
];
|
|
667
|
+
const systemInfo = getSystemInfo();
|
|
220
668
|
const currentVersion = getCurrentVersion();
|
|
221
669
|
const latestVersion = getLatestVersion();
|
|
222
|
-
const report =
|
|
670
|
+
const report = formatReport(checks, systemInfo, currentVersion, latestVersion);
|
|
223
671
|
return {
|
|
224
672
|
type: 'message',
|
|
225
673
|
messageType: 'info',
|
|
226
|
-
content: report,
|
|
674
|
+
content: report.join('\n'),
|
|
227
675
|
};
|
|
228
676
|
},
|
|
229
677
|
};
|
|
678
|
+
function spinnerChar() {
|
|
679
|
+
return colorize('>', colors.cyan);
|
|
680
|
+
}
|
|
230
681
|
//# sourceMappingURL=doctorCommand.js.map
|