> For the complete documentation index, see [llms.txt](https://gotts.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gotts.gitbook.io/docs/prd-shared/cli-architecture.md).

# CLI Architecture

> **Document Type**: SPEC (normative) | **Package**: `packages/cli/` | **Last Updated**: 2026-02-24
>
> Single source of truth for the unified Gotts CLI. All user-facing CLI invocations route through `@gotts.ai`. See [15-install-wizard.md](/docs/gotts-safe-mcp-server/mcp-server/15-install-wizard.md) for the `setup` subcommand spec, [portal/04-local-portal.md](/docs/website/website/portal/04-local-portal.md) for the `portal` subcommand spec.

***

## 1. Overview

`@gotts.ai` is the unified CLI package for all Gotts operations. It lives at `packages/cli/` in the monorepo and publishes to npm as `@gotts.ai`. A single binary (`gotts`) provides Commander-based subcommand routing to every user-facing capability: setup wizard, MCP server, devenv, portal, testnet toolkit, config management, and system diagnostics.

**Design philosophy:** One package to install, one binary to remember. Subcommands replace what were previously separate npm packages (`@gotts.ai/create`, `@gotts.ai/portal`).

```bash
# Zero-install via npx
npx @gotts.ai setup

# Global install
pnpm add -g @gotts.ai
gotts setup
```

***

## 2. Subcommand Registry

Full command tree:

```
gotts setup                     # Install wizard (replaces @gotts.ai/create)
gotts setup --zero              # Zero-dep quick start
gotts setup --ui                # Browser wizard
gotts setup --pair              # Browser pairing mode
gotts setup --self-hosted       # Direct Privy API setup
gotts setup --advanced          # Full control over every option
gotts setup --non-interactive   # CI/CD headless mode

gotts safe                      # Start MCP server (stdio, hot path)
gotts safe --http               # Start MCP server (HTTP transport)
gotts safe inspect              # Launch MCP Inspector

gotts devenv                    # Start Anvil + deploy Uniswap stack
gotts devenv --fresh            # Wipe + redeploy
gotts devenv --ui               # With debug UI

gotts portal                    # Launch agent dashboard
gotts portal --port 4000        # Custom port
gotts portal --no-open          # Don't auto-open browser

gotts testnet <cmd>             # Testnet toolkit
gotts testnet start             # Start Anvil instance
gotts testnet accounts          # List funded accounts
gotts testnet time <seconds>    # Advance block time
gotts testnet mine <blocks>     # Mine blocks

gotts install-mcp               # Inject config into IDE clients
gotts install-mcp --client cursor  # Target specific client
gotts install-mcp --global      # Write to global config
gotts install-mcp --dry-run     # Preview without writing

gotts config show               # Display current configuration
gotts config set <key> <value>  # Set a config value
gotts config reset              # Reset to defaults

gotts doctor                    # System diagnostics
gotts doctor --fix              # Auto-fix common issues
```

### npx Invocation

Every subcommand works via `npx` without global installation:

```bash
npx @gotts.ai setup
npx @gotts.ai safe
npx @gotts.ai portal
npx @gotts.ai install-mcp
npx @gotts.ai doctor
```

***

## 3. Package Strategy

`@gotts.ai` is a **thin Commander routing layer**. It bundles only the minimal dependencies needed for subcommand dispatch and TUI output. Heavy packages are loaded dynamically per subcommand to keep cold-start fast.

### Bundled Dependencies (via tsup `noExternal`)

| Package          | Purpose                      | Size   |
| ---------------- | ---------------------------- | ------ |
| `commander`      | Subcommand routing           | \~30KB |
| `@gotts.ai/tui`  | Spinner, box, logger, banner | \~20KB |
| `@gotts.ai/core` | Config schema, error types   | \~15KB |
| `picocolors`     | Terminal colors              | \~3KB  |

### Dynamic Imports (per subcommand)

| Subcommand    | Dynamic Import                       | Approx Size                   |
| ------------- | ------------------------------------ | ----------------------------- |
| `setup`       | `@gotts.ai/cli/commands/setup`       | \~200KB + Privy SDK on demand |
| `safe`        | `@gotts.ai/safe`                     | \~500KB                       |
| `devenv`      | `@gotts.ai/devenv`                   | \~300KB                       |
| `portal`      | `@gotts.ai/portal`                   | \~150KB + static assets       |
| `testnet`     | `@gotts.ai/testnet`                  | \~100KB                       |
| `install-mcp` | `@gotts.ai/cli/commands/install-mcp` | \~50KB                        |
| `config`      | `@gotts.ai/cli/commands/config`      | \~10KB                        |
| `doctor`      | `@gotts.ai/cli/commands/doctor`      | \~30KB                        |

This architecture means `gotts doctor` loads in <200ms while `gotts safe` loads the full MCP server only when invoked.

### `@gotts.ai/safe` Stays Standalone

`@gotts.ai/safe` remains a separate published package with its own binary. MCP client configurations (Claude Desktop, Cursor, etc.) invoke `@gotts.ai/safe` directly for cold-start speed:

```json
{
  "mcpServers": {
    "uniswap": {
      "command": "npx",
      "args": ["-y", "@gotts.ai/safe@latest"],
      "env": { "GOTTS_PROFILE": "data" }
    }
  }
}
```

MCP clients start the server process on every tool call. The fewer dependencies in the critical path, the faster the first tool response. `@gotts.ai/safe` has no dependency on `@gotts.ai` (the CLI package) -- it is self-contained.

***

## 4. Config Resolution

Configuration is resolved from `~/.gotts/config.json`, extending the `GottsConfig` schema from `@gotts.ai/core`.

### Resolution Order (highest priority first)

1. **CLI flags** -- `gotts safe --http --port 3000`
2. **Environment variables** -- `GOTTS_PROFILE=vault`, `GOTTS_TRANSPORT=http`
3. **`~/.gotts/config.json`** -- persistent user configuration
4. **`.env` file** -- project-level dotenv (walks up to repo root)
5. **Defaults** -- hardcoded in `@gotts.ai/core`

### Config File Schema

```json
{
  "$schema": "https://gotts.ai/schemas/config.json",
  "version": "1.0.0",
  "profile": "vault",
  "transport": "stdio",
  "chains": [1, 8453],
  "wallet": {
    "provider": "privy",
    "walletId": "wlt_abc123xyz",
    "address": "0x1a2B...3c4D"
  },
  "rpc": {
    "ethereum": "https://eth-mainnet.g.alchemy.com/v2/...",
    "base": "https://base-mainnet.g.alchemy.com/v2/..."
  },
  "safety": {
    "maxTxUsd": 1000,
    "maxDailyUsd": 5000,
    "tokenAllowlist": "strict"
  }
}
```

The `gotts config` subcommand manages this file:

```bash
gotts config show                          # Pretty-print current config
gotts config set profile vault             # Set a single key
gotts config set safety.maxTxUsd 5000      # Set nested key
gotts config reset                         # Reset to defaults
```

***

## 5. AI Client Auto-Detection (`install-mcp`)

The `gotts install-mcp` subcommand detects installed AI clients and injects Gotts Safe into their MCP configuration. Detection is based on config file presence:

| Client                   | Config File (global)                                              | Detection Method |
| ------------------------ | ----------------------------------------------------------------- | ---------------- |
| Claude Desktop (macOS)   | `~/Library/Application Support/Claude/claude_desktop_config.json` | File existence   |
| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json`                     | File existence   |
| Claude Code              | `.mcp.json` (project) or `~/.claude.json`                         | File existence   |
| Cursor                   | `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project)     | File existence   |
| VS Code                  | `.vscode/mcp.json` (workspace) or `settings.json`                 | File existence   |
| Windsurf                 | `~/.codeium/windsurf/mcp_config.json`                             | File existence   |
| Continue.dev             | `.continue/config.yaml`                                           | File existence   |
| Cline                    | `cline_mcp_settings.json`                                         | File existence   |
| OpenAI Codex             | `~/.codex/config.toml`                                            | File existence   |
| Zed                      | `~/.config/zed/settings.json`                                     | File existence   |

