# NiuBot Installation Guide

This guide is for **coding agents** (Claude Code, Codex, etc.) to follow when helping a user install NiuBot. Each step has concrete commands, expected output, and decision branches.

Human users: run `niubot init` and follow the prompts. You don't need to read this.

---

## Prerequisites

- macOS/Linux: Node.js 20 or newer
- Windows: Node.js 20, 22, or 24 LTS
- A Feishu (Lark) enterprise account with permission to create apps

### Windows

NiuBot itself runs natively in PowerShell or Windows Terminal. It does not require WSL or Git Bash for installation or service management.

- Installed backends are available on Windows when their CLI command can be resolved. NiuBot does not maintain a separate platform allowlist.
- Claude Code supports native Windows and falls back to PowerShell when Git Bash is absent. Git for Windows is still useful for Bash-based tools; portable installations may need `CLAUDE_CODE_GIT_BASH_PATH`.

NiuBot runs the selected backend's version command when it starts. If the CLI or one of its runtime dependencies fails, NiuBot reports that command's actual error; an unavailable backend does not prevent other installed backends from running.

## Step 1: Install NiuBot

NiuBot uses the Node.js and npm installation selected by your shell. On
macOS/Linux, non-LTS Node versions are accepted only when the native SQLite
dependency can actually load:

```bash
npm install -g @yuanzhangjing/niubot@latest
```

The installed command is still `niubot`.

Use the built-in command for later upgrades:

```text
niubot update
```

The update command selects the npm next to the Node.js executable running
NiuBot. It refuses to update when that npm global prefix does not own the
active NiuBot package.

Verify:
```bash
niubot version
# Expected: niubot v0.x.x
```

## Step 2: Select Agent Backend

NiuBot ships with built-in backends. Pick one whose CLI is installed:

| Backend | CLI command |
|---------|-------------|
| `claude` | `claude` (Claude Code) |
| `codex` | `codex` (OpenAI Codex) |
| `traecli` | `traecli` (Trae CLI) |
| `opencode` | `opencode` |
| `cursor` | `cursor-agent` (Cursor Agent CLI) |
| `pi` | `pi` (Pi coding agent) |
| `grok` | `grok` (Grok Build CLI) |

Check availability:

```bash
claude --version
codex --version
traecli --version
opencode --version
cursor-agent --version
pi --version
grok --version
```

