openmates 0.11.0-alpha.22 → 0.11.0-alpha.24

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.
@@ -12,9 +12,14 @@ import qrcode from "qrcode-terminal";
12
12
 
13
13
  // src/crypto.ts
14
14
  import { webcrypto, createHash } from "crypto";
15
+ import nacl from "tweetnacl";
15
16
  var cryptoApi = globalThis.crypto ?? webcrypto;
16
17
  var PAIR_KDF_ITERATIONS = 1e5;
18
+ var SIGNUP_KDF_ITERATIONS = 1e5;
17
19
  var AES_GCM_IV_LENGTH = 12;
20
+ var EMAIL_SALT_LENGTH = 16;
21
+ var MASTER_KEY_LENGTH = 32;
22
+ var NACL_NONCE_LENGTH = 24;
18
23
  var CIPHERTEXT_MAGIC_0 = 79;
19
24
  var CIPHERTEXT_MAGIC_1 = 77;
20
25
  var FINGERPRINT_LENGTH = 4;
@@ -30,6 +35,136 @@ function toArrayBuffer(input) {
30
35
  new Uint8Array(output).set(input);
31
36
  return output;
32
37
  }
38
+ function generateSalt(length = EMAIL_SALT_LENGTH) {
39
+ return cryptoApi.getRandomValues(new Uint8Array(length));
40
+ }
41
+ function generateSecureRecoveryKey(length = 24) {
42
+ const uppercaseChars = "ABCDEFGHJKLMNPQRSTUVWXYZ";
43
+ const lowercaseChars = "abcdefghijkmnopqrstuvwxyz";
44
+ const numberChars = "23456789";
45
+ const specialChars = "#-=+_&%$";
46
+ const allChars = uppercaseChars + lowercaseChars + numberChars + specialChars;
47
+ const result = new Array(length);
48
+ const requiredSets = [uppercaseChars, lowercaseChars, numberChars, specialChars];
49
+ for (let index = 0; index < requiredSets.length && index < length; index += 1) {
50
+ const randomByte = cryptoApi.getRandomValues(new Uint8Array(1))[0];
51
+ const chars = requiredSets[index];
52
+ result[index] = chars.charAt(randomByte % chars.length);
53
+ }
54
+ const randomBytes3 = cryptoApi.getRandomValues(new Uint8Array(length));
55
+ for (let index = requiredSets.length; index < length; index += 1) {
56
+ result[index] = allChars.charAt(randomBytes3[index] % allChars.length);
57
+ }
58
+ for (let index = result.length - 1; index > 0; index -= 1) {
59
+ const randomByte = cryptoApi.getRandomValues(new Uint8Array(1))[0];
60
+ const swapIndex = Math.floor(randomByte / 256 * (index + 1));
61
+ [result[index], result[swapIndex]] = [result[swapIndex], result[index]];
62
+ }
63
+ return result.join("");
64
+ }
65
+ async function hashEmail(email) {
66
+ const hashBuffer = await cryptoApi.subtle.digest(
67
+ "SHA-256",
68
+ new TextEncoder().encode(email)
69
+ );
70
+ return bytesToBase64(new Uint8Array(hashBuffer));
71
+ }
72
+ async function hashKey(key, salt) {
73
+ const keyBytes = new TextEncoder().encode(key);
74
+ const dataToHash = salt ? new Uint8Array(keyBytes.length + salt.length) : keyBytes;
75
+ if (salt) {
76
+ dataToHash.set(keyBytes);
77
+ dataToHash.set(salt, keyBytes.length);
78
+ }
79
+ const hashBuffer = await cryptoApi.subtle.digest("SHA-256", toArrayBuffer(dataToHash));
80
+ return bytesToBase64(new Uint8Array(hashBuffer));
81
+ }
82
+ async function deriveKeyFromPassword(password, salt) {
83
+ const keyMaterial = await cryptoApi.subtle.importKey(
84
+ "raw",
85
+ new TextEncoder().encode(password),
86
+ "PBKDF2",
87
+ false,
88
+ ["deriveBits"]
89
+ );
90
+ const derivedBits = await cryptoApi.subtle.deriveBits(
91
+ {
92
+ name: "PBKDF2",
93
+ salt: toArrayBuffer(salt),
94
+ iterations: SIGNUP_KDF_ITERATIONS,
95
+ hash: "SHA-256"
96
+ },
97
+ keyMaterial,
98
+ 256
99
+ );
100
+ return new Uint8Array(derivedBits);
101
+ }
102
+ async function encryptEmail(email, key) {
103
+ if (key.length !== MASTER_KEY_LENGTH) {
104
+ throw new Error(`Email encryption key must be 32 bytes, got ${key.length}`);
105
+ }
106
+ const nonce = nacl.randomBytes(NACL_NONCE_LENGTH);
107
+ const ciphertext = nacl.secretbox(new TextEncoder().encode(email), nonce, key);
108
+ const combined = new Uint8Array(nonce.length + ciphertext.length);
109
+ combined.set(nonce);
110
+ combined.set(ciphertext, nonce.length);
111
+ return bytesToBase64(combined);
112
+ }
113
+ async function encryptRawKeyWithAesGcm(rawKey, wrappingKeyBytes) {
114
+ const iv = cryptoApi.getRandomValues(new Uint8Array(AES_GCM_IV_LENGTH));
115
+ const wrappingKey = await cryptoApi.subtle.importKey(
116
+ "raw",
117
+ toArrayBuffer(wrappingKeyBytes),
118
+ { name: "AES-GCM" },
119
+ false,
120
+ ["encrypt"]
121
+ );
122
+ const encrypted = await cryptoApi.subtle.encrypt(
123
+ { name: "AES-GCM", iv: toArrayBuffer(iv) },
124
+ wrappingKey,
125
+ toArrayBuffer(rawKey)
126
+ );
127
+ return {
128
+ wrapped: bytesToBase64(new Uint8Array(encrypted)),
129
+ iv: bytesToBase64(iv)
130
+ };
131
+ }
132
+ async function createSignupCryptoMaterial(email, password) {
133
+ const normalizedEmail = email.trim().toLowerCase();
134
+ const emailSalt = generateSalt(EMAIL_SALT_LENGTH);
135
+ const passwordSalt = generateSalt(EMAIL_SALT_LENGTH);
136
+ const masterKey = generateSalt(MASTER_KEY_LENGTH);
137
+ const emailEncryptionKeyB64 = await deriveEmailEncryptionKeyB64(normalizedEmail, bytesToBase64(emailSalt));
138
+ const emailEncryptionKey = base64ToBytes(emailEncryptionKeyB64);
139
+ const wrappingKey = await deriveKeyFromPassword(password, passwordSalt);
140
+ const encryptedMasterKey = await encryptRawKeyWithAesGcm(masterKey, wrappingKey);
141
+ return {
142
+ hashedEmail: await hashEmail(normalizedEmail),
143
+ encryptedEmail: await encryptEmail(normalizedEmail, emailEncryptionKey),
144
+ encryptedEmailWithMasterKey: await encryptWithAesGcmCombined(normalizedEmail, masterKey),
145
+ userEmailSaltB64: bytesToBase64(emailSalt),
146
+ emailEncryptionKeyB64,
147
+ masterKeyB64: bytesToBase64(masterKey),
148
+ encryptedMasterKey: encryptedMasterKey.wrapped,
149
+ keyIv: encryptedMasterKey.iv,
150
+ saltB64: bytesToBase64(passwordSalt),
151
+ lookupHash: await hashKey(password, emailSalt)
152
+ };
153
+ }
154
+ async function createRecoveryKeyMaterial(masterKeyB64, userEmailSaltB64) {
155
+ const recoveryKey = generateSecureRecoveryKey();
156
+ const userEmailSalt = base64ToBytes(userEmailSaltB64);
157
+ const wrappingSalt = generateSalt(EMAIL_SALT_LENGTH);
158
+ const wrappingKey = await deriveKeyFromPassword(recoveryKey, wrappingSalt);
159
+ const encryptedMasterKey = await encryptRawKeyWithAesGcm(base64ToBytes(masterKeyB64), wrappingKey);
160
+ return {
161
+ recoveryKey,
162
+ lookupHash: await hashKey(recoveryKey, userEmailSalt),
163
+ wrappedMasterKey: encryptedMasterKey.wrapped,
164
+ keyIv: encryptedMasterKey.iv,
165
+ saltB64: bytesToBase64(wrappingSalt)
166
+ };
167
+ }
33
168
  async function derivePairKey(pin, upperToken) {
34
169
  const encoder = new TextEncoder();
35
170
  const keyMaterial = await cryptoApi.subtle.importKey(
@@ -778,14 +913,14 @@ var OpenMatesWsClient = class {
778
913
  });
779
914
  }