### Injection Behavior

1. Detect all installed clients (or use `--client <name>` to target one)
2. Read `~/.gotts/config.json` for wallet address and server settings
3. Create timestamped backup of existing config (`mcp.json.bak.<timestamp>`)
4. Merge Gotts Safe entry without overwriting other servers
5. Write back in the correct format (JSON, YAML, or TOML per client)

See [15-install-wizard.md](/docs/gotts-safe-mcp-server/mcp-server/15-install-wizard.md) for the full `install-mcp` specification including per-client config format differences.

***

## 6. Dual MCP Invocation

There are two ways to start the MCP server, optimized for different consumers:

### Direct Path (for MCP client configs)

```json
{
  "command": "npx",
  "args": ["-y", "@gotts.ai/safe@latest"]
}
```

* Fastest cold-start (fewer dependencies to resolve)
* No TUI output (stdio transport requires clean stdout)
* Used by `install-mcp` by default
* Recommended for all MCP client configurations

### CLI Path (for humans)

```bash
gotts safe
gotts safe --http
gotts safe inspect
```

* Richer TUI output (banner, spinner, status)
* Additional subcommands (`inspect`)
* Loads `@gotts.ai/safe` via dynamic import
* Recommended for interactive use

### Why Two Paths

MCP clients spawn a new process for every conversation or tool call. Cold-start latency directly impacts user experience. `@gotts.ai/safe` has a minimal dependency tree optimized for this. The `@gotts.ai` CLI adds TUI, Commander routing, and other subcommands that are unnecessary overhead for MCP client spawning.

