@ih8e/express-cli 0.1.5 → 0.1.6

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 CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  ## Требования
14
14
 
15
- - **Node.js ≥ 22** (используется глобальный `WebSocket`).
15
+ - **Node.js ≥ 20**.
16
16
  - Аккаунт eXpress и мобильное приложение для входа по QR.
17
17
 
18
18
  ## Установка
package/dist/index.js CHANGED
@@ -26748,8 +26748,9 @@ var ApiClient = class {
26748
26748
  const retryRes = await fetch(url, { ...options, headers: newHeaders });
26749
26749
  return this.handleResponse(retryRes);
26750
26750
  }
26751
+ throw new Error("Token expired and refresh failed. Please re-authenticate with `express auth qr`.");
26751
26752
  }
26752
- throw new Error("Token expired and refresh failed. Please re-authenticate with `express auth qr`.");
26753
+ throw new Error(`API error 401: ${res.statusText}${text ? ` \u2014 ${text.slice(0, 200)}` : ""}`);
26753
26754
  }
26754
26755
  return this.handleResponse(res);
26755
26756
  }
@@ -27725,50 +27726,99 @@ function createChatsCommand() {
27725
27726
  init_esm_shims();
27726
27727
  import { Command as Command8 } from "commander";
27727
27728
 
27728
- // src/api/messaging.ts
27729
+ // src/api/messaging-ws.ts
27730
+ init_esm_shims();
27731
+ import WebSocket2 from "ws";
27732
+ import { randomBytes as randomBytes3, randomUUID as randomUUID4, createHash } from "crypto";
27733
+ import { readFile } from "fs/promises";
27734
+ import { basename } from "path";
27735
+ import nacl4 from "tweetnacl";
27736
+ import sodium2 from "libsodium-wrappers-sumo";
27737
+ import sharp from "sharp";
27738
+
27739
+ // src/api/file-service.ts
27729
27740
  init_esm_shims();
27730
- var MessagingApi = class {
27741
+ var UPLOAD_PART_SIZE = 6291507;
27742
+ var IMAGE_MIME_TYPES = {
27743
+ ".png": "image/png",
27744
+ ".jpg": "image/jpeg",
27745
+ ".jpeg": "image/jpeg",
27746
+ ".gif": "image/gif",
27747
+ ".webp": "image/webp"
27748
+ };
27749
+ function detectImageMimeType(fileName) {
27750
+ const ext = fileName.includes(".") ? "." + fileName.split(".").pop().toLowerCase() : "";
27751
+ return IMAGE_MIME_TYPES[ext] ?? null;
27752
+ }
27753
+ var FileServiceApi = class {
27731
27754
  constructor(client) {
27732
27755
  this.client = client;
27733
27756
  }
27734
27757
  client;
27735
- async sendMessage(params) {
27736
- const payload = {
27737
- group_chat_id: params.groupChatId,
27738
- notification: {
27739
- status: "ok",
27740
- body: params.body,
27741
- ...params.metadata && { metadata: params.metadata },
27742
- ...params.mentions && { mentions: params.mentions },
27743
- ...params.stealthMode && {
27744
- opts: { stealth_mode: true }
27745
- }
27746
- }
27747
- };
27748
- return this.client.post("/api/v4/botx/notifications/direct", payload);
27749
- }
27750
- async sendFile(params) {
27751
- const payload = {
27752
- group_chat_id: params.groupChatId,
27753
- notification: {
27754
- status: "ok",
27755
- body: params.caption ?? ""
27758
+ /** Uploads an already-encrypted content blob plus an already-encrypted JPEG preview via the resumable protocol. */
27759
+ async uploadWithPreview(init, content, preview) {
27760
+ const resumableId = await this.initUpload(
27761
+ init,
27762
+ `content=${content.length};preview=${preview.data.length},${preview.mimeType}`
27763
+ );
27764
+ await this.uploadPart(resumableId, "content", content);
27765
+ const finalRes = await this.uploadPart(resumableId, "preview", preview.data);
27766
+ const body = await finalRes.json();
27767
+ return body.result;
27768
+ }
27769
+ async initUpload(init, shapes) {
27770
+ const res = await this.client.rawRequest("/api/v2/file_service/resumable", {
27771
+ method: "POST",
27772
+ headers: {
27773
+ "Content-Type": "application/json",
27774
+ "upload-part-size": String(UPLOAD_PART_SIZE),
27775
+ "upload-shapes": shapes
27756
27776
  },
27757
- file: {
27758
- file_name: params.fileName,
27759
- data: params.fileData
27760
- }
27761
- };
27762
- return this.client.post("/api/v4/botx/notifications/direct", payload);
27777
+ body: JSON.stringify(init)
27778
+ });
27779
+ if (!res.ok) {
27780
+ throw new Error(`file upload init failed: ${res.status} ${await res.text()}`);
27781
+ }
27782
+ const resumableId = res.headers.get("upload-resumable-id");
27783
+ if (!resumableId) throw new Error("file upload init: missing upload-resumable-id header");
27784
+ return resumableId;
27785
+ }
27786
+ async uploadPart(resumableId, shape, data) {
27787
+ const res = await this.client.rawRequest(`/api/v2/file_service/resumable/${resumableId}`, {
27788
+ method: "POST",
27789
+ headers: {
27790
+ "Content-Type": "application/octet-stream",
27791
+ "upload-shape": shape,
27792
+ "upload-part-size": String(UPLOAD_PART_SIZE),
27793
+ "upload-range": `bytes=0-${data.length - 1}`
27794
+ },
27795
+ body: data
27796
+ });
27797
+ if (!res.ok) {
27798
+ throw new Error(`file upload part (${shape}) failed: ${res.status} ${await res.text()}`);
27799
+ }
27800
+ return res;
27763
27801
  }
27764
27802
  };
27765
27803
 
27766
- // src/api/messaging-ws.ts
27804
+ // src/api/file-crypto.ts
27767
27805
  init_esm_shims();
27768
- import WebSocket2 from "ws";
27769
- import { randomBytes as randomBytes3, randomUUID as randomUUID4 } from "crypto";
27770
- import nacl4 from "tweetnacl";
27771
27806
  import sodium from "libsodium-wrappers-sumo";
27807
+ var FILE_CHUNK_SIZE = 2097152;
27808
+ async function encryptFileStream(data, key) {
27809
+ await sodium.ready;
27810
+ const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
27811
+ const parts = [header];
27812
+ for (let offset = 0; offset < data.length; offset += FILE_CHUNK_SIZE) {
27813
+ const chunk = data.subarray(offset, offset + FILE_CHUNK_SIZE);
27814
+ const isLast = offset + FILE_CHUNK_SIZE >= data.length;
27815
+ const tag = isLast ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL : sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
27816
+ parts.push(sodium.crypto_secretstream_xchacha20poly1305_push(state, chunk, null, tag));
27817
+ }
27818
+ return Buffer.concat(parts);
27819
+ }
27820
+
27821
+ // src/api/messaging-ws.ts
27772
27822
  init_store();
27773
27823
  function buildTextPayload(text, fromHuid, chatId) {
27774
27824
  return JSON.stringify({
@@ -27784,12 +27834,49 @@ function buildTextPayload(text, fromHuid, chatId) {
27784
27834
  body: text
27785
27835
  });
27786
27836
  }
27837
+ function buildImagePayload(params) {
27838
+ return JSON.stringify({
27839
+ type: "image",
27840
+ msg_id: randomUUID4(),
27841
+ from: params.fromHuid,
27842
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
27843
+ group_chat_id: params.chatId,
27844
+ lat: 0,
27845
+ lng: 0,
27846
+ stealth_forwarding: false,
27847
+ payload: {
27848
+ file: params.contentPath,
27849
+ file_name: params.fileName,
27850
+ file_size: params.fileSize,
27851
+ file_hash: params.fileHash,
27852
+ file_mime_type: params.mimeType,
27853
+ chunk_size: FILE_CHUNK_SIZE,
27854
+ file_encryption_algo: "stream",
27855
+ file_preview: params.previewPath,
27856
+ file_preview_width: params.previewWidth,
27857
+ file_preview_height: params.previewHeight,
27858
+ blur_preview_file: params.blurPreviewDataUri,
27859
+ file_id: params.fileId
27860
+ },
27861
+ body: params.caption
27862
+ });
27863
+ }
27787
27864
  function payloadAad(chatId, syncId) {
27788
27865
  return new Uint8Array(Buffer.from(`${chatId}:${syncId}`));
27789
27866
  }
27867
+ function wrapKeyForRecipients(symmetricKey, publicKeys, ctsKey) {
27868
+ return publicKeys.map((kdcKey) => {
27869
+ const recipientPubKey = new Uint8Array(Buffer.from(kdcKey.body, "base64"));
27870
+ const nonce = randomBytes3(nacl4.box.nonceLength);
27871
+ const ciphertext = nacl4.box(symmetricKey, nonce, recipientPubKey, ctsKey.privateKey);
27872
+ if (!ciphertext) throw new Error(`Failed to encrypt key for ${kdcKey.id}`);
27873
+ const combined = Buffer.concat([nonce, Buffer.from(ciphertext)]);
27874
+ return { key_id: kdcKey.id, key: combined.toString("base64"), algo: "xsalsa20:xchacha20_aead_ietf" };
27875
+ });
27876
+ }
27790
27877
  async function sendMessageViaWebSocket(params) {
27791
27878
  const { client, chatId, body, timeoutMs = 2e4 } = params;
27792
- await sodium.ready;
27879
+ await sodium2.ready;
27793
27880
  const apigwKeys = loadApigwKeys();
27794
27881
  if (!apigwKeys) throw new Error("No apigw keys. Run 'express auth login' first.");
27795
27882
  const config = loadConfig();
@@ -27805,27 +27892,11 @@ async function sendMessageViaWebSocket(params) {
27805
27892
  const publicKeys = kdcKeys.filter((k) => k.kind === "cts");
27806
27893
  const symmetricKey = randomBytes3(32);
27807
27894
  const syncId = randomUUID4();
27808
- const encryptedKeys = publicKeys.map((kdcKey) => {
27809
- const recipientPubKey = new Uint8Array(Buffer.from(kdcKey.body, "base64"));
27810
- const nonce = randomBytes3(nacl4.box.nonceLength);
27811
- const ciphertext2 = nacl4.box(
27812
- symmetricKey,
27813
- nonce,
27814
- recipientPubKey,
27815
- ctsKey.privateKey
27816
- );
27817
- if (!ciphertext2) throw new Error(`Failed to encrypt key for ${kdcKey.id}`);
27818
- const combined = Buffer.concat([nonce, Buffer.from(ciphertext2)]);
27819
- return {
27820
- key_id: kdcKey.id,
27821
- key: combined.toString("base64"),
27822
- algo: "xsalsa20:xchacha20_aead_ietf"
27823
- };
27824
- });
27895
+ const encryptedKeys = wrapKeyForRecipients(symmetricKey, publicKeys, ctsKey);
27825
27896
  const selfHuid = (await new UserApi(client).getSelfProfile()).user_huid;
27826
27897
  const plaintext = new Uint8Array(Buffer.from(buildTextPayload(body, selfHuid, chatId)));
27827
- const msgNonce = randomBytes3(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
27828
- const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
27898
+ const msgNonce = randomBytes3(sodium2.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
27899
+ const ciphertext = sodium2.crypto_aead_xchacha20poly1305_ietf_encrypt(
27829
27900
  plaintext,
27830
27901
  payloadAad(chatId, syncId),
27831
27902
  null,
@@ -27856,6 +27927,114 @@ async function sendMessageViaWebSocket(params) {
27856
27927
  timeoutMs
27857
27928
  });
27858
27929
  }
27930
+ async function buildPreview(imageBytes) {
27931
+ const data = await sharp(imageBytes).resize({ width: 300, height: 300, fit: "inside", withoutEnlargement: true }).jpeg({ quality: 90 }).toBuffer();
27932
+ const blurData = data;
27933
+ const { width, height } = await sharp(data).metadata();
27934
+ return { data, width: width ?? 0, height: height ?? 0, blurData };
27935
+ }
27936
+ async function sendImageViaWebSocket(params) {
27937
+ const { client, chatId, filePath, caption = "", timeoutMs = 3e4 } = params;
27938
+ const fileName = basename(filePath);
27939
+ const mimeType = detectImageMimeType(fileName);
27940
+ if (!mimeType) {
27941
+ throw new Error(
27942
+ `Unsupported file type for '${fileName}'. Only images are currently supported: .png, .jpg, .jpeg, .gif, .webp`
27943
+ );
27944
+ }
27945
+ await sodium2.ready;
27946
+ const apigwKeys = loadApigwKeys();
27947
+ if (!apigwKeys) throw new Error("No apigw keys. Run 'express auth login' first.");
27948
+ const config = loadConfig();
27949
+ const host = new URL(getBaseUrl(config)).hostname;
27950
+ const webOrigin = getWebOrigin(config);
27951
+ const ctsToken = getAuthToken();
27952
+ const ctsKey = apigwKeys.ctsKey ?? apigwKeys.encryptionKey;
27953
+ const encKeyId = ctsKey.keyId;
27954
+ const participantKeyIds = await getChatKeyIds(host, webOrigin, ctsToken, encKeyId, chatId);
27955
+ const kdcKeys = await client.get(
27956
+ `/api/v1/kdc/keys/?ids=${participantKeyIds.join(",")}`
27957
+ ) ?? [];
27958
+ const publicKeys = kdcKeys.filter((k) => k.kind === "cts");
27959
+ const originalBytes = await readFile(filePath);
27960
+ const fileHash = createHash("sha256").update(originalBytes).digest("base64");
27961
+ const preview = await buildPreview(originalBytes);
27962
+ const blurPreviewDataUri = `data:image/jpeg;base64,${preview.blurData.toString("base64")}`;
27963
+ const fileKey = randomBytes3(32);
27964
+ const fileKeys = wrapKeyForRecipients(fileKey, publicKeys, ctsKey);
27965
+ const syncId = randomUUID4();
27966
+ const encryptedContent = await encryptFileStream(originalBytes, fileKey);
27967
+ const encryptedPreview = await encryptFileStream(preview.data, fileKey);
27968
+ const uploaded = await new FileServiceApi(client).uploadWithPreview(
27969
+ {
27970
+ subject: "groupchat_file",
27971
+ subject_id: chatId,
27972
+ visible: true,
27973
+ file_name: fileName,
27974
+ mime_type: mimeType,
27975
+ meta: {
27976
+ sync_id: syncId,
27977
+ kind: "media",
27978
+ chunk_size: FILE_CHUNK_SIZE,
27979
+ file_encryption_algo: "stream",
27980
+ sender_key_id: encKeyId,
27981
+ keys: fileKeys,
27982
+ file_hash: fileHash
27983
+ }
27984
+ },
27985
+ encryptedContent,
27986
+ { data: encryptedPreview, mimeType: "image/jpeg" }
27987
+ );
27988
+ const selfHuid = (await new UserApi(client).getSelfProfile()).user_huid;
27989
+ const plaintext = new Uint8Array(Buffer.from(buildImagePayload({
27990
+ fromHuid: selfHuid,
27991
+ chatId,
27992
+ caption,
27993
+ fileName,
27994
+ fileSize: originalBytes.length,
27995
+ fileHash,
27996
+ mimeType,
27997
+ contentPath: uploaded.content,
27998
+ previewPath: uploaded.content_shapes.preview ?? "",
27999
+ previewWidth: preview.width,
28000
+ previewHeight: preview.height,
28001
+ blurPreviewDataUri,
28002
+ fileId: uploaded.id
28003
+ })));
28004
+ const msgKey = randomBytes3(32);
28005
+ const encryptedKeys = wrapKeyForRecipients(msgKey, publicKeys, ctsKey);
28006
+ const msgNonce = randomBytes3(sodium2.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
28007
+ const ciphertext = sodium2.crypto_aead_xchacha20poly1305_ietf_encrypt(
28008
+ plaintext,
28009
+ payloadAad(chatId, syncId),
28010
+ null,
28011
+ new Uint8Array(msgNonce),
28012
+ new Uint8Array(msgKey)
28013
+ );
28014
+ const encryptedPayload = Buffer.concat([msgNonce, Buffer.from(ciphertext)]).toString("base64");
28015
+ const signingKey = apigwKeys.signingKey;
28016
+ const signBytes = signEd25519(
28017
+ signingKey.privateKey,
28018
+ new Uint8Array(Buffer.from(encryptedPayload, "utf8"))
28019
+ );
28020
+ const signature = {
28021
+ sign: Buffer.from(signBytes).toString("base64"),
28022
+ sign_key_id: signingKey.keyId,
28023
+ sign_algo: "ed25519"
28024
+ };
28025
+ return sendMessageNew({
28026
+ host,
28027
+ webOrigin,
28028
+ ctsToken,
28029
+ encKeyId,
28030
+ chatId,
28031
+ syncId,
28032
+ encryptedKeys,
28033
+ encryptedPayload,
28034
+ signature,
28035
+ timeoutMs
28036
+ });
28037
+ }
27859
28038
  async function getChatKeyIds(host, webOrigin, ctsToken, encKeyId, chatId) {
27860
28039
  const wsUrl = `wss://${host}/socket/user/websocket?vsn=1.0.0&auto_join=true&key_id=${encKeyId}&version=6&background=false&voex_unencrypted=true&instance_id=${randomUUID4()}`;
27861
28040
  return new Promise((resolve, reject) => {
@@ -28024,7 +28203,6 @@ Be more specific.`);
28024
28203
  }
28025
28204
 
28026
28205
  // src/cli/send.ts
28027
- import { readFileSync } from "fs";
28028
28206
  function createSendCommand() {
28029
28207
  const cmd = new Command8("send");
28030
28208
  cmd.description("Send messages and files");
@@ -28039,33 +28217,11 @@ function createSendCommand() {
28039
28217
  process.exit(1);
28040
28218
  }
28041
28219
  });
28042
- cmd.command("file <chat-id> <file-path>").description("Send a file to a chat").option("--caption <caption>", "File caption").option("--host <host>", "eXpress host").option("-o, --output <format>", "Output format", "json").action(async (chatId, filePath, opts) => {
28220
+ cmd.command("file <chat-id-or-name> <file-path>").description("Send a file to a chat (chat ID or partial name). Currently images only: .png, .jpg, .jpeg, .gif, .webp").option("--caption <caption>", "File caption").option("--host <host>", "eXpress host").option("-o, --output <format>", "Output format", "json").action(async (chatIdOrName, filePath, opts) => {
28043
28221
  try {
28044
- const data = readFileSync(filePath);
28045
- const fileName = filePath.split("/").pop() ?? "file";
28046
- const mimeMap = {
28047
- ".png": "image/png",
28048
- ".jpg": "image/jpeg",
28049
- ".jpeg": "image/jpeg",
28050
- ".gif": "image/gif",
28051
- ".pdf": "application/pdf",
28052
- ".txt": "text/plain",
28053
- ".json": "application/json",
28054
- ".csv": "text/csv",
28055
- ".zip": "application/zip"
28056
- };
28057
- const ext = fileName.includes(".") ? "." + fileName.split(".").pop().toLowerCase() : "";
28058
- const mime = mimeMap[ext] ?? "application/octet-stream";
28059
- const base64 = data.toString("base64");
28060
- const dataUri = `data:${mime};base64,${base64}`;
28061
28222
  const client = new ApiClient(opts.host ? { host: opts.host } : void 0);
28062
- const api = new MessagingApi(client);
28063
- const result = await api.sendFile({
28064
- groupChatId: chatId,
28065
- fileName,
28066
- fileData: dataUri,
28067
- caption: opts.caption
28068
- });
28223
+ const chatId = await resolveChatId(client, chatIdOrName);
28224
+ const result = await sendImageViaWebSocket({ client, chatId, filePath, caption: opts.caption });
28069
28225
  console.log(formatOutput(result, opts.output));
28070
28226
  } catch (err) {
28071
28227
  console.error(`Error: ${err.message}`);
@@ -28191,7 +28347,7 @@ init_store();
28191
28347
  // src/api/decrypt.ts
28192
28348
  init_esm_shims();
28193
28349
  import nacl5 from "tweetnacl";
28194
- import sodium2 from "libsodium-wrappers-sumo";
28350
+ import sodium3 from "libsodium-wrappers-sumo";
28195
28351
  function decryptMessage(msg, ctsPrivateKey, myKeyId, keyMap, apigwKeys) {
28196
28352
  const encKey = msg.key;
28197
28353
  if (encKey.key_id !== myKeyId) {
@@ -28205,10 +28361,10 @@ function decryptMessage(msg, ctsPrivateKey, myKeyId, keyMap, apigwKeys) {
28205
28361
  const symmetricKey = nacl5.box.open(ciphertext, nonce, senderPubKey, ctsPrivateKey);
28206
28362
  if (!symmetricKey) throw new Error("nacl.box.open failed \u2014 wrong key pair");
28207
28363
  const payloadRaw = Uint8Array.from(Buffer.from(msg.payload, "base64"));
28208
- const msgNonce = payloadRaw.slice(0, sodium2.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
28209
- const msgCiphertext = payloadRaw.slice(sodium2.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
28364
+ const msgNonce = payloadRaw.slice(0, sodium3.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
28365
+ const msgCiphertext = payloadRaw.slice(sodium3.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
28210
28366
  const aad = new Uint8Array(Buffer.from(`${msg.group_chat_id}:${msg.sync_id}`));
28211
- const plaintext = sodium2.crypto_aead_xchacha20poly1305_ietf_decrypt(null, msgCiphertext, aad, msgNonce, symmetricKey);
28367
+ const plaintext = sodium3.crypto_aead_xchacha20poly1305_ietf_decrypt(null, msgCiphertext, aad, msgNonce, symmetricKey);
28212
28368
  return JSON.parse(new TextDecoder().decode(plaintext));
28213
28369
  }
28214
28370
  function toDecrypted(msg, payload) {
@@ -28239,7 +28395,7 @@ function getSenderPublicKey(msg, keyMap, apigwKeys) {
28239
28395
  return null;
28240
28396
  }
28241
28397
  async function decryptMessages(events, apigwKeys, client = new ApiClient()) {
28242
- await sodium2.ready;
28398
+ await sodium3.ready;
28243
28399
  const ctsKey = apigwKeys.ctsKey ?? apigwKeys.encryptionKey;
28244
28400
  const messages = events.filter((e) => e.event_type === "message_new" && e.payload && e.key);
28245
28401
  if (messages.length === 0) return [];
@@ -38587,7 +38743,7 @@ init_esm_shims();
38587
38743
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
38588
38744
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
38589
38745
  import { z as z2 } from "zod";
38590
- import { writeFileSync as writeFileSync3, readFileSync as readFileSync3, unlinkSync } from "fs";
38746
+ import { writeFileSync as writeFileSync3, readFileSync as readFileSync2, unlinkSync } from "fs";
38591
38747
  import { tmpdir } from "os";
38592
38748
  import { join } from "path";
38593
38749
  init_store();
@@ -38828,7 +38984,7 @@ Scan with the eXpress mobile app.
38828
38984
  if (!material || !poll) {
38829
38985
  let saved = null;
38830
38986
  try {
38831
- saved = JSON.parse(readFileSync3(QR_MATERIAL_FILE, "utf8"));
38987
+ saved = JSON.parse(readFileSync2(QR_MATERIAL_FILE, "utf8"));
38832
38988
  } catch {
38833
38989
  }
38834
38990
  if (!saved) return ok("No pending QR session. Call auth_qr_start first.");
@@ -38885,7 +39041,7 @@ function createMcpCommand() {
38885
39041
  // src/cli/root.ts
38886
39042
  function createRootCommand() {
38887
39043
  const program2 = new Command15();
38888
- program2.name("express-cli").description("CLI client for eXpress Chat").version("0.1.5");
39044
+ program2.name("express-cli").description("CLI client for eXpress Chat").version("0.1.6");
38889
39045
  program2.addCommand(createAuthCommand());
38890
39046
  program2.addCommand(createApiCommand());
38891
39047
  program2.addCommand(createConfigCommand());