@mu-cabin/muc-cli 0.5.3 → 0.5.4

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
@@ -141,7 +141,7 @@ npx skills add <everything-cli 仓库地址>/packages/muc/skills -y -g
141
141
 
142
142
  ### Skill 的行为规则
143
143
 
144
- - **首次 TCP 命令前**:Skill 会弹 `AskUserQuestion` 提示槽位风险,等你确认后再执行。
144
+ - **首次直连 TCP 命令前**:Skill 只提醒“同账号 iPad 可能被踢”,不再等待槽位确认,随后直接执行。
145
145
  - **不确定发给谁**:先用 `muc search` 缩小候选名单,通过 `AskUserQuestion` 让你选,不会猜。
146
146
  - **非自身目标**:必须经过你显式确认,不会偷偷给真实联系人发消息。
147
147
  - **token 过期**:自动用 `--token-pwd` 静默续登,续登失败才要求你手动 `muc login`。
@@ -232,25 +232,23 @@ muc withdraw --mid <mid> --to-self # 撤回自己发出的消
232
232
 
233
233
  ---
234
234
 
235
- ## 和桌面端 / 手机端共存
235
+ ## 和桌面端 / 手机端 / iPad 共存
236
236
 
237
237
  REST 命令(`search`、`history`、`whois`、`groups` 等)**永远可以共存**,与桌面端、手机端同时在线互不影响。
238
238
 
239
- 聊天通道命令(`send*`、`withdraw`、`sync`、`search-team`、`team-members`)受 MUC **单点登录**规则约束:每个账号在 **PC 端**和**移动端**各只允许**一个**在线设备,同一端登第二个会把第一个挤下线(和"在另一台 Mac 上登 MUC 就把当前这台顶掉"是一回事)。CLI 通过 `--os` 决定自己算哪一端:
239
+ 聊天通道命令(`send*`、`withdraw`、`sync`、`search-team`、`team-members`)进入 MUC 实时通道。每个账号有 **PC、Mobile、iPad 三个独立槽位**;同槽后登录会把前一个挤下线。CLI 继续沿用 `--os` 选槽位,其中 `iPad` 是逻辑值:线上报文实际发送 `os:"iOS"` 和 `browser:"iPad"`。
240
240
 
241
241
  | 算作哪端 | 对应的 `--os` 取值 |
242
242
  |---|---|
243
243
  | PC 端 | `Windows` / `OS X` / `Mac`(Mac 桌面端登录时上报的是 `OS X`) |
244
- | 移动端 | `iOS`(CLI 默认)/ `Android` |
244
+ | 移动端 | `iOS` / `Android` |
245
+ | iPad 端 | `iPad`(CLI 默认;线上报文为 `os:iOS + browser:iPad`) |
245
246
 
246
- > ⚠️ **默认发送消息会使移动端下线,谨慎使用。** CLI 默认 `--os iOS`,算"移动端"。
247
- > 只要跑了 `send` / `withdraw` 等聊天通道命令,**正在登录 MUC 的 iPhone 会被立刻挤下线**
248
- > (`accessToken` 不失效,但要重新打开 App 才能继续收实时消息)。
247
+ > ⚠️ CLI 默认使用 **iPad 槽位**。PC 桌面端和手机端可以继续在线;如果同账号正登录 iPad,iPad 可能被挤下线,重新打开 App 即可恢复,`accessToken` 不失效。
249
248
  >
250
249
  > - 测试或跑通流程时,先用 `muc send --to-self ...` 发给自己。
251
- > - 真要 `--peer` / `--team` 发出去之前,确认**没人在等 iPhone 上 MUC 的实时消息**。
252
- > - 如果手机 MUC 必须保持在线,**整段流程改用 REST 命令**(`history`、`search`、`groups`…),别碰聊天通道。
253
- > - **不要**显式传 `--os Windows / OS X / Mac`——那会把 Mac 桌面端 MUC 也顶下去。
250
+ > - `--os iOS/Android` 会改占 Mobile 槽位,可能踢手机;`--os Windows/OS X/Mac` 会改占 PC 槽位,可能踢桌面端。
251
+ > - Skill 对默认 iPad 风险只做非阻塞提醒,不再要求预先确认槽位;发送给真实联系人、撤回等业务确认仍保留。
254
252
 
255
253
  `muc tcp-ping` 只做 TCP 连接和 NEGOTIATE,**不发登录帧**,不占任何在线名额,纯连通性测试,可以随时跑。
256
254
 
@@ -312,7 +310,7 @@ python3 analyze_api.py flows.ndjson --host muc.example.com
312
310
  ### 改动后测试流程
313
311
 
314
312
  1. `pnpm run typecheck`(在 `packages/muc` 下)— 先过类型检查。
315
- 2. 按内部 `TEST_CASES.md` 从头到尾跑一遍(§1 REST 读 + §2 `tcp-ping` 无条件跑;Mac 桌面 + iPhone 同时在线时跳过 §3 §4)。
313
+ 2. 按内部 `TEST_CASES.md` 从头到尾跑一遍;实时命令默认走 iPad 槽位,实机验证前确认没有需要保持在线的同账号 iPad。
316
314
  3. 任何一项失败:把 `--json` 原始信封贴给用户,停下来,不要"修一修再跑"。
317
315
 
318
316
  ### 协议层参考(内部文档)
@@ -324,6 +322,6 @@ python3 analyze_api.py flows.ndjson --host muc.example.com
324
322
  ### 红线
325
323
 
326
324
  - **绝不入库** `accessToken`、`tokenPwd`、明文密码、`clientSecret`、`pfxPass`、`bsSecretKey`、`encrypt_key`,以及租户密钥(`APP_KEY` / `sign secrets`)。
327
- - **CLI 默认会顶掉同账号的 iPhone 端 MUC**(默认 `--os iOS`,单点登录规则);显式传 PC 类 `--os` 会顶 Mac 桌面端;桌面 + 手机都需要保持在线时,整段流程退到 REST 命令。
325
+ - **CLI 默认占用同账号的 iPad 槽位**(`--os iPad` → `os:iOS + browser:iPad`),可能顶掉真实 iPad;显式传 Mobile/PC 类 `--os` 才会影响手机/桌面端。
328
326
  - **CLI 发的消息所有参与方都可见。** 非自身目标必须带 `--yes-not-self`,先用 `--to-self` 测。
329
327
  - **不要擅自改 Surge 配置文件**(`~/Library/Application Support/Surge/Profiles/*.conf`)。要加 MITM hostname 让用户在 Surge UI 里加。
package/REFERENCE.md CHANGED
@@ -252,14 +252,10 @@ muc history --team 9009876543210000 --before 2026-04-20 --json | jq .
252
252
  | `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`. |
253
253
 
254
254
  `--os` resolves as **per-call `--os` → `defaultChatOs` in `~/.muc/config.json`
