shep

CLI Architecture

Bootstrap Sequence

Entry point: src/presentation/cli/index.ts

The bootstrap() function runs four sequential steps:

  1. Initialize DI containerinitializeContainer() opens SQLite, runs migrations, registers repositories and use cases. Exposes the container on globalThis.__shepContainer for the web UI’s server-side code.
  2. Initialize settings – Resolves InitializeSettingsUseCase from the container, executes it to load/create settings, then calls initializeSettings(settings) to populate the in-memory singleton.
  3. First-run onboarding gate – If running in an interactive TTY and onboarding is not complete, launches the onboarding wizard (onboardingWizard()). The wizard is lazy-imported to avoid startup cost when already complete.
  4. Configure Commander – Creates the root Command('shep'), registers subcommands, calls program.parseAsync(). The default action (no subcommand) starts the daemon via startDaemon().
async function bootstrap() {
  await initializeContainer();
  (globalThis as Record<string, unknown>).__shepContainer = container;

  const initializeSettingsUseCase = container.resolve(InitializeSettingsUseCase);
  const settings = await initializeSettingsUseCase.execute();
  initializeSettings(settings);

  // First-run onboarding gate (TTY only)
  if (process.stdin.isTTY) {
    const onboardingCheck = new CheckOnboardingStatusUseCase();
    const { isComplete } = await onboardingCheck.execute();
    if (!isComplete) {
      const { onboardingWizard } = await import('../tui/wizards/onboarding/onboarding.wizard.js');
      await onboardingWizard();
    }
  }

  const program = new Command()
    .name('shep')
    .version(version, '-v, --version')
    .action(async () => {
      await startDaemon();
    });

  program.addCommand(createVersionCommand());
  program.addCommand(createSettingsCommand());
  // ... all other commands
  await program.parseAsync();
}

reflect-metadata is imported at the very top of the file (before any other imports) as required by tsyringe.

Command Structure Pattern

Every command is a factory function returning a Command instance:

export function createXxxCommand(): Command {
  return new Command('name')
    .description('...')
    .addOption(...)
    .addHelpText('after', '...')
    .action((options) => { ... });
}

Conventions

File Organization

commands/
  version.command.ts              # Top-level command
  run.command.ts                  # shep run
  ui.command.ts                   # shep ui
  start.command.ts                # shep start (daemon)
  stop.command.ts                 # shep stop (daemon)
  restart.command.ts              # shep restart (daemon)
  status.command.ts               # shep status (daemon)
  _serve.command.ts               # shep serve (hidden, internal)
  upgrade.command.ts              # shep upgrade
  install.command.ts              # shep install
  ide-open.command.ts             # shep ide-open
  tools.command.ts                # shep tools (group)
  settings/
    index.ts                      # settings command group
    show.command.ts               # shep settings show
    init.command.ts               # shep settings init
    agent.command.ts              # shep settings agent
    ide.command.ts                # shep settings ide
    workflow.command.ts           # shep settings workflow
    model.command.ts              # shep settings model
  feat/
    index.ts                      # feat command group
    new.command.ts                # shep feat new
    ls.command.ts                 # shep feat ls
    show.command.ts               # shep feat show
    del.command.ts                # shep feat del
    resume.command.ts             # shep feat resume
    review.command.ts             # shep feat review
    approve.command.ts            # shep feat approve
    reject.command.ts             # shep feat reject
    logs.command.ts               # shep feat logs
  agent/
    index.ts                      # agent command group
    ls.command.ts                 # shep agent ls
    show.command.ts               # shep agent show
    stop.command.ts               # shep agent stop
    logs.command.ts               # shep agent logs
    delete.command.ts             # shep agent delete
    approve.command.ts            # shep agent approve
    reject.command.ts             # shep agent reject
  repo/
    index.ts                      # repo command group
    ls.command.ts                 # shep repo ls
    show.command.ts               # shep repo show
  session/
    index.ts                      # session command group
    ls.command.ts                 # shep session ls
    show.command.ts               # shep session show
  daemon/
    start-daemon.ts               # Daemon start logic
    stop-daemon.ts                # Daemon stop logic

To add a new command group:

  1. Create commands/<group>/index.ts with createGroupCommand().
  2. Add subcommand files as <action>.command.ts.
  3. Register via program.addCommand(createGroupCommand()) in index.ts.

DI Integration

Commands access application services through two mechanisms:

Container resolution (for use cases)

import { container } from '@/infrastructure/di/container';
const useCase = container.resolve(SomeUseCase);
await useCase.execute();

Used during bootstrap for InitializeSettingsUseCase.

Settings singleton (for configuration)

import { getSettings } from '@/infrastructure/services/settings.service';
const settings = getSettings(); // Returns Settings object

The getSettings() singleton is the preferred way to access settings in command handlers. It avoids re-resolving from the DI container on every call. The singleton is set once during bootstrap and is read-only thereafter. The settings init command uses resetSettings() + initializeSettings() to replace the singleton in-place.

Error Handling

Command-level errors

Each command action wraps its body in try/catch. On error:

.action((options) => {
  try {
    // command logic
  } catch (error) {
    const err = error instanceof Error ? error : new Error(String(error));
    messages.error('Failed to do X', err);
    process.exitCode = 1;
  }
});

Bootstrap-level errors

Bootstrap wraps the entire sequence in try/catch. Each step has its own inner try/catch that logs the specific error with messages.error(), then re-throws. The outer catch calls process.exit(1).

Global handlers

Registered at module level for safety:

Debug output

Error stack traces are only printed when the DEBUG environment variable is set. This applies to messages.error() and messages.debug().

Help Text Conventions