# User Guide

## Purpose

Use `docs/user-guide/` for operator-facing instructions on how to run, inspect, and troubleshoot `clisbot`.

This folder should explain:

- how to start and inspect the service
- how to interact with the dedicated tmux server
- where runtime state lives
- where channel-specific operating notes live
- how config reload works in practice
- how stale tmux cleanup and sensitive commands work in practice
- how app ownership, roles, and privileged actions are expected to work for operators

Related pages:

- [Channel Operations](channels.md)
- [Bots And Credentials](bots-and-credentials.md)
- [Release Notes](../releases/README.md)
- [Codex CLI Guide](codex-cli.md)
- [Claude CLI Guide](claude-cli.md)
- [Gemini CLI Guide](gemini-cli.md)
- [Telegram Bot Setup](telegram-setup.md)
- [Zalo Bot Setup](zalo-bot-setup.md)
- [Slack App Setup](slack-setup.md)
- [Slash Commands](slash-commands.md)
- [Native CLI Commands](native-cli-commands.md)
- [CLI Commands](cli-commands.md)
- [Agent Progress Replies](agent-progress-replies.md)
- [Authorization And Roles](auth-and-roles.md)
- [Runtime Operations](runtime-operations.md)

If setup is unclear, clone this repo, open it in Codex or Claude Code, and ask it to help set up `clisbot`. The docs here are kept current enough for guided setup and troubleshooting.

## CLI-Specific Notes

If you are choosing a default coding CLI or debugging routed behavior, start here:

- [Codex CLI Guide](codex-cli.md)
- [Claude CLI Guide](claude-cli.md)
- [Gemini CLI Guide](gemini-cli.md)

## Platform Support

- Linux and macOS are the supported host environments today.
- Native Windows is not supported yet because `clisbot` currently depends on `tmux`, a dedicated tmux socket, and Bash-based runtime helpers.
- If you use Windows, run `clisbot` inside WSL2 and follow the normal Linux-style setup from that WSL2 shell.

## Service

Default config path:

`~/.clisbot/clisbot.json`

Bootstrap the default config once:

```bash
bun run init
```

Fastest path:

```bash
bun run start --cli codex --bot-type personal --telegram-bot-token <your-telegram-bot-token>
```

Focused help exists for first-run bootstrap too:

```bash
clisbot start --help
clisbot init --help
```

For durable one-shot follow-up work, use `clisbot queues --help`. Queue
creation requires explicit routed addressing plus `--sender`; there is no
ambient `--current` mode, and queue commands use `--channel/--target`
addressing.

After your first successful `clisbot start`:

1. Get your principal from a surface the bot can already see:
   Telegram groups or topics can use `/whoami` even before routing, while DMs with `pairing` policy need pairing first.
   `/whoami` and routed `/start` show:
   - `principal`
   Routed `/whoami` also shows `sessionId` plus whether that value is already
   persisted for the conversation.
   Typical values look like `telegram:1276408333` or `slack:U123ABC456`.
2. Grant the first app owner with `clisbot auth add-user app --role owner --user <principal>`, for example `clisbot auth add-user app --role owner --user telegram:1276408333`.
3. Inspect and tune role permissions with `clisbot auth --help`.
4. Continue with route setup in [Channel Operations](channels.md) and auth details in [Authorization And Roles](auth-and-roles.md).

Persist the token for later plain `clisbot start` runs:

```bash
clisbot start --cli codex --bot-type personal --telegram-bot-token <your-telegram-bot-token> --persist
```

If you use the packaged CLI:

```bash
clisbot start --cli codex --bot-type personal --telegram-bot-token <your-telegram-bot-token> --persist
```

Packaged CLI runtime expects Node 20+.

Native Windows shells such as PowerShell or Command Prompt are not supported for runtime operation. Use WSL2 instead.

Start the service in the foreground:

```bash
bun run start
```

Start the packaged CLI in the background:

```bash
npx clisbot start
```

Restart the packaged CLI:

```bash
clisbot restart
```

Stop the packaged CLI:

```bash
clisbot stop
```

Stop the packaged CLI and clean up all tmux sessions on the clisbot socket:

```bash
clisbot stop --hard
```