`gotts install-mcp` generates the **direct path** by default. Operators can override with `--via-cli` to route through the unified CLI if they prefer consistent logging.

***

## 7. Build Strategy

The CLI package uses `tsup` to produce a single entry point:

```typescript
// packages/cli/tsup.config.ts
import { defineConfig } from "tsup";

export default defineConfig({
  entry: ["src/cli.ts"],
  format: ["esm"],
  target: "node20",
  outDir: "dist",
  noExternal: ["@gotts.ai/tui", "@gotts.ai/core", "picocolors"],
  // All other deps are external (dynamic import or peer)
  banner: {
    js: "#!/usr/bin/env node",
  },
});
```

### package.json

```json
{
  "name": "@gotts.ai",
  "bin": {
    "gotts": "./dist/cli.js"
  },
  "files": ["dist"],
  "dependencies": {
    "commander": "^12.0.0"
  },
  "peerDependencies": {
    "@gotts.ai/safe": "workspace:*",
    "@gotts.ai/devenv": "workspace:*",
    "@gotts.ai/portal": "workspace:*",
    "@gotts.ai/testnet": "workspace:*"
  },
  "peerDependenciesMeta": {
    "@gotts.ai/safe": { "optional": true },
    "@gotts.ai/devenv": { "optional": true },
    "@gotts.ai/portal": { "optional": true },
    "@gotts.ai/testnet": { "optional": true }
  }
}
```

Heavy packages are optional peer dependencies. Each subcommand catches the dynamic import failure and prints an actionable install instruction:

```typescript
async function loadSafe() {
  try {
    return await import("@gotts.ai/safe");
  } catch {
    console.error(
      "Missing @gotts.ai/safe. Install it: pnpm add @gotts.ai/safe",
    );
    process.exit(1);
  }
}
```

***

## 8. Non-Interactive Mode

All subcommands support `--json` for CI/scripting output and `--yes` to skip confirmations:

```bash
# JSON output for scripting
gotts doctor --json | jq '.checks[] | select(.status == "fail")'

# Skip all confirmations
gotts setup --non-interactive --intent vault-participant --wallet-provider privy

# Combined
gotts config show --json
```

### `--json` Flag Behavior

* Suppresses all TUI output (no spinner, no banner, no color)
* Outputs structured JSON to stdout
* Errors output JSON to stderr with `{ "error": "...", "code": "..." }`
* Exit codes: 0 (success), 1 (error), 2 (user cancelled)

### `--yes` Flag Behavior

* Accepts all default values for confirmations
* Does not skip required input (credentials, API keys)
* Equivalent to pressing Enter on every confirmation prompt

***

## 9. Dependency Graph

```
@gotts.ai (CLI package)
  |
  |-- [bundled] commander
  |-- [bundled] @gotts.ai/tui
  |-- [bundled] @gotts.ai/core
  |-- [bundled] picocolors
  |
  |-- [dynamic] gotts setup  -->  internal commands/setup/ module
  |       |-- @clack/prompts
  |       |-- @privy-io/node (on demand)
  |       |-- @gotts.ai/crypto
  |       |-- viem
  |
  |-- [dynamic] gotts safe  -->  @gotts.ai/safe
  |       |-- @modelcontextprotocol/sdk
  |       |-- viem
  |       |-- zod
  |
  |-- [dynamic] gotts devenv  -->  @gotts.ai/devenv
  |       |-- @gotts.ai/testnet
  |       |-- viem
  |
  |-- [dynamic] gotts portal  -->  @gotts.ai/portal (static assets + server)
  |       |-- dotenv
  |       |-- open
  |
  |-- [dynamic] gotts testnet  -->  @gotts.ai/testnet
  |       |-- viem
  |
  |-- [dynamic] gotts install-mcp  -->  internal commands/install-mcp/ module
  |       |-- strip-json-comments
  |       |-- js-yaml
  |       |-- smol-toml
  |
  |-- [dynamic] gotts config  -->  internal commands/config/ module
  |
  |-- [dynamic] gotts doctor  -->  internal commands/doctor/ module
```

