# Turumba CLI -- Architecture

## 1. Overview

Turumba CLI is a Python command-line application that wraps the Turumba message automation platform's REST APIs. It communicates with the platform through a KrakenD API gateway and provides both a human-friendly terminal interface and an AI agent integration layer.

```
                                +-----------------------+
                                |   ~/.turumba/         |
                                |   config.yaml         |
                                |   credentials         |
                                +-----------+-----------+
                                            |
                                            v
+----------+     +--------+     +-----------+-----------+     +-------------------+
|  User /  | --> | Typer  | --> | TurumbaClient         | --> | KrakenD Gateway   |
|  Agent   |     | CLI    |     | (client.py)           |     | :8080             |
+----------+     +--------+     |  - Auth injection     |     +--------+----------+
                     |          |  - Envelope parsing    |              |
                     |          +------+--------+-------+     +--------+----------+
                     |                 |        |             |                    |
                     v                 v        v             v                    v
              +-----------+    +------+--+  +--+------+  +---+-------+    +-------+------+
              | output.py |    |config.py|  |creds.py |  |Account API|    |Messaging API |
              |  table    |    |  YAML   |  |keyring  |  |  :8000    |    |    :8000     |
              |  json     |    |  loader |  |file/env |  +-----------+    +--------------+
              |  csv      |    +---------+  +---------+
              +-----------+
```

### Design Principles