Start the service in dev mode:

```bash
bun run dev
```

Print the resolved default config path:

```bash
clisbot status
```

Important distinction:

- `clisbot start` seeds `~/.clisbot/clisbot.json` automatically if it does not exist
- `clisbot start` requires Slack, Telegram, or Zalo Bot token references before it bootstraps anything
- when no agents exist yet, `start` requires both `--cli` and `--bot-type` to create the first `default` agent
- `--slack-app-token`, `--slack-bot-token`, `--telegram-bot-token`, and `--zalo-bot-token` accept either bare env names like `CUSTOM_SLACK_APP_TOKEN` or placeholder form like `${CUSTOM_SLACK_APP_TOKEN}`
- `clisbot start` prints which token refs or credential sources it is using for the channels you requested
- existing enabled channel token refs are validated before the detached runtime is spawned
- fresh bootstrap enables only the channels and bots you named explicitly with flags
- `--persist` writes canonical credential files for any literal channel tokens from that invocation, so later plain `clisbot start` can reuse them
- Gemini-first setups also need Gemini itself to already be authenticated for routed use, either by a prior direct `gemini` login or by headless auth such as `GEMINI_API_KEY` or Vertex AI credentials
- the generated default config does not preseed Slack channel routes, Slack groups, Telegram groups, or Telegram topics
- you must add channel routes manually in `~/.clisbot/clisbot.json`
- `clisbot start` prints a brief agents and channels summary after launch
- `clisbot start` and `clisbot status` print the primary agent workspace before the config path
- that workspace is the default working directory for the agent and contains runtime state, sessions, personality files, and setup guidance
- when no agents exist yet, `clisbot start` prints first-run guidance for direct `start --cli ... --bot-type ...` usage and bootstrap completion
- `clisbot start` runs as a background service and writes runtime pid and log files under `~/.clisbot/state`
- `bun run dev` watches source files in this repo
- `control.configReload.watch` watches the runtime config file
- these are separate mechanisms

## Agents CLI

Use `clisbot agents ...` to manage configured agents, bootstrap files, and agent-local mode overrides.

Focused help:

```bash
clisbot agents --help
```

Most-used commands:

- `clisbot agents list`
- `clisbot agents list --json`
- `clisbot agents add <id> --cli <codex|claude|gemini>`
- `clisbot agents bootstrap <id> --bot-type <personal|team>`
- `clisbot agents response-mode status --agent <id>`
- `clisbot agents response-mode set <capture-pane|message-tool> --agent <id>`
- `clisbot agents response-mode clear --agent <id>`
- `clisbot agents additional-message-mode status --agent <id>`
- `clisbot agents additional-message-mode set <queue|steer> --agent <id>`
- `clisbot agents additional-message-mode clear --agent <id>`

Important rules:

- `agents add` requires `--cli`
- supported tools are `codex`, `claude`, and `gemini`
- `agents bootstrap` requires `--bot-type`
- first-run public choices stay `--bot-type personal|team`
- bot fallback routing now belongs to `clisbot bots ...`
- route-level routing now belongs to `clisbot routes ...`

Bootstrap behavior:

- bootstrap is optional; agents can run without it when you already have a workspace and do not need clisbot to seed guidance files
- `personal-assistant` and `team-assistant` copy the matching bootstrap templates when you pass `--bot-type`
- `AGENTS.md` is the one canonical workspace instruction file across supported CLIs
- codex bootstrap requires `AGENTS.md` and `IDENTITY.md`
- claude bootstrap requires `AGENTS.md`, `IDENTITY.md`, and a `CLAUDE.md -> AGENTS.md` symlink for Claude discovery
- gemini bootstrap requires `AGENTS.md`, `IDENTITY.md`, and a `GEMINI.md -> AGENTS.md` symlink for Gemini discovery
- bootstrap state is `missing`, `not-bootstrapped`, or `bootstrapped`
- Gemini still needs its own direct auth or headless auth path before routed prompts can succeed

Operational note:

- the default generated bot config still points at the `default` agent
- if your first useful agent uses another id, update the fallback with `clisbot bots set-agent ...` or override it on a specific route with `clisbot routes set-agent ...`

## Bots CLI

