<!-- Markdown mirror of https://justscale.sh/docs/features/shell -->

# 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

Bash

```bash
pnpm add @justscale/feature-shell
pnpm add @justscale/core/cli
```

## Basic Setup

src/app.tsTypeScript

```typescript
import JustScale from '@justscale/core';
import { ShellFeature } from '@justscale/feature-shell';

const app = JustScale()
  .add(ShellFeature())
  .build();

await app.serve({ http: 3000 });
```

Connect to the shell:

Bash

```bash
justscale shell
```

## What You Get

The shell feature automatically provides an interactive REPL with:

### Built-in Commands

- help - Show available commands
- info - Show application info (Node version, uptime, memory)
- health - Show health status
- ping - Check connection
- clear - Clear the screen
- exit or quit - 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:

Bash

```bash
$ 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:

src/app.tsTypeScript

```typescript
import JustScale from '@justscale/core';
import { AuthFeature } from '@justscale/auth';
import { ShellFeature } from '@justscale/feature-shell';

const app = JustScale()
  .add(AuthFeature())
  .add(ShellFeature())
  .build();

await app.serve({ http: 3000 });
```

Now you can create and manage users from the shell:

Bash

```bash
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 builds with JustScale().add(...).build()
- When you call app.serve(), it creates a Unix socket
- Running justscale shell connects 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

The shell uses the same CLI infrastructure as standalone commands, so any CLI controller you create automatically works in the shell.

## Creating Custom Shell Commands

Add your own commands by creating CLI controllers:

Files

srccontrollersadmin.tsio-demo.tsusers.ts

servicesstats.tsusers.ts

srccontrollersadmin.tsio-demo.tsusers.ts

servicesstats.tsusers.ts

src/controllers/admin.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Cli } from '@justscale/core/cli';
import { StatsService } from '../services/stats';

export const AdminController = createController({
  inject: { stats: StatsService },
  routes: (services) => ({
    clearCache: Cli('admin clear-cache')
      .describe('Clear all in-process caches')
      .handle(({ io }) => {
        services.stats.clearCache();
        io.log('Cache cleared!');
      }),

    stats: Cli('admin stats')
      .describe('Show request and cache stats')
      .handle(({ io }) => {
        const s = services.stats.get();
        io.table([
          { Metric: 'Total Requests', Value: s.requests },
          { Metric: 'Cache Hits',     Value: s.cacheHits },
          { Metric: 'Cache Misses',   Value: s.cacheMisses },
        ]);
      }),

    reset: Cli('admin reset')
      .describe('Reset all stats to zero')
      .handle(({ io }) => {
        services.stats.reset();
        io.log('Stats reset!');
      }),
  }),
});
```

Use in the shell:

Bash

```bash
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:

Files

srccontrollersadmin.tsio-demo.tsusers.ts

servicesstats.tsusers.ts

srccontrollersadmin.tsio-demo.tsusers.ts

servicesstats.tsusers.ts

src/controllers/users.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Cli } from '@justscale/core/cli';
import { z } from 'zod';
import { UserSearchService } from '../services/users';

const QueryArgsSchema = z.object({
  limit:  z.number().default(10),
  offset: z.number().default(0),
  search: z.string().optional(),
});

export const UsersCliController = createController({
  inject: { users: UserSearchService },
  routes: (services) => ({
    search: Cli('users search')
      .describe('Search users by email (with --limit / --search flags)')
      .input(QueryArgsSchema)
      .handle(({ io, args }) => {
        const rows = services.users.search({
          search: args.search,
          limit:  args.limit,
          offset: args.offset,
        });
        io.table(rows.map((u) => ({ ID: u.id, Email: u.email, Created: u.createdAt })));
      }),
  }),
});
```

Use in the shell:

Bash

```bash
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:

Files

srccontrollersadmin.tsio-demo.tsusers.ts

servicesstats.tsusers.ts

srccontrollersadmin.tsio-demo.tsusers.ts

servicesstats.tsusers.ts

src/controllers/io-demo.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Cli } from '@justscale/core/cli';

const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));

// Minimal stand-in — the example is about the IO API, not the work itself.
async function someAsyncOperation(): Promise<void> {
  await sleep(100);
}

export const DemoController = createController({
  inject: {},
  routes: () => ({
    demo: Cli('demo')
      .describe('Walk through every surface of the CLI IO helpers')
      .handle(async ({ io }) => {
        // Simple logging — log/warn/error/debug (verbose-only).
        io.log('Regular message');
        io.warn('Warning message (yellow)');
        io.error('Error message (red, stderr)');
        io.debug('Only printed when --verbose');

        // Tables
        io.table([
          { Name: 'Alice', Age: 30, City: 'NYC' },
          { Name: 'Bob',   Age: 25, City: 'SF'  },
        ]);

        // Prompts (interactive input).
        const name      = await io.prompt('Enter your name:');
        const confirmed = await io.confirm('Are you sure?');
        io.log(`name=${name} confirmed=${confirmed}`);

        // Spinners — success/fail/stop live on the returned handle.
        const spinner = io.spinner('Loading...');
        await someAsyncOperation();
        spinner.success('Loaded');

        // Progress bars — io.progress(label, total?) returns a bar you .update().
        const bar = io.progress('Processing', 100);
        for (let i = 0; i <= 100; i++) {
          bar.update(i);
          await sleep(10);
        }
        bar.complete();
      }),
  }),
});
```

## Configuration

The ShellFeature accepts optional configuration:

shell-config.tsTypeScript

```typescript
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

The shell provides direct access to your application's internals. In production environments, ensure the socket file has appropriate permissions and is not accessible to untrusted users.

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

src/app.tsTypeScript

```typescript
import JustScale, { createController, defineService } from '@justscale/core';
import { ShellFeature } from '@justscale/feature-shell';
import { Cli } from '@justscale/core/cli';

// Custom service
class StatsService extends defineService({
  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.log('Stats reset!');
    }),
  }),
});

// Build app with shell
const app = JustScale()
  .add(StatsService)
  .add(ShellFeature())
  .add(AdminController)
  .build();

await app.serve({ http: 3000 });

console.log('Server running with interactive shell');
console.log('Run: justscale shell');
```

## Next Steps

- Features Overview
- Datastar
- CLI Usage
