Laravel Interactive Shell with Script Repository, System Checks and Laravel Tinker connectivity
NodiShell is a extensible interactive shell for Laravel applications that provides organized script execution, variable management, system monitoring, and development tools in a beautiful terminal interface.
This project is aimed for projects that need to do maintenance on their apps, they can have all their maintenance or support scripts centralised in NodiShell, acting as a "Script Repository", making support and maintenance tasks easier, by also offering the possibility to interact with their outputs which are automatically loaded into Laravel Tinker whenever you open a session through the shell's interface.
- π NodiShell
- Category-based Script Organization - Organize scripts into logical categories
- Interactive Menu System - Beautiful, intuitive navigation with arrow keys
- Variable Management - Store and reuse variables across script executions
- Search Functionality - Quickly find scripts across all categories
- Command History - Track and review executed commands
- Production Safety - Built-in safety checks for production environments
- Raw PHP Execution - Execute PHP code directly with Laravel context
- Laravel Tinker Integration - Enhanced Tinker with NodiShell variables
- System Status Monitoring - Real-time system health checks
- Custom System Checks - Extensible health check system
- Session Persistence - Variables persist throughout your shell session
- Beautiful Interface - Colorful, organized display with emojis and borders
- Autocomplete Support - Smart search and filtering
- Error Handling - Graceful error handling with helpful messages
- Multi-environment Support - Safe operation across development and production
Add NodiShell to your Laravel project:
composer require nodilabs/nodishellPublish the configuration file to customize NodiShell:
php artisan vendor:publish --provider="NodiLabs\NodiShell\NodiShellServiceProvider"Create the required directories in your Laravel application (You can update these directories in the configuration file config/nodishell.php):
mkdir -p app/Console/NodiShell/Categories
mkdir -p app/Console/NodiShell/Scripts
mkdir -p app/Console/NodiShell/ChecksEdit config/nodishell.php to customize settings:
<?php
return [   
    'features' => [
        'search' => true,
        'raw_php' => true,
        'variable_manager' => true,
        'system_status' => true
    ],
    
    'production_safety' => [
        'safe_mode' => true
    ],
    
    'discovery' => [
        'categories_path' => app_path('Console/NodiShell/Categories'),
        'scripts_path' => app_path('Console/NodiShell/Scripts')
    ],
];Launch NodiShell in interactive mode:
php artisan nodishell# Interactive mode (default)
php artisan nodishell
# Execute a specific script directly
php artisan nodishell --script=script-name
# Start in a specific category
php artisan nodishell --category=database
# Enable production safety mode
php artisan nodishell --safe-mode- Arrow Keys: Navigate through menus
- Enter: Select an option
- Type: Search and filter options
- Ctrl+C: Exit at any time
NodiShell provides powerful generator commands to quickly create categories, scripts, and system checks with proper structure and boilerplate code.
All generator commands follow Laravel's convention and include helpful options:
# Available generator commands
php artisan nodishell:category    # Create a new category
php artisan nodishell:script      # Create a new script
php artisan nodishell:check       # Create a new system checkCommon Options:
- --force- Overwrite existing files
- --help- Show detailed command help
Generate a new category with all the required structure:
# Basic category creation
php artisan nodishell:category UserManagement
# With options
php artisan nodishell:category UserManagement \
    --description="User management and administration" \
    --icon="π₯" \
    --color="blue" \
    --sort-order=50Available Options:
- --description=- Category description
- --icon=- Emoji icon for the category
- --color=- Color theme (blue, green, red, yellow, purple, etc.)
- --sort-order=- Display order (default: 100)
Generated Structure:
<?php
namespace App\Console\NodiShell\Categories;
use App\Console\NodiShell\Categories\BaseCategory;
final class UserManagementCategory extends BaseCategory
{
    protected int $sortOrder = 50;
    public function getName(): string
    {
        return 'User management and administration';
    }
    public function getIcon(): string
    {
        return 'π₯';
    }
    public function getColor(): string
    {
        return 'blue';
    }
    protected function loadScripts(): void
    {
        $this->scripts = [
            // Add your scripts here
        ];
    }
}Generate a new script with proper structure and error handling:
# Basic script creation
php artisan nodishell:script ResetUserPassword
# With options
php artisan nodishell:script ResetUserPassword \
    --category="users" \
    --description="Reset a user's password and send notification" \
    --tags="users,password,security,notification" \
    --production-safeAvailable Options:
- --category=- Script category
- --description=- Script description
- --tags=- Comma-separated tags
- --production-safe- Mark as safe for production
Generated Structure:
<?php
namespace App\Console\NodiShell\Scripts;
use App\Console\NodiShell\Scripts\BaseScript;
final class ResetUserPasswordScript extends BaseScript
{
    protected string $name = 'Reset User Password';
    protected string $description = 'Reset a user\'s password and send notification';
    protected string $category = 'users';
    protected bool $productionSafe = true;
    protected array $tags = ['users', 'password', 'security', 'notification'];
    protected array $parameters = [
        // Add your parameters here
    ];
    public function execute(array $parameters = []): mixed
    {
        try {
            $session = $parameters['_session'] ?? null;
            $variables = $parameters['_variables'] ?? [];
            // Your script logic here
            return [
                'success' => true,
                'data' => null,
                'message' => 'Script executed successfully',
            ];
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage(),
            ];
        }
    }
}Generate a new system check for monitoring:
# Basic check creation
php artisan nodishell:check CacheConnection
# With options
php artisan nodishell:check CacheConnection \
    --label="Cache Connection Status" \
    --description="Verify all cache connections are working properly"Available Options:
- --label=- Display label for the check
- --description=- Detailed description
Generated Structure:
<?php
namespace App\Console\NodiShell\Checks;
use NodiLabs\NodiShell\Contracts\SystemCheckInterface;
use NodiLabs\NodiShell\Data\CheckResultData;
final class CacheConnectionCheck implements SystemCheckInterface
{
    public function getLabel(): string
    {
        return 'Cache Connection Status';
    }
    public function getDescription(): string
    {
        return 'Verify all cache connections are working properly';
    }
    public function run(): array
    {
        $results = [];
        try {
            // Your check logic here
            $results[] = new CheckResultData(
                successful: true,
                message: 'Cache Connection Status: OK'
            );
        } catch (\Exception $e) {
            $results[] = new CheckResultData(
                successful: false,
                message: 'Cache Connection Status: FAILED - ' . $e->getMessage()
            );
        }
        return $results;
    }
}Key Benefits of Generators:
β
 Consistent Structure - All files follow project conventions
β
 Smart Naming - Automatically appends appropriate suffixes
β
 Interactive Prompts - Asks for missing information
β
 Type Safety - Includes proper type hints and strict typing
β
 Error Handling - Built-in exception handling
β
 Documentation - Helpful comments and next steps