Use `clisbot bots ...` to manage provider bot identities, credentials, default bot selection, and bot-level fallback policy.

Focused help:

```bash
clisbot bots --help
```

Most-used commands:

- `clisbot bots list`
- `clisbot bots add --channel telegram --bot default --bot-token TELEGRAM_BOT_TOKEN --persist`
- `clisbot bots add --channel zalo-bot --bot default --bot-token ZALO_BOT_TOKEN --persist`
- `clisbot bots add --channel slack --bot default --app-token SLACK_APP_TOKEN --bot-token SLACK_BOT_TOKEN --persist`
- `clisbot bots set-agent --channel telegram --bot default --agent support`
- `clisbot bots set-default --channel slack --bot ops`
- `clisbot bots get-credentials-source --channel slack --bot default`
- `clisbot bots set-dm-policy --channel telegram --bot default --policy pairing`

## Routes CLI

Use `clisbot routes ...` to admit specific Slack, Telegram, or Zalo Bot surfaces under a bot.

Focused help:

```bash
clisbot routes --help
clisbot routes --help --channel telegram
```

Most-used commands:

- `clisbot routes list`
- `clisbot routes add --channel slack group:C1234567890 --bot default`
- `clisbot routes add --channel slack group:G1234567890 --bot default`
- `clisbot routes add --channel telegram group:-1001234567890 --bot default`
- `clisbot routes add --channel telegram topic:-1001234567890:42 --bot default`
- `clisbot routes add --channel telegram dm:* --bot default`
- `clisbot routes add --channel zalo-bot dm:<user-id> --bot default`
- `clisbot routes set-agent --channel slack group:C1234567890 --bot default --agent support`
- `clisbot routes set-require-mention --channel telegram group:-1001234567890 --bot default --value false`
- `clisbot routes set-response-mode --channel slack group:C1234567890 --bot default --mode message-tool`
- `clisbot routes set-additional-message-mode --channel telegram topic:-1001234567890:42 --bot default --mode queue`

Important behavior:

- preferred route ids are channel-scoped: Slack/Telegram shared surfaces use `group:<id>` or `group:*`, Telegram topics use `topic:<chatId>:<topicId>`, and DM-only providers such as Zalo Bot use `dm:<id|*>`
- stored config uses raw ids plus `*` inside `directMessages` and `groups`
- legacy Slack `channel:<id>` input is still accepted for compatibility
- `group:*` is the default multi-user sender policy node of a bot
- `disabled` means truly silent, including owner/admin and pairing guidance
- owner/admin do not bypass `groupPolicy`/`channelPolicy` admission; after a group is admitted and enabled, they bypass sender allowlists, while `blockUsers` still wins
- `add` is create-only
- if the route already exists, use the matching `set-<key>` command
- route-local auth visibility still depends on `verbose` and resolved auth roles
  - `/loop 3 /codereview`
  - `/loop 3`
  - `/loop status`
  - `/loop cancel <id>`
- every `/loop` command must include an interval, count, or wall-clock schedule
- if no prompt is supplied after that interval, count, or schedule, clisbot uses the workspace `LOOP.md` file
- bare positive integers in `/loop` mean times mode, compact durations such as `5m` mean interval mode, and `every ... at HH:MM` means wall-clock schedule mode
- interval loops must be at least `1m`
- interval loops below `5m` require `--force`
- with leading interval syntax, place `--force` immediately after the interval token
- with `every ...` syntax, place `--force` immediately after the interval clause
- advanced recurring loop creation also accepts `--loop-start <none|brief|full>` to override the default scheduled start notification behavior for that one loop; omit it to keep the route default
- wall-clock schedules must use `HH:MM` in 24-hour format and wait until the next matching local time instead of firing immediately
- wall-clock schedules resolve timezone from the effective timezone resolver: one-off loop override, route/topic, agent, bot, `app.timezone`, legacy fallbacks, then host fallback only when config is missing
- chat `/loop` wall-clock creation persists immediately and returns the resolved timezone, local next run, UTC next run, and exact cancel command so a wrong timezone can be fixed quickly
- managed loops are created with ids, bounded by `control.loop.maxRunsPerLoop`, and capped globally by `control.loop.maxActiveLoops`
- managed loops use `skip-if-busy`, so a tick is dropped instead of stacking more queued work when the session is already busy
- managed loops persist in session state and are restored after restart
- queued prompts persist under the session entry, can be inspected with
  `clisbot queues list`, and are capped per session by
  `control.queue.maxPendingItemsPerSession` (default `20`)
