@quanta-intellect/vessel-browser 0.1.53 → 0.1.56
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/README.md +7 -2
- package/out/main/index.js +1856 -840
- package/out/preload/chunks/channels-Dfv8z3Ui.js +123 -0
- package/out/preload/content-script.js +111 -0
- package/out/preload/index.js +132 -226
- package/out/renderer/assets/{index-hRUKGdgt.js → index-DRVDsSQe.js} +664 -129
- package/out/renderer/assets/{index-eS3ccAls.css → index-DRj77a_O.css} +224 -0
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -4,12 +4,12 @@ const fs$1 = require("node:fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const crypto$1 = require("crypto");
|
|
7
|
-
const crypto$2 = require("node:crypto");
|
|
8
|
-
const path$1 = require("node:path");
|
|
9
7
|
const Anthropic = require("@anthropic-ai/sdk");
|
|
10
8
|
const OpenAI = require("openai");
|
|
9
|
+
const path$1 = require("node:path");
|
|
11
10
|
const node_module = require("node:module");
|
|
12
11
|
const zod = require("zod");
|
|
12
|
+
const crypto$2 = require("node:crypto");
|
|
13
13
|
const http = require("node:http");
|
|
14
14
|
const os = require("node:os");
|
|
15
15
|
const mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
@@ -38,13 +38,13 @@ const defaults = {
|
|
|
38
38
|
expiresAt: ""
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
-
const SAVE_DEBOUNCE_MS$
|
|
41
|
+
const SAVE_DEBOUNCE_MS$5 = 150;
|
|
42
42
|
const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
|
|
43
43
|
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
44
44
|
let settings = null;
|
|
45
45
|
let settingsIssues = [];
|
|
46
|
-
let saveTimer
|
|
47
|
-
let saveDirty
|
|
46
|
+
let saveTimer = null;
|
|
47
|
+
let saveDirty = false;
|
|
48
48
|
function getUserDataPath() {
|
|
49
49
|
if (typeof electron.app?.getPath === "function") {
|
|
50
50
|
return electron.app.getPath("userData");
|
|
@@ -57,7 +57,7 @@ function getSettingsPath() {
|
|
|
57
57
|
function getChatProviderSecretPath() {
|
|
58
58
|
return path.join(getUserDataPath(), CHAT_PROVIDER_SECRET_FILENAME);
|
|
59
59
|
}
|
|
60
|
-
function canUseSafeStorage() {
|
|
60
|
+
function canUseSafeStorage$1() {
|
|
61
61
|
try {
|
|
62
62
|
return electron.safeStorage.isEncryptionAvailable();
|
|
63
63
|
} catch {
|
|
@@ -67,7 +67,7 @@ function canUseSafeStorage() {
|
|
|
67
67
|
function readStoredProviderSecret() {
|
|
68
68
|
try {
|
|
69
69
|
const raw = fs.readFileSync(getChatProviderSecretPath());
|
|
70
|
-
const decoded = canUseSafeStorage() && electron.safeStorage.decryptString ? electron.safeStorage.decryptString(raw) : raw.toString("utf-8");
|
|
70
|
+
const decoded = canUseSafeStorage$1() && electron.safeStorage.decryptString ? electron.safeStorage.decryptString(raw) : raw.toString("utf-8");
|
|
71
71
|
const parsed = JSON.parse(decoded);
|
|
72
72
|
if (parsed && typeof parsed === "object" && typeof parsed.providerId === "string" && typeof parsed.apiKey === "string") {
|
|
73
73
|
return parsed;
|
|
@@ -80,7 +80,7 @@ function writeStoredProviderSecret(secret) {
|
|
|
80
80
|
const filePath = getChatProviderSecretPath();
|
|
81
81
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
82
82
|
const payload = JSON.stringify(secret);
|
|
83
|
-
if (canUseSafeStorage()) {
|
|
83
|
+
if (canUseSafeStorage$1()) {
|
|
84
84
|
const encrypted = electron.safeStorage.encryptString(payload);
|
|
85
85
|
fs.writeFileSync(filePath, encrypted, { mode: 384 });
|
|
86
86
|
return;
|
|
@@ -174,11 +174,11 @@ function loadSettings() {
|
|
|
174
174
|
}
|
|
175
175
|
return settings;
|
|
176
176
|
}
|
|
177
|
-
function persistNow
|
|
178
|
-
saveDirty
|
|
179
|
-
if (saveTimer
|
|
180
|
-
clearTimeout(saveTimer
|
|
181
|
-
saveTimer
|
|
177
|
+
function persistNow() {
|
|
178
|
+
saveDirty = false;
|
|
179
|
+
if (saveTimer) {
|
|
180
|
+
clearTimeout(saveTimer);
|
|
181
|
+
saveTimer = null;
|
|
182
182
|
}
|
|
183
183
|
return fs.promises.mkdir(path.dirname(getSettingsPath()), { recursive: true }).then(
|
|
184
184
|
() => fs.promises.writeFile(
|
|
@@ -188,14 +188,14 @@ function persistNow$3() {
|
|
|
188
188
|
).catch((err) => console.error("[Vessel] Failed to save settings:", err));
|
|
189
189
|
}
|
|
190
190
|
function saveSettings() {
|
|
191
|
-
saveDirty
|
|
192
|
-
if (saveTimer
|
|
193
|
-
saveTimer
|
|
194
|
-
saveTimer
|
|
195
|
-
if (saveDirty
|
|
196
|
-
void persistNow
|
|
191
|
+
saveDirty = true;
|
|
192
|
+
if (saveTimer) return;
|
|
193
|
+
saveTimer = setTimeout(() => {
|
|
194
|
+
saveTimer = null;
|
|
195
|
+
if (saveDirty) {
|
|
196
|
+
void persistNow();
|
|
197
197
|
}
|
|
198
|
-
}, SAVE_DEBOUNCE_MS$
|
|
198
|
+
}, SAVE_DEBOUNCE_MS$5);
|
|
199
199
|
}
|
|
200
200
|
function setSetting(key, value) {
|
|
201
201
|
loadSettings();
|
|
@@ -231,8 +231,8 @@ function setSetting(key, value) {
|
|
|
231
231
|
saveSettings();
|
|
232
232
|
return { ...settings };
|
|
233
233
|
}
|
|
234
|
-
function flushPersist$
|
|
235
|
-
return saveDirty
|
|
234
|
+
function flushPersist$5() {
|
|
235
|
+
return saveDirty ? persistNow() : Promise.resolve();
|
|
236
236
|
}
|
|
237
237
|
function checkDomainPolicy(url) {
|
|
238
238
|
if (!url || url.startsWith("about:")) return null;
|
|
@@ -746,48 +746,132 @@ class Tab {
|
|
|
746
746
|
this.view.webContents.close();
|
|
747
747
|
}
|
|
748
748
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
return path.join(electron.app.getPath("userData"), "vessel-highlights.json");
|
|
749
|
+
function canUseSafeStorage() {
|
|
750
|
+
try {
|
|
751
|
+
return electron.safeStorage.isEncryptionAvailable();
|
|
752
|
+
} catch {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
756
755
|
}
|
|
757
|
-
function
|
|
758
|
-
if (
|
|
756
|
+
function decodeStoredData(data, secure) {
|
|
757
|
+
if (secure && canUseSafeStorage() && electron.safeStorage.decryptString) {
|
|
758
|
+
return electron.safeStorage.decryptString(data);
|
|
759
|
+
}
|
|
760
|
+
return data.toString("utf-8");
|
|
761
|
+
}
|
|
762
|
+
function encodeStoredData(payload, secure) {
|
|
763
|
+
if (secure && canUseSafeStorage() && electron.safeStorage.encryptString) {
|
|
764
|
+
return electron.safeStorage.encryptString(payload);
|
|
765
|
+
}
|
|
766
|
+
return payload;
|
|
767
|
+
}
|
|
768
|
+
function loadJsonFile({
|
|
769
|
+
filePath,
|
|
770
|
+
fallback,
|
|
771
|
+
parse,
|
|
772
|
+
secure = false
|
|
773
|
+
}) {
|
|
759
774
|
try {
|
|
760
|
-
const raw = fs.readFileSync(
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
highlights: Array.isArray(parsed.highlights) ? parsed.highlights : []
|
|
764
|
-
};
|
|
775
|
+
const raw = fs.readFileSync(filePath);
|
|
776
|
+
const decoded = decodeStoredData(raw, secure);
|
|
777
|
+
return parse(JSON.parse(decoded));
|
|
765
778
|
} catch {
|
|
766
|
-
|
|
779
|
+
return fallback;
|
|
767
780
|
}
|
|
768
|
-
return state$3;
|
|
769
781
|
}
|
|
770
|
-
function
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
782
|
+
function createDebouncedJsonPersistence({
|
|
783
|
+
debounceMs,
|
|
784
|
+
filePath,
|
|
785
|
+
getValue,
|
|
786
|
+
logLabel,
|
|
787
|
+
resetOnSchedule = false,
|
|
788
|
+
secure = false,
|
|
789
|
+
serialize
|
|
790
|
+
}) {
|
|
791
|
+
let saveTimer2 = null;
|
|
792
|
+
let saveDirty2 = false;
|
|
793
|
+
const persistNow2 = async () => {
|
|
794
|
+
saveDirty2 = false;
|
|
795
|
+
if (saveTimer2) {
|
|
796
|
+
clearTimeout(saveTimer2);
|
|
797
|
+
saveTimer2 = null;
|
|
798
|
+
}
|
|
799
|
+
const value = getValue();
|
|
800
|
+
if (value == null) return;
|
|
801
|
+
const payload = JSON.stringify(
|
|
802
|
+
serialize ? serialize(value) : value,
|
|
803
|
+
null,
|
|
804
|
+
2
|
|
805
|
+
);
|
|
806
|
+
const data = encodeStoredData(payload, secure);
|
|
807
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true }).then(
|
|
808
|
+
() => fs.promises.writeFile(
|
|
809
|
+
filePath,
|
|
810
|
+
data,
|
|
811
|
+
typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
|
|
812
|
+
)
|
|
813
|
+
).catch(
|
|
814
|
+
(err) => console.error(`[Vessel] Failed to save ${logLabel}:`, err)
|
|
815
|
+
);
|
|
816
|
+
};
|
|
817
|
+
const schedule = () => {
|
|
818
|
+
saveDirty2 = true;
|
|
819
|
+
if (saveTimer2) {
|
|
820
|
+
if (!resetOnSchedule) return;
|
|
821
|
+
clearTimeout(saveTimer2);
|
|
822
|
+
}
|
|
823
|
+
saveTimer2 = setTimeout(() => {
|
|
824
|
+
saveTimer2 = null;
|
|
825
|
+
if (saveDirty2) void persistNow2();
|
|
826
|
+
}, debounceMs);
|
|
827
|
+
};
|
|
828
|
+
const flush2 = () => {
|
|
829
|
+
return saveDirty2 ? persistNow2() : Promise.resolve();
|
|
830
|
+
};
|
|
831
|
+
return {
|
|
832
|
+
persistNow: persistNow2,
|
|
833
|
+
schedule,
|
|
834
|
+
flush: flush2
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
let state$4 = null;
|
|
838
|
+
const listeners$2 = /* @__PURE__ */ new Set();
|
|
839
|
+
const SAVE_DEBOUNCE_MS$4 = 250;
|
|
840
|
+
function getHighlightsPath() {
|
|
841
|
+
return path.join(electron.app.getPath("userData"), "vessel-highlights.json");
|
|
774
842
|
}
|
|
843
|
+
function load$4() {
|
|
844
|
+
if (state$4) return state$4;
|
|
845
|
+
state$4 = loadJsonFile({
|
|
846
|
+
filePath: getHighlightsPath(),
|
|
847
|
+
fallback: { highlights: [] },
|
|
848
|
+
parse: (raw) => {
|
|
849
|
+
const parsed = raw;
|
|
850
|
+
return {
|
|
851
|
+
highlights: Array.isArray(parsed.highlights) ? parsed.highlights : []
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
return state$4;
|
|
856
|
+
}
|
|
857
|
+
const persistence$4 = createDebouncedJsonPersistence({
|
|
858
|
+
debounceMs: SAVE_DEBOUNCE_MS$4,
|
|
859
|
+
filePath: getHighlightsPath(),
|
|
860
|
+
getValue: () => state$4,
|
|
861
|
+
logLabel: "highlights",
|
|
862
|
+
resetOnSchedule: true
|
|
863
|
+
});
|
|
775
864
|
function save$2() {
|
|
776
|
-
|
|
777
|
-
if (saveTimer$2) clearTimeout(saveTimer$2);
|
|
778
|
-
saveTimer$2 = setTimeout(() => {
|
|
779
|
-
saveTimer$2 = null;
|
|
780
|
-
void persistNow$2();
|
|
781
|
-
}, SAVE_DEBOUNCE_MS$2);
|
|
865
|
+
persistence$4.schedule();
|
|
782
866
|
}
|
|
783
867
|
function emit$2() {
|
|
784
|
-
if (!state$
|
|
785
|
-
const snapshot = { highlights: [...state$
|
|
868
|
+
if (!state$4) return;
|
|
869
|
+
const snapshot = { highlights: [...state$4.highlights] };
|
|
786
870
|
for (const listener of listeners$2) {
|
|
787
871
|
listener(snapshot);
|
|
788
872
|
}
|
|
789
873
|
}
|
|
790
|
-
function normalizeUrl(rawUrl) {
|
|
874
|
+
function normalizeUrl$1(rawUrl) {
|
|
791
875
|
try {
|
|
792
876
|
const parsed = new URL(rawUrl);
|
|
793
877
|
parsed.hash = "";
|
|
@@ -797,19 +881,19 @@ function normalizeUrl(rawUrl) {
|
|
|
797
881
|
}
|
|
798
882
|
}
|
|
799
883
|
function getState$2() {
|
|
800
|
-
load$
|
|
801
|
-
return { highlights: [...state$
|
|
884
|
+
load$4();
|
|
885
|
+
return { highlights: [...state$4.highlights] };
|
|
802
886
|
}
|
|
803
887
|
function getHighlightsForUrl(url) {
|
|
804
|
-
load$
|
|
805
|
-
const normalized = normalizeUrl(url);
|
|
806
|
-
return state$
|
|
888
|
+
load$4();
|
|
889
|
+
const normalized = normalizeUrl$1(url);
|
|
890
|
+
return state$4.highlights.filter((h) => h.url === normalized);
|
|
807
891
|
}
|
|
808
892
|
function addHighlight(url, selector, text, label, color, source) {
|
|
809
|
-
load$
|
|
893
|
+
load$4();
|
|
810
894
|
const highlight = {
|
|
811
895
|
id: crypto$1.randomUUID(),
|
|
812
|
-
url: normalizeUrl(url),
|
|
896
|
+
url: normalizeUrl$1(url),
|
|
813
897
|
selector: selector || void 0,
|
|
814
898
|
text: text || void 0,
|
|
815
899
|
label: label || void 0,
|
|
@@ -817,30 +901,30 @@ function addHighlight(url, selector, text, label, color, source) {
|
|
|
817
901
|
source: source || void 0,
|
|
818
902
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
819
903
|
};
|
|
820
|
-
state$
|
|
904
|
+
state$4.highlights.push(highlight);
|
|
821
905
|
save$2();
|
|
822
906
|
emit$2();
|
|
823
907
|
return highlight;
|
|
824
908
|
}
|
|
825
909
|
function removeHighlight(id) {
|
|
826
|
-
load$
|
|
827
|
-
const index = state$
|
|
910
|
+
load$4();
|
|
911
|
+
const index = state$4.highlights.findIndex((h) => h.id === id);
|
|
828
912
|
if (index === -1) return null;
|
|
829
|
-
const [removed] = state$
|
|
913
|
+
const [removed] = state$4.highlights.splice(index, 1);
|
|
830
914
|
save$2();
|
|
831
915
|
emit$2();
|
|
832
916
|
return removed;
|
|
833
917
|
}
|
|
834
918
|
function findHighlightByText(url, text) {
|
|
835
|
-
load$
|
|
836
|
-
const normalized = normalizeUrl(url);
|
|
837
|
-
return state$
|
|
919
|
+
load$4();
|
|
920
|
+
const normalized = normalizeUrl$1(url);
|
|
921
|
+
return state$4.highlights.find(
|
|
838
922
|
(h) => h.url === normalized && h.text && h.text === text
|
|
839
923
|
) ?? null;
|
|
840
924
|
}
|
|
841
925
|
function updateHighlightColor(id, color) {
|
|
842
|
-
load$
|
|
843
|
-
const highlight = state$
|
|
926
|
+
load$4();
|
|
927
|
+
const highlight = state$4.highlights.find((h) => h.id === id);
|
|
844
928
|
if (!highlight) return null;
|
|
845
929
|
highlight.color = color;
|
|
846
930
|
save$2();
|
|
@@ -848,24 +932,19 @@ function updateHighlightColor(id, color) {
|
|
|
848
932
|
return highlight;
|
|
849
933
|
}
|
|
850
934
|
function clearHighlightsForUrl(url) {
|
|
851
|
-
load$
|
|
852
|
-
const normalized = normalizeUrl(url);
|
|
853
|
-
const before = state$
|
|
854
|
-
state$
|
|
855
|
-
const removed = before - state$
|
|
935
|
+
load$4();
|
|
936
|
+
const normalized = normalizeUrl$1(url);
|
|
937
|
+
const before = state$4.highlights.length;
|
|
938
|
+
state$4.highlights = state$4.highlights.filter((h) => h.url !== normalized);
|
|
939
|
+
const removed = before - state$4.highlights.length;
|
|
856
940
|
if (removed > 0) {
|
|
857
941
|
save$2();
|
|
858
942
|
emit$2();
|
|
859
943
|
}
|
|
860
944
|
return removed;
|
|
861
945
|
}
|
|
862
|
-
function flushPersist$
|
|
863
|
-
|
|
864
|
-
clearTimeout(saveTimer$2);
|
|
865
|
-
saveTimer$2 = null;
|
|
866
|
-
}
|
|
867
|
-
if (!saveDirty$2) return Promise.resolve();
|
|
868
|
-
return persistNow$2();
|
|
946
|
+
function flushPersist$4() {
|
|
947
|
+
return persistence$4.flush();
|
|
869
948
|
}
|
|
870
949
|
const SKIP_TAGS_JS = "var SKIP_TAGS = {SCRIPT:1,STYLE:1,NOSCRIPT:1,TEMPLATE:1,IFRAME:1,SVG:1};";
|
|
871
950
|
const CONTENT_ROOTS_JS = `
|
|
@@ -1433,61 +1512,45 @@ function persistHighlight(url, text) {
|
|
|
1433
1512
|
return { success: true, text: capped, id: highlight.id };
|
|
1434
1513
|
}
|
|
1435
1514
|
const MAX_HISTORY_ENTRIES = 5e3;
|
|
1436
|
-
const SAVE_DEBOUNCE_MS$
|
|
1437
|
-
let state$
|
|
1515
|
+
const SAVE_DEBOUNCE_MS$3 = 250;
|
|
1516
|
+
let state$3 = null;
|
|
1438
1517
|
const listeners$1 = /* @__PURE__ */ new Set();
|
|
1439
|
-
let saveTimer$1 = null;
|
|
1440
|
-
let saveDirty$1 = false;
|
|
1441
1518
|
function getHistoryPath() {
|
|
1442
1519
|
return path.join(electron.app.getPath("userData"), "vessel-history.json");
|
|
1443
1520
|
}
|
|
1444
|
-
function load$
|
|
1445
|
-
if (state$
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
function persistNow$1() {
|
|
1458
|
-
saveDirty$1 = false;
|
|
1459
|
-
if (saveTimer$1) {
|
|
1460
|
-
clearTimeout(saveTimer$1);
|
|
1461
|
-
saveTimer$1 = null;
|
|
1462
|
-
}
|
|
1463
|
-
return fs.promises.mkdir(path.dirname(getHistoryPath()), { recursive: true }).then(
|
|
1464
|
-
() => fs.promises.writeFile(
|
|
1465
|
-
getHistoryPath(),
|
|
1466
|
-
JSON.stringify(state$2, null, 2),
|
|
1467
|
-
"utf-8"
|
|
1468
|
-
)
|
|
1469
|
-
).catch((err) => console.error("[Vessel] Failed to save history:", err));
|
|
1521
|
+
function load$3() {
|
|
1522
|
+
if (state$3) return state$3;
|
|
1523
|
+
state$3 = loadJsonFile({
|
|
1524
|
+
filePath: getHistoryPath(),
|
|
1525
|
+
fallback: { entries: [] },
|
|
1526
|
+
parse: (raw) => {
|
|
1527
|
+
const parsed = raw;
|
|
1528
|
+
return {
|
|
1529
|
+
entries: Array.isArray(parsed.entries) ? parsed.entries : []
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1533
|
+
return state$3;
|
|
1470
1534
|
}
|
|
1535
|
+
const persistence$3 = createDebouncedJsonPersistence({
|
|
1536
|
+
debounceMs: SAVE_DEBOUNCE_MS$3,
|
|
1537
|
+
filePath: getHistoryPath(),
|
|
1538
|
+
getValue: () => state$3,
|
|
1539
|
+
logLabel: "history"
|
|
1540
|
+
});
|
|
1471
1541
|
function save$1() {
|
|
1472
|
-
|
|
1473
|
-
if (saveTimer$1) return;
|
|
1474
|
-
saveTimer$1 = setTimeout(() => {
|
|
1475
|
-
saveTimer$1 = null;
|
|
1476
|
-
if (saveDirty$1) {
|
|
1477
|
-
void persistNow$1();
|
|
1478
|
-
}
|
|
1479
|
-
}, SAVE_DEBOUNCE_MS$1);
|
|
1542
|
+
persistence$3.schedule();
|
|
1480
1543
|
}
|
|
1481
1544
|
function emit$1() {
|
|
1482
|
-
if (!state$
|
|
1483
|
-
const snapshot = { entries: [...state$
|
|
1545
|
+
if (!state$3) return;
|
|
1546
|
+
const snapshot = { entries: [...state$3.entries] };
|
|
1484
1547
|
for (const listener of listeners$1) {
|
|
1485
1548
|
listener(snapshot);
|
|
1486
1549
|
}
|
|
1487
1550
|
}
|
|
1488
1551
|
function getState$1() {
|
|
1489
|
-
load$
|
|
1490
|
-
return { entries: [...state$
|
|
1552
|
+
load$3();
|
|
1553
|
+
return { entries: [...state$3.entries] };
|
|
1491
1554
|
}
|
|
1492
1555
|
function subscribe$1(listener) {
|
|
1493
1556
|
listeners$1.add(listener);
|
|
@@ -1497,8 +1560,8 @@ function subscribe$1(listener) {
|
|
|
1497
1560
|
}
|
|
1498
1561
|
function addEntry$1(url, title) {
|
|
1499
1562
|
if (!url || url === "about:blank") return;
|
|
1500
|
-
load$
|
|
1501
|
-
const last = state$
|
|
1563
|
+
load$3();
|
|
1564
|
+
const last = state$3.entries[0];
|
|
1502
1565
|
if (last && last.url === url) {
|
|
1503
1566
|
if (title && title !== last.title) {
|
|
1504
1567
|
last.title = title;
|
|
@@ -1512,28 +1575,28 @@ function addEntry$1(url, title) {
|
|
|
1512
1575
|
title: title || url,
|
|
1513
1576
|
visitedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1514
1577
|
};
|
|
1515
|
-
state$
|
|
1516
|
-
if (state$
|
|
1517
|
-
state$
|
|
1578
|
+
state$3.entries.unshift(entry);
|
|
1579
|
+
if (state$3.entries.length > MAX_HISTORY_ENTRIES) {
|
|
1580
|
+
state$3.entries = state$3.entries.slice(0, MAX_HISTORY_ENTRIES);
|
|
1518
1581
|
}
|
|
1519
1582
|
save$1();
|
|
1520
1583
|
emit$1();
|
|
1521
1584
|
}
|
|
1522
1585
|
function search(query, limit = 50) {
|
|
1523
|
-
load$
|
|
1524
|
-
if (!query.trim()) return state$
|
|
1586
|
+
load$3();
|
|
1587
|
+
if (!query.trim()) return state$3.entries.slice(0, limit);
|
|
1525
1588
|
const normalized = query.toLowerCase();
|
|
1526
|
-
return state$
|
|
1589
|
+
return state$3.entries.filter(
|
|
1527
1590
|
(e) => e.url.toLowerCase().includes(normalized) || e.title.toLowerCase().includes(normalized)
|
|
1528
1591
|
).slice(0, limit);
|
|
1529
1592
|
}
|
|
1530
1593
|
function clearAll$1() {
|
|
1531
|
-
state$
|
|
1594
|
+
state$3 = { entries: [] };
|
|
1532
1595
|
save$1();
|
|
1533
1596
|
emit$1();
|
|
1534
1597
|
}
|
|
1535
|
-
function flushPersist$
|
|
1536
|
-
return
|
|
1598
|
+
function flushPersist$3() {
|
|
1599
|
+
return persistence$3.flush();
|
|
1537
1600
|
}
|
|
1538
1601
|
const MAX_CONSOLE_ENTRIES = 500;
|
|
1539
1602
|
const MAX_NETWORK_ENTRIES = 200;
|
|
@@ -2246,10 +2309,14 @@ class TabManager {
|
|
|
2246
2309
|
window;
|
|
2247
2310
|
onStateChange;
|
|
2248
2311
|
highlightCaptureCallback = null;
|
|
2312
|
+
pageLoadCallback = null;
|
|
2249
2313
|
constructor(window2, onStateChange) {
|
|
2250
2314
|
this.window = window2;
|
|
2251
2315
|
this.onStateChange = onStateChange;
|
|
2252
2316
|
}
|
|
2317
|
+
onPageLoad(cb) {
|
|
2318
|
+
this.pageLoadCallback = cb;
|
|
2319
|
+
}
|
|
2253
2320
|
createTab(url = "about:blank", options) {
|
|
2254
2321
|
const background = options?.background ?? false;
|
|
2255
2322
|
const id = crypto$1.randomUUID();
|
|
@@ -2262,6 +2329,7 @@ class TabManager {
|
|
|
2262
2329
|
onPageLoad: (pageUrl, wc) => {
|
|
2263
2330
|
this.reapplyHighlights(pageUrl, wc);
|
|
2264
2331
|
addEntry$1(pageUrl, wc.getTitle());
|
|
2332
|
+
this.pageLoadCallback?.(pageUrl, wc);
|
|
2265
2333
|
},
|
|
2266
2334
|
onHighlightSelection: (wc) => this.captureHighlightFromPage(wc),
|
|
2267
2335
|
onHighlightRemove: (url2, text) => this.removeHighlightByText(url2, text),
|
|
@@ -2415,7 +2483,7 @@ class TabManager {
|
|
|
2415
2483
|
const wcId = wc.id;
|
|
2416
2484
|
const now = Date.now();
|
|
2417
2485
|
const last = this.lastReapply.get(wcId);
|
|
2418
|
-
const normalized = normalizeUrl(url);
|
|
2486
|
+
const normalized = normalizeUrl$1(url);
|
|
2419
2487
|
if (last && last.url === normalized && now - last.at < 500) return;
|
|
2420
2488
|
this.lastReapply.set(wcId, { url: normalized, at: now });
|
|
2421
2489
|
const highlights = getHighlightsForUrl(url);
|
|
@@ -2464,14 +2532,14 @@ class TabManager {
|
|
|
2464
2532
|
if (highlight) {
|
|
2465
2533
|
removeHighlight(highlight.id);
|
|
2466
2534
|
}
|
|
2467
|
-
const normalized = normalizeUrl(url);
|
|
2535
|
+
const normalized = normalizeUrl$1(url);
|
|
2468
2536
|
for (const id of this.order) {
|
|
2469
2537
|
const tab = this.tabs.get(id);
|
|
2470
2538
|
if (!tab) continue;
|
|
2471
2539
|
const wc = tab.view.webContents;
|
|
2472
2540
|
if (wc.isDestroyed()) continue;
|
|
2473
2541
|
try {
|
|
2474
|
-
const tabUrl = normalizeUrl(wc.getURL());
|
|
2542
|
+
const tabUrl = normalizeUrl$1(wc.getURL());
|
|
2475
2543
|
if (tabUrl === normalized) {
|
|
2476
2544
|
void this.removeHighlightMarksForText(wc, text);
|
|
2477
2545
|
}
|
|
@@ -2488,14 +2556,14 @@ class TabManager {
|
|
|
2488
2556
|
if (highlight) {
|
|
2489
2557
|
updateHighlightColor(highlight.id, color);
|
|
2490
2558
|
}
|
|
2491
|
-
const normalized = normalizeUrl(url);
|
|
2559
|
+
const normalized = normalizeUrl$1(url);
|
|
2492
2560
|
for (const id of this.order) {
|
|
2493
2561
|
const tab = this.tabs.get(id);
|
|
2494
2562
|
if (!tab) continue;
|
|
2495
2563
|
const wc = tab.view.webContents;
|
|
2496
2564
|
if (wc.isDestroyed()) continue;
|
|
2497
2565
|
try {
|
|
2498
|
-
const tabUrl = normalizeUrl(wc.getURL());
|
|
2566
|
+
const tabUrl = normalizeUrl$1(wc.getURL());
|
|
2499
2567
|
if (tabUrl === normalized) {
|
|
2500
2568
|
void this.removeHighlightMarksForText(wc, text).then(() => {
|
|
2501
2569
|
void highlightOnPage(
|
|
@@ -2651,316 +2719,399 @@ const Channels = {
|
|
|
2651
2719
|
// Window controls
|
|
2652
2720
|
WINDOW_MINIMIZE: "window:minimize",
|
|
2653
2721
|
WINDOW_MAXIMIZE: "window:maximize",
|
|
2654
|
-
WINDOW_CLOSE: "window:close"
|
|
2722
|
+
WINDOW_CLOSE: "window:close",
|
|
2723
|
+
// Autofill
|
|
2724
|
+
AUTOFILL_LIST: "autofill:list",
|
|
2725
|
+
AUTOFILL_ADD: "autofill:add",
|
|
2726
|
+
AUTOFILL_UPDATE: "autofill:update",
|
|
2727
|
+
AUTOFILL_DELETE: "autofill:delete",
|
|
2728
|
+
AUTOFILL_FILL: "autofill:fill",
|
|
2729
|
+
// Page snapshots / What Changed
|
|
2730
|
+
PAGE_DIFF_ACTIVITY: "page:diff-activity",
|
|
2731
|
+
PAGE_CHANGED: "page:changed",
|
|
2732
|
+
PAGE_DIFF_GET: "page:diff-get",
|
|
2733
|
+
PAGE_DIFF_DIRTY: "page:diff-dirty"
|
|
2655
2734
|
};
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2735
|
+
const MAX_DETAIL_ITEMS = 3;
|
|
2736
|
+
const MIN_BLOCK_SIMILARITY = 0.82;
|
|
2737
|
+
function normalizeText$2(value) {
|
|
2738
|
+
return value.replace(/\s+/g, " ").trim();
|
|
2739
|
+
}
|
|
2740
|
+
function truncateText(value, max = 180) {
|
|
2741
|
+
const normalized = normalizeText$2(value);
|
|
2742
|
+
if (normalized.length <= max) return normalized;
|
|
2743
|
+
return `${normalized.slice(0, max - 3)}...`;
|
|
2744
|
+
}
|
|
2745
|
+
function tokenize(text) {
|
|
2746
|
+
return normalizeText$2(text).toLowerCase().split(/\s+/).filter(Boolean);
|
|
2747
|
+
}
|
|
2748
|
+
function countOverlap(a, b) {
|
|
2749
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
2750
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2751
|
+
for (const token of b) {
|
|
2752
|
+
counts.set(token, (counts.get(token) || 0) + 1);
|
|
2753
|
+
}
|
|
2754
|
+
let overlap = 0;
|
|
2755
|
+
for (const token of a) {
|
|
2756
|
+
const remaining = counts.get(token) || 0;
|
|
2757
|
+
if (remaining > 0) {
|
|
2758
|
+
overlap += 1;
|
|
2759
|
+
counts.set(token, remaining - 1);
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
return overlap;
|
|
2763
|
+
}
|
|
2764
|
+
function similarityScore(a, b) {
|
|
2765
|
+
const aTokens = tokenize(a);
|
|
2766
|
+
const bTokens = tokenize(b);
|
|
2767
|
+
if (aTokens.length === 0 && bTokens.length === 0) return 1;
|
|
2768
|
+
if (aTokens.length === 0 || bTokens.length === 0) return 0;
|
|
2769
|
+
return countOverlap(aTokens, bTokens) / Math.max(aTokens.length, bTokens.length);
|
|
2770
|
+
}
|
|
2771
|
+
function extractTextBlocks(text) {
|
|
2772
|
+
const compact = text.replace(/\r\n/g, "\n").trim();
|
|
2773
|
+
if (!compact) return [];
|
|
2774
|
+
const paragraphs = compact.split(/\n\s*\n+/).map((block) => normalizeText$2(block)).filter(Boolean);
|
|
2775
|
+
if (paragraphs.length > 1) return paragraphs;
|
|
2776
|
+
return compact.split(/\n+/).map((line) => normalizeText$2(line)).filter(Boolean);
|
|
2777
|
+
}
|
|
2778
|
+
function buildLcsTable(a, b) {
|
|
2779
|
+
const table = Array.from(
|
|
2780
|
+
{ length: a.length + 1 },
|
|
2781
|
+
() => Array.from({ length: b.length + 1 }).fill(0)
|
|
2782
|
+
);
|
|
2783
|
+
for (let i = a.length - 1; i >= 0; i -= 1) {
|
|
2784
|
+
for (let j = b.length - 1; j >= 0; j -= 1) {
|
|
2785
|
+
table[i][j] = a[i] === b[j] ? table[i + 1][j + 1] + 1 : Math.max(table[i + 1][j], table[i][j + 1]);
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
return table;
|
|
2789
|
+
}
|
|
2790
|
+
function diffBlocks(oldBlocks, newBlocks) {
|
|
2791
|
+
const table = buildLcsTable(oldBlocks, newBlocks);
|
|
2792
|
+
const ops = [];
|
|
2793
|
+
let i = 0;
|
|
2794
|
+
let j = 0;
|
|
2795
|
+
while (i < oldBlocks.length && j < newBlocks.length) {
|
|
2796
|
+
if (oldBlocks[i] === newBlocks[j]) {
|
|
2797
|
+
ops.push({ type: "equal", value: oldBlocks[i] });
|
|
2798
|
+
i += 1;
|
|
2799
|
+
j += 1;
|
|
2800
|
+
continue;
|
|
2675
2801
|
}
|
|
2676
|
-
|
|
2802
|
+
if (table[i + 1][j] >= table[i][j + 1]) {
|
|
2803
|
+
ops.push({ type: "removed", value: oldBlocks[i] });
|
|
2804
|
+
i += 1;
|
|
2805
|
+
} else {
|
|
2806
|
+
ops.push({ type: "added", value: newBlocks[j] });
|
|
2807
|
+
j += 1;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
while (i < oldBlocks.length) {
|
|
2811
|
+
ops.push({ type: "removed", value: oldBlocks[i] });
|
|
2812
|
+
i += 1;
|
|
2813
|
+
}
|
|
2814
|
+
while (j < newBlocks.length) {
|
|
2815
|
+
ops.push({ type: "added", value: newBlocks[j] });
|
|
2816
|
+
j += 1;
|
|
2817
|
+
}
|
|
2818
|
+
return ops;
|
|
2677
2819
|
}
|
|
2678
|
-
|
|
2679
|
-
const
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
try {
|
|
2684
|
-
return await sidebarView.webContents.executeJavaScript(
|
|
2685
|
-
`(() => {
|
|
2686
|
-
const el = document.elementFromPoint(${x}, ${y});
|
|
2687
|
-
const nav = el && typeof el.closest === "function"
|
|
2688
|
-
? el.closest(".highlight-nav")
|
|
2689
|
-
: null;
|
|
2690
|
-
const label = nav?.querySelector(".highlight-nav-label")?.textContent?.trim() || "";
|
|
2691
|
-
return {
|
|
2692
|
-
inHighlightNav: !!nav,
|
|
2693
|
-
canRemoveCurrent: /\\d+\\s*\\/\\s*\\d+/.test(label),
|
|
2694
|
-
bookmarkId:
|
|
2695
|
-
el && typeof el.closest === "function"
|
|
2696
|
-
? el.closest("[data-bookmark-id]")?.getAttribute("data-bookmark-id") || undefined
|
|
2697
|
-
: undefined,
|
|
2698
|
-
};
|
|
2699
|
-
})()`,
|
|
2700
|
-
true
|
|
2820
|
+
function summarizeContentChange(changedCount, addedCount, removedCount) {
|
|
2821
|
+
const parts = [];
|
|
2822
|
+
if (changedCount > 0) {
|
|
2823
|
+
parts.push(
|
|
2824
|
+
`${changedCount} updated ${changedCount === 1 ? "section" : "sections"}`
|
|
2701
2825
|
);
|
|
2702
|
-
} catch {
|
|
2703
|
-
return { inHighlightNav: false, canRemoveCurrent: false };
|
|
2704
2826
|
}
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
const menu = new electron.Menu();
|
|
2709
|
-
if (target.inHighlightNav) {
|
|
2710
|
-
if (target.canRemoveCurrent) {
|
|
2711
|
-
menu.append(
|
|
2712
|
-
new electron.MenuItem({
|
|
2713
|
-
label: "Remove Current Highlight",
|
|
2714
|
-
click: () => sidebarView.webContents.send(
|
|
2715
|
-
Channels.SIDEBAR_HIGHLIGHT_ACTION,
|
|
2716
|
-
"remove-current"
|
|
2717
|
-
)
|
|
2718
|
-
})
|
|
2719
|
-
);
|
|
2720
|
-
}
|
|
2721
|
-
menu.append(
|
|
2722
|
-
new electron.MenuItem({
|
|
2723
|
-
label: "Clear All Highlights",
|
|
2724
|
-
click: () => sidebarView.webContents.send(
|
|
2725
|
-
Channels.SIDEBAR_HIGHLIGHT_ACTION,
|
|
2726
|
-
"clear-all"
|
|
2727
|
-
)
|
|
2728
|
-
})
|
|
2827
|
+
if (addedCount > 0) {
|
|
2828
|
+
parts.push(
|
|
2829
|
+
`${addedCount} added ${addedCount === 1 ? "section" : "sections"}`
|
|
2729
2830
|
);
|
|
2730
2831
|
}
|
|
2731
|
-
if (
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
}
|
|
2735
|
-
menu.append(
|
|
2736
|
-
new electron.MenuItem({
|
|
2737
|
-
label: "Add Context to Chat",
|
|
2738
|
-
click: () => sidebarView.webContents.send(
|
|
2739
|
-
Channels.BOOKMARK_ADD_CONTEXT_TO_CHAT,
|
|
2740
|
-
target.bookmarkId
|
|
2741
|
-
)
|
|
2742
|
-
})
|
|
2832
|
+
if (removedCount > 0) {
|
|
2833
|
+
parts.push(
|
|
2834
|
+
`${removedCount} removed ${removedCount === 1 ? "section" : "sections"}`
|
|
2743
2835
|
);
|
|
2744
2836
|
}
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2837
|
+
return parts.join(", ");
|
|
2838
|
+
}
|
|
2839
|
+
function diffSnapshots(oldSnap, currentContent, currentTitle, currentHeadings) {
|
|
2840
|
+
const changes = [];
|
|
2841
|
+
if (oldSnap.title !== currentTitle) {
|
|
2842
|
+
changes.push({
|
|
2843
|
+
kind: "changed",
|
|
2844
|
+
section: "title",
|
|
2845
|
+
summary: `"${oldSnap.title}" → "${currentTitle}"`,
|
|
2846
|
+
before: oldSnap.title,
|
|
2847
|
+
after: currentTitle
|
|
2848
|
+
});
|
|
2849
|
+
}
|
|
2850
|
+
const oldHeadings = oldSnap.headings.split("\n").filter(Boolean);
|
|
2851
|
+
const newHeadings = currentHeadings.split("\n").filter(Boolean);
|
|
2852
|
+
if (oldHeadings.join("\n") !== newHeadings.join("\n")) {
|
|
2853
|
+
const added = newHeadings.filter((h) => !oldHeadings.includes(h));
|
|
2854
|
+
const removed = oldHeadings.filter((h) => !newHeadings.includes(h));
|
|
2855
|
+
const parts = [];
|
|
2856
|
+
if (added.length > 0) parts.push(`New: ${added.join(", ")}`);
|
|
2857
|
+
if (removed.length > 0) parts.push(`Gone: ${removed.join(", ")}`);
|
|
2858
|
+
if (parts.length > 0) {
|
|
2859
|
+
changes.push({
|
|
2860
|
+
kind: added.length > 0 && removed.length > 0 ? "changed" : added.length > 0 ? "added" : "removed",
|
|
2861
|
+
section: "headings",
|
|
2862
|
+
summary: parts.join(". "),
|
|
2863
|
+
addedItems: added.slice(0, MAX_DETAIL_ITEMS),
|
|
2864
|
+
removedItems: removed.slice(0, MAX_DETAIL_ITEMS)
|
|
2865
|
+
});
|
|
2748
2866
|
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
);
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2867
|
+
}
|
|
2868
|
+
const oldBlocks = extractTextBlocks(oldSnap.textContent);
|
|
2869
|
+
const newBlocks = extractTextBlocks(currentContent);
|
|
2870
|
+
const overallSimilarity = similarityScore(oldSnap.textContent, currentContent);
|
|
2871
|
+
if (overallSimilarity < 0.98) {
|
|
2872
|
+
const ops = diffBlocks(oldBlocks, newBlocks);
|
|
2873
|
+
const addedBlocks = [];
|
|
2874
|
+
const removedBlocks = [];
|
|
2875
|
+
const changedPairs = [];
|
|
2876
|
+
let idx = 0;
|
|
2877
|
+
while (idx < ops.length) {
|
|
2878
|
+
if (ops[idx]?.type === "equal") {
|
|
2879
|
+
idx += 1;
|
|
2880
|
+
continue;
|
|
2881
|
+
}
|
|
2882
|
+
const pendingRemoved = [];
|
|
2883
|
+
const pendingAdded = [];
|
|
2884
|
+
while (idx < ops.length && ops[idx]?.type !== "equal") {
|
|
2885
|
+
const op = ops[idx];
|
|
2886
|
+
if (op?.type === "removed") pendingRemoved.push(op.value);
|
|
2887
|
+
if (op?.type === "added") pendingAdded.push(op.value);
|
|
2888
|
+
idx += 1;
|
|
2889
|
+
}
|
|
2890
|
+
while (pendingRemoved.length > 0 && pendingAdded.length > 0) {
|
|
2891
|
+
const before = pendingRemoved[0];
|
|
2892
|
+
const after = pendingAdded[0];
|
|
2893
|
+
if (similarityScore(before, after) < MIN_BLOCK_SIMILARITY) break;
|
|
2894
|
+
changedPairs.push({ before, after });
|
|
2895
|
+
pendingRemoved.shift();
|
|
2896
|
+
pendingAdded.shift();
|
|
2897
|
+
}
|
|
2898
|
+
removedBlocks.push(...pendingRemoved);
|
|
2899
|
+
addedBlocks.push(...pendingAdded);
|
|
2900
|
+
}
|
|
2901
|
+
if (changedPairs.length > 0 || addedBlocks.length > 0 || removedBlocks.length > 0) {
|
|
2902
|
+
changes.push({
|
|
2903
|
+
kind: "changed",
|
|
2904
|
+
section: "content",
|
|
2905
|
+
summary: summarizeContentChange(
|
|
2906
|
+
changedPairs.length,
|
|
2907
|
+
addedBlocks.length,
|
|
2908
|
+
removedBlocks.length
|
|
2909
|
+
),
|
|
2910
|
+
before: changedPairs[0] ? truncateText(changedPairs[0].before) : removedBlocks[0] ? truncateText(removedBlocks[0]) : void 0,
|
|
2911
|
+
after: changedPairs[0] ? truncateText(changedPairs[0].after) : addedBlocks[0] ? truncateText(addedBlocks[0]) : void 0,
|
|
2912
|
+
addedItems: addedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText(item)),
|
|
2913
|
+
removedItems: removedBlocks.slice(0, MAX_DETAIL_ITEMS).map((item) => truncateText(item))
|
|
2914
|
+
});
|
|
2789
2915
|
}
|
|
2790
|
-
menu.append(new electron.MenuItem({ role: "copy" }));
|
|
2791
2916
|
}
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2917
|
+
return {
|
|
2918
|
+
url: oldSnap.url,
|
|
2919
|
+
hasChanges: changes.length > 0,
|
|
2920
|
+
oldSnapshot: { capturedAt: oldSnap.capturedAt, title: oldSnap.title },
|
|
2921
|
+
changes
|
|
2922
|
+
};
|
|
2795
2923
|
}
|
|
2796
|
-
function
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2924
|
+
function normalizePageUrl(rawUrl) {
|
|
2925
|
+
try {
|
|
2926
|
+
const url = new URL(rawUrl);
|
|
2927
|
+
const pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
2928
|
+
return `${url.origin}${pathname}`.toLowerCase();
|
|
2929
|
+
} catch {
|
|
2930
|
+
return rawUrl.trim().replace(/\/+$/, "").toLowerCase();
|
|
2931
|
+
}
|
|
2803
2932
|
}
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
const uiState = {
|
|
2854
|
-
sidebarOpen: true,
|
|
2855
|
-
sidebarWidth: settings2.sidebarWidth,
|
|
2856
|
-
focusMode: false,
|
|
2857
|
-
settingsOpen: false,
|
|
2858
|
-
devtoolsPanelOpen: false,
|
|
2859
|
-
devtoolsPanelHeight: DEFAULT_DEVTOOLS_PANEL_HEIGHT
|
|
2860
|
-
};
|
|
2861
|
-
const tabManager = new TabManager(mainWindow, onTabStateChange);
|
|
2862
|
-
const state2 = {
|
|
2863
|
-
mainWindow,
|
|
2864
|
-
chromeView,
|
|
2865
|
-
sidebarView,
|
|
2866
|
-
devtoolsPanelView,
|
|
2867
|
-
tabManager,
|
|
2868
|
-
uiState
|
|
2869
|
-
};
|
|
2870
|
-
mainWindow.on("resize", () => layoutViews(state2));
|
|
2871
|
-
mainWindow.on("show", () => layoutViews(state2));
|
|
2872
|
-
mainWindow.on("focus", () => layoutViews(state2));
|
|
2873
|
-
layoutViews(state2);
|
|
2874
|
-
return state2;
|
|
2933
|
+
const SNAPSHOT_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
2934
|
+
"q",
|
|
2935
|
+
"query",
|
|
2936
|
+
"search",
|
|
2937
|
+
"s",
|
|
2938
|
+
"term",
|
|
2939
|
+
"keyword",
|
|
2940
|
+
"keywords",
|
|
2941
|
+
"page",
|
|
2942
|
+
"p",
|
|
2943
|
+
"offset",
|
|
2944
|
+
"cursor",
|
|
2945
|
+
"sort",
|
|
2946
|
+
"order",
|
|
2947
|
+
"filter",
|
|
2948
|
+
"filters",
|
|
2949
|
+
"category",
|
|
2950
|
+
"categories",
|
|
2951
|
+
"tag",
|
|
2952
|
+
"tags",
|
|
2953
|
+
"tab",
|
|
2954
|
+
"view"
|
|
2955
|
+
]);
|
|
2956
|
+
const TRACKING_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
2957
|
+
"fbclid",
|
|
2958
|
+
"gclid",
|
|
2959
|
+
"mc_cid",
|
|
2960
|
+
"mc_eid",
|
|
2961
|
+
"ref",
|
|
2962
|
+
"source",
|
|
2963
|
+
"si"
|
|
2964
|
+
]);
|
|
2965
|
+
function normalizeQueryValue(value) {
|
|
2966
|
+
return value.replace(/\s+/g, " ").trim().toLowerCase();
|
|
2967
|
+
}
|
|
2968
|
+
function serializeSnapshotParams(params) {
|
|
2969
|
+
return params.map(
|
|
2970
|
+
([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
|
|
2971
|
+
).join("&");
|
|
2972
|
+
}
|
|
2973
|
+
function normalizeSnapshotParams(entries, pathname) {
|
|
2974
|
+
return Array.from(entries).filter(
|
|
2975
|
+
([key, value]) => shouldKeepSnapshotQueryParam(pathname, key, value)
|
|
2976
|
+
).map(([key, value]) => [
|
|
2977
|
+
key.trim().toLowerCase(),
|
|
2978
|
+
normalizeQueryValue(value)
|
|
2979
|
+
]).sort(
|
|
2980
|
+
([keyA, valueA], [keyB, valueB]) => keyA === keyB ? valueA.localeCompare(valueB) : keyA.localeCompare(keyB)
|
|
2981
|
+
);
|
|
2875
2982
|
}
|
|
2876
|
-
function
|
|
2877
|
-
const
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
if (
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2983
|
+
function shouldKeepSnapshotQueryParam(pathname, rawKey, value) {
|
|
2984
|
+
const key = rawKey.trim().toLowerCase();
|
|
2985
|
+
if (!key || !value.trim()) return false;
|
|
2986
|
+
if (key.startsWith("utm_")) return false;
|
|
2987
|
+
if (TRACKING_QUERY_KEYS.has(key)) return false;
|
|
2988
|
+
if (SNAPSHOT_QUERY_KEYS.has(key)) return true;
|
|
2989
|
+
return /\/(search|results|browse|discover|find|category|tag|topics?|collections?|list)(\/|$)/i.test(
|
|
2990
|
+
pathname
|
|
2991
|
+
);
|
|
2992
|
+
}
|
|
2993
|
+
function buildSnapshotHashKey(hash, pathname) {
|
|
2994
|
+
let raw = hash.replace(/^#/, "").trim();
|
|
2995
|
+
if (!raw) return null;
|
|
2996
|
+
let bang = false;
|
|
2997
|
+
if (raw.startsWith("!")) {
|
|
2998
|
+
bang = true;
|
|
2999
|
+
raw = raw.slice(1).trim();
|
|
3000
|
+
}
|
|
3001
|
+
if (raw.startsWith("/")) {
|
|
3002
|
+
const [routePart, queryPart = ""] = raw.split("?");
|
|
3003
|
+
const route = routePart.replace(/\/+$/, "") || "/";
|
|
3004
|
+
const params = normalizeSnapshotParams(
|
|
3005
|
+
new URLSearchParams(queryPart).entries(),
|
|
3006
|
+
pathname
|
|
3007
|
+
);
|
|
3008
|
+
const query = serializeSnapshotParams(params);
|
|
3009
|
+
return `#${bang ? "!" : ""}${route.toLowerCase()}${query ? `?${query}` : ""}`;
|
|
3010
|
+
}
|
|
3011
|
+
const queryLike = raw.startsWith("?") ? raw.slice(1) : raw;
|
|
3012
|
+
if (queryLike.includes("=")) {
|
|
3013
|
+
const params = normalizeSnapshotParams(
|
|
3014
|
+
new URLSearchParams(queryLike).entries(),
|
|
3015
|
+
pathname
|
|
3016
|
+
);
|
|
3017
|
+
if (params.length === 0) return null;
|
|
3018
|
+
const query = serializeSnapshotParams(params);
|
|
3019
|
+
return `#${bang ? "!" : ""}?${query}`;
|
|
2894
3020
|
}
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
3021
|
+
return null;
|
|
3022
|
+
}
|
|
3023
|
+
function buildPageSnapshotKey(rawUrl) {
|
|
3024
|
+
try {
|
|
3025
|
+
const url = new URL(rawUrl);
|
|
3026
|
+
const pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
3027
|
+
const params = normalizeSnapshotParams(url.searchParams.entries(), pathname);
|
|
3028
|
+
const query = serializeSnapshotParams(params);
|
|
3029
|
+
const hash = buildSnapshotHashKey(url.hash, pathname);
|
|
3030
|
+
return `${url.origin.toLowerCase()}${pathname.toLowerCase()}${query ? `?${query}` : ""}${hash || ""}`;
|
|
3031
|
+
} catch {
|
|
3032
|
+
return normalizePageUrl(rawUrl);
|
|
2905
3033
|
}
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
});
|
|
2914
|
-
} else {
|
|
2915
|
-
devtoolsPanelView.setBounds({ x: 0, y: height, width: 0, height: 0 });
|
|
3034
|
+
}
|
|
3035
|
+
function isTrackablePageUrl(rawUrl) {
|
|
3036
|
+
try {
|
|
3037
|
+
const url = new URL(rawUrl);
|
|
3038
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
3039
|
+
} catch {
|
|
3040
|
+
return false;
|
|
2916
3041
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
height: height - chromeHeight - devtoolsHeight
|
|
2930
|
-
});
|
|
3042
|
+
}
|
|
3043
|
+
const SAVE_DEBOUNCE_MS$2 = 500;
|
|
3044
|
+
const MAX_TEXT_LENGTH = 8e3;
|
|
3045
|
+
let snapshots = null;
|
|
3046
|
+
function getFilePath$1() {
|
|
3047
|
+
return path.join(electron.app.getPath("userData"), "vessel-page-snapshots.json");
|
|
3048
|
+
}
|
|
3049
|
+
function normalizeStoredSnapshot(value) {
|
|
3050
|
+
if (!value || typeof value !== "object") return null;
|
|
3051
|
+
const raw = value;
|
|
3052
|
+
if (typeof raw.url !== "string" || typeof raw.title !== "string" || typeof raw.textContent !== "string" || typeof raw.headings !== "string" || typeof raw.capturedAt !== "string") {
|
|
3053
|
+
return null;
|
|
2931
3054
|
}
|
|
3055
|
+
return {
|
|
3056
|
+
url: raw.url,
|
|
3057
|
+
title: raw.title,
|
|
3058
|
+
textContent: raw.textContent,
|
|
3059
|
+
headings: raw.headings,
|
|
3060
|
+
capturedAt: raw.capturedAt
|
|
3061
|
+
};
|
|
2932
3062
|
}
|
|
2933
|
-
function
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
3063
|
+
function load$2() {
|
|
3064
|
+
if (snapshots) return snapshots;
|
|
3065
|
+
snapshots = loadJsonFile({
|
|
3066
|
+
filePath: getFilePath$1(),
|
|
3067
|
+
fallback: /* @__PURE__ */ new Map(),
|
|
3068
|
+
secure: true,
|
|
3069
|
+
parse: (raw) => {
|
|
3070
|
+
const next = /* @__PURE__ */ new Map();
|
|
3071
|
+
if (!Array.isArray(raw)) return next;
|
|
3072
|
+
for (const entry of raw) {
|
|
3073
|
+
const snapshot = normalizeStoredSnapshot(entry);
|
|
3074
|
+
if (snapshot) next.set(snapshot.url, snapshot);
|
|
3075
|
+
}
|
|
3076
|
+
return next;
|
|
3077
|
+
}
|
|
2946
3078
|
});
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
3079
|
+
return snapshots;
|
|
3080
|
+
}
|
|
3081
|
+
const persistence$2 = createDebouncedJsonPersistence({
|
|
3082
|
+
debounceMs: SAVE_DEBOUNCE_MS$2,
|
|
3083
|
+
filePath: getFilePath$1(),
|
|
3084
|
+
getValue: () => snapshots,
|
|
3085
|
+
logLabel: "page snapshots",
|
|
3086
|
+
secure: true,
|
|
3087
|
+
serialize: (value) => Array.from(value.values()).slice(-500)
|
|
3088
|
+
});
|
|
3089
|
+
function normalizeUrl(rawUrl) {
|
|
3090
|
+
return buildPageSnapshotKey(rawUrl);
|
|
3091
|
+
}
|
|
3092
|
+
function shouldTrackSnapshotUrl(rawUrl) {
|
|
3093
|
+
return isTrackablePageUrl(rawUrl);
|
|
3094
|
+
}
|
|
3095
|
+
function getSnapshot(normalizedUrl) {
|
|
3096
|
+
return load$2().get(normalizedUrl);
|
|
3097
|
+
}
|
|
3098
|
+
function saveSnapshot(rawUrl, title, textContent, headings) {
|
|
3099
|
+
const s = load$2();
|
|
3100
|
+
const key = normalizeUrl(rawUrl);
|
|
3101
|
+
const snapshot = {
|
|
3102
|
+
url: key,
|
|
3103
|
+
title,
|
|
3104
|
+
textContent: textContent.slice(0, MAX_TEXT_LENGTH),
|
|
3105
|
+
headings: headings.map((h) => `${"#".repeat(h.level)} ${h.text}`).join("\n"),
|
|
3106
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3107
|
+
};
|
|
3108
|
+
s.delete(key);
|
|
3109
|
+
s.set(key, snapshot);
|
|
3110
|
+
persistence$2.schedule();
|
|
3111
|
+
return snapshot;
|
|
3112
|
+
}
|
|
3113
|
+
function flushPersist$2() {
|
|
3114
|
+
return persistence$2.flush();
|
|
2964
3115
|
}
|
|
2965
3116
|
const SEARCH_ENGINE_HOSTS = [
|
|
2966
3117
|
"google.",
|
|
@@ -5013,35 +5164,455 @@ function normalizePageContent(value) {
|
|
|
5013
5164
|
pageIssues: Array.isArray(page.pageIssues) ? page.pageIssues : []
|
|
5014
5165
|
};
|
|
5015
5166
|
}
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5167
|
+
const latestPageDiffs = /* @__PURE__ */ new Map();
|
|
5168
|
+
const recentPageDiffBursts = /* @__PURE__ */ new Map();
|
|
5169
|
+
const pendingPageSnapshotTimers = /* @__PURE__ */ new Map();
|
|
5170
|
+
const pendingPageSnapshotDueAt = /* @__PURE__ */ new Map();
|
|
5171
|
+
const lastMutationSnapshotAt = /* @__PURE__ */ new Map();
|
|
5172
|
+
const lastMutationActivityAt = /* @__PURE__ */ new Map();
|
|
5173
|
+
const MIN_MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
5174
|
+
const SETTLE_AFTER_ACTIVITY_MS = 1500;
|
|
5175
|
+
function getLatestPageDiff(rawUrl) {
|
|
5176
|
+
if (!shouldTrackSnapshotUrl(rawUrl)) return null;
|
|
5177
|
+
return latestPageDiffs.get(normalizeUrl(rawUrl)) ?? null;
|
|
5178
|
+
}
|
|
5179
|
+
function summarizeDiffBurst(diff) {
|
|
5180
|
+
const items = diff.changes.slice(0, 2).map((change) => `${change.section}: ${change.summary}`);
|
|
5181
|
+
return items.join(" | ");
|
|
5182
|
+
}
|
|
5183
|
+
function enrichWithBurstHistory(key, diff) {
|
|
5184
|
+
const detectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5185
|
+
const nextBurst = {
|
|
5186
|
+
detectedAt,
|
|
5187
|
+
summary: summarizeDiffBurst(diff)
|
|
5188
|
+
};
|
|
5189
|
+
const bursts = [...recentPageDiffBursts.get(key) || [], nextBurst].slice(
|
|
5190
|
+
-5
|
|
5191
|
+
);
|
|
5192
|
+
recentPageDiffBursts.set(key, bursts);
|
|
5193
|
+
return {
|
|
5194
|
+
...diff,
|
|
5195
|
+
burstCount: bursts.length,
|
|
5196
|
+
firstDetectedAt: bursts[0]?.detectedAt,
|
|
5197
|
+
lastDetectedAt: bursts[bursts.length - 1]?.detectedAt,
|
|
5198
|
+
recentBursts: bursts
|
|
5199
|
+
};
|
|
5200
|
+
}
|
|
5201
|
+
async function capturePageSnapshot(url, wc, sendToRendererViews) {
|
|
5202
|
+
try {
|
|
5203
|
+
if (!shouldTrackSnapshotUrl(url)) return;
|
|
5204
|
+
const key = normalizeUrl(url);
|
|
5205
|
+
const oldSnap = getSnapshot(key);
|
|
5206
|
+
const content = await extractContent(wc);
|
|
5207
|
+
const textContent = content.content || "";
|
|
5208
|
+
const title = content.title || "";
|
|
5209
|
+
const headings = content.headings || [];
|
|
5210
|
+
const currentHeadings = headings.map((h) => `${"#".repeat(h.level)} ${h.text}`).join("\n");
|
|
5211
|
+
if (oldSnap) {
|
|
5212
|
+
const diff = diffSnapshots(oldSnap, textContent, title, currentHeadings);
|
|
5213
|
+
if (diff.hasChanges) {
|
|
5214
|
+
const enrichedDiff = enrichWithBurstHistory(key, diff);
|
|
5215
|
+
latestPageDiffs.set(key, enrichedDiff);
|
|
5216
|
+
sendToRendererViews(Channels.PAGE_CHANGED, enrichedDiff);
|
|
5217
|
+
} else {
|
|
5218
|
+
latestPageDiffs.delete(key);
|
|
5219
|
+
}
|
|
5220
|
+
} else {
|
|
5221
|
+
latestPageDiffs.delete(key);
|
|
5222
|
+
recentPageDiffBursts.delete(key);
|
|
5036
5223
|
}
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5224
|
+
saveSnapshot(url, title, textContent, headings);
|
|
5225
|
+
} catch {
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
5228
|
+
function computeNextSnapshotDueAt(wcId, now, delayMs) {
|
|
5229
|
+
const lastCaptureAt = lastMutationSnapshotAt.get(wcId) || 0;
|
|
5230
|
+
const lastActivityAt = lastMutationActivityAt.get(wcId) || 0;
|
|
5231
|
+
const earliestAllowedAt = lastCaptureAt + MIN_MUTATION_CAPTURE_INTERVAL_MS;
|
|
5232
|
+
const stableAfterActivityAt = lastActivityAt ? lastActivityAt + SETTLE_AFTER_ACTIVITY_MS : 0;
|
|
5233
|
+
return Math.max(now + delayMs, earliestAllowedAt, stableAfterActivityAt);
|
|
5234
|
+
}
|
|
5235
|
+
function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
|
|
5236
|
+
const wcId = wc.id;
|
|
5237
|
+
const existing = pendingPageSnapshotTimers.get(wcId);
|
|
5238
|
+
if (existing) clearTimeout(existing);
|
|
5239
|
+
const timer = setTimeout(() => {
|
|
5240
|
+
pendingPageSnapshotTimers.delete(wcId);
|
|
5241
|
+
pendingPageSnapshotDueAt.delete(wcId);
|
|
5242
|
+
if (wc.isDestroyed()) return;
|
|
5243
|
+
lastMutationSnapshotAt.set(wcId, Date.now());
|
|
5244
|
+
void capturePageSnapshot(wc.getURL(), wc, sendToRendererViews);
|
|
5245
|
+
}, Math.max(0, dueAt - Date.now()));
|
|
5246
|
+
pendingPageSnapshotTimers.set(wcId, timer);
|
|
5247
|
+
pendingPageSnapshotDueAt.set(wcId, dueAt);
|
|
5248
|
+
}
|
|
5249
|
+
function notePageMutationActivity(wc, sendToRendererViews) {
|
|
5250
|
+
if (wc.isDestroyed()) return;
|
|
5251
|
+
const wcId = wc.id;
|
|
5252
|
+
const now = Date.now();
|
|
5253
|
+
lastMutationActivityAt.set(wcId, now);
|
|
5254
|
+
const existingDueAt = pendingPageSnapshotDueAt.get(wcId);
|
|
5255
|
+
if (existingDueAt == null) return;
|
|
5256
|
+
const nextDueAt = computeNextSnapshotDueAt(wcId, now, 0);
|
|
5257
|
+
if (nextDueAt <= existingDueAt) return;
|
|
5258
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
5259
|
+
}
|
|
5260
|
+
function schedulePageSnapshotCapture(wc, sendToRendererViews, delayMs = 0) {
|
|
5261
|
+
if (wc.isDestroyed()) return;
|
|
5262
|
+
const wcId = wc.id;
|
|
5263
|
+
const now = Date.now();
|
|
5264
|
+
const nextDueAt = computeNextSnapshotDueAt(wcId, now, delayMs);
|
|
5265
|
+
const existingDueAt = pendingPageSnapshotDueAt.get(wcId);
|
|
5266
|
+
if (existingDueAt != null && existingDueAt >= nextDueAt) {
|
|
5267
|
+
return;
|
|
5268
|
+
}
|
|
5269
|
+
scheduleTimerAt(wc, sendToRendererViews, nextDueAt);
|
|
5270
|
+
}
|
|
5271
|
+
function enableClipboardShortcuts(view) {
|
|
5272
|
+
view.webContents.on("before-input-event", (event, input) => {
|
|
5273
|
+
if (!input.control && !input.meta) return;
|
|
5274
|
+
const key = input.key.toLowerCase();
|
|
5275
|
+
const wc = view.webContents;
|
|
5276
|
+
if (input.type === "keyDown") {
|
|
5277
|
+
if (key === "c") {
|
|
5278
|
+
wc.copy();
|
|
5279
|
+
event.preventDefault();
|
|
5280
|
+
} else if (key === "v") {
|
|
5281
|
+
wc.paste();
|
|
5282
|
+
event.preventDefault();
|
|
5283
|
+
} else if (key === "x") {
|
|
5284
|
+
wc.cut();
|
|
5285
|
+
event.preventDefault();
|
|
5286
|
+
} else if (key === "a") {
|
|
5287
|
+
wc.selectAll();
|
|
5288
|
+
event.preventDefault();
|
|
5289
|
+
}
|
|
5040
5290
|
}
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5291
|
+
});
|
|
5292
|
+
}
|
|
5293
|
+
const CHROME_HEIGHT = 110;
|
|
5294
|
+
const DEFAULT_DEVTOOLS_PANEL_HEIGHT = 250;
|
|
5295
|
+
const MIN_DEVTOOLS_PANEL = 120;
|
|
5296
|
+
const MAX_DEVTOOLS_PANEL = 600;
|
|
5297
|
+
async function getSidebarContextTarget(sidebarView, x, y) {
|
|
5298
|
+
try {
|
|
5299
|
+
return await sidebarView.webContents.executeJavaScript(
|
|
5300
|
+
`(() => {
|
|
5301
|
+
const el = document.elementFromPoint(${x}, ${y});
|
|
5302
|
+
const nav = el && typeof el.closest === "function"
|
|
5303
|
+
? el.closest(".highlight-nav")
|
|
5304
|
+
: null;
|
|
5305
|
+
const label = nav?.querySelector(".highlight-nav-label")?.textContent?.trim() || "";
|
|
5306
|
+
return {
|
|
5307
|
+
inHighlightNav: !!nav,
|
|
5308
|
+
canRemoveCurrent: /\\d+\\s*\\/\\s*\\d+/.test(label),
|
|
5309
|
+
bookmarkId:
|
|
5310
|
+
el && typeof el.closest === "function"
|
|
5311
|
+
? el.closest("[data-bookmark-id]")?.getAttribute("data-bookmark-id") || undefined
|
|
5312
|
+
: undefined,
|
|
5313
|
+
};
|
|
5314
|
+
})()`,
|
|
5315
|
+
true
|
|
5316
|
+
);
|
|
5317
|
+
} catch {
|
|
5318
|
+
return { inHighlightNav: false, canRemoveCurrent: false };
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
5321
|
+
async function showSidebarContextMenu(mainWindow, sidebarView, params) {
|
|
5322
|
+
const target = await getSidebarContextTarget(sidebarView, params.x, params.y);
|
|
5323
|
+
const menu = new electron.Menu();
|
|
5324
|
+
if (target.inHighlightNav) {
|
|
5325
|
+
if (target.canRemoveCurrent) {
|
|
5326
|
+
menu.append(
|
|
5327
|
+
new electron.MenuItem({
|
|
5328
|
+
label: "Remove Current Highlight",
|
|
5329
|
+
click: () => sidebarView.webContents.send(
|
|
5330
|
+
Channels.SIDEBAR_HIGHLIGHT_ACTION,
|
|
5331
|
+
"remove-current"
|
|
5332
|
+
)
|
|
5333
|
+
})
|
|
5334
|
+
);
|
|
5335
|
+
}
|
|
5336
|
+
menu.append(
|
|
5337
|
+
new electron.MenuItem({
|
|
5338
|
+
label: "Clear All Highlights",
|
|
5339
|
+
click: () => sidebarView.webContents.send(
|
|
5340
|
+
Channels.SIDEBAR_HIGHLIGHT_ACTION,
|
|
5341
|
+
"clear-all"
|
|
5342
|
+
)
|
|
5343
|
+
})
|
|
5344
|
+
);
|
|
5345
|
+
}
|
|
5346
|
+
if (target.bookmarkId) {
|
|
5347
|
+
if (menu.items.length > 0) {
|
|
5348
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
5349
|
+
}
|
|
5350
|
+
menu.append(
|
|
5351
|
+
new electron.MenuItem({
|
|
5352
|
+
label: "Add Context to Chat",
|
|
5353
|
+
click: () => sidebarView.webContents.send(
|
|
5354
|
+
Channels.BOOKMARK_ADD_CONTEXT_TO_CHAT,
|
|
5355
|
+
target.bookmarkId
|
|
5356
|
+
)
|
|
5357
|
+
})
|
|
5358
|
+
);
|
|
5359
|
+
}
|
|
5360
|
+
if (params.isEditable) {
|
|
5361
|
+
if (menu.items.length > 0) {
|
|
5362
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
5363
|
+
}
|
|
5364
|
+
menu.append(
|
|
5365
|
+
new electron.MenuItem({
|
|
5366
|
+
role: "undo",
|
|
5367
|
+
enabled: params.editFlags.canUndo
|
|
5368
|
+
})
|
|
5369
|
+
);
|
|
5370
|
+
menu.append(
|
|
5371
|
+
new electron.MenuItem({
|
|
5372
|
+
role: "redo",
|
|
5373
|
+
enabled: params.editFlags.canRedo
|
|
5374
|
+
})
|
|
5375
|
+
);
|
|
5376
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
5377
|
+
menu.append(
|
|
5378
|
+
new electron.MenuItem({
|
|
5379
|
+
role: "cut",
|
|
5380
|
+
enabled: params.editFlags.canCut
|
|
5381
|
+
})
|
|
5382
|
+
);
|
|
5383
|
+
menu.append(
|
|
5384
|
+
new electron.MenuItem({
|
|
5385
|
+
role: "copy",
|
|
5386
|
+
enabled: params.editFlags.canCopy
|
|
5387
|
+
})
|
|
5388
|
+
);
|
|
5389
|
+
menu.append(
|
|
5390
|
+
new electron.MenuItem({
|
|
5391
|
+
role: "paste",
|
|
5392
|
+
enabled: params.editFlags.canPaste
|
|
5393
|
+
})
|
|
5394
|
+
);
|
|
5395
|
+
menu.append(
|
|
5396
|
+
new electron.MenuItem({
|
|
5397
|
+
role: "selectAll",
|
|
5398
|
+
enabled: params.editFlags.canSelectAll
|
|
5399
|
+
})
|
|
5400
|
+
);
|
|
5401
|
+
} else if (params.selectionText?.trim()) {
|
|
5402
|
+
if (menu.items.length > 0) {
|
|
5403
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
5404
|
+
}
|
|
5405
|
+
menu.append(new electron.MenuItem({ role: "copy" }));
|
|
5406
|
+
}
|
|
5407
|
+
if (menu.items.length === 0) return;
|
|
5408
|
+
sidebarView.webContents.focus();
|
|
5409
|
+
menu.popup({ window: mainWindow });
|
|
5410
|
+
}
|
|
5411
|
+
function getWindowIconPath() {
|
|
5412
|
+
const candidates = [
|
|
5413
|
+
path.join(electron.app.getAppPath(), "resources", "vessel-icon.png"),
|
|
5414
|
+
path.join(process.resourcesPath, "vessel-icon.png"),
|
|
5415
|
+
path.join(__dirname, "../../resources/vessel-icon.png")
|
|
5416
|
+
];
|
|
5417
|
+
return candidates.find((candidate) => fs.existsSync(candidate));
|
|
5418
|
+
}
|
|
5419
|
+
function createMainWindow(onTabStateChange) {
|
|
5420
|
+
const mainWindow = new electron.BaseWindow({
|
|
5421
|
+
width: 1280,
|
|
5422
|
+
height: 800,
|
|
5423
|
+
minWidth: 800,
|
|
5424
|
+
minHeight: 600,
|
|
5425
|
+
frame: false,
|
|
5426
|
+
show: false,
|
|
5427
|
+
backgroundColor: "#1a1a1e",
|
|
5428
|
+
icon: getWindowIconPath()
|
|
5429
|
+
});
|
|
5430
|
+
const chromeView = new electron.WebContentsView({
|
|
5431
|
+
webPreferences: {
|
|
5432
|
+
preload: path.join(__dirname, "../preload/index.js"),
|
|
5433
|
+
sandbox: true,
|
|
5434
|
+
contextIsolation: true,
|
|
5435
|
+
nodeIntegration: false
|
|
5436
|
+
}
|
|
5437
|
+
});
|
|
5438
|
+
chromeView.setBackgroundColor("#00000000");
|
|
5439
|
+
mainWindow.contentView.addChildView(chromeView);
|
|
5440
|
+
const sidebarView = new electron.WebContentsView({
|
|
5441
|
+
webPreferences: {
|
|
5442
|
+
preload: path.join(__dirname, "../preload/index.js"),
|
|
5443
|
+
sandbox: true,
|
|
5444
|
+
contextIsolation: true,
|
|
5445
|
+
nodeIntegration: false
|
|
5446
|
+
}
|
|
5447
|
+
});
|
|
5448
|
+
sidebarView.setBackgroundColor("#00000000");
|
|
5449
|
+
sidebarView.webContents.on("context-menu", (event, params) => {
|
|
5450
|
+
event.preventDefault();
|
|
5451
|
+
void showSidebarContextMenu(mainWindow, sidebarView, params);
|
|
5452
|
+
});
|
|
5453
|
+
mainWindow.contentView.addChildView(sidebarView);
|
|
5454
|
+
const devtoolsPanelView = new electron.WebContentsView({
|
|
5455
|
+
webPreferences: {
|
|
5456
|
+
preload: path.join(__dirname, "../preload/index.js"),
|
|
5457
|
+
sandbox: true,
|
|
5458
|
+
contextIsolation: true,
|
|
5459
|
+
nodeIntegration: false
|
|
5460
|
+
}
|
|
5461
|
+
});
|
|
5462
|
+
devtoolsPanelView.setBackgroundColor("#00000000");
|
|
5463
|
+
mainWindow.contentView.addChildView(devtoolsPanelView);
|
|
5464
|
+
enableClipboardShortcuts(chromeView);
|
|
5465
|
+
enableClipboardShortcuts(sidebarView);
|
|
5466
|
+
enableClipboardShortcuts(devtoolsPanelView);
|
|
5467
|
+
const settings2 = loadSettings();
|
|
5468
|
+
const uiState = {
|
|
5469
|
+
sidebarOpen: true,
|
|
5470
|
+
sidebarWidth: settings2.sidebarWidth,
|
|
5471
|
+
focusMode: false,
|
|
5472
|
+
settingsOpen: false,
|
|
5473
|
+
devtoolsPanelOpen: false,
|
|
5474
|
+
devtoolsPanelHeight: DEFAULT_DEVTOOLS_PANEL_HEIGHT
|
|
5475
|
+
};
|
|
5476
|
+
const tabManager = new TabManager(mainWindow, onTabStateChange);
|
|
5477
|
+
const sendToRendererViews = (channel, ...args) => {
|
|
5478
|
+
chromeView.webContents.send(channel, ...args);
|
|
5479
|
+
sidebarView.webContents.send(channel, ...args);
|
|
5480
|
+
};
|
|
5481
|
+
tabManager.onPageLoad((url, wc) => {
|
|
5482
|
+
void capturePageSnapshot(url, wc, sendToRendererViews);
|
|
5483
|
+
});
|
|
5484
|
+
const state2 = {
|
|
5485
|
+
mainWindow,
|
|
5486
|
+
chromeView,
|
|
5487
|
+
sidebarView,
|
|
5488
|
+
devtoolsPanelView,
|
|
5489
|
+
tabManager,
|
|
5490
|
+
uiState
|
|
5491
|
+
};
|
|
5492
|
+
mainWindow.on("resize", () => layoutViews(state2));
|
|
5493
|
+
mainWindow.on("show", () => layoutViews(state2));
|
|
5494
|
+
mainWindow.on("focus", () => layoutViews(state2));
|
|
5495
|
+
layoutViews(state2);
|
|
5496
|
+
return state2;
|
|
5497
|
+
}
|
|
5498
|
+
function layoutViews(state2) {
|
|
5499
|
+
const {
|
|
5500
|
+
mainWindow,
|
|
5501
|
+
chromeView,
|
|
5502
|
+
sidebarView,
|
|
5503
|
+
devtoolsPanelView,
|
|
5504
|
+
tabManager,
|
|
5505
|
+
uiState
|
|
5506
|
+
} = state2;
|
|
5507
|
+
const [width, height] = mainWindow.getContentSize();
|
|
5508
|
+
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
5509
|
+
const sidebarWidth = uiState.sidebarOpen ? uiState.sidebarWidth : 0;
|
|
5510
|
+
const devtoolsHeight = uiState.devtoolsPanelOpen ? uiState.devtoolsPanelHeight : 0;
|
|
5511
|
+
const chromeNeedsFullHeight = uiState.settingsOpen;
|
|
5512
|
+
if (chromeNeedsFullHeight) {
|
|
5513
|
+
chromeView.setBounds({ x: 0, y: 0, width, height });
|
|
5514
|
+
} else {
|
|
5515
|
+
chromeView.setBounds({ x: 0, y: 0, width, height: chromeHeight });
|
|
5516
|
+
}
|
|
5517
|
+
const resizeHandleOverlap = 6;
|
|
5518
|
+
if (uiState.sidebarOpen) {
|
|
5519
|
+
sidebarView.setBounds({
|
|
5520
|
+
x: width - sidebarWidth - resizeHandleOverlap,
|
|
5521
|
+
y: 0,
|
|
5522
|
+
width: sidebarWidth + resizeHandleOverlap,
|
|
5523
|
+
height
|
|
5524
|
+
});
|
|
5525
|
+
} else {
|
|
5526
|
+
sidebarView.setBounds({ x: width, y: 0, width: 0, height: 0 });
|
|
5527
|
+
}
|
|
5528
|
+
const contentWidth = width - sidebarWidth;
|
|
5529
|
+
if (uiState.devtoolsPanelOpen) {
|
|
5530
|
+
devtoolsPanelView.setBounds({
|
|
5531
|
+
x: 0,
|
|
5532
|
+
y: height - devtoolsHeight,
|
|
5533
|
+
width: contentWidth,
|
|
5534
|
+
height: devtoolsHeight
|
|
5535
|
+
});
|
|
5536
|
+
} else {
|
|
5537
|
+
devtoolsPanelView.setBounds({ x: 0, y: height, width: 0, height: 0 });
|
|
5538
|
+
}
|
|
5539
|
+
mainWindow.contentView.removeChildView(chromeView);
|
|
5540
|
+
mainWindow.contentView.addChildView(chromeView);
|
|
5541
|
+
mainWindow.contentView.removeChildView(sidebarView);
|
|
5542
|
+
mainWindow.contentView.addChildView(sidebarView);
|
|
5543
|
+
mainWindow.contentView.removeChildView(devtoolsPanelView);
|
|
5544
|
+
mainWindow.contentView.addChildView(devtoolsPanelView);
|
|
5545
|
+
const activeTab = tabManager.getActiveTab();
|
|
5546
|
+
if (activeTab) {
|
|
5547
|
+
activeTab.view.setBounds({
|
|
5548
|
+
x: 0,
|
|
5549
|
+
y: chromeHeight,
|
|
5550
|
+
width: contentWidth,
|
|
5551
|
+
height: height - chromeHeight - devtoolsHeight
|
|
5552
|
+
});
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
function resizeSidebarViews(state2) {
|
|
5556
|
+
const { mainWindow, sidebarView, devtoolsPanelView, tabManager, uiState } = state2;
|
|
5557
|
+
const [width, height] = mainWindow.getContentSize();
|
|
5558
|
+
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
5559
|
+
const sidebarWidth = uiState.sidebarOpen ? uiState.sidebarWidth : 0;
|
|
5560
|
+
const devtoolsHeight = uiState.devtoolsPanelOpen ? uiState.devtoolsPanelHeight : 0;
|
|
5561
|
+
const resizeHandleOverlap = 6;
|
|
5562
|
+
const contentWidth = width - sidebarWidth;
|
|
5563
|
+
sidebarView.setBounds({
|
|
5564
|
+
x: width - sidebarWidth - resizeHandleOverlap,
|
|
5565
|
+
y: 0,
|
|
5566
|
+
width: sidebarWidth + resizeHandleOverlap,
|
|
5567
|
+
height
|
|
5568
|
+
});
|
|
5569
|
+
if (uiState.devtoolsPanelOpen) {
|
|
5570
|
+
devtoolsPanelView.setBounds({
|
|
5571
|
+
x: 0,
|
|
5572
|
+
y: height - devtoolsHeight,
|
|
5573
|
+
width: contentWidth,
|
|
5574
|
+
height: devtoolsHeight
|
|
5575
|
+
});
|
|
5576
|
+
}
|
|
5577
|
+
const activeTab = tabManager.getActiveTab();
|
|
5578
|
+
if (activeTab) {
|
|
5579
|
+
activeTab.view.setBounds({
|
|
5580
|
+
x: 0,
|
|
5581
|
+
y: chromeHeight,
|
|
5582
|
+
width: contentWidth,
|
|
5583
|
+
height: height - chromeHeight - devtoolsHeight
|
|
5584
|
+
});
|
|
5585
|
+
}
|
|
5586
|
+
}
|
|
5587
|
+
function generateReaderHTML(page) {
|
|
5588
|
+
const escapedTitle = escapeHtml(page.title);
|
|
5589
|
+
const escapedByline = escapeHtml(page.byline);
|
|
5590
|
+
const renderedContent = renderReaderContent(page);
|
|
5591
|
+
return `<!DOCTYPE html>
|
|
5592
|
+
<html lang="en">
|
|
5593
|
+
<head>
|
|
5594
|
+
<meta charset="utf-8">
|
|
5595
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5596
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'">
|
|
5597
|
+
<title>${escapedTitle}</title>
|
|
5598
|
+
<style>
|
|
5599
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
5600
|
+
body {
|
|
5601
|
+
background: #1a1a1e;
|
|
5602
|
+
color: #d4d4d8;
|
|
5603
|
+
font-family: Charter, Georgia, serif;
|
|
5604
|
+
font-size: 19px;
|
|
5605
|
+
line-height: 1.7;
|
|
5606
|
+
padding: 3rem 1.5rem;
|
|
5607
|
+
}
|
|
5608
|
+
.reader-container {
|
|
5609
|
+
max-width: 680px;
|
|
5610
|
+
margin: 0 auto;
|
|
5611
|
+
}
|
|
5612
|
+
h1 {
|
|
5613
|
+
font-size: 1.8em;
|
|
5614
|
+
line-height: 1.3;
|
|
5615
|
+
margin-bottom: 0.5rem;
|
|
5045
5616
|
color: #e4e4e8;
|
|
5046
5617
|
}
|
|
5047
5618
|
.byline {
|
|
@@ -5123,7 +5694,7 @@ function onRuntimeHealthChange(listener) {
|
|
|
5123
5694
|
};
|
|
5124
5695
|
}
|
|
5125
5696
|
function getMcpStatus() {
|
|
5126
|
-
return state$
|
|
5697
|
+
return state$2.mcp.status;
|
|
5127
5698
|
}
|
|
5128
5699
|
function emitRuntimeHealthChange() {
|
|
5129
5700
|
const snapshot = getRuntimeHealth();
|
|
@@ -5131,7 +5702,7 @@ function emitRuntimeHealthChange() {
|
|
|
5131
5702
|
listener(snapshot);
|
|
5132
5703
|
}
|
|
5133
5704
|
}
|
|
5134
|
-
const state$
|
|
5705
|
+
const state$2 = {
|
|
5135
5706
|
userDataPath: "",
|
|
5136
5707
|
settingsPath: "",
|
|
5137
5708
|
startupIssues: [],
|
|
@@ -5144,248 +5715,46 @@ const state$1 = {
|
|
|
5144
5715
|
}
|
|
5145
5716
|
};
|
|
5146
5717
|
function initializeRuntimeHealth(paths) {
|
|
5147
|
-
state$
|
|
5148
|
-
state$
|
|
5149
|
-
state$
|
|
5150
|
-
state$
|
|
5151
|
-
state$
|
|
5152
|
-
state$
|
|
5153
|
-
state$
|
|
5154
|
-
emitRuntimeHealthChange();
|
|
5155
|
-
}
|
|
5156
|
-
function setStartupIssues(issues) {
|
|
5157
|
-
state$1.startupIssues = issues.map((issue) => ({ ...issue }));
|
|
5158
|
-
emitRuntimeHealthChange();
|
|
5159
|
-
}
|
|
5160
|
-
function getRuntimeHealth() {
|
|
5161
|
-
return {
|
|
5162
|
-
userDataPath: state$1.userDataPath,
|
|
5163
|
-
settingsPath: state$1.settingsPath,
|
|
5164
|
-
startupIssues: state$1.startupIssues.map((issue) => ({ ...issue })),
|
|
5165
|
-
mcp: { ...state$1.mcp }
|
|
5166
|
-
};
|
|
5167
|
-
}
|
|
5168
|
-
function setMcpHealth(update) {
|
|
5169
|
-
if (typeof update.configuredPort === "number") {
|
|
5170
|
-
state$1.mcp.configuredPort = update.configuredPort;
|
|
5171
|
-
}
|
|
5172
|
-
if ("activePort" in update) {
|
|
5173
|
-
state$1.mcp.activePort = update.activePort ?? null;
|
|
5174
|
-
}
|
|
5175
|
-
if ("endpoint" in update) {
|
|
5176
|
-
state$1.mcp.endpoint = update.endpoint ?? null;
|
|
5177
|
-
}
|
|
5178
|
-
const prevStatus = state$1.mcp.status;
|
|
5179
|
-
state$1.mcp.status = update.status;
|
|
5180
|
-
state$1.mcp.message = update.message;
|
|
5181
|
-
if (prevStatus !== state$1.mcp.status) {
|
|
5182
|
-
for (const listener of mcpStatusChangeListeners) {
|
|
5183
|
-
listener(state$1.mcp.status);
|
|
5184
|
-
}
|
|
5185
|
-
}
|
|
5186
|
-
emitRuntimeHealthChange();
|
|
5187
|
-
}
|
|
5188
|
-
const VAULT_FILENAME = "vessel-vault.enc";
|
|
5189
|
-
const KEY_FILENAME = "vessel-vault.key";
|
|
5190
|
-
const ALGORITHM = "aes-256-gcm";
|
|
5191
|
-
const IV_LENGTH = 12;
|
|
5192
|
-
const AUTH_TAG_LENGTH = 16;
|
|
5193
|
-
let cachedEntries = null;
|
|
5194
|
-
function getVaultDir() {
|
|
5195
|
-
return electron.app.getPath("userData");
|
|
5196
|
-
}
|
|
5197
|
-
function getVaultPath() {
|
|
5198
|
-
return path$1.join(getVaultDir(), VAULT_FILENAME);
|
|
5199
|
-
}
|
|
5200
|
-
function getKeyPath() {
|
|
5201
|
-
return path$1.join(getVaultDir(), KEY_FILENAME);
|
|
5202
|
-
}
|
|
5203
|
-
function assertVaultSecretStorageAvailable() {
|
|
5204
|
-
if (!electron.safeStorage.isEncryptionAvailable()) {
|
|
5205
|
-
throw new Error(
|
|
5206
|
-
"Agent Credential Vault requires OS-backed secret storage. Enable Keychain, DPAPI, or libsecret support and restart Vessel."
|
|
5207
|
-
);
|
|
5208
|
-
}
|
|
5209
|
-
}
|
|
5210
|
-
function getOrCreateEncryptionKey() {
|
|
5211
|
-
assertVaultSecretStorageAvailable();
|
|
5212
|
-
const keyPath = getKeyPath();
|
|
5213
|
-
if (fs$1.existsSync(keyPath)) {
|
|
5214
|
-
const encryptedKey = fs$1.readFileSync(keyPath);
|
|
5215
|
-
return Buffer.from(electron.safeStorage.decryptString(encryptedKey), "utf-8");
|
|
5216
|
-
}
|
|
5217
|
-
const key = crypto$2.randomBytes(32);
|
|
5218
|
-
fs$1.mkdirSync(path$1.dirname(keyPath), { recursive: true });
|
|
5219
|
-
const encrypted = electron.safeStorage.encryptString(key.toString("utf-8"));
|
|
5220
|
-
fs$1.writeFileSync(keyPath, encrypted, { mode: 384 });
|
|
5221
|
-
return key;
|
|
5222
|
-
}
|
|
5223
|
-
function encrypt(plaintext) {
|
|
5224
|
-
const key = getOrCreateEncryptionKey();
|
|
5225
|
-
const iv = crypto$2.randomBytes(IV_LENGTH);
|
|
5226
|
-
const cipher = crypto$2.createCipheriv(ALGORITHM, key, iv, {
|
|
5227
|
-
authTagLength: AUTH_TAG_LENGTH
|
|
5228
|
-
});
|
|
5229
|
-
const encrypted = Buffer.concat([
|
|
5230
|
-
cipher.update(plaintext, "utf-8"),
|
|
5231
|
-
cipher.final()
|
|
5232
|
-
]);
|
|
5233
|
-
const authTag = cipher.getAuthTag();
|
|
5234
|
-
return Buffer.concat([iv, authTag, encrypted]);
|
|
5235
|
-
}
|
|
5236
|
-
function decrypt(data) {
|
|
5237
|
-
const key = getOrCreateEncryptionKey();
|
|
5238
|
-
const iv = data.subarray(0, IV_LENGTH);
|
|
5239
|
-
const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
5240
|
-
const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
5241
|
-
const decipher = crypto$2.createDecipheriv(ALGORITHM, key, iv, {
|
|
5242
|
-
authTagLength: AUTH_TAG_LENGTH
|
|
5243
|
-
});
|
|
5244
|
-
decipher.setAuthTag(authTag);
|
|
5245
|
-
return decipher.update(ciphertext, void 0, "utf-8") + decipher.final("utf-8");
|
|
5246
|
-
}
|
|
5247
|
-
function loadVault() {
|
|
5248
|
-
if (cachedEntries) return cachedEntries;
|
|
5249
|
-
const vaultPath = getVaultPath();
|
|
5250
|
-
if (!fs$1.existsSync(vaultPath)) {
|
|
5251
|
-
cachedEntries = [];
|
|
5252
|
-
return cachedEntries;
|
|
5253
|
-
}
|
|
5254
|
-
try {
|
|
5255
|
-
const raw = fs$1.readFileSync(vaultPath);
|
|
5256
|
-
const json = decrypt(raw);
|
|
5257
|
-
cachedEntries = JSON.parse(json);
|
|
5258
|
-
return cachedEntries;
|
|
5259
|
-
} catch (err) {
|
|
5260
|
-
console.error("[Vessel Vault] Failed to load vault:", err);
|
|
5261
|
-
throw new Error(
|
|
5262
|
-
"Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
|
|
5263
|
-
);
|
|
5264
|
-
}
|
|
5265
|
-
}
|
|
5266
|
-
function saveVault(entries) {
|
|
5267
|
-
const json = JSON.stringify(entries, null, 2);
|
|
5268
|
-
const encrypted = encrypt(json);
|
|
5269
|
-
const vaultPath = getVaultPath();
|
|
5270
|
-
fs$1.mkdirSync(path$1.dirname(vaultPath), { recursive: true });
|
|
5271
|
-
fs$1.writeFileSync(vaultPath, encrypted);
|
|
5272
|
-
fs$1.chmodSync(vaultPath, 384);
|
|
5273
|
-
cachedEntries = entries;
|
|
5274
|
-
}
|
|
5275
|
-
function domainMatches(pattern, hostname) {
|
|
5276
|
-
const p = pattern.toLowerCase().trim();
|
|
5277
|
-
const h = hostname.toLowerCase().trim();
|
|
5278
|
-
if (p === h) return true;
|
|
5279
|
-
if (p.startsWith("*.")) {
|
|
5280
|
-
const suffix = p.slice(2);
|
|
5281
|
-
return h === suffix || h.endsWith("." + suffix);
|
|
5282
|
-
}
|
|
5283
|
-
return false;
|
|
5284
|
-
}
|
|
5285
|
-
function listEntries() {
|
|
5286
|
-
return loadVault().map(({ password, totpSecret, ...rest }) => rest);
|
|
5287
|
-
}
|
|
5288
|
-
function findEntriesForDomain(url) {
|
|
5289
|
-
let hostname;
|
|
5290
|
-
try {
|
|
5291
|
-
hostname = new URL(url).hostname;
|
|
5292
|
-
} catch {
|
|
5293
|
-
return [];
|
|
5294
|
-
}
|
|
5295
|
-
return loadVault().filter((e) => domainMatches(e.domainPattern, hostname)).map(({ password, totpSecret, ...rest }) => rest);
|
|
5296
|
-
}
|
|
5297
|
-
function addEntry(entry) {
|
|
5298
|
-
const entries = loadVault();
|
|
5299
|
-
const newEntry = {
|
|
5300
|
-
...entry,
|
|
5301
|
-
id: crypto$2.randomUUID(),
|
|
5302
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5303
|
-
useCount: 0
|
|
5304
|
-
};
|
|
5305
|
-
entries.push(newEntry);
|
|
5306
|
-
saveVault(entries);
|
|
5307
|
-
return newEntry;
|
|
5308
|
-
}
|
|
5309
|
-
function updateEntry(id, updates) {
|
|
5310
|
-
const entries = loadVault();
|
|
5311
|
-
const idx = entries.findIndex((e) => e.id === id);
|
|
5312
|
-
if (idx === -1) return null;
|
|
5313
|
-
entries[idx] = { ...entries[idx], ...updates };
|
|
5314
|
-
saveVault(entries);
|
|
5315
|
-
return entries[idx];
|
|
5316
|
-
}
|
|
5317
|
-
function removeEntry(id) {
|
|
5318
|
-
const entries = loadVault();
|
|
5319
|
-
const idx = entries.findIndex((e) => e.id === id);
|
|
5320
|
-
if (idx === -1) return false;
|
|
5321
|
-
entries.splice(idx, 1);
|
|
5322
|
-
saveVault(entries);
|
|
5323
|
-
return true;
|
|
5324
|
-
}
|
|
5325
|
-
function recordUsage(id) {
|
|
5326
|
-
const entries = loadVault();
|
|
5327
|
-
const entry = entries.find((e) => e.id === id);
|
|
5328
|
-
if (!entry) return;
|
|
5329
|
-
entry.lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5330
|
-
entry.useCount += 1;
|
|
5331
|
-
saveVault(entries);
|
|
5718
|
+
state$2.userDataPath = paths.userDataPath;
|
|
5719
|
+
state$2.settingsPath = paths.settingsPath;
|
|
5720
|
+
state$2.mcp.configuredPort = paths.configuredPort;
|
|
5721
|
+
state$2.mcp.activePort = null;
|
|
5722
|
+
state$2.mcp.endpoint = null;
|
|
5723
|
+
state$2.mcp.status = "stopped";
|
|
5724
|
+
state$2.mcp.message = "MCP server has not started yet.";
|
|
5725
|
+
emitRuntimeHealthChange();
|
|
5332
5726
|
}
|
|
5333
|
-
function
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
return { username: entry.username, password: entry.password };
|
|
5727
|
+
function setStartupIssues(issues) {
|
|
5728
|
+
state$2.startupIssues = issues.map((issue) => ({ ...issue }));
|
|
5729
|
+
emitRuntimeHealthChange();
|
|
5337
5730
|
}
|
|
5338
|
-
function
|
|
5339
|
-
|
|
5340
|
-
|
|
5731
|
+
function getRuntimeHealth() {
|
|
5732
|
+
return {
|
|
5733
|
+
userDataPath: state$2.userDataPath,
|
|
5734
|
+
settingsPath: state$2.settingsPath,
|
|
5735
|
+
startupIssues: state$2.startupIssues.map((issue) => ({ ...issue })),
|
|
5736
|
+
mcp: { ...state$2.mcp }
|
|
5737
|
+
};
|
|
5341
5738
|
}
|
|
5342
|
-
function
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
5346
|
-
const cleanSecret = secret.replace(/[\s=-]/g, "").toUpperCase();
|
|
5347
|
-
let bits = "";
|
|
5348
|
-
for (const ch of cleanSecret) {
|
|
5349
|
-
const val = base32Chars.indexOf(ch);
|
|
5350
|
-
if (val === -1) continue;
|
|
5351
|
-
bits += val.toString(2).padStart(5, "0");
|
|
5739
|
+
function setMcpHealth(update) {
|
|
5740
|
+
if (typeof update.configuredPort === "number") {
|
|
5741
|
+
state$2.mcp.configuredPort = update.configuredPort;
|
|
5352
5742
|
}
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
keyBytes[i] = parseInt(bits.slice(i * 8, i * 8 + 8), 2);
|
|
5743
|
+
if ("activePort" in update) {
|
|
5744
|
+
state$2.mcp.activePort = update.activePort ?? null;
|
|
5356
5745
|
}
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
counterBuf.writeUInt32BE(counter & 4294967295, 4);
|
|
5360
|
-
const hmac = crypto$2.createHmac("sha1", keyBytes).update(counterBuf).digest();
|
|
5361
|
-
const offset = hmac[hmac.length - 1] & 15;
|
|
5362
|
-
const code = (hmac[offset] & 127) << 24 | (hmac[offset + 1] & 255) << 16 | (hmac[offset + 2] & 255) << 8 | hmac[offset + 3] & 255;
|
|
5363
|
-
return (code % 1e6).toString().padStart(6, "0");
|
|
5364
|
-
}
|
|
5365
|
-
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
5366
|
-
const MAX_ENTRIES = 1e3;
|
|
5367
|
-
function getAuditPath() {
|
|
5368
|
-
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
5369
|
-
}
|
|
5370
|
-
function appendAuditEntry(entry) {
|
|
5371
|
-
try {
|
|
5372
|
-
const auditPath = getAuditPath();
|
|
5373
|
-
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
5374
|
-
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
|
|
5375
|
-
} catch (err) {
|
|
5376
|
-
console.error("[Vessel Vault] Failed to write audit log:", err);
|
|
5746
|
+
if ("endpoint" in update) {
|
|
5747
|
+
state$2.mcp.endpoint = update.endpoint ?? null;
|
|
5377
5748
|
}
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
} catch (err) {
|
|
5386
|
-
console.error("[Vessel Vault] Failed to read audit log:", err);
|
|
5387
|
-
return [];
|
|
5749
|
+
const prevStatus = state$2.mcp.status;
|
|
5750
|
+
state$2.mcp.status = update.status;
|
|
5751
|
+
state$2.mcp.message = update.message;
|
|
5752
|
+
if (prevStatus !== state$2.mcp.status) {
|
|
5753
|
+
for (const listener of mcpStatusChangeListeners) {
|
|
5754
|
+
listener(state$2.mcp.status);
|
|
5755
|
+
}
|
|
5388
5756
|
}
|
|
5757
|
+
emitRuntimeHealthChange();
|
|
5389
5758
|
}
|
|
5390
5759
|
function isRichToolResult(value) {
|
|
5391
5760
|
return typeof value === "object" && value !== null && "__richResult" in value && value.__richResult === true;
|
|
@@ -9993,11 +10362,9 @@ function getBookmarkSearchMatch(args) {
|
|
|
9993
10362
|
}
|
|
9994
10363
|
const UNSORTED_ID = "unsorted";
|
|
9995
10364
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
9996
|
-
const SAVE_DEBOUNCE_MS = 250;
|
|
9997
|
-
let state = null;
|
|
10365
|
+
const SAVE_DEBOUNCE_MS$1 = 250;
|
|
10366
|
+
let state$1 = null;
|
|
9998
10367
|
const listeners = /* @__PURE__ */ new Set();
|
|
9999
|
-
let saveTimer = null;
|
|
10000
|
-
let saveDirty = false;
|
|
10001
10368
|
function cloneState(current) {
|
|
10002
10369
|
return {
|
|
10003
10370
|
folders: current.folders.map((folder) => ({ ...folder })),
|
|
@@ -10007,53 +10374,39 @@ function cloneState(current) {
|
|
|
10007
10374
|
function getBookmarksPath() {
|
|
10008
10375
|
return path.join(electron.app.getPath("userData"), "vessel-bookmarks.json");
|
|
10009
10376
|
}
|
|
10010
|
-
function load() {
|
|
10011
|
-
if (state) return state;
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
function persistNow() {
|
|
10025
|
-
saveDirty = false;
|
|
10026
|
-
if (saveTimer) {
|
|
10027
|
-
clearTimeout(saveTimer);
|
|
10028
|
-
saveTimer = null;
|
|
10029
|
-
}
|
|
10030
|
-
return fs.promises.mkdir(path.dirname(getBookmarksPath()), { recursive: true }).then(
|
|
10031
|
-
() => fs.promises.writeFile(
|
|
10032
|
-
getBookmarksPath(),
|
|
10033
|
-
JSON.stringify(state, null, 2),
|
|
10034
|
-
"utf-8"
|
|
10035
|
-
)
|
|
10036
|
-
).catch((err) => console.error("[Vessel] Failed to save bookmarks:", err));
|
|
10377
|
+
function load$1() {
|
|
10378
|
+
if (state$1) return state$1;
|
|
10379
|
+
state$1 = loadJsonFile({
|
|
10380
|
+
filePath: getBookmarksPath(),
|
|
10381
|
+
fallback: { folders: [], bookmarks: [] },
|
|
10382
|
+
parse: (raw) => {
|
|
10383
|
+
const parsed = raw;
|
|
10384
|
+
return {
|
|
10385
|
+
folders: Array.isArray(parsed.folders) ? parsed.folders : [],
|
|
10386
|
+
bookmarks: Array.isArray(parsed.bookmarks) ? parsed.bookmarks : []
|
|
10387
|
+
};
|
|
10388
|
+
}
|
|
10389
|
+
});
|
|
10390
|
+
return state$1;
|
|
10037
10391
|
}
|
|
10392
|
+
const persistence$1 = createDebouncedJsonPersistence({
|
|
10393
|
+
debounceMs: SAVE_DEBOUNCE_MS$1,
|
|
10394
|
+
filePath: getBookmarksPath(),
|
|
10395
|
+
getValue: () => state$1,
|
|
10396
|
+
logLabel: "bookmarks"
|
|
10397
|
+
});
|
|
10038
10398
|
function save() {
|
|
10039
|
-
|
|
10040
|
-
if (saveTimer) return;
|
|
10041
|
-
saveTimer = setTimeout(() => {
|
|
10042
|
-
saveTimer = null;
|
|
10043
|
-
if (saveDirty) {
|
|
10044
|
-
void persistNow();
|
|
10045
|
-
}
|
|
10046
|
-
}, SAVE_DEBOUNCE_MS);
|
|
10399
|
+
persistence$1.schedule();
|
|
10047
10400
|
}
|
|
10048
10401
|
function emit() {
|
|
10049
|
-
if (!state) return;
|
|
10050
|
-
const snapshot = cloneState(state);
|
|
10402
|
+
if (!state$1) return;
|
|
10403
|
+
const snapshot = cloneState(state$1);
|
|
10051
10404
|
for (const listener of listeners) {
|
|
10052
10405
|
listener(snapshot);
|
|
10053
10406
|
}
|
|
10054
10407
|
}
|
|
10055
10408
|
function getState() {
|
|
10056
|
-
return cloneState(load());
|
|
10409
|
+
return cloneState(load$1());
|
|
10057
10410
|
}
|
|
10058
10411
|
function subscribe(listener) {
|
|
10059
10412
|
listeners.add(listener);
|
|
@@ -10062,51 +10415,51 @@ function subscribe(listener) {
|
|
|
10062
10415
|
};
|
|
10063
10416
|
}
|
|
10064
10417
|
function clearAll() {
|
|
10065
|
-
state = { folders: [], bookmarks: [] };
|
|
10418
|
+
state$1 = { folders: [], bookmarks: [] };
|
|
10066
10419
|
save();
|
|
10067
10420
|
emit();
|
|
10068
10421
|
}
|
|
10069
10422
|
function getBookmark(id) {
|
|
10070
|
-
load();
|
|
10071
|
-
const bookmark = state.bookmarks.find((item) => item.id === id);
|
|
10423
|
+
load$1();
|
|
10424
|
+
const bookmark = state$1.bookmarks.find((item) => item.id === id);
|
|
10072
10425
|
return bookmark ? { ...bookmark } : null;
|
|
10073
10426
|
}
|
|
10074
10427
|
function getBookmarkByUrl(url) {
|
|
10075
|
-
load();
|
|
10428
|
+
load$1();
|
|
10076
10429
|
const normalized = url.trim();
|
|
10077
10430
|
if (!normalized) return null;
|
|
10078
|
-
const bookmark = [...state.bookmarks].reverse().find((item) => item.url === normalized);
|
|
10431
|
+
const bookmark = [...state$1.bookmarks].reverse().find((item) => item.url === normalized);
|
|
10079
10432
|
return bookmark ? { ...bookmark } : null;
|
|
10080
10433
|
}
|
|
10081
10434
|
function getBookmarkByUrlInFolder(url, folderId) {
|
|
10082
|
-
load();
|
|
10435
|
+
load$1();
|
|
10083
10436
|
const normalizedUrl = url.trim();
|
|
10084
10437
|
if (!normalizedUrl) return null;
|
|
10085
|
-
const targetFolderId = folderId && folderId !== UNSORTED_ID ? state.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
10086
|
-
const bookmark = [...state.bookmarks].reverse().find(
|
|
10438
|
+
const targetFolderId = folderId && folderId !== UNSORTED_ID ? state$1.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
10439
|
+
const bookmark = [...state$1.bookmarks].reverse().find(
|
|
10087
10440
|
(item) => item.url === normalizedUrl && item.folderId === targetFolderId
|
|
10088
10441
|
);
|
|
10089
10442
|
return bookmark ? { ...bookmark } : null;
|
|
10090
10443
|
}
|
|
10091
10444
|
function getFolder(id) {
|
|
10092
|
-
load();
|
|
10445
|
+
load$1();
|
|
10093
10446
|
if (!id || id === UNSORTED_ID) return null;
|
|
10094
|
-
const folder = state.folders.find((item) => item.id === id);
|
|
10447
|
+
const folder = state$1.folders.find((item) => item.id === id);
|
|
10095
10448
|
return folder ? { ...folder } : null;
|
|
10096
10449
|
}
|
|
10097
10450
|
function findFolderByName(name) {
|
|
10098
|
-
load();
|
|
10451
|
+
load$1();
|
|
10099
10452
|
const normalized = name.trim().toLowerCase();
|
|
10100
10453
|
if (!normalized || normalized === "unsorted") return null;
|
|
10101
|
-
const folder = state.folders.find(
|
|
10454
|
+
const folder = state$1.folders.find(
|
|
10102
10455
|
(item) => item.name.trim().toLowerCase() === normalized
|
|
10103
10456
|
);
|
|
10104
10457
|
return folder ? { ...folder } : null;
|
|
10105
10458
|
}
|
|
10106
10459
|
function listFolderOverviews() {
|
|
10107
|
-
load();
|
|
10460
|
+
load$1();
|
|
10108
10461
|
const counts = /* @__PURE__ */ new Map();
|
|
10109
|
-
for (const bookmark of state.bookmarks) {
|
|
10462
|
+
for (const bookmark of state$1.bookmarks) {
|
|
10110
10463
|
counts.set(bookmark.folderId, (counts.get(bookmark.folderId) ?? 0) + 1);
|
|
10111
10464
|
}
|
|
10112
10465
|
return [
|
|
@@ -10115,7 +10468,7 @@ function listFolderOverviews() {
|
|
|
10115
10468
|
name: "Unsorted",
|
|
10116
10469
|
count: counts.get(UNSORTED_ID) ?? 0
|
|
10117
10470
|
},
|
|
10118
|
-
...state.folders.map((folder) => ({
|
|
10471
|
+
...state$1.folders.map((folder) => ({
|
|
10119
10472
|
id: folder.id,
|
|
10120
10473
|
name: folder.name,
|
|
10121
10474
|
summary: folder.summary,
|
|
@@ -10124,10 +10477,10 @@ function listFolderOverviews() {
|
|
|
10124
10477
|
];
|
|
10125
10478
|
}
|
|
10126
10479
|
function searchBookmarks(query) {
|
|
10127
|
-
load();
|
|
10480
|
+
load$1();
|
|
10128
10481
|
if (!query.trim()) return [];
|
|
10129
|
-
return state.bookmarks.map((bookmark) => {
|
|
10130
|
-
const folder = state.folders.find(
|
|
10482
|
+
return state$1.bookmarks.map((bookmark) => {
|
|
10483
|
+
const folder = state$1.folders.find(
|
|
10131
10484
|
(item) => item.id === bookmark.folderId
|
|
10132
10485
|
);
|
|
10133
10486
|
const { matchedFields, score } = getBookmarkSearchMatch({
|
|
@@ -10154,7 +10507,7 @@ function searchBookmarks(query) {
|
|
|
10154
10507
|
);
|
|
10155
10508
|
}
|
|
10156
10509
|
function createFolderWithSummary(name, summary) {
|
|
10157
|
-
load();
|
|
10510
|
+
load$1();
|
|
10158
10511
|
const trimmed = name.trim();
|
|
10159
10512
|
if (!trimmed) throw new Error("Folder name cannot be empty");
|
|
10160
10513
|
const folder = {
|
|
@@ -10163,7 +10516,7 @@ function createFolderWithSummary(name, summary) {
|
|
|
10163
10516
|
summary: summary?.trim() || void 0,
|
|
10164
10517
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10165
10518
|
};
|
|
10166
|
-
state.folders.push(folder);
|
|
10519
|
+
state$1.folders.push(folder);
|
|
10167
10520
|
save();
|
|
10168
10521
|
emit();
|
|
10169
10522
|
return folder;
|
|
@@ -10188,13 +10541,13 @@ function saveBookmark(url, title, folderId, note) {
|
|
|
10188
10541
|
return result.bookmark;
|
|
10189
10542
|
}
|
|
10190
10543
|
function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
10191
|
-
load();
|
|
10544
|
+
load$1();
|
|
10192
10545
|
const normalizedUrl = url.trim();
|
|
10193
10546
|
if (!normalizedUrl) {
|
|
10194
10547
|
throw new Error("Bookmark URL cannot be empty");
|
|
10195
10548
|
}
|
|
10196
10549
|
const normalizedTitle = title.trim() || normalizedUrl;
|
|
10197
|
-
const targetId = folderId && folderId !== UNSORTED_ID ? state.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
10550
|
+
const targetId = folderId && folderId !== UNSORTED_ID ? state$1.folders.find((f) => f.id === folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
10198
10551
|
const duplicatePolicy = options?.onDuplicate ?? "ask";
|
|
10199
10552
|
const existing = getBookmarkByUrlInFolder(normalizedUrl, targetId);
|
|
10200
10553
|
if (existing) {
|
|
@@ -10205,7 +10558,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
10205
10558
|
};
|
|
10206
10559
|
}
|
|
10207
10560
|
if (duplicatePolicy === "update") {
|
|
10208
|
-
const bookmark2 = state.bookmarks.find((item) => item.id === existing.id);
|
|
10561
|
+
const bookmark2 = state$1.bookmarks.find((item) => item.id === existing.id);
|
|
10209
10562
|
if (!bookmark2) {
|
|
10210
10563
|
return {
|
|
10211
10564
|
status: "conflict",
|
|
@@ -10233,7 +10586,7 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
10233
10586
|
folderId: targetId,
|
|
10234
10587
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10235
10588
|
};
|
|
10236
|
-
state.bookmarks.push(bookmark);
|
|
10589
|
+
state$1.bookmarks.push(bookmark);
|
|
10237
10590
|
save();
|
|
10238
10591
|
emit();
|
|
10239
10592
|
return {
|
|
@@ -10242,10 +10595,10 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
|
|
|
10242
10595
|
};
|
|
10243
10596
|
}
|
|
10244
10597
|
function removeBookmark(id) {
|
|
10245
|
-
load();
|
|
10246
|
-
const before = state.bookmarks.length;
|
|
10247
|
-
state.bookmarks = state.bookmarks.filter((b) => b.id !== id);
|
|
10248
|
-
if (state.bookmarks.length !== before) {
|
|
10598
|
+
load$1();
|
|
10599
|
+
const before = state$1.bookmarks.length;
|
|
10600
|
+
state$1.bookmarks = state$1.bookmarks.filter((b) => b.id !== id);
|
|
10601
|
+
if (state$1.bookmarks.length !== before) {
|
|
10249
10602
|
save();
|
|
10250
10603
|
emit();
|
|
10251
10604
|
return true;
|
|
@@ -10253,8 +10606,8 @@ function removeBookmark(id) {
|
|
|
10253
10606
|
return false;
|
|
10254
10607
|
}
|
|
10255
10608
|
function updateBookmark(id, updates) {
|
|
10256
|
-
load();
|
|
10257
|
-
const bookmark = state.bookmarks.find((item) => item.id === id);
|
|
10609
|
+
load$1();
|
|
10610
|
+
const bookmark = state$1.bookmarks.find((item) => item.id === id);
|
|
10258
10611
|
if (!bookmark) return null;
|
|
10259
10612
|
if (typeof updates.title === "string") {
|
|
10260
10613
|
const trimmed = updates.title.trim();
|
|
@@ -10265,31 +10618,31 @@ function updateBookmark(id, updates) {
|
|
|
10265
10618
|
bookmark.note = trimmed || void 0;
|
|
10266
10619
|
}
|
|
10267
10620
|
if (typeof updates.folderId === "string") {
|
|
10268
|
-
bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
10621
|
+
bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$1.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
|
|
10269
10622
|
}
|
|
10270
10623
|
save();
|
|
10271
10624
|
emit();
|
|
10272
10625
|
return { ...bookmark };
|
|
10273
10626
|
}
|
|
10274
10627
|
function removeFolder(id, deleteContents = false) {
|
|
10275
|
-
load();
|
|
10276
|
-
const exists = state.folders.some((f) => f.id === id);
|
|
10628
|
+
load$1();
|
|
10629
|
+
const exists = state$1.folders.some((f) => f.id === id);
|
|
10277
10630
|
if (!exists) return false;
|
|
10278
10631
|
if (deleteContents) {
|
|
10279
|
-
state.bookmarks = state.bookmarks.filter((b) => b.folderId !== id);
|
|
10632
|
+
state$1.bookmarks = state$1.bookmarks.filter((b) => b.folderId !== id);
|
|
10280
10633
|
} else {
|
|
10281
|
-
state.bookmarks = state.bookmarks.map(
|
|
10634
|
+
state$1.bookmarks = state$1.bookmarks.map(
|
|
10282
10635
|
(b) => b.folderId === id ? { ...b, folderId: UNSORTED_ID } : b
|
|
10283
10636
|
);
|
|
10284
10637
|
}
|
|
10285
|
-
state.folders = state.folders.filter((f) => f.id !== id);
|
|
10638
|
+
state$1.folders = state$1.folders.filter((f) => f.id !== id);
|
|
10286
10639
|
save();
|
|
10287
10640
|
emit();
|
|
10288
10641
|
return true;
|
|
10289
10642
|
}
|
|
10290
10643
|
function renameFolder(id, newName, summary) {
|
|
10291
|
-
load();
|
|
10292
|
-
const folder = state.folders.find((f) => f.id === id);
|
|
10644
|
+
load$1();
|
|
10645
|
+
const folder = state$1.folders.find((f) => f.id === id);
|
|
10293
10646
|
if (!folder) return null;
|
|
10294
10647
|
const trimmed = newName.trim();
|
|
10295
10648
|
if (!trimmed) return null;
|
|
@@ -10299,8 +10652,8 @@ function renameFolder(id, newName, summary) {
|
|
|
10299
10652
|
emit();
|
|
10300
10653
|
return { ...folder };
|
|
10301
10654
|
}
|
|
10302
|
-
function flushPersist() {
|
|
10303
|
-
return
|
|
10655
|
+
function flushPersist$1() {
|
|
10656
|
+
return persistence$1.flush();
|
|
10304
10657
|
}
|
|
10305
10658
|
function normalizeText(text) {
|
|
10306
10659
|
return text?.trim() ?? "";
|
|
@@ -10911,7 +11264,7 @@ function isInvalidTextTargetQuery(rawQuery) {
|
|
|
10911
11264
|
return false;
|
|
10912
11265
|
}
|
|
10913
11266
|
function resolveTextTargetInDocument(doc, rawQuery, mode) {
|
|
10914
|
-
function
|
|
11267
|
+
function normalize2(value) {
|
|
10915
11268
|
return String(value || "").toLowerCase().replace(/\s+/g, " ").trim();
|
|
10916
11269
|
}
|
|
10917
11270
|
function text(value) {
|
|
@@ -11009,7 +11362,7 @@ function resolveTextTargetInDocument(doc, rawQuery, mode) {
|
|
|
11009
11362
|
return [ariaLabel, title, ownText].filter(Boolean).join(" ");
|
|
11010
11363
|
}
|
|
11011
11364
|
function scoreText(query2, candidate) {
|
|
11012
|
-
const normalizedCandidate =
|
|
11365
|
+
const normalizedCandidate = normalize2(candidate);
|
|
11013
11366
|
if (!normalizedCandidate) return -1;
|
|
11014
11367
|
if (normalizedCandidate === query2) return 180;
|
|
11015
11368
|
if (normalizedCandidate.startsWith(query2)) return 150;
|
|
@@ -11025,7 +11378,7 @@ function resolveTextTargetInDocument(doc, rawQuery, mode) {
|
|
|
11025
11378
|
function interactiveBonus(el) {
|
|
11026
11379
|
const htmlEl = el;
|
|
11027
11380
|
const tag = el.tagName.toLowerCase();
|
|
11028
|
-
const label =
|
|
11381
|
+
const label = normalize2(labelFor(el));
|
|
11029
11382
|
let score = 0;
|
|
11030
11383
|
if (tag === "button") score += 40;
|
|
11031
11384
|
if (tag === "a") score += 35;
|
|
@@ -11063,7 +11416,7 @@ function resolveTextTargetInDocument(doc, rawQuery, mode) {
|
|
|
11063
11416
|
return best;
|
|
11064
11417
|
}
|
|
11065
11418
|
if (isInvalidTextTargetQuery(rawQuery)) return null;
|
|
11066
|
-
const query =
|
|
11419
|
+
const query = normalize2(rawQuery);
|
|
11067
11420
|
if (!query) return null;
|
|
11068
11421
|
let bestInteractive = null;
|
|
11069
11422
|
const interactiveSelector = "a[href], button, [role='button'], input[type='submit'], input[type='button'], input[type='radio'], input[type='checkbox'], select, textarea";
|
|
@@ -13554,7 +13907,21 @@ async function setElementValue$1(wc, selector, value) {
|
|
|
13554
13907
|
(function() {
|
|
13555
13908
|
var el = window.__vessel?.resolveShadowSelector?.(${JSON.stringify(selector)});
|
|
13556
13909
|
if (!el) return "Error[stale-index]: Shadow DOM element not found — call read_page to refresh.";
|
|
13557
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) return "Error[not-input]: Element is not a
|
|
13910
|
+
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) return "Error[not-input]: Element is not a fillable input";
|
|
13911
|
+
if (el.disabled || el.getAttribute("aria-disabled") === "true") return "Error[disabled]: Input is disabled";
|
|
13912
|
+
if (el instanceof HTMLSelectElement) {
|
|
13913
|
+
var requested = ${JSON.stringify(value)}.trim().toLowerCase();
|
|
13914
|
+
var option = Array.from(el.options).find(function(item) {
|
|
13915
|
+
return item.value.trim().toLowerCase() === requested ||
|
|
13916
|
+
(item.textContent || "").trim().toLowerCase() === requested;
|
|
13917
|
+
});
|
|
13918
|
+
if (!option) return "Error[option-not-found]: Option not found";
|
|
13919
|
+
el.value = option.value;
|
|
13920
|
+
el.focus();
|
|
13921
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
13922
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
13923
|
+
return "Selected: " + ((option.textContent || option.value).trim().slice(0, 100));
|
|
13924
|
+
}
|
|
13558
13925
|
var proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
13559
13926
|
var desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
13560
13927
|
if (desc && desc.set) { desc.set.call(el, ${JSON.stringify(value)}); } else { el.value = ${JSON.stringify(value)}; }
|
|
@@ -13576,13 +13943,29 @@ async function setElementValue$1(wc, selector, value) {
|
|
|
13576
13943
|
(function() {
|
|
13577
13944
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
13578
13945
|
if (!el) return 'Error[stale-index]: Element not found — the page may have changed. Call read_page to refresh.';
|
|
13579
|
-
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
|
|
13580
|
-
return 'Error[not-input]: Element is not a
|
|
13946
|
+
if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
|
|
13947
|
+
return 'Error[not-input]: Element is not a fillable input';
|
|
13581
13948
|
}
|
|
13582
13949
|
if (el.disabled || el.getAttribute('aria-disabled') === 'true') {
|
|
13583
13950
|
return 'Error[disabled]: Input is disabled';
|
|
13584
13951
|
}
|
|
13585
13952
|
|
|
13953
|
+
if (el instanceof HTMLSelectElement) {
|
|
13954
|
+
const requested = ${JSON.stringify(value)}.trim().toLowerCase();
|
|
13955
|
+
const option = Array.from(el.options).find((item) => {
|
|
13956
|
+
const label = (item.textContent || '').trim().toLowerCase();
|
|
13957
|
+
return label === requested || item.value.trim().toLowerCase() === requested;
|
|
13958
|
+
});
|
|
13959
|
+
if (!option) {
|
|
13960
|
+
return 'Error[option-not-found]: Option not found';
|
|
13961
|
+
}
|
|
13962
|
+
el.value = option.value;
|
|
13963
|
+
el.focus();
|
|
13964
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
13965
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
13966
|
+
return 'Selected: ' + ((option.textContent || option.value).trim().slice(0, 100));
|
|
13967
|
+
}
|
|
13968
|
+
|
|
13586
13969
|
const prototype = el instanceof HTMLTextAreaElement
|
|
13587
13970
|
? HTMLTextAreaElement.prototype
|
|
13588
13971
|
: HTMLInputElement.prototype;
|
|
@@ -16654,6 +17037,183 @@ Exception: ${result.exceptionDetails}`);
|
|
|
16654
17037
|
}
|
|
16655
17038
|
);
|
|
16656
17039
|
}
|
|
17040
|
+
const VAULT_FILENAME = "vessel-vault.enc";
|
|
17041
|
+
const KEY_FILENAME = "vessel-vault.key";
|
|
17042
|
+
const ALGORITHM = "aes-256-gcm";
|
|
17043
|
+
const IV_LENGTH = 12;
|
|
17044
|
+
const AUTH_TAG_LENGTH = 16;
|
|
17045
|
+
let cachedEntries = null;
|
|
17046
|
+
function getVaultDir() {
|
|
17047
|
+
return electron.app.getPath("userData");
|
|
17048
|
+
}
|
|
17049
|
+
function getVaultPath() {
|
|
17050
|
+
return path$1.join(getVaultDir(), VAULT_FILENAME);
|
|
17051
|
+
}
|
|
17052
|
+
function getKeyPath() {
|
|
17053
|
+
return path$1.join(getVaultDir(), KEY_FILENAME);
|
|
17054
|
+
}
|
|
17055
|
+
function assertVaultSecretStorageAvailable() {
|
|
17056
|
+
if (!electron.safeStorage.isEncryptionAvailable()) {
|
|
17057
|
+
throw new Error(
|
|
17058
|
+
"Agent Credential Vault requires OS-backed secret storage. Enable Keychain, DPAPI, or libsecret support and restart Vessel."
|
|
17059
|
+
);
|
|
17060
|
+
}
|
|
17061
|
+
}
|
|
17062
|
+
function getOrCreateEncryptionKey() {
|
|
17063
|
+
assertVaultSecretStorageAvailable();
|
|
17064
|
+
const keyPath = getKeyPath();
|
|
17065
|
+
if (fs$1.existsSync(keyPath)) {
|
|
17066
|
+
const encryptedKey = fs$1.readFileSync(keyPath);
|
|
17067
|
+
return Buffer.from(electron.safeStorage.decryptString(encryptedKey), "utf-8");
|
|
17068
|
+
}
|
|
17069
|
+
const key = crypto$2.randomBytes(32);
|
|
17070
|
+
fs$1.mkdirSync(path$1.dirname(keyPath), { recursive: true });
|
|
17071
|
+
const encrypted = electron.safeStorage.encryptString(key.toString("utf-8"));
|
|
17072
|
+
fs$1.writeFileSync(keyPath, encrypted, { mode: 384 });
|
|
17073
|
+
return key;
|
|
17074
|
+
}
|
|
17075
|
+
function encrypt(plaintext) {
|
|
17076
|
+
const key = getOrCreateEncryptionKey();
|
|
17077
|
+
const iv = crypto$2.randomBytes(IV_LENGTH);
|
|
17078
|
+
const cipher = crypto$2.createCipheriv(ALGORITHM, key, iv, {
|
|
17079
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
17080
|
+
});
|
|
17081
|
+
const encrypted = Buffer.concat([
|
|
17082
|
+
cipher.update(plaintext, "utf-8"),
|
|
17083
|
+
cipher.final()
|
|
17084
|
+
]);
|
|
17085
|
+
const authTag = cipher.getAuthTag();
|
|
17086
|
+
return Buffer.concat([iv, authTag, encrypted]);
|
|
17087
|
+
}
|
|
17088
|
+
function decrypt(data) {
|
|
17089
|
+
const key = getOrCreateEncryptionKey();
|
|
17090
|
+
const iv = data.subarray(0, IV_LENGTH);
|
|
17091
|
+
const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
17092
|
+
const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
17093
|
+
const decipher = crypto$2.createDecipheriv(ALGORITHM, key, iv, {
|
|
17094
|
+
authTagLength: AUTH_TAG_LENGTH
|
|
17095
|
+
});
|
|
17096
|
+
decipher.setAuthTag(authTag);
|
|
17097
|
+
return decipher.update(ciphertext, void 0, "utf-8") + decipher.final("utf-8");
|
|
17098
|
+
}
|
|
17099
|
+
function loadVault() {
|
|
17100
|
+
if (cachedEntries) return cachedEntries;
|
|
17101
|
+
const vaultPath = getVaultPath();
|
|
17102
|
+
if (!fs$1.existsSync(vaultPath)) {
|
|
17103
|
+
cachedEntries = [];
|
|
17104
|
+
return cachedEntries;
|
|
17105
|
+
}
|
|
17106
|
+
try {
|
|
17107
|
+
const raw = fs$1.readFileSync(vaultPath);
|
|
17108
|
+
const json = decrypt(raw);
|
|
17109
|
+
cachedEntries = JSON.parse(json);
|
|
17110
|
+
return cachedEntries;
|
|
17111
|
+
} catch (err) {
|
|
17112
|
+
console.error("[Vessel Vault] Failed to load vault:", err);
|
|
17113
|
+
throw new Error(
|
|
17114
|
+
"Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
|
|
17115
|
+
);
|
|
17116
|
+
}
|
|
17117
|
+
}
|
|
17118
|
+
function saveVault(entries) {
|
|
17119
|
+
const json = JSON.stringify(entries, null, 2);
|
|
17120
|
+
const encrypted = encrypt(json);
|
|
17121
|
+
const vaultPath = getVaultPath();
|
|
17122
|
+
fs$1.mkdirSync(path$1.dirname(vaultPath), { recursive: true });
|
|
17123
|
+
fs$1.writeFileSync(vaultPath, encrypted);
|
|
17124
|
+
fs$1.chmodSync(vaultPath, 384);
|
|
17125
|
+
cachedEntries = entries;
|
|
17126
|
+
}
|
|
17127
|
+
function domainMatches(pattern, hostname) {
|
|
17128
|
+
const p = pattern.toLowerCase().trim();
|
|
17129
|
+
const h = hostname.toLowerCase().trim();
|
|
17130
|
+
if (p === h) return true;
|
|
17131
|
+
if (p.startsWith("*.")) {
|
|
17132
|
+
const suffix = p.slice(2);
|
|
17133
|
+
return h === suffix || h.endsWith("." + suffix);
|
|
17134
|
+
}
|
|
17135
|
+
return false;
|
|
17136
|
+
}
|
|
17137
|
+
function listEntries() {
|
|
17138
|
+
return loadVault().map(({ password, totpSecret, ...rest }) => rest);
|
|
17139
|
+
}
|
|
17140
|
+
function findEntriesForDomain(url) {
|
|
17141
|
+
let hostname;
|
|
17142
|
+
try {
|
|
17143
|
+
hostname = new URL(url).hostname;
|
|
17144
|
+
} catch {
|
|
17145
|
+
return [];
|
|
17146
|
+
}
|
|
17147
|
+
return loadVault().filter((e) => domainMatches(e.domainPattern, hostname)).map(({ password, totpSecret, ...rest }) => rest);
|
|
17148
|
+
}
|
|
17149
|
+
function addEntry(entry) {
|
|
17150
|
+
const entries = loadVault();
|
|
17151
|
+
const newEntry = {
|
|
17152
|
+
...entry,
|
|
17153
|
+
id: crypto$2.randomUUID(),
|
|
17154
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17155
|
+
useCount: 0
|
|
17156
|
+
};
|
|
17157
|
+
entries.push(newEntry);
|
|
17158
|
+
saveVault(entries);
|
|
17159
|
+
return newEntry;
|
|
17160
|
+
}
|
|
17161
|
+
function updateEntry(id, updates) {
|
|
17162
|
+
const entries = loadVault();
|
|
17163
|
+
const idx = entries.findIndex((e) => e.id === id);
|
|
17164
|
+
if (idx === -1) return null;
|
|
17165
|
+
entries[idx] = { ...entries[idx], ...updates };
|
|
17166
|
+
saveVault(entries);
|
|
17167
|
+
return entries[idx];
|
|
17168
|
+
}
|
|
17169
|
+
function removeEntry(id) {
|
|
17170
|
+
const entries = loadVault();
|
|
17171
|
+
const idx = entries.findIndex((e) => e.id === id);
|
|
17172
|
+
if (idx === -1) return false;
|
|
17173
|
+
entries.splice(idx, 1);
|
|
17174
|
+
saveVault(entries);
|
|
17175
|
+
return true;
|
|
17176
|
+
}
|
|
17177
|
+
function recordUsage(id) {
|
|
17178
|
+
const entries = loadVault();
|
|
17179
|
+
const entry = entries.find((e) => e.id === id);
|
|
17180
|
+
if (!entry) return;
|
|
17181
|
+
entry.lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17182
|
+
entry.useCount += 1;
|
|
17183
|
+
saveVault(entries);
|
|
17184
|
+
}
|
|
17185
|
+
function getCredential(id) {
|
|
17186
|
+
const entry = loadVault().find((e) => e.id === id);
|
|
17187
|
+
if (!entry) return null;
|
|
17188
|
+
return { username: entry.username, password: entry.password };
|
|
17189
|
+
}
|
|
17190
|
+
function getTotpSecret(id) {
|
|
17191
|
+
const entry = loadVault().find((e) => e.id === id);
|
|
17192
|
+
return entry?.totpSecret ?? null;
|
|
17193
|
+
}
|
|
17194
|
+
function generateTotpCode(secret) {
|
|
17195
|
+
const epoch = Math.floor(Date.now() / 1e3);
|
|
17196
|
+
const counter = Math.floor(epoch / 30);
|
|
17197
|
+
const base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
17198
|
+
const cleanSecret = secret.replace(/[\s=-]/g, "").toUpperCase();
|
|
17199
|
+
let bits = "";
|
|
17200
|
+
for (const ch of cleanSecret) {
|
|
17201
|
+
const val = base32Chars.indexOf(ch);
|
|
17202
|
+
if (val === -1) continue;
|
|
17203
|
+
bits += val.toString(2).padStart(5, "0");
|
|
17204
|
+
}
|
|
17205
|
+
const keyBytes = Buffer.alloc(Math.floor(bits.length / 8));
|
|
17206
|
+
for (let i = 0; i < keyBytes.length; i++) {
|
|
17207
|
+
keyBytes[i] = parseInt(bits.slice(i * 8, i * 8 + 8), 2);
|
|
17208
|
+
}
|
|
17209
|
+
const counterBuf = Buffer.alloc(8);
|
|
17210
|
+
counterBuf.writeUInt32BE(Math.floor(counter / 4294967296), 0);
|
|
17211
|
+
counterBuf.writeUInt32BE(counter & 4294967295, 4);
|
|
17212
|
+
const hmac = crypto$2.createHmac("sha1", keyBytes).update(counterBuf).digest();
|
|
17213
|
+
const offset = hmac[hmac.length - 1] & 15;
|
|
17214
|
+
const code = (hmac[offset] & 127) << 24 | (hmac[offset + 1] & 255) << 16 | (hmac[offset + 2] & 255) << 8 | hmac[offset + 3] & 255;
|
|
17215
|
+
return (code % 1e6).toString().padStart(6, "0");
|
|
17216
|
+
}
|
|
16657
17217
|
const sessionTrustedDomains = /* @__PURE__ */ new Set();
|
|
16658
17218
|
async function requestConsent(request) {
|
|
16659
17219
|
const domain = request.domain.toLowerCase();
|
|
@@ -16689,6 +17249,31 @@ async function requestConsent(request) {
|
|
|
16689
17249
|
}
|
|
16690
17250
|
return { approved: true, trustForSession };
|
|
16691
17251
|
}
|
|
17252
|
+
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
17253
|
+
const MAX_ENTRIES = 1e3;
|
|
17254
|
+
function getAuditPath() {
|
|
17255
|
+
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
17256
|
+
}
|
|
17257
|
+
function appendAuditEntry(entry) {
|
|
17258
|
+
try {
|
|
17259
|
+
const auditPath = getAuditPath();
|
|
17260
|
+
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
17261
|
+
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
|
|
17262
|
+
} catch (err) {
|
|
17263
|
+
console.error("[Vessel Vault] Failed to write audit log:", err);
|
|
17264
|
+
}
|
|
17265
|
+
}
|
|
17266
|
+
function readAuditLog(limit = 100) {
|
|
17267
|
+
try {
|
|
17268
|
+
const auditPath = getAuditPath();
|
|
17269
|
+
if (!fs$1.existsSync(auditPath)) return [];
|
|
17270
|
+
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
17271
|
+
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
17272
|
+
} catch (err) {
|
|
17273
|
+
console.error("[Vessel Vault] Failed to read audit log:", err);
|
|
17274
|
+
return [];
|
|
17275
|
+
}
|
|
17276
|
+
}
|
|
16692
17277
|
let httpServer = null;
|
|
16693
17278
|
let mcpAuthToken = null;
|
|
16694
17279
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
@@ -19291,7 +19876,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
19291
19876
|
color
|
|
19292
19877
|
);
|
|
19293
19878
|
if (persist && !durationMs && !result.startsWith("Error") && !result.includes("not found")) {
|
|
19294
|
-
const url = normalizeUrl(wc.getURL());
|
|
19879
|
+
const url = normalizeUrl$1(wc.getURL());
|
|
19295
19880
|
addHighlight(
|
|
19296
19881
|
url,
|
|
19297
19882
|
resolvedSelector ?? void 0,
|
|
@@ -19322,7 +19907,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
19322
19907
|
{},
|
|
19323
19908
|
async () => {
|
|
19324
19909
|
const wc = tab.view.webContents;
|
|
19325
|
-
const url = normalizeUrl(wc.getURL());
|
|
19910
|
+
const url = normalizeUrl$1(wc.getURL());
|
|
19326
19911
|
clearHighlightsForUrl(url);
|
|
19327
19912
|
return clearHighlights(wc);
|
|
19328
19913
|
}
|
|
@@ -19343,7 +19928,7 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
19343
19928
|
async ({ url }) => {
|
|
19344
19929
|
const state2 = getState$2();
|
|
19345
19930
|
const activeTab = tabManager.getActiveTab();
|
|
19346
|
-
const activeUrl = activeTab ? normalizeUrl(activeTab.view.webContents.getURL()) : null;
|
|
19931
|
+
const activeUrl = activeTab ? normalizeUrl$1(activeTab.view.webContents.getURL()) : null;
|
|
19347
19932
|
const activeSavedHighlights = activeUrl ? state2.highlights.filter((highlight) => highlight.url === activeUrl) : [];
|
|
19348
19933
|
const liveSnapshot = activeTab && activeUrl ? await captureLiveHighlightSnapshot(
|
|
19349
19934
|
activeTab.view.webContents,
|
|
@@ -19354,9 +19939,9 @@ To analyze visually, call vision_analyze with image_url="${screenshotPath}"`
|
|
|
19354
19939
|
);
|
|
19355
19940
|
if (url) {
|
|
19356
19941
|
const filtered = state2.highlights.filter(
|
|
19357
|
-
(h) => h.url === normalizeUrl(url)
|
|
19942
|
+
(h) => h.url === normalizeUrl$1(url)
|
|
19358
19943
|
);
|
|
19359
|
-
const normalizedUrl = normalizeUrl(url);
|
|
19944
|
+
const normalizedUrl = normalizeUrl$1(url);
|
|
19360
19945
|
const sections2 = [];
|
|
19361
19946
|
if (activeUrl && activeUrl === normalizedUrl) {
|
|
19362
19947
|
if (liveSnapshot.activeSelection) {
|
|
@@ -19437,7 +20022,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
19437
20022
|
}
|
|
19438
20023
|
const remaining = getHighlightsForUrl(removed.url);
|
|
19439
20024
|
for (const tabState of tabManager.getAllStates()) {
|
|
19440
|
-
if (normalizeUrl(tabState.url) !== removed.url) {
|
|
20025
|
+
if (normalizeUrl$1(tabState.url) !== removed.url) {
|
|
19441
20026
|
continue;
|
|
19442
20027
|
}
|
|
19443
20028
|
const tab = tabManager.getTab(tabState.id);
|
|
@@ -21694,16 +22279,491 @@ function registerScheduleHandlers(windowState, runtime2, sendToAll) {
|
|
|
21694
22279
|
return true;
|
|
21695
22280
|
});
|
|
21696
22281
|
}
|
|
21697
|
-
let activeChatProvider = null;
|
|
21698
22282
|
function assertString(value, name) {
|
|
21699
22283
|
if (typeof value !== "string") throw new Error(`${name} must be a string`);
|
|
21700
22284
|
}
|
|
21701
22285
|
function assertOptionalString(value, name) {
|
|
21702
|
-
if (value !== void 0 && typeof value !== "string")
|
|
22286
|
+
if (value !== void 0 && typeof value !== "string") {
|
|
22287
|
+
throw new Error(`${name} must be a string`);
|
|
22288
|
+
}
|
|
21703
22289
|
}
|
|
21704
22290
|
function assertNumber(value, name) {
|
|
21705
|
-
if (typeof value !== "number" || Number.isNaN(value))
|
|
22291
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
22292
|
+
throw new Error(`${name} must be a number`);
|
|
22293
|
+
}
|
|
22294
|
+
}
|
|
22295
|
+
const SAVE_DEBOUNCE_MS = 250;
|
|
22296
|
+
const PROFILE_FIELDS = [
|
|
22297
|
+
"label",
|
|
22298
|
+
"firstName",
|
|
22299
|
+
"lastName",
|
|
22300
|
+
"email",
|
|
22301
|
+
"phone",
|
|
22302
|
+
"organization",
|
|
22303
|
+
"addressLine1",
|
|
22304
|
+
"addressLine2",
|
|
22305
|
+
"city",
|
|
22306
|
+
"state",
|
|
22307
|
+
"postalCode",
|
|
22308
|
+
"country"
|
|
22309
|
+
];
|
|
22310
|
+
let state = null;
|
|
22311
|
+
function getFilePath() {
|
|
22312
|
+
return path.join(electron.app.getPath("userData"), "vessel-autofill.json");
|
|
22313
|
+
}
|
|
22314
|
+
function getDefaultState() {
|
|
22315
|
+
return { profiles: [] };
|
|
22316
|
+
}
|
|
22317
|
+
function normalizeStoredProfile(value) {
|
|
22318
|
+
if (!value || typeof value !== "object") return null;
|
|
22319
|
+
const raw = value;
|
|
22320
|
+
if (typeof raw.id !== "string" || typeof raw.createdAt !== "string" || typeof raw.updatedAt !== "string") {
|
|
22321
|
+
return null;
|
|
22322
|
+
}
|
|
22323
|
+
const profile = {
|
|
22324
|
+
id: raw.id,
|
|
22325
|
+
createdAt: raw.createdAt,
|
|
22326
|
+
updatedAt: raw.updatedAt
|
|
22327
|
+
};
|
|
22328
|
+
for (const field of PROFILE_FIELDS) {
|
|
22329
|
+
if (typeof raw[field] !== "string") return null;
|
|
22330
|
+
profile[field] = raw[field];
|
|
22331
|
+
}
|
|
22332
|
+
return profile;
|
|
22333
|
+
}
|
|
22334
|
+
function load() {
|
|
22335
|
+
if (state) return state;
|
|
22336
|
+
state = loadJsonFile({
|
|
22337
|
+
filePath: getFilePath(),
|
|
22338
|
+
fallback: getDefaultState(),
|
|
22339
|
+
secure: true,
|
|
22340
|
+
parse: (raw) => {
|
|
22341
|
+
const parsed = raw;
|
|
22342
|
+
return {
|
|
22343
|
+
profiles: Array.isArray(parsed.profiles) ? parsed.profiles.map(normalizeStoredProfile).filter((profile) => profile !== null) : []
|
|
22344
|
+
};
|
|
22345
|
+
}
|
|
22346
|
+
});
|
|
22347
|
+
return state;
|
|
22348
|
+
}
|
|
22349
|
+
const persistence = createDebouncedJsonPersistence({
|
|
22350
|
+
debounceMs: SAVE_DEBOUNCE_MS,
|
|
22351
|
+
filePath: getFilePath(),
|
|
22352
|
+
getValue: () => state,
|
|
22353
|
+
logLabel: "autofill",
|
|
22354
|
+
secure: true
|
|
22355
|
+
});
|
|
22356
|
+
function listProfiles() {
|
|
22357
|
+
return load().profiles;
|
|
22358
|
+
}
|
|
22359
|
+
function getProfile(id) {
|
|
22360
|
+
return load().profiles.find((p) => p.id === id);
|
|
22361
|
+
}
|
|
22362
|
+
function addProfile(input) {
|
|
22363
|
+
const s = load();
|
|
22364
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22365
|
+
const profile = {
|
|
22366
|
+
...input,
|
|
22367
|
+
id: crypto$1.randomUUID(),
|
|
22368
|
+
createdAt: now,
|
|
22369
|
+
updatedAt: now
|
|
22370
|
+
};
|
|
22371
|
+
s.profiles.push(profile);
|
|
22372
|
+
persistence.schedule();
|
|
22373
|
+
return profile;
|
|
22374
|
+
}
|
|
22375
|
+
function updateProfile(id, updates) {
|
|
22376
|
+
const s = load();
|
|
22377
|
+
const idx = s.profiles.findIndex((p) => p.id === id);
|
|
22378
|
+
if (idx === -1) return null;
|
|
22379
|
+
s.profiles[idx] = {
|
|
22380
|
+
...s.profiles[idx],
|
|
22381
|
+
...updates,
|
|
22382
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
22383
|
+
};
|
|
22384
|
+
persistence.schedule();
|
|
22385
|
+
return s.profiles[idx];
|
|
22386
|
+
}
|
|
22387
|
+
function deleteProfile(id) {
|
|
22388
|
+
const s = load();
|
|
22389
|
+
const len = s.profiles.length;
|
|
22390
|
+
s.profiles = s.profiles.filter((p) => p.id !== id);
|
|
22391
|
+
if (s.profiles.length === len) return false;
|
|
22392
|
+
persistence.schedule();
|
|
22393
|
+
return true;
|
|
22394
|
+
}
|
|
22395
|
+
function flushPersist() {
|
|
22396
|
+
return persistence.flush();
|
|
22397
|
+
}
|
|
22398
|
+
const AUTOCOMPLETE_MAP = {
|
|
22399
|
+
"given-name": "firstName",
|
|
22400
|
+
"family-name": "lastName",
|
|
22401
|
+
"surname": "lastName",
|
|
22402
|
+
"email": "email",
|
|
22403
|
+
"tel": "phone",
|
|
22404
|
+
"tel-national": "phone",
|
|
22405
|
+
"phone": "phone",
|
|
22406
|
+
"organization": "organization",
|
|
22407
|
+
"company": "organization",
|
|
22408
|
+
"street-address": "addressLine1",
|
|
22409
|
+
"address-line1": "addressLine1",
|
|
22410
|
+
"address-line2": "addressLine2",
|
|
22411
|
+
"address-level1": "state",
|
|
22412
|
+
"address-level2": "city",
|
|
22413
|
+
"state": "state",
|
|
22414
|
+
"province": "state",
|
|
22415
|
+
"city": "city",
|
|
22416
|
+
"postal-code": "postalCode",
|
|
22417
|
+
"zip": "postalCode",
|
|
22418
|
+
"zip-code": "postalCode",
|
|
22419
|
+
"country": "country",
|
|
22420
|
+
"country-name": "country",
|
|
22421
|
+
"country-code": "country"
|
|
22422
|
+
};
|
|
22423
|
+
const INPUT_TYPE_MAP = {
|
|
22424
|
+
email: "email",
|
|
22425
|
+
tel: "phone"
|
|
22426
|
+
};
|
|
22427
|
+
const NAME_MAP = {
|
|
22428
|
+
firstname: "firstName",
|
|
22429
|
+
first_name: "firstName",
|
|
22430
|
+
"first-name": "firstName",
|
|
22431
|
+
fname: "firstName",
|
|
22432
|
+
givenname: "firstName",
|
|
22433
|
+
lastname: "lastName",
|
|
22434
|
+
last_name: "lastName",
|
|
22435
|
+
"last-name": "lastName",
|
|
22436
|
+
lname: "lastName",
|
|
22437
|
+
surname: "lastName",
|
|
22438
|
+
familyname: "lastName",
|
|
22439
|
+
email: "email",
|
|
22440
|
+
e_mail: "email",
|
|
22441
|
+
"e-mail": "email",
|
|
22442
|
+
emailaddress: "email",
|
|
22443
|
+
mail: "email",
|
|
22444
|
+
phone: "phone",
|
|
22445
|
+
telephone: "phone",
|
|
22446
|
+
tel: "phone",
|
|
22447
|
+
mobile: "phone",
|
|
22448
|
+
cell: "phone",
|
|
22449
|
+
company: "organization",
|
|
22450
|
+
organization: "organization",
|
|
22451
|
+
organisation: "organization",
|
|
22452
|
+
companyname: "organization",
|
|
22453
|
+
address: "addressLine1",
|
|
22454
|
+
street: "addressLine1",
|
|
22455
|
+
"street-address": "addressLine1",
|
|
22456
|
+
address1: "addressLine1",
|
|
22457
|
+
"address-line1": "addressLine1",
|
|
22458
|
+
"addr-line1": "addressLine1",
|
|
22459
|
+
address2: "addressLine2",
|
|
22460
|
+
"address-line2": "addressLine2",
|
|
22461
|
+
"addr-line2": "addressLine2",
|
|
22462
|
+
city: "city",
|
|
22463
|
+
town: "city",
|
|
22464
|
+
locality: "city",
|
|
22465
|
+
state: "state",
|
|
22466
|
+
province: "state",
|
|
22467
|
+
region: "state",
|
|
22468
|
+
zip: "postalCode",
|
|
22469
|
+
zipcode: "postalCode",
|
|
22470
|
+
"zip-code": "postalCode",
|
|
22471
|
+
"postal-code": "postalCode",
|
|
22472
|
+
postalcode: "postalCode",
|
|
22473
|
+
postcode: "postalCode",
|
|
22474
|
+
country: "country"
|
|
22475
|
+
};
|
|
22476
|
+
const LABEL_MAP = {
|
|
22477
|
+
"first name": "firstName",
|
|
22478
|
+
"given name": "firstName",
|
|
22479
|
+
"last name": "lastName",
|
|
22480
|
+
"surname": "lastName",
|
|
22481
|
+
"family name": "lastName",
|
|
22482
|
+
email: "email",
|
|
22483
|
+
"e-mail": "email",
|
|
22484
|
+
"email address": "email",
|
|
22485
|
+
phone: "phone",
|
|
22486
|
+
telephone: "phone",
|
|
22487
|
+
"phone number": "phone",
|
|
22488
|
+
mobile: "phone",
|
|
22489
|
+
cell: "phone",
|
|
22490
|
+
company: "organization",
|
|
22491
|
+
organization: "organization",
|
|
22492
|
+
organisation: "organization",
|
|
22493
|
+
"company name": "organization",
|
|
22494
|
+
address: "addressLine1",
|
|
22495
|
+
"street address": "addressLine1",
|
|
22496
|
+
"address line 1": "addressLine1",
|
|
22497
|
+
"address line 2": "addressLine2",
|
|
22498
|
+
city: "city",
|
|
22499
|
+
town: "city",
|
|
22500
|
+
state: "state",
|
|
22501
|
+
province: "state",
|
|
22502
|
+
region: "state",
|
|
22503
|
+
zip: "postalCode",
|
|
22504
|
+
"zip code": "postalCode",
|
|
22505
|
+
"postal code": "postalCode",
|
|
22506
|
+
"post code": "postalCode",
|
|
22507
|
+
country: "country"
|
|
22508
|
+
};
|
|
22509
|
+
function normalize(s) {
|
|
22510
|
+
return s.toLowerCase().trim().replace(/[\s_-]+/g, " ");
|
|
22511
|
+
}
|
|
22512
|
+
function getFullName(profile) {
|
|
22513
|
+
return [profile.firstName, profile.lastName].filter(Boolean).join(" ").trim();
|
|
22514
|
+
}
|
|
22515
|
+
function mk(val, confidence, matchedBy, profileKey) {
|
|
22516
|
+
return { value: val, confidence, matchedBy, profileKey };
|
|
22517
|
+
}
|
|
22518
|
+
function matchField(el, profile) {
|
|
22519
|
+
if (el.type !== "input" && el.type !== "select" && el.type !== "textarea") return null;
|
|
22520
|
+
if (el.disabled) return null;
|
|
22521
|
+
const inputType = (el.inputType || "text").toLowerCase();
|
|
22522
|
+
if (inputType === "hidden" || inputType === "submit" || inputType === "button" || inputType === "file" || inputType === "image") return null;
|
|
22523
|
+
if (inputType === "password" || inputType === "checkbox" || inputType === "radio") return null;
|
|
22524
|
+
if (el.autocomplete) {
|
|
22525
|
+
const key = el.autocomplete.replace(/section-\w+\s+/, "").replace(/^shipping\s+|^billing\s+/, "");
|
|
22526
|
+
if (key === "name" || key === "additional-name") {
|
|
22527
|
+
const fullName = getFullName(profile);
|
|
22528
|
+
if (fullName) return mk(fullName, 100, "autocomplete", "fullName");
|
|
22529
|
+
}
|
|
22530
|
+
const pk = AUTOCOMPLETE_MAP[key];
|
|
22531
|
+
if (pk && profile[pk]) return mk(profile[pk], 100, "autocomplete", pk);
|
|
22532
|
+
}
|
|
22533
|
+
if (INPUT_TYPE_MAP[inputType]) {
|
|
22534
|
+
const pk = INPUT_TYPE_MAP[inputType];
|
|
22535
|
+
if (profile[pk]) return mk(profile[pk], 90, "inputType", pk);
|
|
22536
|
+
}
|
|
22537
|
+
if (el.name) {
|
|
22538
|
+
const norm = normalize(el.name);
|
|
22539
|
+
const pk = NAME_MAP[norm];
|
|
22540
|
+
if (pk && profile[pk]) return mk(profile[pk], 80, "name", pk);
|
|
22541
|
+
for (const [pattern, pk2] of Object.entries(NAME_MAP)) {
|
|
22542
|
+
if (norm.includes(pattern) && profile[pk2]) return mk(profile[pk2], 70, "name", pk2);
|
|
22543
|
+
}
|
|
22544
|
+
}
|
|
22545
|
+
if (el.label) {
|
|
22546
|
+
const norm = normalize(el.label);
|
|
22547
|
+
if (norm === "full name" || norm.includes("full name")) {
|
|
22548
|
+
const fullName = getFullName(profile);
|
|
22549
|
+
if (fullName) return mk(fullName, 75, "label", "fullName");
|
|
22550
|
+
}
|
|
22551
|
+
const pk = LABEL_MAP[norm];
|
|
22552
|
+
if (pk && profile[pk]) return mk(profile[pk], 75, "label", pk);
|
|
22553
|
+
for (const [pattern, pk2] of Object.entries(LABEL_MAP)) {
|
|
22554
|
+
if (norm.includes(pattern) && profile[pk2]) return mk(profile[pk2], 65, "label", pk2);
|
|
22555
|
+
}
|
|
22556
|
+
}
|
|
22557
|
+
if (el.placeholder) {
|
|
22558
|
+
const norm = normalize(el.placeholder);
|
|
22559
|
+
if (norm === "full name" || norm.includes("full name")) {
|
|
22560
|
+
const fullName = getFullName(profile);
|
|
22561
|
+
if (fullName) return mk(fullName, 50, "placeholder", "fullName");
|
|
22562
|
+
}
|
|
22563
|
+
for (const [pattern, pk] of Object.entries(LABEL_MAP)) {
|
|
22564
|
+
if (norm.includes(pattern) && profile[pk]) return mk(profile[pk], 50, "placeholder", pk);
|
|
22565
|
+
}
|
|
22566
|
+
}
|
|
22567
|
+
return null;
|
|
22568
|
+
}
|
|
22569
|
+
function matchFields(elements, profile) {
|
|
22570
|
+
const assigned = /* @__PURE__ */ new Map();
|
|
22571
|
+
for (const el of elements) {
|
|
22572
|
+
const candidate = matchField(el, profile);
|
|
22573
|
+
if (!candidate) continue;
|
|
22574
|
+
const existing = assigned.get(candidate.profileKey);
|
|
22575
|
+
if (existing && existing.confidence >= candidate.confidence) continue;
|
|
22576
|
+
if (el.index == null || !el.selector) continue;
|
|
22577
|
+
assigned.set(candidate.profileKey, {
|
|
22578
|
+
fieldIndex: el.index,
|
|
22579
|
+
selector: el.selector,
|
|
22580
|
+
value: candidate.value,
|
|
22581
|
+
confidence: candidate.confidence,
|
|
22582
|
+
matchedBy: candidate.matchedBy
|
|
22583
|
+
});
|
|
22584
|
+
}
|
|
22585
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22586
|
+
const results = [];
|
|
22587
|
+
for (const match of assigned.values()) {
|
|
22588
|
+
if (seen.has(match.fieldIndex)) continue;
|
|
22589
|
+
seen.add(match.fieldIndex);
|
|
22590
|
+
results.push(match);
|
|
22591
|
+
}
|
|
22592
|
+
return results;
|
|
22593
|
+
}
|
|
22594
|
+
const AUTOFILL_PROFILE_FIELDS = [
|
|
22595
|
+
"label",
|
|
22596
|
+
"firstName",
|
|
22597
|
+
"lastName",
|
|
22598
|
+
"email",
|
|
22599
|
+
"phone",
|
|
22600
|
+
"organization",
|
|
22601
|
+
"addressLine1",
|
|
22602
|
+
"addressLine2",
|
|
22603
|
+
"city",
|
|
22604
|
+
"state",
|
|
22605
|
+
"postalCode",
|
|
22606
|
+
"country"
|
|
22607
|
+
];
|
|
22608
|
+
function sanitizeAutofillProfile(value) {
|
|
22609
|
+
if (!value || typeof value !== "object") throw new Error("Invalid profile");
|
|
22610
|
+
const raw = value;
|
|
22611
|
+
const profile = {};
|
|
22612
|
+
for (const field of AUTOFILL_PROFILE_FIELDS) {
|
|
22613
|
+
assertString(raw[field], field);
|
|
22614
|
+
profile[field] = raw[field];
|
|
22615
|
+
}
|
|
22616
|
+
if (!profile.label.trim()) throw new Error("Label is required");
|
|
22617
|
+
return profile;
|
|
22618
|
+
}
|
|
22619
|
+
function sanitizeAutofillUpdates(value) {
|
|
22620
|
+
if (!value || typeof value !== "object") throw new Error("Invalid updates");
|
|
22621
|
+
const raw = value;
|
|
22622
|
+
const updates = {};
|
|
22623
|
+
for (const field of AUTOFILL_PROFILE_FIELDS) {
|
|
22624
|
+
if (!(field in raw)) continue;
|
|
22625
|
+
assertString(raw[field], field);
|
|
22626
|
+
updates[field] = raw[field];
|
|
22627
|
+
}
|
|
22628
|
+
if ("label" in updates && !updates.label?.trim()) {
|
|
22629
|
+
throw new Error("Label is required");
|
|
22630
|
+
}
|
|
22631
|
+
return updates;
|
|
22632
|
+
}
|
|
22633
|
+
function registerAutofillHandlers(windowState) {
|
|
22634
|
+
electron.ipcMain.handle(Channels.AUTOFILL_LIST, () => {
|
|
22635
|
+
return listProfiles();
|
|
22636
|
+
});
|
|
22637
|
+
electron.ipcMain.handle(
|
|
22638
|
+
Channels.AUTOFILL_ADD,
|
|
22639
|
+
(_, profile) => {
|
|
22640
|
+
return addProfile(sanitizeAutofillProfile(profile));
|
|
22641
|
+
}
|
|
22642
|
+
);
|
|
22643
|
+
electron.ipcMain.handle(Channels.AUTOFILL_UPDATE, (_, id, updates) => {
|
|
22644
|
+
assertString(id, "id");
|
|
22645
|
+
return updateProfile(id, sanitizeAutofillUpdates(updates));
|
|
22646
|
+
});
|
|
22647
|
+
electron.ipcMain.handle(Channels.AUTOFILL_DELETE, (_, id) => {
|
|
22648
|
+
assertString(id, "id");
|
|
22649
|
+
return deleteProfile(id);
|
|
22650
|
+
});
|
|
22651
|
+
electron.ipcMain.handle(Channels.AUTOFILL_FILL, async (_, profileId) => {
|
|
22652
|
+
assertString(profileId, "profileId");
|
|
22653
|
+
const profile = getProfile(profileId);
|
|
22654
|
+
if (!profile) throw new Error("Profile not found");
|
|
22655
|
+
const activeTab = windowState.tabManager.getActiveTab();
|
|
22656
|
+
const wc = activeTab?.view.webContents;
|
|
22657
|
+
if (!wc) throw new Error("No active tab");
|
|
22658
|
+
const content = await extractContent(wc);
|
|
22659
|
+
const elements = content.interactiveElements || [];
|
|
22660
|
+
const matches = matchFields(elements, profile);
|
|
22661
|
+
if (matches.length === 0) {
|
|
22662
|
+
return { filled: 0, skipped: 0, details: [] };
|
|
22663
|
+
}
|
|
22664
|
+
const fields = matches.map((match) => ({
|
|
22665
|
+
index: match.fieldIndex,
|
|
22666
|
+
selector: match.selector,
|
|
22667
|
+
value: match.value
|
|
22668
|
+
}));
|
|
22669
|
+
const results = await fillFormFields(wc, fields);
|
|
22670
|
+
const filled = results.filter(
|
|
22671
|
+
(result) => result.result.startsWith("Typed into:") || result.result.startsWith("Selected:")
|
|
22672
|
+
).length;
|
|
22673
|
+
return {
|
|
22674
|
+
filled,
|
|
22675
|
+
skipped: results.length - filled,
|
|
22676
|
+
details: results.map((result, index) => ({
|
|
22677
|
+
label: elements.find((element) => element.index === matches[index]?.fieldIndex)?.label || `Field ${index + 1}`,
|
|
22678
|
+
value: matches[index]?.value || "",
|
|
22679
|
+
matchedBy: matches[index]?.matchedBy || "unknown",
|
|
22680
|
+
result: result.result
|
|
22681
|
+
}))
|
|
22682
|
+
};
|
|
22683
|
+
});
|
|
22684
|
+
}
|
|
22685
|
+
function registerPageDiffHandlers(windowState, sendToRendererViews) {
|
|
22686
|
+
electron.ipcMain.handle(Channels.PAGE_DIFF_GET, () => {
|
|
22687
|
+
const activeTab = windowState.tabManager.getActiveTab();
|
|
22688
|
+
const wc = activeTab?.view.webContents;
|
|
22689
|
+
if (!wc) return null;
|
|
22690
|
+
return getLatestPageDiff(wc.getURL());
|
|
22691
|
+
});
|
|
22692
|
+
electron.ipcMain.on(Channels.PAGE_DIFF_ACTIVITY, (event) => {
|
|
22693
|
+
const wc = event.sender;
|
|
22694
|
+
if (!wc || wc.isDestroyed()) return;
|
|
22695
|
+
notePageMutationActivity(wc, sendToRendererViews);
|
|
22696
|
+
});
|
|
22697
|
+
electron.ipcMain.on(Channels.PAGE_DIFF_DIRTY, (event) => {
|
|
22698
|
+
const wc = event.sender;
|
|
22699
|
+
if (!wc || wc.isDestroyed()) return;
|
|
22700
|
+
schedulePageSnapshotCapture(wc, sendToRendererViews);
|
|
22701
|
+
});
|
|
22702
|
+
}
|
|
22703
|
+
function registerVaultHandlers() {
|
|
22704
|
+
electron.ipcMain.handle(Channels.VAULT_LIST, () => {
|
|
22705
|
+
return listEntries();
|
|
22706
|
+
});
|
|
22707
|
+
electron.ipcMain.handle(
|
|
22708
|
+
Channels.VAULT_ADD,
|
|
22709
|
+
(_, entry) => {
|
|
22710
|
+
if (!entry || typeof entry !== "object") {
|
|
22711
|
+
throw new Error("Invalid vault entry");
|
|
22712
|
+
}
|
|
22713
|
+
assertString(entry.label, "label");
|
|
22714
|
+
assertString(entry.domainPattern, "domainPattern");
|
|
22715
|
+
assertString(entry.username, "username");
|
|
22716
|
+
assertString(entry.password, "password");
|
|
22717
|
+
if (!entry.label.trim() || !entry.domainPattern.trim() || !entry.username.trim() || !entry.password.trim()) {
|
|
22718
|
+
throw new Error("Label, domain, username, and password are required");
|
|
22719
|
+
}
|
|
22720
|
+
assertOptionalString(entry.totpSecret, "totpSecret");
|
|
22721
|
+
assertOptionalString(entry.notes, "notes");
|
|
22722
|
+
trackVaultAction("credential_added");
|
|
22723
|
+
const created = addEntry(entry);
|
|
22724
|
+
return {
|
|
22725
|
+
id: created.id,
|
|
22726
|
+
label: created.label,
|
|
22727
|
+
domainPattern: created.domainPattern,
|
|
22728
|
+
username: created.username
|
|
22729
|
+
};
|
|
22730
|
+
}
|
|
22731
|
+
);
|
|
22732
|
+
electron.ipcMain.handle(
|
|
22733
|
+
Channels.VAULT_UPDATE,
|
|
22734
|
+
(_, id, updates) => {
|
|
22735
|
+
assertString(id, "id");
|
|
22736
|
+
if (!updates || typeof updates !== "object") {
|
|
22737
|
+
throw new Error("Invalid updates");
|
|
22738
|
+
}
|
|
22739
|
+
return updateEntry(id, updates) !== null;
|
|
22740
|
+
}
|
|
22741
|
+
);
|
|
22742
|
+
electron.ipcMain.handle(Channels.VAULT_REMOVE, (_, id) => {
|
|
22743
|
+
assertString(id, "id");
|
|
22744
|
+
trackVaultAction("credential_removed");
|
|
22745
|
+
return removeEntry(id);
|
|
22746
|
+
});
|
|
22747
|
+
electron.ipcMain.handle(Channels.VAULT_AUDIT_LOG, (_, limit) => {
|
|
22748
|
+
return readAuditLog(limit);
|
|
22749
|
+
});
|
|
21706
22750
|
}
|
|
22751
|
+
function registerWindowControlHandlers(mainWindow) {
|
|
22752
|
+
electron.ipcMain.handle(Channels.WINDOW_MINIMIZE, () => {
|
|
22753
|
+
mainWindow.minimize();
|
|
22754
|
+
});
|
|
22755
|
+
electron.ipcMain.handle(Channels.WINDOW_MAXIMIZE, () => {
|
|
22756
|
+
if (mainWindow.isMaximized()) {
|
|
22757
|
+
mainWindow.unmaximize();
|
|
22758
|
+
} else {
|
|
22759
|
+
mainWindow.maximize();
|
|
22760
|
+
}
|
|
22761
|
+
});
|
|
22762
|
+
electron.ipcMain.handle(Channels.WINDOW_CLOSE, () => {
|
|
22763
|
+
mainWindow.close();
|
|
22764
|
+
});
|
|
22765
|
+
}
|
|
22766
|
+
let activeChatProvider = null;
|
|
21707
22767
|
const VALID_APPROVAL_MODES = /* @__PURE__ */ new Set(["auto", "confirm-dangerous", "manual"]);
|
|
21708
22768
|
function registerIpcHandlers(windowState, runtime2) {
|
|
21709
22769
|
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
@@ -22302,56 +23362,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22302
23362
|
}
|
|
22303
23363
|
return result;
|
|
22304
23364
|
});
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
});
|
|
22308
|
-
electron.ipcMain.handle(
|
|
22309
|
-
Channels.VAULT_ADD,
|
|
22310
|
-
(_, entry) => {
|
|
22311
|
-
if (!entry || typeof entry !== "object") throw new Error("Invalid vault entry");
|
|
22312
|
-
assertString(entry.label, "label");
|
|
22313
|
-
assertString(entry.domainPattern, "domainPattern");
|
|
22314
|
-
assertString(entry.username, "username");
|
|
22315
|
-
assertString(entry.password, "password");
|
|
22316
|
-
if (!entry.label.trim() || !entry.domainPattern.trim() || !entry.username.trim() || !entry.password.trim()) {
|
|
22317
|
-
throw new Error("Label, domain, username, and password are required");
|
|
22318
|
-
}
|
|
22319
|
-
assertOptionalString(entry.totpSecret, "totpSecret");
|
|
22320
|
-
assertOptionalString(entry.notes, "notes");
|
|
22321
|
-
trackVaultAction("credential_added");
|
|
22322
|
-
const created = addEntry(entry);
|
|
22323
|
-
return { id: created.id, label: created.label, domainPattern: created.domainPattern, username: created.username };
|
|
22324
|
-
}
|
|
22325
|
-
);
|
|
22326
|
-
electron.ipcMain.handle(
|
|
22327
|
-
Channels.VAULT_UPDATE,
|
|
22328
|
-
(_, id, updates) => {
|
|
22329
|
-
assertString(id, "id");
|
|
22330
|
-
if (!updates || typeof updates !== "object") throw new Error("Invalid updates");
|
|
22331
|
-
return updateEntry(id, updates) !== null;
|
|
22332
|
-
}
|
|
22333
|
-
);
|
|
22334
|
-
electron.ipcMain.handle(Channels.VAULT_REMOVE, (_, id) => {
|
|
22335
|
-
assertString(id, "id");
|
|
22336
|
-
trackVaultAction("credential_removed");
|
|
22337
|
-
return removeEntry(id);
|
|
22338
|
-
});
|
|
22339
|
-
electron.ipcMain.handle(Channels.VAULT_AUDIT_LOG, (_, limit) => {
|
|
22340
|
-
return readAuditLog(limit);
|
|
22341
|
-
});
|
|
22342
|
-
electron.ipcMain.handle(Channels.WINDOW_MINIMIZE, () => {
|
|
22343
|
-
mainWindow.minimize();
|
|
22344
|
-
});
|
|
22345
|
-
electron.ipcMain.handle(Channels.WINDOW_MAXIMIZE, () => {
|
|
22346
|
-
if (mainWindow.isMaximized()) {
|
|
22347
|
-
mainWindow.unmaximize();
|
|
22348
|
-
} else {
|
|
22349
|
-
mainWindow.maximize();
|
|
22350
|
-
}
|
|
22351
|
-
});
|
|
22352
|
-
electron.ipcMain.handle(Channels.WINDOW_CLOSE, () => {
|
|
22353
|
-
mainWindow.close();
|
|
22354
|
-
});
|
|
23365
|
+
registerVaultHandlers();
|
|
23366
|
+
registerWindowControlHandlers(mainWindow);
|
|
22355
23367
|
electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, () => {
|
|
22356
23368
|
return getInstalledKits();
|
|
22357
23369
|
});
|
|
@@ -22363,6 +23375,8 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22363
23375
|
return uninstallKit(id, getScheduledKitIds());
|
|
22364
23376
|
});
|
|
22365
23377
|
registerScheduleHandlers(windowState, runtime2, sendToRendererViews);
|
|
23378
|
+
registerAutofillHandlers(windowState);
|
|
23379
|
+
registerPageDiffHandlers(windowState, sendToRendererViews);
|
|
22366
23380
|
}
|
|
22367
23381
|
function makeStep(label, status = "pending") {
|
|
22368
23382
|
return { label, status };
|
|
@@ -23815,10 +24829,12 @@ electron.app.on("window-all-closed", () => {
|
|
|
23815
24829
|
stopBackgroundRevalidation();
|
|
23816
24830
|
void Promise.all([
|
|
23817
24831
|
runtime?.flushPersist() ?? Promise.resolve(),
|
|
23818
|
-
flushPersist(),
|
|
23819
24832
|
flushPersist$1(),
|
|
24833
|
+
flushPersist$3(),
|
|
24834
|
+
flushPersist$4(),
|
|
24835
|
+
flushPersist(),
|
|
23820
24836
|
flushPersist$2(),
|
|
23821
|
-
flushPersist$
|
|
24837
|
+
flushPersist$5()
|
|
23822
24838
|
]).finally(() => {
|
|
23823
24839
|
void stopMcpServer().finally(() => {
|
|
23824
24840
|
electron.app.quit();
|