Scripts are the core executable units in NodiShell. Here's how to create one:
Create a new script in app/Console/NodiShell/Scripts/:
<?php
namespace App\Console\NodiShell\Scripts;
use NodiLabs\NodiShell\Contracts\ScriptInterface;
class MyCustomScript implements ScriptInterface
{
    public function getName(): string
    {
        return 'my-custom-script';
    }
    public function getDescription(): string
    {
        return 'This script demonstrates custom functionality';
    }
    public function getCategory(): string
    {
        return 'custom';
    }
    public function isProductionSafe(): bool
    {
        return false; // Set to true if safe for production
    }
    public function getParameters(): array
    {
        return [
            [
                'name' => 'user_id',
                'label' => 'Enter User ID',
                'required' => true,
            ],
            // As many params as you need
        ];
    }
    public function execute(array $parameters = []): mixed
    {
        // Here your script's logic
    }
}| Method | Description | Required | 
|---|---|---|
| getName() | Unique script identifier | β | 
| getDescription() | Human-readable description | β | 
| getCategory() | Category this script belongs to | β | 
| isProductionSafe() | Whether safe to run in production | β | 
| getParameters() | Array of required parameters | β | 
| execute(array $parameters) | Main execution logic | β | 
Categories organize related scripts together. Here's how to create one:
Create a new category in app/Console/NodiShell/Categories/:
<?php
namespace App\Console\NodiShell\Categories;
use NodiLabs\NodiShell\Contracts\CategoryInterface;
use NodiLabs\NodiShell\Contracts\ScriptInterface;
use Illuminate\Support\Collection;
class CustomCategory implements CategoryInterface
{
    public function getName(): string
    {
        return 'Custom Operations';
    }
    public function getDescription(): string
    {
        return 'Custom business logic and operations';
    }
    public function getIcon(): string
    {
        return 'β‘'; // Choose an appropriate emoji
    }
    public function getSortOrder(): int
    {
        return 100; // Higher numbers appear later in the list
    }
    public function isEnabled(): bool
    {
        return true; // Set to false to disable this category
    }
    public function getScripts(): array
    {
        // Return array of script instances
        return [
            new \App\Console\NodiShell\Scripts\MyCustomScript(),
            new \App\Console\NodiShell\Scripts\AnotherCustomScript(),
            // Add more scripts as needed
        ];
    }
}| Method | Description | Required | 
|---|---|---|
| getName() | Display name for the category | β | 
| getDescription() | Category description | β | 
| getIcon() | Emoji icon for visual identification | β | 
| getSortOrder() | Numeric sort order (0-999) | β | 
| isEnabled() | Whether category is active | β | 
| getScripts() | Array of ScriptInterface instances | β | 
System checks provide health monitoring capabilities:
Create a new check in app/Console/NodiShell/Checks/:
<?php
namespace App\Console\NodiShell\Checks;
use NodiLabs\NodiShell\Contracts\SystemCheckInterface;
use NodiLabs\NodiShell\Data\CheckResultData;
class DatabaseConnectionCheck implements SystemCheckInterface
{
    public function getLabel(): string
    {
        return 'Database Connection';
    }
    public function getDescription(): string
    {
        return 'Verifies that database connections are working properly';
    }
    public function run(): array
    {
        $results = [];
        
        try {
            // Test default connection
            \DB::connection()->getPdo();
            $results[] = new CheckResultData(
                successful: true,
                message: 'Default database connection: OK'
            );
            
            // Test additional connections if configured
            $connections = config('database.connections');
            foreach ($connections as $name => $config) {
                if ($name === config('database.default')) {
                    continue; // Skip default, already tested
                }
                
                try {
                    \DB::connection($name)->getPdo();
                    $results[] = new CheckResultData(
                        successful: true,
                        message: "Connection '{$name}': OK"
                    );
                } catch (\Exception $e) {
                    $results[] = new CheckResultData(
                        successful: false,
                        message: "Connection '{$name}': FAILED - {$e->getMessage()}"
                    );
                }
            }
            
        } catch (\Exception $e) {
            $results[] = new CheckResultData(
                successful: false,
                message: 'Default database connection: FAILED - ' . $e->getMessage()
            );
        }
        
        return $results;
    }
}System checks are automatically discovered from the app/Console/NodiShell/Checks/ directory. Just create your check class and NodiShell will find it automatically.
Alternative: Manual Registration
If you prefer manual registration or need to register checks from other locations, add them to your config file:
// config/nodishell.php
'system_checks' => [
    \App\Console\NodiShell\Checks\DatabaseConnectionCheck::class,
    \App\Console\NodiShell\Checks\CacheConnectionCheck::class,
    \App\Console\NodiShell\Checks\QueueConnectionCheck::class,
],| Method | Description | Required | 
|---|---|---|
| getLabel() | Short name for the check | β | 
| getDescription() | Detailed description | β | 
| run() | Execute check and return CheckResultData[] | β | 
<?php
return [    
    // Feature toggles
    'features' => [
        'search' => true,                    // Global script search
        'raw_php' => true,                   // PHP execution mode
        'variable_manager' => true,          // Session variables
        'system_status' => true,             // System monitoring
        'model_explorer' => true,            // Model inspection
    ],
    
    // Production safety
    'production_safety' => [
        'safe_mode' => true,                 // Enable safety checks
    ],
    
    // Auto-discovery paths
    'discovery' => [
        'categories_path' => app_path('Console/NodiShell/Categories'),
        'scripts_path' => app_path('Console/NodiShell/Scripts'),
        'checks_path' => app_path('Console/NodiShell/Checks'),
    ],
    
    // Manual system checks registration (optional)
    'system_checks' => [
        // \App\Console\NodiShell\Checks\DatabaseConnectionCheck::class,
    ],
];You can also configure NodiShell using environment variables, for example:
NODISHELL_SAFE_MODE=true
NODISHELL_ENABLE_SEARCH=true
NODISHELL_ENABLE_RAW_PHP=falseNodiShell provides persistent variable storage throughout your session:
// In a script's execute method
public function execute(array $parameters = []): mixed
{
    $session = $parameters['_session'];
    
    // Store a variable
    $session->setVariable('my_data', ['key' => 'value']);
    
    // Retrieve a variable
    $data = $session->getVariable('my_data');
    
    // Get all variables
    $allVars = $session->getAllVariables();
    
    return $result;
}NodiShell includes built-in production safety features:
- Safe Mode: Warns about potentially dangerous operations
- Production Checks: Scripts marked as isProductionSafe() = falserequire confirmation
- Environment Detection: Automatic environment-aware behavior
NodiShell enhances Laravel Tinker by:
- Injecting session variables into each new Tinker session
- Providing helper functions like nodishell_vars()to list available variables
- Auto-loading variables with type information when Tinker starts
Note: Variables you create inside Tinker are not automatically saved back to NodiShell when you exit Tinker. Each new Tinker session starts fresh with the current NodiShell variables injected.
NodiShell comes with several built-in categories:
- Database migrations status
- Table inspection tools
- Query execution utilities
- Model inspection
- Relationship exploration
- Data manipulation tools
- Test execution helpers
- Test data generation
- Coverage reporting
- Cache management
- Log rotation
- Cleanup operations
- Fork the repository
- Create a feature branch
- Add your functionality
- Include tests
- Submit a pull request
- Follow PSR-12 coding standards
- Add type hints for all methods
- Include comprehensive docblocks
- Write tests for new functionality
<?php
namespace App\Console\NodiShell\Scripts\Users;
use NodiLabs\NodiShell\Contracts\ScriptInterface;
use App\Models\User;
class ResetUserPasswordScript implements ScriptInterface
{
    public function getName(): string
    {
        return 'reset-user-password';
    }
    public function getDescription(): string
    {
        return 'Reset a user password and send notification';
    }
    public function getCategory(): string
    {
        return 'users';
    }
    public function isProductionSafe(): bool
    {
        return false; // Requires careful handling in production
    }
    public function getParameters(): array
    {
        return [
            [
                'name' => 'email',
                'label' => 'User email address',
                'required' => true,
            ],
            [
                'name' => 'send_notification',
                'label' => 'Send email notification? (y/n)',
                'required' => false,
            ],
        ];
    }
    public function execute(array $parameters = []): mixed
    {
        $email = $parameters['email'];
        $sendNotification = strtolower($parameters['send_notification'] ?? 'y') === 'y';
        
        $user = User::where('email', $email)->first();
        
        if (!$user) {
            throw new \Exception("User with email {$email} not found");
        }
        
        // Generate new password
        $newPassword = \Str::random(12);
        $user->password = \Hash::make($newPassword);
        $user->save();
        
        $result = [
            'user_id' => $user->id,
            'email' => $user->email,
            'new_password' => $newPassword,
            'notification_sent' => false,
        ];
        
        if ($sendNotification) {
            // Send notification logic here
            $user->notify(new \App\Notifications\PasswordResetNotification($newPassword));
            $result['notification_sent'] = true;
        }
        
        return $result;
    }
}Scripts not appearing in categories
- Ensure your script implements ScriptInterface
- Check that the script's getCategory()matches your category key
- Verify the script class is properly namespaced
Category not showing up
- Ensure your category implements CategoryInterface
- Check that isEnabled()returnstrue
- Verify the category file is in the correct directory
System checks not running
- Make sure checks are registered in your service provider
- Verify checks implement SystemCheckInterface
- Check that the run()method returns an array ofCheckResultData
MIT License. See LICENSE file for details.
- Documentation: This README and inline code documentation
- Issues: GitHub Issues for bug reports and feature requests
- Community: Laravel community forums and Discord
Built with β€οΈ (and β) for the Laravel community




