Skip to content

Repository files navigation

napcat-cli

Standalone CLI and daemon for NapCat QQ bot management with skills-fs integration.

PyPI Python versions

介绍博文: napcat-cli 群配置与管理速查 — 从零配置、群消息收发、Agent Wake 的完整链路。


napcat-cli provides a CLI for all NapCat API operations, a WebSocket daemon that bridges to a skills-fs HTTP provider, and an agent wake-up mechanism for integrating with Hermes or other AI agents.

napcat send group 123456 -m "Hello"
napcat send private 987654 -m "Hi"
napcat recall 1001
napcat events --limit 10
napcat daemon start
napcat wake --reason NEW_MESSAGE

Commands

Command Description
napcat api <endpoint> Raw API access (like gh api)
napcat send group <id> -m "msg" Send group message
napcat send private <id> -m "msg" Send private message
napcat reply <id> -m "msg" Reply to a message
napcat recall <msg_id> Recall a message
napcat group <sub> Group list, members, settings
napcat friend <sub> Friend list, info
napcat file <sub> File upload, download, list
napcat events Read events from SQLite
napcat alerts [--clear] Pending alerts
napcat status Bot online status
napcat config get/set Configuration management
napcat daemon start/stop/status/restart Watch daemon
napcat fs tree Skills-fs directory tree
napcat wake [--reason R] [--prompt P] [--transport T] [--dry-run] Wake the configured agent (HTTP/CLI, auto-fallback)
napcat setup Interactive setup wizard
napcat phone Textual phone-style TUI

Setup

uv tool install napcat-cli  # recommended — isolated install via uv (alternative: pip install napcat-cli)
napcat setup          # interactive — guides token, data dir, skills-fs, wake
napcat daemon start

