wecom-channel-cli
Built for agents. Designed for humans.

An agent-facing connector for WeCom's grayscale channel APIs. It reads channel content, watches changes by polling or encrypted callbacks, and lets an agent propose text posts, replies or native emoji reactions for independent operator approval.
Status: 0.4.2 is published, tested offline against synthetic services and WeCom's published cryptographic vector. Live authentication, discovery, whole-channel and thread polling, filtering and checkpoint resume passed in one test tenant. Distinct-user isolation passed in a synthetic Linux container; actual production deployment and live sending remain unverified. Linux and macOS are supported. Download native binaries from the GitHub release.
The CLI is a client of a small credential-holding broker. The agent gets a Unix socket for bounded reads and proposals. A separate private socket approves the exact proposal digest. Credentials, callback keys, policy and the write ledger stay with the broker. This only forms a host security boundary when the agent and operator have separate OS identities and the agent's tools are sandboxed. Read the security and deployment guide before connecting a bot.
Install
With Node.js 20 or newer, run this in a trusted operator setup session:
npm install -g @angelmsger/wecom-channel-cli@0.4.2
wecom-channel-cli version
wecom-channel-cli auth guide
The installer downloads the matching Linux/macOS binary and verifies its SHA-256
checksum. If your npm policy blocks install scripts, review the package before
allowing its postinstall script or download a native binary from GitHub.
Agent commands never download a missing binary automatically. Configure the broker
and context below before reading channel data.
Build and inspect
Requires Go 1.25 or newer. From this repository:
make build
bin/wecom-channel-cli --help
bin/wecom-channel-cli auth guide
bin/wecom-channel-cli policy example
make lint test e2e docs-check site-check
Inside the fx workspace, go.work uses the local kit. Use GOWORK=off make test
to verify the agent-cli-kit version pinned in go.mod. make cross builds Linux/macOS
amd64/arm64 executables; cross-compilation is not native runtime verification.
Operator setup
The channel list --bot-id mode and template flags below are available in 0.2.0.
Start with the API-mode bot's Bot ID and Secret, not a channel ID. The WeCom
client does not display channel IDs. In a trusted operator session, have your
secret manager supply WECOM_CHANNEL_BOT_SECRET, then discover the bot's joined
channels before creating policy, sockets or client contexts:
wecom-channel-cli channel list --bot-id BOT_ID
--bot-id explicitly selects operator credentials; if you keep the bot ID in
WECOM_CHANNEL_BOT_ID, pass --bot-id "$WECOM_CHANNEL_BOT_ID". No policy file or
running broker is needed. Match data.topic_name and select its data.topic_id;
that is the value used by --channel and the policy's channels[].id. Names need
not be unique, so retain the returned ID. Discovery returns one bounded page in
the usual {items,next,has_more} envelope. When has_more is true, pass next
as --cursor with the same bot; the default limit is 20, maximum 50.
An empty list is a successful read with no joined channels on that page. If the
target was not on an earlier page, verify bot membership and channel API access;
do not guess its ID. Authentication and permission errors are reported separately.
Membership and permission (public/private channel visibility) do not establish
read_all or write capability. channel list --bot-id BOT_ID --dry-run previews
the request offline without a Secret. Discovery saves no credentials or local state.
Keep the Secret out of argv, chat, source control and the worker environment.
The same channel list command serves both stages:
| Invocation | Connection and scope |
|---|---|
channel list --bot-id BOT_ID |
Initial operator setup: Secret environment, bot's joined channels |
--use-context default channel list |
Saved broker connection: that socket's permitted channels |
--socket /run/wecom-channel-admin/admin.sock channel list |
Existing operator connection: bot's joined channels |
Without --bot-id, listing always uses the broker. Environment credentials never
switch modes, including after a failed or denied broker request. Explicit --bot-id
ignores saved/environment connection settings; combining it with explicit
--socket or --use-context is an error. --dry-run previews either connection
without contacting the broker or WeCom. Channel listing belongs under channel;
broker serve runs the service, and config prepares policy or client settings.
After choosing the channel:
-
Provision the broker and agent identities using the deployment guide.
-
Generate a policy template with the discovered identity:
wecom-channel-cli policy example --bot-id BOT_ID --channel DISCOVERED_TOPIC_IDSave it to an operator-controlled file and review its paths and permissions. Alternatively adapt the single-context policy, polling policy or multi-context deployment. The generated template enables read_all and observe_all for polling, keeps
read_only: true, and disables posting and replying. Sender allowlists are required only for eligible human mention requests; discover user IDs after the broker is running. -
Supply the same bot's
WECOM_CHANNEL_BOT_SECRETto the broker service environment. For callbacks only, also supplyWECOM_CHANNEL_CALLBACK_TOKENandWECOM_CHANNEL_ENCODING_AES_KEY. None belongs in the worker environment. -
Preview and run the broker:
wecom-channel-cli policy validate --file /etc/wecom-channel/broker.json wecom-channel-cli broker serve --config /etc/wecom-channel/broker.json --dry-run wecom-channel-cli broker serve --config /etc/wecom-channel/broker.json -
Use the private socket for
auth check,channel listandmember list. An existing broker can still list joined channels with an empty version 1 allowlist via its operator socket; member/content reads require the selected channel in policy. Restart the broker after deliberate policy edits. -
Create the named client context below using the selected bot/channel and the provisioned agent socket. Polling works with an empty
callback_listen; no public address or callback keys are required. For optional callbacks, expose only the loopback/callbackendpoint through your HTTPS reverse proxy and configure WeCom deliberately. Changing connection mode invalidates the previous connection. Do not change a working bot merely to try this CLI.
doctor checks local broker readiness without authenticating to WeCom.
channel list --bot-id authenticates during initial setup; auth check verifies
an existing deployment through its private operator socket. Neither establishes
every channel's grayscale permissions.
Named channel contexts
Each channel is a security context with separate agent/operator sockets, policy, inbox and proposal ledger. Named client contexts use the family config engine. Use a channel ID selected during operator setup, then configure the client connection. One bot deployment can route its single authenticated callback to up to 32 channel contexts while sharing the token cache. See contexts.
wecom-channel-cli config set-context default \
--socket /run/wecom-channel-agent/agent.sock \
--bot-id BOT_ID --channel CHANNEL_ID
wecom-channel-cli config get-contexts
wecom-channel-cli --use-context default channel list
The template's broker name is default; the client context name must match it.
This example uses the same socket as policy example. For contexts such as
engineering and security, provision the matching named policies and sockets
from the multi-context guide first.
Context names select connections; OS permissions and broker identity checks enforce access. A worker with permission to open every context socket can access every such context. Provision separate context groups for mutually untrusted workers.
Agent usage
The operator supplies WECOM_CHANNEL_SOCKET=/run/wecom-channel-agent/agent.sock.
No credential belongs in the agent environment. Load the bundled Skill with
skill show, or install it with skill install --agent codex after previewing
with --dry-run. A differing existing Skill requires --overwrite.
wecom-channel-cli channel list
wecom-channel-cli event list --limit 10
wecom-channel-cli event get --id EVENT_ID
wecom-channel-cli event context --id EVENT_ID
# These require read_all for the channel.
wecom-channel-cli post list --channel CHANNEL_ID --limit 20
wecom-channel-cli reply list --channel CHANNEL_ID --post POST_ID
wecom-channel-cli search start --channel CHANNEL_ID --keyword incident
wecom-channel-cli search get --channel CHANNEL_ID --job JOB_ID
Do not invent IDs. Events provide exact channel/post/reply targets. A mention's context reads the post and, when present, the precise triggering reply; it does not grant general channel browsing. Mention-only access expires after 24 hours.
Each admitted event has a durable data.sequence. An explicitly requested
consumer can keep its last processed sequence and use
event list --after-sequence N; late callbacks are ordered by admission, not
their remote creation timestamp. Keep checkpoints with the bot identity and
preserve the broker ledger. This does not start monitoring automatically.
An ahead-of-ledger checkpoint or a missing retained sequence returns a conflict,
so a consumer does not silently treat lost history as an empty inbox.
Lists use {items,next,has_more}. Pass next as --cursor with the same
query. NDJSON emits items on stdout and a pagination notice on stderr. Preserve
both streams. Time windows use --since or [--from,--to) with RFC 3339
offsets. The resolved bounds are emitted on stderr; use those absolute bounds
when paging. There is no implicit unbounded traversal or monitoring loop.
Every external item retains trust, warning, source, and data. Output is
JSON or NDJSON only. Attachment names/counts are available, but download URLs,
cookies and arbitrary upstream fields are removed. Treat names, text and file
names as untrusted even when the sender is allowlisted.
Watch a channel or selected threads
See command responsibilities and migration for the full tree and the distinction between retained reads, observation streams and delivery.
wecom-channel-cli --use-context engineering event watch \
--whole-channel --duration 10m --max-events 500
wecom-channel-cli --use-context engineering event watch \
--post POST_ID --duration 10m --checkpoint .watch/thread.json
Create the checkpoint directory first. The operator must enable read_all,
observe_all, and post/reply observations for polling. The default backend polls
when no callback listener is configured, with a 30-second minimum refresh interval.
The first snapshot establishes a quiet baseline; later changes become untrusted,
observation-only metadata. Polling cannot reconstruct every intermediate edit or
infer deletion from absence. Mentions remain restricted to authenticated callbacks
from authorized humans. Watches stop on explicit limits. Resume files
bind the context, policy, ledger and thread filters; they never skip a detected
history gap. See watching for setup, partial coverage and
checkpoint delivery limits.
For unattended Bot integration, run ordinary relay run software. It filters and
batches change metadata, making no request on empty scans. Use durable JSON
--state for recovery or bounded-memory --ephemeral if interruption loss is
acceptable. SQLite is not required. The default receiver contract uses an exact
batch ACK; opt-in http_accepted sends directly to Grok routine webhooks and
records HTTP 200 as acceptance, separately from processing completion. Uncertain
outcomes stop for reconciliation. Worker-owned relay claim/consume deduplicates
business work without exposing sender credentials. See the
setup, host contract and recovery guide.
relay status inspects local delivery or processing records without network or
model calls. This implementation has been tested against local mocks; live Grok
activation requires verification in the actual host environment.
Propose, review, send
A proposal has no remote effect. Use a stable idempotency key and reuse it if a local response is lost. The broker binds that key to the immutable proposal; changing its text or target under the same key fails.
wecom-channel-cli reply propose \
--channel CHANNEL_ID --post POST_ID --parent-reply REPLY_ID \
--event EVENT_ID --key REQUEST_KEY --stdin --dry-run < draft.txt
wecom-channel-cli reply propose \
--channel CHANNEL_ID --post POST_ID --parent-reply REPLY_ID \
--event EVENT_ID --key REQUEST_KEY --stdin < draft.txt
wecom-channel-cli proposal get --id PROPOSAL_ID
Omit --parent-reply when the mention is in the post itself. The target must
match the authenticated event exactly. With read_all and allow_replies, the
operator may permit proposals without a mention. post propose requires
allow_posts. All of these still require independent approval.
The trusted operator reviews the entire proposal, including destination, text, expiry and digest, outside the agent session, then runs:
wecom-channel-cli --socket /run/wecom-channel-admin/admin.sock proposal list
wecom-channel-cli --socket /run/wecom-channel-admin/admin.sock proposal get --id PROPOSAL_ID
wecom-channel-cli --socket /run/wecom-channel-admin/admin.sock proposal approve \
--id PROPOSAL_ID --digest REVIEWED_DIGEST --yes --dry-run
wecom-channel-cli --socket /run/wecom-channel-admin/admin.sock proposal approve \
--id PROPOSAL_ID --digest REVIEWED_DIGEST --yes
Live sending also requires operator policy read_only: false. The agent's
--allow-writes only overrides its own WECOM_CHANNEL_CLI_READ_ONLY; it cannot
override broker policy, route to the operator interface, or grant approval.
The ledger records sending before network I/O. A failed response becomes
uncertain; a crash can leave sending. Both block re-execution, as does sent.
The operator reconciles with WeCom before using proposal resolve --outcome sent --remote-id ID or --outcome not_sent, with the same ID/digest and --yes.
Resolution does not resend. See write recovery.
Native emoji reactions
wecom-channel-cli reaction types
wecom-channel-cli --use-context engineering reaction list --post POST_ID
wecom-channel-cli --use-context engineering reaction propose \
--post POST_ID --emoji-type 1 --action add --key acknowledge-1 --dry-run
Reactions call WeCom's native post_emoji; they do not create emoji-text posts
or comments. Add --reply REPLY_ID for a reply target, or use --action remove
to cancel the bot's reaction. Proposals require operator approval like other writes.
The operator must enable allow_reactions; the default mentioned scope requires
an exact authenticated mention within 24 hours. Explicit reaction_scope: channel
also requires read_all and proactive bot permission, allowing polling deployments
without callbacks. See native reactions for setup, type discovery
and uncertain-result reconciliation. No separate reaction ID is invented.
Official CLI compatibility
Research checked 2026-10-25 against official commit
55785fa.
The official CLI and channels use the same SHA-256 signing formula, but the
official CLI's get_cli_config gateway Bearer token and the channel
get_token access token are different documented contracts. We do not decrypt
its private credential store, import its token, or copy its Rust authentication
implementation. An operator may provision the same existing bot ID/Secret when
that bot is eligible, but separate token endpoints do not establish that token
issuance and refresh cannot affect another integration.
If wecom-cli is already in use, keep its login and bot configuration intact.
For first live verification, use a dedicated eligible test bot in a test channel.
Supply its credentials locally to the broker; do not reset a working bot's Secret
or rerun the official CLI's login to obtain credentials for this connector.
An existing CLI login does not establish channel API access. Polling watches need
no callback reconfiguration. See the coexistence guidance.
Scope and verification
Implemented: bounded channel/thread watches, channel/member discovery, post/reply reads, scoped search, encrypted URL verification and event intake, exact sender/channel allowlists, bounded inbox reads, text/Markdown proposals, independent approvals and durable uncertain-write recovery, and native reaction add/remove/read operations. Tests exercise the CLI process and both socket roles. Live native reactions remain unverified.
Not implemented: media upload/download, content edits/deletes, forwarded chat-record expansion, bot reconfiguration, channel long connections, automatic model execution, or a sandbox for an arbitrary agent runtime. Limits and rationale are in the design. The CLI is not a prompt-injection detector and cannot make a privileged host agent safe by itself.