@@ -287,8 +287,142 @@ function hostInitOptions(flags) {
287287 } ;
288288}
289289
290- export function format ( payload ) {
291- return `${ JSON . stringify ( payload , null , 2 ) } \n` ;
290+ export function format ( payload , options = { } ) {
291+ if ( options . json === true || ! shouldFormatAsText ( payload ) ) {
292+ return `${ JSON . stringify ( payload , null , 2 ) } \n` ;
293+ }
294+ if ( isHelpPayload ( payload ) ) {
295+ return formatHelpText ( payload ) ;
296+ }
297+ return formatDoctorText ( payload ) ;
298+ }
299+
300+ function isHelpPayload ( payload ) {
301+ return payload != null
302+ && typeof payload . usage === "string"
303+ && Array . isArray ( payload . commands )
304+ && typeof payload . group === "string" ;
305+ }
306+
307+ function isDoctorPayload ( payload ) {
308+ return payload != null
309+ && typeof payload . ok === "boolean"
310+ && Array . isArray ( payload . issues )
311+ && typeof payload . hostConfigPath === "string"
312+ && typeof payload . stateRoot === "string" ;
313+ }
314+
315+ function shouldFormatAsText ( payload ) {
316+ return isHelpPayload ( payload ) || isDoctorPayload ( payload ) ;
317+ }
318+
319+ function formatHelpText ( payload ) {
320+ const lines = [
321+ "Simulator Broker" ,
322+ "" ,
323+ `Usage: ${ payload . usage } ` ,
324+ "" ,
325+ ] ;
326+
327+ if ( payload . group === "global" ) {
328+ const topLevel = new Set ( [ "doctor" ] ) ;
329+ const groups = payload . commands . filter ( ( command ) => ! topLevel . has ( command ) ) ;
330+ const topLevelCommands = payload . commands . filter ( ( command ) => topLevel . has ( command ) ) ;
331+ lines . push ( "Command groups:" ) ;
332+ for ( const command of groups ) {
333+ lines . push ( ` ${ command } ` ) ;
334+ }
335+ if ( topLevelCommands . length > 0 ) {
336+ lines . push ( "" , "Top-level commands:" ) ;
337+ for ( const command of topLevelCommands ) {
338+ lines . push ( ` ${ command } ` ) ;
339+ }
340+ }
341+ lines . push (
342+ "" ,
343+ "Use `simbroker <group> --help` for the commands in a group." ,
344+ "Use `simbroker doctor --help` for doctor usage." ,
345+ ) ;
346+ } else {
347+ lines . push ( "Commands:" ) ;
348+ for ( const command of payload . commands ) {
349+ lines . push ( ` ${ command } ` ) ;
350+ }
351+ }
352+
353+ lines . push (
354+ "" ,
355+ "Pass --json for machine-readable output." ,
356+ "" ,
357+ ) ;
358+ return `${ lines . join ( "\n" ) } ` ;
359+ }
360+
361+ function formatDoctorIssue ( issue ) {
362+ if ( issue == null || typeof issue !== "object" ) {
363+ return "- Unexpected doctor issue. Re-run with --json for details." ;
364+ }
365+
366+ if ( issue . reasonCode === "missing-registry" ) {
367+ return [
368+ "- Registry: missing." ,
369+ " Next: run `simbroker host init --bootstrap-config` if this Mac is not set up yet." ,
370+ ] . join ( "\n" ) ;
371+ }
372+
373+ if ( issue . reasonCode === "alias-unhealthy" ) {
374+ const alias = typeof issue . alias === "string" ? issue . alias : "unknown" ;
375+ const health = typeof issue . health === "string" ? issue . health : "unhealthy" ;
376+ return [
377+ `- Alias ${ alias } : ${ health } .` ,
378+ ` Next: inspect with \`simbroker host status\`, then repair with \`simbroker simulators repair --alias ${ alias } \` if needed.` ,
379+ ] . join ( "\n" ) ;
380+ }
381+
382+ if ( typeof issue . error === "string" && issue . error . trim ( ) !== "" ) {
383+ const reason = typeof issue . reasonCode === "string" ? ` (${ issue . reasonCode } )` : "" ;
384+ return `- ${ issue . error } ${ reason } ` ;
385+ }
386+
387+ if ( typeof issue . reasonCode === "string" ) {
388+ return `- ${ issue . reasonCode } . Re-run \`simbroker doctor --json\` for details.` ;
389+ }
390+
391+ return "- Unexpected doctor issue. Re-run with --json for details." ;
392+ }
393+
394+ function formatDoctorText ( payload ) {
395+ const lines = [
396+ "Simulator Broker doctor" ,
397+ "" ,
398+ `Status: ${ payload . ok ? "healthy" : "needs attention" } ` ,
399+ `Host config: ${ payload . hostConfigPath } ` ,
400+ `State root: ${ payload . stateRoot } ` ,
401+ "" ,
402+ "Checklist:" ,
403+ ] ;
404+
405+ if ( payload . issues . length === 0 ) {
406+ lines . push (
407+ "- Host config: ok" ,
408+ "- Registry: ok" ,
409+ "- Alias health: ok" ,
410+ "" ,
411+ "No issues found." ,
412+ "Next: run `simbroker host status` or `simbroker project init` in a repo." ,
413+ ) ;
414+ } else {
415+ for ( const issue of payload . issues ) {
416+ lines . push ( formatDoctorIssue ( issue ) ) ;
417+ }
418+ lines . push (
419+ "" ,
420+ "Pass --json for the machine-readable issue list." ,
421+ ) ;
422+ }
423+
424+ lines . push ( "" ) ;
425+ return `${ lines . join ( "\n" ) } ` ;
292426}
293427
294428export function eventFilters ( flags ) {
@@ -500,6 +634,49 @@ function helpPayload(group) {
500634 group : "lease" ,
501635 usage : "simbroker lease <command>" ,
502636 } ,
637+ events : {
638+ commands : [
639+ "events watch [--follow] [--json-lines] [--limit <n>] [--after-event-id <id>]" ,
640+ ] ,
641+ group : "events" ,
642+ usage : "simbroker events <command>" ,
643+ } ,
644+ pin : {
645+ commands : [
646+ "pin create --purpose <purpose> --alias <alias> [--repo-root <repo>] [--note <note>]" ,
647+ "pin clear --alias <alias>" ,
648+ ] ,
649+ group : "pin" ,
650+ usage : "simbroker pin <command>" ,
651+ } ,
652+ simulators : {
653+ commands : [
654+ "simulators list" ,
655+ "simulators boot --alias <alias>" ,
656+ "simulators shutdown --alias <alias>" ,
657+ "simulators erase --alias <alias>" ,
658+ "simulators repair --alias <alias>" ,
659+ ] ,
660+ group : "simulators" ,
661+ usage : "simbroker simulators <command>" ,
662+ } ,
663+ service : {
664+ commands : [
665+ "service start" ,
666+ "service status" ,
667+ "service stop" ,
668+ ] ,
669+ group : "service" ,
670+ usage : "simbroker service <command>" ,
671+ } ,
672+ doctor : {
673+ commands : [
674+ "doctor" ,
675+ "doctor --json" ,
676+ ] ,
677+ group : "doctor" ,
678+ usage : "simbroker doctor [--json]" ,
679+ } ,
503680 } ;
504681 return {
505682 ok : true ,
@@ -514,6 +691,7 @@ function helpPayload(group) {
514691 "pin" ,
515692 "simulators" ,
516693 "service" ,
694+ "doctor" ,
517695 ] ,
518696 group : "global" ,
519697 usage : "simbroker <group> <command> [flags]" ,
@@ -1003,6 +1181,11 @@ export function executeBrokerCommand(paths, request) {
10031181 case "help:lease" :
10041182 case "help:capacity" :
10051183 case "help:idle" :
1184+ case "help:events" :
1185+ case "help:pin" :
1186+ case "help:simulators" :
1187+ case "help:service" :
1188+ case "help:doctor" :
10061189 return helpPayload ( request . command ) ;
10071190 case "doctor:status" :
10081191 payload = doctorBroker ( paths , options ) ;
0 commit comments