What you need before setup works end-to-end:

  1. A running NapCat instance with HTTP API (default http://127.0.0.1:18801) and WebSocket (default 18800).
  2. Optional: skills-fs binary on PATH (or built from the skills-fs/ submodule) for the FUSE tree. Without it, CLI + wake still work; setup disables FUSE automatically.
  3. Optional: Hermes (or any HTTP/CLI agent) for auto-reply wakes.

The setup wizard writes:

  • <data_dir>/config.json — API URL, token, self_id, ports, triggers
  • <data_dir>/daemon.json — fields consumed by watch.py including skills_fs_*
  • <data_dir>/skills-fs.json — full FUSE mount map with the napcat HTTP provider (installed from the package if missing; existing/symlink configs are never overwritten)

Non-interactive mode uses defaults:

napcat setup --non-interactive  # no prompts, validates token
napcat setup --yes              # skip token validation
napcat setup --force            # overwrite existing config.json (skills-fs.json only if broken)

Full operator reference (OneBot11 empty network, Docker port cross-mapping, WS ?access_token=, wake HTTP vs legacy CLI, PaddleOCR venv, skills-fs health):

napcat config help
# or read docs/CONFIGURATION.md

NapCat OneBot11 binding & Docker port cross-mapping

NapCat ships with OneBot11 disabled by default; you must enable it inside the container config /app/napcat/config/onebot11_<self_id>.json — otherwise the napcat-cli WS/HTTP connections get an immediate reset (Connection reset by peer / Not logged in), even when the QQ account itself is online.

Minimum worker config (see the bundled example napcat_cli/data/onebot11.example.json):

  • One HTTP server on container port 3000 (host: any free port).
  • One WebSocket server on container port 3001 (host: any free port).
  • token: <random_secret> on both — keep it synchronised with napcat-cli config (napcat config set token <value>). Empty token = unauthenticated.
  • For Docker deployments, also enable quick auto-login in /app/napcat/config/webui.json ("autoLoginAccount": "<self_id>") so a container restart uses the saved session instead of presenting a new QR code.

Docker cross-mapping note. Your docker run -p HOST:CONTAINER mapping can transpose host and container ports. With NapCat defaults (HTTP 3000, WS 3001), many setups publish 18800:3000 and 18801:3001. That means the host-side OneBot11 HTTP URL is http://127.0.0.1:18800 and the WS URL is ws://127.0.0.1:18801 — note the swap relative to the conventional napcat-cli defaults (api_url=...18801, ws_port=18800). If napcat status says "Connection reset by peer" but the bot appears online in the WebUI, check docker port napcat and align api_url / ws_url / ws_port with the actual host mapping:

napcat config set api_url http://127.0.0.1:18800      # host HTTP
napcat config set ws_url   ws://127.0.0.1:18801        # host WS (overrides ws_port)
napcat config set token   <the-onebot11-token>
napcat daemon start

Example config files (no commitable secrets)

~/.napcat-data/{config,daemon}.json are user-state files and are gitignored. Reference copies with <SET_ME> / REPLACE_ME_WITH_RANDOM_SECRET placeholders live in the repo for orientation:

  • napcat_cli/data/config.json.example — napcat-cli client (api_url, token, ws_url, ...).
  • napcat_cli/data/daemon.json.example — daemon (watch.py runtime config).
  • napcat_cli/data/onebot11.example.json — NapCat container-side OneBot11 worker.

napcat daemon start prints advisory warnings if the token is empty or OneBot URLs look mis-mapped; it never blocks startup. Run napcat daemon start after changing any of these via napcat config set ....

Agent Wake

When a notable QQ event arrives, the daemon wakes an external agent (Hermes by default) carrying a contextual prompt so it can read the inbox and reply. The wake mechanism is generic and pluggable — Hermes is just the default preset; any HTTP endpoint or shell command works.

How it works

QQ Event (WS) --> EventProcessor --> WakeOrchestrator (debounce/cooldown/queue)
                                          |
                                     Waker (auto-fallback)
                          HTTP API (RECOMMENDED)        CLI (LEGACY — 不推荐)
                          POST /chat                    hermes -z "..."
                          fixed session_id              --continue <session>
  • Debounce (wake_debounce_seconds, default 3): coalesces a burst of same-reason events into one wake.
  • Cooldown (wake_cooldown_seconds, default 30): suppresses repeat wakes for non-urgent reasons.
  • Serial queue: WakeOrchestrator has a single worker thread — wakes to the same session are processed one at a time, preventing concurrent "split personality" responses.
  • Timeout: wake_timeout (default 300s) — how long to wait for the agent to respond.

Every wake is logged to daemon.log:

[WAKE] trigger reason=AT_ME who=Alice where=group123 text='hello'
[WAKE] queued reason=AT_ME pending=1 debounce=1.0s primary=http
[WAKE] delivered reason=AT_ME transport=http detail=POST /api/sessions/.../chat -> 200

Event routing

Trigger Behavior
AT_ME, REPLY_TO_ME, DM_ME Near-immediate wake (cooldown bypassed). Prompt includes who/where/text, image metadata, reply chain, and skill hints.
GROUP_TRIGGER Debounced wake (group trigger-word match)
NEW_MESSAGE (not @) Tracked, not woken; if unread longer than wake_new_message_idle_seconds -> NEW_MESSAGE_BACKLOG wake
NEW_FRIEND, NEW_REQUEST, BOT_BANNED, NEW_POKE, GROUP_ADMIN_CHANGE, NEW_GROUP_MEMBER, BOT_OFFLINE, ... Debounced + cooldown-bounded wake

AT_ME detection supports all NapCat message formats: CQ code ([CQ:at,qq=...]), display name (@name (qq)), and message segments ({"type":"at","data":{"qq":"..."}}).

Two transports — HTTP recommended, CLI legacy

Wake transport recommendation. The HTTP API server is the recommended transport for agent wake. The CLI one-shot backend is kept as a legacy fallback for environments where running the API server isn't viable — it is known to be less reliable (process-spawn latency, quoting hazards, new-session drift, no idempotency). When wake_primary=http falls back to CLI because HTTP is unreachable, the wake may appear to succeed but never deliver the prompt (only HTTP returns a real delivered in daemon.log). Enable the HTTP API server first — only keep CLI for diagnostic --dry-run or as last-resort fallback. See AGENTS.md "Critical Lessons — hermes -z -" for the most common CLI failure mode.

Transport Status When it picks Needs
HTTP API server RECOMMENDED primary=http or primary=http when configured + reachable wake_http_url + wake_http_key (bearer)
CLI one-shot LEGACY / 不推荐 primary=cli (forced) or primary=http only if HTTP is not configured/unreachable as a last-resort fallback Agent CLI on PATH

wake_primary=http tries HTTP first (if configured + reachable), else falls back to CLI.

Configuring for Hermes Agent

Option A: HTTP API Server (recommended)

The HTTP API server is the recommended wake transport. It provides:

  • Session continuity: all wakes go to the same session (no "split personality")
  • Idempotency: Idempotency-Key header prevents duplicate processing on retry
  • Persistence: session history is stored in ~/.hermes/state.db
  • Lower latency: no CLI process spawn overhead

Step 1: Enable Hermes API Server

Add to ~/.hermes/.env:

API_SERVER_ENABLED=true
API_SERVER_KEY=$(openssl rand -hex 32)   # generate a random key

Restart the gateway:

sudo systemctl restart hermes-gateway.service
# Verify API server is listening:
curl http://127.0.0.1:8642/health

Step 2: Create a dedicated QQ session

curl -X POST http://127.0.0.1:8642/api/sessions \
  -H "Authorization: Bearer <API_SERVER_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"title": "napcat-qq"}'
# Response: {"session": {"id": "api_1784824379_59dd9495", ...}}

Step 3: Configure napcat-cli

napcat config set wake_enabled true
napcat config set wake_preset hermes
napcat config set wake_primary http              # try HTTP first, CLI fallback
napcat config set wake_http_url http://127.0.0.1:8642
napcat config set wake_http_key <API_SERVER_KEY>
napcat config set wake_http_session_id <SESSION_ID>  # from Step 2
napcat config set wake_session napcat-qq         # CLI fallback session name
napcat config set wake_timeout 300               # seconds

Or edit ~/.napcat-data/daemon.json directly:

{
  "wake_enabled": true,
  "wake_preset": "hermes",
  "wake_primary": "http",
  "wake_http_url": "http://127.0.0.1:8642",
  "wake_http_key": "<API_SERVER_KEY>",
  "wake_http_session_id": "<SESSION_ID>",
  "wake_timeout": 300.0,
  "wake_cli_command": "hermes --continue {session} -z \"$(cat {prompt_file})\" --yolo --pass-session-id"
}

Key env vars (also checked by Hermes preset, in priority order):

  • NAPCAT_WAKE_HTTP_KEY — HTTP API bearer token
  • HERMES_API_KEY — alias for the above

Option B: CLI one-shot — LEGACY / 不推荐

Not recommended. Prefer Option A (HTTP). The CLI backend is kept for environments where running the API server isn't viable, but it has multiple known failure modes (process-spawn latency, quoting hazards, no idempotency, new-session drift). When wake_primary=http falls back to CLI after an HTTP probe fails, the wake often appears to succeed but never delivers the prompt to the agent. Diagnose by running grep '\[WAKE\]' ~/.napcat-data/daemon.log — only the HTTP backend writes a delivered ... transport=http -> 200 line. For agents other than HTTP, configure Option A first; only fall back to CLI when there is no other choice.

If you don't want to run the API server, napcat-cli can invoke the Hermes CLI directly. The CLI backend writes the prompt to a temp file and passes it via $(cat {prompt_file}) to avoid shell quoting issues.

napcat config set wake_primary cli
napcat config set wake_session napcat-qq
napcat config set wake_cli_command 'hermes --continue {session} -z "$(cat {prompt_file})" --yolo --pass-session-id'

Limitations of CLI mode:

  • Each invocation may create a new session if the name doesn't match an existing one
  • No idempotency — retries may cause duplicate processing
  • ~30-60s per invocation (CLI process startup + model inference)
  • Large prompts with special characters require the $(cat {prompt_file}) pattern (not -z -, which Hermes treats as literal text "-")

Configuring for other agents

napcat-cli's wake system is agent-agnostic. Any HTTP endpoint or shell command works.

Custom HTTP endpoint

napcat config set wake_preset custom
napcat config set wake_primary http
napcat config set wake_http_url http://127.0.0.1:9000
napcat config set wake_http_key my-secret-key
# The HTTP backend POSTs to: {wake_http_url}/api/sessions/{session}/chat
# Body: {"input": "<prompt>"}
# Headers: Authorization: Bearer <key>, Idempotency-Key: <unique-id>
# If your endpoint uses a different path/body format, see wake_backend.py HttpWakeBackend

Custom CLI command — LEGACY / 不推荐

Not recommended. The shell-command backend is kept for legacy / experimental setups only. Prefer Custom HTTP endpoint above — see the "Two transports — HTTP recommended, CLI legacy" note at the top of this section for why.

napcat config set wake_preset custom
napcat config set wake_primary cli
napcat config set wake_cli_command 'my-agent --prompt "$(cat {prompt_file})"'
# Placeholders: {prompt_file} (temp file path), {session}, {prompt} (inline, shell-quoted)

Disable wake entirely

napcat config set wake_preset none

Manual / debug

napcat wake                           # reason: manual, contextual default prompt
napcat wake --reason AT_ME --prompt "hello"
napcat wake --transport http          # recommended; --transport cli is legacy
napcat wake --dry-run                 # render the HTTP request + CLI command without executing
napcat wake test                      # per-transport configured + reachable probe
napcat wake sessions                  # list Hermes sessions (HTTP backend)
grep '\[WAKE\]' ~/.napcat-data/daemon.log    # see when/why/how wakes fired

The agent replies via napcat send / napcat reply (or the skills-fs write path) — see napcat_cli/data/SKILL.md.

Troubleshooting & verification

Verify the full wake chain end-to-end

  1. Check daemon is running:

    ps aux | grep watch.py | grep -v grep
    tail -5 ~/.napcat-data/daemon.log   # should show recent events
  2. Check Hermes API server is listening:

    curl -s http://127.0.0.1:8642/health
    # Expected: {"status": "ok", ...}
  3. Test HTTP wake manually:

    curl -X POST http://127.0.0.1:8642/api/sessions/<SESSION_ID>/chat \
      -H "Authorization: Bearer <KEY>" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: test-001" \
      -d '{"input": "ping"}'
    # Expected: {"message": {"content": "..."}}  with a response
  4. Test CLI wake manually (legacy — prefer HTTP test above):

    CLI is legacy / not recommended. This reference is kept for diagnosing environments where the HTTP API server is genuinely unavailable. Only test CLI as a last resort — the HTTP test (step 3) is the one that matters.

    echo "hello" > /tmp/prompt.txt
    hermes --continue napcat-qq -z "$(cat /tmp/prompt.txt)" --yolo --pass-session-id
    # Expected: agent responds within 30-60s
    # NOTE: -z - does NOT read stdin. Always use -z "$(cat file)" or -z "literal".
Symptom Cause Fix
Bot doesn't respond to @mentions Daemon not running, or crash killed event loop ps aux | grep watch.py; check daemon.log for errors; restart daemon
Connection reset / Not logged in while QQ online Empty OneBot11 network or port cross-map / missing ?access_token= napcat config help; enable WS+HTTP in container; align api_url/ws_url/token
[WAKE] delivered transport=cli but no reply CLI is legacy and may exit 0 without delivering Set wake_primary=http (default) + Hermes API_SERVER_KEY; expect /chat -> 200
[WAKE] delivered http but no reply Agent turn ran but did not send QQ Check Hermes session/tools; model 502/fallback in ~/.hermes/logs/agent.log
[WAKE] failed ... timeout Agent took > wake_timeout seconds Increase wake_timeout; HTTP turns can take 30–60s
[WAKE] failed ... session resolution failed HTTP session ID not found Run napcat wake sessions; recreate if needed
PaddleOCR not installed Daemon system Python; package only in .test-venv Auto-bootstrap or NAPCAT_OCR_SITE_PACKAGES / NAPCAT_VENV — see napcat config help
skills-fs degraded Health probe looked for root status Should use napcat/status + /proc/mounts; restart daemon on latest
Multiple replies ("split personality") Concurrent CLI wakes Prefer wake_primary=http
hermes -z - sends literal "-" -z takes literal string Use -z "$(cat {prompt_file})" (legacy CLI only)
Events stop after a bad message Unhandled exception in event handler Check daemon.log for process() ERROR

Static analysis (prevents regressions)

The test suite includes tests/test_lint.py which runs pyflakes and py_compile on every file in napcat_cli/. This catches undefined names, syntax errors, and other issues that would crash the daemon at runtime.

python -m pytest tests/test_lint.py -v
# Should pass: test_no_undefined_names, test_all_modules_compile

Image & OCR

The wake prompt automatically includes image metadata when an image message is received:

  • file_id — image file ID
  • url — download URL
  • sub_type — image type (0=normal, 1=sticker, 7=like)
  • file_size — file size in bytes
  • summary — PaddleOCR auto-recognized text (if available)
  • reply_id — reply message ID (for tracking reply chains)

PaddleOCR is integrated. Image text is auto-recognized and included in the summary field. The agent can also use multimodal vision to read the image URL directly. NapCat's built-in OCR is unavailable — do not use /napcat/ocr.

Operation CLI skills-fs
Download image napcat get_image <url> /napcat/get_image
View message detail napcat group <gid> get_message <mid> /napcat/groups/:gid/:range/:mid/:content
Send image napcat send group <gid> -f <path> /napcat/groups/:gid/send/image

Environment Variables

Variable Default Description
NAPCAT_API_URL http://127.0.0.1:18801 NapCat HTTP endpoint
NAPCAT_TOKEN API auth token
NAPCAT_DATA_DIR ~/.napcat-data Data directory

skills-fs Integration

The daemon runs an HTTP provider server (port 18820/18821) that skills-fs calls to read/write NapCat API endpoints through the virtual filesystem.

When skills_fs_enabled is true in daemon.json, the daemon spawns skills-fs automatically with the configured mountpoint and config file.

Distribution note: skills-fs is a compiled Go binary (git submodule pinned at cb13f37). It is not bundled in the pip/uv install of napcat-cli. If you need the FUSE mount (optional), build it separately:

git clone https://github.com/yandu-app/skills-fs
cd skills-fs && make build              # produces ./skills-fs
napcat config set skills_fs_binary /path/to/skills-fs
napcat daemon stop && napcat daemon start

Or place skills-fs on your PATH and the daemon will find it. This feature is optional — the CLI, daemon, and wake system work without it.

Manual start:

skills-fs fuse --config ~/.napcat-data/skills-fs.json \
  --mountpoint ~/.napcat-data/skills/napcat-cli --allow-other

Development

napcat-cli/
├── napcat_cli/          # Installable package
│   ├── cli.py           # CLI entry point
│   ├── wake.py          # Wake command-template renderer
│   ├── wake_backend.py  # Generic HTTP/CLI wake transports + Waker (auto-fallback)
│   ├── wake_presets.py  # Hermes/custom/none presets -> Waker
│   ├── wake_orchestrator.py  # Debounce, cooldown, backlog sweep, contextual prompts
│   ├── setup_wizard.py  # Setup wizard
│   ├── daemon/          # Watch daemon, schemas
│   ├── lib/             # API, config, events
│   ├── tui/             # Textual TUI
│   └── data/            # SKILL.md, persona.md
├── pyproject.toml
├── tests/
├── skills-fs/           # Go submodule
└── tools/               # Dev utilities
python -m pytest tests/ -v
python -m build --wheel
uv build && uv publish

License

MIT

About

napcat client for agents

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages