# https://github.com/Sir-chawakorn/sanook-cli Project Manual

Generated at: 2026-06-21 03:00:44 UTC

## Table of Contents

- [Overview & Quickstart](#page-1)
- [Agent Loop, Memory & Second Brain](#page-2)
- [Providers, MCP, Tools & Skills](#page-3)
- [Gateway, Dashboard, TUI & Deployment](#page-4)

<a id='page-1'></a>

## Overview & Quickstart

### Related Pages

Related topics: [Agent Loop, Memory & Second Brain](#page-2), [Gateway, Dashboard, TUI & Deployment](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [package.json](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json)
- [second-brain/README.md](https://github.com/Sir-chawakorn/sanook-cli/blob/main/second-brain/README.md)
- [src/providers/registry.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts)
- [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts)
- [src/dashboard/api-helpers.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.ts)
- [src/dashboard/api-helpers.test.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.test.ts)
- [src/providers/models.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts)
- [src/providers/registry.test.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.test.ts)
</details>

# Overview & Quickstart

## What is sanook-cli

sanook-cli is a terminal-based AI coding agent distributed as a single npm package. The `description` field in [package.json:3](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json) characterises it as "A terminal AI coding agent — BYOK, 9 providers, MCP, cron gateway, skills, and git awareness. Built from scratch in TypeScript." Version `0.5.7` ships two CLI entry points, `sanook` and `sanookai`, both pointing at the compiled binary `dist/bin.js` (Source: [package.json:8-11](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json)).

The package targets Node.js 22 or later and is licensed under Apache-2.0 (Source: [package.json:23-31](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json)). The terminal UI is rendered with [Ink](https://github.com/vadimdemedes/ink) and React 19, while language-model requests flow through the Vercel AI SDK (`ai: ~6.0`), giving the project pluggable support for multiple model providers (Source: [package.json:33-47](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json)).

## Installation

The only currently-ready installation method is npm, confirmed by the dashboard helper test that asserts `npm` is `ready: true` and `recommended: true` (Source: [src/dashboard/api-helpers.test.ts:6-12](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.test.ts)) and by the install table that lists it first (Source: [src/dashboard/api-helpers.ts:14-26](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.ts)):

```bash
npm install -g sanook-cli
# or run without installing
npx sanook-cli
```

Curl, Homebrew, and winget entries exist in the same helper module but are marked `ready: false` with infrastructure notes, since they require hosted install scripts (Source: [src/dashboard/api-helpers.ts:27-43](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.ts), [src/dashboard/api-helpers.test.ts:14-22](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.test.ts)). The build pipeline defined in `scripts.build` recompiles TypeScript, copies dashboard static assets, and sets the binary executable bit before publishing (Source: [package.json:12-22](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json)).

## First Run & Provider Setup

On first run sanook-cli inspects the environment for usable provider keys. The `hasUsableEnvKey` helper checks each provider's `envVar`, validates the value with `assertDirectApiKey` to reject OAuth or subscription tokens, and skips the setup wizard when a valid key is already present (Source: [src/providers/registry.ts:148-165](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts)). Supported cloud providers include Anthropic, OpenAI, Google, xAI, Mistral, and Groq, each declared as a `ProviderConfig` entry in the `PROVIDERS` table (Source: [src/providers/registry.ts:55-110](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts)). Local providers such as Ollama do not require an API key.

Model selection accepts three spec forms: a global alias like `gpt`, a provider alias like `anthropic:haiku`, or a raw `provider:model-id` string. The `parseSpec` function normalises all of them, `canonicalSpec` produces the `provider:model-id` form that the REPL stores as state, and `fastSibling` returns the cheaper sibling of a model for mechanical tasks such as summarisation (Source: [src/providers/registry.ts:111-145](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts)).

The setup wizard can also fetch live model lists per provider through `listRemoteModels`, which issues an authenticated `GET /models` request with a 6 s timeout and falls back to curated aliases on failure. The companion `mergeModelOptions` function de-duplicates alias and remote entries so the picker never shows two options pointing at the same model id (Source: [src/providers/models.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts)).

## Architecture at a Glance

```mermaid
flowchart LR
    User[Terminal User] --> CLI[sanook REPL]
    CLI -->|events| Loop[runAgent loop]
    Loop -->|spec| Registry[Provider Registry]
    Registry -->|BYOK keys| SDK[Vercel AI SDK]
    SDK --> Anthropic
    SDK --> OpenAI
    SDK --> Google
    SDK --> xAI
    SDK --> Mistral
    SDK --> Groq
    SDK --> Ollama
    CLI -->|optional| Dash[Local Web Dashboard]
    Dash -->|SSE| Terminal[src/dashboard/terminal.ts]
    Loop --> Brain[second-brain vault]
```

The REPL sits at the centre: it streams text, reasoning, and tool events back to the terminal, persists multi-turn history server-side per browser session, and emits remembered facts or auto-created skills during the loop (Source: [src/dashboard/terminal.ts:1-15](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts)). The local dashboard reuses the same `runAgent` entry point over Server-Sent Events, so web and terminal sessions share identical agent behaviour (Source: [src/dashboard/terminal.ts:5-12](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts)).

A second major surface is the Obsidian-style "second brain" vault scaffolded by the CLI. The vault README describes a folder layout with `Projects/`, `Sessions/`, `Shared/`, `Goals/`, `Areas/`, `Research/`, `Skills/`, and `Playbooks/` directories, plus constitution files (`CLAUDE.md`, `GEMINI.md`, `AGENTS.md`, `SANOOK.md`) that govern how the agent writes to the vault. `Shared/AI-Context-Index.md` is loaded before any vault work, and every folder exposes an `_Index.md` that defines its AI routing contract (Source: [second-brain/README.md](https://github.com/Sir-chawakorn/sanook-cli/blob/main/second-brain/README.md)).

## Verifying the Install

A few quick smoke tests confirm a healthy install. Running `sanook --help` should print the REPL banner; launching the agent without a key triggers the setup wizard rather than a raw error. The unit test for `resolveEmbedder` shows that the provider layer returns `null` when no embedding credential is present and degrades to `null` instead of accepting an OAuth-style key — the same defensive policy that protects the live REPL (Source: [src/providers/registry.test.ts:13-33](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.test.ts)).

## See Also

- Providers & Models
- Dashboard & Web Terminal
- Second-Brain Vault
- Skills & Cron Gateway

---

<a id='page-2'></a>

## Agent Loop, Memory & Second Brain

### Related Pages

Related topics: [Overview & Quickstart](#page-1), [Providers, MCP, Tools & Skills](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [package.json](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json)
- [second-brain/README.md](https://github.com/Sir-chawakorn/sanook-cli/blob/main/second-brain/README.md)
- [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts)
- [src/providers/registry.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts)
- [src/providers/models.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts)
- [src/dashboard/api-helpers.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.ts)
- [src/providers/codex.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/codex.ts)
- [src/providers/registry.test.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.test.ts)
</details>

# Agent Loop, Memory & Second Brain

Sanook CLI is a terminal AI coding agent built in TypeScript with BYOK (Bring Your Own Key) support, nine provider integrations, MCP, cron, skills, and git awareness. This page documents the agent loop, in-process conversation memory, and the Obsidian-backed **second brain** vault that gives the agent persistent memory across sessions.

Source: [package.json](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json) — project metadata, `bin: sanook`, Node `>=22`.

## Agent Loop

The core loop is exposed as `runAgent` and imported into the web dashboard. Each call receives a `prompt`, prior `history`, and a streaming `onEvent` callback that emits `AgentEvent` records.

Source: [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts) — `import { runAgent, type AgentEvent } from '../loop.js'`.

Key parameters observed at the call site:

| Parameter | Value / Range | Purpose |
|---|---|---|
| `maxSteps` | `20` | Hard cap on tool-calling turns per request |
| `permissionMode` | `'auto'` or `'ask'` | Whether tools run without prompting |
| `signal` | `AbortSignal` | Cancellation for client disconnects |
| `usageMeta` | `{ sessionId, source: 'repl' }` | Tags telemetry for cost tracking |
| `onEvent` | `(e: AgentEvent) => void` | Streams events to REPL or SSE |

Source: [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts) — `runAgent({ model, prompt, history, maxSteps: 20, permissionMode, signal, usageMeta, onEvent })`.

`AgentEvent` is a discriminated union. The dashboard's SSE bridge maps each variant:

- `text` / `reasoning` → streamed model output chunks
- `tool-call` → human title via `describeToolCall(tool, detail)` plus optional `diff`
- `tool-result` → tool-finished notification
- `status` → ephemeral progress text
- `error` → redacted message (secrets stripped via `redactKey`)

Source: [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts) — `onEvent` switch and `describeToolCall` import.

Provider selection flows through `resolveModel(spec)`, which parses `provider:model` specs, validates direct API keys (OAuth/subscription tokens are rejected by `assertDirectApiKey`), and constructs a `LanguageModel` via the per-provider factory. Aliases like `gpt`, `sonnet`, or `groq:fast` are normalized via `canonicalSpec` and `fastSibling`.

Source: [src/providers/registry.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts) — `resolveModel`, `canonicalSpec`, `fastSibling`, `hasUsableEnvKey`.

A `kind: 'delegate'` variant exists for providers that run as a subprocess rather than through the Vercel AI SDK. The Codex integration spawns the `codex` CLI, parses JSONL output for `thread.started`, `item.completed` (agent_message), and `turn.completed` events, and streams them back through the same `AgentEvent` shape.

Source: [src/providers/codex.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/codex.ts) — `spawn('codex', args, …)` and JSONL parsing.

## In-Process Conversation Memory

For the web dashboard, multi-turn history is held server-side, scoped by a browser session id. The store is an in-memory `Map<string, ModelMessage[]>` capped at 20 simultaneous sessions.

```ts
const HISTORY = new Map<string, ModelMessage[]>(); // sessionId → conversation
const MAX_HISTORY_SESSIONS = 20;
```

Source: [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts) — history store.

Each turn calls `runAgent` with the accumulated `history` plus the new `prompt`. Because the loop returns `messages` and the store is in-process, conversation context survives across page reloads until the server restarts or the session is evicted.

## 🧠 remember & ✨ Auto-Created Skills

Two side channels are captured from the `tool-call` stream:

- `remember` tool calls contribute a string `fact` that is pushed into a per-request `rememberedFacts` buffer and surfaced to the dashboard.
- Other tool calls are summarized via `describeToolCall(tool, detail)`, producing a `title` and optional `diff`.

Source: [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts) — `case 'tool-call'` branch.

These captures are short-lived. For persistence across sessions, the agent writes to the second brain vault.

## The Second Brain (Obsidian Vault)

`sanook brain` scaffolds an Obsidian vault that acts as long-term, cross-session memory. The structure separates concerns into dedicated folders:

| Folder | Role |
|---|---|
| `Projects/` | One folder per real project |
| `Sessions/` | Flat, date-stamped AI work logs |
| `Shared/` | Central memory, rules, decisions, state |
| `Intake/`, `Runbooks/`, `Templates/`, `Bugs/`, `Handoffs/` | Core workflow surfaces |
| `Goals/`, `Areas/` | North-star + ongoing domains |
| `Research/`, `Learning/`, `Distillations/` | Knowledge pipeline |
| `Skills/`, `Playbooks/`, `Evals/`, `Entities/` | Frontier loops (self-improving) |

Source: [second-brain/README.md](https://github.com/Sir-chawakorn/sanook-cli/blob/main/second-brain/README.md) — folder table.

The AI routing contract is enforced by per-folder `_Index.md` files. Before creating or relocating a note, an AI agent must:

1. Read `Shared/AI-Context-Index.md` first.
2. Choose the destination from `Vault Structure Map.md`.
3. Open that folder's `_Index.md` and follow its routing rules.

Source: [second-brain/README.md](https://github.com/Sir-chawakorn/sanook-cli/blob/main/second-brain/README.md) — usage section.

The constitution (behavioral rules for the AI) lives in `CLAUDE.md`, `GEMINI.md`, `AGENTS.md`, and `SANOOK.md`. User identity and preferences are stored in `USER.md` and `Shared/User-Memory/user-preferences.md`.

## Data Flow

```mermaid
sequenceDiagram
    participant U as User
    participant UI as REPL / Dashboard
    participant L as runAgent
    participant P as Provider
    participant B as Second Brain

    U->>UI: prompt + history
    UI->>L: invoke (maxSteps=20)
    loop until done or cap
        L->>P: stream chat completion
        P-->>L: text / reasoning / tool-call
        L-->>UI: AgentEvent via onEvent
        L->>B: remember facts / auto-create skills
    end
    L-->>UI: final messages
    UI->>U: rendered response
```

## Failure Modes & Limits

- **Dead SSE socket**: `sseSend` checks `res.destroyed || res.writableEnded` before writing; closed sockets are skipped silently. Source: [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts).
- **OAuth / subscription keys**: `assertDirectApiKey` rejects tokens like `sk-ant-oat…` or `ya29.…` so a "ready" status never reports for unusable credentials. Source: [src/providers/registry.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts) — `hasUsableEnvKey`.
- **History eviction**: When the 20-session cap is reached, the oldest entry in the `HISTORY` map is effectively dropped on the next overwrite (Map insertion order is preserved).
- **Embedder auto-detect**: If no embedding-capable provider key is present in env, `resolveEmbedder()` returns `null` rather than failing the run. Source: [src/providers/registry.test.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.test.ts) — `auto-detect returns null when no embedding provider key is present`.
- **Remote model listing**: `/v1/models` fetches require a usable API key and base URL; on failure, only locally-registered aliases appear in the model picker. Source: [src/providers/models.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts) — `fetchRemoteModels`.
- **Install prerequisites**: The dashboard advertises `npm install -g sanook-cli` or `npx sanook-cli` and notes Node.js ≥ 22. Source: [src/dashboard/api-helpers.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.ts) — `dashboardInstall`.

## See Also

- Providers & Model Resolution (registry, key policy, aliases)
- Dashboard / Web Terminal (SSE streaming, optional PTY shell)
- Skills & Auto-Creation (✨ skill synthesis pipeline)
- Obsidian Vault Conventions (`Vault Structure Map.md`)

---

<a id='page-3'></a>

## Providers, MCP, Tools & Skills

### Related Pages

Related topics: [Agent Loop, Memory & Second Brain](#page-2), [Gateway, Dashboard, TUI & Deployment](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [package.json](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json)
- [src/providers/registry.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts)
- [src/providers/models.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts)
- [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts)
- [src/dashboard/api-helpers.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.ts)
- [src/dashboard/api-helpers.test.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.test.ts)
- [second-brain/README.md](https://github.com/Sir-chawakorn/sanook-cli/blob/main/second-brain/README.md)
</details>

# Providers, MCP, Tools & Skills

## Overview

The `sanook-cli` project is a terminal-based AI coding agent designed around four interconnected capability layers: **Providers** (model backends), **MCP** (Model Context Protocol servers), **Tools** (agent-callable functions), and **Skills** (reusable behavior packs). Together, these layers allow the agent to communicate with multiple LLM vendors, expose external capabilities through a standardized protocol, execute local actions, and persist self-improving knowledge across sessions.

The package exposes two binaries — `sanook` and `sanookai` — both pointing to the same entrypoint, and is published under the Apache-2.0 license with Node.js ≥ 22 as the runtime. Source: [package.json:1-40](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json).

## Provider System

### Architecture

The provider system is implemented as a single registry table, `PROVIDERS`, where each entry declares its SDK factory, environment variable, key policy, and model alias map. Adding a new vendor requires only adding one record — no changes to the agent loop, pricing, or key-resolution code. Source: [src/providers/registry.ts:30-60](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts).

```mermaid
flowchart LR
  REPL[REPL / Dashboard] -->|spec string| Parser[parseSpec]
  Parser -->|provider:model| Registry[PROVIDERS table]
  Registry -->|factory call| SDK[Vercel AI SDK]
  SDK -->|HTTPS| Cloud[(Cloud LLM API)]
  Registry -.->|optional| Local[(Ollama / LMStudio)]
  Registry -.->|delegate| CodexCLI[Codex CLI subprocess]
  Keys[resolveKeyFromEnv] --> Registry
  Keys -->|OAuth reject| Blocked[hasUsableEnvKey → false]
```

### Supported Providers

The package ships SDK adapters for seven providers plus a delegate to the Codex CLI. Source: [package.json:33-42](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json).

| Provider | Label | Env Variable | Notes |
|---|---|---|---|
| `anthropic` | Anthropic (Claude) | `ANTHROPIC_API_KEY` | Direct API key; OAuth reuse rejected |
| `google` | Google (Gemini) | `GOOGLE_API_KEY` | Direct API key; OAuth reuse rejected |
| `openai` | OpenAI | `OPENAI_API_KEY` | Bearer key; ChatGPT/Codex OAuth banned |
| `xai` | xAI Grok | `XAI_API_KEY` | Format `/^xai-[A-Za-z0-9]{16,}$/` |
| `mistral` | Mistral | `MISTRAL_API_KEY` | Free-format key |
| `groq` | Groq | `GROQ_API_KEY` | Format `/^gsk_[A-Za-z0-9]{20,}$/` |
| `openai-compatible` | OpenAI-compatible | varies | Custom baseURL |
| `ollama` | Ollama (local) | — | No key required |
| `lmstudio` | LMStudio (local) | — | No key required |
| `codex` | Codex CLI (delegate) | — | Spawns subprocess agent |

Source: [src/providers/registry.ts:60-180](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts).

### Spec Resolution

Model references use a `provider:model` syntax. `parseSpec` accepts three forms: `provider:model`, `provider:alias`, or a bare global alias (e.g., `sonnet`). Aliases resolve through each provider's `models` map, while bare model IDs default to `anthropic`. The `canonicalSpec` function normalizes aliases to `provider:full-id` before reaching REPL state. Source: [src/providers/registry.ts:120-145](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts).

The `fastSibling()` function returns a provider's cheaper tier for cost-sensitive tasks (e.g., summarization). It inspects a priority chain of aliases: `fast` → `flash` → `haiku` → `air`, returning the original spec if no fast tier exists. Source: [src/providers/registry.ts:15-25](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts).

### Key Policy

`hasUsableEnvKey()` is the authoritative gate for whether a provider is ready. It rejects OAuth-prefixed keys and tokens failing the declared regex format, ensuring the first-run wizard is not bypassed when an unusable key is present. Source: [src/providers/registry.ts:100-115](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts).

## Remote Model Discovery

The `listRemoteModels()` function queries each provider's `/models` endpoint and returns the canonical model IDs the user actually owns, rather than relying on a static curated list. It handles three response shapes: Google's `?key=` query with `models[].name`, Anthropic's `x-api-key` header form, and the OpenAI-compatible `Bearer` form with `data[].id`. On failure, timeout, or for local/delegate providers, it returns `[]` so callers fall back to the curated alias map. Source: [src/providers/models.ts:10-60](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts).

The `mergeModelOptions()` function combines remote-discovered IDs with the curated alias map, grouping aliases by their resolved model ID. This prevents duplicate `Select` entries when one model ID is exposed under multiple aliases (e.g., `haiku` and `fast` pointing to the same underlying model). Source: [src/providers/models.ts:70-100](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts).

## MCP, Tools & Dashboard Integration

The dashboard exposes a web terminal backend that streams the agent loop over Server-Sent Events at `POST /api/terminal/run`, reusing `runAgent` and forwarding text, reasoning, tool-call events, remembered facts, and auto-created skills exactly like the REPL. Multi-turn conversation history is maintained server-side, keyed by browser session ID, with a maximum of 20 concurrent sessions (localhost, single-user). Source: [src/dashboard/terminal.ts:10-25](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts).

A raw shell endpoint at `/api/terminal/shell` upgrades to a real PTY when both `node-pty` and `ws` are installed (listed as `optionalDependencies` in `package.json`); otherwise the UI degrades gracefully. The `shellStatus()` helper reports availability and reason for the absence. Source: [src/dashboard/api-helpers.test.ts:20-28](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.test.ts).

Tool activity is rendered through `describeToolCall()`, which produces human-readable summaries shown alongside streamed events. Source: [src/dashboard/terminal.ts:5-10](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts).

## Skills & Second Brain

The second-brain vault scaffolded by `sanook brain` provides persistent storage for agent knowledge across sessions. It defines folders for `Skills/`, `Playbooks/`, `Evals/`, and `Entities/`, collectively described as "frontier loops (self-improving)" — meaning the agent can write, refine, and re-evaluate its own behavior artifacts. Source: [second-brain/README.md:10-15](https://github.com/Sir-chawakorn/sanook-cli/blob/main/second-brain/README.md).

The vault routing contract requires AI agents to read `Shared/AI-Context-Index.md` before any task, and to consult the target folder's `_Index.md` before creating or moving notes. This enforces consistent placement across `Projects/`, `Sessions/`, `Goals/`, `Areas/`, `Research/`, `Learning/`, and `Distillations/` folders. Source: [second-brain/README.md:20-25](https://github.com/Sir-chawakorn/sanook-cli/blob/main/second-brain/README.md).

Constitution files (`CLAUDE.md`, `GEMINI.md`, `AGENTS.md`, `SANOOK.md`) define AI conduct rules, while `USER.md` and `Shared/User-Memory/user-preferences.md` hold personalization. Source: [second-brain/README.md:25-28](https://github.com/Sir-chawakorn/sanook-cli/blob/main/second-brain/README.md).

## Common Failure Modes

- **OAuth key rejection**: Setting `ANTHROPIC_API_KEY=sk-ant-oat…` causes `hasUsableEnvKey()` to return `false`, forcing the wizard to run instead of marking the provider ready. Source: [src/providers/registry.ts:100-115](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts).
- **Stale model IDs**: Providers retire models (e.g., `grok-4` retired 2026-05-15); the registry redirects aliases to current IDs but users can always override with the explicit `provider:full-id` form. Source: [src/providers/registry.ts:80-95](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts).
- **PTY unavailable**: When `node-pty` is not installed (e.g., in CI), the shell endpoint reports a reason matching `/node-pty|ws/`, and the dashboard UI degrades. Source: [src/dashboard/api-helpers.test.ts:20-28](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.test.ts).
- **Duplicate model entries**: Without `mergeModelOptions()` grouping aliases by resolved ID, the model picker shows duplicate entries or misses models whose only alias is `default`. Source: [src/providers/models.ts:70-100](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts).

## See Also

- [Project Architecture Overview](./README.md)
- [Configuration & Environment](./configuration.md)
- [Dashboard & Web Terminal](./dashboard.md)
- [Second Brain Vault Guide](./second-brain.md)

---

<a id='page-4'></a>

## Gateway, Dashboard, TUI & Deployment

### Related Pages

Related topics: [Overview & Quickstart](#page-1), [Providers, MCP, Tools & Skills](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [package.json](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json)
- [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts)
- [src/dashboard/api-helpers.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.ts)
- [src/dashboard/api-helpers.test.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.test.ts)
- [src/providers/registry.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts)
- [src/providers/models.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts)
- [src/providers/codex.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/codex.ts)
</details>

# Gateway, Dashboard, TUI & Deployment

## Overview and Purpose

Sanook CLI exposes the same agent loop through three coordinated surfaces: a long-running **Gateway** for HTTP/SSE and scheduled jobs, a browser-based **Dashboard** for interactive control, and an in-terminal **TUI** for keyboard-driven REPL use. All three are built and shipped through a single TypeScript pipeline that produces a `dist/` bundle ready for npm distribution.

The agent core is reused everywhere: the dashboard's `/api/terminal/run` route streams `runAgent` over Server-Sent Events, forwarding text, reasoning, and tool events (including the 🧠 remember facts and ✨ auto-created skills) just like the REPL. Source: [src/dashboard/terminal.ts:1-15]()

Multi-turn history is kept server-side, keyed by browser session id, which keeps the dashboard a localhost single-user surface simple. Source: [src/dashboard/terminal.ts:10-13]()

## Dashboard (Web UI)

The dashboard is a Node HTTP server that ships with the CLI. It bundles two distinct surfaces that share the same agent backend.

### Agent Console (SSE)

The agent console accepts a POST body, runs the agent via `runAgent` from `../loop.js`, and streams events as `data: <json>\n\n` SSE frames. The handler guards against dead sockets: if the response is already destroyed or ended, it returns silently to avoid writing to a closed connection. Source: [src/dashboard/terminal.ts:17-25]()

Body parsing is intentionally minimal — a buffered JSON reader used by the request body — and the handler enforces a bounded history per session to protect memory on long-running localhost instances. Source: [src/dashboard/terminal.ts:27-33]()

### Raw Shell (WebSocket + PTY, Optional)

The raw shell feature upgrades `ws://…/api/terminal/shell` to a real PTY session when both `node-pty` and `ws` are installed. Both are declared as **optionalDependencies** in [package.json:65-68](), so the dashboard degrades gracefully when they are absent. The helper `shellStatus()` reports availability and a reason string (e.g. `node-pty/ws not installed`) that the UI surfaces to the user. Source: [src/dashboard/api-helpers.test.ts:20-25]()

### Install Helper API

The dashboard exposes installation metadata for first-time users via `dashboardInstall()`. It returns an array of install methods, each tagged `ready: boolean` and an optional `note` explaining infrastructure dependencies. The npm method is `ready: true` and `recommended: true`; curl, homebrew, and winget entries are returned with `ready: false` and a `note` describing the missing infrastructure (hosted install scripts, formula, manifest). Source: [src/dashboard/api-helpers.ts:1-35]() and verified by [src/dashboard/api-helpers.test.ts:9-18]().

The install commands are templated around the `NPM_PKG` constant (`sanook-cli`) and a placeholder `INSTALL_DOMAIN` (`sanook.ai`) that the project should swap in once the install-script infrastructure is hosted. Source: [src/dashboard/api-helpers.ts:11-34]()

## Terminal User Interface (TUI)

The TUI runs in the user's terminal and is the canonical keyboard-driven surface. It is built on **Ink** (React 19 renderer for CLIs) and the `@inkjs/ui` component library, which together provide the box-drawing, text input, and select primitives used by the REPL. Source: [package.json:43-47]()

Cosmetic components come from `ink-big-text` (the brand banner) and `ink-gradient`, layered on top of the Ink renderer. Source: [package.json:44-46]()

The TUI is started in development through `tsx src/bin.ts` and, after build, by the compiled entry `dist/bin.js`, which is shipped as the npm `bin` for both `sanook` and `sanookai` commands. Source: [package.json:6-10, 18-19]()

The dashboard static assets are copied into `dist/` by `scripts/copy-dashboard-static.mjs` as part of the build, then `dist/bin.js` is marked executable (`0o755`). Source: [package.json:19-19]()

## Provider Backbone and Sub-Agent Delegation

The gateway, dashboard, and TUI all call into the same provider registry. The `PROVIDERS` table is the single point of truth for which models are addressable, their env-var names, key formats, and factories. ToS-sensitive providers (Anthropic, Google) explicitly reject OAuth/subscription tokens via `assertDirectApiKey` so that the same code path is safe across surfaces. Source: [src/providers/registry.ts:60-82]()

For models whose identities change frequently, the registry exposes a remote listing endpoint via `listRemoteModels`, which calls each provider's `GET /models`, handles the three shape variants (Google `?key=` with `models[].name`, Anthropic `x-api-key` header, OpenAI-compatible Bearer with `data[].id`), and falls back to a curated alias list on failure, timeout, or for local providers. Source: [src/providers/models.ts:7-19]()

A separate `kind: 'delegate'` entry exists for the OpenAI Codex CLI, which is reached by spawning `codex exec` and parsing JSONL events. This lets Sanook forward coding work to a subprocess agent while reusing the same `runAgent` boundary for SDK-backed providers. Source: [src/providers/registry.ts:36-42]() and [src/providers/codex.ts:1-15]()

## Deployment

Deployment is driven by the npm `scripts` block. A clean build runs in order: wipe `dist/`, run `tsc -p tsconfig.build.json`, copy dashboard static assets, and chmod the bin. A prepublish gate (`prepublishOnly: npm run build`) ensures the published tarball always contains a fresh `dist/`. Source: [package.json:18-22]()

The published package ships `dist/`, `skills/`, `second-brain/`, the `postinstall` script, the README, the changelog, the license, and `.env.example`. The `postinstall` hook (`scripts/postinstall.mjs`) runs after install to perform first-run scaffolding such as writing a default config or seeding the second-brain vault. Source: [package.json:11-17, 23-25]()

The runtime contract is strict: `engines.node: ">=22"` is enforced to keep modern `node:fs`, `node:http`, and the AI SDK runtime available. Source: [package.json:26-28]()

```mermaid
flowchart LR
    User[Operator] --> TUI["TUI (Ink + @inkjs/ui)"]
    User --> Dashboard["Dashboard (browser)"]
    Cron[Scheduler / Cron gateway] --> Gateway["Gateway (HTTP + SSE)"]
    TUI --> Loop["runAgent (loop.js)"]
    Dashboard -->|POST /api/terminal/run| Loop
    Gateway --> Loop
    Loop --> Registry["Provider Registry"]
    Registry --> SDK["AI SDK providers"]
    Registry --> Delegate["codex exec subprocess"]
    Dashboard -. optional .-> PTY["Raw shell (node-pty + ws)"]
```

## Operational Notes and Failure Modes

- **Optional deps absent.** If `node-pty` or `ws` are not installed, the dashboard's raw shell section reports `available: false` with a reason string; the agent console still works because it uses only Node's built-in `http`. Source: [src/dashboard/api-helpers.test.ts:20-25]()
- **Dead SSE clients.** The SSE writer early-exits when the response is destroyed or ended, preventing `ERR_STREAM_DESTROYED` from a tab being closed mid-stream. Source: [src/dashboard/terminal.ts:18-25]()
- **OAuth/subscription keys rejected.** The provider policy refuses Anthropic/Google OAuth tokens even when the env var is set, so the wizard cannot be silently skipped on a token that will not work. Source: [src/providers/registry.ts:90-105]()
- **Local providers.** Ollama and LM Studio use `requiresKey: false` and a placeholder key, and `listRemoteModels` short-circuits for local providers by returning `[]` and letting the curated alias list win. Source: [src/providers/registry.ts:48-60]() and [src/providers/models.ts:11-12]()
- **First-run UX.** The dashboard install helper advertises `npm` as the recommended path; alternative channels (curl, homebrew, winget) are listed for discoverability but flagged `ready: false` until their infrastructure is published. Source: [src/dashboard/api-helpers.ts:16-35]()

## See Also

- Provider registry and key policy: [src/providers/registry.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/registry.ts)
- Remote model listing: [src/providers/models.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/models.ts)
- Codex CLI sub-agent integration: [src/providers/codex.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/providers/codex.ts)
- Dashboard terminal backend: [src/dashboard/terminal.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/terminal.ts)
- Dashboard install helpers: [src/dashboard/api-helpers.ts](https://github.com/Sir-chawakorn/sanook-cli/blob/main/src/dashboard/api-helpers.ts)
- Project manifest and scripts: [package.json](https://github.com/Sir-chawakorn/sanook-cli/blob/main/package.json)

---

<!-- evidence_pipeline_checked: true -->

---

## Pitfall Log

Project: Sir-chawakorn/sanook-cli

Summary: Found 7 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Configuration risk - Configuration risk requires verification.

## 1. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.host_targets | https://github.com/Sir-chawakorn/sanook-cli

## 2. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/Sir-chawakorn/sanook-cli

## 3. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/Sir-chawakorn/sanook-cli

## 4. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/Sir-chawakorn/sanook-cli

## 5. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/Sir-chawakorn/sanook-cli

## 6. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/Sir-chawakorn/sanook-cli

## 7. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/Sir-chawakorn/sanook-cli

<!-- canonical_name: Sir-chawakorn/sanook-cli; human_manual_source: deepwiki_human_wiki -->
