# AI Support Agent CLI

[![npm version](https://img.shields.io/npm/v/@ai-support-agent/cli.svg)](https://www.npmjs.com/package/@ai-support-agent/cli)
[![license](https://img.shields.io/npm/l/@ai-support-agent/cli.svg)](https://github.com/mbc-net/ai-support-agent-cli/blob/main/LICENSE)

A daemon that turns your machine into an AI-powered remote agent. Install it, authenticate via browser, and your team can send shell commands, AI chat prompts, file operations, and more — all from a Web UI, with results streamed back in real time.

```
┌──────────────┐  AppSync     ┌─────────────────┐  WebSocket/HTTP  ┌──────────────────┐
│   Web UI     │ ──────────── │   API Server    │ ─────────────── │  Agent CLI       │
│  (browser)   │  subscription│   (NestJS)      │  commands/result │  (your machine)  │
└──────────────┘              └─────────────────┘                  └──────────────────┘
                                                                     ├─ Claude Code
                                                                     ├─ Shell / PTY
                                                                     ├─ File I/O
                                                                     ├─ Git repos
                                                                     ├─ MCP server
                                                                     └─ VS Code tunnel
```

## Why?

- **Remote AI pair-programming** — Ask questions about your codebase from the Web UI; Claude Code runs on the agent machine with full repository access.
- **Multi-tenant, multi-project** — A single agent manages multiple projects across different tenants. Add/remove projects on the fly; the agent hot-reloads.
- **Zero-config realtime** — AppSync WebSocket subscriptions deliver commands instantly. No polling delay, no port forwarding.
- **Secure by default** — OAuth login, 0600 config permissions, nonce-protected callbacks, SQL injection detection, path traversal guards, and environment sanitization.

## Hosted Service

The quickest way to get started is with the managed service at **[ai-support-agent.com](https://ai-support-agent.com)**. Create an account there, then the CLI connects to it out of the box — these are the built-in defaults, so no extra flags are needed:

| Endpoint | URL | Role |
|----------|-----|------|
| Product site | [ai-support-agent.com](https://ai-support-agent.com) | Landing page — sign up and learn more |
| Web console | [app.ai-support-agent.com](https://app.ai-support-agent.com) | The Web UI where you send commands and watch results. Also the OAuth target that `login` opens in your browser (default `--url`). |
| API | `https://api.ai-support-agent.com` | Agent ↔ server API the daemon talks to (default `--api-url`). |

Running `ai-support-agent login` opens the hosted web console (`app.ai-support-agent.com`) in your browser, where you sign in and pick the tenant and project to link.

**Self-hosting?** The CLI is not tied to the hosted service. Point it at your own deployment with `--url` / `--api-url` (or the `AI_SUPPORT_AGENT_API_URL` environment variable) — see [Configuration](#configuration).

## Quick Start

```bash
npm install -g @ai-support-agent/cli

# Opens app.ai-support-agent.com in your browser → sign in → select tenant & project → done
ai-support-agent login

# Start the daemon (Ctrl-C to stop)
ai-support-agent start
```

That's it. The agent registers with the server, syncs project config, clones repositories, and starts listening for commands.

Docker must be running: each project runs in a container by default, and the first `start` obtains the agent image (see [Run in Docker](#run-in-docker)). Add `--no-docker` to run natively on the host instead.

### Add more projects

```bash
ai-support-agent add-project          # browser OAuth flow
ai-support-agent status               # verify registered projects
```

### Start with a Personal Access Token (agent scope)

Instead of the browser OAuth flow, you can start an agent with a **Personal Access
Token (PAT)** that you issue for yourself in the web app. Issue a PAT whose scope
includes `agent` and whose `projectCode` is set, then start the agent under your own
permissions ("personal plan"):

```bash
# The tenant comes from the token; the project is given with --project.
ai-support-agent start --token <tenantCode>:<tokenId>:<rawToken> --project <tenantCode>/<projectCode>
```

- No browser login and no prior `login`/`configure` step is required.
- `--api-url` is optional and defaults to the production API (`https://api.ai-support-agent.com`).
- The tenant in `--project` must match the tenant the PAT was issued for; a mismatch
  is rejected before the agent registers.
- Prefer passing the token via the `AI_SUPPORT_AGENT_TOKEN` environment variable so it
  does not appear in process listings (`ps aux`).

### Run in Docker

Docker is the default: plain `ai-support-agent start` runs each project in a container, and `--no-docker` opts out into native mode.

```bash
ai-support-agent start              # Docker mode (default)
ai-support-agent start --no-docker  # run natively on the host instead
```

Project dirs, `~/.claude/`, and `~/.aws/` are auto-mounted. The container runs with your host UID/GID.

**Base image.** The first run of a given version obtains `ai-support-agent:<version>`:

| Situation | What happens |
|-----------|--------------|
| Nothing customised (default) | Pulls `ghcr.io/mbc-net/ai-support-agent-cli:<version>` — public, `linux/amd64` + `linux/arm64` — and tags it locally (~2.6 GB compressed, so minutes rather than an hour) |
| `~/.ai-support-agent/Dockerfile` or a bundled build asset it COPYs (`entrypoint.sh`, `tmux.conf`, `bashrc-extra.sh`, `nvim/init.lua`, `starship.toml`) differs from the bundled version, or `--dockerfile` / `"dockerfilePath"` is set | Builds locally from your Dockerfile — expect tens of minutes (neovim is compiled from source, plus a rust toolchain and Playwright dependencies) |
| `--no-image-pull`, or `"dockerImagePull": "never"` in `config.json` | Always builds locally |
| The pull fails (offline, or the tag is not published yet) | Warns and falls back to a local build, so startup still succeeds |

The published image is built by this repository's release workflow from the same `docker/Dockerfile` and the same CLI version, so for an unmodified setup pulling and building produce the same image.

Once the image exists locally it is reused as-is — customising the Dockerfile afterwards does **not** trigger a rebuild. Use:

```bash
ai-support-agent docker-diff-dockerfile   # compare your Dockerfile with the bundled default
ai-support-agent docker-build             # force a local rebuild after customising
ai-support-agent docker-ensure-image      # obtain the image if missing (pull or build)
```

### Run as a background service

```bash
ai-support-agent service install      # install as a system service
ai-support-agent service start        # start the service
ai-support-agent service status       # check service status
ai-support-agent service stop         # stop the service
ai-support-agent service restart      # restart the service
ai-support-agent service uninstall    # remove the service
```

## How It Works

1. **Register** — Agent sends its capabilities (shell, file I/O, chat, terminal, vscode) to the API.
2. **Subscribe** — Connects to AppSync for real-time command delivery.
3. **Config sync** — Pulls project settings, MCP config, and repo list from the server.
4. **Execute** — Receives commands, runs them locally, streams results back chunk-by-chunk.

Each project runs in its own forked child process. The main process watches `config.json` and hot-adds/removes projects without restart.

### Supported Commands

| Command Type | What It Does |
|-------------|-------------|
| `chat` | Runs Claude Code (or Anthropic API) with your codebase context |
| `execute_command` | Executes a shell command with timeout and output capture |
| `file_read` / `file_write` / `file_list` / `file_rename` / `file_delete` / `file_mkdir` | File system operations |
| `process_list` / `process_kill` | Process management |
| `chat_cancel` | Cancels a running chat process |
| `e2e_test` | End-to-end test execution with Playwright browser automation and step reporting |
| `setup` / `config_sync` / `reboot` / `update` | Lifecycle management |

### Chat Modes

| Mode | How it works |
|------|-------------|
| `claude_code` (default) | Spawns Claude Code CLI as a subprocess with MCP tools, system prompts, and tool allowlists |
| `api` | Direct Anthropic API streaming. Requires `ANTHROPIC_API_KEY` |

The mode is resolved as: agent config → server default → auto-detection (whether `claude` CLI is installed).

### Built-in MCP Server

The agent ships a [Model Context Protocol](https://modelcontextprotocol.io/) server that Claude Code can call:

**General tools:**

| Tool | Description |
|------|-------------|
| `get_credentials` | Fetch AWS STS or database credentials from the API |
| `db_query` | Run SELECT queries (injection-protected) |
| `get_db_schemas` | Retrieve table/column metadata |
| `file_upload` | Upload files to S3 via presigned URLs |
| `get_project_info` | Fetch project configuration |
| `read_conversation_file` | Read conversation history |

**Browser automation tools (Playwright-based):**

| Tool | Description |
|------|-------------|
| `browser_navigate` | Navigate to a URL and take a screenshot |
| `browser_click` | Click an element by CSS selector |
| `browser_fill` | Fill a form field with a value |
| `browser_get_text` | Extract text from a specific element |
| `browser_login` | Log in using saved credentials |
| `browser_extract` | Extract text and save to a session variable atomically |
| `browser_set_variable` / `browser_get_variable` / `browser_list_variables` | Manage session variables |
| `browser_close` | Close the browser session |

**E2E test tools:**

| Tool | Description |
|------|-------------|
| `report_test_step` | Report an E2E test step result (status, screenshot, duration) to the API |

## CLI Reference

```
ai-support-agent <command> [options]

Commands:
  start              Start the agent daemon
  stop               Stop the running agent
  login              Authenticate via browser OAuth
  add-project        Add a project (browser OAuth)
  remove-project     Remove a project by code
  configure          Register with --token and --api-url
  status             Show agent status and registered projects
  set-language       Set display language (en | ja)
  set-auto-update    Configure auto-update (--enable | --disable | --channel)
  set-project-dir    Set project working directory
  docker-login       Login and start in Docker
  docker-build       Always build the Docker image for this version locally
                     (--dockerfile <path>)
  docker-ensure-image  Make the Docker image available: pull
                     ghcr.io/mbc-net/ai-support-agent-cli when nothing is
                     customised, build locally otherwise (--dockerfile <path>,
                     --no-image-pull)
  docker-diff-dockerfile  Show diff between a Dockerfile and the bundled default
  log-rotate         Read stdin and write to a size-rotating log file
                     (used by the per-project wrapper script)
  service            Manage agent background service

Global options:
  --lang <lang>      Override display language for this invocation
  --version          Show version
```

### `service` Subcommands

```
service install     Install the agent as a system background service
service uninstall   Uninstall the agent service
service start       Start the agent service
service stop        Stop the agent service
service restart     Restart the agent service
service status      Show service status

Options (install):
  --verbose          Show detailed installation info
  --no-docker        Install in native mode (skip Docker)
```

### `start` Options

```
--token <token>            Auth token or agent-scoped PAT (overrides config file)
--api-url <url>            API URL (defaults to the production API)
--verbose                  Enable debug logging
--heartbeat-interval <ms>  Heartbeat interval (default: 60000)
--no-auto-update           Disable auto-update for this session
--update-channel <channel> Release channel: latest | beta | alpha
--no-docker                Force native mode (skip Docker)
--dockerfile <path>        Custom Dockerfile path (overrides config and bundled default)
--no-dockerfile-sync       Skip writing the default Dockerfile to config dir
--no-image-pull            Never pull the base image from the container registry;
                           always build it locally (same as config
                           "dockerImagePull": "never"). By default an
                           uncustomised Dockerfile means the published
                           ghcr.io/mbc-net/ai-support-agent-cli image is pulled
                           instead of being rebuilt on this machine
--project <tenantCode/projectCode>  Start only the matching project; with --token
                                    it selects the project for a direct (PAT) start
```

### `set-project-dir` Options

```
--default <template>       Set default dir template (e.g. ~/projects/{projectCode})
--project <code> --path <path>  Set dir for a specific project
```

## Configuration

Stored at `~/.ai-support-agent/config.json` (mode `0600`):

```jsonc
{
  "agentId": "macbook-a1b2c3d4",       // auto-generated
  "createdAt": "2025-01-01T00:00:00Z",
  "lastConnected": "2025-06-15T09:00:00Z",
  "language": "en",                     // en | ja
  "agentChatMode": "claude_code",       // claude_code | api
  "defaultProjectDir": "~/.ai-support-agent/projects/{projectCode}",
  "autoUpdate": {
    "enabled": true,
    "autoRestart": true,
    "channel": "latest"                 // latest | beta | alpha
  },
  "projects": [
    {
      "projectCode": "MY_PROJECT",
      "token": "agt_xxxx",
      "apiUrl": "https://api.ai-support-agent.com",   // defaults to the hosted API
      "projectDir": "~/custom-path"     // optional override
    }
  ]
}
```

### Environment Variables

| Variable | Description |
|----------|-------------|
| `AI_SUPPORT_AGENT_TOKEN` | Override auth token (lowest priority) |
| `AI_SUPPORT_AGENT_API_URL` | Override API URL (lowest priority) |
| `AI_SUPPORT_AGENT_CONFIG_DIR` | Override config directory path |
| `AI_SUPPORT_AGENT_TERMINAL_GRACE_MS` | PTY grace window after WebSocket disconnect, in milliseconds (positive integer only; default 3,600,000 = 60 min). Keep in sync with the API-side `TERMINAL_SESSION_GRACE_MS` |
| `AI_SUPPORT_AGENT_ALLOW_HTTP` | Set to `true` to allow a non-HTTPS API URL (development only) |
| `ANTHROPIC_API_KEY` | Required for `api` chat mode |
| `CLAUDE_CODE_OAUTH_TOKEN` | Claude Code OAuth token; passed through to the Docker container if set on the host (normally delivered by the server) |
| `AI_SUPPORT_AGENT_IN_DOCKER` | Set automatically to `1` inside the Docker container |
| `AI_SUPPORT_AGENT_PROJECT_DIR_MAP` | Host-to-container project directory mapping (set automatically in Docker mode) |
| `AI_SUPPORT_AGENT_TENANT_CODE` | Tenant code for MCP server processes (set automatically) |
| `AI_SUPPORT_AGENT_PROJECT_CODE` | Project code for MCP server processes (set automatically) |
| `AI_SUPPORT_E2E_EXECUTION_ID` | E2E test execution ID (set automatically during `e2e_test` runs) |
| `AI_SUPPORT_E2E_TEST_CASE_ID` | E2E test case ID (set automatically during `e2e_test` runs) |

Priority: CLI flags > config file > environment variables.

## Project Directory Structure

Each project gets an isolated workspace:

```
{projectDir}/
├── workspace/
│   ├── repos/          # Git-cloned repositories
│   ├── docs/           # Documentation from server
│   └── artifacts/      # Generated outputs
├── uploads/            # File uploads staging
└── .ai-support-agent/
    ├── cache/          # Temporary cache
    └── aws/            # AWS credential cache
```

## Security

| Layer | Detail |
|-------|--------|
| Authentication | Browser-based OAuth with localhost callback, CSRF nonce |
| Config file | Mode `0600`, directory `0700` |
| Path traversal | Blocked paths: `/root`, `/etc`, `/sys`, `/proc`, `/dev`, `~/.ssh`, `~/.aws`, etc. |
| SQL injection | Pattern-based detection: comments, time-based blind, encoding bypass, file system access |
| Subprocess env | Sensitive variables stripped before spawning child processes |
| File transfer | Extension allowlist (53 types), `basename()` sanitization |
| Git operations | Branch name validation to prevent CLI injection |
| Container image | Pulled from `ghcr.io/mbc-net/ai-support-agent-cli`, published by this repository's release workflow, only when the Dockerfile and its build assets are unmodified; `--no-image-pull` or `"dockerImagePull": "never"` forces a local build instead |

## Development

```bash
git clone https://github.com/mbc-net/ai-support-agent-cli.git
cd ai-support-agent-cli

npm install
npm run dev -- start --verbose    # development mode
npm test                          # run tests
npm run test:cov                  # coverage (95%+ thresholds enforced)
npm run build                     # compile to dist/
```

Requires Node.js >= 20. `node-pty` is an optional dependency for terminal/PTY sessions.

## License

[MIT](LICENSE)
