osagent-cli 0.1.12 → 0.1.13

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.
@@ -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
- * Get the current installed version of osagent
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
- return 'unknown';
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
- * Check Node.js version
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} (supported)` };
71
+ return { name: 'Node.js Runtime', status: 'ok', message: `${version}` };
45
72
  }
46
73
  else if (major >= 18) {
47
- return { name: 'Node.js', status: 'warning', message: `${version} (upgrade recommended)`, fix: 'Install Node.js 20+' };
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 { name: 'Node.js', status: 'error', message: `${version} (unsupported)`, fix: 'Install Node.js 20+' };
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 { name: 'Custom Agents', status: 'ok', message: `${agents.length} found in ~/.osagent/agents/` };
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: 'None (create in ~/.osagent/agents/)' };
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 { name: 'Custom Commands', status: 'ok', message: `${commands.length} found in ~/.osagent/commands/` };
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: 'None (create in ~/.osagent/commands/)' };
282
+ return { name: 'Custom Commands', status: 'ok', message: 'none' };
86
283
  }
87
- /**
88
- * Check settings file
89
- */
90
- function checkSettings() {
91
- const settingsPath = join(homedir(), '.osagent', 'settings.json');
92
- if (existsSync(settingsPath)) {
93
- return { name: 'Settings', status: 'ok', message: '~/.osagent/settings.json exists' };
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: 'Settings', status: 'ok', message: 'Using defaults (no settings.json)' };
309
+ return { name: 'MCP Servers', status: 'ok', message: 'none' };
96
310
  }
97
- /**
98
- * Check system prompt
99
- */
100
- function checkSystemPrompt() {
101
- const promptPath = join(homedir(), '.osagent', 'system.md');
102
- if (existsSync(promptPath)) {
103
- return { name: 'System Prompt', status: 'ok', message: '~/.osagent/system.md (custom)' };
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
- return { name: 'System Prompt', status: 'ok', message: 'Using default' };
333
+ catch {
334
+ // ignore
335
+ }
336
+ return { name: 'Disk Space', status: 'ok', message: 'unknown' };
106
337
  }
107
- /**
108
- * Format health check results
109
- */
110
- function formatResults(checks, currentVer, latestVer) {
338
+ // ============================================================================
339
+ // FORMATTERS
340
+ // ============================================================================
341
+ function formatReport(checks, systemInfo, currentVersion, latestVersion, autoFix = false) {
111
342
  const lines = [];
112
- lines.push('╭─────────────────────────────────────────╮');
113
- lines.push('│ OS Agent Doctor Report │');
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 = currentVer !== 'unknown' && latestVer !== 'unknown' && currentVer !== latestVer;
118
- lines.push(`Version: ${currentVer}${updateAvailable ? ` → ${latestVer} available` : ' (latest)'}`);
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 === 'ok' ? '✓' : check.status === 'warning' ? '!' : '✗';
124
- lines.push(` ${icon} ${check.name}: ${check.message}`);
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
- lines.push(` Fix: ${check.fix}`);
395
+ fixes.push(`${check.name}: ${check.fix}`);
127
396
  }
128
397
  }
129
398
  lines.push('');
130
- lines.push('Customization Paths:');
131
- lines.push(' ~/.osagent/agents/ - Custom subagents (JSON/YAML)');
132
- lines.push(' ~/.osagent/commands/ - Custom commands (JS/TS)');
133
- lines.push(' ~/.osagent/settings.json - Configuration');
134
- lines.push(' ~/.osagent/system.md - Custom system prompt');
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(`Update available! Run: npm install -g osagent@latest`);
428
+ lines.push(colorize(`${icons.update} Update available! Run: /doctor update`, colors.cyan));
138
429
  }
139
- return lines.join('\n');
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, updates, and configuration');
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
- execSync('npm install -g osagent@latest', { encoding: 'utf-8', stdio: 'pipe' });
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: t(`✓ Updated to osagent@${newVersion}\nRestart osagent to use the new version.`),
482
+ content: lines.join('\n'),
162
483
  };
163
484
  }
164
- catch (_error) {
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: t(`Failed to update. Try manually: sudo npm install -g osagent@latest`),
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 { mkdirSync, writeFileSync } = await import('fs');
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 file
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
- writeFileSync(join(baseDir, 'agents', 'example-agent.json'), JSON.stringify(exampleAgent, null, 2));
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: t(`✓ Initialized ~/.osagent/ with:\n - agents/ (custom subagents)\n - commands/ (custom commands)\n - prompts/ (custom prompts)\n\nExample agent created: ~/.osagent/agents/example-agent.json`),
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: t(`Failed to initialize: ${error}`),
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 = formatResults(checks, currentVersion, latestVersion);
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