Interactive Shell Feature
Connect to your running application with an interactive REPL
The @justscale/feature-shell package provides an interactive shell for your application. Think SSH, but you're connecting to the Node.js process itself to run commands, inspect state, and interact with your services.
Installation
pnpm add @justscale/feature-shell
pnpm add @justscale/cliBasic Setup
import { createCluster } from '@justscale/cluster';
import { ShellFeature } from '@justscale/feature-shell';
const cluster = createCluster({
features: [
ShellFeature(),
],
});
await cluster.serve({ http: 3000 });Connect to the shell:
justscale shellWhat You Get
The shell feature automatically provides an interactive REPL with:
Built-in Commands
help- Show available commandsinfo- Show application info (Node version, uptime, memory)health- Show health statusping- Check connectionclear- Clear the screenexitorquit- Exit the shell
Command Execution
Any CLI command registered in your application can be executed from within the shell. This includes commands from features like AuthFeature.
Using the Shell
Start the shell and interact with your application:
$ justscale shell
JustScale Interactive Shell
Type 'help' for commands, 'exit' to quit.
justscale> help
Built-in commands:
help Show this help message
info Show app information
health Show health status
ping Check connection
clear Clear the screen
exit Exit the shell
App commands:
auth create-user
auth list-users
justscale> info
┌──────────────┬────────────────────┐
│ Field │ Value │
├──────────────┼────────────────────┤
│ Node Version │ v20.10.0 │
│ Platform │ darwin │
│ Uptime │ 0h 5m 23s │
│ Memory (RSS) │ 45MB │
│ Memory (Heap)│ 12MB │
│ PID │ 12345 │
└──────────────┴────────────────────┘
justscale> health
┌─────────┬─────────┐
│ Check │ Status │
├─────────┼─────────┤
│ Process │ healthy │
│ Memory │ healthy │
└─────────┴─────────┘
justscale> ping
pong
justscale> exit
Goodbye!With AuthFeature
When combined with the AuthFeature, you can manage users directly from the shell:
import { createCluster } from '@justscale/cluster';
import { AuthFeature } from '@justscale/auth';
import { ShellFeature } from '@justscale/feature-shell';
const cluster = createCluster({
features: [
AuthFeature(),
ShellFeature(),
],
});
await cluster.serve({ http: 3000 });Now you can create and manage users from the shell:
justscale> auth create-user --email admin@example.com --password secret123
User created successfully:
ID: abc123
Email: admin@example.com
justscale> auth list-users
┌────────┬─────────────────────┬──────────────────────┐
│ ID │ Email │ Created At │
├────────┼─────────────────────┼──────────────────────┤
│ abc123 │ admin@example.com │ 2024-12-10T10:30:00Z │
└────────┴─────────────────────┴──────────────────────┘How It Works
The shell connects to your running application via a Unix socket (or named pipe on Windows). This allows you to interact with the live process without restarting or making HTTP requests.
Connection Flow
- Your app creates a cluster with
createCluster() - When you call
cluster.serve(), it creates a Unix socket - Running
justscale shellconnects to this socket - Commands are sent to the server and executed in the same process
- Output is streamed back to your terminal in real-time
Info
Creating Custom Shell Commands
Add your own commands by creating CLI controllers:
import { createController } from '@justscale/core';
import { Cli } from '@justscale/cli';
import { MyService } from '../services/my-service';
export const AdminController = createController({
inject: { myService: MyService },
routes: (services) => ({
clearCache: Cli('admin clear-cache').handle(({ io }) => {
services.myService.clearCache();
io.success('Cache cleared!');
}),
stats: Cli('admin stats').handle(({ io }) => {
const stats = services.myService.getStats();
io.table([
{ Metric: 'Total Requests', Value: stats.requests },
{ Metric: 'Cache Hits', Value: stats.cacheHits },
{ Metric: 'Cache Misses', Value: stats.cacheMisses },
]);
}),
restart: Cli('admin restart').handle(({ io }) => {
io.warn('Restarting server...');
process.exit(0); // Let process manager restart
}),
}),
});Use in the shell:
justscale> help
App commands:
admin clear-cache
admin stats
admin restart
justscale> admin stats
┌────────────────┬────────┐
│ Metric │ Value │
├────────────────┼────────┤
│ Total Requests │ 12,543 │
│ Cache Hits │ 8,932 │
│ Cache Misses │ 3,611 │
└────────────────┴────────┘
justscale> admin clear-cache
✓ Cache cleared!Command Arguments
Commands can accept arguments via flags and positional parameters:
import { z } from 'zod';
const QueryArgsSchema = z.object({
limit: z.number().default(10),
offset: z.number().default(0),
search: z.string().optional(),
});
Cli('users search')
.args(QueryArgsSchema)
.handle(({ io, args, userService }) => {
const users = userService.search({
search: args.search,
limit: args.limit,
offset: args.offset,
});
io.table(users.map(u => ({
ID: u.id,
Email: u.email,
Created: u.createdAt,
})));
});Use in the shell:
justscale> users search --limit 5 --search john
┌────────┬──────────────────┬──────────────────────┐
│ ID │ Email │ Created │
├────────┼──────────────────┼──────────────────────┤
│ 1 │ john@example.com │ 2024-12-10T10:00:00Z │
│ 2 │ johnny@test.com │ 2024-12-10T11:00:00Z │
└────────┴──────────────────┴──────────────────────┘IO Utilities
The shell provides rich IO utilities for formatting output:
Cli('demo').handle(({ io }) => {
// Simple logging
io.log('Regular message');
io.success('Success message (green)');
io.error('Error message (red)');
io.warn('Warning message (yellow)');
io.info('Info message (blue)');
// Tables
io.table([
{ Name: 'Alice', Age: 30, City: 'NYC' },
{ Name: 'Bob', Age: 25, City: 'SF' },
]);
// Prompts (for interactive input)
const name = await io.prompt('Enter your name:');
const confirmed = await io.confirm('Are you sure?');
// Spinners
const spinner = io.spinner('Loading...');
await someAsyncOperation();
spinner.stop();
// Progress bars
for (let i = 0; i <= 100; i++) {
io.progress(i, 100, 'Processing');
await sleep(10);
}
});Configuration
The ShellFeature accepts optional configuration:
ShellFeature({
// Reserved for future options like:
// - Custom prompt
// - Additional built-in commands
// - Auth requirements
});Currently, no configuration is required. Future versions may add options for customizing the prompt, adding built-in commands, or requiring authentication.
Security Considerations
Warning
Best practices:
- Run the app as a dedicated user with restricted permissions
- Ensure the socket file is in a protected directory
- Consider adding authentication for sensitive commands
- Audit what commands are available in production
- Use separate configurations for dev vs production
Use Cases
Development
- Create test users without writing seed scripts
- Inspect application state and configuration
- Trigger background jobs manually
- Clear caches and reset state
Operations
- Monitor application health and metrics
- Run database migrations
- Manage feature flags
- Inspect and modify system state
Debugging
- Check service configurations
- Inspect dependency injection container
- Run diagnostic commands
- Test individual components in isolation
Complete Example
import { createCluster } from '@justscale/cluster';
import { createController, createService } from '@justscale/core';
import { ShellFeature } from '@justscale/feature-shell';
import { Cli } from '@justscale/cli';
// Custom service
const StatsService = createService({
inject: {},
factory: () => {
let requests = 0;
return {
increment: () => requests++,
get: () => requests,
reset: () => { requests = 0; },
};
},
});
// Custom CLI controller
const AdminController = createController({
inject: { stats: StatsService },
routes: (services) => ({
stats: Cli('admin stats').handle(({ io }) => {
io.table([
{ Metric: 'Total Requests', Value: services.stats.get() },
]);
}),
reset: Cli('admin reset').handle(({ io }) => {
services.stats.reset();
io.success('Stats reset!');
}),
}),
});
// Create cluster with shell
const cluster = createCluster({
services: [StatsService],
controllers: [AdminController],
features: [ShellFeature()],
});
await cluster.serve({ http: 3000 });
console.log('Server running with interactive shell');
console.log('Run: justscale shell');