***

## 10. Subcommand Routing Implementation

```typescript
// packages/cli/src/cli.ts
import { Command } from "commander";

const program = new Command();

program
  .name("gotts")
  .description("Gotts — Agent Capital Markets powered by Uniswap")
  .version("1.0.0");

// Setup wizard (replaces @gotts.ai/create)
program
  .command("setup")
  .description(
    "Interactive setup wizard — get a Uniswap agent running in 5 minutes",
  )
  .option("--zero", "Zero-dependency quick start")
  .option("--ui", "Browser wizard")
  .option("--pair", "Browser pairing mode")
  .option("--self-hosted", "Direct Privy API setup")
  .option("--advanced", "Full control over every option")
  .option("--non-interactive", "Suppress all prompts")
  .option("--reset", "Delete existing config and start fresh")
  .action(async (options) => {
    const { runSetup } = await import("./commands/setup/index.js");
    await runSetup(options);
  });

// MCP server
program
  .command("safe")
  .description("Start the Gotts Safe MCP server")
  .option("--http", "Use HTTP transport (default: stdio)")
  .option("--port <port>", "HTTP port", "3000")
  .command("inspect")
  .description("Launch MCP Inspector")
  .action(async () => {
    const { runInspect } = await import("./commands/safe/inspect.js");
    await runInspect();
  });

// Devenv
program
  .command("devenv")
  .description("Start Anvil + deploy full Uniswap stack")
  .option("--fresh", "Wipe state and redeploy from scratch")
  .option("--ui", "Launch debug UI")
  .action(async (options) => {
    const { runDevenv } = await import("./commands/devenv/index.js");
    await runDevenv(options);
  });

// Portal
program
  .command("portal")
  .description("Launch the agent management dashboard")
  .option("--port <port>", "Local server port", "3002")
  .option("--no-open", "Don't auto-open browser")
  .action(async (options) => {
    const { runPortal } = await import("./commands/portal/index.js");
    await runPortal(options);
  });

// Install MCP (top-level, not under setup)
program
  .command("install-mcp")
  .description("Inject Gotts Safe config into AI client MCP settings")
  .option("--client <name>", "Target a specific client")
  .option("--global", "Write to global config")
  .option("--local", "Write to project-level config")
  .option("--dry-run", "Preview without writing")
  .action(async (options) => {
    const { runInstallMcp } = await import("./commands/install-mcp/index.js");
    await runInstallMcp(options);
  });

// Config management
const configCmd = program
  .command("config")
  .description("Configuration management");

configCmd
  .command("show")
  .description("Display current configuration")
  .option("--json", "Output as JSON")
  .action(async (options) => {
    const { showConfig } = await import("./commands/config/index.js");
    await showConfig(options);
  });

configCmd
  .command("set <key> <value>")
  .description("Set a config value")
  .action(async (key, value) => {
    const { setConfig } = await import("./commands/config/index.js");
    await setConfig(key, value);
  });

configCmd
  .command("reset")
  .description("Reset to defaults")
  .action(async () => {
    const { resetConfig } = await import("./commands/config/index.js");
    await resetConfig();
  });

// Testnet toolkit
const testnetCmd = program.command("testnet").description("Testnet toolkit");

testnetCmd
  .command("start")
  .description("Start Anvil instance")
  .action(async () => {
    const { startTestnet } = await import("./commands/testnet/index.js");
    await startTestnet();
  });

// Doctor
program
  .command("doctor")
  .description("System diagnostics")
  .option("--fix", "Auto-fix common issues")
  .option("--json", "Output as JSON")
  .action(async (options) => {
    const { runDoctor } = await import("./commands/doctor/index.js");
    await runDoctor(options);
  });

program.parse();
```

***

## Cross-References

* [15-install-wizard.md](/docs/gotts-safe-mcp-server/mcp-server/15-install-wizard.md) -- `setup` subcommand specification
* [portal/04-local-portal.md](/docs/website/website/portal/04-local-portal.md) -- `portal` subcommand specification
* [shared/package-registry.md](/docs/prd-shared/package-registry.md) -- Package registry entry for `@gotts.ai`
* [shared/port-allocation.md](/docs/prd-shared/port-allocation.md) -- Port allocations for devenv, portal, browser wizard
* [mcp-server/11-config.md](/docs/gotts-safe-mcp-server/mcp-server/11-config.md) -- Full configuration schema