255
- → built-in `iOS`**. Valid values: `Windows`, `OS X`, `Mac`, `iOS`, `Android`.
256
- Set the per-machine default once with `muc init --default-os <os>` (or the
257
- `MUC_DEFAULT_OS` env var, or the interactive prompt during `muc init`). Useful
258
- if your real device is an iPhone — flip the CLI default to `windows` so
259
- chat-channel commands park on the PC slot instead of kicking your phone.
260
- **`--os` / `defaultChatOs` does not provide coexistence — read the section
261
- below before using any chat-channel command if you have a real device
262
- logged in.**
255
+ → built-in `iPad`**. Valid values: `Windows`, `OS X`, `Mac`, `iOS`, `Android`,
256
+ and `iPad`. The existing option name is retained for compatibility: logical
257
+ `iPad` serializes as `os:"iOS", browser:"iPad"` and selects the third slot.
258
+ Set an override with `muc init --default-os <os>` or `MUC_DEFAULT_OS`.
263
259
 
264
260
  Sending to anyone other than yourself requires the explicit `--yes-not-self`
265
261
  flag — a guard against accidentally messaging real humans during testing.
@@ -325,7 +321,7 @@ through `muc daemon-start`, not through a dedicated assistant account.
325
321
 
326
322
  | Command | What it does |
327
323
  |---|---|