- `/stop` interrupts the current run only; use `/loop cancel` to cancel loops
- `/nudge` sends one extra `Enter` to the current tmux session without resending the prompt body; use it only as a manual recovery tool when a session seems stuck after input delivery

Timezone config examples:

```json
{
  "app": {
    "timezone": "Asia/Ho_Chi_Minh"
  }
}
```

```json
{
  "bots": {
    "telegram": {
      "default": {
        "groups": {
          "-1001234567890": {
            "timezone": "Asia/Ho_Chi_Minh",
            "topics": {
              "4": {
                "timezone": "America/Los_Angeles"
              }
            }
          }
        }
      }
    }
  }
}
```

- agent `timezone` overrides `app.timezone` for that assistant/workspace
- route `timezone` overrides app/agent timezone for that surface
- use `clisbot timezone set <iana>` for the app default, `clisbot agents set-timezone --agent <id> <iana>` for one assistant/workspace, and `clisbot routes set-timezone ... <iana>` for one surface
- once a wall-clock loop is created, its effective timezone is persisted on that loop record
- if the service is already running, restart it after changing channel enablement
- `clisbot routes` and `clisbot routes --help` print setup guidance for Slack ids, Telegram group or topic ids, allowlists, and routed auth docs

## Loops CLI

Use `clisbot loops ...` to create, inspect, or cancel loop work from the operator CLI.

Current subcommands:

- `clisbot loops list`
- `clisbot loops status`
- `clisbot loops status --channel slack --target group:C123 --thread-id 1712345678.123456`
- `clisbot loops create --channel slack --target group:C123 --thread-id 1712345678.123456 --sender slack:U1234567890 every day at 07:00 check CI`
- `clisbot loops --channel telegram --target -1001234567890 --topic-id 42 --sender telegram:1276408333 5m check CI`
- `clisbot loops --channel slack --target group:C123 --thread-id 1712345678.123456 --sender slack:U1234567890 3 review backlog`
- `clisbot loops cancel <id>`
- `clisbot loops cancel --channel slack --target group:C123 --thread-id 1712345678.123456 --all`
- `clisbot loops cancel --all`

Targeting rules:

- `--target` chooses the routed surface
- on Slack, use `group:<id>`, `dm:<user-or-channel-id>`, or raw `C...` / `G...` / `D...` ids
- on Telegram, use `group:<chat-id>` or `topic:<chat-id>:<topic-id>`; raw numeric chat ids are still accepted for compatibility
- `--thread-id` means an existing Slack thread ts
- `--topic-id` means a Telegram topic id
- omitting the sub-surface flag targets the parent Slack channel/group/DM or the parent Telegram chat
- use `clisbot loops --help --channel <channel-name>` or `clisbot loops create --help --channel <channel-name>` for channel-specific loop extensions
- `--sender <principal>` is required for loop creation and records the human creator as `slack:<user-id>`, `telegram:<user-id>`, or `zalo-bot:<user-id>`
- `--sender-name <name>` and `--sender-handle <handle>` optionally store readable creator context for scheduled prompts
- in Telegram forum groups, omitting `--topic-id` targets the parent chat surface; sends then follow Telegram's normal no-`message_thread_id` behavior, which is the General topic when that forum has one

Important behavior:

