Skip to content

Commit 76ad9c8

Browse files
committed
design: make it look nicer
1 parent bc7372a commit 76ad9c8

File tree

1 file changed

+218
-24
lines changed

1 file changed

+218
-24
lines changed

src/Console/InstallCommand.php

Lines changed: 218 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Illuminate\Support\Facades\Process;
1111
use Illuminate\Support\Str;
1212
use Laravel\Prompts\Concerns\Colors;
13+
use Laravel\Prompts\Terminal;
1314
use Laravel\Roster\Enums\Packages;
1415
use Laravel\Roster\Roster;
1516
use Symfony\Component\Console\Attribute\AsCommand;
@@ -54,15 +55,25 @@ class InstallCommand extends Command
5455

5556
protected Roster $roster;
5657

58+
private string $greenTick;
59+
60+
private string $redCross;
61+
62+
private Terminal $terminal;
63+
5764
public function handle(Roster $roster): void
5865
{
66+
$this->terminal = new Terminal;
67+
$this->terminal->initDimensions();
5968
$this->agentsToInstallTo = collect();
6069
$this->idesToInstallTo = collect();
6170
$this->roster = $roster;
6271
$this->colors = new class
6372
{
6473
use Colors;
6574
};
75+
$this->greenTick = $this->colors->green('');
76+
$this->redCross = $this->colors->red('');
6677

6778
$this->projectName = basename(base_path());
6879

@@ -110,7 +121,8 @@ protected function enact(): void
110121

111122
if ($hasOtherIde) {
112123
$this->newLine();
113-
$this->line('Add to your mcp file: ./artisan boost:mcp'); // some ides require absolute
124+
$this->line('Add Boost MCP manually if needed:'); // some ides require absolute
125+
$this->datatable([['Command', base_path('artisan')], ['Args', 'boost:mcp']]);
114126
}
115127
}
116128

