unbrowse 3.5.4 → 3.6.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2544 -339
- package/dist/mcp.js +12 -6
- package/dist/server.js +730 -43
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ var __promiseAll = (args) => Promise.all(args);
|
|
|
31
31
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
32
32
|
|
|
33
33
|
// ../../src/build-info.generated.ts
|
|
34
|
-
var BUILD_RELEASE_VERSION = "3.
|
|
34
|
+
var BUILD_RELEASE_VERSION = "3.6.0-preview.0", BUILD_GIT_SHA = "53592d89047b", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy42LjAtcHJldmlldy4wIiwiZ2l0X3NoYSI6IjUzNTkyZDg5MDQ3YiIsImNvZGVfaGFzaCI6IjVkOWViZjYxOWM2MSIsInRyYWNlX3ZlcnNpb24iOiI1ZDllYmY2MTljNjFANTM1OTJkODkwNDdiIiwiaXNzdWVkX2F0IjoiMjAyNi0wNC0wOVQyMDo0MTozMC4wODJaIn0", BUILD_RELEASE_MANIFEST_SIGNATURE = "HlE5m4HLbj0SjURsLry2pCTQTH-KmnP05XG5sH6-CSk", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
|
|
35
35
|
|
|
36
36
|
// ../../src/version.ts
|
|
37
37
|
import { createHash } from "crypto";
|
|
@@ -529,6 +529,11 @@ function getRegistrableDomain(hostname2) {
|
|
|
529
529
|
}
|
|
530
530
|
return parts.slice(-2).join(".");
|
|
531
531
|
}
|
|
532
|
+
function isDomainMatch(cookieDomain, targetDomain) {
|
|
533
|
+
const c = cookieDomain.replace(/^\./, "").toLowerCase();
|
|
534
|
+
const t = targetDomain.replace(/^\./, "").toLowerCase();
|
|
535
|
+
return t === c || t.endsWith("." + c);
|
|
536
|
+
}
|
|
532
537
|
var CC_TLDS, GEO_TLD_SUFFIXES;
|
|
533
538
|
var init_domain = __esm(() => {
|
|
534
539
|
CC_TLDS = new Set([
|
|
@@ -634,6 +639,7 @@ var init_logger = __esm(() => {
|
|
|
634
639
|
// ../../src/kuri/client.ts
|
|
635
640
|
import { execFileSync as execFileSync2, spawn as spawn2 } from "node:child_process";
|
|
636
641
|
import { existsSync as existsSync9 } from "node:fs";
|
|
642
|
+
import net from "node:net";
|
|
637
643
|
import path5 from "node:path";
|
|
638
644
|
function createBrokerState(port = KURI_DEFAULT_PORT) {
|
|
639
645
|
return {
|
|
@@ -646,6 +652,39 @@ function createBrokerState(port = KURI_DEFAULT_PORT) {
|
|
|
646
652
|
requestedPort: port
|
|
647
653
|
};
|
|
648
654
|
}
|
|
655
|
+
function brokerCacheKey(port) {
|
|
656
|
+
return port === undefined ? "default" : `port:${port}`;
|
|
657
|
+
}
|
|
658
|
+
function rememberBrokerClient(client, state) {
|
|
659
|
+
brokerClients.set(brokerCacheKey(state.requestedPort), client);
|
|
660
|
+
brokerClients.set(brokerCacheKey(state.port), client);
|
|
661
|
+
}
|
|
662
|
+
function forgetBrokerClient(state) {
|
|
663
|
+
brokerClients.delete(brokerCacheKey(state.requestedPort));
|
|
664
|
+
brokerClients.delete(brokerCacheKey(state.port));
|
|
665
|
+
}
|
|
666
|
+
function envFlag(value) {
|
|
667
|
+
return value === "1" || value?.toLowerCase() === "true";
|
|
668
|
+
}
|
|
669
|
+
function falseyEnv(value) {
|
|
670
|
+
if (!value)
|
|
671
|
+
return false;
|
|
672
|
+
const normalized = value.trim().toLowerCase();
|
|
673
|
+
return normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off";
|
|
674
|
+
}
|
|
675
|
+
function resolveKuriLaunchConfig(env = process.env) {
|
|
676
|
+
const explicitHeadless = env.KURI_HEADLESS ?? env.HEADLESS;
|
|
677
|
+
const headless = explicitHeadless !== undefined ? envFlag(explicitHeadless) : process.platform === "linux" && !env.DISPLAY;
|
|
678
|
+
const cleanRoom = envFlag(env.UNBROWSE_LOCAL_ONLY) || envFlag(env.KURI_CLEAN_ROOM);
|
|
679
|
+
const browserCookieOptOut = falseyEnv(env.UNBROWSE_IMPORT_BROWSER_COOKIES);
|
|
680
|
+
const explicitAttach = envFlag(env.KURI_ATTACH_EXISTING_CHROME ?? env.UNBROWSE_ATTACH_EXISTING_CHROME);
|
|
681
|
+
const disableCdpAttach = envFlag(env.KURI_DISABLE_CDP_ATTACH);
|
|
682
|
+
const canAttachToExistingChrome = !headless && !disableCdpAttach && !cleanRoom;
|
|
683
|
+
return {
|
|
684
|
+
headless,
|
|
685
|
+
attachToExistingChrome: canAttachToExistingChrome && (explicitAttach || browserCookieOptOut)
|
|
686
|
+
};
|
|
687
|
+
}
|
|
649
688
|
function kuriBinaryName() {
|
|
650
689
|
return process.platform === "win32" ? "kuri.exe" : "kuri";
|
|
651
690
|
}
|
|
@@ -704,13 +743,676 @@ function getKuriBinaryCandidates() {
|
|
|
704
743
|
addCandidate(candidates, resolveBinaryOnPath("kuri"));
|
|
705
744
|
return candidates;
|
|
706
745
|
}
|
|
746
|
+
async function discoverCdpPort(state) {
|
|
747
|
+
const portsToTry = [9222, 9223, 9224, 9225];
|
|
748
|
+
for (const port of portsToTry) {
|
|
749
|
+
try {
|
|
750
|
+
const res = await fetch(`http://127.0.0.1:${port}/json/version`, {
|
|
751
|
+
signal: AbortSignal.timeout(500)
|
|
752
|
+
});
|
|
753
|
+
if (res.ok) {
|
|
754
|
+
state.cdpPort = port;
|
|
755
|
+
kuriCdpPort = port;
|
|
756
|
+
log("kuri", `found Chrome CDP on port ${port}`);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
} catch {}
|
|
760
|
+
}
|
|
761
|
+
log("kuri", "could not discover CDP port — tab discovery may fail");
|
|
762
|
+
}
|
|
763
|
+
async function findFreeCdpPort() {
|
|
764
|
+
for (let port = 9222;port < 9230; port++) {
|
|
765
|
+
try {
|
|
766
|
+
await fetch(`http://127.0.0.1:${port}/json/version`, {
|
|
767
|
+
signal: AbortSignal.timeout(300)
|
|
768
|
+
});
|
|
769
|
+
} catch {
|
|
770
|
+
return port;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return 9222;
|
|
774
|
+
}
|
|
775
|
+
async function isKuriHealthyOnPort(port) {
|
|
776
|
+
try {
|
|
777
|
+
const health = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
778
|
+
signal: AbortSignal.timeout(1000)
|
|
779
|
+
});
|
|
780
|
+
return health.ok;
|
|
781
|
+
} catch {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async function isChromeCdpAvailable(port) {
|
|
786
|
+
try {
|
|
787
|
+
const res = await fetch(`http://127.0.0.1:${port}/json/version`, {
|
|
788
|
+
signal: AbortSignal.timeout(1000)
|
|
789
|
+
});
|
|
790
|
+
return res.ok;
|
|
791
|
+
} catch {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
async function listRegisteredTabs(state) {
|
|
796
|
+
try {
|
|
797
|
+
const tabs = await kuriGet(state, "/tabs");
|
|
798
|
+
if (!Array.isArray(tabs))
|
|
799
|
+
return [];
|
|
800
|
+
return tabs.filter((tab) => typeof tab?.id === "string").map((tab) => ({ id: tab.id, url: tab.url ?? "", title: tab.title }));
|
|
801
|
+
} catch {
|
|
802
|
+
return [];
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
async function waitForChromeCdpReady(state, timeoutMs = KURI_CDP_READY_TIMEOUT_MS) {
|
|
806
|
+
if (typeof state.cdpPort !== "number")
|
|
807
|
+
return false;
|
|
808
|
+
const deadline = Date.now() + timeoutMs;
|
|
809
|
+
while (Date.now() < deadline) {
|
|
810
|
+
if (await isChromeCdpAvailable(state.cdpPort))
|
|
811
|
+
return true;
|
|
812
|
+
await new Promise((resolve) => setTimeout(resolve, KURI_CDP_POLL_INTERVAL_MS));
|
|
813
|
+
}
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
function shouldReuseManagedChrome(launchConfig, state, managedChromeAvailable) {
|
|
817
|
+
return !launchConfig.attachToExistingChrome && state.managedChrome === true && typeof state.cdpPort === "number" && managedChromeAvailable;
|
|
818
|
+
}
|
|
819
|
+
async function isTcpPortOpen(port, timeoutMs = 400) {
|
|
820
|
+
return await new Promise((resolve) => {
|
|
821
|
+
const socket = net.createConnection({ host: "127.0.0.1", port });
|
|
822
|
+
const finish = (open) => {
|
|
823
|
+
socket.removeAllListeners();
|
|
824
|
+
socket.destroy();
|
|
825
|
+
resolve(open);
|
|
826
|
+
};
|
|
827
|
+
socket.setTimeout(timeoutMs);
|
|
828
|
+
socket.once("connect", () => finish(true));
|
|
829
|
+
socket.once("timeout", () => finish(false));
|
|
830
|
+
socket.once("error", () => finish(false));
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
async function resolveKuriPort(preferredPort, deps = {}) {
|
|
834
|
+
const isHealthyPort = deps.isHealthyPort ?? isKuriHealthyOnPort;
|
|
835
|
+
const isPortOpen = deps.isPortOpen ?? isTcpPortOpen;
|
|
836
|
+
const searchLimit = deps.searchLimit ?? KURI_PORT_SEARCH_LIMIT;
|
|
837
|
+
if (await isHealthyPort(preferredPort))
|
|
838
|
+
return preferredPort;
|
|
839
|
+
if (!await isPortOpen(preferredPort))
|
|
840
|
+
return preferredPort;
|
|
841
|
+
for (let candidate = preferredPort + 1;candidate <= preferredPort + searchLimit; candidate++) {
|
|
842
|
+
if (await isHealthyPort(candidate))
|
|
843
|
+
return candidate;
|
|
844
|
+
if (!await isPortOpen(candidate))
|
|
845
|
+
return candidate;
|
|
846
|
+
}
|
|
847
|
+
return preferredPort;
|
|
848
|
+
}
|
|
849
|
+
async function ensureUserChromeRunning(state) {
|
|
850
|
+
for (const port2 of [9222, 9223, 9224, 9225]) {
|
|
851
|
+
try {
|
|
852
|
+
const res = await fetch(`http://127.0.0.1:${port2}/json/version`, { signal: AbortSignal.timeout(500) });
|
|
853
|
+
if (res.ok) {
|
|
854
|
+
state.cdpPort = port2;
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
} catch {}
|
|
858
|
+
}
|
|
859
|
+
const chromePaths = {
|
|
860
|
+
darwin: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
861
|
+
linux: "google-chrome",
|
|
862
|
+
win32: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
|
863
|
+
};
|
|
864
|
+
const chromeBin = chromePaths[process.platform];
|
|
865
|
+
if (!chromeBin)
|
|
866
|
+
return;
|
|
867
|
+
const port = await findFreeCdpPort();
|
|
868
|
+
state.cdpPort = port;
|
|
869
|
+
log("kuri", `launching user Chrome with CDP on port ${port}`);
|
|
870
|
+
try {
|
|
871
|
+
const child = spawn2(chromeBin, [
|
|
872
|
+
`--remote-debugging-port=${port}`,
|
|
873
|
+
"--no-first-run",
|
|
874
|
+
"--no-default-browser-check"
|
|
875
|
+
], {
|
|
876
|
+
stdio: "ignore",
|
|
877
|
+
detached: true
|
|
878
|
+
});
|
|
879
|
+
child.unref();
|
|
880
|
+
const deadline = Date.now() + 8000;
|
|
881
|
+
while (Date.now() < deadline) {
|
|
882
|
+
try {
|
|
883
|
+
const res = await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(500) });
|
|
884
|
+
if (res.ok) {
|
|
885
|
+
log("kuri", `user Chrome ready with CDP on port ${port}`);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
} catch {}
|
|
889
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
890
|
+
}
|
|
891
|
+
log("kuri", "user Chrome launched but CDP not responding — Kuri will launch managed Chrome");
|
|
892
|
+
state.cdpPort = null;
|
|
893
|
+
} catch (err) {
|
|
894
|
+
log("kuri", `failed to launch user Chrome: ${err instanceof Error ? err.message : err}`);
|
|
895
|
+
state.cdpPort = null;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
async function terminateBrokerOnPort(port) {
|
|
899
|
+
try {
|
|
900
|
+
const output = execFileSync2("lsof", ["-tiTCP:" + String(port), "-sTCP:LISTEN"], {
|
|
901
|
+
encoding: "utf8",
|
|
902
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
903
|
+
});
|
|
904
|
+
const pids = output.split(/\r?\n/).map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
905
|
+
for (const pid of pids) {
|
|
906
|
+
try {
|
|
907
|
+
process.kill(pid, "SIGTERM");
|
|
908
|
+
} catch {}
|
|
909
|
+
}
|
|
910
|
+
if (pids.length > 0) {
|
|
911
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
912
|
+
}
|
|
913
|
+
} catch {}
|
|
914
|
+
}
|
|
915
|
+
async function reuseHealthyBrokerIfPossible(state, launchConfig, deps = {}) {
|
|
916
|
+
const isHealthyPort = deps.isHealthyPort ?? isKuriHealthyOnPort;
|
|
917
|
+
if (!await isHealthyPort(state.port))
|
|
918
|
+
return false;
|
|
919
|
+
log("kuri", `already running on port ${state.port}`);
|
|
920
|
+
state.ready = true;
|
|
921
|
+
const cdpAvailable = deps.isChromeCdpAvailable ?? isChromeCdpAvailable;
|
|
922
|
+
const discover = deps.discoverCdpPort ?? discoverCdpPort;
|
|
923
|
+
await discover(state);
|
|
924
|
+
if (!state.cdpPort && launchConfig.attachToExistingChrome) {
|
|
925
|
+
const ensureChrome = deps.ensureUserChromeRunning ?? ensureUserChromeRunning;
|
|
926
|
+
await ensureChrome(state);
|
|
927
|
+
}
|
|
928
|
+
if (typeof state.cdpPort === "number" && !await cdpAvailable(state.cdpPort)) {
|
|
929
|
+
state.cdpPort = null;
|
|
930
|
+
}
|
|
931
|
+
const syncTabs = deps.ensureTabsDiscovered ?? ensureTabsDiscovered;
|
|
932
|
+
await syncTabs(state).catch(() => {});
|
|
933
|
+
const tabs = await (deps.listTabs ?? listRegisteredTabs)(state).catch(() => []);
|
|
934
|
+
if (state.cdpPort) {
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
if (tabs.length > 0) {
|
|
938
|
+
log("kuri", `healthy broker on port ${state.port} has stale registered tabs but no CDP; recycling startup path`);
|
|
939
|
+
} else {
|
|
940
|
+
log("kuri", `healthy broker on port ${state.port} has no CDP or tabs; recycling startup path`);
|
|
941
|
+
}
|
|
942
|
+
state.ready = false;
|
|
943
|
+
if (state.process) {
|
|
944
|
+
state.process.kill("SIGTERM");
|
|
945
|
+
await waitForChildExit(state.process);
|
|
946
|
+
state.process = null;
|
|
947
|
+
} else {
|
|
948
|
+
const terminate = deps.terminateBrokerOnPort ?? terminateBrokerOnPort;
|
|
949
|
+
await terminate(state.port);
|
|
950
|
+
}
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
function kuriUrl(state, path6, params) {
|
|
954
|
+
const base = `http://127.0.0.1:${state.port}${path6}`;
|
|
955
|
+
if (!params || Object.keys(params).length === 0)
|
|
956
|
+
return base;
|
|
957
|
+
const parts = Object.entries(params).map(([k, v]) => `${k}=${v.replace(/#/g, "%23").replace(/&/g, "%26").replace(/\+/g, "%2B")}`);
|
|
958
|
+
return `${base}?${parts.join("&")}`;
|
|
959
|
+
}
|
|
960
|
+
function shouldRetryKuriTransportError(error) {
|
|
961
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
962
|
+
return /fetch failed|econnrefused|connection refused|socket hang up|other side closed|transport closed/i.test(message);
|
|
963
|
+
}
|
|
964
|
+
async function kuriGet(state, path6, params, retryOnTransportFailure = true) {
|
|
965
|
+
const url = kuriUrl(state, path6, params);
|
|
966
|
+
const controller = new AbortController;
|
|
967
|
+
const timeout = setTimeout(() => controller.abort(), KURI_REQUEST_TIMEOUT_MS);
|
|
968
|
+
try {
|
|
969
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
970
|
+
const text = await res.text();
|
|
971
|
+
try {
|
|
972
|
+
return JSON.parse(text);
|
|
973
|
+
} catch {
|
|
974
|
+
return text;
|
|
975
|
+
}
|
|
976
|
+
} catch (error) {
|
|
977
|
+
if (retryOnTransportFailure && path6 !== "/health" && shouldRetryKuriTransportError(error)) {
|
|
978
|
+
log("kuri", `transport failed for ${path6}; restarting Kuri and retrying once on port ${state.port}`);
|
|
979
|
+
await stopOn(state);
|
|
980
|
+
await startOn(state, state.requestedPort || state.port);
|
|
981
|
+
return await kuriGet(state, path6, params, false);
|
|
982
|
+
}
|
|
983
|
+
throw error;
|
|
984
|
+
} finally {
|
|
985
|
+
clearTimeout(timeout);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
707
988
|
function findKuriBinary() {
|
|
708
989
|
if (process.env.KURI_BIN)
|
|
709
990
|
return process.env.KURI_BIN;
|
|
710
991
|
const candidates = getKuriBinaryCandidates();
|
|
711
992
|
return candidates.find((candidate) => existsSync9(candidate)) ?? candidates[0] ?? kuriBinaryName();
|
|
712
993
|
}
|
|
713
|
-
|
|
994
|
+
async function waitForChildExit(child, timeoutMs = 2000) {
|
|
995
|
+
if (!child)
|
|
996
|
+
return;
|
|
997
|
+
if (child.exitCode !== null || child.killed)
|
|
998
|
+
return;
|
|
999
|
+
await new Promise((resolve) => {
|
|
1000
|
+
const timer = setTimeout(resolve, timeoutMs);
|
|
1001
|
+
child.once("exit", () => {
|
|
1002
|
+
clearTimeout(timer);
|
|
1003
|
+
resolve();
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
async function startOn(state, port) {
|
|
1008
|
+
const requestedPort = port ?? Number(process.env.KURI_PORT || KURI_DEFAULT_PORT);
|
|
1009
|
+
const externalPort = process.env.KURI_EXTERNAL_PORT;
|
|
1010
|
+
if (externalPort && !state.ready) {
|
|
1011
|
+
const ep = Number(externalPort);
|
|
1012
|
+
if (await isKuriHealthyOnPort(ep)) {
|
|
1013
|
+
state.port = ep;
|
|
1014
|
+
state.requestedPort = ep;
|
|
1015
|
+
state.ready = true;
|
|
1016
|
+
state.managedChrome = false;
|
|
1017
|
+
const cdpUrl = process.env.CDP_URL;
|
|
1018
|
+
if (cdpUrl) {
|
|
1019
|
+
const m = cdpUrl.match(/:(\d+)/);
|
|
1020
|
+
if (m)
|
|
1021
|
+
state.cdpPort = Number(m[1]);
|
|
1022
|
+
}
|
|
1023
|
+
log("kuri", `using external Kuri broker on port ${ep}${state.cdpPort ? ` (CDP port ${state.cdpPort})` : ""}`);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
log("kuri", `external Kuri on port ${ep} not healthy — falling through to normal start`);
|
|
1027
|
+
}
|
|
1028
|
+
if (state.ready) {
|
|
1029
|
+
const activePort = state.port || requestedPort;
|
|
1030
|
+
if (await isKuriHealthyOnPort(activePort))
|
|
1031
|
+
return;
|
|
1032
|
+
log("kuri", `cached ready state stale on port ${activePort}; restarting`);
|
|
1033
|
+
state.ready = false;
|
|
1034
|
+
state.process = null;
|
|
1035
|
+
state.startPromise = null;
|
|
1036
|
+
}
|
|
1037
|
+
if (state.startPromise)
|
|
1038
|
+
return state.startPromise;
|
|
1039
|
+
const startPromise = (async () => {
|
|
1040
|
+
const launchConfig = resolveKuriLaunchConfig();
|
|
1041
|
+
state.requestedPort = requestedPort;
|
|
1042
|
+
state.port = await resolveKuriPort(requestedPort);
|
|
1043
|
+
const existingClient = brokerClients.get(brokerCacheKey(requestedPort));
|
|
1044
|
+
if (existingClient)
|
|
1045
|
+
rememberBrokerClient(existingClient, state);
|
|
1046
|
+
if (state.port !== requestedPort) {
|
|
1047
|
+
log("kuri", `preferred port ${requestedPort} is occupied but unhealthy; falling back to ${state.port}`);
|
|
1048
|
+
}
|
|
1049
|
+
if (await reuseHealthyBrokerIfPossible(state, launchConfig))
|
|
1050
|
+
return;
|
|
1051
|
+
const binary = findKuriBinary();
|
|
1052
|
+
log("kuri", `starting: ${binary} on port ${state.port}`);
|
|
1053
|
+
if (!existsSync9(binary)) {
|
|
1054
|
+
throw new Error(`Kuri binary not found at ${binary}`);
|
|
1055
|
+
}
|
|
1056
|
+
const reusableManagedChrome = shouldReuseManagedChrome(launchConfig, state, typeof state.cdpPort === "number" && await isChromeCdpAvailable(state.cdpPort));
|
|
1057
|
+
if (launchConfig.attachToExistingChrome) {
|
|
1058
|
+
await discoverCdpPort(state);
|
|
1059
|
+
state.managedChrome = false;
|
|
1060
|
+
} else if (reusableManagedChrome) {
|
|
1061
|
+
log("kuri", `reconnecting to surviving managed Chrome on port ${state.cdpPort}`);
|
|
1062
|
+
} else {
|
|
1063
|
+
state.cdpPort = null;
|
|
1064
|
+
state.managedChrome = false;
|
|
1065
|
+
}
|
|
1066
|
+
const env = {
|
|
1067
|
+
...process.env,
|
|
1068
|
+
PORT: String(state.port),
|
|
1069
|
+
HOST: "127.0.0.1",
|
|
1070
|
+
HEADLESS: launchConfig.headless ? "true" : "false"
|
|
1071
|
+
};
|
|
1072
|
+
if (state.cdpPort && (launchConfig.attachToExistingChrome || reusableManagedChrome)) {
|
|
1073
|
+
env.CDP_URL = `ws://127.0.0.1:${state.cdpPort}`;
|
|
1074
|
+
log("kuri", reusableManagedChrome ? `connecting to surviving managed Chrome on port ${state.cdpPort}` : `connecting to existing Chrome on port ${state.cdpPort}`);
|
|
1075
|
+
} else if (launchConfig.headless) {
|
|
1076
|
+
log("kuri", "starting in headless mode with managed Chrome");
|
|
1077
|
+
} else {
|
|
1078
|
+
log("kuri", "no existing Chrome found — Kuri will launch managed Chrome");
|
|
1079
|
+
}
|
|
1080
|
+
const maxAttempts = KURI_SPAWN_RETRIES + 1;
|
|
1081
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
1082
|
+
if (attempt > 1) {
|
|
1083
|
+
log("kuri", `spawn retry ${attempt}/${maxAttempts} after ${KURI_SPAWN_RETRY_DELAY_MS}ms`);
|
|
1084
|
+
await new Promise((r) => setTimeout(r, KURI_SPAWN_RETRY_DELAY_MS));
|
|
1085
|
+
}
|
|
1086
|
+
let exitedBeforeReady = false;
|
|
1087
|
+
state.process = spawn2(binary, [], {
|
|
1088
|
+
env,
|
|
1089
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1090
|
+
});
|
|
1091
|
+
const childPid = state.process.pid;
|
|
1092
|
+
log("kuri", `spawned pid ${childPid ?? "unknown"} on broker port ${state.port}`);
|
|
1093
|
+
state.process.stderr?.on("data", (chunk) => {
|
|
1094
|
+
const line = chunk.toString().trim();
|
|
1095
|
+
if (line)
|
|
1096
|
+
log("kuri", `[stderr] ${line}`);
|
|
1097
|
+
const cdpMatch = line.match(/CDP port:\s*(\d+)/);
|
|
1098
|
+
if (cdpMatch) {
|
|
1099
|
+
state.cdpPort = parseInt(cdpMatch[1], 10);
|
|
1100
|
+
kuriCdpPort = state.cdpPort;
|
|
1101
|
+
log("kuri", `discovered CDP port: ${state.cdpPort}`);
|
|
1102
|
+
}
|
|
1103
|
+
if (/launched Chrome \(pid=\d+\) on CDP port/i.test(line) || /launching managed Chrome instance/i.test(line)) {
|
|
1104
|
+
state.managedChrome = true;
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
state.process.on("exit", (code, signal) => {
|
|
1108
|
+
if (!state.ready)
|
|
1109
|
+
exitedBeforeReady = true;
|
|
1110
|
+
log("kuri", `process exited pid=${childPid ?? "unknown"} code=${code === null ? "null" : code} signal=${signal ?? "none"} broker_port=${state.port} cdp_port=${state.cdpPort ?? "unknown"}`);
|
|
1111
|
+
state.ready = false;
|
|
1112
|
+
state.process = null;
|
|
1113
|
+
forgetBrokerClient(state);
|
|
1114
|
+
});
|
|
1115
|
+
const deadline = Date.now() + KURI_STARTUP_TIMEOUT_MS;
|
|
1116
|
+
while (Date.now() < deadline) {
|
|
1117
|
+
if (exitedBeforeReady)
|
|
1118
|
+
break;
|
|
1119
|
+
try {
|
|
1120
|
+
const res = await fetch(`http://127.0.0.1:${state.port}/health`, {
|
|
1121
|
+
signal: AbortSignal.timeout(500)
|
|
1122
|
+
});
|
|
1123
|
+
if (res.ok) {
|
|
1124
|
+
state.ready = true;
|
|
1125
|
+
log("kuri", `ready on port ${state.port}`);
|
|
1126
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
1127
|
+
if (!state.cdpPort)
|
|
1128
|
+
await discoverCdpPort(state);
|
|
1129
|
+
await waitForChromeCdpReady(state).catch(() => false);
|
|
1130
|
+
await ensureTabsDiscovered(state);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
} catch {}
|
|
1134
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
1135
|
+
}
|
|
1136
|
+
if (state.ready)
|
|
1137
|
+
return;
|
|
1138
|
+
if (state.process) {
|
|
1139
|
+
state.process.kill();
|
|
1140
|
+
await waitForChildExit(state.process);
|
|
1141
|
+
}
|
|
1142
|
+
try {
|
|
1143
|
+
execFileSync2("pkill", ["-f", `remote-debugging-port=${state.cdpPort ?? 9222}`], { stdio: "ignore" });
|
|
1144
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
1145
|
+
} catch {}
|
|
1146
|
+
}
|
|
1147
|
+
throw new Error(`Kuri failed to start after ${maxAttempts} attempts`);
|
|
1148
|
+
})();
|
|
1149
|
+
state.startPromise = startPromise.finally(() => {
|
|
1150
|
+
if (state.startPromise === startPromise) {
|
|
1151
|
+
state.startPromise = null;
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
return state.startPromise;
|
|
1155
|
+
}
|
|
1156
|
+
async function stopOn(state) {
|
|
1157
|
+
if (state.startPromise) {
|
|
1158
|
+
await state.startPromise.catch(() => {});
|
|
1159
|
+
}
|
|
1160
|
+
if (state.process) {
|
|
1161
|
+
state.process.kill("SIGTERM");
|
|
1162
|
+
state.process = null;
|
|
1163
|
+
}
|
|
1164
|
+
state.ready = false;
|
|
1165
|
+
state.cdpPort = null;
|
|
1166
|
+
kuriCdpPort = null;
|
|
1167
|
+
state.managedChrome = false;
|
|
1168
|
+
state.startPromise = null;
|
|
1169
|
+
forgetBrokerClient(state);
|
|
1170
|
+
}
|
|
1171
|
+
async function getDefaultTabOn(state) {
|
|
1172
|
+
await ensureTabsDiscovered(state);
|
|
1173
|
+
try {
|
|
1174
|
+
const tabs = await kuriGet(state, "/tabs");
|
|
1175
|
+
if (Array.isArray(tabs) && tabs.length > 0)
|
|
1176
|
+
return tabs[0].id;
|
|
1177
|
+
} catch {}
|
|
1178
|
+
const cdpTabId = await createTabViaChromeCdp("about:blank", state);
|
|
1179
|
+
if (cdpTabId)
|
|
1180
|
+
return cdpTabId;
|
|
1181
|
+
throw new Error("No tabs available and failed to create one");
|
|
1182
|
+
}
|
|
1183
|
+
async function start(port, state = defaultBrokerState) {
|
|
1184
|
+
return startOn(state, port);
|
|
1185
|
+
}
|
|
1186
|
+
async function getDefaultTab(state = defaultBrokerState) {
|
|
1187
|
+
return getDefaultTabOn(state);
|
|
1188
|
+
}
|
|
1189
|
+
async function ensureTabsDiscovered(state) {
|
|
1190
|
+
try {
|
|
1191
|
+
if (state.cdpPort)
|
|
1192
|
+
await waitForChromeCdpReady(state).catch(() => false);
|
|
1193
|
+
const params = {};
|
|
1194
|
+
if (state.cdpPort)
|
|
1195
|
+
params.cdp_url = `ws://127.0.0.1:${state.cdpPort}`;
|
|
1196
|
+
await kuriGet(state, "/discover", params);
|
|
1197
|
+
} catch {}
|
|
1198
|
+
}
|
|
1199
|
+
async function waitForTabRegistration(state, tabId, timeoutMs = 2000) {
|
|
1200
|
+
const deadline = Date.now() + timeoutMs;
|
|
1201
|
+
while (Date.now() < deadline) {
|
|
1202
|
+
await ensureTabsDiscovered(state);
|
|
1203
|
+
try {
|
|
1204
|
+
const tabs = await kuriGet(state, "/tabs");
|
|
1205
|
+
if (Array.isArray(tabs) && tabs.some((tab) => tab?.id === tabId))
|
|
1206
|
+
return;
|
|
1207
|
+
} catch {}
|
|
1208
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
async function createTabViaChromeCdp(url = "about:blank", state = defaultBrokerState) {
|
|
1212
|
+
if (!state.cdpPort)
|
|
1213
|
+
return "";
|
|
1214
|
+
for (let attempt = 0;attempt < KURI_TAB_CREATE_RETRIES; attempt += 1) {
|
|
1215
|
+
await waitForChromeCdpReady(state).catch(() => false);
|
|
1216
|
+
try {
|
|
1217
|
+
const res = await fetch(`http://127.0.0.1:${state.cdpPort}/json/new?${url}`, {
|
|
1218
|
+
method: "PUT",
|
|
1219
|
+
signal: AbortSignal.timeout(5000)
|
|
1220
|
+
});
|
|
1221
|
+
const target = await res.json();
|
|
1222
|
+
const tabId = target?.id ?? target?.targetId ?? "";
|
|
1223
|
+
if (tabId) {
|
|
1224
|
+
log("kuri", `created new Chrome tab: ${tabId}`);
|
|
1225
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1226
|
+
await ensureTabsDiscovered(state).catch(() => {});
|
|
1227
|
+
await waitForTabRegistration(state, tabId).catch(() => {});
|
|
1228
|
+
return tabId;
|
|
1229
|
+
}
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
if (attempt === KURI_TAB_CREATE_RETRIES - 1) {
|
|
1232
|
+
log("kuri", `Chrome tab creation failed: ${err instanceof Error ? err.message : err}`);
|
|
1233
|
+
return "";
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
await new Promise((resolve) => setTimeout(resolve, KURI_CDP_POLL_INTERVAL_MS));
|
|
1237
|
+
}
|
|
1238
|
+
return "";
|
|
1239
|
+
}
|
|
1240
|
+
async function navigate(tabId, url, state = defaultBrokerState) {
|
|
1241
|
+
await kuriGet(state, "/navigate", { tab_id: tabId, url });
|
|
1242
|
+
}
|
|
1243
|
+
async function evaluate(tabId, expression, state = defaultBrokerState) {
|
|
1244
|
+
let raw;
|
|
1245
|
+
if (expression.length > 2000) {
|
|
1246
|
+
const url = kuriUrl(state, "/evaluate", { tab_id: tabId, expression });
|
|
1247
|
+
const controller = new AbortController;
|
|
1248
|
+
const timeout = setTimeout(() => controller.abort(), KURI_REQUEST_TIMEOUT_MS);
|
|
1249
|
+
try {
|
|
1250
|
+
const res = await fetch(url, {
|
|
1251
|
+
method: "POST",
|
|
1252
|
+
headers: { "Content-Type": "text/plain" },
|
|
1253
|
+
body: expression,
|
|
1254
|
+
signal: controller.signal
|
|
1255
|
+
});
|
|
1256
|
+
const text = await res.text();
|
|
1257
|
+
try {
|
|
1258
|
+
raw = JSON.parse(text);
|
|
1259
|
+
} catch {
|
|
1260
|
+
raw = text;
|
|
1261
|
+
}
|
|
1262
|
+
} finally {
|
|
1263
|
+
clearTimeout(timeout);
|
|
1264
|
+
}
|
|
1265
|
+
} else {
|
|
1266
|
+
raw = await kuriGet(state, "/evaluate", { tab_id: tabId, expression });
|
|
1267
|
+
}
|
|
1268
|
+
const inner = raw?.result?.result;
|
|
1269
|
+
if (!inner)
|
|
1270
|
+
return raw;
|
|
1271
|
+
if (inner.type === "undefined")
|
|
1272
|
+
return;
|
|
1273
|
+
if ("value" in inner)
|
|
1274
|
+
return inner.value;
|
|
1275
|
+
return inner.description ?? raw;
|
|
1276
|
+
}
|
|
1277
|
+
async function getCookies(tabId, state = defaultBrokerState) {
|
|
1278
|
+
const raw = await kuriGet(state, "/cookies", { tab_id: tabId });
|
|
1279
|
+
return raw?.result?.cookies ?? [];
|
|
1280
|
+
}
|
|
1281
|
+
async function setCookieViaCDP(wsUrl, cookie) {
|
|
1282
|
+
return new Promise((resolve) => {
|
|
1283
|
+
const timer = setTimeout(() => {
|
|
1284
|
+
resolve(false);
|
|
1285
|
+
}, 3000);
|
|
1286
|
+
try {
|
|
1287
|
+
const ws = new (__require("ws"))(wsUrl);
|
|
1288
|
+
ws.on("open", () => {
|
|
1289
|
+
ws.send(JSON.stringify({
|
|
1290
|
+
id: 1,
|
|
1291
|
+
method: "Network.setCookie",
|
|
1292
|
+
params: {
|
|
1293
|
+
...cookie,
|
|
1294
|
+
url: `https://${cookie.domain.replace(/^\./, "")}/`
|
|
1295
|
+
}
|
|
1296
|
+
}));
|
|
1297
|
+
});
|
|
1298
|
+
ws.on("message", (data) => {
|
|
1299
|
+
clearTimeout(timer);
|
|
1300
|
+
try {
|
|
1301
|
+
const msg = JSON.parse(data.toString());
|
|
1302
|
+
if (msg.id === 1) {
|
|
1303
|
+
ws.close();
|
|
1304
|
+
resolve(msg.result?.success ?? false);
|
|
1305
|
+
}
|
|
1306
|
+
} catch {
|
|
1307
|
+
ws.close();
|
|
1308
|
+
resolve(false);
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
ws.on("error", () => {
|
|
1312
|
+
clearTimeout(timer);
|
|
1313
|
+
resolve(false);
|
|
1314
|
+
});
|
|
1315
|
+
} catch {
|
|
1316
|
+
clearTimeout(timer);
|
|
1317
|
+
resolve(false);
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
async function resolveCdpDebuggerUrlForTab(tabId) {
|
|
1322
|
+
const portsToTry = Array.from(new Set([kuriCdpPort, 9222, 9223, 9224, 9225].filter((port) => typeof port === "number")));
|
|
1323
|
+
for (const port of portsToTry) {
|
|
1324
|
+
try {
|
|
1325
|
+
const res = await fetch(`http://127.0.0.1:${port}/json`, { signal: AbortSignal.timeout(1000) });
|
|
1326
|
+
if (!res.ok)
|
|
1327
|
+
continue;
|
|
1328
|
+
const pages = await res.json();
|
|
1329
|
+
const page = pages.find((candidate) => candidate.id === tabId);
|
|
1330
|
+
if (page?.webSocketDebuggerUrl) {
|
|
1331
|
+
if (kuriCdpPort !== port) {
|
|
1332
|
+
kuriCdpPort = port;
|
|
1333
|
+
log("kuri", `updated CDP port from tab discovery: ${port}`);
|
|
1334
|
+
}
|
|
1335
|
+
return page.webSocketDebuggerUrl;
|
|
1336
|
+
}
|
|
1337
|
+
} catch {}
|
|
1338
|
+
}
|
|
1339
|
+
return null;
|
|
1340
|
+
}
|
|
1341
|
+
async function setCookie(tabId, cookie, state = defaultBrokerState) {
|
|
1342
|
+
const value = cookie.value.replace(/^"|"$/g, "");
|
|
1343
|
+
if (cookie.secure || cookie.httpOnly) {
|
|
1344
|
+
try {
|
|
1345
|
+
const debuggerUrl = await resolveCdpDebuggerUrlForTab(tabId);
|
|
1346
|
+
if (debuggerUrl) {
|
|
1347
|
+
const success = await setCookieViaCDP(debuggerUrl, {
|
|
1348
|
+
name: cookie.name,
|
|
1349
|
+
value,
|
|
1350
|
+
domain: cookie.domain,
|
|
1351
|
+
path: cookie.path || "/",
|
|
1352
|
+
secure: cookie.secure ?? false,
|
|
1353
|
+
httpOnly: cookie.httpOnly ?? false,
|
|
1354
|
+
sameSite: cookie.sameSite || "Lax",
|
|
1355
|
+
...cookie.expires && cookie.expires > 0 ? { expires: cookie.expires } : {}
|
|
1356
|
+
});
|
|
1357
|
+
if (success)
|
|
1358
|
+
return;
|
|
1359
|
+
log("kuri", `CDP cookie set failed for ${cookie.name} on ${cookie.domain}; falling back to /cookies`);
|
|
1360
|
+
} else {
|
|
1361
|
+
log("kuri", `no CDP websocket for tab ${tabId}; falling back to /cookies for secure cookie ${cookie.name}`);
|
|
1362
|
+
}
|
|
1363
|
+
} catch {}
|
|
1364
|
+
}
|
|
1365
|
+
await kuriGet(state, "/cookies", {
|
|
1366
|
+
tab_id: tabId,
|
|
1367
|
+
name: cookie.name,
|
|
1368
|
+
value,
|
|
1369
|
+
domain: cookie.domain,
|
|
1370
|
+
...cookie.path ? { path: cookie.path } : {}
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
async function networkEnable(tabId, state = defaultBrokerState) {
|
|
1374
|
+
await kuriGet(state, "/network", { tab_id: tabId, mode: "enable" });
|
|
1375
|
+
}
|
|
1376
|
+
async function getCurrentUrl(tabId, state = defaultBrokerState) {
|
|
1377
|
+
const result = await evaluate(tabId, "window.location.href", state);
|
|
1378
|
+
return typeof result === "string" ? result : "";
|
|
1379
|
+
}
|
|
1380
|
+
async function action(tabId, actionType, ref, value, state = defaultBrokerState) {
|
|
1381
|
+
const params = { tab_id: tabId, action: actionType, ref };
|
|
1382
|
+
if (value !== undefined)
|
|
1383
|
+
params.value = value;
|
|
1384
|
+
return kuriGet(state, "/action", params);
|
|
1385
|
+
}
|
|
1386
|
+
async function click(tabId, ref, state = defaultBrokerState) {
|
|
1387
|
+
await scrollIntoView(tabId, ref, state);
|
|
1388
|
+
return action(tabId, "click", ref, undefined, state);
|
|
1389
|
+
}
|
|
1390
|
+
async function fill(tabId, ref, value, state = defaultBrokerState) {
|
|
1391
|
+
await click(tabId, ref, state);
|
|
1392
|
+
const result = await action(tabId, "fill", ref, value, state);
|
|
1393
|
+
const currentValue = await evaluate(tabId, `(() => {
|
|
1394
|
+
const active = document.activeElement;
|
|
1395
|
+
return active && "value" in active ? active.value : undefined;
|
|
1396
|
+
})()`, state);
|
|
1397
|
+
if (currentValue !== value) {
|
|
1398
|
+
return evaluate(tabId, `(function() {
|
|
1399
|
+
const active = document.activeElement;
|
|
1400
|
+
if (!active || !("value" in active)) return false;
|
|
1401
|
+
active.value = ${JSON.stringify(value)};
|
|
1402
|
+
active.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1403
|
+
active.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1404
|
+
return active.value;
|
|
1405
|
+
})()`, state);
|
|
1406
|
+
}
|
|
1407
|
+
return result;
|
|
1408
|
+
}
|
|
1409
|
+
async function scrollIntoView(tabId, ref, state = defaultBrokerState) {
|
|
1410
|
+
return kuriGet(state, "/scrollintoview", { tab_id: tabId, ref });
|
|
1411
|
+
}
|
|
1412
|
+
async function authProfileSave(tabId, name, state = defaultBrokerState) {
|
|
1413
|
+
return kuriGet(state, "/auth/profile/save", { tab_id: tabId, name });
|
|
1414
|
+
}
|
|
1415
|
+
var KURI_DEFAULT_PORT = 7700, KURI_STARTUP_TIMEOUT_MS = 60000, KURI_REQUEST_TIMEOUT_MS = 30000, KURI_SPAWN_RETRIES = 3, KURI_SPAWN_RETRY_DELAY_MS = 1000, KURI_PORT_SEARCH_LIMIT = 10, KURI_CDP_READY_TIMEOUT_MS = 5000, KURI_CDP_POLL_INTERVAL_MS = 200, KURI_TAB_CREATE_RETRIES = 5, kuriCdpPort = null, defaultBrokerState, brokerClients;
|
|
714
1416
|
var init_client2 = __esm(() => {
|
|
715
1417
|
init_logger();
|
|
716
1418
|
init_paths();
|
|
@@ -829,6 +1531,8 @@ var init_reverse_engineer = __esm(() => {
|
|
|
829
1531
|
});
|
|
830
1532
|
|
|
831
1533
|
// ../../src/vault/index.ts
|
|
1534
|
+
import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
|
|
1535
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
832
1536
|
import { join as join7 } from "path";
|
|
833
1537
|
import { homedir as homedir5 } from "os";
|
|
834
1538
|
function normalizeKeytarModule(mod) {
|
|
@@ -845,10 +1549,133 @@ function normalizeKeytarModule(mod) {
|
|
|
845
1549
|
}
|
|
846
1550
|
return null;
|
|
847
1551
|
}
|
|
848
|
-
|
|
1552
|
+
function isKeytarBindingError(error) {
|
|
1553
|
+
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
1554
|
+
return KEYTAR_BINDING_ERROR_RE.test(message);
|
|
1555
|
+
}
|
|
1556
|
+
function disableKeytar(error) {
|
|
1557
|
+
keytar = null;
|
|
1558
|
+
if (keytarFallbackLogged)
|
|
1559
|
+
return;
|
|
1560
|
+
const summary = error instanceof Error ? error.message : String(error);
|
|
1561
|
+
log("vault", `keytar runtime unavailable (${summary}); using encrypted file fallback`);
|
|
1562
|
+
keytarFallbackLogged = true;
|
|
1563
|
+
}
|
|
1564
|
+
async function callKeytar(op) {
|
|
1565
|
+
if (!keytar)
|
|
1566
|
+
return KEYTAR_UNAVAILABLE;
|
|
1567
|
+
try {
|
|
1568
|
+
return await op(keytar);
|
|
1569
|
+
} catch (error) {
|
|
1570
|
+
if (!isKeytarBindingError(error))
|
|
1571
|
+
throw error;
|
|
1572
|
+
disableKeytar(error);
|
|
1573
|
+
return KEYTAR_UNAVAILABLE;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
function getOrCreateKey() {
|
|
1577
|
+
if (!existsSync10(VAULT_DIR))
|
|
1578
|
+
mkdirSync5(VAULT_DIR, { recursive: true, mode: 448 });
|
|
1579
|
+
if (existsSync10(KEY_FILE))
|
|
1580
|
+
return readFileSync6(KEY_FILE);
|
|
1581
|
+
const key = randomBytes2(32);
|
|
1582
|
+
writeFileSync3(KEY_FILE, key, { mode: 384 });
|
|
1583
|
+
return key;
|
|
1584
|
+
}
|
|
1585
|
+
function withVaultLock(fn) {
|
|
1586
|
+
const prev = vaultLock;
|
|
1587
|
+
let release;
|
|
1588
|
+
vaultLock = new Promise((r) => {
|
|
1589
|
+
release = r;
|
|
1590
|
+
});
|
|
1591
|
+
return prev.then(fn).finally(() => release());
|
|
1592
|
+
}
|
|
1593
|
+
function readVaultFile() {
|
|
1594
|
+
if (!existsSync10(VAULT_FILE))
|
|
1595
|
+
return {};
|
|
1596
|
+
try {
|
|
1597
|
+
const key = getOrCreateKey();
|
|
1598
|
+
const raw = readFileSync6(VAULT_FILE);
|
|
1599
|
+
const iv = raw.subarray(0, 16);
|
|
1600
|
+
const enc = raw.subarray(16);
|
|
1601
|
+
const decipher = createDecipheriv("aes-256-cbc", key, iv);
|
|
1602
|
+
const dec = Buffer.concat([decipher.update(enc), decipher.final()]);
|
|
1603
|
+
return JSON.parse(dec.toString("utf8"));
|
|
1604
|
+
} catch {
|
|
1605
|
+
return {};
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function writeVaultFile(data) {
|
|
1609
|
+
const key = getOrCreateKey();
|
|
1610
|
+
const iv = randomBytes2(16);
|
|
1611
|
+
const cipher = createCipheriv("aes-256-cbc", key, iv);
|
|
1612
|
+
const enc = Buffer.concat([cipher.update(JSON.stringify(data), "utf8"), cipher.final()]);
|
|
1613
|
+
writeFileSync3(VAULT_FILE, Buffer.concat([iv, enc]), { mode: 384 });
|
|
1614
|
+
}
|
|
1615
|
+
async function storeCredential(account, value, opts) {
|
|
1616
|
+
const wrapped = {
|
|
1617
|
+
value,
|
|
1618
|
+
stored_at: new Date().toISOString(),
|
|
1619
|
+
expires_at: opts?.expires_at,
|
|
1620
|
+
max_age_ms: opts?.max_age_ms
|
|
1621
|
+
};
|
|
1622
|
+
const serialized = JSON.stringify(wrapped);
|
|
1623
|
+
const keytarResult = await callKeytar((client) => client.setPassword(SERVICE, account, serialized));
|
|
1624
|
+
if (keytarResult !== KEYTAR_UNAVAILABLE)
|
|
1625
|
+
return;
|
|
1626
|
+
await withVaultLock(() => {
|
|
1627
|
+
const data = readVaultFile();
|
|
1628
|
+
data[account] = serialized;
|
|
1629
|
+
writeVaultFile(data);
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
function isExpired(cred) {
|
|
1633
|
+
if (cred.expires_at) {
|
|
1634
|
+
return new Date(cred.expires_at).getTime() <= Date.now();
|
|
1635
|
+
}
|
|
1636
|
+
if (cred.max_age_ms) {
|
|
1637
|
+
return new Date(cred.stored_at).getTime() + cred.max_age_ms <= Date.now();
|
|
1638
|
+
}
|
|
1639
|
+
return false;
|
|
1640
|
+
}
|
|
1641
|
+
async function getCredential(account) {
|
|
1642
|
+
let raw;
|
|
1643
|
+
const keytarResult = await callKeytar((client) => client.getPassword(SERVICE, account));
|
|
1644
|
+
if (keytarResult !== KEYTAR_UNAVAILABLE) {
|
|
1645
|
+
raw = keytarResult;
|
|
1646
|
+
} else {
|
|
1647
|
+
const data = readVaultFile();
|
|
1648
|
+
raw = data[account] ?? null;
|
|
1649
|
+
}
|
|
1650
|
+
if (!raw)
|
|
1651
|
+
return null;
|
|
1652
|
+
try {
|
|
1653
|
+
const parsed = JSON.parse(raw);
|
|
1654
|
+
if (parsed.value && parsed.stored_at) {
|
|
1655
|
+
if (isExpired(parsed)) {
|
|
1656
|
+
await deleteCredential(account);
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
return parsed.value;
|
|
1660
|
+
}
|
|
1661
|
+
} catch {}
|
|
1662
|
+
return raw;
|
|
1663
|
+
}
|
|
1664
|
+
async function deleteCredential(account) {
|
|
1665
|
+
const keytarResult = await callKeytar((client) => client.deletePassword(SERVICE, account));
|
|
1666
|
+
if (keytarResult !== KEYTAR_UNAVAILABLE)
|
|
1667
|
+
return;
|
|
1668
|
+
await withVaultLock(() => {
|
|
1669
|
+
const data = readVaultFile();
|
|
1670
|
+
delete data[account];
|
|
1671
|
+
writeVaultFile(data);
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
var KEYTAR_UNAVAILABLE, KEYTAR_BINDING_ERROR_RE, keytar = null, keytarFallbackLogged = false, SERVICE = "unbrowse", VAULT_DIR, VAULT_FILE, KEY_FILE, vaultLock;
|
|
849
1675
|
var init_vault = __esm(async () => {
|
|
850
1676
|
init_logger();
|
|
851
1677
|
KEYTAR_UNAVAILABLE = Symbol("KEYTAR_UNAVAILABLE");
|
|
1678
|
+
KEYTAR_BINDING_ERROR_RE = /(keytar(?:\.node)?|native bindings?|bindings file|no native build was found|could not locate the bindings file|module did not self-register|err_dlopen_failed|dlopen\(|compiled against a different node\.js version|cannot find module .*keytar|wasm is not supported on this platform|(set|get|delete)password is not a function)/i;
|
|
852
1679
|
try {
|
|
853
1680
|
keytar = normalizeKeytarModule(await import("keytar"));
|
|
854
1681
|
} catch {}
|
|
@@ -866,37 +1693,1061 @@ import { nanoid as nanoid4 } from "nanoid";
|
|
|
866
1693
|
var activeTabRegistry, interceptorInjectedTabs, cdpDocStartTabs, cdpCapturedHeaders;
|
|
867
1694
|
var init_capture = __esm(async () => {
|
|
868
1695
|
init_client2();
|
|
869
|
-
init_domain();
|
|
870
|
-
init_logger();
|
|
871
|
-
init_reverse_engineer();
|
|
872
|
-
init_browser_access();
|
|
873
|
-
await init_vault();
|
|
874
|
-
activeTabRegistry = new Set;
|
|
875
|
-
interceptorInjectedTabs = new Set;
|
|
876
|
-
cdpDocStartTabs = new Set;
|
|
877
|
-
cdpCapturedHeaders = new Map;
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
// ../../src/reverse-engineer/bundle-scanner.ts
|
|
881
|
-
var init_bundle_scanner = __esm(() => {
|
|
1696
|
+
init_domain();
|
|
1697
|
+
init_logger();
|
|
1698
|
+
init_reverse_engineer();
|
|
1699
|
+
init_browser_access();
|
|
1700
|
+
await init_vault();
|
|
1701
|
+
activeTabRegistry = new Set;
|
|
1702
|
+
interceptorInjectedTabs = new Set;
|
|
1703
|
+
cdpDocStartTabs = new Set;
|
|
1704
|
+
cdpCapturedHeaders = new Map;
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1707
|
+
// ../../src/reverse-engineer/bundle-scanner.ts
|
|
1708
|
+
var init_bundle_scanner = __esm(() => {
|
|
1709
|
+
init_logger();
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
// ../../src/reverse-engineer/token-sources.ts
|
|
1713
|
+
var init_token_sources = () => {};
|
|
1714
|
+
|
|
1715
|
+
// ../../src/execution/token-resolver.ts
|
|
1716
|
+
var init_token_resolver = __esm(() => {
|
|
1717
|
+
init_token_sources();
|
|
1718
|
+
});
|
|
1719
|
+
|
|
1720
|
+
// ../../src/auth/agent-mail.ts
|
|
1721
|
+
var exports_agent_mail = {};
|
|
1722
|
+
__export(exports_agent_mail, {
|
|
1723
|
+
waitForVerificationEmail: () => waitForVerificationEmail,
|
|
1724
|
+
tryAgentMailAuth: () => tryAgentMailAuth,
|
|
1725
|
+
isAgentMailAvailable: () => isAgentMailAvailable,
|
|
1726
|
+
getOrCreateSiteInbox: () => getOrCreateSiteInbox,
|
|
1727
|
+
extractVerificationLink: () => extractVerificationLink,
|
|
1728
|
+
extractOtpFromEmail: () => extractOtpFromEmail,
|
|
1729
|
+
autonomousEmailLogin: () => autonomousEmailLogin
|
|
1730
|
+
});
|
|
1731
|
+
import { AgentMailClient } from "agentmail";
|
|
1732
|
+
function getClient() {
|
|
1733
|
+
if (_client)
|
|
1734
|
+
return _client;
|
|
1735
|
+
const apiKey = process.env.AGENTMAIL_API_KEY;
|
|
1736
|
+
if (!apiKey)
|
|
1737
|
+
throw new Error("AGENTMAIL_API_KEY not set — cannot use agent mail");
|
|
1738
|
+
_client = new AgentMailClient({ apiKey });
|
|
1739
|
+
return _client;
|
|
1740
|
+
}
|
|
1741
|
+
async function getStoredInbox(domain) {
|
|
1742
|
+
const key = `${VAULT_PREFIX}${getRegistrableDomain(domain)}`;
|
|
1743
|
+
const raw = await getCredential(key);
|
|
1744
|
+
if (!raw)
|
|
1745
|
+
return null;
|
|
1746
|
+
try {
|
|
1747
|
+
return JSON.parse(raw);
|
|
1748
|
+
} catch {
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
async function storeInbox(domain, inbox) {
|
|
1753
|
+
const key = `${VAULT_PREFIX}${getRegistrableDomain(domain)}`;
|
|
1754
|
+
await storeCredential(key, JSON.stringify(inbox));
|
|
1755
|
+
log("agent-mail", `stored inbox ${inbox.email} for ${domain}`);
|
|
1756
|
+
}
|
|
1757
|
+
async function getOrCreateSiteInbox(domain) {
|
|
1758
|
+
const stored = await getStoredInbox(domain);
|
|
1759
|
+
if (stored) {
|
|
1760
|
+
log("agent-mail", `reusing stored inbox ${stored.email} for ${domain}`);
|
|
1761
|
+
return { inboxId: stored.inboxId, email: stored.email };
|
|
1762
|
+
}
|
|
1763
|
+
const client = getClient();
|
|
1764
|
+
const safeDomain = getRegistrableDomain(domain).replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
1765
|
+
const username = `unbrowse-${safeDomain}`;
|
|
1766
|
+
const clientId = `unbrowse-login-${safeDomain}`;
|
|
1767
|
+
try {
|
|
1768
|
+
const inbox = await client.inboxes.create({
|
|
1769
|
+
username,
|
|
1770
|
+
domain: "agentmail.to",
|
|
1771
|
+
displayName: `Unbrowse Agent - ${domain}`,
|
|
1772
|
+
clientId
|
|
1773
|
+
});
|
|
1774
|
+
const result = { inboxId: inbox.inboxId, email: inbox.email };
|
|
1775
|
+
await storeInbox(domain, { ...result, domain, createdAt: new Date().toISOString() });
|
|
1776
|
+
log("agent-mail", `created inbox ${inbox.email} for ${domain}`);
|
|
1777
|
+
return result;
|
|
1778
|
+
} catch (err) {
|
|
1779
|
+
log("agent-mail", `create failed (likely exists), listing to find match: ${err instanceof Error ? err.message : err}`);
|
|
1780
|
+
try {
|
|
1781
|
+
const list = await client.inboxes.list({ limit: 100 });
|
|
1782
|
+
const match = list.inboxes.find((i) => i.clientId === clientId || i.email === `${username}@agentmail.to`);
|
|
1783
|
+
if (match) {
|
|
1784
|
+
const result = { inboxId: match.inboxId, email: match.email };
|
|
1785
|
+
await storeInbox(domain, { ...result, domain, createdAt: new Date().toISOString() });
|
|
1786
|
+
return result;
|
|
1787
|
+
}
|
|
1788
|
+
} catch {}
|
|
1789
|
+
throw new Error(`Failed to create or find AgentMail inbox for ${domain}`);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
async function waitForVerificationEmail(inboxId, fromDomain, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
1793
|
+
const client = getClient();
|
|
1794
|
+
const start2 = Date.now();
|
|
1795
|
+
const normalizedDomain = fromDomain.toLowerCase();
|
|
1796
|
+
log("agent-mail", `polling inbox ${inboxId} for email from *@${normalizedDomain} (timeout: ${timeoutMs}ms)`);
|
|
1797
|
+
while (Date.now() - start2 < timeoutMs) {
|
|
1798
|
+
try {
|
|
1799
|
+
const response = await client.inboxes.messages.list(inboxId, {
|
|
1800
|
+
limit: 10,
|
|
1801
|
+
labels: ["received"]
|
|
1802
|
+
});
|
|
1803
|
+
for (const item of response.messages ?? []) {
|
|
1804
|
+
const msg = await client.inboxes.messages.get(inboxId, item.messageId);
|
|
1805
|
+
const from = typeof msg.from === "string" ? msg.from : (msg.from ?? []).join(", ");
|
|
1806
|
+
if (from.toLowerCase().includes(normalizedDomain)) {
|
|
1807
|
+
log("agent-mail", `found verification email from ${from}: "${msg.subject}"`);
|
|
1808
|
+
return {
|
|
1809
|
+
messageId: msg.messageId,
|
|
1810
|
+
threadId: msg.threadId,
|
|
1811
|
+
subject: msg.subject ?? "",
|
|
1812
|
+
from,
|
|
1813
|
+
text: msg.extractedText ?? msg.text ?? "",
|
|
1814
|
+
html: msg.extractedHtml ?? msg.html ?? "",
|
|
1815
|
+
receivedAt: msg.createdAt
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
} catch (err) {
|
|
1820
|
+
log("agent-mail", `poll error: ${err instanceof Error ? err.message : err}`);
|
|
1821
|
+
}
|
|
1822
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
1823
|
+
}
|
|
1824
|
+
log("agent-mail", `timeout waiting for email from ${normalizedDomain}`);
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1827
|
+
function extractOtpFromEmail(text) {
|
|
1828
|
+
const codePhrase = text.match(/(?:verification|confirm|security|login|one[- ]time|otp)\s*(?:code|pin|number)[:\s]+(\w{4,8})/i);
|
|
1829
|
+
if (codePhrase)
|
|
1830
|
+
return codePhrase[1];
|
|
1831
|
+
const codeIs = text.match(/(?:code|otp|pin)[:\s]+(\w{4,8})/i);
|
|
1832
|
+
if (codeIs)
|
|
1833
|
+
return codeIs[1];
|
|
1834
|
+
const six = text.match(/\b(\d{6})\b/);
|
|
1835
|
+
if (six)
|
|
1836
|
+
return six[1];
|
|
1837
|
+
const four = text.match(/\b(\d{4})\b/);
|
|
1838
|
+
if (four)
|
|
1839
|
+
return four[1];
|
|
1840
|
+
return null;
|
|
1841
|
+
}
|
|
1842
|
+
function extractVerificationLink(text, html) {
|
|
1843
|
+
const htmlLink = html.match(/href="(https?:\/\/[^"]*(?:verify|confirm|activate|magic|token|auth|callback|validate|approve|email)[^"]*)"/i);
|
|
1844
|
+
if (htmlLink)
|
|
1845
|
+
return htmlLink[1];
|
|
1846
|
+
const textLink = text.match(/(https?:\/\/\S*(?:verify|confirm|activate|magic|token|auth|callback|validate|approve|email)\S*)/i);
|
|
1847
|
+
if (textLink)
|
|
1848
|
+
return textLink[1];
|
|
1849
|
+
const anyHtmlLink = html.match(/href="(https?:\/\/[^"]+)"/);
|
|
1850
|
+
if (anyHtmlLink)
|
|
1851
|
+
return anyHtmlLink[1];
|
|
1852
|
+
const anyTextLink = text.match(/(https?:\/\/\S+)/);
|
|
1853
|
+
if (anyTextLink)
|
|
1854
|
+
return anyTextLink[1];
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1857
|
+
async function autonomousEmailLogin(domain) {
|
|
1858
|
+
const inbox = await getOrCreateSiteInbox(domain);
|
|
1859
|
+
const client = getClient();
|
|
1860
|
+
log("agent-mail", `session ready for ${domain} — email: ${inbox.email}`);
|
|
1861
|
+
return {
|
|
1862
|
+
email: inbox.email,
|
|
1863
|
+
inboxId: inbox.inboxId,
|
|
1864
|
+
domain,
|
|
1865
|
+
waitForOtp: async (timeoutMs = DEFAULT_TIMEOUT_MS) => {
|
|
1866
|
+
const email = await waitForVerificationEmail(inbox.inboxId, domain, timeoutMs);
|
|
1867
|
+
if (!email)
|
|
1868
|
+
return null;
|
|
1869
|
+
const otp = extractOtpFromEmail(email.text);
|
|
1870
|
+
if (otp)
|
|
1871
|
+
log("agent-mail", `extracted OTP: ${otp} from ${email.subject}`);
|
|
1872
|
+
return otp;
|
|
1873
|
+
},
|
|
1874
|
+
waitForLink: async (timeoutMs = DEFAULT_TIMEOUT_MS) => {
|
|
1875
|
+
const email = await waitForVerificationEmail(inbox.inboxId, domain, timeoutMs);
|
|
1876
|
+
if (!email)
|
|
1877
|
+
return null;
|
|
1878
|
+
const link = extractVerificationLink(email.text, email.html);
|
|
1879
|
+
if (link)
|
|
1880
|
+
log("agent-mail", `extracted verification link from ${email.subject}`);
|
|
1881
|
+
return link;
|
|
1882
|
+
},
|
|
1883
|
+
waitForEmail: (timeoutMs = DEFAULT_TIMEOUT_MS) => waitForVerificationEmail(inbox.inboxId, domain, timeoutMs),
|
|
1884
|
+
sendEmail: async (to, subject, body) => {
|
|
1885
|
+
const result = await client.inboxes.messages.send(inbox.inboxId, {
|
|
1886
|
+
to: [to],
|
|
1887
|
+
subject,
|
|
1888
|
+
text: body
|
|
1889
|
+
});
|
|
1890
|
+
log("agent-mail", `sent email to ${to}: "${subject}" (${result.messageId})`);
|
|
1891
|
+
return { messageId: result.messageId, threadId: result.threadId };
|
|
1892
|
+
},
|
|
1893
|
+
replyTo: async (messageId, body) => {
|
|
1894
|
+
const result = await client.inboxes.messages.reply(inbox.inboxId, messageId, {
|
|
1895
|
+
text: body
|
|
1896
|
+
});
|
|
1897
|
+
log("agent-mail", `replied to ${messageId} (${result.messageId})`);
|
|
1898
|
+
return { messageId: result.messageId, threadId: result.threadId };
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
function isAgentMailAvailable() {
|
|
1903
|
+
return Boolean(process.env.AGENTMAIL_API_KEY);
|
|
1904
|
+
}
|
|
1905
|
+
async function tryAgentMailAuth(domain) {
|
|
1906
|
+
if (!isAgentMailAvailable()) {
|
|
1907
|
+
log("agent-mail", `skipping — AGENTMAIL_API_KEY not set`);
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
try {
|
|
1911
|
+
return await autonomousEmailLogin(domain);
|
|
1912
|
+
} catch (err) {
|
|
1913
|
+
log("agent-mail", `auth failed for ${domain}: ${err instanceof Error ? err.message : err}`);
|
|
1914
|
+
return null;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
var POLL_INTERVAL_MS = 3000, DEFAULT_TIMEOUT_MS = 90000, _client = null, VAULT_PREFIX = "agentmail:";
|
|
1918
|
+
var init_agent_mail = __esm(async () => {
|
|
1919
|
+
init_logger();
|
|
1920
|
+
init_domain();
|
|
1921
|
+
await init_vault();
|
|
1922
|
+
});
|
|
1923
|
+
|
|
1924
|
+
// ../../src/auth/browser-cookies.ts
|
|
1925
|
+
var exports_browser_cookies = {};
|
|
1926
|
+
__export(exports_browser_cookies, {
|
|
1927
|
+
scanAllBrowserSessions: () => scanAllBrowserSessions,
|
|
1928
|
+
resolveChromiumCookiesPath: () => resolveChromiumCookiesPath,
|
|
1929
|
+
findBestBrowserSession: () => findBestBrowserSession,
|
|
1930
|
+
extractFromFirefox: () => extractFromFirefox,
|
|
1931
|
+
extractFromChromium: () => extractFromChromium,
|
|
1932
|
+
extractFromChrome: () => extractFromChrome,
|
|
1933
|
+
extractBrowserCookies: () => extractBrowserCookies,
|
|
1934
|
+
decodeChromiumCookieValue: () => decodeChromiumCookieValue
|
|
1935
|
+
});
|
|
1936
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1937
|
+
import { createDecipheriv as createDecipheriv2, pbkdf2Sync } from "node:crypto";
|
|
1938
|
+
import { copyFileSync, existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync3, rmSync } from "node:fs";
|
|
1939
|
+
import { tmpdir, homedir as homedir6, platform } from "node:os";
|
|
1940
|
+
import { join as join8 } from "node:path";
|
|
1941
|
+
function getChromeUserDataDir() {
|
|
1942
|
+
const home = homedir6();
|
|
1943
|
+
if (platform() === "darwin") {
|
|
1944
|
+
return join8(home, "Library", "Application Support", "Google", "Chrome");
|
|
1945
|
+
}
|
|
1946
|
+
if (platform() === "win32") {
|
|
1947
|
+
const appData = process.env.LOCALAPPDATA ?? join8(home, "AppData", "Local");
|
|
1948
|
+
return join8(appData, "Google", "Chrome", "User Data");
|
|
1949
|
+
}
|
|
1950
|
+
return join8(home, ".config", "google-chrome");
|
|
1951
|
+
}
|
|
1952
|
+
function resolveChromiumCookiesPath(opts) {
|
|
1953
|
+
if (opts?.cookieDbPath) {
|
|
1954
|
+
return opts.cookieDbPath.replace(/^~\//, homedir6() + "/");
|
|
1955
|
+
}
|
|
1956
|
+
const profileDir = opts?.profile || "Default";
|
|
1957
|
+
const userDataDir = (opts?.userDataDir || getChromeUserDataDir()).replace(/^~\//, homedir6() + "/");
|
|
1958
|
+
const candidates = [
|
|
1959
|
+
join8(userDataDir, profileDir, "Network", "Cookies"),
|
|
1960
|
+
join8(userDataDir, profileDir, "Cookies"),
|
|
1961
|
+
join8(userDataDir, "Network", "Cookies"),
|
|
1962
|
+
join8(userDataDir, "Cookies")
|
|
1963
|
+
];
|
|
1964
|
+
return candidates.find((candidate) => existsSync11(candidate)) ?? candidates[0] ?? null;
|
|
1965
|
+
}
|
|
1966
|
+
function getFirefoxProfilesRoot() {
|
|
1967
|
+
const home = homedir6();
|
|
1968
|
+
if (platform() === "darwin") {
|
|
1969
|
+
return join8(home, "Library", "Application Support", "Firefox", "Profiles");
|
|
1970
|
+
}
|
|
1971
|
+
if (platform() === "linux") {
|
|
1972
|
+
return join8(home, ".mozilla", "firefox");
|
|
1973
|
+
}
|
|
1974
|
+
if (platform() === "win32") {
|
|
1975
|
+
const appData = process.env.APPDATA;
|
|
1976
|
+
if (!appData)
|
|
1977
|
+
return null;
|
|
1978
|
+
return join8(appData, "Mozilla", "Firefox", "Profiles");
|
|
1979
|
+
}
|
|
1980
|
+
return null;
|
|
1981
|
+
}
|
|
1982
|
+
function pickFirefoxProfile(profilesRoot, profile) {
|
|
1983
|
+
if (profile) {
|
|
1984
|
+
const candidate2 = join8(profilesRoot, profile, "cookies.sqlite");
|
|
1985
|
+
return existsSync11(candidate2) ? candidate2 : null;
|
|
1986
|
+
}
|
|
1987
|
+
const entries = readdirSync3(profilesRoot, { withFileTypes: true });
|
|
1988
|
+
const defaultRelease = entries.find((e) => e.isDirectory() && e.name.includes("default-release"));
|
|
1989
|
+
const targetDir = defaultRelease?.name ?? entries.find((e) => e.isDirectory())?.name;
|
|
1990
|
+
if (!targetDir)
|
|
1991
|
+
return null;
|
|
1992
|
+
const candidate = join8(profilesRoot, targetDir, "cookies.sqlite");
|
|
1993
|
+
return existsSync11(candidate) ? candidate : null;
|
|
1994
|
+
}
|
|
1995
|
+
function getFirefoxCookiesPath(profile) {
|
|
1996
|
+
const profilesRoot = getFirefoxProfilesRoot();
|
|
1997
|
+
if (!profilesRoot || !existsSync11(profilesRoot))
|
|
1998
|
+
return null;
|
|
1999
|
+
return pickFirefoxProfile(profilesRoot, profile);
|
|
2000
|
+
}
|
|
2001
|
+
function getChromiumKeychainServiceName(opts) {
|
|
2002
|
+
if (opts?.safeStorageService)
|
|
2003
|
+
return opts.safeStorageService;
|
|
2004
|
+
return `${opts?.browserName || "Chrome"} Safe Storage`;
|
|
2005
|
+
}
|
|
2006
|
+
function getChromiumDecryptionKey(opts) {
|
|
2007
|
+
const service = getChromiumKeychainServiceName(opts);
|
|
2008
|
+
const cached = _chromiumKeyCache.get(service);
|
|
2009
|
+
if (cached)
|
|
2010
|
+
return cached;
|
|
2011
|
+
if (platform() !== "darwin")
|
|
2012
|
+
return null;
|
|
2013
|
+
try {
|
|
2014
|
+
const keyOutput = execFileSync3("security", ["find-generic-password", "-s", service, "-w"], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2015
|
+
if (!keyOutput)
|
|
2016
|
+
return null;
|
|
2017
|
+
const derived = pbkdf2Sync(keyOutput, "saltysalt", 1003, 16, "sha1");
|
|
2018
|
+
_chromiumKeyCache.set(service, derived);
|
|
2019
|
+
return derived;
|
|
2020
|
+
} catch {
|
|
2021
|
+
return null;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
function decryptChromiumValue(encryptedHex, opts) {
|
|
2025
|
+
try {
|
|
2026
|
+
const buf = Buffer.from(encryptedHex, "hex");
|
|
2027
|
+
if (buf.length < 4)
|
|
2028
|
+
return null;
|
|
2029
|
+
const version = buf.subarray(0, 3).toString("utf8");
|
|
2030
|
+
if (version !== "v10" && version !== "v11") {
|
|
2031
|
+
return buf.toString("utf8");
|
|
2032
|
+
}
|
|
2033
|
+
const key = getChromiumDecryptionKey(opts);
|
|
2034
|
+
if (!key)
|
|
2035
|
+
return null;
|
|
2036
|
+
const payload = buf.subarray(3);
|
|
2037
|
+
if (payload.length >= 48) {
|
|
2038
|
+
try {
|
|
2039
|
+
const iv2 = payload.subarray(16, 32);
|
|
2040
|
+
const encrypted = payload.subarray(32);
|
|
2041
|
+
const decipher2 = createDecipheriv2("aes-128-cbc", key, iv2);
|
|
2042
|
+
decipher2.setAutoPadding(true);
|
|
2043
|
+
const decrypted2 = Buffer.concat([decipher2.update(encrypted), decipher2.final()]);
|
|
2044
|
+
const val = decrypted2.toString("utf8").replace(/[^\x20-\x7E]/g, "");
|
|
2045
|
+
if (val.length > 0)
|
|
2046
|
+
return val;
|
|
2047
|
+
} catch {}
|
|
2048
|
+
}
|
|
2049
|
+
const iv = Buffer.alloc(16, 32);
|
|
2050
|
+
const decipher = createDecipheriv2("aes-128-cbc", key, iv);
|
|
2051
|
+
decipher.setAutoPadding(true);
|
|
2052
|
+
const decrypted = Buffer.concat([decipher.update(payload), decipher.final()]);
|
|
2053
|
+
return decrypted.toString("utf8").replace(/[^\x20-\x7E]/g, "");
|
|
2054
|
+
} catch {
|
|
2055
|
+
return null;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
function decodeChromiumCookieValue(rawValue, encryptedHex, opts) {
|
|
2059
|
+
if (rawValue)
|
|
2060
|
+
return rawValue;
|
|
2061
|
+
if (!encryptedHex)
|
|
2062
|
+
return null;
|
|
2063
|
+
return decryptChromiumValue(encryptedHex, opts);
|
|
2064
|
+
}
|
|
2065
|
+
function withTempCopy(dbPath, fn) {
|
|
2066
|
+
const tempDir = mkdtempSync(join8(tmpdir(), "unbrowse-cookies-"));
|
|
2067
|
+
const tempDb = join8(tempDir, "cookies.db");
|
|
2068
|
+
try {
|
|
2069
|
+
copyFileSync(dbPath, tempDb);
|
|
2070
|
+
for (const ext of ["-wal", "-shm"]) {
|
|
2071
|
+
const src = dbPath + ext;
|
|
2072
|
+
if (existsSync11(src))
|
|
2073
|
+
copyFileSync(src, tempDb + ext);
|
|
2074
|
+
}
|
|
2075
|
+
return fn(tempDb);
|
|
2076
|
+
} finally {
|
|
2077
|
+
try {
|
|
2078
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2079
|
+
} catch {}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
function sqliteQuery(dbPath, sql) {
|
|
2083
|
+
return execFileSync3("sqlite3", ["-separator", "|", dbPath, sql], {
|
|
2084
|
+
encoding: "utf8",
|
|
2085
|
+
maxBuffer: 4 * 1024 * 1024
|
|
2086
|
+
}).trim();
|
|
2087
|
+
}
|
|
2088
|
+
function buildDomainWhereClause(domain, column) {
|
|
2089
|
+
const reg = getRegistrableDomain(domain);
|
|
2090
|
+
const variants = new Set([
|
|
2091
|
+
reg,
|
|
2092
|
+
`.${reg}`,
|
|
2093
|
+
domain,
|
|
2094
|
+
`.${domain}`,
|
|
2095
|
+
`www.${reg}`,
|
|
2096
|
+
`.www.${reg}`
|
|
2097
|
+
]);
|
|
2098
|
+
for (const d of variants) {
|
|
2099
|
+
if (d.includes("'"))
|
|
2100
|
+
throw new Error(`Invalid domain for cookie query: ${d}`);
|
|
2101
|
+
}
|
|
2102
|
+
const escaped = [...variants].map((d) => `'${d}'`);
|
|
2103
|
+
const likeReg = reg.includes("'") ? reg : reg;
|
|
2104
|
+
const likePattern = `'%.${likeReg}'`;
|
|
2105
|
+
return `(${column} IN (${escaped.join(", ")}) OR ${column} LIKE ${likePattern})`;
|
|
2106
|
+
}
|
|
2107
|
+
function extractFromChrome(domain, opts) {
|
|
2108
|
+
return extractFromChromium(domain, {
|
|
2109
|
+
profile: opts?.profile,
|
|
2110
|
+
browserName: "Chrome"
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
function extractFromChromium(domain, opts) {
|
|
2114
|
+
const warnings = [];
|
|
2115
|
+
const dbPath = resolveChromiumCookiesPath(opts);
|
|
2116
|
+
const sourceLabel = opts?.browserName || "Chromium";
|
|
2117
|
+
if (!dbPath || !existsSync11(dbPath)) {
|
|
2118
|
+
warnings.push(`${sourceLabel} cookies DB not found${dbPath ? ` at ${dbPath}` : ""}`);
|
|
2119
|
+
return { cookies: [], source: null, warnings };
|
|
2120
|
+
}
|
|
2121
|
+
try {
|
|
2122
|
+
const cookies = withTempCopy(dbPath, (tempDb) => {
|
|
2123
|
+
const where = buildDomainWhereClause(domain, "host_key");
|
|
2124
|
+
const sql = `SELECT name, value, hex(encrypted_value) as ev, host_key, path, is_secure, is_httponly, samesite, expires_utc FROM cookies WHERE ${where};`;
|
|
2125
|
+
const rows = sqliteQuery(tempDb, sql);
|
|
2126
|
+
if (!rows)
|
|
2127
|
+
return [];
|
|
2128
|
+
const results = [];
|
|
2129
|
+
for (const line of rows.split(`
|
|
2130
|
+
`)) {
|
|
2131
|
+
const parts = line.split("|");
|
|
2132
|
+
if (parts.length < 9)
|
|
2133
|
+
continue;
|
|
2134
|
+
const [name, rawValue, encHex, host, cookiePath, secure, httpOnly, sameSite, expiresUtc] = parts;
|
|
2135
|
+
const value = decodeChromiumCookieValue(rawValue, encHex, opts);
|
|
2136
|
+
if (!value)
|
|
2137
|
+
continue;
|
|
2138
|
+
results.push({
|
|
2139
|
+
name,
|
|
2140
|
+
value,
|
|
2141
|
+
domain: host,
|
|
2142
|
+
path: cookiePath || "/",
|
|
2143
|
+
secure: secure === "1",
|
|
2144
|
+
httpOnly: httpOnly === "1",
|
|
2145
|
+
sameSite: sameSite === "0" ? "None" : sameSite === "1" ? "Lax" : "Strict",
|
|
2146
|
+
expires: expiresUtc === "0" ? -1 : Math.floor((Number(expiresUtc) - 11644473600000000) / 1e6)
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
return results;
|
|
2150
|
+
});
|
|
2151
|
+
const source = opts?.cookieDbPath ? `${sourceLabel} cookie DB "${dbPath}"` : opts?.userDataDir ? `${sourceLabel} user data "${opts.userDataDir}"${opts.profile ? ` profile "${opts.profile}"` : ""}` : opts?.profile ? `${sourceLabel} profile "${opts.profile}"` : `${sourceLabel} default profile`;
|
|
2152
|
+
if (cookies.length === 0) {
|
|
2153
|
+
warnings.push(`No cookies for ${domain} found in ${source}`);
|
|
2154
|
+
}
|
|
2155
|
+
log("auth", `extracted ${cookies.length} cookies for ${domain} from ${source}`);
|
|
2156
|
+
return { cookies, source: cookies.length > 0 ? source : null, warnings };
|
|
2157
|
+
} catch (err) {
|
|
2158
|
+
warnings.push(`${sourceLabel} extraction failed: ${err instanceof Error ? err.message : err}`);
|
|
2159
|
+
return { cookies: [], source: null, warnings };
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
function extractFromFirefox(domain, opts) {
|
|
2163
|
+
const warnings = [];
|
|
2164
|
+
const dbPath = getFirefoxCookiesPath(opts?.profile);
|
|
2165
|
+
if (!dbPath) {
|
|
2166
|
+
warnings.push("Firefox cookies DB not found");
|
|
2167
|
+
return { cookies: [], source: null, warnings };
|
|
2168
|
+
}
|
|
2169
|
+
try {
|
|
2170
|
+
const cookies = withTempCopy(dbPath, (tempDb) => {
|
|
2171
|
+
const where = buildDomainWhereClause(domain, "host");
|
|
2172
|
+
const sql = `SELECT name, value, host, path, isSecure, isHttpOnly, sameSite, expiry FROM moz_cookies WHERE ${where};`;
|
|
2173
|
+
const rows = sqliteQuery(tempDb, sql);
|
|
2174
|
+
if (!rows)
|
|
2175
|
+
return [];
|
|
2176
|
+
const results = [];
|
|
2177
|
+
for (const line of rows.split(`
|
|
2178
|
+
`)) {
|
|
2179
|
+
const parts = line.split("|");
|
|
2180
|
+
if (parts.length < 8)
|
|
2181
|
+
continue;
|
|
2182
|
+
const [name, value, host, cookiePath, secure, httpOnly, sameSite, expiry] = parts;
|
|
2183
|
+
if (!name || !value)
|
|
2184
|
+
continue;
|
|
2185
|
+
results.push({
|
|
2186
|
+
name,
|
|
2187
|
+
value,
|
|
2188
|
+
domain: host,
|
|
2189
|
+
path: cookiePath || "/",
|
|
2190
|
+
secure: secure === "1",
|
|
2191
|
+
httpOnly: httpOnly === "1",
|
|
2192
|
+
sameSite: sameSite === "0" ? "None" : sameSite === "1" ? "Lax" : "Strict",
|
|
2193
|
+
expires: Number(expiry) || -1
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
return results;
|
|
2197
|
+
});
|
|
2198
|
+
const source = opts?.profile ? `Firefox profile "${opts.profile}"` : "Firefox default profile";
|
|
2199
|
+
if (cookies.length === 0) {
|
|
2200
|
+
warnings.push(`No cookies for ${domain} found in ${source}`);
|
|
2201
|
+
}
|
|
2202
|
+
log("auth", `extracted ${cookies.length} cookies for ${domain} from ${source}`);
|
|
2203
|
+
return { cookies, source: cookies.length > 0 ? source : null, warnings };
|
|
2204
|
+
} catch (err) {
|
|
2205
|
+
warnings.push(`Firefox extraction failed: ${err instanceof Error ? err.message : err}`);
|
|
2206
|
+
return { cookies: [], source: null, warnings };
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
function extractBrowserCookies(domain, opts) {
|
|
2210
|
+
if (opts?.browser === "firefox") {
|
|
2211
|
+
return extractFromFirefox(domain, { profile: opts.firefoxProfile });
|
|
2212
|
+
}
|
|
2213
|
+
if (opts?.browser === "chrome") {
|
|
2214
|
+
return extractFromChrome(domain, { profile: opts.chromeProfile });
|
|
2215
|
+
}
|
|
2216
|
+
if (opts?.browser === "chromium") {
|
|
2217
|
+
return extractFromChromium(domain, opts.chromium);
|
|
2218
|
+
}
|
|
2219
|
+
const ff = extractFromFirefox(domain, { profile: opts?.firefoxProfile });
|
|
2220
|
+
if (ff.cookies.length > 0)
|
|
2221
|
+
return ff;
|
|
2222
|
+
if (opts?.chromium?.cookieDbPath || opts?.chromium?.userDataDir) {
|
|
2223
|
+
const chromium = extractFromChromium(domain, opts.chromium);
|
|
2224
|
+
chromium.warnings.push(...ff.warnings);
|
|
2225
|
+
return chromium;
|
|
2226
|
+
}
|
|
2227
|
+
const chrome = extractFromChrome(domain, { profile: opts?.chromeProfile });
|
|
2228
|
+
chrome.warnings.push(...ff.warnings);
|
|
2229
|
+
return chrome;
|
|
2230
|
+
}
|
|
2231
|
+
function scanAllBrowserSessions(domain) {
|
|
2232
|
+
const results = [];
|
|
2233
|
+
const home = homedir6();
|
|
2234
|
+
for (const browser of CHROMIUM_BROWSERS) {
|
|
2235
|
+
const userDataDir = platform() === "darwin" ? join8(home, "Library", "Application Support", browser.macPath) : platform() === "win32" ? join8(process.env.LOCALAPPDATA ?? join8(home, "AppData", "Local"), browser.macPath, "User Data") : join8(home, ".config", browser.macPath.toLowerCase());
|
|
2236
|
+
if (!existsSync11(userDataDir))
|
|
2237
|
+
continue;
|
|
2238
|
+
try {
|
|
2239
|
+
const result = extractFromChromium(domain, {
|
|
2240
|
+
userDataDir,
|
|
2241
|
+
browserName: browser.name
|
|
2242
|
+
});
|
|
2243
|
+
if (result.cookies.length > 0) {
|
|
2244
|
+
const sessionCookies = result.cookies.filter((c) => c.httpOnly || c.secure).length;
|
|
2245
|
+
results.push({
|
|
2246
|
+
browser: browser.name,
|
|
2247
|
+
cookies: result.cookies,
|
|
2248
|
+
sessionCookies,
|
|
2249
|
+
source: result.source
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
} catch {}
|
|
2253
|
+
}
|
|
2254
|
+
try {
|
|
2255
|
+
const ff = extractFromFirefox(domain);
|
|
2256
|
+
if (ff.cookies.length > 0) {
|
|
2257
|
+
const sessionCookies = ff.cookies.filter((c) => c.httpOnly || c.secure).length;
|
|
2258
|
+
results.push({
|
|
2259
|
+
browser: "Firefox",
|
|
2260
|
+
cookies: ff.cookies,
|
|
2261
|
+
sessionCookies,
|
|
2262
|
+
source: ff.source
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
} catch {}
|
|
2266
|
+
results.sort((a, b) => b.sessionCookies - a.sessionCookies);
|
|
2267
|
+
return results;
|
|
2268
|
+
}
|
|
2269
|
+
function findBestBrowserSession(domain) {
|
|
2270
|
+
const sessions = scanAllBrowserSessions(domain);
|
|
2271
|
+
return sessions[0] ?? null;
|
|
2272
|
+
}
|
|
2273
|
+
var _chromiumKeyCache, CHROMIUM_BROWSERS;
|
|
2274
|
+
var init_browser_cookies = __esm(() => {
|
|
2275
|
+
init_logger();
|
|
2276
|
+
init_domain();
|
|
2277
|
+
_chromiumKeyCache = new Map;
|
|
2278
|
+
CHROMIUM_BROWSERS = [
|
|
2279
|
+
{ name: "Chrome", macPath: "Google/Chrome" },
|
|
2280
|
+
{ name: "Arc", macPath: "Arc/User Data" },
|
|
2281
|
+
{ name: "Brave", macPath: "BraveSoftware/Brave-Browser" },
|
|
2282
|
+
{ name: "Edge", macPath: "Microsoft Edge" },
|
|
2283
|
+
{ name: "Vivaldi", macPath: "Vivaldi" },
|
|
2284
|
+
{ name: "Opera", macPath: "com.operasoftware.Opera" },
|
|
2285
|
+
{ name: "Dia", macPath: "Dia/User Data" },
|
|
2286
|
+
{ name: "Chromium", macPath: "Chromium" }
|
|
2287
|
+
];
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
// ../../src/auth/index.ts
|
|
2291
|
+
function formatAuthError(error) {
|
|
2292
|
+
if (error instanceof Error && error.message)
|
|
2293
|
+
return error.message;
|
|
2294
|
+
if (typeof error === "string")
|
|
2295
|
+
return error;
|
|
2296
|
+
try {
|
|
2297
|
+
return JSON.stringify(error);
|
|
2298
|
+
} catch {
|
|
2299
|
+
return String(error);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function normalizeAuthDomain(domain) {
|
|
2303
|
+
return domain.toLowerCase().replace(/^www\./, "");
|
|
2304
|
+
}
|
|
2305
|
+
function shouldImportBrowserCookies() {
|
|
2306
|
+
const raw = process.env.UNBROWSE_IMPORT_BROWSER_COOKIES?.trim();
|
|
2307
|
+
if (!raw)
|
|
2308
|
+
return true;
|
|
2309
|
+
return !DISABLE_BROWSER_COOKIE_IMPORT.test(raw);
|
|
2310
|
+
}
|
|
2311
|
+
function isLikelyAuthenticatedCookie(targetDomain, cookie) {
|
|
2312
|
+
const cookieName = cookie.name.toLowerCase();
|
|
2313
|
+
const registrableDomain = getRegistrableDomain(normalizeAuthDomain(targetDomain));
|
|
2314
|
+
const requiredCookieNames = DOMAIN_AUTH_COOKIE_NAMES[registrableDomain];
|
|
2315
|
+
if (requiredCookieNames)
|
|
2316
|
+
return requiredCookieNames.includes(cookieName);
|
|
2317
|
+
return GENERIC_AUTH_COOKIE_NAMES.test(cookieName);
|
|
2318
|
+
}
|
|
2319
|
+
function getAuthenticatedCookiesForDomain(targetDomain, cookies) {
|
|
2320
|
+
return cookies.filter((cookie) => isDomainMatch(cookie.domain, targetDomain) && isLikelyAuthenticatedCookie(targetDomain, cookie));
|
|
2321
|
+
}
|
|
2322
|
+
async function importBrowserCookiesIntoTab(tabId, domain) {
|
|
2323
|
+
if (!shouldImportBrowserCookies())
|
|
2324
|
+
return 0;
|
|
2325
|
+
try {
|
|
2326
|
+
const { extractBrowserCookies: extractBrowserCookies2, findBestBrowserSession: findBestBrowserSession2 } = await Promise.resolve().then(() => (init_browser_cookies(), exports_browser_cookies));
|
|
2327
|
+
const bestSession = findBestBrowserSession2(domain);
|
|
2328
|
+
const cookies = bestSession ? bestSession.cookies : extractBrowserCookies2(domain).cookies;
|
|
2329
|
+
let imported = 0;
|
|
2330
|
+
for (const cookie of cookies) {
|
|
2331
|
+
try {
|
|
2332
|
+
await setCookie(tabId, cookie);
|
|
2333
|
+
imported += 1;
|
|
2334
|
+
} catch (error) {
|
|
2335
|
+
log("auth", `browser_cookie_import_failed domain=${normalizeAuthDomain(domain)} tab_id=${tabId} cookie=${cookie.name} error=${formatAuthError(error)}`);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
if (imported > 0) {
|
|
2339
|
+
log("auth", `browser_cookie_imported domain=${normalizeAuthDomain(domain)} tab_id=${tabId} count=${imported}`);
|
|
2340
|
+
}
|
|
2341
|
+
return imported;
|
|
2342
|
+
} catch (error) {
|
|
2343
|
+
log("auth", `browser_cookie_extract_failed domain=${normalizeAuthDomain(domain)} tab_id=${tabId} error=${formatAuthError(error)}`);
|
|
2344
|
+
return 0;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
async function saveAuthProfileBestEffort(tabId, domain, context = "auth") {
|
|
2348
|
+
const profileName = normalizeAuthDomain(domain);
|
|
2349
|
+
if (!profileName || profileName === "unknown")
|
|
2350
|
+
return false;
|
|
2351
|
+
try {
|
|
2352
|
+
await authProfileSave(tabId, profileName);
|
|
2353
|
+
return true;
|
|
2354
|
+
} catch (error) {
|
|
2355
|
+
log("auth", `${context} auth_profile_failed op=save domain=${profileName} tab_id=${tabId} error=${formatAuthError(error)}`);
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
async function extractBrowserAuth(domain, opts) {
|
|
2360
|
+
const { extractBrowserCookies: extractBrowserCookies2 } = await Promise.resolve().then(() => (init_browser_cookies(), exports_browser_cookies));
|
|
2361
|
+
const result = extractBrowserCookies2(domain, opts);
|
|
2362
|
+
if (result.cookies.length === 0) {
|
|
2363
|
+
return {
|
|
2364
|
+
success: false,
|
|
2365
|
+
domain,
|
|
2366
|
+
cookies_stored: 0,
|
|
2367
|
+
error: result.warnings.join("; ") || "No cookies found in any browser"
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
const storableCookies = result.cookies.map((c) => ({
|
|
2371
|
+
name: c.name,
|
|
2372
|
+
value: c.value,
|
|
2373
|
+
domain: c.domain,
|
|
2374
|
+
path: c.path,
|
|
2375
|
+
secure: c.secure,
|
|
2376
|
+
httpOnly: c.httpOnly,
|
|
2377
|
+
sameSite: c.sameSite,
|
|
2378
|
+
expires: c.expires
|
|
2379
|
+
}));
|
|
2380
|
+
const vaultKey = `auth:${getRegistrableDomain(domain)}`;
|
|
2381
|
+
await storeCredential(vaultKey, JSON.stringify({ cookies: storableCookies }));
|
|
2382
|
+
log("auth", `stored ${storableCookies.length} cookies for ${domain} (key: ${vaultKey}) from ${result.source}`);
|
|
2383
|
+
return { success: true, domain, cookies_stored: storableCookies.length };
|
|
2384
|
+
}
|
|
2385
|
+
function filterExpired(cookies) {
|
|
2386
|
+
const now = Math.floor(Date.now() / 1000);
|
|
2387
|
+
return cookies.filter((c) => {
|
|
2388
|
+
if (c.expires == null || c.expires <= 0)
|
|
2389
|
+
return true;
|
|
2390
|
+
return c.expires > now;
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
async function getStoredAuth(domain) {
|
|
2394
|
+
const bundle = await getStoredAuthBundle(domain);
|
|
2395
|
+
return bundle?.cookies?.length ? bundle.cookies : null;
|
|
2396
|
+
}
|
|
2397
|
+
async function getStoredAuthBundle(domain) {
|
|
2398
|
+
const regDomain = getRegistrableDomain(domain);
|
|
2399
|
+
const keysToTry = [`auth:${regDomain}`];
|
|
2400
|
+
if (domain !== regDomain)
|
|
2401
|
+
keysToTry.push(`auth:${domain}`);
|
|
2402
|
+
for (const key of keysToTry) {
|
|
2403
|
+
const stored = await getCredential(key);
|
|
2404
|
+
if (!stored)
|
|
2405
|
+
continue;
|
|
2406
|
+
try {
|
|
2407
|
+
const parsed = JSON.parse(stored);
|
|
2408
|
+
const cookies = parsed.cookies ?? [];
|
|
2409
|
+
const valid = filterExpired(cookies);
|
|
2410
|
+
if (cookies.length > 0 && valid.length === 0 && Object.keys(parsed.headers ?? {}).length === 0) {
|
|
2411
|
+
log("auth", `all ${cookies.length} cookies for ${domain} (key: ${key}) are expired — deleting`);
|
|
2412
|
+
await deleteCredential(key);
|
|
2413
|
+
continue;
|
|
2414
|
+
}
|
|
2415
|
+
if (valid.length < cookies.length) {
|
|
2416
|
+
log("auth", `filtered ${cookies.length - valid.length} expired cookies for ${domain}`);
|
|
2417
|
+
}
|
|
2418
|
+
return {
|
|
2419
|
+
cookies: valid,
|
|
2420
|
+
headers: parsed.headers ?? {},
|
|
2421
|
+
source_keys: parsed.source_keys ?? [],
|
|
2422
|
+
source_meta: parsed.source_meta ?? null
|
|
2423
|
+
};
|
|
2424
|
+
} catch {
|
|
2425
|
+
continue;
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
return null;
|
|
2429
|
+
}
|
|
2430
|
+
async function getAuthCookies(domain) {
|
|
2431
|
+
const vaultCookies = await getStoredAuth(domain);
|
|
2432
|
+
if (vaultCookies && vaultCookies.length > 0)
|
|
2433
|
+
return vaultCookies;
|
|
2434
|
+
log("auth", `no vault cookies for ${domain} — auto-extracting from browser`);
|
|
2435
|
+
try {
|
|
2436
|
+
const result = await extractBrowserAuth(domain);
|
|
2437
|
+
if (result.success && result.cookies_stored > 0) {
|
|
2438
|
+
return getStoredAuth(domain);
|
|
2439
|
+
}
|
|
2440
|
+
} catch (err) {
|
|
2441
|
+
log("auth", `browser auto-extract failed for ${domain}: ${err instanceof Error ? err.message : err}`);
|
|
2442
|
+
}
|
|
2443
|
+
return null;
|
|
2444
|
+
}
|
|
2445
|
+
var DISABLE_BROWSER_COOKIE_IMPORT, GENERIC_AUTH_COOKIE_NAMES, DOMAIN_AUTH_COOKIE_NAMES;
|
|
2446
|
+
var init_auth = __esm(async () => {
|
|
2447
|
+
init_client2();
|
|
2448
|
+
init_domain();
|
|
2449
|
+
init_logger();
|
|
2450
|
+
init_supervisor();
|
|
2451
|
+
await __promiseAll([
|
|
2452
|
+
init_vault(),
|
|
2453
|
+
init_agent_mail()
|
|
2454
|
+
]);
|
|
2455
|
+
DISABLE_BROWSER_COOKIE_IMPORT = /^(0|false|no|off)$/i;
|
|
2456
|
+
GENERIC_AUTH_COOKIE_NAMES = /(^|[_\-.])(sess(?:ion)?(?:id)?|auth|token|csrf|xsrf|jwt|sid|sso|remember|logged[_-]?in|connect\.sid)([_\-.]|$)/i;
|
|
2457
|
+
DOMAIN_AUTH_COOKIE_NAMES = {
|
|
2458
|
+
"linkedin.com": ["li_at"]
|
|
2459
|
+
};
|
|
2460
|
+
});
|
|
2461
|
+
|
|
2462
|
+
// ../../src/auth/email-provider.ts
|
|
2463
|
+
class AgentMailProvider {
|
|
2464
|
+
name = "agentmail";
|
|
2465
|
+
get configured() {
|
|
2466
|
+
return Boolean(process.env.AGENTMAIL_API_KEY);
|
|
2467
|
+
}
|
|
2468
|
+
async getAddress(domain) {
|
|
2469
|
+
const { getOrCreateSiteInbox: getOrCreateSiteInbox2 } = await init_agent_mail().then(() => exports_agent_mail);
|
|
2470
|
+
const inbox = await getOrCreateSiteInbox2(domain);
|
|
2471
|
+
return inbox.email;
|
|
2472
|
+
}
|
|
2473
|
+
async send(to, subject, body) {
|
|
2474
|
+
const { autonomousEmailLogin: autonomousEmailLogin2 } = await init_agent_mail().then(() => exports_agent_mail);
|
|
2475
|
+
const session = await autonomousEmailLogin2("general");
|
|
2476
|
+
const result = await session.sendEmail(to, subject, body);
|
|
2477
|
+
return { messageId: result.messageId };
|
|
2478
|
+
}
|
|
2479
|
+
async waitForMessage(fromDomain, timeoutMs = 90000) {
|
|
2480
|
+
const { autonomousEmailLogin: autonomousEmailLogin2, waitForVerificationEmail: waitForVerificationEmail2 } = await init_agent_mail().then(() => exports_agent_mail);
|
|
2481
|
+
const session = await autonomousEmailLogin2(fromDomain);
|
|
2482
|
+
const email = await waitForVerificationEmail2(session.inboxId, fromDomain, timeoutMs);
|
|
2483
|
+
return email;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
class GmailProvider {
|
|
2488
|
+
name = "gmail";
|
|
2489
|
+
get configured() {
|
|
2490
|
+
try {
|
|
2491
|
+
const { existsSync: existsSync12 } = __require("fs");
|
|
2492
|
+
const { homedir: homedir7 } = __require("os");
|
|
2493
|
+
const { join: join9 } = __require("path");
|
|
2494
|
+
return existsSync12(join9(homedir7(), ".config", "gcloud", "application_default_credentials.json"));
|
|
2495
|
+
} catch {
|
|
2496
|
+
return false;
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
async getAddress(_domain) {
|
|
2500
|
+
const { execSync: execSync3 } = __require("child_process");
|
|
2501
|
+
try {
|
|
2502
|
+
const email = execSync3("gcloud config get-value account 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
2503
|
+
if (email && email.includes("@"))
|
|
2504
|
+
return email;
|
|
2505
|
+
} catch {}
|
|
2506
|
+
throw new Error("Gmail not configured — run: gcloud auth login");
|
|
2507
|
+
}
|
|
2508
|
+
async send(_to, _subject, _body) {
|
|
2509
|
+
throw new Error("Gmail send not yet wired — use AgentMail or the gws-gmail-send skill directly");
|
|
2510
|
+
}
|
|
2511
|
+
async waitForMessage(_fromDomain, _timeoutMs = 90000) {
|
|
2512
|
+
throw new Error("Gmail inbox polling not yet wired — use AgentMail or the gws-gmail skill directly");
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
function getEmailProvider(purpose = "general") {
|
|
2516
|
+
if (purpose === "register") {
|
|
2517
|
+
const agentmail = providers.find((p) => p.name === "agentmail" && p.configured);
|
|
2518
|
+
if (agentmail)
|
|
2519
|
+
return agentmail;
|
|
2520
|
+
}
|
|
2521
|
+
if (purpose === "login") {
|
|
2522
|
+
const gmail = providers.find((p) => p.name === "gmail" && p.configured);
|
|
2523
|
+
if (gmail)
|
|
2524
|
+
return gmail;
|
|
2525
|
+
}
|
|
2526
|
+
return providers.find((p) => p.configured) ?? null;
|
|
2527
|
+
}
|
|
2528
|
+
var providers;
|
|
2529
|
+
var init_email_provider = __esm(() => {
|
|
2530
|
+
providers = [
|
|
2531
|
+
new GmailProvider,
|
|
2532
|
+
new AgentMailProvider
|
|
2533
|
+
];
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2536
|
+
// ../../src/auth/autonomous-login.ts
|
|
2537
|
+
async function detectEmailInput(tabId) {
|
|
2538
|
+
const ref = await evaluate(tabId, `(() => {
|
|
2539
|
+
// Priority order: type=email, name/id containing email, placeholder containing email
|
|
2540
|
+
const byType = document.querySelector('input[type="email"]');
|
|
2541
|
+
if (byType) return byType.getAttribute("data-ref") || "input[type=email]";
|
|
2542
|
+
|
|
2543
|
+
const byName = document.querySelector('input[name*="email" i], input[id*="email" i]');
|
|
2544
|
+
if (byName) return byName.getAttribute("data-ref") || 'input[name*="email" i]';
|
|
2545
|
+
|
|
2546
|
+
const byPlaceholder = document.querySelector('input[placeholder*="email" i]');
|
|
2547
|
+
if (byPlaceholder) return byPlaceholder.getAttribute("data-ref") || 'input[placeholder*="email" i]';
|
|
2548
|
+
|
|
2549
|
+
// Generic text input on a login page (single input + submit button pattern)
|
|
2550
|
+
const inputs = document.querySelectorAll('input[type="text"], input:not([type])');
|
|
2551
|
+
const submit = document.querySelector('button[type="submit"], input[type="submit"], button:not([type])');
|
|
2552
|
+
if (inputs.length === 1 && submit) {
|
|
2553
|
+
return inputs[0].getAttribute("data-ref") || 'input[type="text"]';
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
return null;
|
|
2557
|
+
})()`);
|
|
2558
|
+
return typeof ref === "string" ? ref : null;
|
|
2559
|
+
}
|
|
2560
|
+
async function detectOtpInput(tabId) {
|
|
2561
|
+
const ref = await evaluate(tabId, `(() => {
|
|
2562
|
+
// OTP-specific inputs
|
|
2563
|
+
const byAutocomplete = document.querySelector('input[autocomplete="one-time-code"]');
|
|
2564
|
+
if (byAutocomplete) return byAutocomplete.getAttribute("data-ref") || 'input[autocomplete="one-time-code"]';
|
|
2565
|
+
|
|
2566
|
+
// Name/id/placeholder containing otp, code, verification, pin
|
|
2567
|
+
const byName = document.querySelector(
|
|
2568
|
+
'input[name*="otp" i], input[name*="code" i], input[name*="verification" i], input[name*="pin" i], ' +
|
|
2569
|
+
'input[id*="otp" i], input[id*="code" i], input[id*="verification" i], ' +
|
|
2570
|
+
'input[placeholder*="code" i], input[placeholder*="verification" i], input[placeholder*="otp" i]'
|
|
2571
|
+
);
|
|
2572
|
+
if (byName) return byName.getAttribute("data-ref") || 'input[name*="code" i]';
|
|
2573
|
+
|
|
2574
|
+
// Numeric input with maxlength 4-8 (common OTP pattern)
|
|
2575
|
+
const numericInputs = document.querySelectorAll('input[type="number"], input[type="tel"], input[inputmode="numeric"]');
|
|
2576
|
+
for (const el of numericInputs) {
|
|
2577
|
+
const ml = parseInt(el.getAttribute("maxlength") || "0");
|
|
2578
|
+
if (ml >= 4 && ml <= 8) return el.getAttribute("data-ref") || 'input[inputmode="numeric"]';
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
// Single short text input that appeared after email submit
|
|
2582
|
+
const textInputs = document.querySelectorAll('input[type="text"], input:not([type])');
|
|
2583
|
+
for (const el of textInputs) {
|
|
2584
|
+
const ml = parseInt(el.getAttribute("maxlength") || "0");
|
|
2585
|
+
if (ml >= 4 && ml <= 8) return el.getAttribute("data-ref") || 'input[type="text"]';
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
return null;
|
|
2589
|
+
})()`);
|
|
2590
|
+
return typeof ref === "string" ? ref : null;
|
|
2591
|
+
}
|
|
2592
|
+
async function detectSubmitButton(tabId) {
|
|
2593
|
+
const ref = await evaluate(tabId, `(() => {
|
|
2594
|
+
const submit = document.querySelector(
|
|
2595
|
+
'button[type="submit"], input[type="submit"], ' +
|
|
2596
|
+
'button:not([type]):not([aria-label*="close" i]):not([aria-label*="cancel" i])'
|
|
2597
|
+
);
|
|
2598
|
+
if (submit) return submit.getAttribute("data-ref") || 'button[type="submit"]';
|
|
2599
|
+
|
|
2600
|
+
// Look for buttons with login/register/continue/verify text
|
|
2601
|
+
const buttons = document.querySelectorAll('button, a[role="button"]');
|
|
2602
|
+
for (const btn of buttons) {
|
|
2603
|
+
const text = btn.textContent?.toLowerCase() || "";
|
|
2604
|
+
if (/logs*in|signs*in|register|signs*up|continue|submit|verify|next/i.test(text)) {
|
|
2605
|
+
return btn.getAttribute("data-ref") || null;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return null;
|
|
2609
|
+
})()`);
|
|
2610
|
+
return typeof ref === "string" ? ref : null;
|
|
2611
|
+
}
|
|
2612
|
+
async function checkLoginSuccess(tabId, domain) {
|
|
2613
|
+
const cookies = await getCookies(tabId);
|
|
2614
|
+
const domainCookies = cookies.filter((c) => isDomainMatch(c.domain, domain));
|
|
2615
|
+
const authCookies = getAuthenticatedCookiesForDomain(domain, domainCookies);
|
|
2616
|
+
return authCookies.length > 0;
|
|
2617
|
+
}
|
|
2618
|
+
async function autonomousLogin(loginUrl, domain) {
|
|
2619
|
+
const start2 = Date.now();
|
|
2620
|
+
const targetDomain = domain ?? (() => {
|
|
2621
|
+
try {
|
|
2622
|
+
return new URL(loginUrl).hostname.replace(/^www\./, "");
|
|
2623
|
+
} catch {
|
|
2624
|
+
return loginUrl;
|
|
2625
|
+
}
|
|
2626
|
+
})();
|
|
2627
|
+
const elapsed = () => Date.now() - start2;
|
|
2628
|
+
const emailProvider = getEmailProvider("register");
|
|
2629
|
+
if (!emailProvider) {
|
|
2630
|
+
return { success: false, method: "failed", domain: targetDomain, cookies_stored: 0, error: "No email provider configured (set AGENTMAIL_API_KEY or configure Gmail via gcloud)", duration_ms: elapsed() };
|
|
2631
|
+
}
|
|
2632
|
+
log("autonomous-login", `starting for ${targetDomain} — url: ${loginUrl}`);
|
|
2633
|
+
const prevHeadless = process.env.HEADLESS;
|
|
2634
|
+
process.env.HEADLESS = "false";
|
|
2635
|
+
let tabId;
|
|
2636
|
+
try {
|
|
2637
|
+
await start();
|
|
2638
|
+
tabId = await getDefaultTab();
|
|
2639
|
+
await networkEnable(tabId);
|
|
2640
|
+
} catch (err) {
|
|
2641
|
+
if (prevHeadless !== undefined)
|
|
2642
|
+
process.env.HEADLESS = prevHeadless;
|
|
2643
|
+
else
|
|
2644
|
+
delete process.env.HEADLESS;
|
|
2645
|
+
return { success: false, method: "failed", domain: targetDomain, cookies_stored: 0, error: `Kuri start failed: ${err}`, duration_ms: elapsed() };
|
|
2646
|
+
}
|
|
2647
|
+
try {
|
|
2648
|
+
await navigate(tabId, loginUrl);
|
|
2649
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
2650
|
+
let emailRef = await detectEmailInput(tabId);
|
|
2651
|
+
if (!emailRef) {
|
|
2652
|
+
const regUrl = loginUrl.replace(/login|signin|sign-in/i, "register").replace(/register/i, "signup");
|
|
2653
|
+
if (regUrl !== loginUrl) {
|
|
2654
|
+
await navigate(tabId, regUrl);
|
|
2655
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
2656
|
+
emailRef = await detectEmailInput(tabId);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
if (!emailRef) {
|
|
2660
|
+
log("autonomous-login", `no email input found on ${loginUrl}`);
|
|
2661
|
+
return { success: false, method: "failed", domain: targetDomain, cookies_stored: 0, error: "no email input field detected", duration_ms: elapsed() };
|
|
2662
|
+
}
|
|
2663
|
+
let agentEmail;
|
|
2664
|
+
try {
|
|
2665
|
+
agentEmail = await emailProvider.getAddress(targetDomain);
|
|
2666
|
+
} catch (err) {
|
|
2667
|
+
return { success: false, method: "failed", domain: targetDomain, cookies_stored: 0, error: `Email provider (${emailProvider.name}) failed: ${err}`, duration_ms: elapsed() };
|
|
2668
|
+
}
|
|
2669
|
+
log("autonomous-login", `filling email: ${agentEmail} (via ${emailProvider.name}) into ${emailRef}`);
|
|
2670
|
+
await fill(tabId, emailRef, agentEmail);
|
|
2671
|
+
const submitRef = await detectSubmitButton(tabId);
|
|
2672
|
+
if (submitRef) {
|
|
2673
|
+
await click(tabId, submitRef);
|
|
2674
|
+
} else {
|
|
2675
|
+
await evaluate(tabId, `document.querySelector('${emailRef}')?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))`);
|
|
2676
|
+
}
|
|
2677
|
+
await new Promise((r) => setTimeout(r, POST_SUBMIT_SETTLE_MS));
|
|
2678
|
+
const otpRef = await detectOtpInput(tabId);
|
|
2679
|
+
if (otpRef) {
|
|
2680
|
+
log("autonomous-login", `OTP input detected (${otpRef}), waiting for verification email via ${emailProvider.name}...`);
|
|
2681
|
+
const verificationEmail = await emailProvider.waitForMessage(targetDomain, OTP_FILL_TIMEOUT_MS);
|
|
2682
|
+
const otp = verificationEmail ? extractOtpFromEmail(verificationEmail.text) : null;
|
|
2683
|
+
if (!otp) {
|
|
2684
|
+
return { success: false, method: "failed", domain: targetDomain, email: agentEmail, cookies_stored: 0, error: "OTP email not received within timeout", duration_ms: elapsed() };
|
|
2685
|
+
}
|
|
2686
|
+
log("autonomous-login", `filling OTP: ${otp}`);
|
|
2687
|
+
await fill(tabId, otpRef, otp);
|
|
2688
|
+
const otpSubmitRef = await detectSubmitButton(tabId);
|
|
2689
|
+
if (otpSubmitRef) {
|
|
2690
|
+
await click(tabId, otpSubmitRef);
|
|
2691
|
+
}
|
|
2692
|
+
await new Promise((r) => setTimeout(r, POST_SUBMIT_SETTLE_MS));
|
|
2693
|
+
} else {
|
|
2694
|
+
log("autonomous-login", `no OTP input, trying magic link flow via ${emailProvider.name}...`);
|
|
2695
|
+
const verificationEmail = await emailProvider.waitForMessage(targetDomain, OTP_FILL_TIMEOUT_MS);
|
|
2696
|
+
const link = verificationEmail ? extractVerificationLink(verificationEmail.text, verificationEmail.html) : null;
|
|
2697
|
+
if (link) {
|
|
2698
|
+
log("autonomous-login", `navigating to magic link`);
|
|
2699
|
+
await navigate(tabId, link);
|
|
2700
|
+
await new Promise((r) => setTimeout(r, POST_SUBMIT_SETTLE_MS));
|
|
2701
|
+
} else {
|
|
2702
|
+
log("autonomous-login", `no magic link received, checking if already authenticated...`);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
const success = await checkLoginSuccess(tabId, targetDomain);
|
|
2706
|
+
if (success) {
|
|
2707
|
+
const cookies = await getCookies(tabId);
|
|
2708
|
+
const domainCookies = cookies.filter((c) => isDomainMatch(c.domain, targetDomain));
|
|
2709
|
+
const storableCookies = domainCookies.map((c) => ({
|
|
2710
|
+
name: c.name,
|
|
2711
|
+
value: c.value,
|
|
2712
|
+
domain: c.domain,
|
|
2713
|
+
path: c.path,
|
|
2714
|
+
secure: c.secure,
|
|
2715
|
+
httpOnly: c.httpOnly,
|
|
2716
|
+
sameSite: c.sameSite,
|
|
2717
|
+
expires: c.expires
|
|
2718
|
+
}));
|
|
2719
|
+
const vaultKey = `auth:${getRegistrableDomain(targetDomain)}`;
|
|
2720
|
+
await storeCredential(vaultKey, JSON.stringify({ cookies: storableCookies }));
|
|
2721
|
+
await saveAuthProfileBestEffort(tabId, targetDomain, "autonomous_login");
|
|
2722
|
+
log("autonomous-login", `login succeeded for ${targetDomain} — stored ${storableCookies.length} cookies (${elapsed()}ms)`);
|
|
2723
|
+
return {
|
|
2724
|
+
success: true,
|
|
2725
|
+
method: otpRef ? "agent-mail-otp" : "agent-mail-link",
|
|
2726
|
+
domain: targetDomain,
|
|
2727
|
+
email: agentEmail,
|
|
2728
|
+
cookies_stored: storableCookies.length,
|
|
2729
|
+
duration_ms: elapsed()
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
return { success: false, method: "failed", domain: targetDomain, email: agentEmail, cookies_stored: 0, error: "login flow completed but no auth cookies detected", duration_ms: elapsed() };
|
|
2733
|
+
} finally {
|
|
2734
|
+
if (prevHeadless !== undefined)
|
|
2735
|
+
process.env.HEADLESS = prevHeadless;
|
|
2736
|
+
else
|
|
2737
|
+
delete process.env.HEADLESS;
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
var OTP_FILL_TIMEOUT_MS = 120000, POST_SUBMIT_SETTLE_MS = 3000;
|
|
2741
|
+
var init_autonomous_login = __esm(async () => {
|
|
2742
|
+
init_client2();
|
|
882
2743
|
init_logger();
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
// ../../src/reverse-engineer/token-sources.ts
|
|
886
|
-
var init_token_sources = () => {};
|
|
887
|
-
|
|
888
|
-
// ../../src/execution/token-resolver.ts
|
|
889
|
-
var init_token_resolver = __esm(() => {
|
|
890
|
-
init_token_sources();
|
|
891
|
-
});
|
|
892
|
-
|
|
893
|
-
// ../../src/auth/index.ts
|
|
894
|
-
var init_auth = __esm(async () => {
|
|
895
|
-
init_client2();
|
|
2744
|
+
init_email_provider();
|
|
896
2745
|
init_domain();
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
2746
|
+
await __promiseAll([
|
|
2747
|
+
init_agent_mail(),
|
|
2748
|
+
init_auth(),
|
|
2749
|
+
init_vault()
|
|
2750
|
+
]);
|
|
900
2751
|
});
|
|
901
2752
|
|
|
902
2753
|
// ../../src/auth/runtime.ts
|
|
@@ -904,16 +2755,32 @@ class LocalAuthRuntime {
|
|
|
904
2755
|
sessions = new Map;
|
|
905
2756
|
async resolveAuth(dep) {
|
|
906
2757
|
if (dep.strategy === "none")
|
|
907
|
-
return { authenticated: true };
|
|
2758
|
+
return { authenticated: true, method: "cached" };
|
|
908
2759
|
const session = this.sessions.get(dep.domain);
|
|
909
2760
|
if (session && session.expires > Date.now()) {
|
|
910
|
-
return { authenticated: true, session_token: session.token };
|
|
2761
|
+
return { authenticated: true, session_token: session.token, method: "cached" };
|
|
911
2762
|
}
|
|
2763
|
+
try {
|
|
2764
|
+
const cookies = await getStoredAuth(dep.domain);
|
|
2765
|
+
if (cookies && cookies.length > 0) {
|
|
2766
|
+
log("auth-runtime", `found ${cookies.length} stored cookies for ${dep.domain}`);
|
|
2767
|
+
this.setSession(dep.domain, "vault-cookies", 3600000);
|
|
2768
|
+
return { authenticated: true, method: "cookies" };
|
|
2769
|
+
}
|
|
2770
|
+
} catch {}
|
|
2771
|
+
try {
|
|
2772
|
+
const result = await extractBrowserAuth(dep.domain);
|
|
2773
|
+
if (result.success && result.cookies_stored > 0) {
|
|
2774
|
+
log("auth-runtime", `extracted ${result.cookies_stored} browser cookies for ${dep.domain}`);
|
|
2775
|
+
this.setSession(dep.domain, "browser-cookies", 3600000);
|
|
2776
|
+
return { authenticated: true, method: "cookies" };
|
|
2777
|
+
}
|
|
2778
|
+
} catch {}
|
|
912
2779
|
if (dep.strategy === "refresh_session" && session) {
|
|
913
2780
|
const refreshed = await this.refreshSession(dep.domain);
|
|
914
2781
|
if (refreshed) {
|
|
915
2782
|
const updated = this.sessions.get(dep.domain);
|
|
916
|
-
return { authenticated: true, session_token: updated?.token };
|
|
2783
|
+
return { authenticated: true, session_token: updated?.token, method: "cached" };
|
|
917
2784
|
}
|
|
918
2785
|
}
|
|
919
2786
|
return { authenticated: false };
|
|
@@ -930,10 +2797,30 @@ class LocalAuthRuntime {
|
|
|
930
2797
|
}
|
|
931
2798
|
return false;
|
|
932
2799
|
}
|
|
933
|
-
async loginIfNeeded(domain,
|
|
2800
|
+
async loginIfNeeded(domain, loginUrl) {
|
|
934
2801
|
const refreshed = await this.refreshSession(domain);
|
|
935
2802
|
if (refreshed)
|
|
936
2803
|
return true;
|
|
2804
|
+
try {
|
|
2805
|
+
const cookies = await getAuthCookies(domain);
|
|
2806
|
+
if (cookies && cookies.length > 0) {
|
|
2807
|
+
this.setSession(domain, "auto-cookies", 3600000);
|
|
2808
|
+
log("auth-runtime", `loginIfNeeded resolved via cookies for ${domain}`);
|
|
2809
|
+
return true;
|
|
2810
|
+
}
|
|
2811
|
+
} catch {}
|
|
2812
|
+
const url = loginUrl ?? `https://${domain}/login`;
|
|
2813
|
+
try {
|
|
2814
|
+
const result = await autonomousLogin(url, domain);
|
|
2815
|
+
if (result.success) {
|
|
2816
|
+
this.setSession(domain, "autonomous-login", 3600000);
|
|
2817
|
+
log("auth-runtime", `loginIfNeeded resolved via autonomous login for ${domain} (${result.method}, ${result.duration_ms}ms)`);
|
|
2818
|
+
return true;
|
|
2819
|
+
}
|
|
2820
|
+
log("auth-runtime", `autonomous login failed for ${domain}: ${result.error}`);
|
|
2821
|
+
} catch (err) {
|
|
2822
|
+
log("auth-runtime", `autonomous login error for ${domain}: ${err instanceof Error ? err.message : err}`);
|
|
2823
|
+
}
|
|
937
2824
|
return false;
|
|
938
2825
|
}
|
|
939
2826
|
setSession(domain, token, ttlMs = 3600000) {
|
|
@@ -941,7 +2828,12 @@ class LocalAuthRuntime {
|
|
|
941
2828
|
}
|
|
942
2829
|
}
|
|
943
2830
|
var authRuntime;
|
|
944
|
-
var init_runtime = __esm(() => {
|
|
2831
|
+
var init_runtime = __esm(async () => {
|
|
2832
|
+
init_logger();
|
|
2833
|
+
await __promiseAll([
|
|
2834
|
+
init_auth(),
|
|
2835
|
+
init_autonomous_login()
|
|
2836
|
+
]);
|
|
945
2837
|
authRuntime = new LocalAuthRuntime;
|
|
946
2838
|
});
|
|
947
2839
|
|
|
@@ -1025,7 +2917,7 @@ var init_schema_review = __esm(() => {
|
|
|
1025
2917
|
});
|
|
1026
2918
|
|
|
1027
2919
|
// ../../src/indexer/index.ts
|
|
1028
|
-
import { join as
|
|
2920
|
+
import { join as join9 } from "node:path";
|
|
1029
2921
|
var SKILL_SNAPSHOT_DIR, indexInFlight, pendingIndexJobs;
|
|
1030
2922
|
var init_indexer = __esm(async () => {
|
|
1031
2923
|
init_graph();
|
|
@@ -1040,7 +2932,7 @@ var init_indexer = __esm(async () => {
|
|
|
1040
2932
|
init_graph();
|
|
1041
2933
|
init_schema_review();
|
|
1042
2934
|
await init_orchestrator();
|
|
1043
|
-
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
2935
|
+
SKILL_SNAPSHOT_DIR = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join9(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
1044
2936
|
indexInFlight = new Map;
|
|
1045
2937
|
pendingIndexJobs = new Map;
|
|
1046
2938
|
});
|
|
@@ -1123,7 +3015,6 @@ var init_execution = __esm(async () => {
|
|
|
1123
3015
|
init_bundle_scanner();
|
|
1124
3016
|
init_token_resolver();
|
|
1125
3017
|
init_marketplace();
|
|
1126
|
-
init_runtime();
|
|
1127
3018
|
init_transform();
|
|
1128
3019
|
init_drift();
|
|
1129
3020
|
init_client();
|
|
@@ -1146,6 +3037,8 @@ var init_execution = __esm(async () => {
|
|
|
1146
3037
|
init_capture(),
|
|
1147
3038
|
init_vault(),
|
|
1148
3039
|
init_auth(),
|
|
3040
|
+
init_runtime(),
|
|
3041
|
+
init_autonomous_login(),
|
|
1149
3042
|
init_indexer(),
|
|
1150
3043
|
init_orchestrator()
|
|
1151
3044
|
]);
|
|
@@ -1186,9 +3079,9 @@ var init_execution = __esm(async () => {
|
|
|
1186
3079
|
|
|
1187
3080
|
// ../../src/graph/planner.ts
|
|
1188
3081
|
var MUTABLE_METHODS, sessionNegatives, sessionTraces;
|
|
1189
|
-
var init_planner = __esm(() => {
|
|
3082
|
+
var init_planner = __esm(async () => {
|
|
1190
3083
|
init_graph();
|
|
1191
|
-
init_runtime();
|
|
3084
|
+
await init_runtime();
|
|
1192
3085
|
MUTABLE_METHODS = new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
1193
3086
|
sessionNegatives = new Map;
|
|
1194
3087
|
sessionTraces = new Map;
|
|
@@ -1204,9 +3097,9 @@ var init_graph_client = __esm(() => {
|
|
|
1204
3097
|
});
|
|
1205
3098
|
|
|
1206
3099
|
// ../../src/orchestrator/dag-advisor.ts
|
|
1207
|
-
var init_dag_advisor = __esm(() => {
|
|
1208
|
-
init_planner();
|
|
3100
|
+
var init_dag_advisor = __esm(async () => {
|
|
1209
3101
|
init_graph_client();
|
|
3102
|
+
await init_planner();
|
|
1210
3103
|
});
|
|
1211
3104
|
|
|
1212
3105
|
// ../../src/orchestrator/dag-feedback.ts
|
|
@@ -1262,15 +3155,15 @@ var init_routing_telemetry = __esm(() => {
|
|
|
1262
3155
|
});
|
|
1263
3156
|
// ../../src/orchestrator/index.ts
|
|
1264
3157
|
import { nanoid as nanoid9 } from "nanoid";
|
|
1265
|
-
import { existsSync as
|
|
1266
|
-
import { dirname as dirname3, join as
|
|
3158
|
+
import { existsSync as existsSync12, writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
3159
|
+
import { dirname as dirname3, join as join10 } from "node:path";
|
|
1267
3160
|
function _writeRouteCacheToDisk() {
|
|
1268
3161
|
try {
|
|
1269
3162
|
const dir = dirname3(ROUTE_CACHE_FILE);
|
|
1270
|
-
if (!
|
|
1271
|
-
|
|
3163
|
+
if (!existsSync12(dir))
|
|
3164
|
+
mkdirSync6(dir, { recursive: true });
|
|
1272
3165
|
const entries = Object.fromEntries(skillRouteCache);
|
|
1273
|
-
|
|
3166
|
+
writeFileSync4(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
|
|
1274
3167
|
} catch {}
|
|
1275
3168
|
_routeCacheDirty = false;
|
|
1276
3169
|
}
|
|
@@ -1281,7 +3174,6 @@ var init_orchestrator = __esm(async () => {
|
|
|
1281
3174
|
init_telemetry();
|
|
1282
3175
|
init_marketplace();
|
|
1283
3176
|
init_graph();
|
|
1284
|
-
init_dag_advisor();
|
|
1285
3177
|
init_domain();
|
|
1286
3178
|
init_debug_trace();
|
|
1287
3179
|
init_dag_feedback();
|
|
@@ -1292,24 +3184,25 @@ var init_orchestrator = __esm(async () => {
|
|
|
1292
3184
|
init_payments();
|
|
1293
3185
|
init_wallet();
|
|
1294
3186
|
init_version();
|
|
1295
|
-
init_runtime();
|
|
1296
3187
|
init_routing_telemetry();
|
|
1297
3188
|
await __promiseAll([
|
|
1298
3189
|
init_execution(),
|
|
1299
|
-
|
|
3190
|
+
init_dag_advisor(),
|
|
3191
|
+
init_prefetch(),
|
|
3192
|
+
init_runtime()
|
|
1300
3193
|
]);
|
|
1301
3194
|
LIVE_CAPTURE_TIMEOUT_MS = Number(process.env.UNBROWSE_LIVE_CAPTURE_TIMEOUT_MS ?? "120000");
|
|
1302
3195
|
capturedDomainCache = new Map;
|
|
1303
3196
|
captureInFlight = new Map;
|
|
1304
3197
|
captureDomainLocks = new Map;
|
|
1305
3198
|
skillRouteCache = new Map;
|
|
1306
|
-
ROUTE_CACHE_FILE =
|
|
1307
|
-
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
3199
|
+
ROUTE_CACHE_FILE = join10(process.env.HOME ?? "/tmp", ".unbrowse", "route-cache.json");
|
|
3200
|
+
SKILL_SNAPSHOT_DIR2 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join10(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
1308
3201
|
domainSkillCache = new Map;
|
|
1309
|
-
DOMAIN_CACHE_FILE =
|
|
3202
|
+
DOMAIN_CACHE_FILE = join10(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
|
|
1310
3203
|
try {
|
|
1311
|
-
if (
|
|
1312
|
-
const data = JSON.parse(
|
|
3204
|
+
if (existsSync12(DOMAIN_CACHE_FILE)) {
|
|
3205
|
+
const data = JSON.parse(readFileSync7(DOMAIN_CACHE_FILE, "utf-8"));
|
|
1313
3206
|
for (const [k, v] of Object.entries(data)) {
|
|
1314
3207
|
const entry = v;
|
|
1315
3208
|
if (Date.now() - entry.ts < 7 * 24 * 60 * 60000) {
|
|
@@ -1326,8 +3219,8 @@ var init_orchestrator = __esm(async () => {
|
|
|
1326
3219
|
}, 5000);
|
|
1327
3220
|
routeCacheFlushTimer.unref?.();
|
|
1328
3221
|
try {
|
|
1329
|
-
if (
|
|
1330
|
-
const data = JSON.parse(
|
|
3222
|
+
if (existsSync12(ROUTE_CACHE_FILE)) {
|
|
3223
|
+
const data = JSON.parse(readFileSync7(ROUTE_CACHE_FILE, "utf-8"));
|
|
1331
3224
|
for (const [k, v] of Object.entries(data)) {
|
|
1332
3225
|
const entry = v;
|
|
1333
3226
|
if (Date.now() - entry.ts < 24 * 60 * 60000) {
|
|
@@ -1423,18 +3316,18 @@ __export(exports_wallet, {
|
|
|
1423
3316
|
getWalletContext: () => getWalletContext2,
|
|
1424
3317
|
checkWalletConfigured: () => checkWalletConfigured2
|
|
1425
3318
|
});
|
|
1426
|
-
import { existsSync as
|
|
1427
|
-
import { homedir as
|
|
1428
|
-
import { join as
|
|
3319
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10 } from "node:fs";
|
|
3320
|
+
import { homedir as homedir7 } from "node:os";
|
|
3321
|
+
import { join as join12 } from "node:path";
|
|
1429
3322
|
function asNonEmptyString2(value) {
|
|
1430
3323
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1431
3324
|
}
|
|
1432
3325
|
function getLobsterWalletFromLocalConfig2() {
|
|
1433
|
-
const agentsPath =
|
|
1434
|
-
if (!
|
|
3326
|
+
const agentsPath = join12(process.env.HOME || homedir7(), ".lobster", "agents.json");
|
|
3327
|
+
if (!existsSync16(agentsPath))
|
|
1435
3328
|
return;
|
|
1436
3329
|
try {
|
|
1437
|
-
const raw = JSON.parse(
|
|
3330
|
+
const raw = JSON.parse(readFileSync10(agentsPath, "utf8"));
|
|
1438
3331
|
const activeAgentId = asNonEmptyString2(raw.activeAgentId);
|
|
1439
3332
|
const activeAgent = Array.isArray(raw.agents) ? raw.agents.find((agent) => asNonEmptyString2(agent.id) === activeAgentId) : activeAgentId ? raw.agents?.[activeAgentId] : undefined;
|
|
1440
3333
|
return asNonEmptyString2(activeAgent?.authorizedWallets?.solana) ?? asNonEmptyString2(activeAgent?.walletAddress) ?? asNonEmptyString2(activeAgent?.wallet_address);
|
|
@@ -1471,6 +3364,173 @@ function checkWalletConfigured2() {
|
|
|
1471
3364
|
}
|
|
1472
3365
|
var init_wallet2 = () => {};
|
|
1473
3366
|
|
|
3367
|
+
// ../../src/auth/bootstrap-agentmail.ts
|
|
3368
|
+
var exports_bootstrap_agentmail = {};
|
|
3369
|
+
__export(exports_bootstrap_agentmail, {
|
|
3370
|
+
bootstrapAgentMailKey: () => bootstrapAgentMailKey
|
|
3371
|
+
});
|
|
3372
|
+
import os6 from "node:os";
|
|
3373
|
+
import fs2 from "node:fs";
|
|
3374
|
+
import path9 from "node:path";
|
|
3375
|
+
async function bootstrapAgentMailKey() {
|
|
3376
|
+
log("bootstrap-agentmail", "starting — opening console.agentmail.to");
|
|
3377
|
+
const prevHeadless = process.env.HEADLESS;
|
|
3378
|
+
process.env.HEADLESS = "false";
|
|
3379
|
+
try {
|
|
3380
|
+
await start();
|
|
3381
|
+
const tabId = await getDefaultTab();
|
|
3382
|
+
await networkEnable(tabId);
|
|
3383
|
+
await importBrowserCookiesIntoTab(tabId, "agentmail.to");
|
|
3384
|
+
await importBrowserCookiesIntoTab(tabId, "clerk.agentmail.to");
|
|
3385
|
+
await importBrowserCookiesIntoTab(tabId, "github.com");
|
|
3386
|
+
await navigate(tabId, CONSOLE_URL);
|
|
3387
|
+
await new Promise((r) => setTimeout(r, SETTLE_MS));
|
|
3388
|
+
let currentUrl = await getCurrentUrl(tabId);
|
|
3389
|
+
let onDashboard = typeof currentUrl === "string" && currentUrl.includes("/dashboard");
|
|
3390
|
+
if (!onDashboard) {
|
|
3391
|
+
log("bootstrap-agentmail", "not logged in — looking for GitHub OAuth");
|
|
3392
|
+
const githubBtn = await evaluate(tabId, `(() => {
|
|
3393
|
+
// Clerk renders OAuth buttons — look for GitHub
|
|
3394
|
+
const btns = document.querySelectorAll('button, a');
|
|
3395
|
+
for (const btn of btns) {
|
|
3396
|
+
const text = (btn.textContent || '').toLowerCase();
|
|
3397
|
+
const ariaLabel = (btn.getAttribute('aria-label') || '').toLowerCase();
|
|
3398
|
+
if (text.includes('github') || ariaLabel.includes('github') ||
|
|
3399
|
+
btn.querySelector('img[alt*="github" i], svg[aria-label*="github" i]')) {
|
|
3400
|
+
btn.click();
|
|
3401
|
+
return 'clicked';
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
// Also check for social login containers
|
|
3405
|
+
const social = document.querySelector('.cl-socialButtonsBlockButton__github, [data-provider="github"]');
|
|
3406
|
+
if (social) { social.click(); return 'clicked'; }
|
|
3407
|
+
return 'not_found';
|
|
3408
|
+
})()`);
|
|
3409
|
+
if (githubBtn === "clicked") {
|
|
3410
|
+
log("bootstrap-agentmail", "clicked GitHub OAuth — waiting for redirect");
|
|
3411
|
+
const start2 = Date.now();
|
|
3412
|
+
while (Date.now() - start2 < AUTH_TIMEOUT_MS) {
|
|
3413
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
3414
|
+
currentUrl = await getCurrentUrl(tabId);
|
|
3415
|
+
if (typeof currentUrl === "string" && currentUrl.includes("/dashboard")) {
|
|
3416
|
+
onDashboard = true;
|
|
3417
|
+
log("bootstrap-agentmail", "GitHub OAuth completed — on dashboard");
|
|
3418
|
+
break;
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
if (!onDashboard) {
|
|
3423
|
+
log("bootstrap-agentmail", "could not auto-login — manual setup needed");
|
|
3424
|
+
return {
|
|
3425
|
+
success: false,
|
|
3426
|
+
method: "manual",
|
|
3427
|
+
error: "Could not auto-login to AgentMail console. Sign up at https://console.agentmail.to and create an API key manually."
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
log("bootstrap-agentmail", "on dashboard — navigating to API keys");
|
|
3432
|
+
await navigate(tabId, `${DASHBOARD_URL}/api-keys`);
|
|
3433
|
+
await new Promise((r) => setTimeout(r, SETTLE_MS));
|
|
3434
|
+
const createResult = await evaluate(tabId, `(() => {
|
|
3435
|
+
const btns = document.querySelectorAll('button, a');
|
|
3436
|
+
for (const btn of btns) {
|
|
3437
|
+
const text = (btn.textContent || '').toLowerCase();
|
|
3438
|
+
if (text.includes('create') && (text.includes('key') || text.includes('api'))) {
|
|
3439
|
+
btn.click();
|
|
3440
|
+
return 'clicked';
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
return 'not_found';
|
|
3444
|
+
})()`);
|
|
3445
|
+
if (createResult === "clicked") {
|
|
3446
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
3447
|
+
await evaluate(tabId, `(() => {
|
|
3448
|
+
const input = document.querySelector('input[placeholder*="name" i], input[name*="name" i], input[type="text"]');
|
|
3449
|
+
if (input) {
|
|
3450
|
+
input.value = 'unbrowse-agent';
|
|
3451
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
3452
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
3453
|
+
}
|
|
3454
|
+
})()`);
|
|
3455
|
+
await evaluate(tabId, `(() => {
|
|
3456
|
+
const btns = document.querySelectorAll('button');
|
|
3457
|
+
for (const btn of btns) {
|
|
3458
|
+
const text = (btn.textContent || '').toLowerCase();
|
|
3459
|
+
if (text.includes('create') || text.includes('confirm') || text.includes('generate')) {
|
|
3460
|
+
btn.click();
|
|
3461
|
+
return 'clicked';
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
})()`);
|
|
3465
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
3466
|
+
}
|
|
3467
|
+
const apiKey = await evaluate(tabId, `(() => {
|
|
3468
|
+
// Look for the key in common patterns
|
|
3469
|
+
// 1. Input/textarea with the key value
|
|
3470
|
+
const inputs = document.querySelectorAll('input[readonly], input[type="text"], textarea, code, pre');
|
|
3471
|
+
for (const el of inputs) {
|
|
3472
|
+
const val = el.value || el.textContent || '';
|
|
3473
|
+
// AgentMail keys typically start with 'am_' or are long alphanumeric strings
|
|
3474
|
+
if (/^(am_|sk_|key_)/.test(val) || (val.length > 30 && /^[a-zA-Z0-9_-]+$/.test(val))) {
|
|
3475
|
+
return val.trim();
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
// 2. Look in the page text for key patterns
|
|
3479
|
+
const text = document.body.innerText;
|
|
3480
|
+
const match = text.match(/(am_[a-zA-Z0-9_-]{20,}|sk_[a-zA-Z0-9_-]{20,})/);
|
|
3481
|
+
if (match) return match[1];
|
|
3482
|
+
return null;
|
|
3483
|
+
})()`);
|
|
3484
|
+
if (apiKey && typeof apiKey === "string" && apiKey.length > 10) {
|
|
3485
|
+
log("bootstrap-agentmail", `extracted API key: ${apiKey.substring(0, 8)}...`);
|
|
3486
|
+
await storeCredential("agentmail:api_key", apiKey);
|
|
3487
|
+
persistApiKeyToShell(apiKey);
|
|
3488
|
+
process.env.AGENTMAIL_API_KEY = apiKey;
|
|
3489
|
+
return {
|
|
3490
|
+
success: true,
|
|
3491
|
+
api_key: apiKey,
|
|
3492
|
+
method: onDashboard ? "existing-session" : "github-oauth"
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
return {
|
|
3496
|
+
success: false,
|
|
3497
|
+
error: "Reached dashboard but could not extract API key. Create one manually at https://console.agentmail.to/dashboard/api-keys"
|
|
3498
|
+
};
|
|
3499
|
+
} finally {
|
|
3500
|
+
if (prevHeadless !== undefined)
|
|
3501
|
+
process.env.HEADLESS = prevHeadless;
|
|
3502
|
+
else
|
|
3503
|
+
delete process.env.HEADLESS;
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
function persistApiKeyToShell(apiKey) {
|
|
3507
|
+
const shell = process.env.SHELL ?? "/bin/zsh";
|
|
3508
|
+
const rcFile = shell.includes("zsh") ? path9.join(os6.homedir(), ".zshrc") : path9.join(os6.homedir(), ".bashrc");
|
|
3509
|
+
try {
|
|
3510
|
+
const content = fs2.existsSync(rcFile) ? fs2.readFileSync(rcFile, "utf-8") : "";
|
|
3511
|
+
if (content.includes("AGENTMAIL_API_KEY")) {
|
|
3512
|
+
log("bootstrap-agentmail", `${rcFile} already has AGENTMAIL_API_KEY — not overwriting`);
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
fs2.appendFileSync(rcFile, `
|
|
3516
|
+
# AgentMail API key (added by unbrowse setup)
|
|
3517
|
+
export AGENTMAIL_API_KEY="${apiKey}"
|
|
3518
|
+
`);
|
|
3519
|
+
log("bootstrap-agentmail", `wrote AGENTMAIL_API_KEY to ${rcFile}`);
|
|
3520
|
+
} catch (err) {
|
|
3521
|
+
log("bootstrap-agentmail", `failed to write to ${rcFile}: ${err}`);
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
var CONSOLE_URL = "https://console.agentmail.to", DASHBOARD_URL = "https://console.agentmail.to/dashboard", SETTLE_MS = 3000, AUTH_TIMEOUT_MS = 120000;
|
|
3525
|
+
var init_bootstrap_agentmail = __esm(async () => {
|
|
3526
|
+
init_client2();
|
|
3527
|
+
init_logger();
|
|
3528
|
+
await __promiseAll([
|
|
3529
|
+
init_vault(),
|
|
3530
|
+
init_auth()
|
|
3531
|
+
]);
|
|
3532
|
+
});
|
|
3533
|
+
|
|
1474
3534
|
// ../../src/version.ts
|
|
1475
3535
|
var exports_version = {};
|
|
1476
3536
|
__export(exports_version, {
|
|
@@ -1487,13 +3547,13 @@ __export(exports_version, {
|
|
|
1487
3547
|
CODE_HASH: () => CODE_HASH2
|
|
1488
3548
|
});
|
|
1489
3549
|
import { createHash as createHash3 } from "crypto";
|
|
1490
|
-
import { existsSync as
|
|
1491
|
-
import { dirname as dirname4, join as
|
|
3550
|
+
import { existsSync as existsSync17, readFileSync as readFileSync11, readdirSync as readdirSync5 } from "fs";
|
|
3551
|
+
import { dirname as dirname4, join as join13, parse as parse2 } from "path";
|
|
1492
3552
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1493
3553
|
function collectTsFiles2(dir) {
|
|
1494
3554
|
const results = [];
|
|
1495
|
-
for (const entry of
|
|
1496
|
-
const full =
|
|
3555
|
+
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
3556
|
+
const full = join13(dir, entry.name);
|
|
1497
3557
|
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
1498
3558
|
results.push(...collectTsFiles2(full));
|
|
1499
3559
|
} else if (entry.name.endsWith(".ts")) {
|
|
@@ -1506,21 +3566,21 @@ function hashFiles2(srcDir, files) {
|
|
|
1506
3566
|
const hash = createHash3("sha256");
|
|
1507
3567
|
for (const file of files) {
|
|
1508
3568
|
hash.update(file.slice(srcDir.length));
|
|
1509
|
-
hash.update(
|
|
3569
|
+
hash.update(readFileSync11(file, "utf-8"));
|
|
1510
3570
|
}
|
|
1511
3571
|
return hash.digest("hex").slice(0, 12);
|
|
1512
3572
|
}
|
|
1513
3573
|
function resolveCodeHashSourceDir2(moduleDir) {
|
|
1514
3574
|
const candidates = [
|
|
1515
3575
|
moduleDir,
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
3576
|
+
join13(moduleDir, "runtime-src"),
|
|
3577
|
+
join13(moduleDir, "..", "runtime-src"),
|
|
3578
|
+
join13(moduleDir, "src"),
|
|
3579
|
+
join13(moduleDir, "..", "src")
|
|
1520
3580
|
];
|
|
1521
3581
|
for (const candidate of candidates) {
|
|
1522
3582
|
try {
|
|
1523
|
-
if (!
|
|
3583
|
+
if (!existsSync17(candidate))
|
|
1524
3584
|
continue;
|
|
1525
3585
|
const files = collectTsFiles2(candidate);
|
|
1526
3586
|
if (files.length > 0)
|
|
@@ -1570,7 +3630,7 @@ function getPackageVersionForModuleDir2(moduleDir) {
|
|
|
1570
3630
|
const root = parse2(dir).root;
|
|
1571
3631
|
while (true) {
|
|
1572
3632
|
try {
|
|
1573
|
-
const pkg = JSON.parse(
|
|
3633
|
+
const pkg = JSON.parse(readFileSync11(join13(dir, "package.json"), "utf-8"));
|
|
1574
3634
|
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
1575
3635
|
} catch {}
|
|
1576
3636
|
if (dir === root)
|
|
@@ -1597,218 +3657,310 @@ var init_version2 = __esm(() => {
|
|
|
1597
3657
|
});
|
|
1598
3658
|
|
|
1599
3659
|
// ../../src/auth/agent-mail.ts
|
|
1600
|
-
var
|
|
1601
|
-
__export(
|
|
1602
|
-
waitForVerificationEmail: () =>
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
3660
|
+
var exports_agent_mail2 = {};
|
|
3661
|
+
__export(exports_agent_mail2, {
|
|
3662
|
+
waitForVerificationEmail: () => waitForVerificationEmail2,
|
|
3663
|
+
tryAgentMailAuth: () => tryAgentMailAuth2,
|
|
3664
|
+
isAgentMailAvailable: () => isAgentMailAvailable2,
|
|
3665
|
+
getOrCreateSiteInbox: () => getOrCreateSiteInbox2,
|
|
3666
|
+
extractVerificationLink: () => extractVerificationLink2,
|
|
3667
|
+
extractOtpFromEmail: () => extractOtpFromEmail2,
|
|
3668
|
+
autonomousEmailLogin: () => autonomousEmailLogin2
|
|
1607
3669
|
});
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
3670
|
+
import { AgentMailClient as AgentMailClient2 } from "agentmail";
|
|
3671
|
+
function getClient2() {
|
|
3672
|
+
if (_client2)
|
|
3673
|
+
return _client2;
|
|
3674
|
+
const apiKey = process.env.AGENTMAIL_API_KEY;
|
|
3675
|
+
if (!apiKey)
|
|
3676
|
+
throw new Error("AGENTMAIL_API_KEY not set — cannot use agent mail");
|
|
3677
|
+
_client2 = new AgentMailClient2({ apiKey });
|
|
3678
|
+
return _client2;
|
|
3679
|
+
}
|
|
3680
|
+
async function getStoredInbox2(domain) {
|
|
3681
|
+
const key = `${VAULT_PREFIX2}${getRegistrableDomain(domain)}`;
|
|
3682
|
+
const raw = await getCredential(key);
|
|
3683
|
+
if (!raw)
|
|
3684
|
+
return null;
|
|
3685
|
+
try {
|
|
3686
|
+
return JSON.parse(raw);
|
|
3687
|
+
} catch {
|
|
3688
|
+
return null;
|
|
1620
3689
|
}
|
|
1621
|
-
return res.json();
|
|
1622
3690
|
}
|
|
1623
|
-
async function
|
|
1624
|
-
const
|
|
3691
|
+
async function storeInbox2(domain, inbox) {
|
|
3692
|
+
const key = `${VAULT_PREFIX2}${getRegistrableDomain(domain)}`;
|
|
3693
|
+
await storeCredential(key, JSON.stringify(inbox));
|
|
3694
|
+
log("agent-mail", `stored inbox ${inbox.email} for ${domain}`);
|
|
3695
|
+
}
|
|
3696
|
+
async function getOrCreateSiteInbox2(domain) {
|
|
3697
|
+
const stored = await getStoredInbox2(domain);
|
|
3698
|
+
if (stored) {
|
|
3699
|
+
log("agent-mail", `reusing stored inbox ${stored.email} for ${domain}`);
|
|
3700
|
+
return { inboxId: stored.inboxId, email: stored.email };
|
|
3701
|
+
}
|
|
3702
|
+
const client = getClient2();
|
|
3703
|
+
const safeDomain = getRegistrableDomain(domain).replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
1625
3704
|
const username = `unbrowse-${safeDomain}`;
|
|
1626
3705
|
const clientId = `unbrowse-login-${safeDomain}`;
|
|
1627
3706
|
try {
|
|
1628
|
-
const inbox = await
|
|
3707
|
+
const inbox = await client.inboxes.create({
|
|
1629
3708
|
username,
|
|
1630
3709
|
domain: "agentmail.to",
|
|
1631
|
-
|
|
1632
|
-
|
|
3710
|
+
displayName: `Unbrowse Agent - ${domain}`,
|
|
3711
|
+
clientId
|
|
1633
3712
|
});
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
3713
|
+
const result = { inboxId: inbox.inboxId, email: inbox.email };
|
|
3714
|
+
await storeInbox2(domain, { ...result, domain, createdAt: new Date().toISOString() });
|
|
3715
|
+
log("agent-mail", `created inbox ${inbox.email} for ${domain}`);
|
|
3716
|
+
return result;
|
|
3717
|
+
} catch (err) {
|
|
3718
|
+
log("agent-mail", `create failed (likely exists), listing to find match: ${err instanceof Error ? err.message : err}`);
|
|
1637
3719
|
try {
|
|
1638
|
-
const
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
3720
|
+
const list = await client.inboxes.list({ limit: 100 });
|
|
3721
|
+
const match = list.inboxes.find((i) => i.clientId === clientId || i.email === `${username}@agentmail.to`);
|
|
3722
|
+
if (match) {
|
|
3723
|
+
const result = { inboxId: match.inboxId, email: match.email };
|
|
3724
|
+
await storeInbox2(domain, { ...result, domain, createdAt: new Date().toISOString() });
|
|
3725
|
+
return result;
|
|
3726
|
+
}
|
|
3727
|
+
} catch {}
|
|
3728
|
+
throw new Error(`Failed to create or find AgentMail inbox for ${domain}`);
|
|
1643
3729
|
}
|
|
1644
3730
|
}
|
|
1645
|
-
async function
|
|
3731
|
+
async function waitForVerificationEmail2(inboxId, fromDomain, timeoutMs = DEFAULT_TIMEOUT_MS2) {
|
|
3732
|
+
const client = getClient2();
|
|
1646
3733
|
const start2 = Date.now();
|
|
1647
|
-
const
|
|
3734
|
+
const normalizedDomain = fromDomain.toLowerCase();
|
|
3735
|
+
log("agent-mail", `polling inbox ${inboxId} for email from *@${normalizedDomain} (timeout: ${timeoutMs}ms)`);
|
|
1648
3736
|
while (Date.now() - start2 < timeoutMs) {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
3737
|
+
try {
|
|
3738
|
+
const response = await client.inboxes.messages.list(inboxId, {
|
|
3739
|
+
limit: 10,
|
|
3740
|
+
labels: ["received"]
|
|
3741
|
+
});
|
|
3742
|
+
for (const item of response.messages ?? []) {
|
|
3743
|
+
const msg = await client.inboxes.messages.get(inboxId, item.messageId);
|
|
3744
|
+
const from = typeof msg.from === "string" ? msg.from : (msg.from ?? []).join(", ");
|
|
3745
|
+
if (from.toLowerCase().includes(normalizedDomain)) {
|
|
3746
|
+
log("agent-mail", `found verification email from ${from}: "${msg.subject}"`);
|
|
3747
|
+
return {
|
|
3748
|
+
messageId: msg.messageId,
|
|
3749
|
+
threadId: msg.threadId,
|
|
3750
|
+
subject: msg.subject ?? "",
|
|
3751
|
+
from,
|
|
3752
|
+
text: msg.extractedText ?? msg.text ?? "",
|
|
3753
|
+
html: msg.extractedHtml ?? msg.html ?? "",
|
|
3754
|
+
receivedAt: msg.createdAt
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
1657
3757
|
}
|
|
3758
|
+
} catch (err) {
|
|
3759
|
+
log("agent-mail", `poll error: ${err instanceof Error ? err.message : err}`);
|
|
1658
3760
|
}
|
|
1659
|
-
await new Promise((r) => setTimeout(r,
|
|
3761
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
1660
3762
|
}
|
|
3763
|
+
log("agent-mail", `timeout waiting for email from ${normalizedDomain}`);
|
|
1661
3764
|
return null;
|
|
1662
3765
|
}
|
|
1663
|
-
function
|
|
3766
|
+
function extractOtpFromEmail2(text) {
|
|
3767
|
+
const codePhrase = text.match(/(?:verification|confirm|security|login|one[- ]time|otp)\s*(?:code|pin|number)[:\s]+(\w{4,8})/i);
|
|
3768
|
+
if (codePhrase)
|
|
3769
|
+
return codePhrase[1];
|
|
3770
|
+
const codeIs = text.match(/(?:code|otp|pin)[:\s]+(\w{4,8})/i);
|
|
3771
|
+
if (codeIs)
|
|
3772
|
+
return codeIs[1];
|
|
1664
3773
|
const six = text.match(/\b(\d{6})\b/);
|
|
1665
3774
|
if (six)
|
|
1666
3775
|
return six[1];
|
|
1667
3776
|
const four = text.match(/\b(\d{4})\b/);
|
|
1668
3777
|
if (four)
|
|
1669
3778
|
return four[1];
|
|
1670
|
-
const codeIs = text.match(/code[:\s]+(\w{4,8})/i);
|
|
1671
|
-
if (codeIs)
|
|
1672
|
-
return codeIs[1];
|
|
1673
3779
|
return null;
|
|
1674
3780
|
}
|
|
1675
|
-
function
|
|
1676
|
-
const htmlLink = html.match(/href="(https?:\/\/[^"]*(?:verify|confirm|activate|magic|token|auth)[^"]*)"/i);
|
|
3781
|
+
function extractVerificationLink2(text, html) {
|
|
3782
|
+
const htmlLink = html.match(/href="(https?:\/\/[^"]*(?:verify|confirm|activate|magic|token|auth|callback|validate|approve|email)[^"]*)"/i);
|
|
1677
3783
|
if (htmlLink)
|
|
1678
3784
|
return htmlLink[1];
|
|
1679
|
-
const textLink = text.match(/(https?:\/\/\S*(?:verify|confirm|activate|magic|token|auth)\S*)/i);
|
|
3785
|
+
const textLink = text.match(/(https?:\/\/\S*(?:verify|confirm|activate|magic|token|auth|callback|validate|approve|email)\S*)/i);
|
|
1680
3786
|
if (textLink)
|
|
1681
3787
|
return textLink[1];
|
|
1682
|
-
const
|
|
1683
|
-
if (
|
|
1684
|
-
return
|
|
3788
|
+
const anyHtmlLink = html.match(/href="(https?:\/\/[^"]+)"/);
|
|
3789
|
+
if (anyHtmlLink)
|
|
3790
|
+
return anyHtmlLink[1];
|
|
3791
|
+
const anyTextLink = text.match(/(https?:\/\/\S+)/);
|
|
3792
|
+
if (anyTextLink)
|
|
3793
|
+
return anyTextLink[1];
|
|
1685
3794
|
return null;
|
|
1686
3795
|
}
|
|
1687
|
-
async function
|
|
1688
|
-
const inbox = await
|
|
3796
|
+
async function autonomousEmailLogin2(domain) {
|
|
3797
|
+
const inbox = await getOrCreateSiteInbox2(domain);
|
|
3798
|
+
const client = getClient2();
|
|
3799
|
+
log("agent-mail", `session ready for ${domain} — email: ${inbox.email}`);
|
|
1689
3800
|
return {
|
|
1690
3801
|
email: inbox.email,
|
|
1691
|
-
inboxId: inbox.
|
|
1692
|
-
|
|
1693
|
-
|
|
3802
|
+
inboxId: inbox.inboxId,
|
|
3803
|
+
domain,
|
|
3804
|
+
waitForOtp: async (timeoutMs = DEFAULT_TIMEOUT_MS2) => {
|
|
3805
|
+
const email = await waitForVerificationEmail2(inbox.inboxId, domain, timeoutMs);
|
|
1694
3806
|
if (!email)
|
|
1695
3807
|
return null;
|
|
1696
|
-
|
|
3808
|
+
const otp = extractOtpFromEmail2(email.text);
|
|
3809
|
+
if (otp)
|
|
3810
|
+
log("agent-mail", `extracted OTP: ${otp} from ${email.subject}`);
|
|
3811
|
+
return otp;
|
|
1697
3812
|
},
|
|
1698
|
-
waitForLink: async () => {
|
|
1699
|
-
const email = await
|
|
3813
|
+
waitForLink: async (timeoutMs = DEFAULT_TIMEOUT_MS2) => {
|
|
3814
|
+
const email = await waitForVerificationEmail2(inbox.inboxId, domain, timeoutMs);
|
|
1700
3815
|
if (!email)
|
|
1701
3816
|
return null;
|
|
1702
|
-
|
|
3817
|
+
const link = extractVerificationLink2(email.text, email.html);
|
|
3818
|
+
if (link)
|
|
3819
|
+
log("agent-mail", `extracted verification link from ${email.subject}`);
|
|
3820
|
+
return link;
|
|
3821
|
+
},
|
|
3822
|
+
waitForEmail: (timeoutMs = DEFAULT_TIMEOUT_MS2) => waitForVerificationEmail2(inbox.inboxId, domain, timeoutMs),
|
|
3823
|
+
sendEmail: async (to, subject, body) => {
|
|
3824
|
+
const result = await client.inboxes.messages.send(inbox.inboxId, {
|
|
3825
|
+
to: [to],
|
|
3826
|
+
subject,
|
|
3827
|
+
text: body
|
|
3828
|
+
});
|
|
3829
|
+
log("agent-mail", `sent email to ${to}: "${subject}" (${result.messageId})`);
|
|
3830
|
+
return { messageId: result.messageId, threadId: result.threadId };
|
|
3831
|
+
},
|
|
3832
|
+
replyTo: async (messageId, body) => {
|
|
3833
|
+
const result = await client.inboxes.messages.reply(inbox.inboxId, messageId, {
|
|
3834
|
+
text: body
|
|
3835
|
+
});
|
|
3836
|
+
log("agent-mail", `replied to ${messageId} (${result.messageId})`);
|
|
3837
|
+
return { messageId: result.messageId, threadId: result.threadId };
|
|
1703
3838
|
}
|
|
1704
3839
|
};
|
|
1705
3840
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
3841
|
+
function isAgentMailAvailable2() {
|
|
3842
|
+
return Boolean(process.env.AGENTMAIL_API_KEY);
|
|
3843
|
+
}
|
|
3844
|
+
async function tryAgentMailAuth2(domain) {
|
|
3845
|
+
if (!isAgentMailAvailable2()) {
|
|
3846
|
+
log("agent-mail", `skipping — AGENTMAIL_API_KEY not set`);
|
|
3847
|
+
return null;
|
|
3848
|
+
}
|
|
3849
|
+
try {
|
|
3850
|
+
return await autonomousEmailLogin2(domain);
|
|
3851
|
+
} catch (err) {
|
|
3852
|
+
log("agent-mail", `auth failed for ${domain}: ${err instanceof Error ? err.message : err}`);
|
|
3853
|
+
return null;
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
var POLL_INTERVAL_MS2 = 3000, DEFAULT_TIMEOUT_MS2 = 90000, _client2 = null, VAULT_PREFIX2 = "agentmail:";
|
|
3857
|
+
var init_agent_mail2 = __esm(async () => {
|
|
3858
|
+
init_logger();
|
|
3859
|
+
init_domain();
|
|
3860
|
+
await init_vault();
|
|
1709
3861
|
});
|
|
1710
3862
|
|
|
1711
3863
|
// ../../src/auth/browser-cookies.ts
|
|
1712
|
-
var
|
|
1713
|
-
__export(
|
|
1714
|
-
scanAllBrowserSessions: () =>
|
|
1715
|
-
resolveChromiumCookiesPath: () =>
|
|
1716
|
-
findBestBrowserSession: () =>
|
|
1717
|
-
extractFromFirefox: () =>
|
|
1718
|
-
extractFromChromium: () =>
|
|
1719
|
-
extractFromChrome: () =>
|
|
1720
|
-
extractBrowserCookies: () =>
|
|
1721
|
-
decodeChromiumCookieValue: () =>
|
|
3864
|
+
var exports_browser_cookies2 = {};
|
|
3865
|
+
__export(exports_browser_cookies2, {
|
|
3866
|
+
scanAllBrowserSessions: () => scanAllBrowserSessions2,
|
|
3867
|
+
resolveChromiumCookiesPath: () => resolveChromiumCookiesPath2,
|
|
3868
|
+
findBestBrowserSession: () => findBestBrowserSession2,
|
|
3869
|
+
extractFromFirefox: () => extractFromFirefox2,
|
|
3870
|
+
extractFromChromium: () => extractFromChromium2,
|
|
3871
|
+
extractFromChrome: () => extractFromChrome2,
|
|
3872
|
+
extractBrowserCookies: () => extractBrowserCookies2,
|
|
3873
|
+
decodeChromiumCookieValue: () => decodeChromiumCookieValue2
|
|
1722
3874
|
});
|
|
1723
|
-
import { execFileSync as
|
|
1724
|
-
import { createDecipheriv, pbkdf2Sync } from "node:crypto";
|
|
1725
|
-
import { copyFileSync, existsSync as
|
|
1726
|
-
import { tmpdir, homedir as
|
|
1727
|
-
import { join as
|
|
1728
|
-
function
|
|
1729
|
-
const home =
|
|
1730
|
-
if (
|
|
1731
|
-
return
|
|
1732
|
-
}
|
|
1733
|
-
if (
|
|
1734
|
-
const appData = process.env.LOCALAPPDATA ??
|
|
1735
|
-
return
|
|
1736
|
-
}
|
|
1737
|
-
return
|
|
1738
|
-
}
|
|
1739
|
-
function
|
|
3875
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
3876
|
+
import { createDecipheriv as createDecipheriv3, pbkdf2Sync as pbkdf2Sync2 } from "node:crypto";
|
|
3877
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync18, mkdtempSync as mkdtempSync2, readdirSync as readdirSync6, rmSync as rmSync2 } from "node:fs";
|
|
3878
|
+
import { tmpdir as tmpdir2, homedir as homedir8, platform as platform2 } from "node:os";
|
|
3879
|
+
import { join as join14 } from "node:path";
|
|
3880
|
+
function getChromeUserDataDir2() {
|
|
3881
|
+
const home = homedir8();
|
|
3882
|
+
if (platform2() === "darwin") {
|
|
3883
|
+
return join14(home, "Library", "Application Support", "Google", "Chrome");
|
|
3884
|
+
}
|
|
3885
|
+
if (platform2() === "win32") {
|
|
3886
|
+
const appData = process.env.LOCALAPPDATA ?? join14(home, "AppData", "Local");
|
|
3887
|
+
return join14(appData, "Google", "Chrome", "User Data");
|
|
3888
|
+
}
|
|
3889
|
+
return join14(home, ".config", "google-chrome");
|
|
3890
|
+
}
|
|
3891
|
+
function resolveChromiumCookiesPath2(opts) {
|
|
1740
3892
|
if (opts?.cookieDbPath) {
|
|
1741
|
-
return opts.cookieDbPath.replace(/^~\//,
|
|
3893
|
+
return opts.cookieDbPath.replace(/^~\//, homedir8() + "/");
|
|
1742
3894
|
}
|
|
1743
3895
|
const profileDir = opts?.profile || "Default";
|
|
1744
|
-
const userDataDir = (opts?.userDataDir ||
|
|
3896
|
+
const userDataDir = (opts?.userDataDir || getChromeUserDataDir2()).replace(/^~\//, homedir8() + "/");
|
|
1745
3897
|
const candidates = [
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
3898
|
+
join14(userDataDir, profileDir, "Network", "Cookies"),
|
|
3899
|
+
join14(userDataDir, profileDir, "Cookies"),
|
|
3900
|
+
join14(userDataDir, "Network", "Cookies"),
|
|
3901
|
+
join14(userDataDir, "Cookies")
|
|
1750
3902
|
];
|
|
1751
|
-
return candidates.find((candidate) =>
|
|
3903
|
+
return candidates.find((candidate) => existsSync18(candidate)) ?? candidates[0] ?? null;
|
|
1752
3904
|
}
|
|
1753
|
-
function
|
|
1754
|
-
const home =
|
|
1755
|
-
if (
|
|
1756
|
-
return
|
|
3905
|
+
function getFirefoxProfilesRoot2() {
|
|
3906
|
+
const home = homedir8();
|
|
3907
|
+
if (platform2() === "darwin") {
|
|
3908
|
+
return join14(home, "Library", "Application Support", "Firefox", "Profiles");
|
|
1757
3909
|
}
|
|
1758
|
-
if (
|
|
1759
|
-
return
|
|
3910
|
+
if (platform2() === "linux") {
|
|
3911
|
+
return join14(home, ".mozilla", "firefox");
|
|
1760
3912
|
}
|
|
1761
|
-
if (
|
|
3913
|
+
if (platform2() === "win32") {
|
|
1762
3914
|
const appData = process.env.APPDATA;
|
|
1763
3915
|
if (!appData)
|
|
1764
3916
|
return null;
|
|
1765
|
-
return
|
|
3917
|
+
return join14(appData, "Mozilla", "Firefox", "Profiles");
|
|
1766
3918
|
}
|
|
1767
3919
|
return null;
|
|
1768
3920
|
}
|
|
1769
|
-
function
|
|
3921
|
+
function pickFirefoxProfile2(profilesRoot, profile) {
|
|
1770
3922
|
if (profile) {
|
|
1771
|
-
const candidate2 =
|
|
1772
|
-
return
|
|
3923
|
+
const candidate2 = join14(profilesRoot, profile, "cookies.sqlite");
|
|
3924
|
+
return existsSync18(candidate2) ? candidate2 : null;
|
|
1773
3925
|
}
|
|
1774
|
-
const entries =
|
|
3926
|
+
const entries = readdirSync6(profilesRoot, { withFileTypes: true });
|
|
1775
3927
|
const defaultRelease = entries.find((e) => e.isDirectory() && e.name.includes("default-release"));
|
|
1776
3928
|
const targetDir = defaultRelease?.name ?? entries.find((e) => e.isDirectory())?.name;
|
|
1777
3929
|
if (!targetDir)
|
|
1778
3930
|
return null;
|
|
1779
|
-
const candidate =
|
|
1780
|
-
return
|
|
3931
|
+
const candidate = join14(profilesRoot, targetDir, "cookies.sqlite");
|
|
3932
|
+
return existsSync18(candidate) ? candidate : null;
|
|
1781
3933
|
}
|
|
1782
|
-
function
|
|
1783
|
-
const profilesRoot =
|
|
1784
|
-
if (!profilesRoot || !
|
|
3934
|
+
function getFirefoxCookiesPath2(profile) {
|
|
3935
|
+
const profilesRoot = getFirefoxProfilesRoot2();
|
|
3936
|
+
if (!profilesRoot || !existsSync18(profilesRoot))
|
|
1785
3937
|
return null;
|
|
1786
|
-
return
|
|
3938
|
+
return pickFirefoxProfile2(profilesRoot, profile);
|
|
1787
3939
|
}
|
|
1788
|
-
function
|
|
3940
|
+
function getChromiumKeychainServiceName2(opts) {
|
|
1789
3941
|
if (opts?.safeStorageService)
|
|
1790
3942
|
return opts.safeStorageService;
|
|
1791
3943
|
return `${opts?.browserName || "Chrome"} Safe Storage`;
|
|
1792
3944
|
}
|
|
1793
|
-
function
|
|
1794
|
-
const service =
|
|
1795
|
-
const cached =
|
|
3945
|
+
function getChromiumDecryptionKey2(opts) {
|
|
3946
|
+
const service = getChromiumKeychainServiceName2(opts);
|
|
3947
|
+
const cached = _chromiumKeyCache2.get(service);
|
|
1796
3948
|
if (cached)
|
|
1797
3949
|
return cached;
|
|
1798
|
-
if (
|
|
3950
|
+
if (platform2() !== "darwin")
|
|
1799
3951
|
return null;
|
|
1800
3952
|
try {
|
|
1801
|
-
const keyOutput =
|
|
3953
|
+
const keyOutput = execFileSync5("security", ["find-generic-password", "-s", service, "-w"], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
1802
3954
|
if (!keyOutput)
|
|
1803
3955
|
return null;
|
|
1804
|
-
const derived =
|
|
1805
|
-
|
|
3956
|
+
const derived = pbkdf2Sync2(keyOutput, "saltysalt", 1003, 16, "sha1");
|
|
3957
|
+
_chromiumKeyCache2.set(service, derived);
|
|
1806
3958
|
return derived;
|
|
1807
3959
|
} catch {
|
|
1808
3960
|
return null;
|
|
1809
3961
|
}
|
|
1810
3962
|
}
|
|
1811
|
-
function
|
|
3963
|
+
function decryptChromiumValue2(encryptedHex, opts) {
|
|
1812
3964
|
try {
|
|
1813
3965
|
const buf = Buffer.from(encryptedHex, "hex");
|
|
1814
3966
|
if (buf.length < 4)
|
|
@@ -1817,7 +3969,7 @@ function decryptChromiumValue(encryptedHex, opts) {
|
|
|
1817
3969
|
if (version !== "v10" && version !== "v11") {
|
|
1818
3970
|
return buf.toString("utf8");
|
|
1819
3971
|
}
|
|
1820
|
-
const key =
|
|
3972
|
+
const key = getChromiumDecryptionKey2(opts);
|
|
1821
3973
|
if (!key)
|
|
1822
3974
|
return null;
|
|
1823
3975
|
const payload = buf.subarray(3);
|
|
@@ -1825,7 +3977,7 @@ function decryptChromiumValue(encryptedHex, opts) {
|
|
|
1825
3977
|
try {
|
|
1826
3978
|
const iv2 = payload.subarray(16, 32);
|
|
1827
3979
|
const encrypted = payload.subarray(32);
|
|
1828
|
-
const decipher2 =
|
|
3980
|
+
const decipher2 = createDecipheriv3("aes-128-cbc", key, iv2);
|
|
1829
3981
|
decipher2.setAutoPadding(true);
|
|
1830
3982
|
const decrypted2 = Buffer.concat([decipher2.update(encrypted), decipher2.final()]);
|
|
1831
3983
|
const val = decrypted2.toString("utf8").replace(/[^\x20-\x7E]/g, "");
|
|
@@ -1834,7 +3986,7 @@ function decryptChromiumValue(encryptedHex, opts) {
|
|
|
1834
3986
|
} catch {}
|
|
1835
3987
|
}
|
|
1836
3988
|
const iv = Buffer.alloc(16, 32);
|
|
1837
|
-
const decipher =
|
|
3989
|
+
const decipher = createDecipheriv3("aes-128-cbc", key, iv);
|
|
1838
3990
|
decipher.setAutoPadding(true);
|
|
1839
3991
|
const decrypted = Buffer.concat([decipher.update(payload), decipher.final()]);
|
|
1840
3992
|
return decrypted.toString("utf8").replace(/[^\x20-\x7E]/g, "");
|
|
@@ -1842,37 +3994,37 @@ function decryptChromiumValue(encryptedHex, opts) {
|
|
|
1842
3994
|
return null;
|
|
1843
3995
|
}
|
|
1844
3996
|
}
|
|
1845
|
-
function
|
|
3997
|
+
function decodeChromiumCookieValue2(rawValue, encryptedHex, opts) {
|
|
1846
3998
|
if (rawValue)
|
|
1847
3999
|
return rawValue;
|
|
1848
4000
|
if (!encryptedHex)
|
|
1849
4001
|
return null;
|
|
1850
|
-
return
|
|
4002
|
+
return decryptChromiumValue2(encryptedHex, opts);
|
|
1851
4003
|
}
|
|
1852
|
-
function
|
|
1853
|
-
const tempDir =
|
|
1854
|
-
const tempDb =
|
|
4004
|
+
function withTempCopy2(dbPath, fn) {
|
|
4005
|
+
const tempDir = mkdtempSync2(join14(tmpdir2(), "unbrowse-cookies-"));
|
|
4006
|
+
const tempDb = join14(tempDir, "cookies.db");
|
|
1855
4007
|
try {
|
|
1856
|
-
|
|
4008
|
+
copyFileSync2(dbPath, tempDb);
|
|
1857
4009
|
for (const ext of ["-wal", "-shm"]) {
|
|
1858
4010
|
const src = dbPath + ext;
|
|
1859
|
-
if (
|
|
1860
|
-
|
|
4011
|
+
if (existsSync18(src))
|
|
4012
|
+
copyFileSync2(src, tempDb + ext);
|
|
1861
4013
|
}
|
|
1862
4014
|
return fn(tempDb);
|
|
1863
4015
|
} finally {
|
|
1864
4016
|
try {
|
|
1865
|
-
|
|
4017
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
1866
4018
|
} catch {}
|
|
1867
4019
|
}
|
|
1868
4020
|
}
|
|
1869
|
-
function
|
|
1870
|
-
return
|
|
4021
|
+
function sqliteQuery2(dbPath, sql) {
|
|
4022
|
+
return execFileSync5("sqlite3", ["-separator", "|", dbPath, sql], {
|
|
1871
4023
|
encoding: "utf8",
|
|
1872
4024
|
maxBuffer: 4 * 1024 * 1024
|
|
1873
4025
|
}).trim();
|
|
1874
4026
|
}
|
|
1875
|
-
function
|
|
4027
|
+
function buildDomainWhereClause2(domain, column) {
|
|
1876
4028
|
const reg = getRegistrableDomain(domain);
|
|
1877
4029
|
const variants = new Set([
|
|
1878
4030
|
reg,
|
|
@@ -1891,25 +4043,25 @@ function buildDomainWhereClause(domain, column) {
|
|
|
1891
4043
|
const likePattern = `'%.${likeReg}'`;
|
|
1892
4044
|
return `(${column} IN (${escaped.join(", ")}) OR ${column} LIKE ${likePattern})`;
|
|
1893
4045
|
}
|
|
1894
|
-
function
|
|
1895
|
-
return
|
|
4046
|
+
function extractFromChrome2(domain, opts) {
|
|
4047
|
+
return extractFromChromium2(domain, {
|
|
1896
4048
|
profile: opts?.profile,
|
|
1897
4049
|
browserName: "Chrome"
|
|
1898
4050
|
});
|
|
1899
4051
|
}
|
|
1900
|
-
function
|
|
4052
|
+
function extractFromChromium2(domain, opts) {
|
|
1901
4053
|
const warnings = [];
|
|
1902
|
-
const dbPath =
|
|
4054
|
+
const dbPath = resolveChromiumCookiesPath2(opts);
|
|
1903
4055
|
const sourceLabel = opts?.browserName || "Chromium";
|
|
1904
|
-
if (!dbPath || !
|
|
4056
|
+
if (!dbPath || !existsSync18(dbPath)) {
|
|
1905
4057
|
warnings.push(`${sourceLabel} cookies DB not found${dbPath ? ` at ${dbPath}` : ""}`);
|
|
1906
4058
|
return { cookies: [], source: null, warnings };
|
|
1907
4059
|
}
|
|
1908
4060
|
try {
|
|
1909
|
-
const cookies =
|
|
1910
|
-
const where =
|
|
4061
|
+
const cookies = withTempCopy2(dbPath, (tempDb) => {
|
|
4062
|
+
const where = buildDomainWhereClause2(domain, "host_key");
|
|
1911
4063
|
const sql = `SELECT name, value, hex(encrypted_value) as ev, host_key, path, is_secure, is_httponly, samesite, expires_utc FROM cookies WHERE ${where};`;
|
|
1912
|
-
const rows =
|
|
4064
|
+
const rows = sqliteQuery2(tempDb, sql);
|
|
1913
4065
|
if (!rows)
|
|
1914
4066
|
return [];
|
|
1915
4067
|
const results = [];
|
|
@@ -1919,7 +4071,7 @@ function extractFromChromium(domain, opts) {
|
|
|
1919
4071
|
if (parts.length < 9)
|
|
1920
4072
|
continue;
|
|
1921
4073
|
const [name, rawValue, encHex, host, cookiePath, secure, httpOnly, sameSite, expiresUtc] = parts;
|
|
1922
|
-
const value =
|
|
4074
|
+
const value = decodeChromiumCookieValue2(rawValue, encHex, opts);
|
|
1923
4075
|
if (!value)
|
|
1924
4076
|
continue;
|
|
1925
4077
|
results.push({
|
|
@@ -1946,18 +4098,18 @@ function extractFromChromium(domain, opts) {
|
|
|
1946
4098
|
return { cookies: [], source: null, warnings };
|
|
1947
4099
|
}
|
|
1948
4100
|
}
|
|
1949
|
-
function
|
|
4101
|
+
function extractFromFirefox2(domain, opts) {
|
|
1950
4102
|
const warnings = [];
|
|
1951
|
-
const dbPath =
|
|
4103
|
+
const dbPath = getFirefoxCookiesPath2(opts?.profile);
|
|
1952
4104
|
if (!dbPath) {
|
|
1953
4105
|
warnings.push("Firefox cookies DB not found");
|
|
1954
4106
|
return { cookies: [], source: null, warnings };
|
|
1955
4107
|
}
|
|
1956
4108
|
try {
|
|
1957
|
-
const cookies =
|
|
1958
|
-
const where =
|
|
4109
|
+
const cookies = withTempCopy2(dbPath, (tempDb) => {
|
|
4110
|
+
const where = buildDomainWhereClause2(domain, "host");
|
|
1959
4111
|
const sql = `SELECT name, value, host, path, isSecure, isHttpOnly, sameSite, expiry FROM moz_cookies WHERE ${where};`;
|
|
1960
|
-
const rows =
|
|
4112
|
+
const rows = sqliteQuery2(tempDb, sql);
|
|
1961
4113
|
if (!rows)
|
|
1962
4114
|
return [];
|
|
1963
4115
|
const results = [];
|
|
@@ -1993,37 +4145,37 @@ function extractFromFirefox(domain, opts) {
|
|
|
1993
4145
|
return { cookies: [], source: null, warnings };
|
|
1994
4146
|
}
|
|
1995
4147
|
}
|
|
1996
|
-
function
|
|
4148
|
+
function extractBrowserCookies2(domain, opts) {
|
|
1997
4149
|
if (opts?.browser === "firefox") {
|
|
1998
|
-
return
|
|
4150
|
+
return extractFromFirefox2(domain, { profile: opts.firefoxProfile });
|
|
1999
4151
|
}
|
|
2000
4152
|
if (opts?.browser === "chrome") {
|
|
2001
|
-
return
|
|
4153
|
+
return extractFromChrome2(domain, { profile: opts.chromeProfile });
|
|
2002
4154
|
}
|
|
2003
4155
|
if (opts?.browser === "chromium") {
|
|
2004
|
-
return
|
|
4156
|
+
return extractFromChromium2(domain, opts.chromium);
|
|
2005
4157
|
}
|
|
2006
|
-
const ff =
|
|
4158
|
+
const ff = extractFromFirefox2(domain, { profile: opts?.firefoxProfile });
|
|
2007
4159
|
if (ff.cookies.length > 0)
|
|
2008
4160
|
return ff;
|
|
2009
4161
|
if (opts?.chromium?.cookieDbPath || opts?.chromium?.userDataDir) {
|
|
2010
|
-
const chromium =
|
|
4162
|
+
const chromium = extractFromChromium2(domain, opts.chromium);
|
|
2011
4163
|
chromium.warnings.push(...ff.warnings);
|
|
2012
4164
|
return chromium;
|
|
2013
4165
|
}
|
|
2014
|
-
const chrome =
|
|
4166
|
+
const chrome = extractFromChrome2(domain, { profile: opts?.chromeProfile });
|
|
2015
4167
|
chrome.warnings.push(...ff.warnings);
|
|
2016
4168
|
return chrome;
|
|
2017
4169
|
}
|
|
2018
|
-
function
|
|
4170
|
+
function scanAllBrowserSessions2(domain) {
|
|
2019
4171
|
const results = [];
|
|
2020
|
-
const home =
|
|
2021
|
-
for (const browser of
|
|
2022
|
-
const userDataDir =
|
|
2023
|
-
if (!
|
|
4172
|
+
const home = homedir8();
|
|
4173
|
+
for (const browser of CHROMIUM_BROWSERS2) {
|
|
4174
|
+
const userDataDir = platform2() === "darwin" ? join14(home, "Library", "Application Support", browser.macPath) : platform2() === "win32" ? join14(process.env.LOCALAPPDATA ?? join14(home, "AppData", "Local"), browser.macPath, "User Data") : join14(home, ".config", browser.macPath.toLowerCase());
|
|
4175
|
+
if (!existsSync18(userDataDir))
|
|
2024
4176
|
continue;
|
|
2025
4177
|
try {
|
|
2026
|
-
const result =
|
|
4178
|
+
const result = extractFromChromium2(domain, {
|
|
2027
4179
|
userDataDir,
|
|
2028
4180
|
browserName: browser.name
|
|
2029
4181
|
});
|
|
@@ -2039,7 +4191,7 @@ function scanAllBrowserSessions(domain) {
|
|
|
2039
4191
|
} catch {}
|
|
2040
4192
|
}
|
|
2041
4193
|
try {
|
|
2042
|
-
const ff =
|
|
4194
|
+
const ff = extractFromFirefox2(domain);
|
|
2043
4195
|
if (ff.cookies.length > 0) {
|
|
2044
4196
|
const sessionCookies = ff.cookies.filter((c) => c.httpOnly || c.secure).length;
|
|
2045
4197
|
results.push({
|
|
@@ -2053,16 +4205,16 @@ function scanAllBrowserSessions(domain) {
|
|
|
2053
4205
|
results.sort((a, b) => b.sessionCookies - a.sessionCookies);
|
|
2054
4206
|
return results;
|
|
2055
4207
|
}
|
|
2056
|
-
function
|
|
2057
|
-
const sessions =
|
|
4208
|
+
function findBestBrowserSession2(domain) {
|
|
4209
|
+
const sessions = scanAllBrowserSessions2(domain);
|
|
2058
4210
|
return sessions[0] ?? null;
|
|
2059
4211
|
}
|
|
2060
|
-
var
|
|
2061
|
-
var
|
|
4212
|
+
var _chromiumKeyCache2, CHROMIUM_BROWSERS2;
|
|
4213
|
+
var init_browser_cookies2 = __esm(() => {
|
|
2062
4214
|
init_logger();
|
|
2063
4215
|
init_domain();
|
|
2064
|
-
|
|
2065
|
-
|
|
4216
|
+
_chromiumKeyCache2 = new Map;
|
|
4217
|
+
CHROMIUM_BROWSERS2 = [
|
|
2066
4218
|
{ name: "Chrome", macPath: "Google/Chrome" },
|
|
2067
4219
|
{ name: "Arc", macPath: "Arc/User Data" },
|
|
2068
4220
|
{ name: "Brave", macPath: "BraveSoftware/Brave-Browser" },
|
|
@@ -3328,8 +5480,8 @@ init_publish();
|
|
|
3328
5480
|
init_settings();
|
|
3329
5481
|
init_graph();
|
|
3330
5482
|
init_schema_review();
|
|
3331
|
-
import { join as
|
|
3332
|
-
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ??
|
|
5483
|
+
import { join as join11 } from "node:path";
|
|
5484
|
+
var SKILL_SNAPSHOT_DIR3 = process.env.UNBROWSE_SKILL_SNAPSHOT_DIR ?? join11(process.env.HOME ?? "/tmp", ".unbrowse", "skill-snapshots");
|
|
3333
5485
|
var indexInFlight2 = new Map;
|
|
3334
5486
|
var pendingIndexJobs2 = new Map;
|
|
3335
5487
|
async function drainPendingIndexJobs() {
|
|
@@ -3366,14 +5518,14 @@ init_paths();
|
|
|
3366
5518
|
init_client2();
|
|
3367
5519
|
init_logger();
|
|
3368
5520
|
init_wallet();
|
|
3369
|
-
import { execFileSync as
|
|
3370
|
-
import { existsSync as
|
|
5521
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
5522
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3371
5523
|
import os4 from "node:os";
|
|
3372
5524
|
import path7 from "node:path";
|
|
3373
5525
|
|
|
3374
5526
|
// ../../src/runtime/update-hints.ts
|
|
3375
5527
|
init_paths();
|
|
3376
|
-
import { existsSync as
|
|
5528
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3377
5529
|
import os3 from "node:os";
|
|
3378
5530
|
import path6 from "node:path";
|
|
3379
5531
|
var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
@@ -3387,20 +5539,20 @@ function getConfigDir2() {
|
|
|
3387
5539
|
return path6.join(getHomeDir(), ".unbrowse");
|
|
3388
5540
|
}
|
|
3389
5541
|
function ensureDir3(dir) {
|
|
3390
|
-
if (!
|
|
3391
|
-
|
|
5542
|
+
if (!existsSync13(dir))
|
|
5543
|
+
mkdirSync7(dir, { recursive: true });
|
|
3392
5544
|
return dir;
|
|
3393
5545
|
}
|
|
3394
5546
|
function readJsonFile(file) {
|
|
3395
5547
|
try {
|
|
3396
|
-
return JSON.parse(
|
|
5548
|
+
return JSON.parse(readFileSync8(file, "utf8"));
|
|
3397
5549
|
} catch {
|
|
3398
5550
|
return null;
|
|
3399
5551
|
}
|
|
3400
5552
|
}
|
|
3401
5553
|
function writeJsonFile(file, value) {
|
|
3402
5554
|
ensureDir3(path6.dirname(file));
|
|
3403
|
-
|
|
5555
|
+
writeFileSync5(file, `${JSON.stringify(value, null, 2)}
|
|
3404
5556
|
`);
|
|
3405
5557
|
}
|
|
3406
5558
|
function getInstallSourcePath() {
|
|
@@ -3410,7 +5562,7 @@ function detectRepoRoot(start2) {
|
|
|
3410
5562
|
let dir = path6.resolve(start2);
|
|
3411
5563
|
const root = path6.parse(dir).root;
|
|
3412
5564
|
while (dir !== root) {
|
|
3413
|
-
if (
|
|
5565
|
+
if (existsSync13(path6.join(dir, ".git")))
|
|
3414
5566
|
return dir;
|
|
3415
5567
|
dir = path6.dirname(dir);
|
|
3416
5568
|
}
|
|
@@ -3489,13 +5641,13 @@ codex_hooks = true
|
|
|
3489
5641
|
}
|
|
3490
5642
|
function writeCodexHook(metaUrl) {
|
|
3491
5643
|
const configPath = getCodexConfigPath();
|
|
3492
|
-
if (!
|
|
5644
|
+
if (!existsSync13(path6.dirname(configPath))) {
|
|
3493
5645
|
return { host: "codex", action: "not-detected", config_file: configPath };
|
|
3494
5646
|
}
|
|
3495
5647
|
try {
|
|
3496
5648
|
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
3497
|
-
const fileExistsBefore =
|
|
3498
|
-
let content = fileExistsBefore ?
|
|
5649
|
+
const fileExistsBefore = existsSync13(configPath);
|
|
5650
|
+
let content = fileExistsBefore ? readFileSync8(configPath, "utf8") : "";
|
|
3499
5651
|
const previous = content;
|
|
3500
5652
|
content = ensureCodexHooksFeature(content);
|
|
3501
5653
|
if (!content.includes("unbrowse-update-hint.mjs")) {
|
|
@@ -3510,7 +5662,7 @@ command = ${JSON.stringify(command)}
|
|
|
3510
5662
|
`;
|
|
3511
5663
|
}
|
|
3512
5664
|
if (content !== previous) {
|
|
3513
|
-
|
|
5665
|
+
writeFileSync5(configPath, content, "utf8");
|
|
3514
5666
|
return {
|
|
3515
5667
|
host: "codex",
|
|
3516
5668
|
action: fileExistsBefore ? "updated" : "installed",
|
|
@@ -3529,13 +5681,13 @@ command = ${JSON.stringify(command)}
|
|
|
3529
5681
|
}
|
|
3530
5682
|
function writeClaudeHook(metaUrl) {
|
|
3531
5683
|
const settingsPath = getClaudeSettingsPath();
|
|
3532
|
-
if (!
|
|
5684
|
+
if (!existsSync13(path6.dirname(settingsPath))) {
|
|
3533
5685
|
return { host: "claude", action: "not-detected", config_file: settingsPath };
|
|
3534
5686
|
}
|
|
3535
5687
|
try {
|
|
3536
5688
|
const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
|
|
3537
5689
|
const command = `node "${hookScript}"`;
|
|
3538
|
-
const fileExistsBefore =
|
|
5690
|
+
const fileExistsBefore = existsSync13(settingsPath);
|
|
3539
5691
|
const settings = readJsonFile(settingsPath) ?? {};
|
|
3540
5692
|
settings.hooks ??= {};
|
|
3541
5693
|
settings.hooks.SessionStart ??= [];
|
|
@@ -3573,7 +5725,7 @@ function configureUpdateHintHooks(metaUrl, install) {
|
|
|
3573
5725
|
function hasBinary(name) {
|
|
3574
5726
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
3575
5727
|
try {
|
|
3576
|
-
|
|
5728
|
+
execFileSync4(checker, [name], { stdio: "ignore" });
|
|
3577
5729
|
return true;
|
|
3578
5730
|
} catch {
|
|
3579
5731
|
return false;
|
|
@@ -3600,7 +5752,7 @@ function getOpenCodeProjectCommandsDir(cwd) {
|
|
|
3600
5752
|
return path7.join(cwd, ".opencode", "commands");
|
|
3601
5753
|
}
|
|
3602
5754
|
function detectOpenCode(cwd) {
|
|
3603
|
-
return hasBinary("opencode") ||
|
|
5755
|
+
return hasBinary("opencode") || existsSync14(path7.join(resolveConfigHome(), "opencode")) || existsSync14(path7.join(cwd, ".opencode"));
|
|
3604
5756
|
}
|
|
3605
5757
|
function renderOpenCodeCommand() {
|
|
3606
5758
|
return `---
|
|
@@ -3628,13 +5780,13 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
3628
5780
|
if (scope === "auto" && !detected) {
|
|
3629
5781
|
return { detected: false, action: "not-detected", scope: "off" };
|
|
3630
5782
|
}
|
|
3631
|
-
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" :
|
|
5783
|
+
const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync14(path7.join(cwd, ".opencode")) ? "project" : "global";
|
|
3632
5784
|
const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
|
|
3633
5785
|
const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
|
|
3634
5786
|
const content = renderOpenCodeCommand();
|
|
3635
|
-
const action2 =
|
|
3636
|
-
|
|
3637
|
-
|
|
5787
|
+
const action2 = existsSync14(commandFile) ? "updated" : "installed";
|
|
5788
|
+
mkdirSync8(path7.dirname(commandFile), { recursive: true });
|
|
5789
|
+
writeFileSync6(commandFile, content);
|
|
3638
5790
|
return {
|
|
3639
5791
|
detected: detected || scope !== "auto",
|
|
3640
5792
|
action: action2,
|
|
@@ -3644,10 +5796,10 @@ function writeOpenCodeCommand(scope, cwd) {
|
|
|
3644
5796
|
}
|
|
3645
5797
|
async function ensureBrowserEngineInstalled() {
|
|
3646
5798
|
const binary = findKuriBinary();
|
|
3647
|
-
if (
|
|
5799
|
+
if (existsSync14(binary)) {
|
|
3648
5800
|
return { installed: true, action: "already-installed" };
|
|
3649
5801
|
}
|
|
3650
|
-
const sourceDir = getKuriSourceCandidates().find((candidate) =>
|
|
5802
|
+
const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync14(path7.join(candidate, "build.zig")));
|
|
3651
5803
|
if (!sourceDir) {
|
|
3652
5804
|
return {
|
|
3653
5805
|
installed: false,
|
|
@@ -3663,13 +5815,13 @@ async function ensureBrowserEngineInstalled() {
|
|
|
3663
5815
|
};
|
|
3664
5816
|
}
|
|
3665
5817
|
try {
|
|
3666
|
-
|
|
5818
|
+
execFileSync4("zig", ["build", "-Doptimize=ReleaseFast"], {
|
|
3667
5819
|
cwd: sourceDir,
|
|
3668
5820
|
stdio: "inherit",
|
|
3669
5821
|
timeout: 300000
|
|
3670
5822
|
});
|
|
3671
5823
|
const builtBinary = findKuriBinary();
|
|
3672
|
-
if (
|
|
5824
|
+
if (existsSync14(builtBinary)) {
|
|
3673
5825
|
return {
|
|
3674
5826
|
installed: true,
|
|
3675
5827
|
action: "installed",
|
|
@@ -3694,12 +5846,12 @@ async function runSetup(options) {
|
|
|
3694
5846
|
const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
|
|
3695
5847
|
const walletCheck = checkWalletConfigured();
|
|
3696
5848
|
const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
|
|
3697
|
-
let lobsterInstalled = hasBinary("lobstercash") ||
|
|
5849
|
+
let lobsterInstalled = hasBinary("lobstercash") || existsSync14(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
|
|
3698
5850
|
if (!skipWalletSetup && !walletCheck.configured) {
|
|
3699
5851
|
if (!lobsterInstalled) {
|
|
3700
5852
|
console.log("[unbrowse] Setting up Crossmint wallet (required for earning + payments)...");
|
|
3701
5853
|
try {
|
|
3702
|
-
|
|
5854
|
+
execFileSync4("npx", ["@crossmint/lobster-cli", "setup"], {
|
|
3703
5855
|
stdio: "inherit",
|
|
3704
5856
|
timeout: 120000
|
|
3705
5857
|
});
|
|
@@ -3711,7 +5863,7 @@ async function runSetup(options) {
|
|
|
3711
5863
|
} else {
|
|
3712
5864
|
console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
|
|
3713
5865
|
try {
|
|
3714
|
-
|
|
5866
|
+
execFileSync4("npx", ["@crossmint/lobster-cli", "setup"], {
|
|
3715
5867
|
stdio: "inherit",
|
|
3716
5868
|
timeout: 60000
|
|
3717
5869
|
});
|
|
@@ -3742,13 +5894,17 @@ async function runSetup(options) {
|
|
|
3742
5894
|
browser_engine: browser,
|
|
3743
5895
|
opencode: writeOpenCodeCommand(options?.opencode ?? "auto", cwd),
|
|
3744
5896
|
update_hints: configureUpdateHintHooks(import.meta.url, installSource),
|
|
3745
|
-
wallet
|
|
5897
|
+
wallet,
|
|
5898
|
+
agent_mail: {
|
|
5899
|
+
configured: Boolean(process.env.AGENTMAIL_API_KEY),
|
|
5900
|
+
message: process.env.AGENTMAIL_API_KEY ? "AgentMail configured — autonomous email login enabled." : "AgentMail not configured. Set AGENTMAIL_API_KEY to enable autonomous email login/registration. Get a key at https://agentmail.to"
|
|
5901
|
+
}
|
|
3746
5902
|
};
|
|
3747
5903
|
}
|
|
3748
5904
|
|
|
3749
5905
|
// ../../src/runtime/update-hints.ts
|
|
3750
5906
|
init_paths();
|
|
3751
|
-
import { existsSync as
|
|
5907
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
3752
5908
|
import os5 from "node:os";
|
|
3753
5909
|
import path8 from "node:path";
|
|
3754
5910
|
var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
|
|
@@ -3762,20 +5918,20 @@ function getConfigDir3() {
|
|
|
3762
5918
|
return path8.join(getHomeDir2(), ".unbrowse");
|
|
3763
5919
|
}
|
|
3764
5920
|
function ensureDir4(dir) {
|
|
3765
|
-
if (!
|
|
3766
|
-
|
|
5921
|
+
if (!existsSync15(dir))
|
|
5922
|
+
mkdirSync9(dir, { recursive: true });
|
|
3767
5923
|
return dir;
|
|
3768
5924
|
}
|
|
3769
5925
|
function readJsonFile2(file) {
|
|
3770
5926
|
try {
|
|
3771
|
-
return JSON.parse(
|
|
5927
|
+
return JSON.parse(readFileSync9(file, "utf8"));
|
|
3772
5928
|
} catch {
|
|
3773
5929
|
return null;
|
|
3774
5930
|
}
|
|
3775
5931
|
}
|
|
3776
5932
|
function writeJsonFile2(file, value) {
|
|
3777
5933
|
ensureDir4(path8.dirname(file));
|
|
3778
|
-
|
|
5934
|
+
writeFileSync7(file, `${JSON.stringify(value, null, 2)}
|
|
3779
5935
|
`);
|
|
3780
5936
|
}
|
|
3781
5937
|
function getInstallSourcePath2() {
|
|
@@ -3788,7 +5944,7 @@ function detectRepoRoot2(start2) {
|
|
|
3788
5944
|
let dir = path8.resolve(start2);
|
|
3789
5945
|
const root = path8.parse(dir).root;
|
|
3790
5946
|
while (dir !== root) {
|
|
3791
|
-
if (
|
|
5947
|
+
if (existsSync15(path8.join(dir, ".git")))
|
|
3792
5948
|
return dir;
|
|
3793
5949
|
dir = path8.dirname(dir);
|
|
3794
5950
|
}
|
|
@@ -3821,7 +5977,7 @@ function detectInstallHost2(repoRoot) {
|
|
|
3821
5977
|
function getInstalledVersion(metaUrl) {
|
|
3822
5978
|
const packageRoot = getPackageRoot(metaUrl);
|
|
3823
5979
|
try {
|
|
3824
|
-
const pkg = JSON.parse(
|
|
5980
|
+
const pkg = JSON.parse(readFileSync9(path8.join(packageRoot, "package.json"), "utf8"));
|
|
3825
5981
|
return pkg.version ?? "unknown";
|
|
3826
5982
|
} catch {
|
|
3827
5983
|
return "unknown";
|
|
@@ -3946,8 +6102,8 @@ function parseArgs(argv) {
|
|
|
3946
6102
|
}
|
|
3947
6103
|
return { command, args: positional, flags };
|
|
3948
6104
|
}
|
|
3949
|
-
async function api2(method,
|
|
3950
|
-
let target = `${BASE_URL}${
|
|
6105
|
+
async function api2(method, path10, body) {
|
|
6106
|
+
let target = `${BASE_URL}${path10}`;
|
|
3951
6107
|
let requestBody = body;
|
|
3952
6108
|
if (method === "GET" && body && typeof body === "object") {
|
|
3953
6109
|
const params = new URLSearchParams;
|
|
@@ -4251,7 +6407,7 @@ async function cmdResolve(flags) {
|
|
|
4251
6407
|
const { checkWalletConfigured: checkWalletConfigured3 } = await Promise.resolve().then(() => (init_wallet2(), exports_wallet));
|
|
4252
6408
|
const wallet = checkWalletConfigured3();
|
|
4253
6409
|
if (!wallet.configured) {
|
|
4254
|
-
info("
|
|
6410
|
+
info("No payout wallet \u2014 you won't earn when others reuse your routes. Run: npx @crossmint/lobster-cli setup");
|
|
4255
6411
|
walletNudgeShown = true;
|
|
4256
6412
|
}
|
|
4257
6413
|
} catch (_e) {}
|
|
@@ -4277,8 +6433,8 @@ async function cmdResolve(flags) {
|
|
|
4277
6433
|
throw error;
|
|
4278
6434
|
}
|
|
4279
6435
|
}
|
|
4280
|
-
function drillPath(data,
|
|
4281
|
-
const segments =
|
|
6436
|
+
function drillPath(data, path10) {
|
|
6437
|
+
const segments = path10.split(/\./).flatMap((s) => {
|
|
4282
6438
|
const m = s.match(/^(.+)\[\]$/);
|
|
4283
6439
|
return m ? [m[1], "[]"] : [s];
|
|
4284
6440
|
});
|
|
@@ -4305,9 +6461,9 @@ function drillPath(data, path9) {
|
|
|
4305
6461
|
}
|
|
4306
6462
|
return values;
|
|
4307
6463
|
}
|
|
4308
|
-
function resolveDotPath(obj,
|
|
6464
|
+
function resolveDotPath(obj, path10) {
|
|
4309
6465
|
let cur = obj;
|
|
4310
|
-
for (const key of
|
|
6466
|
+
for (const key of path10.split(".")) {
|
|
4311
6467
|
if (cur == null || typeof cur !== "object")
|
|
4312
6468
|
return;
|
|
4313
6469
|
cur = cur[key];
|
|
@@ -4324,8 +6480,8 @@ function applyExtract(items, extractSpec) {
|
|
|
4324
6480
|
return items.map((item) => {
|
|
4325
6481
|
const row = {};
|
|
4326
6482
|
let hasValue = false;
|
|
4327
|
-
for (const { alias, path:
|
|
4328
|
-
const val = resolveDotPath(item,
|
|
6483
|
+
for (const { alias, path: path10 } of fields) {
|
|
6484
|
+
const val = resolveDotPath(item, path10);
|
|
4329
6485
|
row[alias] = val ?? null;
|
|
4330
6486
|
if (val != null)
|
|
4331
6487
|
hasValue = true;
|
|
@@ -4675,13 +6831,49 @@ async function cmdSetup(flags) {
|
|
|
4675
6831
|
if (report.wallet.configured) {
|
|
4676
6832
|
info(`Wallet configured (${report.wallet.provider}): ${report.wallet.wallet_address ?? "linked"}`);
|
|
4677
6833
|
} else if (report.wallet.lobster_installed) {
|
|
4678
|
-
info("Wallet not paired \u2014
|
|
6834
|
+
info("Wallet not paired \u2014 you won't earn when other agents use routes you discovered.");
|
|
4679
6835
|
info("Run: npx @crossmint/lobster-cli setup");
|
|
4680
6836
|
} else {
|
|
4681
|
-
info("No wallet configured \u2014 you
|
|
4682
|
-
info("Set up a wallet
|
|
6837
|
+
info("No wallet configured \u2014 you earn when other agents reuse routes you discovered.");
|
|
6838
|
+
info("Set up a wallet to start earning:");
|
|
4683
6839
|
info(" npx @crossmint/lobster-cli setup");
|
|
4684
6840
|
}
|
|
6841
|
+
if (!report.agent_mail.configured) {
|
|
6842
|
+
info("AgentMail not configured \u2014 attempting to bootstrap via console.agentmail.to...");
|
|
6843
|
+
try {
|
|
6844
|
+
const { bootstrapAgentMailKey: bootstrapAgentMailKey2 } = await init_bootstrap_agentmail().then(() => exports_bootstrap_agentmail);
|
|
6845
|
+
const result = await bootstrapAgentMailKey2();
|
|
6846
|
+
if (result.success) {
|
|
6847
|
+
info(`AgentMail API key obtained (${result.method}) \u2014 autonomous email login enabled`);
|
|
6848
|
+
report.agent_mail.configured = true;
|
|
6849
|
+
} else {
|
|
6850
|
+
info(`AgentMail bootstrap: ${result.error}`);
|
|
6851
|
+
}
|
|
6852
|
+
} catch (err) {
|
|
6853
|
+
info(`AgentMail bootstrap failed: ${err instanceof Error ? err.message : err}`);
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
const hasGcloud = (() => {
|
|
6857
|
+
try {
|
|
6858
|
+
const { existsSync: existsSync19 } = __require("fs");
|
|
6859
|
+
const { homedir: homedir9 } = __require("os");
|
|
6860
|
+
const { join: join15 } = __require("path");
|
|
6861
|
+
return existsSync19(join15(homedir9(), ".config", "gcloud", "application_default_credentials.json"));
|
|
6862
|
+
} catch {
|
|
6863
|
+
return false;
|
|
6864
|
+
}
|
|
6865
|
+
})();
|
|
6866
|
+
const emailProviders = [];
|
|
6867
|
+
if (hasGcloud)
|
|
6868
|
+
emailProviders.push("Gmail (via GWS)");
|
|
6869
|
+
if (report.agent_mail.configured)
|
|
6870
|
+
emailProviders.push("AgentMail");
|
|
6871
|
+
if (emailProviders.length > 0) {
|
|
6872
|
+
info(`Email providers: ${emailProviders.join(", ")} \u2014 autonomous login enabled`);
|
|
6873
|
+
} else {
|
|
6874
|
+
info("No email provider configured \u2014 agents can't auto-register on gated sites.");
|
|
6875
|
+
info("Options: export AGENTMAIL_API_KEY=<key> (https://agentmail.to) or gcloud auth login (Gmail)");
|
|
6876
|
+
}
|
|
4685
6877
|
await recordInstallTelemetryEvent("setup", {
|
|
4686
6878
|
hostType,
|
|
4687
6879
|
status: report.browser_engine.action === "failed" ? "failed" : "installed",
|
|
@@ -5062,7 +7254,7 @@ async function cmdEarnings(flags) {
|
|
|
5062
7254
|
lines.push(` Progress: ${pct}% to self-sustaining`);
|
|
5063
7255
|
}
|
|
5064
7256
|
lines.push("");
|
|
5065
|
-
lines.push("
|
|
7257
|
+
lines.push("Every resolve discovers routes \u2014 you earn when other agents reuse them.");
|
|
5066
7258
|
info(lines.join(`
|
|
5067
7259
|
`));
|
|
5068
7260
|
} catch (err) {
|
|
@@ -5417,56 +7609,69 @@ async function cmdSync(flags) {
|
|
|
5417
7609
|
async function cmdClose(flags) {
|
|
5418
7610
|
output(await api2("POST", "/v1/browse/close", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
|
|
5419
7611
|
}
|
|
5420
|
-
async function cmdLoginAuto(flags) {
|
|
5421
|
-
const
|
|
5422
|
-
if (!
|
|
5423
|
-
return die("
|
|
7612
|
+
async function cmdLoginAuto(args, flags) {
|
|
7613
|
+
const urlOrDomain = args[0] ?? flags.url ?? flags.domain;
|
|
7614
|
+
if (!urlOrDomain)
|
|
7615
|
+
return die("usage: unbrowse login-auto <domain-or-url> [--wait-otp | --wait-link | --send-to <email>]");
|
|
5424
7616
|
const domain = (() => {
|
|
5425
7617
|
try {
|
|
5426
|
-
return new URL(
|
|
7618
|
+
return new URL(urlOrDomain.startsWith("http") ? urlOrDomain : `https://${urlOrDomain}`).hostname.replace(/^www\./, "");
|
|
5427
7619
|
} catch {
|
|
5428
|
-
return
|
|
7620
|
+
return urlOrDomain;
|
|
5429
7621
|
}
|
|
5430
7622
|
})();
|
|
7623
|
+
const { autonomousEmailLogin: autonomousEmailLogin3, isAgentMailAvailable: isAgentMailAvailable3 } = await init_agent_mail2().then(() => exports_agent_mail2);
|
|
7624
|
+
if (!isAgentMailAvailable3())
|
|
7625
|
+
return die("AGENTMAIL_API_KEY not set \u2014 run: export AGENTMAIL_API_KEY=<key>");
|
|
5431
7626
|
info(`[login-auto] creating agent email for ${domain}...`);
|
|
5432
|
-
const
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
7627
|
+
const session = await autonomousEmailLogin3(domain);
|
|
7628
|
+
info(`[login-auto] email: ${session.email}`);
|
|
7629
|
+
const sendTo = flags["send-to"];
|
|
7630
|
+
if (sendTo) {
|
|
7631
|
+
const subject = flags.subject ?? `Verify ${domain}`;
|
|
7632
|
+
const body = flags.body ?? `This is an automated verification email from Unbrowse agent for ${domain}.`;
|
|
7633
|
+
info(`[login-auto] sending email to ${sendTo}...`);
|
|
7634
|
+
const result = await session.sendEmail(sendTo, subject, body);
|
|
7635
|
+
output({ email: session.email, sent_to: sendTo, message_id: result.messageId, domain });
|
|
7636
|
+
return;
|
|
7637
|
+
}
|
|
5438
7638
|
if (flags["wait-otp"]) {
|
|
5439
|
-
|
|
5440
|
-
|
|
7639
|
+
const timeout = Number(flags.timeout) || 90000;
|
|
7640
|
+
info(`[login-auto] waiting for OTP from ${domain} (${Math.round(timeout / 1000)}s timeout)...`);
|
|
7641
|
+
const otp = await session.waitForOtp(timeout);
|
|
5441
7642
|
if (otp) {
|
|
5442
|
-
info(`[login-auto] OTP
|
|
7643
|
+
info(`[login-auto] OTP: ${otp}`);
|
|
5443
7644
|
output({ email: session.email, otp, domain });
|
|
5444
7645
|
} else {
|
|
5445
|
-
info(`[login-auto] no OTP received
|
|
7646
|
+
info(`[login-auto] no OTP received`);
|
|
5446
7647
|
output({ email: session.email, otp: null, domain, error: "timeout" });
|
|
5447
7648
|
}
|
|
5448
7649
|
return;
|
|
5449
7650
|
}
|
|
5450
7651
|
if (flags["wait-link"]) {
|
|
5451
|
-
|
|
5452
|
-
|
|
7652
|
+
const timeout = Number(flags.timeout) || 90000;
|
|
7653
|
+
info(`[login-auto] waiting for verification link from ${domain} (${Math.round(timeout / 1000)}s timeout)...`);
|
|
7654
|
+
const link = await session.waitForLink(timeout);
|
|
5453
7655
|
if (link) {
|
|
5454
|
-
info(`[login-auto]
|
|
7656
|
+
info(`[login-auto] link: ${link}`);
|
|
5455
7657
|
output({ email: session.email, link, domain });
|
|
5456
7658
|
} else {
|
|
5457
|
-
info(`[login-auto] no verification
|
|
7659
|
+
info(`[login-auto] no verification link received`);
|
|
5458
7660
|
output({ email: session.email, link: null, domain, error: "timeout" });
|
|
5459
7661
|
}
|
|
5460
7662
|
return;
|
|
5461
7663
|
}
|
|
7664
|
+
info(`[login-auto] use this email to register/login on ${domain}`);
|
|
7665
|
+
info(`[login-auto] then: unbrowse login-auto ${domain} --wait-otp`);
|
|
7666
|
+
info(`[login-auto] or: unbrowse login-auto ${domain} --wait-link`);
|
|
5462
7667
|
output({ email: session.email, inbox_id: session.inboxId, domain });
|
|
5463
7668
|
}
|
|
5464
7669
|
async function cmdSessionsScan(flags) {
|
|
5465
7670
|
const domain = flags.domain;
|
|
5466
|
-
const { scanAllBrowserSessions:
|
|
5467
|
-
const { execFileSync:
|
|
7671
|
+
const { scanAllBrowserSessions: scanAllBrowserSessions3, findBestBrowserSession: findBestBrowserSession3 } = await Promise.resolve().then(() => (init_browser_cookies2(), exports_browser_cookies2));
|
|
7672
|
+
const { execFileSync: execFileSync6, existsSync: existsSync19 } = await import("./cli-imports.js").catch(() => ({ execFileSync: __require("child_process").execFileSync, existsSync: __require("fs").existsSync }));
|
|
5468
7673
|
if (domain) {
|
|
5469
|
-
const sessions =
|
|
7674
|
+
const sessions = scanAllBrowserSessions3(domain);
|
|
5470
7675
|
if (sessions.length === 0) {
|
|
5471
7676
|
info(`No browser has a session for ${domain}`);
|
|
5472
7677
|
output({ domain, sessions: [], best: null });
|
|
@@ -5486,13 +7691,13 @@ async function cmdSessionsScan(flags) {
|
|
|
5486
7691
|
return;
|
|
5487
7692
|
}
|
|
5488
7693
|
const home = __require("os").homedir();
|
|
5489
|
-
const { join:
|
|
7694
|
+
const { join: join15 } = __require("path");
|
|
5490
7695
|
const browsers = [
|
|
5491
|
-
{ name: "Chrome", path:
|
|
5492
|
-
{ name: "Dia", path:
|
|
5493
|
-
{ name: "Arc", path:
|
|
5494
|
-
{ name: "Brave", path:
|
|
5495
|
-
{ name: "Edge", path:
|
|
7696
|
+
{ name: "Chrome", path: join15(home, "Library/Application Support/Google/Chrome/Default/Cookies") },
|
|
7697
|
+
{ name: "Dia", path: join15(home, "Library/Application Support/Dia/User Data/Default/Cookies") },
|
|
7698
|
+
{ name: "Arc", path: join15(home, "Library/Application Support/Arc/User Data/Default/Cookies") },
|
|
7699
|
+
{ name: "Brave", path: join15(home, "Library/Application Support/BraveSoftware/Brave-Browser/Default/Cookies") },
|
|
7700
|
+
{ name: "Edge", path: join15(home, "Library/Application Support/Microsoft Edge/Default/Cookies") }
|
|
5496
7701
|
];
|
|
5497
7702
|
const allSessions = [];
|
|
5498
7703
|
for (const b of browsers) {
|
|
@@ -5603,8 +7808,8 @@ async function cmdCorpusRun(flags) {
|
|
|
5603
7808
|
}
|
|
5604
7809
|
let gitSha = "unknown";
|
|
5605
7810
|
try {
|
|
5606
|
-
const { execSync:
|
|
5607
|
-
gitSha =
|
|
7811
|
+
const { execSync: execSync4 } = __require("child_process");
|
|
7812
|
+
gitSha = execSync4("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
|
|
5608
7813
|
} catch {}
|
|
5609
7814
|
const startTime = Date.now();
|
|
5610
7815
|
const results = [];
|
|
@@ -5680,7 +7885,7 @@ async function cmdCorpusRun(flags) {
|
|
|
5680
7885
|
output(snapshot2, !!flags.pretty);
|
|
5681
7886
|
}
|
|
5682
7887
|
async function cmdConnectChrome() {
|
|
5683
|
-
const { execSync:
|
|
7888
|
+
const { execSync: execSync4, spawn: spawnProc } = __require("child_process");
|
|
5684
7889
|
try {
|
|
5685
7890
|
const res = await fetch("http://127.0.0.1:9222/json/version", { signal: AbortSignal.timeout(1000) });
|
|
5686
7891
|
if (res.ok) {
|
|
@@ -5693,16 +7898,16 @@ async function cmdConnectChrome() {
|
|
|
5693
7898
|
}
|
|
5694
7899
|
} catch {}
|
|
5695
7900
|
try {
|
|
5696
|
-
|
|
7901
|
+
execSync4("pkill -f kuri/chrome-profile", { stdio: "ignore" });
|
|
5697
7902
|
} catch {}
|
|
5698
7903
|
console.log("Quitting Chrome to relaunch with remote debugging...");
|
|
5699
7904
|
if (process.platform === "darwin") {
|
|
5700
7905
|
try {
|
|
5701
|
-
|
|
7906
|
+
execSync4('osascript -e "quit app \\"Google Chrome\\""', { stdio: "ignore", timeout: 5000 });
|
|
5702
7907
|
} catch {}
|
|
5703
7908
|
} else {
|
|
5704
7909
|
try {
|
|
5705
|
-
|
|
7910
|
+
execSync4("pkill -f chrome", { stdio: "ignore" });
|
|
5706
7911
|
} catch {}
|
|
5707
7912
|
}
|
|
5708
7913
|
await new Promise((r) => setTimeout(r, 2000));
|
|
@@ -5761,7 +7966,7 @@ async function main() {
|
|
|
5761
7966
|
if (command === "sessions-scan")
|
|
5762
7967
|
return cmdSessionsScan(flags);
|
|
5763
7968
|
if (command === "login-auto")
|
|
5764
|
-
return cmdLoginAuto(flags);
|
|
7969
|
+
return cmdLoginAuto(args, flags);
|
|
5765
7970
|
const KNOWN_COMMANDS = new Set([
|
|
5766
7971
|
"health",
|
|
5767
7972
|
"mcp",
|
|
@@ -5925,7 +8130,7 @@ async function main() {
|
|
|
5925
8130
|
case "sessions-scan":
|
|
5926
8131
|
return cmdSessionsScan(flags);
|
|
5927
8132
|
case "login-auto":
|
|
5928
|
-
return cmdLoginAuto(flags);
|
|
8133
|
+
return cmdLoginAuto(args, flags);
|
|
5929
8134
|
default:
|
|
5930
8135
|
info(`Unknown command: ${command}`);
|
|
5931
8136
|
printHelp();
|