openmates 0.11.0-alpha.0 → 0.11.0-alpha.10
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-N6QY7K6L.js → chunk-GDSV23MQ.js} +1009 -309
- package/dist/chunk-SFTCIVE2.js +131 -0
- package/dist/cli.js +2 -1
- package/dist/index.d.ts +43 -8
- package/dist/index.js +2 -1
- package/dist/uploadService-3CAJXU4L.js +8 -0
- package/package.json +2 -2
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
uploadFile
|
|
3
|
+
} from "./chunk-SFTCIVE2.js";
|
|
4
|
+
|
|
1
5
|
// src/client.ts
|
|
2
6
|
import { randomUUID } from "crypto";
|
|
3
7
|
import { platform as platform2, release } from "os";
|
|
@@ -87,6 +91,16 @@ async function decryptWithAesGcmCombined(encryptedWithIvB64, rawKeyBytes) {
|
|
|
87
91
|
return null;
|
|
88
92
|
}
|
|
89
93
|
}
|
|
94
|
+
async function deriveEmailEncryptionKeyB64(email, emailSaltB64) {
|
|
95
|
+
const encoder = new TextEncoder();
|
|
96
|
+
const emailBytes = encoder.encode(email);
|
|
97
|
+
const saltBytes = base64ToBytes(emailSaltB64);
|
|
98
|
+
const combined = new Uint8Array(emailBytes.length + saltBytes.length);
|
|
99
|
+
combined.set(emailBytes);
|
|
100
|
+
combined.set(saltBytes, emailBytes.length);
|
|
101
|
+
const hashBuffer = await cryptoApi.subtle.digest("SHA-256", toArrayBuffer(combined));
|
|
102
|
+
return bytesToBase64(new Uint8Array(hashBuffer));
|
|
103
|
+
}
|
|
90
104
|
async function decryptBytesWithAesGcm(encryptedWithIvB64, rawKeyBytes) {
|
|
91
105
|
try {
|
|
92
106
|
const combined = base64ToBytes(encryptedWithIvB64);
|
|
@@ -180,12 +194,66 @@ var OpenMatesHttpClient = class {
|
|
|
180
194
|
async post(path, body, headers = {}) {
|
|
181
195
|
return this.request("POST", path, body, headers);
|
|
182
196
|
}
|
|
183
|
-
async delete(path, headers = {}) {
|
|
184
|
-
return this.request("DELETE", path,
|
|
197
|
+
async delete(path, body, headers = {}) {
|
|
198
|
+
return this.request("DELETE", path, body, headers);
|
|
185
199
|
}
|
|
186
200
|
async patch(path, body, headers = {}) {
|
|
187
201
|
return this.request("PATCH", path, body, headers);
|
|
188
202
|
}
|
|
203
|
+
async getBinary(path, headers = {}) {
|
|
204
|
+
const url = `${this.apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
205
|
+
const requestHeaders = {
|
|
206
|
+
Accept: "application/pdf,application/octet-stream",
|
|
207
|
+
...headers
|
|
208
|
+
};
|
|
209
|
+
const cookieHeader = this.formatCookieHeader();
|
|
210
|
+
if (cookieHeader) requestHeaders.Cookie = cookieHeader;
|
|
211
|
+
const response = await fetch(url, { method: "GET", headers: requestHeaders });
|
|
212
|
+
this.captureCookies(response);
|
|
213
|
+
return {
|
|
214
|
+
ok: response.ok,
|
|
215
|
+
status: response.status,
|
|
216
|
+
data: new Uint8Array(await response.arrayBuffer()),
|
|
217
|
+
headers: response.headers
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async *streamSse(path, headers = {}) {
|
|
221
|
+
const url = `${this.apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
222
|
+
const requestHeaders = {
|
|
223
|
+
Accept: "text/event-stream",
|
|
224
|
+
...headers
|
|
225
|
+
};
|
|
226
|
+
const cookieHeader = this.formatCookieHeader();
|
|
227
|
+
if (cookieHeader) requestHeaders.Cookie = cookieHeader;
|
|
228
|
+
const response = await fetch(url, { method: "GET", headers: requestHeaders });
|
|
229
|
+
this.captureCookies(response);
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
throw new Error(`SSE request failed with HTTP ${response.status}`);
|
|
232
|
+
}
|
|
233
|
+
if (!response.body) {
|
|
234
|
+
throw new Error("SSE response body is not readable");
|
|
235
|
+
}
|
|
236
|
+
const reader = response.body.getReader();
|
|
237
|
+
const decoder = new TextDecoder();
|
|
238
|
+
let buffer = "";
|
|
239
|
+
try {
|
|
240
|
+
while (true) {
|
|
241
|
+
const { done, value } = await reader.read();
|
|
242
|
+
if (done) break;
|
|
243
|
+
buffer += decoder.decode(value, { stream: true });
|
|
244
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
245
|
+
while (separatorIndex >= 0) {
|
|
246
|
+
const rawEvent = buffer.slice(0, separatorIndex);
|
|
247
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
248
|
+
const message = parseSseMessage(rawEvent);
|
|
249
|
+
if (message) yield message;
|
|
250
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
reader.releaseLock();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
189
257
|
async request(method, path, body, headers = {}) {
|
|
190
258
|
const url = `${this.apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
191
259
|
const requestHeaders = {
|
|
@@ -252,6 +320,19 @@ var OpenMatesHttpClient = class {
|
|
|
252
320
|
return single ? [single] : [];
|
|
253
321
|
}
|
|
254
322
|
};
|
|
323
|
+
function parseSseMessage(rawEvent) {
|
|
324
|
+
const message = { data: "" };
|
|
325
|
+
for (const line of rawEvent.split("\n")) {
|
|
326
|
+
if (!line || line.startsWith(":")) continue;
|
|
327
|
+
const separator = line.indexOf(":");
|
|
328
|
+
const field = separator >= 0 ? line.slice(0, separator) : line;
|
|
329
|
+
const value = separator >= 0 ? line.slice(separator + 1).replace(/^ /, "") : "";
|
|
330
|
+
if (field === "id") message.id = value;
|
|
331
|
+
if (field === "event") message.event = value;
|
|
332
|
+
if (field === "data") message.data += `${message.data ? "\n" : ""}${value}`;
|
|
333
|
+
}
|
|
334
|
+
return message.data ? message : null;
|
|
335
|
+
}
|
|
255
336
|
|
|
256
337
|
// src/storage.ts
|
|
257
338
|
import {
|
|
@@ -533,6 +614,18 @@ function saveSession(session) {
|
|
|
533
614
|
} else if (result.type === "plaintext") {
|
|
534
615
|
onDisk.masterKeyExportedB64 = session.masterKeyExportedB64;
|
|
535
616
|
}
|
|
617
|
+
if (session.emailEncryptionKeyB64) {
|
|
618
|
+
const emailKeyResult = storeMasterKey(
|
|
619
|
+
session.emailEncryptionKeyB64,
|
|
620
|
+
`${session.hashedEmail}:email`
|
|
621
|
+
);
|
|
622
|
+
onDisk.emailEncryptionKeyStorage = emailKeyResult.type;
|
|
623
|
+
if (emailKeyResult.type === "encrypted") {
|
|
624
|
+
onDisk.emailEncryptionKeyEncrypted = emailKeyResult.encryptedData;
|
|
625
|
+
} else if (emailKeyResult.type === "plaintext") {
|
|
626
|
+
onDisk.emailEncryptionKeyB64 = session.emailEncryptionKeyB64;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
536
629
|
writeJsonFile(filePath, onDisk);
|
|
537
630
|
if (result.type !== "plaintext") {
|
|
538
631
|
process.stderr.write("Decrypting data...\n");
|
|
@@ -543,17 +636,19 @@ function loadSession() {
|
|
|
543
636
|
const onDisk = readJsonFile(filePath);
|
|
544
637
|
if (!onDisk) return null;
|
|
545
638
|
let masterKey = null;
|
|
639
|
+
let emailEncryptionKey = null;
|
|
546
640
|
if (!onDisk.masterKeyStorage) {
|
|
547
641
|
masterKey = onDisk.masterKeyExportedB64 ?? null;
|
|
548
642
|
if (masterKey) {
|
|
549
|
-
|
|
643
|
+
emailEncryptionKey = getEmailEncryptionKeyFromDisk(onDisk);
|
|
644
|
+
const session = buildSession(onDisk, masterKey, emailEncryptionKey);
|
|
550
645
|
try {
|
|
551
646
|
saveSession(session);
|
|
552
647
|
process.stderr.write("Decrypting data...\n");
|
|
553
648
|
} catch {
|
|
554
649
|
}
|
|
555
650
|
}
|
|
556
|
-
return masterKey ? buildSession(onDisk, masterKey) : null;
|
|
651
|
+
return masterKey ? buildSession(onDisk, masterKey, getEmailEncryptionKeyFromDisk(onDisk)) : null;
|
|
557
652
|
}
|
|
558
653
|
switch (onDisk.masterKeyStorage) {
|
|
559
654
|
case "keychain":
|
|
@@ -577,7 +672,7 @@ function loadSession() {
|
|
|
577
672
|
);
|
|
578
673
|
return null;
|
|
579
674
|
}
|
|
580
|
-
return buildSession(onDisk, masterKey);
|
|
675
|
+
return buildSession(onDisk, masterKey, getEmailEncryptionKeyFromDisk(onDisk));
|
|
581
676
|
}
|
|
582
677
|
function clearSession() {
|
|
583
678
|
const filePath = join(ensureStateDir(), "session.json");
|
|
@@ -585,17 +680,36 @@ function clearSession() {
|
|
|
585
680
|
if (onDisk?.masterKeyStorage) {
|
|
586
681
|
deleteMasterKey(onDisk.masterKeyStorage, onDisk.hashedEmail);
|
|
587
682
|
}
|
|
683
|
+
if (onDisk?.emailEncryptionKeyStorage) {
|
|
684
|
+
deleteMasterKey(onDisk.emailEncryptionKeyStorage, `${onDisk.hashedEmail}:email`);
|
|
685
|
+
}
|
|
588
686
|
if (existsSync2(filePath)) {
|
|
589
687
|
rmSync(filePath);
|
|
590
688
|
}
|
|
591
689
|
}
|
|
592
|
-
function
|
|
690
|
+
function getEmailEncryptionKeyFromDisk(onDisk) {
|
|
691
|
+
if (!onDisk.emailEncryptionKeyStorage) return onDisk.emailEncryptionKeyB64 ?? null;
|
|
692
|
+
switch (onDisk.emailEncryptionKeyStorage) {
|
|
693
|
+
case "keychain":
|
|
694
|
+
return retrieveMasterKey("keychain", `${onDisk.hashedEmail}:email`);
|
|
695
|
+
case "encrypted":
|
|
696
|
+
return retrieveMasterKey(
|
|
697
|
+
"encrypted",
|
|
698
|
+
`${onDisk.hashedEmail}:email`,
|
|
699
|
+
onDisk.emailEncryptionKeyEncrypted
|
|
700
|
+
);
|
|
701
|
+
case "plaintext":
|
|
702
|
+
return onDisk.emailEncryptionKeyB64 ?? null;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function buildSession(onDisk, masterKey, emailEncryptionKey) {
|
|
593
706
|
return {
|
|
594
707
|
apiUrl: onDisk.apiUrl,
|
|
595
708
|
sessionId: onDisk.sessionId,
|
|
596
709
|
wsToken: onDisk.wsToken,
|
|
597
710
|
cookies: onDisk.cookies,
|
|
598
711
|
masterKeyExportedB64: masterKey,
|
|
712
|
+
emailEncryptionKeyB64: emailEncryptionKey,
|
|
599
713
|
hashedEmail: onDisk.hashedEmail,
|
|
600
714
|
userEmailSalt: onDisk.userEmailSalt,
|
|
601
715
|
createdAt: onDisk.createdAt,
|
|
@@ -603,18 +717,6 @@ function buildSession(onDisk, masterKey) {
|
|
|
603
717
|
autoLogoutMinutes: onDisk.autoLogoutMinutes
|
|
604
718
|
};
|
|
605
719
|
}
|
|
606
|
-
function loadIncognitoHistory() {
|
|
607
|
-
const filePath = join(ensureStateDir(), "incognito.json");
|
|
608
|
-
return readJsonFile(filePath) ?? [];
|
|
609
|
-
}
|
|
610
|
-
function saveIncognitoHistory(items) {
|
|
611
|
-
const filePath = join(ensureStateDir(), "incognito.json");
|
|
612
|
-
writeJsonFile(filePath, items);
|
|
613
|
-
}
|
|
614
|
-
function clearIncognitoHistory() {
|
|
615
|
-
const filePath = join(ensureStateDir(), "incognito.json");
|
|
616
|
-
writeJsonFile(filePath, []);
|
|
617
|
-
}
|
|
618
720
|
var SYNC_CACHE_FILE = "sync_cache.json";
|
|
619
721
|
function saveSyncCache(cache) {
|
|
620
722
|
const filePath = join(ensureStateDir(), SYNC_CACHE_FILE);
|
|
@@ -955,6 +1057,7 @@ var CHAT_MODELS = [
|
|
|
955
1057
|
{ id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5" },
|
|
956
1058
|
{ id: "gpt-5.4", name: "GPT-5.4" },
|
|
957
1059
|
{ id: "gpt-oss-120b", name: "GPT-OSS-120b" },
|
|
1060
|
+
{ id: "gpt-oss-20b", name: "GPT-OSS-20b" },
|
|
958
1061
|
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash" },
|
|
959
1062
|
{ id: "gemini-3-pro-image-preview", name: "Gemini 3 Pro" },
|
|
960
1063
|
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro" },
|
|
@@ -1717,7 +1820,11 @@ var BLOCKED_SETTINGS_MUTATE_PATHS = /* @__PURE__ */ new Set([
|
|
|
1717
1820
|
"/v1/auth/setup_password",
|
|
1718
1821
|
"/v1/auth/2fa/setup/initiate",
|
|
1719
1822
|
"/v1/auth/2fa/setup/provider",
|
|
1720
|
-
"/v1/auth/2fa/setup/verify-signup"
|
|
1823
|
+
"/v1/auth/2fa/setup/verify-signup",
|
|
1824
|
+
"/v1/settings/delete-account",
|
|
1825
|
+
"/v1/settings/request-action-verification",
|
|
1826
|
+
"/v1/settings/verify-action-code",
|
|
1827
|
+
"/v1/settings/user/disable-2fa"
|
|
1721
1828
|
]);
|
|
1722
1829
|
var OpenMatesClient = class _OpenMatesClient {
|
|
1723
1830
|
apiUrl;
|
|
@@ -1814,6 +1921,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
1814
1921
|
authorizerDeviceName: complete.data.authorizer_device_name ?? null,
|
|
1815
1922
|
autoLogoutMinutes: complete.data.auto_logout_minutes ?? null
|
|
1816
1923
|
};
|
|
1924
|
+
await this.hydrateEmailEncryptionKey(session);
|
|
1817
1925
|
saveSession(session);
|
|
1818
1926
|
}
|
|
1819
1927
|
async whoAmI() {
|
|
@@ -1833,7 +1941,6 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
1833
1941
|
await this.http.post("/v1/auth/logout", {}, this.getCliRequestHeaders()).catch(() => void 0);
|
|
1834
1942
|
}
|
|
1835
1943
|
clearSession();
|
|
1836
|
-
clearIncognitoHistory();
|
|
1837
1944
|
}
|
|
1838
1945
|
// -------------------------------------------------------------------------
|
|
1839
1946
|
// Chats
|
|
@@ -2374,12 +2481,6 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2374
2481
|
let followUpSuggestions = [];
|
|
2375
2482
|
const streamOpts = { onStream: params.onStream };
|
|
2376
2483
|
if (params.incognito) {
|
|
2377
|
-
const history = loadIncognitoHistory();
|
|
2378
|
-
history.push({
|
|
2379
|
-
role: "user",
|
|
2380
|
-
content: params.message,
|
|
2381
|
-
createdAt: Date.now()
|
|
2382
|
-
});
|
|
2383
2484
|
try {
|
|
2384
2485
|
const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
|
|
2385
2486
|
assistant = resp.content;
|
|
@@ -2388,12 +2489,6 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2388
2489
|
} finally {
|
|
2389
2490
|
ws.close();
|
|
2390
2491
|
}
|
|
2391
|
-
history.push({
|
|
2392
|
-
role: "assistant",
|
|
2393
|
-
content: assistant,
|
|
2394
|
-
createdAt: Date.now()
|
|
2395
|
-
});
|
|
2396
|
-
saveIncognitoHistory(history);
|
|
2397
2492
|
} else {
|
|
2398
2493
|
try {
|
|
2399
2494
|
const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
|
|
@@ -2415,12 +2510,6 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2415
2510
|
followUpSuggestions
|
|
2416
2511
|
};
|
|
2417
2512
|
}
|
|
2418
|
-
getIncognitoHistory() {
|
|
2419
|
-
return loadIncognitoHistory();
|
|
2420
|
-
}
|
|
2421
|
-
clearIncognitoHistory() {
|
|
2422
|
-
clearIncognitoHistory();
|
|
2423
|
-
}
|
|
2424
2513
|
/**
|
|
2425
2514
|
* Delete a chat by ID.
|
|
2426
2515
|
*
|
|
@@ -2733,7 +2822,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2733
2822
|
}
|
|
2734
2823
|
return response.data;
|
|
2735
2824
|
}
|
|
2736
|
-
async settingsDelete(path) {
|
|
2825
|
+
async settingsDelete(path, body) {
|
|
2737
2826
|
this.requireSession();
|
|
2738
2827
|
const normalizedPath = this.normalizePath(path);
|
|
2739
2828
|
if (BLOCKED_SETTINGS_MUTATE_PATHS.has(normalizedPath)) {
|
|
@@ -2741,6 +2830,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2741
2830
|
}
|
|
2742
2831
|
const response = await this.http.delete(
|
|
2743
2832
|
normalizedPath,
|
|
2833
|
+
body,
|
|
2744
2834
|
this.getCliRequestHeaders()
|
|
2745
2835
|
);
|
|
2746
2836
|
if (!response.ok) {
|
|
@@ -2792,6 +2882,149 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2792
2882
|
}
|
|
2793
2883
|
return response.data;
|
|
2794
2884
|
}
|
|
2885
|
+
async listInvoices() {
|
|
2886
|
+
this.requireSession();
|
|
2887
|
+
const response = await this.http.get(
|
|
2888
|
+
"/v1/payments/invoices",
|
|
2889
|
+
this.getCliRequestHeaders()
|
|
2890
|
+
);
|
|
2891
|
+
if (!response.ok) {
|
|
2892
|
+
throw new Error(`Failed to fetch invoices (HTTP ${response.status})`);
|
|
2893
|
+
}
|
|
2894
|
+
return { invoices: response.data.invoices ?? [] };
|
|
2895
|
+
}
|
|
2896
|
+
async downloadInvoice(invoiceId) {
|
|
2897
|
+
return this.downloadPaymentPdf(
|
|
2898
|
+
`/v1/payments/invoices/${encodeURIComponent(invoiceId)}/download`,
|
|
2899
|
+
`Invoice_${invoiceId}.pdf`
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
async downloadCreditNote(invoiceId) {
|
|
2903
|
+
return this.downloadPaymentPdf(
|
|
2904
|
+
`/v1/payments/invoices/${encodeURIComponent(invoiceId)}/credit-note/download`,
|
|
2905
|
+
`CreditNote_${invoiceId}.pdf`
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2908
|
+
async requestRefund(invoiceId) {
|
|
2909
|
+
const session = this.requireSession();
|
|
2910
|
+
const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
|
|
2911
|
+
const response = await this.http.post(
|
|
2912
|
+
"/v1/payments/refund",
|
|
2913
|
+
{ invoice_id: invoiceId, email_encryption_key: emailEncryptionKey },
|
|
2914
|
+
this.getCliRequestHeaders()
|
|
2915
|
+
);
|
|
2916
|
+
if (!response.ok) {
|
|
2917
|
+
throw new Error(`Refund request failed (HTTP ${response.status})`);
|
|
2918
|
+
}
|
|
2919
|
+
return response.data;
|
|
2920
|
+
}
|
|
2921
|
+
async updateUsername(username) {
|
|
2922
|
+
return this.settingsPost("user/username", { username });
|
|
2923
|
+
}
|
|
2924
|
+
async updateProfileImage(filePath) {
|
|
2925
|
+
const { uploadProfileImage } = await import("./uploadService-3CAJXU4L.js");
|
|
2926
|
+
const result = await uploadProfileImage(filePath, this.requireSession());
|
|
2927
|
+
if (result.status === "rejected") {
|
|
2928
|
+
throw new Error(result.detail ?? "Profile image rejected by content safety checks.");
|
|
2929
|
+
}
|
|
2930
|
+
if (result.status === "account_deleted") {
|
|
2931
|
+
throw new Error("Account deleted due to repeated profile image policy violations.");
|
|
2932
|
+
}
|
|
2933
|
+
if (result.status !== "ok") {
|
|
2934
|
+
throw new Error(result.detail ?? `Profile image upload failed with status '${result.status}'.`);
|
|
2935
|
+
}
|
|
2936
|
+
return result;
|
|
2937
|
+
}
|
|
2938
|
+
async getNewsletterCategories() {
|
|
2939
|
+
this.requireSession();
|
|
2940
|
+
const response = await this.http.get(
|
|
2941
|
+
"/v1/newsletter/categories",
|
|
2942
|
+
this.getCliRequestHeaders()
|
|
2943
|
+
);
|
|
2944
|
+
if (!response.ok) {
|
|
2945
|
+
throw new Error(`Failed to fetch newsletter categories (HTTP ${response.status})`);
|
|
2946
|
+
}
|
|
2947
|
+
return response.data;
|
|
2948
|
+
}
|
|
2949
|
+
async updateNewsletterCategories(categories) {
|
|
2950
|
+
this.requireSession();
|
|
2951
|
+
const response = await this.http.patch(
|
|
2952
|
+
"/v1/newsletter/categories",
|
|
2953
|
+
{ categories },
|
|
2954
|
+
this.getCliRequestHeaders()
|
|
2955
|
+
);
|
|
2956
|
+
if (!response.ok) {
|
|
2957
|
+
throw new Error(`Failed to update newsletter categories (HTTP ${response.status})`);
|
|
2958
|
+
}
|
|
2959
|
+
return response.data;
|
|
2960
|
+
}
|
|
2961
|
+
async subscribeNewsletter(email, language = "en", darkmode = false) {
|
|
2962
|
+
const response = await this.http.post(
|
|
2963
|
+
"/v1/newsletter/subscribe",
|
|
2964
|
+
{ email, language, darkmode },
|
|
2965
|
+
this.getCliRequestHeaders()
|
|
2966
|
+
);
|
|
2967
|
+
if (!response.ok) {
|
|
2968
|
+
throw new Error(`Newsletter subscribe failed (HTTP ${response.status})`);
|
|
2969
|
+
}
|
|
2970
|
+
return response.data;
|
|
2971
|
+
}
|
|
2972
|
+
async confirmNewsletter(token) {
|
|
2973
|
+
const response = await this.http.get(
|
|
2974
|
+
`/v1/newsletter/confirm/${encodeURIComponent(token)}`,
|
|
2975
|
+
this.getCliRequestHeaders()
|
|
2976
|
+
);
|
|
2977
|
+
if (!response.ok) {
|
|
2978
|
+
throw new Error(`Newsletter confirmation failed (HTTP ${response.status})`);
|
|
2979
|
+
}
|
|
2980
|
+
return response.data;
|
|
2981
|
+
}
|
|
2982
|
+
async unsubscribeNewsletter(token) {
|
|
2983
|
+
const response = await this.http.get(
|
|
2984
|
+
`/v1/newsletter/unsubscribe/${encodeURIComponent(token)}`,
|
|
2985
|
+
this.getCliRequestHeaders()
|
|
2986
|
+
);
|
|
2987
|
+
if (!response.ok) {
|
|
2988
|
+
throw new Error(`Newsletter unsubscribe failed (HTTP ${response.status})`);
|
|
2989
|
+
}
|
|
2990
|
+
return response.data;
|
|
2991
|
+
}
|
|
2992
|
+
async updateEmailNotificationSettings(payload) {
|
|
2993
|
+
const { ws } = await this.openWsClient();
|
|
2994
|
+
try {
|
|
2995
|
+
const ackPromise = ws.waitForMessage("email_notification_settings_ack");
|
|
2996
|
+
ws.send("email_notification_settings", payload);
|
|
2997
|
+
const ack = await ackPromise;
|
|
2998
|
+
return ack.payload;
|
|
2999
|
+
} finally {
|
|
3000
|
+
ws.close();
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
async listNotifications(limit = 50) {
|
|
3004
|
+
this.requireSession();
|
|
3005
|
+
const response = await this.http.get(
|
|
3006
|
+
`/v1/notifications?limit=${encodeURIComponent(String(limit))}`,
|
|
3007
|
+
this.getCliRequestHeaders()
|
|
3008
|
+
);
|
|
3009
|
+
if (!response.ok) {
|
|
3010
|
+
throw new Error(`Failed to fetch notifications (HTTP ${response.status})`);
|
|
3011
|
+
}
|
|
3012
|
+
return response.data;
|
|
3013
|
+
}
|
|
3014
|
+
async *streamNotifications() {
|
|
3015
|
+
this.requireSession();
|
|
3016
|
+
for await (const message of this.http.streamSse(
|
|
3017
|
+
"/v1/notifications/stream",
|
|
3018
|
+
this.getCliRequestHeaders()
|
|
3019
|
+
)) {
|
|
3020
|
+
if (message.event && message.event !== "notification") continue;
|
|
3021
|
+
try {
|
|
3022
|
+
yield JSON.parse(message.data);
|
|
3023
|
+
} catch {
|
|
3024
|
+
yield { event: message.event ?? "message", data: message.data };
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
2795
3028
|
// -------------------------------------------------------------------------
|
|
2796
3029
|
// Daily Inspirations
|
|
2797
3030
|
// -------------------------------------------------------------------------
|
|
@@ -3256,6 +3489,45 @@ Required: ${schema.required.join(", ")}`
|
|
|
3256
3489
|
if (path.startsWith("/")) return path;
|
|
3257
3490
|
return `/v1/settings/${path}`;
|
|
3258
3491
|
}
|
|
3492
|
+
async downloadPaymentPdf(path, fallbackFilename) {
|
|
3493
|
+
this.requireSession();
|
|
3494
|
+
const response = await this.http.getBinary(path, this.getCliRequestHeaders());
|
|
3495
|
+
if (!response.ok) {
|
|
3496
|
+
throw new Error(`Download failed (HTTP ${response.status})`);
|
|
3497
|
+
}
|
|
3498
|
+
return {
|
|
3499
|
+
filename: filenameFromContentDisposition(response.headers.get("content-disposition")) ?? fallbackFilename,
|
|
3500
|
+
data: response.data
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
async ensureEmailEncryptionKey(session) {
|
|
3504
|
+
if (session.emailEncryptionKeyB64) return session.emailEncryptionKeyB64;
|
|
3505
|
+
await this.hydrateEmailEncryptionKey(session);
|
|
3506
|
+
if (session.emailEncryptionKeyB64) return session.emailEncryptionKeyB64;
|
|
3507
|
+
throw new Error(
|
|
3508
|
+
"Email encryption key is missing. Run `openmates login` again to refresh your local encryption keys."
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3511
|
+
async hydrateEmailEncryptionKey(session) {
|
|
3512
|
+
const response = await this.http.post(
|
|
3513
|
+
"/v1/auth/session",
|
|
3514
|
+
{ session_id: session.sessionId },
|
|
3515
|
+
this.getCliRequestHeaders()
|
|
3516
|
+
);
|
|
3517
|
+
const encryptedEmail = response.data.user?.encrypted_email_with_master_key;
|
|
3518
|
+
if (!response.ok || !response.data.success || typeof encryptedEmail !== "string") {
|
|
3519
|
+
return;
|
|
3520
|
+
}
|
|
3521
|
+
const email = await decryptWithAesGcmCombined(
|
|
3522
|
+
encryptedEmail,
|
|
3523
|
+
base64ToBytes(session.masterKeyExportedB64)
|
|
3524
|
+
);
|
|
3525
|
+
if (!email) return;
|
|
3526
|
+
session.emailEncryptionKeyB64 = await deriveEmailEncryptionKeyB64(
|
|
3527
|
+
email,
|
|
3528
|
+
session.userEmailSalt
|
|
3529
|
+
);
|
|
3530
|
+
}
|
|
3259
3531
|
requireSession() {
|
|
3260
3532
|
if (!this.session) {
|
|
3261
3533
|
throw new Error("Not logged in. Run `openmates login`.");
|
|
@@ -3631,6 +3903,15 @@ function parseNewChatSuggestionText(text) {
|
|
|
3631
3903
|
const skillId = raw.slice(dashIdx + 1);
|
|
3632
3904
|
return { body, appId, skillId };
|
|
3633
3905
|
}
|
|
3906
|
+
function filenameFromContentDisposition(header2) {
|
|
3907
|
+
if (!header2) return null;
|
|
3908
|
+
const encoded = /filename\*=UTF-8''([^;]+)/i.exec(header2)?.[1];
|
|
3909
|
+
if (encoded) return decodeURIComponent(encoded);
|
|
3910
|
+
const quoted = /filename="([^"]+)"/i.exec(header2)?.[1];
|
|
3911
|
+
if (quoted) return quoted;
|
|
3912
|
+
const plain = /filename=([^;]+)/i.exec(header2)?.[1];
|
|
3913
|
+
return plain?.trim() ?? null;
|
|
3914
|
+
}
|
|
3634
3915
|
function sleep(ms) {
|
|
3635
3916
|
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
3636
3917
|
}
|
|
@@ -3920,8 +4201,8 @@ var SecretRegistry = class {
|
|
|
3920
4201
|
const sortedResults = [];
|
|
3921
4202
|
for (const result of results) {
|
|
3922
4203
|
const endIdx = result[0];
|
|
3923
|
-
const
|
|
3924
|
-
for (const match of
|
|
4204
|
+
const matches2 = result[1];
|
|
4205
|
+
for (const match of matches2) {
|
|
3925
4206
|
sortedResults.push({ endIndex: endIdx, match });
|
|
3926
4207
|
}
|
|
3927
4208
|
}
|
|
@@ -4844,84 +5125,6 @@ function formatEmbedsForMessage(embeds) {
|
|
|
4844
5125
|
return "\n" + embeds.map((e) => e.referenceBlock).join("\n");
|
|
4845
5126
|
}
|
|
4846
5127
|
|
|
4847
|
-
// src/uploadService.ts
|
|
4848
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
4849
|
-
import { basename as basename2 } from "path";
|
|
4850
|
-
var UPLOAD_MAX_ATTEMPTS = 3;
|
|
4851
|
-
var UPLOAD_RETRY_DELAY_MS = 2e3;
|
|
4852
|
-
function getUploadUrl(apiUrl) {
|
|
4853
|
-
try {
|
|
4854
|
-
const url = new URL(apiUrl);
|
|
4855
|
-
if (url.hostname === "localhost") return "http://localhost:8001";
|
|
4856
|
-
} catch {
|
|
4857
|
-
}
|
|
4858
|
-
return "https://upload.openmates.org";
|
|
4859
|
-
}
|
|
4860
|
-
async function uploadFile(filePath, session) {
|
|
4861
|
-
const filename = basename2(filePath);
|
|
4862
|
-
const fileBytes = readFileSync4(filePath);
|
|
4863
|
-
const uploadUrl = `${getUploadUrl(session.apiUrl)}/v1/upload/file`;
|
|
4864
|
-
const cookies = [];
|
|
4865
|
-
if (session.cookies?.auth_refresh_token) {
|
|
4866
|
-
cookies.push(`auth_refresh_token=${session.cookies.auth_refresh_token}`);
|
|
4867
|
-
}
|
|
4868
|
-
let response;
|
|
4869
|
-
let lastError;
|
|
4870
|
-
for (let attempt = 1; attempt <= UPLOAD_MAX_ATTEMPTS; attempt++) {
|
|
4871
|
-
try {
|
|
4872
|
-
const blob = new Blob([fileBytes]);
|
|
4873
|
-
const formData = new FormData();
|
|
4874
|
-
formData.append("file", blob, filename);
|
|
4875
|
-
response = await fetch(uploadUrl, {
|
|
4876
|
-
method: "POST",
|
|
4877
|
-
body: formData,
|
|
4878
|
-
headers: {
|
|
4879
|
-
...cookies.length > 0 ? { Cookie: cookies.join("; ") } : {}
|
|
4880
|
-
},
|
|
4881
|
-
signal: AbortSignal.timeout(10 * 60 * 1e3)
|
|
4882
|
-
// 10-minute timeout
|
|
4883
|
-
});
|
|
4884
|
-
break;
|
|
4885
|
-
} catch (error) {
|
|
4886
|
-
lastError = error;
|
|
4887
|
-
if (attempt === UPLOAD_MAX_ATTEMPTS) break;
|
|
4888
|
-
await new Promise((resolve4) => setTimeout(resolve4, UPLOAD_RETRY_DELAY_MS));
|
|
4889
|
-
}
|
|
4890
|
-
}
|
|
4891
|
-
if (!response) {
|
|
4892
|
-
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
4893
|
-
throw new Error(message || "Upload request failed.");
|
|
4894
|
-
}
|
|
4895
|
-
if (!response.ok) {
|
|
4896
|
-
const status = response.status;
|
|
4897
|
-
let errorMessage;
|
|
4898
|
-
switch (status) {
|
|
4899
|
-
case 401:
|
|
4900
|
-
errorMessage = "Authentication failed. Run `openmates login` to re-authenticate.";
|
|
4901
|
-
break;
|
|
4902
|
-
case 413:
|
|
4903
|
-
errorMessage = "File too large (maximum 100 MB).";
|
|
4904
|
-
break;
|
|
4905
|
-
case 415:
|
|
4906
|
-
errorMessage = "Unsupported file type.";
|
|
4907
|
-
break;
|
|
4908
|
-
case 422: {
|
|
4909
|
-
const body = await response.text().catch(() => "");
|
|
4910
|
-
errorMessage = body.includes("malware") ? "File rejected: malware detected." : body.includes("content_safety") ? "File rejected: content safety violation." : `Upload validation failed: ${body}`;
|
|
4911
|
-
break;
|
|
4912
|
-
}
|
|
4913
|
-
case 429:
|
|
4914
|
-
errorMessage = "Upload rate limit exceeded. Try again in a minute.";
|
|
4915
|
-
break;
|
|
4916
|
-
default:
|
|
4917
|
-
errorMessage = `Upload failed (HTTP ${status}).`;
|
|
4918
|
-
}
|
|
4919
|
-
throw new Error(errorMessage);
|
|
4920
|
-
}
|
|
4921
|
-
const data = await response.json();
|
|
4922
|
-
return data;
|
|
4923
|
-
}
|
|
4924
|
-
|
|
4925
5128
|
// src/embedRenderers.ts
|
|
4926
5129
|
var str = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
4927
5130
|
var DIRECT_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -5971,13 +6174,13 @@ function formatTs(ts) {
|
|
|
5971
6174
|
|
|
5972
6175
|
// src/server.ts
|
|
5973
6176
|
import { execSync, spawn as nodeSpawn } from "child_process";
|
|
5974
|
-
import { copyFileSync, existsSync as existsSync5, readFileSync as
|
|
6177
|
+
import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync5, rmSync as rmSync3 } from "fs";
|
|
5975
6178
|
import { createInterface as createInterface2 } from "readline";
|
|
5976
6179
|
import { homedir as homedir5 } from "os";
|
|
5977
6180
|
import { join as join3, resolve as resolve3 } from "path";
|
|
5978
6181
|
|
|
5979
6182
|
// src/serverConfig.ts
|
|
5980
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as
|
|
6183
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
5981
6184
|
import { homedir as homedir4 } from "os";
|
|
5982
6185
|
import { join as join2, resolve as resolve2 } from "path";
|
|
5983
6186
|
var STATE_DIR = join2(homedir4(), ".openmates");
|
|
@@ -5998,7 +6201,7 @@ function loadServerConfig() {
|
|
|
5998
6201
|
const filePath = join2(STATE_DIR, CONFIG_FILE);
|
|
5999
6202
|
if (!existsSync4(filePath)) return null;
|
|
6000
6203
|
try {
|
|
6001
|
-
return JSON.parse(
|
|
6204
|
+
return JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
6002
6205
|
} catch {
|
|
6003
6206
|
return null;
|
|
6004
6207
|
}
|
|
@@ -6076,7 +6279,7 @@ function requireGit() {
|
|
|
6076
6279
|
}
|
|
6077
6280
|
function hasLlmCredentials(envPath) {
|
|
6078
6281
|
if (!existsSync5(envPath)) return false;
|
|
6079
|
-
const content =
|
|
6282
|
+
const content = readFileSync5(envPath, "utf-8");
|
|
6080
6283
|
for (const line of content.split("\n")) {
|
|
6081
6284
|
const trimmed = line.trim();
|
|
6082
6285
|
if (trimmed.startsWith("#") || !trimmed) continue;
|
|
@@ -6807,17 +7010,11 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
6807
7010
|
return;
|
|
6808
7011
|
}
|
|
6809
7012
|
if (subcommand === "incognito-history") {
|
|
6810
|
-
|
|
6811
|
-
if (flags.json === true) {
|
|
6812
|
-
printJson2(history);
|
|
6813
|
-
} else {
|
|
6814
|
-
printIncognitoHistory(history);
|
|
6815
|
-
}
|
|
7013
|
+
printIncognitoNoHistoryNotice(flags.json === true);
|
|
6816
7014
|
return;
|
|
6817
7015
|
}
|
|
6818
7016
|
if (subcommand === "incognito-clear") {
|
|
6819
|
-
|
|
6820
|
-
console.log("Incognito history cleared.");
|
|
7017
|
+
printIncognitoNoHistoryNotice(flags.json === true);
|
|
6821
7018
|
return;
|
|
6822
7019
|
}
|
|
6823
7020
|
if (subcommand === "show") {
|
|
@@ -7685,125 +7882,675 @@ async function handleEmbeds(client, subcommand, rest, flags) {
|
|
|
7685
7882
|
printEmbedsHelp();
|
|
7686
7883
|
process.exit(1);
|
|
7687
7884
|
}
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7885
|
+
var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
7886
|
+
{ path: ["account", "info"], description: "Show account info", examples: ["openmates settings account info --json"] },
|
|
7887
|
+
{ path: ["account", "timezone", "set"], description: "Set account timezone", examples: ["openmates settings account timezone set Europe/Berlin"] },
|
|
7888
|
+
{ path: ["account", "export", "manifest"], description: "Show account export manifest", examples: ["openmates settings account export manifest --json"] },
|
|
7889
|
+
{ path: ["account", "export", "data"], description: "Fetch account export data", examples: ["openmates settings account export data --json"] },
|
|
7890
|
+
{ path: ["account", "import-chat"], description: "Import a CLI chat export file", examples: ["openmates settings account import-chat ./chat.yml", "openmates settings account import-chat ./payload.json"] },
|
|
7891
|
+
{ path: ["account", "username", "set"], description: "Change account username", examples: ["openmates settings account username set alice_123"] },
|
|
7892
|
+
{ path: ["account", "profile-picture", "set"], description: "Upload a profile picture", examples: ["openmates settings account profile-picture set ./avatar.jpg"] },
|
|
7893
|
+
{ path: ["account", "chats", "stats"], description: "Show chat statistics", examples: ["openmates settings account chats stats"] },
|
|
7894
|
+
{ path: ["account", "delete", "preview"], description: "Preview account deletion impact", examples: ["openmates settings account delete preview"] },
|
|
7895
|
+
{ path: ["account", "storage", "overview"], description: "Show storage overview", examples: ["openmates settings account storage overview"] },
|
|
7896
|
+
{ path: ["account", "storage", "files"], description: "List stored files", examples: ["openmates settings account storage files --category images"] },
|
|
7897
|
+
{ path: ["account", "storage", "delete"], description: "Delete one stored file by file ID", examples: ["openmates settings account storage delete <file-id> --yes"] },
|
|
7898
|
+
{ path: ["interface", "language", "set"], description: "Set interface language", examples: ["openmates settings interface language set en"] },
|
|
7899
|
+
{ path: ["interface", "dark-mode", "set"], description: "Set dark mode on or off", examples: ["openmates settings interface dark-mode set on"] },
|
|
7900
|
+
{ path: ["interface", "font", "set"], description: "Set interface font", examples: ["openmates settings interface font set lexend"] },
|
|
7901
|
+
{ path: ["ai", "models", "set-defaults"], description: "Set default AI models", examples: ["openmates settings ai models set-defaults --simple gpt-5.4 --complex claude-opus-4-7"] },
|
|
7902
|
+
{ path: ["privacy", "auto-delete", "chats", "set"], description: "Set chat auto-deletion period", examples: ["openmates settings privacy auto-delete chats set 90d"] },
|
|
7903
|
+
{ path: ["privacy", "debug-logs", "share"], description: "Create a debug log sharing session", examples: ["openmates settings privacy debug-logs share --duration 1h --confirm"] },
|
|
7904
|
+
{ path: ["billing", "overview"], description: "Show billing overview", examples: ["openmates settings billing overview"] },
|
|
7905
|
+
{ path: ["billing", "usage"], description: "Show usage history", examples: ["openmates settings billing usage --json"] },
|
|
7906
|
+
{ path: ["billing", "usage", "summaries"], description: "Show usage summaries", examples: ["openmates settings billing usage summaries"] },
|
|
7907
|
+
{ path: ["billing", "usage", "daily"], description: "Show daily usage overview", examples: ["openmates settings billing usage daily"] },
|
|
7908
|
+
{ path: ["billing", "usage", "export"], description: "Export usage data", examples: ["openmates settings billing usage export --json"] },
|
|
7909
|
+
{ path: ["billing", "invoices", "list"], description: "List invoices", examples: ["openmates settings billing invoices list --json"] },
|
|
7910
|
+
{ path: ["billing", "invoices", "download"], description: "Download an invoice PDF", examples: ["openmates settings billing invoices download <invoice-id> --output ./invoices"] },
|
|
7911
|
+
{ path: ["billing", "invoices", "credit-note"], description: "Download a credit note PDF", examples: ["openmates settings billing invoices credit-note <invoice-id> --output ./invoices"] },
|
|
7912
|
+
{ path: ["billing", "invoices", "refund"], description: "Request a refund for an invoice", examples: ["openmates settings billing invoices refund <invoice-id> --yes"] },
|
|
7913
|
+
{ path: ["billing", "gift-card", "redeem"], description: "Redeem a gift card", examples: ["openmates settings billing gift-card redeem ABCD-1234"] },
|
|
7914
|
+
{ path: ["billing", "gift-card", "list"], description: "List redeemed gift cards", examples: ["openmates settings billing gift-card list"] },
|
|
7915
|
+
{ 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"] },
|
|
7916
|
+
{ path: ["notifications", "status"], description: "Show notification settings", examples: ["openmates settings notifications status --json"] },
|
|
7917
|
+
{ path: ["notifications", "list"], description: "List recent notification events", examples: ["openmates settings notifications list --limit 20 --json"] },
|
|
7918
|
+
{ path: ["notifications", "stream"], description: "Stream notification events with SSE", examples: ["openmates settings notifications stream", "openmates settings notifications stream --count 1 --json"] },
|
|
7919
|
+
{ path: ["notifications", "email", "set"], description: "Configure email notifications", examples: ["openmates settings notifications email set --enabled true --email you@example.com --ai-responses true --backup-reminder true --webhook-chats true"] },
|
|
7920
|
+
{ path: ["notifications", "backup", "set"], description: "Configure backup reminder emails", examples: ["openmates settings notifications backup set --enabled true --interval 30 --email you@example.com"] },
|
|
7921
|
+
{ path: ["reminders", "list"], description: "List active reminders", examples: ["openmates settings reminders list"] },
|
|
7922
|
+
{ path: ["reminders", "update"], description: "Update a reminder", examples: ["openmates settings reminders update <id> --enabled false"] },
|
|
7923
|
+
{ path: ["reminders", "delete"], description: "Delete a reminder", examples: ["openmates settings reminders delete <id> --yes"] },
|
|
7924
|
+
{ path: ["developers", "api-keys", "list"], description: "List API keys", examples: ["openmates settings developers api-keys list"] },
|
|
7925
|
+
{ path: ["developers", "api-keys", "revoke"], description: "Revoke an API key", examples: ["openmates settings developers api-keys revoke <key-id> --yes"] },
|
|
7926
|
+
{ path: ["report-issue", "create"], description: "Report an issue", examples: ['openmates settings report-issue create --title "Bug" --body "What happened"'] },
|
|
7927
|
+
{ path: ["report-issue", "status"], description: "Show issue status", examples: ["openmates settings report-issue status <issue-id>"] },
|
|
7928
|
+
{ path: ["mates", "list"], description: "List available mates", examples: ["openmates settings mates list"] },
|
|
7929
|
+
{ path: ["mates", "info"], description: "Show mate details", examples: ["openmates settings mates info software_development"] },
|
|
7930
|
+
{ path: ["mates", "consent"], description: "Record mate settings consent", examples: ["openmates settings mates consent --yes"] },
|
|
7931
|
+
{ path: ["newsletter", "categories"], description: "Show newsletter category preferences", examples: ["openmates settings newsletter categories"] },
|
|
7932
|
+
{ path: ["newsletter", "categories", "set"], description: "Set newsletter category preferences", examples: ["openmates settings newsletter categories set --updates true --tips true --daily false"] },
|
|
7933
|
+
{ path: ["newsletter", "subscribe"], description: "Subscribe an email to the newsletter", examples: ["openmates settings newsletter subscribe you@example.com --language en"] },
|
|
7934
|
+
{ path: ["newsletter", "confirm"], description: "Confirm newsletter subscription token", examples: ["openmates settings newsletter confirm <token>"] },
|
|
7935
|
+
{ path: ["newsletter", "unsubscribe"], description: "Unsubscribe with newsletter token", examples: ["openmates settings newsletter unsubscribe <token>"] },
|
|
7936
|
+
{ path: ["memories"], description: "Manage encrypted memories", examples: ["openmates settings memories list", `openmates settings memories create --app-id code --item-type projects --data '{"name":"OpenMates"}'`] }
|
|
7937
|
+
];
|
|
7938
|
+
var SETTINGS_INFO_COMMANDS = [
|
|
7939
|
+
{ 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"] },
|
|
7940
|
+
{ 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"] },
|
|
7941
|
+
{ 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"] },
|
|
7942
|
+
{ path: ["security", "passkeys"], description: "Passkeys are web-only", webPath: "account/security/passkeys", reason: "Passkeys require WebAuthn browser APIs.", examples: ["openmates settings security passkeys"] },
|
|
7943
|
+
{ 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"] },
|
|
7944
|
+
{ path: ["security", "2fa"], description: "2FA setup and changes are web-only", webPath: "account/security/2fa", reason: "2FA setup requires a guided browser verification flow.", examples: ["openmates settings security 2fa"] },
|
|
7945
|
+
{ path: ["security", "recovery-key"], description: "Recovery key settings are web-only", webPath: "account/security/recovery-key", reason: "Recovery keys are a high-risk account recovery surface.", examples: ["openmates settings security recovery-key"] },
|
|
7946
|
+
{ path: ["security", "sessions"], description: "Session management is web-only", webPath: "account/security/sessions", reason: "The CLI is a paired restricted session; approval and revocation stay in the browser.", examples: ["openmates settings security sessions"] },
|
|
7947
|
+
{ path: ["billing", "buy-credits"], description: "Credit purchase is web-only", webPath: "billing/buy-credits", reason: "Payment checkout must use the browser/payment provider UI.", examples: ["openmates settings billing buy-credits"] },
|
|
7948
|
+
{ path: ["billing", "gift-card", "buy"], description: "Gift card purchase is web-only", webPath: "billing/gift-cards/buy", reason: "Payment checkout must use the browser/payment provider UI.", examples: ["openmates settings billing gift-card buy"] },
|
|
7949
|
+
{ path: ["billing", "auto-topup", "monthly"], description: "Monthly auto top-up is web-only for now", webPath: "billing/auto-topup/monthly", reason: "Recurring payment setup needs a payment-flow audit before CLI support.", examples: ["openmates settings billing auto-topup monthly"] },
|
|
7950
|
+
{ path: ["privacy", "personal-data"], description: "Personal data management is not CLI-ready yet", webPath: "privacy/hide-personal-data", reason: "The CLI needs a dedicated encrypted personal-data UX before exposing writes.", examples: ["openmates settings privacy personal-data"] },
|
|
7951
|
+
{ path: ["shared", "tip"], description: "Tips are web-only", webPath: "shared/tip", reason: "Payment checkout must use the browser/payment provider UI.", examples: ["openmates settings shared tip"] },
|
|
7952
|
+
{ path: ["developers", "api-keys", "create"], description: "API key creation is web-only", webPath: "developers/api-keys", reason: "API key secrets are shown once and need the browser approval flow.", examples: ["openmates settings developers api-keys create"] },
|
|
7953
|
+
{ path: ["developers", "devices"], description: "Developer devices are web-only", webPath: "developers/devices", reason: "Device approvals and revocations are sensitive.", examples: ["openmates settings developers devices"] },
|
|
7954
|
+
{ path: ["developers", "webhooks"], description: "Developer webhooks are not CLI-ready yet", webPath: "developers/webhooks", reason: "Webhook CRUD needs a backend/API audit before CLI support.", examples: ["openmates settings developers webhooks"] },
|
|
7955
|
+
{ path: ["support"], description: "Support payments are web-only", webPath: "support", reason: "Payment flows must use the browser/payment provider UI.", examples: ["openmates settings support"] },
|
|
7956
|
+
{ path: ["incognito", "info"], description: "Explain incognito mode", reason: "Incognito chats are sent without saving chat history. The CLI stores no incognito transcript.", examples: ['openmates chats incognito "Private question"'] },
|
|
7957
|
+
{ path: ["server"], description: "Server admin settings are web/admin-only", webPath: "server", reason: "Use `openmates server --help` for self-hosted terminal server management.", examples: ["openmates server status"] }
|
|
7958
|
+
];
|
|
7959
|
+
function matches(actual, expected) {
|
|
7960
|
+
return expected.every((part, index) => actual[index] === part);
|
|
7961
|
+
}
|
|
7962
|
+
function findSettingsInfoCommand(tokens) {
|
|
7963
|
+
const all = [...SETTINGS_INFO_COMMANDS, ...SETTINGS_EXECUTABLE_COMMANDS];
|
|
7964
|
+
return all.sort((a, b) => b.path.length - a.path.length).find((command) => matches(tokens, command.path)) ?? null;
|
|
7965
|
+
}
|
|
7966
|
+
async function printSettingsResult(resultPromise, flags) {
|
|
7967
|
+
const result = await resultPromise;
|
|
7968
|
+
flags.json === true ? printJson2(result) : printGenericObject(result);
|
|
7969
|
+
}
|
|
7970
|
+
async function printSettingsMutationResult(resultPromise, flags) {
|
|
7971
|
+
const result = await resultPromise;
|
|
7972
|
+
if (flags.json === true) {
|
|
7973
|
+
printJson2(result);
|
|
7691
7974
|
return;
|
|
7692
7975
|
}
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7976
|
+
process.stdout.write("\x1B[32m\u2713\x1B[0m Settings updated\n");
|
|
7977
|
+
if (result && typeof result === "object") printGenericObject(result);
|
|
7978
|
+
}
|
|
7979
|
+
function addQueryParam(params, key, value) {
|
|
7980
|
+
if (typeof value === "string" && value.length > 0) params.set(key, value);
|
|
7981
|
+
}
|
|
7982
|
+
function parseOnOff(value, label) {
|
|
7983
|
+
if (value === "on" || value === "true" || value === "1") return true;
|
|
7984
|
+
if (value === "off" || value === "false" || value === "0") return false;
|
|
7985
|
+
throw new Error(`Invalid ${label} value '${value ?? ""}'. Use on/off or true/false.`);
|
|
7986
|
+
}
|
|
7987
|
+
function parseRequiredNumber(value, flag) {
|
|
7988
|
+
if (typeof value !== "string") throw new Error(`Missing ${flag}.`);
|
|
7989
|
+
const parsed = Number(value);
|
|
7990
|
+
if (!Number.isFinite(parsed)) throw new Error(`Invalid ${flag}: ${value}`);
|
|
7991
|
+
return parsed;
|
|
7992
|
+
}
|
|
7993
|
+
function parseOptionalNumber(value, fallback, flag) {
|
|
7994
|
+
if (value === void 0) return fallback;
|
|
7995
|
+
return parseRequiredNumber(value, flag);
|
|
7996
|
+
}
|
|
7997
|
+
function parseOptionalBoolean(value, fallback, label) {
|
|
7998
|
+
if (value === void 0) return fallback;
|
|
7999
|
+
if (typeof value === "boolean") return value;
|
|
8000
|
+
return parseOnOff(value, label);
|
|
8001
|
+
}
|
|
8002
|
+
function parseDataOrFlags(flags, booleanFlags) {
|
|
8003
|
+
if (typeof flags.data === "string") return JSON.parse(flags.data);
|
|
8004
|
+
const body = {};
|
|
8005
|
+
for (const key of booleanFlags) {
|
|
8006
|
+
if (flags[key] !== void 0) body[key] = parseOnOff(String(flags[key]), key);
|
|
8007
|
+
}
|
|
8008
|
+
if (Object.keys(body).length === 0) throw new Error("Provide --data '<json>' or a supported flag.");
|
|
8009
|
+
return body;
|
|
8010
|
+
}
|
|
8011
|
+
function parseChatImportPayload(raw) {
|
|
8012
|
+
const trimmed = raw.trim();
|
|
8013
|
+
if (!trimmed) throw new Error("Import file is empty.");
|
|
8014
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
8015
|
+
const parsed = JSON.parse(trimmed);
|
|
8016
|
+
if (Array.isArray(parsed)) return { chats: parsed };
|
|
8017
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.chats)) {
|
|
8018
|
+
return parsed;
|
|
8019
|
+
}
|
|
8020
|
+
if (parsed && typeof parsed === "object") {
|
|
8021
|
+
const object = parsed;
|
|
8022
|
+
if (object.chat || object.messages) return { chats: [normalizeImportedChat(object)] };
|
|
8023
|
+
}
|
|
8024
|
+
throw new Error("JSON import must contain a chats array, a chat object, or messages.");
|
|
8025
|
+
}
|
|
8026
|
+
return { chats: [parseCliExportYaml(trimmed)] };
|
|
8027
|
+
}
|
|
8028
|
+
function normalizeImportedChat(source) {
|
|
8029
|
+
const chat = source.chat && typeof source.chat === "object" ? source.chat : source;
|
|
8030
|
+
const messages = Array.isArray(source.messages) ? source.messages : [];
|
|
8031
|
+
return {
|
|
8032
|
+
title: chat.title ?? null,
|
|
8033
|
+
draft: chat.draft ?? null,
|
|
8034
|
+
summary: chat.summary ?? null,
|
|
8035
|
+
messages: messages.map((message) => normalizeImportedMessage(message))
|
|
8036
|
+
};
|
|
8037
|
+
}
|
|
8038
|
+
function normalizeImportedMessage(message) {
|
|
8039
|
+
return {
|
|
8040
|
+
role: message.role,
|
|
8041
|
+
content: message.content,
|
|
8042
|
+
completed_at: message.completed_at ?? message.timestamp ?? null,
|
|
8043
|
+
assistant_category: message.assistant_category ?? null,
|
|
8044
|
+
thinking: message.thinking ?? null,
|
|
8045
|
+
has_thinking: message.has_thinking ?? null,
|
|
8046
|
+
thinking_tokens: message.thinking_tokens ?? null
|
|
8047
|
+
};
|
|
8048
|
+
}
|
|
8049
|
+
function parseCliExportYaml(raw) {
|
|
8050
|
+
const chat = {};
|
|
8051
|
+
const messages = [];
|
|
8052
|
+
const lines = raw.split("\n");
|
|
8053
|
+
let section = null;
|
|
8054
|
+
let currentMessage = null;
|
|
8055
|
+
const multilineState = { current: null };
|
|
8056
|
+
const setValue = (target, key, value, indent) => {
|
|
8057
|
+
if (value === "|") {
|
|
8058
|
+
target[key] = "";
|
|
8059
|
+
multilineState.current = { target, key, indent: indent + 2 };
|
|
8060
|
+
return;
|
|
7699
8061
|
}
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
8062
|
+
target[key] = parseYamlScalar(value);
|
|
8063
|
+
};
|
|
8064
|
+
for (const line of lines) {
|
|
8065
|
+
const indent = line.match(/^ */)?.[0].length ?? 0;
|
|
8066
|
+
const trimmed = line.trimEnd();
|
|
8067
|
+
if (!trimmed.trim()) continue;
|
|
8068
|
+
if (multilineState.current) {
|
|
8069
|
+
if (indent >= multilineState.current.indent) {
|
|
8070
|
+
const previous = String(multilineState.current.target[multilineState.current.key] ?? "");
|
|
8071
|
+
const nextLine = line.slice(multilineState.current.indent);
|
|
8072
|
+
multilineState.current.target[multilineState.current.key] = previous ? `${previous}
|
|
8073
|
+
${nextLine}` : nextLine;
|
|
8074
|
+
continue;
|
|
8075
|
+
}
|
|
8076
|
+
multilineState.current = null;
|
|
7705
8077
|
}
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
if (!path) {
|
|
7711
|
-
console.error("Missing path.\n");
|
|
7712
|
-
printSettingsHelp();
|
|
7713
|
-
process.exit(1);
|
|
8078
|
+
if (trimmed === "chat:") {
|
|
8079
|
+
section = "chat";
|
|
8080
|
+
currentMessage = null;
|
|
8081
|
+
continue;
|
|
7714
8082
|
}
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
printJson2(result);
|
|
7720
|
-
} else {
|
|
7721
|
-
printGenericObject(result);
|
|
8083
|
+
if (trimmed === "messages:") {
|
|
8084
|
+
section = "messages";
|
|
8085
|
+
currentMessage = null;
|
|
8086
|
+
continue;
|
|
7722
8087
|
}
|
|
8088
|
+
if (section === "messages" && trimmed.trim() === "-") {
|
|
8089
|
+
currentMessage = {};
|
|
8090
|
+
messages.push(currentMessage);
|
|
8091
|
+
continue;
|
|
8092
|
+
}
|
|
8093
|
+
const match = /^\s*([\w-]+):\s*(.*)$/.exec(line);
|
|
8094
|
+
if (!match) continue;
|
|
8095
|
+
const [, key, value] = match;
|
|
8096
|
+
if (section === "chat") setValue(chat, key, value, indent);
|
|
8097
|
+
if (section === "messages" && currentMessage) setValue(currentMessage, key, value, indent);
|
|
8098
|
+
}
|
|
8099
|
+
if (messages.length === 0) throw new Error("Import YAML did not contain any messages.");
|
|
8100
|
+
return normalizeImportedChat({ chat, messages });
|
|
8101
|
+
}
|
|
8102
|
+
function parseYamlScalar(value) {
|
|
8103
|
+
const trimmed = value.trim();
|
|
8104
|
+
if (trimmed === "null") return null;
|
|
8105
|
+
if (trimmed === "true") return true;
|
|
8106
|
+
if (trimmed === "false") return false;
|
|
8107
|
+
if (trimmed !== "" && Number.isFinite(Number(trimmed))) return Number(trimmed);
|
|
8108
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
8109
|
+
return trimmed.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
8110
|
+
}
|
|
8111
|
+
return trimmed;
|
|
8112
|
+
}
|
|
8113
|
+
async function saveDownloadedDocument(document, output) {
|
|
8114
|
+
const { mkdir, writeFile } = await import("fs/promises");
|
|
8115
|
+
const { join: join4, basename: basename2, dirname } = await import("path");
|
|
8116
|
+
const target = typeof output === "string" ? output : ".";
|
|
8117
|
+
const filename = basename2(document.filename || "document.pdf");
|
|
8118
|
+
const filePath = target.endsWith(".pdf") ? target : join4(target, filename);
|
|
8119
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
8120
|
+
await writeFile(filePath, document.data);
|
|
8121
|
+
return filePath;
|
|
8122
|
+
}
|
|
8123
|
+
function printMates(json) {
|
|
8124
|
+
const mates = Object.entries(MATE_NAMES).map(([id, name]) => ({ id, name, mention: `@mate:${id}` }));
|
|
8125
|
+
if (json) {
|
|
8126
|
+
printJson2(mates);
|
|
7723
8127
|
return;
|
|
7724
8128
|
}
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
printJson2(result);
|
|
7735
|
-
} else {
|
|
7736
|
-
printGenericObject(result);
|
|
7737
|
-
}
|
|
8129
|
+
header("Mates");
|
|
8130
|
+
for (const mate of mates) console.log(`${mate.id.padEnd(28)} ${mate.name.padEnd(10)} ${mate.mention}`);
|
|
8131
|
+
}
|
|
8132
|
+
function printMateInfo(mateId, json) {
|
|
8133
|
+
const name = MATE_NAMES[mateId];
|
|
8134
|
+
if (!name) throw new Error(`Unknown mate '${mateId}'. Run 'openmates settings mates list'.`);
|
|
8135
|
+
const data = { id: mateId, name, mention: `@mate:${mateId}` };
|
|
8136
|
+
if (json) {
|
|
8137
|
+
printJson2(data);
|
|
7738
8138
|
return;
|
|
7739
8139
|
}
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
8140
|
+
header(`${name} (${mateId})`);
|
|
8141
|
+
console.log(`Mention: ${data.mention}`);
|
|
8142
|
+
console.log(`Use: openmates chats send "${data.mention} <message>"`);
|
|
8143
|
+
}
|
|
8144
|
+
async function confirmOrExit(question) {
|
|
8145
|
+
const rl = await import("readline");
|
|
8146
|
+
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
8147
|
+
const answer = await new Promise((resolve4) => iface.question(question, resolve4));
|
|
8148
|
+
iface.close();
|
|
8149
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
8150
|
+
console.log("Aborted.");
|
|
8151
|
+
process.exit(0);
|
|
8152
|
+
}
|
|
8153
|
+
}
|
|
8154
|
+
function printSettingsInfoCommand(client, command, json) {
|
|
8155
|
+
const appUrl = deriveAppUrl(client.apiUrl);
|
|
8156
|
+
const webUrl = command.webPath ? `${appUrl}/#settings/${command.webPath}` : null;
|
|
8157
|
+
if (json) {
|
|
8158
|
+
printJson2({
|
|
8159
|
+
command: `openmates settings ${command.path.join(" ")}`,
|
|
8160
|
+
supported_in_cli: false,
|
|
8161
|
+
description: command.description,
|
|
8162
|
+
reason: command.reason ?? null,
|
|
8163
|
+
web_url: webUrl,
|
|
8164
|
+
examples: command.examples
|
|
8165
|
+
});
|
|
8166
|
+
return;
|
|
8167
|
+
}
|
|
8168
|
+
header(command.description);
|
|
8169
|
+
if (command.reason) console.log(command.reason);
|
|
8170
|
+
if (webUrl) console.log(`
|
|
8171
|
+
Open in web app:
|
|
8172
|
+
${webUrl}`);
|
|
8173
|
+
if (command.examples.length > 0) {
|
|
8174
|
+
console.log("\nExamples:");
|
|
8175
|
+
for (const example of command.examples) console.log(` ${example}`);
|
|
8176
|
+
}
|
|
8177
|
+
}
|
|
8178
|
+
async function handleSettings(client, subcommand, rest, flags) {
|
|
8179
|
+
if (!subcommand || subcommand === "help") {
|
|
8180
|
+
printSettingsHelp(client, subcommand ? [] : void 0);
|
|
8181
|
+
return;
|
|
8182
|
+
}
|
|
8183
|
+
const tokens = [subcommand, ...rest].filter((token) => token !== "help");
|
|
8184
|
+
if (rest.includes("help") || Boolean(flags.help)) {
|
|
8185
|
+
printSettingsHelp(client, tokens);
|
|
8186
|
+
return;
|
|
8187
|
+
}
|
|
8188
|
+
if (["get", "post", "patch", "delete"].includes(subcommand)) {
|
|
8189
|
+
console.error(
|
|
8190
|
+
"Raw settings passthrough is no longer supported. Use a predefined settings command.\n"
|
|
8191
|
+
);
|
|
8192
|
+
printSettingsHelp(client);
|
|
8193
|
+
process.exit(1);
|
|
8194
|
+
}
|
|
8195
|
+
if (matches(tokens, ["account", "info"])) {
|
|
8196
|
+
const user = await client.whoAmI();
|
|
8197
|
+
flags.json === true ? printJson2(user) : printWhoAmI(user);
|
|
8198
|
+
return;
|
|
8199
|
+
}
|
|
8200
|
+
if (matches(tokens, ["account", "timezone", "set"])) {
|
|
8201
|
+
const timezone = rest[2];
|
|
8202
|
+
if (!timezone) throw new Error("Missing timezone. Example: openmates settings account timezone set Europe/Berlin");
|
|
8203
|
+
await printSettingsMutationResult(
|
|
8204
|
+
client.settingsPost("user/timezone", { timezone }),
|
|
8205
|
+
flags
|
|
8206
|
+
);
|
|
8207
|
+
return;
|
|
8208
|
+
}
|
|
8209
|
+
if (matches(tokens, ["account", "export", "manifest"])) {
|
|
8210
|
+
await printSettingsResult(client.settingsGet("export-account-manifest"), flags);
|
|
8211
|
+
return;
|
|
8212
|
+
}
|
|
8213
|
+
if (matches(tokens, ["account", "export", "data"])) {
|
|
8214
|
+
await printSettingsResult(client.settingsGet("export-account-data"), flags);
|
|
8215
|
+
return;
|
|
8216
|
+
}
|
|
8217
|
+
if (matches(tokens, ["account", "import-chat"])) {
|
|
8218
|
+
const file = rest[1];
|
|
8219
|
+
if (!file) throw new Error("Missing import file. Example: openmates settings account import-chat ./chat.yml");
|
|
8220
|
+
const { readFile } = await import("fs/promises");
|
|
8221
|
+
const content = await readFile(file, "utf-8");
|
|
8222
|
+
await printSettingsMutationResult(
|
|
8223
|
+
client.settingsPost("import-chat", parseChatImportPayload(content)),
|
|
8224
|
+
flags
|
|
8225
|
+
);
|
|
8226
|
+
return;
|
|
8227
|
+
}
|
|
8228
|
+
if (matches(tokens, ["account", "username", "set"])) {
|
|
8229
|
+
const username = rest[2];
|
|
8230
|
+
if (!username) throw new Error("Missing username. Example: openmates settings account username set alice_123");
|
|
8231
|
+
await printSettingsMutationResult(client.updateUsername(username), flags);
|
|
8232
|
+
return;
|
|
8233
|
+
}
|
|
8234
|
+
if (matches(tokens, ["account", "profile-picture", "set"])) {
|
|
8235
|
+
const file = rest[2];
|
|
8236
|
+
if (!file) throw new Error("Missing image file. Example: openmates settings account profile-picture set ./avatar.jpg");
|
|
8237
|
+
await printSettingsMutationResult(client.updateProfileImage(file), flags);
|
|
8238
|
+
return;
|
|
8239
|
+
}
|
|
8240
|
+
if (matches(tokens, ["account", "chats", "stats"])) {
|
|
8241
|
+
await printSettingsResult(client.settingsGet("chats"), flags);
|
|
8242
|
+
return;
|
|
8243
|
+
}
|
|
8244
|
+
if (matches(tokens, ["account", "delete", "preview"])) {
|
|
8245
|
+
await printSettingsResult(client.settingsGet("delete-account-preview"), flags);
|
|
8246
|
+
return;
|
|
8247
|
+
}
|
|
8248
|
+
if (matches(tokens, ["account", "storage", "overview"])) {
|
|
8249
|
+
await printSettingsResult(client.settingsGet("storage"), flags);
|
|
8250
|
+
return;
|
|
8251
|
+
}
|
|
8252
|
+
if (matches(tokens, ["account", "storage", "files"])) {
|
|
8253
|
+
const params = new URLSearchParams();
|
|
8254
|
+
addQueryParam(params, "category", flags.category ?? flags.type);
|
|
8255
|
+
const query = params.toString();
|
|
8256
|
+
await printSettingsResult(client.settingsGet(`storage/files${query ? `?${query}` : ""}`), flags);
|
|
8257
|
+
return;
|
|
8258
|
+
}
|
|
8259
|
+
if (matches(tokens, ["account", "storage", "delete"])) {
|
|
8260
|
+
const fileId = rest[3];
|
|
8261
|
+
const category = typeof flags.category === "string" ? flags.category : void 0;
|
|
8262
|
+
const scope = flags.all === true ? "all" : category ? "category" : "single";
|
|
8263
|
+
if (scope === "single" && !fileId) throw new Error("Missing file ID.");
|
|
8264
|
+
if (flags.yes !== true) await confirmOrExit(`Delete stored file data (${scope})? This cannot be undone. [y/N] `);
|
|
8265
|
+
await printSettingsMutationResult(
|
|
8266
|
+
client.settingsDelete("storage/files", { scope, file_id: fileId, category }),
|
|
8267
|
+
flags
|
|
8268
|
+
);
|
|
8269
|
+
return;
|
|
8270
|
+
}
|
|
8271
|
+
if (matches(tokens, ["interface", "language", "set"])) {
|
|
8272
|
+
const language = rest[2];
|
|
8273
|
+
if (!language) throw new Error("Missing language code. Example: openmates settings interface language set en");
|
|
8274
|
+
await printSettingsMutationResult(client.settingsPost("user/language", { language }), flags);
|
|
8275
|
+
return;
|
|
8276
|
+
}
|
|
8277
|
+
if (matches(tokens, ["interface", "dark-mode", "set"])) {
|
|
8278
|
+
const value = parseOnOff(rest[2], "dark mode");
|
|
8279
|
+
await printSettingsMutationResult(client.settingsPost("user/darkmode", { dark_mode: value }), flags);
|
|
8280
|
+
return;
|
|
8281
|
+
}
|
|
8282
|
+
if (matches(tokens, ["interface", "font", "set"])) {
|
|
8283
|
+
const font = rest[2];
|
|
8284
|
+
if (!font) throw new Error("Missing font. Example: openmates settings interface font set lexend");
|
|
8285
|
+
await printSettingsMutationResult(client.settingsPost("user/ui-font", { ui_font: font }), flags);
|
|
8286
|
+
return;
|
|
8287
|
+
}
|
|
8288
|
+
if (matches(tokens, ["ai", "models", "set-defaults"])) {
|
|
8289
|
+
const simple = typeof flags.simple === "string" ? flags.simple : void 0;
|
|
8290
|
+
const complex = typeof flags.complex === "string" ? flags.complex : void 0;
|
|
8291
|
+
if (!simple && !complex) throw new Error("Provide --simple <model-id> and/or --complex <model-id>.");
|
|
8292
|
+
await printSettingsMutationResult(client.settingsPost("ai-model-defaults", { simple, complex }), flags);
|
|
8293
|
+
return;
|
|
8294
|
+
}
|
|
8295
|
+
if (matches(tokens, ["privacy", "auto-delete", "chats", "set"])) {
|
|
8296
|
+
const period = rest[3];
|
|
8297
|
+
if (!period) throw new Error("Missing period. Example: openmates settings privacy auto-delete chats set 90d");
|
|
8298
|
+
await printSettingsMutationResult(client.settingsPost("auto-delete-chats", { period }), flags);
|
|
8299
|
+
return;
|
|
8300
|
+
}
|
|
8301
|
+
if (matches(tokens, ["privacy", "debug-logs", "share"])) {
|
|
8302
|
+
if (flags.yes !== true && flags.confirm !== true) {
|
|
8303
|
+
await confirmOrExit("Share debug logs with OpenMates support? [y/N] ");
|
|
7746
8304
|
}
|
|
7747
|
-
const
|
|
7748
|
-
|
|
7749
|
-
|
|
8305
|
+
const duration = typeof flags.duration === "string" ? flags.duration : "1h";
|
|
8306
|
+
await printSettingsMutationResult(client.settingsPost("debug-session", { duration }), flags);
|
|
8307
|
+
return;
|
|
8308
|
+
}
|
|
8309
|
+
if (matches(tokens, ["billing", "overview"])) {
|
|
8310
|
+
await printSettingsResult(client.settingsGet("billing"), flags);
|
|
8311
|
+
return;
|
|
8312
|
+
}
|
|
8313
|
+
if (matches(tokens, ["billing", "usage", "summaries"])) {
|
|
8314
|
+
await printSettingsResult(client.settingsGet("usage/summaries"), flags);
|
|
8315
|
+
return;
|
|
8316
|
+
}
|
|
8317
|
+
if (matches(tokens, ["billing", "usage", "daily"])) {
|
|
8318
|
+
await printSettingsResult(client.settingsGet("usage/daily-overview"), flags);
|
|
8319
|
+
return;
|
|
8320
|
+
}
|
|
8321
|
+
if (matches(tokens, ["billing", "usage", "export"])) {
|
|
8322
|
+
await printSettingsResult(client.settingsGet("usage/export"), flags);
|
|
8323
|
+
return;
|
|
8324
|
+
}
|
|
8325
|
+
if (matches(tokens, ["billing", "usage"])) {
|
|
8326
|
+
await printSettingsResult(client.settingsGet("usage"), flags);
|
|
8327
|
+
return;
|
|
8328
|
+
}
|
|
8329
|
+
if (matches(tokens, ["billing", "invoices", "list"])) {
|
|
8330
|
+
await printSettingsResult(client.listInvoices(), flags);
|
|
8331
|
+
return;
|
|
8332
|
+
}
|
|
8333
|
+
if (matches(tokens, ["billing", "invoices", "download"])) {
|
|
8334
|
+
const invoiceId = rest[2];
|
|
8335
|
+
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
8336
|
+
const document = await client.downloadInvoice(invoiceId);
|
|
8337
|
+
const filePath = await saveDownloadedDocument(document, flags.output);
|
|
8338
|
+
if (flags.json === true) printJson2({ path: filePath, filename: document.filename });
|
|
8339
|
+
else console.log(`\x1B[32m\u2713\x1B[0m Invoice saved to ${filePath}`);
|
|
8340
|
+
return;
|
|
8341
|
+
}
|
|
8342
|
+
if (matches(tokens, ["billing", "invoices", "credit-note"])) {
|
|
8343
|
+
const invoiceId = rest[2];
|
|
8344
|
+
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
8345
|
+
const document = await client.downloadCreditNote(invoiceId);
|
|
8346
|
+
const filePath = await saveDownloadedDocument(document, flags.output);
|
|
8347
|
+
if (flags.json === true) printJson2({ path: filePath, filename: document.filename });
|
|
8348
|
+
else console.log(`\x1B[32m\u2713\x1B[0m Credit note saved to ${filePath}`);
|
|
8349
|
+
return;
|
|
8350
|
+
}
|
|
8351
|
+
if (matches(tokens, ["billing", "invoices", "refund"])) {
|
|
8352
|
+
const invoiceId = rest[2];
|
|
8353
|
+
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
8354
|
+
if (flags.yes !== true) await confirmOrExit(`Request refund for invoice ${invoiceId}? [y/N] `);
|
|
8355
|
+
await printSettingsMutationResult(client.requestRefund(invoiceId), flags);
|
|
8356
|
+
return;
|
|
8357
|
+
}
|
|
8358
|
+
if (matches(tokens, ["billing", "gift-card", "redeem"]) || subcommand === "gift-card" && rest[0] === "redeem") {
|
|
8359
|
+
const code = matches(tokens, ["billing", "gift-card", "redeem"]) ? rest[2] : rest[1];
|
|
8360
|
+
if (!code) throw new Error("Missing gift card code.");
|
|
8361
|
+
const result = await client.redeemGiftCard(code);
|
|
7750
8362
|
if (flags.json === true) {
|
|
7751
8363
|
printJson2(result);
|
|
8364
|
+
} else if (result.success) {
|
|
8365
|
+
process.stdout.write(`\x1B[32m\u2713\x1B[0m Gift card redeemed! +${result.credits_added} credits
|
|
8366
|
+
`);
|
|
8367
|
+
process.stdout.write(` Balance: ${result.current_credits} credits
|
|
8368
|
+
`);
|
|
7752
8369
|
} else {
|
|
7753
|
-
|
|
8370
|
+
process.stdout.write(`\x1B[31m\u2717\x1B[0m ${result.message}
|
|
8371
|
+
`);
|
|
7754
8372
|
}
|
|
7755
8373
|
return;
|
|
7756
8374
|
}
|
|
7757
|
-
if (subcommand === "
|
|
7758
|
-
await
|
|
8375
|
+
if (matches(tokens, ["billing", "gift-card", "list"]) || subcommand === "gift-card" && rest[0] === "list") {
|
|
8376
|
+
await printSettingsResult(client.listRedeemedGiftCards(), flags);
|
|
7759
8377
|
return;
|
|
7760
8378
|
}
|
|
7761
|
-
if (
|
|
7762
|
-
const
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
8379
|
+
if (matches(tokens, ["billing", "auto-topup", "low-balance", "set"])) {
|
|
8380
|
+
const enabled = parseOnOff(String(flags.enabled ?? ""), "low-balance auto top-up");
|
|
8381
|
+
const amount = parseRequiredNumber(flags.amount, "--amount");
|
|
8382
|
+
const currency = typeof flags.currency === "string" ? flags.currency : "eur";
|
|
8383
|
+
const email = typeof flags.email === "string" ? flags.email : void 0;
|
|
8384
|
+
if (enabled && !email) throw new Error("Provide --email when enabling low-balance auto top-up.");
|
|
8385
|
+
await printSettingsMutationResult(
|
|
8386
|
+
client.settingsPost("auto-topup/low-balance", { enabled, threshold: 100, amount, currency, email }),
|
|
8387
|
+
flags
|
|
8388
|
+
);
|
|
8389
|
+
return;
|
|
8390
|
+
}
|
|
8391
|
+
if (matches(tokens, ["notifications", "status"])) {
|
|
8392
|
+
const user = await client.whoAmI();
|
|
8393
|
+
const status = {
|
|
8394
|
+
enabled: user.email_notifications_enabled ?? false,
|
|
8395
|
+
preferences: user.email_notification_preferences ?? {},
|
|
8396
|
+
backup_reminder_interval_days: user.backup_reminder_interval_days ?? null,
|
|
8397
|
+
encrypted_notification_email_configured: Boolean(user.encrypted_notification_email)
|
|
8398
|
+
};
|
|
8399
|
+
flags.json === true ? printJson2(status) : printGenericObject(status);
|
|
8400
|
+
return;
|
|
8401
|
+
}
|
|
8402
|
+
if (matches(tokens, ["notifications", "list"])) {
|
|
8403
|
+
const limit = parseOptionalNumber(flags.limit, 50, "--limit");
|
|
8404
|
+
await printSettingsResult(client.listNotifications(limit), flags);
|
|
8405
|
+
return;
|
|
8406
|
+
}
|
|
8407
|
+
if (matches(tokens, ["notifications", "stream"])) {
|
|
8408
|
+
const count = flags.count === void 0 ? null : parseRequiredNumber(flags.count, "--count");
|
|
8409
|
+
let received = 0;
|
|
8410
|
+
for await (const event of client.streamNotifications()) {
|
|
7792
8411
|
if (flags.json === true) {
|
|
7793
|
-
printJson2(
|
|
8412
|
+
printJson2(event);
|
|
7794
8413
|
} else {
|
|
7795
|
-
printGenericObject(
|
|
8414
|
+
printGenericObject(event);
|
|
7796
8415
|
}
|
|
7797
|
-
|
|
8416
|
+
received += 1;
|
|
8417
|
+
if (count !== null && received >= count) break;
|
|
7798
8418
|
}
|
|
7799
|
-
console.log(`Gift card commands:
|
|
7800
|
-
openmates settings gift-card redeem <CODE> Redeem a gift card
|
|
7801
|
-
openmates settings gift-card list List redeemed gift cards`);
|
|
7802
8419
|
return;
|
|
7803
8420
|
}
|
|
7804
|
-
|
|
8421
|
+
if (matches(tokens, ["notifications", "email", "set"])) {
|
|
8422
|
+
const enabled = parseOnOff(String(flags.enabled ?? ""), "email notifications");
|
|
8423
|
+
const email = typeof flags.email === "string" ? flags.email : null;
|
|
8424
|
+
if (enabled && !email) throw new Error("Provide --email when enabling email notifications.");
|
|
8425
|
+
const preferences = {
|
|
8426
|
+
aiResponses: parseOptionalBoolean(flags["ai-responses"], true, "AI response notifications"),
|
|
8427
|
+
backupReminder: parseOptionalBoolean(flags["backup-reminder"], false, "backup reminder notifications"),
|
|
8428
|
+
webhookChats: parseOptionalBoolean(flags["webhook-chats"], false, "webhook chat notifications")
|
|
8429
|
+
};
|
|
8430
|
+
await printSettingsMutationResult(
|
|
8431
|
+
client.updateEmailNotificationSettings({ enabled, email, preferences }),
|
|
8432
|
+
flags
|
|
8433
|
+
);
|
|
8434
|
+
return;
|
|
8435
|
+
}
|
|
8436
|
+
if (matches(tokens, ["notifications", "backup", "set"])) {
|
|
8437
|
+
const enabled = parseOnOff(String(flags.enabled ?? ""), "backup reminders");
|
|
8438
|
+
const email = typeof flags.email === "string" ? flags.email : null;
|
|
8439
|
+
const interval = parseRequiredNumber(flags.interval, "--interval");
|
|
8440
|
+
if (enabled && !email) throw new Error("Provide --email when enabling backup reminders.");
|
|
8441
|
+
await printSettingsMutationResult(
|
|
8442
|
+
client.updateEmailNotificationSettings({
|
|
8443
|
+
enabled,
|
|
8444
|
+
email,
|
|
8445
|
+
preferences: { aiResponses: false, backupReminder: enabled },
|
|
8446
|
+
backup_reminder_interval_days: interval
|
|
8447
|
+
}),
|
|
8448
|
+
flags
|
|
8449
|
+
);
|
|
8450
|
+
return;
|
|
8451
|
+
}
|
|
8452
|
+
if (matches(tokens, ["reminders", "list"])) {
|
|
8453
|
+
await printSettingsResult(client.settingsGet("reminders"), flags);
|
|
8454
|
+
return;
|
|
8455
|
+
}
|
|
8456
|
+
if (matches(tokens, ["reminders", "update"])) {
|
|
8457
|
+
const id = rest[1];
|
|
8458
|
+
if (!id) throw new Error("Missing reminder ID.");
|
|
8459
|
+
const body = parseDataOrFlags(flags, ["enabled"]);
|
|
8460
|
+
await printSettingsMutationResult(client.settingsPatch(`reminders/${id}`, body), flags);
|
|
8461
|
+
return;
|
|
8462
|
+
}
|
|
8463
|
+
if (matches(tokens, ["reminders", "delete"])) {
|
|
8464
|
+
const id = rest[1];
|
|
8465
|
+
if (!id) throw new Error("Missing reminder ID.");
|
|
8466
|
+
if (flags.yes !== true) await confirmOrExit(`Delete reminder ${id}? [y/N] `);
|
|
8467
|
+
await printSettingsMutationResult(client.settingsDelete(`reminders/${id}`), flags);
|
|
8468
|
+
return;
|
|
8469
|
+
}
|
|
8470
|
+
if (matches(tokens, ["developers", "api-keys", "list"])) {
|
|
8471
|
+
await printSettingsResult(client.settingsGet("api-keys"), flags);
|
|
8472
|
+
return;
|
|
8473
|
+
}
|
|
8474
|
+
if (matches(tokens, ["developers", "api-keys", "revoke"])) {
|
|
8475
|
+
const id = rest[2];
|
|
8476
|
+
if (!id) throw new Error("Missing API key ID.");
|
|
8477
|
+
if (flags.yes !== true) await confirmOrExit(`Revoke API key ${id}? [y/N] `);
|
|
8478
|
+
await printSettingsMutationResult(client.settingsDelete(`api-keys/${id}`), flags);
|
|
8479
|
+
return;
|
|
8480
|
+
}
|
|
8481
|
+
if (matches(tokens, ["report-issue", "create"])) {
|
|
8482
|
+
const title = typeof flags.title === "string" ? flags.title : void 0;
|
|
8483
|
+
const body = typeof flags.body === "string" ? flags.body : void 0;
|
|
8484
|
+
if (!title || !body) throw new Error("Provide --title and --body.");
|
|
8485
|
+
await printSettingsMutationResult(client.settingsPost("issues", { title, description: body }), flags);
|
|
8486
|
+
return;
|
|
8487
|
+
}
|
|
8488
|
+
if (matches(tokens, ["report-issue", "status"])) {
|
|
8489
|
+
const id = rest[1];
|
|
8490
|
+
if (!id) throw new Error("Missing issue ID.");
|
|
8491
|
+
await printSettingsResult(client.settingsGet(`issues/${id}/status`), flags);
|
|
8492
|
+
return;
|
|
8493
|
+
}
|
|
8494
|
+
if (matches(tokens, ["mates", "list"])) {
|
|
8495
|
+
printMates(flags.json === true);
|
|
8496
|
+
return;
|
|
8497
|
+
}
|
|
8498
|
+
if (matches(tokens, ["mates", "info"])) {
|
|
8499
|
+
const mateId = rest[1];
|
|
8500
|
+
if (!mateId) throw new Error("Missing mate ID. Example: openmates settings mates info software_development");
|
|
8501
|
+
printMateInfo(mateId, flags.json === true);
|
|
8502
|
+
return;
|
|
8503
|
+
}
|
|
8504
|
+
if (matches(tokens, ["mates", "consent"])) {
|
|
8505
|
+
if (flags.yes !== true) await confirmOrExit("Record consent for mate settings? [y/N] ");
|
|
8506
|
+
await printSettingsMutationResult(client.settingsPost("user/consent/mates", { consent: true }), flags);
|
|
8507
|
+
return;
|
|
8508
|
+
}
|
|
8509
|
+
if (matches(tokens, ["newsletter", "categories"]) && tokens.length === 2) {
|
|
8510
|
+
await printSettingsResult(client.getNewsletterCategories(), flags);
|
|
8511
|
+
return;
|
|
8512
|
+
}
|
|
8513
|
+
if (matches(tokens, ["newsletter", "categories", "set"])) {
|
|
8514
|
+
const categories = {
|
|
8515
|
+
updates_and_announcements: parseOptionalBoolean(flags.updates, true, "updates newsletter"),
|
|
8516
|
+
tips_and_tricks: parseOptionalBoolean(flags.tips, true, "tips newsletter"),
|
|
8517
|
+
daily_inspirations: parseOptionalBoolean(flags.daily, true, "daily inspirations newsletter")
|
|
8518
|
+
};
|
|
8519
|
+
await printSettingsMutationResult(client.updateNewsletterCategories(categories), flags);
|
|
8520
|
+
return;
|
|
8521
|
+
}
|
|
8522
|
+
if (matches(tokens, ["newsletter", "subscribe"])) {
|
|
8523
|
+
const email = rest[1];
|
|
8524
|
+
if (!email) throw new Error("Missing email. Example: openmates settings newsletter subscribe you@example.com");
|
|
8525
|
+
const language = typeof flags.language === "string" ? flags.language : "en";
|
|
8526
|
+
const darkmode = parseOptionalBoolean(flags.darkmode, false, "newsletter dark mode");
|
|
8527
|
+
await printSettingsMutationResult(client.subscribeNewsletter(email, language, darkmode), flags);
|
|
8528
|
+
return;
|
|
8529
|
+
}
|
|
8530
|
+
if (matches(tokens, ["newsletter", "confirm"])) {
|
|
8531
|
+
const token = rest[1];
|
|
8532
|
+
if (!token) throw new Error("Missing confirmation token.");
|
|
8533
|
+
await printSettingsMutationResult(client.confirmNewsletter(token), flags);
|
|
8534
|
+
return;
|
|
8535
|
+
}
|
|
8536
|
+
if (matches(tokens, ["newsletter", "unsubscribe"])) {
|
|
8537
|
+
const token = rest[1];
|
|
8538
|
+
if (!token) throw new Error("Missing unsubscribe token.");
|
|
8539
|
+
await printSettingsMutationResult(client.unsubscribeNewsletter(token), flags);
|
|
8540
|
+
return;
|
|
8541
|
+
}
|
|
8542
|
+
if (subcommand === "memories") {
|
|
8543
|
+
await handleMemories(client, rest, flags);
|
|
8544
|
+
return;
|
|
8545
|
+
}
|
|
8546
|
+
const webOnly = findSettingsInfoCommand(tokens);
|
|
8547
|
+
if (webOnly) {
|
|
8548
|
+
printSettingsInfoCommand(client, webOnly, flags.json === true);
|
|
8549
|
+
return;
|
|
8550
|
+
}
|
|
8551
|
+
console.error(`Unknown settings command '${tokens.join(" ")}'.
|
|
7805
8552
|
`);
|
|
7806
|
-
printSettingsHelp();
|
|
8553
|
+
printSettingsHelp(client, [subcommand]);
|
|
7807
8554
|
process.exit(1);
|
|
7808
8555
|
}
|
|
7809
8556
|
async function handleMemories(client, rest, flags) {
|
|
@@ -8356,21 +9103,13 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
8356
9103
|
}
|
|
8357
9104
|
return result;
|
|
8358
9105
|
}
|
|
8359
|
-
function
|
|
8360
|
-
|
|
8361
|
-
|
|
9106
|
+
function printIncognitoNoHistoryNotice(json) {
|
|
9107
|
+
const message = "Incognito chats are not stored. There is no incognito history to show or clear.";
|
|
9108
|
+
if (json) {
|
|
9109
|
+
printJson2({ history: [], stored: false, message });
|
|
8362
9110
|
return;
|
|
8363
9111
|
}
|
|
8364
|
-
|
|
8365
|
-
console.log();
|
|
8366
|
-
for (const msg of history) {
|
|
8367
|
-
const ts = formatTimestamp(Math.floor(msg.createdAt / 1e3));
|
|
8368
|
-
const roleLabel = msg.role === "user" ? "\x1B[1mYou\x1B[0m" : "\x1B[36mAssistant\x1B[0m";
|
|
8369
|
-
process.stdout.write(`${roleLabel} \x1B[2m${ts}\x1B[0m
|
|
8370
|
-
`);
|
|
8371
|
-
console.log(msg.content);
|
|
8372
|
-
console.log();
|
|
8373
|
-
}
|
|
9112
|
+
console.log(message);
|
|
8374
9113
|
}
|
|
8375
9114
|
var SEP = `\x1B[2m${"\u2500".repeat(60)}\x1B[0m`;
|
|
8376
9115
|
function parseMessageSegments(content) {
|
|
@@ -9337,23 +10076,23 @@ async function handleMentions(client, subcommand, _rest, flags) {
|
|
|
9337
10076
|
const context = await client.buildMentionContext();
|
|
9338
10077
|
const allOptions = listMentionOptions(context);
|
|
9339
10078
|
const normalizedQuery = query.toLowerCase().replace(/[\s_-]+/g, "");
|
|
9340
|
-
const
|
|
10079
|
+
const matches2 = allOptions.filter((opt) => {
|
|
9341
10080
|
const normalizedName = opt.displayName.toLowerCase().replace(/[@\s_-]+/g, "");
|
|
9342
10081
|
const normalizedDesc = opt.description.toLowerCase().replace(/[\s_-]+/g, "");
|
|
9343
10082
|
return normalizedName.includes(normalizedQuery) || normalizedDesc.includes(normalizedQuery);
|
|
9344
10083
|
}).slice(0, 15);
|
|
9345
10084
|
if (flags.json === true) {
|
|
9346
|
-
console.log(JSON.stringify(
|
|
10085
|
+
console.log(JSON.stringify(matches2, null, 2));
|
|
9347
10086
|
return;
|
|
9348
10087
|
}
|
|
9349
|
-
if (
|
|
10088
|
+
if (matches2.length === 0) {
|
|
9350
10089
|
console.log(`No mentions matching '${query}'.`);
|
|
9351
10090
|
return;
|
|
9352
10091
|
}
|
|
9353
10092
|
process.stdout.write(`
|
|
9354
10093
|
\x1B[1mMatches for '${query}':\x1B[0m
|
|
9355
10094
|
`);
|
|
9356
|
-
for (const m of
|
|
10095
|
+
for (const m of matches2) {
|
|
9357
10096
|
const typeLabel = m.type.replace("_", " ");
|
|
9358
10097
|
process.stdout.write(
|
|
9359
10098
|
` \x1B[36m${m.displayName.padEnd(35)}\x1B[0m \x1B[2m${m.description} (${typeLabel})\x1B[0m
|
|
@@ -9498,7 +10237,7 @@ Commands:
|
|
|
9498
10237
|
openmates apps [--help] App skill commands (list, run, ...)
|
|
9499
10238
|
openmates mentions [--help] List available @mentions
|
|
9500
10239
|
openmates embeds [--help] Embed commands (show)
|
|
9501
|
-
openmates settings [--help]
|
|
10240
|
+
openmates settings [--help] Predefined settings commands
|
|
9502
10241
|
openmates inspirations [--lang <code>] [--json] Daily inspirations
|
|
9503
10242
|
openmates newchatsuggestions [--limit <n>] [--json] Personalized new chat suggestions
|
|
9504
10243
|
openmates server [--help] Server management (install, start, stop, ...)
|
|
@@ -9523,8 +10262,8 @@ function printChatsHelp() {
|
|
|
9523
10262
|
openmates chats delete <id1> [id2] [id3] ... [--yes]
|
|
9524
10263
|
openmates chats share [<chat-id>] [--expires <seconds>] [--password <pwd>] [--json]
|
|
9525
10264
|
openmates chats incognito <message> [--json]
|
|
9526
|
-
openmates chats incognito-history [--json]
|
|
9527
|
-
openmates chats incognito-clear
|
|
10265
|
+
openmates chats incognito-history [--json] Deprecated: incognito stores no history
|
|
10266
|
+
openmates chats incognito-clear Deprecated: incognito stores no history
|
|
9528
10267
|
|
|
9529
10268
|
Options for 'list':
|
|
9530
10269
|
--limit <n> Number of chats per page (default: 10)
|
|
@@ -9648,72 +10387,33 @@ Examples:
|
|
|
9648
10387
|
openmates inspirations --lang de
|
|
9649
10388
|
openmates inspirations --json`);
|
|
9650
10389
|
}
|
|
9651
|
-
function printSettingsHelp(client) {
|
|
10390
|
+
function printSettingsHelp(client, filter) {
|
|
10391
|
+
const commands = [...SETTINGS_EXECUTABLE_COMMANDS, ...SETTINGS_INFO_COMMANDS].filter((command) => {
|
|
10392
|
+
if (!filter || filter.length === 0) return true;
|
|
10393
|
+
return filter.every((part, index) => command.path[index] === part);
|
|
10394
|
+
}).sort((a, b) => a.path.join(" ").localeCompare(b.path.join(" ")));
|
|
9652
10395
|
const appUrl = client ? deriveAppUrl(client.apiUrl) : "https://openmates.org";
|
|
9653
|
-
const
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
openmates settings get usage/export [--json] Export usage as CSV
|
|
9675
|
-
openmates settings gift-card redeem <CODE> Redeem a gift card
|
|
9676
|
-
openmates settings gift-card list List redeemed gift cards
|
|
9677
|
-
\x1B[2mBuy credits: ${s("billing/buy-credits")}\x1B[0m
|
|
9678
|
-
\x1B[2mMonthly auto top-up: ${s("billing/auto-topup/monthly")}\x1B[0m
|
|
9679
|
-
\x1B[2mInvoices: ${s("billing/invoices")}\x1B[0m
|
|
9680
|
-
\x1B[2mGift cards (buy/manage): ${s("billing/gift-cards")}\x1B[0m
|
|
9681
|
-
${h("Privacy")}
|
|
9682
|
-
openmates settings post auto-delete-chats --data '{"period":"90d"}'
|
|
9683
|
-
\x1B[2mHide personal data / anonymization: ${s("privacy/hide-personal-data")}\x1B[0m
|
|
9684
|
-
${h("Notifications")}
|
|
9685
|
-
openmates settings get reminders [--json] Active reminders
|
|
9686
|
-
\x1B[2mChat notifications: ${s("notifications/chat")}\x1B[0m
|
|
9687
|
-
\x1B[2mBackup reminders: ${s("notifications/backup")}\x1B[0m
|
|
9688
|
-
${h("Interface")}
|
|
9689
|
-
openmates settings post user/language --data '{"language":"en"}'
|
|
9690
|
-
openmates settings post user/darkmode --data '{"dark_mode":true}'
|
|
9691
|
-
openmates settings post ai-model-defaults --data '{"simple":"...","complex":"..."}'
|
|
9692
|
-
${h("Apps")}
|
|
9693
|
-
openmates apps list Same as Apps
|
|
9694
|
-
openmates apps <app-id> App details
|
|
9695
|
-
\x1B[2mWeb: ${s("app_store")}\x1B[0m
|
|
9696
|
-
${h("Mates")}
|
|
9697
|
-
\x1B[2m${s("mates")}\x1B[0m
|
|
9698
|
-
${h("Memories & app settings")}
|
|
9699
|
-
openmates settings memories list [--app-id <id>] [--item-type <type>] [--json]
|
|
9700
|
-
openmates settings memories types [--app-id <id>] [--json]
|
|
9701
|
-
openmates settings memories create --app-id <id> --item-type <type> --data '<json>'
|
|
9702
|
-
openmates settings memories update --id <id> --app-id <id> --item-type <type> --data '<json>'
|
|
9703
|
-
openmates settings memories delete --id <entry-id>
|
|
9704
|
-
${h("Developers")}
|
|
9705
|
-
openmates settings get api-keys [--json] List API keys
|
|
9706
|
-
openmates settings delete api-keys/<key-id> Revoke API key
|
|
9707
|
-
\x1B[2mCreate API key (shows secret once): ${s("developers/api-keys")}\x1B[0m
|
|
9708
|
-
\x1B[2mManage devices: ${s("developers/devices")}\x1B[0m
|
|
9709
|
-
${h("Support")}
|
|
9710
|
-
openmates settings post issues --data '<json>' Report an issue
|
|
9711
|
-
|
|
9712
|
-
\x1B[2mWeb app only (security \u2014 manage in browser):\x1B[0m
|
|
9713
|
-
\x1B[2mPasskeys: ${s("account/security/passkeys")}\x1B[0m
|
|
9714
|
-
\x1B[2mPassword: ${s("account/security/password")}\x1B[0m
|
|
9715
|
-
\x1B[2m2FA: ${s("account/security/2fa")}\x1B[0m
|
|
9716
|
-
\x1B[2mSessions: ${s("account/security/sessions")}\x1B[0m`);
|
|
10396
|
+
const title = filter && filter.length > 0 ? `Settings: ${filter.join(" ")}` : "Settings";
|
|
10397
|
+
header(title);
|
|
10398
|
+
console.log("Predefined commands only. Raw settings get/post/patch/delete is not supported.\n");
|
|
10399
|
+
if (commands.length === 0) {
|
|
10400
|
+
console.log("No matching settings commands.");
|
|
10401
|
+
return;
|
|
10402
|
+
}
|
|
10403
|
+
for (const command of commands) {
|
|
10404
|
+
const isInfoOnly = SETTINGS_INFO_COMMANDS.includes(command);
|
|
10405
|
+
const label = `openmates settings ${command.path.join(" ")}`;
|
|
10406
|
+
process.stdout.write(` ${label.padEnd(58)} ${command.description}`);
|
|
10407
|
+
if (isInfoOnly) process.stdout.write(" \x1B[2m(info/web-only)\x1B[0m");
|
|
10408
|
+
process.stdout.write("\n");
|
|
10409
|
+
if (filter && filter.length > 0) {
|
|
10410
|
+
for (const example of command.examples) process.stdout.write(` e.g. ${example}
|
|
10411
|
+
`);
|
|
10412
|
+
if (command.webPath) process.stdout.write(` web: ${appUrl}/#settings/${command.webPath}
|
|
10413
|
+
`);
|
|
10414
|
+
}
|
|
10415
|
+
}
|
|
10416
|
+
console.log("\nUse --help after a group for examples, e.g. openmates settings billing --help");
|
|
9717
10417
|
}
|
|
9718
10418
|
function printNewChatSuggestionsHelp() {
|
|
9719
10419
|
console.log(`New chat suggestions command:
|