openmates 0.11.0-alpha.2 → 0.11.0-alpha.21
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-AXNRPVLE.js +242 -0
- package/dist/{chunk-N6QY7K6L.js → chunk-JQ5CPV6P.js} +2389 -748
- package/dist/cli.js +2 -1
- package/dist/index.d.ts +108 -9
- package/dist/index.js +4 -1
- package/dist/uploadService-S464XJRA.js +10 -0
- package/package.json +5 -3
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
transcribeUploadedAudio,
|
|
3
|
+
uploadFile
|
|
4
|
+
} from "./chunk-AXNRPVLE.js";
|
|
5
|
+
|
|
1
6
|
// src/client.ts
|
|
2
|
-
import { randomUUID } from "crypto";
|
|
7
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3
8
|
import { platform as platform2, release } from "os";
|
|
4
9
|
import { createInterface } from "readline/promises";
|
|
5
10
|
import { stdin, stdout } from "process";
|
|
@@ -87,6 +92,16 @@ async function decryptWithAesGcmCombined(encryptedWithIvB64, rawKeyBytes) {
|
|
|
87
92
|
return null;
|
|
88
93
|
}
|
|
89
94
|
}
|
|
95
|
+
async function deriveEmailEncryptionKeyB64(email, emailSaltB64) {
|
|
96
|
+
const encoder = new TextEncoder();
|
|
97
|
+
const emailBytes = encoder.encode(email);
|
|
98
|
+
const saltBytes = base64ToBytes(emailSaltB64);
|
|
99
|
+
const combined = new Uint8Array(emailBytes.length + saltBytes.length);
|
|
100
|
+
combined.set(emailBytes);
|
|
101
|
+
combined.set(saltBytes, emailBytes.length);
|
|
102
|
+
const hashBuffer = await cryptoApi.subtle.digest("SHA-256", toArrayBuffer(combined));
|
|
103
|
+
return bytesToBase64(new Uint8Array(hashBuffer));
|
|
104
|
+
}
|
|
90
105
|
async function decryptBytesWithAesGcm(encryptedWithIvB64, rawKeyBytes) {
|
|
91
106
|
try {
|
|
92
107
|
const combined = base64ToBytes(encryptedWithIvB64);
|
|
@@ -180,12 +195,66 @@ var OpenMatesHttpClient = class {
|
|
|
180
195
|
async post(path, body, headers = {}) {
|
|
181
196
|
return this.request("POST", path, body, headers);
|
|
182
197
|
}
|
|
183
|
-
async delete(path, headers = {}) {
|
|
184
|
-
return this.request("DELETE", path,
|
|
198
|
+
async delete(path, body, headers = {}) {
|
|
199
|
+
return this.request("DELETE", path, body, headers);
|
|
185
200
|
}
|
|
186
201
|
async patch(path, body, headers = {}) {
|
|
187
202
|
return this.request("PATCH", path, body, headers);
|
|
188
203
|
}
|
|
204
|
+
async getBinary(path, headers = {}) {
|
|
205
|
+
const url = `${this.apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
206
|
+
const requestHeaders = {
|
|
207
|
+
Accept: "application/pdf,application/octet-stream",
|
|
208
|
+
...headers
|
|
209
|
+
};
|
|
210
|
+
const cookieHeader = this.formatCookieHeader();
|
|
211
|
+
if (cookieHeader) requestHeaders.Cookie = cookieHeader;
|
|
212
|
+
const response = await fetch(url, { method: "GET", headers: requestHeaders });
|
|
213
|
+
this.captureCookies(response);
|
|
214
|
+
return {
|
|
215
|
+
ok: response.ok,
|
|
216
|
+
status: response.status,
|
|
217
|
+
data: new Uint8Array(await response.arrayBuffer()),
|
|
218
|
+
headers: response.headers
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async *streamSse(path, headers = {}) {
|
|
222
|
+
const url = `${this.apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
223
|
+
const requestHeaders = {
|
|
224
|
+
Accept: "text/event-stream",
|
|
225
|
+
...headers
|
|
226
|
+
};
|
|
227
|
+
const cookieHeader = this.formatCookieHeader();
|
|
228
|
+
if (cookieHeader) requestHeaders.Cookie = cookieHeader;
|
|
229
|
+
const response = await fetch(url, { method: "GET", headers: requestHeaders });
|
|
230
|
+
this.captureCookies(response);
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
throw new Error(`SSE request failed with HTTP ${response.status}`);
|
|
233
|
+
}
|
|
234
|
+
if (!response.body) {
|
|
235
|
+
throw new Error("SSE response body is not readable");
|
|
236
|
+
}
|
|
237
|
+
const reader = response.body.getReader();
|
|
238
|
+
const decoder = new TextDecoder();
|
|
239
|
+
let buffer = "";
|
|
240
|
+
try {
|
|
241
|
+
while (true) {
|
|
242
|
+
const { done, value } = await reader.read();
|
|
243
|
+
if (done) break;
|
|
244
|
+
buffer += decoder.decode(value, { stream: true });
|
|
245
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
246
|
+
while (separatorIndex >= 0) {
|
|
247
|
+
const rawEvent = buffer.slice(0, separatorIndex);
|
|
248
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
249
|
+
const message = parseSseMessage(rawEvent);
|
|
250
|
+
if (message) yield message;
|
|
251
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} finally {
|
|
255
|
+
reader.releaseLock();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
189
258
|
async request(method, path, body, headers = {}) {
|
|
190
259
|
const url = `${this.apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
191
260
|
const requestHeaders = {
|
|
@@ -252,6 +321,19 @@ var OpenMatesHttpClient = class {
|
|
|
252
321
|
return single ? [single] : [];
|
|
253
322
|
}
|
|
254
323
|
};
|
|
324
|
+
function parseSseMessage(rawEvent) {
|
|
325
|
+
const message = { data: "" };
|
|
326
|
+
for (const line of rawEvent.split("\n")) {
|
|
327
|
+
if (!line || line.startsWith(":")) continue;
|
|
328
|
+
const separator = line.indexOf(":");
|
|
329
|
+
const field = separator >= 0 ? line.slice(0, separator) : line;
|
|
330
|
+
const value = separator >= 0 ? line.slice(separator + 1).replace(/^ /, "") : "";
|
|
331
|
+
if (field === "id") message.id = value;
|
|
332
|
+
if (field === "event") message.event = value;
|
|
333
|
+
if (field === "data") message.data += `${message.data ? "\n" : ""}${value}`;
|
|
334
|
+
}
|
|
335
|
+
return message.data ? message : null;
|
|
336
|
+
}
|
|
255
337
|
|
|
256
338
|
// src/storage.ts
|
|
257
339
|
import {
|
|
@@ -533,6 +615,18 @@ function saveSession(session) {
|
|
|
533
615
|
} else if (result.type === "plaintext") {
|
|
534
616
|
onDisk.masterKeyExportedB64 = session.masterKeyExportedB64;
|
|
535
617
|
}
|
|
618
|
+
if (session.emailEncryptionKeyB64) {
|
|
619
|
+
const emailKeyResult = storeMasterKey(
|
|
620
|
+
session.emailEncryptionKeyB64,
|
|
621
|
+
`${session.hashedEmail}:email`
|
|
622
|
+
);
|
|
623
|
+
onDisk.emailEncryptionKeyStorage = emailKeyResult.type;
|
|
624
|
+
if (emailKeyResult.type === "encrypted") {
|
|
625
|
+
onDisk.emailEncryptionKeyEncrypted = emailKeyResult.encryptedData;
|
|
626
|
+
} else if (emailKeyResult.type === "plaintext") {
|
|
627
|
+
onDisk.emailEncryptionKeyB64 = session.emailEncryptionKeyB64;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
536
630
|
writeJsonFile(filePath, onDisk);
|
|
537
631
|
if (result.type !== "plaintext") {
|
|
538
632
|
process.stderr.write("Decrypting data...\n");
|
|
@@ -543,17 +637,19 @@ function loadSession() {
|
|
|
543
637
|
const onDisk = readJsonFile(filePath);
|
|
544
638
|
if (!onDisk) return null;
|
|
545
639
|
let masterKey = null;
|
|
640
|
+
let emailEncryptionKey = null;
|
|
546
641
|
if (!onDisk.masterKeyStorage) {
|
|
547
642
|
masterKey = onDisk.masterKeyExportedB64 ?? null;
|
|
548
643
|
if (masterKey) {
|
|
549
|
-
|
|
644
|
+
emailEncryptionKey = getEmailEncryptionKeyFromDisk(onDisk);
|
|
645
|
+
const session = buildSession(onDisk, masterKey, emailEncryptionKey);
|
|
550
646
|
try {
|
|
551
647
|
saveSession(session);
|
|
552
648
|
process.stderr.write("Decrypting data...\n");
|
|
553
649
|
} catch {
|
|
554
650
|
}
|
|
555
651
|
}
|
|
556
|
-
return masterKey ? buildSession(onDisk, masterKey) : null;
|
|
652
|
+
return masterKey ? buildSession(onDisk, masterKey, getEmailEncryptionKeyFromDisk(onDisk)) : null;
|
|
557
653
|
}
|
|
558
654
|
switch (onDisk.masterKeyStorage) {
|
|
559
655
|
case "keychain":
|
|
@@ -577,7 +673,7 @@ function loadSession() {
|
|
|
577
673
|
);
|
|
578
674
|
return null;
|
|
579
675
|
}
|
|
580
|
-
return buildSession(onDisk, masterKey);
|
|
676
|
+
return buildSession(onDisk, masterKey, getEmailEncryptionKeyFromDisk(onDisk));
|
|
581
677
|
}
|
|
582
678
|
function clearSession() {
|
|
583
679
|
const filePath = join(ensureStateDir(), "session.json");
|
|
@@ -585,17 +681,36 @@ function clearSession() {
|
|
|
585
681
|
if (onDisk?.masterKeyStorage) {
|
|
586
682
|
deleteMasterKey(onDisk.masterKeyStorage, onDisk.hashedEmail);
|
|
587
683
|
}
|
|
684
|
+
if (onDisk?.emailEncryptionKeyStorage) {
|
|
685
|
+
deleteMasterKey(onDisk.emailEncryptionKeyStorage, `${onDisk.hashedEmail}:email`);
|
|
686
|
+
}
|
|
588
687
|
if (existsSync2(filePath)) {
|
|
589
688
|
rmSync(filePath);
|
|
590
689
|
}
|
|
591
690
|
}
|
|
592
|
-
function
|
|
691
|
+
function getEmailEncryptionKeyFromDisk(onDisk) {
|
|
692
|
+
if (!onDisk.emailEncryptionKeyStorage) return onDisk.emailEncryptionKeyB64 ?? null;
|
|
693
|
+
switch (onDisk.emailEncryptionKeyStorage) {
|
|
694
|
+
case "keychain":
|
|
695
|
+
return retrieveMasterKey("keychain", `${onDisk.hashedEmail}:email`);
|
|
696
|
+
case "encrypted":
|
|
697
|
+
return retrieveMasterKey(
|
|
698
|
+
"encrypted",
|
|
699
|
+
`${onDisk.hashedEmail}:email`,
|
|
700
|
+
onDisk.emailEncryptionKeyEncrypted
|
|
701
|
+
);
|
|
702
|
+
case "plaintext":
|
|
703
|
+
return onDisk.emailEncryptionKeyB64 ?? null;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function buildSession(onDisk, masterKey, emailEncryptionKey) {
|
|
593
707
|
return {
|
|
594
708
|
apiUrl: onDisk.apiUrl,
|
|
595
709
|
sessionId: onDisk.sessionId,
|
|
596
710
|
wsToken: onDisk.wsToken,
|
|
597
711
|
cookies: onDisk.cookies,
|
|
598
712
|
masterKeyExportedB64: masterKey,
|
|
713
|
+
emailEncryptionKeyB64: emailEncryptionKey,
|
|
599
714
|
hashedEmail: onDisk.hashedEmail,
|
|
600
715
|
userEmailSalt: onDisk.userEmailSalt,
|
|
601
716
|
createdAt: onDisk.createdAt,
|
|
@@ -603,18 +718,6 @@ function buildSession(onDisk, masterKey) {
|
|
|
603
718
|
autoLogoutMinutes: onDisk.autoLogoutMinutes
|
|
604
719
|
};
|
|
605
720
|
}
|
|
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
721
|
var SYNC_CACHE_FILE = "sync_cache.json";
|
|
619
722
|
function saveSyncCache(cache) {
|
|
620
723
|
const filePath = join(ensureStateDir(), SYNC_CACHE_FILE);
|
|
@@ -624,6 +727,12 @@ function loadSyncCache() {
|
|
|
624
727
|
const filePath = join(ensureStateDir(), SYNC_CACHE_FILE);
|
|
625
728
|
return readJsonFile(filePath);
|
|
626
729
|
}
|
|
730
|
+
function clearSyncCache() {
|
|
731
|
+
const filePath = join(ensureStateDir(), SYNC_CACHE_FILE);
|
|
732
|
+
if (existsSync2(filePath)) {
|
|
733
|
+
rmSync(filePath);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
627
736
|
function isSyncCacheFresh(maxAgeMs = 3e5) {
|
|
628
737
|
const cache = loadSyncCache();
|
|
629
738
|
if (!cache) return false;
|
|
@@ -631,7 +740,20 @@ function isSyncCacheFresh(maxAgeMs = 3e5) {
|
|
|
631
740
|
}
|
|
632
741
|
|
|
633
742
|
// src/ws.ts
|
|
634
|
-
import
|
|
743
|
+
import { createRequire } from "module";
|
|
744
|
+
var require2 = createRequire(import.meta.url);
|
|
745
|
+
var WebSocket = require2("ws");
|
|
746
|
+
var SUB_CHAT_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
747
|
+
"spawn_sub_chats",
|
|
748
|
+
"sub_chat_progress",
|
|
749
|
+
"sub_chat_confirmation_required",
|
|
750
|
+
"sub_chat_confirmation_resolved",
|
|
751
|
+
"awaiting_sub_chats_completion",
|
|
752
|
+
"sub_chat_completed",
|
|
753
|
+
"awaiting_user_input"
|
|
754
|
+
]);
|
|
755
|
+
var SUB_CHAT_PARENT_STATUS_MESSAGE = "I've started the sub-chats and will continue once they finish.";
|
|
756
|
+
var SUB_CHAT_COMPLETION_TIMEOUT_MS = 10 * 6e4;
|
|
635
757
|
var OpenMatesWsClient = class {
|
|
636
758
|
socket;
|
|
637
759
|
constructor(options) {
|
|
@@ -691,6 +813,14 @@ var OpenMatesWsClient = class {
|
|
|
691
813
|
send(type, payload) {
|
|
692
814
|
this.socket.send(JSON.stringify({ type, payload }));
|
|
693
815
|
}
|
|
816
|
+
sendAsync(type, payload) {
|
|
817
|
+
return new Promise((resolve4, reject) => {
|
|
818
|
+
this.socket.send(JSON.stringify({ type, payload }), (error) => {
|
|
819
|
+
if (error) reject(error);
|
|
820
|
+
else resolve4();
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
}
|
|
694
824
|
waitForMessage(expectedType, predicate, timeoutMs = 2e4) {
|
|
695
825
|
return new Promise((resolve4, reject) => {
|
|
696
826
|
const onMessage = (rawData) => {
|
|
@@ -793,35 +923,119 @@ var OpenMatesWsClient = class {
|
|
|
793
923
|
collectAiResponse(userMessageId, chatId, options) {
|
|
794
924
|
const timeoutMs = options?.timeoutMs ?? 9e4;
|
|
795
925
|
const onStream = options?.onStream;
|
|
926
|
+
const asyncEmbedWaitMs = options?.asyncEmbedWaitMs ?? 12e4;
|
|
796
927
|
return new Promise((resolve4, reject) => {
|
|
797
928
|
let latestContent = "";
|
|
929
|
+
let messageId = null;
|
|
930
|
+
let taskId = null;
|
|
798
931
|
let category = null;
|
|
799
932
|
let modelName = null;
|
|
800
933
|
let followUpSuggestions = [];
|
|
934
|
+
const subChatEvents = [];
|
|
935
|
+
const pendingSubChatHandlers = /* @__PURE__ */ new Set();
|
|
936
|
+
const embeds = /* @__PURE__ */ new Map();
|
|
937
|
+
const processingEmbedIds = /* @__PURE__ */ new Set();
|
|
801
938
|
let aiResponseDone = false;
|
|
939
|
+
let postProcessingDone = false;
|
|
802
940
|
const POST_PROCESSING_WINDOW_MS = 12e3;
|
|
803
941
|
let postProcessingTimer = null;
|
|
804
|
-
|
|
942
|
+
let asyncEmbedTimer = null;
|
|
943
|
+
const startTimeout = (ms) => setTimeout(() => {
|
|
805
944
|
cleanup();
|
|
806
945
|
reject(new Error("Timed out waiting for AI response"));
|
|
807
|
-
},
|
|
946
|
+
}, ms);
|
|
947
|
+
let timeout = startTimeout(timeoutMs);
|
|
948
|
+
let awaitingSubChatsCompletion = false;
|
|
949
|
+
const resetTimeout = (ms) => {
|
|
950
|
+
clearTimeout(timeout);
|
|
951
|
+
timeout = startTimeout(ms);
|
|
952
|
+
};
|
|
808
953
|
const capture = (p) => {
|
|
954
|
+
if (typeof p.message_id === "string" && p.message_id)
|
|
955
|
+
messageId = p.message_id;
|
|
956
|
+
if (typeof p.task_id === "string" && p.task_id) taskId = p.task_id;
|
|
809
957
|
if (typeof p.category === "string" && p.category) category = p.category;
|
|
810
958
|
if (typeof p.model_name === "string" && p.model_name)
|
|
811
959
|
modelName = p.model_name;
|
|
812
960
|
};
|
|
961
|
+
const extractMessageContent = (message) => {
|
|
962
|
+
if (typeof message.content === "string") return message.content;
|
|
963
|
+
const content = message.content;
|
|
964
|
+
if (!content || typeof content !== "object") return "";
|
|
965
|
+
try {
|
|
966
|
+
const root = content;
|
|
967
|
+
const text = root.content?.[0]?.content?.[0]?.text;
|
|
968
|
+
return typeof text === "string" ? text : "";
|
|
969
|
+
} catch {
|
|
970
|
+
return "";
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
const finishPostProcessingWait = () => {
|
|
974
|
+
postProcessingDone = true;
|
|
975
|
+
maybeResolve();
|
|
976
|
+
};
|
|
977
|
+
const maybeResolve = () => {
|
|
978
|
+
if (!aiResponseDone || !postProcessingDone) return;
|
|
979
|
+
if (pendingSubChatHandlers.size > 0) return;
|
|
980
|
+
if (processingEmbedIds.size > 0 && !asyncEmbedTimer) {
|
|
981
|
+
asyncEmbedTimer = setTimeout(() => {
|
|
982
|
+
cleanup();
|
|
983
|
+
resolve4({
|
|
984
|
+
messageId,
|
|
985
|
+
taskId,
|
|
986
|
+
content: latestContent,
|
|
987
|
+
category,
|
|
988
|
+
modelName,
|
|
989
|
+
followUpSuggestions,
|
|
990
|
+
embeds: [...embeds.values()],
|
|
991
|
+
subChatEvents
|
|
992
|
+
});
|
|
993
|
+
}, asyncEmbedWaitMs);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
if (processingEmbedIds.size > 0) return;
|
|
997
|
+
cleanup();
|
|
998
|
+
resolve4({
|
|
999
|
+
messageId,
|
|
1000
|
+
taskId,
|
|
1001
|
+
content: latestContent,
|
|
1002
|
+
category,
|
|
1003
|
+
modelName,
|
|
1004
|
+
followUpSuggestions,
|
|
1005
|
+
embeds: [...embeds.values()],
|
|
1006
|
+
subChatEvents
|
|
1007
|
+
});
|
|
1008
|
+
};
|
|
1009
|
+
const handleSubChatEvent = (type, p) => {
|
|
1010
|
+
const eventChatId = typeof p.chat_id === "string" ? p.chat_id : typeof p.parent_id === "string" ? p.parent_id : null;
|
|
1011
|
+
if (eventChatId && eventChatId !== chatId) return;
|
|
1012
|
+
const event = { type, payload: p };
|
|
1013
|
+
subChatEvents.push(event);
|
|
1014
|
+
if (type === "awaiting_sub_chats_completion") {
|
|
1015
|
+
awaitingSubChatsCompletion = true;
|
|
1016
|
+
resetTimeout(SUB_CHAT_COMPLETION_TIMEOUT_MS);
|
|
1017
|
+
}
|
|
1018
|
+
const handler = options?.onSubChatEvent;
|
|
1019
|
+
if (!handler) return;
|
|
1020
|
+
const pending = Promise.resolve(handler(event));
|
|
1021
|
+
pendingSubChatHandlers.add(pending);
|
|
1022
|
+
pending.catch((error) => {
|
|
1023
|
+
cleanup();
|
|
1024
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
1025
|
+
}).finally(() => {
|
|
1026
|
+
pendingSubChatHandlers.delete(pending);
|
|
1027
|
+
maybeResolve();
|
|
1028
|
+
});
|
|
1029
|
+
};
|
|
813
1030
|
const scheduleResolve = (content) => {
|
|
1031
|
+
if (awaitingSubChatsCompletion && content.trim() === SUB_CHAT_PARENT_STATUS_MESSAGE) {
|
|
1032
|
+
latestContent = "";
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
814
1035
|
aiResponseDone = true;
|
|
815
1036
|
latestContent = content;
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
resolve4({
|
|
819
|
-
content: latestContent,
|
|
820
|
-
category,
|
|
821
|
-
modelName,
|
|
822
|
-
followUpSuggestions
|
|
823
|
-
});
|
|
824
|
-
}, POST_PROCESSING_WINDOW_MS);
|
|
1037
|
+
clearTimeout(timeout);
|
|
1038
|
+
postProcessingTimer = setTimeout(finishPostProcessingWait, POST_PROCESSING_WINDOW_MS);
|
|
825
1039
|
};
|
|
826
1040
|
const onMessage = (rawData) => {
|
|
827
1041
|
try {
|
|
@@ -837,9 +1051,31 @@ var OpenMatesWsClient = class {
|
|
|
837
1051
|
);
|
|
838
1052
|
return;
|
|
839
1053
|
}
|
|
1054
|
+
if (SUB_CHAT_EVENT_TYPES.has(type)) {
|
|
1055
|
+
handleSubChatEvent(type, p);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (type === "send_embed_data") {
|
|
1059
|
+
const embedPayload = p.payload && typeof p.payload === "object" ? p.payload : p;
|
|
1060
|
+
if (typeof embedPayload.chat_id === "string" && embedPayload.chat_id !== chatId) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
const embedId = embedPayload.embed_id;
|
|
1064
|
+
if (typeof embedId === "string" && embedId) {
|
|
1065
|
+
const status = typeof embedPayload.status === "string" ? embedPayload.status : "finished";
|
|
1066
|
+
embeds.set(embedId, embedPayload);
|
|
1067
|
+
if (status === "processing") {
|
|
1068
|
+
processingEmbedIds.add(embedId);
|
|
1069
|
+
} else {
|
|
1070
|
+
processingEmbedIds.delete(embedId);
|
|
1071
|
+
}
|
|
1072
|
+
maybeResolve();
|
|
1073
|
+
}
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
840
1076
|
if (type === "ai_message_update") {
|
|
841
1077
|
const msgId = p.user_message_id ?? p.userMessageId;
|
|
842
|
-
if (msgId !== userMessageId) return;
|
|
1078
|
+
if (msgId !== userMessageId && p.chat_id !== chatId) return;
|
|
843
1079
|
capture(p);
|
|
844
1080
|
if (typeof p.full_content_so_far === "string") {
|
|
845
1081
|
latestContent = p.full_content_so_far;
|
|
@@ -864,7 +1100,7 @@ var OpenMatesWsClient = class {
|
|
|
864
1100
|
}
|
|
865
1101
|
if (type === "ai_background_response_completed") {
|
|
866
1102
|
const msgId = p.user_message_id ?? p.userMessageId;
|
|
867
|
-
if (msgId && msgId !== userMessageId) return;
|
|
1103
|
+
if (msgId && msgId !== userMessageId && p.chat_id !== chatId) return;
|
|
868
1104
|
if (!msgId && p.chat_id !== chatId) return;
|
|
869
1105
|
capture(p);
|
|
870
1106
|
const content = typeof p.full_content === "string" ? p.full_content : latestContent;
|
|
@@ -872,6 +1108,25 @@ var OpenMatesWsClient = class {
|
|
|
872
1108
|
scheduleResolve(content);
|
|
873
1109
|
return;
|
|
874
1110
|
}
|
|
1111
|
+
if (type === "chat_message_added") {
|
|
1112
|
+
if (p.chat_id !== chatId) return;
|
|
1113
|
+
const rawMessage = p.message;
|
|
1114
|
+
if (!rawMessage || typeof rawMessage !== "object") return;
|
|
1115
|
+
const message = rawMessage;
|
|
1116
|
+
if (message.role !== "assistant") return;
|
|
1117
|
+
const content = extractMessageContent(message);
|
|
1118
|
+
if (!content) return;
|
|
1119
|
+
capture(message);
|
|
1120
|
+
if (typeof message.category === "string" && message.category) {
|
|
1121
|
+
category = message.category;
|
|
1122
|
+
}
|
|
1123
|
+
if (typeof message.model_name === "string" && message.model_name) {
|
|
1124
|
+
modelName = message.model_name;
|
|
1125
|
+
}
|
|
1126
|
+
onStream?.({ kind: "done", content, category, modelName });
|
|
1127
|
+
scheduleResolve(content);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
875
1130
|
if (type === "ai_typing_started") {
|
|
876
1131
|
capture(p);
|
|
877
1132
|
onStream?.({
|
|
@@ -895,13 +1150,7 @@ var OpenMatesWsClient = class {
|
|
|
895
1150
|
clearTimeout(postProcessingTimer);
|
|
896
1151
|
postProcessingTimer = null;
|
|
897
1152
|
}
|
|
898
|
-
|
|
899
|
-
resolve4({
|
|
900
|
-
content: latestContent,
|
|
901
|
-
category,
|
|
902
|
-
modelName,
|
|
903
|
-
followUpSuggestions
|
|
904
|
-
});
|
|
1153
|
+
finishPostProcessingWait();
|
|
905
1154
|
}
|
|
906
1155
|
return;
|
|
907
1156
|
}
|
|
@@ -916,10 +1165,14 @@ var OpenMatesWsClient = class {
|
|
|
916
1165
|
if (aiResponseDone) {
|
|
917
1166
|
cleanup();
|
|
918
1167
|
resolve4({
|
|
1168
|
+
messageId,
|
|
1169
|
+
taskId,
|
|
919
1170
|
content: latestContent,
|
|
920
1171
|
category,
|
|
921
1172
|
modelName,
|
|
922
|
-
followUpSuggestions
|
|
1173
|
+
followUpSuggestions,
|
|
1174
|
+
embeds: [...embeds.values()],
|
|
1175
|
+
subChatEvents
|
|
923
1176
|
});
|
|
924
1177
|
return;
|
|
925
1178
|
}
|
|
@@ -932,6 +1185,10 @@ var OpenMatesWsClient = class {
|
|
|
932
1185
|
clearTimeout(postProcessingTimer);
|
|
933
1186
|
postProcessingTimer = null;
|
|
934
1187
|
}
|
|
1188
|
+
if (asyncEmbedTimer) {
|
|
1189
|
+
clearTimeout(asyncEmbedTimer);
|
|
1190
|
+
asyncEmbedTimer = null;
|
|
1191
|
+
}
|
|
935
1192
|
this.socket.off("message", onMessage);
|
|
936
1193
|
this.socket.off("error", onError);
|
|
937
1194
|
this.socket.off("close", onClose);
|
|
@@ -955,6 +1212,7 @@ var CHAT_MODELS = [
|
|
|
955
1212
|
{ id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5" },
|
|
956
1213
|
{ id: "gpt-5.4", name: "GPT-5.4" },
|
|
957
1214
|
{ id: "gpt-oss-120b", name: "GPT-OSS-120b" },
|
|
1215
|
+
{ id: "gpt-oss-20b", name: "GPT-OSS-20b" },
|
|
958
1216
|
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash" },
|
|
959
1217
|
{ id: "gemini-3-pro-image-preview", name: "Gemini 3 Pro" },
|
|
960
1218
|
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro" },
|
|
@@ -1242,9 +1500,139 @@ function escapeRegExp(s) {
|
|
|
1242
1500
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1243
1501
|
}
|
|
1244
1502
|
|
|
1503
|
+
// src/embedCreator.ts
|
|
1504
|
+
import { randomUUID, createHash as createHash3, randomBytes, webcrypto as webcrypto3 } from "crypto";
|
|
1505
|
+
import { encode as toonEncode } from "@toon-format/toon";
|
|
1506
|
+
var cryptoApi3 = webcrypto3;
|
|
1507
|
+
var AES_GCM_IV_LENGTH3 = 12;
|
|
1508
|
+
function bytesToBase642(input) {
|
|
1509
|
+
return Buffer.from(input).toString("base64");
|
|
1510
|
+
}
|
|
1511
|
+
function toArrayBuffer2(input) {
|
|
1512
|
+
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
1513
|
+
}
|
|
1514
|
+
async function encryptAesGcm(plaintext, rawKeyBytes) {
|
|
1515
|
+
const iv = cryptoApi3.getRandomValues(new Uint8Array(AES_GCM_IV_LENGTH3));
|
|
1516
|
+
const key = await cryptoApi3.subtle.importKey(
|
|
1517
|
+
"raw",
|
|
1518
|
+
toArrayBuffer2(rawKeyBytes),
|
|
1519
|
+
{ name: "AES-GCM" },
|
|
1520
|
+
false,
|
|
1521
|
+
["encrypt"]
|
|
1522
|
+
);
|
|
1523
|
+
const encrypted = await cryptoApi3.subtle.encrypt(
|
|
1524
|
+
{ name: "AES-GCM", iv: toArrayBuffer2(iv) },
|
|
1525
|
+
key,
|
|
1526
|
+
new TextEncoder().encode(plaintext)
|
|
1527
|
+
);
|
|
1528
|
+
const cipherBytes = new Uint8Array(encrypted);
|
|
1529
|
+
const combined = new Uint8Array(iv.length + cipherBytes.length);
|
|
1530
|
+
combined.set(iv);
|
|
1531
|
+
combined.set(cipherBytes, iv.length);
|
|
1532
|
+
return bytesToBase642(combined);
|
|
1533
|
+
}
|
|
1534
|
+
async function wrapKey(embedKey, wrappingKey) {
|
|
1535
|
+
const cryptoKey = await cryptoApi3.subtle.importKey(
|
|
1536
|
+
"raw",
|
|
1537
|
+
toArrayBuffer2(wrappingKey),
|
|
1538
|
+
{ name: "AES-GCM" },
|
|
1539
|
+
false,
|
|
1540
|
+
["encrypt"]
|
|
1541
|
+
);
|
|
1542
|
+
const iv = cryptoApi3.getRandomValues(new Uint8Array(AES_GCM_IV_LENGTH3));
|
|
1543
|
+
const encrypted = await cryptoApi3.subtle.encrypt(
|
|
1544
|
+
{ name: "AES-GCM", iv: toArrayBuffer2(iv) },
|
|
1545
|
+
cryptoKey,
|
|
1546
|
+
toArrayBuffer2(embedKey)
|
|
1547
|
+
);
|
|
1548
|
+
const cipherBytes = new Uint8Array(encrypted);
|
|
1549
|
+
const combined = new Uint8Array(iv.length + cipherBytes.length);
|
|
1550
|
+
combined.set(iv);
|
|
1551
|
+
combined.set(cipherBytes, iv.length);
|
|
1552
|
+
return bytesToBase642(combined);
|
|
1553
|
+
}
|
|
1554
|
+
function generateEmbedKey() {
|
|
1555
|
+
return new Uint8Array(randomBytes(32));
|
|
1556
|
+
}
|
|
1557
|
+
function computeSHA256(content) {
|
|
1558
|
+
return createHash3("sha256").update(content).digest("hex");
|
|
1559
|
+
}
|
|
1560
|
+
function toonEncodeContent(data) {
|
|
1561
|
+
try {
|
|
1562
|
+
return toonEncode(data);
|
|
1563
|
+
} catch {
|
|
1564
|
+
return JSON.stringify(data);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
function generateEmbedId() {
|
|
1568
|
+
return randomUUID();
|
|
1569
|
+
}
|
|
1570
|
+
function createEmbedReferenceBlock(type, embedId, url) {
|
|
1571
|
+
const data = { type, embed_id: embedId };
|
|
1572
|
+
if (url) data.url = url;
|
|
1573
|
+
const ref = JSON.stringify(data, null, 2);
|
|
1574
|
+
return "```json\n" + ref + "\n```";
|
|
1575
|
+
}
|
|
1576
|
+
async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId) {
|
|
1577
|
+
try {
|
|
1578
|
+
const embedKey = generateEmbedKey();
|
|
1579
|
+
const encryptedContent = await encryptAesGcm(embed.content, embedKey);
|
|
1580
|
+
const encryptedType = await encryptAesGcm(embed.type, embedKey);
|
|
1581
|
+
const encryptedTextPreview = await encryptAesGcm(embed.textPreview, embedKey);
|
|
1582
|
+
const hashedEmbedId = computeSHA256(embed.embedId);
|
|
1583
|
+
const hashedChatId = computeSHA256(chatId);
|
|
1584
|
+
const hashedMessageId = computeSHA256(messageId);
|
|
1585
|
+
const hashedUserId = computeSHA256(userId);
|
|
1586
|
+
const wrappedWithMaster = await wrapKey(embedKey, masterKey);
|
|
1587
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
1588
|
+
const embedKeys = [
|
|
1589
|
+
{
|
|
1590
|
+
hashed_embed_id: hashedEmbedId,
|
|
1591
|
+
key_type: "master",
|
|
1592
|
+
hashed_chat_id: null,
|
|
1593
|
+
encrypted_embed_key: wrappedWithMaster,
|
|
1594
|
+
hashed_user_id: hashedUserId,
|
|
1595
|
+
created_at: nowSeconds
|
|
1596
|
+
}
|
|
1597
|
+
];
|
|
1598
|
+
if (chatKey) {
|
|
1599
|
+
const wrappedWithChat = await wrapKey(embedKey, chatKey);
|
|
1600
|
+
embedKeys.push({
|
|
1601
|
+
hashed_embed_id: hashedEmbedId,
|
|
1602
|
+
key_type: "chat",
|
|
1603
|
+
hashed_chat_id: hashedChatId,
|
|
1604
|
+
encrypted_embed_key: wrappedWithChat,
|
|
1605
|
+
hashed_user_id: hashedUserId,
|
|
1606
|
+
created_at: nowSeconds
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
return {
|
|
1610
|
+
embed_id: embed.embedId,
|
|
1611
|
+
encrypted_type: encryptedType,
|
|
1612
|
+
encrypted_content: encryptedContent,
|
|
1613
|
+
encrypted_text_preview: encryptedTextPreview,
|
|
1614
|
+
status: embed.status,
|
|
1615
|
+
hashed_chat_id: hashedChatId,
|
|
1616
|
+
hashed_message_id: hashedMessageId,
|
|
1617
|
+
hashed_user_id: hashedUserId,
|
|
1618
|
+
file_path: embed.filePath,
|
|
1619
|
+
content_hash: embed.contentHash,
|
|
1620
|
+
text_length_chars: embed.textLengthChars,
|
|
1621
|
+
created_at: nowSeconds,
|
|
1622
|
+
updated_at: nowSeconds,
|
|
1623
|
+
embed_keys: embedKeys
|
|
1624
|
+
};
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1627
|
+
process.stderr.write(`\x1B[31mError:\x1B[0m Failed to encrypt embed: ${msg}
|
|
1628
|
+
`);
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1245
1633
|
// src/shareEncryption.ts
|
|
1246
|
-
import { webcrypto as
|
|
1247
|
-
var crypto =
|
|
1634
|
+
import { webcrypto as webcrypto4 } from "crypto";
|
|
1635
|
+
var crypto = webcrypto4;
|
|
1248
1636
|
function base64UrlEncode(data) {
|
|
1249
1637
|
return Buffer.from(data).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1250
1638
|
}
|
|
@@ -1356,6 +1744,48 @@ function buildEmbedShareUrl(origin, embedId, blob) {
|
|
|
1356
1744
|
}
|
|
1357
1745
|
|
|
1358
1746
|
// src/client.ts
|
|
1747
|
+
function normalizeUnixSeconds(value, fallback) {
|
|
1748
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
1749
|
+
return fallback;
|
|
1750
|
+
}
|
|
1751
|
+
return value > 1e10 ? Math.floor(value / 1e3) : Math.floor(value);
|
|
1752
|
+
}
|
|
1753
|
+
function buildSubChatConfirmationPayload(params) {
|
|
1754
|
+
return {
|
|
1755
|
+
chat_id: params.chatId,
|
|
1756
|
+
task_id: params.taskId,
|
|
1757
|
+
action: params.approved ? "approve" : "cancel",
|
|
1758
|
+
approve_count: params.approved ? params.approveCount ?? null : null
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
async function buildSubChatEncryptedMetadataPayloads(params) {
|
|
1762
|
+
const createdAt = params.createdAt ?? Math.floor(Date.now() / 1e3);
|
|
1763
|
+
const payloads = [];
|
|
1764
|
+
for (const subChat of params.subChats) {
|
|
1765
|
+
const chatId = subChat.id || subChat.chat_id;
|
|
1766
|
+
const messageId = subChat.user_message_id || subChat.message_id;
|
|
1767
|
+
const prompt = subChat.prompt || "";
|
|
1768
|
+
if (!chatId || !messageId) continue;
|
|
1769
|
+
const title = prompt.substring(0, 30);
|
|
1770
|
+
payloads.push({
|
|
1771
|
+
chat_id: chatId,
|
|
1772
|
+
parent_id: params.parentChatId,
|
|
1773
|
+
is_sub_chat: true,
|
|
1774
|
+
message_id: messageId,
|
|
1775
|
+
encrypted_content: await encryptWithAesGcmCombined(prompt, params.parentChatKey),
|
|
1776
|
+
encrypted_sender_name: await encryptWithAesGcmCombined("User", params.parentChatKey),
|
|
1777
|
+
encrypted_title: await encryptWithAesGcmCombined(title, params.parentChatKey),
|
|
1778
|
+
created_at: createdAt,
|
|
1779
|
+
encrypted_chat_key: params.encryptedParentChatKey,
|
|
1780
|
+
versions: {
|
|
1781
|
+
messages_v: 1,
|
|
1782
|
+
title_v: 0,
|
|
1783
|
+
last_edited_overall_timestamp: createdAt
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
return payloads;
|
|
1788
|
+
}
|
|
1359
1789
|
var MEMORY_TYPE_REGISTRY = {
|
|
1360
1790
|
"ai/communication_style": {
|
|
1361
1791
|
appId: "ai",
|
|
@@ -1717,15 +2147,19 @@ var BLOCKED_SETTINGS_MUTATE_PATHS = /* @__PURE__ */ new Set([
|
|
|
1717
2147
|
"/v1/auth/setup_password",
|
|
1718
2148
|
"/v1/auth/2fa/setup/initiate",
|
|
1719
2149
|
"/v1/auth/2fa/setup/provider",
|
|
1720
|
-
"/v1/auth/2fa/setup/verify-signup"
|
|
2150
|
+
"/v1/auth/2fa/setup/verify-signup",
|
|
2151
|
+
"/v1/settings/delete-account",
|
|
2152
|
+
"/v1/settings/request-action-verification",
|
|
2153
|
+
"/v1/settings/verify-action-code",
|
|
2154
|
+
"/v1/settings/user/disable-2fa"
|
|
1721
2155
|
]);
|
|
1722
2156
|
var OpenMatesClient = class _OpenMatesClient {
|
|
1723
2157
|
apiUrl;
|
|
1724
2158
|
session;
|
|
1725
2159
|
http;
|
|
1726
2160
|
constructor(options = {}) {
|
|
1727
|
-
this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
1728
2161
|
const diskSession = options.session ?? this.getValidSessionFromDisk();
|
|
2162
|
+
this.apiUrl = (options.apiUrl ?? process.env.OPENMATES_API_URL ?? diskSession?.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
1729
2163
|
this.session = diskSession;
|
|
1730
2164
|
this.http = new OpenMatesHttpClient({
|
|
1731
2165
|
apiUrl: this.apiUrl,
|
|
@@ -1785,7 +2219,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
1785
2219
|
pin: pin.trim().toUpperCase(),
|
|
1786
2220
|
token
|
|
1787
2221
|
});
|
|
1788
|
-
const sessionId =
|
|
2222
|
+
const sessionId = randomUUID2();
|
|
1789
2223
|
const login = await this.http.post(
|
|
1790
2224
|
"/v1/auth/login",
|
|
1791
2225
|
{
|
|
@@ -1814,6 +2248,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
1814
2248
|
authorizerDeviceName: complete.data.authorizer_device_name ?? null,
|
|
1815
2249
|
autoLogoutMinutes: complete.data.auto_logout_minutes ?? null
|
|
1816
2250
|
};
|
|
2251
|
+
await this.hydrateEmailEncryptionKey(session);
|
|
1817
2252
|
saveSession(session);
|
|
1818
2253
|
}
|
|
1819
2254
|
async whoAmI() {
|
|
@@ -1826,6 +2261,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
1826
2261
|
if (!response.ok || !response.data.success) {
|
|
1827
2262
|
throw new Error("Session is invalid. Please run `openmates login`.");
|
|
1828
2263
|
}
|
|
2264
|
+
if (response.data.ws_token) {
|
|
2265
|
+
session.wsToken = response.data.ws_token;
|
|
2266
|
+
}
|
|
2267
|
+
session.cookies = this.http.getCookieMap();
|
|
2268
|
+
saveSession(session);
|
|
1829
2269
|
return response.data.user ?? {};
|
|
1830
2270
|
}
|
|
1831
2271
|
async logout() {
|
|
@@ -1833,7 +2273,6 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
1833
2273
|
await this.http.post("/v1/auth/logout", {}, this.getCliRequestHeaders()).catch(() => void 0);
|
|
1834
2274
|
}
|
|
1835
2275
|
clearSession();
|
|
1836
|
-
clearIncognitoHistory();
|
|
1837
2276
|
}
|
|
1838
2277
|
// -------------------------------------------------------------------------
|
|
1839
2278
|
// Chats
|
|
@@ -2280,7 +2719,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2280
2719
|
async sendMessage(params) {
|
|
2281
2720
|
let chatId;
|
|
2282
2721
|
if (!params.chatId) {
|
|
2283
|
-
chatId =
|
|
2722
|
+
chatId = randomUUID2();
|
|
2284
2723
|
} else if (params.chatId.length < 36) {
|
|
2285
2724
|
const resolved = await this.resolveFullChatId(params.chatId);
|
|
2286
2725
|
if (!resolved) {
|
|
@@ -2293,7 +2732,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2293
2732
|
chatId = params.chatId;
|
|
2294
2733
|
}
|
|
2295
2734
|
const { ws, session } = await this.openWsClient();
|
|
2296
|
-
const messageId =
|
|
2735
|
+
const messageId = randomUUID2();
|
|
2297
2736
|
const createdAt = Math.floor(Date.now() / 1e3);
|
|
2298
2737
|
const isNewChat = !params.chatId;
|
|
2299
2738
|
ws.send("set_active_chat", { chat_id: chatId });
|
|
@@ -2313,6 +2752,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2313
2752
|
};
|
|
2314
2753
|
let chatKeyBytes = null;
|
|
2315
2754
|
let encryptedChatKey = null;
|
|
2755
|
+
let baselineMessagesV = 0;
|
|
2316
2756
|
if (!params.incognito) {
|
|
2317
2757
|
const masterKey = this.getMasterKeyBytes();
|
|
2318
2758
|
if (isNewChat) {
|
|
@@ -2332,6 +2772,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2332
2772
|
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
2333
2773
|
);
|
|
2334
2774
|
if (chat) {
|
|
2775
|
+
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
2335
2776
|
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
2336
2777
|
if (encKey) {
|
|
2337
2778
|
chatKeyBytes = await decryptBytesWithAesGcm(encKey, masterKey);
|
|
@@ -2340,8 +2781,32 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2340
2781
|
}
|
|
2341
2782
|
}
|
|
2342
2783
|
}
|
|
2343
|
-
if (params.
|
|
2344
|
-
messagePayload.
|
|
2784
|
+
if (params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
2785
|
+
messagePayload.embeds = params.preparedEmbeds.map((embed) => ({
|
|
2786
|
+
embed_id: embed.embedId,
|
|
2787
|
+
type: embed.type,
|
|
2788
|
+
content: embed.content,
|
|
2789
|
+
status: embed.status,
|
|
2790
|
+
text_preview: embed.textPreview
|
|
2791
|
+
}));
|
|
2792
|
+
}
|
|
2793
|
+
const encryptedEmbeds = [...params.encryptedEmbeds ?? []];
|
|
2794
|
+
if (!params.incognito && params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
2795
|
+
const masterKey = this.getMasterKeyBytes();
|
|
2796
|
+
for (const embed of params.preparedEmbeds) {
|
|
2797
|
+
const encrypted = await encryptEmbed(
|
|
2798
|
+
embed,
|
|
2799
|
+
masterKey,
|
|
2800
|
+
chatKeyBytes,
|
|
2801
|
+
chatId,
|
|
2802
|
+
messageId,
|
|
2803
|
+
session.hashedEmail
|
|
2804
|
+
);
|
|
2805
|
+
if (encrypted) encryptedEmbeds.push(encrypted);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
if (encryptedEmbeds.length > 0) {
|
|
2809
|
+
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
2345
2810
|
}
|
|
2346
2811
|
ws.send("chat_message_added", messagePayload);
|
|
2347
2812
|
if (!params.incognito && chatKeyBytes) {
|
|
@@ -2369,38 +2834,139 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2369
2834
|
ws.send("encrypted_chat_metadata", metadataPayload);
|
|
2370
2835
|
}
|
|
2371
2836
|
let assistant = "";
|
|
2837
|
+
let assistantMessageId = null;
|
|
2372
2838
|
let category = null;
|
|
2373
2839
|
let modelName = null;
|
|
2374
2840
|
let followUpSuggestions = [];
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
}
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2841
|
+
let subChatEvents = [];
|
|
2842
|
+
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2843
|
+
const isApprovalWithinServerLimits = (request) => {
|
|
2844
|
+
const count = request.subChats.length;
|
|
2845
|
+
if (count === 0) return false;
|
|
2846
|
+
if (request.remainingSubChats !== null && count > request.remainingSubChats) {
|
|
2847
|
+
return false;
|
|
2848
|
+
}
|
|
2849
|
+
if (request.maxDirectSubChats !== null && request.existingSubChats !== null && request.existingSubChats + count > request.maxDirectSubChats) {
|
|
2850
|
+
return false;
|
|
2851
|
+
}
|
|
2852
|
+
return true;
|
|
2853
|
+
};
|
|
2854
|
+
const isAutoApprovalWithinServerLimits = (request) => {
|
|
2855
|
+
if (!isApprovalWithinServerLimits(request)) return false;
|
|
2856
|
+
return request.maxAutoSubChats === null || request.subChats.length <= request.maxAutoSubChats;
|
|
2857
|
+
};
|
|
2858
|
+
const handleSubChatEvent = async (event) => {
|
|
2859
|
+
params.onSubChatEvent?.(event);
|
|
2860
|
+
if (event.type === "spawn_sub_chats") {
|
|
2861
|
+
if (params.incognito || !chatKeyBytes) return;
|
|
2862
|
+
const rawSubChats = event.payload.sub_chats;
|
|
2863
|
+
if (!Array.isArray(rawSubChats)) return;
|
|
2864
|
+
const metadataPayloads = await buildSubChatEncryptedMetadataPayloads({
|
|
2865
|
+
parentChatId: chatId,
|
|
2866
|
+
parentChatKey: chatKeyBytes,
|
|
2867
|
+
encryptedParentChatKey: encryptedChatKey,
|
|
2868
|
+
subChats: rawSubChats
|
|
2869
|
+
});
|
|
2870
|
+
for (const metadataPayload of metadataPayloads) {
|
|
2871
|
+
await ws.sendAsync("encrypted_chat_metadata", metadataPayload);
|
|
2872
|
+
}
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
if (event.type !== "sub_chat_confirmation_required") return;
|
|
2876
|
+
const payload = event.payload;
|
|
2877
|
+
const confirmationChatId = typeof payload.chat_id === "string" ? payload.chat_id : chatId;
|
|
2878
|
+
const taskId = typeof payload.task_id === "string" ? payload.task_id : null;
|
|
2879
|
+
const subChats = Array.isArray(payload.sub_chats) ? payload.sub_chats : [];
|
|
2880
|
+
if (!taskId) return;
|
|
2881
|
+
const request = {
|
|
2882
|
+
chatId: confirmationChatId,
|
|
2883
|
+
taskId,
|
|
2884
|
+
subChats,
|
|
2885
|
+
maxAutoSubChats: numberOrNull(payload.max_auto_sub_chats),
|
|
2886
|
+
maxDirectSubChats: numberOrNull(payload.max_direct_sub_chats),
|
|
2887
|
+
existingSubChats: numberOrNull(payload.existing_sub_chats),
|
|
2888
|
+
remainingSubChats: numberOrNull(payload.remaining_sub_chats)
|
|
2889
|
+
};
|
|
2890
|
+
const withinLimits = isApprovalWithinServerLimits(request);
|
|
2891
|
+
const approved = params.autoApproveSubChats ? isAutoApprovalWithinServerLimits(request) : withinLimits && params.onSubChatApprovalRequest ? await params.onSubChatApprovalRequest(request) : false;
|
|
2892
|
+
await ws.sendAsync(
|
|
2893
|
+
"sub_chat_confirmation",
|
|
2894
|
+
buildSubChatConfirmationPayload({
|
|
2895
|
+
chatId: request.chatId,
|
|
2896
|
+
taskId: request.taskId,
|
|
2897
|
+
approved,
|
|
2898
|
+
approveCount: approved ? request.subChats.length : void 0
|
|
2899
|
+
})
|
|
2900
|
+
);
|
|
2901
|
+
};
|
|
2902
|
+
const streamOpts = {
|
|
2903
|
+
onStream: params.onStream,
|
|
2904
|
+
onSubChatEvent: handleSubChatEvent
|
|
2905
|
+
};
|
|
2906
|
+
if (params.incognito) {
|
|
2907
|
+
try {
|
|
2908
|
+
const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
|
|
2909
|
+
assistantMessageId = resp.messageId;
|
|
2910
|
+
assistant = resp.content;
|
|
2911
|
+
category = resp.category;
|
|
2912
|
+
modelName = resp.modelName;
|
|
2913
|
+
subChatEvents = resp.subChatEvents;
|
|
2914
|
+
} finally {
|
|
2915
|
+
ws.close();
|
|
2390
2916
|
}
|
|
2391
|
-
history.push({
|
|
2392
|
-
role: "assistant",
|
|
2393
|
-
content: assistant,
|
|
2394
|
-
createdAt: Date.now()
|
|
2395
|
-
});
|
|
2396
|
-
saveIncognitoHistory(history);
|
|
2397
2917
|
} else {
|
|
2398
2918
|
try {
|
|
2399
2919
|
const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
|
|
2920
|
+
assistantMessageId = resp.messageId;
|
|
2400
2921
|
assistant = resp.content;
|
|
2401
2922
|
category = resp.category;
|
|
2402
2923
|
modelName = resp.modelName;
|
|
2403
2924
|
followUpSuggestions = resp.followUpSuggestions;
|
|
2925
|
+
subChatEvents = resp.subChatEvents;
|
|
2926
|
+
if (chatKeyBytes && assistant) {
|
|
2927
|
+
const completedAt = Math.floor(Date.now() / 1e3);
|
|
2928
|
+
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
2929
|
+
assistant,
|
|
2930
|
+
chatKeyBytes
|
|
2931
|
+
);
|
|
2932
|
+
const encryptedCategory = category ? await encryptWithAesGcmCombined(category, chatKeyBytes) : void 0;
|
|
2933
|
+
const encryptedModelName = modelName ? await encryptWithAesGcmCombined(modelName, chatKeyBytes) : void 0;
|
|
2934
|
+
const persistedAssistantMessageId = assistantMessageId ?? randomUUID2();
|
|
2935
|
+
ws.send("ai_response_completed", {
|
|
2936
|
+
chat_id: chatId,
|
|
2937
|
+
message: {
|
|
2938
|
+
message_id: persistedAssistantMessageId,
|
|
2939
|
+
chat_id: chatId,
|
|
2940
|
+
role: "assistant",
|
|
2941
|
+
created_at: completedAt,
|
|
2942
|
+
status: "synced",
|
|
2943
|
+
user_message_id: messageId,
|
|
2944
|
+
encrypted_content: encryptedAssistantContent,
|
|
2945
|
+
encrypted_category: encryptedCategory,
|
|
2946
|
+
encrypted_model_name: encryptedModelName
|
|
2947
|
+
},
|
|
2948
|
+
versions: {
|
|
2949
|
+
messages_v: baselineMessagesV + 2,
|
|
2950
|
+
last_edited_overall_timestamp: completedAt
|
|
2951
|
+
}
|
|
2952
|
+
});
|
|
2953
|
+
await ws.waitForMessage(
|
|
2954
|
+
"ai_response_storage_confirmed",
|
|
2955
|
+
(payload) => {
|
|
2956
|
+
const p = payload;
|
|
2957
|
+
return p.message_id === persistedAssistantMessageId;
|
|
2958
|
+
},
|
|
2959
|
+
2e4
|
|
2960
|
+
);
|
|
2961
|
+
await this.persistStreamedEmbeds({
|
|
2962
|
+
ws,
|
|
2963
|
+
embeds: resp.embeds,
|
|
2964
|
+
chatId,
|
|
2965
|
+
chatKeyBytes,
|
|
2966
|
+
fallbackMessageId: persistedAssistantMessageId
|
|
2967
|
+
});
|
|
2968
|
+
clearSyncCache();
|
|
2969
|
+
}
|
|
2404
2970
|
} finally {
|
|
2405
2971
|
ws.close();
|
|
2406
2972
|
}
|
|
@@ -2412,14 +2978,113 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2412
2978
|
category,
|
|
2413
2979
|
modelName,
|
|
2414
2980
|
mateName,
|
|
2415
|
-
followUpSuggestions
|
|
2981
|
+
followUpSuggestions,
|
|
2982
|
+
subChatEvents
|
|
2416
2983
|
};
|
|
2417
2984
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2985
|
+
async persistStreamedEmbeds(params) {
|
|
2986
|
+
const finalized = new Map(
|
|
2987
|
+
params.embeds.filter((embed) => {
|
|
2988
|
+
const status = embed.status ?? "finished";
|
|
2989
|
+
return embed.embed_id && embed.content && status !== "processing" && status !== "error" && status !== "cancelled";
|
|
2990
|
+
}).map((embed) => [embed.embed_id, embed])
|
|
2991
|
+
);
|
|
2992
|
+
if (finalized.size === 0) return;
|
|
2993
|
+
const session = this.requireSession();
|
|
2994
|
+
const masterKey = this.getMasterKeyBytes();
|
|
2995
|
+
const parentKeys = /* @__PURE__ */ new Map();
|
|
2996
|
+
const processed = /* @__PURE__ */ new Set();
|
|
2997
|
+
const makeKey = () => {
|
|
2998
|
+
const key = new Uint8Array(32);
|
|
2999
|
+
globalThis.crypto.getRandomValues(key);
|
|
3000
|
+
return key;
|
|
3001
|
+
};
|
|
3002
|
+
const persistOne = async (embed, embedKey, isChild) => {
|
|
3003
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3004
|
+
const createdAt = normalizeUnixSeconds(embed.createdAt, now);
|
|
3005
|
+
const updatedAt = normalizeUnixSeconds(embed.updatedAt, now);
|
|
3006
|
+
const messageId = embed.message_id || params.fallbackMessageId;
|
|
3007
|
+
const userId = embed.user_id || session.hashedEmail;
|
|
3008
|
+
const hashedChatId = computeSHA256(params.chatId);
|
|
3009
|
+
const hashedMessageId = computeSHA256(messageId);
|
|
3010
|
+
const hashedUserId = computeSHA256(userId);
|
|
3011
|
+
const hashedEmbedId = computeSHA256(embed.embed_id);
|
|
3012
|
+
const encryptedContent = await encryptWithAesGcmCombined(
|
|
3013
|
+
embed.content ?? "",
|
|
3014
|
+
embedKey
|
|
3015
|
+
);
|
|
3016
|
+
const encryptedType = await encryptWithAesGcmCombined(
|
|
3017
|
+
embed.type || "app_skill_use",
|
|
3018
|
+
embedKey
|
|
3019
|
+
);
|
|
3020
|
+
const encryptedTextPreview = embed.text_preview ? await encryptWithAesGcmCombined(embed.text_preview, embedKey) : void 0;
|
|
3021
|
+
await params.ws.sendAsync("store_embed", {
|
|
3022
|
+
embed_id: embed.embed_id,
|
|
3023
|
+
encrypted_type: encryptedType,
|
|
3024
|
+
encrypted_content: encryptedContent,
|
|
3025
|
+
encrypted_text_preview: encryptedTextPreview,
|
|
3026
|
+
status: embed.status || "finished",
|
|
3027
|
+
hashed_chat_id: hashedChatId,
|
|
3028
|
+
hashed_message_id: hashedMessageId,
|
|
3029
|
+
hashed_task_id: embed.task_id ? computeSHA256(embed.task_id) : void 0,
|
|
3030
|
+
hashed_user_id: hashedUserId,
|
|
3031
|
+
embed_ids: embed.embed_ids,
|
|
3032
|
+
parent_embed_id: embed.parent_embed_id || void 0,
|
|
3033
|
+
version_number: embed.version_number,
|
|
3034
|
+
file_path: embed.file_path,
|
|
3035
|
+
content_hash: embed.content_hash,
|
|
3036
|
+
text_length_chars: embed.text_length_chars,
|
|
3037
|
+
is_private: embed.is_private ?? false,
|
|
3038
|
+
is_shared: embed.is_shared ?? false,
|
|
3039
|
+
created_at: createdAt,
|
|
3040
|
+
updated_at: updatedAt
|
|
3041
|
+
});
|
|
3042
|
+
if (!isChild) {
|
|
3043
|
+
const keys = [
|
|
3044
|
+
{
|
|
3045
|
+
hashed_embed_id: hashedEmbedId,
|
|
3046
|
+
key_type: "master",
|
|
3047
|
+
hashed_chat_id: null,
|
|
3048
|
+
encrypted_embed_key: await encryptBytesWithAesGcm(embedKey, masterKey),
|
|
3049
|
+
hashed_user_id: hashedUserId,
|
|
3050
|
+
created_at: now
|
|
3051
|
+
},
|
|
3052
|
+
{
|
|
3053
|
+
hashed_embed_id: hashedEmbedId,
|
|
3054
|
+
key_type: "chat",
|
|
3055
|
+
hashed_chat_id: hashedChatId,
|
|
3056
|
+
encrypted_embed_key: await encryptBytesWithAesGcm(
|
|
3057
|
+
embedKey,
|
|
3058
|
+
params.chatKeyBytes
|
|
3059
|
+
),
|
|
3060
|
+
hashed_user_id: hashedUserId,
|
|
3061
|
+
created_at: now
|
|
3062
|
+
}
|
|
3063
|
+
];
|
|
3064
|
+
await params.ws.sendAsync("store_embed_keys", { keys });
|
|
3065
|
+
parentKeys.set(embed.embed_id, embedKey);
|
|
3066
|
+
}
|
|
3067
|
+
};
|
|
3068
|
+
let madeProgress = true;
|
|
3069
|
+
while (processed.size < finalized.size && madeProgress) {
|
|
3070
|
+
madeProgress = false;
|
|
3071
|
+
for (const embed of finalized.values()) {
|
|
3072
|
+
if (processed.has(embed.embed_id)) continue;
|
|
3073
|
+
const parentId = embed.parent_embed_id || null;
|
|
3074
|
+
if (parentId && finalized.has(parentId) && !parentKeys.has(parentId)) {
|
|
3075
|
+
continue;
|
|
3076
|
+
}
|
|
3077
|
+
const parentKey = parentId ? parentKeys.get(parentId) : void 0;
|
|
3078
|
+
await persistOne(embed, parentKey ?? makeKey(), Boolean(parentKey));
|
|
3079
|
+
processed.add(embed.embed_id);
|
|
3080
|
+
madeProgress = true;
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
for (const embed of finalized.values()) {
|
|
3084
|
+
if (processed.has(embed.embed_id)) continue;
|
|
3085
|
+
await persistOne(embed, makeKey(), false);
|
|
3086
|
+
processed.add(embed.embed_id);
|
|
3087
|
+
}
|
|
2423
3088
|
}
|
|
2424
3089
|
/**
|
|
2425
3090
|
* Delete a chat by ID.
|
|
@@ -2733,7 +3398,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2733
3398
|
}
|
|
2734
3399
|
return response.data;
|
|
2735
3400
|
}
|
|
2736
|
-
async settingsDelete(path) {
|
|
3401
|
+
async settingsDelete(path, body) {
|
|
2737
3402
|
this.requireSession();
|
|
2738
3403
|
const normalizedPath = this.normalizePath(path);
|
|
2739
3404
|
if (BLOCKED_SETTINGS_MUTATE_PATHS.has(normalizedPath)) {
|
|
@@ -2741,6 +3406,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2741
3406
|
}
|
|
2742
3407
|
const response = await this.http.delete(
|
|
2743
3408
|
normalizedPath,
|
|
3409
|
+
body,
|
|
2744
3410
|
this.getCliRequestHeaders()
|
|
2745
3411
|
);
|
|
2746
3412
|
if (!response.ok) {
|
|
@@ -2792,6 +3458,149 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2792
3458
|
}
|
|
2793
3459
|
return response.data;
|
|
2794
3460
|
}
|
|
3461
|
+
async listInvoices() {
|
|
3462
|
+
this.requireSession();
|
|
3463
|
+
const response = await this.http.get(
|
|
3464
|
+
"/v1/payments/invoices",
|
|
3465
|
+
this.getCliRequestHeaders()
|
|
3466
|
+
);
|
|
3467
|
+
if (!response.ok) {
|
|
3468
|
+
throw new Error(`Failed to fetch invoices (HTTP ${response.status})`);
|
|
3469
|
+
}
|
|
3470
|
+
return { invoices: response.data.invoices ?? [] };
|
|
3471
|
+
}
|
|
3472
|
+
async downloadInvoice(invoiceId) {
|
|
3473
|
+
return this.downloadPaymentPdf(
|
|
3474
|
+
`/v1/payments/invoices/${encodeURIComponent(invoiceId)}/download`,
|
|
3475
|
+
`Invoice_${invoiceId}.pdf`
|
|
3476
|
+
);
|
|
3477
|
+
}
|
|
3478
|
+
async downloadCreditNote(invoiceId) {
|
|
3479
|
+
return this.downloadPaymentPdf(
|
|
3480
|
+
`/v1/payments/invoices/${encodeURIComponent(invoiceId)}/credit-note/download`,
|
|
3481
|
+
`CreditNote_${invoiceId}.pdf`
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3484
|
+
async requestRefund(invoiceId) {
|
|
3485
|
+
const session = this.requireSession();
|
|
3486
|
+
const emailEncryptionKey = await this.ensureEmailEncryptionKey(session);
|
|
3487
|
+
const response = await this.http.post(
|
|
3488
|
+
"/v1/payments/refund",
|
|
3489
|
+
{ invoice_id: invoiceId, email_encryption_key: emailEncryptionKey },
|
|
3490
|
+
this.getCliRequestHeaders()
|
|
3491
|
+
);
|
|
3492
|
+
if (!response.ok) {
|
|
3493
|
+
throw new Error(`Refund request failed (HTTP ${response.status})`);
|
|
3494
|
+
}
|
|
3495
|
+
return response.data;
|
|
3496
|
+
}
|
|
3497
|
+
async updateUsername(username) {
|
|
3498
|
+
return this.settingsPost("user/username", { username });
|
|
3499
|
+
}
|
|
3500
|
+
async updateProfileImage(filePath) {
|
|
3501
|
+
const { uploadProfileImage } = await import("./uploadService-S464XJRA.js");
|
|
3502
|
+
const result = await uploadProfileImage(filePath, this.requireSession());
|
|
3503
|
+
if (result.status === "rejected") {
|
|
3504
|
+
throw new Error(result.detail ?? "Profile image rejected by content safety checks.");
|
|
3505
|
+
}
|
|
3506
|
+
if (result.status === "account_deleted") {
|
|
3507
|
+
throw new Error("Account deleted due to repeated profile image policy violations.");
|
|
3508
|
+
}
|
|
3509
|
+
if (result.status !== "ok") {
|
|
3510
|
+
throw new Error(result.detail ?? `Profile image upload failed with status '${result.status}'.`);
|
|
3511
|
+
}
|
|
3512
|
+
return result;
|
|
3513
|
+
}
|
|
3514
|
+
async getNewsletterCategories() {
|
|
3515
|
+
this.requireSession();
|
|
3516
|
+
const response = await this.http.get(
|
|
3517
|
+
"/v1/newsletter/categories",
|
|
3518
|
+
this.getCliRequestHeaders()
|
|
3519
|
+
);
|
|
3520
|
+
if (!response.ok) {
|
|
3521
|
+
throw new Error(`Failed to fetch newsletter categories (HTTP ${response.status})`);
|
|
3522
|
+
}
|
|
3523
|
+
return response.data;
|
|
3524
|
+
}
|
|
3525
|
+
async updateNewsletterCategories(categories) {
|
|
3526
|
+
this.requireSession();
|
|
3527
|
+
const response = await this.http.patch(
|
|
3528
|
+
"/v1/newsletter/categories",
|
|
3529
|
+
{ categories },
|
|
3530
|
+
this.getCliRequestHeaders()
|
|
3531
|
+
);
|
|
3532
|
+
if (!response.ok) {
|
|
3533
|
+
throw new Error(`Failed to update newsletter categories (HTTP ${response.status})`);
|
|
3534
|
+
}
|
|
3535
|
+
return response.data;
|
|
3536
|
+
}
|
|
3537
|
+
async subscribeNewsletter(email, language = "en", darkmode = false) {
|
|
3538
|
+
const response = await this.http.post(
|
|
3539
|
+
"/v1/newsletter/subscribe",
|
|
3540
|
+
{ email, language, darkmode },
|
|
3541
|
+
this.getCliRequestHeaders()
|
|
3542
|
+
);
|
|
3543
|
+
if (!response.ok) {
|
|
3544
|
+
throw new Error(`Newsletter subscribe failed (HTTP ${response.status})`);
|
|
3545
|
+
}
|
|
3546
|
+
return response.data;
|
|
3547
|
+
}
|
|
3548
|
+
async confirmNewsletter(token) {
|
|
3549
|
+
const response = await this.http.get(
|
|
3550
|
+
`/v1/newsletter/confirm/${encodeURIComponent(token)}`,
|
|
3551
|
+
this.getCliRequestHeaders()
|
|
3552
|
+
);
|
|
3553
|
+
if (!response.ok) {
|
|
3554
|
+
throw new Error(`Newsletter confirmation failed (HTTP ${response.status})`);
|
|
3555
|
+
}
|
|
3556
|
+
return response.data;
|
|
3557
|
+
}
|
|
3558
|
+
async unsubscribeNewsletter(token) {
|
|
3559
|
+
const response = await this.http.get(
|
|
3560
|
+
`/v1/newsletter/unsubscribe/${encodeURIComponent(token)}`,
|
|
3561
|
+
this.getCliRequestHeaders()
|
|
3562
|
+
);
|
|
3563
|
+
if (!response.ok) {
|
|
3564
|
+
throw new Error(`Newsletter unsubscribe failed (HTTP ${response.status})`);
|
|
3565
|
+
}
|
|
3566
|
+
return response.data;
|
|
3567
|
+
}
|
|
3568
|
+
async updateEmailNotificationSettings(payload) {
|
|
3569
|
+
const { ws } = await this.openWsClient();
|
|
3570
|
+
try {
|
|
3571
|
+
const ackPromise = ws.waitForMessage("email_notification_settings_ack");
|
|
3572
|
+
ws.send("email_notification_settings", payload);
|
|
3573
|
+
const ack = await ackPromise;
|
|
3574
|
+
return ack.payload;
|
|
3575
|
+
} finally {
|
|
3576
|
+
ws.close();
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
async listNotifications(limit = 50) {
|
|
3580
|
+
this.requireSession();
|
|
3581
|
+
const response = await this.http.get(
|
|
3582
|
+
`/v1/notifications?limit=${encodeURIComponent(String(limit))}`,
|
|
3583
|
+
this.getCliRequestHeaders()
|
|
3584
|
+
);
|
|
3585
|
+
if (!response.ok) {
|
|
3586
|
+
throw new Error(`Failed to fetch notifications (HTTP ${response.status})`);
|
|
3587
|
+
}
|
|
3588
|
+
return response.data;
|
|
3589
|
+
}
|
|
3590
|
+
async *streamNotifications() {
|
|
3591
|
+
this.requireSession();
|
|
3592
|
+
for await (const message of this.http.streamSse(
|
|
3593
|
+
"/v1/notifications/stream",
|
|
3594
|
+
this.getCliRequestHeaders()
|
|
3595
|
+
)) {
|
|
3596
|
+
if (message.event && message.event !== "notification") continue;
|
|
3597
|
+
try {
|
|
3598
|
+
yield JSON.parse(message.data);
|
|
3599
|
+
} catch {
|
|
3600
|
+
yield { event: message.event ?? "message", data: message.data };
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
2795
3604
|
// -------------------------------------------------------------------------
|
|
2796
3605
|
// Daily Inspirations
|
|
2797
3606
|
// -------------------------------------------------------------------------
|
|
@@ -3051,7 +3860,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
3051
3860
|
);
|
|
3052
3861
|
}
|
|
3053
3862
|
}
|
|
3054
|
-
const entryId = params.entryId ??
|
|
3863
|
+
const entryId = params.entryId ?? randomUUID2();
|
|
3055
3864
|
const now = Math.floor(Date.now() / 1e3);
|
|
3056
3865
|
const hashedKey = hashItemKey(params.appId, params.itemType);
|
|
3057
3866
|
const plaintextPayload = {
|
|
@@ -3256,6 +4065,45 @@ Required: ${schema.required.join(", ")}`
|
|
|
3256
4065
|
if (path.startsWith("/")) return path;
|
|
3257
4066
|
return `/v1/settings/${path}`;
|
|
3258
4067
|
}
|
|
4068
|
+
async downloadPaymentPdf(path, fallbackFilename) {
|
|
4069
|
+
this.requireSession();
|
|
4070
|
+
const response = await this.http.getBinary(path, this.getCliRequestHeaders());
|
|
4071
|
+
if (!response.ok) {
|
|
4072
|
+
throw new Error(`Download failed (HTTP ${response.status})`);
|
|
4073
|
+
}
|
|
4074
|
+
return {
|
|
4075
|
+
filename: filenameFromContentDisposition(response.headers.get("content-disposition")) ?? fallbackFilename,
|
|
4076
|
+
data: response.data
|
|
4077
|
+
};
|
|
4078
|
+
}
|
|
4079
|
+
async ensureEmailEncryptionKey(session) {
|
|
4080
|
+
if (session.emailEncryptionKeyB64) return session.emailEncryptionKeyB64;
|
|
4081
|
+
await this.hydrateEmailEncryptionKey(session);
|
|
4082
|
+
if (session.emailEncryptionKeyB64) return session.emailEncryptionKeyB64;
|
|
4083
|
+
throw new Error(
|
|
4084
|
+
"Email encryption key is missing. Run `openmates login` again to refresh your local encryption keys."
|
|
4085
|
+
);
|
|
4086
|
+
}
|
|
4087
|
+
async hydrateEmailEncryptionKey(session) {
|
|
4088
|
+
const response = await this.http.post(
|
|
4089
|
+
"/v1/auth/session",
|
|
4090
|
+
{ session_id: session.sessionId },
|
|
4091
|
+
this.getCliRequestHeaders()
|
|
4092
|
+
);
|
|
4093
|
+
const encryptedEmail = response.data.user?.encrypted_email_with_master_key;
|
|
4094
|
+
if (!response.ok || !response.data.success || typeof encryptedEmail !== "string") {
|
|
4095
|
+
return;
|
|
4096
|
+
}
|
|
4097
|
+
const email = await decryptWithAesGcmCombined(
|
|
4098
|
+
encryptedEmail,
|
|
4099
|
+
base64ToBytes(session.masterKeyExportedB64)
|
|
4100
|
+
);
|
|
4101
|
+
if (!email) return;
|
|
4102
|
+
session.emailEncryptionKeyB64 = await deriveEmailEncryptionKeyB64(
|
|
4103
|
+
email,
|
|
4104
|
+
session.userEmailSalt
|
|
4105
|
+
);
|
|
4106
|
+
}
|
|
3259
4107
|
requireSession() {
|
|
3260
4108
|
if (!this.session) {
|
|
3261
4109
|
throw new Error("Not logged in. Run `openmates login`.");
|
|
@@ -3284,7 +4132,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
3284
4132
|
makeWsClient(session) {
|
|
3285
4133
|
return new OpenMatesWsClient({
|
|
3286
4134
|
apiUrl: session.apiUrl,
|
|
3287
|
-
sessionId:
|
|
4135
|
+
sessionId: randomUUID2(),
|
|
3288
4136
|
wsToken: session.wsToken,
|
|
3289
4137
|
refreshToken: session.cookies.auth_refresh_token ?? null,
|
|
3290
4138
|
// Same User-Agent as login so OS-based device fingerprint hash matches.
|
|
@@ -3631,6 +4479,15 @@ function parseNewChatSuggestionText(text) {
|
|
|
3631
4479
|
const skillId = raw.slice(dashIdx + 1);
|
|
3632
4480
|
return { body, appId, skillId };
|
|
3633
4481
|
}
|
|
4482
|
+
function filenameFromContentDisposition(header2) {
|
|
4483
|
+
if (!header2) return null;
|
|
4484
|
+
const encoded = /filename\*=UTF-8''([^;]+)/i.exec(header2)?.[1];
|
|
4485
|
+
if (encoded) return decodeURIComponent(encoded);
|
|
4486
|
+
const quoted = /filename="([^"]+)"/i.exec(header2)?.[1];
|
|
4487
|
+
if (quoted) return quoted;
|
|
4488
|
+
const plain = /filename=([^;]+)/i.exec(header2)?.[1];
|
|
4489
|
+
return plain?.trim() ?? null;
|
|
4490
|
+
}
|
|
3634
4491
|
function sleep(ms) {
|
|
3635
4492
|
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
3636
4493
|
}
|
|
@@ -3646,8 +4503,11 @@ function printLogo() {
|
|
|
3646
4503
|
stdout.write(lines.join("\n") + "\n");
|
|
3647
4504
|
}
|
|
3648
4505
|
|
|
4506
|
+
// src/cli.ts
|
|
4507
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
4508
|
+
|
|
3649
4509
|
// ../secret-scanner/src/registry.ts
|
|
3650
|
-
import { createRequire } from "module";
|
|
4510
|
+
import { createRequire as createRequire2 } from "module";
|
|
3651
4511
|
|
|
3652
4512
|
// ../secret-scanner/src/types.ts
|
|
3653
4513
|
var SECRET_ENV_PATTERNS = [
|
|
@@ -3781,8 +4641,8 @@ var SECRET_PATTERNS = [
|
|
|
3781
4641
|
];
|
|
3782
4642
|
|
|
3783
4643
|
// ../secret-scanner/src/registry.ts
|
|
3784
|
-
var
|
|
3785
|
-
var AhoCorasick =
|
|
4644
|
+
var require3 = createRequire2(import.meta.url);
|
|
4645
|
+
var AhoCorasick = require3("ahocorasick");
|
|
3786
4646
|
var MIN_SECRET_LENGTH = 8;
|
|
3787
4647
|
var SecretRegistry = class {
|
|
3788
4648
|
/** Map from plaintext value → registry entry */
|
|
@@ -3920,8 +4780,8 @@ var SecretRegistry = class {
|
|
|
3920
4780
|
const sortedResults = [];
|
|
3921
4781
|
for (const result of results) {
|
|
3922
4782
|
const endIdx = result[0];
|
|
3923
|
-
const
|
|
3924
|
-
for (const match of
|
|
4783
|
+
const matches2 = result[1];
|
|
4784
|
+
for (const match of matches2) {
|
|
3925
4785
|
sortedResults.push({ endIndex: endIdx, match });
|
|
3926
4786
|
}
|
|
3927
4787
|
}
|
|
@@ -4324,228 +5184,108 @@ import { readFileSync as readFileSync3, statSync, existsSync as existsSync3 } fr
|
|
|
4324
5184
|
import { basename, extname, resolve } from "path";
|
|
4325
5185
|
import { homedir as homedir3 } from "os";
|
|
4326
5186
|
import { createHash as createHash4 } from "crypto";
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
created_at: nowSeconds
|
|
4430
|
-
});
|
|
4431
|
-
}
|
|
4432
|
-
return {
|
|
4433
|
-
embed_id: embed.embedId,
|
|
4434
|
-
encrypted_type: encryptedType,
|
|
4435
|
-
encrypted_content: encryptedContent,
|
|
4436
|
-
encrypted_text_preview: encryptedTextPreview,
|
|
4437
|
-
status: embed.status,
|
|
4438
|
-
hashed_chat_id: hashedChatId,
|
|
4439
|
-
hashed_message_id: hashedMessageId,
|
|
4440
|
-
hashed_user_id: hashedUserId,
|
|
4441
|
-
file_path: embed.filePath,
|
|
4442
|
-
content_hash: embed.contentHash,
|
|
4443
|
-
text_length_chars: embed.textLengthChars,
|
|
4444
|
-
created_at: nowSeconds,
|
|
4445
|
-
updated_at: nowSeconds,
|
|
4446
|
-
embed_keys: embedKeys
|
|
4447
|
-
};
|
|
4448
|
-
} catch (error) {
|
|
4449
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
4450
|
-
process.stderr.write(`\x1B[31mError:\x1B[0m Failed to encrypt embed: ${msg}
|
|
4451
|
-
`);
|
|
4452
|
-
return null;
|
|
4453
|
-
}
|
|
4454
|
-
}
|
|
4455
|
-
|
|
4456
|
-
// src/fileEmbed.ts
|
|
4457
|
-
var MAX_PER_FILE_SIZE = 100 * 1024 * 1024;
|
|
4458
|
-
var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4459
|
-
".pem",
|
|
4460
|
-
".key",
|
|
4461
|
-
".p12",
|
|
4462
|
-
".pfx",
|
|
4463
|
-
".keystore",
|
|
4464
|
-
".kdbx",
|
|
4465
|
-
".credentials"
|
|
4466
|
-
]);
|
|
4467
|
-
var BLOCKED_NAMES = /* @__PURE__ */ new Set([
|
|
4468
|
-
"id_rsa",
|
|
4469
|
-
"id_ed25519",
|
|
4470
|
-
"id_dsa",
|
|
4471
|
-
"id_ecdsa",
|
|
4472
|
-
"authorized_keys",
|
|
4473
|
-
"known_hosts",
|
|
4474
|
-
".git-credentials",
|
|
4475
|
-
".netrc",
|
|
4476
|
-
".pgpass",
|
|
4477
|
-
".my.cnf"
|
|
4478
|
-
]);
|
|
4479
|
-
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4480
|
-
"py",
|
|
4481
|
-
"js",
|
|
4482
|
-
"ts",
|
|
4483
|
-
"html",
|
|
4484
|
-
"css",
|
|
4485
|
-
"json",
|
|
4486
|
-
"svelte",
|
|
4487
|
-
"java",
|
|
4488
|
-
"cpp",
|
|
4489
|
-
"c",
|
|
4490
|
-
"h",
|
|
4491
|
-
"hpp",
|
|
4492
|
-
"rs",
|
|
4493
|
-
"go",
|
|
4494
|
-
"rb",
|
|
4495
|
-
"php",
|
|
4496
|
-
"swift",
|
|
4497
|
-
"kt",
|
|
4498
|
-
"txt",
|
|
4499
|
-
"md",
|
|
4500
|
-
"xml",
|
|
4501
|
-
"yaml",
|
|
4502
|
-
"yml",
|
|
4503
|
-
"sh",
|
|
4504
|
-
"bash",
|
|
4505
|
-
"sql",
|
|
4506
|
-
"vue",
|
|
4507
|
-
"jsx",
|
|
4508
|
-
"tsx",
|
|
4509
|
-
"scss",
|
|
4510
|
-
"less",
|
|
4511
|
-
"sass",
|
|
4512
|
-
"dockerfile",
|
|
4513
|
-
"toml",
|
|
4514
|
-
"ini",
|
|
4515
|
-
"cfg",
|
|
4516
|
-
"conf",
|
|
4517
|
-
"env",
|
|
4518
|
-
"envrc",
|
|
4519
|
-
"graphql",
|
|
4520
|
-
"gql",
|
|
4521
|
-
"r",
|
|
4522
|
-
"m",
|
|
4523
|
-
"pl",
|
|
4524
|
-
"lua",
|
|
4525
|
-
"ex",
|
|
4526
|
-
"exs",
|
|
4527
|
-
"erl",
|
|
4528
|
-
"hs",
|
|
4529
|
-
"scala",
|
|
4530
|
-
"dart",
|
|
4531
|
-
"tf"
|
|
4532
|
-
]);
|
|
4533
|
-
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4534
|
-
"jpg",
|
|
4535
|
-
"jpeg",
|
|
4536
|
-
"png",
|
|
4537
|
-
"webp",
|
|
4538
|
-
"gif",
|
|
4539
|
-
"heic",
|
|
4540
|
-
"heif",
|
|
4541
|
-
"bmp",
|
|
4542
|
-
"tiff",
|
|
4543
|
-
"tif",
|
|
4544
|
-
"svg"
|
|
4545
|
-
]);
|
|
4546
|
-
function isEnvFile(filename) {
|
|
4547
|
-
const lower = filename.toLowerCase();
|
|
4548
|
-
return lower === ".env" || lower.startsWith(".env.") || lower === ".envrc";
|
|
5187
|
+
var MAX_PER_FILE_SIZE = 100 * 1024 * 1024;
|
|
5188
|
+
var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5189
|
+
".pem",
|
|
5190
|
+
".key",
|
|
5191
|
+
".p12",
|
|
5192
|
+
".pfx",
|
|
5193
|
+
".keystore",
|
|
5194
|
+
".kdbx",
|
|
5195
|
+
".credentials"
|
|
5196
|
+
]);
|
|
5197
|
+
var BLOCKED_NAMES = /* @__PURE__ */ new Set([
|
|
5198
|
+
"id_rsa",
|
|
5199
|
+
"id_ed25519",
|
|
5200
|
+
"id_dsa",
|
|
5201
|
+
"id_ecdsa",
|
|
5202
|
+
"authorized_keys",
|
|
5203
|
+
"known_hosts",
|
|
5204
|
+
".git-credentials",
|
|
5205
|
+
".netrc",
|
|
5206
|
+
".pgpass",
|
|
5207
|
+
".my.cnf"
|
|
5208
|
+
]);
|
|
5209
|
+
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5210
|
+
"py",
|
|
5211
|
+
"js",
|
|
5212
|
+
"ts",
|
|
5213
|
+
"html",
|
|
5214
|
+
"css",
|
|
5215
|
+
"json",
|
|
5216
|
+
"svelte",
|
|
5217
|
+
"java",
|
|
5218
|
+
"cpp",
|
|
5219
|
+
"c",
|
|
5220
|
+
"h",
|
|
5221
|
+
"hpp",
|
|
5222
|
+
"rs",
|
|
5223
|
+
"go",
|
|
5224
|
+
"rb",
|
|
5225
|
+
"php",
|
|
5226
|
+
"swift",
|
|
5227
|
+
"kt",
|
|
5228
|
+
"txt",
|
|
5229
|
+
"md",
|
|
5230
|
+
"xml",
|
|
5231
|
+
"yaml",
|
|
5232
|
+
"yml",
|
|
5233
|
+
"sh",
|
|
5234
|
+
"bash",
|
|
5235
|
+
"sql",
|
|
5236
|
+
"vue",
|
|
5237
|
+
"jsx",
|
|
5238
|
+
"tsx",
|
|
5239
|
+
"scss",
|
|
5240
|
+
"less",
|
|
5241
|
+
"sass",
|
|
5242
|
+
"dockerfile",
|
|
5243
|
+
"toml",
|
|
5244
|
+
"ini",
|
|
5245
|
+
"cfg",
|
|
5246
|
+
"conf",
|
|
5247
|
+
"env",
|
|
5248
|
+
"envrc",
|
|
5249
|
+
"graphql",
|
|
5250
|
+
"gql",
|
|
5251
|
+
"r",
|
|
5252
|
+
"m",
|
|
5253
|
+
"pl",
|
|
5254
|
+
"lua",
|
|
5255
|
+
"ex",
|
|
5256
|
+
"exs",
|
|
5257
|
+
"erl",
|
|
5258
|
+
"hs",
|
|
5259
|
+
"scala",
|
|
5260
|
+
"dart",
|
|
5261
|
+
"tf"
|
|
5262
|
+
]);
|
|
5263
|
+
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5264
|
+
"jpg",
|
|
5265
|
+
"jpeg",
|
|
5266
|
+
"png",
|
|
5267
|
+
"webp",
|
|
5268
|
+
"gif",
|
|
5269
|
+
"heic",
|
|
5270
|
+
"heif",
|
|
5271
|
+
"bmp",
|
|
5272
|
+
"tiff",
|
|
5273
|
+
"tif",
|
|
5274
|
+
"svg"
|
|
5275
|
+
]);
|
|
5276
|
+
var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5277
|
+
"mp3",
|
|
5278
|
+
"m4a",
|
|
5279
|
+
"mp4",
|
|
5280
|
+
"wav",
|
|
5281
|
+
"webm",
|
|
5282
|
+
"ogg",
|
|
5283
|
+
"oga",
|
|
5284
|
+
"aac"
|
|
5285
|
+
]);
|
|
5286
|
+
function isEnvFile(filename) {
|
|
5287
|
+
const lower = filename.toLowerCase();
|
|
5288
|
+
return lower === ".env" || lower.startsWith(".env.") || lower === ".envrc";
|
|
4549
5289
|
}
|
|
4550
5290
|
function getExt(filename) {
|
|
4551
5291
|
const ext = extname(filename).toLowerCase();
|
|
@@ -4563,6 +5303,9 @@ function isImageFile(filename) {
|
|
|
4563
5303
|
function isPDFFile(filename) {
|
|
4564
5304
|
return getExt(filename) === "pdf";
|
|
4565
5305
|
}
|
|
5306
|
+
function isAudioFile(filename) {
|
|
5307
|
+
return AUDIO_EXTENSIONS.has(getExt(filename));
|
|
5308
|
+
}
|
|
4566
5309
|
var LANGUAGE_MAP = {
|
|
4567
5310
|
ts: "typescript",
|
|
4568
5311
|
tsx: "typescript",
|
|
@@ -4707,10 +5450,14 @@ function processFiles(filePaths, redactor) {
|
|
|
4707
5450
|
const result = processPDFFile(resolvedPath, filename);
|
|
4708
5451
|
if (result) embeds.push(result);
|
|
4709
5452
|
else errors.push({ path: rawPath, error: "Failed to process PDF" });
|
|
5453
|
+
} else if (isAudioFile(filename)) {
|
|
5454
|
+
const result = processAudioFile(resolvedPath, filename);
|
|
5455
|
+
if (result) embeds.push(result);
|
|
5456
|
+
else errors.push({ path: rawPath, error: "Failed to process audio" });
|
|
4710
5457
|
} else {
|
|
4711
5458
|
errors.push({
|
|
4712
5459
|
path: rawPath,
|
|
4713
|
-
error: `Unsupported file type: .${ext}. Supported: code/text, images, PDFs.`
|
|
5460
|
+
error: `Unsupported file type: .${ext}. Supported: code/text, images, PDFs, audio.`
|
|
4714
5461
|
});
|
|
4715
5462
|
}
|
|
4716
5463
|
}
|
|
@@ -4839,87 +5586,107 @@ function processPDFFile(filePath, filename) {
|
|
|
4839
5586
|
return null;
|
|
4840
5587
|
}
|
|
4841
5588
|
}
|
|
5589
|
+
function processAudioFile(filePath, filename) {
|
|
5590
|
+
try {
|
|
5591
|
+
const embedId = generateEmbedId();
|
|
5592
|
+
const embedContent = toonEncodeContent({
|
|
5593
|
+
app_id: "audio",
|
|
5594
|
+
skill_id: "transcribe",
|
|
5595
|
+
type: "audio-recording",
|
|
5596
|
+
status: "uploading",
|
|
5597
|
+
filename
|
|
5598
|
+
});
|
|
5599
|
+
const embed = {
|
|
5600
|
+
embedId,
|
|
5601
|
+
type: "audio-recording",
|
|
5602
|
+
content: embedContent,
|
|
5603
|
+
textPreview: filename,
|
|
5604
|
+
status: "processing"
|
|
5605
|
+
};
|
|
5606
|
+
return {
|
|
5607
|
+
embed,
|
|
5608
|
+
referenceBlock: createEmbedReferenceBlock("audio-recording", embedId),
|
|
5609
|
+
displayName: filename,
|
|
5610
|
+
secretsRedacted: false,
|
|
5611
|
+
zeroKnowledge: false,
|
|
5612
|
+
requiresUpload: true,
|
|
5613
|
+
localPath: filePath
|
|
5614
|
+
};
|
|
5615
|
+
} catch (e) {
|
|
5616
|
+
process.stderr.write(
|
|
5617
|
+
`\x1B[31mError:\x1B[0m Failed to process ${filename}: ${e instanceof Error ? e.message : String(e)}
|
|
5618
|
+
`
|
|
5619
|
+
);
|
|
5620
|
+
return null;
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
4842
5623
|
function formatEmbedsForMessage(embeds) {
|
|
4843
5624
|
if (embeds.length === 0) return "";
|
|
4844
5625
|
return "\n" + embeds.map((e) => e.referenceBlock).join("\n");
|
|
4845
5626
|
}
|
|
4846
5627
|
|
|
4847
|
-
// src/
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
var
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
5628
|
+
// src/urlEmbed.ts
|
|
5629
|
+
var URL_PATTERN = /\bhttps?:\/\/[^\s<>()`"']+/gi;
|
|
5630
|
+
var FENCED_BLOCK_PATTERN = /(```[\s\S]*?```)/g;
|
|
5631
|
+
var TRAILING_URL_PUNCTUATION = /[.,!?;:]+$/;
|
|
5632
|
+
function trimTrailingUrlPunctuation(rawUrl) {
|
|
5633
|
+
let url = rawUrl;
|
|
5634
|
+
let suffix = "";
|
|
5635
|
+
const punctuation = url.match(TRAILING_URL_PUNCTUATION)?.[0] ?? "";
|
|
5636
|
+
if (punctuation) {
|
|
5637
|
+
url = url.slice(0, -punctuation.length);
|
|
5638
|
+
suffix = punctuation + suffix;
|
|
5639
|
+
}
|
|
5640
|
+
while (url.endsWith(")")) {
|
|
5641
|
+
const openCount = (url.match(/\(/g) ?? []).length;
|
|
5642
|
+
const closeCount = (url.match(/\)/g) ?? []).length;
|
|
5643
|
+
if (closeCount <= openCount) break;
|
|
5644
|
+
url = url.slice(0, -1);
|
|
5645
|
+
suffix = `)${suffix}`;
|
|
5646
|
+
}
|
|
5647
|
+
return { url, suffix };
|
|
5648
|
+
}
|
|
5649
|
+
function createWebsiteEmbed(url) {
|
|
5650
|
+
const embedId = generateEmbedId();
|
|
5651
|
+
const content = toonEncodeContent({
|
|
5652
|
+
url,
|
|
5653
|
+
title: null,
|
|
5654
|
+
description: null,
|
|
5655
|
+
favicon: null,
|
|
5656
|
+
image: null,
|
|
5657
|
+
site_name: null,
|
|
5658
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5659
|
+
});
|
|
5660
|
+
return {
|
|
5661
|
+
embedId,
|
|
5662
|
+
type: "web-website",
|
|
5663
|
+
content,
|
|
5664
|
+
textPreview: url,
|
|
5665
|
+
status: "finished"
|
|
5666
|
+
};
|
|
4859
5667
|
}
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
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++) {
|
|
5668
|
+
function replaceUrlsInText(text, embeds) {
|
|
5669
|
+
return text.replace(URL_PATTERN, (rawUrl) => {
|
|
5670
|
+
const { url, suffix } = trimTrailingUrlPunctuation(rawUrl);
|
|
5671
|
+
if (!url) return rawUrl;
|
|
4871
5672
|
try {
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
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));
|
|
5673
|
+
new URL(url);
|
|
5674
|
+
} catch {
|
|
5675
|
+
return rawUrl;
|
|
4889
5676
|
}
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
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;
|
|
5677
|
+
const embed = createWebsiteEmbed(url);
|
|
5678
|
+
embeds.push(embed);
|
|
5679
|
+
return `${createEmbedReferenceBlock("website", embed.embedId, url)}${suffix}`;
|
|
5680
|
+
});
|
|
5681
|
+
}
|
|
5682
|
+
function prepareUrlEmbeds(message) {
|
|
5683
|
+
const embeds = [];
|
|
5684
|
+
const parts = message.split(FENCED_BLOCK_PATTERN);
|
|
5685
|
+
const processed = parts.map((part, index) => {
|
|
5686
|
+
const isFence = index % 2 === 1 && part.startsWith("```");
|
|
5687
|
+
return isFence ? part : replaceUrlsInText(part, embeds);
|
|
5688
|
+
}).join("");
|
|
5689
|
+
return { message: processed, embeds };
|
|
4923
5690
|
}
|
|
4924
5691
|
|
|
4925
5692
|
// src/embedRenderers.ts
|
|
@@ -5971,13 +6738,14 @@ function formatTs(ts) {
|
|
|
5971
6738
|
|
|
5972
6739
|
// src/server.ts
|
|
5973
6740
|
import { execSync, spawn as nodeSpawn } from "child_process";
|
|
5974
|
-
import {
|
|
6741
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
6742
|
+
import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5975
6743
|
import { createInterface as createInterface2 } from "readline";
|
|
5976
6744
|
import { homedir as homedir5 } from "os";
|
|
5977
6745
|
import { join as join3, resolve as resolve3 } from "path";
|
|
5978
6746
|
|
|
5979
6747
|
// src/serverConfig.ts
|
|
5980
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as
|
|
6748
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
5981
6749
|
import { homedir as homedir4 } from "os";
|
|
5982
6750
|
import { join as join2, resolve as resolve2 } from "path";
|
|
5983
6751
|
var STATE_DIR = join2(homedir4(), ".openmates");
|
|
@@ -5998,7 +6766,7 @@ function loadServerConfig() {
|
|
|
5998
6766
|
const filePath = join2(STATE_DIR, CONFIG_FILE);
|
|
5999
6767
|
if (!existsSync4(filePath)) return null;
|
|
6000
6768
|
try {
|
|
6001
|
-
return JSON.parse(
|
|
6769
|
+
return JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
6002
6770
|
} catch {
|
|
6003
6771
|
return null;
|
|
6004
6772
|
}
|
|
@@ -6009,16 +6777,17 @@ function removeServerConfig() {
|
|
|
6009
6777
|
rmSync2(filePath);
|
|
6010
6778
|
}
|
|
6011
6779
|
}
|
|
6012
|
-
var
|
|
6780
|
+
var SOURCE_COMPOSE_MARKER = join2("backend", "core", "docker-compose.yml");
|
|
6781
|
+
var IMAGE_COMPOSE_MARKER = join2("backend", "core", "docker-compose.selfhost.yml");
|
|
6013
6782
|
function isOpenMatesDir(dir) {
|
|
6014
|
-
return existsSync4(join2(dir,
|
|
6783
|
+
return existsSync4(join2(dir, SOURCE_COMPOSE_MARKER)) || existsSync4(join2(dir, IMAGE_COMPOSE_MARKER));
|
|
6015
6784
|
}
|
|
6016
6785
|
function resolveServerPath(flags) {
|
|
6017
6786
|
if (typeof flags.path === "string" && flags.path) {
|
|
6018
6787
|
const explicit = resolve2(flags.path);
|
|
6019
6788
|
if (!isOpenMatesDir(explicit)) {
|
|
6020
6789
|
throw new Error(
|
|
6021
|
-
`${explicit} does not appear to be an OpenMates installation (missing ${
|
|
6790
|
+
`${explicit} does not appear to be an OpenMates installation (missing ${SOURCE_COMPOSE_MARKER} or ${IMAGE_COMPOSE_MARKER}).`
|
|
6022
6791
|
);
|
|
6023
6792
|
}
|
|
6024
6793
|
return explicit;
|
|
@@ -6037,10 +6806,78 @@ function resolveServerPath(flags) {
|
|
|
6037
6806
|
}
|
|
6038
6807
|
|
|
6039
6808
|
// src/server.ts
|
|
6040
|
-
var
|
|
6809
|
+
var SOURCE_COMPOSE_FILE = join3("backend", "core", "docker-compose.yml");
|
|
6810
|
+
var IMAGE_COMPOSE_FILE = join3("backend", "core", "docker-compose.selfhost.yml");
|
|
6041
6811
|
var COMPOSE_OVERRIDE = join3("backend", "core", "docker-compose.override.yml");
|
|
6042
6812
|
var DEFAULT_INSTALL_PATH = join3(homedir5(), "openmates");
|
|
6043
6813
|
var REPO_URL = "https://github.com/glowingkitty/OpenMates.git";
|
|
6814
|
+
var DEV_BRANCH = "dev";
|
|
6815
|
+
var DEFAULT_IMAGE_REGISTRY = "ghcr.io/glowingkitty";
|
|
6816
|
+
var MINIMAL_ENV_TEMPLATE = `# OpenMates self-host image-mode environment
|
|
6817
|
+
SECRET__MISTRAL_AI__API_KEY=
|
|
6818
|
+
SECRET__CEREBRAS__API_KEY=
|
|
6819
|
+
SECRET__GROQ__API_KEY=
|
|
6820
|
+
SECRET__OPENAI__API_KEY=
|
|
6821
|
+
SECRET__ANTHROPIC__API_KEY=
|
|
6822
|
+
SECRET__GOOGLE_AI_STUDIO__API_KEY=
|
|
6823
|
+
SECRET__OPENROUTER__API_KEY=
|
|
6824
|
+
SECRET__TOGETHER__API_KEY=
|
|
6825
|
+
SECRET__BRAVE__API_KEY=
|
|
6826
|
+
SECRET__FIRECRAWL__API_KEY=
|
|
6827
|
+
SECRET__CONTEXT7__API_KEY=
|
|
6828
|
+
DATABASE_ADMIN_EMAIL=admin@example.com
|
|
6829
|
+
DATABASE_ADMIN_PASSWORD=
|
|
6830
|
+
DATABASE_NAME=directus
|
|
6831
|
+
DATABASE_USERNAME=directus
|
|
6832
|
+
DATABASE_PASSWORD=
|
|
6833
|
+
DIRECTUS_TOKEN=
|
|
6834
|
+
DIRECTUS_SECRET=
|
|
6835
|
+
DRAGONFLY_PASSWORD=
|
|
6836
|
+
OPENOBSERVE_ROOT_EMAIL=admin@openmates.internal
|
|
6837
|
+
OPENOBSERVE_ROOT_PASSWORD=
|
|
6838
|
+
INTERNAL_API_SHARED_TOKEN=
|
|
6839
|
+
TUNNEL_TRIGGER_SECRET=<PLACEHOLDER>
|
|
6840
|
+
SERVER_ENVIRONMENT=production
|
|
6841
|
+
FRONTEND_URLS="http://localhost:5173"
|
|
6842
|
+
PRODUCTION_URL="http://localhost:5173"
|
|
6843
|
+
TRUSTED_PROXY_IPS="172.16.0.0/12"
|
|
6844
|
+
CORE_SIDECAR_URL=http://admin-sidecar:8001
|
|
6845
|
+
CLEAR_CACHE_ON_UPDATE=true
|
|
6846
|
+
SIGNUP_LIMIT=20
|
|
6847
|
+
SELF_HOST_SIGNUP_MODE=invite_only
|
|
6848
|
+
SELF_HOST_SIGNUP_ALLOWED_DOMAINS=
|
|
6849
|
+
SELF_HOST_FIRST_INVITE_CODE=
|
|
6850
|
+
APPLICATION_PREVIEW_ORIGIN=
|
|
6851
|
+
OPENMATES_IMAGE_REGISTRY=${DEFAULT_IMAGE_REGISTRY}
|
|
6852
|
+
OPENMATES_IMAGE_TAG=
|
|
6853
|
+
GIT_WORK_DIR=
|
|
6854
|
+
DOCKER_GID=999
|
|
6855
|
+
`;
|
|
6856
|
+
var VAULT_CONFIG_TEMPLATE = `# Minimal Vault configuration
|
|
6857
|
+
listener "tcp" {
|
|
6858
|
+
address = "0.0.0.0:8200"
|
|
6859
|
+
tls_disable = true
|
|
6860
|
+
}
|
|
6861
|
+
|
|
6862
|
+
storage "file" {
|
|
6863
|
+
path = "/vault/file"
|
|
6864
|
+
}
|
|
6865
|
+
|
|
6866
|
+
api_addr = "http://0.0.0.0:8200"
|
|
6867
|
+
ui = false
|
|
6868
|
+
disable_mlock = true
|
|
6869
|
+
log_level = "info"
|
|
6870
|
+
`;
|
|
6871
|
+
var LLM_PROVIDER_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
6872
|
+
"SECRET__MISTRAL_AI__API_KEY",
|
|
6873
|
+
"SECRET__CEREBRAS__API_KEY",
|
|
6874
|
+
"SECRET__GROQ__API_KEY",
|
|
6875
|
+
"SECRET__OPENAI__API_KEY",
|
|
6876
|
+
"SECRET__ANTHROPIC__API_KEY",
|
|
6877
|
+
"SECRET__GOOGLE_AI_STUDIO__API_KEY",
|
|
6878
|
+
"SECRET__OPENROUTER__API_KEY",
|
|
6879
|
+
"SECRET__TOGETHER__API_KEY"
|
|
6880
|
+
]);
|
|
6044
6881
|
function exec(cmd, cwd) {
|
|
6045
6882
|
return execSync(cmd, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6046
6883
|
}
|
|
@@ -6051,12 +6888,40 @@ function runInteractive(cmd, args, cwd) {
|
|
|
6051
6888
|
child.on("error", reject);
|
|
6052
6889
|
});
|
|
6053
6890
|
}
|
|
6054
|
-
function
|
|
6055
|
-
const
|
|
6891
|
+
function loadConfigForInstallPath(installPath) {
|
|
6892
|
+
const config = loadServerConfig();
|
|
6893
|
+
return config?.installPath === installPath ? config : null;
|
|
6894
|
+
}
|
|
6895
|
+
function getInstallMode(installPath, config = loadConfigForInstallPath(installPath)) {
|
|
6896
|
+
if (config?.installMode) return config.installMode;
|
|
6897
|
+
if (existsSync5(join3(installPath, IMAGE_COMPOSE_FILE))) return "image";
|
|
6898
|
+
return "source";
|
|
6899
|
+
}
|
|
6900
|
+
function shouldPullImages() {
|
|
6901
|
+
return process.env.OPENMATES_SKIP_IMAGE_PULL !== "1";
|
|
6902
|
+
}
|
|
6903
|
+
function composeArgs(installPath, withOverrides, installMode = getInstallMode(installPath)) {
|
|
6904
|
+
const composeFile = installMode === "image" ? IMAGE_COMPOSE_FILE : SOURCE_COMPOSE_FILE;
|
|
6905
|
+
const args = ["compose", "--env-file", ".env", "-f", composeFile];
|
|
6056
6906
|
if (withOverrides && existsSync5(join3(installPath, COMPOSE_OVERRIDE))) {
|
|
6057
6907
|
args.push("-f", COMPOSE_OVERRIDE);
|
|
6058
6908
|
}
|
|
6059
|
-
return args;
|
|
6909
|
+
return args;
|
|
6910
|
+
}
|
|
6911
|
+
function ensureGitWorkDirEnv(installPath) {
|
|
6912
|
+
const envPath = join3(installPath, ".env");
|
|
6913
|
+
if (!existsSync5(envPath)) return;
|
|
6914
|
+
const content = readFileSync5(envPath, "utf-8");
|
|
6915
|
+
const lineRegex = /^GIT_WORK_DIR=.*$/m;
|
|
6916
|
+
const value = `GIT_WORK_DIR=${installPath}`;
|
|
6917
|
+
if (lineRegex.test(content)) {
|
|
6918
|
+
const next = content.replace(lineRegex, value);
|
|
6919
|
+
if (next !== content) writeFileSync3(envPath, next);
|
|
6920
|
+
return;
|
|
6921
|
+
}
|
|
6922
|
+
const separator = content.endsWith("\n") ? "" : "\n";
|
|
6923
|
+
writeFileSync3(envPath, `${content}${separator}${value}
|
|
6924
|
+
`);
|
|
6060
6925
|
}
|
|
6061
6926
|
function requireDocker() {
|
|
6062
6927
|
try {
|
|
@@ -6074,9 +6939,94 @@ function requireGit() {
|
|
|
6074
6939
|
throw new Error("git is not installed. Install it first: https://git-scm.com/downloads");
|
|
6075
6940
|
}
|
|
6076
6941
|
}
|
|
6942
|
+
function getPackageVersion() {
|
|
6943
|
+
try {
|
|
6944
|
+
const packageJson = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf-8"));
|
|
6945
|
+
return packageJson.version ?? "";
|
|
6946
|
+
} catch {
|
|
6947
|
+
return "";
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
function getDefaultImageTag() {
|
|
6951
|
+
const version = getPackageVersion();
|
|
6952
|
+
return version ? `v${version}` : "dev";
|
|
6953
|
+
}
|
|
6954
|
+
function defaultTemplateRefForVersion(version) {
|
|
6955
|
+
return /-(alpha|beta|rc)(\.|\d|$)/.test(version) ? DEV_BRANCH : `v${version}`;
|
|
6956
|
+
}
|
|
6957
|
+
function randomHex(bytes) {
|
|
6958
|
+
return randomBytes2(bytes).toString("hex");
|
|
6959
|
+
}
|
|
6960
|
+
function generateInviteCode() {
|
|
6961
|
+
const digits = Array.from(randomBytes2(12), (byte) => String(byte % 10)).join("");
|
|
6962
|
+
return `${digits.slice(0, 4)}-${digits.slice(4, 8)}-${digits.slice(8, 12)}`;
|
|
6963
|
+
}
|
|
6964
|
+
function getEnvVar(content, name) {
|
|
6965
|
+
const match = content.match(new RegExp(`^${name}=(.*)$`, "m"));
|
|
6966
|
+
return match?.[1]?.replace(/^"|"$/g, "") ?? "";
|
|
6967
|
+
}
|
|
6968
|
+
function setEnvVar(content, name, value) {
|
|
6969
|
+
const line = `${name}=${value}`;
|
|
6970
|
+
const pattern = new RegExp(`^${name}=.*$`, "m");
|
|
6971
|
+
if (pattern.test(content)) {
|
|
6972
|
+
return content.replace(pattern, line);
|
|
6973
|
+
}
|
|
6974
|
+
const separator = content.endsWith("\n") ? "" : "\n";
|
|
6975
|
+
return `${content}${separator}${line}
|
|
6976
|
+
`;
|
|
6977
|
+
}
|
|
6978
|
+
function setEnvIfEmpty(content, name, value) {
|
|
6979
|
+
return getEnvVar(content, name) ? content : setEnvVar(content, name, value);
|
|
6980
|
+
}
|
|
6981
|
+
async function fetchText(url) {
|
|
6982
|
+
const response = await fetch(url);
|
|
6983
|
+
if (!response.ok) {
|
|
6984
|
+
throw new Error(`Failed to download ${url}: HTTP ${response.status}`);
|
|
6985
|
+
}
|
|
6986
|
+
return response.text();
|
|
6987
|
+
}
|
|
6988
|
+
async function loadSelfHostComposeTemplate(version) {
|
|
6989
|
+
const templateDir = process.env.OPENMATES_SELFHOST_TEMPLATE_DIR;
|
|
6990
|
+
if (templateDir) {
|
|
6991
|
+
return readFileSync5(join3(resolve3(templateDir), IMAGE_COMPOSE_FILE), "utf-8");
|
|
6992
|
+
}
|
|
6993
|
+
const overrideUrl = process.env.OPENMATES_SELFHOST_COMPOSE_URL;
|
|
6994
|
+
if (overrideUrl) {
|
|
6995
|
+
return fetchText(overrideUrl);
|
|
6996
|
+
}
|
|
6997
|
+
const ref = defaultTemplateRefForVersion(version);
|
|
6998
|
+
return fetchText(
|
|
6999
|
+
`https://raw.githubusercontent.com/glowingkitty/OpenMates/${ref}/backend/core/docker-compose.selfhost.yml`
|
|
7000
|
+
);
|
|
7001
|
+
}
|
|
7002
|
+
async function writeImageModeRuntimeFiles(installPath, imageTag) {
|
|
7003
|
+
const coreDir = join3(installPath, "backend", "core");
|
|
7004
|
+
const vaultConfigDir = join3(coreDir, "vault", "config");
|
|
7005
|
+
mkdirSync3(vaultConfigDir, { recursive: true });
|
|
7006
|
+
writeFileSync3(join3(coreDir, "docker-compose.selfhost.yml"), await loadSelfHostComposeTemplate(getPackageVersion()));
|
|
7007
|
+
writeFileSync3(join3(vaultConfigDir, "vault.hcl"), VAULT_CONFIG_TEMPLATE);
|
|
7008
|
+
const envPath = join3(installPath, ".env");
|
|
7009
|
+
let envContent = existsSync5(envPath) ? readFileSync5(envPath, "utf-8") : MINIMAL_ENV_TEMPLATE;
|
|
7010
|
+
envContent = setEnvIfEmpty(envContent, "DATABASE_ADMIN_PASSWORD", randomHex(12));
|
|
7011
|
+
envContent = setEnvIfEmpty(envContent, "DATABASE_PASSWORD", randomHex(12));
|
|
7012
|
+
envContent = setEnvIfEmpty(envContent, "DIRECTUS_TOKEN", randomHex(32));
|
|
7013
|
+
envContent = setEnvIfEmpty(envContent, "DIRECTUS_SECRET", randomHex(32));
|
|
7014
|
+
envContent = setEnvIfEmpty(envContent, "DRAGONFLY_PASSWORD", randomHex(12));
|
|
7015
|
+
envContent = setEnvIfEmpty(envContent, "OPENOBSERVE_ROOT_PASSWORD", randomHex(32));
|
|
7016
|
+
envContent = setEnvIfEmpty(envContent, "INTERNAL_API_SHARED_TOKEN", randomHex(32));
|
|
7017
|
+
envContent = setEnvIfEmpty(envContent, "SELF_HOST_FIRST_INVITE_CODE", generateInviteCode());
|
|
7018
|
+
envContent = setEnvVar(envContent, "OPENMATES_IMAGE_TAG", imageTag);
|
|
7019
|
+
envContent = setEnvVar(envContent, "OPENMATES_IMAGE_REGISTRY", DEFAULT_IMAGE_REGISTRY);
|
|
7020
|
+
envContent = setEnvVar(envContent, "GIT_WORK_DIR", installPath);
|
|
7021
|
+
writeFileSync3(envPath, envContent.endsWith("\n") ? envContent : `${envContent}
|
|
7022
|
+
`);
|
|
7023
|
+
}
|
|
7024
|
+
function defaultCloneBranchForVersion(version) {
|
|
7025
|
+
return /-(alpha|beta|rc)(\.|\d|$)/.test(version) ? DEV_BRANCH : null;
|
|
7026
|
+
}
|
|
6077
7027
|
function hasLlmCredentials(envPath) {
|
|
6078
7028
|
if (!existsSync5(envPath)) return false;
|
|
6079
|
-
const content =
|
|
7029
|
+
const content = readFileSync5(envPath, "utf-8");
|
|
6080
7030
|
for (const line of content.split("\n")) {
|
|
6081
7031
|
const trimmed = line.trim();
|
|
6082
7032
|
if (trimmed.startsWith("#") || !trimmed) continue;
|
|
@@ -6084,22 +7034,23 @@ function hasLlmCredentials(envPath) {
|
|
|
6084
7034
|
if (eqIdx === -1) continue;
|
|
6085
7035
|
const key = trimmed.slice(0, eqIdx);
|
|
6086
7036
|
const value = trimmed.slice(eqIdx + 1).trim();
|
|
6087
|
-
if (
|
|
7037
|
+
if (LLM_PROVIDER_ENV_KEYS.has(key) && value && value !== "IMPORTED_TO_VAULT") {
|
|
6088
7038
|
return true;
|
|
6089
7039
|
}
|
|
6090
7040
|
}
|
|
6091
7041
|
return false;
|
|
6092
7042
|
}
|
|
6093
|
-
function
|
|
7043
|
+
function warnIfMissingLlmCredentials(installPath) {
|
|
6094
7044
|
const envPath = join3(installPath, ".env");
|
|
6095
7045
|
if (!existsSync5(envPath)) {
|
|
6096
|
-
|
|
7046
|
+
console.error(
|
|
6097
7047
|
"No .env file found. Run 'openmates server install' first, or create .env from .env.example."
|
|
6098
7048
|
);
|
|
7049
|
+
return;
|
|
6099
7050
|
}
|
|
6100
7051
|
if (!hasLlmCredentials(envPath)) {
|
|
6101
|
-
|
|
6102
|
-
"No LLM provider API key found in .env.\
|
|
7052
|
+
console.error(
|
|
7053
|
+
"No LLM provider API key found in .env.\nOpenMates will start, but AI chat/model processing will stay unavailable until you add one.\n\nAdd at least one of these to your .env file:\n SECRET__OPENAI__API_KEY=sk-...\n SECRET__ANTHROPIC__API_KEY=sk-ant-...\n SECRET__GOOGLE_AI_STUDIO__API_KEY=...\n\nAfter updating .env, run 'openmates server restart'."
|
|
6103
7054
|
);
|
|
6104
7055
|
}
|
|
6105
7056
|
}
|
|
@@ -6118,9 +7069,10 @@ function printJson(data) {
|
|
|
6118
7069
|
async function serverStatus(flags) {
|
|
6119
7070
|
requireDocker();
|
|
6120
7071
|
const installPath = resolveServerPath(flags);
|
|
6121
|
-
|
|
7072
|
+
ensureGitWorkDirEnv(installPath);
|
|
7073
|
+
const config = loadConfigForInstallPath(installPath);
|
|
6122
7074
|
const withOverrides = config?.composeProfile === "full";
|
|
6123
|
-
const args = [...composeArgs(installPath, withOverrides), "ps"];
|
|
7075
|
+
const args = [...composeArgs(installPath, withOverrides, getInstallMode(installPath, config)), "ps"];
|
|
6124
7076
|
if (flags.json === true) {
|
|
6125
7077
|
args.push("--format", "json");
|
|
6126
7078
|
}
|
|
@@ -6130,20 +7082,30 @@ async function serverStatus(flags) {
|
|
|
6130
7082
|
async function serverStart(flags) {
|
|
6131
7083
|
requireDocker();
|
|
6132
7084
|
const installPath = resolveServerPath(flags);
|
|
6133
|
-
|
|
7085
|
+
ensureGitWorkDirEnv(installPath);
|
|
7086
|
+
warnIfMissingLlmCredentials(installPath);
|
|
6134
7087
|
const withOverrides = flags["with-overrides"] === true;
|
|
6135
|
-
const
|
|
6136
|
-
const
|
|
7088
|
+
const config = loadConfigForInstallPath(installPath);
|
|
7089
|
+
const installMode = getInstallMode(installPath, config);
|
|
7090
|
+
const pullArgs = [...composeArgs(installPath, withOverrides, installMode), "pull"];
|
|
7091
|
+
const args = [...composeArgs(installPath, withOverrides, installMode), "up", "-d"];
|
|
6137
7092
|
if (config && withOverrides && config.composeProfile !== "full") {
|
|
6138
7093
|
saveServerConfig({ ...config, composeProfile: "full" });
|
|
6139
7094
|
}
|
|
6140
7095
|
console.error("Starting OpenMates server...");
|
|
6141
|
-
|
|
7096
|
+
let code = 0;
|
|
7097
|
+
if (installMode === "image" && shouldPullImages()) {
|
|
7098
|
+
code = await runInteractive("docker", pullArgs, installPath);
|
|
7099
|
+
if (code !== 0) process.exit(code);
|
|
7100
|
+
}
|
|
7101
|
+
code = await runInteractive("docker", args, installPath);
|
|
6142
7102
|
if (code !== 0) process.exit(code);
|
|
6143
7103
|
if (flags.json === true) {
|
|
6144
7104
|
printJson({ command: "start", status: "success", path: installPath });
|
|
6145
7105
|
} else {
|
|
6146
|
-
console.log("\nServer started.
|
|
7106
|
+
console.log("\nServer started.");
|
|
7107
|
+
console.log("Web app: http://localhost:5173");
|
|
7108
|
+
console.log("API: http://localhost:8000");
|
|
6147
7109
|
if (withOverrides) {
|
|
6148
7110
|
console.log("Directus CMS: http://localhost:8055");
|
|
6149
7111
|
console.log("Grafana: http://localhost:3000");
|
|
@@ -6153,9 +7115,10 @@ async function serverStart(flags) {
|
|
|
6153
7115
|
async function serverStop(flags) {
|
|
6154
7116
|
requireDocker();
|
|
6155
7117
|
const installPath = resolveServerPath(flags);
|
|
6156
|
-
|
|
7118
|
+
ensureGitWorkDirEnv(installPath);
|
|
7119
|
+
const config = loadConfigForInstallPath(installPath);
|
|
6157
7120
|
const withOverrides = config?.composeProfile === "full";
|
|
6158
|
-
const args = [...composeArgs(installPath, withOverrides), "down"];
|
|
7121
|
+
const args = [...composeArgs(installPath, withOverrides, getInstallMode(installPath, config)), "down"];
|
|
6159
7122
|
console.error("Stopping OpenMates server...");
|
|
6160
7123
|
const code = await runInteractive("docker", args, installPath);
|
|
6161
7124
|
if (code !== 0) process.exit(code);
|
|
@@ -6168,26 +7131,33 @@ async function serverStop(flags) {
|
|
|
6168
7131
|
async function serverRestart(flags) {
|
|
6169
7132
|
requireDocker();
|
|
6170
7133
|
const installPath = resolveServerPath(flags);
|
|
6171
|
-
|
|
7134
|
+
ensureGitWorkDirEnv(installPath);
|
|
7135
|
+
const config = loadConfigForInstallPath(installPath);
|
|
6172
7136
|
const withOverrides = config?.composeProfile === "full";
|
|
7137
|
+
const installMode = getInstallMode(installPath, config);
|
|
6173
7138
|
if (flags.rebuild === true) {
|
|
7139
|
+
if (installMode === "image") {
|
|
7140
|
+
throw new Error(
|
|
7141
|
+
"Image-mode installs use prebuilt images and cannot rebuild locally. Run 'openmates server update' to pull newer images, or reinstall with --from-source to build from source."
|
|
7142
|
+
);
|
|
7143
|
+
}
|
|
6174
7144
|
console.error("Rebuilding OpenMates server (this may take a few minutes)...");
|
|
6175
|
-
const downArgs = [...composeArgs(installPath, withOverrides), "down"];
|
|
7145
|
+
const downArgs = [...composeArgs(installPath, withOverrides, installMode), "down"];
|
|
6176
7146
|
let code = await runInteractive("docker", downArgs, installPath);
|
|
6177
7147
|
if (code !== 0) process.exit(code);
|
|
6178
7148
|
try {
|
|
6179
7149
|
exec("docker volume rm openmates-cache-data", installPath);
|
|
6180
7150
|
} catch {
|
|
6181
7151
|
}
|
|
6182
|
-
const buildArgs = [...composeArgs(installPath, withOverrides), "build"];
|
|
7152
|
+
const buildArgs = [...composeArgs(installPath, withOverrides, installMode), "build"];
|
|
6183
7153
|
code = await runInteractive("docker", buildArgs, installPath);
|
|
6184
7154
|
if (code !== 0) process.exit(code);
|
|
6185
|
-
const upArgs = [...composeArgs(installPath, withOverrides), "up", "-d"];
|
|
7155
|
+
const upArgs = [...composeArgs(installPath, withOverrides, installMode), "up", "-d"];
|
|
6186
7156
|
code = await runInteractive("docker", upArgs, installPath);
|
|
6187
7157
|
if (code !== 0) process.exit(code);
|
|
6188
7158
|
} else {
|
|
6189
7159
|
console.error("Restarting OpenMates server...");
|
|
6190
|
-
const args = [...composeArgs(installPath, withOverrides), "restart"];
|
|
7160
|
+
const args = [...composeArgs(installPath, withOverrides, installMode), "restart"];
|
|
6191
7161
|
const code = await runInteractive("docker", args, installPath);
|
|
6192
7162
|
if (code !== 0) process.exit(code);
|
|
6193
7163
|
}
|
|
@@ -6200,9 +7170,10 @@ async function serverRestart(flags) {
|
|
|
6200
7170
|
async function serverLogs(flags) {
|
|
6201
7171
|
requireDocker();
|
|
6202
7172
|
const installPath = resolveServerPath(flags);
|
|
6203
|
-
|
|
7173
|
+
ensureGitWorkDirEnv(installPath);
|
|
7174
|
+
const config = loadConfigForInstallPath(installPath);
|
|
6204
7175
|
const withOverrides = config?.composeProfile === "full";
|
|
6205
|
-
const args = [...composeArgs(installPath, withOverrides), "logs"];
|
|
7176
|
+
const args = [...composeArgs(installPath, withOverrides, getInstallMode(installPath, config)), "logs"];
|
|
6206
7177
|
if (flags.follow === true || flags.f === true) {
|
|
6207
7178
|
args.push("--follow");
|
|
6208
7179
|
}
|
|
@@ -6220,17 +7191,70 @@ async function serverLogs(flags) {
|
|
|
6220
7191
|
if (code !== 0) process.exit(code);
|
|
6221
7192
|
}
|
|
6222
7193
|
async function serverInstall(flags) {
|
|
6223
|
-
requireGit();
|
|
6224
7194
|
const installPath = typeof flags.path === "string" ? resolve3(flags.path) : DEFAULT_INSTALL_PATH;
|
|
6225
|
-
|
|
7195
|
+
const sourcePath = typeof flags["source-path"] === "string" ? resolve3(flags["source-path"]) : null;
|
|
7196
|
+
const fromSource = flags["from-source"] === true || sourcePath !== null;
|
|
7197
|
+
if (existsSync5(join3(installPath, SOURCE_COMPOSE_FILE)) || existsSync5(join3(installPath, IMAGE_COMPOSE_FILE))) {
|
|
6226
7198
|
console.error(`OpenMates already exists at ${installPath}.`);
|
|
6227
7199
|
console.error("Use 'openmates server update' to update, or choose a different --path.");
|
|
6228
7200
|
process.exit(1);
|
|
6229
7201
|
}
|
|
6230
|
-
|
|
7202
|
+
if (!fromSource) {
|
|
7203
|
+
requireDocker();
|
|
7204
|
+
mkdirSync3(installPath, { recursive: true });
|
|
7205
|
+
if (typeof flags["env-path"] === "string") {
|
|
7206
|
+
const envSource = resolve3(flags["env-path"]);
|
|
7207
|
+
if (!existsSync5(envSource)) {
|
|
7208
|
+
throw new Error(`Env file not found: ${envSource}`);
|
|
7209
|
+
}
|
|
7210
|
+
copyFileSync(envSource, join3(installPath, ".env"));
|
|
7211
|
+
console.error(`Copied ${envSource} to ${installPath}/.env`);
|
|
7212
|
+
}
|
|
7213
|
+
const imageTag = typeof flags["image-tag"] === "string" ? flags["image-tag"] : getDefaultImageTag();
|
|
7214
|
+
console.error(`Preparing OpenMates image-mode install at ${installPath}...`);
|
|
7215
|
+
await writeImageModeRuntimeFiles(installPath, imageTag);
|
|
7216
|
+
try {
|
|
7217
|
+
exec("docker network create openmates", installPath);
|
|
7218
|
+
} catch {
|
|
7219
|
+
}
|
|
7220
|
+
saveServerConfig({
|
|
7221
|
+
installPath,
|
|
7222
|
+
installedAt: Date.now(),
|
|
7223
|
+
composeProfile: "core",
|
|
7224
|
+
installMode: "image",
|
|
7225
|
+
imageTag
|
|
7226
|
+
});
|
|
7227
|
+
if (flags.json === true) {
|
|
7228
|
+
printJson({ command: "install", status: "success", path: installPath, mode: "image", imageTag });
|
|
7229
|
+
} else {
|
|
7230
|
+
const firstInvite = getEnvVar(readFileSync5(join3(installPath, ".env"), "utf-8"), "SELF_HOST_FIRST_INVITE_CODE");
|
|
7231
|
+
console.log(`
|
|
7232
|
+
OpenMates installed at ${installPath}`);
|
|
7233
|
+
console.log(`Mode: image (${DEFAULT_IMAGE_REGISTRY}, tag ${imageTag})`);
|
|
7234
|
+
console.log("\nNext steps:");
|
|
7235
|
+
console.log(" 1. Run: openmates server start");
|
|
7236
|
+
console.log(" 2. Open http://localhost:5173");
|
|
7237
|
+
if (firstInvite) console.log(` 3. Sign up with invite code: ${firstInvite}`);
|
|
7238
|
+
console.log(" 4. After signup, make yourself admin: openmates server make-admin your@email.com");
|
|
7239
|
+
console.log("\nOptional: edit .env first to add LLM provider API keys. Source builds are available with --from-source.");
|
|
7240
|
+
}
|
|
7241
|
+
return;
|
|
7242
|
+
}
|
|
7243
|
+
requireGit();
|
|
7244
|
+
const cloneSource = sourcePath ?? REPO_URL;
|
|
7245
|
+
const cloneBranch = sourcePath ? null : defaultCloneBranchForVersion(getPackageVersion());
|
|
7246
|
+
const cloneArgs = ["clone"];
|
|
7247
|
+
if (cloneBranch) {
|
|
7248
|
+
cloneArgs.push("--branch", cloneBranch);
|
|
7249
|
+
}
|
|
7250
|
+
cloneArgs.push(cloneSource, installPath);
|
|
7251
|
+
console.error(`Cloning OpenMates from ${cloneSource} to ${installPath}...`);
|
|
7252
|
+
if (cloneBranch) {
|
|
7253
|
+
console.error(`Using ${cloneBranch} branch for this prerelease CLI.`);
|
|
7254
|
+
}
|
|
6231
7255
|
const cloneCode = await runInteractive(
|
|
6232
7256
|
"git",
|
|
6233
|
-
|
|
7257
|
+
cloneArgs,
|
|
6234
7258
|
process.cwd()
|
|
6235
7259
|
);
|
|
6236
7260
|
if (cloneCode !== 0) {
|
|
@@ -6257,26 +7281,45 @@ async function serverInstall(flags) {
|
|
|
6257
7281
|
saveServerConfig({
|
|
6258
7282
|
installPath,
|
|
6259
7283
|
installedAt: Date.now(),
|
|
6260
|
-
composeProfile: "core"
|
|
7284
|
+
composeProfile: "core",
|
|
7285
|
+
installMode: "source"
|
|
6261
7286
|
});
|
|
6262
7287
|
if (flags.json === true) {
|
|
6263
|
-
printJson({ command: "install", status: "success", path: installPath });
|
|
7288
|
+
printJson({ command: "install", status: "success", path: installPath, mode: "source" });
|
|
6264
7289
|
} else {
|
|
6265
7290
|
console.log(`
|
|
6266
7291
|
OpenMates installed at ${installPath}`);
|
|
6267
7292
|
console.log("\nNext steps:");
|
|
6268
|
-
console.log(" 1.
|
|
6269
|
-
console.log(" 2.
|
|
6270
|
-
console.log(" 3.
|
|
6271
|
-
console.log("
|
|
6272
|
-
console.log(" 5. Make yourself admin: openmates server make-admin your@email.com");
|
|
7293
|
+
console.log(" 1. Run: openmates server start");
|
|
7294
|
+
console.log(" 2. Open http://localhost:5173");
|
|
7295
|
+
console.log(" 3. After signup, make yourself admin: openmates server make-admin your@email.com");
|
|
7296
|
+
console.log("\nOptional: edit .env first to add LLM provider API keys. Without keys, the web app and backend still start, but AI model processing is unavailable.");
|
|
6273
7297
|
}
|
|
6274
7298
|
}
|
|
6275
7299
|
async function serverUpdate(flags) {
|
|
6276
|
-
requireGit();
|
|
6277
7300
|
requireDocker();
|
|
6278
7301
|
const installPath = resolveServerPath(flags);
|
|
7302
|
+
ensureGitWorkDirEnv(installPath);
|
|
6279
7303
|
console.error("Updating OpenMates...");
|
|
7304
|
+
const config = loadConfigForInstallPath(installPath);
|
|
7305
|
+
const withOverrides = config?.composeProfile === "full";
|
|
7306
|
+
const installMode = getInstallMode(installPath, config);
|
|
7307
|
+
if (installMode === "image") {
|
|
7308
|
+
const pullArgs = [...composeArgs(installPath, withOverrides, installMode), "pull"];
|
|
7309
|
+
console.error("Pulling prebuilt images...");
|
|
7310
|
+
let code2 = await runInteractive("docker", pullArgs, installPath);
|
|
7311
|
+
if (code2 !== 0) process.exit(code2);
|
|
7312
|
+
const upArgs2 = [...composeArgs(installPath, withOverrides, installMode), "up", "-d"];
|
|
7313
|
+
code2 = await runInteractive("docker", upArgs2, installPath);
|
|
7314
|
+
if (code2 !== 0) process.exit(code2);
|
|
7315
|
+
if (flags.json === true) {
|
|
7316
|
+
printJson({ command: "update", status: "success", path: installPath, mode: "image" });
|
|
7317
|
+
} else {
|
|
7318
|
+
console.log("Server images pulled and containers restarted.");
|
|
7319
|
+
}
|
|
7320
|
+
return;
|
|
7321
|
+
}
|
|
7322
|
+
requireGit();
|
|
6280
7323
|
if (flags.force === true) {
|
|
6281
7324
|
console.error("Stashing local changes...");
|
|
6282
7325
|
try {
|
|
@@ -6304,13 +7347,11 @@ async function serverUpdate(flags) {
|
|
|
6304
7347
|
} catch {
|
|
6305
7348
|
}
|
|
6306
7349
|
}
|
|
6307
|
-
const
|
|
6308
|
-
const withOverrides = config?.composeProfile === "full";
|
|
6309
|
-
const buildArgs = [...composeArgs(installPath, withOverrides), "build"];
|
|
7350
|
+
const buildArgs = [...composeArgs(installPath, withOverrides, installMode), "build"];
|
|
6310
7351
|
console.error("Rebuilding containers...");
|
|
6311
7352
|
let code = await runInteractive("docker", buildArgs, installPath);
|
|
6312
7353
|
if (code !== 0) process.exit(code);
|
|
6313
|
-
const upArgs = [...composeArgs(installPath, withOverrides), "up", "-d"];
|
|
7354
|
+
const upArgs = [...composeArgs(installPath, withOverrides, installMode), "up", "-d"];
|
|
6314
7355
|
code = await runInteractive("docker", upArgs, installPath);
|
|
6315
7356
|
if (code !== 0) process.exit(code);
|
|
6316
7357
|
if (flags.json === true) {
|
|
@@ -6322,8 +7363,10 @@ async function serverUpdate(flags) {
|
|
|
6322
7363
|
async function serverReset(flags) {
|
|
6323
7364
|
requireDocker();
|
|
6324
7365
|
const installPath = resolveServerPath(flags);
|
|
6325
|
-
|
|
7366
|
+
ensureGitWorkDirEnv(installPath);
|
|
7367
|
+
const config = loadConfigForInstallPath(installPath);
|
|
6326
7368
|
const withOverrides = config?.composeProfile === "full";
|
|
7369
|
+
const installMode = getInstallMode(installPath, config);
|
|
6327
7370
|
const userDataOnly = flags["delete-user-data-only"] === true;
|
|
6328
7371
|
if (userDataOnly) {
|
|
6329
7372
|
console.error("\nWARNING: This will delete all user data (database and cache).");
|
|
@@ -6343,24 +7386,26 @@ async function serverReset(flags) {
|
|
|
6343
7386
|
}
|
|
6344
7387
|
console.error("Resetting server...");
|
|
6345
7388
|
if (userDataOnly) {
|
|
6346
|
-
const downArgs = [...composeArgs(installPath, withOverrides), "down"];
|
|
7389
|
+
const downArgs = [...composeArgs(installPath, withOverrides, installMode), "down"];
|
|
6347
7390
|
let code = await runInteractive("docker", downArgs, installPath);
|
|
6348
7391
|
if (code !== 0) process.exit(code);
|
|
6349
|
-
for (const vol of ["openmates-cache-data", "openmates-cms-database-data"]) {
|
|
7392
|
+
for (const vol of ["openmates-cache-data", "openmates-postgres-data", "openmates-cms-database-data"]) {
|
|
6350
7393
|
try {
|
|
6351
7394
|
exec(`docker volume rm ${vol}`, installPath);
|
|
6352
7395
|
console.error(` Removed volume: ${vol}`);
|
|
6353
7396
|
} catch {
|
|
6354
7397
|
}
|
|
6355
7398
|
}
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
7399
|
+
if (installMode === "source") {
|
|
7400
|
+
const buildArgs = [...composeArgs(installPath, withOverrides, installMode), "build"];
|
|
7401
|
+
code = await runInteractive("docker", buildArgs, installPath);
|
|
7402
|
+
if (code !== 0) process.exit(code);
|
|
7403
|
+
}
|
|
7404
|
+
const upArgs = [...composeArgs(installPath, withOverrides, installMode), "up", "-d"];
|
|
6360
7405
|
code = await runInteractive("docker", upArgs, installPath);
|
|
6361
7406
|
if (code !== 0) process.exit(code);
|
|
6362
7407
|
} else {
|
|
6363
|
-
const args = [...composeArgs(installPath, withOverrides), "down", "-v"];
|
|
7408
|
+
const args = [...composeArgs(installPath, withOverrides, installMode), "down", "-v"];
|
|
6364
7409
|
const code = await runInteractive("docker", args, installPath);
|
|
6365
7410
|
if (code !== 0) process.exit(code);
|
|
6366
7411
|
}
|
|
@@ -6402,7 +7447,8 @@ async function serverMakeAdmin(rest, flags) {
|
|
|
6402
7447
|
async function serverUninstall(flags) {
|
|
6403
7448
|
requireDocker();
|
|
6404
7449
|
const installPath = resolveServerPath(flags);
|
|
6405
|
-
|
|
7450
|
+
ensureGitWorkDirEnv(installPath);
|
|
7451
|
+
const config = loadConfigForInstallPath(installPath);
|
|
6406
7452
|
const withOverrides = config?.composeProfile === "full";
|
|
6407
7453
|
const keepData = flags["keep-data"] === true;
|
|
6408
7454
|
console.error("\nWARNING: This will completely uninstall OpenMates:");
|
|
@@ -6424,7 +7470,7 @@ async function serverUninstall(flags) {
|
|
|
6424
7470
|
}
|
|
6425
7471
|
}
|
|
6426
7472
|
console.error("Uninstalling OpenMates...");
|
|
6427
|
-
const downArgs = [...composeArgs(installPath, withOverrides), "down", "--rmi", "local"];
|
|
7473
|
+
const downArgs = [...composeArgs(installPath, withOverrides, getInstallMode(installPath, config)), "down", "--rmi", "local"];
|
|
6428
7474
|
if (!keepData) {
|
|
6429
7475
|
downArgs.push("-v");
|
|
6430
7476
|
}
|
|
@@ -6457,13 +7503,13 @@ OpenMates Server Management
|
|
|
6457
7503
|
Usage: openmates server <command> [options]
|
|
6458
7504
|
|
|
6459
7505
|
Commands:
|
|
6460
|
-
install Install OpenMates server (
|
|
7506
|
+
install Install OpenMates server (prebuilt GHCR images by default)
|
|
6461
7507
|
start Start the server
|
|
6462
7508
|
stop Stop the server
|
|
6463
7509
|
restart Restart the server
|
|
6464
7510
|
status Show server status (container health)
|
|
6465
7511
|
logs Display server logs
|
|
6466
|
-
update Update to latest version (git pull + rebuild)
|
|
7512
|
+
update Update to latest version (pull images, or git pull + rebuild for source installs)
|
|
6467
7513
|
make-admin Grant admin privileges to a user
|
|
6468
7514
|
reset Reset server data (requires confirmation)
|
|
6469
7515
|
uninstall Completely remove OpenMates (requires confirmation)
|
|
@@ -6477,6 +7523,9 @@ Command Options:
|
|
|
6477
7523
|
install:
|
|
6478
7524
|
--path <dir> Install directory (default: ~/openmates)
|
|
6479
7525
|
--env-path <file> Copy a pre-existing .env file during install
|
|
7526
|
+
--image-tag <tag> Prebuilt image tag (default: CLI version tag)
|
|
7527
|
+
--from-source Clone/build from source instead of using prebuilt GHCR images
|
|
7528
|
+
--source-path <dir> Clone from a local checkout instead of GitHub (implies --from-source)
|
|
6480
7529
|
|
|
6481
7530
|
start:
|
|
6482
7531
|
--with-overrides Include admin UIs (Directus CMS, Grafana)
|
|
@@ -6710,7 +7759,8 @@ async function handleChats(client, subcommand, rest, flags, redactor) {
|
|
|
6710
7759
|
message,
|
|
6711
7760
|
chatId: void 0,
|
|
6712
7761
|
incognito: false,
|
|
6713
|
-
json: flags.json === true
|
|
7762
|
+
json: flags.json === true,
|
|
7763
|
+
autoApproveSubChats: flags["auto-approve"] === true
|
|
6714
7764
|
},
|
|
6715
7765
|
redactor
|
|
6716
7766
|
);
|
|
@@ -6781,7 +7831,8 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
6781
7831
|
message,
|
|
6782
7832
|
chatId,
|
|
6783
7833
|
incognito: flags.incognito === true,
|
|
6784
|
-
json: flags.json === true
|
|
7834
|
+
json: flags.json === true,
|
|
7835
|
+
autoApproveSubChats: flags["auto-approve"] === true
|
|
6785
7836
|
},
|
|
6786
7837
|
redactor
|
|
6787
7838
|
);
|
|
@@ -6799,7 +7850,8 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
6799
7850
|
{
|
|
6800
7851
|
message,
|
|
6801
7852
|
incognito: true,
|
|
6802
|
-
json: flags.json === true
|
|
7853
|
+
json: flags.json === true,
|
|
7854
|
+
autoApproveSubChats: flags["auto-approve"] === true
|
|
6803
7855
|
},
|
|
6804
7856
|
redactor
|
|
6805
7857
|
);
|
|
@@ -6807,17 +7859,11 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
6807
7859
|
return;
|
|
6808
7860
|
}
|
|
6809
7861
|
if (subcommand === "incognito-history") {
|
|
6810
|
-
|
|
6811
|
-
if (flags.json === true) {
|
|
6812
|
-
printJson2(history);
|
|
6813
|
-
} else {
|
|
6814
|
-
printIncognitoHistory(history);
|
|
6815
|
-
}
|
|
7862
|
+
printIncognitoNoHistoryNotice(flags.json === true);
|
|
6816
7863
|
return;
|
|
6817
7864
|
}
|
|
6818
7865
|
if (subcommand === "incognito-clear") {
|
|
6819
|
-
|
|
6820
|
-
console.log("Incognito history cleared.");
|
|
7866
|
+
printIncognitoNoHistoryNotice(flags.json === true);
|
|
6821
7867
|
return;
|
|
6822
7868
|
}
|
|
6823
7869
|
if (subcommand === "show") {
|
|
@@ -7594,216 +8640,780 @@ function levenshtein(a, b) {
|
|
|
7594
8640
|
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
7595
8641
|
}
|
|
7596
8642
|
}
|
|
7597
|
-
return dp[m][n];
|
|
7598
|
-
}
|
|
7599
|
-
function findClosestMatch(input, candidates) {
|
|
7600
|
-
let best = null;
|
|
7601
|
-
let bestDist = 4;
|
|
7602
|
-
for (const candidate of candidates) {
|
|
7603
|
-
const dist = levenshtein(input.toLowerCase(), candidate.toLowerCase());
|
|
7604
|
-
if (dist < bestDist) {
|
|
7605
|
-
bestDist = dist;
|
|
7606
|
-
best = candidate;
|
|
7607
|
-
}
|
|
8643
|
+
return dp[m][n];
|
|
8644
|
+
}
|
|
8645
|
+
function findClosestMatch(input, candidates) {
|
|
8646
|
+
let best = null;
|
|
8647
|
+
let bestDist = 4;
|
|
8648
|
+
for (const candidate of candidates) {
|
|
8649
|
+
const dist = levenshtein(input.toLowerCase(), candidate.toLowerCase());
|
|
8650
|
+
if (dist < bestDist) {
|
|
8651
|
+
bestDist = dist;
|
|
8652
|
+
best = candidate;
|
|
8653
|
+
}
|
|
8654
|
+
}
|
|
8655
|
+
return best;
|
|
8656
|
+
}
|
|
8657
|
+
async function handleEmbeds(client, subcommand, rest, flags) {
|
|
8658
|
+
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
8659
|
+
printEmbedsHelp();
|
|
8660
|
+
return;
|
|
8661
|
+
}
|
|
8662
|
+
if (subcommand === "show") {
|
|
8663
|
+
const embedId = rest[0];
|
|
8664
|
+
if (!embedId) {
|
|
8665
|
+
console.error("Missing embed ID.\n");
|
|
8666
|
+
printEmbedsHelp();
|
|
8667
|
+
process.exit(1);
|
|
8668
|
+
}
|
|
8669
|
+
const embed = await client.getEmbed(embedId);
|
|
8670
|
+
if (flags.json === true) {
|
|
8671
|
+
printJson2(embed);
|
|
8672
|
+
} else {
|
|
8673
|
+
await renderEmbedFullscreen(embed, client);
|
|
8674
|
+
}
|
|
8675
|
+
return;
|
|
8676
|
+
}
|
|
8677
|
+
if (subcommand === "share") {
|
|
8678
|
+
const id = rest[0];
|
|
8679
|
+
if (!id) {
|
|
8680
|
+
console.error(
|
|
8681
|
+
"Missing embed ID. Usage: openmates embeds share <embed-id>"
|
|
8682
|
+
);
|
|
8683
|
+
process.exit(1);
|
|
8684
|
+
}
|
|
8685
|
+
const durationSeconds = typeof flags.expires === "string" ? parseInt(flags.expires, 10) : 0;
|
|
8686
|
+
const password = typeof flags.password === "string" ? flags.password : void 0;
|
|
8687
|
+
if (password && password.length > 10) {
|
|
8688
|
+
console.error("Password must be at most 10 characters.");
|
|
8689
|
+
process.exit(1);
|
|
8690
|
+
}
|
|
8691
|
+
try {
|
|
8692
|
+
const url = await client.createEmbedShareLink(
|
|
8693
|
+
id,
|
|
8694
|
+
durationSeconds,
|
|
8695
|
+
password
|
|
8696
|
+
);
|
|
8697
|
+
if (flags.json === true) {
|
|
8698
|
+
printJson2({
|
|
8699
|
+
url,
|
|
8700
|
+
embed_id: id,
|
|
8701
|
+
expires: durationSeconds,
|
|
8702
|
+
password_protected: !!password
|
|
8703
|
+
});
|
|
8704
|
+
} else {
|
|
8705
|
+
process.stdout.write(`
|
|
8706
|
+
\x1B[1mEmbed share link\x1B[0m
|
|
8707
|
+
`);
|
|
8708
|
+
process.stdout.write(`${url}
|
|
8709
|
+
|
|
8710
|
+
`);
|
|
8711
|
+
if (durationSeconds > 0) {
|
|
8712
|
+
process.stdout.write(
|
|
8713
|
+
`\x1B[2mExpires in ${humanizeDuration(durationSeconds)}\x1B[0m
|
|
8714
|
+
`
|
|
8715
|
+
);
|
|
8716
|
+
}
|
|
8717
|
+
if (password) {
|
|
8718
|
+
process.stdout.write(`\x1B[2mPassword protected\x1B[0m
|
|
8719
|
+
`);
|
|
8720
|
+
}
|
|
8721
|
+
}
|
|
8722
|
+
} catch (err) {
|
|
8723
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8724
|
+
console.error(`Share link error: ${msg}`);
|
|
8725
|
+
process.exit(1);
|
|
8726
|
+
}
|
|
8727
|
+
return;
|
|
8728
|
+
}
|
|
8729
|
+
console.error(`Unknown embeds subcommand '${subcommand}'.
|
|
8730
|
+
`);
|
|
8731
|
+
printEmbedsHelp();
|
|
8732
|
+
process.exit(1);
|
|
8733
|
+
}
|
|
8734
|
+
var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
8735
|
+
{ path: ["account", "info"], description: "Show account info", examples: ["openmates settings account info --json"] },
|
|
8736
|
+
{ path: ["account", "timezone", "set"], description: "Set account timezone", examples: ["openmates settings account timezone set Europe/Berlin"] },
|
|
8737
|
+
{ path: ["account", "export", "manifest"], description: "Show account export manifest", examples: ["openmates settings account export manifest --json"] },
|
|
8738
|
+
{ path: ["account", "export", "data"], description: "Fetch account export data", examples: ["openmates settings account export data --json"] },
|
|
8739
|
+
{ 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"] },
|
|
8740
|
+
{ path: ["account", "username", "set"], description: "Change account username", examples: ["openmates settings account username set alice_123"] },
|
|
8741
|
+
{ path: ["account", "profile-picture", "set"], description: "Upload a profile picture", examples: ["openmates settings account profile-picture set ./avatar.jpg"] },
|
|
8742
|
+
{ path: ["account", "chats", "stats"], description: "Show chat statistics", examples: ["openmates settings account chats stats"] },
|
|
8743
|
+
{ path: ["account", "delete", "preview"], description: "Preview account deletion impact", examples: ["openmates settings account delete preview"] },
|
|
8744
|
+
{ path: ["account", "storage", "overview"], description: "Show storage overview", examples: ["openmates settings account storage overview"] },
|
|
8745
|
+
{ path: ["account", "storage", "files"], description: "List stored files", examples: ["openmates settings account storage files --category images"] },
|
|
8746
|
+
{ path: ["account", "storage", "delete"], description: "Delete one stored file by file ID", examples: ["openmates settings account storage delete <file-id> --yes"] },
|
|
8747
|
+
{ path: ["interface", "language", "set"], description: "Set interface language", examples: ["openmates settings interface language set en"] },
|
|
8748
|
+
{ path: ["interface", "dark-mode", "set"], description: "Set dark mode on or off", examples: ["openmates settings interface dark-mode set on"] },
|
|
8749
|
+
{ path: ["interface", "font", "set"], description: "Set interface font", examples: ["openmates settings interface font set lexend"] },
|
|
8750
|
+
{ path: ["ai", "models", "set-defaults"], description: "Set default AI models", examples: ["openmates settings ai models set-defaults --simple mistral/mistral-small-2506", "openmates settings ai models set-defaults --simple auto"] },
|
|
8751
|
+
{ path: ["privacy", "auto-delete", "chats", "set"], description: "Set chat auto-deletion period", examples: ["openmates settings privacy auto-delete chats set 90d"] },
|
|
8752
|
+
{ path: ["privacy", "debug-logs", "share"], description: "Create a debug log sharing session", examples: ["openmates settings privacy debug-logs share --duration 1h --confirm"] },
|
|
8753
|
+
{ path: ["billing", "overview"], description: "Show billing overview", examples: ["openmates settings billing overview"] },
|
|
8754
|
+
{ path: ["billing", "usage"], description: "Show usage history", examples: ["openmates settings billing usage --json"] },
|
|
8755
|
+
{ path: ["billing", "usage", "summaries"], description: "Show usage summaries", examples: ["openmates settings billing usage summaries"] },
|
|
8756
|
+
{ path: ["billing", "usage", "daily"], description: "Show daily usage overview", examples: ["openmates settings billing usage daily"] },
|
|
8757
|
+
{ path: ["billing", "usage", "export"], description: "Export usage data", examples: ["openmates settings billing usage export --json"] },
|
|
8758
|
+
{ path: ["billing", "invoices", "list"], description: "List invoices", examples: ["openmates settings billing invoices list --json"] },
|
|
8759
|
+
{ path: ["billing", "invoices", "download"], description: "Download an invoice PDF", examples: ["openmates settings billing invoices download <invoice-id> --output ./invoices"] },
|
|
8760
|
+
{ path: ["billing", "invoices", "credit-note"], description: "Download a credit note PDF", examples: ["openmates settings billing invoices credit-note <invoice-id> --output ./invoices"] },
|
|
8761
|
+
{ path: ["billing", "invoices", "refund"], description: "Request a refund for an invoice", examples: ["openmates settings billing invoices refund <invoice-id> --yes"] },
|
|
8762
|
+
{ path: ["billing", "gift-card", "redeem"], description: "Redeem a gift card", examples: ["openmates settings billing gift-card redeem ABCD-1234"] },
|
|
8763
|
+
{ path: ["billing", "gift-card", "list"], description: "List redeemed gift cards", examples: ["openmates settings billing gift-card list"] },
|
|
8764
|
+
{ 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
|
+
{ path: ["notifications", "status"], description: "Show notification settings", examples: ["openmates settings notifications status --json"] },
|
|
8766
|
+
{ path: ["notifications", "list"], description: "List recent notification events", examples: ["openmates settings notifications list --limit 20 --json"] },
|
|
8767
|
+
{ path: ["notifications", "stream"], description: "Stream notification events with SSE", examples: ["openmates settings notifications stream", "openmates settings notifications stream --count 1 --json"] },
|
|
8768
|
+
{ 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"] },
|
|
8769
|
+
{ path: ["notifications", "backup", "set"], description: "Configure backup reminder emails", examples: ["openmates settings notifications backup set --enabled true --interval 30 --email you@example.com"] },
|
|
8770
|
+
{ path: ["reminders", "list"], description: "List active reminders", examples: ["openmates settings reminders list"] },
|
|
8771
|
+
{ path: ["reminders", "update"], description: "Update a reminder", examples: ["openmates settings reminders update <id> --enabled false"] },
|
|
8772
|
+
{ path: ["reminders", "delete"], description: "Delete a reminder", examples: ["openmates settings reminders delete <id> --yes"] },
|
|
8773
|
+
{ path: ["developers", "api-keys", "list"], description: "List API keys", examples: ["openmates settings developers api-keys list"] },
|
|
8774
|
+
{ path: ["developers", "api-keys", "revoke"], description: "Revoke an API key", examples: ["openmates settings developers api-keys revoke <key-id> --yes"] },
|
|
8775
|
+
{ path: ["report-issue", "create"], description: "Report an issue", examples: ['openmates settings report-issue create --title "Bug" --body "What happened"'] },
|
|
8776
|
+
{ path: ["report-issue", "status"], description: "Show issue status", examples: ["openmates settings report-issue status <issue-id>"] },
|
|
8777
|
+
{ path: ["mates", "list"], description: "List available mates", examples: ["openmates settings mates list"] },
|
|
8778
|
+
{ path: ["mates", "info"], description: "Show mate details", examples: ["openmates settings mates info software_development"] },
|
|
8779
|
+
{ path: ["mates", "consent"], description: "Record mate settings consent", examples: ["openmates settings mates consent --yes"] },
|
|
8780
|
+
{ path: ["newsletter", "categories"], description: "Show newsletter category preferences", examples: ["openmates settings newsletter categories"] },
|
|
8781
|
+
{ path: ["newsletter", "categories", "set"], description: "Set newsletter category preferences", examples: ["openmates settings newsletter categories set --updates true --tips true --daily false"] },
|
|
8782
|
+
{ path: ["newsletter", "subscribe"], description: "Subscribe an email to the newsletter", examples: ["openmates settings newsletter subscribe you@example.com --language en"] },
|
|
8783
|
+
{ path: ["newsletter", "confirm"], description: "Confirm newsletter subscription token", examples: ["openmates settings newsletter confirm <token>"] },
|
|
8784
|
+
{ path: ["newsletter", "unsubscribe"], description: "Unsubscribe with newsletter token", examples: ["openmates settings newsletter unsubscribe <token>"] },
|
|
8785
|
+
{ path: ["memories"], description: "Manage encrypted memories", examples: ["openmates settings memories list", `openmates settings memories create --app-id code --item-type projects --data '{"name":"OpenMates"}'`] }
|
|
8786
|
+
];
|
|
8787
|
+
var SETTINGS_INFO_COMMANDS = [
|
|
8788
|
+
{ 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
|
+
{ 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
|
+
{ path: ["security", "passkeys"], description: "Passkeys are web-only", webPath: "account/security/passkeys", reason: "Passkeys require WebAuthn browser APIs.", examples: ["openmates settings security passkeys"] },
|
|
8792
|
+
{ 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"] },
|
|
8793
|
+
{ 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"] },
|
|
8794
|
+
{ 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"] },
|
|
8795
|
+
{ 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"] },
|
|
8796
|
+
{ 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"] },
|
|
8797
|
+
{ 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"] },
|
|
8798
|
+
{ 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"] },
|
|
8799
|
+
{ 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"] },
|
|
8800
|
+
{ 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"] },
|
|
8801
|
+
{ 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"] },
|
|
8802
|
+
{ path: ["developers", "devices"], description: "Developer devices are web-only", webPath: "developers/devices", reason: "Device approvals and revocations are sensitive.", examples: ["openmates settings developers devices"] },
|
|
8803
|
+
{ 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"] },
|
|
8804
|
+
{ path: ["support"], description: "Support payments are web-only", webPath: "support", reason: "Payment flows must use the browser/payment provider UI.", examples: ["openmates settings support"] },
|
|
8805
|
+
{ 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"'] },
|
|
8806
|
+
{ 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"] }
|
|
8807
|
+
];
|
|
8808
|
+
function matches(actual, expected) {
|
|
8809
|
+
return expected.every((part, index) => actual[index] === part);
|
|
8810
|
+
}
|
|
8811
|
+
function findSettingsInfoCommand(tokens) {
|
|
8812
|
+
const all = [...SETTINGS_INFO_COMMANDS, ...SETTINGS_EXECUTABLE_COMMANDS];
|
|
8813
|
+
return all.sort((a, b) => b.path.length - a.path.length).find((command) => matches(tokens, command.path)) ?? null;
|
|
8814
|
+
}
|
|
8815
|
+
async function printSettingsResult(resultPromise, flags) {
|
|
8816
|
+
const result = await resultPromise;
|
|
8817
|
+
flags.json === true ? printJson2(result) : printGenericObject(result);
|
|
8818
|
+
}
|
|
8819
|
+
async function printSettingsMutationResult(resultPromise, flags) {
|
|
8820
|
+
const result = await resultPromise;
|
|
8821
|
+
if (flags.json === true) {
|
|
8822
|
+
printJson2(result);
|
|
8823
|
+
return;
|
|
8824
|
+
}
|
|
8825
|
+
process.stdout.write("\x1B[32m\u2713\x1B[0m Settings updated\n");
|
|
8826
|
+
if (result && typeof result === "object") printGenericObject(result);
|
|
8827
|
+
}
|
|
8828
|
+
function addQueryParam(params, key, value) {
|
|
8829
|
+
if (typeof value === "string" && value.length > 0) params.set(key, value);
|
|
8830
|
+
}
|
|
8831
|
+
function parseOnOff(value, label) {
|
|
8832
|
+
if (value === "on" || value === "true" || value === "1") return true;
|
|
8833
|
+
if (value === "off" || value === "false" || value === "0") return false;
|
|
8834
|
+
throw new Error(`Invalid ${label} value '${value ?? ""}'. Use on/off or true/false.`);
|
|
8835
|
+
}
|
|
8836
|
+
function parseModelDefaultFlag(value, flag) {
|
|
8837
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
8838
|
+
throw new Error(`Missing value for ${flag}. Use a provider/model-id or auto.`);
|
|
8839
|
+
}
|
|
8840
|
+
if (value === "auto" || value === "null") return null;
|
|
8841
|
+
return value;
|
|
8842
|
+
}
|
|
8843
|
+
function parseRequiredNumber(value, flag) {
|
|
8844
|
+
if (typeof value !== "string") throw new Error(`Missing ${flag}.`);
|
|
8845
|
+
const parsed = Number(value);
|
|
8846
|
+
if (!Number.isFinite(parsed)) throw new Error(`Invalid ${flag}: ${value}`);
|
|
8847
|
+
return parsed;
|
|
8848
|
+
}
|
|
8849
|
+
function parseOptionalNumber(value, fallback, flag) {
|
|
8850
|
+
if (value === void 0) return fallback;
|
|
8851
|
+
return parseRequiredNumber(value, flag);
|
|
8852
|
+
}
|
|
8853
|
+
function parseOptionalBoolean(value, fallback, label) {
|
|
8854
|
+
if (value === void 0) return fallback;
|
|
8855
|
+
if (typeof value === "boolean") return value;
|
|
8856
|
+
return parseOnOff(value, label);
|
|
8857
|
+
}
|
|
8858
|
+
function parseDataOrFlags(flags, booleanFlags) {
|
|
8859
|
+
if (typeof flags.data === "string") return JSON.parse(flags.data);
|
|
8860
|
+
const body = {};
|
|
8861
|
+
for (const key of booleanFlags) {
|
|
8862
|
+
if (flags[key] !== void 0) body[key] = parseOnOff(String(flags[key]), key);
|
|
8863
|
+
}
|
|
8864
|
+
if (Object.keys(body).length === 0) throw new Error("Provide --data '<json>' or a supported flag.");
|
|
8865
|
+
return body;
|
|
8866
|
+
}
|
|
8867
|
+
function parseChatImportPayload(raw) {
|
|
8868
|
+
const trimmed = raw.trim();
|
|
8869
|
+
if (!trimmed) throw new Error("Import file is empty.");
|
|
8870
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
8871
|
+
const parsed = JSON.parse(trimmed);
|
|
8872
|
+
if (Array.isArray(parsed)) return { chats: parsed };
|
|
8873
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.chats)) {
|
|
8874
|
+
return parsed;
|
|
8875
|
+
}
|
|
8876
|
+
if (parsed && typeof parsed === "object") {
|
|
8877
|
+
const object = parsed;
|
|
8878
|
+
if (object.chat || object.messages) return { chats: [normalizeImportedChat(object)] };
|
|
8879
|
+
}
|
|
8880
|
+
throw new Error("JSON import must contain a chats array, a chat object, or messages.");
|
|
8881
|
+
}
|
|
8882
|
+
return { chats: [parseCliExportYaml(trimmed)] };
|
|
8883
|
+
}
|
|
8884
|
+
function normalizeImportedChat(source) {
|
|
8885
|
+
const chat = source.chat && typeof source.chat === "object" ? source.chat : source;
|
|
8886
|
+
const messages = Array.isArray(source.messages) ? source.messages : [];
|
|
8887
|
+
return {
|
|
8888
|
+
title: chat.title ?? null,
|
|
8889
|
+
draft: chat.draft ?? null,
|
|
8890
|
+
summary: chat.summary ?? null,
|
|
8891
|
+
messages: messages.map((message) => normalizeImportedMessage(message))
|
|
8892
|
+
};
|
|
8893
|
+
}
|
|
8894
|
+
function normalizeImportedMessage(message) {
|
|
8895
|
+
return {
|
|
8896
|
+
role: message.role,
|
|
8897
|
+
content: message.content,
|
|
8898
|
+
completed_at: message.completed_at ?? message.timestamp ?? null,
|
|
8899
|
+
assistant_category: message.assistant_category ?? null,
|
|
8900
|
+
thinking: message.thinking ?? null,
|
|
8901
|
+
has_thinking: message.has_thinking ?? null,
|
|
8902
|
+
thinking_tokens: message.thinking_tokens ?? null
|
|
8903
|
+
};
|
|
8904
|
+
}
|
|
8905
|
+
function parseCliExportYaml(raw) {
|
|
8906
|
+
const chat = {};
|
|
8907
|
+
const messages = [];
|
|
8908
|
+
const lines = raw.split("\n");
|
|
8909
|
+
let section = null;
|
|
8910
|
+
let currentMessage = null;
|
|
8911
|
+
const multilineState = { current: null };
|
|
8912
|
+
const setValue = (target, key, value, indent) => {
|
|
8913
|
+
if (value === "|") {
|
|
8914
|
+
target[key] = "";
|
|
8915
|
+
multilineState.current = { target, key, indent: indent + 2 };
|
|
8916
|
+
return;
|
|
8917
|
+
}
|
|
8918
|
+
target[key] = parseYamlScalar(value);
|
|
8919
|
+
};
|
|
8920
|
+
for (const line of lines) {
|
|
8921
|
+
const indent = line.match(/^ */)?.[0].length ?? 0;
|
|
8922
|
+
const trimmed = line.trimEnd();
|
|
8923
|
+
if (!trimmed.trim()) continue;
|
|
8924
|
+
if (multilineState.current) {
|
|
8925
|
+
if (indent >= multilineState.current.indent) {
|
|
8926
|
+
const previous = String(multilineState.current.target[multilineState.current.key] ?? "");
|
|
8927
|
+
const nextLine = line.slice(multilineState.current.indent);
|
|
8928
|
+
multilineState.current.target[multilineState.current.key] = previous ? `${previous}
|
|
8929
|
+
${nextLine}` : nextLine;
|
|
8930
|
+
continue;
|
|
8931
|
+
}
|
|
8932
|
+
multilineState.current = null;
|
|
8933
|
+
}
|
|
8934
|
+
if (trimmed === "chat:") {
|
|
8935
|
+
section = "chat";
|
|
8936
|
+
currentMessage = null;
|
|
8937
|
+
continue;
|
|
8938
|
+
}
|
|
8939
|
+
if (trimmed === "messages:") {
|
|
8940
|
+
section = "messages";
|
|
8941
|
+
currentMessage = null;
|
|
8942
|
+
continue;
|
|
8943
|
+
}
|
|
8944
|
+
if (section === "messages" && trimmed.trim() === "-") {
|
|
8945
|
+
currentMessage = {};
|
|
8946
|
+
messages.push(currentMessage);
|
|
8947
|
+
continue;
|
|
8948
|
+
}
|
|
8949
|
+
const match = /^\s*([\w-]+):\s*(.*)$/.exec(line);
|
|
8950
|
+
if (!match) continue;
|
|
8951
|
+
const [, key, value] = match;
|
|
8952
|
+
if (section === "chat") setValue(chat, key, value, indent);
|
|
8953
|
+
if (section === "messages" && currentMessage) setValue(currentMessage, key, value, indent);
|
|
8954
|
+
}
|
|
8955
|
+
if (messages.length === 0) throw new Error("Import YAML did not contain any messages.");
|
|
8956
|
+
return normalizeImportedChat({ chat, messages });
|
|
8957
|
+
}
|
|
8958
|
+
function parseYamlScalar(value) {
|
|
8959
|
+
const trimmed = value.trim();
|
|
8960
|
+
if (trimmed === "null") return null;
|
|
8961
|
+
if (trimmed === "true") return true;
|
|
8962
|
+
if (trimmed === "false") return false;
|
|
8963
|
+
if (trimmed !== "" && Number.isFinite(Number(trimmed))) return Number(trimmed);
|
|
8964
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
8965
|
+
return trimmed.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
8966
|
+
}
|
|
8967
|
+
return trimmed;
|
|
8968
|
+
}
|
|
8969
|
+
async function saveDownloadedDocument(document, output) {
|
|
8970
|
+
const { mkdir, writeFile } = await import("fs/promises");
|
|
8971
|
+
const { join: join4, basename: basename2, dirname } = await import("path");
|
|
8972
|
+
const target = typeof output === "string" ? output : ".";
|
|
8973
|
+
const filename = basename2(document.filename || "document.pdf");
|
|
8974
|
+
const filePath = target.endsWith(".pdf") ? target : join4(target, filename);
|
|
8975
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
8976
|
+
await writeFile(filePath, document.data);
|
|
8977
|
+
return filePath;
|
|
8978
|
+
}
|
|
8979
|
+
function printMates(json) {
|
|
8980
|
+
const mates = Object.entries(MATE_NAMES).map(([id, name]) => ({ id, name, mention: `@mate:${id}` }));
|
|
8981
|
+
if (json) {
|
|
8982
|
+
printJson2(mates);
|
|
8983
|
+
return;
|
|
8984
|
+
}
|
|
8985
|
+
header("Mates");
|
|
8986
|
+
for (const mate of mates) console.log(`${mate.id.padEnd(28)} ${mate.name.padEnd(10)} ${mate.mention}`);
|
|
8987
|
+
}
|
|
8988
|
+
function printMateInfo(mateId, json) {
|
|
8989
|
+
const name = MATE_NAMES[mateId];
|
|
8990
|
+
if (!name) throw new Error(`Unknown mate '${mateId}'. Run 'openmates settings mates list'.`);
|
|
8991
|
+
const data = { id: mateId, name, mention: `@mate:${mateId}` };
|
|
8992
|
+
if (json) {
|
|
8993
|
+
printJson2(data);
|
|
8994
|
+
return;
|
|
8995
|
+
}
|
|
8996
|
+
header(`${name} (${mateId})`);
|
|
8997
|
+
console.log(`Mention: ${data.mention}`);
|
|
8998
|
+
console.log(`Use: openmates chats send "${data.mention} <message>"`);
|
|
8999
|
+
}
|
|
9000
|
+
async function confirmOrExit(question) {
|
|
9001
|
+
const rl = await import("readline");
|
|
9002
|
+
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
9003
|
+
const answer = await new Promise((resolve4) => iface.question(question, resolve4));
|
|
9004
|
+
iface.close();
|
|
9005
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
9006
|
+
console.log("Aborted.");
|
|
9007
|
+
process.exit(0);
|
|
9008
|
+
}
|
|
9009
|
+
}
|
|
9010
|
+
function printSettingsInfoCommand(client, command, json) {
|
|
9011
|
+
const appUrl = deriveAppUrl(client.apiUrl);
|
|
9012
|
+
const webUrl = command.webPath ? `${appUrl}/#settings/${command.webPath}` : null;
|
|
9013
|
+
if (json) {
|
|
9014
|
+
printJson2({
|
|
9015
|
+
command: `openmates settings ${command.path.join(" ")}`,
|
|
9016
|
+
supported_in_cli: false,
|
|
9017
|
+
description: command.description,
|
|
9018
|
+
reason: command.reason ?? null,
|
|
9019
|
+
web_url: webUrl,
|
|
9020
|
+
examples: command.examples
|
|
9021
|
+
});
|
|
9022
|
+
return;
|
|
9023
|
+
}
|
|
9024
|
+
header(command.description);
|
|
9025
|
+
if (command.reason) console.log(command.reason);
|
|
9026
|
+
if (webUrl) console.log(`
|
|
9027
|
+
Open in web app:
|
|
9028
|
+
${webUrl}`);
|
|
9029
|
+
if (command.examples.length > 0) {
|
|
9030
|
+
console.log("\nExamples:");
|
|
9031
|
+
for (const example of command.examples) console.log(` ${example}`);
|
|
9032
|
+
}
|
|
9033
|
+
}
|
|
9034
|
+
async function handleSettings(client, subcommand, rest, flags) {
|
|
9035
|
+
if (!subcommand || subcommand === "help") {
|
|
9036
|
+
printSettingsHelp(client, subcommand ? [] : void 0);
|
|
9037
|
+
return;
|
|
9038
|
+
}
|
|
9039
|
+
const tokens = [subcommand, ...rest].filter((token) => token !== "help");
|
|
9040
|
+
if (rest.includes("help") || Boolean(flags.help)) {
|
|
9041
|
+
printSettingsHelp(client, tokens);
|
|
9042
|
+
return;
|
|
9043
|
+
}
|
|
9044
|
+
if (["get", "post", "patch", "delete"].includes(subcommand)) {
|
|
9045
|
+
console.error(
|
|
9046
|
+
"Raw settings passthrough is no longer supported. Use a predefined settings command.\n"
|
|
9047
|
+
);
|
|
9048
|
+
printSettingsHelp(client);
|
|
9049
|
+
process.exit(1);
|
|
9050
|
+
}
|
|
9051
|
+
if (matches(tokens, ["account", "info"])) {
|
|
9052
|
+
const user = await client.whoAmI();
|
|
9053
|
+
flags.json === true ? printJson2(user) : printWhoAmI(user);
|
|
9054
|
+
return;
|
|
9055
|
+
}
|
|
9056
|
+
if (matches(tokens, ["account", "timezone", "set"])) {
|
|
9057
|
+
const timezone = rest[2];
|
|
9058
|
+
if (!timezone) throw new Error("Missing timezone. Example: openmates settings account timezone set Europe/Berlin");
|
|
9059
|
+
await printSettingsMutationResult(
|
|
9060
|
+
client.settingsPost("user/timezone", { timezone }),
|
|
9061
|
+
flags
|
|
9062
|
+
);
|
|
9063
|
+
return;
|
|
9064
|
+
}
|
|
9065
|
+
if (matches(tokens, ["account", "export", "manifest"])) {
|
|
9066
|
+
await printSettingsResult(client.settingsGet("export-account-manifest"), flags);
|
|
9067
|
+
return;
|
|
9068
|
+
}
|
|
9069
|
+
if (matches(tokens, ["account", "export", "data"])) {
|
|
9070
|
+
await printSettingsResult(client.settingsGet("export-account-data"), flags);
|
|
9071
|
+
return;
|
|
9072
|
+
}
|
|
9073
|
+
if (matches(tokens, ["account", "import-chat"])) {
|
|
9074
|
+
const file = rest[1];
|
|
9075
|
+
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 readFile(file, "utf-8");
|
|
9078
|
+
await printSettingsMutationResult(
|
|
9079
|
+
client.settingsPost("import-chat", parseChatImportPayload(content)),
|
|
9080
|
+
flags
|
|
9081
|
+
);
|
|
9082
|
+
return;
|
|
9083
|
+
}
|
|
9084
|
+
if (matches(tokens, ["account", "username", "set"])) {
|
|
9085
|
+
const username = rest[2];
|
|
9086
|
+
if (!username) throw new Error("Missing username. Example: openmates settings account username set alice_123");
|
|
9087
|
+
await printSettingsMutationResult(client.updateUsername(username), flags);
|
|
9088
|
+
return;
|
|
9089
|
+
}
|
|
9090
|
+
if (matches(tokens, ["account", "profile-picture", "set"])) {
|
|
9091
|
+
const file = rest[2];
|
|
9092
|
+
if (!file) throw new Error("Missing image file. Example: openmates settings account profile-picture set ./avatar.jpg");
|
|
9093
|
+
await printSettingsMutationResult(client.updateProfileImage(file), flags);
|
|
9094
|
+
return;
|
|
9095
|
+
}
|
|
9096
|
+
if (matches(tokens, ["account", "chats", "stats"])) {
|
|
9097
|
+
await printSettingsResult(client.settingsGet("chats"), flags);
|
|
9098
|
+
return;
|
|
9099
|
+
}
|
|
9100
|
+
if (matches(tokens, ["account", "delete", "preview"])) {
|
|
9101
|
+
await printSettingsResult(client.settingsGet("delete-account-preview"), flags);
|
|
9102
|
+
return;
|
|
9103
|
+
}
|
|
9104
|
+
if (matches(tokens, ["account", "storage", "overview"])) {
|
|
9105
|
+
await printSettingsResult(client.settingsGet("storage"), flags);
|
|
9106
|
+
return;
|
|
9107
|
+
}
|
|
9108
|
+
if (matches(tokens, ["account", "storage", "files"])) {
|
|
9109
|
+
const params = new URLSearchParams();
|
|
9110
|
+
addQueryParam(params, "category", flags.category ?? flags.type);
|
|
9111
|
+
const query = params.toString();
|
|
9112
|
+
await printSettingsResult(client.settingsGet(`storage/files${query ? `?${query}` : ""}`), flags);
|
|
9113
|
+
return;
|
|
9114
|
+
}
|
|
9115
|
+
if (matches(tokens, ["account", "storage", "delete"])) {
|
|
9116
|
+
const fileId = rest[3];
|
|
9117
|
+
const category = typeof flags.category === "string" ? flags.category : void 0;
|
|
9118
|
+
const scope = flags.all === true ? "all" : category ? "category" : "single";
|
|
9119
|
+
if (scope === "single" && !fileId) throw new Error("Missing file ID.");
|
|
9120
|
+
if (flags.yes !== true) await confirmOrExit(`Delete stored file data (${scope})? This cannot be undone. [y/N] `);
|
|
9121
|
+
await printSettingsMutationResult(
|
|
9122
|
+
client.settingsDelete("storage/files", { scope, file_id: fileId, category }),
|
|
9123
|
+
flags
|
|
9124
|
+
);
|
|
9125
|
+
return;
|
|
9126
|
+
}
|
|
9127
|
+
if (matches(tokens, ["interface", "language", "set"])) {
|
|
9128
|
+
const language = rest[2];
|
|
9129
|
+
if (!language) throw new Error("Missing language code. Example: openmates settings interface language set en");
|
|
9130
|
+
await printSettingsMutationResult(client.settingsPost("user/language", { language }), flags);
|
|
9131
|
+
return;
|
|
7608
9132
|
}
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
7613
|
-
printEmbedsHelp();
|
|
9133
|
+
if (matches(tokens, ["interface", "dark-mode", "set"])) {
|
|
9134
|
+
const value = parseOnOff(rest[2], "dark mode");
|
|
9135
|
+
await printSettingsMutationResult(client.settingsPost("user/darkmode", { dark_mode: value }), flags);
|
|
7614
9136
|
return;
|
|
7615
9137
|
}
|
|
7616
|
-
if (
|
|
7617
|
-
const
|
|
7618
|
-
if (!
|
|
7619
|
-
|
|
7620
|
-
printEmbedsHelp();
|
|
7621
|
-
process.exit(1);
|
|
7622
|
-
}
|
|
7623
|
-
const embed = await client.getEmbed(embedId);
|
|
7624
|
-
if (flags.json === true) {
|
|
7625
|
-
printJson2(embed);
|
|
7626
|
-
} else {
|
|
7627
|
-
await renderEmbedFullscreen(embed, client);
|
|
7628
|
-
}
|
|
9138
|
+
if (matches(tokens, ["interface", "font", "set"])) {
|
|
9139
|
+
const font = rest[2];
|
|
9140
|
+
if (!font) throw new Error("Missing font. Example: openmates settings interface font set lexend");
|
|
9141
|
+
await printSettingsMutationResult(client.settingsPost("user/ui-font", { ui_font: font }), flags);
|
|
7629
9142
|
return;
|
|
7630
9143
|
}
|
|
7631
|
-
if (
|
|
7632
|
-
const
|
|
7633
|
-
if (
|
|
7634
|
-
|
|
7635
|
-
"Missing embed ID. Usage: openmates embeds share <embed-id>"
|
|
7636
|
-
);
|
|
7637
|
-
process.exit(1);
|
|
9144
|
+
if (matches(tokens, ["ai", "models", "set-defaults"])) {
|
|
9145
|
+
const body = {};
|
|
9146
|
+
if (flags.simple !== void 0) {
|
|
9147
|
+
body.default_ai_model_simple = parseModelDefaultFlag(flags.simple, "--simple");
|
|
7638
9148
|
}
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
if (password && password.length > 10) {
|
|
7642
|
-
console.error("Password must be at most 10 characters.");
|
|
7643
|
-
process.exit(1);
|
|
9149
|
+
if (flags.complex !== void 0) {
|
|
9150
|
+
body.default_ai_model_complex = parseModelDefaultFlag(flags.complex, "--complex");
|
|
7644
9151
|
}
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
id,
|
|
7648
|
-
durationSeconds,
|
|
7649
|
-
password
|
|
7650
|
-
);
|
|
7651
|
-
if (flags.json === true) {
|
|
7652
|
-
printJson2({
|
|
7653
|
-
url,
|
|
7654
|
-
embed_id: id,
|
|
7655
|
-
expires: durationSeconds,
|
|
7656
|
-
password_protected: !!password
|
|
7657
|
-
});
|
|
7658
|
-
} else {
|
|
7659
|
-
process.stdout.write(`
|
|
7660
|
-
\x1B[1mEmbed share link\x1B[0m
|
|
7661
|
-
`);
|
|
7662
|
-
process.stdout.write(`${url}
|
|
7663
|
-
|
|
7664
|
-
`);
|
|
7665
|
-
if (durationSeconds > 0) {
|
|
7666
|
-
process.stdout.write(
|
|
7667
|
-
`\x1B[2mExpires in ${humanizeDuration(durationSeconds)}\x1B[0m
|
|
7668
|
-
`
|
|
7669
|
-
);
|
|
7670
|
-
}
|
|
7671
|
-
if (password) {
|
|
7672
|
-
process.stdout.write(`\x1B[2mPassword protected\x1B[0m
|
|
7673
|
-
`);
|
|
7674
|
-
}
|
|
7675
|
-
}
|
|
7676
|
-
} catch (err) {
|
|
7677
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
7678
|
-
console.error(`Share link error: ${msg}`);
|
|
7679
|
-
process.exit(1);
|
|
9152
|
+
if (Object.keys(body).length === 0) {
|
|
9153
|
+
throw new Error("Provide --simple <model-id|auto> and/or --complex <model-id|auto>.");
|
|
7680
9154
|
}
|
|
9155
|
+
await printSettingsMutationResult(client.settingsPost("ai-model-defaults", body), flags);
|
|
7681
9156
|
return;
|
|
7682
9157
|
}
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
}
|
|
7688
|
-
async function handleSettings(client, subcommand, rest, flags) {
|
|
7689
|
-
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
7690
|
-
printSettingsHelp(client);
|
|
9158
|
+
if (matches(tokens, ["privacy", "auto-delete", "chats", "set"])) {
|
|
9159
|
+
const period = rest[3];
|
|
9160
|
+
if (!period) throw new Error("Missing period. Example: openmates settings privacy auto-delete chats set 90d");
|
|
9161
|
+
await printSettingsMutationResult(client.settingsPost("auto-delete-chats", { period }), flags);
|
|
7691
9162
|
return;
|
|
7692
9163
|
}
|
|
7693
|
-
if (
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
console.error("Missing path.\n");
|
|
7697
|
-
printSettingsHelp();
|
|
7698
|
-
process.exit(1);
|
|
7699
|
-
}
|
|
7700
|
-
const result = await client.settingsGet(path);
|
|
7701
|
-
if (flags.json === true) {
|
|
7702
|
-
printJson2(result);
|
|
7703
|
-
} else {
|
|
7704
|
-
printGenericObject(result);
|
|
9164
|
+
if (matches(tokens, ["privacy", "debug-logs", "share"])) {
|
|
9165
|
+
if (flags.yes !== true && flags.confirm !== true) {
|
|
9166
|
+
await confirmOrExit("Share debug logs with OpenMates support? [y/N] ");
|
|
7705
9167
|
}
|
|
9168
|
+
const duration = typeof flags.duration === "string" ? flags.duration : "1h";
|
|
9169
|
+
await printSettingsMutationResult(client.settingsPost("debug-session", { duration }), flags);
|
|
7706
9170
|
return;
|
|
7707
9171
|
}
|
|
7708
|
-
if (
|
|
7709
|
-
|
|
7710
|
-
if (!path) {
|
|
7711
|
-
console.error("Missing path.\n");
|
|
7712
|
-
printSettingsHelp();
|
|
7713
|
-
process.exit(1);
|
|
7714
|
-
}
|
|
7715
|
-
const dataRaw = typeof flags.data === "string" ? flags.data : "{}";
|
|
7716
|
-
const data = JSON.parse(dataRaw);
|
|
7717
|
-
const result = await client.settingsPost(path, data);
|
|
7718
|
-
if (flags.json === true) {
|
|
7719
|
-
printJson2(result);
|
|
7720
|
-
} else {
|
|
7721
|
-
printGenericObject(result);
|
|
7722
|
-
}
|
|
9172
|
+
if (matches(tokens, ["billing", "overview"])) {
|
|
9173
|
+
await printSettingsResult(client.settingsGet("billing"), flags);
|
|
7723
9174
|
return;
|
|
7724
9175
|
}
|
|
7725
|
-
if (
|
|
7726
|
-
|
|
7727
|
-
if (!path) {
|
|
7728
|
-
console.error("Missing path.\n");
|
|
7729
|
-
printSettingsHelp();
|
|
7730
|
-
process.exit(1);
|
|
7731
|
-
}
|
|
7732
|
-
const result = await client.settingsDelete(path);
|
|
7733
|
-
if (flags.json === true) {
|
|
7734
|
-
printJson2(result);
|
|
7735
|
-
} else {
|
|
7736
|
-
printGenericObject(result);
|
|
7737
|
-
}
|
|
9176
|
+
if (matches(tokens, ["billing", "usage", "summaries"])) {
|
|
9177
|
+
await printSettingsResult(client.settingsGet("usage/summaries"), flags);
|
|
7738
9178
|
return;
|
|
7739
9179
|
}
|
|
7740
|
-
if (
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
9180
|
+
if (matches(tokens, ["billing", "usage", "daily"])) {
|
|
9181
|
+
await printSettingsResult(client.settingsGet("usage/daily-overview"), flags);
|
|
9182
|
+
return;
|
|
9183
|
+
}
|
|
9184
|
+
if (matches(tokens, ["billing", "usage", "export"])) {
|
|
9185
|
+
await printSettingsResult(client.settingsGet("usage/export"), flags);
|
|
9186
|
+
return;
|
|
9187
|
+
}
|
|
9188
|
+
if (matches(tokens, ["billing", "usage"])) {
|
|
9189
|
+
await printSettingsResult(client.settingsGet("usage"), flags);
|
|
9190
|
+
return;
|
|
9191
|
+
}
|
|
9192
|
+
if (matches(tokens, ["billing", "invoices", "list"])) {
|
|
9193
|
+
await printSettingsResult(client.listInvoices(), flags);
|
|
9194
|
+
return;
|
|
9195
|
+
}
|
|
9196
|
+
if (matches(tokens, ["billing", "invoices", "download"])) {
|
|
9197
|
+
const invoiceId = rest[2];
|
|
9198
|
+
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
9199
|
+
const document = await client.downloadInvoice(invoiceId);
|
|
9200
|
+
const filePath = await saveDownloadedDocument(document, flags.output);
|
|
9201
|
+
if (flags.json === true) printJson2({ path: filePath, filename: document.filename });
|
|
9202
|
+
else console.log(`\x1B[32m\u2713\x1B[0m Invoice saved to ${filePath}`);
|
|
9203
|
+
return;
|
|
9204
|
+
}
|
|
9205
|
+
if (matches(tokens, ["billing", "invoices", "credit-note"])) {
|
|
9206
|
+
const invoiceId = rest[2];
|
|
9207
|
+
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
9208
|
+
const document = await client.downloadCreditNote(invoiceId);
|
|
9209
|
+
const filePath = await saveDownloadedDocument(document, flags.output);
|
|
9210
|
+
if (flags.json === true) printJson2({ path: filePath, filename: document.filename });
|
|
9211
|
+
else console.log(`\x1B[32m\u2713\x1B[0m Credit note saved to ${filePath}`);
|
|
9212
|
+
return;
|
|
9213
|
+
}
|
|
9214
|
+
if (matches(tokens, ["billing", "invoices", "refund"])) {
|
|
9215
|
+
const invoiceId = rest[2];
|
|
9216
|
+
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
9217
|
+
if (flags.yes !== true) await confirmOrExit(`Request refund for invoice ${invoiceId}? [y/N] `);
|
|
9218
|
+
await printSettingsMutationResult(client.requestRefund(invoiceId), flags);
|
|
9219
|
+
return;
|
|
9220
|
+
}
|
|
9221
|
+
if (matches(tokens, ["billing", "gift-card", "redeem"]) || subcommand === "gift-card" && rest[0] === "redeem") {
|
|
9222
|
+
const code = matches(tokens, ["billing", "gift-card", "redeem"]) ? rest[2] : rest[1];
|
|
9223
|
+
if (!code) throw new Error("Missing gift card code.");
|
|
9224
|
+
const result = await client.redeemGiftCard(code);
|
|
7750
9225
|
if (flags.json === true) {
|
|
7751
9226
|
printJson2(result);
|
|
9227
|
+
} else if (result.success) {
|
|
9228
|
+
process.stdout.write(`\x1B[32m\u2713\x1B[0m Gift card redeemed! +${result.credits_added} credits
|
|
9229
|
+
`);
|
|
9230
|
+
process.stdout.write(` Balance: ${result.current_credits} credits
|
|
9231
|
+
`);
|
|
7752
9232
|
} else {
|
|
7753
|
-
|
|
9233
|
+
process.stdout.write(`\x1B[31m\u2717\x1B[0m ${result.message}
|
|
9234
|
+
`);
|
|
7754
9235
|
}
|
|
7755
9236
|
return;
|
|
7756
9237
|
}
|
|
7757
|
-
if (subcommand === "
|
|
7758
|
-
await
|
|
9238
|
+
if (matches(tokens, ["billing", "gift-card", "list"]) || subcommand === "gift-card" && rest[0] === "list") {
|
|
9239
|
+
await printSettingsResult(client.listRedeemedGiftCards(), flags);
|
|
7759
9240
|
return;
|
|
7760
9241
|
}
|
|
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
|
-
|
|
9242
|
+
if (matches(tokens, ["billing", "auto-topup", "low-balance", "set"])) {
|
|
9243
|
+
const enabled = parseOnOff(String(flags.enabled ?? ""), "low-balance auto top-up");
|
|
9244
|
+
const amount = parseRequiredNumber(flags.amount, "--amount");
|
|
9245
|
+
const currency = typeof flags.currency === "string" ? flags.currency : "eur";
|
|
9246
|
+
const email = typeof flags.email === "string" ? flags.email : void 0;
|
|
9247
|
+
if (enabled && !email) throw new Error("Provide --email when enabling low-balance auto top-up.");
|
|
9248
|
+
await printSettingsMutationResult(
|
|
9249
|
+
client.settingsPost("auto-topup/low-balance", { enabled, threshold: 100, amount, currency, email }),
|
|
9250
|
+
flags
|
|
9251
|
+
);
|
|
9252
|
+
return;
|
|
9253
|
+
}
|
|
9254
|
+
if (matches(tokens, ["notifications", "status"])) {
|
|
9255
|
+
const user = await client.whoAmI();
|
|
9256
|
+
const status = {
|
|
9257
|
+
enabled: user.email_notifications_enabled ?? false,
|
|
9258
|
+
preferences: user.email_notification_preferences ?? {},
|
|
9259
|
+
backup_reminder_interval_days: user.backup_reminder_interval_days ?? null,
|
|
9260
|
+
encrypted_notification_email_configured: Boolean(user.encrypted_notification_email)
|
|
9261
|
+
};
|
|
9262
|
+
flags.json === true ? printJson2(status) : printGenericObject(status);
|
|
9263
|
+
return;
|
|
9264
|
+
}
|
|
9265
|
+
if (matches(tokens, ["notifications", "list"])) {
|
|
9266
|
+
const limit = parseOptionalNumber(flags.limit, 50, "--limit");
|
|
9267
|
+
await printSettingsResult(client.listNotifications(limit), flags);
|
|
9268
|
+
return;
|
|
9269
|
+
}
|
|
9270
|
+
if (matches(tokens, ["notifications", "stream"])) {
|
|
9271
|
+
const count = flags.count === void 0 ? null : parseRequiredNumber(flags.count, "--count");
|
|
9272
|
+
let received = 0;
|
|
9273
|
+
for await (const event of client.streamNotifications()) {
|
|
7792
9274
|
if (flags.json === true) {
|
|
7793
|
-
printJson2(
|
|
9275
|
+
printJson2(event);
|
|
7794
9276
|
} else {
|
|
7795
|
-
printGenericObject(
|
|
9277
|
+
printGenericObject(event);
|
|
7796
9278
|
}
|
|
7797
|
-
|
|
9279
|
+
received += 1;
|
|
9280
|
+
if (count !== null && received >= count) break;
|
|
7798
9281
|
}
|
|
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
9282
|
return;
|
|
7803
9283
|
}
|
|
7804
|
-
|
|
9284
|
+
if (matches(tokens, ["notifications", "email", "set"])) {
|
|
9285
|
+
const enabled = parseOnOff(String(flags.enabled ?? ""), "email notifications");
|
|
9286
|
+
const email = typeof flags.email === "string" ? flags.email : null;
|
|
9287
|
+
if (enabled && !email) throw new Error("Provide --email when enabling email notifications.");
|
|
9288
|
+
const preferences = {
|
|
9289
|
+
aiResponses: parseOptionalBoolean(flags["ai-responses"], true, "AI response notifications"),
|
|
9290
|
+
backupReminder: parseOptionalBoolean(flags["backup-reminder"], false, "backup reminder notifications"),
|
|
9291
|
+
webhookChats: parseOptionalBoolean(flags["webhook-chats"], false, "webhook chat notifications")
|
|
9292
|
+
};
|
|
9293
|
+
await printSettingsMutationResult(
|
|
9294
|
+
client.updateEmailNotificationSettings({ enabled, email, preferences }),
|
|
9295
|
+
flags
|
|
9296
|
+
);
|
|
9297
|
+
return;
|
|
9298
|
+
}
|
|
9299
|
+
if (matches(tokens, ["notifications", "backup", "set"])) {
|
|
9300
|
+
const enabled = parseOnOff(String(flags.enabled ?? ""), "backup reminders");
|
|
9301
|
+
const email = typeof flags.email === "string" ? flags.email : null;
|
|
9302
|
+
const interval = parseRequiredNumber(flags.interval, "--interval");
|
|
9303
|
+
if (enabled && !email) throw new Error("Provide --email when enabling backup reminders.");
|
|
9304
|
+
await printSettingsMutationResult(
|
|
9305
|
+
client.updateEmailNotificationSettings({
|
|
9306
|
+
enabled,
|
|
9307
|
+
email,
|
|
9308
|
+
preferences: { aiResponses: false, backupReminder: enabled },
|
|
9309
|
+
backup_reminder_interval_days: interval
|
|
9310
|
+
}),
|
|
9311
|
+
flags
|
|
9312
|
+
);
|
|
9313
|
+
return;
|
|
9314
|
+
}
|
|
9315
|
+
if (matches(tokens, ["reminders", "list"])) {
|
|
9316
|
+
await printSettingsResult(client.settingsGet("reminders"), flags);
|
|
9317
|
+
return;
|
|
9318
|
+
}
|
|
9319
|
+
if (matches(tokens, ["reminders", "update"])) {
|
|
9320
|
+
const id = rest[1];
|
|
9321
|
+
if (!id) throw new Error("Missing reminder ID.");
|
|
9322
|
+
const body = parseDataOrFlags(flags, ["enabled"]);
|
|
9323
|
+
await printSettingsMutationResult(client.settingsPatch(`reminders/${id}`, body), flags);
|
|
9324
|
+
return;
|
|
9325
|
+
}
|
|
9326
|
+
if (matches(tokens, ["reminders", "delete"])) {
|
|
9327
|
+
const id = rest[1];
|
|
9328
|
+
if (!id) throw new Error("Missing reminder ID.");
|
|
9329
|
+
if (flags.yes !== true) await confirmOrExit(`Delete reminder ${id}? [y/N] `);
|
|
9330
|
+
await printSettingsMutationResult(client.settingsDelete(`reminders/${id}`), flags);
|
|
9331
|
+
return;
|
|
9332
|
+
}
|
|
9333
|
+
if (matches(tokens, ["developers", "api-keys", "list"])) {
|
|
9334
|
+
await printSettingsResult(client.settingsGet("api-keys"), flags);
|
|
9335
|
+
return;
|
|
9336
|
+
}
|
|
9337
|
+
if (matches(tokens, ["developers", "api-keys", "revoke"])) {
|
|
9338
|
+
const id = rest[2];
|
|
9339
|
+
if (!id) throw new Error("Missing API key ID.");
|
|
9340
|
+
if (flags.yes !== true) await confirmOrExit(`Revoke API key ${id}? [y/N] `);
|
|
9341
|
+
await printSettingsMutationResult(client.settingsDelete(`api-keys/${id}`), flags);
|
|
9342
|
+
return;
|
|
9343
|
+
}
|
|
9344
|
+
if (matches(tokens, ["report-issue", "create"])) {
|
|
9345
|
+
const title = typeof flags.title === "string" ? flags.title : void 0;
|
|
9346
|
+
const body = typeof flags.body === "string" ? flags.body : void 0;
|
|
9347
|
+
if (!title || !body) throw new Error("Provide --title and --body.");
|
|
9348
|
+
await printSettingsMutationResult(client.settingsPost("issues", { title, description: body }), flags);
|
|
9349
|
+
return;
|
|
9350
|
+
}
|
|
9351
|
+
if (matches(tokens, ["report-issue", "status"])) {
|
|
9352
|
+
const id = rest[1];
|
|
9353
|
+
if (!id) throw new Error("Missing issue ID.");
|
|
9354
|
+
await printSettingsResult(client.settingsGet(`issues/${id}/status`), flags);
|
|
9355
|
+
return;
|
|
9356
|
+
}
|
|
9357
|
+
if (matches(tokens, ["mates", "list"])) {
|
|
9358
|
+
printMates(flags.json === true);
|
|
9359
|
+
return;
|
|
9360
|
+
}
|
|
9361
|
+
if (matches(tokens, ["mates", "info"])) {
|
|
9362
|
+
const mateId = rest[1];
|
|
9363
|
+
if (!mateId) throw new Error("Missing mate ID. Example: openmates settings mates info software_development");
|
|
9364
|
+
printMateInfo(mateId, flags.json === true);
|
|
9365
|
+
return;
|
|
9366
|
+
}
|
|
9367
|
+
if (matches(tokens, ["mates", "consent"])) {
|
|
9368
|
+
if (flags.yes !== true) await confirmOrExit("Record consent for mate settings? [y/N] ");
|
|
9369
|
+
await printSettingsMutationResult(client.settingsPost("user/consent/mates", { consent: true }), flags);
|
|
9370
|
+
return;
|
|
9371
|
+
}
|
|
9372
|
+
if (matches(tokens, ["newsletter", "categories"]) && tokens.length === 2) {
|
|
9373
|
+
await printSettingsResult(client.getNewsletterCategories(), flags);
|
|
9374
|
+
return;
|
|
9375
|
+
}
|
|
9376
|
+
if (matches(tokens, ["newsletter", "categories", "set"])) {
|
|
9377
|
+
const categories = {
|
|
9378
|
+
updates_and_announcements: parseOptionalBoolean(flags.updates, true, "updates newsletter"),
|
|
9379
|
+
tips_and_tricks: parseOptionalBoolean(flags.tips, true, "tips newsletter"),
|
|
9380
|
+
daily_inspirations: parseOptionalBoolean(flags.daily, true, "daily inspirations newsletter")
|
|
9381
|
+
};
|
|
9382
|
+
await printSettingsMutationResult(client.updateNewsletterCategories(categories), flags);
|
|
9383
|
+
return;
|
|
9384
|
+
}
|
|
9385
|
+
if (matches(tokens, ["newsletter", "subscribe"])) {
|
|
9386
|
+
const email = rest[1];
|
|
9387
|
+
if (!email) throw new Error("Missing email. Example: openmates settings newsletter subscribe you@example.com");
|
|
9388
|
+
const language = typeof flags.language === "string" ? flags.language : "en";
|
|
9389
|
+
const darkmode = parseOptionalBoolean(flags.darkmode, false, "newsletter dark mode");
|
|
9390
|
+
await printSettingsMutationResult(client.subscribeNewsletter(email, language, darkmode), flags);
|
|
9391
|
+
return;
|
|
9392
|
+
}
|
|
9393
|
+
if (matches(tokens, ["newsletter", "confirm"])) {
|
|
9394
|
+
const token = rest[1];
|
|
9395
|
+
if (!token) throw new Error("Missing confirmation token.");
|
|
9396
|
+
await printSettingsMutationResult(client.confirmNewsletter(token), flags);
|
|
9397
|
+
return;
|
|
9398
|
+
}
|
|
9399
|
+
if (matches(tokens, ["newsletter", "unsubscribe"])) {
|
|
9400
|
+
const token = rest[1];
|
|
9401
|
+
if (!token) throw new Error("Missing unsubscribe token.");
|
|
9402
|
+
await printSettingsMutationResult(client.unsubscribeNewsletter(token), flags);
|
|
9403
|
+
return;
|
|
9404
|
+
}
|
|
9405
|
+
if (subcommand === "memories") {
|
|
9406
|
+
await handleMemories(client, rest, flags);
|
|
9407
|
+
return;
|
|
9408
|
+
}
|
|
9409
|
+
const webOnly = findSettingsInfoCommand(tokens);
|
|
9410
|
+
if (webOnly) {
|
|
9411
|
+
printSettingsInfoCommand(client, webOnly, flags.json === true);
|
|
9412
|
+
return;
|
|
9413
|
+
}
|
|
9414
|
+
console.error(`Unknown settings command '${tokens.join(" ")}'.
|
|
7805
9415
|
`);
|
|
7806
|
-
printSettingsHelp();
|
|
9416
|
+
printSettingsHelp(client, [subcommand]);
|
|
7807
9417
|
process.exit(1);
|
|
7808
9418
|
}
|
|
7809
9419
|
async function handleMemories(client, rest, flags) {
|
|
@@ -8132,7 +9742,57 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
8132
9742
|
processedRawLength = raw.length;
|
|
8133
9743
|
}
|
|
8134
9744
|
};
|
|
8135
|
-
const
|
|
9745
|
+
const onSubChatEvent = (event) => {
|
|
9746
|
+
if (params.json) return;
|
|
9747
|
+
clearTyping();
|
|
9748
|
+
const payload = event.payload;
|
|
9749
|
+
if (event.type === "spawn_sub_chats") {
|
|
9750
|
+
const count = Array.isArray(payload.sub_chats) ? payload.sub_chats.length : 0;
|
|
9751
|
+
process.stderr.write(
|
|
9752
|
+
`\x1B[2mStarting ${count} sub-chat${count === 1 ? "" : "s"} for parallel research...\x1B[0m
|
|
9753
|
+
`
|
|
9754
|
+
);
|
|
9755
|
+
return;
|
|
9756
|
+
}
|
|
9757
|
+
if (event.type === "sub_chat_progress") {
|
|
9758
|
+
const completed = typeof payload.completed === "number" ? payload.completed : null;
|
|
9759
|
+
const total = typeof payload.total === "number" ? payload.total : null;
|
|
9760
|
+
const status = typeof payload.status === "string" ? payload.status : "running";
|
|
9761
|
+
const progress = completed !== null && total !== null ? `${completed}/${total}` : status;
|
|
9762
|
+
process.stderr.write(`\x1B[2mSub-chats: ${progress} ${status}\x1B[0m
|
|
9763
|
+
`);
|
|
9764
|
+
return;
|
|
9765
|
+
}
|
|
9766
|
+
if (event.type === "sub_chat_confirmation_resolved") {
|
|
9767
|
+
const status = typeof payload.status === "string" ? payload.status : "resolved";
|
|
9768
|
+
process.stderr.write(`\x1B[2mSub-chat approval ${status}.\x1B[0m
|
|
9769
|
+
`);
|
|
9770
|
+
return;
|
|
9771
|
+
}
|
|
9772
|
+
if (event.type === "awaiting_user_input") {
|
|
9773
|
+
process.stderr.write(
|
|
9774
|
+
"\x1B[33mA sub-chat needs additional user input. Continue this chat in the web app if the parent cannot finish.\x1B[0m\n"
|
|
9775
|
+
);
|
|
9776
|
+
}
|
|
9777
|
+
};
|
|
9778
|
+
const onSubChatApprovalRequest = async (request) => {
|
|
9779
|
+
if (params.autoApproveSubChats) return true;
|
|
9780
|
+
clearTyping();
|
|
9781
|
+
const count = request.subChats.length;
|
|
9782
|
+
const remaining = request.remainingSubChats === null ? "" : ` (${request.remainingSubChats} remaining for this parent chat)`;
|
|
9783
|
+
process.stderr.write(
|
|
9784
|
+
`\x1B[33mDeep research wants to start ${count} additional sub-chat${count === 1 ? "" : "s"}${remaining}.\x1B[0m
|
|
9785
|
+
`
|
|
9786
|
+
);
|
|
9787
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
9788
|
+
try {
|
|
9789
|
+
const answer = await rl.question("Approve? [y/N] ");
|
|
9790
|
+
return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
|
|
9791
|
+
} finally {
|
|
9792
|
+
rl.close();
|
|
9793
|
+
}
|
|
9794
|
+
};
|
|
9795
|
+
const preparedEmbeds = [];
|
|
8136
9796
|
let finalMessage = params.message;
|
|
8137
9797
|
if (params.message.includes("@")) {
|
|
8138
9798
|
try {
|
|
@@ -8192,8 +9852,44 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
8192
9852
|
try {
|
|
8193
9853
|
const session = client.getSession();
|
|
8194
9854
|
const uploadResult = await uploadFile(fe.localPath, session);
|
|
8195
|
-
fe.embed.
|
|
8196
|
-
|
|
9855
|
+
const embedType = fe.embed.type;
|
|
9856
|
+
const audioTranscription = embedType === "audio-recording" ? await transcribeUploadedAudio(
|
|
9857
|
+
uploadResult,
|
|
9858
|
+
fe.displayName,
|
|
9859
|
+
session,
|
|
9860
|
+
{ chatId: params.chatId, requestId: uploadResult.embed_id }
|
|
9861
|
+
) : null;
|
|
9862
|
+
const uploadedContent = embedType === "audio-recording" ? {
|
|
9863
|
+
app_id: "audio",
|
|
9864
|
+
skill_id: "transcribe",
|
|
9865
|
+
type: "audio-recording",
|
|
9866
|
+
status: "finished",
|
|
9867
|
+
filename: fe.displayName,
|
|
9868
|
+
mime_type: uploadResult.content_type,
|
|
9869
|
+
transcript: audioTranscription?.transcript ?? null,
|
|
9870
|
+
transcript_original: audioTranscription?.transcript_original ?? null,
|
|
9871
|
+
transcript_corrected: audioTranscription?.transcript_corrected ?? null,
|
|
9872
|
+
use_corrected: audioTranscription?.use_corrected ?? null,
|
|
9873
|
+
correction_model: audioTranscription?.correction_model ?? null,
|
|
9874
|
+
model: audioTranscription?.model ?? null,
|
|
9875
|
+
s3_base_url: uploadResult.s3_base_url,
|
|
9876
|
+
files: uploadResult.files,
|
|
9877
|
+
aes_key: uploadResult.aes_key,
|
|
9878
|
+
aes_nonce: uploadResult.aes_nonce,
|
|
9879
|
+
vault_wrapped_aes_key: uploadResult.vault_wrapped_aes_key
|
|
9880
|
+
} : embedType === "pdf" ? {
|
|
9881
|
+
type: "pdf",
|
|
9882
|
+
status: "processing",
|
|
9883
|
+
filename: fe.displayName,
|
|
9884
|
+
page_count: uploadResult.page_count ?? null,
|
|
9885
|
+
content_hash: uploadResult.content_hash,
|
|
9886
|
+
s3_base_url: uploadResult.s3_base_url,
|
|
9887
|
+
files: uploadResult.files,
|
|
9888
|
+
aes_key: uploadResult.aes_key,
|
|
9889
|
+
aes_nonce: uploadResult.aes_nonce,
|
|
9890
|
+
vault_wrapped_aes_key: uploadResult.vault_wrapped_aes_key
|
|
9891
|
+
} : {
|
|
9892
|
+
type: "image",
|
|
8197
9893
|
app_id: "images",
|
|
8198
9894
|
skill_id: "upload",
|
|
8199
9895
|
status: "finished",
|
|
@@ -8205,10 +9901,15 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
8205
9901
|
aes_nonce: uploadResult.aes_nonce,
|
|
8206
9902
|
vault_wrapped_aes_key: uploadResult.vault_wrapped_aes_key,
|
|
8207
9903
|
ai_detection: uploadResult.ai_detection
|
|
8208
|
-
}
|
|
8209
|
-
fe.embed.
|
|
9904
|
+
};
|
|
9905
|
+
fe.embed.content = toonEncodeContent(uploadedContent);
|
|
9906
|
+
fe.embed.status = embedType === "pdf" ? "processing" : "finished";
|
|
8210
9907
|
fe.embed.contentHash = uploadResult.content_hash;
|
|
8211
9908
|
fe.embed.embedId = uploadResult.embed_id;
|
|
9909
|
+
fe.referenceBlock = createEmbedReferenceBlock(
|
|
9910
|
+
embedType,
|
|
9911
|
+
uploadResult.embed_id
|
|
9912
|
+
);
|
|
8212
9913
|
if (!params.json) {
|
|
8213
9914
|
process.stderr.write(
|
|
8214
9915
|
`\x1B[32m \u2713\x1B[0m \x1B[2m${fe.displayName} uploaded\x1B[0m
|
|
@@ -8239,31 +9940,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
8239
9940
|
if (embedRefs) {
|
|
8240
9941
|
finalMessage += embedRefs;
|
|
8241
9942
|
}
|
|
8242
|
-
|
|
8243
|
-
const { masterKey, userId } = client.getEmbedEncryptionKeys();
|
|
8244
|
-
for (const fe of fileResult.embeds) {
|
|
8245
|
-
const encrypted = await encryptEmbed(
|
|
8246
|
-
fe.embed,
|
|
8247
|
-
masterKey,
|
|
8248
|
-
null,
|
|
8249
|
-
// chatKey — not available in CLI yet for new chats
|
|
8250
|
-
params.chatId || "new",
|
|
8251
|
-
// will be replaced by server
|
|
8252
|
-
"pending",
|
|
8253
|
-
// messageId set during send
|
|
8254
|
-
userId
|
|
8255
|
-
);
|
|
8256
|
-
if (encrypted) {
|
|
8257
|
-
encryptedEmbeds.push(encrypted);
|
|
8258
|
-
}
|
|
8259
|
-
}
|
|
8260
|
-
} catch (err) {
|
|
8261
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
8262
|
-
process.stderr.write(
|
|
8263
|
-
`\x1B[33mWarning:\x1B[0m Embed encryption failed: ${msg}. Files sent without encryption.
|
|
8264
|
-
`
|
|
8265
|
-
);
|
|
8266
|
-
}
|
|
9943
|
+
preparedEmbeds.push(...fileResult.embeds.map((fileEmbed) => fileEmbed.embed));
|
|
8267
9944
|
}
|
|
8268
9945
|
if (parsed.resolved.length > 0 && !params.json) {
|
|
8269
9946
|
clearTyping();
|
|
@@ -8278,12 +9955,18 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
8278
9955
|
} catch {
|
|
8279
9956
|
}
|
|
8280
9957
|
}
|
|
9958
|
+
const urlResult = prepareUrlEmbeds(finalMessage);
|
|
9959
|
+
finalMessage = urlResult.message;
|
|
9960
|
+
preparedEmbeds.push(...urlResult.embeds);
|
|
8281
9961
|
const result = await client.sendMessage({
|
|
8282
9962
|
message: finalMessage,
|
|
8283
9963
|
chatId: params.chatId,
|
|
8284
9964
|
incognito: params.incognito,
|
|
8285
9965
|
onStream,
|
|
8286
|
-
|
|
9966
|
+
onSubChatEvent,
|
|
9967
|
+
onSubChatApprovalRequest,
|
|
9968
|
+
autoApproveSubChats: params.autoApproveSubChats,
|
|
9969
|
+
preparedEmbeds: preparedEmbeds.length > 0 ? preparedEmbeds : void 0
|
|
8287
9970
|
});
|
|
8288
9971
|
clearTyping();
|
|
8289
9972
|
if (!params.json) {
|
|
@@ -8356,21 +10039,13 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
8356
10039
|
}
|
|
8357
10040
|
return result;
|
|
8358
10041
|
}
|
|
8359
|
-
function
|
|
8360
|
-
|
|
8361
|
-
|
|
10042
|
+
function printIncognitoNoHistoryNotice(json) {
|
|
10043
|
+
const message = "Incognito chats are not stored. There is no incognito history to show or clear.";
|
|
10044
|
+
if (json) {
|
|
10045
|
+
printJson2({ history: [], stored: false, message });
|
|
8362
10046
|
return;
|
|
8363
10047
|
}
|
|
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
|
-
}
|
|
10048
|
+
console.log(message);
|
|
8374
10049
|
}
|
|
8375
10050
|
var SEP = `\x1B[2m${"\u2500".repeat(60)}\x1B[0m`;
|
|
8376
10051
|
function parseMessageSegments(content) {
|
|
@@ -9337,23 +11012,23 @@ async function handleMentions(client, subcommand, _rest, flags) {
|
|
|
9337
11012
|
const context = await client.buildMentionContext();
|
|
9338
11013
|
const allOptions = listMentionOptions(context);
|
|
9339
11014
|
const normalizedQuery = query.toLowerCase().replace(/[\s_-]+/g, "");
|
|
9340
|
-
const
|
|
11015
|
+
const matches2 = allOptions.filter((opt) => {
|
|
9341
11016
|
const normalizedName = opt.displayName.toLowerCase().replace(/[@\s_-]+/g, "");
|
|
9342
11017
|
const normalizedDesc = opt.description.toLowerCase().replace(/[\s_-]+/g, "");
|
|
9343
11018
|
return normalizedName.includes(normalizedQuery) || normalizedDesc.includes(normalizedQuery);
|
|
9344
11019
|
}).slice(0, 15);
|
|
9345
11020
|
if (flags.json === true) {
|
|
9346
|
-
console.log(JSON.stringify(
|
|
11021
|
+
console.log(JSON.stringify(matches2, null, 2));
|
|
9347
11022
|
return;
|
|
9348
11023
|
}
|
|
9349
|
-
if (
|
|
11024
|
+
if (matches2.length === 0) {
|
|
9350
11025
|
console.log(`No mentions matching '${query}'.`);
|
|
9351
11026
|
return;
|
|
9352
11027
|
}
|
|
9353
11028
|
process.stdout.write(`
|
|
9354
11029
|
\x1B[1mMatches for '${query}':\x1B[0m
|
|
9355
11030
|
`);
|
|
9356
|
-
for (const m of
|
|
11031
|
+
for (const m of matches2) {
|
|
9357
11032
|
const typeLabel = m.type.replace("_", " ");
|
|
9358
11033
|
process.stdout.write(
|
|
9359
11034
|
` \x1B[36m${m.displayName.padEnd(35)}\x1B[0m \x1B[2m${m.description} (${typeLabel})\x1B[0m
|
|
@@ -9498,7 +11173,7 @@ Commands:
|
|
|
9498
11173
|
openmates apps [--help] App skill commands (list, run, ...)
|
|
9499
11174
|
openmates mentions [--help] List available @mentions
|
|
9500
11175
|
openmates embeds [--help] Embed commands (show)
|
|
9501
|
-
openmates settings [--help]
|
|
11176
|
+
openmates settings [--help] Predefined settings commands
|
|
9502
11177
|
openmates inspirations [--lang <code>] [--json] Daily inspirations
|
|
9503
11178
|
openmates newchatsuggestions [--limit <n>] [--json] Personalized new chat suggestions
|
|
9504
11179
|
openmates server [--help] Server management (install, start, stop, ...)
|
|
@@ -9516,15 +11191,15 @@ function printChatsHelp() {
|
|
|
9516
11191
|
openmates chats show <chat-id> [--raw] [--json]
|
|
9517
11192
|
openmates chats open [<n>] [--json]
|
|
9518
11193
|
openmates chats search <query> [--json]
|
|
9519
|
-
openmates chats new <message> [--json]
|
|
9520
|
-
openmates chats send [--chat <id>] [--incognito] <message> [--json]
|
|
9521
|
-
openmates chats send --chat <id> --followup <n> [--json]
|
|
11194
|
+
openmates chats new <message> [--json] [--auto-approve]
|
|
11195
|
+
openmates chats send [--chat <id>] [--incognito] <message> [--json] [--auto-approve]
|
|
11196
|
+
openmates chats send --chat <id> --followup <n> [--json] [--auto-approve]
|
|
9522
11197
|
openmates chats download <chat-id> [--output <path>] [--zip] [--json]
|
|
9523
11198
|
openmates chats delete <id1> [id2] [id3] ... [--yes]
|
|
9524
11199
|
openmates chats share [<chat-id>] [--expires <seconds>] [--password <pwd>] [--json]
|
|
9525
11200
|
openmates chats incognito <message> [--json]
|
|
9526
|
-
openmates chats incognito-history [--json]
|
|
9527
|
-
openmates chats incognito-clear
|
|
11201
|
+
openmates chats incognito-history [--json] Deprecated: incognito stores no history
|
|
11202
|
+
openmates chats incognito-clear Deprecated: incognito stores no history
|
|
9528
11203
|
|
|
9529
11204
|
Options for 'list':
|
|
9530
11205
|
--limit <n> Number of chats per page (default: 10)
|
|
@@ -9545,6 +11220,10 @@ Options for 'send':
|
|
|
9545
11220
|
typing the full message (requires --chat)
|
|
9546
11221
|
--incognito Send without saving to chat history
|
|
9547
11222
|
|
|
11223
|
+
Options for 'new', 'send', and 'incognito':
|
|
11224
|
+
--auto-approve Automatically approve server-requested sub-chat batches.
|
|
11225
|
+
Without this, the CLI prompts in the terminal like the web app.
|
|
11226
|
+
|
|
9548
11227
|
Options for 'download':
|
|
9549
11228
|
--output <path> Target directory (default: current directory)
|
|
9550
11229
|
--zip Create a .zip archive instead of a folder
|
|
@@ -9648,72 +11327,33 @@ Examples:
|
|
|
9648
11327
|
openmates inspirations --lang de
|
|
9649
11328
|
openmates inspirations --json`);
|
|
9650
11329
|
}
|
|
9651
|
-
function printSettingsHelp(client) {
|
|
11330
|
+
function printSettingsHelp(client, filter) {
|
|
11331
|
+
const commands = [...SETTINGS_EXECUTABLE_COMMANDS, ...SETTINGS_INFO_COMMANDS].filter((command) => {
|
|
11332
|
+
if (!filter || filter.length === 0) return true;
|
|
11333
|
+
return filter.every((part, index) => command.path[index] === part);
|
|
11334
|
+
}).sort((a, b) => a.path.join(" ").localeCompare(b.path.join(" ")));
|
|
9652
11335
|
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`);
|
|
11336
|
+
const title = filter && filter.length > 0 ? `Settings: ${filter.join(" ")}` : "Settings";
|
|
11337
|
+
header(title);
|
|
11338
|
+
console.log("Predefined commands only. Raw settings get/post/patch/delete is not supported.\n");
|
|
11339
|
+
if (commands.length === 0) {
|
|
11340
|
+
console.log("No matching settings commands.");
|
|
11341
|
+
return;
|
|
11342
|
+
}
|
|
11343
|
+
for (const command of commands) {
|
|
11344
|
+
const isInfoOnly = SETTINGS_INFO_COMMANDS.includes(command);
|
|
11345
|
+
const label = `openmates settings ${command.path.join(" ")}`;
|
|
11346
|
+
process.stdout.write(` ${label.padEnd(58)} ${command.description}`);
|
|
11347
|
+
if (isInfoOnly) process.stdout.write(" \x1B[2m(info/web-only)\x1B[0m");
|
|
11348
|
+
process.stdout.write("\n");
|
|
11349
|
+
if (filter && filter.length > 0) {
|
|
11350
|
+
for (const example of command.examples) process.stdout.write(` e.g. ${example}
|
|
11351
|
+
`);
|
|
11352
|
+
if (command.webPath) process.stdout.write(` web: ${appUrl}/#settings/${command.webPath}
|
|
11353
|
+
`);
|
|
11354
|
+
}
|
|
11355
|
+
}
|
|
11356
|
+
console.log("\nUse --help after a group for examples, e.g. openmates settings billing --help");
|
|
9717
11357
|
}
|
|
9718
11358
|
function printNewChatSuggestionsHelp() {
|
|
9719
11359
|
console.log(`New chat suggestions command:
|
|
@@ -9889,6 +11529,7 @@ export {
|
|
|
9889
11529
|
deriveAppUrl,
|
|
9890
11530
|
OpenMatesClient,
|
|
9891
11531
|
parseNewChatSuggestionText,
|
|
11532
|
+
defaultCloneBranchForVersion,
|
|
9892
11533
|
serializeToYaml,
|
|
9893
11534
|
getExtForLang
|
|
9894
11535
|
};
|