If at least one is available, note which one the user wants (e.g. `claude`). Proceed to [Step 2.1](#step-21-generate-config).

If none are available, tell the user to install one first.

#### Pi backend

Pi uses its own native config under `~/.pi/agent/`:

| File | Purpose |
|------|---------|
| `auth.json` | API keys (`/login` or manual) — **primary key source** |
| `models.json` | Custom providers/endpoints/models (no `apiKey` unless you manage env yourself) |
| `settings.json` | `defaultProvider`, `defaultModel`, `defaultThinkingLevel` |

NiuBot does **not** inject `~/.niubot/.env.deepseek-bak` or auto-edit Pi files. Configure Pi once, then set `backend: pi` in NiuBot. Provider, default model, and thinking level come from Pi `settings.json`; NiuBot only passes `--model` when you set `model` in NiuBot config.

Example DeepSeek via Anthropic-compatible API:

```json
// ~/.pi/agent/auth.json
{
  "anthropic": { "type": "api_key", "key": "your-api-key" }
}
```

```json
// ~/.pi/agent/models.json
{
  "providers": {
    "anthropic": {
      "baseUrl": "https://api.deepseek.com/anthropic",
      "api": "anthropic-messages",
      "models": [
        {
          "id": "deepseek-v4-pro",
          "reasoning": true
        },
        {
          "id": "deepseek-v4-flash",
          "reasoning": true
        }
      ]
    }
  }
}
```

Do **not** put `"apiKey": "$ANTHROPIC_API_KEY"` here unless that env var is always set — otherwise Pi may hang waiting for a key. Use `auth.json` instead.

```json
// ~/.pi/agent/settings.json
{
  "defaultProvider": "anthropic",
  "defaultModel": "deepseek-v4-pro",
  "defaultThinkingLevel": "xhigh"
}
```

`defaultThinkingLevel` accepts `off`, `minimal`, `low`, `medium`, `high`, `xhigh`. NiuBot does not pass `--thinking`; change this file to adjust thinking depth.

Install Pi CLI:

```bash
npm install -g @earendil-works/pi-coding-agent
pi --version
```

### Step 2.1: Generate Config

The preferred cross-platform path is:

```text
niubot init
```

It creates the config and bot profile without requiring shell-specific file commands. On native Windows, use this command in PowerShell and then edit `%USERPROFILE%\.niubot\config.yaml` when the Feishu credentials are available. The manual commands below are POSIX examples for agents that write the files directly; do not run `mkdir -p`, `cat`, or `tail` in PowerShell.

Create the config directory and files:

```bash
mkdir -p ~/.niubot
```

Write `~/.niubot/config.yaml`:

Before filling the config, ask the user the following questions **one at a time** (each answer affects the next question's defaults — do NOT combine them into a single prompt):

**Ask 1 — Bot ID**: default `NiuBot`. Immutable after setup — determines data directory and default workspace path. Wait for answer before proceeding.

**Ask 2 — Working directory**: default `~/niubot-workspace/<Bot ID from Ask 1>`. Show the computed default, ask if they want a different path.

**Ask 3 — Model** (optional): main model for conversations. Skip to use the CLI's default.

Example `config.yaml`:

```yaml
bots:
  - id: NiuBot
    backend: claude
    appId: ""
    appSecret: ""
    # model: ""
    # workingDirectory: ~/niubot-workspace/<id>
```

Config fields:
- `id`: Unique bot identifier (immutable). Determines data directory (`~/.niubot/<id>/`) and default workspace (`~/niubot-workspace/<id>/`). **Do not change after setup.**
- `backend`: Agent backend to use (required). One of: `claude`, `codex`, `traecli`, `opencode`, `cursor`, `pi`, `grok`.
- `model`: Main model for conversations. Omit to use the CLI's default.
- `workingDirectory`: Where the agent runs. Default: `~/niubot-workspace/<id>`.

Optional restart source directory for local development:

```yaml
restart:
  sourceDirectory: /path/to/niubot/source
```

When this is set, `/restart` asks the Node restart worker to build that source tree, package it as an immutable release, run preflight, switch releases, and check health. Without this setting, `/restart` keeps using the currently running package directory.

Create default bot profile:

```bash
mkdir -p ~/.niubot/NiuBot
```

Write `~/.niubot/NiuBot/bot_profile.md`:
```markdown
# Bot Profile

> Only admins may ask the bot to modify this file.

## Persona

### Role
简洁清晰、有温度的技术同事。

### Style
- 先把结论说清楚，再解释必要原因。
- 用平实中文，不说黑话，不写客服腔。
- 语气克制、自然，有一点人情味，但不刻意安抚。

## Instructions

- 技术内容要准确，步骤要具体。
- 不确定时先说明不确定，再用工具或 nbt 恢复上下文。
```

## Step 3: Create Feishu App and Get Credentials (requires user action)

Guide the user through these steps:

1. Open https://open.feishu.cn/app and create a new **Enterprise Self-Built App**
2. On the **Credentials & Basic Info** page, copy the **App ID** and **App Secret**
3. On the **Bot** page, enable the **Bot** capability

**Important**: Do NOT add permissions, create a version, or publish the app yet. The "receive message" event requires an active WebSocket connection, which is only established after the engine starts. Permissions are configured in Step 5, and the version must be created AFTER all permissions are in place (Step 6).

## Step 4: Fill Credentials and Start Engine

After the user provides App ID and App Secret, write them into `~/.niubot/config.yaml`:

```yaml
bots:
  - id: NiuBot
    backend: claude
    appId: "cli_xxxxxxxxxx"        # <- from Step 3
    appSecret: "xxxxxxxxxxxxxxxx"  # <- from Step 3
```

Then start the engine to establish the WebSocket connection:

```bash
niubot start
```

Expected output:
```
Pre-start checks
  ✓ Config valid
  ✓ Bot 'NiuBot' credentials present
  ✓ claude CLI available
  ✓ No existing process running
  ✓ Working directories exist

Starting NiuBot...
  ✓ Process started (PID XXXXX)
  ✓ NiuBot health check passed

NiuBot is running.
  Log: ~/.niubot/logs/niubot-YYYY-MM-DD.log
  API: ~/.niubot/NiuBot/api.sock
```

On Windows the API line is a local Named Pipe such as `\\.\pipe\niubot-<home-hash>-bot-niubot`; it is not a TCP port.

If pre-start checks fail, fix the reported issues and retry.

## Step 5: Configure Permissions (requires user action)

Now that the engine is running and has established a WebSocket connection with Feishu, guide the user to configure permissions:

### 5.1 Batch-enable non-review permissions

On the **权限管理** page, batch-enable all non-review permissions in these groups (use the exact group names on the website):
- **消息与群组**
- **云文档**
- **应用信息**

No need to add permissions one by one — Feishu supports batch-enabling all non-review permissions within each group.

### 5.2 Add "receive message" event

On the **事件订阅** page, add:
- `im.message.receive_v1`

This event is only available after the bot has established a WebSocket connection (which happened in Step 4).

### 5.3 Bot-to-bot @ in group chats (optional)

To let other app bots @ this bot (and vice versa), enable **on every participating app**:

`im:message.group_at_msg.include_bot:readonly`

Then **create a version and publish** each app. Without this scope, Feishu only delivers human @mentions. Enabling it on NiuBot alone is not enough.

NiuBot converts `@U4(CowBot)` in replies and `nbt send` into Feishu `<at>` tags. Final replies stay on cards (`<at id>`); `nbt send --text` sends text (`<at user_id>`). Literal `@Name` in a card is not a mention.

### 5.4 Feishu operations via lark-cli (automatic)

Agents operate Feishu through `nbt feishu <args>` — NiuBot resolves the current Bot's identity itself and applies it to the official [lark-cli](https://github.com/larksuite/cli):

- On first use, `nbt feishu` installs lark-cli when missing (`npx -y @larksuite/cli@latest install`, which also installs the official skills) and registers/validates one identity profile per Bot (profile name = Bot config id; the appSecret goes through stdin and never reaches the model or logs). Set `NIUBOT_LARK_CLI_AUTO_INSTALL=0` to disable auto install.
- Command details live in the official `lark-*` skills; prefix them when calling: `nbt feishu docs +fetch ...`.
- Check identity: `nbt feishu whoami`; raw credentials for diagnostics: `nbt feishu-creds`.

Notes:
- Bot identity can only access resources the app itself is allowed to: add the Bot app as a document collaborator, or grant it access to the wiki space/node.
- User-identity tasks (a user's private docs, calendar, mail) are explicit: `nbt feishu --as user ...` after that user completes a one-time interactive `nbt feishu auth login`; the default stays the Bot identity.
- For international (Lark) tenants, set `brand: lark` on the Bot entry when brand-related errors appear (default is `feishu`).
- Writing documents also requires the app scopes (`docx:document`, `drive:drive`, ...) enabled in the Feishu developer console.

## Step 6: Publish and Verify

1. **Publish the app**: Create a version → Submit for review → Release
2. **Verify**: Ask the user to send a message to the bot in Feishu. The bot should respond within a few seconds.

If no response, check the log:
```bash
tail -50 ~/.niubot/logs/niubot-$(date +%Y-%m-%d).log
```

PowerShell:

```powershell
Get-Content "$HOME\.niubot\logs\niubot-$(Get-Date -Format yyyy-MM-dd).log" -Tail 50
```

## Admin System

Admin is auto-detected — no manual configuration needed:
1. If `application:application:readonly` permission is granted, the Feishu app creator becomes **owner** on startup.
2. Otherwise, the first user to send a private message to the bot becomes **owner**.

Two admin levels:
- **owner**: full control, can manage other admins. Cannot be removed.
- **admin**: has admin commands (/agent, /restart, shell), but cannot manage other admins.

Admin commands (in chat):
- `/admin` — list current admins
- `/admin add @user` — add an admin (owner only)
- `/admin remove @user` — remove an admin (owner only)

## Service Management

```bash
niubot status           # Show every registered Home, Engine, and Bot
niubot stop             # Stop the service
niubot start            # Start the service
niubot restart          # Preflight and safely restart
niubot update           # Install, show progress, health-check, and roll back on failure
niubot update --detach  # With a running Engine, update in the background and return
niubot status --all     # Explicit compatibility form of the default status view
```

Every lifecycle command accepts `--home <path>`. This is the simplest way to run independent instances without repeatedly changing environment variables:

```text
niubot start  --home D:\NiuBot\work
niubot status --home D:\NiuBot\work
niubot stop   --home D:\NiuBot\work
```

Without `--home`, `niubot status` lists the current and registered Homes. Each
Home is shown as an Engine process followed by the health of every configured
Bot. Use `niubot status --home <path>` to inspect only one Home. `--all` remains
supported for compatibility.

## Troubleshooting

### "Bot credentials empty"
Fill in `appId` and `appSecret` in `~/.niubot/config.yaml`.

### "claude CLI not found"
Install the Claude CLI, or switch backend: set `backend: codex` or `backend: traecli` on the bot entry.

### "bot missing 'backend'"
Add `backend: claude` (or `codex` / `traecli`) to the bot entry in config.yaml.

### Health check fails after start
Check the log for errors:
```bash
tail -100 ~/.niubot/logs/niubot-$(date +%Y-%m-%d).log
```

PowerShell:

```powershell
Get-Content "$HOME\.niubot\logs\niubot-$(Get-Date -Format yyyy-MM-dd).log" -Tail 100
```
Common causes: invalid Feishu credentials, missing permissions, agent CLI not working.

Engine startup health checks allow 120 seconds by default. This includes
database initialization, backend validation, Bot creation, and local API
startup. Slow hosts can raise the shared start deadline in seconds for both
`niubot start` and restart/update health checks:

```powershell
$env:NIUBOT_ENGINE_START_TIMEOUT = "180"
niubot start --restart
```

Backend version validation allows 60 seconds and runs once for each backend
that is actually used during startup. It can be adjusted separately:

```powershell
$env:NIUBOT_BACKEND_PROBE_TIMEOUT = "90"
```

Graceful shutdown allows 60 seconds by default before process-tree cleanup:

```powershell
$env:NIUBOT_ENGINE_SHUTDOWN_TIMEOUT = "90"
```

### Update preflight times out

Restart and update run the candidate package in read-only preflight mode before
stopping the current service. The default preflight timeout is 120 seconds. For
slower Windows hosts, set a larger value in seconds before retrying:

```powershell
$env:NIUBOT_RESTART_PREFLIGHT_TIMEOUT = "180"
niubot update
```

The candidate startup after preflight uses `NIUBOT_ENGINE_START_TIMEOUT`.
`NIUBOT_RESTART_HEALTH_TIMEOUT` remains supported as a compatibility alias,
but new installations should use the shared setting.

When upgrading from a release whose restart worker still has the old 20-second
preflight limit, the new candidate automatically uses a fast read-only
compatibility preflight. Workers from the new release declare extended
preflight support and run the full validation with the 120-second default.
The old worker also has a 15-second health default, so set its supported alias
for that first upgrade on a slow host:

```powershell
$env:NIUBOT_RESTART_HEALTH_TIMEOUT = "120"
niubot update
```

The current service remains running when candidate preflight fails. Check
`$HOME\.niubot\logs\restart-debug.log` for per-stage timings covering the
database snapshot, backend validation, Bot initialization, temporary API start,
and total preflight duration.

When an Engine is running, `niubot update` launches an independent update worker
and waits while printing its stage changes. The command exits successfully only
after the new Engine passes its health check. Pressing Ctrl-C stops waiting but
does not cancel the worker. Use `niubot update --detach` to return immediately;
progress and the final result remain available in
`$HOME\.niubot\logs\restart-debug.log`.

### Windows uses another Node or npm during install

NiuBot supports Node.js 20, 22, and 24 LTS. It pins the native SQLite dependency
to a version with Windows x64 prebuilt binaries for all three versions. Update
commands also put the active Node.js directory first on the child PATH.

To inspect a machine with multiple Node or npm installations:

```powershell
niubot version --verbose
Get-Command node -All
Get-Command npm -All
Get-Command niubot -All
```

The `Node`, `npm`, `npm root`, and `Package` entries shown by
`niubot version --verbose` must belong to the same Node.js installation. If
they do not, fix PATH or reinstall NiuBot with the intended npm before
updating.

When no Engine is running, `niubot update` first installs and verifies the
candidate in an isolated directory. Before the real global npm install, it
backs up the active package and npm command shims. An install or post-install
verification failure restores the previous version. If automatic restore
itself fails, the command prints the retained recovery directory.

---

## Adding a Bot

This section is for adding a **new bot** to an existing NiuBot installation. If you haven't installed NiuBot yet, start from [Step 1](#step-1-install-niubot).

There are two ways:
- **CLI** (quick): `niubot add-bot` — interactive prompts, handles config and directory setup
- **Manual** (agent-guided): follow the steps below

### Quick: CLI Command

```bash
niubot add-bot
```

The CLI will walk through: backend selection → Bot ID → model config → Feishu credentials → update config.yaml → create data directory. If the service is running, it offers to restart.

After the CLI finishes, continue to [Post-Setup: Feishu Permissions](#post-setup-feishu-permissions) below.

### Manual: Step-by-Step

On Windows, prefer `niubot add-bot`; it performs the same file operations without POSIX commands. The manual snippets below are POSIX examples.

#### 1. Choose a Bot ID

Pick a unique ID (e.g. `MyBot`). This determines the data directory (`~/.niubot/<id>/`) and cannot be changed after setup.

Check existing bots to avoid conflicts:
```bash
cat ~/.niubot/config.yaml   # look at the bots array
```

#### 2. Create Bot Directory and Profile

```bash
mkdir -p ~/.niubot/<BotID>
```

Write `~/.niubot/<BotID>/bot_profile.md`:
```markdown
# Bot Profile

> Only admins may ask the bot to modify this file.

## Persona

### Role
简洁清晰、有温度的技术同事。

### Style
- 先把结论说清楚，再解释必要原因。
- 用平实中文，不说黑话，不写客服腔。
- 语气克制、自然，有一点人情味，但不刻意安抚。

## Instructions

- 技术内容要准确，步骤要具体。
- 不确定时先说明不确定，再用工具或 nbt 恢复上下文。
```

#### 3. Append Bot to config.yaml

Read existing `~/.niubot/config.yaml` and append a new entry to the `bots` array. **Do not modify or remove existing bot entries.**

```yaml
bots:
  - id: ExistingBot          # ← keep existing entries untouched
    backend: claude
    appId: "cli_xxx"
    appSecret: "xxx"

  - id: NewBot                # ← append new bot
    backend: claude            # claude / codex / traecli / opencode / cursor / pi / grok
    appId: "cli_yyy"          # from Feishu app (Step 4)
    appSecret: "yyy"
    # model: ""               # optional: main model
    # workingDirectory: ~/niubot-workspace/NewBot  # optional
```

#### 4. Create Feishu App (if new)

Each bot needs its own Feishu app. If you already have one, skip to credentials.

1. Open https://open.feishu.cn/app and create a new **Enterprise Self-Built App**
2. **Credentials & Basic Info** → copy App ID + App Secret
3. **Bot** page → enable Bot capability
4. Fill the credentials into config.yaml

**Important**: Do NOT add permissions or publish yet — that requires an active connection (see below).

#### 5. Restart and Load

```bash
niubot start --restart
```

Wait for the health check to pass for the new bot.

### Post-Setup: Feishu Permissions

After the engine is running with the new bot:

1. **权限管理** → batch-enable non-review permissions in: 消息与群组, 云文档, 应用信息
2. **事件订阅** → add `im.message.receive_v1`
3. **Create a version** → publish the app
4. **Verify**: send a message to the bot in Feishu

## Export, Import, or Move One Bot

The Bot bundle contains only one Bot's config entry, a consistent SQLite snapshot, and
`bot_profile.md`. It does not contain the workspace, session archives, installed NiuBot
program, skills, logs, or caches.

Export omits Feishu credentials by default:

```bash
niubot bot export MyBot --home ~/.niubot --output ./MyBot.nbot
```

Import the bundle into another NiuBot home. The command starts an independent worker,
automatically stops the target Engine, imports the Bot, starts the Engine, and checks every
Bot. Supply new credentials when the export did not include them:

```bash
# macOS / Linux (hidden input)
read -s NIUBOT_APP_SECRET
echo
(umask 077; printf '%s' "$NIUBOT_APP_SECRET" > ./app-secret.txt)
unset NIUBOT_APP_SECRET
niubot bot import ./MyBot.nbot --home ~/.niubot-stable \
  --app-id '<app-id>' --app-secret-file ./app-secret.txt
rm ./app-secret.txt
```

PowerShell 7:

```powershell
$secret = Read-Host "App secret" -MaskInput
Set-Content -NoNewline -Path ./app-secret.txt -Value $secret
Remove-Variable secret
niubot bot import ./MyBot.nbot --home ~/.niubot-stable `
  --app-id '<app-id>' --app-secret-file ./app-secret.txt
Remove-Item ./app-secret.txt
```

Use `--include-secrets` only when the bundle must carry credentials. The generated file is
private (`0600` on Unix), but it must still be handled as a secret.

For a same-device move, the command is dry-run unless `--apply` is set. Applying starts an
independent worker: it stops both Engines, moves the Bot, always starts the target, and
restarts the source only when it was running before the move and still contains another Bot:

```bash
niubot bot move MyBot --from-home ~/.niubot --to-home ~/.niubot-stable
niubot bot move MyBot --from-home ~/.niubot --to-home ~/.niubot-stable --apply
```

After a successful move, the old database, profile, and recovery metadata remain under
`<source-home>/.bot-move-trash/`. If migration or health checks fail, the worker restores
the original data and Engine running states automatically.

Ordinary import clears device-local backend session references and uses the target device's
default workspace path unless `--working-directory <path>` is provided. Same-device move
keeps both references because their local files remain available.
