openmates 0.11.0-alpha.21 → 0.11.0-alpha.23
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/{chunk-JQ5CPV6P.js → chunk-Z7CD2LI4.js} +1195 -36
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +118 -2
- package/dist/index.js +1 -1
- package/package.json +8 -2
|
@@ -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((
|
|
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
|
-
|
|
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((
|
|
952
|
+
return new Promise((resolve5, reject) => {
|
|
818
953
|
this.socket.send(JSON.stringify({ type, payload }), (error) => {
|
|
819
954
|
if (error) reject(error);
|
|
820
|
-
else
|
|
955
|
+
else resolve5();
|
|
821
956
|
});
|
|
822
957
|
});
|
|
823
958
|
}
|
|
824
959
|
waitForMessage(expectedType, predicate, timeoutMs = 2e4) {
|
|
825
|
-
return new Promise((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1302
|
+
resolve5({
|
|
1168
1303
|
messageId,
|
|
1169
1304
|
taskId,
|
|
1170
1305
|
content: latestContent,
|
|
@@ -1206,6 +1341,7 @@ var MODEL_ALIASES = {
|
|
|
1206
1341
|
fast: "qwen3-235b-a22b-2507"
|
|
1207
1342
|
};
|
|
1208
1343
|
var CHAT_MODELS = [
|
|
1344
|
+
{ id: "claude-fable-5", name: "Claude Fable 5", providerId: "anthropic" },
|
|
1209
1345
|
{ id: "claude-opus-4-7", name: "Claude Opus 4.7" },
|
|
1210
1346
|
{ id: "claude-opus-4-6", name: "Claude Opus 4.6" },
|
|
1211
1347
|
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
|
|
@@ -1260,6 +1396,20 @@ function fuzzyScore(query, candidate) {
|
|
|
1260
1396
|
return Math.round(matched / Math.max(q.length, 1) * 40);
|
|
1261
1397
|
}
|
|
1262
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
|
+
}
|
|
1263
1413
|
const normalized = normalize(token);
|
|
1264
1414
|
if (MODEL_ALIASES[normalized]) {
|
|
1265
1415
|
return {
|
|
@@ -1274,7 +1424,7 @@ function resolveToken(token, context) {
|
|
|
1274
1424
|
return {
|
|
1275
1425
|
original: `@${token}`,
|
|
1276
1426
|
type: "model",
|
|
1277
|
-
wireSyntax: `@ai-model:${model.id}`,
|
|
1427
|
+
wireSyntax: `@ai-model:${model.id}${model.providerId ? `:${model.providerId}` : ""}`,
|
|
1278
1428
|
displayName: `@${model.name.replace(/\s+/g, "-")}`
|
|
1279
1429
|
};
|
|
1280
1430
|
}
|
|
@@ -1665,7 +1815,7 @@ async function deriveKeyFromId(id) {
|
|
|
1665
1815
|
["encrypt"]
|
|
1666
1816
|
);
|
|
1667
1817
|
}
|
|
1668
|
-
async function
|
|
1818
|
+
async function deriveKeyFromPassword2(password, id) {
|
|
1669
1819
|
const material = await crypto.subtle.importKey(
|
|
1670
1820
|
"raw",
|
|
1671
1821
|
new TextEncoder().encode(password),
|
|
@@ -1691,7 +1841,7 @@ async function generateChatShareBlob(chatId, chatKeyBytes, durationSeconds = 0,
|
|
|
1691
1841
|
let keyForBlob = Buffer.from(chatKeyBytes).toString("base64");
|
|
1692
1842
|
let pwdFlag = 0;
|
|
1693
1843
|
if (password && password.length > 0) {
|
|
1694
|
-
const pwdKey = await
|
|
1844
|
+
const pwdKey = await deriveKeyFromPassword2(password, chatId);
|
|
1695
1845
|
keyForBlob = await encryptAESGCM(enc.encode(keyForBlob), pwdKey);
|
|
1696
1846
|
pwdFlag = 1;
|
|
1697
1847
|
}
|
|
@@ -1709,7 +1859,7 @@ async function generateEmbedShareBlob(embedId, embedKeyBytes, durationSeconds =
|
|
|
1709
1859
|
let keyForBlob = Buffer.from(embedKeyBytes).toString("base64");
|
|
1710
1860
|
let pwdFlag = 0;
|
|
1711
1861
|
if (password && password.length > 0) {
|
|
1712
|
-
const pwdKey = await
|
|
1862
|
+
const pwdKey = await deriveKeyFromPassword2(password, embedId);
|
|
1713
1863
|
keyForBlob = await encryptAESGCM(enc.encode(keyForBlob), pwdKey);
|
|
1714
1864
|
pwdFlag = 1;
|
|
1715
1865
|
}
|
|
@@ -2274,6 +2424,168 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2274
2424
|
}
|
|
2275
2425
|
clearSession();
|
|
2276
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
|
+
}
|
|
2277
2589
|
// -------------------------------------------------------------------------
|
|
2278
2590
|
// Chats
|
|
2279
2591
|
// -------------------------------------------------------------------------
|
|
@@ -3175,7 +3487,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3175
3487
|
if (response.data.status === "failed") {
|
|
3176
3488
|
throw new Error(response.data.error ?? "Task failed");
|
|
3177
3489
|
}
|
|
3178
|
-
await new Promise((
|
|
3490
|
+
await new Promise((resolve5) => setTimeout(resolve5, SKILL_TASK_POLL_INTERVAL_MS));
|
|
3179
3491
|
}
|
|
3180
3492
|
throw new Error(`Task ${taskId} did not complete within ${SKILL_TASK_POLL_TIMEOUT_MS / 1e3}s`);
|
|
3181
3493
|
}
|
|
@@ -3327,6 +3639,24 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3327
3639
|
}
|
|
3328
3640
|
return this.resolveAsyncSkillResponse(response.data, headers);
|
|
3329
3641
|
}
|
|
3642
|
+
getCodeRunStreamAuth() {
|
|
3643
|
+
const session = this.session;
|
|
3644
|
+
if (!session) return null;
|
|
3645
|
+
const token = session.wsToken || session.cookies.auth_refresh_token;
|
|
3646
|
+
if (!token) return null;
|
|
3647
|
+
return { sessionId: session.sessionId, token };
|
|
3648
|
+
}
|
|
3649
|
+
async getCodeRunStatus(path, apiKey) {
|
|
3650
|
+
const headers = {
|
|
3651
|
+
...this.getCliRequestHeaders()
|
|
3652
|
+
};
|
|
3653
|
+
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
3654
|
+
const response = await this.http.get(path, headers);
|
|
3655
|
+
if (!response.ok) {
|
|
3656
|
+
throw new Error(`Code Run status request failed with HTTP ${response.status}`);
|
|
3657
|
+
}
|
|
3658
|
+
return response.data;
|
|
3659
|
+
}
|
|
3330
3660
|
// -------------------------------------------------------------------------
|
|
3331
3661
|
// Travel: booking link resolution
|
|
3332
3662
|
// -------------------------------------------------------------------------
|
|
@@ -3374,7 +3704,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3374
3704
|
`Rate limited by settings API; retrying in ${Math.ceil(SETTINGS_GET_RATE_LIMIT_RETRY_MS / 1e3)}s...
|
|
3375
3705
|
`
|
|
3376
3706
|
);
|
|
3377
|
-
await new Promise((
|
|
3707
|
+
await new Promise((resolve5) => setTimeout(resolve5, SETTINGS_GET_RATE_LIMIT_RETRY_MS));
|
|
3378
3708
|
response = await this.http.get(normalizedPath, this.getCliRequestHeaders());
|
|
3379
3709
|
}
|
|
3380
3710
|
if (!response.ok) {
|
|
@@ -3434,10 +3764,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3434
3764
|
// Gift cards (under /v1/payments, not /v1/settings)
|
|
3435
3765
|
// -------------------------------------------------------------------------
|
|
3436
3766
|
async redeemGiftCard(code) {
|
|
3437
|
-
this.requireSession();
|
|
3767
|
+
const session = this.requireSession();
|
|
3768
|
+
const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
|
|
3438
3769
|
const response = await this.http.post(
|
|
3439
3770
|
"/v1/payments/redeem-gift-card",
|
|
3440
|
-
{ code },
|
|
3771
|
+
{ code, email_encryption_key: emailEncryptionKey },
|
|
3441
3772
|
this.getCliRequestHeaders()
|
|
3442
3773
|
);
|
|
3443
3774
|
if (!response.ok) {
|
|
@@ -3458,6 +3789,86 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3458
3789
|
}
|
|
3459
3790
|
return response.data;
|
|
3460
3791
|
}
|
|
3792
|
+
async listPurchasedGiftCards() {
|
|
3793
|
+
this.requireSession();
|
|
3794
|
+
const response = await this.http.get(
|
|
3795
|
+
"/v1/payments/purchased-gift-cards",
|
|
3796
|
+
this.getCliRequestHeaders()
|
|
3797
|
+
);
|
|
3798
|
+
if (!response.ok) {
|
|
3799
|
+
throw new Error(
|
|
3800
|
+
`Failed to fetch purchased gift cards (HTTP ${response.status})`
|
|
3801
|
+
);
|
|
3802
|
+
}
|
|
3803
|
+
return response.data;
|
|
3804
|
+
}
|
|
3805
|
+
async createBankTransferOrder(creditsAmount) {
|
|
3806
|
+
const session = this.requireSession();
|
|
3807
|
+
const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
|
|
3808
|
+
const response = await this.http.post(
|
|
3809
|
+
"/v1/payments/create-bank-transfer-order",
|
|
3810
|
+
{
|
|
3811
|
+
credits_amount: creditsAmount,
|
|
3812
|
+
currency: "eur",
|
|
3813
|
+
email_encryption_key: emailEncryptionKey
|
|
3814
|
+
},
|
|
3815
|
+
this.getCliRequestHeaders()
|
|
3816
|
+
);
|
|
3817
|
+
if (!response.ok) {
|
|
3818
|
+
throw new Error(`Bank transfer order failed (HTTP ${response.status})`);
|
|
3819
|
+
}
|
|
3820
|
+
return response.data;
|
|
3821
|
+
}
|
|
3822
|
+
async createGiftCardBankTransferOrder(creditsAmount) {
|
|
3823
|
+
const session = this.requireSession();
|
|
3824
|
+
const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
|
|
3825
|
+
const response = await this.http.post(
|
|
3826
|
+
"/v1/payments/create-gift-card-bank-transfer-order",
|
|
3827
|
+
{
|
|
3828
|
+
credits_amount: creditsAmount,
|
|
3829
|
+
currency: "eur",
|
|
3830
|
+
email_encryption_key: emailEncryptionKey
|
|
3831
|
+
},
|
|
3832
|
+
this.getCliRequestHeaders()
|
|
3833
|
+
);
|
|
3834
|
+
if (!response.ok) {
|
|
3835
|
+
throw new Error(`Gift card bank transfer order failed (HTTP ${response.status})`);
|
|
3836
|
+
}
|
|
3837
|
+
return response.data;
|
|
3838
|
+
}
|
|
3839
|
+
async getBankTransferStatus(orderId) {
|
|
3840
|
+
this.requireSession();
|
|
3841
|
+
const response = await this.http.get(
|
|
3842
|
+
`/v1/payments/bank-transfer-status/${encodeURIComponent(orderId)}`,
|
|
3843
|
+
this.getCliRequestHeaders()
|
|
3844
|
+
);
|
|
3845
|
+
if (!response.ok) {
|
|
3846
|
+
throw new Error(`Failed to fetch bank transfer status (HTTP ${response.status})`);
|
|
3847
|
+
}
|
|
3848
|
+
return response.data;
|
|
3849
|
+
}
|
|
3850
|
+
async listBankTransferOrders() {
|
|
3851
|
+
this.requireSession();
|
|
3852
|
+
const response = await this.http.get(
|
|
3853
|
+
"/v1/payments/bank-transfer-pending",
|
|
3854
|
+
this.getCliRequestHeaders()
|
|
3855
|
+
);
|
|
3856
|
+
if (!response.ok) {
|
|
3857
|
+
throw new Error(`Failed to fetch bank transfer orders (HTTP ${response.status})`);
|
|
3858
|
+
}
|
|
3859
|
+
return response.data;
|
|
3860
|
+
}
|
|
3861
|
+
async getGiftCardPurchaseStatus(orderId) {
|
|
3862
|
+
this.requireSession();
|
|
3863
|
+
const response = await this.http.get(
|
|
3864
|
+
`/v1/payments/gift-card-purchase-status/${encodeURIComponent(orderId)}`,
|
|
3865
|
+
this.getCliRequestHeaders()
|
|
3866
|
+
);
|
|
3867
|
+
if (!response.ok) {
|
|
3868
|
+
throw new Error(`Failed to fetch gift card purchase status (HTTP ${response.status})`);
|
|
3869
|
+
}
|
|
3870
|
+
return response.data;
|
|
3871
|
+
}
|
|
3461
3872
|
async listInvoices() {
|
|
3462
3873
|
this.requireSession();
|
|
3463
3874
|
const response = await this.http.get(
|
|
@@ -3494,6 +3905,63 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3494
3905
|
}
|
|
3495
3906
|
return response.data;
|
|
3496
3907
|
}
|
|
3908
|
+
async getAuthMethodsStatus() {
|
|
3909
|
+
this.requireSession();
|
|
3910
|
+
const response = await this.http.get(
|
|
3911
|
+
"/v1/payments/user-auth-methods",
|
|
3912
|
+
this.getCliRequestHeaders()
|
|
3913
|
+
);
|
|
3914
|
+
if (!response.ok) {
|
|
3915
|
+
throw new Error(`Failed to fetch auth methods (HTTP ${response.status})`);
|
|
3916
|
+
}
|
|
3917
|
+
return response.data;
|
|
3918
|
+
}
|
|
3919
|
+
async requestDeleteAccountEmailCode() {
|
|
3920
|
+
const session = this.requireSession();
|
|
3921
|
+
const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
|
|
3922
|
+
const response = await this.http.post(
|
|
3923
|
+
"/v1/settings/request-action-verification",
|
|
3924
|
+
{ action: "delete_account", email_encryption_key: emailEncryptionKey },
|
|
3925
|
+
this.getCliRequestHeaders()
|
|
3926
|
+
);
|
|
3927
|
+
if (!response.ok) {
|
|
3928
|
+
throw new Error(`Failed to request account deletion email code (HTTP ${response.status})`);
|
|
3929
|
+
}
|
|
3930
|
+
return response.data;
|
|
3931
|
+
}
|
|
3932
|
+
async verifyDeleteAccountEmailCode(code) {
|
|
3933
|
+
this.requireSession();
|
|
3934
|
+
const response = await this.http.post(
|
|
3935
|
+
"/v1/settings/verify-action-code",
|
|
3936
|
+
{ action: "delete_account", code },
|
|
3937
|
+
this.getCliRequestHeaders()
|
|
3938
|
+
);
|
|
3939
|
+
if (!response.ok) {
|
|
3940
|
+
throw new Error(`Account deletion email code verification failed (HTTP ${response.status})`);
|
|
3941
|
+
}
|
|
3942
|
+
return response.data;
|
|
3943
|
+
}
|
|
3944
|
+
async deleteAccountWithCliVerification(totpCode) {
|
|
3945
|
+
const session = this.requireSession();
|
|
3946
|
+
const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
|
|
3947
|
+
const response = await this.http.post(
|
|
3948
|
+
"/v1/settings/delete-account",
|
|
3949
|
+
{
|
|
3950
|
+
confirm_data_deletion: true,
|
|
3951
|
+
auth_method: totpCode ? "2fa_otp" : "email_otp",
|
|
3952
|
+
auth_code: totpCode,
|
|
3953
|
+
email_encryption_key: emailEncryptionKey,
|
|
3954
|
+
require_email_verification: true
|
|
3955
|
+
},
|
|
3956
|
+
this.getCliRequestHeaders()
|
|
3957
|
+
);
|
|
3958
|
+
if (!response.ok) {
|
|
3959
|
+
throw new Error(`Account deletion failed (HTTP ${response.status})`);
|
|
3960
|
+
}
|
|
3961
|
+
clearSession();
|
|
3962
|
+
clearSyncCache();
|
|
3963
|
+
return response.data;
|
|
3964
|
+
}
|
|
3497
3965
|
async updateUsername(username) {
|
|
3498
3966
|
return this.settingsPost("user/username", { username });
|
|
3499
3967
|
}
|
|
@@ -4489,7 +4957,7 @@ function filenameFromContentDisposition(header2) {
|
|
|
4489
4957
|
return plain?.trim() ?? null;
|
|
4490
4958
|
}
|
|
4491
4959
|
function sleep(ms) {
|
|
4492
|
-
return new Promise((
|
|
4960
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
4493
4961
|
}
|
|
4494
4962
|
function printLogo() {
|
|
4495
4963
|
const W = "\x1B[1;37m";
|
|
@@ -4505,6 +4973,7 @@ function printLogo() {
|
|
|
4505
4973
|
|
|
4506
4974
|
// src/cli.ts
|
|
4507
4975
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
4976
|
+
import WebSocket2 from "ws";
|
|
4508
4977
|
|
|
4509
4978
|
// ../secret-scanner/src/registry.ts
|
|
4510
4979
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -6882,9 +7351,9 @@ function exec(cmd, cwd) {
|
|
|
6882
7351
|
return execSync(cmd, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6883
7352
|
}
|
|
6884
7353
|
function runInteractive(cmd, args, cwd) {
|
|
6885
|
-
return new Promise((
|
|
7354
|
+
return new Promise((resolve5, reject) => {
|
|
6886
7355
|
const child = nodeSpawn(cmd, args, { cwd, stdio: "inherit", shell: false });
|
|
6887
|
-
child.on("close", (code) =>
|
|
7356
|
+
child.on("close", (code) => resolve5(code ?? 1));
|
|
6888
7357
|
child.on("error", reject);
|
|
6889
7358
|
});
|
|
6890
7359
|
}
|
|
@@ -7056,10 +7525,10 @@ function warnIfMissingLlmCredentials(installPath) {
|
|
|
7056
7525
|
}
|
|
7057
7526
|
async function confirmDestructive(phrase) {
|
|
7058
7527
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
7059
|
-
return new Promise((
|
|
7528
|
+
return new Promise((resolve5) => {
|
|
7060
7529
|
rl.question(`Type "${phrase}" to confirm: `, (answer) => {
|
|
7061
7530
|
rl.close();
|
|
7062
|
-
|
|
7531
|
+
resolve5(answer.trim() === phrase);
|
|
7063
7532
|
});
|
|
7064
7533
|
});
|
|
7065
7534
|
}
|
|
@@ -7592,6 +8061,214 @@ async function handleServer(subcommand, rest, flags) {
|
|
|
7592
8061
|
}
|
|
7593
8062
|
}
|
|
7594
8063
|
|
|
8064
|
+
// src/codeRunInput.ts
|
|
8065
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
8066
|
+
import { basename as basename2, relative, resolve as resolve4 } from "path";
|
|
8067
|
+
var CODE_RUN_MAX_FILES = 50;
|
|
8068
|
+
var CODE_RUN_MAX_FILE_BYTES = 1e6;
|
|
8069
|
+
var CODE_RUN_MAX_TOTAL_BYTES = 1e7;
|
|
8070
|
+
var DEFAULT_IGNORES = [".git", "node_modules", "__pycache__", ".venv", "dist", "build"];
|
|
8071
|
+
async function buildCodeRunRequestsFromFlags(flags) {
|
|
8072
|
+
if (flags.chat || flags.targetEmbed) {
|
|
8073
|
+
if (!flags.chat || !flags.targetEmbed) {
|
|
8074
|
+
throw new Error("Chat-bound Code Run requires both --chat and --target-embed.");
|
|
8075
|
+
}
|
|
8076
|
+
return [{
|
|
8077
|
+
mode: "chat_bound",
|
|
8078
|
+
chat_id: flags.chat,
|
|
8079
|
+
target_embed_id: flags.targetEmbed,
|
|
8080
|
+
enable_internet: flags.noInternet !== true,
|
|
8081
|
+
files: []
|
|
8082
|
+
}];
|
|
8083
|
+
}
|
|
8084
|
+
const files = [];
|
|
8085
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8086
|
+
const entry = flags.entry ? normalizeCodeRunPath(flags.entry) : void 0;
|
|
8087
|
+
let totalBytes = 0;
|
|
8088
|
+
const addFile = (file, byteLength) => {
|
|
8089
|
+
const path = normalizeCodeRunPath(file.path);
|
|
8090
|
+
if (seen.has(path)) throw new Error(`Duplicate Code Run file path: ${path}`);
|
|
8091
|
+
seen.add(path);
|
|
8092
|
+
totalBytes += byteLength;
|
|
8093
|
+
if (files.length + 1 > CODE_RUN_MAX_FILES) throw new Error(`Code Run supports at most ${CODE_RUN_MAX_FILES} files.`);
|
|
8094
|
+
if (byteLength > CODE_RUN_MAX_FILE_BYTES) throw new Error(`Code Run file is too large: ${path}`);
|
|
8095
|
+
if (totalBytes > CODE_RUN_MAX_TOTAL_BYTES) throw new Error("Code Run file bundle is too large.");
|
|
8096
|
+
files.push({ ...file, path });
|
|
8097
|
+
};
|
|
8098
|
+
if (flags.code !== void 0) {
|
|
8099
|
+
const filename = normalizeCodeRunPath(flags.filename || entry || defaultFilename(flags.language));
|
|
8100
|
+
const buffer = Buffer.from(flags.code, "utf8");
|
|
8101
|
+
addFile({
|
|
8102
|
+
path: filename,
|
|
8103
|
+
content_base64: buffer.toString("base64"),
|
|
8104
|
+
language: flags.language || inferLanguage(filename),
|
|
8105
|
+
mime_type: "text/plain",
|
|
8106
|
+
is_target: true
|
|
8107
|
+
}, buffer.byteLength);
|
|
8108
|
+
}
|
|
8109
|
+
for (const filePath of toList(flags.file)) {
|
|
8110
|
+
const absolute = resolve4(filePath);
|
|
8111
|
+
const buffer = await readFile(absolute);
|
|
8112
|
+
const sandboxPath = normalizeCodeRunPath(basename2(filePath));
|
|
8113
|
+
addFile({
|
|
8114
|
+
path: sandboxPath,
|
|
8115
|
+
content_base64: buffer.toString("base64"),
|
|
8116
|
+
language: inferLanguage(sandboxPath),
|
|
8117
|
+
mime_type: "text/plain",
|
|
8118
|
+
is_target: entry ? sandboxPath === entry : files.length === 0
|
|
8119
|
+
}, buffer.byteLength);
|
|
8120
|
+
}
|
|
8121
|
+
for (const dirPath of toList(flags.dir)) {
|
|
8122
|
+
const root = resolve4(dirPath);
|
|
8123
|
+
const dirFiles = await collectDirectoryFiles(root, flags.include, flags.exclude);
|
|
8124
|
+
for (const absolute of dirFiles) {
|
|
8125
|
+
const sandboxPath = normalizeCodeRunPath(relative(root, absolute).replace(/\\/g, "/"));
|
|
8126
|
+
const buffer = await readFile(absolute);
|
|
8127
|
+
addFile({
|
|
8128
|
+
path: sandboxPath,
|
|
8129
|
+
content_base64: buffer.toString("base64"),
|
|
8130
|
+
language: inferLanguage(sandboxPath),
|
|
8131
|
+
mime_type: "text/plain",
|
|
8132
|
+
is_target: entry ? sandboxPath === entry : files.length === 0
|
|
8133
|
+
}, buffer.byteLength);
|
|
8134
|
+
}
|
|
8135
|
+
}
|
|
8136
|
+
for (const url of toList(flags.url)) {
|
|
8137
|
+
const response = await fetch(url);
|
|
8138
|
+
if (!response.ok) throw new Error(`Failed to download Code Run URL ${redactUrl(url)} (HTTP ${response.status})`);
|
|
8139
|
+
const text = await response.text();
|
|
8140
|
+
const urlPath = new URL(url).pathname;
|
|
8141
|
+
const sandboxPath = normalizeCodeRunPath(entry || basename2(urlPath) || "main.txt");
|
|
8142
|
+
const buffer = Buffer.from(text, "utf8");
|
|
8143
|
+
addFile({
|
|
8144
|
+
path: sandboxPath,
|
|
8145
|
+
content_base64: buffer.toString("base64"),
|
|
8146
|
+
language: inferLanguage(sandboxPath),
|
|
8147
|
+
mime_type: "text/plain",
|
|
8148
|
+
is_target: true
|
|
8149
|
+
}, buffer.byteLength);
|
|
8150
|
+
}
|
|
8151
|
+
if (flags.repo) {
|
|
8152
|
+
await addGithubRepoFiles(flags.repo, toList(flags.include), async (path, text) => {
|
|
8153
|
+
const sandboxPath = normalizeCodeRunPath(path);
|
|
8154
|
+
const buffer = Buffer.from(text, "utf8");
|
|
8155
|
+
addFile({
|
|
8156
|
+
path: sandboxPath,
|
|
8157
|
+
content_base64: buffer.toString("base64"),
|
|
8158
|
+
language: inferLanguage(sandboxPath),
|
|
8159
|
+
mime_type: "text/plain",
|
|
8160
|
+
is_target: entry ? sandboxPath === entry : files.length === 0
|
|
8161
|
+
}, buffer.byteLength);
|
|
8162
|
+
});
|
|
8163
|
+
}
|
|
8164
|
+
if (files.length === 0) throw new Error("Code Run requires --code, --file, --dir, --url, --repo, or --chat/--target-embed.");
|
|
8165
|
+
const entryPath = entry || files.find((file) => file.is_target)?.path || files[0].path;
|
|
8166
|
+
if (!files.some((file) => file.path === entryPath)) throw new Error(`Code Run entry file was not included: ${entryPath}`);
|
|
8167
|
+
for (const file of files) file.is_target = file.path === entryPath;
|
|
8168
|
+
return [{
|
|
8169
|
+
mode: "direct",
|
|
8170
|
+
entry_path: entryPath,
|
|
8171
|
+
enable_internet: flags.noInternet !== true,
|
|
8172
|
+
files
|
|
8173
|
+
}];
|
|
8174
|
+
}
|
|
8175
|
+
function buildCodeRunStreamUrl(params) {
|
|
8176
|
+
const base = params.apiUrl.replace(/\/$/, "").replace(/^http:/, "ws:").replace(/^https:/, "wss:");
|
|
8177
|
+
const query = new URLSearchParams({ sessionId: params.sessionId, token: params.token });
|
|
8178
|
+
return `${base}/v1/code/run/${encodeURIComponent(params.executionId)}/stream?${query.toString()}`;
|
|
8179
|
+
}
|
|
8180
|
+
function normalizeCodeRunPath(rawPath) {
|
|
8181
|
+
const path = rawPath.trim().replace(/\\/g, "/");
|
|
8182
|
+
if (!path || path.includes("\0") || path.startsWith("/") || path.startsWith("~/") || /^[A-Za-z]:\//.test(path)) {
|
|
8183
|
+
throw new Error(`Unsafe Code Run path: ${rawPath}`);
|
|
8184
|
+
}
|
|
8185
|
+
const parts = path.split("/");
|
|
8186
|
+
if (parts.some((part) => !part || part === "." || part === "..")) {
|
|
8187
|
+
throw new Error(`Unsafe Code Run path: ${rawPath}`);
|
|
8188
|
+
}
|
|
8189
|
+
return parts.join("/");
|
|
8190
|
+
}
|
|
8191
|
+
async function collectDirectoryFiles(root, include, exclude) {
|
|
8192
|
+
const includes = toList(include);
|
|
8193
|
+
const excludes = [...DEFAULT_IGNORES, ...toList(exclude)];
|
|
8194
|
+
const files = [];
|
|
8195
|
+
async function walk(directory) {
|
|
8196
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
8197
|
+
const absolute = resolve4(directory, entry.name);
|
|
8198
|
+
const rel = relative(root, absolute).replace(/\\/g, "/");
|
|
8199
|
+
if (matchesAny(rel, excludes) || matchesAny(entry.name, excludes)) continue;
|
|
8200
|
+
if (entry.isDirectory()) {
|
|
8201
|
+
await walk(absolute);
|
|
8202
|
+
} else if (entry.isFile()) {
|
|
8203
|
+
if (includes.length === 0 || matchesAny(rel, includes)) files.push(absolute);
|
|
8204
|
+
}
|
|
8205
|
+
}
|
|
8206
|
+
}
|
|
8207
|
+
const rootStat = await stat(root);
|
|
8208
|
+
if (!rootStat.isDirectory()) throw new Error(`Code Run --dir is not a directory: ${root}`);
|
|
8209
|
+
await walk(root);
|
|
8210
|
+
return files.sort();
|
|
8211
|
+
}
|
|
8212
|
+
async function addGithubRepoFiles(repoUrl, includes, add) {
|
|
8213
|
+
if (includes.length === 0) throw new Error("Code Run --repo requires at least one --include path in v1.");
|
|
8214
|
+
const parsed = parseGithubRepoUrl(repoUrl);
|
|
8215
|
+
for (const includePath of includes) {
|
|
8216
|
+
const path = normalizeCodeRunPath(includePath);
|
|
8217
|
+
const rawUrl = `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/${parsed.ref}/${path}`;
|
|
8218
|
+
const response = await fetch(rawUrl);
|
|
8219
|
+
if (!response.ok) throw new Error(`Failed to download GitHub file ${path} (HTTP ${response.status})`);
|
|
8220
|
+
await add(path, await response.text());
|
|
8221
|
+
}
|
|
8222
|
+
}
|
|
8223
|
+
function parseGithubRepoUrl(repoUrl) {
|
|
8224
|
+
const url = new URL(repoUrl);
|
|
8225
|
+
if (url.hostname !== "github.com") throw new Error("Code Run --repo supports public github.com URLs in v1.");
|
|
8226
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
8227
|
+
if (parts.length < 2) throw new Error("Invalid GitHub repository URL.");
|
|
8228
|
+
return { owner: parts[0], repo: parts[1].replace(/\.git$/, ""), ref: "main" };
|
|
8229
|
+
}
|
|
8230
|
+
function toList(value) {
|
|
8231
|
+
if (Array.isArray(value)) return value.flatMap((item) => item.split("\n")).filter(Boolean);
|
|
8232
|
+
if (typeof value === "string") return value.split("\n").filter(Boolean);
|
|
8233
|
+
return [];
|
|
8234
|
+
}
|
|
8235
|
+
function matchesAny(path, patterns) {
|
|
8236
|
+
return patterns.some((pattern) => matchesPattern(path, pattern));
|
|
8237
|
+
}
|
|
8238
|
+
function matchesPattern(path, pattern) {
|
|
8239
|
+
if (!pattern) return false;
|
|
8240
|
+
if (pattern.includes("*")) {
|
|
8241
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
8242
|
+
return new RegExp(`^${escaped}$`).test(path) || new RegExp(`(^|/)${escaped}$`).test(path);
|
|
8243
|
+
}
|
|
8244
|
+
return path === pattern || path.startsWith(`${pattern}/`) || path.endsWith(`/${pattern}`);
|
|
8245
|
+
}
|
|
8246
|
+
function inferLanguage(path) {
|
|
8247
|
+
const ext = path.split(".").pop()?.toLowerCase();
|
|
8248
|
+
if (ext === "py") return "python";
|
|
8249
|
+
if (ext === "js" || ext === "mjs" || ext === "cjs") return "javascript";
|
|
8250
|
+
if (ext === "ts") return "typescript";
|
|
8251
|
+
if (ext === "sh") return "bash";
|
|
8252
|
+
if (ext === "go") return "go";
|
|
8253
|
+
if (ext === "rs") return "rust";
|
|
8254
|
+
if (ext === "c") return "c";
|
|
8255
|
+
if (ext === "cc" || ext === "cpp" || ext === "cxx") return "cpp";
|
|
8256
|
+
return "";
|
|
8257
|
+
}
|
|
8258
|
+
function defaultFilename(language) {
|
|
8259
|
+
if (language === "python" || language === "py") return "main.py";
|
|
8260
|
+
if (language === "javascript" || language === "js" || language === "node") return "main.js";
|
|
8261
|
+
if (language === "typescript" || language === "ts") return "main.ts";
|
|
8262
|
+
if (language === "bash" || language === "sh") return "main.sh";
|
|
8263
|
+
return "main.txt";
|
|
8264
|
+
}
|
|
8265
|
+
function redactUrl(rawUrl) {
|
|
8266
|
+
const url = new URL(rawUrl);
|
|
8267
|
+
url.search = "";
|
|
8268
|
+
url.hash = "";
|
|
8269
|
+
return url.toString();
|
|
8270
|
+
}
|
|
8271
|
+
|
|
7595
8272
|
// src/cli.ts
|
|
7596
8273
|
async function main() {
|
|
7597
8274
|
const parsed = parseArgs(process.argv.slice(2));
|
|
@@ -7624,6 +8301,14 @@ async function main() {
|
|
|
7624
8301
|
printSettingsHelp(client);
|
|
7625
8302
|
return;
|
|
7626
8303
|
}
|
|
8304
|
+
if (command === "signup") {
|
|
8305
|
+
printSignupHelp();
|
|
8306
|
+
return;
|
|
8307
|
+
}
|
|
8308
|
+
if (command === "e2e") {
|
|
8309
|
+
printE2EHelp();
|
|
8310
|
+
return;
|
|
8311
|
+
}
|
|
7627
8312
|
if (command === "mentions") {
|
|
7628
8313
|
printMentionsHelp();
|
|
7629
8314
|
return;
|
|
@@ -7664,6 +8349,14 @@ async function main() {
|
|
|
7664
8349
|
console.log("Login successful.");
|
|
7665
8350
|
return;
|
|
7666
8351
|
}
|
|
8352
|
+
if (command === "signup") {
|
|
8353
|
+
await handleSignup(client, parsed.flags);
|
|
8354
|
+
return;
|
|
8355
|
+
}
|
|
8356
|
+
if (command === "e2e") {
|
|
8357
|
+
await handleE2E(client, subcommand, rest, parsed.flags);
|
|
8358
|
+
return;
|
|
8359
|
+
}
|
|
7667
8360
|
if (command === "logout") {
|
|
7668
8361
|
await client.logout();
|
|
7669
8362
|
console.log("Logged out.");
|
|
@@ -7915,10 +8608,10 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
7915
8608
|
input: process.stdin,
|
|
7916
8609
|
output: process.stdout
|
|
7917
8610
|
});
|
|
7918
|
-
const answer = await new Promise((
|
|
8611
|
+
const answer = await new Promise((resolve5) => {
|
|
7919
8612
|
iface.question(
|
|
7920
8613
|
`Delete ${resolved.length} chat(s)? This cannot be undone. [y/N] `,
|
|
7921
|
-
|
|
8614
|
+
resolve5
|
|
7922
8615
|
);
|
|
7923
8616
|
});
|
|
7924
8617
|
iface.close();
|
|
@@ -8472,6 +9165,10 @@ The booking_token is shown in the output of:
|
|
|
8472
9165
|
}
|
|
8473
9166
|
return;
|
|
8474
9167
|
}
|
|
9168
|
+
if (subcommand === "code" && rest[0] === "run") {
|
|
9169
|
+
await handleCodeRun(client, flags, apiKey);
|
|
9170
|
+
return;
|
|
9171
|
+
}
|
|
8475
9172
|
const app = subcommand;
|
|
8476
9173
|
const skill = rest[0];
|
|
8477
9174
|
if (app && skill) {
|
|
@@ -8587,6 +9284,100 @@ Usage:
|
|
|
8587
9284
|
printAppsHelp();
|
|
8588
9285
|
process.exit(1);
|
|
8589
9286
|
}
|
|
9287
|
+
async function handleCodeRun(client, flags, apiKey) {
|
|
9288
|
+
const requests = await buildCodeRunRequestsFromFlags({
|
|
9289
|
+
code: typeof flags.code === "string" ? flags.code : void 0,
|
|
9290
|
+
language: typeof flags.language === "string" ? flags.language : void 0,
|
|
9291
|
+
filename: typeof flags.filename === "string" ? flags.filename : void 0,
|
|
9292
|
+
entry: typeof flags.entry === "string" ? flags.entry : void 0,
|
|
9293
|
+
file: typeof flags.file === "string" ? flags.file : void 0,
|
|
9294
|
+
dir: typeof flags.dir === "string" ? flags.dir : void 0,
|
|
9295
|
+
url: typeof flags.url === "string" ? flags.url : void 0,
|
|
9296
|
+
repo: typeof flags.repo === "string" ? flags.repo : void 0,
|
|
9297
|
+
include: typeof flags.include === "string" ? flags.include : void 0,
|
|
9298
|
+
exclude: typeof flags.exclude === "string" ? flags.exclude : void 0,
|
|
9299
|
+
noInternet: flags["no-internet"] === true,
|
|
9300
|
+
chat: typeof flags.chat === "string" ? flags.chat : void 0,
|
|
9301
|
+
targetEmbed: typeof flags["target-embed"] === "string" ? flags["target-embed"] : void 0
|
|
9302
|
+
});
|
|
9303
|
+
if (flags.json !== true) {
|
|
9304
|
+
const first = requests[0];
|
|
9305
|
+
if (first.mode === "direct") {
|
|
9306
|
+
process.stderr.write(`Starting Code Run for ${first.entry_path} (${first.files.length} file${first.files.length === 1 ? "" : "s"})...
|
|
9307
|
+
`);
|
|
9308
|
+
}
|
|
9309
|
+
}
|
|
9310
|
+
const response = await client.runSkill({
|
|
9311
|
+
app: "code",
|
|
9312
|
+
skill: "run",
|
|
9313
|
+
inputData: { requests },
|
|
9314
|
+
apiKey
|
|
9315
|
+
});
|
|
9316
|
+
const result = response.data?.results?.[0];
|
|
9317
|
+
if (!result?.execution_id || !result.status_path) {
|
|
9318
|
+
throw new Error("Code Run did not return an execution id.");
|
|
9319
|
+
}
|
|
9320
|
+
const streamAuth = apiKey ? null : client.getCodeRunStreamAuth();
|
|
9321
|
+
let finalStatus;
|
|
9322
|
+
if (streamAuth && result.stream_path) {
|
|
9323
|
+
const url = buildCodeRunStreamUrl({
|
|
9324
|
+
apiUrl: client.apiUrl,
|
|
9325
|
+
executionId: result.execution_id,
|
|
9326
|
+
sessionId: streamAuth.sessionId,
|
|
9327
|
+
token: streamAuth.token
|
|
9328
|
+
});
|
|
9329
|
+
finalStatus = await streamCodeRunToTerminal(url, flags.json === true);
|
|
9330
|
+
} else {
|
|
9331
|
+
finalStatus = await pollCodeRunStatus(client, result.status_path, apiKey, flags.json === true);
|
|
9332
|
+
}
|
|
9333
|
+
if (flags.json === true) {
|
|
9334
|
+
printJson2({ ...result, final: finalStatus });
|
|
9335
|
+
}
|
|
9336
|
+
}
|
|
9337
|
+
async function streamCodeRunToTerminal(url, jsonMode) {
|
|
9338
|
+
return await new Promise((resolve5, reject) => {
|
|
9339
|
+
const ws = new WebSocket2(url);
|
|
9340
|
+
let lastStatus = {};
|
|
9341
|
+
ws.on("message", (data) => {
|
|
9342
|
+
try {
|
|
9343
|
+
const message = JSON.parse(String(data));
|
|
9344
|
+
const payload = message.payload ?? {};
|
|
9345
|
+
if (message.type === "code_run_event") {
|
|
9346
|
+
const kind = String(payload.kind ?? "status");
|
|
9347
|
+
const text = String(payload.text ?? "");
|
|
9348
|
+
if (!jsonMode) {
|
|
9349
|
+
if (kind === "stdout") process.stdout.write(text);
|
|
9350
|
+
else process.stderr.write(text);
|
|
9351
|
+
}
|
|
9352
|
+
} else if (message.type === "code_run_update") {
|
|
9353
|
+
lastStatus = { ...lastStatus, ...payload };
|
|
9354
|
+
const status = String(payload.status ?? "");
|
|
9355
|
+
if (["finished", "failed", "timeout", "cancelled"].includes(status)) {
|
|
9356
|
+
ws.close();
|
|
9357
|
+
resolve5(lastStatus);
|
|
9358
|
+
}
|
|
9359
|
+
}
|
|
9360
|
+
} catch (err) {
|
|
9361
|
+
ws.close();
|
|
9362
|
+
reject(err);
|
|
9363
|
+
}
|
|
9364
|
+
});
|
|
9365
|
+
ws.on("error", () => reject(new Error("Code Run stream failed.")));
|
|
9366
|
+
ws.on("close", () => {
|
|
9367
|
+
if (Object.keys(lastStatus).length > 0) resolve5(lastStatus);
|
|
9368
|
+
});
|
|
9369
|
+
});
|
|
9370
|
+
}
|
|
9371
|
+
async function pollCodeRunStatus(client, statusPath, apiKey, jsonMode) {
|
|
9372
|
+
for (; ; ) {
|
|
9373
|
+
const status = await client.getCodeRunStatus(statusPath, apiKey);
|
|
9374
|
+
const value = String(status.status ?? "");
|
|
9375
|
+
if (!jsonMode && value) process.stderr.write(`Code Run status: ${value}
|
|
9376
|
+
`);
|
|
9377
|
+
if (["finished", "failed", "timeout", "cancelled"].includes(value)) return status;
|
|
9378
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
8590
9381
|
function buildSkillInput(flags, inlineTokens, schemaParams) {
|
|
8591
9382
|
if (typeof flags.input === "string") {
|
|
8592
9383
|
return JSON.parse(flags.input);
|
|
@@ -8741,6 +9532,7 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
|
8741
9532
|
{ path: ["account", "profile-picture", "set"], description: "Upload a profile picture", examples: ["openmates settings account profile-picture set ./avatar.jpg"] },
|
|
8742
9533
|
{ path: ["account", "chats", "stats"], description: "Show chat statistics", examples: ["openmates settings account chats stats"] },
|
|
8743
9534
|
{ path: ["account", "delete", "preview"], description: "Preview account deletion impact", examples: ["openmates settings account delete preview"] },
|
|
9535
|
+
{ path: ["account", "delete"], description: "Delete your account with email code plus 2FA when configured", examples: ["openmates settings account delete --yes"] },
|
|
8744
9536
|
{ path: ["account", "storage", "overview"], description: "Show storage overview", examples: ["openmates settings account storage overview"] },
|
|
8745
9537
|
{ path: ["account", "storage", "files"], description: "List stored files", examples: ["openmates settings account storage files --category images"] },
|
|
8746
9538
|
{ path: ["account", "storage", "delete"], description: "Delete one stored file by file ID", examples: ["openmates settings account storage delete <file-id> --yes"] },
|
|
@@ -8755,12 +9547,18 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
|
8755
9547
|
{ path: ["billing", "usage", "summaries"], description: "Show usage summaries", examples: ["openmates settings billing usage summaries"] },
|
|
8756
9548
|
{ path: ["billing", "usage", "daily"], description: "Show daily usage overview", examples: ["openmates settings billing usage daily"] },
|
|
8757
9549
|
{ path: ["billing", "usage", "export"], description: "Export usage data", examples: ["openmates settings billing usage export --json"] },
|
|
9550
|
+
{ path: ["billing", "buy-credits", "bank-transfer"], description: "Buy credits by SEPA bank transfer", examples: ["openmates settings billing buy-credits bank-transfer --credits 110000"] },
|
|
9551
|
+
{ path: ["billing", "bank-transfer", "status"], description: "Show bank-transfer order status", examples: ["openmates settings billing bank-transfer status <order-id>"] },
|
|
9552
|
+
{ path: ["billing", "bank-transfer", "list"], description: "List pending bank-transfer orders", examples: ["openmates settings billing bank-transfer list"] },
|
|
8758
9553
|
{ path: ["billing", "invoices", "list"], description: "List invoices", examples: ["openmates settings billing invoices list --json"] },
|
|
8759
9554
|
{ path: ["billing", "invoices", "download"], description: "Download an invoice PDF", examples: ["openmates settings billing invoices download <invoice-id> --output ./invoices"] },
|
|
8760
9555
|
{ path: ["billing", "invoices", "credit-note"], description: "Download a credit note PDF", examples: ["openmates settings billing invoices credit-note <invoice-id> --output ./invoices"] },
|
|
8761
9556
|
{ path: ["billing", "invoices", "refund"], description: "Request a refund for an invoice", examples: ["openmates settings billing invoices refund <invoice-id> --yes"] },
|
|
8762
9557
|
{ path: ["billing", "gift-card", "redeem"], description: "Redeem a gift card", examples: ["openmates settings billing gift-card redeem ABCD-1234"] },
|
|
8763
9558
|
{ path: ["billing", "gift-card", "list"], description: "List redeemed gift cards", examples: ["openmates settings billing gift-card list"] },
|
|
9559
|
+
{ 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"] },
|
|
9560
|
+
{ path: ["billing", "gift-card", "purchase-status"], description: "Show gift-card bank-transfer purchase status", examples: ["openmates settings billing gift-card purchase-status <order-id>"] },
|
|
9561
|
+
{ path: ["billing", "gift-card", "purchased"], description: "List purchased unused gift cards", examples: ["openmates settings billing gift-card purchased"] },
|
|
8764
9562
|
{ 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"] },
|
|
8765
9563
|
{ path: ["notifications", "status"], description: "Show notification settings", examples: ["openmates settings notifications status --json"] },
|
|
8766
9564
|
{ path: ["notifications", "list"], description: "List recent notification events", examples: ["openmates settings notifications list --limit 20 --json"] },
|
|
@@ -8786,7 +9584,6 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
|
8786
9584
|
];
|
|
8787
9585
|
var SETTINGS_INFO_COMMANDS = [
|
|
8788
9586
|
{ 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"] },
|
|
8789
|
-
{ 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"] },
|
|
8790
9587
|
{ 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"] },
|
|
8791
9588
|
{ path: ["security", "passkeys"], description: "Passkeys are web-only", webPath: "account/security/passkeys", reason: "Passkeys require WebAuthn browser APIs.", examples: ["openmates settings security passkeys"] },
|
|
8792
9589
|
{ 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"] },
|
|
@@ -8968,9 +9765,9 @@ function parseYamlScalar(value) {
|
|
|
8968
9765
|
}
|
|
8969
9766
|
async function saveDownloadedDocument(document, output) {
|
|
8970
9767
|
const { mkdir, writeFile } = await import("fs/promises");
|
|
8971
|
-
const { join: join4, basename:
|
|
9768
|
+
const { join: join4, basename: basename3, dirname } = await import("path");
|
|
8972
9769
|
const target = typeof output === "string" ? output : ".";
|
|
8973
|
-
const filename =
|
|
9770
|
+
const filename = basename3(document.filename || "document.pdf");
|
|
8974
9771
|
const filePath = target.endsWith(".pdf") ? target : join4(target, filename);
|
|
8975
9772
|
await mkdir(dirname(filePath), { recursive: true });
|
|
8976
9773
|
await writeFile(filePath, document.data);
|
|
@@ -9000,13 +9797,274 @@ function printMateInfo(mateId, json) {
|
|
|
9000
9797
|
async function confirmOrExit(question) {
|
|
9001
9798
|
const rl = await import("readline");
|
|
9002
9799
|
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
9003
|
-
const answer = await new Promise((
|
|
9800
|
+
const answer = await new Promise((resolve5) => iface.question(question, resolve5));
|
|
9004
9801
|
iface.close();
|
|
9005
9802
|
if (answer.trim().toLowerCase() !== "y") {
|
|
9006
9803
|
console.log("Aborted.");
|
|
9007
9804
|
process.exit(0);
|
|
9008
9805
|
}
|
|
9009
9806
|
}
|
|
9807
|
+
async function promptLine(question) {
|
|
9808
|
+
const rl = await import("readline");
|
|
9809
|
+
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
9810
|
+
const answer = await new Promise((resolve5) => iface.question(question, resolve5));
|
|
9811
|
+
iface.close();
|
|
9812
|
+
return answer.trim();
|
|
9813
|
+
}
|
|
9814
|
+
async function promptSecret(question) {
|
|
9815
|
+
if (!process.stdin.isTTY) {
|
|
9816
|
+
return promptLine(question);
|
|
9817
|
+
}
|
|
9818
|
+
return new Promise((resolve5) => {
|
|
9819
|
+
const stdin2 = process.stdin;
|
|
9820
|
+
const wasRaw = stdin2.isRaw;
|
|
9821
|
+
let value = "";
|
|
9822
|
+
process.stdout.write(question);
|
|
9823
|
+
stdin2.setRawMode(true);
|
|
9824
|
+
stdin2.resume();
|
|
9825
|
+
const onData = (chunk) => {
|
|
9826
|
+
const char = chunk.toString("utf8");
|
|
9827
|
+
if (char === "\r" || char === "\n") {
|
|
9828
|
+
stdin2.off("data", onData);
|
|
9829
|
+
stdin2.setRawMode(wasRaw);
|
|
9830
|
+
process.stdout.write("\n");
|
|
9831
|
+
resolve5(value);
|
|
9832
|
+
return;
|
|
9833
|
+
}
|
|
9834
|
+
if (char === "") {
|
|
9835
|
+
stdin2.off("data", onData);
|
|
9836
|
+
stdin2.setRawMode(wasRaw);
|
|
9837
|
+
process.stdout.write("\n");
|
|
9838
|
+
process.exit(130);
|
|
9839
|
+
}
|
|
9840
|
+
if (char === "\x7F" || char === "\b") {
|
|
9841
|
+
value = value.slice(0, -1);
|
|
9842
|
+
return;
|
|
9843
|
+
}
|
|
9844
|
+
value += char;
|
|
9845
|
+
};
|
|
9846
|
+
stdin2.on("data", onData);
|
|
9847
|
+
});
|
|
9848
|
+
}
|
|
9849
|
+
async function writeSecretFile(filePath, content, force = false) {
|
|
9850
|
+
const { mkdir, writeFile, stat: stat2 } = await import("fs/promises");
|
|
9851
|
+
const { dirname } = await import("path");
|
|
9852
|
+
try {
|
|
9853
|
+
await stat2(filePath);
|
|
9854
|
+
if (!force) throw new Error(`${filePath} already exists. Use --force to overwrite.`);
|
|
9855
|
+
} catch (error) {
|
|
9856
|
+
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
9857
|
+
throw error;
|
|
9858
|
+
}
|
|
9859
|
+
if (error instanceof Error && !("code" in error)) throw error;
|
|
9860
|
+
}
|
|
9861
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
9862
|
+
await writeFile(filePath, content, { mode: 384 });
|
|
9863
|
+
return filePath;
|
|
9864
|
+
}
|
|
9865
|
+
async function generateProvisioningPassword() {
|
|
9866
|
+
const { randomBytes: randomBytes3 } = await import("crypto");
|
|
9867
|
+
return `OM-${randomBytes3(18).toString("base64url")}-aA2#`;
|
|
9868
|
+
}
|
|
9869
|
+
function decodeBase32(input) {
|
|
9870
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
9871
|
+
const cleaned = input.toUpperCase().replace(/[^A-Z2-7]/g, "");
|
|
9872
|
+
let bits = "";
|
|
9873
|
+
for (const char of cleaned) {
|
|
9874
|
+
const value = alphabet.indexOf(char);
|
|
9875
|
+
if (value >= 0) bits += value.toString(2).padStart(5, "0");
|
|
9876
|
+
}
|
|
9877
|
+
const bytes = [];
|
|
9878
|
+
for (let index = 0; index + 8 <= bits.length; index += 8) {
|
|
9879
|
+
bytes.push(Number.parseInt(bits.slice(index, index + 8), 2));
|
|
9880
|
+
}
|
|
9881
|
+
return Buffer.from(bytes);
|
|
9882
|
+
}
|
|
9883
|
+
async function generateTotpCode(secret) {
|
|
9884
|
+
const { createHmac } = await import("crypto");
|
|
9885
|
+
const counter = Math.floor(Date.now() / 1e3 / 30);
|
|
9886
|
+
const counterBuffer = Buffer.alloc(8);
|
|
9887
|
+
counterBuffer.writeBigUInt64BE(BigInt(counter));
|
|
9888
|
+
const hmac = createHmac("sha1", decodeBase32(secret)).update(counterBuffer).digest();
|
|
9889
|
+
const offset = hmac[hmac.length - 1] & 15;
|
|
9890
|
+
const code = (hmac.readUInt32BE(offset) & 2147483647) % 1e6;
|
|
9891
|
+
return String(code).padStart(6, "0");
|
|
9892
|
+
}
|
|
9893
|
+
async function runSecuritySetup(client, flags, options = {}) {
|
|
9894
|
+
const result = {};
|
|
9895
|
+
if (flags["skip-2fa"] === true) {
|
|
9896
|
+
if (flags.yes !== true) await confirmOrExit("Skip 2FA setup? This weakens account protection. [y/N] ");
|
|
9897
|
+
} else {
|
|
9898
|
+
const setup = await client.startTotpSetup();
|
|
9899
|
+
result.otpSecret = setup.secret ?? null;
|
|
9900
|
+
if (setup.otpauth_url) client.renderTotpQrCode(setup.otpauth_url);
|
|
9901
|
+
if (setup.secret) console.log(`Manual TOTP secret: ${setup.secret}`);
|
|
9902
|
+
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: ");
|
|
9903
|
+
await client.verifyTotpSetup(code);
|
|
9904
|
+
const provider = typeof flags.provider === "string" ? flags.provider : "Authenticator app";
|
|
9905
|
+
await client.setTotpProvider(provider);
|
|
9906
|
+
const backup = await client.requestBackupCodes();
|
|
9907
|
+
result.backupCodes = backup.backup_codes;
|
|
9908
|
+
const backupOutput = typeof flags["backup-codes-output"] === "string" ? flags["backup-codes-output"] : void 0;
|
|
9909
|
+
if (backupOutput) {
|
|
9910
|
+
result.backupCodesPath = await writeSecretFile(backupOutput, `${backup.backup_codes.join("\n")}
|
|
9911
|
+
`, flags.force === true);
|
|
9912
|
+
await client.confirmBackupCodesStored();
|
|
9913
|
+
} else if (flags.yes === true) {
|
|
9914
|
+
await client.confirmBackupCodesStored();
|
|
9915
|
+
} else {
|
|
9916
|
+
console.log("Backup codes:");
|
|
9917
|
+
for (const backupCode of backup.backup_codes) console.log(` ${backupCode}`);
|
|
9918
|
+
await confirmOrExit("I stored these backup codes safely. Continue? [y/N] ");
|
|
9919
|
+
await client.confirmBackupCodesStored();
|
|
9920
|
+
}
|
|
9921
|
+
}
|
|
9922
|
+
if (flags["skip-recovery-key"] === true) {
|
|
9923
|
+
if (flags.yes !== true) await confirmOrExit("Skip recovery-key setup? You may lose access to encrypted data. [y/N] ");
|
|
9924
|
+
} else {
|
|
9925
|
+
const recovery = await client.createAndConfirmRecoveryKey();
|
|
9926
|
+
result.recoveryKey = recovery.recoveryKey;
|
|
9927
|
+
const recoveryOutput = typeof flags["recovery-key-output"] === "string" ? flags["recovery-key-output"] : void 0;
|
|
9928
|
+
if (recoveryOutput) {
|
|
9929
|
+
result.recoveryKeyPath = await writeSecretFile(recoveryOutput, `${recovery.recoveryKey}
|
|
9930
|
+
`, flags.force === true);
|
|
9931
|
+
} else {
|
|
9932
|
+
console.log(`Recovery key: ${recovery.recoveryKey}`);
|
|
9933
|
+
if (flags.yes !== true) await confirmOrExit("I stored this recovery key safely. Continue? [y/N] ");
|
|
9934
|
+
}
|
|
9935
|
+
}
|
|
9936
|
+
return result;
|
|
9937
|
+
}
|
|
9938
|
+
async function handleSignup(client, flags, options = {}) {
|
|
9939
|
+
if (flags.password !== void 0) {
|
|
9940
|
+
throw new Error("Passwords must be entered through hidden prompts, not command-line flags.");
|
|
9941
|
+
}
|
|
9942
|
+
const email = typeof flags.email === "string" ? flags.email : await promptLine("Email: ");
|
|
9943
|
+
const username = typeof flags.username === "string" ? flags.username : await promptLine("Username: ");
|
|
9944
|
+
const inviteCode = typeof flags["invite-code"] === "string" ? flags["invite-code"] : "";
|
|
9945
|
+
const language = typeof flags.language === "string" ? flags.language : "en";
|
|
9946
|
+
const password = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await promptSecret("Password: ");
|
|
9947
|
+
if (!process.env.OPENMATES_CLI_SIGNUP_PASSWORD) {
|
|
9948
|
+
const confirmPassword = await promptSecret("Confirm password: ");
|
|
9949
|
+
if (password !== confirmPassword) throw new Error("Passwords do not match.");
|
|
9950
|
+
}
|
|
9951
|
+
await client.requestSignupEmailCode({ email, inviteCode, language });
|
|
9952
|
+
const emailCode = process.env.OPENMATES_CLI_SIGNUP_EMAIL_CODE ?? await promptLine("Email verification code: ");
|
|
9953
|
+
await client.verifySignupEmailCode({ email, username, inviteCode, code: emailCode, language });
|
|
9954
|
+
const signup = await client.setupPasswordAccount({ email, username, password, inviteCode, language });
|
|
9955
|
+
const security = await runSecuritySetup(client, flags, options);
|
|
9956
|
+
let giftCardResult = null;
|
|
9957
|
+
if (typeof flags["gift-card-code"] === "string") {
|
|
9958
|
+
giftCardResult = await client.redeemGiftCard(flags["gift-card-code"]);
|
|
9959
|
+
}
|
|
9960
|
+
const response = {
|
|
9961
|
+
success: true,
|
|
9962
|
+
user: signup.user ?? null,
|
|
9963
|
+
backup_codes_path: security.backupCodesPath ?? null,
|
|
9964
|
+
recovery_key_path: security.recoveryKeyPath ?? null,
|
|
9965
|
+
gift_card: giftCardResult
|
|
9966
|
+
};
|
|
9967
|
+
if (flags.json === true) {
|
|
9968
|
+
printJson2(response);
|
|
9969
|
+
} else {
|
|
9970
|
+
console.log("\x1B[32m\u2713\x1B[0m Account created and CLI session saved.");
|
|
9971
|
+
if (security.backupCodesPath) console.log(`Backup codes saved to ${security.backupCodesPath}`);
|
|
9972
|
+
if (security.recoveryKeyPath) console.log(`Recovery key saved to ${security.recoveryKeyPath}`);
|
|
9973
|
+
console.log("Buy credits or redeem a gift card:");
|
|
9974
|
+
console.log(" openmates settings billing buy-credits bank-transfer --credits 110000");
|
|
9975
|
+
console.log(" openmates settings billing gift-card redeem <CODE>");
|
|
9976
|
+
}
|
|
9977
|
+
return {
|
|
9978
|
+
user: signup.user ?? null,
|
|
9979
|
+
backupCodesPath: security.backupCodesPath,
|
|
9980
|
+
recoveryKeyPath: security.recoveryKeyPath,
|
|
9981
|
+
security,
|
|
9982
|
+
giftCard: giftCardResult
|
|
9983
|
+
};
|
|
9984
|
+
}
|
|
9985
|
+
async function handleE2E(client, subcommand, rest, flags) {
|
|
9986
|
+
if (subcommand !== "provision-auth-accounts") {
|
|
9987
|
+
printE2EHelp();
|
|
9988
|
+
return;
|
|
9989
|
+
}
|
|
9990
|
+
if (client.apiUrl.includes("api.openmates.org") && !client.apiUrl.includes("api.dev.openmates.org")) {
|
|
9991
|
+
throw new Error("E2E provisioning refuses production API URLs.");
|
|
9992
|
+
}
|
|
9993
|
+
if (flags.password !== void 0) throw new Error("Use generated passwords or OPENMATES_CLI_SIGNUP_PASSWORD, not --password.");
|
|
9994
|
+
const slot = parseRequiredNumber(flags.slot, "--slot");
|
|
9995
|
+
if (![15, 17].includes(slot)) throw new Error("Only reserved slots 15 and 17 are supported.");
|
|
9996
|
+
const artifact = typeof flags.artifact === "string" ? flags.artifact : `test-results/credential-updates/slot-${slot}.env`;
|
|
9997
|
+
const domain = typeof flags.domain === "string" ? flags.domain : process.env.OPENMATES_CLI_E2E_EMAIL_DOMAIN;
|
|
9998
|
+
const email = typeof flags.email === "string" ? flags.email : domain ? `cli-e2e-slot-${slot}-${Date.now()}@${domain}` : await promptLine("E2E account email: ");
|
|
9999
|
+
const username = typeof flags.username === "string" ? flags.username : `cli_e2e_slot_${slot}_${Date.now()}`;
|
|
10000
|
+
const generatedPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await generateProvisioningPassword();
|
|
10001
|
+
const originalSignupPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD;
|
|
10002
|
+
process.env.OPENMATES_CLI_SIGNUP_PASSWORD = generatedPassword;
|
|
10003
|
+
let signup = null;
|
|
10004
|
+
try {
|
|
10005
|
+
signup = await handleSignup(client, {
|
|
10006
|
+
...flags,
|
|
10007
|
+
email,
|
|
10008
|
+
username,
|
|
10009
|
+
yes: true,
|
|
10010
|
+
json: false,
|
|
10011
|
+
"backup-codes-output": typeof flags["backup-codes-output"] === "string" ? flags["backup-codes-output"] : `${artifact}.backup-codes`,
|
|
10012
|
+
"recovery-key-output": typeof flags["recovery-key-output"] === "string" ? flags["recovery-key-output"] : `${artifact}.recovery-key`
|
|
10013
|
+
}, { autoGenerateTotp: true });
|
|
10014
|
+
} finally {
|
|
10015
|
+
if (originalSignupPassword === void 0) delete process.env.OPENMATES_CLI_SIGNUP_PASSWORD;
|
|
10016
|
+
else process.env.OPENMATES_CLI_SIGNUP_PASSWORD = originalSignupPassword;
|
|
10017
|
+
}
|
|
10018
|
+
if (!signup.security.otpSecret) throw new Error("Provisioning did not create a TOTP secret.");
|
|
10019
|
+
const artifactBody = [
|
|
10020
|
+
`OPENMATES_TEST_ACCOUNT_${slot}_EMAIL=${email}`,
|
|
10021
|
+
`OPENMATES_TEST_ACCOUNT_${slot}_PASSWORD=${generatedPassword}`,
|
|
10022
|
+
`OPENMATES_TEST_ACCOUNT_${slot}_OTP_KEY=${signup.security.otpSecret}`,
|
|
10023
|
+
`# Backup codes: ${artifact}.backup-codes`,
|
|
10024
|
+
`# Recovery key: ${artifact}.recovery-key`,
|
|
10025
|
+
""
|
|
10026
|
+
].join("\n");
|
|
10027
|
+
const path = await writeSecretFile(artifact, artifactBody, flags.force === true);
|
|
10028
|
+
if (flags.json === true) printJson2({ success: true, artifact: path, slot, email });
|
|
10029
|
+
else console.log(`Provisioning artifact written to ${path}. Upload secrets through the trusted manual process.`);
|
|
10030
|
+
}
|
|
10031
|
+
function printBankTransferOrder(order, giftCard) {
|
|
10032
|
+
header(giftCard ? "Gift Card Bank Transfer" : "Bank Transfer");
|
|
10033
|
+
console.log(`Order ID: ${order.order_id}`);
|
|
10034
|
+
console.log(`Credits: ${order.credits_amount}`);
|
|
10035
|
+
console.log(`Amount: EUR ${order.amount_eur}`);
|
|
10036
|
+
console.log(`Reference: ${order.reference}`);
|
|
10037
|
+
console.log(`IBAN: ${order.iban}`);
|
|
10038
|
+
console.log(`BIC: ${order.bic}`);
|
|
10039
|
+
console.log(`Bank: ${order.bank_name}`);
|
|
10040
|
+
console.log(`Account holder: ${order.account_holder_name}`);
|
|
10041
|
+
if (order.account_holder_address_line1) console.log(`Address: ${order.account_holder_address_line1}`);
|
|
10042
|
+
if (order.account_holder_address_line2) console.log(` ${order.account_holder_address_line2}`);
|
|
10043
|
+
if (order.account_holder_postal_code || order.account_holder_city) {
|
|
10044
|
+
console.log(` ${[order.account_holder_postal_code, order.account_holder_city].filter(Boolean).join(" ")}`);
|
|
10045
|
+
}
|
|
10046
|
+
if (order.account_holder_country) console.log(` ${order.account_holder_country}`);
|
|
10047
|
+
console.log(`Expires: ${order.expires_at}`);
|
|
10048
|
+
console.log("\nInclude the exact reference in your bank transfer. Missing or changed references require manual review.");
|
|
10049
|
+
if (giftCard) {
|
|
10050
|
+
console.log(`Gift-card code appears after payment is matched: openmates settings billing gift-card purchase-status ${order.order_id}`);
|
|
10051
|
+
} else {
|
|
10052
|
+
console.log(`Check status: openmates settings billing bank-transfer status ${order.order_id}`);
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
10055
|
+
function printBankTransferStatus(status, giftCard) {
|
|
10056
|
+
header(giftCard ? "Gift Card Purchase Status" : "Bank Transfer Status");
|
|
10057
|
+
console.log(`Order ID: ${status.order_id}`);
|
|
10058
|
+
console.log(`Status: ${status.status}`);
|
|
10059
|
+
console.log(`Credits: ${status.credits_amount}`);
|
|
10060
|
+
console.log(`Amount: EUR ${status.amount_eur}`);
|
|
10061
|
+
console.log(`Reference: ${status.reference}`);
|
|
10062
|
+
console.log(`Expires: ${status.expires_at}`);
|
|
10063
|
+
if (giftCard) {
|
|
10064
|
+
const code = status.gift_card_code;
|
|
10065
|
+
console.log(`Gift-card code: ${code || "available after payment is matched"}`);
|
|
10066
|
+
}
|
|
10067
|
+
}
|
|
9010
10068
|
function printSettingsInfoCommand(client, command, json) {
|
|
9011
10069
|
const appUrl = deriveAppUrl(client.apiUrl);
|
|
9012
10070
|
const webUrl = command.webPath ? `${appUrl}/#settings/${command.webPath}` : null;
|
|
@@ -9073,8 +10131,8 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
9073
10131
|
if (matches(tokens, ["account", "import-chat"])) {
|
|
9074
10132
|
const file = rest[1];
|
|
9075
10133
|
if (!file) throw new Error("Missing import file. Example: openmates settings account import-chat ./chat.yml");
|
|
9076
|
-
const { readFile } = await import("fs/promises");
|
|
9077
|
-
const content = await
|
|
10134
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
10135
|
+
const content = await readFile2(file, "utf-8");
|
|
9078
10136
|
await printSettingsMutationResult(
|
|
9079
10137
|
client.settingsPost("import-chat", parseChatImportPayload(content)),
|
|
9080
10138
|
flags
|
|
@@ -9101,6 +10159,28 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
9101
10159
|
await printSettingsResult(client.settingsGet("delete-account-preview"), flags);
|
|
9102
10160
|
return;
|
|
9103
10161
|
}
|
|
10162
|
+
if (matches(tokens, ["account", "delete"])) {
|
|
10163
|
+
if (flags["email-code"] !== void 0 || flags["totp-code"] !== void 0) {
|
|
10164
|
+
throw new Error("Deletion verification codes must be entered through interactive prompts, not command-line flags.");
|
|
10165
|
+
}
|
|
10166
|
+
const preview = await client.settingsGet("delete-account-preview");
|
|
10167
|
+
if (flags.json !== true) {
|
|
10168
|
+
header("Delete Account Preview");
|
|
10169
|
+
printGenericObject(preview);
|
|
10170
|
+
}
|
|
10171
|
+
if (flags.yes !== true) {
|
|
10172
|
+
await confirmOrExit("Delete this account and all associated data? [y/N] ");
|
|
10173
|
+
}
|
|
10174
|
+
await client.requestDeleteAccountEmailCode();
|
|
10175
|
+
const emailCode = await promptLine("Enter email verification code: ");
|
|
10176
|
+
await client.verifyDeleteAccountEmailCode(emailCode);
|
|
10177
|
+
const authMethods = await client.getAuthMethodsStatus();
|
|
10178
|
+
const totpCode = authMethods.has_2fa ? await promptLine("Enter 2FA code: ") : void 0;
|
|
10179
|
+
const result = await client.deleteAccountWithCliVerification(totpCode);
|
|
10180
|
+
if (flags.json === true) printJson2(result);
|
|
10181
|
+
else console.log("\x1B[32m\u2713\x1B[0m Account deletion requested and local CLI session cleared");
|
|
10182
|
+
return;
|
|
10183
|
+
}
|
|
9104
10184
|
if (matches(tokens, ["account", "storage", "overview"])) {
|
|
9105
10185
|
await printSettingsResult(client.settingsGet("storage"), flags);
|
|
9106
10186
|
return;
|
|
@@ -9189,6 +10269,23 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
9189
10269
|
await printSettingsResult(client.settingsGet("usage"), flags);
|
|
9190
10270
|
return;
|
|
9191
10271
|
}
|
|
10272
|
+
if (matches(tokens, ["billing", "buy-credits", "bank-transfer"])) {
|
|
10273
|
+
const credits = parseRequiredNumber(flags.credits, "--credits");
|
|
10274
|
+
const order = await client.createBankTransferOrder(credits);
|
|
10275
|
+
flags.json === true ? printJson2(order) : printBankTransferOrder(order, false);
|
|
10276
|
+
return;
|
|
10277
|
+
}
|
|
10278
|
+
if (matches(tokens, ["billing", "bank-transfer", "status"])) {
|
|
10279
|
+
const orderId = rest[2];
|
|
10280
|
+
if (!orderId) throw new Error("Missing order ID.");
|
|
10281
|
+
const status = await client.getBankTransferStatus(orderId);
|
|
10282
|
+
flags.json === true ? printJson2(status) : printBankTransferStatus(status, false);
|
|
10283
|
+
return;
|
|
10284
|
+
}
|
|
10285
|
+
if (matches(tokens, ["billing", "bank-transfer", "list"])) {
|
|
10286
|
+
await printSettingsResult(client.listBankTransferOrders(), flags);
|
|
10287
|
+
return;
|
|
10288
|
+
}
|
|
9192
10289
|
if (matches(tokens, ["billing", "invoices", "list"])) {
|
|
9193
10290
|
await printSettingsResult(client.listInvoices(), flags);
|
|
9194
10291
|
return;
|
|
@@ -9239,6 +10336,23 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
9239
10336
|
await printSettingsResult(client.listRedeemedGiftCards(), flags);
|
|
9240
10337
|
return;
|
|
9241
10338
|
}
|
|
10339
|
+
if (matches(tokens, ["billing", "gift-card", "buy", "bank-transfer"])) {
|
|
10340
|
+
const credits = parseRequiredNumber(flags.credits, "--credits");
|
|
10341
|
+
const order = await client.createGiftCardBankTransferOrder(credits);
|
|
10342
|
+
flags.json === true ? printJson2(order) : printBankTransferOrder(order, true);
|
|
10343
|
+
return;
|
|
10344
|
+
}
|
|
10345
|
+
if (matches(tokens, ["billing", "gift-card", "purchase-status"])) {
|
|
10346
|
+
const orderId = rest[3];
|
|
10347
|
+
if (!orderId) throw new Error("Missing order ID.");
|
|
10348
|
+
const status = await client.getGiftCardPurchaseStatus(orderId);
|
|
10349
|
+
flags.json === true ? printJson2(status) : printBankTransferStatus(status, true);
|
|
10350
|
+
return;
|
|
10351
|
+
}
|
|
10352
|
+
if (matches(tokens, ["billing", "gift-card", "purchased"])) {
|
|
10353
|
+
await printSettingsResult(client.listPurchasedGiftCards(), flags);
|
|
10354
|
+
return;
|
|
10355
|
+
}
|
|
9242
10356
|
if (matches(tokens, ["billing", "auto-topup", "low-balance", "set"])) {
|
|
9243
10357
|
const enabled = parseOnOff(String(flags.enabled ?? ""), "low-balance auto top-up");
|
|
9244
10358
|
const amount = parseRequiredNumber(flags.amount, "--amount");
|
|
@@ -9551,12 +10665,12 @@ function parseArgs(argv) {
|
|
|
9551
10665
|
const key = keyValue[0];
|
|
9552
10666
|
const valueFromEquals = keyValue[1];
|
|
9553
10667
|
if (valueFromEquals !== void 0) {
|
|
9554
|
-
flags[key] = valueFromEquals;
|
|
10668
|
+
flags[key] = appendFlagValue(flags[key], valueFromEquals);
|
|
9555
10669
|
continue;
|
|
9556
10670
|
}
|
|
9557
10671
|
const next = argv[i + 1];
|
|
9558
10672
|
if (next && !next.startsWith("--")) {
|
|
9559
|
-
flags[key] = next;
|
|
10673
|
+
flags[key] = appendFlagValue(flags[key], next);
|
|
9560
10674
|
i += 1;
|
|
9561
10675
|
} else {
|
|
9562
10676
|
flags[key] = true;
|
|
@@ -9564,6 +10678,11 @@ function parseArgs(argv) {
|
|
|
9564
10678
|
}
|
|
9565
10679
|
return { positionals, flags };
|
|
9566
10680
|
}
|
|
10681
|
+
function appendFlagValue(existing, value) {
|
|
10682
|
+
if (typeof existing === "string" && existing.length > 0) return `${existing}
|
|
10683
|
+
${value}`;
|
|
10684
|
+
return value;
|
|
10685
|
+
}
|
|
9567
10686
|
function resolveApiKey(flags) {
|
|
9568
10687
|
if (typeof flags["api-key"] === "string" && flags["api-key"].length > 0) {
|
|
9569
10688
|
return flags["api-key"];
|
|
@@ -11167,6 +12286,7 @@ function printHelp() {
|
|
|
11167
12286
|
|
|
11168
12287
|
Commands:
|
|
11169
12288
|
openmates login Pair-auth login
|
|
12289
|
+
openmates signup Create an account from the terminal
|
|
11170
12290
|
openmates logout Log out and clear session
|
|
11171
12291
|
openmates whoami [--json] Show account info
|
|
11172
12292
|
openmates chats [--help] Chat commands (list, search, show, ...)
|
|
@@ -11178,6 +12298,7 @@ Commands:
|
|
|
11178
12298
|
openmates newchatsuggestions [--limit <n>] [--json] Personalized new chat suggestions
|
|
11179
12299
|
openmates server [--help] Server management (install, start, stop, ...)
|
|
11180
12300
|
openmates docs [--help] Browse, search, and download documentation
|
|
12301
|
+
openmates e2e provision-auth-accounts Provision local E2E auth-account artifacts
|
|
11181
12302
|
|
|
11182
12303
|
Flags:
|
|
11183
12304
|
--json Output raw JSON instead of formatted output
|
|
@@ -11185,6 +12306,40 @@ Flags:
|
|
|
11185
12306
|
--api-key <key> Optional API key override (or set OPENMATES_API_KEY)
|
|
11186
12307
|
--help Show contextual help for any command`);
|
|
11187
12308
|
}
|
|
12309
|
+
function printSignupHelp() {
|
|
12310
|
+
console.log(`Signup command:
|
|
12311
|
+
openmates signup --email <email> --username <name> --invite-code <code>
|
|
12312
|
+
|
|
12313
|
+
Creates a password account using client-side encrypted signup crypto. Passwords
|
|
12314
|
+
are entered through hidden prompts and cannot be passed with --password.
|
|
12315
|
+
|
|
12316
|
+
Options:
|
|
12317
|
+
--email <email> Email address; prompted when omitted
|
|
12318
|
+
--username <name> Username; prompted when omitted
|
|
12319
|
+
--invite-code <code> Invite code when required
|
|
12320
|
+
--gift-card-code <code> Redeem after account creation
|
|
12321
|
+
--backup-codes-output <path> Save backup codes to a 0600 file
|
|
12322
|
+
--recovery-key-output <path> Save recovery key to a 0600 file
|
|
12323
|
+
--skip-2fa Explicitly skip 2FA setup after warning
|
|
12324
|
+
--skip-recovery-key Explicitly skip recovery key after warning
|
|
12325
|
+
--yes Confirm warning prompts
|
|
12326
|
+
--json Output non-secret JSON summary`);
|
|
12327
|
+
}
|
|
12328
|
+
function printE2EHelp() {
|
|
12329
|
+
console.log(`E2E provisioning command:
|
|
12330
|
+
openmates e2e provision-auth-accounts --slot 15 --artifact ./test-results/credential-updates/slot-15.env --api-url https://api.dev.openmates.org
|
|
12331
|
+
|
|
12332
|
+
Creates local ignored credential artifacts for reserved E2E auth accounts. The
|
|
12333
|
+
command refuses production API URLs and does not upload GitHub secrets.
|
|
12334
|
+
|
|
12335
|
+
Options:
|
|
12336
|
+
--slot <15|17> Reserved auth-account slot
|
|
12337
|
+
--artifact <path> Output .env artifact path
|
|
12338
|
+
--email <email> Test email; prompted/generated when omitted
|
|
12339
|
+
--domain <mail-domain> Generate email at this domain
|
|
12340
|
+
--force Overwrite local artifact files
|
|
12341
|
+
--yes Confirm prompts where possible`);
|
|
12342
|
+
}
|
|
11188
12343
|
function printChatsHelp() {
|
|
11189
12344
|
console.log(`Chats commands:
|
|
11190
12345
|
openmates chats list [--limit <n>] [--page <n>] [--json]
|
|
@@ -11280,6 +12435,9 @@ function printAppsHelp() {
|
|
|
11280
12435
|
openmates apps skill-info <app-id> <skill-id> [--json]
|
|
11281
12436
|
openmates apps <app-id> <skill-id> "<query>" [--json]
|
|
11282
12437
|
openmates apps <app-id> <skill-id> --input '<json>' [--json]
|
|
12438
|
+
openmates apps code run --language python --code 'print("Hello")'
|
|
12439
|
+
openmates apps code run --entry main.py --file main.py [--file requirements.txt]
|
|
12440
|
+
openmates apps code run --entry main.py --dir ./project [--exclude node_modules]
|
|
11283
12441
|
openmates apps travel booking-link --token "<token>" [--context '<json>']
|
|
11284
12442
|
|
|
11285
12443
|
Authentication:
|
|
@@ -11292,6 +12450,7 @@ Examples:
|
|
|
11292
12450
|
openmates apps web search "latest AI news"
|
|
11293
12451
|
openmates apps news search "climate change"
|
|
11294
12452
|
openmates apps ai ask "Summarise this: ..."
|
|
12453
|
+
openmates apps code run --language python --filename hello.py --code 'print("Hello from CLI")'
|
|
11295
12454
|
openmates apps travel search_connections --input '{"requests":[{"legs":[{"origin":"BER","destination":"LHR","date":"2026-04-15"}]}]}'
|
|
11296
12455
|
openmates apps travel booking-link --token "<booking_token from search result>"
|
|
11297
12456
|
openmates apps skill-info web search`);
|