@mu-cabin/muc-cli 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,471 +1,328 @@
1
- # muc-cli
1
+ # MUC-cli
2
2
 
3
- Terminal client for 东航 MUC IM (the `com.meicloud.im.dh` desktop app, also
4
- called MUC.app). Drives the same REST surface the desktop talks to, plus the
5
- binary chat protocol on raw TCP 8101 for sending messages, withdrawing, and
6
- syncing sessions.
3
+ **muc-cli** 把 MUC IM(com.meicloud.im.dh)搬进终端。在 shell 里登录、找人、列群、读历史、收发消息和文件,不用打开桌面端 GUI。它同时附带**MUC Skill**,让你直接对 agent 说"把这个报告发给 张三",Skill 会自动选命令、解析返回值。
7
4
 
8
- REST commands always coexist with the running desktop and phone — the server
9
- allows concurrent `accessToken`s. Chat-channel commands share online slots
10
- with real devices; see [Coexistence](#coexistence) below before sending.
5
+ ---
11
6
 
12
- ## What it is
7
+ ## 快速安装(复制给 Agent)
13
8
 
14
- A single command (`muc`) packaging:
9
+ 把下面这段话发给 Agent, 它会帮你完成全部安装:
15
10
 
16
- - **Send** — 1:1 and group text, files, images, and voice clips. Add and
17
- remove emoticon reactions on existing messages.
18
- - **Read** — server-side chat history, profile lookups, group lists, sticker
19
- packs, directory search (people, departments, apps, service numbers), and
20
- long-lived `listen` tail of inbound messages with sender/keyword filters.
21
- - **Manage** — login / token refresh, withdraw a message, clear a session,
22
- download a chat-attached file, wait for a human emoticon reaction before
23
- proceeding (`wait-react`).
24
- - **Self-describe** — every command has a `--json` mode emitting a stable
25
- envelope; `muc manifest --json` returns the full command graph + per-command
26
- output JSON Schema for agent / script consumers.
11
+ ```
12
+ 帮我安装 muc-cli 和 MUC Skill:
13
+ 1. 运行 `npm install -g @mu-cabin/muc-cli` 安装 CLI
14
+ 2. 运行 `npx skills add <everything-cli 仓库地址>/packages/muc/skills -y -g` 注册 Skill(全局安装到所有 agent)
15
+ 3. 运行下面这条 `muc init` 一次性写入租户配置(值见你的 MUC 部署管理员):
16
+ muc init --no-interactive \
17
+ --app-key <APP_KEY> \
18
+ --app-id <APP_ID> \
19
+ --sign-secret-rest '<SIGN_SECRET_REST>' \
20
+ --sign-secret-secure '<SIGN_SECRET_SECURE>'
21
+ 4. 运行 `muc login --account <UID大写,如 ALICE> --password '<密码>'` 登录
22
+ 5. 运行 `muc whoami` 确认登录成功
23
+ ```
24
+
25
+ ---
26
+
27
+ ## MUC 租户配置
28
+
29
+ CLI 不把租户密钥编进发布包;首次安装后必须用 `muc init` 把这四个值写入
30
+ `~/.muc/config.json`(权限 0600)。**这些值因部署而异——向你的 MUC 管理员索取,
31
+ 不要外发。**
32
+
33
+ | 字段 | 值 |
34
+ |---|---|
35
+ | `APP_KEY` | `<APP_KEY>`(8 位小写 hex) |
36
+ | `APP_ID` | `<APP_ID>`(24 位小写 hex) |
37
+ | `SIGN_SECRET_REST` | `<SIGN_SECRET_REST>` |
38
+ | `SIGN_SECRET_SECURE` | `<SIGN_SECRET_SECURE>` |
39
+ | `PWD_DES_KEY` | 留空(自动用 `APP_KEY`) |
40
+
41
+ 主机/端口(向管理员索取你的部署地址,CLI 内置默认仅适用于特定租户):
42
+
43
+ | 字段 | 示例 |
44
+ |---|---|
45
+ | `restHost` | `https://muc.example.com/` |
46
+ | `ssoHost` | `https://store.example.com/` |
47
+ | `imHost` / `imPort` | `muc.example.com` / `8101` |
48
+ | `fileHost` / `filePort` | `muc.example.com` / `6101` |
27
49
 
28
- ## Install
50
+ 一行式(非交互,CI/Agent 友好):
29
51
 
30
52
  ```bash
31
- npm i -g @mu-cabin/muc-cli
32
- muc --version
53
+ muc init --no-interactive \
54
+ --app-key <APP_KEY> \
55
+ --app-id <APP_ID> \
56
+ --sign-secret-rest '<SIGN_SECRET_REST>' \
57
+ --sign-secret-secure '<SIGN_SECRET_SECURE>'
33
58
  ```
34
59
 
35
- Requirements: **Node ≥ 18**. macOS, Linux, and Windows are all supported.
36
- DES-CBC password encryption is `crypto-js` (pure JS) — no
37
- `--openssl-legacy-provider` flag needed.
38
-
39
- `~/.muc/credentials.json` and `~/.muc/config.json` are written with mode
40
- `600` and the parent dir with mode `700`. POSIX honors that; on Windows the
41
- modes are silently ignored — protect the files with NTFS ACLs if you're on a
42
- shared host.
60
+ 或交互式:
43
61
 
44
- ## First-time setup
62
+ ```bash
63
+ muc init # 逐项提示,密钥手输,主机/端口按回车采用默认
64
+ ```
45
65
 
46
- Before you can `login`, run `muc init` to seed the tenant configuration. The
47
- CLI is generic across MUC deployments — your MUC admin provides the
48
- tenant-specific values.
66
+ 校验:
49
67
 
50
68
  ```bash
51
- muc init # interactive: walks through APP_KEY, APP_ID, signing secrets, hosts
69
+ muc init --check --json # exit 0=ok, 6=missing, 7=invalid
70
+ muc init --show # 仅显示打码后的指纹(不会回显原值)
52
71
  ```
53
72
 
54
- What `muc init` collects:
73
+ > 这些值是租户级密钥,**绝不要复制到 npm 包、Skill 包或任何公开渠道**。向你的
74
+ > MUC 部署管理员索取,并只通过私有渠道传递。
55
75
 
56
- | Field | Source | Notes |
57
- |---|---|---|
58
- | `appKey` | admin | 8 hex chars |
59
- | `appId` | admin | 24 hex chars |
60
- | `signSecretRest` | admin | secret for the regular-API signing scheme |
61
- | `signSecretSecure` | admin | secret for the `/contacts/secure/...` signing scheme |
62
- | `pwdDesKey` | admin (optional) | DES-CBC key for password encryption; defaults to `appKey` |
63
- | `restHost` | default `https://muc.ceair.com/` | press enter to accept |
64
- | `ssoHost` | default `https://store.ceair.com/` | reserved (advanced) |
65
- | `imHost` / `imPort` | default `muc.ceair.com` / `8101` | TCP chat channel |
66
- | `fileHost` / `filePort` | default `muc.ceair.com` / `6101` | TCP file channel |
76
+ ---
67
77
 
68
- Built-in defaults are provided **for hosts and ports only** — never for
69
- `appKey`, `appId`, or signing secrets.
78
+ ## 安装 CLI
70
79
 
71
- ### Non-interactive (CI, scripts)
80
+ 环境: Node ≥ 18
72
81
 
73
- You can supply values via flags or env vars:
82
+ ```bash
83
+ npm install -g @mu-cabin/muc-cli
84
+ muc --version
85
+ ```
74
86
 
75
- Get the four secrets from your MUC admin first; the snippets below use
76
- placeholder names — substitute the real values your admin gives you.
87
+ ### 首次配置(一次性)
88
+
89
+ CLI 没有内置可用的租户密钥,必须先用 `muc init` 写入。值见上面 "MUC 租户配置" 表:
77
90
 
78
91
  ```bash
79
- # Flag form
80
92
  muc init --no-interactive \
81
- --app-key "$APP_KEY" \
82
- --app-id "$APP_ID" \
83
- --sign-secret-rest "$SIGN_SECRET_REST" \
84
- --sign-secret-secure "$SIGN_SECRET_SECURE" \
85
- --json
86
-
87
- # Env-var form
88
- MUC_APP_KEY="$APP_KEY" \
89
- MUC_APP_ID="$APP_ID" \
90
- MUC_SIGN_SECRET_REST="$SIGN_SECRET_REST" \
91
- MUC_SIGN_SECRET_SECURE="$SIGN_SECRET_SECURE" \
92
- muc init --no-interactive
93
+ --app-key <APP_KEY> \
94
+ --app-id <APP_ID> \
95
+ --sign-secret-rest '<SIGN_SECRET_REST>' \
96
+ --sign-secret-secure '<SIGN_SECRET_SECURE>'
97
+
98
+ muc init --check # 应输出 ok
93
99
  ```
94
100
 
95
- Environment variables: `MUC_APP_KEY`, `MUC_APP_ID`, `MUC_SIGN_SECRET_REST`,
96
- `MUC_SIGN_SECRET_SECURE`, `MUC_PWD_DES_KEY`, `MUC_REST_HOST`, `MUC_SSO_HOST`,
97
- `MUC_IM_HOST`, `MUC_IM_PORT`, `MUC_FILE_HOST`, `MUC_FILE_PORT`.
101
+ ### 首次登录
98
102
 
99
- > **Env-var leakage warning.** Values exported into the environment are
100
- > visible to other processes on the same machine via `/proc/<pid>/environ`
101
- > on Linux, and they land in shell history if you don't prefix the command
102
- > with a space (and `HISTCONTROL=ignorespace` is set). For production setups
103
- > prefer the **interactive** prompt or **flag-based** invocation — you can
104
- > protect the flag values with secret-manager retrieval that never touches
105
- > the env.
103
+ ```bash
104
+ muc login --account ALICE --password '<你的密码>'
105
+ muc whoami
106
+ ```
106
107
 
107
- ### Inspect, repair, reset
108
+ 登录成功后凭证写入 `~/.muc/credentials.json`(权限 600),有效期约 7 天;`tokenPwd`(30 天)可用于静默续登:
108
109
 
109
110
  ```bash
110
- muc init --check # validate; exit 0 ok, 6 missing, 7 invalid
111
- muc init --check --json # same, machine-readable
112
- muc init --show # print config (secrets always redacted)
113
- muc init --show --json # JSON envelope — secrets are also redacted here
114
- # (read ~/.muc/config.json directly for raw values)
115
- muc init --reset --yes # delete ~/.muc/config.json
111
+ muc login --token-pwd # 无需密码,直接续登
116
112
  ```
117
113
 
118
- Resolution order per field: CLI flag → env var → existing saved value → built-in
119
- default (hosts/ports only) → interactive prompt (TTY) → fail.
114
+ ---
115
+
116
+ ## 安装并使用 Agent Code Skill
120
117
 
121
- ## Login
118
+ Skill 注册之后,你可以直接用自然语言驱动 `muc`,不用记命令格式。
122
119
 
123
- `muc login` is **non-interactive on the credential side** — it never prompts
124
- for a password. Pass it via flag, stdin, env var, or `--token-pwd` (reuse
125
- the saved tokenPwd).
120
+ ### 安装 Skill
126
121
 
127
122
  ```bash
128
- # Plaintext flag (avoid; lands in shell history)
129
- muc login --account WANGLULU8 --password '…'
123
+ npx skills add <everything-cli 仓库地址>/packages/muc/skills -y -g
124
+ ```
125
+
126
+ 注册后 Agent Code 会自动识别 muc 相关请求并调用对应命令。
130
127
 
131
- # Safer: read password from stdin, no flag value
132
- printf '%s' "$MY_PWD" | muc login --account WANGLULU8 --password-stdin
128
+ ### 对 Agent 说什么
133
129
 
134
- # Reuse saved tokenPwd (no password needed; ~30 day TTL)
135
- muc login --token-pwd
130
+ 以下这些说法都会触发 muc-cli Skill:
136
131
 
137
- # Env-var form
138
- MUC_ACCOUNT=WANGLULU8 MUC_PASSWORD='…' muc login
132
+ ```
133
+ "把今天的周报发给 张三"
134
+ "拉一下 王五 本周的聊天记录"
135
+ "列一下我在 MUC 的群"
136
+ "帮我搜一下 MUC 上叫 李四 的人"
137
+ "IT 值班群发个通知:今晚 22:00 有计划性维护"
138
+ "把 muc 上刚才发的消息撤回"
139
+ "下载一下 <mid> 这条消息里的附件"
139
140
  ```
140
141
 
141
- - `account` is your MUC uid (uppercase ASCII, e.g. `WANGLULU8`); falls back
142
- to `$MUC_ACCOUNT` or the previously-saved uid if `--account` is omitted.
143
- - One of `--password` / `--password-stdin` / `--token-pwd` / `$MUC_PASSWORD`
144
- is required — running `muc login` with no creds fails fast with
145
- `AUTH_REQUIRED` (exit 3).
146
- - The password is encrypted client-side (DES-CBC) before being sent — same
147
- scheme the desktop client uses.
148
- - A successful login saves to `~/.muc/credentials.json` (mode `600`):
149
- `accessToken` (≈7 day TTL) and `tokenPwd` (≈30 day TTL, used for silent
150
- re-login with `--token-pwd`).
151
- - A stable per-CLI device id is generated at `~/.muc/device-id` on first
152
- run. It is intentionally **different** from the desktop's machine-id so
153
- REST sessions stay independent.
154
-
155
- ## Output contract
156
-
157
- - **Default:** human-readable plain text on stdout, errors on stderr in the
158
- form `error: <msg>` / `hint: <hint>` / `code: <code> (exit <n>)`.
159
- - **`--json`** (anywhere on the command line): every command emits a single
160
- stable JSON envelope on stdout — `{ok:true, schema_version:1, data}` or
161
- `{ok:false, schema_version:1, code, msg, hint?}` — and stderr stays
162
- silent. Agents and scripts should always pass `--json`.
163
- - **Exit codes** are typed and identical in both modes: `0` success, `1`
164
- internal bug, `2` usage, `3` auth required / expired, `4` not found, `5`
165
- remote / network error, `6` config missing, `7` config invalid.
166
- - `--help` / `--version` are human escape hatches and bypass the JSON
167
- envelope by design. The canonical agent surface is `muc manifest --json`.
168
-
169
- ## Commands
170
-
171
- ### Setup
172
-
173
- | Command | What it does |
174
- |---|---|
175
- | `muc init` | Seed / inspect / reset tenant config (`~/.muc/config.json`). Run before `login`. See [First-time setup](#first-time-setup). |
142
+ ### Skill 的行为规则
176
143
 
177
- ### REST commands (no impact on the desktop app)
144
+ - **首次 TCP 命令前**:Skill 会弹 `AskUserQuestion` 提示槽位风险,等你确认后再执行。
145
+ - **不确定发给谁**:先用 `muc search` 缩小候选名单,通过 `AskUserQuestion` 让你选,不会猜。
146
+ - **非自身目标**:必须经过你显式确认,不会偷偷给真实联系人发消息。
147
+ - **token 过期**:自动用 `--token-pwd` 静默续登,续登失败才要求你手动 `muc login`。
178
148
 
179
- | Command | What it does |
180
- |---|---|
181
- | `muc login` | Authenticate, persist `accessToken` + `tokenPwd`. |
182
- | `muc whoami` | Show the saved profile + token freshness. Local only — does not read tenant config. |
183
- | `muc search <keyword>` | Org / employee directory search (12 results max, server-side). |
184
- | `muc search-dept <keyword>` | Department directory search (with employee count + path). |
185
- | `muc search-app <keyword>` | Search the user's MUC widgets/apps drawer. |
186
- | `muc search-sno <keyword>` | Search service-numbers (服务号); shows `★` for already-subscribed. |
187
- | `muc whois <UID>` | Employee info + ext info (`secureApi`). Renders `cn / id / mail / departmentName / positionName / empStatusText`. `--no-ext` skips the slower `getExtInfo` call. |
188
- | `muc groups` | Starred groups (flat list keyed by `team_id`) + department groups (keyed by `groupId / deptName`). |
189
- | `muc emoticons` | Sticker packages installed on your account. |
190
- | `muc history --peer UID \| --team TEAM_ID \| --to-self` | Server-side chat history via the roaming-messages endpoint. Stateless: each call fetches from the server. Default window: 30 days, 50 messages newest-first. |
191
- | `muc download --file-key <key> [--out <path>]` | Download a chat-attached file from the V5 file server (TCP 6101). **Read-only** — opens a fresh per-request session, no chat login, doesn't kick anyone. Pass `--mid <mid>` instead of `--file-key` plus `--peer/--team/--to-self` to look the file up via roaming history. Verifies md5 on completion. |
192
- | `muc react --mid <MID> --sticker-id <id> [target]` | Add an emoticon reaction to an existing message. **Visible.** Defaults to `--to-self`; non-self chats need `--yes-not-self`. By default assumes the original message was sent by the current user; pass `--src-sender-id <UID>` (and `--src-sender-app <appkey>`) to react to someone else's message. |
193
- | `muc reactions --mid <MID>` | List all emoticon reactions on a single message. Read-only. |
194
- | `muc unreact --emoticon-id <id> [target]` | Revoke one of your own reactions (id from `muc react` or `muc reactions`). |
195
- | `muc manifest` | Print the full command graph + per-command output JSON Schema. Pair with `--json` for agents. |
196
-
197
- `history` flags: scope with one of `--peer UID`, `--team TEAM_ID`,
198
- `--sid <UID1|UID2 or teamId>`, or `--to-self`. `--limit N` (default 50),
199
- `--days N` (window from now, default 30), `--before <ts|ISO>` to fetch
200
- older than a moment, `--sub-type N` to filter to one type (e.g. `1` text,
201
- `7` file, `8` image), `--desc` for newest-first (default: oldest at top).
202
- The fetched `timestamp` is normalized to ms.
149
+ ### Agent 场景示例
150
+
151
+ MUC Skill 的价值在于嵌入更大的 agent 工作流——先让 agent 完成主任务,再用 MUC 传递结果或读取上下文。
152
+
153
+ **跑测试 → 失败通知值班群**
154
+ > 帮我跑一下单测,如果有失败把结果发到 IT 值班群
155
+
156
+ Agent 跑完测试、整理失败摘要,再通过 MUC 推送到群里。MUC 是"结果出口"。
157
+
158
+ **Code Review → 通知作者**
159
+ > 帮我 review 这个 PR,review 完把结论发给作者
160
+
161
+ Agent 分析 diff、生成意见,从 git log 拿到作者,搜出对应 MUC 账号后发消息。
162
+
163
+ **读群聊上下文 → 代为回复**
164
+ > IT 值班群今天在讨论什么?帮我接着回复,说为什么要用 Redis 而不是本地缓存
165
+
166
+ Agent 拉取今日群消息,理解讨论脉络,起草回复并等你确认后发出。MUC 历史是**输入上下文**。
167
+
168
+ **分析日志 → 发文件给同事**
169
+ > 帮我分析 logs/access.log,生成访问量报告发给 王五
170
+
171
+ Agent 统计 PV/UV、Top 路径、错误率,写成报告文件,搜出收件人后 `muc send-file` 发出。
172
+
173
+ **读 MUC 需求 → 生成代码骨架**
174
+ > 看看 王五 昨天发给我的需求,整理成 TODO 并创建对应的函数骨架
175
+
176
+ Agent 拉取消息、提取功能点、在项目里生成文件,最后用 MUC 回复确认。MUC 历史是**任务来源**。
177
+
178
+ **定时周报(配合 `/schedule`)**
179
+ > /schedule 每周五 17:00 帮我把本周 git 提交整理成周报发给上级
180
+
181
+ Agent 定时触发,跑 `git log`、整理自然语言周报、确认收件人、发送。
182
+
183
+ ---
184
+
185
+ ## cli 命令速查
186
+
187
+ ### 查人 / 查组织
203
188
 
204
189
  ```bash
205
- muc history --peer YANWENYU --limit 100
206
- muc history --to-self --days 7
207
- muc history --team 9009876543210000 --before 2026-04-20 --json | jq .
190
+ muc search 李四 # 关键字搜员工
191
+ muc search-dept "信息技术部" # 按部门搜(含员工数 + 层级路径)
192
+ muc search-app "审批" # 搜应用/小程序
193
+ muc whois BOB # 单人完整档案
194
+ muc groups # 我的星标群 + 部门群
195
+ muc search-team "值班" --enrich # 查已加入群(TCP)
196
+ muc team-members 9009876543210000 --profiles # 查群成员 + 部门/岗位(TCP + REST)
208
197
  ```
209
198
 
210
- ### Chat-channel commands (TCP 8101) — see [Coexistence](#coexistence)
199
+ ### 聊天历史
211
200
 
212
- | Command | What it does |
213
- |---|---|
214
- | `muc tcp-ping [--wait N] [--debug]` | TCP connect + NEGOTIATE only. Smoke test, no login frame, **zero** kick risk. |
215
- | `muc sync [--os OS]` | After chat-login, run `syncDone` / `syncSession` / `syncWithdraws`. Read-only. |
216
- | `muc search-team <keyword> [--enrich]` | Search groups you belong to via the chat channel; `--enrich` follows up with `team/get_team` per id to print names + member counts. |
217
- | `muc send (--to-self \| --peer UID \| --team TEAM_ID) <text> [--yes-not-self]` | Send a plain-text message. **Visible to the recipient.** |
218
- | `muc send-file <file> [target] [--yes-not-self]` | Upload a file (TCP 6101) and broadcast as `MESSAGE_CHAT_FILE` (subType=7). Returns persistent `fileKey` + `mid`. **Visible.** Uses both 6101 (upload) and 8101 (broadcast) — costs the chosen `--os` slot for the broadcast. |
219
- | `muc send-image <file> [target] [--width N --height N]` | Same as `send-file` but as `MESSAGE_CHAT_IMAGE` (subType=8). Width/height auto-probed from PNG/JPEG/GIF/BMP headers; pass `--width/--height` to override. |
220
- | `muc send-voice <file> --duration <ms> [target]` | Same as `send-file` but as `MESSAGE_CHAT_AUDIO` (subType=9). `--duration <ms>` is required (the desktop renders the bubble's clip length from this metadata, not from the file). |
221
- | `muc withdraw --mid <MID> [target]` | Recall a sent message. Original gets replaced server-side with "您撤回了一条消息". Target flag must match the original message's chat. |
222
- | `muc clear-session [target]` | Delete a session from your client-side history (syncs to all your devices). **Doesn't delete server-side history** — `history` still returns the messages. |
223
- | `muc wait-react --mid <MID> [--from-uid UID] [--sticker-id ID] [--timeout 300]` | Block until someone reacts with an emoticon to `--mid`, then exit. Useful as a "human-in-the-loop approval" gate after `muc send`. Exits with `TIMEOUT` (exit 5) if no matching reaction arrives within `--timeout` seconds. |
224
- | `muc listen [--from-uid UID] [--team TEAM] [--peer UID] [--contains TEXT] [--regex …] [--at-me] [--max N]` | Long-lived TCP tap that emits one **NDJSON line per matching inbound message** (in `--json` mode each line is the standard envelope; in human mode it's a one-line summary). Supports repeatable filters (OR semantics within each kind, AND across kinds), `--at-me` to restrict to group messages where another user @-mentions you (implies excluding self), `--include-self` to include your own sends, `--include-reactions` to also surface type=3 sub=300/301 frames, `--max N` to exit after N matches. Stop with `^C`. |
225
-
226
- `--os` resolves as **per-call `--os` → `defaultChatOs` in `~/.muc/config.json`
227
- → built-in `iOS`**. Valid values: `Windows`, `OS X`, `Mac`, `iOS`, `Android`.
228
- Set the per-machine default once with `muc init --default-os <os>` (or the
229
- `MUC_DEFAULT_OS` env var, or the interactive prompt during `muc init`). Useful
230
- if your real device is an iPhone — flip the CLI default to `windows` so
231
- chat-channel commands park on the PC slot instead of kicking your phone.
232
- **`--os` / `defaultChatOs` does not provide coexistence — read the section
233
- below before using any chat-channel command if you have a real device
234
- logged in.**
235
-
236
- Sending to anyone other than yourself requires the explicit `--yes-not-self`
237
- flag — a guard against accidentally messaging real humans during testing.
238
-
239
- **Message tail (signature)**: by default `muc send` appends a short tail to
240
- the text it sends (e.g. `"\n—— 来自王璐璐的muc-cli"`) so recipients can
241
- tell automated traffic from a typed message. The check is idempotent —
242
- if your text already ends with the resolved tail, it is not appended a
243
- second time. Customise it with the
244
- `messageTail` field in `~/.muc/config.json` — set to a non-empty string to
245
- override, or to `""` to disable entirely. You can also use
246
- `muc init --message-tail "<custom>"` / `muc init --no-message-tail` to
247
- write the value, and per-call flags `muc send --tail "<one-off>"` /
248
- `muc send --no-tail` to override on a single send. Only `muc send`
249
- (plain-text) is affected; `send-file` / `send-image` / `send-voice` are
250
- not.
201
+ ```bash
202
+ muc history --peer WANGWU --days 7 --limit 200
203
+ muc history --team 9009876543210000 --days 30 --json | jq '.data.messages[]'
204
+ muc history --to-self --days 1
205
+ ```
206
+
207
+ ### 发消息
208
+
209
+ ```bash
210
+ muc send --to-self "hello from CLI" # 发给自己(安全测试)
211
+ muc send --peer BOB "明天上午有空吗?" --yes-not-self
212
+ muc send --team 9009876543210000 "值班通知" --yes-not-self
213
+ ```
214
+
215
+ ### 发文件 / 图片 / 语音
216
+
217
+ ```bash
218
+ muc send-file ./dist/release.dmg --peer QA_LEAD --yes-not-self
219
+ muc send-image ~/Desktop/screenshot.png --to-self # 自动探测宽高
220
+ muc send-voice ./recording.amr --duration 3500 --to-self # duration 单位 ms
221
+ ```
222
+
223
+ ### 下载 / 撤回
251
224
 
252
225
  ```bash
253
- muc tcp-ping --wait 8 # safe protocol smoke test
254
- muc send "hello from the CLI" # to-self (default)
255
- muc send --peer ZHANG_W2 "ping" --yes-not-self
256
- muc send --team 9009876543210000 "team message" --yes-not-self
257
-
258
- muc send-file ./report.pdf --to-self # self test
259
- muc send-image ./screenshot.png --peer ZHANG_W2 --yes-not-self # auto width/height
260
- muc send-voice ./clip.amr --duration 3500 --to-self # 3.5 s clip
261
-
262
- muc download --file-key /chat/m/WANGLULU8/.../<md5> --out ./got.bin # by explicit key
263
- muc download --mid 69ef1ae07e5462c465c05e00 --to-self # look up body via roaming
264
- muc download --mid <MID> --peer YANWENYU --out ./inbox/ # save into a directory
226
+ muc download --mid <mid> --to-self --out ./got.bin # 通过消息 ID 下载附件
227
+ muc withdraw --mid <mid> --to-self # 撤回自己发出的消息
265
228
  ```
266
229
 
267
- `download` is the only file-channel command that **doesn't** open a chat
268
- session — it speaks only port 6101 (and a one-time bucket lookup on first
269
- call). Run it from any environment without disturbing your desktop or phone.
230
+ > 任何命令加 `--json` 输出稳定的机器可读信封:成功 `{ok:true, data}` / 失败 `{ok:false, code, msg}`。
231
+ > 退出码:`0` 成功 / `2` 用法错误 / `3` 鉴权 / `4` 未找到 / `5` 远端或网络 / `1` 内部错误。
232
+
233
+ ---
270
234
 
271
- ## Coexistence
235
+ ## 和桌面端 / 手机端共存
272
236
 
273
- **REST commands always coexist** with a running desktop and phone — the
274
- server allows concurrent `accessToken`s. `whoami`, `search`, `search-dept`,
275
- `search-app`, `search-sno`, `history`, `whois`, `groups`, `emoticons`, and
276
- `download` are REST-only (or 6101-only, in `download`'s case) and safe to
277
- run anytime.
237
+ REST 命令(`search`、`history`、`whois`、`groups` 等)**永远可以共存**,与桌面端、手机端同时在线互不影响。
278
238
 
279
- **Chat-channel commands** (`sync`, `search-team`, `send`, `send-file`,
280
- `send-image`, `send-voice`, `withdraw`, `clear-session`) open a TCP-8101
281
- session and share a per-user **two-slot** model with real devices:
239
+ 聊天通道命令(`send*`、`withdraw`、`sync`、`search-team`、`team-members`)受 MUC **单点登录**规则约束:每个账号在 **PC 端**和**移动端**各只允许**一个**在线设备,同一端登第二个会把第一个挤下线(和"在另一台 Mac 上登 MUC 就把当前这台顶掉"是一回事)。CLI 通过 `--os` 决定自己算哪一端:
282
240
 
283
- | Slot | `--os` values |
241
+ | 算作哪端 | 对应的 `--os` 取值 |
284
242
  |---|---|
285
- | PC | `Windows`, `"OS X"`, `Mac` |
286
- | Mobile | `iOS` (default), `Android` |
243
+ | PC 端 | `Windows` / `OS X` / `Mac`(Mac 桌面端登录时上报的是 `OS X`) |
244
+ | 移动端 | `iOS`(CLI 默认)/ `Android` |
287
245
 
288
- A second login in the same slot kicks the previous client off the realtime
289
- channel — the IM panel goes offline, but tokens stay valid (reopen to
290
- recover). The Mac desktop signs in as `"OS X"` (PC slot); the iPhone signs
291
- in as `iOS` (Mobile slot).
246
+ > ⚠️ **默认发送消息会使移动端下线,谨慎使用。** CLI 默认 `--os iOS`,算"移动端"。
247
+ > 只要跑了 `send` / `withdraw` 等聊天通道命令,**正在登录 MUC 的 iPhone 会被立刻挤下线**
248
+ > (`accessToken` 不失效,但要重新打开 App 才能继续收实时消息)。
249
+ >
250
+ > - 测试或跑通流程时,先用 `muc send --to-self ...` 发给自己。
251
+ > - 真要 `--peer` / `--team` 发出去之前,确认**没人在等 iPhone 上 MUC 的实时消息**。
252
+ > - 如果手机 MUC 必须保持在线,**整段流程改用 REST 命令**(`history`、`search`、`groups`…),别碰聊天通道。
253
+ > - **不要**显式传 `--os Windows / OS X / Mac`——那会把 Mac 桌面端 MUC 也顶下去。
292
254
 
293
- The CLI defaults to `--os iOS` so it coexists with a Mac desktop. If your
294
- iPhone is also signed in, both slots are taken and any TCP command will
295
- kick one of them — fall back to REST-only commands, or run
296
- `muc init --default-os windows` to flip the per-machine default to the PC
297
- slot (which kicks the desktop instead of the phone).
255
+ `muc tcp-ping` 只做 TCP 连接和 NEGOTIATE,**不发登录帧**,不占任何在线名额,纯连通性测试,可以随时跑。
298
256
 
299
- `tcp-ping` only does TCP connect + NEGOTIATE (no `auth/login`), so it can't
300
- kick anything. Run it first to confirm the wire protocol is happy on your
301
- network.
257
+ ---
302
258
 
303
- If you do get kicked, reopen the desktop's IM panel or relaunch the mobile
304
- app to recover — tokens stay valid.
259
+ ## 开发者指南
305
260
 
306
- ## Examples
261
+ ### 从源码运行
307
262
 
308
263
  ```bash
309
- $ printf '%s' "$MY_PWD" | muc login --account WANGLULU8 --password-stdin
310
- uid: WANGLULU8
311
- empId: 5394483846707200
312
- name: 王璐璐
313
- accessToken: TV5l…u9kQ (redacted; use --json for raw)
314
- accessTokenExpireAt: 1745678340000
315
- tokenPwdRefreshed: true
316
- tokenPwdExpireAt: 1748097540000
317
-
318
- $ muc whoami
319
- uid: WANGLULU8
320
- empId: 5394483846707200
321
- name: 王璐璐
322
- deviceId: 8e413b4141…
323
- deviceName: WANGLULU8-mbp.local (muc-cli)
324
- accessToken: TV5l…u9kQ (redacted; use --json for raw)
325
- accessTokenExpireAt: 1745678340000
326
- tokenPwdSet: true
327
- tokenPwdExpireAt: 1748097540000
328
- savedAt: 1745073540000
329
- credentialsPath: ~/.muc/credentials.json
330
-
331
- $ muc search 张
332
- ZHANG_W2 张伟 飞行部 / 机长室
333
- ZHANGSAN03 张三 地面服务部
334
- …
335
-
336
- $ muc whois ZHANG_W2
337
- uid ZHANG_W2
338
- name 张伟
339
- empId 4892173049281024
340
- mobile 1380013…
341
- email zhang.wei@ceair.com
342
- dept 飞行部 / 机长室
343
- title 机长
344
-
345
- $ muc groups
346
- starred:
347
- [常用] (3)
348
- 9001234567890123 IT 值班群
349
- 9001234567890124 运行联络
350
- 9001234567890125 部门通知
351
- dept groups: 1
352
- 9009876543210000 飞行部 / 机长室
353
-
354
- $ muc emoticons
355
- 1001 默认表情包 (32)
356
- 1042 航空主题 (18)
264
+ git clone <repo> everything-cli && cd everything-cli
265
+ pnpm install # 工作区依赖装在仓库根
266
+ cd packages/muc
267
+ pnpm run typecheck # tsc --noEmit,必须先过
268
+ ./bin/muc <command> # 直接走 tsx 跑 src/,零构建
357
269
  ```
358
270
 
359
- ## Files & paths
360
-
361
- | Path | Purpose | Mode |
362
- |---|---|---|
363
- | `~/.muc/config.json` | tenant config: APP_KEY, APP_ID, signing secrets, hosts/ports | 600 |
364
- | `~/.muc/credentials.json` | accessToken, tokenPwd, profile | 600 |
365
- | `~/.muc/device-id` | stable per-CLI deviceId (sha256 hex) | 600 |
366
-
367
- Both files share the `~/.muc/` parent (mode 700). The CLI creates them on
368
- first use; you should never need to edit them by hand.
369
-
370
- ## Changelog
371
-
372
- #### `0.3.1`
373
- `2026.04.29`
374
- - Add `defaultChatOs` to `~/.muc/config.json`: per-machine fallback for the chat-channel `--os` slot. Configure via `muc init --default-os <windows|osx|mac|ios|android>`, the `MUC_DEFAULT_OS` env var, or the interactive prompt during `muc init`. Resolution order: per-call `--os` → `defaultChatOs` → built-in `iOS`. Use this when your real device is on the Mobile slot (e.g. iPhone) so chat-channel commands park on PC instead of kicking your phone.
375
-
376
- #### `0.3.0`
377
- `2026.04.29`
378
- - Add `muc react` / `muc unreact` / `muc reactions` to add, revoke, and list emoticon reactions on existing messages
379
- - Add `muc wait-react` to block until a human reacts to a given mid; useful as a "human-in-the-loop approval" gate after `muc send`
380
- - Add `muc listen`, a long-lived TCP tap with sender / chat / keyword / `--at-me` filters, one NDJSON line per match
381
- - Exit code `5` now also covers `TIMEOUT` and `KICKED`
382
-
383
- #### `0.2.2`
384
- `2026.04.28`
385
- - `muc send` plain-text now appends an idempotent signature tail by default; override via `--tail` / `--no-tail`, `muc init --message-tail`, or `messageTail` in `~/.muc/config.json`
386
-
387
- #### `0.2.1`
388
- `2026.04.28`
389
- - Skills unified across macOS / Linux / Windows
390
- - On Windows, file mode `600` is silently ignored — use NTFS ACLs on shared hosts to protect `~/.muc/`
391
-
392
- #### `0.2.0`
393
- `2026.04.27`
394
- - Add `muc init`: extract `appKey` / `appId` / signing secrets out of source code, write to `~/.muc/config.json` (mode 600)
395
- - Companion subcommands: `muc init --check` / `--show` / `--reset`
396
- - License changed to MIT; package published to npm with public access
397
-
398
- ## What it can't do (yet)
399
-
400
- - **Richtext messages** (`subType=11`, mixed text + image + @-mention in one
401
- bubble). The image segments share the file-channel upload path so the
402
- bulk of the work is already there; just needs a body-builder + a
403
- `send-rich` command.
404
- - **Forwarded / merged messages** (`subType=26`).
405
- - **SSO / scan-to-login.** REST username+password works; the SSO flow is
406
- not wired up.
407
- - **Image thumbnails.** The upload step asks the server to make `tn1`/`tn5`
408
- thumbs, but `muc download` only fetches the original `fileKey`.
409
- - **End-to-end-encrypted file content.** Not active on the default tenant
410
- and the CLI doesn't implement it.
411
-
412
- ## Troubleshooting
413
-
414
- - `code: CONFIG_MISSING` (exit 6) — `~/.muc/config.json` is absent. Run
415
- `muc init` to create it. The CLI cannot guess your tenant's signing
416
- secrets.
417
- - `code: CONFIG_INVALID` (exit 7) — the file exists but failed schema
418
- validation (corruption, manual edit gone wrong, or a stale schema after
419
- upgrade). Run `muc init` to repair it; `--reset --yes` to start clean.
420
- - `login failed: code=…` — sign or DES is wrong. Double-check the signing
421
- secrets and `appKey` / `appId` you supplied to `muc init` against the
422
- values your MUC admin provided. `muc init --show` (secrets redacted) lets
423
- you confirm what's currently saved; `muc init` (interactive) lets you
424
- re-enter the values.
425
- - `error: not logged in` (exit 3, code `AUTH_REQUIRED`) — the saved
426
- credentials file is missing or unreadable. Check `~/.muc/credentials.json`
427
- and re-run `muc login`.
428
- - `code: AUTH_EXPIRED` (exit 3) — accessToken or tokenPwd was rejected by
429
- the server. Re-run `muc login --token-pwd` (or full `muc login` if
430
- `tokenPwd` is also stale).
431
- - `EPROTO` / TLS errors — corporate proxy or VPN intercepting traffic.
432
- `muc-cli` does not pin certificates, so adding the proxy CA to your Node
433
- trust store should suffice (`NODE_EXTRA_CA_CERTS=/path/to/ca.pem`).
434
- - A TCP send kicks your desktop off the IM panel — the CLI's `--os` value
435
- collided with the desktop's slot. See [Coexistence](#coexistence) above:
436
- default `--os iOS` only risks the Mobile slot (your iPhone), not the
437
- desktop. If both desktop and phone are online, drop to REST-only
438
- commands.
439
-
440
- ### Exit-code summary
441
-
442
- | Exit | Code |
443
- |---|---|
444
- | 0 | success |
445
- | 1 | INTERNAL |
446
- | 2 | USAGE |
447
- | 3 | AUTH_REQUIRED, AUTH_EXPIRED |
448
- | 4 | NOT_FOUND |
449
- | 5 | REMOTE_ERROR, NETWORK, TIMEOUT, KICKED |
450
- | 6 | CONFIG_MISSING |
451
- | 7 | CONFIG_INVALID |
452
-
453
- ## Security
454
-
455
- - Both files in `~/.muc/` are written with mode `600`. **Don't** check them
456
- into git or copy them to shared machines.
457
- - **Don't share `~/.muc/config.json`** — it contains your tenant's signing
458
- secrets (`signSecretRest`, `signSecretSecure`, `pwdDesKey`, `appKey`).
459
- Anyone with these can impersonate API calls against your MUC tenant.
460
- - `MUC_PASSWORD` ends up in shell history; prefer `--password-stdin` or
461
- `--token-pwd` for refreshes.
462
- - **Prefer interactive `muc init` over env vars.** Environment variables
463
- leak through shell history and `/proc/<pid>/environ` to other processes
464
- on the same machine. Use the interactive prompt or one-shot flags pulled
465
- from a secret manager rather than persistent `MUC_*` exports.
466
- - TCP login over 8101 registers a device session in one of two slots per
467
- user (PC or Mobile). A second login in the same slot kicks the first.
468
- Default `--os iOS` keeps the CLI on the Mobile slot; only your iPhone is
469
- ever at risk.
470
- - `tcp-ping` is the only chat-channel command that cannot kick anything —
471
- use it as a network smoke-test before trying TCP sends.
271
+ ### 仓库结构
272
+
273
+ ```
274
+ packages/muc/ (everything-cli 工作区中的一个包)
275
+ ├── src/index.ts 命令分发器
276
+ ├── src/commands/*.ts 每个子命令一个文件
277
+ ├── src/lib/ 协议层:wire.ts / chat.ts / sign.ts / …
278
+ ├── src/assistant/ auto-reply 守护进程 + 各 backend
279
+ ├── skills/ muc-* Agent Skills(muc-message / muc-chat / muc-shared / …)
280
+ ├── scripts/build.mjs esbuild 打包脚本(→ dist/muc.mjs)
281
+ ├── bin/muc tsx 入口(零构建直接跑 src/)
282
+ ├── README.md 本文件
283
+ └── REFERENCE.md 完整命令 / 协议参考
284
+ ```
285
+
286
+ > 协议研究文档与抓包脚本保留在内部源码研究仓库,未随本包导入;下文"协议抓包 /
287
+ > 改动后测试 / 协议层参考"小节中对它们的引用均指内部仓库。
288
+
289
+ ### 协议抓包:通过 Surge.app
290
+
291
+ 不用切系统代理、不用安装额外 CA —— Surge.app 已在本机运行即可。
292
+
293
+ **一次性配置**:在 Surge UI 里把你的 MUC 主机(如 `*.example.com`)加进 MITM hostname,打开 MITM + Capture + HTTP API。
294
+
295
+ ```bash
296
+ # 抓 60 秒的 muc HTTP/S 流量,输出 NDJSON
297
+ python3 surge_capture.py --key <surge-http-api-key> --process MUC --duration 60 > flows.ndjson
298
+
299
+ # 聚合成 API 地图(人读)
300
+ python3 analyze_api.py flows.ndjson
301
+
302
+ # 机器读
303
+ python3 analyze_api.py flows.ndjson --json
304
+
305
+ # 只看某个主机
306
+ python3 analyze_api.py flows.ndjson --host muc.example.com
307
+ ```
308
+
309
+ > 如果 Surge 配置了 `http-api-tls = true`,用 `https://127.0.0.1:6171` + 关闭 SSL 验证访问,或直接去掉该配置项。
310
+
311
+ ### 改动后测试流程
312
+
313
+ 1. `pnpm run typecheck`(在 `packages/muc` 下)— 先过类型检查。
314
+ 2. 按内部 `TEST_CASES.md` 从头到尾跑一遍(§1 REST 读 + §2 `tcp-ping` 无条件跑;Mac 桌面 + iPhone 同时在线时跳过 §3 §4)。
315
+ 3. 任何一项失败:把 `--json` 原始信封贴给用户,停下来,不要"修一修再跑"。
316
+
317
+ ### 协议层参考(内部文档)
318
+
319
+ - `HANDOFF.md` — 当前状态、已验证里程碑、常量(hosts / 密钥 / transport AES key)、关键算法的踩坑笔记。
320
+ - `MUC_API.md` — REST 端点全图(`/muc /imm /msm /contacts /mam /...`)+ TCP 8101 帧规范 + 6101 文件通道。
321
+ - `API_CLIENT.md` — 从解包客户端摘出的全量 API 目录,找 CLI 还没封装的端点去这里翻。
322
+
323
+ ### 红线
324
+
325
+ - **绝不入库** `accessToken`、`tokenPwd`、明文密码、`clientSecret`、`pfxPass`、`bsSecretKey`、`encrypt_key`,以及租户密钥(`APP_KEY` / `sign secrets`)。
326
+ - **CLI 默认会顶掉同账号的 iPhone 端 MUC**(默认 `--os iOS`,单点登录规则);显式传 PC 类 `--os` 会顶 Mac 桌面端;桌面 + 手机都需要保持在线时,整段流程退到 REST 命令。
327
+ - **CLI 发的消息所有参与方都可见。** 非自身目标必须带 `--yes-not-self`,先用 `--to-self` 测。
328
+ - **不要擅自改 Surge 配置文件**(`~/Library/Application Support/Surge/Profiles/*.conf`)。要加 MITM hostname 让用户在 Surge UI 里加。