- bare `list` and `status` are app-wide inventory
- scoped `list` or `status` with `--channel ... --target ...` matches one routed session
- recurring CLI-created loops reuse the same parser family as `/loop` and land in the same persisted session store
- CLI loop creation fails without `--sender` so delayed work keeps a real creator instead of rendering sender as unavailable
- the CLI accepts the same expression families as `/loop`: interval, forced interval, times/count, and wall-clock schedules
- advanced recurring `clisbot loops create` also accepts `--loop-start <none|brief|full>` to override the default scheduled start notification behavior for that one loop
- omitting the prompt body loads `LOOP.md` from the target workspace, matching maintenance-loop behavior from chat
- every row includes `agentId` and `sessionKey` because the operator CLI is app-wide rather than route-scoped
- `cancel --all` is app-wide when no routed target is given
- scoped `cancel --all` clears one routed session and scoped `cancel --all --app` clears the whole app
- if runtime is already running, it reconciles new recurring loops from persistence; if runtime is stopped, those loops activate on the next start
- count/times loops reserve all iterations immediately as durable queue items for the routed session; recurring loops are persisted for the runtime scheduler
- loop state is read from `session.storePath`, which defaults to `~/.clisbot/state/sessions.json`
- if `CLISBOT_HOME` is set, the default session store becomes `<CLISBOT_HOME>/state/sessions.json`
- the runtime scheduler re-checks persisted loop state before each scheduled tick, so cancelling through the CLI suppresses future runs without adding a separate control socket
- cancelling through the CLI does not interrupt an iteration that is already running

## Start And Status Output

`clisbot start` now prints an operator summary after startup.

What it includes:

- configured agents with tool, bootstrap state, routed usage, and last activity
- configured Slack and Telegram channel summaries with connection state and last activity
- first-run guidance when no agents are configured
- bootstrap follow-up guidance when an agent workspace is `missing` or `not-bootstrapped`
- direct next steps for `clisbot --help` and the user guide when no agents are configured
- channel-specific token checks such as `Slack channel: found token...` or `Telegram channel: token not found...`
- the same operator help even when the service is already running

## Custom Token References

If your shell uses different environment variable names, pass them directly on first run:

```bash
clisbot start \
  --cli codex \
  --bot-type personal \
  --slack-app-token CUSTOM_SLACK_APP_TOKEN \
  --slack-bot-token CUSTOM_SLACK_BOT_TOKEN
```

Or for Telegram:

```bash
clisbot start \
  --cli claude \
  --bot-type team \
  --telegram-bot-token CUSTOM_TELEGRAM_BOT_TOKEN
```

Important behavior:

- these values are written into `~/.clisbot/clisbot.json` as `${ENV_NAME}` placeholders
- bare env names are normalized into `${ENV_NAME}` placeholders in config
- `clisbot` does not resolve or print the secret value during config bootstrap
- this is meant for env variable names you chose, not raw secret literals
- if you still prefer manual setup, `clisbot init` accepts the same `--cli`, `--bot-type`, and token-reference flags as `clisbot start`, but it does not start the runtime

`clisbot status` now prints:

- process status, pid, config path, log path, and tmux socket
- aggregate stats for agents, bootstrapped agents, pending bootstrap agents, and running tmux sessions
- per-agent last activity
- per-channel last activity and effective connection state
- active runtime channel identity when available, such as account id, bot label, app id, and a short token fingerprint

Connection state meaning:

- `active`: channel is enabled and the runtime process is running
- `stopped`: channel is enabled in config but the runtime process is not running
- `disabled`: channel is turned off in config

## Runtime Operations

For runtime-level tuning and troubleshooting, use the dedicated guide:

- [Runtime Operations](runtime-operations.md)

That page now holds:

- turn execution timeout semantics
- long-running session commands such as `/attach`, `/detach`, and `/watch`
- runner debug commands such as `clisbot runner list|inspect|watch` and the `clisbot inspect` / `clisbot watch` shorthand
- dedicated tmux socket usage and raw tmux fallback commands
- runtime state file locations and Codex trust troubleshooting
- stale tmux cleanup behavior
- config reload rules and follow-up-state notes

## Notes

- the default workspace for agent `default` is `~/.clisbot/workspaces/default`
- tmux session names now derive from session keys, so one agent can have multiple tmux sessions at once
- the default session name template is `agents.defaults.session.name = "{sessionKey}"`
- tmux session names use a readable tmux-safe prefix plus a stable short hash from the logical `sessionKey`
- prefer `clisbot runner list`, `clisbot inspect --latest`, and `clisbot watch --latest|--next` before dropping to raw tmux
- if the Codex trust screen appears stale after attaching, press `Ctrl-L` inside the tmux session to redraw the pane