1. **Gateway-only communication** -- The CLI never calls backend services directly. All requests go through the KrakenD gateway, which handles context enrichment (injecting `x-account-ids` and `x-role-ids` headers from the user's JWT).

2. **Envelope transparency** -- The Turumba APIs use a standard response envelope (`{success, data, meta}`). The `TurumbaClient` unwraps this so command handlers always receive clean data dicts. Commands never deal with envelopes.

3. **Storage layering** -- Both config and credentials use a layered approach: secure storage (keyring) with progressive fallbacks (file, env vars). This makes the CLI work securely on desktops and in headless CI environments.

4. **Non-interactive by design** -- Every interactive prompt has a corresponding `--flag`. The `--output json` mode produces machine-parseable output with no decorations. This ensures the CLI works equally well for humans, scripts, and AI agents.

5. **Convention over configuration** -- Command names match API resource names. CRUD subcommands are the same across all resources. Filter/sort syntax is passed through to the API unchanged.

6. **Account ID resolution** -- Resources that require `account_id` (persons, contacts, group-messages, scheduled-messages) accept `--account-id / -a` and fall back to `config.default_account_id`. This enables one-shot command execution after a one-time config setup.

## 2. Module Architecture

```
src/turumba_cli/
|
|-- main.py              Entry point. Typer app. Registers all command groups.
|
|-- Core Modules --------
|   |-- config.py        Config loader/saver (~/.turumba/config.yaml)
|   |-- credentials.py   Token storage with layered backends
|   |-- client.py        HTTP client with auth injection + envelope parsing
|   |-- output.py        Output formatters (table, JSON, CSV, detail)
|   |-- errors.py        Error type hierarchy
|   |-- crud_factory.py  CRUD command generator (Phase 3)
|
|-- commands/ -----------
|   |-- auth.py          login, logout, register, verify-email, status
|   |-- context.py       whoami (accounts/roles)
|   |-- config_cmd.py    set, get, list, use-account
|   |-- channels.py      CRUD + test-connection (reference implementation)
|   |-- persons.py       CRUD (with --account-id)
|   |-- contacts.py      CRUD (with --account-id)
|   |-- group_messages.py    CRUD (with --account-id)
|   |-- scheduled_messages.py  CRUD (with --account-id)
|   |-- chat_endpoints.py     CRUD
|   |-- conversation_configs.py  CRUD + evaluate
|   |-- skills.py        Skills system commands (list, show, how-to)
|   |-- accounts.py      CRUD (Phase 3)
|   |-- users.py         CRUD (Phase 3)
|   |-- groups.py        CRUD + membership (Phase 3)
|   |-- roles.py         CRUD (Phase 3)
|   |-- messages.py      CRUD + send (Phase 3)
|   |-- templates.py     CRUD (Phase 3)
|   |-- conversations.py CRUD + send-message (Phase 3)
|
|-- skills/ -------------
|   |-- registry.py      SkillTopic + ParamSpec dataclasses, topic registry, search
|   |-- topics/          One module per skill topic (self-registering on import)
|       |-- __init__.py         Imports all topic modules (triggers registration)
|       |-- overview.py         Platform overview and getting started
|       |-- auth.py             Authentication workflows
|       |-- channels.py         Channel creation with credential schemas
|       |-- persons.py          Person creation with account_id + properties
|       |-- contacts.py         Contact creation with account_id + properties
|       |-- templates.py        Template creation with variable syntax
|       |-- group_messages.py   Group message creation with recipient targeting
|       |-- scheduled_messages.py  Scheduled message with recurrence rules
|       |-- sending.py          Overview of all sending methods
|       |-- chat_endpoints.py   Chat widget setup
|       |-- conversation_configs.py  Conversation routing rules
|       |-- create_resource.py  Unified quick-reference for ALL resource creation
|       |-- filtering.py        Filter and sort syntax
|       |-- troubleshooting.py  Common issues and debugging
```

### Module Dependency Graph

```
main.py
  |-- commands/*.py
        |-- client.py
        |     |-- config.py      (loads gateway_url)
        |     |-- credentials.py (loads tokens for auth headers)
        |     |-- errors.py      (raises typed errors on failure)
        |
        |-- config.py            (reads default_account_id for account resolution)
        |-- output.py            (renders results to terminal)
        |-- errors.py            (catches errors, shows friendly messages)
        |-- credentials.py       (stores/loads tokens)
  |
  |-- skills/registry.py         (topic dataclasses and search)
        |-- skills/topics/*.py   (self-registering topic modules)
```

Key constraint: **No circular dependencies.** Core modules (`config`, `credentials`, `errors`) have no internal imports. `client.py` depends on core modules. `commands/*` depend on `client.py` and core modules. `main.py` depends only on `commands/*`.

## 3. Authentication Flow

### 3.1 Login Sequence

```
User                CLI                   Gateway              Account API
  |                  |                       |                      |
  |  turumba auth login                     |                      |
  |----------------->|                       |                      |
  |                  | POST /v1/auth/login   |                      |
  |                  | {email, password}     |                      |
  |                  |  (no auth header)     |                      |
  |                  |---------------------->| POST /auth/login     |
  |                  |                       |--------------------->|
  |                  |                       |                      | Cognito auth
  |                  |                       |<---------------------|
  |                  |<----------------------|                      |
  |                  | {access_token,        |                      |
  |                  |  id_token,            |                      |
  |                  |  refresh_token,       |                      |
  |                  |  expires_in: 3600}    |                      |
  |                  |                       |                      |
  |                  | save_tokens()         |                      |
  |                  | (keyring or file)     |                      |
  |                  |                       |                      |
  |                  | GET /v1/context       |                      |
  |                  | Authorization: Bearer |                      |
  |                  |---------------------->| context-enricher     |
  |                  |                       | GET /context/basic   |
  |                  |                       |--------------------->|
  |                  |<----------------------|                      |
  |                  | {accounts, roles}     |                      |
  |                  |                       |                      |
  |                  | auto-select account   |                      |
  |                  | if only 1             |                      |
  |<-----------------|                       |                      |
  | "Logged in as..."                       |                      |
```

### 3.2 Authenticated Request Sequence

```
User                CLI                   Gateway              Backend API
  |                  |                       |                      |
  |  turumba channels list                  |                      |
  |----------------->|                       |                      |
  |                  | load_tokens()         |                      |
  |                  | check is_expired      |                      |
  |                  |                       |                      |
  |                  | GET /v1/channels      |                      |
  |                  | Authorization: Bearer |                      |
  |                  |---------------------->|                      |
  |                  |                       | context-enricher:    |
  |                  |                       | - validate JWT       |
  |                  |                       | - GET /context/basic |
  |                  |                       | - inject headers:    |
  |                  |                       |   x-account-ids      |
  |                  |                       |   x-role-ids         |
  |                  |                       |                      |
  |                  |                       | GET /v1/channels     |
  |                  |                       | + injected headers   |
  |                  |                       |--------------------->|
  |                  |                       |                      | query by
  |                  |                       |                      | account scope
  |                  |                       |<---------------------|
  |                  |<----------------------|                      |
  |                  | {success: true,       |                      |
  |                  |  data: [...],         |                      |
  |                  |  meta: {total, ...}}  |                      |
  |                  |                       |                      |
  |                  | unwrap envelope       |                      |
  |                  | render table/json/csv |                      |
  |<-----------------|                       |                      |
  | (formatted output)                      |                      |
```

### 3.3 Token Storage Hierarchy

```
                    load_tokens()
                         |
            +------------+------------+
            |            |            |
            v            v            v
     1. Env Vars   2. Keyring   3. File
     TURUMBA_       macOS:       ~/.turumba/
     ACCESS_TOKEN   Keychain     credentials
     TURUMBA_       Linux:       (JSON, 0600)
     REFRESH_TOKEN  Secret Svc
                    Windows:
                    Cred Mgr

     Priority: env vars > keyring > file
     Save: tries keyring first, falls back to file
```

**TokenSet** stores:
- `access_token` -- JWT from Cognito (RS256, 1-hour expiry)
- `id_token` -- Cognito ID token
- `refresh_token` -- For future token refresh support
- `expires_at` -- Unix timestamp (computed as `now + expires_in`)
- `email` -- User's email (for display in `auth status`)

**Expiry check**: `is_expired` returns `True` if current time > `expires_at - 300` (5-minute buffer to avoid mid-request expiry).

## 4. HTTP Client (client.py)

The `TurumbaClient` class is the single point of contact with the Turumba API.

### 4.1 Request Lifecycle

```
command calls client.list("/v1/channels", filter_=[...])
    |
    v
client.request("GET", "/v1/channels", params={...})
    |
    +-- 1. Load config for gateway_url
    +-- 2. Load tokens for auth header (if auth=True)
    +-- 3. Check token expiry (raise TurumbaAuthError if expired)
    +-- 4. Build full URL: {gateway_url}{path}
    +-- 5. httpx.Client.request(method, url, headers, params, json)
    +-- 6. _parse_response(response)
            |
            +-- 204: return None
            +-- JSON parse failure + success: return response.text
            +-- JSON parse failure + error: raise TurumbaAPIError
            +-- 401: raise TurumbaAuthError
            +-- 404 + envelope: raise TurumbaNotFoundError
            +-- Other error + envelope: raise TurumbaAPIError
            +-- Success: return parsed body
    |
    v
client.list() unwraps envelope: body["data"], body["meta"]
    |
    v
command renders data via output.render()
```

### 4.2 Response Envelope Handling

The Turumba API uses a standard envelope format:

```
Single item:  { "success": true,  "data": {...}, "message": null }
List:         { "success": true,  "data": [...], "meta": {"total": N, "skip": 0, "limit": 100} }
Error:        { "success": false, "error": "not_found", "message": "...", "details": {} }
Delete:       204 No Content (no body)
```

The client's convenience methods (`list`, `get`, `create`, `update`, `delete`) unwrap the envelope so commands receive clean data. The raw `request()` method returns the full parsed body for cases where custom handling is needed.

### 4.3 Error Mapping

| HTTP Status | API Body | CLI Exception |
|-------------|----------|---------------|
| 204 | *(none)* | `None` (no error) |
| 401 | `{detail: "..."}` | `TurumbaAuthError` |
| 404 | `{error: "not_found", ...}` | `TurumbaNotFoundError` |
| 4xx/5xx | `{error: "...", message: "..."}` | `TurumbaAPIError` |
| Any | *(unparseable)* | `TurumbaAPIError` (parse_error) |

## 5. Output System (output.py)

### 5.1 Output Modes

| Mode | Flag | Target | Description |
|------|------|--------|-------------|
| Table | `--output table` | Humans | Rich-formatted table with colors, alignment |
| JSON | `--output json` | Agents, scripts | Pretty-printed JSON via Rich |
| CSV | `--output csv` | Spreadsheets | Standard CSV with header row |
| Detail | *(auto)* | Humans | Key-value pairs for single items |

### 5.2 Render Pipeline

```
command has data (list or single item)
    |
    v
render(data, output_format, columns, title, is_list)
    |
    +-- json? --> render_json(data) --> stdout
    |
    +-- is_list=False + table? --> render_detail(data, title) --> stdout
    |
    +-- is_list=True + columns? --> render_table(data, columns, title) --> stdout
    |
    +-- csv? --> render_csv(data, columns) --> stdout
```

### 5.3 Table Column Mapping

Each resource command defines a `LIST_COLUMNS` dict that maps display names to data keys:

```python
LIST_COLUMNS = {
    "ID": "id",           # Display "ID", read data["id"]
    "Name": "name",
    "Type": "channel_type",
    "Status": "status",
    "Enabled": "is_enabled",
}
```

The table renderer handles type coercion:
- `None` --> "-"
- `bool` --> "Yes" / "No"
- `list` --> comma-separated string
- Everything else --> `str(value)`

### 5.4 Console Streams

- **stdout** (`console`): Data output (tables, JSON, CSV, detail views)
- **stderr** (`err_console`): Status messages (`success()`, `error()`, `info()`, `warn()`), pagination metadata

This separation allows `turumba channels list -o json | jq '.[] | .name'` to work correctly -- status messages don't pollute the JSON stream.

## 6. Command Architecture

### 6.1 Command Group Pattern

Each resource is a Typer sub-application registered on the main app:

```python
# commands/channels.py
app = typer.Typer(name="channels", help="Manage delivery channels.")

@app.command("list")
def list_channels(...): ...

@app.command("get")
def get_channel(...): ...

# main.py
from turumba_cli.commands import channels
app.add_typer(channels.app)
```

### 6.2 CRUD Command Template

Every resource follows the same CRUD pattern (channels.py is the reference):

```
list    GET    /v1/{resource}          --filter, --sort, --limit, --skip, --output
get     GET    /v1/{resource}/{id}     --output
create  POST   /v1/{resource}          --<field> for each create field, --output
update  PATCH  /v1/{resource}/{id}     --<field> for each update field, --output
delete  DELETE /v1/{resource}/{id}     --force (skip confirmation)
```

Each command:
1. Instantiates `TurumbaClient(gateway_url=gateway_url)`
2. Calls the appropriate client method in a `try/except`
3. On error, calls `_handle_error(e)` which prints and exits with code 1
4. On success, renders output via `render()` or `render() + render_meta()`

### 6.3 Account ID Resolution Pattern

Resources that require `account_id` (persons, contacts, group-messages, scheduled-messages) follow this pattern:

```python
from turumba_cli.config import get_value

@app.command("create")
def create_person(
    account_id: str = typer.Option(
        None, "--account-id", "-a", help="Account ID (defaults to config default_account_id)"
    ),
    ...
) -> None:
    resolved_account_id = account_id or get_value("default_account_id")
    if not resolved_account_id:
        error("account_id is required. Use --account-id or set: turumba config set default_account_id <id>")
        raise typer.Exit(1)

    data: dict = {"account_id": resolved_account_id}
    ...
```

Resolution order:
1. `--account-id` flag (per-command)
2. `config.default_account_id` (persistent, set via `turumba config set`)
3. Error with guidance message

Note: Some resources (channels, templates, chat-endpoints, conversation-configs) do not need `account_id` in the request body -- the gateway injects it via the context-enricher plugin.

### 6.4 Error Handling Pattern

```python
def _handle_error(e: Exception) -> None:
    if isinstance(e, TurumbaAuthError | TurumbaAPIError):
        error(str(e))
    else:
        error(f"Unexpected error: {e}")
    raise typer.Exit(1)

@app.command("list")
def list_items(...):
    client = TurumbaClient(gateway_url=gateway_url)
    try:
        data, meta = client.list(API_PATH, filter_=filter_, ...)
    except Exception as e:
        _handle_error(e)
    render(data, ...)
```

### 6.5 CRUD Factory (Phase 3)

To reduce boilerplate across remaining resources, a `create_crud_commands()` factory will generate the 5 standard commands:

```python
def create_crud_commands(
    app: typer.Typer,
    resource_name: str,          # "channels"
    api_path: str,               # "/v1/channels"
    list_columns: dict,          # {"ID": "id", "Name": "name", ...}
    create_fields: list,         # Field definitions for create
    update_fields: list,         # Field definitions for update
) -> None:
    """Register list, get, create, update, delete commands."""
```

Resource-specific commands (like `channels test-connection` or `groups add-persons`) are added manually alongside the factory-generated ones.

## 7. Skills System

The skills system is an interactive knowledge base built into the CLI, designed for AI agents to discover and execute Turumba operations in one shot.

### 7.1 Architecture

```
turumba skills list               --> registry.list_topics()
turumba skills show <topic>       --> registry.get_topic(topic) --> render table or JSON
turumba skills how-to "<query>"   --> registry.search(query)
```

### 7.2 Topic Registry

Topics self-register on import. The `__init__.py` in `skills/topics/` imports all topic modules, which triggers registration via `register_topics()`.

```python
# skills/registry.py

@dataclass
class ParamSpec:
    """Structured parameter specification for create commands."""
    name: str                          # "--name / -n"
    type: str                          # "string", "UUID", "enum", "JSON string", etc.
    required: bool                     # True if always required
    description: str                   # Human-readable description
    default: str | None = None         # Default value
    enum_values: list[str] | None = None  # Allowed values for enum types
    condition: str | None = None       # Conditional requirement (e.g. "required if no --body")

@dataclass
class SkillTopic:
    name: str              # "channels"
    summary: str           # "Delivery channels: SMS, Telegram, WhatsApp, Email, etc."
    commands: list[str]    # Related CLI commands
    examples: list[str]    # Copy-pasteable one-shot examples
    workflows: list[dict]  # Multi-step sequences with named steps
    related: list[str]     # Cross-references to other topics
    create_params: list[ParamSpec]  # Structured parameter specs for create command
    notes: list[str]       # Validation rules, credential schemas, important caveats
```

**Registration pattern** (each topic file):

```python
# skills/topics/channels.py
from turumba_cli.skills.registry import ParamSpec, SkillTopic, register_topics

topic = SkillTopic(
    name="channels",
    summary="Delivery channels: SMS, Telegram, WhatsApp, Email, etc.",
    create_params=[
        ParamSpec(name="--name / -n", type="string", required=True, description="Channel display name"),
        ParamSpec(name="--type / -t", type="enum", required=True, description="Channel type",
                  enum_values=["sms", "smpp", "telegram", "whatsapp", "messenger", "email"]),
        ParamSpec(name="--credentials", type="JSON string", required=True,
                  description="Provider credentials (schema depends on --type, see notes)"),
        ...
    ],
    notes=[
        'Credential schemas per channel type:',
        '  telegram:  {"bot_token": "..."}',
        '  sms:       {"api_key": "...", "api_secret": "...", "sender_id": "..."}',
        ...
    ],
    ...
)
register_topics(topic)
```

### 7.3 Available Topics (14)

| Topic | Description | Has create_params? |
|-------|-------------|-------------------|
| `create-resource` | Unified quick-reference for ALL resource creation | No (uses notes) |
| `channels` | Delivery channels with credential schemas per type | Yes |
| `persons` | Persons with account_id + dynamic properties | Yes |
| `contacts` | Contacts with account_id + properties | Yes |
| `templates` | Templates with variable syntax and fallback strategies | Yes |
| `group-messages` | Broadcast with conditional recipient targeting | Yes |
| `scheduled-messages` | Scheduling with send-type conditionals + recurrence | Yes |
| `sending` | Overview of all sending methods | No |
| `chat-endpoints` | Embeddable chat widgets | No |
| `conversation-configs` | Conversation routing rules | No |
| `auth` | Authentication workflows | No |
| `filtering` | Filter and sort syntax | No |
| `overview` | Getting started | No |
| `troubleshooting` | Common issues | No |

### 7.4 How-To Matching

The `how-to` subcommand uses keyword matching to find the most relevant topic and workflow:

1. Tokenize the query into keywords
2. Score each topic by keyword overlap against name, summary, commands, and examples
3. Return the highest-scoring match with step-by-step instructions
4. Output as plain text (default) or JSON (`--output json`)

### 7.5 Agent Integration Flow

```
Agent receives user request: "Create a person with phone number 1003774364"
    |
    v
Agent runs: turumba skills show persons -o json
    |
    v
CLI returns structured JSON:
  - create_params: [{name: "--account-id", required: true, ...}, ...]
  - notes: ["account_id is REQUIRED...", "Properties are free-form..."]
  - examples: ["turumba persons create --properties '{...}'"]
    |
    v
Agent constructs and executes:
  turumba persons create --properties '{"phone": "1003774364"}' -o json
    |
    v
Agent parses JSON result and reports to user
```

For a quick overview of ALL resources at once:
```
turumba skills show create-resource -o json
```

### 7.6 Skills Command Rendering

The `skills show` command renders differently for table vs JSON output:

**Table output** (for humans):
- Header with topic name and summary
- Commands list
- Create Parameters table (Rich table with Parameter, Type, Req?, Description, Default/Values columns)
- Notes section (bulleted list)
- Examples section (with `$` prefix)
- Workflows section (numbered steps)
- Related topics

**JSON output** (for agents):
- All fields serialized as a flat JSON object
- `create_params` as array of objects with all ParamSpec fields
- Ready for programmatic parsing

## 8. Configuration Architecture

### 8.1 File Locations

```
~/.turumba/
    config.yaml          # User preferences (YAML)
    credentials          # Auth tokens (JSON, 0600 perms)
```

### 8.2 Config Schema

```yaml
# ~/.turumba/config.yaml
gateway_url: https://api.dev.turumba.net
default_account_id: null    # Set via: turumba config set default_account_id <uuid>
default_output: table
```

All keys have defaults defined in `config.py:DEFAULTS`. The `load_config()` function merges saved values with defaults, so new config keys are available immediately after CLI upgrade without requiring user action.

### 8.3 Config Precedence

For gateway URL (as an example):

```
1. --gateway-url flag   (highest priority, per-command)
2. config.yaml          (persistent, user-set)
3. DEFAULTS dict        (lowest priority, hardcoded)
```

For account ID:

```
1. --account-id flag    (highest priority, per-command)
2. config.yaml default_account_id  (persistent, user-set)
3. Error with guidance  (if neither is set)
```

## 9. Error Hierarchy

```
TurumbaError (base)
|
+-- TurumbaAuthError
|     "Authentication required. Run: turumba auth login"
|     "Token expired. Run: turumba auth login"
|     "Unauthorized: <detail from API>"
|
+-- TurumbaAPIError
|     "[{status_code}] {error}: {message}"
|     Fields: status_code, error, message, details
|     |
|     +-- TurumbaNotFoundError
|           "[404] not_found: {message}"
|
+-- TurumbaConfigError
      "Configuration is missing or invalid"
```

Exit codes:
- `0` -- Success
- `1` -- Error (auth failure, API error, validation error, missing args)

## 10. Adding a New Resource Command

Follow this checklist when adding a new resource (e.g., `messages`):

### Step 1: Create the command module

Create `src/turumba_cli/commands/messages.py`:

```python
"""Messages commands -- CRUD + send."""

from __future__ import annotations

import typer

from turumba_cli.client import TurumbaClient
from turumba_cli.config import get_value
from turumba_cli.errors import TurumbaAPIError, TurumbaAuthError
from turumba_cli.output import error, render, render_meta, success

app = typer.Typer(name="messages", help="Manage messages.")

API_PATH = "/v1/messages"

LIST_COLUMNS = {
    "ID": "id",
    "Direction": "direction",
    "Status": "status",
    "Body": "message_body",
    "Channel": "channel_id",
    "Created": "created_at",
}


def _handle_error(e: Exception) -> None:
    if isinstance(e, TurumbaAuthError | TurumbaAPIError):
        error(str(e))
    else:
        error(f"Unexpected error: {e}")
    raise typer.Exit(1)


@app.command("list")
def list_messages(
    filter_: list[str] | None = typer.Option(None, "--filter", "-f"),
    sort: list[str] | None = typer.Option(None, "--sort", "-s"),
    limit: int = typer.Option(100, "--limit", "-l"),
    skip: int = typer.Option(0, "--skip"),
    output: str = typer.Option("table", "--output", "-o"),
    gateway_url: str = typer.Option(None, "--gateway-url", "-g"),
) -> None:
    """List messages."""
    client = TurumbaClient(gateway_url=gateway_url)
    try:
        data, meta = client.list(API_PATH, filter_=filter_, sort=sort, limit=limit, skip=skip)
    except Exception as e:
        _handle_error(e)
    render(data, output_format=output, columns=LIST_COLUMNS, title="Messages")
    render_meta(meta, output_format=output)

# ... get, create, update, delete following the same pattern
# ... plus any resource-specific commands (e.g., send)
```

**If the resource requires `account_id`**, add the resolution pattern to the create command (see Section 6.3).

### Step 2: Register in main.py

```python
from turumba_cli.commands import messages
app.add_typer(messages.app)
```

### Step 3: Add skill topic

Create `src/turumba_cli/skills/topics/messages.py` with:
- `create_params`: List of `ParamSpec` for every create parameter
- `notes`: Validation rules, required field combinations, credential schemas
- `examples`: Ready-to-run one-shot command examples
- `workflows`: Multi-step sequences for common tasks

Register the import in `skills/topics/__init__.py`.

Update the `create-resource` topic in `skills/topics/create_resource.py` to include the new resource in the quick-reference notes.

### Step 4: Test

```bash
ruff check src/turumba_cli/commands/messages.py
ruff check src/turumba_cli/skills/topics/messages.py
pytest tests/test_commands/test_messages.py
turumba messages --help
turumba skills show messages
```

## 11. Testing Strategy

### Test Categories

| Category | Marker | Scope |
|----------|--------|-------|
| Unit | `@pytest.mark.unit` | Core modules (config, credentials, output, client) |
| Integration | `@pytest.mark.integration` | Commands with mocked HTTP (respx) |

### Mocking Strategy

- **HTTP calls**: Use `respx` to mock `httpx` requests. No real API calls in tests.
- **Keyring**: Mock `keyring` module to avoid system keyring access.
- **File system**: Use `tmp_path` fixture for config/credentials file tests.
- **Console output**: Capture Rich console output for assertion.

### Coverage Target

80% minimum (enforced in `pyproject.toml`).

## 12. Deployment and Distribution

### PyPI Package

```toml
[project.scripts]
turumba = "turumba_cli.main:app"
```

Published as `turumba-cli` on PyPI. Users install with:

```bash
pipx install turumba-cli    # Recommended (isolated)
pip install turumba-cli     # Alternative
```

### Version Management

Version is defined in `src/turumba_cli/__init__.py`:

```python
__version__ = "0.1.0"
```

Follows [Semantic Versioning](https://semver.org/):
- `0.x.y` -- Pre-1.0 development (current)
- Breaking changes increment minor version
- Bug fixes increment patch version

## 13. Relationship to Turumba Platform

### Service Map

```
turumba_cli  ----HTTP---->  turumba_gateway  ----HTTP---->  turumba_account_api
                                             ----HTTP---->  turumba_messaging_api
```

The CLI is a **client** of the gateway. It has no direct access to databases, message queues, or backend services. All state management (multi-tenancy, message dispatch, contact resolution) happens server-side.

### API Endpoint Coverage

| Resource Group | API Endpoints | CLI Commands | Status |
|---------------|---------------|--------------|--------|
| Auth | 4 | login, logout, register, verify-email, status | Done |
| Context | 1 | context (whoami) | Done |
| Channels | 7 | list, get, create, update, delete, test-connection | Done |
| Persons | 5 | list, get, create, update, delete | Done |
| Contacts | 5 | list, get, create, update, delete | Done |
| Group Messages | 5 | list, get, create, update, delete | Done |
| Scheduled Messages | 5 | list, get, create, update, delete | Done |
| Chat Endpoints | 5 | list, get, create, update, delete | Done |
| Conversation Configs | 6 | list, get, create, update, delete, evaluate | Done |
| Skills | -- | list, show, how-to | Done |
| Accounts | 5 | list, get, create, update, delete | Phase 3 |
| Users | 5 | list, get, create, update, delete | Phase 3 |
| Groups | 9 | list, get, create, update, delete, add/remove-persons, import | Phase 3 |
| Roles | 5 | list, get, create, update, delete | Phase 3 |
| Messages | 5 | list, get, create, update, delete, send | Phase 3 |
| Templates | 5 | list, get, create, update, delete | Phase 3 |
| Conversations | 7 | list, get, create, update, delete, send-message | Phase 3 |
| **Total** | **84** | | |
