@mu-cabin/muc-cli 0.2.0 → 0.2.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 (2) hide show
  1. package/dist/muc.mjs +123 -54
  2. package/package.json +1 -1
package/dist/muc.mjs CHANGED
@@ -21634,11 +21634,59 @@ function read() {
21634
21634
  var CLI_VERSION = read();
21635
21635
 
21636
21636
  // src/commands/login.ts
21637
- import { release } from "node:os";
21637
+ import { release as release2 } from "node:os";
21638
21638
  import * as readline from "node:readline";
21639
21639
 
21640
21640
  // src/lib/http.ts
21641
21641
  var import_needle = __toESM(require_needle(), 1);
21642
+ import { release } from "node:os";
21643
+
21644
+ // src/lib/sign.ts
21645
+ var import_crypto_js = __toESM(require_crypto_js(), 1);
21646
+ import { createHash } from "node:crypto";
21647
+ function getMucSign(qs, secret) {
21648
+ const keys = Object.keys(qs).sort();
21649
+ const body = keys.map((k) => `${k}${qs[k] ?? ""}`).join("");
21650
+ return createHash("md5").update(`${secret}${body}${secret}`).digest("hex");
21651
+ }
21652
+ function signRest(qs, secret) {
21653
+ return getMucSign(qs, secret);
21654
+ }
21655
+ function signSecure(qs, secret) {
21656
+ return getMucSign(qs, secret);
21657
+ }
21658
+ function encryptPassword(plain, desKey) {
21659
+ const key = import_crypto_js.default.enc.Utf8.parse(desKey);
21660
+ const iv = import_crypto_js.default.enc.Hex.parse("0102030405060708");
21661
+ const enc = import_crypto_js.default.DES.encrypt(plain, key, {
21662
+ iv,
21663
+ mode: import_crypto_js.default.mode.CBC,
21664
+ padding: import_crypto_js.default.pad.Pkcs7
21665
+ });
21666
+ return enc.toString();
21667
+ }
21668
+
21669
+ // src/lib/device.ts
21670
+ import { homedir as homedir3, hostname } from "node:os";
21671
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
21672
+ import { join as join3 } from "node:path";
21673
+ import { randomBytes, createHash as createHash2 } from "node:crypto";
21674
+ var DIR3 = join3(homedir3(), ".muc");
21675
+ var FILE3 = join3(DIR3, "device-id");
21676
+ function getDeviceId() {
21677
+ if (!existsSync3(DIR3)) mkdirSync3(DIR3, { recursive: true, mode: 448 });
21678
+ if (existsSync3(FILE3)) {
21679
+ const v = readFileSync4(FILE3, "utf8").trim();
21680
+ if (v) return v;
21681
+ }
21682
+ const seed = randomBytes(32).toString("hex") + ":" + Date.now();
21683
+ const id = createHash2("sha256").update(seed).digest("hex");
21684
+ writeFileSync3(FILE3, id, { mode: 384 });
21685
+ return id;
21686
+ }
21687
+ function getDeviceName() {
21688
+ return `${hostname()} (muc-cli)`;
21689
+ }
21642
21690
 
21643
21691
  // src/lib/constants.ts
21644
21692
  var APP_NAME = "MUC";
@@ -21690,7 +21738,75 @@ function redact(url) {
21690
21738
  return url;
21691
21739
  }
21692
21740
  }
