@ih8e/express-cli 0.1.4 → 0.1.5

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/dist/index.js CHANGED
@@ -49,6 +49,219 @@ var init_esm_shims = __esm({
49
49
  }
50
50
  });
51
51
 
52
+ // src/types/express.ts
53
+ var init_express = __esm({
54
+ "src/types/express.ts"() {
55
+ "use strict";
56
+ init_esm_shims();
57
+ }
58
+ });
59
+
60
+ // src/types/api.ts
61
+ var init_api = __esm({
62
+ "src/types/api.ts"() {
63
+ "use strict";
64
+ init_esm_shims();
65
+ }
66
+ });
67
+
68
+ // src/types/config.ts
69
+ import { z } from "zod";
70
+ var configSchema, envSchema;
71
+ var init_config = __esm({
72
+ "src/types/config.ts"() {
73
+ "use strict";
74
+ init_esm_shims();
75
+ configSchema = z.object({
76
+ host: z.string().min(1, "host is required \u2014 set EXPRESS_HOST or configure via `express config set host <host>`"),
77
+ protocol: z.enum(["https", "http"]).default("https"),
78
+ token: z.string().optional(),
79
+ locale: z.string().default("ru"),
80
+ platform: z.string().default("web"),
81
+ platform_package_id: z.string().default("ru.alfabank"),
82
+ app_version: z.string().default("3.66.47"),
83
+ output: z.enum(["table", "json"]).default("table")
84
+ });
85
+ envSchema = z.object({
86
+ EXPRESS_HOST: z.string().optional(),
87
+ EXPRESS_TOKEN: z.string().optional(),
88
+ EXPRESS_LOCALE: z.string().optional(),
89
+ EXPRESS_OUTPUT: z.enum(["table", "json"]).optional()
90
+ });
91
+ }
92
+ });
93
+
94
+ // src/types/index.ts
95
+ var init_types = __esm({
96
+ "src/types/index.ts"() {
97
+ "use strict";
98
+ init_esm_shims();
99
+ init_express();
100
+ init_api();
101
+ init_config();
102
+ }
103
+ });
104
+
105
+ // src/config/store.ts
106
+ import Conf from "conf";
107
+ function getStoredConfig() {
108
+ return store.get("config") ?? {};
109
+ }
110
+ function setStoredConfig(partial) {
111
+ const current = getStoredConfig();
112
+ store.set("config", { ...current, ...partial });
113
+ }
114
+ function getAuthToken() {
115
+ return store.get("authToken") ?? null;
116
+ }
117
+ function setAuthToken(token) {
118
+ if (token === null) {
119
+ store.delete("authToken");
120
+ } else {
121
+ store.set("authToken", token);
122
+ }
123
+ }
124
+ function getRtsAuthToken() {
125
+ return store.get("rtsAuthToken") ?? null;
126
+ }
127
+ function setRtsAuthToken(token) {
128
+ if (token === null) {
129
+ store.delete("rtsAuthToken");
130
+ } else {
131
+ store.set("rtsAuthToken", token);
132
+ }
133
+ }
134
+ function getRefreshToken() {
135
+ return store.get("refreshToken") ?? null;
136
+ }
137
+ function setRefreshToken(token) {
138
+ if (token === null) {
139
+ store.delete("refreshToken");
140
+ } else {
141
+ store.set("refreshToken", token);
142
+ }
143
+ }
144
+ function getTokenExpiresAt() {
145
+ return store.get("tokenExpiresAt") ?? null;
146
+ }
147
+ function setTokenExpiresAt(expiresAt) {
148
+ if (expiresAt === null) {
149
+ store.delete("tokenExpiresAt");
150
+ } else {
151
+ store.set("tokenExpiresAt", expiresAt);
152
+ }
153
+ }
154
+ function calcTokenExpiresAt(expiresIn) {
155
+ return Date.now() + Math.floor(expiresIn / 2) * 1e3;
156
+ }
157
+ function isTokenExpiringSoon() {
158
+ const expiresAt = getTokenExpiresAt();
159
+ if (!expiresAt) return true;
160
+ return Date.now() >= expiresAt;
161
+ }
162
+ function getEtsAuthToken() {
163
+ return store.get("etsAuthToken") ?? null;
164
+ }
165
+ function setEtsAuthToken(token) {
166
+ if (token === null) {
167
+ store.delete("etsAuthToken");
168
+ } else {
169
+ store.set("etsAuthToken", token);
170
+ }
171
+ }
172
+ function getApigwKeysRaw() {
173
+ return store.get("apigwKeys") ?? null;
174
+ }
175
+ function setApigwKeysRaw(data) {
176
+ if (data === null) {
177
+ store.delete("apigwKeys");
178
+ } else {
179
+ store.set("apigwKeys", data);
180
+ }
181
+ }
182
+ function clearAll() {
183
+ store.clear();
184
+ }
185
+ var store;
186
+ var init_store = __esm({
187
+ "src/config/store.ts"() {
188
+ "use strict";
189
+ init_esm_shims();
190
+ store = new Conf({
191
+ projectName: "express-cli",
192
+ defaults: {
193
+ config: {},
194
+ authToken: null,
195
+ refreshToken: null,
196
+ rtsAuthToken: null,
197
+ apigwKeys: null,
198
+ tokenExpiresAt: null,
199
+ etsAuthToken: null
200
+ }
201
+ });
202
+ }
203
+ });
204
+
205
+ // src/config/loader.ts
206
+ var loader_exports = {};
207
+ __export(loader_exports, {
208
+ getBaseUrl: () => getBaseUrl,
209
+ getEtsBaseUrl: () => getEtsBaseUrl,
210
+ getWebOrigin: () => getWebOrigin,
211
+ loadConfig: () => loadConfig
212
+ });
213
+ import { ZodError } from "zod";
214
+ function loadConfig(cliOverrides = {}) {
215
+ const stored = getStoredConfig();
216
+ const env2 = {};
217
+ if (process.env.EXPRESS_HOST) env2.host = process.env.EXPRESS_HOST;
218
+ if (process.env.EXPRESS_TOKEN) env2.token = process.env.EXPRESS_TOKEN;
219
+ if (process.env.EXPRESS_LOCALE) env2.locale = process.env.EXPRESS_LOCALE;
220
+ if (process.env.EXPRESS_OUTPUT) env2.output = process.env.EXPRESS_OUTPUT;
221
+ const merged = {
222
+ ...stored,
223
+ ...env2,
224
+ ...cliOverrides
225
+ };
226
+ if (!merged.token) {
227
+ const storedToken = getAuthToken();
228
+ if (storedToken) merged.token = storedToken;
229
+ }
230
+ try {
231
+ return configSchema.parse(merged);
232
+ } catch (err) {
233
+ if (err instanceof ZodError) {
234
+ const details = err.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
235
+ throw new Error(`Configuration error:
236
+ ${details}
237
+
238
+ Run: express-cli config set host <hostname>`);
239
+ }
240
+ throw err;
241
+ }
242
+ }
243
+ function getBaseUrl(config) {
244
+ return `${config.protocol}://${config.host}`;
245
+ }
246
+ function getDomain(ctsHost) {
247
+ const parts = ctsHost.split(".");
248
+ return parts.length > 2 ? parts.slice(1).join(".") : ctsHost;
249
+ }
250
+ function getEtsBaseUrl(config) {
251
+ return `https://ets.${getDomain(config.host)}`;
252
+ }
253
+ function getWebOrigin(config) {
254
+ return `https://${getDomain(config.host)}`;
255
+ }
256
+ var init_loader = __esm({
257
+ "src/config/loader.ts"() {
258
+ "use strict";
259
+ init_esm_shims();
260
+ init_types();
261
+ init_store();
262
+ }
263
+ });
264
+
52
265
  // node_modules/react/cjs/react.production.js