328
- | `muc assistantd-start [--os OS] [--on-kicked POLICY] [--debug]` | Start the assistant daemon in the foreground. Holds the chat slot for the chosen `--os` (priority: `--os` → the active profile's `defaultChatOs` → built-in `iOS`). `POLICY` is shared with the personal daemon and defaults to `3m`. With a [dedicated account](#dedicated-account-for-the-daemon) provisioned, that profile defaults `defaultChatOs` to `Windows` (PC slot). Without a dedicated profile it now fails unless `muc init --allow-assistant-shared-profile` explicitly opted into shared-profile mode. v1 has no launchd integration — wrap it in `tmux` / `screen` / `nohup` if you want it persistent. |
324
+ | `muc assistantd-start [--os OS] [--on-kicked POLICY] [--debug]` | Start the assistant daemon in the foreground. Holds the chosen slot (priority: `--os` → active profile `defaultChatOs` → built-in `iPad`). Personal and dedicated-assistant behavior share this default. `POLICY` defaults to `3m`. Without a dedicated profile it fails unless `muc init --allow-assistant-shared-profile` explicitly opted into shared-profile mode. |
329
325
  | `muc assistantd-status` | Probe the daemon: running? slot? attached assistants? last paused-state message? |
330
326
  | `muc assistantd-stop` | Send `SIGTERM` to the daemon (graceful shutdown). |
331
327
  | `muc assistantd-resume` | Manual resume for `--on-kicked pause`, auth expiry, or exhausted network retries is reserved for v1.1. Today: `assistantd-stop` then `assistantd-start` again. |
@@ -352,10 +348,9 @@ Shared-profile assistant mode is an explicit advanced opt-in:
352
348
  as the personal `~/.muc` account and is mutually exclusive with
353
349
  `muc daemon-start` for that uid.
354
350
 
355
- - **Chat slot.** A daemon and a real device on the **same** slot kick
356
- each other (`auth/kicked`, "same account login"). The CLI's built-in
357
- default slot is `iOS` (Mobile) — so an iPhone login knocks a shared daemon
358
- offline.
351
+ - **Chat slot.** A daemon and a real device on the **same** slot kick each
352
+ other (`auth/kicked`, "same account login"). The built-in default is the
353
+ separate iPad slot, so desktop and phone coexist; a real iPad may conflict.
359
354
  - **REST `tokenPwd`.** A fresh `muc login` rotates the account's refresh
360
355
  token; other devices on that account get logged out when their token
361
356
  next refreshes.
@@ -367,7 +362,7 @@ separate from your personal `~/.muc/`:
367
362
  ```bash
368
363
  muc assistant-init # tenant config → ~/.muc/assistant/config.json
369
364
  # (tenant secrets/hosts default to whatever your
370
- # personal `muc init` saved; defaultChatOs → Windows)
365
+ # personal `muc init` saved; defaultChatOs → iPad)
371
366
  muc assistant-login --account <BOT_UID> # → ~/.muc/assistant/credentials.json
372
367
  # (--platform defaults to mac)
373
368
  muc assistantd-start # picks up the dedicated profile
@@ -376,10 +371,9 @@ muc whoami --assistant # confirm which account it's on
376
371
 
377
372
  Once provisioned, `muc assistantd-start` reads the dedicated profile
378
373
  automatically (every REST call the daemon makes follows it too). The
379
- daemon parks on the **PC slot** (`Windows`), so it coexists with your
380
- personal account on a real iPhone (Mobile slot) on the same machine — at
381
- the cost of conflicting with the MUC **desktop app** (also PC slot; don't
382
- run it). Remove the profile (`rm -rf ~/.muc/assistant/config.json
374
+ daemon defaults to the **iPad slot**, so the account's desktop and phone
375
+ slots remain independent. A real iPad logged into that same assistant
376
+ account may be kicked. Remove the profile (`rm -rf ~/.muc/assistant/config.json
383
377
  ~/.muc/assistant/credentials.json`) only if you intend to use the explicit
384
378
  shared-profile mode.
385
379
 
@@ -463,7 +457,7 @@ Hand-edit, or seeded by `assistant-new`:
463
457
 
464
458
  ```bash
465
459
  # v1: foreground only; wrap in tmux/screen for persistence.
466
- muc assistantd-start --os iOS # default Mobile slot
460
+ muc assistantd-start # default iPad slot
467
461
  muc assistant-new desk-bot --description "out-of-hours triage in IT 值班群"
468
462
  $EDITOR ~/.muc/assistants/desk-bot/config.json # add scope.tids etc.
469
463
  muc assistant-edit desk-bot # tune the persona
@@ -526,23 +520,22 @@ run anytime.
526
520
 
527
521
  **Chat-channel commands** (`sync`, `search-team`, `team-members`, `send`, `send-file`,
528
522
  `send-image`, `send-voice`, `withdraw`, `clear-session`, `pin`, `unpin`, `pinned`) open a TCP-8101
529
- session and share a per-user **two-slot** model with real devices:
523
+ session and share a per-user **three-slot** model with real devices:
530
524
 
531
525
  | Slot | `--os` values |
532
526
  |---|---|
533
527
  | PC | `Windows`, `"OS X"`, `Mac` |
534
- | Mobile | `iOS` (default), `Android` |
528
+ | Mobile | `iOS`, `Android` |
529
+ | iPad | `iPad` (default; wire `os:"iOS", browser:"iPad"`) |
535
530
 
536
531
  A second login in the same slot kicks the previous client off the realtime
537
532
  channel — the IM panel goes offline, but tokens stay valid (reopen to
538
- recover). The Mac desktop signs in as `"OS X"` (PC slot); the iPhone signs
539
- in as `iOS` (Mobile slot).
533
+ recover). The Mac desktop signs in as `"OS X"` (PC); the iPhone signs in as
534
+ `iOS` (Mobile); the iPad app adds `browser:"iPad"` (iPad).
540
535
 
541
- The CLI defaults to `--os iOS` so it coexists with a Mac desktop. If your
542
- iPhone is also signed in, both slots are taken and any TCP command will
543
- kick one of them — fall back to REST-only commands, or run
544
- `muc init --default-os windows` to flip the per-machine default to the PC
545
- slot (which kicks the desktop instead of the phone).
536
+ The CLI defaults to `--os iPad`, so desktop and phone can remain online.
537
+ Only a real iPad on the same account may be kicked by the default. Explicit
538
+ PC or Mobile values intentionally target those older slots.
546
539
 
547
540
  `tcp-ping` only does TCP connect + NEGOTIATE (no `auth/login`), so it can't
548
541
  kick anything. Run it first to confirm the wire protocol is happy on your
@@ -636,13 +629,15 @@ files by hand.
636
629
  ## Changelog
637
630
 
638
631
  #### Unreleased
632
+ - Add the third iPad chat slot. Logical `--os iPad` serializes as `os:"iOS", browser:"iPad"` and is now the built-in default for personal commands, the personal daemon, and assistantd. Existing explicit `defaultChatOs` values remain authoritative.
633
+ - Slot safety guidance is now non-blocking: skills remind that a real iPad may be kicked and continue without asking for slot confirmation. Recipient, content, and destructive-action confirmations are unchanged.
639
634
  - Add the personal daemon command family: `muc daemon-start`, `daemon-status`, `daemon-stop`, and `daemon-events`. Ordinary chat-channel commands now route only through the personal daemon and report `daemon_kind:"personal"` when routed.
640
635
  - Dedicated `assistantd` is no longer borrowed by ordinary commands. Without a dedicated assistant profile, `assistantd-start` now requires explicit `muc init --allow-assistant-shared-profile`; same-uid shared-profile assistantd conflicts with the personal daemon / direct ordinary TCP via `DAEMON_CONFLICT`.
641
636
  - Add raw event cursors (`<epoch_ms>-<sequence>`), 90-day personal daemon event retention, and `CURSOR_NOT_FOUND` handling for missing retained cursors.
642
637
 
643
638
  #### `0.5.0`
644
639
  `2026.05.11`
645
- - Add `muc assistant-init` / `muc assistant-login` and `muc whoami --assistant`: the assistant daemon can now authenticate as a **dedicated account** stored under `~/.muc/assistant/` (config.json, credentials.json, device-id, refresh logs), fully separate from the personal `~/.muc/` account every other command uses. `muc assistantd-start` picks it up automatically when present; the dedicated profile defaults `defaultChatOs` to `Windows` (PC slot) so the daemon coexists with a personal account on a real iPhone (Mobile slot). `assistant-init` seeds tenant secrets/hosts from your personal `muc init`; `assistant-login` defaults `--platform` to `mac`. Older builds kept shared-profile behavior when no profile existed; current builds require explicit opt-in.
640
+ - Add `muc assistant-init` / `muc assistant-login` and `muc whoami --assistant`: the assistant daemon can authenticate as a **dedicated account** under `~/.muc/assistant/`, fully separate from the personal profile. At this release it defaulted to the PC slot; the current Unreleased behavior supersedes that with the iPad default. `assistant-login` still defaults REST `--platform` to `mac`.
646
641
  - Internals: `config.json` / `credentials.json` / `device-id` / `remember.salt` / `runtime/refresh-log.ndjson` paths now resolve through a process-wide *profile root* (default `~/.muc`) instead of being hardcoded. `configPath` / `credentialsPath` / `configDir` are now functions.
647
642
 
648
643
  #### `0.4.1`
@@ -661,7 +656,7 @@ files by hand.
661
656
 
662
657
  #### `0.3.1`
663
658
  `2026.04.29`
664
- - 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.
659
+ - Add `defaultChatOs` to `~/.muc/config.json`. At this release the accepted values stopped at Android and the built-in fallback was `iOS`; current Unreleased behavior also accepts `ipad` and defaults to `iPad`.
665
660
 
666
661
  #### `0.3.0`
667
662
  `2026.04.29`
@@ -724,11 +719,9 @@ files by hand.
724
719
  - `EPROTO` / TLS errors — corporate proxy or VPN intercepting traffic.
725
720
  `muc-cli` does not pin certificates, so adding the proxy CA to your Node
726
721
  trust store should suffice (`NODE_EXTRA_CA_CERTS=/path/to/ca.pem`).
727
- - A TCP send kicks your desktop off the IM panel — the CLI's `--os` value
728
- collided with the desktop's slot. See [Coexistence](#coexistence) above:
729
- default `--os iOS` only risks the Mobile slot (your iPhone), not the
730
- desktop. If both desktop and phone are online, drop to REST-only
731
- commands.
722
+ - A TCP command kicks a client off the IM panel — the CLI's `--os` value
723
+ collided with that client's slot. The default `--os iPad` only risks a
724
+ real iPad; explicit PC or Mobile values risk the matching client.
732
725
 
733
726
  ### 5.9 Gemini CLI headless timeout
734
727
 
@@ -779,9 +772,8 @@ works interactively.
779
772
  leak through shell history and `/proc/<pid>/environ` to other processes
780
773
  on the same machine. Use the interactive prompt or one-shot flags pulled
781
774
  from a secret manager rather than persistent `MUC_*` exports.
782
- - TCP login over 8101 registers a device session in one of two slots per
783
- user (PC or Mobile). A second login in the same slot kicks the first.
784
- Default `--os iOS` keeps the CLI on the Mobile slot; only your iPhone is
785
- ever at risk.
775
+ - TCP login over 8101 registers a device session in one of three slots per
776
+ user (PC, Mobile, or iPad). A second login in the same slot kicks the first.
777
+ Default `--os iPad` only risks a real iPad on the same account.
786
778
  - `tcp-ping` is the only chat-channel command that cannot kick anything —
787
779
  use it as a network smoke-test before trying TCP sends.
package/dist/muc.mjs CHANGED
@@ -21479,7 +21479,7 @@ function file() {
21479
21479
  return join2(dir(), "config.json");
21480
21480
  }
21481
21481
  var CONFIG_SCHEMA_VERSION = 1;
21482
- var CHAT_OS_VALUES = ["Windows", "OS X", "Mac", "iOS", "Android"];
21482
+ var CHAT_OS_VALUES = ["Windows", "OS X", "Mac", "iOS", "Android", "iPad"];
21483
21483
  var MucConfigSchema = external_exports.object({
21484
21484
  appKey: external_exports.string().regex(/^[0-9a-f]{8}$/, "appKey must be 8 lowercase hex chars"),
21485
21485
  appId: external_exports.string().regex(/^[0-9a-f]{24}$/, "appId must be 24 lowercase hex chars"),
@@ -21499,9 +21499,9 @@ var MucConfigSchema = external_exports.object({
21499
21499
  // any other → that literal string (e.g. "\n— bot")
21500
21500
  // Per-call `--tail` / `--no-tail` flags on `muc send` override this.
21501
21501
  messageTail: external_exports.string().max(200).optional(),
21502
- // Default chat-channel `os` slot for TCP commands when --os is not passed.
21503
- // PC ({Windows, OS X, Mac}) vs Mobile ({iOS, Android}); same slot as the
21504
- // running desktop will kick it. Built-in fallback is 'iOS' (Mobile).
21502
+ // Default logical chat slot for TCP commands when --os is not passed.
21503
+ // PC: Windows/OS X/Mac; Mobile: iOS/Android; iPad: iPad. The iPad value is
21504
+ // serialized on the wire as os=iOS + browser=iPad. Built-in fallback: iPad.
21505
21505
  defaultChatOs: external_exports.enum(CHAT_OS_VALUES).optional(),
21506
21506
  // Opt-in: when true, the assistant daemon writes a live status headline
21507
21507
  // to your profile signature (`ext.sign`, visible to anyone who hovers
@@ -21652,7 +21652,7 @@ var PLATFORM_WIN = 4;
21652
21652
  var PLATFORM_MAC = 5;
21653
21653
  var SECURE_SYS_KEY = "mx_app_key";
21654
21654
  var PWD_DES_IV = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
21655
- var DEFAULT_CHAT_OS = "iOS";
21655
+ var DEFAULT_CHAT_OS = "iPad";
21656
21656
  var DEFAULT_REST_PLATFORM = PLATFORM_IOS;
21657
21657
  function defaultMessageTail(userName) {
21658
21658
  const name = userName.trim() || "unknown";
@@ -21855,7 +21855,8 @@ var CHAT_OS_MAP = {
21855
21855
  mac: "Mac",
21856
21856
  macos: "Mac",
21857
21857
  ios: "iOS",
21858
- android: "Android"
21858
+ android: "Android",
21859
+ ipad: "iPad"
21859
21860
  };
21860
21861
  function parseChatOs(raw) {
21861
21862
  const k = raw.trim().toLowerCase();
@@ -21863,7 +21864,7 @@ function parseChatOs(raw) {
21863
21864
  throw new CliError(
21864
21865
  "USAGE",
21865
21866
  `invalid --os: ${raw}`,
21866
- "expected windows|osx|mac|ios|android"
21867
+ "expected windows|osx|mac|ios|android|ipad"
21867
21868
  );
21868
21869
  }
21869
21870
  function resolveChatOs(optsOs, cfg) {
@@ -22494,7 +22495,7 @@ function register(program3, variant) {
22494
22495
  const isAssistant = variant?.kind === "assistant";
22495
22496
  define2(program3, {
22496
22497
  name: isAssistant ? "assistant-login" : "login",
22497
- description: isAssistant ? "Authenticate the assistant daemon's DEDICATED account and persist its tokens to ~/.muc/assistant/credentials.json (separate from your personal ~/.muc/credentials.json \u2014 the two never rotate each other's tokenPwd). Requires `muc assistant-init` first. Defaults --platform to mac (the daemon parks on the PC chat slot via cfg.defaultChatOs). Run interactively: `muc assistant-login --account <UID>` prompts for the password with echo off." : "Authenticate against MUC and persist accessToken + tokenPwd. Run interactively in your own terminal: `muc login --account <UID>` prompts for the password with echo off. `--token-pwd` reuses the saved refresh token and needs no password (this is the only form an agent should run silently). Avoid `--password` / `--password-stdin` / `$MUC_PASSWORD` \u2014 they leak the password into shell history, process lists, and agent chat scrollback.",
22498
+ description: isAssistant ? "Authenticate the assistant daemon's DEDICATED account and persist its tokens to ~/.muc/assistant/credentials.json (separate from your personal ~/.muc/credentials.json \u2014 the two never rotate each other's tokenPwd). Requires `muc assistant-init` first. REST login still defaults --platform to mac; the daemon chat connection separately defaults to the iPad slot via cfg.defaultChatOs. Run interactively: `muc assistant-login --account <UID>` prompts for the password with echo off." : "Authenticate against MUC and persist accessToken + tokenPwd. Run interactively in your own terminal: `muc login --account <UID>` prompts for the password with echo off. `--token-pwd` reuses the saved refresh token and needs no password (this is the only form an agent should run silently). Avoid `--password` / `--password-stdin` / `$MUC_PASSWORD` \u2014 they leak the password into shell history, process lists, and agent chat scrollback.",
22498
22499
  output: Output,
22499
22500
  configure: (cmd) => {
22500
22501
  cmd.option("-a, --account <uid>", "MUC account uid (uppercase, e.g. WANGLULU8)").option("-p, --password <pwd>", "plaintext password (avoid; ends up in shell history)").option("--password-stdin", "read password from stdin (no trailing newline)").option("--token-pwd", "reuse the saved tokenPwd (skips password)").option("--platform <p>", "ios|android|win|mac or numeric code", (v) => parsePlatform(v)).option("--app-version <v>", "override appVersion sent in the login form").option("--remember", "persist an encrypted copy of the password for unattended re-login").option("--no-remember", "wipe any previously --remember'ed password");
@@ -22714,7 +22715,7 @@ var FingerprintShape = external_exports.object({
22714
22715
  messageTail: external_exports.string(),
22715
22716
  // summary: "default" / "none" / "custom (N chars)"
22716
22717
  defaultChatOs: external_exports.string(),
22717
- // summary: explicit value or "default (iOS)"
22718
+ // summary: explicit value or "default (iPad)"
22718
22719
  exposeStatusInSignature: external_exports.boolean(),
22719
22720
  noRouteHint: external_exports.boolean(),
22720
22721
  allowAssistantSharedProfile: external_exports.boolean(),
@@ -22911,10 +22912,10 @@ function register2(program3, variant) {
22911
22912
  const isAssistant = variant?.kind === "assistant";
22912
22913
  define2(program3, {
22913
22914
  name: isAssistant ? "assistant-init" : "init",
22914
- description: isAssistant ? "Bootstrap or update the assistant daemon's DEDICATED-account tenant config at ~/.muc/assistant/config.json (separate from your personal ~/.muc/config.json). Provisioning this makes `muc assistantd-start` authenticate as that account on its own chat slot (defaults defaultChatOs=Windows \u2192 PC slot, so it coexists with a real iPhone on the Mobile slot). Tenant secrets default to whatever your personal `muc init` saved \u2014 pass --app-key/--app-id/--sign-secret-* to override. Then run `muc assistant-login`." : "Bootstrap or update ~/.muc/config.json (tenant config: APP_KEY, APP_ID, signing secrets, hosts/ports). Interactive prompts by default; non-interactive via flags or MUC_* env vars. WARNING: env vars are visible to other processes via /proc/<pid>/environ and shell history \u2014 prefer interactive or flag-based for production.",
22915
+ description: isAssistant ? "Bootstrap or update the assistant daemon's DEDICATED-account tenant config at ~/.muc/assistant/config.json (separate from your personal ~/.muc/config.json). Provisioning this makes `muc assistantd-start` authenticate as that account on its own chat slot (defaults defaultChatOs=iPad, separate from PC and Mobile; a real iPad on the same account may be kicked). Tenant secrets default to whatever your personal `muc init` saved \u2014 pass --app-key/--app-id/--sign-secret-* to override. Then run `muc assistant-login`." : "Bootstrap or update ~/.muc/config.json (tenant config: APP_KEY, APP_ID, signing secrets, hosts/ports). Interactive prompts by default; non-interactive via flags or MUC_* env vars. WARNING: env vars are visible to other processes via /proc/<pid>/environ and shell history \u2014 prefer interactive or flag-based for production.",
22915
22916
  output: Output2,
22916
22917
  configure: (cmd) => {
22917
- cmd.option("--check", "verify config presence and validity (exit 0 ok / 6 missing / 7 invalid); does not modify").option("--show", "print the saved config (secrets are always redacted in both human and JSON output; cat ~/.muc/config.json for raw values)").option("--reset", "delete ~/.muc/config.json (requires --yes)").option("--yes", "confirm a destructive action like --reset").option("--no-interactive", "fail rather than prompt for missing fields").option("--app-key <hex>", "tenant APP_KEY (8 lowercase hex)").option("--app-id <hex>", "tenant APP_ID (24 lowercase hex)").option("--sign-secret-rest <s>", "REST signing secret").option("--sign-secret-secure <s>", "secure-API signing secret").option("--pwd-des-key <s>", "password DES key (defaults to APP_KEY)").option("--rest-host <url>", "REST host URL").option("--sso-host <url>", "SSO host URL (optional)").option("--im-host <h>", "IM TCP host").option("--im-port <n>", "IM TCP port").option("--file-host <h>", "file-channel TCP host").option("--file-port <n>", "file-channel TCP port").option("--message-tail <text>", "custom signature appended to outbound `muc send` text (max 200 chars)").option("--no-message-tail", "explicitly disable the message tail (no signature appended)").option("--default-os <os>", "default chat-channel os slot for TCP commands when --os is not passed (windows|osx|mac|ios|android); built-in fallback is iOS").option("--expose-status-in-signature", "opt in: assistant daemon writes a live status headline to your MUC profile signature (visible to anyone who hovers your name). Daemon reads this flag at start; restart `muc assistantd-start` after toggling for the change to take effect.").option("--no-expose-status-in-signature", "opt out (default) of profile-signature status updates; /status chat command stays available regardless. Daemon reads this flag at start; restart needed for the change to take effect.").option("--allow-assistant-shared-profile", "advanced opt-in: allow assistantd to use this personal profile when no dedicated assistant profile exists").option("--no-allow-assistant-shared-profile", "opt out (default): assistantd must use its dedicated ~/.muc/assistant profile");
22918
+ cmd.option("--check", "verify config presence and validity (exit 0 ok / 6 missing / 7 invalid); does not modify").option("--show", "print the saved config (secrets are always redacted in both human and JSON output; cat ~/.muc/config.json for raw values)").option("--reset", "delete ~/.muc/config.json (requires --yes)").option("--yes", "confirm a destructive action like --reset").option("--no-interactive", "fail rather than prompt for missing fields").option("--app-key <hex>", "tenant APP_KEY (8 lowercase hex)").option("--app-id <hex>", "tenant APP_ID (24 lowercase hex)").option("--sign-secret-rest <s>", "REST signing secret").option("--sign-secret-secure <s>", "secure-API signing secret").option("--pwd-des-key <s>", "password DES key (defaults to APP_KEY)").option("--rest-host <url>", "REST host URL").option("--sso-host <url>", "SSO host URL (optional)").option("--im-host <h>", "IM TCP host").option("--im-port <n>", "IM TCP port").option("--file-host <h>", "file-channel TCP host").option("--file-port <n>", "file-channel TCP port").option("--message-tail <text>", "custom signature appended to outbound `muc send` text (max 200 chars)").option("--no-message-tail", "explicitly disable the message tail (no signature appended)").option("--default-os <os>", "default chat-channel slot for TCP commands when --os is not passed (windows|osx|mac|ios|android|ipad); built-in fallback is iPad").option("--expose-status-in-signature", "opt in: assistant daemon writes a live status headline to your MUC profile signature (visible to anyone who hovers your name). Daemon reads this flag at start; restart `muc assistantd-start` after toggling for the change to take effect.").option("--no-expose-status-in-signature", "opt out (default) of profile-signature status updates; /status chat command stays available regardless. Daemon reads this flag at start; restart needed for the change to take effect.").option("--allow-assistant-shared-profile", "advanced opt-in: allow assistantd to use this personal profile when no dedicated assistant profile exists").option("--no-allow-assistant-shared-profile", "opt out (default): assistantd must use its dedicated ~/.muc/assistant profile");
22918
22919
  },
22919
22920
  run: async (cmd) => {
22920
22921
  const opts = cmd.opts();
@@ -23056,7 +23057,7 @@ function register2(program3, variant) {
23056
23057
  }
23057
23058
  }
23058
23059
  }
23059
- const chatOsDefault = isAssistant ? "windows" : DEFAULT_CHAT_OS.toLowerCase();
23060
+ const chatOsDefault = DEFAULT_CHAT_OS.toLowerCase();
23060
23061
  let defaultChatOs;
23061
23062
  let dcoRaw;
23062
23063
  const dcoFlag = opts.defaultOs;
@@ -23069,7 +23070,7 @@ function register2(program3, variant) {
23069
23070
  defaultChatOs = existing.defaultChatOs;
23070
23071
  } else if (interactive) {
23071
23072
  const answer = await readLine(
23072
- "Default chat OS slot (windows|osx|mac|ios|android)",
23073
+ "Default chat slot (windows|osx|mac|ios|android|ipad)",
23073
23074
  chatOsDefault
23074
23075
  );
23075
23076
  if (answer.length > 0) dcoRaw = answer;
@@ -23078,7 +23079,7 @@ function register2(program3, variant) {
23078
23079
  defaultChatOs = parseChatOs(dcoRaw);
23079
23080
  }
23080
23081
  if (defaultChatOs === void 0 && isAssistant) {
23081
- defaultChatOs = "Windows";
23082
+ defaultChatOs = DEFAULT_CHAT_OS;
23082
23083
  }
23083
23084
  const noRouteHint = existing?.noRouteHint;
23084
23085
  const sharedConflict = detectFlagConflict(process.argv.slice(2), "allow-assistant-shared-profile");
@@ -23913,12 +23914,15 @@ var MucChatSocket = class extends EventEmitter {
23913
23914
 
23914
23915
  // src/lib/chat.ts
23915
23916
  init_profile();
23917
+ function chatLoginIdentity(chatOs) {
23918
+ return chatOs === "iPad" ? { os: "iOS", browser: "iPad" } : { os: chatOs };
23919
+ }
23916
23920
  function buildLoginPayload(cred, chatOs, cfg) {
23917
23921
  return {
23918
23922
  login: {
23919
23923
  account: cred.uid,
23920
23924
  accessToken: cred.accessToken,
23921
- os: chatOs,
23925
+ ...chatLoginIdentity(chatOs),
23922
23926
  osVersion: release3(),
23923
23927
  sdkVersion: 21,
23924
23928
  deviceId: cred.deviceId,
@@ -25080,7 +25084,7 @@ function register10(program3) {
25080
25084
  description: 'Search groups the current user belongs to (TCP chat channel sid="team", cid="search_own_teams"). Pass --enrich for names + member counts. Will kick a session in the same online-slot.',
25081
25085
  output: Output10,
25082
25086
  configure: (cmd) => {
25083
- cmd.argument("<keyword>", "team name fragment").option("--enrich", "follow up with team/get_team per id").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
25087
+ cmd.argument("<keyword>", "team name fragment").option("--enrich", "follow up with team/get_team per id").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
25084
25088
  },
25085
25089
  run: async (cmd) => {
25086
25090
  const [kw] = cmd.args;
@@ -25205,7 +25209,7 @@ function register11(program3) {
25205
25209
  description: "List members of a joined group by TEAM_ID via TCP team/get_team_members. Pass --profiles to enrich department/title via organization search. Will kick a session in the same online-slot.",
25206
25210
  output: Output11,
25207
25211
  configure: (cmd) => {
25208
- cmd.argument("<teamId>", "team/group id from search-team").option("--profiles", "enrich each member with org directory fields via REST organization/search").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
25212
+ cmd.argument("<teamId>", "team/group id from search-team").option("--profiles", "enrich each member with org directory fields via REST organization/search").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
25209
25213
  },
25210
25214
  run: async (cmd) => {
25211
25215
  const [id] = cmd.args;
@@ -25500,7 +25504,7 @@ function register15(program3) {
25500
25504
  description: "After chat-login, run sync/syncDone, sync/syncSession, sync/syncWithdraws and return the raw responses.",
25501
25505
  output: Output15,
25502
25506
  configure: (cmd) => {
25503
- cmd.option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
25507
+ cmd.option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
25504
25508
  },
25505
25509
  run: async (cmd) => {
25506
25510
  const { os: optsOs, debug, daemon } = cmd.opts();
@@ -25825,7 +25829,7 @@ function register17(program3) {
25825
25829
  description: "Send a plain-text chat message via the TCP chat channel. VISIBLE TO THE RECIPIENT \u2014 non-self targets require --yes-not-self.",
25826
25830
  output: Output17,
25827
25831
  configure: (cmd) => {
25828
- cmd.argument("<text...>", "message text (variadic; words rejoined with spaces)").option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "send to your own uid (default if no target given)").option("--yes-not-self", "required to confirm sending to anyone other than yourself").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--tail <text>", "override message tail for this call only").option("--no-tail", "do not append a message tail for this call").option(
25832
+ cmd.argument("<text...>", "message text (variadic; words rejoined with spaces)").option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "send to your own uid (default if no target given)").option("--yes-not-self", "required to confirm sending to anyone other than yourself").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--tail <text>", "override message tail for this call only").option("--no-tail", "do not append a message tail for this call").option(
25829
25833
  "--at <uid[:name][,uid[:name]...]>",
25830
25834
  '@-mention one or more uids in a group (use "uid:name" to control the rendered display name; group target only)'
25831
25835
  ).option("--debug", "forward wire-level debug events to stderr");
@@ -26689,7 +26693,7 @@ function register18(program3) {
26689
26693
  description: "Upload a local file (TCP 6101) and broadcast as MESSAGE_CHAT_FILE (subType=7). VISIBLE \u2014 non-self targets require --yes-not-self.",
26690
26694
  output: Output18,
26691
26695
  configure: (cmd) => {
26692
- cmd.argument("<file>", "absolute or relative path to a local file").option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "send to your own uid (default if no target given)").option("--yes-not-self", "required to confirm sending to anyone other than yourself").option("--dry-run", "resolve target + sanity-check the local file but do not upload or send").option("--os <os>", "chat-channel os slot for the talk/sendMsg (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
26696
+ cmd.argument("<file>", "absolute or relative path to a local file").option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "send to your own uid (default if no target given)").option("--yes-not-self", "required to confirm sending to anyone other than yourself").option("--dry-run", "resolve target + sanity-check the local file but do not upload or send").option("--os <os>", "chat-channel slot for talk/sendMsg (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
26693
26697
  },
26694
26698
  run: async (cmd) => {
26695
26699
  const filePath = path2.resolve(String(cmd.args[0] ?? ""));
@@ -26887,7 +26891,7 @@ function register19(program3) {
26887
26891
  description: "Upload an image (TCP 6101) and broadcast as MESSAGE_CHAT_IMAGE (subType=8). VISIBLE \u2014 non-self targets require --yes-not-self.",
26888
26892
  output: Output19,
26889
26893
  configure: (cmd) => {
26890
- cmd.argument("<file>", "PNG/JPEG/GIF/BMP image path").option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "send to your own uid (default)").option("--yes-not-self", "required for non-self targets").option("--width <px>", "override probed image width", parsePosInt("--width")).option("--height <px>", "override probed image height", parsePosInt("--height")).option("--dry-run", "sanity-check the local file (probe + stat) but do not upload or send").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
26894
+ cmd.argument("<file>", "PNG/JPEG/GIF/BMP image path").option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "send to your own uid (default)").option("--yes-not-self", "required for non-self targets").option("--width <px>", "override probed image width", parsePosInt("--width")).option("--height <px>", "override probed image height", parsePosInt("--height")).option("--dry-run", "sanity-check the local file (probe + stat) but do not upload or send").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
26891
26895
  },
26892
26896
  run: async (cmd) => {
26893
26897
  const filePath = path3.resolve(String(cmd.args[0] ?? ""));
@@ -27065,7 +27069,7 @@ function register20(program3) {
27065
27069
  description: "Upload an audio clip (TCP 6101) and broadcast as MESSAGE_CHAT_AUDIO (subType=9). VISIBLE \u2014 non-self targets require --yes-not-self.",
27066
27070
  output: Output20,
27067
27071
  configure: (cmd) => {
27068
- cmd.argument("<file>", "audio file path (typically .amr)").requiredOption("--duration <ms>", "clip duration in milliseconds", parsePosInt("--duration")).option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "send to your own uid (default)").option("--yes-not-self", "required for non-self targets").option("--dry-run", "sanity-check the local file but do not upload or send").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27072
+ cmd.argument("<file>", "audio file path (typically .amr)").requiredOption("--duration <ms>", "clip duration in milliseconds", parsePosInt("--duration")).option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "send to your own uid (default)").option("--yes-not-self", "required for non-self targets").option("--dry-run", "sanity-check the local file but do not upload or send").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27069
27073
  },
27070
27074
  run: async (cmd) => {
27071
27075
  const filePath = path4.resolve(String(cmd.args[0] ?? ""));
@@ -27326,7 +27330,7 @@ function register22(program3) {
27326
27330
  description: "Withdraw a previously-sent message (P2P_MSG_CANCEL). VISIBLE TO THE RECIPIENT \u2014 non-self targets require --yes-not-self.",
27327
27331
  output: Output22,
27328
27332
  configure: (cmd) => {
27329
- cmd.requiredOption("--mid <mid>", "message id to withdraw").option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "withdraw in your self-chat (default if no target)").option("--yes-not-self", "required to confirm withdrawing in a non-self conversation").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27333
+ cmd.requiredOption("--mid <mid>", "message id to withdraw").option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "withdraw in your self-chat (default if no target)").option("--yes-not-self", "required to confirm withdrawing in a non-self conversation").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27330
27334
  },
27331
27335
  run: async (cmd) => {
27332
27336
  const opts = cmd.opts();
@@ -27440,7 +27444,7 @@ function register23(program3) {
27440
27444
  description: "Send user/delSession to drop the local session record on all of the user's devices.",
27441
27445
  output: Output23,
27442
27446
  configure: (cmd) => {
27443
- cmd.option("--peer <uid>", "session with a peer uid").option("--team <id>", "session with a team id").option("--to-self", "self-chat session (default if no target)").option("--yes-not-self", "required to confirm clearing a non-self session").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27447
+ cmd.option("--peer <uid>", "session with a peer uid").option("--team <id>", "session with a team id").option("--to-self", "self-chat session (default if no target)").option("--yes-not-self", "required to confirm clearing a non-self session").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and use a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27444
27448
  },
27445
27449
  run: async (cmd) => {
27446
27450
  const opts = cmd.opts();
@@ -27642,7 +27646,7 @@ function registerPin(program3) {
27642
27646
  description: "Pin a chat session to the top of the conversation list (TCP user/setCacheField sticky_on_top_id, op=1). Multi-device via user cache. Will kick a session in the same online-slot.",
27643
27647
  output: MutateOutput,
27644
27648
  configure: (cmd) => {
27645
- cmd.option("--peer <uid>", "1:1 session with this uid").option("--team <id>", "group session (team id)").option("--to-self", "self-chat session (default if no target)").option("--sid <sId>", "raw session id (teamId or UID|UID); overrides --peer/--team/--to-self").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27649
+ cmd.option("--peer <uid>", "1:1 session with this uid").option("--team <id>", "group session (team id)").option("--to-self", "self-chat session (default if no target)").option("--sid <sId>", "raw session id (teamId or UID|UID); overrides --peer/--team/--to-self").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27646
27650
  },
27647
27651
  run: async (cmd) => mutateSticky(cmd, "pin")
27648
27652
  });
@@ -27653,7 +27657,7 @@ function registerUnpin(program3) {
27653
27657
  description: "Remove a session from the pin list (TCP user/setCacheField sticky_on_top_id, op=2). Will kick a session in the same online-slot.",
27654
27658
  output: MutateOutput,
27655
27659
  configure: (cmd) => {
27656
- cmd.option("--peer <uid>", "1:1 session with this uid").option("--team <id>", "group session (team id)").option("--to-self", "self-chat session (default if no target)").option("--sid <sId>", "raw session id (teamId or UID|UID); overrides --peer/--team/--to-self").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27660
+ cmd.option("--peer <uid>", "1:1 session with this uid").option("--team <id>", "group session (team id)").option("--to-self", "self-chat session (default if no target)").option("--sid <sId>", "raw session id (teamId or UID|UID); overrides --peer/--team/--to-self").option("--dry-run", "build the frame but do not connect").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27657
27661
  },
27658
27662
  run: async (cmd) => mutateSticky(cmd, "unpin")
27659
27663
  });
@@ -27711,7 +27715,7 @@ function registerPinned(program3) {
27711
27715
  description: "List pinned session sIds from user cache (TCP user/setCacheField no-op delete probe \u2192 sticky_on_top_id). Will kick a session in the same online-slot.",
27712
27716
  output: ListOutput,
27713
27717
  configure: (cmd) => {
27714
- cmd.option("--dry-run", "build the probe frame but do not connect").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27718
+ cmd.option("--dry-run", "build the probe frame but do not connect").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
27715
27719
  },
27716
27720
  run: async (cmd) => {
27717
27721
  const opts = cmd.opts();
@@ -28036,10 +28040,10 @@ var Output27 = external_exports.object({
28036
28040
  function register28(program3) {
28037
28041
  define2(program3, {
28038
28042
  name: "wait-react",
28039
- description: "Block until someone adds a default emoji reaction (subType=300) to --mid. Consumes a TCP slot \u2014 defaults to --os iOS (Mobile slot).",
28043
+ description: "Block until someone adds a default emoji reaction (subType=300) to --mid. Consumes a TCP slot \u2014 defaults to the iPad slot.",
28040
28044
  output: Output27,
28041
28045
  configure: (cmd) => {
28042
- cmd.requiredOption("--mid <mid>", "message id to wait for a reaction on").option("--from-uid <uid>", "only consider reactions from this UID (case-insensitive match against fId of the type=3 notification)").option("--emoji <name>", "only consider this default reaction emoji name, e.g. [\u70B9\u8D5E] or \u70B9\u8D5E").option("--sticker-id <name>", "deprecated alias for --emoji").option("--timeout <seconds>", "give up after this many seconds (default 300)", parsePosInt("--timeout"), 300).option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and wait on a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
28046
+ cmd.requiredOption("--mid <mid>", "message id to wait for a reaction on").option("--from-uid <uid>", "only consider reactions from this UID (case-insensitive match against fId of the type=3 notification)").option("--emoji <name>", "only consider this default reaction emoji name, e.g. [\u70B9\u8D5E] or \u70B9\u8D5E").option("--sticker-id <name>", "deprecated alias for --emoji").option("--timeout <seconds>", "give up after this many seconds (default 300)", parsePosInt("--timeout"), 300).option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and wait on a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
28043
28047
  },
28044
28048
  run: async (cmd) => {
28045
28049
  const opts = cmd.opts();
@@ -28252,7 +28256,7 @@ function register29(program3) {
28252
28256
  description: "Tail incoming chat messages, filtered by sender / team / peer / keyword / regex / @-mention. Emit one line per match \u2014 NDJSON envelope under `--json`, human summary otherwise. Consumes a TCP slot.",
28253
28257
  streamSchema: STREAM_SCHEMA,
28254
28258
  configure: (cmd) => {
28255
- cmd.option("--from-uid <uid>", "only emit messages from this UID (repeat for OR; matched against fId, case-insensitive)", collect, []).option("--team <id>", "only emit messages in this team (repeat for OR)", collect, []).option("--peer <uid>", "only emit p2p messages with this peer UID (repeat for OR)", collect, []).option("--contains <text>", "only emit messages whose text contains this substring, case-insensitive (repeat for OR)", collect, []).option("--regex <pattern>", "only emit messages whose text matches this RE2-flavoured regex (repeat for OR)", collect, []).option("--type <n>", "only emit messages of this `type` field (default 1; repeat for OR)", collectInt("--type"), []).option("--subtype <n>", "only emit messages of this `subType` field (repeat for OR; default = any)", collectInt("--subtype"), []).option("--max <n>", "exit after emitting this many matching lines", parsePosInt("--max")).option("--at-me", "only emit group messages that @-mention you (mirrors the desktop SDK isAtMe: scene=team_g, fId\u2260self, atIds includes self uid). Implies excluding self.").option("--include-self", "do not filter out messages whose fId is your own UID").option("--include-reactions", "also emit type=3 sub=300/301 (reaction add/remove) frames").option("--include-offline", "also emit messages flushed via cid=offMsg on connect (default: only post-connect msg)").option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and listen on a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
28259
+ cmd.option("--from-uid <uid>", "only emit messages from this UID (repeat for OR; matched against fId, case-insensitive)", collect, []).option("--team <id>", "only emit messages in this team (repeat for OR)", collect, []).option("--peer <uid>", "only emit p2p messages with this peer UID (repeat for OR)", collect, []).option("--contains <text>", "only emit messages whose text contains this substring, case-insensitive (repeat for OR)", collect, []).option("--regex <pattern>", "only emit messages whose text matches this RE2-flavoured regex (repeat for OR)", collect, []).option("--type <n>", "only emit messages of this `type` field (default 1; repeat for OR)", collectInt("--type"), []).option("--subtype <n>", "only emit messages of this `subType` field (repeat for OR; default = any)", collectInt("--subtype"), []).option("--max <n>", "exit after emitting this many matching lines", parsePosInt("--max")).option("--at-me", "only emit group messages that @-mention you (mirrors the desktop SDK isAtMe: scene=team_g, fId\u2260self, atIds includes self uid). Implies excluding self.").option("--include-self", "do not filter out messages whose fId is your own UID").option("--include-reactions", "also emit type=3 sub=300/301 (reaction add/remove) frames").option("--include-offline", "also emit messages flushed via cid=offMsg on connect (default: only post-connect msg)").option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--no-daemon", "bypass any running personal daemon and listen on a direct connection on your own slot (also: MUC_NO_DAEMON=1)").option("--debug", "forward wire-level debug events to stderr");
28256
28260
  },
28257
28261
  run: async (cmd) => {
28258
28262
  const opts = cmd.opts();
@@ -28642,7 +28646,7 @@ var ChatBroker = class {
28642
28646
  );
28643
28647
  }
28644
28648
  this.cred = cred;
28645
- this.os = this.opts.os ?? this.cfg.defaultChatOs ?? "iOS";
28649
+ this.os = this.opts.os ?? this.cfg.defaultChatOs ?? DEFAULT_CHAT_OS;
28646
28650
  this.startedAt = Date.now();
28647
28651
  ensureRuntimeDir(this.paths);
28648
28652
  writePid(this.paths, process.pid);
@@ -29220,7 +29224,7 @@ function register30(program3) {
29220
29224
  description: "Start the personal-account daemon in the foreground. Holds the chat socket for ordinary commands and writes raw daemon events.",
29221
29225
  output: StartOutput,
29222
29226
  configure: (cmd) => {
29223
- cmd.option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--on-kicked <policy>", "when another device takes the slot: pause, immediate, or a delay such as 30s/5m/2h/1d (default: 3m)", parseKickedRecoveryPolicy).option("--debug", "forward wire-level debug events to stderr");
29227
+ cmd.option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--on-kicked <policy>", "when another device takes the slot: pause, immediate, or a delay such as 30s/5m/2h/1d (default: 3m)", parseKickedRecoveryPolicy).option("--debug", "forward wire-level debug events to stderr");
29224
29228
  },
29225
29229
  run: async (cmd) => {
29226
29230
  const opts = cmd.opts();
@@ -33500,7 +33504,7 @@ return the JSON envelope and let the daemon send.
33500
33504
  Every TCP verb above rides the assistant daemon's existing socket \u2014 no
33501
33505
  slot taken, no device kicked. This exception applies only inside
33502
33506
  assistant backend turns. Ordinary Codex/MUC skills use personal daemon
33503
- routing (\`muc daemon-start\`) and must follow their own warn-once rules.
33507
+ routing (\`muc daemon-start\`) and must follow the non-blocking iPad notice rules.
33504
33508
 
33505
33509
  ## Attachments and quotes
33506
33510
 
@@ -33957,7 +33961,7 @@ var AssistantDaemon = class {
33957
33961
  if (!usingAssistantProfile) {
33958
33962
  await assertNoPersonalDaemonConflict(this.cred.uid);
33959
33963
  }
33960
- this.os = this.opts.os ?? this.cfg.defaultChatOs ?? "iOS";
33964
+ this.os = this.opts.os ?? this.cfg.defaultChatOs ?? DEFAULT_CHAT_OS;
33961
33965
  ensureRuntimeDir2();
33962
33966
  writePid2(process.pid);
33963
33967
  this.startedAt = Date.now();
@@ -36040,7 +36044,7 @@ function register32(program3) {
36040
36044
  description: "Start the assistant daemon in the foreground. Holds the chat socket on the chosen --os slot. v1 has no launchd integration; rerun under tmux/screen if you want it persistent.",
36041
36045
  output: StartOutput2,
36042
36046
  configure: (cmd) => {
36043
- cmd.option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--on-kicked <policy>", "when another device takes the slot: pause, immediate, or a delay such as 30s/5m/2h/1d (default: 3m)", parseKickedRecoveryPolicy).option("--debug", "forward wire-level debug events to stderr");
36047
+ cmd.option("--os <os>", "chat-channel slot (default: cfg.defaultChatOs ?? iPad)", parseChatOs).option("--on-kicked <policy>", "when another device takes the slot: pause, immediate, or a delay such as 30s/5m/2h/1d (default: 3m)", parseKickedRecoveryPolicy).option("--debug", "forward wire-level debug events to stderr");
36044
36048
  },
36045
36049
  run: async (cmd) => {
36046
36050
  const opts = cmd.opts();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mu-cabin/muc-cli",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Terminal client for MUC IM (com.meicloud.im.dh).",
5
5
  "license": "MIT",
6
6
  "type": "module",