liangzimixin 0.3.25 → 0.3.26

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.
@@ -4399,7 +4399,8 @@ async function qss_calculate_mac(randomA, randomB, fkCipher) {
4399
4399
  }
4400
4400
  async function qss_encrypt(fkCipher, skCipher, iv, mode, plain, isFile) {
4401
4401
  const mod = await ready;
4402
- const MAX_CIPHER = plain.length + 256;
4402
+ const plainLen = Buffer.from(plain, "utf-8").length;
4403
+ const MAX_CIPHER = plainLen + 256;
4403
4404
  let plainPtr;
4404
4405
  if (isFile) {
4405
4406
  plainPtr = allocBytes(mod, plain);
@@ -4419,7 +4420,7 @@ async function qss_encrypt(fkCipher, skCipher, iv, mode, plain, isFile) {
4419
4420
  ivPtr,
4420
4421
  mode,
4421
4422
  plainPtr,
4422
- plain.length,
4423
+ plainLen,
4423
4424
  cipherPtr,
4424
4425
  cipherLenPtr
4425
4426
  );
@@ -4469,7 +4470,6 @@ async function qss_decrypt(fkCipher, skCipher, iv, mode, cipher, isFile) {
4469
4470
  if (isFile) {
4470
4471
  return Buffer.from(mod.HEAPU8.subarray(plainPtr, plainPtr + plainLen));
4471
4472
  }
4472
- const hex = Buffer.from(mod.HEAPU8.subarray(plainPtr, plainPtr + plainLen)).toString("hex");
4473
4473
  return readCString(mod, plainPtr, plainLen);
4474
4474
  } finally {
4475
4475
  mod._free(fkCipherPtr);
@@ -4490,6 +4490,7 @@ var import_node_child_process = require("node:child_process");
4490
4490
  var cache2 = new store_default();
4491
4491
  var TOKEN_KEY2 = "kms_access_token";
4492
4492
  var KEY_DIR = "keys";
4493
+ var scmAddPromise = null;
4493
4494
  function getDeviceFingerprint() {
4494
4495
  var _a, _b, _c, _d;
4495
4496
  try {
@@ -4550,15 +4551,15 @@ function decryptWithDeviceKey(encryptedData) {
4550
4551
  function uuid() {
4551
4552
  return import_node_crypto.default.randomUUID().replace(/-/g, "");
4552
4553
  }
4553
- async function getKeyAddress(baseUrl, spmId) {
4554
+ async function getKeyAddress(baseUrl, spmId2) {
4554
4555
  let params = {
4555
4556
  account: config_default.account,
4556
4557
  appId: config_default.appID,
4557
4558
  type: 2
4558
4559
  };
4559
- if (spmId) {
4560
+ if (spmId2) {
4560
4561
  params = {
4561
- spmId,
4562
+ spmId: spmId2,
4562
4563
  type: 2
4563
4564
  };
4564
4565
  }
@@ -4603,38 +4604,55 @@ async function getKeyFile() {
4603
4604
  }
4604
4605
  }
4605
4606
  async function scmAdd2(kmsUrl) {
4606
- const spmInfo = await getSpmInfo(kmsUrl);
4607
- let isPullAgain = 2;
4608
- if (spmInfo.keyLength - spmInfo.useNumber < 1e3) {
4609
- logger_default.info("\u5BC6\u94A5\u4F59\u91CF\u4E0D\u8DB3\uFF0C\u91CD\u65B0\u62C9\u53D6\u5BC6\u94A5");
4610
- isPullAgain = 2;
4607
+ if (scmAddPromise) {
4608
+ logger_default.info("scmAdd \u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u7B49\u5F85\u590D\u7528\u7ED3\u679C");
4609
+ try {
4610
+ const result = await scmAddPromise;
4611
+ return result;
4612
+ } catch (err) {
4613
+ logger_default.info("\u4E4B\u524D\u7684 scmAdd \u8BF7\u6C42\u5931\u8D25\uFF0C\u91CD\u65B0\u6267\u884C");
4614
+ }
4611
4615
  }
4612
- const fkLen = 100 * 1024;
4613
- const params = await qss_assemble_fillkey_req(config_default.account, isPullAgain, fkLen);
4614
- const response = await scmAdd(kmsUrl, JSON.parse(params));
4615
- const keyTag = response.data.keyTag;
4616
- const { headerCipher, fkCipher } = await qss_parse_fillkey_rep(JSON.stringify(response.data));
4617
- const combinedData = headerCipher + fkCipher;
4618
- const encryptedData = encryptWithDeviceKey(combinedData);
4619
- const keysDir = import_node_path4.default.join(process.cwd(), ".cache", KEY_DIR);
4620
- const filePath = import_node_path4.default.join(keysDir, spmInfo.spmId);
4621
- await import_promises2.default.mkdir(keysDir, { recursive: true });
4622
- try {
4623
- const files = await import_promises2.default.readdir(keysDir);
4624
- for (const file of files) {
4625
- if (file !== spmInfo.spmId) {
4626
- await import_promises2.default.unlink(import_node_path4.default.join(keysDir, file));
4616
+ scmAddPromise = (async () => {
4617
+ try {
4618
+ const spmInfo = await getSpmInfo(kmsUrl);
4619
+ let isPullAgain = 1;
4620
+ if (spmInfo.keyLength - spmInfo.useNumber < 1e3) {
4621
+ logger_default.info("\u5BC6\u94A5\u4F59\u91CF\u4E0D\u8DB3\uFF0C\u91CD\u65B0\u62C9\u53D6\u5BC6\u94A5");
4622
+ isPullAgain = 2;
4627
4623
  }
4624
+ const fkLen = 100 * 1024;
4625
+ const params = await qss_assemble_fillkey_req(config_default.account, isPullAgain, fkLen);
4626
+ const response = await scmAdd(kmsUrl, JSON.parse(params));
4627
+ const keyTag = response.data.keyTag;
4628
+ logger_default.info("\u5BC6\u94A5\u5DF2\u83B7\u53D6\uFF0CkeyTag:", keyTag);
4629
+ const { headerCipher, fkCipher } = await qss_parse_fillkey_rep(JSON.stringify(response.data));
4630
+ const combinedData = headerCipher + fkCipher;
4631
+ const encryptedData = encryptWithDeviceKey(combinedData);
4632
+ const keysDir = import_node_path4.default.join(process.cwd(), ".cache", KEY_DIR);
4633
+ const filePath = import_node_path4.default.join(keysDir, spmInfo.spmId);
4634
+ await import_promises2.default.mkdir(keysDir, { recursive: true });
4635
+ try {
4636
+ const files = await import_promises2.default.readdir(keysDir);
4637
+ for (const file of files) {
4638
+ if (file !== spmInfo.spmId) {
4639
+ await import_promises2.default.unlink(import_node_path4.default.join(keysDir, file));
4640
+ }
4641
+ }
4642
+ } catch (err) {
4643
+ if (err.code !== "ENOENT")
4644
+ throw err;
4645
+ }
4646
+ await import_promises2.default.writeFile(filePath, encryptedData, "utf-8");
4647
+ logger_default.info("\u5BC6\u94A5\u5DF2\u52A0\u5BC6\u4FDD\u5B58\u5230:", filePath);
4648
+ return { headerCipher, fkCipher, keyTag, spmId: spmInfo.spmId, isPullAgain };
4649
+ } finally {
4650
+ scmAddPromise = null;
4628
4651
  }
4629
- } catch (err) {
4630
- if (err.code !== "ENOENT")
4631
- throw err;
4632
- }
4633
- await import_promises2.default.writeFile(filePath, encryptedData, "utf-8");
4634
- logger_default.info("\u5BC6\u94A5\u5DF2\u52A0\u5BC6\u4FDD\u5B58\u5230:", filePath);
4635
- return { headerCipher, fkCipher, keyTag, spmId: spmInfo.spmId };
4652
+ })();
4653
+ return scmAddPromise;
4636
4654
  }
4637
- async function kmsLogin(kmsUrl, spmId, fkCiphers, localKeyTag) {
4655
+ async function kmsLogin(kmsUrl, spmId2, fkCiphers, localKeyTag) {
4638
4656
  const cachedToken = await cache2.get(TOKEN_KEY2);
4639
4657
  if (cachedToken) {
4640
4658
  logger_default.info("\u4F7F\u7528\u7F13\u5B58\u7684 accessToken");
@@ -4645,12 +4663,12 @@ async function kmsLogin(kmsUrl, spmId, fkCiphers, localKeyTag) {
4645
4663
  requestSn,
4646
4664
  account: config_default.account,
4647
4665
  appId: config_default.appID,
4648
- spmId,
4666
+ spmId: spmId2,
4649
4667
  spmType: 6
4650
4668
  });
4651
4669
  const responseKeyTag = firstRequestResponse.data.keyTag;
4652
4670
  if (responseKeyTag + "" !== localKeyTag + "") {
4653
- logger_default.info("keyTag \u4E0D\u4E00\u81F4\uFF0C\u9700\u8981\u91CD\u65B0\u62C9\u53D6\u5BC6\u94A5");
4671
+ logger_default.info("keyTag \u4E0D\u4E00\u81F4\uFF0C\u9700\u8981\u91CD\u65B0\u62C9\u53D6\u5BC6\u94A5", responseKeyTag, localKeyTag);
4654
4672
  throw new Error("KEY_TAG_MISMATCH");
4655
4673
  }
4656
4674
  const randomA = await qss_get_random(16);
@@ -4662,7 +4680,7 @@ async function kmsLogin(kmsUrl, spmId, fkCiphers, localKeyTag) {
4662
4680
  requestSn,
4663
4681
  account: config_default.account,
4664
4682
  appId: config_default.appID,
4665
- spmId,
4683
+ spmId: spmId2,
4666
4684
  spmType: 6,
4667
4685
  randomA,
4668
4686
  randomB,
@@ -4725,6 +4743,7 @@ var QuantumEncryptPlug = class {
4725
4743
  try {
4726
4744
  const result = await qss_get_header_info(headerCipher);
4727
4745
  this.keyTag = result.keyTag;
4746
+ logger_default.info("get_header_info \u6210\u529F\uFF0CkeyTag:", this.keyTag);
4728
4747
  } catch (error) {
4729
4748
  if (error.message === "KEY_TAG_MISMATCH") {
4730
4749
  logger_default.info("get_header_info \u5BC6\u94A5\u4E0D\u53EF\u4EE5\u7528\uFF0C\u91CD\u65B0\u62C9\u53D6\u5BC6\u94A5");
@@ -4743,20 +4762,20 @@ var QuantumEncryptPlug = class {
4743
4762
  }
4744
4763
  if (!this.fkCipher) {
4745
4764
  logger_default.info("\u5F00\u59CB\u62C9\u53D6\u5BC6\u94A5");
4746
- const { fkCipher, keyTag, spmId } = await scmAdd2(kmsUrl);
4765
+ const { fkCipher, keyTag, spmId: spmId2 } = await scmAdd2(kmsUrl);
4747
4766
  this.fkCipher = fkCipher;
4748
4767
  this.keyTag = keyTag;
4749
- this.spmId = spmId;
4768
+ this.spmId = spmId2;
4750
4769
  }
4751
4770
  try {
4752
4771
  await kmsLogin(kmsUrl, this.spmId, this.fkCipher, this.keyTag);
4753
4772
  } catch (err) {
4754
4773
  if (err.message === "KEY_TAG_MISMATCH") {
4755
4774
  logger_default.info("keyTag \u4E0D\u4E00\u81F4\uFF0C\u91CD\u65B0\u62C9\u53D6\u5BC6\u94A5");
4756
- const { fkCipher, keyTag, spmId } = await scmAdd2(kmsUrl);
4775
+ const { fkCipher, keyTag, spmId: spmId2 } = await scmAdd2(kmsUrl);
4757
4776
  this.fkCipher = fkCipher;
4758
4777
  this.keyTag = keyTag;
4759
- this.spmId = spmId;
4778
+ this.spmId = spmId2;
4760
4779
  await kmsLogin(kmsUrl, this.spmId, this.fkCipher, this.keyTag);
4761
4780
  } else {
4762
4781
  throw err;
@@ -4791,6 +4810,15 @@ var QuantumEncryptPlug = class {
4791
4810
  }
4792
4811
  async _doEncrypt(plainText, iv, mode) {
4793
4812
  const normalizedIV = this._normalizeIV(iv);
4813
+ const sessionKeyInfo = await this._getSessionKey();
4814
+ const fkCipher = this.fkCipher.slice(sessionKeyInfo.decKeySn * 2, sessionKeyInfo.decKeySn * 2 + 32);
4815
+ const cipherText = await qss_encrypt(fkCipher, sessionKeyInfo.keyCipher, normalizedIV, this.encryptMode[mode], plainText);
4816
+ return { cipherText, keyId: sessionKeyInfo.keyId };
4817
+ }
4818
+ /**
4819
+ * 获取会话密钥(用于加密场景)
4820
+ */
4821
+ async _getSessionKey() {
4794
4822
  const { data: sessionKeyInfo } = await sessionKeyAdd(this.kmsUrl, {
4795
4823
  spmId: this.spmId,
4796
4824
  "keyLength": 16,
@@ -4801,15 +4829,62 @@ var QuantumEncryptPlug = class {
4801
4829
  "keyType": 2,
4802
4830
  "spmType": 6
4803
4831
  });
4804
- if (sessionKeyInfo.decKeyTag !== this.keyTag) {
4805
- logger_default.info("encrypt: keyTag \u4E0D\u4E00\u81F4\uFF0C\u91CD\u65B0\u83B7\u53D6\u5BC6\u94A5");
4806
- const { fkCipher: fkCipher2, keyTag } = await scmAdd2(this.kmsUrl);
4807
- this.fkCipher = fkCipher2;
4832
+ if (sessionKeyInfo.decKeyTag + "" !== this.keyTag + "") {
4833
+ logger_default.info("keyTag \u4E0D\u4E00\u81F4\uFF0C\u91CD\u65B0\u83B7\u53D6\u5BC6\u94A5", sessionKeyInfo.decKeyTag, this.keyTag);
4834
+ const { fkCipher, keyTag, isPullAgain } = await scmAdd2(this.kmsUrl);
4835
+ this.fkCipher = fkCipher;
4808
4836
  this.keyTag = keyTag;
4837
+ if (isPullAgain === 2) {
4838
+ return await this._refreshSessionKey();
4839
+ }
4840
+ } else {
4841
+ const response = await getSpmId(this.kmsUrl, {
4842
+ account: config_default.account,
4843
+ appId: config_default.appID
4844
+ });
4845
+ const spmInfo = response.data;
4846
+ if (spmInfo.keyLength - spmInfo.useNumber < 1e3) {
4847
+ scmAdd2(this.kmsUrl).then(({ fkCipher, keyTag }) => {
4848
+ this.fkCipher = fkCipher;
4849
+ this.keyTag = keyTag;
4850
+ this.spmId = spmId;
4851
+ });
4852
+ }
4809
4853
  }
4810
- const fkCipher = this.fkCipher.slice(sessionKeyInfo.decKeySn * 2, sessionKeyInfo.decKeySn * 2 + 32);
4811
- const cipherText = await qss_encrypt(fkCipher, sessionKeyInfo.keyCipher, normalizedIV || this.iv, this.encryptMode[mode], plainText);
4812
- return { cipherText, keyId: sessionKeyInfo.keyId };
4854
+ return sessionKeyInfo;
4855
+ }
4856
+ /**
4857
+ * 通过 keyId 获取会话密钥(用于解密场景)
4858
+ */
4859
+ async _getSessionKeyById(keyId) {
4860
+ const { data: sessionKeyInfo } = await sessionKeyQuery(this.kmsUrl, {
4861
+ spmId: this.spmId,
4862
+ keyId
4863
+ });
4864
+ if (sessionKeyInfo.decKeyTag + "" !== this.keyTag + "") {
4865
+ logger_default.info("keyTag \u4E0D\u4E00\u81F4\uFF0C\u91CD\u65B0\u83B7\u53D6\u5BC6\u94A5", sessionKeyInfo.decKeyTag, this.keyTag);
4866
+ const { fkCipher, keyTag, isPullAgain } = await scmAdd2(this.kmsUrl);
4867
+ this.fkCipher = fkCipher;
4868
+ this.keyTag = keyTag;
4869
+ this.spmId = spmId;
4870
+ if (isPullAgain === 2) {
4871
+ return await this._refreshSessionKeyById(keyId);
4872
+ }
4873
+ } else {
4874
+ const response = await getSpmId(this.kmsUrl, {
4875
+ account: config_default.account,
4876
+ appId: config_default.appID
4877
+ });
4878
+ const spmInfo = response.data;
4879
+ if (spmInfo.keyLength - spmInfo.useNumber < 1e3) {
4880
+ scmAdd2(this.kmsUrl).then(({ fkCipher, keyTag }) => {
4881
+ this.fkCipher = fkCipher;
4882
+ this.keyTag = keyTag;
4883
+ this.spmId = spmId;
4884
+ });
4885
+ }
4886
+ }
4887
+ return sessionKeyInfo;
4813
4888
  }
4814
4889
  async _reLogin() {
4815
4890
  try {
@@ -4817,10 +4892,10 @@ var QuantumEncryptPlug = class {
4817
4892
  } catch (err) {
4818
4893
  if (err.message === "KEY_TAG_MISMATCH") {
4819
4894
  logger_default.info("kmslogin keyTag \u4E0D\u4E00\u81F4\uFF0C\u91CD\u65B0\u62C9\u53D6\u5BC6\u94A5");
4820
- const { fkCipher, keyTag, spmId } = await scmAdd2(this.kmsUrl);
4895
+ const { fkCipher, keyTag, spmId: spmId2 } = await scmAdd2(this.kmsUrl);
4821
4896
  this.fkCipher = fkCipher;
4822
4897
  this.keyTag = keyTag;
4823
- this.spmId = spmId;
4898
+ this.spmId = spmId2;
4824
4899
  await kmsLogin(this.kmsUrl, this.spmId, this.fkCipher, this.keyTag);
4825
4900
  } else {
4826
4901
  throw err;
@@ -4865,19 +4940,38 @@ var QuantumEncryptPlug = class {
4865
4940
  }
4866
4941
  async _doDecrypt(cipherText, keyId, iv, mode) {
4867
4942
  const normalizedIV = this._normalizeIV(iv);
4943
+ const sessionKeyInfo = await this._getSessionKeyById(keyId);
4944
+ const fkCipher = this.fkCipher.slice(sessionKeyInfo.decKeySn * 2, sessionKeyInfo.decKeySn * 2 + 32);
4945
+ const plainText = await qss_decrypt(fkCipher, sessionKeyInfo.keyCipher, normalizedIV, this.encryptMode[mode], cipherText);
4946
+ return { plainText };
4947
+ }
4948
+ /**
4949
+ * 刷新会话密钥(用于 isPullAgain === 2 时)
4950
+ */
4951
+ async _refreshSessionKey() {
4952
+ logger_default.info("isPullAgain === 2\uFF0C\u91CD\u65B0\u83B7\u53D6\u4F1A\u8BDD\u5BC6\u94A5");
4953
+ const { data: sessionKeyInfo } = await sessionKeyAdd(this.kmsUrl, {
4954
+ spmId: this.spmId,
4955
+ "keyLength": 16,
4956
+ "calcMode": 1,
4957
+ "auth": 0,
4958
+ "accounts": [config_default.account],
4959
+ "appId": config_default.appID,
4960
+ "keyType": 2,
4961
+ "spmType": 6
4962
+ });
4963
+ return sessionKeyInfo;
4964
+ }
4965
+ /**
4966
+ * 通过 keyId 刷新会话密钥(用于 isPullAgain === 2 时)
4967
+ */
4968
+ async _refreshSessionKeyById(keyId) {
4969
+ logger_default.info("isPullAgain === 2\uFF0C\u91CD\u65B0\u67E5\u8BE2\u4F1A\u8BDD\u5BC6\u94A5");
4868
4970
  const { data: sessionKeyInfo } = await sessionKeyQuery(this.kmsUrl, {
4869
4971
  spmId: this.spmId,
4870
4972
  keyId
4871
4973
  });
4872
- if (sessionKeyInfo.decKeyTag !== this.keyTag) {
4873
- logger_default.info("decrypt: keyTag \u4E0D\u4E00\u81F4\uFF0C\u91CD\u65B0\u83B7\u53D6\u5BC6\u94A5");
4874
- const { fkCipher: fkCipher2, keyTag } = await scmAdd2(this.kmsUrl);
4875
- this.fkCipher = fkCipher2;
4876
- this.keyTag = keyTag;
4877
- }
4878
- const fkCipher = this.fkCipher.slice(sessionKeyInfo.decKeySn * 2, sessionKeyInfo.decKeySn * 2 + 32);
4879
- const plainText = await qss_decrypt(fkCipher, sessionKeyInfo.keyCipher, normalizedIV || this.iv, this.encryptMode[mode], cipherText);
4880
- return { plainText };
4974
+ return sessionKeyInfo;
4881
4975
  }
4882
4976
  /**
4883
4977
  * 文件加密
@@ -4906,22 +5000,7 @@ var QuantumEncryptPlug = class {
4906
5000
  let keySn = 0;
4907
5001
  if (!sessionKey || !fillKey) {
4908
5002
  logger_default.info("encryptFile: sessionKey or fillKey is null");
4909
- const { data: sessionKeyInfo } = await sessionKeyAdd(this.kmsUrl, {
4910
- spmId: this.spmId,
4911
- "keyLength": 16,
4912
- "calcMode": 1,
4913
- "auth": 0,
4914
- "accounts": [config_default.account],
4915
- "appId": config_default.appID,
4916
- "keyType": 2,
4917
- "spmType": 6
4918
- });
4919
- if (sessionKeyInfo.decKeyTag !== this.keyTag) {
4920
- logger_default.info("encrypt: keyTag \u4E0D\u4E00\u81F4\uFF0C\u91CD\u65B0\u83B7\u53D6\u5BC6\u94A5");
4921
- const { fkCipher, keyTag } = await scmAdd2(this.kmsUrl);
4922
- this.fkCipher = fkCipher;
4923
- this.keyTag = keyTag;
4924
- }
5003
+ const sessionKeyInfo = await this._getSessionKey();
4925
5004
  sessionKey = sessionKeyInfo.keyCipher;
4926
5005
  keyId = sessionKeyInfo.keyId;
4927
5006
  keySn = sessionKeyInfo.decKeySn;
@@ -4957,21 +5036,12 @@ var QuantumEncryptPlug = class {
4957
5036
  if (sessionKey && fillKey) {
4958
5037
  logger_default.info("decryptFile: sessionKey \u548C fillKey \u5DF2\u6709\uFF0C\u65E0\u9700\u91CD\u65B0\u83B7\u53D6");
4959
5038
  } else {
4960
- const { data: sessionKeyInfo } = await sessionKeyQuery(this.kmsUrl, {
4961
- spmId: this.spmId,
4962
- keyId
4963
- });
4964
- if (sessionKeyInfo.decKeyTag !== this.keyTag) {
4965
- logger_default.info("decryptFile: keyTag \u4E0D\u4E00\u81F4\uFF0C\u91CD\u65B0\u83B7\u53D6\u5BC6\u94A5");
4966
- const { fkCipher: fkCipher2, keyTag } = await scmAdd2(this.kmsUrl);
4967
- this.fkCipher = fkCipher2;
4968
- this.keyTag = keyTag;
4969
- }
4970
- const fkCipher = this.fkCipher.slice(sessionKeyInfo.decKeySn * 2, sessionKeyInfo.decKeySn * 2 + 32);
5039
+ const sessionKeyInfo = await this._getSessionKeyById(keyId);
4971
5040
  sessionKey = sessionKeyInfo.keyCipher;
5041
+ const fkCipher = this.fkCipher.slice(sessionKeyInfo.decKeySn * 2, sessionKeyInfo.decKeySn * 2 + 32);
4972
5042
  fillKey = fkCipher;
4973
5043
  }
4974
- const plainData = await qss_decrypt(fillKey, sessionKey, normalizedIV || this.iv, this.encryptMode[mode], fileData, true);
5044
+ const plainData = await qss_decrypt(fillKey, sessionKey, normalizedIV, this.encryptMode[mode], fileData, true);
4975
5045
  const fileBuffer = Buffer.isBuffer(plainData) ? plainData : Buffer.from(plainData, "hex");
4976
5046
  return { fileBuffer, sessionKey, fillKey };
4977
5047
  }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liangzimixin",
3
- "version": "0.3.25",
3
+ "version": "0.3.26",
4
4
  "description": "Quantum-encrypted IM channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",