53
266
  var require_react_production = __commonJS({
54
267
  "node_modules/react/cjs/react.production.js"(exports) {
@@ -25062,179 +25275,11 @@ init_esm_shims();
25062
25275
 
25063
25276
  // src/config/index.ts
25064
25277
  init_esm_shims();
25065
-
25066
- // src/config/loader.ts
25067
- init_esm_shims();
25068
- import { ZodError } from "zod";
25069
-
25070
- // src/types/index.ts
25071
- init_esm_shims();
25072
-
25073
- // src/types/express.ts
25074
- init_esm_shims();
25075
-
25076
- // src/types/api.ts
25077
- init_esm_shims();
25078
-
25079
- // src/types/config.ts
25080
- init_esm_shims();
25081
- import { z } from "zod";
25082
- var configSchema = z.object({
25083
- host: z.string().min(1, "host is required \u2014 set EXPRESS_HOST or configure via `express config set host <host>`"),
25084
- protocol: z.enum(["https", "http"]).default("https"),
25085
- token: z.string().optional(),
25086
- locale: z.string().default("ru"),
25087
- platform: z.string().default("web"),
25088
- platform_package_id: z.string().default("ru.alfabank"),
25089
- app_version: z.string().default("3.66.47"),
25090
- output: z.enum(["table", "json"]).default("table")
25091
- });
25092
- var envSchema = z.object({
25093
- EXPRESS_HOST: z.string().optional(),
25094
- EXPRESS_TOKEN: z.string().optional(),
25095
- EXPRESS_LOCALE: z.string().optional(),
25096
- EXPRESS_OUTPUT: z.enum(["table", "json"]).optional()
25097
- });
25098
-
25099
- // src/config/store.ts
25100
- init_esm_shims();
25101
- import Conf from "conf";
25102
- var store = new Conf({
25103
- projectName: "express-cli",
25104
- defaults: {
25105
- config: {},
25106
- authToken: null,
25107
- refreshToken: null,
25108
- rtsAuthToken: null,
25109
- apigwKeys: null,
25110
- tokenExpiresAt: null,
25111
- etsAuthToken: null
25112
- }
25113
- });
25114
- function getStoredConfig() {
25115
- return store.get("config") ?? {};
25116
- }
25117
- function setStoredConfig(partial) {
25118
- const current = getStoredConfig();
25119
- store.set("config", { ...current, ...partial });
25120
- }
25121
- function getAuthToken() {
25122
- return store.get("authToken") ?? null;
25123
- }
25124
- function setAuthToken(token) {
25125
- if (token === null) {
25126
- store.delete("authToken");
25127
- } else {
25128
- store.set("authToken", token);
25129
- }
25130
- }
25131
- function getRtsAuthToken() {
25132
- return store.get("rtsAuthToken") ?? null;
25133
- }
25134
- function setRtsAuthToken(token) {
25135
- if (token === null) {
25136
- store.delete("rtsAuthToken");
25137
- } else {
25138
- store.set("rtsAuthToken", token);
25139
- }
25140
- }
25141
- function getRefreshToken() {
25142
- return store.get("refreshToken") ?? null;
25143
- }
25144
- function setRefreshToken(token) {
25145
- if (token === null) {
25146
- store.delete("refreshToken");
25147
- } else {
25148
- store.set("refreshToken", token);
25149
- }
25150
- }
25151
- function getTokenExpiresAt() {
25152
- return store.get("tokenExpiresAt") ?? null;
25153
- }
25154
- function setTokenExpiresAt(expiresAt) {
25155
- if (expiresAt === null) {
25156
- store.delete("tokenExpiresAt");
25157
- } else {
25158
- store.set("tokenExpiresAt", expiresAt);
25159
- }
25160
- }
25161
- function calcTokenExpiresAt(expiresIn) {
25162
- return Date.now() + Math.floor(expiresIn / 2) * 1e3;
25163
- }
25164
- function isTokenExpiringSoon() {
25165
- const expiresAt = getTokenExpiresAt();
25166
- if (!expiresAt) return true;
25167
- return Date.now() >= expiresAt;
25168
- }
25169
- function getEtsAuthToken() {
25170
- return store.get("etsAuthToken") ?? null;
25171
- }
25172
- function setEtsAuthToken(token) {
25173
- if (token === null) {
25174
- store.delete("etsAuthToken");
25175
- } else {
25176
- store.set("etsAuthToken", token);
25177
- }
25178
- }
25179
- function getApigwKeysRaw() {
25180
- return store.get("apigwKeys") ?? null;
25181
- }
25182
- function setApigwKeysRaw(data) {
25183
- if (data === null) {
25184
- store.delete("apigwKeys");
25185
- } else {
25186
- store.set("apigwKeys", data);
25187
- }
25188
- }
25189
- function clearAll() {
25190
- store.clear();
25191
- }
25192
-
25193
- // src/config/loader.ts
25194
- function loadConfig(cliOverrides = {}) {
25195
- const stored = getStoredConfig();
25196
- const env2 = {};
25197
- if (process.env.EXPRESS_HOST) env2.host = process.env.EXPRESS_HOST;
25198
- if (process.env.EXPRESS_TOKEN) env2.token = process.env.EXPRESS_TOKEN;
25199
- if (process.env.EXPRESS_LOCALE) env2.locale = process.env.EXPRESS_LOCALE;
25200
- if (process.env.EXPRESS_OUTPUT) env2.output = process.env.EXPRESS_OUTPUT;
25201
- const merged = {
25202
- ...stored,
25203
- ...env2,
25204
- ...cliOverrides
25205
- };
25206
- if (!merged.token) {
25207
- const storedToken = getAuthToken();
25208
- if (storedToken) merged.token = storedToken;
25209
- }
25210
- try {
25211
- return configSchema.parse(merged);
25212
- } catch (err) {
25213
- if (err instanceof ZodError) {
25214
- const details = err.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
25215
- throw new Error(`Configuration error:
25216
- ${details}
25217
-
25218
- Run: express-cli config set host <hostname>`);
25219
- }
25220
- throw err;
25221
- }
25222
- }
25223
- function getBaseUrl(config) {
25224
- return `${config.protocol}://${config.host}`;
25225
- }
25226
- function getDomain(ctsHost) {
25227
- const parts = ctsHost.split(".");
25228
- return parts.length > 2 ? parts.slice(1).join(".") : ctsHost;
25229
- }
25230
- function getEtsBaseUrl(config) {
25231
- return `https://ets.${getDomain(config.host)}`;
25232
- }
25233
- function getWebOrigin(config) {
25234
- return `https://${getDomain(config.host)}`;
25235
- }
25278
+ init_loader();
25279
+ init_store();
25236
25280
 
25237
25281
  // src/auth/import.ts
25282
+ init_loader();
25238
25283
  async function importToken(token, cliOverrides = {}) {
25239
25284
  const config = loadConfig(cliOverrides);
25240
25285
  const baseUrl = getBaseUrl(config);
@@ -25275,6 +25320,7 @@ init_esm_shims();
25275
25320
 
25276
25321
  // src/auth/keys.ts
25277
25322
  init_esm_shims();
25323
+ init_store();
25278
25324
  import { ed25519 } from "@noble/curves/ed25519.js";
25279
25325
  import { randomBytes } from "crypto";
25280
25326
  import nacl from "tweetnacl";
@@ -25561,6 +25607,8 @@ ${signingString}`);
25561
25607
  }
25562
25608
 
25563
25609
  // src/auth/device-login.ts
25610
+ init_store();
25611
+ init_loader();
25564
25612
  import nacl2 from "tweetnacl";
25565
25613
  import { randomUUID } from "crypto";
25566
25614
  async function fetchCurrentAccountCtsKey(baseUrl, token, userHuid, webOrigin) {
@@ -25958,6 +26006,30 @@ Minting a new one would break your other devices. Import the shared key instead:
25958
26006
 
25959
26007
  // src/auth/qr-login.ts
25960
26008
  init_esm_shims();
26009
+
26010
+ // src/auth/qr-browser.ts
26011
+ init_esm_shims();
26012
+ import { execFile } from "child_process";
26013
+ import QRCode from "qrcode";
26014
+ async function openQrInBrowser(payload, registrationId) {
26015
+ let pngDataUrl;
26016
+ try {
26017
+ pngDataUrl = await QRCode.toDataURL(payload, { scale: 8, margin: 2 });
26018
+ } catch {
26019
+ return;
26020
+ }
26021
+ const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>eXpress QR</title><style>body{font-family:sans-serif;background:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0}h2{color:#1a1a1a;margin-bottom:16px}img{width:280px;height:280px;image-rendering:pixelated;border:1px solid #eee;border-radius:8px}p{color:#999;font-size:11px;margin-top:12px;font-family:monospace}</style></head><body><h2>Scan with eXpress</h2><img src="${pngDataUrl}" alt="QR"><p>${registrationId}</p></body></html>`;
26022
+ const htmlDataUrl = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
26023
+ try {
26024
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
26025
+ execFile(opener, [htmlDataUrl]);
26026
+ } catch {
26027
+ }
26028
+ }
26029
+
26030
+ // src/auth/qr-login.ts
26031
+ init_store();
26032
+ init_loader();
25961
26033
  import { randomUUID as randomUUID2, randomBytes as randomBytes2 } from "crypto";
25962
26034
  import qrcode from "qrcode-terminal";
25963
26035
  import nacl3 from "tweetnacl";
@@ -25992,16 +26064,20 @@ function commonHeaders(webOrigin) {
25992
26064
  "sec-ch-ua-platform": '"macOS"'
25993
26065
  };
25994
26066
  }
25995
- async function qrLogin(cliOverrides = {}) {
26067
+ function buildQrMaterial(cliOverrides = {}) {
25996
26068
  const config = loadConfig(cliOverrides);
25997
- const etsBaseUrl = getEtsBaseUrl(config);
25998
- const webOrigin = getWebOrigin(config);
25999
26069
  const qrSigningKey = generateSigningKeyPair();
26000
26070
  const registrationId = qrSigningKey.keyId;
26001
26071
  const registrationToken = Buffer.from(randomBytes2(64)).toString("base64");
26002
26072
  const signPubKey = publicKeyToBase64(qrSigningKey.publicKey);
26003
26073
  const udid = randomUUID2();
26004
26074
  const encryptionKey = randomBytes2(32);
26075
+ const qrPayload = JSON.stringify({
26076
+ registration_id: registrationId,
26077
+ registration_token: registrationToken,
26078
+ registration_key: Buffer.from(encryptionKey).toString("base64"),
26079
+ version: 1
26080
+ });
26005
26081
  const qrBody = JSON.stringify({
26006
26082
  registration_id: registrationId,
26007
26083
  registration_token: registrationToken,
@@ -26021,63 +26097,53 @@ async function qrLogin(cliOverrides = {}) {
26021
26097
  platform: "web",
26022
26098
  platform_package_id: "com.pyligrim.alphach"
26023
26099
  });
26024
- const qrPayload = JSON.stringify({
26025
- registration_id: registrationId,
26026
- registration_token: registrationToken,
26027
- registration_key: Buffer.from(encryptionKey).toString("base64"),
26028
- version: 1
26029
- });
26030
- console.log("Step 1/7: Scan this QR code with your eXpress app:\n");
26031
- qrcode.generate(qrPayload, { small: true }, (qr) => {
26032
- console.log(qr);
26033
- });
26034
- console.log(`
26035
- registration_id: ${registrationId}`);
26036
- console.log(" Waiting for scan (server long-polling)...\n");
26100
+ return { registrationId, encryptionKey, qrSigningKey, udid, qrPayload, qrBody, config };
26101
+ }
26102
+ async function pollForQrScan(mat) {
26103
+ const etsBaseUrl = getEtsBaseUrl(mat.config);
26104
+ const webOrigin = getWebOrigin(mat.config);
26037
26105
  const etsUrl = `${etsBaseUrl}/api/v1/authentication/qr/mobile_to_web/request`;
26038
26106
  const qrHeaders = signQrRequest({
26039
26107
  method: "POST",
26040
26108
  url: etsUrl,
26041
- body: qrBody,
26042
- registrationId,
26043
- privateKey: qrSigningKey.privateKey
26109
+ body: mat.qrBody,
26110
+ registrationId: mat.registrationId,
26111
+ privateKey: mat.qrSigningKey.privateKey
26044
26112
  });
26045
- let qrRes;
26113
+ let res;
26046
26114
  try {
26047
- qrRes = await fetch(etsUrl, {
26115
+ res = await fetch(etsUrl, {
26048
26116
  method: "POST",
26049
26117
  headers: { ...commonHeaders(webOrigin), ...qrHeaders },
26050
- body: qrBody
26118
+ body: mat.qrBody
26051
26119
  });
26052
26120
  } catch (err) {
26053
26121
  throw new Error(`QR request network error: ${err.message}`);
26054
26122
  }
26055
- const qrText = await qrRes.text();
26056
- if (!qrRes.ok) {
26057
- console.log(` Response (${qrRes.status}): ${qrText.slice(0, 500)}`);
26058
- throw new Error(`QR request failed (${qrRes.status}): ${qrText.slice(0, 500)}`);
26059
- }
26060
- let qrData;
26061
- try {
26062
- qrData = JSON.parse(qrText);
26063
- } catch {
26064
- throw new Error(`Invalid QR response: ${qrText.slice(0, 500)}`);
26065
- }
26066
- if (process.env.EXPRESS_DEBUG) {
26067
- console.log(` [DEBUG] QR full response: ${qrText.slice(0, 1e3)}`);
26123
+ const text = await res.text();
26124
+ if (!res.ok) throw new Error(`QR request failed (${res.status}): ${text.slice(0, 500)}`);
26125
+ const data = JSON.parse(text);
26126
+ if (process.env.EXPRESS_DEBUG) process.stderr.write(`[qr] full response: ${text.slice(0, 1e3)}
26127
+ `);
26128
+ const result = extractResult2(data);
26129
+ const ctsRegistrationToken = result.cts_registration_token ?? "";
26130
+ const rtsRegistrationToken = result.rts_registration_token ?? "";
26131
+ const registrationData = result.registration_data ?? "";
26132
+ if (!ctsRegistrationToken && !rtsRegistrationToken) {
26133
+ throw new Error(`No tokens in QR response: ${text.slice(0, 500)}`);
26068
26134
  }
26069
- const qrResult = extractResult2(qrData);
26070
- const ctsRegistrationToken = qrResult.cts_registration_token ?? "";
26071
- const rtsRegistrationToken = qrResult.rts_registration_token ?? "";
26072
- const registrationData = qrResult.registration_data ?? "";
26073
- console.log(" QR scanned! Got tokens from server.");
26135
+ return { ctsRegistrationToken, rtsRegistrationToken, registrationData };
26136
+ }
26137
+ async function completeQrRegistration(mat, poll, log = console.log) {
26138
+ const { config, registrationId, encryptionKey, qrSigningKey } = mat;
26139
+ const { ctsRegistrationToken, rtsRegistrationToken, registrationData } = poll;
26140
+ const etsBaseUrl = getEtsBaseUrl(config);
26141
+ const webOrigin = getWebOrigin(config);
26142
+ log(" QR scanned! Got tokens from server.");
26074
26143
  if (process.env.EXPRESS_DEBUG) {
26075
- console.log(` [DEBUG] registration_data length: ${registrationData.length}`);
26076
- console.log(` [DEBUG] registration_data raw: ${registrationData.slice(0, 100)}...`);
26077
- console.log(` [DEBUG] encryptionKey (registration_key) hex: ${Buffer.from(encryptionKey).toString("hex")}`);
26078
- }
26079
- if (!ctsRegistrationToken && !rtsRegistrationToken) {
26080
- throw new Error(`No tokens in QR response: ${qrText.slice(0, 500)}`);
26144
+ log(` [DEBUG] registration_data length: ${registrationData.length}`);
26145
+ log(` [DEBUG] registration_data raw: ${registrationData.slice(0, 100)}...`);
26146
+ log(` [DEBUG] encryptionKey (registration_key) hex: ${Buffer.from(encryptionKey).toString("hex")}`);
26081
26147
  }
26082
26148
  let rtsPrivateKey = null;
26083
26149
  let rtsPublicKeyId = "";
@@ -26087,14 +26153,14 @@ async function qrLogin(cliOverrides = {}) {
26087
26153
  try {
26088
26154
  const raw = Uint8Array.from(Buffer.from(registrationData, "base64"));
26089
26155
  if (process.env.EXPRESS_DEBUG) {
26090
- console.log(` [DEBUG] registration_data decoded length: ${raw.length}`);
26091
- console.log(` [DEBUG] first 40 bytes hex: ${Buffer.from(raw.slice(0, 40)).toString("hex")}`);
26092
- console.log(` [DEBUG] encryptionKey hex: ${Buffer.from(encryptionKey).toString("hex")}`);
26093
- console.log(` [DEBUG] encryptionKey length: ${encryptionKey.length}`);
26156
+ log(` [DEBUG] registration_data decoded length: ${raw.length}`);
26157
+ log(` [DEBUG] first 40 bytes hex: ${Buffer.from(raw.slice(0, 40)).toString("hex")}`);
26158
+ log(` [DEBUG] encryptionKey hex: ${Buffer.from(encryptionKey).toString("hex")}`);
26159
+ log(` [DEBUG] encryptionKey length: ${encryptionKey.length}`);
26094
26160
  }
26095
26161
  const decrypted = decryptRegistrationData(registrationData, encryptionKey);
26096
26162
  if (process.env.EXPRESS_DEBUG) {
26097
- console.log(" Decrypted registration_data:", JSON.stringify(decrypted).slice(0, 500));
26163
+ log(" Decrypted registration_data: " + JSON.stringify(decrypted).slice(0, 500));
26098
26164
  }
26099
26165
  if (decrypted && typeof decrypted === "object") {
26100
26166
  const data = decrypted;
@@ -26110,10 +26176,10 @@ async function qrLogin(cliOverrides = {}) {
26110
26176
  }
26111
26177
  }
26112
26178
  } catch (err) {
26113
- console.log(` Warning: could not decrypt registration_data: ${err.message}`);
26179
+ log(` Warning: could not decrypt registration_data: ${err.message}`);
26114
26180
  }
26115
26181
  }
26116
- console.log("\nStep 2/7: Confirming with ETS...");
26182
+ log("\nStep 2: Confirming with ETS...");
26117
26183
  const confirmUrl = `${etsBaseUrl}/api/v1/authentication/register_confirm/qr`;
26118
26184
  const confirmBody = JSON.stringify({
26119
26185
  registration_id: registrationId,
@@ -26132,23 +26198,17 @@ async function qrLogin(cliOverrides = {}) {
26132
26198
  body: confirmBody
26133
26199
  });
26134
26200
  const confirmText = await confirmRes.text();
26135
- if (!confirmRes.ok) {
26136
- throw new Error(`ETS register_confirm failed (${confirmRes.status}): ${confirmText.slice(0, 500)}`);
26137
- }
26201
+ if (!confirmRes.ok) throw new Error(`ETS register_confirm failed (${confirmRes.status}): ${confirmText.slice(0, 500)}`);
26138
26202
  const confirmData = extractResult2(JSON.parse(confirmText));
26139
26203
  const userHuid = confirmData.user_huid ?? "";
26140
26204
  const etsAuthToken = confirmData.auth_token ?? "";
26141
- console.log(` ETS confirmed. User: ${userHuid || "unknown"}`);
26205
+ log(` ETS confirmed. User: ${userHuid || "unknown"}`);
26142
26206
  if (etsAuthToken) {
26143
26207
  setEtsAuthToken(etsAuthToken);
26144
- if (process.env.EXPRESS_DEBUG) {
26145
- console.log(` [DEBUG] ETS auth_token saved (${etsAuthToken.length} chars)`);
26146
- }
26208
+ if (process.env.EXPRESS_DEBUG) log(` [DEBUG] ETS auth_token saved (${etsAuthToken.length} chars)`);
26147
26209
  }
26148
- if (!ctsRegistrationToken) {
26149
- throw new Error("No cts_registration_token \u2014 cannot confirm with CTS");
26150
- }
26151
- console.log("\nStep 3/7: Confirming with CTS (AD integration)...");
26210
+ if (!ctsRegistrationToken) throw new Error("No cts_registration_token \u2014 cannot confirm with CTS");
26211
+ log("\nStep 3: Confirming with CTS (AD integration)...");
26152
26212
  const ctsUrl = `${getBaseUrl(config)}/api/v1/ad_integration/register_confirm/qr`;
26153
26213
  const adConfirmBody = JSON.stringify({
26154
26214
  rts_registration_id: registrationId,
@@ -26168,9 +26228,7 @@ async function qrLogin(cliOverrides = {}) {
26168
26228
  body: adConfirmBody
26169
26229
  });
26170
26230
  const adText = await adRes.text();
26171
- if (!adRes.ok) {
26172
- throw new Error(`AD integration confirm failed (${adRes.status}): ${adText.slice(0, 500)}`);
26173
- }
26231
+ if (!adRes.ok) throw new Error(`AD integration confirm failed (${adRes.status}): ${adText.slice(0, 500)}`);
26174
26232
  const adData = extractResult2(JSON.parse(adText));
26175
26233
  const accessToken = adData.access_token;
26176
26234
  const refreshToken2 = adData.refresh_token;
@@ -26179,26 +26237,19 @@ async function qrLogin(cliOverrides = {}) {
26179
26237
  const encryptedRtsToken = adData.encrypted_rts_token;
26180
26238
  if (process.env.EXPRESS_DEBUG) {
26181
26239
  const adDataRaw = JSON.parse(adText);
26182
- console.log(` [DEBUG] AD confirm full result keys: ${JSON.stringify(Object.keys(adDataRaw.result || adDataRaw))}`);
26183
- if (encryptedRtsToken) {
26184
- console.log(` [DEBUG] encrypted_rts_token found: ${encryptedRtsToken.slice(0, 60)}...`);
26185
- } else {
26186
- console.log(` [DEBUG] encrypted_rts_token NOT found in response`);
26187
- }
26188
- }
26189
- if (!accessToken) {
26190
- throw new Error(`No access_token in AD confirm response: ${adText.slice(0, 500)}`);
26240
+ log(` [DEBUG] AD confirm full result keys: ${JSON.stringify(Object.keys(adDataRaw.result || adDataRaw))}`);
26241
+ if (encryptedRtsToken) log(` [DEBUG] encrypted_rts_token found: ${encryptedRtsToken.slice(0, 60)}...`);
26242
+ else log(` [DEBUG] encrypted_rts_token NOT found in response`);
26191
26243
  }
26244
+ if (!accessToken) throw new Error(`No access_token in AD confirm response: ${adText.slice(0, 500)}`);
26192
26245
  setAuthToken(accessToken);
26193
- if (refreshToken2) {
26194
- setRefreshToken(refreshToken2);
26195
- }
26246
+ if (refreshToken2) setRefreshToken(refreshToken2);
26196
26247
  if (typeof expiresIn === "number") {
26197
26248
  setTokenExpiresAt(calcTokenExpiresAt(expiresIn));
26198
- console.log(` Token expires in ${expiresIn}s (refresh after ${(expiresIn / 2 / 60).toFixed(0)} min)`);
26249
+ log(` Token expires in ${expiresIn}s (refresh after ${(expiresIn / 2 / 60).toFixed(0)} min)`);
26199
26250
  }
26200
- console.log(` CTS confirmed. Access token: ${accessToken.slice(0, 40)}...`);
26201
- console.log("\nStep 4/7: Registering device token...");
26251
+ log(` CTS confirmed. Access token: ${accessToken.slice(0, 40)}...`);
26252
+ log("\nStep 4: Registering device token...");
26202
26253
  const tokenUrl = `${getBaseUrl(config)}/api/v1/ad_integration/token`;
26203
26254
  const tokenBody = JSON.stringify({
26204
26255
  app_version: config.app_version,
@@ -26217,23 +26268,19 @@ async function qrLogin(cliOverrides = {}) {
26217
26268
  });
26218
26269
  const tokenRes = await fetch(tokenUrl, {
26219
26270
  method: "PUT",
26220
- headers: {
26221
- ...commonHeaders(webOrigin),
26222
- Authorization: `Bearer ${accessToken}`,
26223
- "Content-Type": "application/json"
26224
- },
26271
+ headers: { ...commonHeaders(webOrigin), Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
26225
26272
  body: tokenBody
26226
26273
  });
26227
26274
  if (!tokenRes.ok) {
26228
26275
  const tokenErrText = await tokenRes.text().catch(() => "");
26229
- console.log(` Warning: device token registration failed (${tokenRes.status}): ${tokenErrText.slice(0, 200)}`);
26276
+ log(` Warning: device token registration failed (${tokenRes.status}): ${tokenErrText.slice(0, 200)}`);
26230
26277
  } else {
26231
- console.log(" Device token registered.");
26278
+ log(" Device token registered.");
26232
26279
  }
26233
- console.log("\nStep 5/7: Registering signing key + fetching server key...");
26280
+ log("\nStep 5: Registering signing key + fetching server key...");
26234
26281
  if (process.env.EXPRESS_DEBUG) {
26235
- console.log(` [DEBUG] serverId: ${serverId}`);
26236
- console.log(` [DEBUG] userHuid: ${userHuid}`);
26282
+ log(` [DEBUG] serverId: ${serverId}`);
26283
+ log(` [DEBUG] userHuid: ${userHuid}`);
26237
26284
  }
26238
26285
  const apigwSigningKey = generateSigningKeyPair();
26239
26286
  const apigwKeyPublicBase64 = publicKeyToBase64(apigwSigningKey.publicKey);
@@ -26247,56 +26294,42 @@ async function qrLogin(cliOverrides = {}) {
26247
26294
  const [kdcSignRes, etsKdcSignRes, etsKdcStartRes] = await Promise.all([
26248
26295
  fetch(kdcSignUrl, {
26249
26296
  method: "POST",
26250
- headers: {
26251
- ...commonHeaders(webOrigin),
26252
- Authorization: `Bearer ${accessToken}`,
26253
- "Content-Type": "application/json"
26254
- },
26297
+ headers: { ...commonHeaders(webOrigin), Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
26255
26298
  body: kdcSignBody
26256
26299
  }),
26257
26300
  fetch(`${etsBaseUrl}/api/v2/kdc/keys/${userHuid}`, {
26258
26301
  method: "POST",
26259
- headers: {
26260
- ...commonHeaders(webOrigin),
26261
- Authorization: `Bearer ${etsAuthToken}`,
26262
- "Content-Type": "application/json"
26263
- },
26302
+ headers: { ...commonHeaders(webOrigin), Authorization: `Bearer ${etsAuthToken}`, "Content-Type": "application/json" },
26264
26303
  body: kdcSignBody
26265
26304
  }),
26266
- fetch(`${etsBaseUrl}/api/v1/kdc/start`, {
26267
- headers: {
26268
- ...commonHeaders(webOrigin)
26269
- }
26270
- })
26305
+ fetch(`${etsBaseUrl}/api/v1/kdc/start`, { headers: { ...commonHeaders(webOrigin) } })
26271
26306
  ]);
26272
26307
  if (!kdcSignRes.ok) {
26273
26308
  const kdcErrText = await kdcSignRes.text().catch(() => "");
26274
- console.log(` Warning: CTS KDC signing key registration failed (${kdcSignRes.status}): ${kdcErrText.slice(0, 200)}`);
26309
+ log(` Warning: CTS KDC signing key registration failed (${kdcSignRes.status}): ${kdcErrText.slice(0, 200)}`);
26275
26310
  } else {
26276
26311
  const kdcSignData = await kdcSignRes.json().catch(() => null);
26277
- console.log(` Signing key registered in CTS: ${apigwSigningKey.keyId}`, kdcSignData ? JSON.stringify(kdcSignData).slice(0, 200) : "");
26312
+ log(` Signing key registered in CTS: ${apigwSigningKey.keyId}` + (kdcSignData ? " " + JSON.stringify(kdcSignData).slice(0, 200) : ""));
26278
26313
  }
26279
26314
  if (!etsKdcSignRes.ok) {
26280
26315
  const etsErrText = await etsKdcSignRes.text().catch(() => "");
26281
- console.log(` Warning: ETS KDC signing key registration failed (${etsKdcSignRes.status}): ${etsErrText.slice(0, 200)}`);
26316
+ log(` Warning: ETS KDC signing key registration failed (${etsKdcSignRes.status}): ${etsErrText.slice(0, 200)}`);
26282
26317
  } else {
26283
26318
  const etsSignData = await etsKdcSignRes.json().catch(() => null);
26284
- console.log(` Signing key registered in ETS: ${apigwSigningKey.keyId}`, etsSignData ? JSON.stringify(etsSignData).slice(0, 200) : "");
26319
+ log(` Signing key registered in ETS: ${apigwSigningKey.keyId}` + (etsSignData ? " " + JSON.stringify(etsSignData).slice(0, 200) : ""));
26285
26320
  }
26286
26321
  let serverPublicKey = new Uint8Array(0);
26287
26322
  let serverPublicKeyId = "";
26288
26323
  if (etsKdcStartRes.ok) {
26289
26324
  const kdcStartText = await etsKdcStartRes.text();
26290
- if (process.env.EXPRESS_DEBUG) {
26291
- console.log(` [DEBUG] ETS KDC start response: ${kdcStartText.slice(0, 500)}`);
26292
- }
26325
+ if (process.env.EXPRESS_DEBUG) log(` [DEBUG] ETS KDC start response: ${kdcStartText.slice(0, 500)}`);
26293
26326
  try {
26294
26327
  const kdcStartData = JSON.parse(kdcStartText);
26295
26328
  const keyBody = kdcStartData.result ?? kdcStartText;
26296
26329
  serverPublicKey = new Uint8Array(Buffer.from(keyBody, "base64"));
26297
26330
  serverPublicKeyId = "kdc-start-ets";
26298
26331
  const rawB64 = Buffer.from(serverPublicKey).toString("base64");
26299
- console.log(` ETS server public key from /kdc/start: ${rawB64} (curve25519, used directly)`);
26332
+ log(` ETS server public key from /kdc/start: ${rawB64} (curve25519, used directly)`);
26300
26333
  } catch {
26301
26334
  try {
26302
26335
  serverPublicKey = new Uint8Array(Buffer.from(kdcStartText, "base64"));
@@ -26306,11 +26339,7 @@ async function qrLogin(cliOverrides = {}) {
26306
26339
  }
26307
26340
  if (!serverPublicKey.length) {
26308
26341
  if (process.env.EXPRESS_DEBUG && !etsKdcStartRes.ok) {
26309
- console.log(` [DEBUG] ETS KDC start status: ${etsKdcStartRes.status}`);
26310
- try {
26311
- console.log(` [DEBUG] ETS KDC start body: ${(await etsKdcStartRes.text()).slice(0, 500)}`);
26312
- } catch {
26313
- }
26342
+ log(` [DEBUG] ETS KDC start status: ${etsKdcStartRes.status}`);
26314
26343
  }
26315
26344
  throw new Error("Could not fetch server public key from ETS /kdc/start");
26316
26345
  }
@@ -26320,34 +26349,26 @@ async function qrLogin(cliOverrides = {}) {
26320
26349
  const [ctsRtsRes, etsRtsRes] = await Promise.all([
26321
26350
  fetch(kdcSignUrl, {
26322
26351
  method: "POST",
26323
- headers: {
26324
- ...commonHeaders(webOrigin),
26325
- Authorization: `Bearer ${accessToken}`,
26326
- "Content-Type": "application/json"
26327
- },
26352
+ headers: { ...commonHeaders(webOrigin), Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
26328
26353
  body: rtsKeyBody
26329
26354
  }),
26330
26355
  fetch(`${etsBaseUrl}/api/v2/kdc/keys/${userHuid}`, {
26331
26356
  method: "POST",
26332
- headers: {
26333
- ...commonHeaders(webOrigin),
26334
- Authorization: `Bearer ${etsAuthToken}`,
26335
- "Content-Type": "application/json"
26336
- },
26357
+ headers: { ...commonHeaders(webOrigin), Authorization: `Bearer ${etsAuthToken}`, "Content-Type": "application/json" },
26337
26358
  body: rtsKeyBody
26338
26359
  })
26339
26360
  ]);
26340
26361
  if (!ctsRtsRes.ok) {
26341
26362
  const errText = await ctsRtsRes.text().catch(() => "");
26342
- console.log(` Warning: CTS RTS key registration failed (${ctsRtsRes.status}): ${errText.slice(0, 200)}`);
26363
+ log(` Warning: CTS RTS key registration failed (${ctsRtsRes.status}): ${errText.slice(0, 200)}`);
26343
26364
  } else {
26344
- console.log(` RTS key registered in CTS: ${rtsPublicKeyId}`);
26365
+ log(` RTS key registered in CTS: ${rtsPublicKeyId}`);
26345
26366
  }
26346
26367
  if (!etsRtsRes.ok) {
26347
26368
  const errText = await etsRtsRes.text().catch(() => "");
26348
- console.log(` Warning: ETS RTS key registration failed (${etsRtsRes.status}): ${errText.slice(0, 200)}`);
26369
+ log(` Warning: ETS RTS key registration failed (${etsRtsRes.status}): ${errText.slice(0, 200)}`);
26349
26370
  } else {
26350
- console.log(` RTS key registered in ETS: ${rtsPublicKeyId}`);
26371
+ log(` RTS key registered in ETS: ${rtsPublicKeyId}`);
26351
26372
  }
26352
26373
  }
26353
26374
  if (!rtsPrivateKey) {
@@ -26359,34 +26380,26 @@ async function qrLogin(cliOverrides = {}) {
26359
26380
  const [ctsEncRes, etsEncRes] = await Promise.all([
26360
26381
  fetch(kdcSignUrl, {
26361
26382
  method: "POST",
26362
- headers: {
26363
- ...commonHeaders(webOrigin),
26364
- Authorization: `Bearer ${accessToken}`,
26365
- "Content-Type": "application/json"
26366
- },
26383
+ headers: { ...commonHeaders(webOrigin), Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
26367
26384
  body: rtsFallbackBody
26368
26385
  }),
26369
26386
  fetch(`${etsBaseUrl}/api/v2/kdc/keys/${userHuid}`, {
26370
26387
  method: "POST",
26371
- headers: {
26372
- ...commonHeaders(webOrigin),
26373
- Authorization: `Bearer ${etsAuthToken}`,
26374
- "Content-Type": "application/json"
26375
- },
26388
+ headers: { ...commonHeaders(webOrigin), Authorization: `Bearer ${etsAuthToken}`, "Content-Type": "application/json" },
26376
26389
  body: rtsFallbackBody
26377
26390
  })
26378
26391
  ]);
26379
26392
  if (!ctsEncRes.ok) {
26380
26393
  const errText = await ctsEncRes.text().catch(() => "");
26381
- console.log(` Warning: CTS fallback RTS key registration failed (${ctsEncRes.status}): ${errText.slice(0, 200)}`);
26394
+ log(` Warning: CTS fallback RTS key registration failed (${ctsEncRes.status}): ${errText.slice(0, 200)}`);
26382
26395
  } else {
26383
- console.log(` Fallback encryption key registered in CTS: ${rtsPublicKeyId}`);
26396
+ log(` Fallback encryption key registered in CTS: ${rtsPublicKeyId}`);
26384
26397
  }
26385
26398
  if (!etsEncRes.ok) {
26386
26399
  const errText = await etsEncRes.text().catch(() => "");
26387
- console.log(` Warning: ETS fallback RTS key registration failed (${etsEncRes.status}): ${errText.slice(0, 200)}`);
26400
+ log(` Warning: ETS fallback RTS key registration failed (${etsEncRes.status}): ${errText.slice(0, 200)}`);
26388
26401
  } else {
26389
- console.log(` Fallback encryption key registered in ETS: ${rtsPublicKeyId}`);
26402
+ log(` Fallback encryption key registered in ETS: ${rtsPublicKeyId}`);
26390
26403
  }
26391
26404
  }
26392
26405
  const rtsPublicKey = nacl3.box.keyPair.fromSecretKey(rtsPrivateKey).publicKey;
@@ -26395,18 +26408,14 @@ async function qrLogin(cliOverrides = {}) {
26395
26408
  try {
26396
26409
  rtsAuthToken = decryptRtsToken(encryptedRtsToken, serverPublicKey, rtsPrivateKey);
26397
26410
  setRtsAuthToken(rtsAuthToken);
26398
- if (process.env.EXPRESS_DEBUG) {
26399
- console.log(` [DEBUG] Decrypted RTS auth token: ${rtsAuthToken.slice(0, 60)}...`);
26400
- }
26401
- console.log(` RTS auth token decrypted from encrypted_rts_token`);
26411
+ if (process.env.EXPRESS_DEBUG) log(` [DEBUG] Decrypted RTS auth token: ${rtsAuthToken.slice(0, 60)}...`);
26412
+ log(` RTS auth token decrypted from encrypted_rts_token`);
26402
26413
  } catch (err) {
26403
- console.log(` Warning: could not decrypt encrypted_rts_token: ${err.message}`);
26414
+ log(` Warning: could not decrypt encrypted_rts_token: ${err.message}`);
26404
26415
  }
26405
26416
  }
26406
26417
  const rtsIdFromToken = extractRtsKeyIdFromToken(accessToken);
26407
- if (rtsIdFromToken && process.env.EXPRESS_DEBUG) {
26408
- console.log(` [DEBUG] rts_id from CTS token: ${rtsIdFromToken}`);
26409
- }
26418
+ if (rtsIdFromToken && process.env.EXPRESS_DEBUG) log(` [DEBUG] rts_id from CTS token: ${rtsIdFromToken}`);
26410
26419
  const existingCts = loadApigwKeys()?.ctsKey;
26411
26420
  let ctsKey;
26412
26421
  if (qrCtsPrivateKey && qrCtsKeyId) {
@@ -26415,10 +26424,10 @@ async function qrLogin(cliOverrides = {}) {
26415
26424
  privateKey: qrCtsPrivateKey,
26416
26425
  publicKey: nacl3.box.keyPair.fromSecretKey(qrCtsPrivateKey).publicKey
26417
26426
  };
26418
- console.log(` Using CTS key from QR handshake: ${qrCtsKeyId.slice(0, 8)}... (shared account key)`);
26427
+ log(` Using CTS key from QR handshake: ${qrCtsKeyId.slice(0, 8)}... (shared account key)`);
26419
26428
  } else if (existingCts) {
26420
26429
  ctsKey = existingCts;
26421
- console.log(` Reusing existing CTS key: ${existingCts.keyId.slice(0, 8)}... (not re-registering)`);
26430
+ log(` Reusing existing CTS key: ${existingCts.keyId.slice(0, 8)}... (not re-registering)`);
26422
26431
  } else {
26423
26432
  const currentCts = await fetchCurrentAccountCtsKey2(getBaseUrl(config), accessToken, userHuid, webOrigin);
26424
26433
  if (currentCts) {
@@ -26426,29 +26435,25 @@ async function qrLogin(cliOverrides = {}) {
26426
26435
  `Account already has a shared CTS key (${currentCts}) that this CLI doesn't hold.
26427
26436
  Minting a new one would break your other devices (they can't fetch its private key).
26428
26437
  Instead, extract the key from a logged-in web client (IndexedDB authState \u2192 encryptionKeys \u2192 user.privateKeys.cts) and run:
26429
- express auth import-cts <private_key_b64> ${currentCts}
26438
+ express-cli auth import-cts <private_key_b64> ${currentCts}
26430
26439
  Then re-run login, or just use 'auth refresh' for tokens.`
26431
26440
  );
26432
26441
  }
26433
- console.log(" No existing account CTS key found \u2014 minting a new one (first device).");
26442
+ log(" No existing account CTS key found \u2014 minting a new one (first device).");
26434
26443
  const ctsKeyPair = nacl3.box.keyPair();
26435
26444
  const ctsKeyId = crypto.randomUUID();
26436
26445
  const ctsKeyPubB64 = Buffer.from(ctsKeyPair.publicKey).toString("base64");
26437
26446
  const ctsKeyBody = JSON.stringify({ key: ctsKeyPubB64, kind: "cts", algo: "xsalsa20", id: ctsKeyId });
26438
26447
  const ctsCtsKeyRes = await fetch(kdcSignUrl, {
26439
26448
  method: "POST",
26440
- headers: {
26441
- ...commonHeaders(webOrigin),
26442
- Authorization: `Bearer ${accessToken}`,
26443
- "Content-Type": "application/json"
26444
- },
26449
+ headers: { ...commonHeaders(webOrigin), Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
26445
26450
  body: ctsKeyBody
26446
26451
  });
26447
26452
  if (!ctsCtsKeyRes.ok) {
26448
26453
  const errText = await ctsCtsKeyRes.text().catch(() => "");
26449
- console.log(` Warning: CTS encryption key registration failed (${ctsCtsKeyRes.status}): ${errText.slice(0, 200)}`);
26454
+ log(` Warning: CTS encryption key registration failed (${ctsCtsKeyRes.status}): ${errText.slice(0, 200)}`);
26450
26455
  } else {
26451
- console.log(` CTS encryption key registered: ${ctsKeyId.slice(0, 8)}...`);
26456
+ log(` CTS encryption key registered: ${ctsKeyId.slice(0, 8)}...`);
26452
26457
  }
26453
26458
  ctsKey = {
26454
26459
  keyId: ctsKeyId,
@@ -26468,7 +26473,7 @@ Then re-run login, or just use 'auth refresh' for tokens.`
26468
26473
  serverPublicKeyId
26469
26474
  };
26470
26475
  saveApigwKeys(apigwKeys);
26471
- console.log("\nStep 6/7: Activating apigw via ETS...");
26476
+ log("\nStep 6: Activating apigw via ETS...");
26472
26477
  const activationUrl = `${etsBaseUrl}/api/v1/apigw/api/v1/authentication/activation`;
26473
26478
  const activationBody = JSON.stringify({
26474
26479
  app_version: config.app_version,
@@ -26496,20 +26501,39 @@ Then re-run login, or just use 'auth refresh' for tokens.`
26496
26501
  });
26497
26502
  if (!activationRes.ok) {
26498
26503
  const actErrText = await activationRes.text().catch(() => "");
26499
- console.log(` Warning: apigw activation failed (${activationRes.status}): ${actErrText.slice(0, 200)}`);
26504
+ log(` Warning: apigw activation failed (${activationRes.status}): ${actErrText.slice(0, 200)}`);
26500
26505
  } else {
26501
- console.log(" Apigw activated.");
26506
+ log(" Apigw activated.");
26502
26507
  }
26503
- console.log(`
26508
+ log(`
26504
26509
  User HUID: ${userHuid || "unknown"}`);
26505
- console.log(` Signing key: ${apigwSigningKey.keyId.slice(0, 8)}...`);
26506
- console.log(` Encryption key: ${rtsPublicKeyId.slice(0, 8)}...`);
26507
- console.log(` Server key: ${serverPublicKeyId.slice(0, 8)}...`);
26508
- console.log("\nQR login complete! You are now authenticated.");
26510
+ log(` Signing key: ${apigwSigningKey.keyId.slice(0, 8)}...`);
26511
+ log(` Encryption key: ${rtsPublicKeyId.slice(0, 8)}...`);
26512
+ log(` Server key: ${serverPublicKeyId.slice(0, 8)}...`);
26513
+ log("\nQR login complete! You are now authenticated.");
26514
+ }
26515
+ async function qrLogin(cliOverrides = {}) {
26516
+ const mat = buildQrMaterial(cliOverrides);
26517
+ console.log("Step 1/6: Scan this QR code with your eXpress app:\n");
26518
+ qrcode.generate(mat.qrPayload, { small: true }, (qr) => {
26519
+ console.log(qr);
26520
+ });
26521
+ console.log(`
26522
+ registration_id: ${mat.registrationId}`);
26523
+ console.log(" Waiting for scan (server long-polling)...\n");
26524
+ openQrInBrowser(mat.qrPayload, mat.registrationId).catch(() => {
26525
+ });
26526
+ const pollResult = await pollForQrScan(mat);
26527
+ await completeQrRegistration(mat, pollResult);
26509
26528
  }
26510
26529
 
26530
+ // src/cli/auth.ts
26531
+ init_store();
26532
+
26511
26533
  // src/auth/token-refresh.ts
26512
26534
  init_esm_shims();
26535
+ init_store();
26536
+ init_loader();
26513
26537
  var refreshPromise = null;
26514
26538
  async function refreshToken(cliOverrides = {}) {
26515
26539
  if (refreshPromise) return refreshPromise;
@@ -26657,6 +26681,7 @@ import { Command as Command2 } from "commander";
26657
26681
 
26658
26682
  // src/api/client.ts
26659
26683
  init_esm_shims();
26684
+ init_store();
26660
26685
  var ApiClient = class {
26661
26686
  config;
26662
26687
  baseUrl;
@@ -27126,8 +27151,97 @@ async function fetchChatListViaWebSocket(params) {
27126
27151
  });
27127
27152
  });
27128
27153
  }
27154
+ async function createPersonalChatViaWebSocket(params) {
27155
+ const { host, ctsToken, encryptionKeyId, myHuid, targetHuid, timeoutMs = 15e3 } = params;
27156
+ const instanceId = randomUUID3();
27157
+ const wsUrl = `wss://${host}/socket/user/websocket?vsn=1.0.0&auto_join=true&key_id=${encryptionKeyId}&version=6&background=false&instance_id=${instanceId}`;
27158
+ return new Promise((resolve, reject) => {
27159
+ let settled = false;
27160
+ const done = (fn) => {
27161
+ if (!settled) {
27162
+ settled = true;
27163
+ fn();
27164
+ }
27165
+ };
27166
+ const timer = setTimeout(() => {
27167
+ try {
27168
+ ws2.close();
27169
+ } catch {
27170
+ }
27171
+ done(() => reject(new Error("WebSocket timeout: no chat_new response")));
27172
+ }, timeoutMs);
27173
+ const hostParts = host.split(".");
27174
+ const webOrigin = `https://${hostParts.length > 2 ? hostParts.slice(1).join(".") : host}`;
27175
+ const ws2 = new WebSocket(wsUrl, {
27176
+ headers: {
27177
+ Origin: webOrigin,
27178
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
27179
+ }
27180
+ });
27181
+ const authRef = 0;
27182
+ const createRef = 1;
27183
+ const send = (msg) => {
27184
+ ws2.send(JSON.stringify(msg));
27185
+ };
27186
+ ws2.addEventListener("open", () => {
27187
+ send({ topic: "phoenix", event: "authenticate", payload: { token: ctsToken }, ref: authRef });
27188
+ });
27189
+ ws2.addEventListener("message", (event) => {
27190
+ let msg;
27191
+ try {
27192
+ msg = JSON.parse(event.data);
27193
+ } catch {
27194
+ return;
27195
+ }
27196
+ if (msg.ref === authRef && msg.event === "phx_reply") {
27197
+ if (msg.payload.status !== "ok") {
27198
+ clearTimeout(timer);
27199
+ ws2.close();
27200
+ done(() => reject(new Error(`WS auth failed: ${JSON.stringify(msg.payload.response)}`)));
27201
+ return;
27202
+ }
27203
+ send({
27204
+ topic: "system",
27205
+ event: "chat_new",
27206
+ payload: {
27207
+ chat_type: "chat",
27208
+ members: [targetHuid, myHuid],
27209
+ name: "personal chat",
27210
+ description: "",
27211
+ threads_enabled: false
27212
+ },
27213
+ ref: createRef
27214
+ });
27215
+ return;
27216
+ }
27217
+ if (msg.ref === createRef && msg.event === "phx_reply") {
27218
+ clearTimeout(timer);
27219
+ ws2.close();
27220
+ if (msg.payload.status !== "ok") {
27221
+ done(() => reject(new Error(`chat_new failed: ${JSON.stringify(msg.payload.response)}`)));
27222
+ return;
27223
+ }
27224
+ const chatId = msg.payload.response?.chat_new?.payload?.group_chat_id;
27225
+ if (!chatId) {
27226
+ done(() => reject(new Error(`chat_new: no group_chat_id in response`)));
27227
+ return;
27228
+ }
27229
+ done(() => resolve(chatId));
27230
+ }
27231
+ });
27232
+ ws2.addEventListener("error", (err) => {
27233
+ clearTimeout(timer);
27234
+ done(() => reject(new Error(`WS error: ${String(err)}`)));
27235
+ });
27236
+ ws2.addEventListener("close", (event) => {
27237
+ clearTimeout(timer);
27238
+ done(() => reject(new Error(`WS closed before chat_new reply (code=${event.code})`)));
27239
+ });
27240
+ });
27241
+ }
27129
27242
 
27130
27243
  // src/api/chats.ts
27244
+ init_store();
27131
27245
  var ChatsApi = class {
27132
27246
  constructor(client) {
27133
27247
  this.client = client;
@@ -27174,6 +27288,16 @@ var ChatsApi = class {
27174
27288
  });
27175
27289
  return data?.open_chats?.[0] ?? null;
27176
27290
  }
27291
+ async createDm(targetHuid) {
27292
+ const config = loadConfig();
27293
+ const host = new URL(getBaseUrl(config)).hostname;
27294
+ const ctsToken = getAuthToken();
27295
+ const keys = loadApigwKeys();
27296
+ const encryptionKeyId = (keys?.ctsKey ?? keys?.encryptionKey)?.keyId;
27297
+ if (!ctsToken || !encryptionKeyId) throw new Error("Not authenticated");
27298
+ const myHuid = (await new UserApi(this.client).getSelfProfile()).user_huid;
27299
+ return createPersonalChatViaWebSocket({ host, ctsToken, encryptionKeyId, myHuid, targetHuid });
27300
+ }
27177
27301
  };
27178
27302
 
27179
27303
  // src/api/phonebook.ts
@@ -27221,8 +27345,7 @@ var PhonebookApi = class {
27221
27345
  );
27222
27346
  if (!data || typeof data !== "object") return [];
27223
27347
  const obj = data;
27224
- const result = obj.result;
27225
- const phonebook = result?.phonebook ?? [];
27348
+ const phonebook = obj.phonebook ?? [];
27226
27349
  const huids = [
27227
27350
  ...new Set(
27228
27351
  phonebook.flatMap((entry) => entry.contacts ?? []).map((c) => c.user_huid).filter(Boolean)
@@ -27646,6 +27769,7 @@ import WebSocket2 from "ws";
27646
27769
  import { randomBytes as randomBytes3, randomUUID as randomUUID4 } from "crypto";
27647
27770
  import nacl4 from "tweetnacl";
27648
27771
  import sodium from "libsodium-wrappers-sumo";
27772
+ init_store();
27649
27773
  function buildTextPayload(text, fromHuid, chatId) {
27650
27774
  return JSON.stringify({
27651
27775
  type: "text",
@@ -27875,14 +27999,28 @@ async function resolveChatId(client, chatIdOrName) {
27875
27999
  const chats = await listChatsWithNames(client);
27876
28000
  const lower = chatIdOrName.toLowerCase();
27877
28001
  const matches = chats.filter((c) => (c.name ?? "").toLowerCase().includes(lower));
27878
- if (matches.length === 0) throw new Error(`No chat found matching "${chatIdOrName}"`);
28002
+ if (matches.length === 1) return matches[0].group_chat_id;
27879
28003
  if (matches.length > 1) {
27880
28004
  const names = matches.map((c) => ` ${c.name} (${c.group_chat_id})`).join("\n");
27881
28005
  throw new Error(`Multiple chats match "${chatIdOrName}":
27882
28006
  ${names}
27883
28007
  Use the full chat ID.`);
27884
28008
  }
27885
- return matches[0].group_chat_id;
28009
+ const results = await new PhonebookApi(client).searchUsers(chatIdOrName, 5);
28010
+ if (results.length === 0) throw new Error(`No chat or contact found matching "${chatIdOrName}"`);
28011
+ if (results.length > 1) {
28012
+ const names = results.map((p) => ` ${p.name} (${p.user_huid})`).join("\n");
28013
+ throw new Error(`No existing DM with "${chatIdOrName}", found multiple contacts:
28014
+ ${names}
28015
+ Be more specific.`);
28016
+ }
28017
+ const person = results[0];
28018
+ process.stderr.write(`No DM with "${person.name}" \u2014 creating one...
28019
+ `);
28020
+ const chatId = await new ChatsApi(client).createDm(person.user_huid);
28021
+ process.stderr.write(`DM created: ${chatId}
28022
+ `);
28023
+ return chatId;
27886
28024
  }
27887
28025
 
27888
28026
  // src/cli/send.ts
@@ -28048,6 +28186,7 @@ import { Command as Command11 } from "commander";
28048
28186
  init_esm_shims();
28049
28187
  import WebSocket3 from "ws";
28050
28188
  import { randomUUID as randomUUID5 } from "crypto";
28189
+ init_store();
28051
28190
 
28052
28191
  // src/api/decrypt.ts
28053
28192
  init_esm_shims();
@@ -28249,6 +28388,7 @@ init_esm_shims();
28249
28388
  import WebSocket4 from "ws";
28250
28389
  import { EventEmitter } from "events";
28251
28390
  import { randomUUID as randomUUID6 } from "crypto";
28391
+ init_store();
28252
28392
  var HEARTBEAT_MS = 25e3;
28253
28393
  var REQUEST_TIMEOUT_MS = 15e3;
28254
28394
  var MAX_RECONNECT_DELAY_MS = 3e4;
@@ -38447,9 +38587,23 @@ init_esm_shims();
38447
38587
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
38448
38588
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
38449
38589
  import { z as z2 } from "zod";
38590
+ import { writeFileSync as writeFileSync3, readFileSync as readFileSync3, unlinkSync } from "fs";
38591
+ import { tmpdir } from "os";
38592
+ import { join } from "path";
38593
+ init_store();
38594
+ import qrcode2 from "qrcode-terminal";
38595
+ var AUTH_INSTRUCTION = "Run 'express-cli auth qr' (or 'npx @ih8e/express-cli auth qr') in a terminal to log in.";
38450
38596
  var ok = (data) => ({
38451
38597
  content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }]
38452
38598
  });
38599
+ var notAuthenticated = () => ok({ error: "not_authenticated", instruction: AUTH_INSTRUCTION });
38600
+ async function ensureAuth() {
38601
+ const exp = getTokenExpiresAt();
38602
+ if (exp && Date.now() > exp - 5 * 60 * 1e3) {
38603
+ await refreshToken();
38604
+ }
38605
+ return !!getAuthToken();
38606
+ }
38453
38607
  var Inbox = class {
38454
38608
  buf = [];
38455
38609
  seq = 0;
@@ -38535,6 +38689,7 @@ async function runMcpServer() {
38535
38689
  description: "List chats (DMs, groups, channels) with names and full chat IDs. DM names are resolved to the person's full name.",
38536
38690
  inputSchema: { type: z2.enum(["all", "dm", "group", "channel"]).optional().describe("Filter by chat type (default all)") }
38537
38691
  }, async ({ type }) => {
38692
+ if (!await ensureAuth()) return notAuthenticated();
38538
38693
  const chats = await listChatsWithNames(new ApiClient());
38539
38694
  const kind = { dm: "chat", group: "group_chat", channel: "channel" };
38540
38695
  const filtered = !type || type === "all" ? chats : chats.filter((c) => c.chat_type === kind[type]);
@@ -38545,6 +38700,7 @@ async function runMcpServer() {
38545
38700
  description: "Find chats whose name (person or group) contains the query. Returns name + full chat_id to use with other tools.",
38546
38701
  inputSchema: { query: z2.string().describe("Part of the chat or person name") }
38547
38702
  }, async ({ query }) => {
38703
+ if (!await ensureAuth()) return notAuthenticated();
38548
38704
  const chats = await listChatsWithNames(new ApiClient());
38549
38705
  const q = query.toLowerCase();
38550
38706
  return ok(chats.filter((c) => (c.name ?? "").toLowerCase().includes(q)).map((c) => ({ name: c.name, chat_id: c.group_chat_id, type: c.chat_type })));
@@ -38557,6 +38713,7 @@ async function runMcpServer() {
38557
38713
  limit: z2.number().int().min(1).max(200).optional().describe("How many recent messages (default 20)")
38558
38714
  }
38559
38715
  }, async ({ chat, limit }) => {
38716
+ if (!await ensureAuth()) return notAuthenticated();
38560
38717
  const client = new ApiClient();
38561
38718
  const chatId = await resolveChatId(client, chat);
38562
38719
  const msgs = await readMessages({ chatId, limit: limit ?? 20 });
@@ -38568,9 +38725,10 @@ async function runMcpServer() {
38568
38725
  });
38569
38726
  server.registerTool("send_message", {
38570
38727
  title: "Send a message",
38571
- description: "Send a text message to a chat, identified by name (partial ok) or chat_id. Returns the sync_id.",
38728
+ description: "Send a text message to a chat, identified by name (partial ok) or chat_id. If no DM exists with that person, one is created automatically. Returns the sync_id.",
38572
38729
  inputSchema: { chat: z2.string().describe("Chat name or full chat_id"), text: z2.string().describe("Message text") }
38573
38730
  }, async ({ chat, text }) => {
38731
+ if (!await ensureAuth()) return notAuthenticated();
38574
38732
  const client = new ApiClient();
38575
38733
  const chatId = await resolveChatId(client, chat);
38576
38734
  const res = await sendMessageViaWebSocket({ client, chatId, body: text });
@@ -38581,6 +38739,7 @@ async function runMcpServer() {
38581
38739
  description: "Global company phonebook search across all employees by name.",
38582
38740
  inputSchema: { query: z2.string().describe("Name to search"), limit: z2.number().int().min(1).max(50).optional() }
38583
38741
  }, async ({ query, limit }) => {
38742
+ if (!await ensureAuth()) return notAuthenticated();
38584
38743
  const profiles = await new PhonebookApi(new ApiClient()).searchUsers(query, limit ?? 20);
38585
38744
  return ok(profiles.map((p) => ({ name: p.name, huid: p.user_huid, email: p.email, position: p.company_position, department: p.department })));
38586
38745
  });
@@ -38588,26 +38747,123 @@ async function runMcpServer() {
38588
38747
  title: "My profile",
38589
38748
  description: "Get the authenticated user's own profile.",
38590
38749
  inputSchema: {}
38591
- }, async () => ok(await new UserApi(new ApiClient()).getSelfProfile()));
38750
+ }, async () => {
38751
+ if (!await ensureAuth()) return notAuthenticated();
38752
+ return ok(await new UserApi(new ApiClient()).getSelfProfile());
38753
+ });
38592
38754
  server.registerTool("wait_for_messages", {
38593
38755
  title: "Wait for incoming messages",
38594
38756
  description: "Block until new incoming messages arrive (from any chat/discussion), then return them. Returns messages received since the previous call to this tool; if none are pending, waits up to timeout_seconds. Your own sent messages are not included. Use this to react to new messages instead of polling.",
38595
38757
  inputSchema: { timeout_seconds: z2.number().int().min(1).max(120).optional().describe("Max seconds to wait when nothing is pending (default 30)") }
38596
38758
  }, async ({ timeout_seconds }) => {
38597
- if (!session) return ok({ error: "Session unavailable \u2014 run 'express auth qr' to authenticate." });
38759
+ if (!session) return ok({ error: "Session unavailable.", instruction: AUTH_INSTRUCTION });
38598
38760
  const items = await inbox.take((timeout_seconds ?? 30) * 1e3);
38599
38761
  return ok({ connected: sessionReady, count: items.length, messages: await enrich(items) });
38600
38762
  });
38601
38763
  server.registerTool("status", {
38602
38764
  title: "Auth status",
38603
- description: "Check authentication and access-token status.",
38765
+ description: "Check authentication and access-token status. Call this first if other tools return not_authenticated.",
38604
38766
  inputSchema: {}
38605
38767
  }, async () => {
38606
38768
  const exp = getTokenExpiresAt();
38607
38769
  return ok({
38608
38770
  authenticated: !!getAuthToken(),
38609
- token_expires_in_seconds: exp ? Math.max(0, Math.floor((exp - Date.now()) / 1e3)) : null
38771
+ token_expires_in_seconds: exp ? Math.max(0, Math.floor((exp - Date.now()) / 1e3)) : null,
38772
+ login_instruction: AUTH_INSTRUCTION
38773
+ });
38774
+ });
38775
+ const QR_MATERIAL_FILE = join(tmpdir(), "express-qr-material.json");
38776
+ let pendingQrMaterial = null;
38777
+ let pendingQrPoll = null;
38778
+ server.registerTool("auth_qr_start", {
38779
+ title: "Start QR login \u2014 step 1 of 2",
38780
+ description: "Step 1: generate a QR code for eXpress login. The QR code is included in the response \u2014 display it to the user as-is. After displaying the QR you MUST immediately call the MCP tool `auth_qr_poll` (step 2) \u2014 do NOT wait for the user to confirm scanning first; auth_qr_poll waits automatically. Do NOT run auth_qr_poll as a shell command \u2014 it is an MCP tool. Use when status reports not_authenticated.",
38781
+ inputSchema: {}
38782
+ }, async () => {
38783
+ const material = buildQrMaterial();
38784
+ let qrAscii = "";
38785
+ qrcode2.generate(material.qrPayload, { small: true }, (qr) => {
38786
+ qrAscii = qr;
38787
+ });
38788
+ writeFileSync3(join(tmpdir(), "express-qr.txt"), qrAscii, "utf8");
38789
+ process.stderr.write("\n" + qrAscii + "\n");
38790
+ openQrInBrowser(material.qrPayload, material.registrationId).catch(() => {
38610
38791
  });
38792
+ const persistent = {
38793
+ registrationId: material.registrationId,
38794
+ encryptionKey: Buffer.from(material.encryptionKey).toString("base64"),
38795
+ qrSigningKey: {
38796
+ keyId: material.qrSigningKey.keyId,
38797
+ privateKey: Buffer.from(material.qrSigningKey.privateKey).toString("base64"),
38798
+ publicKey: Buffer.from(material.qrSigningKey.publicKey).toString("base64")
38799
+ },
38800
+ udid: material.udid,
38801
+ qrPayload: material.qrPayload,
38802
+ qrBody: material.qrBody
38803
+ };
38804
+ writeFileSync3(QR_MATERIAL_FILE, JSON.stringify(persistent), "utf8");
38805
+ pendingQrMaterial = material;
38806
+ pendingQrPoll = pollForQrScan(material).catch((err) => {
38807
+ process.stderr.write(`[mcp] QR poll error: ${err.message}
38808
+ `);
38809
+ pendingQrMaterial = null;
38810
+ pendingQrPoll = null;
38811
+ throw err;
38812
+ });
38813
+ return ok(
38814
+ `${qrAscii.trim()}
38815
+
38816
+ Scan with the eXpress mobile app.
38817
+
38818
+ [AGENT: call auth_qr_poll immediately \u2014 do not wait for user input]`
38819
+ );
38820
+ });
38821
+ server.registerTool("auth_qr_poll", {
38822
+ title: "Complete QR login \u2014 step 2 of 2",
38823
+ description: "Step 2: wait for the phone scan and complete QR login. Call this immediately after auth_qr_start \u2014 do NOT wait for user confirmation. Waits up to 90 s for the server handshake, then saves tokens and keys. IMPORTANT: call this as an MCP tool, not as a shell command.",
38824
+ inputSchema: {}
38825
+ }, async () => {
38826
+ let material = pendingQrMaterial;
38827
+ let poll = pendingQrPoll;
38828
+ if (!material || !poll) {
38829
+ let saved = null;
38830
+ try {
38831
+ saved = JSON.parse(readFileSync3(QR_MATERIAL_FILE, "utf8"));
38832
+ } catch {
38833
+ }
38834
+ if (!saved) return ok("No pending QR session. Call auth_qr_start first.");
38835
+ const sk = saved.qrSigningKey;
38836
+ const hydrated = {
38837
+ registrationId: saved.registrationId,
38838
+ encryptionKey: Buffer.from(saved.encryptionKey, "base64"),
38839
+ qrSigningKey: {
38840
+ keyId: sk.keyId,
38841
+ privateKey: Buffer.from(sk.privateKey, "base64"),
38842
+ publicKey: Buffer.from(sk.publicKey, "base64")
38843
+ },
38844
+ udid: saved.udid,
38845
+ qrPayload: saved.qrPayload,
38846
+ qrBody: saved.qrBody,
38847
+ config: (await Promise.resolve().then(() => (init_loader(), loader_exports))).loadConfig()
38848
+ };
38849
+ material = hydrated;
38850
+ poll = pollForQrScan(material);
38851
+ }
38852
+ pendingQrMaterial = null;
38853
+ pendingQrPoll = null;
38854
+ try {
38855
+ unlinkSync(QR_MATERIAL_FILE);
38856
+ } catch {
38857
+ }
38858
+ try {
38859
+ const pollResult = await poll;
38860
+ await completeQrRegistration(material, pollResult, (msg) => {
38861
+ process.stderr.write(msg + "\n");
38862
+ });
38863
+ return ok("Login successful. You are now authenticated.");
38864
+ } catch (err) {
38865
+ return ok(`Login failed: ${err.message}`);
38866
+ }
38611
38867
  });
38612
38868
  await server.connect(new StdioServerTransport());
38613
38869
  }
@@ -38629,7 +38885,7 @@ function createMcpCommand() {
38629
38885
  // src/cli/root.ts
38630
38886
  function createRootCommand() {
38631
38887
  const program2 = new Command15();
38632
- program2.name("express-cli").description("CLI client for eXpress Chat").version("0.1.4");
38888
+ program2.name("express-cli").description("CLI client for eXpress Chat").version("0.1.5");
38633
38889
  program2.addCommand(createAuthCommand());
38634
38890
  program2.addCommand(createApiCommand());
38635
38891
  program2.addCommand(createConfigCommand());