@mu-cabin/muc-cli 0.2.0 → 0.2.2

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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/dist/muc.mjs +207 -57
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -222,6 +222,19 @@ before using any chat-channel command if you have a real device logged in.**
222
222
  Sending to anyone other than yourself requires the explicit `--yes-not-self`
223
223
  flag — a guard against accidentally messaging real humans during testing.
224
224
 
225
+ **Message tail (signature)**: by default `muc send` appends a short tail to
226
+ the text it sends (e.g. `"\n—— 来自王璐璐的muc-cli"`) so recipients can
227
+ tell automated traffic from a typed message. The check is idempotent —
228
+ if your text already ends with the resolved tail, it is not appended a
229
+ second time. Customise it with the
230
+ `messageTail` field in `~/.muc/config.json` — set to a non-empty string to
231
+ override, or to `""` to disable entirely. You can also use
232
+ `muc init --message-tail "<custom>"` / `muc init --no-message-tail` to
233
+ write the value, and per-call flags `muc send --tail "<one-off>"` /
234
+ `muc send --no-tail` to override on a single send. Only `muc send`
235
+ (plain-text) is affected; `send-file` / `send-image` / `send-voice` are
236
+ not.
237
+
225
238
  ```bash
226
239
  muc tcp-ping --wait 8 # safe protocol smoke test
227
240
  muc send "hello from the CLI" # to-self (default)
package/dist/muc.mjs CHANGED
@@ -21301,6 +21301,12 @@ var MucConfigSchema = external_exports.object({
21301
21301
  imPort: external_exports.number().int().min(1).max(65535),
21302
21302
  fileHost: external_exports.string().min(1, "fileHost required"),
21303
21303
  filePort: external_exports.number().int().min(1).max(65535),
21304
+ // Optional signature appended to outbound `muc send` text. Tri-state:
21305
+ // undefined → use built-in default (defaultMessageTail in constants.ts)
21306
+ // "" → explicit opt-out (no tail appended)
21307
+ // any other → that literal string (e.g. "\n— bot")
21308
+ // Per-call `--tail` / `--no-tail` flags on `muc send` override this.
21309
+ messageTail: external_exports.string().max(200).optional(),
21304
21310
  savedAt: external_exports.number().int().nonnegative(),
21305
21311
  schemaVersion: external_exports.literal(CONFIG_SCHEMA_VERSION)
21306
21312
  });
@@ -21634,11 +21640,59 @@ function read() {
21634
21640
  var CLI_VERSION = read();
21635
21641
 
21636
21642
  // src/commands/login.ts
21637
- import { release } from "node:os";
21643
+ import { release as release2 } from "node:os";
21638
21644
  import * as readline from "node:readline";
21639
21645
 
21640
21646
  // src/lib/http.ts
21641
21647
  var import_needle = __toESM(require_needle(), 1);
21648
+ import { release } from "node:os";
21649
+
21650
+ // src/lib/sign.ts
21651
+ var import_crypto_js = __toESM(require_crypto_js(), 1);
21652
+ import { createHash } from "node:crypto";
21653
+ function getMucSign(qs, secret) {
21654
+ const keys = Object.keys(qs).sort();
21655
+ const body = keys.map((k) => `${k}${qs[k] ?? ""}`).join("");
21656
+ return createHash("md5").update(`${secret}${body}${secret}`).digest("hex");
21657
+ }
21658
+ function signRest(qs, secret) {
21659
+ return getMucSign(qs, secret);
21660
+ }
21661
+ function signSecure(qs, secret) {
21662
+ return getMucSign(qs, secret);
21663
+ }
21664
+ function encryptPassword(plain, desKey) {
21665
+ const key = import_crypto_js.default.enc.Utf8.parse(desKey);
21666
+ const iv = import_crypto_js.default.enc.Hex.parse("0102030405060708");
21667
+ const enc = import_crypto_js.default.DES.encrypt(plain, key, {
21668
+ iv,
21669
+ mode: import_crypto_js.default.mode.CBC,
21670
+ padding: import_crypto_js.default.pad.Pkcs7
21671
+ });
21672
+ return enc.toString();
21673
+ }
21674
+
21675
+ // src/lib/device.ts
21676
+ import { homedir as homedir3, hostname } from "node:os";
21677
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
21678
+ import { join as join3 } from "node:path";
21679
+ import { randomBytes, createHash as createHash2 } from "node:crypto";
21680
+ var DIR3 = join3(homedir3(), ".muc");
21681
+ var FILE3 = join3(DIR3, "device-id");
21682
+ function getDeviceId() {
21683
+ if (!existsSync3(DIR3)) mkdirSync3(DIR3, { recursive: true, mode: 448 });
21684
+ if (existsSync3(FILE3)) {
21685
+ const v = readFileSync4(FILE3, "utf8").trim();
21686
+ if (v) return v;
21687
+ }
21688
+ const seed = randomBytes(32).toString("hex") + ":" + Date.now();
21689
+ const id = createHash2("sha256").update(seed).digest("hex");
21690
+ writeFileSync3(FILE3, id, { mode: 384 });
21691
+ return id;
21692
+ }
21693
+ function getDeviceName() {
21694
+ return `${hostname()} (muc-cli)`;
21695
+ }
21642
21696
 
21643
21697
  // src/lib/constants.ts
21644
21698
  var APP_NAME = "MUC";
@@ -21652,6 +21706,11 @@ var SECURE_SYS_KEY = "mx_app_key";
21652
21706
  var PWD_DES_IV = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
21653
21707
  var DEFAULT_CHAT_OS = "iOS";
21654
21708
  var DEFAULT_REST_PLATFORM = PLATFORM_IOS;
21709
+ function defaultMessageTail(userName) {
21710
+ const name = userName.trim() || "unknown";
21711
+ return `
21712
+ \u2014\u2014 \u6765\u81EA${name}\u7684muc-cli`;
21713
+ }
21655
21714
  var DEFAULTS = {
21656
21715
  // Hosts/ports for the 东航 deployment. Other tenants override at init.
21657
21716
  restHost: "https://muc.ceair.com/",
@@ -21690,7 +21749,75 @@ function redact(url) {
21690
21749
  return url;
21691
21750
  }
21692
21751
  }
21752
+ var inflightRefresh = null;
21753
+ async function refreshAccessTokenWithTokenPwd() {
21754
+ if (inflightRefresh) return inflightRefresh;
21755
+ inflightRefresh = (async () => {
21756
+ const cred = loadCredentials();
21757
+ if (!cred?.tokenPwd) {
21758
+ throw new CliError(
21759
+ "AUTH_REQUIRED",
21760
+ "no saved tokenPwd; cannot refresh accessToken",
21761
+ "run `muc login --account <UID>` interactively in your own terminal"
21762
+ );
21763
+ }
21764
+ const cfg = requireConfig();
21765
+ const platform2 = DEFAULT_REST_PLATFORM;
21766
+ const appVersion = platform2 === PLATFORM_IOS ? APP_VERSION_IOS : APP_VERSION;
21767
+ const form = {
21768
+ account: cred.uid,
21769
+ password: encryptPassword(cred.tokenPwd, pwdDesKeyOf(cfg)),
21770
+ appName: APP_NAME,
21771
+ appKey: cfg.appKey,
21772
+ appVersion,
21773
+ platform: platform2,
21774
+ osVersion: release(),
21775
+ deviceId: cred.deviceId || getDeviceId(),
21776
+ deviceName: cred.deviceName || getDeviceName(),
21777
+ passwordType: 1,
21778
+ createTokenPwd: false,
21779
+ encrypt: true
21780
+ };
21781
+ form.sign = signRest(form, cfg.signSecretRest);
21782
+ const res = await requestRaw({
21783
+ method: "POST",
21784
+ url: `${apiUrls(cfg).MUC}/emp/login`,
21785
+ form
21786
+ });
21787
+ if (res.code !== 0 || !res.data?.accessToken) {
21788
+ throw new CliError(
21789
+ "AUTH_REQUIRED",
21790
+ `tokenPwd refresh rejected: code=${res.code} msg=${res.msg ?? ""}`.trim(),
21791
+ "run `muc login --account <UID>` interactively to mint fresh credentials"
21792
+ );
21793
+ }
21794
+ const next = {
21795
+ ...cred,
21796
+ accessToken: res.data.accessToken,
21797
+ accessTokenExpireAt: res.data.accessTokenExpireTime ?? res.data.accessTokenExpireAt ?? cred.accessTokenExpireAt,
21798
+ savedAt: Date.now()
21799
+ };
21800
+ saveCredentials(next);
21801
+ return next;
21802
+ })();
21803
+ try {
21804
+ return await inflightRefresh;
21805
+ } finally {
21806
+ inflightRefresh = null;
21807
+ }
21808
+ }
21693
21809
  async function request(opts) {
21810
+ try {
21811
+ return await requestRaw(opts);
21812
+ } catch (e) {
21813
+ if (e instanceof CliError && e.code === "AUTH_EXPIRED" && opts.auth === true && !opts._isRefreshRetry && loadCredentials()?.tokenPwd) {
21814
+ await refreshAccessTokenWithTokenPwd();
21815
+ return await requestRaw({ ...opts, _isRefreshRetry: true });
21816
+ }
21817
+ throw e;
21818
+ }
21819
+ }
21820
+ async function requestRaw(opts) {
21694
21821
  const method = opts.method ?? "GET";
21695
21822
  const url = appendQs(opts.url, opts.qs);
21696
21823
  const headers = {
@@ -21761,53 +21888,6 @@ function appendQs(url, qs) {
21761
21888
  return u.toString();
21762
21889
  }
21763
21890
 
21764
- // src/lib/sign.ts
21765
- var import_crypto_js = __toESM(require_crypto_js(), 1);
21766
- import { createHash } from "node:crypto";
21767
- function getMucSign(qs, secret) {
21768
- const keys = Object.keys(qs).sort();
21769
- const body = keys.map((k) => `${k}${qs[k] ?? ""}`).join("");
21770
- return createHash("md5").update(`${secret}${body}${secret}`).digest("hex");
21771
- }
21772
- function signRest(qs, secret) {
21773
- return getMucSign(qs, secret);
21774
- }
21775
- function signSecure(qs, secret) {
21776
- return getMucSign(qs, secret);
21777
- }
21778
- function encryptPassword(plain, desKey) {
21779
- const key = import_crypto_js.default.enc.Utf8.parse(desKey);
21780
- const iv = import_crypto_js.default.enc.Hex.parse("0102030405060708");
21781
- const enc = import_crypto_js.default.DES.encrypt(plain, key, {
21782
- iv,
21783
- mode: import_crypto_js.default.mode.CBC,
21784
- padding: import_crypto_js.default.pad.Pkcs7
21785
- });
21786
- return enc.toString();
21787
- }
21788
-
21789
- // src/lib/device.ts
21790
- import { homedir as homedir3, hostname } from "node:os";
21791
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
21792
- import { join as join3 } from "node:path";
21793
- import { randomBytes, createHash as createHash2 } from "node:crypto";
21794
- var DIR3 = join3(homedir3(), ".muc");
21795
- var FILE3 = join3(DIR3, "device-id");
21796
- function getDeviceId() {
21797
- if (!existsSync3(DIR3)) mkdirSync3(DIR3, { recursive: true, mode: 448 });
21798
- if (existsSync3(FILE3)) {
21799
- const v = readFileSync4(FILE3, "utf8").trim();
21800
- if (v) return v;
21801
- }
21802
- const seed = randomBytes(32).toString("hex") + ":" + Date.now();
21803
- const id = createHash2("sha256").update(seed).digest("hex");
21804
- writeFileSync3(FILE3, id, { mode: 384 });
21805
- return id;
21806
- }
21807
- function getDeviceName() {
21808
- return `${hostname()} (muc-cli)`;
21809
- }
21810
-
21811
21891
  // src/commands/login.ts
21812
21892
  var PLATFORM_NAMES = {
21813
21893
  ios: PLATFORM_IOS,
@@ -21866,7 +21946,7 @@ var Output = external_exports.object({
21866
21946
  function register(program3) {
21867
21947
  define2(program3, {
21868
21948
  name: "login",
21869
- description: "Authenticate against MUC and persist accessToken + tokenPwd. Non-interactive: pass --account and one of --password / --password-stdin / --token-pwd / $MUC_PASSWORD. Interactive: omit those and a TTY prompt with echo suppressed will appear.",
21949
+ description: "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.",
21870
21950
  output: Output,
21871
21951
  configure: (cmd) => {
21872
21952
  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");
@@ -21913,7 +21993,7 @@ function register(program3) {
21913
21993
  throw new CliError(
21914
21994
  "AUTH_REQUIRED",
21915
21995
  "password required",
21916
- "pass --password / --password-stdin / --token-pwd, set $MUC_PASSWORD, or run interactively (TTY)"
21996
+ "open your own terminal and run `muc login --account <UID>` \u2014 it will prompt for the password with echo off. Do not pipe / env-var / inline the password from an agent shell; the prompt cannot be proxied through Claude Code's `!` shell-exec."
21917
21997
  );
21918
21998
  }
21919
21999
  const cfg = requireConfig();
@@ -21929,7 +22009,7 @@ function register(program3) {
21929
22009
  appKey: cfg.appKey,
21930
22010
  appVersion,
21931
22011
  platform: platform2,
21932
- osVersion: release(),
22012
+ osVersion: release2(),
21933
22013
  deviceId,
21934
22014
  deviceName,
21935
22015
  passwordType: isAutoLogin ? 1 : 0,
@@ -21979,6 +22059,46 @@ function register(program3) {
21979
22059
 
21980
22060
  // src/commands/init.ts
21981
22061
  import * as readline2 from "node:readline";
22062
+
22063
+ // src/lib/tail.ts
22064
+ function resolveTail(configured, userName, override) {
22065
+ if (override.disable) return "";
22066
+ if (override.custom != null) return override.custom;
22067
+ if (configured === "") return "";
22068
+ if (configured != null) return configured;
22069
+ return defaultMessageTail(userName);
22070
+ }
22071
+ function applyTail(body, tail) {
22072
+ if (!tail) return body;
22073
+ if (body.endsWith(tail)) return body;
22074
+ return body + tail;
22075
+ }
22076
+ function detectFlagConflict(argv2, name) {
22077
+ const valueLong = `--${name}`;
22078
+ const negatedLong = `--no-${name}`;
22079
+ const valueEq = `${valueLong}=`;
22080
+ let sawValueForm = false;
22081
+ let sawNegatedForm = false;
22082
+ for (let i = 0; i < argv2.length; i++) {
22083
+ const a = argv2[i];
22084
+ if (a === "--") break;
22085
+ if (a === valueLong) {
22086
+ sawValueForm = true;
22087
+ i++;
22088
+ continue;
22089
+ }
22090
+ if (a.startsWith(valueEq)) {
22091
+ sawValueForm = true;
22092
+ continue;
22093
+ }
22094
+ if (a === negatedLong) {
22095
+ sawNegatedForm = true;
22096
+ }
22097
+ }
22098
+ return { sawValueForm, sawNegatedForm };
22099
+ }
22100
+
22101
+ // src/commands/init.ts
21982
22102
  var FingerprintShape = external_exports.object({
21983
22103
  appKey: external_exports.string(),
21984
22104
  appId: external_exports.string(),
@@ -21991,6 +22111,8 @@ var FingerprintShape = external_exports.object({
21991
22111
  imPort: external_exports.number(),
21992
22112
  fileHost: external_exports.string(),
21993
22113
  filePort: external_exports.number(),
22114
+ messageTail: external_exports.string(),
22115
+ // summary: "default" / "none" / "custom (N chars)"
21994
22116
  savedAt: external_exports.number(),
21995
22117
  schemaVersion: external_exports.literal(CONFIG_SCHEMA_VERSION)
21996
22118
  });
@@ -22011,6 +22133,11 @@ function fingerprint2(v) {
22011
22133
  if (v.length <= 8) return "***";
22012
22134
  return `${v.slice(0, 4)}\u2026${v.slice(-4)}`;
22013
22135
  }
22136
+ function summarizeTail(t) {
22137
+ if (t === void 0) return "default";
22138
+ if (t === "") return "none";
22139
+ return `custom (${t.length} chars)`;
22140
+ }
22014
22141
  function redactConfig(c) {
22015
22142
  return {
22016
22143
  appKey: fingerprint2(c.appKey),
@@ -22024,6 +22151,7 @@ function redactConfig(c) {
22024
22151
  imPort: c.imPort,
22025
22152
  fileHost: c.fileHost,
22026
22153
  filePort: c.filePort,
22154
+ messageTail: summarizeTail(c.messageTail),
22027
22155
  savedAt: c.savedAt,
22028
22156
  schemaVersion: c.schemaVersion
22029
22157
  };
@@ -22169,7 +22297,7 @@ function register2(program3) {
22169
22297
  description: "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.",
22170
22298
  output: Output2,
22171
22299
  configure: (cmd) => {
22172
- 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");
22300
+ 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)");
22173
22301
  },
22174
22302
  run: async (cmd) => {
22175
22303
  const opts = cmd.opts();
@@ -22267,6 +22395,18 @@ function register2(program3) {
22267
22395
  partial[f.key] = parsed;
22268
22396
  }
22269
22397
  }
22398
+ const mtConflict = detectFlagConflict(process.argv.slice(2), "message-tail");
22399
+ if (mtConflict.sawValueForm && mtConflict.sawNegatedForm) {
22400
+ throw new CliError("USAGE", "--message-tail and --no-message-tail are mutually exclusive");
22401
+ }
22402
+ let messageTail;
22403
+ if (opts.messageTail === false) {
22404
+ messageTail = "";
22405
+ } else if (typeof opts.messageTail === "string") {
22406
+ messageTail = opts.messageTail;
22407
+ } else if (existing && "messageTail" in existing) {
22408
+ messageTail = existing.messageTail;
22409
+ }
22270
22410
  const draft = {
22271
22411
  appKey: String(partial.appKey ?? ""),
22272
22412
  appId: String(partial.appId ?? ""),
@@ -22279,6 +22419,7 @@ function register2(program3) {
22279
22419
  imPort: Number(partial.imPort ?? 0),
22280
22420
  fileHost: String(partial.fileHost ?? ""),
22281
22421
  filePort: Number(partial.filePort ?? 0),
22422
+ messageTail,
22282
22423
  savedAt: Date.now(),
22283
22424
  schemaVersion: CONFIG_SCHEMA_VERSION
22284
22425
  };
@@ -22614,7 +22755,7 @@ function register8(program3) {
22614
22755
  }
22615
22756
 
22616
22757
  // src/lib/chat.ts
22617
- import { release as release2 } from "node:os";
22758
+ import { release as release3 } from "node:os";
22618
22759
 
22619
22760
  // src/lib/wire.ts
22620
22761
  import { EventEmitter } from "node:events";
@@ -22856,7 +22997,7 @@ function buildLoginPayload(cred, chatOs, cfg) {
22856
22997
  account: cred.uid,
22857
22998
  accessToken: cred.accessToken,
22858
22999
  os: chatOs,
22859
- osVersion: release2(),
23000
+ osVersion: release3(),
22860
23001
  sdkVersion: 21,
22861
23002
  deviceId: cred.deviceId,
22862
23003
  deviceName: cred.deviceName,
@@ -22951,7 +23092,7 @@ async function refreshAccessTokenViaTokenPwd(cred, cfg) {
22951
23092
  appKey: cfg.appKey,
22952
23093
  appVersion: APP_VERSION,
22953
23094
  platform: PLATFORM_MAC,
22954
- osVersion: release2(),
23095
+ osVersion: release3(),
22955
23096
  deviceId: cred.deviceId,
22956
23097
  deviceName: cred.deviceName,
22957
23098
  passwordType: 1,
@@ -23450,7 +23591,7 @@ function register15(program3) {
23450
23591
  description: "Send a plain-text chat message via the TCP chat channel. VISIBLE TO THE RECIPIENT \u2014 non-self targets require --yes-not-self.",
23451
23592
  output: Output15,
23452
23593
  configure: (cmd) => {
23453
- 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", parseChatOs, DEFAULT_CHAT_OS).option("--debug", "forward wire-level debug events to stderr");
23594
+ 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", parseChatOs, DEFAULT_CHAT_OS).option("--tail <text>", "override message tail for this call only").option("--no-tail", "do not append a message tail for this call").option("--debug", "forward wire-level debug events to stderr");
23454
23595
  },
23455
23596
  run: async (cmd) => {
23456
23597
  const text = cmd.args.join(" ").trim();
@@ -23459,6 +23600,15 @@ function register15(program3) {
23459
23600
  const cfg = requireConfig();
23460
23601
  const cred = requireAuth(loadCredentials());
23461
23602
  requireSingleTarget(opts, "send");
23603
+ const conflict = detectFlagConflict(process.argv.slice(2), "tail");
23604
+ if (conflict.sawValueForm && conflict.sawNegatedForm) {
23605
+ throw new CliError("USAGE", "--tail and --no-tail are mutually exclusive");
23606
+ }
23607
+ const override = {};
23608
+ if (opts.tail === false) override.disable = true;
23609
+ else if (typeof opts.tail === "string") override.custom = opts.tail;
23610
+ const tail = resolveTail(cfg.messageTail, cred.name ?? "", override);
23611
+ const finalText = applyTail(text, tail);
23462
23612
  let scene;
23463
23613
  let toId;
23464
23614
  if (opts.team) {
@@ -23485,7 +23635,7 @@ function register15(program3) {
23485
23635
  scene,
23486
23636
  type: 1,
23487
23637
  subType: 1,
23488
- body: text,
23638
+ body: finalText,
23489
23639
  fName: cred.name,
23490
23640
  platform: opts.os === "Mac" || opts.os === "OS X" ? "Mac" : "Windows",
23491
23641
  appKey: cfg.appKey
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mu-cabin/muc-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Terminal client for MUC IM (com.meicloud.im.dh).",
5
5
  "license": "MIT",
6
6
  "type": "module",