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
244340Usage: 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
266363Knowledge Watcher:
267364 diffs Show diff stats (--unprocessed for pending)
0 commit comments