780
915
  async open(timeoutMs = 1e4) {
781
- await new Promise((resolve4, reject) => {
916
+ await new Promise((resolve5, reject) => {
782
917
  const timeout = setTimeout(
783
918
  () => reject(new Error("WebSocket open timeout")),
784
919
  timeoutMs
785
920
  );
786
921
  this.socket.once("open", () => {
787
922
  clearTimeout(timeout);
788
- resolve4();
923
+ resolve5();
789
924
  });
790
925
  this.socket.once("error", (error) => {
791
926
  clearTimeout(timeout);
@@ -814,15 +949,15 @@ var OpenMatesWsClient = class {
814
949
  this.socket.send(JSON.stringify({ type, payload }));
815
950
  }
816
951
  sendAsync(type, payload) {
817
- return new Promise((resolve4, reject) => {
952
+ return new Promise((resolve5, reject) => {
818
953
  this.socket.send(JSON.stringify({ type, payload }), (error) => {
819
954
  if (error) reject(error);
820
- else resolve4();
955
+ else resolve5();
821
956
  });
822
957
  });
823
958
  }
824
959
  waitForMessage(expectedType, predicate, timeoutMs = 2e4) {
825
- return new Promise((resolve4, reject) => {
960
+ return new Promise((resolve5, reject) => {
826
961
  const onMessage = (rawData) => {
827
962
  try {
828
963
  const parsed = JSON.parse(rawData.toString());
@@ -833,7 +968,7 @@ var OpenMatesWsClient = class {
833
968
  return;
834
969
  }
835
970
  cleanup();
836
- resolve4(parsed);
971
+ resolve5(parsed);
837
972
  } catch {
838
973
  }
839
974
  };
@@ -866,14 +1001,14 @@ var OpenMatesWsClient = class {
866
1001
  * Used by ensureSynced to consume the full phased-sync event stream.
867
1002
  */
868
1003
  collectMessages(terminatorType, timeoutMs = 9e4) {
869
- return new Promise((resolve4, reject) => {
1004
+ return new Promise((resolve5, reject) => {
870
1005
  const collected = [];
871
1006
  const onMessage = (rawData) => {
872
1007
  try {
873
1008
  const parsed = JSON.parse(rawData.toString());
874
1009
  if (parsed.type === terminatorType) {
875
1010
  cleanup();
876
- resolve4(collected);
1011
+ resolve5(collected);
877
1012
  return;
878
1013
  }
879
1014
  collected.push(parsed);
@@ -886,7 +1021,7 @@ var OpenMatesWsClient = class {
886
1021
  };
887
1022
  const onClose = () => {
888
1023
  cleanup();
889
- resolve4(collected);
1024
+ resolve5(collected);
890
1025
  };
891
1026
  const timeout = setTimeout(() => {
892
1027
  cleanup();
@@ -924,7 +1059,7 @@ var OpenMatesWsClient = class {
924
1059
  const timeoutMs = options?.timeoutMs ?? 9e4;
925
1060
  const onStream = options?.onStream;
926
1061
  const asyncEmbedWaitMs = options?.asyncEmbedWaitMs ?? 12e4;
927
- return new Promise((resolve4, reject) => {
1062
+ return new Promise((resolve5, reject) => {
928
1063
  let latestContent = "";
929
1064
  let messageId = null;
930
1065
  let taskId = null;
@@ -980,7 +1115,7 @@ var OpenMatesWsClient = class {
980
1115
  if (processingEmbedIds.size > 0 && !asyncEmbedTimer) {
981
1116
  asyncEmbedTimer = setTimeout(() => {
982
1117
  cleanup();
983
- resolve4({
1118
+ resolve5({
984
1119
  messageId,
985
1120
  taskId,
986
1121
  content: latestContent,
@@ -995,7 +1130,7 @@ var OpenMatesWsClient = class {
995
1130
  }
996
1131
  if (processingEmbedIds.size > 0) return;
997
1132
  cleanup();
998
- resolve4({
1133
+ resolve5({
999
1134
  messageId,
1000
1135
  taskId,
1001
1136
  content: latestContent,
@@ -1164,7 +1299,7 @@ var OpenMatesWsClient = class {
1164
1299
  const onClose = () => {
1165
1300
  if (aiResponseDone) {
1166
1301
  cleanup();
1167
- resolve4({
1302
+ resolve5({
1168
1303
  messageId,
1169
1304
  taskId,
1170
1305
  content: latestContent,
@@ -1261,6 +1396,20 @@ function fuzzyScore(query, candidate) {
1261
1396
  return Math.round(matched / Math.max(q.length, 1) * 40);
1262
1397
  }
1263
1398
  function resolveToken(token, context) {
1399
+ const wireFocusMatch = token.match(/^focus:([a-z0-9_-]+):([a-z0-9_-]+)$/i);
1400
+ if (wireFocusMatch) {
1401
+ const [, appId, focusModeId] = wireFocusMatch;
1402
+ const app = context.apps.find((candidate) => candidate.id === appId);
1403
+ const focusMode = app?.focus_modes?.find((candidate) => candidate.id === focusModeId);
1404
+ if (app && focusMode) {
1405
+ return {
1406
+ original: `@${token}`,
1407
+ type: "focus_mode",
1408
+ wireSyntax: `@focus:${app.id}:${focusMode.id}`,
1409
+ displayName: `@${capitalize(app.name)}-${capitalize(focusMode.name).replace(/\s+/g, "-")}`
1410
+ };
1411
+ }
1412
+ }
1264
1413
  const normalized = normalize(token);
1265
1414
  if (MODEL_ALIASES[normalized]) {
1266
1415
  return {
@@ -1666,7 +1815,7 @@ async function deriveKeyFromId(id) {
1666
1815
  ["encrypt"]
1667
1816
  );
1668
1817
  }
1669
- async function deriveKeyFromPassword(password, id) {
1818
+ async function deriveKeyFromPassword2(password, id) {
1670
1819
  const material = await crypto.subtle.importKey(
1671
1820
  "raw",
1672
1821
  new TextEncoder().encode(password),
@@ -1692,7 +1841,7 @@ async function generateChatShareBlob(chatId, chatKeyBytes, durationSeconds = 0,
1692
1841
  let keyForBlob = Buffer.from(chatKeyBytes).toString("base64");
1693
1842
  let pwdFlag = 0;
1694
1843
  if (password && password.length > 0) {
1695
- const pwdKey = await deriveKeyFromPassword(password, chatId);
1844
+ const pwdKey = await deriveKeyFromPassword2(password, chatId);
1696
1845
  keyForBlob = await encryptAESGCM(enc.encode(keyForBlob), pwdKey);
1697
1846
  pwdFlag = 1;
1698
1847
  }
@@ -1710,7 +1859,7 @@ async function generateEmbedShareBlob(embedId, embedKeyBytes, durationSeconds =
1710
1859
  let keyForBlob = Buffer.from(embedKeyBytes).toString("base64");
1711
1860
  let pwdFlag = 0;
1712
1861
  if (password && password.length > 0) {
1713
- const pwdKey = await deriveKeyFromPassword(password, embedId);
1862
+ const pwdKey = await deriveKeyFromPassword2(password, embedId);
1714
1863
  keyForBlob = await encryptAESGCM(enc.encode(keyForBlob), pwdKey);
1715
1864
  pwdFlag = 1;
1716
1865
  }
@@ -2275,6 +2424,168 @@ var OpenMatesClient = class _OpenMatesClient {
2275
2424
  }
2276
2425
  clearSession();
2277
2426
  }
2427
+ async requestSignupEmailCode(params) {
2428
+ const hashedEmail = await hashEmail(params.email.trim().toLowerCase());
2429
+ const response = await this.http.post(
2430
+ "/v1/auth/request_confirm_email_code",
2431
+ {
2432
+ email: params.email.trim().toLowerCase(),
2433
+ hashed_email: hashedEmail,
2434
+ invite_code: params.inviteCode ?? "",
2435
+ language: params.language ?? "en",
2436
+ darkmode: params.darkmode ?? false
2437
+ },
2438
+ this.getCliRequestHeaders()
2439
+ );
2440
+ if (!response.ok || response.data.success === false) {
2441
+ throw new Error(response.data.message ?? `Email code request failed (HTTP ${response.status})`);
2442
+ }
2443
+ return response.data;
2444
+ }
2445
+ async verifySignupEmailCode(params) {
2446
+ const response = await this.http.post(
2447
+ "/v1/auth/check_confirm_email_code",
2448
+ {
2449
+ code: params.code,
2450
+ email: params.email.trim().toLowerCase(),
2451
+ username: params.username,
2452
+ invite_code: params.inviteCode ?? "",
2453
+ language: params.language ?? "en",
2454
+ darkmode: params.darkmode ?? false
2455
+ },
2456
+ this.getCliRequestHeaders()
2457
+ );
2458
+ if (!response.ok || response.data.success === false) {
2459
+ throw new Error(response.data.message ?? `Email code verification failed (HTTP ${response.status})`);
2460
+ }
2461
+ return response.data;
2462
+ }
2463
+ async setupPasswordAccount(params) {
2464
+ const material = await createSignupCryptoMaterial(params.email, params.password);
2465
+ const response = await this.http.post(
2466
+ "/v1/auth/setup_password",
2467
+ {
2468
+ hashed_email: material.hashedEmail,
2469
+ encrypted_email: material.encryptedEmail,
2470
+ user_email_salt: material.userEmailSaltB64,
2471
+ username: params.username,
2472
+ invite_code: params.inviteCode ?? "",
2473
+ encrypted_master_key: material.encryptedMasterKey,
2474
+ key_iv: material.keyIv,
2475
+ salt: material.saltB64,
2476
+ lookup_hash: material.lookupHash,
2477
+ language: params.language ?? "en",
2478
+ darkmode: params.darkmode ?? false
2479
+ },
2480
+ this.getCliRequestHeaders()
2481
+ );
2482
+ if (!response.ok || !response.data.success) {
2483
+ throw new Error(response.data.message ?? `Password signup failed (HTTP ${response.status})`);
2484
+ }
2485
+ const session = {
2486
+ apiUrl: this.apiUrl,
2487
+ sessionId: randomUUID2(),
2488
+ wsToken: null,
2489
+ cookies: this.http.getCookieMap(),
2490
+ masterKeyExportedB64: material.masterKeyB64,
2491
+ emailEncryptionKeyB64: material.emailEncryptionKeyB64,
2492
+ hashedEmail: material.hashedEmail,
2493
+ userEmailSalt: material.userEmailSaltB64,
2494
+ createdAt: Date.now(),
2495
+ authorizerDeviceName: null,
2496
+ autoLogoutMinutes: null
2497
+ };
2498
+ saveSession(session);
2499
+ this.session = session;
2500
+ return {
2501
+ success: true,
2502
+ message: response.data.message ?? "Account created.",
2503
+ user: response.data.user,
2504
+ crypto: material
2505
+ };
2506
+ }
2507
+ async startTotpSetup() {
2508
+ const session = this.requireSession();
2509
+ const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
2510
+ const response = await this.http.post(
2511
+ "/v1/auth/2fa/setup/initiate",
2512
+ { email_encryption_key: emailEncryptionKey },
2513
+ this.getCliRequestHeaders()
2514
+ );
2515
+ if (!response.ok || !response.data.success) {
2516
+ throw new Error(response.data.message ?? `2FA setup failed (HTTP ${response.status})`);
2517
+ }
2518
+ return response.data;
2519
+ }
2520
+ renderTotpQrCode(otpauthUrl) {
2521
+ qrcode.generate(otpauthUrl, { small: true });
2522
+ }
2523
+ async verifyTotpSetup(code) {
2524
+ this.requireSession();
2525
+ const response = await this.http.post(
2526
+ "/v1/auth/2fa/setup/verify-signup",
2527
+ { code },
2528
+ this.getCliRequestHeaders()
2529
+ );
2530
+ if (!response.ok || response.data.success === false) {
2531
+ throw new Error(response.data.message ?? `2FA verification failed (HTTP ${response.status})`);
2532
+ }
2533
+ return response.data;
2534
+ }
2535
+ async setTotpProvider(provider) {
2536
+ this.requireSession();
2537
+ const response = await this.http.post(
2538
+ "/v1/auth/2fa/setup/provider",
2539
+ { provider },
2540
+ this.getCliRequestHeaders()
2541
+ );
2542
+ if (!response.ok || response.data.success === false) {
2543
+ throw new Error(response.data.message ?? `2FA provider save failed (HTTP ${response.status})`);
2544
+ }
2545
+ return response.data;
2546
+ }
2547
+ async requestBackupCodes() {
2548
+ this.requireSession();
2549
+ const response = await this.http.get(
2550
+ "/v1/auth/2fa/setup/request-backup-codes",
2551
+ this.getCliRequestHeaders()
2552
+ );
2553
+ if (!response.ok || !response.data.success) {
2554
+ throw new Error(response.data.message ?? `Backup-code request failed (HTTP ${response.status})`);
2555
+ }
2556
+ return response.data;
2557
+ }
2558
+ async confirmBackupCodesStored() {
2559
+ this.requireSession();
2560
+ const response = await this.http.post(
2561
+ "/v1/auth/2fa/setup/confirm-codes-stored",
2562
+ { confirmed: true },
2563
+ this.getCliRequestHeaders()
2564
+ );
2565
+ if (!response.ok || response.data.success === false) {
2566
+ throw new Error(response.data.message ?? `Backup-code confirmation failed (HTTP ${response.status})`);
2567
+ }
2568
+ return response.data;
2569
+ }
2570
+ async createAndConfirmRecoveryKey() {
2571
+ const session = this.requireSession();
2572
+ const material = await createRecoveryKeyMaterial(session.masterKeyExportedB64, session.userEmailSalt);
2573
+ const response = await this.http.post(
2574
+ "/v1/auth/recovery-key/confirm-stored",
2575
+ {
2576
+ confirmed: true,
2577
+ lookup_hash: material.lookupHash,
2578
+ wrapped_master_key: material.wrappedMasterKey,
2579
+ key_iv: material.keyIv,
2580
+ salt: material.saltB64
2581
+ },
2582
+ this.getCliRequestHeaders()
2583
+ );
2584
+ if (!response.ok || response.data.success === false) {
2585
+ throw new Error(response.data.message ?? `Recovery-key confirmation failed (HTTP ${response.status})`);
2586
+ }
2587
+ return material;
2588
+ }
2278
2589
  // -------------------------------------------------------------------------
2279
2590
  // Chats
2280
2591
  // -------------------------------------------------------------------------
@@ -3176,7 +3487,7 @@ var OpenMatesClient = class _OpenMatesClient {
3176
3487
  if (response.data.status === "failed") {
3177
3488
  throw new Error(response.data.error ?? "Task failed");
3178
3489
  }
3179
- await new Promise((resolve4) => setTimeout(resolve4, SKILL_TASK_POLL_INTERVAL_MS));
3490
+ await new Promise((resolve5) => setTimeout(resolve5, SKILL_TASK_POLL_INTERVAL_MS));
3180
3491
  }
3181
3492
  throw new Error(`Task ${taskId} did not complete within ${SKILL_TASK_POLL_TIMEOUT_MS / 1e3}s`);
3182
3493
  }
@@ -3328,6 +3639,26 @@ var OpenMatesClient = class _OpenMatesClient {
3328
3639
  }
3329
3640
  return this.resolveAsyncSkillResponse(response.data, headers);
3330
3641
  }
3642
+ async getCodeRunStreamAuth() {
3643
+ const session = this.session;
3644
+ if (!session) return null;
3645
+ await this.refreshWsToken();
3646
+ const token = session.wsToken || session.cookies.auth_refresh_token;
3647
+ if (!token) return null;
3648
+ const fallbackToken = session.cookies.auth_refresh_token;
3649
+ return { sessionId: session.sessionId, token, fallbackToken };
3650
+ }
3651
+ async getCodeRunStatus(path, apiKey) {
3652
+ const headers = {
3653
+ ...this.getCliRequestHeaders()
3654
+ };
3655
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
3656
+ const response = await this.http.get(path, headers);
3657
+ if (!response.ok) {
3658
+ throw new Error(`Code Run status request failed with HTTP ${response.status}`);
3659
+ }
3660
+ return response.data;
3661
+ }
3331
3662
  // -------------------------------------------------------------------------
3332
3663
  // Travel: booking link resolution
3333
3664
  // -------------------------------------------------------------------------
@@ -3375,7 +3706,7 @@ var OpenMatesClient = class _OpenMatesClient {
3375
3706
  `Rate limited by settings API; retrying in ${Math.ceil(SETTINGS_GET_RATE_LIMIT_RETRY_MS / 1e3)}s...
3376
3707
  `
3377
3708
  );
3378
- await new Promise((resolve4) => setTimeout(resolve4, SETTINGS_GET_RATE_LIMIT_RETRY_MS));
3709
+ await new Promise((resolve5) => setTimeout(resolve5, SETTINGS_GET_RATE_LIMIT_RETRY_MS));
3379
3710
  response = await this.http.get(normalizedPath, this.getCliRequestHeaders());
3380
3711
  }
3381
3712
  if (!response.ok) {
@@ -3435,10 +3766,11 @@ var OpenMatesClient = class _OpenMatesClient {
3435
3766
  // Gift cards (under /v1/payments, not /v1/settings)
3436
3767
  // -------------------------------------------------------------------------
3437
3768
  async redeemGiftCard(code) {
3438
- this.requireSession();
3769
+ const session = this.requireSession();
3770
+ const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
3439
3771
  const response = await this.http.post(
3440
3772
  "/v1/payments/redeem-gift-card",
3441
- { code },
3773
+ { code, email_encryption_key: emailEncryptionKey },
3442
3774
  this.getCliRequestHeaders()
3443
3775
  );
3444
3776
  if (!response.ok) {
@@ -3459,6 +3791,86 @@ var OpenMatesClient = class _OpenMatesClient {
3459
3791
  }
3460
3792
  return response.data;
3461
3793
  }
3794
+ async listPurchasedGiftCards() {
3795
+ this.requireSession();
3796
+ const response = await this.http.get(
3797
+ "/v1/payments/purchased-gift-cards",
3798
+ this.getCliRequestHeaders()
3799
+ );
3800
+ if (!response.ok) {
3801
+ throw new Error(
3802
+ `Failed to fetch purchased gift cards (HTTP ${response.status})`
3803
+ );
3804
+ }
3805
+ return response.data;
3806
+ }
3807
+ async createBankTransferOrder(creditsAmount) {
3808
+ const session = this.requireSession();
3809
+ const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
3810
+ const response = await this.http.post(
3811
+ "/v1/payments/create-bank-transfer-order",
3812
+ {
3813
+ credits_amount: creditsAmount,
3814
+ currency: "eur",
3815
+ email_encryption_key: emailEncryptionKey
3816
+ },
3817
+ this.getCliRequestHeaders()
3818
+ );
3819
+ if (!response.ok) {
3820
+ throw new Error(`Bank transfer order failed (HTTP ${response.status})`);
3821
+ }
3822
+ return response.data;
3823
+ }
3824
+ async createGiftCardBankTransferOrder(creditsAmount) {
3825
+ const session = this.requireSession();
3826
+ const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
3827
+ const response = await this.http.post(
3828
+ "/v1/payments/create-gift-card-bank-transfer-order",
3829
+ {
3830
+ credits_amount: creditsAmount,
3831
+ currency: "eur",
3832
+ email_encryption_key: emailEncryptionKey
3833
+ },
3834
+ this.getCliRequestHeaders()
3835
+ );
3836
+ if (!response.ok) {
3837
+ throw new Error(`Gift card bank transfer order failed (HTTP ${response.status})`);
3838
+ }
3839
+ return response.data;
3840
+ }
3841
+ async getBankTransferStatus(orderId) {
3842
+ this.requireSession();
3843
+ const response = await this.http.get(
3844
+ `/v1/payments/bank-transfer-status/${encodeURIComponent(orderId)}`,
3845
+ this.getCliRequestHeaders()
3846
+ );
3847
+ if (!response.ok) {
3848
+ throw new Error(`Failed to fetch bank transfer status (HTTP ${response.status})`);
3849
+ }
3850
+ return response.data;
3851
+ }
3852
+ async listBankTransferOrders() {
3853
+ this.requireSession();
3854
+ const response = await this.http.get(
3855
+ "/v1/payments/bank-transfer-pending",
3856
+ this.getCliRequestHeaders()
3857
+ );
3858
+ if (!response.ok) {
3859
+ throw new Error(`Failed to fetch bank transfer orders (HTTP ${response.status})`);
3860
+ }
3861
+ return response.data;
3862
+ }
3863
+ async getGiftCardPurchaseStatus(orderId) {
3864
+ this.requireSession();
3865
+ const response = await this.http.get(
3866
+ `/v1/payments/gift-card-purchase-status/${encodeURIComponent(orderId)}`,
3867
+ this.getCliRequestHeaders()
3868
+ );
3869
+ if (!response.ok) {
3870
+ throw new Error(`Failed to fetch gift card purchase status (HTTP ${response.status})`);
3871
+ }
3872
+ return response.data;
3873
+ }
3462
3874
  async listInvoices() {
3463
3875
  this.requireSession();
3464
3876
  const response = await this.http.get(
@@ -3495,6 +3907,63 @@ var OpenMatesClient = class _OpenMatesClient {
3495
3907
  }
3496
3908
  return response.data;
3497
3909
  }
3910
+ async getAuthMethodsStatus() {
3911
+ this.requireSession();
3912
+ const response = await this.http.get(
3913
+ "/v1/payments/user-auth-methods",
3914
+ this.getCliRequestHeaders()
3915
+ );
3916
+ if (!response.ok) {
3917
+ throw new Error(`Failed to fetch auth methods (HTTP ${response.status})`);
3918
+ }
3919
+ return response.data;
3920
+ }
3921
+ async requestDeleteAccountEmailCode() {
3922
+ const session = this.requireSession();
3923
+ const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
3924
+ const response = await this.http.post(
3925
+ "/v1/settings/request-action-verification",
3926
+ { action: "delete_account", email_encryption_key: emailEncryptionKey },
3927
+ this.getCliRequestHeaders()
3928
+ );
3929
+ if (!response.ok) {
3930
+ throw new Error(`Failed to request account deletion email code (HTTP ${response.status})`);
3931
+ }
3932
+ return response.data;
3933
+ }
3934
+ async verifyDeleteAccountEmailCode(code) {
3935
+ this.requireSession();
3936
+ const response = await this.http.post(
3937
+ "/v1/settings/verify-action-code",
3938
+ { action: "delete_account", code },
3939
+ this.getCliRequestHeaders()
3940
+ );
3941
+ if (!response.ok) {
3942
+ throw new Error(`Account deletion email code verification failed (HTTP ${response.status})`);
3943
+ }
3944
+ return response.data;
3945
+ }
3946
+ async deleteAccountWithCliVerification(totpCode) {
3947
+ const session = this.requireSession();
3948
+ const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
3949
+ const response = await this.http.post(
3950
+ "/v1/settings/delete-account",
3951
+ {
3952
+ confirm_data_deletion: true,
3953
+ auth_method: totpCode ? "2fa_otp" : "email_otp",
3954
+ auth_code: totpCode,
3955
+ email_encryption_key: emailEncryptionKey,
3956
+ require_email_verification: true
3957
+ },
3958
+ this.getCliRequestHeaders()
3959
+ );
3960
+ if (!response.ok) {
3961
+ throw new Error(`Account deletion failed (HTTP ${response.status})`);
3962
+ }
3963
+ clearSession();
3964
+ clearSyncCache();
3965
+ return response.data;
3966
+ }
3498
3967
  async updateUsername(username) {
3499
3968
  return this.settingsPost("user/username", { username });
3500
3969
  }
@@ -4490,7 +4959,7 @@ function filenameFromContentDisposition(header2) {
4490
4959
  return plain?.trim() ?? null;
4491
4960
  }
4492
4961
  function sleep(ms) {
4493
- return new Promise((resolve4) => setTimeout(resolve4, ms));
4962
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
4494
4963
  }
4495
4964
  function printLogo() {
4496
4965
  const W = "\x1B[1;37m";
@@ -4506,6 +4975,7 @@ function printLogo() {
4506
4975
 
4507
4976
  // src/cli.ts
4508
4977
  import { createInterface as createInterface3 } from "readline/promises";
4978
+ import WebSocket2 from "ws";
4509
4979
 
4510
4980
  // ../secret-scanner/src/registry.ts
4511
4981
  import { createRequire as createRequire2 } from "module";
@@ -6883,9 +7353,9 @@ function exec(cmd, cwd) {
6883
7353
  return execSync(cmd, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6884
7354
  }
6885
7355
  function runInteractive(cmd, args, cwd) {
6886
- return new Promise((resolve4, reject) => {
7356
+ return new Promise((resolve5, reject) => {
6887
7357
  const child = nodeSpawn(cmd, args, { cwd, stdio: "inherit", shell: false });
6888
- child.on("close", (code) => resolve4(code ?? 1));
7358
+ child.on("close", (code) => resolve5(code ?? 1));
6889
7359
  child.on("error", reject);
6890
7360
  });
6891
7361
  }
@@ -7057,10 +7527,10 @@ function warnIfMissingLlmCredentials(installPath) {
7057
7527
  }
7058
7528
  async function confirmDestructive(phrase) {
7059
7529
  const rl = createInterface2({ input: process.stdin, output: process.stderr });
7060
- return new Promise((resolve4) => {
7530
+ return new Promise((resolve5) => {
7061
7531
  rl.question(`Type "${phrase}" to confirm: `, (answer) => {
7062
7532
  rl.close();
7063
- resolve4(answer.trim() === phrase);
7533
+ resolve5(answer.trim() === phrase);
7064
7534
  });
7065
7535
  });
7066
7536
  }
@@ -7593,6 +8063,214 @@ async function handleServer(subcommand, rest, flags) {
7593
8063
  }
7594
8064
  }
7595
8065
 
8066
+ // src/codeRunInput.ts
8067
+ import { readdir, readFile, stat } from "fs/promises";
8068
+ import { basename as basename2, relative, resolve as resolve4 } from "path";
8069
+ var CODE_RUN_MAX_FILES = 50;
8070
+ var CODE_RUN_MAX_FILE_BYTES = 1e6;
8071
+ var CODE_RUN_MAX_TOTAL_BYTES = 1e7;
8072
+ var DEFAULT_IGNORES = [".git", "node_modules", "__pycache__", ".venv", "dist", "build"];
8073
+ async function buildCodeRunRequestsFromFlags(flags) {
8074
+ if (flags.chat || flags.targetEmbed) {
8075
+ if (!flags.chat || !flags.targetEmbed) {
8076
+ throw new Error("Chat-bound Code Run requires both --chat and --target-embed.");
8077
+ }
8078
+ return [{
8079
+ mode: "chat_bound",
8080
+ chat_id: flags.chat,
8081
+ target_embed_id: flags.targetEmbed,
8082
+ enable_internet: flags.noInternet !== true,
8083
+ files: []
8084
+ }];
8085
+ }
8086
+ const files = [];
8087
+ const seen = /* @__PURE__ */ new Set();
8088
+ const entry = flags.entry ? normalizeCodeRunPath(flags.entry) : void 0;
8089
+ let totalBytes = 0;
8090
+ const addFile = (file, byteLength) => {
8091
+ const path = normalizeCodeRunPath(file.path);
8092
+ if (seen.has(path)) throw new Error(`Duplicate Code Run file path: ${path}`);
8093
+ seen.add(path);
8094
+ totalBytes += byteLength;
8095
+ if (files.length + 1 > CODE_RUN_MAX_FILES) throw new Error(`Code Run supports at most ${CODE_RUN_MAX_FILES} files.`);
8096
+ if (byteLength > CODE_RUN_MAX_FILE_BYTES) throw new Error(`Code Run file is too large: ${path}`);
8097
+ if (totalBytes > CODE_RUN_MAX_TOTAL_BYTES) throw new Error("Code Run file bundle is too large.");
8098
+ files.push({ ...file, path });
8099
+ };
8100
+ if (flags.code !== void 0) {
8101
+ const filename = normalizeCodeRunPath(flags.filename || entry || defaultFilename(flags.language));
8102
+ const buffer = Buffer.from(flags.code, "utf8");
8103
+ addFile({
8104
+ path: filename,
8105
+ content_base64: buffer.toString("base64"),
8106
+ language: flags.language || inferLanguage(filename),
8107
+ mime_type: "text/plain",
8108
+ is_target: true
8109
+ }, buffer.byteLength);
8110
+ }
8111
+ for (const filePath of toList(flags.file)) {
8112
+ const absolute = resolve4(filePath);
8113
+ const buffer = await readFile(absolute);
8114
+ const sandboxPath = normalizeCodeRunPath(basename2(filePath));
8115
+ addFile({
8116
+ path: sandboxPath,
8117
+ content_base64: buffer.toString("base64"),
8118
+ language: inferLanguage(sandboxPath),
8119
+ mime_type: "text/plain",
8120
+ is_target: entry ? sandboxPath === entry : files.length === 0
8121
+ }, buffer.byteLength);
8122
+ }
8123
+ for (const dirPath of toList(flags.dir)) {
8124
+ const root = resolve4(dirPath);
8125
+ const dirFiles = await collectDirectoryFiles(root, flags.include, flags.exclude);
8126
+ for (const absolute of dirFiles) {
8127
+ const sandboxPath = normalizeCodeRunPath(relative(root, absolute).replace(/\\/g, "/"));
8128
+ const buffer = await readFile(absolute);
8129
+ addFile({
8130
+ path: sandboxPath,
8131
+ content_base64: buffer.toString("base64"),
8132
+ language: inferLanguage(sandboxPath),
8133
+ mime_type: "text/plain",
8134
+ is_target: entry ? sandboxPath === entry : files.length === 0
8135
+ }, buffer.byteLength);
8136
+ }
8137
+ }
8138
+ for (const url of toList(flags.url)) {
8139
+ const response = await fetch(url);
8140
+ if (!response.ok) throw new Error(`Failed to download Code Run URL ${redactUrl(url)} (HTTP ${response.status})`);
8141
+ const text = await response.text();
8142
+ const urlPath = new URL(url).pathname;
8143
+ const sandboxPath = normalizeCodeRunPath(entry || basename2(urlPath) || "main.txt");
8144
+ const buffer = Buffer.from(text, "utf8");
8145
+ addFile({
8146
+ path: sandboxPath,
8147
+ content_base64: buffer.toString("base64"),
8148
+ language: inferLanguage(sandboxPath),
8149
+ mime_type: "text/plain",
8150
+ is_target: true
8151
+ }, buffer.byteLength);
8152
+ }
8153
+ if (flags.repo) {
8154
+ await addGithubRepoFiles(flags.repo, toList(flags.include), async (path, text) => {
8155
+ const sandboxPath = normalizeCodeRunPath(path);
8156
+ const buffer = Buffer.from(text, "utf8");
8157
+ addFile({
8158
+ path: sandboxPath,
8159
+ content_base64: buffer.toString("base64"),
8160
+ language: inferLanguage(sandboxPath),
8161
+ mime_type: "text/plain",
8162
+ is_target: entry ? sandboxPath === entry : files.length === 0
8163
+ }, buffer.byteLength);
8164
+ });
8165
+ }
8166
+ if (files.length === 0) throw new Error("Code Run requires --code, --file, --dir, --url, --repo, or --chat/--target-embed.");
8167
+ const entryPath = entry || files.find((file) => file.is_target)?.path || files[0].path;
8168
+ if (!files.some((file) => file.path === entryPath)) throw new Error(`Code Run entry file was not included: ${entryPath}`);
8169
+ for (const file of files) file.is_target = file.path === entryPath;
8170
+ return [{
8171
+ mode: "direct",
8172
+ entry_path: entryPath,
8173
+ enable_internet: flags.noInternet !== true,
8174
+ files
8175
+ }];
8176
+ }
8177
+ function buildCodeRunStreamUrl(params) {
8178
+ const base = params.apiUrl.replace(/\/$/, "").replace(/^http:/, "ws:").replace(/^https:/, "wss:");
8179
+ const query = new URLSearchParams({ sessionId: params.sessionId, token: params.token });
8180
+ return `${base}/v1/code/run/${encodeURIComponent(params.executionId)}/stream?${query.toString()}`;
8181
+ }
8182
+ function normalizeCodeRunPath(rawPath) {
8183
+ const path = rawPath.trim().replace(/\\/g, "/");
8184
+ if (!path || path.includes("\0") || path.startsWith("/") || path.startsWith("~/") || /^[A-Za-z]:\//.test(path)) {
8185
+ throw new Error(`Unsafe Code Run path: ${rawPath}`);
8186
+ }
8187
+ const parts = path.split("/");
8188
+ if (parts.some((part) => !part || part === "." || part === "..")) {
8189
+ throw new Error(`Unsafe Code Run path: ${rawPath}`);
8190
+ }
8191
+ return parts.join("/");
8192
+ }
8193
+ async function collectDirectoryFiles(root, include, exclude) {
8194
+ const includes = toList(include);
8195
+ const excludes = [...DEFAULT_IGNORES, ...toList(exclude)];
8196
+ const files = [];
8197
+ async function walk(directory) {
8198
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
8199
+ const absolute = resolve4(directory, entry.name);
8200
+ const rel = relative(root, absolute).replace(/\\/g, "/");
8201
+ if (matchesAny(rel, excludes) || matchesAny(entry.name, excludes)) continue;
8202
+ if (entry.isDirectory()) {
8203
+ await walk(absolute);
8204
+ } else if (entry.isFile()) {
8205
+ if (includes.length === 0 || matchesAny(rel, includes)) files.push(absolute);
8206
+ }
8207
+ }
8208
+ }
8209
+ const rootStat = await stat(root);
8210
+ if (!rootStat.isDirectory()) throw new Error(`Code Run --dir is not a directory: ${root}`);
8211
+ await walk(root);
8212
+ return files.sort();
8213
+ }
8214
+ async function addGithubRepoFiles(repoUrl, includes, add) {
8215
+ if (includes.length === 0) throw new Error("Code Run --repo requires at least one --include path in v1.");
8216
+ const parsed = parseGithubRepoUrl(repoUrl);
8217
+ for (const includePath of includes) {
8218
+ const path = normalizeCodeRunPath(includePath);
8219
+ const rawUrl = `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/${parsed.ref}/${path}`;
8220
+ const response = await fetch(rawUrl);
8221
+ if (!response.ok) throw new Error(`Failed to download GitHub file ${path} (HTTP ${response.status})`);
8222
+ await add(path, await response.text());
8223
+ }
8224
+ }
8225
+ function parseGithubRepoUrl(repoUrl) {
8226
+ const url = new URL(repoUrl);
8227
+ if (url.hostname !== "github.com") throw new Error("Code Run --repo supports public github.com URLs in v1.");
8228
+ const parts = url.pathname.split("/").filter(Boolean);
8229
+ if (parts.length < 2) throw new Error("Invalid GitHub repository URL.");
8230
+ return { owner: parts[0], repo: parts[1].replace(/\.git$/, ""), ref: "main" };
8231
+ }
8232
+ function toList(value) {
8233
+ if (Array.isArray(value)) return value.flatMap((item) => item.split("\n")).filter(Boolean);
8234
+ if (typeof value === "string") return value.split("\n").filter(Boolean);
8235
+ return [];
8236
+ }
8237
+ function matchesAny(path, patterns) {
8238
+ return patterns.some((pattern) => matchesPattern(path, pattern));
8239
+ }
8240
+ function matchesPattern(path, pattern) {
8241
+ if (!pattern) return false;
8242
+ if (pattern.includes("*")) {
8243
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
8244
+ return new RegExp(`^${escaped}$`).test(path) || new RegExp(`(^|/)${escaped}$`).test(path);
8245
+ }
8246
+ return path === pattern || path.startsWith(`${pattern}/`) || path.endsWith(`/${pattern}`);
8247
+ }
8248
+ function inferLanguage(path) {
8249
+ const ext = path.split(".").pop()?.toLowerCase();
8250
+ if (ext === "py") return "python";
8251
+ if (ext === "js" || ext === "mjs" || ext === "cjs") return "javascript";
8252
+ if (ext === "ts") return "typescript";
8253
+ if (ext === "sh") return "bash";
8254
+ if (ext === "go") return "go";
8255
+ if (ext === "rs") return "rust";
8256
+ if (ext === "c") return "c";
8257
+ if (ext === "cc" || ext === "cpp" || ext === "cxx") return "cpp";
8258
+ return "";
8259
+ }
8260
+ function defaultFilename(language) {
8261
+ if (language === "python" || language === "py") return "main.py";
8262
+ if (language === "javascript" || language === "js" || language === "node") return "main.js";
8263
+ if (language === "typescript" || language === "ts") return "main.ts";
8264
+ if (language === "bash" || language === "sh") return "main.sh";
8265
+ return "main.txt";
8266
+ }
8267
+ function redactUrl(rawUrl) {
8268
+ const url = new URL(rawUrl);
8269
+ url.search = "";
8270
+ url.hash = "";
8271
+ return url.toString();
8272
+ }
8273
+
7596
8274
  // src/cli.ts
7597
8275
  async function main() {
7598
8276
  const parsed = parseArgs(process.argv.slice(2));
@@ -7625,6 +8303,14 @@ async function main() {
7625
8303
  printSettingsHelp(client);
7626
8304
  return;
7627
8305
  }
8306
+ if (command === "signup") {
8307
+ printSignupHelp();
8308
+ return;
8309
+ }
8310
+ if (command === "e2e") {
8311
+ printE2EHelp();
8312
+ return;
8313
+ }
7628
8314
  if (command === "mentions") {
7629
8315
  printMentionsHelp();
7630
8316
  return;
@@ -7665,6 +8351,14 @@ async function main() {
7665
8351
  console.log("Login successful.");
7666
8352
  return;
7667
8353
  }
8354
+ if (command === "signup") {
8355
+ await handleSignup(client, parsed.flags);
8356
+ return;
8357
+ }
8358
+ if (command === "e2e") {
8359
+ await handleE2E(client, subcommand, rest, parsed.flags);
8360
+ return;
8361
+ }
7668
8362
  if (command === "logout") {
7669
8363
  await client.logout();
7670
8364
  console.log("Logged out.");
@@ -7916,10 +8610,10 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
7916
8610
  input: process.stdin,
7917
8611
  output: process.stdout
7918
8612
  });
7919
- const answer = await new Promise((resolve4) => {
8613
+ const answer = await new Promise((resolve5) => {
7920
8614
  iface.question(
7921
8615
  `Delete ${resolved.length} chat(s)? This cannot be undone. [y/N] `,
7922
- resolve4
8616
+ resolve5
7923
8617
  );
7924
8618
  });
7925
8619
  iface.close();
@@ -8473,6 +9167,10 @@ The booking_token is shown in the output of:
8473
9167
  }
8474
9168
  return;
8475
9169
  }
9170
+ if (subcommand === "code" && rest[0] === "run") {
9171
+ await handleCodeRun(client, flags, apiKey);
9172
+ return;
9173
+ }
8476
9174
  const app = subcommand;
8477
9175
  const skill = rest[0];
8478
9176
  if (app && skill) {
@@ -8588,6 +9286,111 @@ Usage:
8588
9286
  printAppsHelp();
8589
9287
  process.exit(1);
8590
9288
  }
9289
+ async function handleCodeRun(client, flags, apiKey) {
9290
+ const requests = await buildCodeRunRequestsFromFlags({
9291
+ code: typeof flags.code === "string" ? flags.code : void 0,
9292
+ language: typeof flags.language === "string" ? flags.language : void 0,
9293
+ filename: typeof flags.filename === "string" ? flags.filename : void 0,
9294
+ entry: typeof flags.entry === "string" ? flags.entry : void 0,
9295
+ file: typeof flags.file === "string" ? flags.file : void 0,
9296
+ dir: typeof flags.dir === "string" ? flags.dir : void 0,
9297
+ url: typeof flags.url === "string" ? flags.url : void 0,
9298
+ repo: typeof flags.repo === "string" ? flags.repo : void 0,
9299
+ include: typeof flags.include === "string" ? flags.include : void 0,
9300
+ exclude: typeof flags.exclude === "string" ? flags.exclude : void 0,
9301
+ noInternet: flags["no-internet"] === true,
9302
+ chat: typeof flags.chat === "string" ? flags.chat : void 0,
9303
+ targetEmbed: typeof flags["target-embed"] === "string" ? flags["target-embed"] : void 0
9304
+ });
9305
+ if (flags.json !== true) {
9306
+ const first = requests[0];
9307
+ if (first.mode === "direct") {
9308
+ process.stderr.write(`Starting Code Run for ${first.entry_path} (${first.files.length} file${first.files.length === 1 ? "" : "s"})...
9309
+ `);
9310
+ }
9311
+ }
9312
+ const response = await client.runSkill({
9313
+ app: "code",
9314
+ skill: "run",
9315
+ inputData: { requests },
9316
+ apiKey
9317
+ });
9318
+ const result = response.data?.results?.[0];
9319
+ if (!result?.execution_id || !result.status_path) {
9320
+ throw new Error("Code Run did not return an execution id.");
9321
+ }
9322
+ const streamAuth = apiKey ? null : await client.getCodeRunStreamAuth();
9323
+ let finalStatus;
9324
+ if (streamAuth && result.stream_path) {
9325
+ const url = buildCodeRunStreamUrl({
9326
+ apiUrl: client.apiUrl,
9327
+ executionId: result.execution_id,
9328
+ sessionId: streamAuth.sessionId,
9329
+ token: streamAuth.token
9330
+ });
9331
+ try {
9332
+ finalStatus = await streamCodeRunToTerminal(url, flags.json === true);
9333
+ } catch (err) {
9334
+ if (!streamAuth.fallbackToken || streamAuth.fallbackToken === streamAuth.token) throw err;
9335
+ const fallbackUrl = buildCodeRunStreamUrl({
9336
+ apiUrl: client.apiUrl,
9337
+ executionId: result.execution_id,
9338
+ sessionId: streamAuth.sessionId,
9339
+ token: streamAuth.fallbackToken
9340
+ });
9341
+ finalStatus = await streamCodeRunToTerminal(fallbackUrl, flags.json === true);
9342
+ }
9343
+ } else {
9344
+ finalStatus = await pollCodeRunStatus(client, result.status_path, apiKey, flags.json === true);
9345
+ }
9346
+ if (flags.json === true) {
9347
+ printJson2({ ...result, final: finalStatus });
9348
+ }
9349
+ }
9350
+ async function streamCodeRunToTerminal(url, jsonMode) {
9351
+ return await new Promise((resolve5, reject) => {
9352
+ const ws = new WebSocket2(url);
9353
+ let lastStatus = {};
9354
+ ws.on("message", (data) => {
9355
+ try {
9356
+ const message = JSON.parse(String(data));
9357
+ const payload = message.payload ?? {};
9358
+ if (message.type === "code_run_event") {
9359
+ const kind = String(payload.kind ?? "status");
9360
+ const text = String(payload.text ?? "");
9361
+ if (!jsonMode) {
9362
+ if (kind === "stdout") process.stdout.write(text);
9363
+ else process.stderr.write(text);
9364
+ }
9365
+ } else if (message.type === "code_run_update") {
9366
+ lastStatus = { ...lastStatus, ...payload };
9367
+ const status = String(payload.status ?? "");
9368
+ if (["finished", "failed", "timeout", "cancelled"].includes(status)) {
9369
+ ws.close();
9370
+ resolve5(lastStatus);
9371
+ }
9372
+ }
9373
+ } catch (err) {
9374
+ ws.close();
9375
+ reject(err);
9376
+ }
9377
+ });
9378
+ ws.on("error", () => reject(new Error("Code Run stream failed.")));
9379
+ ws.on("close", () => {
9380
+ if (Object.keys(lastStatus).length > 0) resolve5(lastStatus);
9381
+ });
9382
+ });
9383
+ }
9384
+ async function pollCodeRunStatus(client, statusPath, apiKey, jsonMode) {
9385
+ for (; ; ) {
9386
+ const status = await client.getCodeRunStatus(statusPath, apiKey);
9387
+ const value = String(status.status ?? "");
9388
+ if (!jsonMode && value) process.stderr.write(`Code Run status: ${value}
9389
+ `);
9390
+ if (["finished", "failed", "timeout", "cancelled"].includes(value)) return status;
9391
+ await new Promise((resolve5) => setTimeout(resolve5, 1e3));
9392
+ }
9393
+ }
8591
9394
  function buildSkillInput(flags, inlineTokens, schemaParams) {
8592
9395
  if (typeof flags.input === "string") {
8593
9396
  return JSON.parse(flags.input);
@@ -8742,6 +9545,7 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
8742
9545
  { path: ["account", "profile-picture", "set"], description: "Upload a profile picture", examples: ["openmates settings account profile-picture set ./avatar.jpg"] },
8743
9546
  { path: ["account", "chats", "stats"], description: "Show chat statistics", examples: ["openmates settings account chats stats"] },
8744
9547
  { path: ["account", "delete", "preview"], description: "Preview account deletion impact", examples: ["openmates settings account delete preview"] },
9548
+ { path: ["account", "delete"], description: "Delete your account with email code plus 2FA when configured", examples: ["openmates settings account delete --yes"] },
8745
9549
  { path: ["account", "storage", "overview"], description: "Show storage overview", examples: ["openmates settings account storage overview"] },
8746
9550
  { path: ["account", "storage", "files"], description: "List stored files", examples: ["openmates settings account storage files --category images"] },
8747
9551
  { path: ["account", "storage", "delete"], description: "Delete one stored file by file ID", examples: ["openmates settings account storage delete <file-id> --yes"] },
@@ -8756,12 +9560,18 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
8756
9560
  { path: ["billing", "usage", "summaries"], description: "Show usage summaries", examples: ["openmates settings billing usage summaries"] },
8757
9561
  { path: ["billing", "usage", "daily"], description: "Show daily usage overview", examples: ["openmates settings billing usage daily"] },
8758
9562
  { path: ["billing", "usage", "export"], description: "Export usage data", examples: ["openmates settings billing usage export --json"] },
9563
+ { path: ["billing", "buy-credits", "bank-transfer"], description: "Buy credits by SEPA bank transfer", examples: ["openmates settings billing buy-credits bank-transfer --credits 110000"] },
9564
+ { path: ["billing", "bank-transfer", "status"], description: "Show bank-transfer order status", examples: ["openmates settings billing bank-transfer status <order-id>"] },
9565
+ { path: ["billing", "bank-transfer", "list"], description: "List pending bank-transfer orders", examples: ["openmates settings billing bank-transfer list"] },
8759
9566
  { path: ["billing", "invoices", "list"], description: "List invoices", examples: ["openmates settings billing invoices list --json"] },
8760
9567
  { path: ["billing", "invoices", "download"], description: "Download an invoice PDF", examples: ["openmates settings billing invoices download <invoice-id> --output ./invoices"] },
8761
9568
  { path: ["billing", "invoices", "credit-note"], description: "Download a credit note PDF", examples: ["openmates settings billing invoices credit-note <invoice-id> --output ./invoices"] },
8762
9569
  { path: ["billing", "invoices", "refund"], description: "Request a refund for an invoice", examples: ["openmates settings billing invoices refund <invoice-id> --yes"] },
8763
9570
  { path: ["billing", "gift-card", "redeem"], description: "Redeem a gift card", examples: ["openmates settings billing gift-card redeem ABCD-1234"] },
8764
9571
  { path: ["billing", "gift-card", "list"], description: "List redeemed gift cards", examples: ["openmates settings billing gift-card list"] },
9572
+ { path: ["billing", "gift-card", "buy", "bank-transfer"], description: "Buy a gift card by SEPA bank transfer", examples: ["openmates settings billing gift-card buy bank-transfer --credits 21000"] },
9573
+ { path: ["billing", "gift-card", "purchase-status"], description: "Show gift-card bank-transfer purchase status", examples: ["openmates settings billing gift-card purchase-status <order-id>"] },
9574
+ { path: ["billing", "gift-card", "purchased"], description: "List purchased unused gift cards", examples: ["openmates settings billing gift-card purchased"] },
8765
9575
  { path: ["billing", "auto-topup", "low-balance", "set"], description: "Configure low-balance auto top-up", examples: ["openmates settings billing auto-topup low-balance set --enabled true --amount 1000 --currency eur --email you@example.com"] },
8766
9576
  { path: ["notifications", "status"], description: "Show notification settings", examples: ["openmates settings notifications status --json"] },
8767
9577
  { path: ["notifications", "list"], description: "List recent notification events", examples: ["openmates settings notifications list --limit 20 --json"] },
@@ -8787,7 +9597,6 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
8787
9597
  ];
8788
9598
  var SETTINGS_INFO_COMMANDS = [
8789
9599
  { path: ["account", "email"], description: "Email changes are web-only", webPath: "account/email", reason: "Email changes require a guided identity verification flow.", examples: ["openmates settings account email"] },
8790
- { path: ["account", "delete"], description: "Account deletion is web-only", webPath: "account/delete", reason: "Account deletion requires browser-based reauthentication and explicit confirmation.", examples: ["openmates settings account delete"] },
8791
9600
  { path: ["security"], description: "Security settings are web-only", webPath: "account/security", reason: "Security settings require browser APIs or high-risk reauthentication.", examples: ["openmates settings security"] },
8792
9601
  { path: ["security", "passkeys"], description: "Passkeys are web-only", webPath: "account/security/passkeys", reason: "Passkeys require WebAuthn browser APIs.", examples: ["openmates settings security passkeys"] },
8793
9602
  { path: ["security", "password"], description: "Password changes are web-only", webPath: "account/security/password", reason: "The CLI never asks for account credentials.", examples: ["openmates settings security password"] },
@@ -8969,9 +9778,9 @@ function parseYamlScalar(value) {
8969
9778
  }
8970
9779
  async function saveDownloadedDocument(document, output) {
8971
9780
  const { mkdir, writeFile } = await import("fs/promises");
8972
- const { join: join4, basename: basename2, dirname } = await import("path");
9781
+ const { join: join4, basename: basename3, dirname } = await import("path");
8973
9782
  const target = typeof output === "string" ? output : ".";
8974
- const filename = basename2(document.filename || "document.pdf");
9783
+ const filename = basename3(document.filename || "document.pdf");
8975
9784
  const filePath = target.endsWith(".pdf") ? target : join4(target, filename);
8976
9785
  await mkdir(dirname(filePath), { recursive: true });
8977
9786
  await writeFile(filePath, document.data);
@@ -9001,13 +9810,274 @@ function printMateInfo(mateId, json) {
9001
9810
  async function confirmOrExit(question) {
9002
9811
  const rl = await import("readline");
9003
9812
  const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
9004
- const answer = await new Promise((resolve4) => iface.question(question, resolve4));
9813
+ const answer = await new Promise((resolve5) => iface.question(question, resolve5));
9005
9814
  iface.close();
9006
9815
  if (answer.trim().toLowerCase() !== "y") {
9007
9816
  console.log("Aborted.");
9008
9817
  process.exit(0);
9009
9818
  }
9010
9819
  }
9820
+ async function promptLine(question) {
9821
+ const rl = await import("readline");
9822
+ const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
9823
+ const answer = await new Promise((resolve5) => iface.question(question, resolve5));
9824
+ iface.close();
9825
+ return answer.trim();
9826
+ }
9827
+ async function promptSecret(question) {
9828
+ if (!process.stdin.isTTY) {
9829
+ return promptLine(question);
9830
+ }
9831
+ return new Promise((resolve5) => {
9832
+ const stdin2 = process.stdin;
9833
+ const wasRaw = stdin2.isRaw;
9834
+ let value = "";
9835
+ process.stdout.write(question);
9836
+ stdin2.setRawMode(true);
9837
+ stdin2.resume();
9838
+ const onData = (chunk) => {
9839
+ const char = chunk.toString("utf8");
9840
+ if (char === "\r" || char === "\n") {
9841
+ stdin2.off("data", onData);
9842
+ stdin2.setRawMode(wasRaw);
9843
+ process.stdout.write("\n");
9844
+ resolve5(value);
9845
+ return;
9846
+ }
9847
+ if (char === "") {
9848
+ stdin2.off("data", onData);
9849
+ stdin2.setRawMode(wasRaw);
9850
+ process.stdout.write("\n");
9851
+ process.exit(130);
9852
+ }
9853
+ if (char === "\x7F" || char === "\b") {
9854
+ value = value.slice(0, -1);
9855
+ return;
9856
+ }
9857
+ value += char;
9858
+ };
9859
+ stdin2.on("data", onData);
9860
+ });
9861
+ }
9862
+ async function writeSecretFile(filePath, content, force = false) {
9863
+ const { mkdir, writeFile, stat: stat2 } = await import("fs/promises");
9864
+ const { dirname } = await import("path");
9865
+ try {
9866
+ await stat2(filePath);
9867
+ if (!force) throw new Error(`${filePath} already exists. Use --force to overwrite.`);
9868
+ } catch (error) {
9869
+ if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
9870
+ throw error;
9871
+ }
9872
+ if (error instanceof Error && !("code" in error)) throw error;
9873
+ }
9874
+ await mkdir(dirname(filePath), { recursive: true });
9875
+ await writeFile(filePath, content, { mode: 384 });
9876
+ return filePath;
9877
+ }
9878
+ async function generateProvisioningPassword() {
9879
+ const { randomBytes: randomBytes3 } = await import("crypto");
9880
+ return `OM-${randomBytes3(18).toString("base64url")}-aA2#`;
9881
+ }
9882
+ function decodeBase32(input) {
9883
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
9884
+ const cleaned = input.toUpperCase().replace(/[^A-Z2-7]/g, "");
9885
+ let bits = "";
9886
+ for (const char of cleaned) {
9887
+ const value = alphabet.indexOf(char);
9888
+ if (value >= 0) bits += value.toString(2).padStart(5, "0");
9889
+ }
9890
+ const bytes = [];
9891
+ for (let index = 0; index + 8 <= bits.length; index += 8) {
9892
+ bytes.push(Number.parseInt(bits.slice(index, index + 8), 2));
9893
+ }
9894
+ return Buffer.from(bytes);
9895
+ }
9896
+ async function generateTotpCode(secret) {
9897
+ const { createHmac } = await import("crypto");
9898
+ const counter = Math.floor(Date.now() / 1e3 / 30);
9899
+ const counterBuffer = Buffer.alloc(8);
9900
+ counterBuffer.writeBigUInt64BE(BigInt(counter));
9901
+ const hmac = createHmac("sha1", decodeBase32(secret)).update(counterBuffer).digest();
9902
+ const offset = hmac[hmac.length - 1] & 15;
9903
+ const code = (hmac.readUInt32BE(offset) & 2147483647) % 1e6;
9904
+ return String(code).padStart(6, "0");
9905
+ }
9906
+ async function runSecuritySetup(client, flags, options = {}) {
9907
+ const result = {};
9908
+ if (flags["skip-2fa"] === true) {
9909
+ if (flags.yes !== true) await confirmOrExit("Skip 2FA setup? This weakens account protection. [y/N] ");
9910
+ } else {
9911
+ const setup = await client.startTotpSetup();
9912
+ result.otpSecret = setup.secret ?? null;
9913
+ if (setup.otpauth_url) client.renderTotpQrCode(setup.otpauth_url);
9914
+ if (setup.secret) console.log(`Manual TOTP secret: ${setup.secret}`);
9915
+ const code = typeof process.env.OPENMATES_CLI_SIGNUP_TOTP_CODE === "string" ? process.env.OPENMATES_CLI_SIGNUP_TOTP_CODE : options.autoGenerateTotp && setup.secret ? await generateTotpCode(setup.secret) : await promptLine("Enter current 2FA code: ");
9916
+ await client.verifyTotpSetup(code);
9917
+ const provider = typeof flags.provider === "string" ? flags.provider : "Authenticator app";
9918
+ await client.setTotpProvider(provider);
9919
+ const backup = await client.requestBackupCodes();
9920
+ result.backupCodes = backup.backup_codes;
9921
+ const backupOutput = typeof flags["backup-codes-output"] === "string" ? flags["backup-codes-output"] : void 0;
9922
+ if (backupOutput) {
9923
+ result.backupCodesPath = await writeSecretFile(backupOutput, `${backup.backup_codes.join("\n")}
9924
+ `, flags.force === true);
9925
+ await client.confirmBackupCodesStored();
9926
+ } else if (flags.yes === true) {
9927
+ await client.confirmBackupCodesStored();
9928
+ } else {
9929
+ console.log("Backup codes:");
9930
+ for (const backupCode of backup.backup_codes) console.log(` ${backupCode}`);
9931
+ await confirmOrExit("I stored these backup codes safely. Continue? [y/N] ");
9932
+ await client.confirmBackupCodesStored();
9933
+ }
9934
+ }
9935
+ if (flags["skip-recovery-key"] === true) {
9936
+ if (flags.yes !== true) await confirmOrExit("Skip recovery-key setup? You may lose access to encrypted data. [y/N] ");
9937
+ } else {
9938
+ const recovery = await client.createAndConfirmRecoveryKey();
9939
+ result.recoveryKey = recovery.recoveryKey;
9940
+ const recoveryOutput = typeof flags["recovery-key-output"] === "string" ? flags["recovery-key-output"] : void 0;
9941
+ if (recoveryOutput) {
9942
+ result.recoveryKeyPath = await writeSecretFile(recoveryOutput, `${recovery.recoveryKey}
9943
+ `, flags.force === true);
9944
+ } else {
9945
+ console.log(`Recovery key: ${recovery.recoveryKey}`);
9946
+ if (flags.yes !== true) await confirmOrExit("I stored this recovery key safely. Continue? [y/N] ");
9947
+ }
9948
+ }
9949
+ return result;
9950
+ }
9951
+ async function handleSignup(client, flags, options = {}) {
9952
+ if (flags.password !== void 0) {
9953
+ throw new Error("Passwords must be entered through hidden prompts, not command-line flags.");
9954
+ }
9955
+ const email = typeof flags.email === "string" ? flags.email : await promptLine("Email: ");
9956
+ const username = typeof flags.username === "string" ? flags.username : await promptLine("Username: ");
9957
+ const inviteCode = typeof flags["invite-code"] === "string" ? flags["invite-code"] : "";
9958
+ const language = typeof flags.language === "string" ? flags.language : "en";
9959
+ const password = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await promptSecret("Password: ");
9960
+ if (!process.env.OPENMATES_CLI_SIGNUP_PASSWORD) {
9961
+ const confirmPassword = await promptSecret("Confirm password: ");
9962
+ if (password !== confirmPassword) throw new Error("Passwords do not match.");
9963
+ }
9964
+ await client.requestSignupEmailCode({ email, inviteCode, language });
9965
+ const emailCode = process.env.OPENMATES_CLI_SIGNUP_EMAIL_CODE ?? await promptLine("Email verification code: ");
9966
+ await client.verifySignupEmailCode({ email, username, inviteCode, code: emailCode, language });
9967
+ const signup = await client.setupPasswordAccount({ email, username, password, inviteCode, language });
9968
+ const security = await runSecuritySetup(client, flags, options);
9969
+ let giftCardResult = null;
9970
+ if (typeof flags["gift-card-code"] === "string") {
9971
+ giftCardResult = await client.redeemGiftCard(flags["gift-card-code"]);
9972
+ }
9973
+ const response = {
9974
+ success: true,
9975
+ user: signup.user ?? null,
9976
+ backup_codes_path: security.backupCodesPath ?? null,
9977
+ recovery_key_path: security.recoveryKeyPath ?? null,
9978
+ gift_card: giftCardResult
9979
+ };
9980
+ if (flags.json === true) {
9981
+ printJson2(response);
9982
+ } else {
9983
+ console.log("\x1B[32m\u2713\x1B[0m Account created and CLI session saved.");
9984
+ if (security.backupCodesPath) console.log(`Backup codes saved to ${security.backupCodesPath}`);
9985
+ if (security.recoveryKeyPath) console.log(`Recovery key saved to ${security.recoveryKeyPath}`);
9986
+ console.log("Buy credits or redeem a gift card:");
9987
+ console.log(" openmates settings billing buy-credits bank-transfer --credits 110000");
9988
+ console.log(" openmates settings billing gift-card redeem <CODE>");
9989
+ }
9990
+ return {
9991
+ user: signup.user ?? null,
9992
+ backupCodesPath: security.backupCodesPath,
9993
+ recoveryKeyPath: security.recoveryKeyPath,
9994
+ security,
9995
+ giftCard: giftCardResult
9996
+ };
9997
+ }
9998
+ async function handleE2E(client, subcommand, rest, flags) {
9999
+ if (subcommand !== "provision-auth-accounts") {
10000
+ printE2EHelp();
10001
+ return;
10002
+ }
10003
+ if (client.apiUrl.includes("api.openmates.org") && !client.apiUrl.includes("api.dev.openmates.org")) {
10004
+ throw new Error("E2E provisioning refuses production API URLs.");
10005
+ }
10006
+ if (flags.password !== void 0) throw new Error("Use generated passwords or OPENMATES_CLI_SIGNUP_PASSWORD, not --password.");
10007
+ const slot = parseRequiredNumber(flags.slot, "--slot");
10008
+ if (![15, 17].includes(slot)) throw new Error("Only reserved slots 15 and 17 are supported.");
10009
+ const artifact = typeof flags.artifact === "string" ? flags.artifact : `test-results/credential-updates/slot-${slot}.env`;
10010
+ const domain = typeof flags.domain === "string" ? flags.domain : process.env.OPENMATES_CLI_E2E_EMAIL_DOMAIN;
10011
+ const email = typeof flags.email === "string" ? flags.email : domain ? `cli-e2e-slot-${slot}-${Date.now()}@${domain}` : await promptLine("E2E account email: ");
10012
+ const username = typeof flags.username === "string" ? flags.username : `cli_e2e_slot_${slot}_${Date.now()}`;
10013
+ const generatedPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await generateProvisioningPassword();
10014
+ const originalSignupPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD;
10015
+ process.env.OPENMATES_CLI_SIGNUP_PASSWORD = generatedPassword;
10016
+ let signup = null;
10017
+ try {
10018
+ signup = await handleSignup(client, {
10019
+ ...flags,
10020
+ email,
10021
+ username,
10022
+ yes: true,
10023
+ json: false,
10024
+ "backup-codes-output": typeof flags["backup-codes-output"] === "string" ? flags["backup-codes-output"] : `${artifact}.backup-codes`,
10025
+ "recovery-key-output": typeof flags["recovery-key-output"] === "string" ? flags["recovery-key-output"] : `${artifact}.recovery-key`
10026
+ }, { autoGenerateTotp: true });
10027
+ } finally {
10028
+ if (originalSignupPassword === void 0) delete process.env.OPENMATES_CLI_SIGNUP_PASSWORD;
10029
+ else process.env.OPENMATES_CLI_SIGNUP_PASSWORD = originalSignupPassword;
10030
+ }
10031
+ if (!signup.security.otpSecret) throw new Error("Provisioning did not create a TOTP secret.");
10032
+ const artifactBody = [
10033
+ `OPENMATES_TEST_ACCOUNT_${slot}_EMAIL=${email}`,
10034
+ `OPENMATES_TEST_ACCOUNT_${slot}_PASSWORD=${generatedPassword}`,
10035
+ `OPENMATES_TEST_ACCOUNT_${slot}_OTP_KEY=${signup.security.otpSecret}`,
10036
+ `# Backup codes: ${artifact}.backup-codes`,
10037
+ `# Recovery key: ${artifact}.recovery-key`,
10038
+ ""
10039
+ ].join("\n");
10040
+ const path = await writeSecretFile(artifact, artifactBody, flags.force === true);
10041
+ if (flags.json === true) printJson2({ success: true, artifact: path, slot, email });
10042
+ else console.log(`Provisioning artifact written to ${path}. Upload secrets through the trusted manual process.`);
10043
+ }
10044
+ function printBankTransferOrder(order, giftCard) {
10045
+ header(giftCard ? "Gift Card Bank Transfer" : "Bank Transfer");
10046
+ console.log(`Order ID: ${order.order_id}`);
10047
+ console.log(`Credits: ${order.credits_amount}`);
10048
+ console.log(`Amount: EUR ${order.amount_eur}`);
10049
+ console.log(`Reference: ${order.reference}`);
10050
+ console.log(`IBAN: ${order.iban}`);
10051
+ console.log(`BIC: ${order.bic}`);
10052
+ console.log(`Bank: ${order.bank_name}`);
10053
+ console.log(`Account holder: ${order.account_holder_name}`);
10054
+ if (order.account_holder_address_line1) console.log(`Address: ${order.account_holder_address_line1}`);
10055
+ if (order.account_holder_address_line2) console.log(` ${order.account_holder_address_line2}`);
10056
+ if (order.account_holder_postal_code || order.account_holder_city) {
10057
+ console.log(` ${[order.account_holder_postal_code, order.account_holder_city].filter(Boolean).join(" ")}`);
10058
+ }
10059
+ if (order.account_holder_country) console.log(` ${order.account_holder_country}`);
10060
+ console.log(`Expires: ${order.expires_at}`);
10061
+ console.log("\nInclude the exact reference in your bank transfer. Missing or changed references require manual review.");
10062
+ if (giftCard) {
10063
+ console.log(`Gift-card code appears after payment is matched: openmates settings billing gift-card purchase-status ${order.order_id}`);
10064
+ } else {
10065
+ console.log(`Check status: openmates settings billing bank-transfer status ${order.order_id}`);
10066
+ }
10067
+ }
10068
+ function printBankTransferStatus(status, giftCard) {
10069
+ header(giftCard ? "Gift Card Purchase Status" : "Bank Transfer Status");
10070
+ console.log(`Order ID: ${status.order_id}`);
10071
+ console.log(`Status: ${status.status}`);
10072
+ console.log(`Credits: ${status.credits_amount}`);
10073
+ console.log(`Amount: EUR ${status.amount_eur}`);
10074
+ console.log(`Reference: ${status.reference}`);
10075
+ console.log(`Expires: ${status.expires_at}`);
10076
+ if (giftCard) {
10077
+ const code = status.gift_card_code;
10078
+ console.log(`Gift-card code: ${code || "available after payment is matched"}`);
10079
+ }
10080
+ }
9011
10081
  function printSettingsInfoCommand(client, command, json) {
9012
10082
  const appUrl = deriveAppUrl(client.apiUrl);
9013
10083
  const webUrl = command.webPath ? `${appUrl}/#settings/${command.webPath}` : null;
@@ -9074,8 +10144,8 @@ async function handleSettings(client, subcommand, rest, flags) {
9074
10144
  if (matches(tokens, ["account", "import-chat"])) {
9075
10145
  const file = rest[1];
9076
10146
  if (!file) throw new Error("Missing import file. Example: openmates settings account import-chat ./chat.yml");
9077
- const { readFile } = await import("fs/promises");
9078
- const content = await readFile(file, "utf-8");
10147
+ const { readFile: readFile2 } = await import("fs/promises");
10148
+ const content = await readFile2(file, "utf-8");
9079
10149
  await printSettingsMutationResult(
9080
10150
  client.settingsPost("import-chat", parseChatImportPayload(content)),
9081
10151
  flags
@@ -9102,6 +10172,28 @@ async function handleSettings(client, subcommand, rest, flags) {
9102
10172
  await printSettingsResult(client.settingsGet("delete-account-preview"), flags);
9103
10173
  return;
9104
10174
  }
10175
+ if (matches(tokens, ["account", "delete"])) {
10176
+ if (flags["email-code"] !== void 0 || flags["totp-code"] !== void 0) {
10177
+ throw new Error("Deletion verification codes must be entered through interactive prompts, not command-line flags.");
10178
+ }
10179
+ const preview = await client.settingsGet("delete-account-preview");
10180
+ if (flags.json !== true) {
10181
+ header("Delete Account Preview");
10182
+ printGenericObject(preview);
10183
+ }
10184
+ if (flags.yes !== true) {
10185
+ await confirmOrExit("Delete this account and all associated data? [y/N] ");
10186
+ }
10187
+ await client.requestDeleteAccountEmailCode();
10188
+ const emailCode = await promptLine("Enter email verification code: ");
10189
+ await client.verifyDeleteAccountEmailCode(emailCode);
10190
+ const authMethods = await client.getAuthMethodsStatus();
10191
+ const totpCode = authMethods.has_2fa ? await promptLine("Enter 2FA code: ") : void 0;
10192
+ const result = await client.deleteAccountWithCliVerification(totpCode);
10193
+ if (flags.json === true) printJson2(result);
10194
+ else console.log("\x1B[32m\u2713\x1B[0m Account deletion requested and local CLI session cleared");
10195
+ return;
10196
+ }
9105
10197
  if (matches(tokens, ["account", "storage", "overview"])) {
9106
10198
  await printSettingsResult(client.settingsGet("storage"), flags);
9107
10199
  return;
@@ -9190,6 +10282,23 @@ async function handleSettings(client, subcommand, rest, flags) {
9190
10282
  await printSettingsResult(client.settingsGet("usage"), flags);
9191
10283
  return;
9192
10284
  }
10285
+ if (matches(tokens, ["billing", "buy-credits", "bank-transfer"])) {
10286
+ const credits = parseRequiredNumber(flags.credits, "--credits");
10287
+ const order = await client.createBankTransferOrder(credits);
10288
+ flags.json === true ? printJson2(order) : printBankTransferOrder(order, false);
10289
+ return;
10290
+ }
10291
+ if (matches(tokens, ["billing", "bank-transfer", "status"])) {
10292
+ const orderId = rest[2];
10293
+ if (!orderId) throw new Error("Missing order ID.");
10294
+ const status = await client.getBankTransferStatus(orderId);
10295
+ flags.json === true ? printJson2(status) : printBankTransferStatus(status, false);
10296
+ return;
10297
+ }
10298
+ if (matches(tokens, ["billing", "bank-transfer", "list"])) {
10299
+ await printSettingsResult(client.listBankTransferOrders(), flags);
10300
+ return;
10301
+ }
9193
10302
  if (matches(tokens, ["billing", "invoices", "list"])) {
9194
10303
  await printSettingsResult(client.listInvoices(), flags);
9195
10304
  return;
@@ -9240,6 +10349,23 @@ async function handleSettings(client, subcommand, rest, flags) {
9240
10349
  await printSettingsResult(client.listRedeemedGiftCards(), flags);
9241
10350
  return;
9242
10351
  }
10352
+ if (matches(tokens, ["billing", "gift-card", "buy", "bank-transfer"])) {
10353
+ const credits = parseRequiredNumber(flags.credits, "--credits");
10354
+ const order = await client.createGiftCardBankTransferOrder(credits);
10355
+ flags.json === true ? printJson2(order) : printBankTransferOrder(order, true);
10356
+ return;
10357
+ }
10358
+ if (matches(tokens, ["billing", "gift-card", "purchase-status"])) {
10359
+ const orderId = rest[3];
10360
+ if (!orderId) throw new Error("Missing order ID.");
10361
+ const status = await client.getGiftCardPurchaseStatus(orderId);
10362
+ flags.json === true ? printJson2(status) : printBankTransferStatus(status, true);
10363
+ return;
10364
+ }
10365
+ if (matches(tokens, ["billing", "gift-card", "purchased"])) {
10366
+ await printSettingsResult(client.listPurchasedGiftCards(), flags);
10367
+ return;
10368
+ }
9243
10369
  if (matches(tokens, ["billing", "auto-topup", "low-balance", "set"])) {
9244
10370
  const enabled = parseOnOff(String(flags.enabled ?? ""), "low-balance auto top-up");
9245
10371
  const amount = parseRequiredNumber(flags.amount, "--amount");
@@ -9552,12 +10678,12 @@ function parseArgs(argv) {
9552
10678
  const key = keyValue[0];
9553
10679
  const valueFromEquals = keyValue[1];
9554
10680
  if (valueFromEquals !== void 0) {
9555
- flags[key] = valueFromEquals;
10681
+ flags[key] = appendFlagValue(flags[key], valueFromEquals);
9556
10682
  continue;
9557
10683
  }
9558
10684
  const next = argv[i + 1];
9559
10685
  if (next && !next.startsWith("--")) {
9560
- flags[key] = next;
10686
+ flags[key] = appendFlagValue(flags[key], next);
9561
10687
  i += 1;
9562
10688
  } else {
9563
10689
  flags[key] = true;
@@ -9565,6 +10691,11 @@ function parseArgs(argv) {
9565
10691
  }
9566
10692
  return { positionals, flags };
9567
10693
  }
10694
+ function appendFlagValue(existing, value) {
10695
+ if (typeof existing === "string" && existing.length > 0) return `${existing}
10696
+ ${value}`;
10697
+ return value;
10698
+ }
9568
10699
  function resolveApiKey(flags) {
9569
10700
  if (typeof flags["api-key"] === "string" && flags["api-key"].length > 0) {
9570
10701
  return flags["api-key"];
@@ -11168,6 +12299,7 @@ function printHelp() {
11168
12299
 
11169
12300
  Commands:
11170
12301
  openmates login Pair-auth login
12302
+ openmates signup Create an account from the terminal
11171
12303
  openmates logout Log out and clear session
11172
12304
  openmates whoami [--json] Show account info
11173
12305
  openmates chats [--help] Chat commands (list, search, show, ...)
@@ -11179,6 +12311,7 @@ Commands:
11179
12311
  openmates newchatsuggestions [--limit <n>] [--json] Personalized new chat suggestions
11180
12312
  openmates server [--help] Server management (install, start, stop, ...)
11181
12313
  openmates docs [--help] Browse, search, and download documentation
12314
+ openmates e2e provision-auth-accounts Provision local E2E auth-account artifacts
11182
12315
 
11183
12316
  Flags:
11184
12317
  --json Output raw JSON instead of formatted output
@@ -11186,6 +12319,40 @@ Flags:
11186
12319
  --api-key <key> Optional API key override (or set OPENMATES_API_KEY)
11187
12320
  --help Show contextual help for any command`);
11188
12321
  }
12322
+ function printSignupHelp() {
12323
+ console.log(`Signup command:
12324
+ openmates signup --email <email> --username <name> --invite-code <code>
12325
+
12326
+ Creates a password account using client-side encrypted signup crypto. Passwords
12327
+ are entered through hidden prompts and cannot be passed with --password.
12328
+
12329
+ Options:
12330
+ --email <email> Email address; prompted when omitted
12331
+ --username <name> Username; prompted when omitted
12332
+ --invite-code <code> Invite code when required
12333
+ --gift-card-code <code> Redeem after account creation
12334
+ --backup-codes-output <path> Save backup codes to a 0600 file
12335
+ --recovery-key-output <path> Save recovery key to a 0600 file
12336
+ --skip-2fa Explicitly skip 2FA setup after warning
12337
+ --skip-recovery-key Explicitly skip recovery key after warning
12338
+ --yes Confirm warning prompts
12339
+ --json Output non-secret JSON summary`);
12340
+ }
12341
+ function printE2EHelp() {
12342
+ console.log(`E2E provisioning command:
12343
+ openmates e2e provision-auth-accounts --slot 15 --artifact ./test-results/credential-updates/slot-15.env --api-url https://api.dev.openmates.org
12344
+
12345
+ Creates local ignored credential artifacts for reserved E2E auth accounts. The
12346
+ command refuses production API URLs and does not upload GitHub secrets.
12347
+
12348
+ Options:
12349
+ --slot <15|17> Reserved auth-account slot
12350
+ --artifact <path> Output .env artifact path
12351
+ --email <email> Test email; prompted/generated when omitted
12352
+ --domain <mail-domain> Generate email at this domain
12353
+ --force Overwrite local artifact files
12354
+ --yes Confirm prompts where possible`);
12355
+ }
11189
12356
  function printChatsHelp() {
11190
12357
  console.log(`Chats commands:
11191
12358
  openmates chats list [--limit <n>] [--page <n>] [--json]
@@ -11281,6 +12448,9 @@ function printAppsHelp() {
11281
12448
  openmates apps skill-info <app-id> <skill-id> [--json]
11282
12449
  openmates apps <app-id> <skill-id> "<query>" [--json]
11283
12450
  openmates apps <app-id> <skill-id> --input '<json>' [--json]
12451
+ openmates apps code run --language python --code 'print("Hello")'
12452
+ openmates apps code run --entry main.py --file main.py [--file requirements.txt]
12453
+ openmates apps code run --entry main.py --dir ./project [--exclude node_modules]
11284
12454
  openmates apps travel booking-link --token "<token>" [--context '<json>']
11285
12455
 
11286
12456
  Authentication:
@@ -11293,6 +12463,7 @@ Examples:
11293
12463
  openmates apps web search "latest AI news"
11294
12464
  openmates apps news search "climate change"
11295
12465
  openmates apps ai ask "Summarise this: ..."
12466
+ openmates apps code run --language python --filename hello.py --code 'print("Hello from CLI")'
11296
12467
  openmates apps travel search_connections --input '{"requests":[{"legs":[{"origin":"BER","destination":"LHR","date":"2026-04-15"}]}]}'
11297
12468
  openmates apps travel booking-link --token "<booking_token from search result>"
11298
12469
  openmates apps skill-info web search`);