@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.
- package/README.md +13 -0
- package/dist/muc.mjs +207 -57
- 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.
|
|
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
|
-
"
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|