Skip to content

Commit 72da6b7

Browse files
feat(cli): add dashboard command for visual skill health overview
New `skill-bus dashboard` command with: - Color-coded skill health table (green/yellow/red) - Trend indicators (↑ improving, ↓ declining, ✗ broken) - Flagged skills section with score bar visualization - Queue summary (pending/running/done counts) - --no-color and --days flags - NO_COLOR env var support Bump version to 1.3.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d3d13c3 commit 72da6b7

File tree

2 files changed

+99
-2
lines changed

2 files changed

+99
-2
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "agent-skill-bus",
3-
"version": "1.2.1",
3+
"version": "1.3.0",
44
"description": "Self-improving task orchestration framework for AI agent systems",
55
"type": "module",
66
"main": "src/index.js",

src/cli.js

100644100755
Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* skill-bus record-run --agent dev --skill api-caller --task "fetch" --result success --score 1.0
1414
* skill-bus flagged [--days 7]
1515
* skill-bus drift
16+
* skill-bus dashboard [--days 7] [--no-color]
1617
* skill-bus diffs [--unprocessed]
1718
* skill-bus locks [--release-expired]
1819
*/
@@ -212,6 +213,101 @@ switch (command) {
212213
break;
213214
}
214215

216+
case 'dashboard': {
217+
const days = parseInt(getFlag('days', '7'), 10);
218+
const noColor = hasFlag('no-color') || process.env.NO_COLOR;
219+
const health = monitor.analyze(days);
220+
const queueStats = queue.stats();
221+
222+
// ANSI helpers
223+
const c = noColor
224+
? { reset: '', bold: '', dim: '', red: '', green: '', yellow: '', blue: '', cyan: '', magenta: '', bgRed: '', bgGreen: '', bgYellow: '' }
225+
: { reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', magenta: '\x1b[35m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m', bgYellow: '\x1b[43m' };
226+
227+
const bar = (score, width = 20) => {
228+
const filled = Math.round(score * width);
229+
const empty = width - filled;
230+
const color = score >= 0.8 ? c.green : score >= 0.6 ? c.yellow : c.red;
231+
return color + '█'.repeat(filled) + c.dim + '░'.repeat(empty) + c.reset;
232+
};
233+
234+
const trendIcon = (trend) => {
235+
switch (trend) {
236+
case 'improving': return c.green + '↑' + c.reset;
237+
case 'declining': return c.red + '↓' + c.reset;
238+
case 'broken': return c.bgRed + c.bold + ' ✗ ' + c.reset;
239+
default: return c.dim + '─' + c.reset;
240+
}
241+
};
242+
243+
const statusDot = (flagged) => flagged
244+
? c.red + '●' + c.reset
245+
: c.green + '●' + c.reset;
246+
247+
// Header
248+
console.log();
249+
console.log(`${c.bold}${c.cyan}╔══════════════════════════════════════════════════════════════╗${c.reset}`);
250+
console.log(`${c.bold}${c.cyan}${c.reset} ${c.bold}Agent Skill Bus — Dashboard${c.reset} ${c.dim}(${days}-day window)${c.reset} ${c.bold}${c.cyan}${c.reset}`);
251+
console.log(`${c.bold}${c.cyan}╚══════════════════════════════════════════════════════════════╝${c.reset}`);
252+
console.log();
253+
254+
// Queue summary
255+
console.log(`${c.bold} Queue${c.reset}`);
256+
console.log(` ${c.dim}├─${c.reset} Total: ${c.bold}${queueStats.total}${c.reset} ${c.dim}${c.reset} Pending: ${c.yellow}${queueStats.byStatus?.pending || 0}${c.reset} ${c.dim}${c.reset} Running: ${c.blue}${queueStats.byStatus?.running || 0}${c.reset} ${c.dim}${c.reset} Done: ${c.green}${queueStats.byStatus?.done || 0}${c.reset}`);
257+
console.log(` ${c.dim}└─${c.reset} Active locks: ${queueStats.activeLocks || 0}`);
258+
console.log();
259+
260+
// Skills table
261+
const entries = Object.entries(health);
262+
if (entries.length === 0) {
263+
console.log(` ${c.dim}No skill runs recorded yet.${c.reset}`);
264+
console.log(` ${c.dim}Run: npx agent-skill-bus record-run --agent my-agent --skill my-skill --task "test" --result success --score 0.9${c.reset}`);
265+
} else {
266+
console.log(`${c.bold} Skills${c.reset} ${c.dim}(${entries.length} tracked)${c.reset}`);
267+
console.log(` ${c.dim}${'─'.repeat(58)}${c.reset}`);
268+
console.log(` ${c.dim} Status Skill Score Trend Runs Fails${c.reset}`);
269+
console.log(` ${c.dim}${'─'.repeat(58)}${c.reset}`);
270+
271+
// Sort: flagged first, then by score ascending
272+
entries.sort((a, b) => {
273+
if (a[1].flagged !== b[1].flagged) return b[1].flagged ? 1 : -1;
274+
return a[1].avgScore - b[1].avgScore;
275+
});
276+
277+
for (const [name, data] of entries) {
278+
const displayName = name.length > 18 ? name.slice(0, 17) + '…' : name.padEnd(18);
279+
const scoreStr = data.avgScore.toFixed(2).padStart(5);
280+
const scoreColor = data.avgScore >= 0.8 ? c.green : data.avgScore >= 0.6 ? c.yellow : c.red;
281+
const runsStr = String(data.runs).padStart(4);
282+
const failsStr = data.consecutiveFails > 0
283+
? c.red + String(data.consecutiveFails).padStart(4) + c.reset
284+
: c.dim + ' 0' + c.reset;
285+
console.log(` ${statusDot(data.flagged)} ${displayName} ${scoreColor}${scoreStr}${c.reset} ${trendIcon(data.trend)} ${runsStr} ${failsStr}`);
286+
}
287+
console.log(` ${c.dim}${'─'.repeat(58)}${c.reset}`);
288+
289+
// Score distribution bar chart
290+
const flaggedCount = entries.filter(([_, d]) => d.flagged).length;
291+
const healthyCount = entries.length - flaggedCount;
292+
console.log();
293+
console.log(` ${c.green}● Healthy: ${healthyCount}${c.reset} ${c.red}● Flagged: ${flaggedCount}${c.reset}`);
294+
295+
if (flaggedCount > 0) {
296+
console.log();
297+
console.log(` ${c.bold}${c.red}⚠ Flagged Skills${c.reset}`);
298+
for (const [name, data] of entries.filter(([_, d]) => d.flagged)) {
299+
const reason = data.trend === 'broken' ? 'consecutive failures'
300+
: data.trend === 'declining' ? 'score declining'
301+
: `avg score ${data.avgScore.toFixed(2)} < 0.70`;
302+
console.log(` ${c.red}${c.reset} ${name}: ${reason} ${bar(data.avgScore, 15)}`);
303+
}
304+
}
305+
}
306+
307+
console.log();
308+
break;
309+
}
310+
215311
case 'diffs': {
216312
if (hasFlag('unprocessed')) {
217313
output(watcher.getUnprocessed());
@@ -239,7 +335,7 @@ switch (command) {
239335
}
240336

241337
default:
242-
console.log(`Agent Skill Bus v1.2.0
338+
console.log(`Agent Skill Bus v1.3.0
243339
244340
Usage: skill-bus <command> [options]
245341
npx agent-skill-bus <command> [options]
@@ -262,6 +358,7 @@ Skill Monitoring:
262358
health Update and show skill health summary
263359
flagged Show skills that need attention
264360
drift Detect silent score degradation
361+
dashboard Visual skill health dashboard (--days N, --no-color)
265362
266363
Knowledge Watcher:
267364
diffs Show diff stats (--unprocessed for pending)

0 commit comments

Comments
 (0)