21741
+ var inflightRefresh = null;
21742
+ async function refreshAccessTokenWithTokenPwd() {
21743
+ if (inflightRefresh) return inflightRefresh;
21744
+ inflightRefresh = (async () => {
21745
+ const cred = loadCredentials();
21746
+ if (!cred?.tokenPwd) {
21747
+ throw new CliError(
21748
+ "AUTH_REQUIRED",
21749
+ "no saved tokenPwd; cannot refresh accessToken",
21750
+ "run `muc login --account <UID>` interactively in your own terminal"
21751
+ );
21752
+ }
21753
+ const cfg = requireConfig();
21754
+ const platform2 = DEFAULT_REST_PLATFORM;
21755
+ const appVersion = platform2 === PLATFORM_IOS ? APP_VERSION_IOS : APP_VERSION;
21756
+ const form = {
21757
+ account: cred.uid,
21758
+ password: encryptPassword(cred.tokenPwd, pwdDesKeyOf(cfg)),
21759
+ appName: APP_NAME,
21760
+ appKey: cfg.appKey,
21761
+ appVersion,
21762
+ platform: platform2,
21763
+ osVersion: release(),
21764
+ deviceId: cred.deviceId || getDeviceId(),
21765
+ deviceName: cred.deviceName || getDeviceName(),
21766
+ passwordType: 1,
21767
+ createTokenPwd: false,
21768
+ encrypt: true
21769
+ };
21770
+ form.sign = signRest(form, cfg.signSecretRest);
21771
+ const res = await requestRaw({
21772
+ method: "POST",
21773
+ url: `${apiUrls(cfg).MUC}/emp/login`,
21774
+ form
21775
+ });
21776
+ if (res.code !== 0 || !res.data?.accessToken) {
21777
+ throw new CliError(
21778
+ "AUTH_REQUIRED",
21779
+ `tokenPwd refresh rejected: code=${res.code} msg=${res.msg ?? ""}`.trim(),
21780
+ "run `muc login --account <UID>` interactively to mint fresh credentials"
21781
+ );
21782
+ }
21783
+ const next = {
21784
+ ...cred,
21785
+ accessToken: res.data.accessToken,
21786
+ accessTokenExpireAt: res.data.accessTokenExpireTime ?? res.data.accessTokenExpireAt ?? cred.accessTokenExpireAt,
21787
+ savedAt: Date.now()
21788
+ };
21789
+ saveCredentials(next);
21790
+ return next;
21791
+ })();
21792
+ try {
21793
+ return await inflightRefresh;
21794
+ } finally {
21795
+ inflightRefresh = null;
21796
+ }
21797
+ }
21693
21798
  async function request(opts) {
21799
+ try {
21800
+ return await requestRaw(opts);
21801
+ } catch (e) {
21802
+ if (e instanceof CliError && e.code === "AUTH_EXPIRED" && opts.auth === true && !opts._isRefreshRetry && loadCredentials()?.tokenPwd) {
21803
+ await refreshAccessTokenWithTokenPwd();
21804
+ return await requestRaw({ ...opts, _isRefreshRetry: true });
21805
+ }
21806
+ throw e;
21807
+ }
21808
+ }
21809
+ async function requestRaw(opts) {
21694
21810
  const method = opts.method ?? "GET";
21695
21811
  const url = appendQs(opts.url, opts.qs);
21696
21812
  const headers = {
@@ -21761,53 +21877,6 @@ function appendQs(url, qs) {
21761
21877
  return u.toString();
21762
21878
  }
21763
21879
 
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
21880
  // src/commands/login.ts
21812
21881
  var PLATFORM_NAMES = {
21813
21882
  ios: PLATFORM_IOS,
@@ -21866,7 +21935,7 @@ var Output = external_exports.object({
21866
21935
  function register(program3) {
21867
21936
  define2(program3, {
21868
21937
  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.",
21938
+ 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
21939
  output: Output,
21871
21940
  configure: (cmd) => {
21872
21941
  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 +21982,7 @@ function register(program3) {
21913
21982
  throw new CliError(
21914
21983
  "AUTH_REQUIRED",
21915
21984
  "password required",
21916
- "pass --password / --password-stdin / --token-pwd, set $MUC_PASSWORD, or run interactively (TTY)"
21985
+ "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
21986
  );
21918
21987
  }
21919
21988
  const cfg = requireConfig();
@@ -21929,7 +21998,7 @@ function register(program3) {
21929
21998
  appKey: cfg.appKey,
21930
21999
  appVersion,
21931
22000
  platform: platform2,
21932
- osVersion: release(),
22001
+ osVersion: release2(),
21933
22002
  deviceId,
21934
22003
  deviceName,
21935
22004
  passwordType: isAutoLogin ? 1 : 0,
@@ -22614,7 +22683,7 @@ function register8(program3) {
22614
22683
  }
22615
22684
 
22616
22685
  // src/lib/chat.ts
22617
- import { release as release2 } from "node:os";
22686
+ import { release as release3 } from "node:os";
22618
22687
 
22619
22688
  // src/lib/wire.ts
22620
22689
  import { EventEmitter } from "node:events";
@@ -22856,7 +22925,7 @@ function buildLoginPayload(cred, chatOs, cfg) {
22856
22925
  account: cred.uid,
22857
22926
  accessToken: cred.accessToken,
22858
22927
  os: chatOs,
22859
- osVersion: release2(),
22928
+ osVersion: release3(),
22860
22929
  sdkVersion: 21,
22861
22930
  deviceId: cred.deviceId,
22862
22931
  deviceName: cred.deviceName,
@@ -22951,7 +23020,7 @@ async function refreshAccessTokenViaTokenPwd(cred, cfg) {
22951
23020
  appKey: cfg.appKey,
22952
23021
  appVersion: APP_VERSION,
22953
23022
  platform: PLATFORM_MAC,
22954
- osVersion: release2(),
23023
+ osVersion: release3(),
22955
23024
  deviceId: cred.deviceId,
22956
23025
  deviceName: cred.deviceName,
22957
23026
  passwordType: 1,
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.1",
4
4
  "description": "Terminal client for MUC IM (com.meicloud.im.dh).",
5
5
  "license": "MIT",
6
6
  "type": "module",