CLI reference
Day to day, working with Zhin mostly means working with one command, zhin: start the bot, change config, install plugins, diagnose problems. It comes from @zhin.js/cli and is called in a project as npx zhin … or pnpm exec zhin …. Creating a new project uses a separate command, npm create zhin-app.
npx zhin --help # list every commandRunning outside a bot project
Inside a project that has @zhin.js/cli installed, npx zhin uses it directly. Outside one (for example creating a standalone plugin, or publishing from a plugin directory), npx downloads the npm package named zhin:
- Since
zhin@4.0,zhinon npm is an alias of@zhin.js/cli:npx zhin <command>is the same asnpx @zhin.js/cli <command>. zhin@3.xis the old, unmaintained framework package, not this CLI. If your npx cache still holds 3.x, or a project pinszhin@^3, you get that one instead, which shows up as missing commands or startup errors. Usenpx @zhin.js/cli(reliable with every version), ornpx zhin@latestto force the new one.
The commands you'll use most
| I want to… | Command |
|---|---|
| Start the bot (dev mode, hot reload on save) | pnpm dev (i.e. zhin runtime start) |
| Connect a platform, configure AI or a database | npx zhin setup (optionally --adapters / --ai / --database) |
| Diagnose environment or config problems | npx zhin doctor |
| Check the config file is valid | npx zhin config check |
| Install a community plugin | npx zhin install <plugin> |
| Find plugins and setup steps by purpose | npx zhin use-cases |
| Create a new, publishable plugin | npx @zhin.js/cli new my-plugin --standalone |
| Check a plugin before publishing | npx @zhin.js/cli plugin check |
| Add a local plugin to an existing bot project | npx zhin new my-plugin |
| Upgrade Zhin | npx zhin migrate --dry-run, then again without --dry-run |
| Install as a start-on-boot system service | npx zhin service install |
All commands, grouped by purpose, follow.
Running
runtime: Run and Manage Projects
zhin runtime start [--mode production] [--no-watch] [--daemon]
zhin runtime inspect
zhin runtime build
zhin runtime publish [--execute|--resume] [--tag latest]
zhin runtime migrate [extract|cutover|status] [--check|--write]| Subcommand | Purpose |
|---|---|
start | Start the bot, with dev hot reload, production and daemon modes; see zhin runtime start |
inspect | Print the project structure as JSON (plugin list, build plan) |
build / publish | Build and publish according to the project structure. publish only prints the plan by default; --execute runs it, and --resume continues after an interruption |
migrate | Generate and apply a project-structure migration plan: --check previews, --write writes atomically, status prints a readiness report |
Scaffolded projects already have two conventional scripts:
{
"scripts": {
"dev": "zhin runtime start",
"start": "zhin runtime start --mode production --no-watch"
}
}stop: Stop the Bot
zhin stopStops the process started with --daemon in this project (reads .zhin.pid). With nothing running it just says so; it isn't an error.
service: System Service
zhin service install [--user]
zhin service uninstall [--user]
zhin service status [--user]Installs a start-on-boot / supervised service. --user uses user-level systemd on Linux; status works on Linux and macOS only.
Configuration and diagnostics
setup: Interactive Configuration Wizard
zhin setup # full wizard
zhin setup --database # database only
zhin setup --adapters # adapters only (add accounts)
zhin setup --ai # AI only
zhin setup --decisions # optional TypeSafe Jev decision service (needs existing AI config)
zhin setup --bootstrap # bootstrap files only (SOUL.md / TOOLS.md / AGENTS.md)
zhin setup --global # initialize a global instance in ~/.zhinThe wizard edits the matching parts of zhin.config.yml and writes secrets to .env.
For network databases, the CLI checks the port range, writes connection fields as environment references and puts the real values in .env. Afterwards run npx zhin config check to catch missing database variables before startup; if the values only live in .env.production, use npx zhin config check --environment production.
config: Read and Write Configuration Files
zhin config list
zhin config get <key> # nested paths such as ai.enabled
zhin config set <key> <value> # value accepts JSON
zhin config delete <key>
zhin config path # print the config file path
zhin config check [--fix] [--json] [--strict]config check validates the config file, and --fix repairs fields that can be migrated safely. Missing database connection variables are always errors; other warnings become errors with --strict, which suits CI.
doctor: Environment Diagnostics
zhin doctor [--fix] [--upgrade-l4]Checks Node, ports, tokens, config and other environment issues; --fix repairs what it can; --upgrade-l4 diagnoses what's needed to go from a minimal project to the full AI stack. When --fix adds dependencies it writes the real version ranges released alongside the CLI (e.g. ^1.1.11), never latest; run pnpm install afterwards.
dev: Develop Against a Local zhin Checkout or a Standalone Plugin
zhin dev link <zhin-path> [--project <dir>] [--no-install]
zhin dev unlink [--project <dir>] [--no-install] [--force]
zhin dev link <plugin-dir> [--project <dir>] [--as <instance>]
zhin dev unlink <instance> [--project <dir>]When <plugin-dir>/package.json has a zhin field, connects the standalone plugin to the current bot: links it under plugins/, adds ./plugins/<instance>, and makes the plugin's @zhin.js/* use the bot's copies, so edits hot-reload. See hot-reload a standalone plugin.
Given a zhin checkout, in an external plugin project it switches zhin.js / @zhin.js/* to a local checkout as a whole (packs the full dependency closure and writes pnpm.overrides, so each package has exactly one instance); unlink restores the original files. The "Zhin 包单实例" check in doctor detects duplicate instances and suggests this command. See the local development guide.
onboard: Project Onboarding
zhin onboard [--quick] [--flow quickstart|full]Reuses existing config, environment variables and data in a project, letting you keep or reconfigure them. --quick (-q) only shows the quick-start hints without the wizard; --flow defaults to full, and quickstart asks fewer questions.
migrate: Project Upgrade
zhin migrate [--dry-run] [--no-install] [--include-pinned]Upgrades an older Zhin project to the latest: dependencies, scripts and folder structure. --dry-run only prints the plan and ends with "nothing was changed"; --no-install skips the final pnpm install. Dependencies you locked (exact versions such as 1.1.6, link:, file:) are kept unless you pass --include-pinned. Full upgrade flow: Compatibility and migration.
zhin migrate never edits config files. When startup reports configVersion must be 2, or zhin doctor / zhin config check flags a v1 config, stop the Bot and run zhin migrate instances (preview) and then zhin migrate instances --apply. The preview lists changes by key name without printing configuration values; --apply first copies the originals to .zhin/backups/config-v2-<timestamp>/ (with a manifest.json) and prints that path. Restore with zhin migrate instances --recover rollback, or copy the files back as listed in the manifest. zhin runtime migrate migrates legacy plugin capability code and is unrelated to the config version.
Plugins
new: Create a Plugin
zhin new [plugin-name] [--standalone] [--type normal|service|adapter] [--package-name <npm-name>] [--is-official] [--skip-install]This is the single official way to create a plugin. The name must match /^[a-z][a-z0-9-]*$/; without arguments it asks interactively.
Standalone plugin (an external plugin to publish to npm):
npx @zhin.js/cli new my-plugin --standalonegenerates./my-plugin/in the current directory. No bot project is needed and nopackage.jsonis modified. The output has thepackage.json#zhinmanifest, aplugin.tsthat default-exportsdefinePlugin(), convention directories,tests/andREADME.md, and works out of the box withpnpm test/pnpm typecheck/pnpm publish.Local plugin inside a bot project: run
zhin new my-plugin(without--standalone) at the project root. It generatesplugins/<name>/and adds it to the rootpackage.jsondependencies.The package name defaults to
zhin.js-<name>. Use--package-namefor your own name or scope (for example@me/zhin-plugin-x). The@zhin.js/scope is reserved for official plugins (--is-official).zhin.jsand the capability packages (@zhin.js/command,@zhin.js/skill, and so on) are peerDependencies with a^x.y.zrange derived from the installed versions. The same versions go into devDependencies for local tests and type checks.The build output follows the published package layout (publishing guide, Chinese):
src/holds library code.pnpm buildcompiles it intolib/(.js+.d.ts), andmain,typesandexportsall point atlib/.zhin.entryis./plugin.js; the local runtime falls back toplugin.ts. When you pack (pnpm packor publish),prepackruns the build and thenscripts/build-runtime-entries.mjs, which compilesplugin.tsand the convention directories into sibling.jsfiles and rewrites imports ofsrc/tolib/.postpackremoves those generated files again.filesis a whitelist. It ships onlylib/,plugin.js, the.jsfiles in convention directories,schema.json,skills/,agents/and docs. TypeScript sources, tests and configs are not published.
The generated
pnpm test(Vitest) andpnpm typecheck(tsc --noEmit) work as is; noNODE_ENVsetting is needed.--skip-installskipspnpm installin both the plugin directory and the project root.
install / add: Install a Plugin
zhin install <plugin name or git URL> [--no-enable] [--dry-run]
zhin add ... # alias of installBy default it installs with pnpm and writes the package reference into plugins.<instanceKey> in zhin.config.yml. Business settings use the top-level alias block; disable_plugins controls activation. For adapters, follow the prompt to run zhin setup --adapters and add an account.
| Option | Description |
|---|---|
-S, --save | Write to dependencies (default) |
-D, --save-dev | Write to devDependencies |
-g, --global | Install globally (doesn't enable the plugin) |
--no-enable | Install dependencies only; don't change config |
--dry-run | Print the install and config changes without writing |
uninstall: Uninstall
zhin uninstall plugin <name> [--remove-pkg]
zhin uninstall adapter <name> [--remove-pkg]
zhin uninstall servicename can be an npm package name or an instanceKey; --remove-pkg also removes the dependency from package.json. uninstall service removes the system service.
search / info: Discover Plugins
zhin search [keyword] [-c utility|service|game|adapter|admin|ai] [-l 20] [--official]
zhin info <package>Search the plugin marketplace or show one plugin's details.
use-cases: Find a Setup Path by Purpose
zhin use-cases
zhin use-cases reminder
zhin use-cases personal-reminder
zhin use-cases group-welcome
zhin use-cases keyword-replyFind the required plugin, installation preview, steps and current limitations for reminders, welcome messages and keyword replies. This command only prints a guide: it does not install plugins, change configuration, start the bot or send messages. The existing installation, configuration and chat interfaces still own those operations.
The Console administrator task center uses the same catalog. Its first creation flow manages administrator reminders. Welcome messages and keyword replies currently link to existing setup paths; Console does not impersonate a chat member.
build: Build a Plugin
zhin build [plugin] [--clean] [--production] [--analyze]plugin is a path relative to plugins/ (e.g. adapters/icqq); when omitted it builds the current package from inside a plugin folder, or every plugins/* from the workspace root. src/ is compiled with tsc and client/ bundled with esbuild; --clean clears lib/ and dist/ first; --production (on by default) minifies client output.
plugin check: Check a Plugin Before Publishing
zhin plugin check [plugin-dir] [--no-pack] [--json] [--strict]Checks whether the plugin in a directory (the current one by default) will publish correctly. Run it once before publishing: npx @zhin.js/cli plugin check.
Manifest and layout: checks the
package.jsonnameandzhinmanifest, and thatzhin.entrypoints at JavaScript with a matching source file. It also checks that thefileswhitelist covers the entry, the convention directories (the.jsbuilt fromcommands/,tools/and so on, plusskills/andagents/) andschema.json, and thatprepack/postpackgenerate and clean the runtime entries. A missing README or missing tests only produce warnings.zhin manifest and dependencies:
- the
zhinmanifest (protocol,type,entry,runtime,features,plugins) is validated with the same parser the Runtime uses at load time; zhin.enginemust be a semver range the current Runtime engine satisfies, andfeatures[].apimust be a valid range;- packages referenced by
features/pluginsmust be listed independenciesorpeerDependencies; peerDependenciesmust declarezhin.jswith real semver ranges:latest,*andfile:/link:are errors, and a locally installed version outside the range is a warning;- Zhin stack packages in
dependenciesproduce a warning.
- the
Published package: runs the plugin's real
npm pack(itsprepackbuild included) and unpacks the tarball in a temporary directory. It then checks that:- the entry and
main/exportsare packed; - relative imports in packed JavaScript resolve;
- no tests or build configuration slipped in;
- every
.tsin a convention directory has a packed.js; - the packed
zhin.entrycan be imported: it is loaded in a child process using the plugin's ownnode_modules(skipped with a warning when dependencies are not installed).
These are the same rules as the repository's
pnpm check:pack-imports.--no-packskips this step.- the entry and
Exits with code 1 on errors;
--strictalso fails on warnings;--jsonprints a structured result for CI.
pub: Publish a Plugin
zhin pub [plugin-name|plugin-dir] [--tag latest] [--access public] [--registry <url>] [--dry-run] [--skip-build] [--skip-check] [--yes]- Inside a standalone plugin from
zhin new --standalone, run it without arguments to publish the current directory:npx @zhin.js/cli pub --dry-runto preview,npx @zhin.js/cli pubto publish. (npx @zhin.js/cliis reliable with every version;npx zhinonly means the same CLI fromzhin@4on, see the tip at the top of this page.) - The argument can also be any plugin directory (its
package.jsonhas azhinfield), e.g.zhin pub ../my-plugin. - In a bot project root, the argument is a path relative to
plugins/; without it a single plugin is picked automatically, several are offered interactively. --dry-rungoes through the flow without publishing. Non-interactive shells (CI) need--yesto skip the publish confirmation. Packages withprivate: trueare rejected, and azhin.entrypointing at.tsgets a hint to publish the built.js.- Before publishing it runs
zhin plugin checkautomatically (npm packincluded). Errors stop the publish; warnings alone let it continue. Use--skip-checkto skip it if you really must. - Full release flow: Complete plugin delivery.
packages: Plugin Status and Extension Packages
Manage skill / extension packages (ADR 0010):
zhin packages install <pkg|npm:skill-pack|git:url> [--local]
zhin packages remove <name> [--local]
zhin packages list [--local]
zhin packages update [source] [--local]--local (-l) installs into the project's .zhin/packages/ instead of globally. Without an npm: / git: prefix, it uses the same install flow as zhin install <package> and Console.
Check whether a plugin is really live: installing doesn't mean it's loaded, its platform is online, or its features are verified. Use these to inspect status and run verification:
zhin packages status <instanceKey>
zhin packages status <instanceKey> --live http://127.0.0.1:8068
zhin packages verification-plan <instanceKey> --live http://127.0.0.1:8068
zhin packages apply <instanceKey> --live http://127.0.0.1:8068 --project-revision <revision>
zhin packages verify <instanceKey> <check-id> --live http://127.0.0.1:8068 \
--generation <generation> --project-revision <revision> --check-revision <checkRevision>States returned by status:
| State | Meaning |
|---|---|
installed | The package resolves |
declared | An instance is declared |
missing-config | Required config or environment variables are missing, or values don't match the schema |
pending-restart | The running version hasn't applied it yet |
enabled | Live, with config and package version consistent |
degraded | Declaration and actual installation disagree, or a dependency check failed |
readiness records readiness evidence separately (unknown when an adapter is present but there's no platform health evidence); verification only says whether business checks exist — a successful start is not a business pass.
Live requests, verification flow and concurrency rules
- Token: live requests read the Console token from
ZHIN_CONSOLE_TOKEN, or the variable named by--token-env <name>.verification-plan,applyandverifyneed full permissions. Never put tokens in URLs or command arguments. - HTTPS: remote requests carrying a token must use HTTPS; plain HTTP is allowed only for
localhost,127.0.0.0/8and::1. - Offline mode: offline
statusprovides only file and package-resolution evidence; an offline verification plan has no current version or runnable checks, and never starts the Runtime. - Verification steps: review the scope and side effects in
verification-planfirst, then pass the returnedgeneration,projectRevisionandcheckRevisiontoverify. Changes to config, version or check definitions reject old plans. Plugins without checks returnnot-configured; passing local checks doesn't mean platform delivery is verified.applyonly requests application — confirm with a livestatusafterwards. - Schema: a loaded config schema applies only to the same package version; after an update the on-disk
schema.jsonis read. If the new version declares config only in TypeScript with no readable schema,validation.schemaAvailableisfalseand only environment references are checked; full validation needs a real Runtime pre-check. - Production dependencies: auto-enabled plugins must be production dependencies;
--save-devmust be combined with--no-enable, and this is checked before installing. - Concurrency: project changes use a file lock and revision checks. Concurrent operations or config drift fail explicitly; if install steps already completed, partial-completion evidence is returned and user changes are never overwritten. If the lock's owning process has exited, the error shows the lock path — delete it after confirming no installer is running, then retry.
Working with a running bot
These commands talk to a running bot over HTTP, so start it with zhin runtime start with the HTTP service enabled first.
send: Send a Message to a Running Bot
zhin send <scene_id> [content...] [-s private|group|channel] [-a <adapter>] [-e <endpoint>]scene_id is a user ID for DMs or a group number for groups; without content it reads one line from stdin. -a defaults to process; -e picks an account ID, defaulting to the adapter's first online account.
watch: Monitor a Running Instance
zhin watch [-i 3] [--once] [--json] [--no-clear]Refreshes a status panel in the terminal (stats, accounts, assistant jobs). --once fetches once and exits; --json suits scripts.
schedule: Persistent Scheduled Tasks
Have the AI run a prompt at given times. Jobs are stored in data/schedule-jobs.json; restart the bot for changes to take effect.
zhin schedule list
zhin schedule add "0 0 9 * * *" --prompt "Good morning, summarize today's schedule" [--kind solar|lunar|workday|freeDay]
zhin schedule add --every 30m --prompt "Check RSS"
zhin schedule add --at 2026-08-01T09:00:00Z --prompt "One-off reminder"
zhin schedule remove <id>
zhin schedule pause <id>
zhin schedule resume <id>add option | Description |
|---|---|
--prompt | Required: the prompt sent to the AI when due |
-l, --label | Job name |
--kind | solar (default, Gregorian) / lunar / workday / freeDay |
--notify-channel | im / silent (default) / log |
cron expression, --at, --every | Pick one: a six-field cron (sec min hour day month weekday), a one-off ISO8601 time, or an interval (e.g. 30m, 1h, 1d) |
agent: Agent Diagnostics and Workroom Observation
Requires the AI stack (@zhin.js/agent + zod + ai).
zhin agent info [--json]
zhin agent workroom runs --project <projectId> [--json]
zhin agent workroom run <runId> --project <projectId> [--json]
zhin agent workroom readiness <runId> --project <projectId> [--json]
zhin agent workroom cancel <runId> --project <projectId> --expected-sequence <n> --reason-code <code> --control-deadline <timestamp> [--operation-id <id>] [--json]
zhin agent workroom request-replan <runId> --project <projectId> --expected-sequence <n> --reason-code <code> [--operation-id <id>] [--json]| Command | Purpose |
|---|---|
info | List discovered Agent capabilities and named Agents (Tools, Skills, Hooks…) |
workroom runs / run / readiness | Connect to the running bot and read Run, Task, Assignment and Blocker state (no content) using the token identity from config |
workroom cancel / request-replan | Require the current Project Sponsor permission and use expected-sequence to prevent concurrent conflicts. request-replan only records the replan request and pauses scheduling; it never pretends a new plan revision exists |
Exit Codes
| Code | Meaning |
|---|---|
51 | Console requested a restart (the daemon starts a new process) |
75 | The runtime requires a restart (e.g. lockfile changed); the daemon restarts with storm protection |
Process model and hot-reload behavior: zhin runtime start.