@mu-cabin/muc-cli 0.2.2 → 0.3.1

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 +53 -11
  2. package/dist/muc.mjs +845 -83
  3. package/package.json +1 -1
package/dist/muc.mjs CHANGED
@@ -21288,6 +21288,7 @@ import { join } from "node:path";
21288
21288
  var DIR = join(homedir(), ".muc");
21289
21289
  var FILE = join(DIR, "config.json");
21290
21290
  var CONFIG_SCHEMA_VERSION = 1;
21291
+ var CHAT_OS_VALUES = ["Windows", "OS X", "Mac", "iOS", "Android"];
21291
21292
  var MucConfigSchema = external_exports.object({
21292
21293
  appKey: external_exports.string().regex(/^[0-9a-f]{8}$/, "appKey must be 8 lowercase hex chars"),
21293
21294
  appId: external_exports.string().regex(/^[0-9a-f]{24}$/, "appId must be 24 lowercase hex chars"),
@@ -21307,6 +21308,10 @@ var MucConfigSchema = external_exports.object({
21307
21308
  // any other → that literal string (e.g. "\n— bot")
21308
21309
  // Per-call `--tail` / `--no-tail` flags on `muc send` override this.
21309
21310
  messageTail: external_exports.string().max(200).optional(),
21311
+ // Default chat-channel `os` slot for TCP commands when --os is not passed.
21312
+ // PC ({Windows, OS X, Mac}) vs Mobile ({iOS, Android}); same slot as the
21313
+ // running desktop will kick it. Built-in fallback is 'iOS' (Mobile).
21314
+ defaultChatOs: external_exports.enum(CHAT_OS_VALUES).optional(),
21310
21315
  savedAt: external_exports.number().int().nonnegative(),
21311
21316
  schemaVersion: external_exports.literal(CONFIG_SCHEMA_VERSION)
21312
21317
  });
@@ -21394,6 +21399,47 @@ function saveCredentials(c) {
21394
21399
  }
21395
21400
  var credentialsPath = FILE2;
21396
21401
 
21402
+ // src/lib/constants.ts
21403
+ var APP_NAME = "MUC";
21404
+ var APP_VERSION = "3.3.1-250925001";
21405
+ var APP_VERSION_IOS = "3.3.0301";
21406
+ var PLATFORM_IOS = 1;
21407
+ var PLATFORM_ANDROID = 2;
21408
+ var PLATFORM_WIN = 4;
21409
+ var PLATFORM_MAC = 5;
21410
+ var SECURE_SYS_KEY = "mx_app_key";
21411
+ var PWD_DES_IV = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
21412
+ var DEFAULT_CHAT_OS = "iOS";
21413
+ var DEFAULT_REST_PLATFORM = PLATFORM_IOS;
21414
+ function defaultMessageTail(userName) {
21415
+ const name = userName.trim() || "unknown";
21416
+ return `
21417
+ \u2014\u2014 \u6765\u81EA${name}\u7684muc-cli`;
21418
+ }
21419
+ var DEFAULTS = {
21420
+ // Hosts/ports for the 东航 deployment. Other tenants override at init.
21421
+ restHost: "https://muc.ceair.com/",
21422
+ ssoHost: "https://store.ceair.com/",
21423
+ imHost: "muc.ceair.com",
21424
+ imPort: 8101,
21425
+ fileHost: "muc.ceair.com",
21426
+ filePort: 6101
21427
+ };
21428
+ function apiUrls(cfg) {
21429
+ const host = cfg.restHost.endsWith("/") ? cfg.restHost : `${cfg.restHost}/`;
21430
+ return {
21431
+ SYS: `${host}sysconfig/v5/app`,
21432
+ MUC: `${host}muc/v5/app`,
21433
+ MAM: `${host}mam/v5/app`,
21434
+ MAS: `${host}mas/v5/app`,
21435
+ MPM: `${host}mpm/v5/app`,
21436
+ IMM: `${host}imm/v5/app`,
21437
+ MRM: `${host}mrm/v5/app`,
21438
+ CONTACT: `${host}contacts/v5/app`,
21439
+ SECURE: `${host}contacts/secure/v5/app`
21440
+ };
21441
+ }
21442
+
21397
21443
  // src/lib/cli.ts
21398
21444
  var SCHEMA_VERSION = 1;
21399
21445
  var EXIT_CODES = {
@@ -21403,6 +21449,8 @@ var EXIT_CODES = {
21403
21449
  NOT_FOUND: 4,
21404
21450
  REMOTE_ERROR: 5,
21405
21451
  NETWORK: 5,
21452
+ TIMEOUT: 5,
21453
+ KICKED: 5,
21406
21454
  CONFIG_MISSING: 6,
21407
21455
  CONFIG_INVALID: 7,
21408
21456
  INTERNAL: 1
@@ -21420,6 +21468,9 @@ var JSON_MODE = false;
21420
21468
  function setJsonMode(v) {
21421
21469
  JSON_MODE = v;
21422
21470
  }
21471
+ function isJsonMode() {
21472
+ return JSON_MODE;
21473
+ }
21423
21474
  function stableStringify(value) {
21424
21475
  return JSON.stringify(value, (_k, v) => {
21425
21476
  if (v && typeof v === "object" && !Array.isArray(v)) {
@@ -21568,6 +21619,11 @@ function parseChatOs(raw) {
21568
21619
  "expected windows|osx|mac|ios|android"
21569
21620
  );
21570
21621
  }
21622
+ function resolveChatOs(optsOs, cfg) {
21623
+ if (optsOs !== void 0) return optsOs;
21624
+ if (cfg.defaultChatOs !== void 0) return cfg.defaultChatOs;
21625
+ return DEFAULT_CHAT_OS;
21626
+ }
21571
21627
  function parseMoment(label) {
21572
21628
  return (raw) => {
21573
21629
  if (/^\d+$/.test(raw)) {
@@ -21591,6 +21647,24 @@ function requireSingleTarget(opts, label) {
21591
21647
  );
21592
21648
  }
21593
21649
  }
21650
+ function defineStream(program3, spec) {
21651
+ const cmd = program3.command(spec.name).description(spec.description);
21652
+ spec.configure?.(cmd);
21653
+ cmd.action(async () => {
21654
+ try {
21655
+ await spec.run(cmd);
21656
+ } catch (e) {
21657
+ if (e instanceof CliError) fail(e.code, e.message, e.hint);
21658
+ const cls = classifyError(e);
21659
+ fail(cls.code, cls.msg, cls.hint);
21660
+ }
21661
+ });
21662
+ REGISTRY.push({
21663
+ name: spec.name,
21664
+ description: spec.description,
21665
+ output_schema: spec.streamSchema ?? { type: "object", description: "NDJSON-stream command \u2014 one JSON object per stdout line" }
21666
+ });
21667
+ }
21594
21668
  function define2(program3, spec) {
21595
21669
  const cmd = program3.command(spec.name).description(spec.description);
21596
21670
  spec.configure?.(cmd);
@@ -21694,47 +21768,6 @@ function getDeviceName() {
21694
21768
  return `${hostname()} (muc-cli)`;
21695
21769
  }
21696
21770
 
21697
- // src/lib/constants.ts
21698
- var APP_NAME = "MUC";
21699
- var APP_VERSION = "3.3.1-250925001";
21700
- var APP_VERSION_IOS = "3.3.0301";
21701
- var PLATFORM_IOS = 1;
21702
- var PLATFORM_ANDROID = 2;
21703
- var PLATFORM_WIN = 4;
21704
- var PLATFORM_MAC = 5;
21705
- var SECURE_SYS_KEY = "mx_app_key";
21706
- var PWD_DES_IV = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
21707
- var DEFAULT_CHAT_OS = "iOS";
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
- }
21714
- var DEFAULTS = {
21715
- // Hosts/ports for the 东航 deployment. Other tenants override at init.
21716
- restHost: "https://muc.ceair.com/",
21717
- ssoHost: "https://store.ceair.com/",
21718
- imHost: "muc.ceair.com",
21719
- imPort: 8101,
21720
- fileHost: "muc.ceair.com",
21721
- filePort: 6101
21722
- };
21723
- function apiUrls(cfg) {
21724
- const host = cfg.restHost.endsWith("/") ? cfg.restHost : `${cfg.restHost}/`;
21725
- return {
21726
- SYS: `${host}sysconfig/v5/app`,
21727
- MUC: `${host}muc/v5/app`,
21728
- MAM: `${host}mam/v5/app`,
21729
- MAS: `${host}mas/v5/app`,
21730
- MPM: `${host}mpm/v5/app`,
21731
- IMM: `${host}imm/v5/app`,
21732
- MRM: `${host}mrm/v5/app`,
21733
- CONTACT: `${host}contacts/v5/app`,
21734
- SECURE: `${host}contacts/secure/v5/app`
21735
- };
21736
- }
21737
-
21738
21771
  // src/lib/http.ts
21739
21772
  var UA = `MUC/${APP_VERSION} (CLI; muc-cli/${CLI_VERSION})`;
21740
21773
  var SENSITIVE_QS = /* @__PURE__ */ new Set(["accessToken", "tokenPwd", "sign"]);
@@ -22113,6 +22146,8 @@ var FingerprintShape = external_exports.object({
22113
22146
  filePort: external_exports.number(),
22114
22147
  messageTail: external_exports.string(),
22115
22148
  // summary: "default" / "none" / "custom (N chars)"
22149
+ defaultChatOs: external_exports.string(),
22150
+ // summary: explicit value or "default (iOS)"
22116
22151
  savedAt: external_exports.number(),
22117
22152
  schemaVersion: external_exports.literal(CONFIG_SCHEMA_VERSION)
22118
22153
  });
@@ -22138,6 +22173,10 @@ function summarizeTail(t) {
22138
22173
  if (t === "") return "none";
22139
22174
  return `custom (${t.length} chars)`;
22140
22175
  }
22176
+ function summarizeChatOs(v) {
22177
+ if (v === void 0) return `default (${DEFAULT_CHAT_OS})`;
22178
+ return v;
22179
+ }
22141
22180
  function redactConfig(c) {
22142
22181
  return {
22143
22182
  appKey: fingerprint2(c.appKey),
@@ -22152,6 +22191,7 @@ function redactConfig(c) {
22152
22191
  fileHost: c.fileHost,
22153
22192
  filePort: c.filePort,
22154
22193
  messageTail: summarizeTail(c.messageTail),
22194
+ defaultChatOs: summarizeChatOs(c.defaultChatOs),
22155
22195
  savedAt: c.savedAt,
22156
22196
  schemaVersion: c.schemaVersion
22157
22197
  };
@@ -22297,7 +22337,7 @@ function register2(program3) {
22297
22337
  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.",
22298
22338
  output: Output2,
22299
22339
  configure: (cmd) => {
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)");
22340
+ 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");
22301
22341
  },
22302
22342
  run: async (cmd) => {
22303
22343
  const opts = cmd.opts();
@@ -22407,6 +22447,26 @@ function register2(program3) {
22407
22447
  } else if (existing && "messageTail" in existing) {
22408
22448
  messageTail = existing.messageTail;
22409
22449
  }
22450
+ let defaultChatOs;
22451
+ let dcoRaw;
22452
+ const dcoFlag = opts.defaultOs;
22453
+ const dcoEnv = process.env.MUC_DEFAULT_OS;
22454
+ if (typeof dcoFlag === "string" && dcoFlag.length > 0) {
22455
+ dcoRaw = dcoFlag;
22456
+ } else if (dcoEnv !== void 0 && dcoEnv.length > 0) {
22457
+ dcoRaw = dcoEnv;
22458
+ } else if (existing?.defaultChatOs !== void 0) {
22459
+ defaultChatOs = existing.defaultChatOs;
22460
+ } else if (interactive) {
22461
+ const answer = await readLine(
22462
+ "Default chat OS slot (windows|osx|mac|ios|android)",
22463
+ DEFAULT_CHAT_OS.toLowerCase()
22464
+ );
22465
+ if (answer.length > 0) dcoRaw = answer;
22466
+ }
22467
+ if (dcoRaw !== void 0) {
22468
+ defaultChatOs = parseChatOs(dcoRaw);
22469
+ }
22410
22470
  const draft = {
22411
22471
  appKey: String(partial.appKey ?? ""),
22412
22472
  appId: String(partial.appId ?? ""),
@@ -22420,6 +22480,7 @@ function register2(program3) {
22420
22480
  fileHost: String(partial.fileHost ?? ""),
22421
22481
  filePort: Number(partial.filePort ?? 0),
22422
22482
  messageTail,
22483
+ defaultChatOs,
22423
22484
  savedAt: Date.now(),
22424
22485
  schemaVersion: CONFIG_SCHEMA_VERSION
22425
22486
  };
@@ -22835,17 +22896,20 @@ var MucChatSocket = class extends EventEmitter {
22835
22896
  host;
22836
22897
  port;
22837
22898
  ackTimeoutMs;
22899
+ idleTimeoutMs;
22838
22900
  debug;
22839
22901
  sock = null;
22840
22902
  reader;
22841
22903
  sq = 2;
22842
22904
  pending = /* @__PURE__ */ new Map();
22843
22905
  closed = false;
22906
+ idleTimer = null;
22844
22907
  constructor(opts) {
22845
22908
  super();
22846
22909
  this.host = opts.host;
22847
22910
  this.port = opts.port;
22848
22911
  this.ackTimeoutMs = opts.ackTimeoutMs ?? 25e3;
22912
+ this.idleTimeoutMs = opts.idleTimeoutMs ?? 75e3;
22849
22913
  this.debug = !!opts.debug;
22850
22914
  this.reader = new FrameReader((f) => this.handleFrame(f));
22851
22915
  }
@@ -22855,12 +22919,20 @@ var MucChatSocket = class extends EventEmitter {
22855
22919
  const sock = createConnection({ host: this.host, port: this.port });
22856
22920
  this.sock = sock;
22857
22921
  sock.setKeepAlive(true, 2e4);
22922
+ const errorSink = (_e) => {
22923
+ };
22924
+ this.on("error", errorSink);
22858
22925
  const onErr = (e) => {
22859
22926
  cleanup();
22860
22927
  reject(e);
22861
22928
  };
22929
+ const onPreReadyClose = () => {
22930
+ cleanup();
22931
+ reject(new Error("chat socket closed before negotiate-ack"));
22932
+ };
22862
22933
  const cleanup = () => {
22863
22934
  sock.off("error", onErr);
22935
+ sock.off("close", onPreReadyClose);
22864
22936
  this.off("ready", onReady);
22865
22937
  };
22866
22938
  const onReady = () => {
@@ -22868,26 +22940,56 @@ var MucChatSocket = class extends EventEmitter {
22868
22940
  resolve2();
22869
22941
  };
22870
22942
  sock.once("error", onErr);
22943
+ sock.once("close", onPreReadyClose);
22871
22944
  this.once("ready", onReady);
22872
22945
  sock.on("connect", () => {
22873
22946
  if (this.debug) console.error("[wire] tcp connect");
22874
22947
  this.emit("connect");
22948
+ this.armIdle();
22875
22949
  this.sendNegotiate();
22876
22950
  });
22877
- sock.on("data", (buf) => this.reader.feed(buf));
22878
- sock.on("error", (e) => this.emit("error", e));
22879
- sock.on("close", () => {
22880
- if (this.closed) return;
22881
- this.closed = true;
22882
- for (const { reject: reject2, timer } of this.pending.values()) {
22883
- clearTimeout(timer);
22884
- reject2(new Error("socket closed"));
22885
- }
22886
- this.pending.clear();
22887
- this.emit("close");
22951
+ sock.on("data", (buf) => {
22952
+ this.armIdle();
22953
+ this.reader.feed(buf);
22888
22954
  });
22955
+ sock.on("error", (e) => this.emit("error", e));
22956
+ sock.on("close", () => this.teardown(new Error("socket closed")));
22889
22957
  });
22890
22958
  }
22959
+ /**
22960
+ * (Re)arm the inactivity watchdog. Called every time inbound bytes land
22961
+ * (data callback) or any active confirmation. If no traffic arrives for
22962
+ * `idleTimeoutMs`, we synthesize a teardown so commands don't hang on a
22963
+ * half-open socket the kernel hasn't surfaced yet.
22964
+ */
22965
+ armIdle() {
22966
+ if (this.closed) return;
22967
+ if (this.idleTimer) clearTimeout(this.idleTimer);
22968
+ this.idleTimer = setTimeout(() => {
22969
+ if (this.closed) return;
22970
+ this.emit("error", new Error(`chat socket idle for ${Math.round(this.idleTimeoutMs / 1e3)}s \u2014 tearing down`));
22971
+ try {
22972
+ this.sock?.destroy();
22973
+ } catch {
22974
+ }
22975
+ this.teardown(new Error("idle timeout"));
22976
+ }, this.idleTimeoutMs);
22977
+ }
22978
+ /** Single owner of close-side cleanup: clears idle timer, rejects all pending sends, emits 'close' once. */
22979
+ teardown(reason) {
22980
+ if (this.closed) return;
22981
+ this.closed = true;
22982
+ if (this.idleTimer) {
22983
+ clearTimeout(this.idleTimer);
22984
+ this.idleTimer = null;
22985
+ }
22986
+ for (const { reject, timer } of this.pending.values()) {
22987
+ clearTimeout(timer);
22988
+ reject(reason);
22989
+ }
22990
+ this.pending.clear();
22991
+ this.emit("close");
22992
+ }
22891
22993
  sendNegotiate() {
22892
22994
  const frame = buildFrame(T.NEGOTIATE, NEGOTIATE_BODY, NEGOTIATE_CRC);
22893
22995
  if (this.debug) console.error("[wire] \u2192 NEGOTIATE", frame.toString("hex"));
@@ -22926,11 +23028,26 @@ var MucChatSocket = class extends EventEmitter {
22926
23028
  if (!this.sock || this.closed) throw new Error("not connected");
22927
23029
  this.sock.write(buildFrame(type, payload, crcOverride));
22928
23030
  }
23031
+ /**
23032
+ * Send an unsolicited frame that *re-uses* a sq from a server-pushed frame
23033
+ * (rather than allocating a new one via nextSq). This is how the desktop
23034
+ * SDK ACKs inbound `talk/msg` (message-server/modules/message.js:961-984):
23035
+ * it echoes the original frame's sq back with `{sid, cid, data:{tids}}`.
23036
+ * Fire-and-forget — no pending registration, no response expected.
23037
+ */
23038
+ sendAck(sid, cid, sq, data = {}) {
23039
+ if (!this.sock || this.closed) throw new Error("not connected");
23040
+ const obj = { sid, cid, sq, data };
23041
+ const payload = aesEncrypt(JSON.stringify(obj));
23042
+ if (this.debug) console.error(`[wire] \u2192 ACK ${sid}/${cid} sq=${sq}`);
23043
+ this.sock.write(buildFrame(T.AES_JSON, payload));
23044
+ }
22929
23045
  close() {
22930
23046
  if (!this.sock || this.closed) return;
22931
- this.closed = true;
23047
+ const sock = this.sock;
23048
+ this.teardown(new Error("socket closed by client"));
22932
23049
  try {
22933
- this.sock.end();
23050
+ sock.end();
22934
23051
  } catch {
22935
23052
  }
22936
23053
  }
@@ -22940,10 +23057,10 @@ var MucChatSocket = class extends EventEmitter {
22940
23057
  try {
22941
23058
  bodyText = aesDecrypt(f.payload);
22942
23059
  } catch (e) {
22943
- if (this.listenerCount("error") > 0) {
22944
- this.emit("error", new Error(`aes decrypt failed (type=${f.type} len=${f.payload.length}): ${e.message}`));
22945
- } else {
22946
- console.error(`[wire] aes decrypt failed (type=${f.type} len=${f.payload.length}): ${e.message}`);
23060
+ const err = new Error(`aes decrypt failed (type=${f.type} len=${f.payload.length}): ${e.message}`);
23061
+ this.emit("error", err);
23062
+ if (this.listenerCount("error") <= 1) {
23063
+ console.error(`[wire] ${err.message}`);
22947
23064
  }
22948
23065
  return;
22949
23066
  }
@@ -23141,13 +23258,14 @@ function register9(program3) {
23141
23258
  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.',
23142
23259
  output: Output9,
23143
23260
  configure: (cmd) => {
23144
- cmd.argument("<keyword>", "team name fragment").option("--enrich", "follow up with team/get_team per id").option("--os <os>", "chat-channel os slot", parseChatOs, DEFAULT_CHAT_OS).option("--debug", "forward wire-level debug events to stderr");
23261
+ 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("--debug", "forward wire-level debug events to stderr");
23145
23262
  },
23146
23263
  run: async (cmd) => {
23147
23264
  const [kw] = cmd.args;
23148
- const { enrich, os, debug } = cmd.opts();
23265
+ const { enrich, os: optsOs, debug } = cmd.opts();
23149
23266
  const cfg = requireConfig();
23150
23267
  const cred = requireAuth(loadCredentials());
23268
+ const os = resolveChatOs(optsOs, cfg);
23151
23269
  const session = await openChatSession(cred, { os, debug });
23152
23270
  const { socket } = session;
23153
23271
  try {
@@ -23333,11 +23451,13 @@ function register13(program3) {
23333
23451
  description: "After chat-login, run sync/syncDone, sync/syncSession, sync/syncWithdraws and return the raw responses.",
23334
23452
  output: Output13,
23335
23453
  configure: (cmd) => {
23336
- cmd.option("--os <os>", "chat-channel os slot", parseChatOs, DEFAULT_CHAT_OS).option("--debug", "forward wire-level debug events to stderr");
23454
+ cmd.option("--os <os>", "chat-channel os slot (default: cfg.defaultChatOs ?? iOS)", parseChatOs).option("--debug", "forward wire-level debug events to stderr");
23337
23455
  },
23338
23456
  run: async (cmd) => {
23339
- const { os, debug } = cmd.opts();
23457
+ const { os: optsOs, debug } = cmd.opts();
23458
+ const cfg = requireConfig();
23340
23459
  const cred = requireAuth(loadCredentials());
23460
+ const os = resolveChatOs(optsOs, cfg);
23341
23461
  const session = await openChatSession(cred, { os, debug });
23342
23462
  const { socket } = session;
23343
23463
  try {
@@ -23591,7 +23711,7 @@ function register15(program3) {
23591
23711
  description: "Send a plain-text chat message via the TCP chat channel. VISIBLE TO THE RECIPIENT \u2014 non-self targets require --yes-not-self.",
23592
23712
  output: Output15,
23593
23713
  configure: (cmd) => {
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");
23714
+ 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("--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");
23595
23715
  },
23596
23716
  run: async (cmd) => {
23597
23717
  const text = cmd.args.join(" ").trim();
@@ -23599,6 +23719,7 @@ function register15(program3) {
23599
23719
  const opts = cmd.opts();
23600
23720
  const cfg = requireConfig();
23601
23721
  const cred = requireAuth(loadCredentials());
23722
+ const os = resolveChatOs(opts.os, cfg);
23602
23723
  requireSingleTarget(opts, "send");
23603
23724
  const conflict = detectFlagConflict(process.argv.slice(2), "tail");
23604
23725
  if (conflict.sawValueForm && conflict.sawNegatedForm) {
@@ -23637,7 +23758,7 @@ function register15(program3) {
23637
23758
  subType: 1,
23638
23759
  body: finalText,
23639
23760
  fName: cred.name,
23640
- platform: opts.os === "Mac" || opts.os === "OS X" ? "Mac" : "Windows",
23761
+ platform: os === "Mac" || os === "OS X" ? "Mac" : "Windows",
23641
23762
  appKey: cfg.appKey
23642
23763
  });
23643
23764
  if (opts.dryRun) {
@@ -23649,7 +23770,7 @@ function register15(program3) {
23649
23770
  frame: { sid: "talk", cid: "sendMsg", data: { msg } }
23650
23771
  };
23651
23772
  }
23652
- const session = await openChatSession(cred, { os: opts.os, debug: opts.debug });
23773
+ const session = await openChatSession(cred, { os, debug: opts.debug });
23653
23774
  const { socket } = session;
23654
23775
  let res;
23655
23776
  try {
@@ -24389,7 +24510,7 @@ function register16(program3) {
24389
24510
  description: "Upload a local file (TCP 6101) and broadcast as MESSAGE_CHAT_FILE (subType=7). VISIBLE \u2014 non-self targets require --yes-not-self.",
24390
24511
  output: Output16,
24391
24512
  configure: (cmd) => {
24392
- 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", parseChatOs, DEFAULT_CHAT_OS).option("--debug", "forward wire-level debug events to stderr");
24513
+ 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("--debug", "forward wire-level debug events to stderr");
24393
24514
  },
24394
24515
  run: async (cmd) => {
24395
24516
  const filePath = path2.resolve(String(cmd.args[0] ?? ""));
@@ -24397,6 +24518,7 @@ function register16(program3) {
24397
24518
  const opts = cmd.opts();
24398
24519
  const cfg = requireConfig();
24399
24520
  const cred = requireAuth(loadCredentials());
24521
+ const os = resolveChatOs(opts.os, cfg);
24400
24522
  requireSingleTarget(opts, "send-file");
24401
24523
  let scene;
24402
24524
  let toId;
@@ -24447,10 +24569,10 @@ function register16(program3) {
24447
24569
  expiredDay: up.expiredDay
24448
24570
  }),
24449
24571
  fName: cred.name,
24450
- platform: opts.os === "Mac" || opts.os === "OS X" ? "Mac" : "Windows",
24572
+ platform: os === "Mac" || os === "OS X" ? "Mac" : "Windows",
24451
24573
  appKey: cfg.appKey
24452
24574
  });
24453
- const session = await openChatSession(cred, { os: opts.os, debug: opts.debug });
24575
+ const session = await openChatSession(cred, { os, debug: opts.debug });
24454
24576
  let res;
24455
24577
  try {
24456
24578
  res = await session.socket.send("talk", "sendMsg", { msg });
@@ -24563,13 +24685,14 @@ function register17(program3) {
24563
24685
  description: "Upload an image (TCP 6101) and broadcast as MESSAGE_CHAT_IMAGE (subType=8). VISIBLE \u2014 non-self targets require --yes-not-self.",
24564
24686
  output: Output17,
24565
24687
  configure: (cmd) => {
24566
- 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", parseChatOs, DEFAULT_CHAT_OS).option("--debug", "forward wire-level debug events to stderr");
24688
+ 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("--debug", "forward wire-level debug events to stderr");
24567
24689
  },
24568
24690
  run: async (cmd) => {
24569
24691
  const filePath = path3.resolve(String(cmd.args[0] ?? ""));
24570
24692
  const opts = cmd.opts();
24571
24693
  const cfg = requireConfig();
24572
24694
  const cred = requireAuth(loadCredentials());
24695
+ const os = resolveChatOs(opts.os, cfg);
24573
24696
  requireSingleTarget(opts, "send-image");
24574
24697
  let scene;
24575
24698
  let toId;
@@ -24635,10 +24758,10 @@ function register17(program3) {
24635
24758
  height
24636
24759
  }),
24637
24760
  fName: cred.name,
24638
- platform: opts.os === "Mac" || opts.os === "OS X" ? "Mac" : "Windows",
24761
+ platform: os === "Mac" || os === "OS X" ? "Mac" : "Windows",
24639
24762
  appKey: cfg.appKey
24640
24763
  });
24641
- const session = await openChatSession(cred, { os: opts.os, debug: opts.debug });
24764
+ const session = await openChatSession(cred, { os, debug: opts.debug });
24642
24765
  let res;
24643
24766
  try {
24644
24767
  res = await session.socket.send("talk", "sendMsg", { msg });
@@ -24717,13 +24840,14 @@ function register18(program3) {
24717
24840
  description: "Upload an audio clip (TCP 6101) and broadcast as MESSAGE_CHAT_AUDIO (subType=9). VISIBLE \u2014 non-self targets require --yes-not-self.",
24718
24841
  output: Output18,
24719
24842
  configure: (cmd) => {
24720
- 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", parseChatOs, DEFAULT_CHAT_OS).option("--debug", "forward wire-level debug events to stderr");
24843
+ 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("--debug", "forward wire-level debug events to stderr");
24721
24844
  },
24722
24845
  run: async (cmd) => {
24723
24846
  const filePath = path4.resolve(String(cmd.args[0] ?? ""));
24724
24847
  const opts = cmd.opts();
24725
24848
  const cfg = requireConfig();
24726
24849
  const cred = requireAuth(loadCredentials());
24850
+ const os = resolveChatOs(opts.os, cfg);
24727
24851
  requireSingleTarget(opts, "send-voice");
24728
24852
  let scene;
24729
24853
  let toId;
@@ -24782,10 +24906,10 @@ function register18(program3) {
24782
24906
  duration: opts.duration
24783
24907
  }),
24784
24908
  fName: cred.name,
24785
- platform: opts.os === "Mac" || opts.os === "OS X" ? "Mac" : "Windows",
24909
+ platform: os === "Mac" || os === "OS X" ? "Mac" : "Windows",
24786
24910
  appKey: cfg.appKey
24787
24911
  });
24788
- const session = await openChatSession(cred, { os: opts.os, debug: opts.debug });
24912
+ const session = await openChatSession(cred, { os, debug: opts.debug });
24789
24913
  let res;
24790
24914
  try {
24791
24915
  res = await session.socket.send("talk", "sendMsg", { msg });
@@ -24954,12 +25078,13 @@ function register20(program3) {
24954
25078
  description: "Withdraw a previously-sent message (P2P_MSG_CANCEL). VISIBLE TO THE RECIPIENT \u2014 non-self targets require --yes-not-self.",
24955
25079
  output: Output20,
24956
25080
  configure: (cmd) => {
24957
- 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", parseChatOs, DEFAULT_CHAT_OS).option("--debug", "forward wire-level debug events to stderr");
25081
+ 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("--debug", "forward wire-level debug events to stderr");
24958
25082
  },
24959
25083
  run: async (cmd) => {
24960
25084
  const opts = cmd.opts();
24961
25085
  const cfg = requireConfig();
24962
25086
  const cred = requireAuth(loadCredentials());
25087
+ const os = resolveChatOs(opts.os, cfg);
24963
25088
  requireSingleTarget(opts, "withdraw");
24964
25089
  let scene;
24965
25090
  let toId;
@@ -24989,7 +25114,7 @@ function register20(program3) {
24989
25114
  subType: 2,
24990
25115
  body: { mids: [opts.mid] },
24991
25116
  fName: cred.name,
24992
- platform: opts.os === "Mac" || opts.os === "OS X" ? "Mac" : "Windows",
25117
+ platform: os === "Mac" || os === "OS X" ? "Mac" : "Windows",
24993
25118
  appKey: cfg.appKey
24994
25119
  });
24995
25120
  if (opts.dryRun) {
@@ -25001,7 +25126,7 @@ function register20(program3) {
25001
25126
  frame: { sid: "talk", cid: "sendMsg", data: { msg } }
25002
25127
  };
25003
25128
  }
25004
- const session = await openChatSession(cred, { os: opts.os, debug: opts.debug });
25129
+ const session = await openChatSession(cred, { os, debug: opts.debug });
25005
25130
  const { socket } = session;
25006
25131
  let res;
25007
25132
  try {
@@ -25044,11 +25169,13 @@ function register21(program3) {
25044
25169
  description: "Send user/delSession to drop the local session record on all of the user's devices.",
25045
25170
  output: Output21,
25046
25171
  configure: (cmd) => {
25047
- 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", parseChatOs, DEFAULT_CHAT_OS).option("--debug", "forward wire-level debug events to stderr");
25172
+ 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("--debug", "forward wire-level debug events to stderr");
25048
25173
  },
25049
25174
  run: async (cmd) => {
25050
25175
  const opts = cmd.opts();
25176
+ const cfg = requireConfig();
25051
25177
  const cred = requireAuth(loadCredentials());
25178
+ const os = resolveChatOs(opts.os, cfg);
25052
25179
  requireSingleTarget(opts, "clear-session");
25053
25180
  let scene;
25054
25181
  let toId;
@@ -25075,7 +25202,7 @@ function register21(program3) {
25075
25202
  if (opts.dryRun) {
25076
25203
  return { scene, toId, sId, dryRun: true, frame };
25077
25204
  }
25078
- const session = await openChatSession(cred, { os: opts.os, debug: opts.debug });
25205
+ const session = await openChatSession(cred, { os, debug: opts.debug });
25079
25206
  const { socket } = session;
25080
25207
  let res;
25081
25208
  try {
@@ -25091,8 +25218,638 @@ function register21(program3) {
25091
25218
  });
25092
25219
  }
25093
25220
 
25094
- // src/commands/manifest.ts
25221
+ // src/lib/reactions.ts
25222
+ async function saveReply(cfg, args) {
25223
+ const { ctx, stickerId, senderName, isFromMe, selfAppKey } = args;
25224
+ const isGroup = ctx.scene === "team_g";
25225
+ const targetApp = ctx.targetApp ?? selfAppKey;
25226
+ const body = {
25227
+ sid: ctx.sid,
25228
+ mid: ctx.mid,
25229
+ appkey: selfAppKey,
25230
+ toId: isGroup ? ctx.sid : isFromMe ? ctx.peerUid : ctx.srcSenderId,
25231
+ toAppKey: isGroup ? targetApp : isFromMe ? targetApp : ctx.srcSenderApp || ctx.peerApp,
25232
+ groupId: isGroup ? ctx.sid : void 0,
25233
+ stickerId,
25234
+ senderName,
25235
+ srcSenderId: ctx.srcSenderId,
25236
+ srcSenderApp: ctx.srcSenderApp
25237
+ };
25238
+ return await request({
25239
+ method: "POST",
25240
+ auth: true,
25241
+ url: `${apiUrls(cfg).IMM}/emoticon/reply/save`,
25242
+ body,
25243
+ headers: { "Content-Type": "application/json" }
25244
+ });
25245
+ }
25246
+ async function listReplies(cfg, mid) {
25247
+ return await request({
25248
+ method: "GET",
25249
+ auth: true,
25250
+ url: `${apiUrls(cfg).IMM}/emoticon/reply/list`,
25251
+ qs: { mid }
25252
+ });
25253
+ }
25254
+ async function revokeReply(cfg, args) {
25255
+ const { emoticonId, ctx, isFromMe, selfAppKey } = args;
25256
+ const targetApp = ctx.targetApp ?? selfAppKey;
25257
+ const body = {
25258
+ emoticonId,
25259
+ appKey: selfAppKey
25260
+ };
25261
+ if (ctx.scene === "p2p") {
25262
+ body.toId = isFromMe ? ctx.peerUid : ctx.srcSenderId;
25263
+ body.toAppKey = isFromMe ? targetApp : ctx.srcSenderApp || ctx.peerApp;
25264
+ } else {
25265
+ body.groupId = ctx.sid;
25266
+ body.toAppKey = targetApp;
25267
+ }
25268
+ return await request({
25269
+ method: "POST",
25270
+ auth: true,
25271
+ url: `${apiUrls(cfg).IMM}/emoticon/reply/revocation`,
25272
+ body,
25273
+ headers: { "Content-Type": "application/json" }
25274
+ });
25275
+ }
25276
+
25277
+ // src/commands/react.ts
25095
25278
  var Output22 = external_exports.object({
25279
+ scene: external_exports.enum(["p2p", "team_g"]),
25280
+ sId: external_exports.string(),
25281
+ mid: external_exports.string(),
25282
+ stickerId: external_exports.string(),
25283
+ emoticonId: external_exports.string().optional(),
25284
+ raw: external_exports.unknown()
25285
+ });
25286
+ function register22(program3) {
25287
+ define2(program3, {
25288
+ name: "react",
25289
+ description: "Add an emoticon reaction to a message (POST emoticon/reply/save). VISIBLE TO THE TARGET CHAT \u2014 non-self targets require --yes-not-self.",
25290
+ output: Output22,
25291
+ configure: (cmd) => {
25292
+ cmd.requiredOption("--mid <mid>", "message id to react to").requiredOption("--sticker-id <id>", "emoticon/sticker id (run `muc emoticons` to list packages)").option("--peer <uid>", "1:1 chat with a peer uid (the OTHER side of the conversation)").option("--team <id>", "group chat with a team id").option("--to-self", "react in your self-chat (default if no target)").option("--src-sender-id <uid>", "UID of the ORIGINAL message sender (defaults to current user \u2014 i.e. reacting to your own message)").option("--src-sender-app <appkey>", "appKey of the original message sender (defaults to your app key)").option("--target-app-key <appkey>", "original message `app_key` field \u2014 distinct from --src-sender-app (`fApp`); only matters in cross-tenant reacts. Defaults to your app key.").option("--sender-name <name>", "override displayed sender name (defaults to your saved name)").option("--yes-not-self", "required to confirm reacting in a non-self conversation");
25293
+ },
25294
+ run: async (cmd) => {
25295
+ const opts = cmd.opts();
25296
+ const cfg = requireConfig();
25297
+ const cred = requireAuth(loadCredentials());
25298
+ requireSingleTarget(opts, "react");
25299
+ let scene;
25300
+ let peerUid;
25301
+ if (opts.team) {
25302
+ scene = "team_g";
25303
+ } else if (opts.toSelf || !opts.peer && !opts.team) {
25304
+ scene = "p2p";
25305
+ peerUid = cred.uid;
25306
+ } else {
25307
+ scene = "p2p";
25308
+ peerUid = opts.peer.toUpperCase();
25309
+ }
25310
+ const isSelf = scene === "p2p" && peerUid === cred.uid;
25311
+ if (!isSelf && !opts.yesNotSelf) {
25312
+ throw new CliError(
25313
+ "USAGE",
25314
+ `refusing to react in ${scene}:${opts.team ?? peerUid} without --yes-not-self`,
25315
+ "add --yes-not-self to confirm \u2014 reactions are visible to all participants"
25316
+ );
25317
+ }
25318
+ const srcSenderId = (opts.srcSenderId ?? cred.uid).toUpperCase();
25319
+ const srcSenderApp = opts.srcSenderApp ?? cfg.appKey;
25320
+ const isFromMe = srcSenderId === cred.uid && srcSenderApp === cfg.appKey;
25321
+ const sid = scene === "team_g" ? opts.team : buildSId("p2p", cred.uid, peerUid);
25322
+ const ctx = {
25323
+ scene,
25324
+ sid,
25325
+ mid: opts.mid,
25326
+ srcSenderId,
25327
+ srcSenderApp,
25328
+ peerUid,
25329
+ peerApp: cfg.appKey,
25330
+ targetApp: opts.targetAppKey
25331
+ };
25332
+ const res = await saveReply(cfg, {
25333
+ ctx,
25334
+ stickerId: opts.stickerId,
25335
+ senderName: opts.senderName ?? cred.name ?? cred.uid,
25336
+ isFromMe,
25337
+ selfAppKey: cfg.appKey
25338
+ });
25339
+ if (res.code !== 0) {
25340
+ throw new CliError("REMOTE_ERROR", `react rejected: code=${res.code} msg=${res.msg ?? ""}`.trim());
25341
+ }
25342
+ const data = res.data ?? {};
25343
+ const emoticonId = pickEmoticonId(data);
25344
+ return {
25345
+ scene,
25346
+ sId: sid,
25347
+ mid: opts.mid,
25348
+ stickerId: opts.stickerId,
25349
+ emoticonId,
25350
+ raw: res
25351
+ };
25352
+ }
25353
+ });
25354
+ }
25355
+ function pickEmoticonId(data) {
25356
+ if (!data || typeof data !== "object") return void 0;
25357
+ const d = data;
25358
+ for (const k of ["emoticonId", "id", "replyId", "commentId"]) {
25359
+ const v = d[k];
25360
+ if (typeof v === "string" && v.length > 0) return v;
25361
+ if (typeof v === "number") return String(v);
25362
+ }
25363
+ return void 0;
25364
+ }
25365
+
25366
+ // src/commands/reactions.ts
25367
+ var Output23 = external_exports.object({
25368
+ mid: external_exports.string(),
25369
+ count: external_exports.number(),
25370
+ reactions: external_exports.array(external_exports.unknown()),
25371
+ raw: external_exports.unknown()
25372
+ });
25373
+ function register23(program3) {
25374
+ define2(program3, {
25375
+ name: "reactions",
25376
+ description: "List emoticon reactions on a message (GET emoticon/reply/list?mid=\u2026).",
25377
+ output: Output23,
25378
+ configure: (cmd) => {
25379
+ cmd.requiredOption("--mid <mid>", "message id to inspect");
25380
+ },
25381
+ run: async (cmd) => {
25382
+ const { mid } = cmd.opts();
25383
+ const cfg = requireConfig();
25384
+ requireAuth(loadCredentials());
25385
+ const res = await listReplies(cfg, mid);
25386
+ if (res.code !== 0) {
25387
+ throw new CliError("REMOTE_ERROR", `reactions list rejected: code=${res.code} msg=${res.msg ?? ""}`.trim());
25388
+ }
25389
+ const reactions = extractList(res.data);
25390
+ return {
25391
+ mid,
25392
+ count: reactions.length,
25393
+ reactions,
25394
+ raw: res
25395
+ };
25396
+ }
25397
+ });
25398
+ }
25399
+ function extractList(data) {
25400
+ if (Array.isArray(data)) return data;
25401
+ if (data && typeof data === "object") {
25402
+ for (const k of ["list", "reactions", "replies", "records", "items"]) {
25403
+ const v = data[k];
25404
+ if (Array.isArray(v)) return v;
25405
+ }
25406
+ }
25407
+ return [];
25408
+ }
25409
+
25410
+ // src/commands/unreact.ts
25411
+ var Output24 = external_exports.object({
25412
+ scene: external_exports.enum(["p2p", "team_g"]),
25413
+ emoticonId: external_exports.string(),
25414
+ raw: external_exports.unknown()
25415
+ });
25416
+ function register24(program3) {
25417
+ define2(program3, {
25418
+ name: "unreact",
25419
+ description: "Revoke one of your emoticon reactions (POST emoticon/reply/revocation). VISIBLE TO THE TARGET CHAT \u2014 non-self targets require --yes-not-self.",
25420
+ output: Output24,
25421
+ configure: (cmd) => {
25422
+ cmd.requiredOption("--emoticon-id <id>", "reaction id (returned by `muc react` or seen in `muc reactions --mid \u2026`)").option("--peer <uid>", "1:1 chat with a peer uid").option("--team <id>", "group chat with a team id").option("--to-self", "revoke in your self-chat (default if no target)").option("--src-sender-id <uid>", "UID of the original message sender (defaults to current user)").option("--src-sender-app <appkey>", "appKey of the original message sender (defaults to your app key)").option("--target-app-key <appkey>", "original message `app_key` field \u2014 distinct from --src-sender-app (`fApp`); only matters in cross-tenant revokes. Defaults to your app key.").option("--yes-not-self", "required to confirm revoking in a non-self conversation");
25423
+ },
25424
+ run: async (cmd) => {
25425
+ const opts = cmd.opts();
25426
+ const cfg = requireConfig();
25427
+ const cred = requireAuth(loadCredentials());
25428
+ requireSingleTarget(opts, "unreact");
25429
+ let scene;
25430
+ let peerUid;
25431
+ if (opts.team) {
25432
+ scene = "team_g";
25433
+ } else if (opts.toSelf || !opts.peer && !opts.team) {
25434
+ scene = "p2p";
25435
+ peerUid = cred.uid;
25436
+ } else {
25437
+ scene = "p2p";
25438
+ peerUid = opts.peer.toUpperCase();
25439
+ }
25440
+ const isSelf = scene === "p2p" && peerUid === cred.uid;
25441
+ if (!isSelf && !opts.yesNotSelf) {
25442
+ throw new CliError(
25443
+ "USAGE",
25444
+ `refusing to revoke reaction in ${scene}:${opts.team ?? peerUid} without --yes-not-self`,
25445
+ "add --yes-not-self to confirm"
25446
+ );
25447
+ }
25448
+ const srcSenderId = (opts.srcSenderId ?? cred.uid).toUpperCase();
25449
+ const srcSenderApp = opts.srcSenderApp ?? cfg.appKey;
25450
+ const isFromMe = srcSenderId === cred.uid && srcSenderApp === cfg.appKey;
25451
+ const sid = scene === "team_g" ? opts.team : buildSId("p2p", cred.uid, peerUid);
25452
+ const ctx = {
25453
+ scene,
25454
+ sid,
25455
+ // mid not needed for revoke (server identifies by emoticonId)
25456
+ mid: "",
25457
+ srcSenderId,
25458
+ srcSenderApp,
25459
+ peerUid,
25460
+ peerApp: cfg.appKey,
25461
+ targetApp: opts.targetAppKey
25462
+ };
25463
+ const res = await revokeReply(cfg, {
25464
+ emoticonId: opts.emoticonId,
25465
+ ctx,
25466
+ isFromMe,
25467
+ selfAppKey: cfg.appKey
25468
+ });
25469
+ if (res.code !== 0) {
25470
+ throw new CliError("REMOTE_ERROR", `unreact rejected: code=${res.code} msg=${res.msg ?? ""}`.trim());
25471
+ }
25472
+ return {
25473
+ scene,
25474
+ emoticonId: opts.emoticonId,
25475
+ raw: res
25476
+ };
25477
+ }
25478
+ });
25479
+ }
25480
+
25481
+ // src/commands/wait-react.ts
25482
+ var Output25 = external_exports.object({
25483
+ matched: external_exports.literal(true),
25484
+ mid: external_exports.string(),
25485
+ reaction: external_exports.unknown()
25486
+ });
25487
+ function register25(program3) {
25488
+ define2(program3, {
25489
+ name: "wait-react",
25490
+ description: "Block until someone adds an emoticon reaction (subType=300) to --mid. Consumes a TCP slot \u2014 defaults to --os iOS (Mobile slot).",
25491
+ output: Output25,
25492
+ configure: (cmd) => {
25493
+ 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("--sticker-id <id>", "only consider reactions whose stickerId matches").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("--debug", "forward wire-level debug events to stderr");
25494
+ },
25495
+ run: async (cmd) => {
25496
+ const opts = cmd.opts();
25497
+ const cfg = requireConfig();
25498
+ const cred = requireAuth(loadCredentials());
25499
+ const os = resolveChatOs(opts.os, cfg);
25500
+ const wantedFrom = opts.fromUid?.toUpperCase();
25501
+ const session = await openChatSession(cred, { os, debug: opts.debug });
25502
+ const { socket } = session;
25503
+ try {
25504
+ return await new Promise((resolve2, reject) => {
25505
+ const timer = setTimeout(() => {
25506
+ reject(new CliError(
25507
+ "TIMEOUT",
25508
+ `no matching reaction on mid=${opts.mid} after ${opts.timeout}s`
25509
+ ));
25510
+ }, opts.timeout * 1e3);
25511
+ const cleanup = () => clearTimeout(timer);
25512
+ socket.on("kicked", (payload) => {
25513
+ cleanup();
25514
+ reject(new CliError(
25515
+ "KICKED",
25516
+ `chat session kicked: ${JSON.stringify(payload).slice(0, 200)}`,
25517
+ "another login claimed the same device slot \u2014 see HANDOFF.md slot model"
25518
+ ));
25519
+ });
25520
+ socket.on("error", (e) => {
25521
+ cleanup();
25522
+ reject(new CliError("NETWORK", `chat socket error: ${e.message}`));
25523
+ });
25524
+ socket.on("close", () => {
25525
+ cleanup();
25526
+ reject(new CliError("NETWORK", "chat socket closed before a matching reaction arrived"));
25527
+ });
25528
+ socket.on("push", (frame) => {
25529
+ ackInboundIfNeeded(socket, frame);
25530
+ if (frame.sid !== "talk") return;
25531
+ if (frame.cid !== "msg" && frame.cid !== "offMsg") return;
25532
+ const msgs = frame.data?.msgs ?? [];
25533
+ for (const m of msgs) {
25534
+ if (m?.type !== 3 || m?.subType !== 300) continue;
25535
+ const body = m.body ?? {};
25536
+ const refMid = String(body.mid ?? body.refMid ?? "");
25537
+ if (refMid !== opts.mid) continue;
25538
+ if (wantedFrom && String(m.fId ?? "").toUpperCase() !== wantedFrom) continue;
25539
+ if (opts.stickerId) {
25540
+ const sid = String(body.stickerId ?? "");
25541
+ if (sid !== opts.stickerId) continue;
25542
+ }
25543
+ cleanup();
25544
+ resolve2({ matched: true, mid: opts.mid, reaction: m });
25545
+ return;
25546
+ }
25547
+ });
25548
+ socket.send("sync", "syncDone", { timestamp: 0 }).catch(() => {
25549
+ });
25550
+ });
25551
+ } finally {
25552
+ socket.close();
25553
+ }
25554
+ }
25555
+ });
25556
+ }
25557
+ function ackInboundIfNeeded(socket, frame) {
25558
+ if (frame.sid !== "talk") return;
25559
+ if (frame.cid !== "msg" && frame.cid !== "offMsg") return;
25560
+ const sqRaw = frame.sq;
25561
+ const sq = typeof sqRaw === "number" ? sqRaw : Number.parseInt(String(sqRaw ?? ""), 10);
25562
+ if (!Number.isFinite(sq)) return;
25563
+ const tids = Array.isArray(frame.data?.tids) ? frame.data.tids : [];
25564
+ if (tids.length === 0) return;
25565
+ try {
25566
+ socket.sendAck(frame.sid, frame.cid, sq, { tids });
25567
+ } catch {
25568
+ }
25569
+ }
25570
+
25571
+ // src/commands/listen.ts
25572
+ var MESSAGE_DATA_SCHEMA = {
25573
+ type: "object",
25574
+ properties: {
25575
+ mid: { type: "string" },
25576
+ sId: { type: "string" },
25577
+ fId: { type: "string" },
25578
+ fApp: { type: "string" },
25579
+ toId: { type: "string" },
25580
+ scene: { type: "string", enum: ["p2p", "team_g"] },
25581
+ type: { type: "number" },
25582
+ subType: { type: "number" },
25583
+ timestamp: { type: "number" },
25584
+ timestamp_u: { type: "number" },
25585
+ body: {}
25586
+ }
25587
+ };
25588
+ var STREAM_SCHEMA = {
25589
+ description: "NDJSON stream of envelopes. Under --json each stdout line is one JSON object: either {ok:true, schema_version, data:<message>} for a match, or {ok:false, schema_version, code, msg, hint?} for an in-band error (KICKED / NETWORK). Without --json each line is a one-line human summary (NOT JSON). Use --json for any pipe consumer.",
25590
+ oneOf: [
25591
+ {
25592
+ type: "object",
25593
+ required: ["ok", "schema_version", "data"],
25594
+ properties: {
25595
+ ok: { const: true },
25596
+ schema_version: { type: "number" },
25597
+ data: MESSAGE_DATA_SCHEMA
25598
+ }
25599
+ },
25600
+ {
25601
+ type: "object",
25602
+ required: ["ok", "schema_version", "code", "msg"],
25603
+ properties: {
25604
+ ok: { const: false },
25605
+ schema_version: { type: "number" },
25606
+ // The streaming `push` path emits KICKED / NETWORK in-band; the
25607
+ // wrapping `defineStream` action handler may also fail() with any
25608
+ // CliError code raised before the loop starts (USAGE for bad
25609
+ // --regex/--type, AUTH_REQUIRED/AUTH_EXPIRED from the credential
25610
+ // gate, CONFIG_MISSING/CONFIG_INVALID from `requireConfig`,
25611
+ // REMOTE_ERROR from the chat-login refresh path, INTERNAL for
25612
+ // unexpected throws). Advertise all of them.
25613
+ code: {
25614
+ type: "string",
25615
+ enum: [
25616
+ "KICKED",
25617
+ "NETWORK",
25618
+ "TIMEOUT",
25619
+ "USAGE",
25620
+ "AUTH_REQUIRED",
25621
+ "AUTH_EXPIRED",
25622
+ "NOT_FOUND",
25623
+ "REMOTE_ERROR",
25624
+ "CONFIG_MISSING",
25625
+ "CONFIG_INVALID",
25626
+ "INTERNAL"
25627
+ ]
25628
+ },
25629
+ msg: { type: "string" },
25630
+ hint: { type: "string" }
25631
+ }
25632
+ }
25633
+ ]
25634
+ };
25635
+ function register26(program3) {
25636
+ defineStream(program3, {
25637
+ name: "listen",
25638
+ 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.",
25639
+ streamSchema: STREAM_SCHEMA,
25640
+ configure: (cmd) => {
25641
+ 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("--debug", "forward wire-level debug events to stderr");
25642
+ },
25643
+ run: async (cmd) => {
25644
+ const opts = cmd.opts();
25645
+ const cred = requireAuth(loadCredentials());
25646
+ const wantedFromUids = new Set(opts.fromUid.map((s) => s.toUpperCase()));
25647
+ const wantedTeams = new Set(opts.team.map((s) => s));
25648
+ const wantedPeers = new Set(opts.peer.map((s) => s.toUpperCase()));
25649
+ const containsTerms = opts.contains.map((s) => s.toLowerCase());
25650
+ const regexes = opts.regex.map((p) => {
25651
+ try {
25652
+ return new RegExp(p);
25653
+ } catch (e) {
25654
+ throw new CliError("USAGE", `invalid --regex "${p}": ${e.message}`);
25655
+ }
25656
+ });
25657
+ const subtypeImpliesReactions = opts.subtype.some((s) => s === 300 || s === 301);
25658
+ const wantedTypes = opts.type.length ? new Set(opts.type) : /* @__PURE__ */ new Set([1, ...opts.includeReactions || subtypeImpliesReactions ? [3] : []]);
25659
+ const wantedSubtypes = opts.subtype.length ? new Set(opts.subtype) : null;
25660
+ const includeReactions = !!opts.includeReactions;
25661
+ const max = opts.max;
25662
+ const atMeOnly = !!opts.atMe;
25663
+ if (atMeOnly && opts.includeSelf) {
25664
+ throw new CliError("USAGE", "--at-me and --include-self are mutually exclusive (--at-me only matches messages from other users)");
25665
+ }
25666
+ const includeSelf = !!opts.includeSelf;
25667
+ const selfUidUpper = cred.uid.toUpperCase();
25668
+ const cfg = requireConfig();
25669
+ const selfAppKey = cfg.appKey;
25670
+ const os = resolveChatOs(opts.os, cfg);
25671
+ const session = await openChatSession(cred, { os, debug: opts.debug });
25672
+ const { socket } = session;
25673
+ const json = isJsonMode();
25674
+ let matched = 0;
25675
+ let exited = false;
25676
+ const closeAndExit = (code) => {
25677
+ if (exited) return;
25678
+ exited = true;
25679
+ try {
25680
+ socket.close();
25681
+ } catch {
25682
+ }
25683
+ process.exit(code);
25684
+ };
25685
+ const onSigint = () => closeAndExit(0);
25686
+ process.on("SIGINT", onSigint);
25687
+ process.on("SIGTERM", onSigint);
25688
+ socket.on("kicked", (payload) => {
25689
+ emitErr(
25690
+ "KICKED",
25691
+ `chat session kicked: ${JSON.stringify(payload).slice(0, 200)}`,
25692
+ "another login claimed the same device slot \u2014 see HANDOFF.md slot model"
25693
+ );
25694
+ closeAndExit(5);
25695
+ });
25696
+ socket.on("error", (e) => {
25697
+ emitErr("NETWORK", `chat socket error: ${e.message}`);
25698
+ closeAndExit(5);
25699
+ });
25700
+ socket.on("close", () => {
25701
+ if (!exited) {
25702
+ emitErr("NETWORK", "chat socket closed unexpectedly");
25703
+ closeAndExit(5);
25704
+ }
25705
+ });
25706
+ socket.on("push", (frame) => {
25707
+ ackInboundIfNeeded2(socket, frame);
25708
+ if (frame.sid !== "talk") return;
25709
+ if (frame.cid !== "msg" && !(opts.includeOffline && frame.cid === "offMsg")) return;
25710
+ const msgs = frame.data?.msgs ?? [];
25711
+ for (const m of msgs) {
25712
+ if (!matches(m)) continue;
25713
+ emitMatch(m);
25714
+ matched++;
25715
+ if (max && matched >= max) {
25716
+ closeAndExit(0);
25717
+ return;
25718
+ }
25719
+ }
25720
+ });
25721
+ socket.send("sync", "syncDone", { timestamp: 0 }).catch(() => {
25722
+ });
25723
+ function matches(m) {
25724
+ if (!m || typeof m !== "object") return false;
25725
+ if (!wantedTypes.has(m.type)) {
25726
+ return false;
25727
+ }
25728
+ if (wantedSubtypes && !wantedSubtypes.has(m.subType)) return false;
25729
+ if (m.type === 3 && (m.subType === 300 || m.subType === 301) && !includeReactions && !opts.subtype.length && !opts.type.length) {
25730
+ return false;
25731
+ }
25732
+ if (!includeSelf && String(m.fId ?? "").toUpperCase() === selfUidUpper) {
25733
+ return false;
25734
+ }
25735
+ if (atMeOnly) {
25736
+ if (m.scene !== "team_g") return false;
25737
+ if (String(m.fId ?? "").toUpperCase() === selfUidUpper) return false;
25738
+ const atIds = Array.isArray(m.atIds) ? m.atIds : [];
25739
+ const idx = atIds.findIndex((id) => String(id ?? "").toUpperCase() === selfUidUpper);
25740
+ if (idx < 0) return false;
25741
+ const atAppkeys = Array.isArray(m.atAppkeys) ? m.atAppkeys : void 0;
25742
+ if (atAppkeys && selfAppKey) {
25743
+ const includesSelfApp = atAppkeys.includes(selfAppKey);
25744
+ const slotEmpty = !atAppkeys[idx];
25745
+ if (!includesSelfApp && !slotEmpty) return false;
25746
+ }
25747
+ }
25748
+ if (wantedFromUids.size && !wantedFromUids.has(String(m.fId ?? "").toUpperCase())) return false;
25749
+ if (wantedTeams.size) {
25750
+ if (m.scene !== "team_g") return false;
25751
+ if (!wantedTeams.has(String(m.toId ?? ""))) return false;
25752
+ }
25753
+ if (wantedPeers.size) {
25754
+ if (m.scene !== "p2p") return false;
25755
+ const fromId = String(m.fId ?? "").toUpperCase();
25756
+ const toId = String(m.toId ?? "").toUpperCase();
25757
+ const peer = fromId === cred.uid.toUpperCase() ? toId : fromId;
25758
+ if (!wantedPeers.has(peer)) return false;
25759
+ }
25760
+ if (containsTerms.length || regexes.length) {
25761
+ const text = extractText(m).toLowerCase();
25762
+ if (containsTerms.length && !containsTerms.some((t) => text.includes(t))) return false;
25763
+ if (regexes.length && !regexes.some((r) => r.test(text))) return false;
25764
+ }
25765
+ return true;
25766
+ }
25767
+ function emitMatch(m) {
25768
+ const out = {
25769
+ mid: String(m.mid ?? ""),
25770
+ sId: String(m.sId ?? ""),
25771
+ fId: String(m.fId ?? ""),
25772
+ fApp: String(m.fApp ?? ""),
25773
+ toId: String(m.toId ?? ""),
25774
+ scene: m.scene,
25775
+ type: m.type,
25776
+ subType: m.subType,
25777
+ timestamp: m.timestamp,
25778
+ timestamp_u: m.timestamp_u,
25779
+ body: m.body
25780
+ };
25781
+ if (json) {
25782
+ process.stdout.write(JSON.stringify({ ok: true, schema_version: SCHEMA_VERSION, data: out }) + "\n");
25783
+ } else {
25784
+ process.stdout.write(formatHuman(out) + "\n");
25785
+ }
25786
+ }
25787
+ function emitErr(code, msg, hint) {
25788
+ if (json) {
25789
+ process.stdout.write(JSON.stringify({ ok: false, schema_version: SCHEMA_VERSION, code, msg, hint }) + "\n");
25790
+ } else {
25791
+ process.stderr.write(`error: ${msg}
25792
+ `);
25793
+ if (hint) process.stderr.write(` hint: ${hint}
25794
+ `);
25795
+ process.stderr.write(` code: ${code}
25796
+ `);
25797
+ }
25798
+ }
25799
+ await new Promise(() => {
25800
+ });
25801
+ }
25802
+ });
25803
+ }
25804
+ function collect(value, prev) {
25805
+ return [...prev, value];
25806
+ }
25807
+ function collectInt(label) {
25808
+ return (raw, prev) => {
25809
+ if (!/^\d+$/.test(raw)) throw new CliError("USAGE", `${label} must be a non-negative integer (got ${raw})`);
25810
+ return [...prev, Number(raw)];
25811
+ };
25812
+ }
25813
+ function ackInboundIfNeeded2(socket, frame) {
25814
+ if (frame.sid !== "talk") return;
25815
+ if (frame.cid !== "msg" && frame.cid !== "offMsg") return;
25816
+ const sqRaw = frame.sq;
25817
+ const sq = typeof sqRaw === "number" ? sqRaw : Number.parseInt(String(sqRaw ?? ""), 10);
25818
+ if (!Number.isFinite(sq)) return;
25819
+ const tids = Array.isArray(frame.data?.tids) ? frame.data.tids : [];
25820
+ if (tids.length === 0) return;
25821
+ try {
25822
+ socket.sendAck(frame.sid, frame.cid, sq, { tids });
25823
+ } catch {
25824
+ }
25825
+ }
25826
+ function extractText(m) {
25827
+ const body = m?.body;
25828
+ if (typeof body === "string") return body;
25829
+ if (Array.isArray(body)) {
25830
+ return body.map((seg) => typeof seg?.content === "string" ? seg.content : typeof seg?.text === "string" ? seg.text : "").join(" ");
25831
+ }
25832
+ if (body && typeof body === "object") {
25833
+ const candidates = ["text", "content", "fName", "name"];
25834
+ for (const k of candidates) {
25835
+ if (typeof body[k] === "string") return body[k];
25836
+ }
25837
+ }
25838
+ return "";
25839
+ }
25840
+ function formatHuman(m) {
25841
+ const ts = typeof m.timestamp === "number" ? new Date(m.timestamp * 1e3).toISOString() : "?";
25842
+ const where = m.scene === "team_g" ? `team:${m.toId}` : `p2p:${m.toId}`;
25843
+ const text = (() => {
25844
+ if (typeof m.body === "string") return m.body;
25845
+ if (m.body == null) return "";
25846
+ return JSON.stringify(m.body).slice(0, 200);
25847
+ })();
25848
+ return `${ts} ${m.fId} \u2192 ${where} (type=${m.type}/${m.subType}): ${text}`;
25849
+ }
25850
+
25851
+ // src/commands/manifest.ts
25852
+ var Output26 = external_exports.object({
25096
25853
  version: external_exports.string(),
25097
25854
  schema_version: external_exports.literal(1),
25098
25855
  commands: external_exports.array(external_exports.object({
@@ -25101,11 +25858,11 @@ var Output22 = external_exports.object({
25101
25858
  output_schema: external_exports.unknown()
25102
25859
  }))
25103
25860
  });
25104
- function register22(program3) {
25861
+ function register27(program3) {
25105
25862
  define2(program3, {
25106
25863
  name: "manifest",
25107
25864
  description: "Emit the full command graph + per-command output JSON Schema. Skill authors should consume this rather than parse --help.",
25108
- output: Output22,
25865
+ output: Output26,
25109
25866
  run: async () => ({
25110
25867
  version: CLI_VERSION,
25111
25868
  schema_version: 1,
@@ -25152,6 +25909,11 @@ register19(program2);
25152
25909
  register20(program2);
25153
25910
  register21(program2);
25154
25911
  register22(program2);
25912
+ register23(program2);
25913
+ register24(program2);
25914
+ register25(program2);
25915
+ register26(program2);
25916
+ register27(program2);
25155
25917
  async function main() {
25156
25918
  try {
25157
25919
  await program2.parseAsync(argv);