@@ -329,22 +341,26 @@ protected function herdMcpPath(): string
329341
private function intro()
330342
{
331343
$this->newline();
332-
$this->line(<<<'HEADER'
333-
██████╗ ██████╗ ██████╗ ███████╗ ████████╗
334-
██╔══██╗ ██╔═══██╗ ██╔═══██╗ ██╔════╝ ╚══██╔══╝
335-
██████╔╝ ██║ ██║ ██║ ██║ ███████╗ ██║
336-
██╔══██╗ ██║ ██║ ██║ ██║ ╚════██║ ██║
337-
██████╔╝ ╚██████╔╝ ╚██████╔╝ ███████║ ██║
338-
╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝
339-
HEADER
340-
);
344+
$header = <<<HEADER
345+
\e[38;2;234;36;16m██████╗ ██████╗ ██████╗ ███████╗ ████████╗
346+
\e[38;2;234;42;22m██╔══██╗ ██╔═══██╗ ██╔═══██╗ ██╔════╝ ╚══██╔══╝
347+
\e[38;2;234;48;28m██████╔╝ ██║ ██║ ██║ ██║ ███████╗ ██║
348+
\e[38;2;234;54;34m██╔══██╗ ██║ ██║ ██║ ██║ ╚════██║ ██║
349+
\e[38;2;234;60;40m██████╔╝ ╚██████╔╝ ╚██████╔╝ ███████║ ██║
350+
\e[38;2;234;66;46m╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝\e[0m
351+
HEADER;
352+
foreach (explode(PHP_EOL, $header) as $i => $line) {
353+
echo "{$line}\n";
354+
}
355+
341356
intro('✦ Laravel Boost :: Install :: We Must Ship ✦');
342357
$this->line(' Let\'s give '.$this->colors->bgYellow($this->colors->black($this->projectName)).' a Boost');
343358
}
344359

345360
private function outro()
346361
{
347362
outro('All done. Enjoy the boost 🚀');
363+
outro('Get the most out of Boost by visiting https://boost.laravel.com/installed');
348364
}
349365

350366
protected function projectPurpose(): string
@@ -528,27 +544,29 @@ protected function enactGuidelines(Collection $composed): void
528544
}
529545

530546
$this->newLine();
531-
$this->info(sprintf('Found %d guidelines and adding to your selected agents', $composed->count()));
532-
$this->line($composed->keys()->join(', ', ' & '));
547+
$this->info(sprintf('Adding %d guidelines to your selected agents', $composed->count()));
548+
$this->grid($composed->keys()->toArray());
533549
$this->newLine();
534550

535551
$successful = [];
536552
$failed = [];
537553
$composedAiGuidelines = $this->compose($composed);
538554

555+
$longestAgentName = max(1, ...$this->agentsToInstallTo->map(fn ($agent) => Str::length(class_basename($agent)))->toArray());
539556
foreach ($this->agentsToInstallTo as $agent) {
540557
$agentName = class_basename($agent);
541-
$this->output->write(" {$agentName}... ");
558+
$displayAgentName = str_pad($agentName, $longestAgentName, ' ', STR_PAD_RIGHT);
559+
$this->output->write(" {$displayAgentName}... ");
542560

543561
try {
544562
$guidelineWriter = new \Laravel\Boost\Install\GuidelineWriter($agent);
545563
$guidelineWriter->write($composedAiGuidelines);
546564

547565
$successful[] = $agentName;
548-
$this->line('');
566+
$this->line($this->greenTick);
549567
} catch (\Exception $e) {
550568
$failed[$agentName] = $e->getMessage();
551-
$this->line('');
569+
$this->line($this->redCross);
552570
}
553571
}
554572

@@ -585,32 +603,208 @@ protected function installingHerdMcp(): bool
585603
return in_array('herd_mcp', $this->boostToInstall, true);
586604
}
587605

606+
protected function datatable(array $data): void
607+
{
608+
if (empty($data)) {
609+
return;
610+
}
611+
612+
// Calculate column widths
613+
$columnWidths = [];
614+
foreach ($data as $row) {
615+
$colIndex = 0;
616+
foreach ($row as $cell) {
617+
$length = mb_strlen((string) $cell);
618+
if (! isset($columnWidths[$colIndex]) || $length > $columnWidths[$colIndex]) {
619+
$columnWidths[$colIndex] = $length;
620+
}
621+
$colIndex++;
622+
}
623+
}
624+
625+
// Add padding
626+
$columnWidths = array_map(fn ($width) => $width + 2, $columnWidths);
627+
628+
// Unicode box drawing characters
629+
$topLeft = '';
630+
$topRight = '';
631+
$bottomLeft = '';
632+
$bottomRight = '';
633+
$horizontal = '';
634+
$vertical = '';
635+
$cross = '';
636+
$topT = '';
637+
$bottomT = '';
638+
$leftT = '';
639+
$rightT = '';
640+
641+
// Draw top border
642+
$topBorder = $topLeft;
643+
foreach ($columnWidths as $index => $width) {
644+
$topBorder .= str_repeat($horizontal, $width);
645+
if ($index < count($columnWidths) - 1) {
646+
$topBorder .= $topT;
647+
}
648+
}
649+
$topBorder .= $topRight;
650+
$this->line($topBorder);
651+
652+
// Draw rows
653+
$rowCount = 0;
654+
foreach ($data as $row) {
655+
$line = $vertical;
656+
$colIndex = 0;
657+
foreach ($row as $cell) {
658+
$cellStr = ($colIndex === 0) ? "\e[1m".$cell."\e[0m" : $cell;
659+
$padding = $columnWidths[$colIndex] - mb_strlen($cell);
660+
$line .= ' '.$cellStr.str_repeat(' ', $padding - 1).$vertical;
661+
$colIndex++;
662+
}
663+
$this->line($line);
664+
665+
// Draw separator between rows (except after last row)
666+
if ($rowCount < count($data) - 1) {
667+
$separator = $leftT;
668+
foreach ($columnWidths as $index => $width) {
669+
$separator .= str_repeat($horizontal, $width);
670+
if ($index < count($columnWidths) - 1) {
671+
$separator .= $cross;
672+
}
673+
}
674+
$separator .= $rightT;
675+
$this->line($separator);
676+
}
677+
$rowCount++;
678+
}
679+
680+
// Draw bottom border
681+
$bottomBorder = $bottomLeft;
682+
foreach ($columnWidths as $index => $width) {
683+
$bottomBorder .= str_repeat($horizontal, $width);
684+
if ($index < count($columnWidths) - 1) {
685+
$bottomBorder .= $bottomT;
686+
}
687+
}
688+
$bottomBorder .= $bottomRight;
689+
$this->line($bottomBorder);
690+
}
691+
692+
protected function grid(array $items): void
693+
{
694+
if (empty($items)) {
695+
return;
696+
}
697+
698+
// Get terminal width
699+
$terminalWidth = $this->terminal->cols() ?? 80;
700+
701+
// Calculate the longest item length
702+
$maxItemLength = max(array_map('mb_strlen', $items));
703+
704+
// Add padding (2 spaces on each side + 1 for border)
705+
$cellWidth = $maxItemLength + 4;
706+
707+
// Calculate how many cells can fit per row
708+
$cellsPerRow = max(1, (int) floor(($terminalWidth - 1) / ($cellWidth + 1)));
709+
710+
// Unicode box drawing characters
711+
$topLeft = '';
712+
$topRight = '';
713+
$bottomLeft = '';
714+
$bottomRight = '';
715+
$horizontal = '';
716+
$vertical = '';
717+
$cross = '';
718+
$topT = '';
719+
$bottomT = '';
720+
$leftT = '';
721+
$rightT = '';
722+
723+
// Group items into rows
724+
$rows = array_chunk($items, $cellsPerRow);
725+
726+
// Draw top border
727+
$topBorder = $topLeft;
728+
for ($i = 0; $i < $cellsPerRow; $i++) {
729+
$topBorder .= str_repeat($horizontal, $cellWidth);
730+
if ($i < $cellsPerRow - 1) {
731+
$topBorder .= $topT;
732+
}
733+
}
734+
$topBorder .= $topRight;
735+
$this->line($topBorder);
736+
737+
// Draw rows
738+
$rowCount = 0;
739+
foreach ($rows as $row) {
740+
$line = $vertical;
741+
for ($i = 0; $i < $cellsPerRow; $i++) {
742+
if (isset($row[$i])) {
743+
$item = $row[$i];
744+
$padding = $cellWidth - mb_strlen($item) - 2;
745+
$line .= ' '.$item.str_repeat(' ', $padding + 1).$vertical;
746+
} else {
747+
// Empty cell
748+
$line .= str_repeat(' ', $cellWidth).$vertical;
749+
}
750+
}
751+
$this->line($line);
752+
753+
// Draw separator between rows (except after last row)
754+
if ($rowCount < count($rows) - 1) {
755+
$separator = $leftT;
756+
for ($i = 0; $i < $cellsPerRow; $i++) {
757+
$separator .= str_repeat($horizontal, $cellWidth);
758+
if ($i < $cellsPerRow - 1) {
759+
$separator .= $cross;
760+
}
761+
}
762+
$separator .= $rightT;
763+
$this->line($separator);
764+
}
765+
$rowCount++;
766+
}
767+
768+
// Draw bottom border
769+
$bottomBorder = $bottomLeft;
770+
for ($i = 0; $i < $cellsPerRow; $i++) {
771+
$bottomBorder .= str_repeat($horizontal, $cellWidth);
772+
if ($i < $cellsPerRow - 1) {
773+
$bottomBorder .= $bottomT;
774+
}
775+
}
776+
$bottomBorder .= $bottomRight;
777+
$this->line($bottomBorder);
778+
}
779+
588780
protected function enactMcpServers(): void
589781
{
590782
$this->newLine();
591783
$this->info('Installing MCP servers to your selected IDEs');
592784
$this->newLine();
593785

594786
$failed = [];
787+
$longestIdeName = max(1, ...$this->idesToInstallTo->map(fn ($ide) => Str::length(class_basename($ide)))->toArray());
595788

596789
foreach ($this->idesToInstallTo as $ide) {
597790
$ideName = class_basename($ide);
598-
$this->output->write(" {$ideName}... ");
791+
$ideDisplay = str_pad($ideName, $longestIdeName, ' ', STR_PAD_RIGHT);
792+
$this->output->write(" {$ideDisplay}... ");
599793
$results = [];
600794

601795
// Install Laravel Boost MCP if enabled
602796
if ($this->installingMcp()) {
603797
try {
604798
$result = $ide->installMcp('laravel-boost', base_path('artisan'), ['boost:mcp']);
605-
799+
606800
if ($result) {
607-
$results[] = ' Boost';
801+
$results[] = $this->greenTick.' Boost';
608802
} else {
609-
$results[] = ' Boost';
803+
$results[] = $this->redCross.' Boost';
610804
$failed[$ideName]['boost'] = 'Failed to write configuration';
611805
}
612806
} catch (\Exception $e) {
613-
$results[] = ' Boost';
807+
$results[] = $this->redCross.' Boost';
614808
$failed[$ideName]['boost'] = $e->getMessage();
615809
}
616810
}
@@ -619,15 +813,15 @@ protected function enactMcpServers(): void
619813
if ($this->installingHerdMcp()) {
620814
try {
621815
$result = $ide->installMcp('herd', PHP_BINARY, [$this->herdMcpPath()]);
622-
816+
623817
if ($result) {
624-
$results[] = ' Herd';
818+
$results[] = $this->greenTick.' Herd';
625819
} else {
626-
$results[] = ' Herd';
820+
$results[] = $this->redCross.' Herd';
627821
$failed[$ideName]['herd'] = 'Failed to write configuration';
628822
}
629823
} catch (\Exception $e) {
630-
$results[] = ' Herd';
824+
$results[] = $this->redCross.' Herd';
631825
$failed[$ideName]['herd'] = $e->getMessage();
632826
}
633827
}

0 commit comments

Comments
 (0)