---
title: "Scaffold the CLI"
description: "Set up commander, register a `review <repoUrl>` command, and parse the argument so we have somewhere to wire the Sandbox lifecycle in the next lessons. Just the skeleton, no Sandbox calls yet."
canonical_url: "https://vercel.com/academy/vercel-sandbox/scaffold-the-cli"
md_url: "https://vercel.com/academy/vercel-sandbox/scaffold-the-cli.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-05-17T05:31:00.154Z"
content_type: "lesson"
course: "vercel-sandbox"
course_title: "Vercel Sandbox"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Scaffold the CLI

# Scaffold the `repo-review` CLI

A reusable function is great. Nobody's typing `pnpm tsx src/sandbox-lifecycle.ts https://github.com/...` every day, though.

We need a command-line surface. Something a person can run from a terminal, something a CI job can call, something with a name. We'll use `commander` because it handles the boring parts (parsing args, generating help text, exit behavior) and we'd rather not.

## Outcome

Create `src/cli.ts` with a `review <repoUrl>` command using `commander`. The command parses its argument and prints what it received. No Sandbox work yet.

## Fast Track

1. Add `commander` to the project.
2. Create `src/cli.ts` and import `Command`.
3. Register a `review <repoUrl>` command whose action logs the URL.

## Hands-on exercise

`commander` is already in the starter's `package.json`, so no install needed. If you were spinning this up from scratch, you'd run:

```bash
pnpm add commander
```

Create `src/cli.ts`:

```ts
import { Command } from 'commander';

const program = new Command();

program
  .name('repo-review')
  .description('Clone and review a GitHub repository in a Sandbox')
  .version('0.1.0');

program
  .command('review <repoUrl>')
  .description('Run a Sandbox review against a GitHub repository URL')
  .action(async (repoUrl: string) => {
    console.log(`Would review: ${repoUrl}`);
  });

program.parse();
```

That's the whole skeleton. The `action` callback is where the Sandbox lifecycle will live in lesson 2.3, but right now we're just confirming the plumbing works. If you run it with a URL and see the URL echoed back, the CLI surface is in place.

Add a script to your `package.json` so we don't have to type the long form every time:

```json title="package.json"
{
  "scripts": {
    "review": "tsx src/cli.ts review"
  }
}
```

Now `pnpm review <url>` runs your CLI.

\*\*Warning: Troubleshooting: command not found\*\*

If `pnpm review` reports "command not found", check that the script is in your `package.json` and that `tsx` is installed (`pnpm add -D tsx` if not). The `commander` part of "command not found" is misleading; the error usually comes from missing scripts, not missing commander.

\*\*Note: Troubleshooting: help text empty\*\*

`program.parse()` reads from `process.argv`. If you run `pnpm review` with no args, commander prints "missing required argument 'repoUrl'", not your description. That's expected behavior, not a bug.

## Try It

```bash
pnpm review https://github.com/vercel/examples
```

Expected output:

```txt
Would review: https://github.com/vercel/examples
```

And without an argument, you should see commander's built-in error:

```bash
pnpm review
```

```txt
error: missing required argument 'repoUrl'
```

Both behaviors are good. The first proves the action runs; the second proves commander is enforcing the argument shape for us.

## Commit

```bash
git add src/cli.ts package.json
git commit -m "feat(cli): scaffold review command with commander"
```

## Done-When

- [ ] `commander` is installed
- [ ] `src/cli.ts` registers a `review <repoUrl>` command
- [ ] `pnpm review <url>` echoes the URL back
- [ ] `pnpm review` (no args) shows commander's missing-argument error

## Solution

```ts title="src/cli.ts"
import { Command } from 'commander';

const program = new Command();

program
  .name('repo-review')
  .description('Clone and review a GitHub repository in a Sandbox')
  .version('0.1.0');

program
  .command('review <repoUrl>')
  .description('Run a Sandbox review against a GitHub repository URL')
  .action(async (repoUrl: string) => {
    console.log(`Would review: ${repoUrl}`);
  });

program.parse();
```

```json title="package.json (scripts section)"
{
  "scripts": {
    "review": "tsx src/cli.ts review"
  }
}
```


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
