unbrowse 3.8.0 → 4.0.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 +16 -1785
- package/dist/mcp.js +5 -11
- package/dist/server.js +22 -660
- package/package.json +1 -2
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 = "
|
|
34
|
+
var BUILD_RELEASE_VERSION = "4.0.0", BUILD_GIT_SHA = "d08d50cd0432", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNC4wLjAiLCJnaXRfc2hhIjoiZDA4ZDUwY2QwNDMyIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUBkMDhkNTBjZDA0MzIiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTI1VDAyOjM5OjQxLjc4OVoifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "ICQ4NQIuyX0CMPhz1VoclQwk4PHMivS0ddVrr1ZTFgc", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
|
|
35
35
|
|
|
36
36
|
// ../../src/version.ts
|
|
37
37
|
import { createHash } from "crypto";
|
|
@@ -530,11 +530,6 @@ function getRegistrableDomain(hostname2) {
|
|
|
530
530
|
}
|
|
531
531
|
return parts.slice(-2).join(".");
|
|
532
532
|
}
|
|
533
|
-
function isDomainMatch(cookieDomain, targetDomain) {
|
|
534
|
-
const c = cookieDomain.replace(/^\./, "").toLowerCase();
|
|
535
|
-
const t = targetDomain.replace(/^\./, "").toLowerCase();
|
|
536
|
-
return t === c || t.endsWith("." + c);
|
|
537
|
-
}
|
|
538
533
|
var CC_TLDS, GEO_TLD_SUFFIXES;
|
|
539
534
|
var init_domain = __esm(() => {
|
|
540
535
|
CC_TLDS = new Set([
|
|
@@ -640,7 +635,6 @@ var init_logger = __esm(() => {
|
|
|
640
635
|
// ../../src/kuri/client.ts
|
|
641
636
|
import { execFileSync as execFileSync2, spawn as spawn2 } from "node:child_process";
|
|
642
637
|
import { existsSync as existsSync9 } from "node:fs";
|
|
643
|
-
import net from "node:net";
|
|
644
638
|
import path5 from "node:path";
|
|
645
639
|
function createBrokerState(port = KURI_DEFAULT_PORT) {
|
|
646
640
|
return {
|
|
@@ -653,39 +647,6 @@ function createBrokerState(port = KURI_DEFAULT_PORT) {
|
|
|
653
647
|
requestedPort: port
|
|
654
648
|
};
|
|
655
649
|
}
|
|
656
|
-
function brokerCacheKey(port) {
|
|
657
|
-
return port === undefined ? "default" : `port:${port}`;
|
|
658
|
-
}
|
|
659
|
-
function rememberBrokerClient(client, state) {
|
|
660
|
-
brokerClients.set(brokerCacheKey(state.requestedPort), client);
|
|
661
|
-
brokerClients.set(brokerCacheKey(state.port), client);
|
|
662
|
-
}
|
|
663
|
-
function forgetBrokerClient(state) {
|
|
664
|
-
brokerClients.delete(brokerCacheKey(state.requestedPort));
|
|
665
|
-
brokerClients.delete(brokerCacheKey(state.port));
|
|
666
|
-
}
|
|
667
|
-
function envFlag(value) {
|
|
668
|
-
return value === "1" || value?.toLowerCase() === "true";
|
|
669
|
-
}
|
|
670
|
-
function falseyEnv(value) {
|
|
671
|
-
if (!value)
|
|
672
|
-
return false;
|
|
673
|
-
const normalized = value.trim().toLowerCase();
|
|
674
|
-
return normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off";
|
|
675
|
-
}
|
|
676
|
-
function resolveKuriLaunchConfig(env = process.env) {
|
|
677
|
-
const explicitHeadless = env.KURI_HEADLESS ?? env.HEADLESS;
|
|
678
|
-
const headless = explicitHeadless !== undefined ? envFlag(explicitHeadless) : process.platform === "linux" && !env.DISPLAY;
|
|
679
|
-
const cleanRoom = envFlag(env.UNBROWSE_LOCAL_ONLY) || envFlag(env.KURI_CLEAN_ROOM);
|
|
680
|
-
const browserCookieOptOut = falseyEnv(env.UNBROWSE_IMPORT_BROWSER_COOKIES);
|
|
681
|
-
const explicitAttach = envFlag(env.KURI_ATTACH_EXISTING_CHROME ?? env.UNBROWSE_ATTACH_EXISTING_CHROME);
|
|
682
|
-
const disableCdpAttach = envFlag(env.KURI_DISABLE_CDP_ATTACH);
|
|
683
|
-
const canAttachToExistingChrome = !headless && !disableCdpAttach && !cleanRoom;
|
|
684
|
-
return {
|
|
685
|
-
headless,
|
|
686
|
-
attachToExistingChrome: canAttachToExistingChrome && (explicitAttach || browserCookieOptOut)
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
650
|
function kuriBinaryName() {
|
|
690
651
|
return process.platform === "win32" ? "kuri.exe" : "kuri";
|
|
691
652
|
}
|
|
@@ -744,679 +705,13 @@ function getKuriBinaryCandidates() {
|
|
|
744
705
|
addCandidate(candidates, resolveBinaryOnPath("kuri"));
|
|
745
706
|
return candidates;
|
|
746
707
|
}
|
|
747
|
-
async function discoverCdpPort(state) {
|
|
748
|
-
const portsToTry = [9222, 9223, 9224, 9225];
|
|
749
|
-
for (const port of portsToTry) {
|
|
750
|
-
try {
|
|
751
|
-
const res = await fetch(`http://127.0.0.1:${port}/json/version`, {
|
|
752
|
-
signal: AbortSignal.timeout(500)
|
|
753
|
-
});
|
|
754
|
-
if (res.ok) {
|
|
755
|
-
state.cdpPort = port;
|
|
756
|
-
kuriCdpPort = port;
|
|
757
|
-
log("kuri", `found Chrome CDP on port ${port}`);
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
} catch {}
|
|
761
|
-
}
|
|
762
|
-
log("kuri", "could not discover CDP port — tab discovery may fail");
|
|
763
|
-
}
|
|
764
|
-
async function findFreeCdpPort() {
|
|
765
|
-
for (let port = 9222;port < 9230; port++) {
|
|
766
|
-
try {
|
|
767
|
-
await fetch(`http://127.0.0.1:${port}/json/version`, {
|
|
768
|
-
signal: AbortSignal.timeout(300)
|
|
769
|
-
});
|
|
770
|
-
} catch {
|
|
771
|
-
return port;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
return 9222;
|
|
775
|
-
}
|
|
776
|
-
async function isKuriHealthyOnPort(port) {
|
|
777
|
-
try {
|
|
778
|
-
const health = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
779
|
-
signal: AbortSignal.timeout(1000)
|
|
780
|
-
});
|
|
781
|
-
return health.ok;
|
|
782
|
-
} catch {
|
|
783
|
-
return false;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
async function isChromeCdpAvailable(port) {
|
|
787
|
-
try {
|
|
788
|
-
const res = await fetch(`http://127.0.0.1:${port}/json/version`, {
|
|
789
|
-
signal: AbortSignal.timeout(1000)
|
|
790
|
-
});
|
|
791
|
-
return res.ok;
|
|
792
|
-
} catch {
|
|
793
|
-
return false;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
async function listRegisteredTabs(state) {
|
|
797
|
-
try {
|
|
798
|
-
const tabs = await kuriGet(state, "/tabs");
|
|
799
|
-
if (!Array.isArray(tabs))
|
|
800
|
-
return [];
|
|
801
|
-
return tabs.filter((tab) => typeof tab?.id === "string").map((tab) => ({ id: tab.id, url: tab.url ?? "", title: tab.title }));
|
|
802
|
-
} catch {
|
|
803
|
-
return [];
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
async function waitForChromeCdpReady(state, timeoutMs = KURI_CDP_READY_TIMEOUT_MS) {
|
|
807
|
-
if (typeof state.cdpPort !== "number")
|
|
808
|
-
return false;
|
|
809
|
-
const deadline = Date.now() + timeoutMs;
|
|
810
|
-
while (Date.now() < deadline) {
|
|
811
|
-
if (await isChromeCdpAvailable(state.cdpPort))
|
|
812
|
-
return true;
|
|
813
|
-
await new Promise((resolve) => setTimeout(resolve, KURI_CDP_POLL_INTERVAL_MS));
|
|
814
|
-
}
|
|
815
|
-
return false;
|
|
816
|
-
}
|
|
817
|
-
function shouldReuseManagedChrome(launchConfig, state, managedChromeAvailable) {
|
|
818
|
-
return !launchConfig.attachToExistingChrome && state.managedChrome === true && typeof state.cdpPort === "number" && managedChromeAvailable;
|
|
819
|
-
}
|
|
820
|
-
async function isTcpPortOpen(port, timeoutMs = 400) {
|
|
821
|
-
return await new Promise((resolve) => {
|
|
822
|
-
const socket = net.createConnection({ host: "127.0.0.1", port });
|
|
823
|
-
const finish = (open) => {
|
|
824
|
-
socket.removeAllListeners();
|
|
825
|
-
socket.destroy();
|
|
826
|
-
resolve(open);
|
|
827
|
-
};
|
|
828
|
-
socket.setTimeout(timeoutMs);
|
|
829
|
-
socket.once("connect", () => finish(true));
|
|
830
|
-
socket.once("timeout", () => finish(false));
|
|
831
|
-
socket.once("error", () => finish(false));
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
async function resolveKuriPort(preferredPort, deps = {}) {
|
|
835
|
-
const isHealthyPort = deps.isHealthyPort ?? isKuriHealthyOnPort;
|
|
836
|
-
const isPortOpen = deps.isPortOpen ?? isTcpPortOpen;
|
|
837
|
-
const searchLimit = deps.searchLimit ?? KURI_PORT_SEARCH_LIMIT;
|
|
838
|
-
if (await isHealthyPort(preferredPort))
|
|
839
|
-
return preferredPort;
|
|
840
|
-
if (!await isPortOpen(preferredPort))
|
|
841
|
-
return preferredPort;
|
|
842
|
-
for (let candidate = preferredPort + 1;candidate <= preferredPort + searchLimit; candidate++) {
|
|
843
|
-
if (await isHealthyPort(candidate))
|
|
844
|
-
return candidate;
|
|
845
|
-
if (!await isPortOpen(candidate))
|
|
846
|
-
return candidate;
|
|
847
|
-
}
|
|
848
|
-
return preferredPort;
|
|
849
|
-
}
|
|
850
|
-
async function ensureUserChromeRunning(state) {
|
|
851
|
-
for (const port2 of [9222, 9223, 9224, 9225]) {
|
|
852
|
-
try {
|
|
853
|
-
const res = await fetch(`http://127.0.0.1:${port2}/json/version`, { signal: AbortSignal.timeout(500) });
|
|
854
|
-
if (res.ok) {
|
|
855
|
-
state.cdpPort = port2;
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
} catch {}
|
|
859
|
-
}
|
|
860
|
-
const chromePaths = {
|
|
861
|
-
darwin: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
862
|
-
linux: "google-chrome",
|
|
863
|
-
win32: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
|
864
|
-
};
|
|
865
|
-
const chromeBin = chromePaths[process.platform];
|
|
866
|
-
if (!chromeBin)
|
|
867
|
-
return;
|
|
868
|
-
const port = await findFreeCdpPort();
|
|
869
|
-
state.cdpPort = port;
|
|
870
|
-
log("kuri", `launching user Chrome with CDP on port ${port}`);
|
|
871
|
-
try {
|
|
872
|
-
const child = spawn2(chromeBin, [
|
|
873
|
-
`--remote-debugging-port=${port}`,
|
|
874
|
-
"--no-first-run",
|
|
875
|
-
"--no-default-browser-check"
|
|
876
|
-
], {
|
|
877
|
-
stdio: "ignore",
|
|
878
|
-
detached: true
|
|
879
|
-
});
|
|
880
|
-
child.unref();
|
|
881
|
-
const deadline = Date.now() + 8000;
|
|
882
|
-
while (Date.now() < deadline) {
|
|
883
|
-
try {
|
|
884
|
-
const res = await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(500) });
|
|
885
|
-
if (res.ok) {
|
|
886
|
-
log("kuri", `user Chrome ready with CDP on port ${port}`);
|
|
887
|
-
return;
|
|
888
|
-
}
|
|
889
|
-
} catch {}
|
|
890
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
891
|
-
}
|
|
892
|
-
log("kuri", "user Chrome launched but CDP not responding — Kuri will launch managed Chrome");
|
|
893
|
-
state.cdpPort = null;
|
|
894
|
-
} catch (err) {
|
|
895
|
-
log("kuri", `failed to launch user Chrome: ${err instanceof Error ? err.message : err}`);
|
|
896
|
-
state.cdpPort = null;
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
async function terminateBrokerOnPort(port) {
|
|
900
|
-
try {
|
|
901
|
-
const output = execFileSync2("lsof", ["-tiTCP:" + String(port), "-sTCP:LISTEN"], {
|
|
902
|
-
encoding: "utf8",
|
|
903
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
904
|
-
});
|
|
905
|
-
const pids = output.split(/\r?\n/).map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
906
|
-
for (const pid of pids) {
|
|
907
|
-
try {
|
|
908
|
-
process.kill(pid, "SIGTERM");
|
|
909
|
-
} catch {}
|
|
910
|
-
}
|
|
911
|
-
if (pids.length > 0) {
|
|
912
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
913
|
-
}
|
|
914
|
-
} catch {}
|
|
915
|
-
}
|
|
916
|
-
async function reuseHealthyBrokerIfPossible(state, launchConfig, deps = {}) {
|
|
917
|
-
const isHealthyPort = deps.isHealthyPort ?? isKuriHealthyOnPort;
|
|
918
|
-
if (!await isHealthyPort(state.port))
|
|
919
|
-
return false;
|
|
920
|
-
log("kuri", `already running on port ${state.port}`);
|
|
921
|
-
state.ready = true;
|
|
922
|
-
const cdpAvailable = deps.isChromeCdpAvailable ?? isChromeCdpAvailable;
|
|
923
|
-
const discover = deps.discoverCdpPort ?? discoverCdpPort;
|
|
924
|
-
await discover(state);
|
|
925
|
-
if (!state.cdpPort && launchConfig.attachToExistingChrome) {
|
|
926
|
-
const ensureChrome = deps.ensureUserChromeRunning ?? ensureUserChromeRunning;
|
|
927
|
-
await ensureChrome(state);
|
|
928
|
-
}
|
|
929
|
-
if (typeof state.cdpPort === "number" && !await cdpAvailable(state.cdpPort)) {
|
|
930
|
-
state.cdpPort = null;
|
|
931
|
-
}
|
|
932
|
-
const syncTabs = deps.ensureTabsDiscovered ?? ensureTabsDiscovered;
|
|
933
|
-
await syncTabs(state).catch(() => {});
|
|
934
|
-
const tabs = await (deps.listTabs ?? listRegisteredTabs)(state).catch(() => []);
|
|
935
|
-
if (state.cdpPort) {
|
|
936
|
-
return true;
|
|
937
|
-
}
|
|
938
|
-
if (tabs.length > 0) {
|
|
939
|
-
log("kuri", `healthy broker on port ${state.port} has stale registered tabs but no CDP; recycling startup path`);
|
|
940
|
-
} else {
|
|
941
|
-
log("kuri", `healthy broker on port ${state.port} has no CDP or tabs; recycling startup path`);
|
|
942
|
-
}
|
|
943
|
-
state.ready = false;
|
|
944
|
-
if (state.process) {
|
|
945
|
-
state.process.kill("SIGTERM");
|
|
946
|
-
await waitForChildExit(state.process);
|
|
947
|
-
state.process = null;
|
|
948
|
-
} else {
|
|
949
|
-
const terminate = deps.terminateBrokerOnPort ?? terminateBrokerOnPort;
|
|
950
|
-
await terminate(state.port);
|
|
951
|
-
}
|
|
952
|
-
return false;
|
|
953
|
-
}
|
|
954
|
-
function kuriUrl(state, path6, params) {
|
|
955
|
-
const base = `http://127.0.0.1:${state.port}${path6}`;
|
|
956
|
-
if (!params || Object.keys(params).length === 0)
|
|
957
|
-
return base;
|
|
958
|
-
const parts = Object.entries(params).map(([k, v]) => `${k}=${v.replace(/#/g, "%23").replace(/&/g, "%26").replace(/\+/g, "%2B")}`);
|
|
959
|
-
return `${base}?${parts.join("&")}`;
|
|
960
|
-
}
|
|
961
|
-
function shouldRetryKuriTransportError(error) {
|
|
962
|
-
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
963
|
-
return /fetch failed|econnrefused|connection refused|socket hang up|other side closed|transport closed/i.test(message);
|
|
964
|
-
}
|
|
965
|
-
async function kuriGet(state, path6, params, retryOnTransportFailure = true) {
|
|
966
|
-
const url = kuriUrl(state, path6, params);
|
|
967
|
-
const controller = new AbortController;
|
|
968
|
-
const timeout = setTimeout(() => controller.abort(), KURI_REQUEST_TIMEOUT_MS);
|
|
969
|
-
try {
|
|
970
|
-
const res = await fetch(url, { signal: controller.signal });
|
|
971
|
-
const text = await res.text();
|
|
972
|
-
try {
|
|
973
|
-
return JSON.parse(text);
|
|
974
|
-
} catch {
|
|
975
|
-
return text;
|
|
976
|
-
}
|
|
977
|
-
} catch (error) {
|
|
978
|
-
if (retryOnTransportFailure && path6 !== "/health" && shouldRetryKuriTransportError(error)) {
|
|
979
|
-
log("kuri", `transport failed for ${path6}; restarting Kuri and retrying once on port ${state.port}`);
|
|
980
|
-
await stopOn(state);
|
|
981
|
-
await startOn(state, state.requestedPort || state.port);
|
|
982
|
-
return await kuriGet(state, path6, params, false);
|
|
983
|
-
}
|
|
984
|
-
throw error;
|
|
985
|
-
} finally {
|
|
986
|
-
clearTimeout(timeout);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
708
|
function findKuriBinary() {
|
|
990
709
|
if (process.env.KURI_BIN)
|
|
991
710
|
return process.env.KURI_BIN;
|
|
992
711
|
const candidates = getKuriBinaryCandidates();
|
|
993
712
|
return candidates.find((candidate) => existsSync9(candidate)) ?? candidates[0] ?? kuriBinaryName();
|
|
994
713
|
}
|
|
995
|
-
|
|
996
|
-
if (!child)
|
|
997
|
-
return;
|
|
998
|
-
if (child.exitCode !== null || child.killed)
|
|
999
|
-
return;
|
|
1000
|
-
await new Promise((resolve) => {
|
|
1001
|
-
const timer = setTimeout(resolve, timeoutMs);
|
|
1002
|
-
child.once("exit", () => {
|
|
1003
|
-
clearTimeout(timer);
|
|
1004
|
-
resolve();
|
|
1005
|
-
});
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
async function startOn(state, port) {
|
|
1009
|
-
const requestedPort = port ?? Number(process.env.KURI_PORT || KURI_DEFAULT_PORT);
|
|
1010
|
-
const externalPort = process.env.KURI_EXTERNAL_PORT;
|
|
1011
|
-
if (externalPort && !state.ready) {
|
|
1012
|
-
const ep = Number(externalPort);
|
|
1013
|
-
if (await isKuriHealthyOnPort(ep)) {
|
|
1014
|
-
state.port = ep;
|
|
1015
|
-
state.requestedPort = ep;
|
|
1016
|
-
state.ready = true;
|
|
1017
|
-
state.managedChrome = false;
|
|
1018
|
-
const cdpUrl = process.env.CDP_URL;
|
|
1019
|
-
if (cdpUrl) {
|
|
1020
|
-
const m = cdpUrl.match(/:(\d+)/);
|
|
1021
|
-
if (m)
|
|
1022
|
-
state.cdpPort = Number(m[1]);
|
|
1023
|
-
}
|
|
1024
|
-
log("kuri", `using external Kuri broker on port ${ep}${state.cdpPort ? ` (CDP port ${state.cdpPort})` : ""}`);
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
|
-
log("kuri", `external Kuri on port ${ep} not healthy — falling through to normal start`);
|
|
1028
|
-
}
|
|
1029
|
-
if (state.ready) {
|
|
1030
|
-
const activePort = state.port || requestedPort;
|
|
1031
|
-
if (await isKuriHealthyOnPort(activePort))
|
|
1032
|
-
return;
|
|
1033
|
-
log("kuri", `cached ready state stale on port ${activePort}; restarting`);
|
|
1034
|
-
state.ready = false;
|
|
1035
|
-
state.process = null;
|
|
1036
|
-
state.startPromise = null;
|
|
1037
|
-
}
|
|
1038
|
-
if (state.startPromise)
|
|
1039
|
-
return state.startPromise;
|
|
1040
|
-
const startPromise = (async () => {
|
|
1041
|
-
const launchConfig = resolveKuriLaunchConfig();
|
|
1042
|
-
state.requestedPort = requestedPort;
|
|
1043
|
-
state.port = await resolveKuriPort(requestedPort);
|
|
1044
|
-
const existingClient = brokerClients.get(brokerCacheKey(requestedPort));
|
|
1045
|
-
if (existingClient)
|
|
1046
|
-
rememberBrokerClient(existingClient, state);
|
|
1047
|
-
if (state.port !== requestedPort) {
|
|
1048
|
-
log("kuri", `preferred port ${requestedPort} is occupied but unhealthy; falling back to ${state.port}`);
|
|
1049
|
-
}
|
|
1050
|
-
if (await reuseHealthyBrokerIfPossible(state, launchConfig))
|
|
1051
|
-
return;
|
|
1052
|
-
const binary = findKuriBinary();
|
|
1053
|
-
log("kuri", `starting: ${binary} on port ${state.port}`);
|
|
1054
|
-
if (!existsSync9(binary)) {
|
|
1055
|
-
throw new Error(`Kuri binary not found at ${binary}`);
|
|
1056
|
-
}
|
|
1057
|
-
const reusableManagedChrome = shouldReuseManagedChrome(launchConfig, state, typeof state.cdpPort === "number" && await isChromeCdpAvailable(state.cdpPort));
|
|
1058
|
-
if (launchConfig.attachToExistingChrome) {
|
|
1059
|
-
await discoverCdpPort(state);
|
|
1060
|
-
state.managedChrome = false;
|
|
1061
|
-
} else if (reusableManagedChrome) {
|
|
1062
|
-
log("kuri", `reconnecting to surviving managed Chrome on port ${state.cdpPort}`);
|
|
1063
|
-
} else {
|
|
1064
|
-
state.cdpPort = null;
|
|
1065
|
-
state.managedChrome = false;
|
|
1066
|
-
}
|
|
1067
|
-
const env = {
|
|
1068
|
-
...process.env,
|
|
1069
|
-
PORT: String(state.port),
|
|
1070
|
-
HOST: "127.0.0.1",
|
|
1071
|
-
HEADLESS: launchConfig.headless ? "true" : "false"
|
|
1072
|
-
};
|
|
1073
|
-
if (state.cdpPort && (launchConfig.attachToExistingChrome || reusableManagedChrome)) {
|
|
1074
|
-
env.CDP_URL = `ws://127.0.0.1:${state.cdpPort}`;
|
|
1075
|
-
log("kuri", reusableManagedChrome ? `connecting to surviving managed Chrome on port ${state.cdpPort}` : `connecting to existing Chrome on port ${state.cdpPort}`);
|
|
1076
|
-
} else if (launchConfig.headless) {
|
|
1077
|
-
log("kuri", "starting in headless mode with managed Chrome");
|
|
1078
|
-
} else {
|
|
1079
|
-
log("kuri", "no existing Chrome found — Kuri will launch managed Chrome");
|
|
1080
|
-
}
|
|
1081
|
-
const maxAttempts = KURI_SPAWN_RETRIES + 1;
|
|
1082
|
-
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
1083
|
-
if (attempt > 1) {
|
|
1084
|
-
log("kuri", `spawn retry ${attempt}/${maxAttempts} after ${KURI_SPAWN_RETRY_DELAY_MS}ms`);
|
|
1085
|
-
await new Promise((r) => setTimeout(r, KURI_SPAWN_RETRY_DELAY_MS));
|
|
1086
|
-
}
|
|
1087
|
-
let exitedBeforeReady = false;
|
|
1088
|
-
state.process = spawn2(binary, [], {
|
|
1089
|
-
env,
|
|
1090
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1091
|
-
});
|
|
1092
|
-
const childPid = state.process.pid;
|
|
1093
|
-
log("kuri", `spawned pid ${childPid ?? "unknown"} on broker port ${state.port}`);
|
|
1094
|
-
state.process.stderr?.on("data", (chunk) => {
|
|
1095
|
-
const line = chunk.toString().trim();
|
|
1096
|
-
if (line)
|
|
1097
|
-
log("kuri", `[stderr] ${line}`);
|
|
1098
|
-
const cdpMatch = line.match(/CDP port:\s*(\d+)/);
|
|
1099
|
-
if (cdpMatch) {
|
|
1100
|
-
state.cdpPort = parseInt(cdpMatch[1], 10);
|
|
1101
|
-
kuriCdpPort = state.cdpPort;
|
|
1102
|
-
log("kuri", `discovered CDP port: ${state.cdpPort}`);
|
|
1103
|
-
}
|
|
1104
|
-
if (/launched Chrome \(pid=\d+\) on CDP port/i.test(line) || /launching managed Chrome instance/i.test(line)) {
|
|
1105
|
-
state.managedChrome = true;
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
|
-
state.process.on("exit", (code, signal) => {
|
|
1109
|
-
if (!state.ready)
|
|
1110
|
-
exitedBeforeReady = true;
|
|
1111
|
-
log("kuri", `process exited pid=${childPid ?? "unknown"} code=${code === null ? "null" : code} signal=${signal ?? "none"} broker_port=${state.port} cdp_port=${state.cdpPort ?? "unknown"}`);
|
|
1112
|
-
state.ready = false;
|
|
1113
|
-
state.process = null;
|
|
1114
|
-
forgetBrokerClient(state);
|
|
1115
|
-
});
|
|
1116
|
-
const deadline = Date.now() + KURI_STARTUP_TIMEOUT_MS;
|
|
1117
|
-
while (Date.now() < deadline) {
|
|
1118
|
-
if (exitedBeforeReady)
|
|
1119
|
-
break;
|
|
1120
|
-
try {
|
|
1121
|
-
const res = await fetch(`http://127.0.0.1:${state.port}/health`, {
|
|
1122
|
-
signal: AbortSignal.timeout(500)
|
|
1123
|
-
});
|
|
1124
|
-
if (res.ok) {
|
|
1125
|
-
state.ready = true;
|
|
1126
|
-
log("kuri", `ready on port ${state.port}`);
|
|
1127
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
1128
|
-
if (!state.cdpPort)
|
|
1129
|
-
await discoverCdpPort(state);
|
|
1130
|
-
await waitForChromeCdpReady(state).catch(() => false);
|
|
1131
|
-
await ensureTabsDiscovered(state);
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
} catch {}
|
|
1135
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
1136
|
-
}
|
|
1137
|
-
if (state.ready)
|
|
1138
|
-
return;
|
|
1139
|
-
if (state.process) {
|
|
1140
|
-
state.process.kill();
|
|
1141
|
-
await waitForChildExit(state.process);
|
|
1142
|
-
}
|
|
1143
|
-
try {
|
|
1144
|
-
execFileSync2("pkill", ["-f", `remote-debugging-port=${state.cdpPort ?? 9222}`], { stdio: "ignore" });
|
|
1145
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
1146
|
-
} catch {}
|
|
1147
|
-
}
|
|
1148
|
-
throw new Error(`Kuri failed to start after ${maxAttempts} attempts`);
|
|
1149
|
-
})();
|
|
1150
|
-
state.startPromise = startPromise.finally(() => {
|
|
1151
|
-
if (state.startPromise === startPromise) {
|
|
1152
|
-
state.startPromise = null;
|
|
1153
|
-
}
|
|
1154
|
-
});
|
|
1155
|
-
return state.startPromise;
|
|
1156
|
-
}
|
|
1157
|
-
async function stopOn(state) {
|
|
1158
|
-
if (state.startPromise) {
|
|
1159
|
-
await state.startPromise.catch(() => {});
|
|
1160
|
-
}
|
|
1161
|
-
if (state.process) {
|
|
1162
|
-
state.process.kill("SIGTERM");
|
|
1163
|
-
state.process = null;
|
|
1164
|
-
}
|
|
1165
|
-
state.ready = false;
|
|
1166
|
-
state.cdpPort = null;
|
|
1167
|
-
kuriCdpPort = null;
|
|
1168
|
-
state.managedChrome = false;
|
|
1169
|
-
state.startPromise = null;
|
|
1170
|
-
forgetBrokerClient(state);
|
|
1171
|
-
}
|
|
1172
|
-
async function getDefaultTabOn(state) {
|
|
1173
|
-
await ensureTabsDiscovered(state);
|
|
1174
|
-
try {
|
|
1175
|
-
const tabs = await kuriGet(state, "/tabs");
|
|
1176
|
-
if (Array.isArray(tabs) && tabs.length > 0)
|
|
1177
|
-
return tabs[0].id;
|
|
1178
|
-
} catch {}
|
|
1179
|
-
const cdpTabId = await createTabViaChromeCdp("about:blank", state);
|
|
1180
|
-
if (cdpTabId)
|
|
1181
|
-
return cdpTabId;
|
|
1182
|
-
throw new Error("No tabs available and failed to create one");
|
|
1183
|
-
}
|
|
1184
|
-
async function start(port, state = defaultBrokerState) {
|
|
1185
|
-
return startOn(state, port);
|
|
1186
|
-
}
|
|
1187
|
-
async function stop(state = defaultBrokerState) {
|
|
1188
|
-
return stopOn(state);
|
|
1189
|
-
}
|
|
1190
|
-
async function getDefaultTab(state = defaultBrokerState) {
|
|
1191
|
-
return getDefaultTabOn(state);
|
|
1192
|
-
}
|
|
1193
|
-
async function ensureTabsDiscovered(state) {
|
|
1194
|
-
try {
|
|
1195
|
-
if (state.cdpPort)
|
|
1196
|
-
await waitForChromeCdpReady(state).catch(() => false);
|
|
1197
|
-
const params = {};
|
|
1198
|
-
if (state.cdpPort)
|
|
1199
|
-
params.cdp_url = `ws://127.0.0.1:${state.cdpPort}`;
|
|
1200
|
-
await kuriGet(state, "/discover", params);
|
|
1201
|
-
} catch {}
|
|
1202
|
-
}
|
|
1203
|
-
async function waitForTabRegistration(state, tabId, timeoutMs = 2000) {
|
|
1204
|
-
const deadline = Date.now() + timeoutMs;
|
|
1205
|
-
while (Date.now() < deadline) {
|
|
1206
|
-
await ensureTabsDiscovered(state);
|
|
1207
|
-
try {
|
|
1208
|
-
const tabs = await kuriGet(state, "/tabs");
|
|
1209
|
-
if (Array.isArray(tabs) && tabs.some((tab) => tab?.id === tabId))
|
|
1210
|
-
return;
|
|
1211
|
-
} catch {}
|
|
1212
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
async function createTabViaChromeCdp(url = "about:blank", state = defaultBrokerState) {
|
|
1216
|
-
if (!state.cdpPort)
|
|
1217
|
-
return "";
|
|
1218
|
-
for (let attempt = 0;attempt < KURI_TAB_CREATE_RETRIES; attempt += 1) {
|
|
1219
|
-
await waitForChromeCdpReady(state).catch(() => false);
|
|
1220
|
-
try {
|
|
1221
|
-
const res = await fetch(`http://127.0.0.1:${state.cdpPort}/json/new?${url}`, {
|
|
1222
|
-
method: "PUT",
|
|
1223
|
-
signal: AbortSignal.timeout(5000)
|
|
1224
|
-
});
|
|
1225
|
-
const target = await res.json();
|
|
1226
|
-
const tabId = target?.id ?? target?.targetId ?? "";
|
|
1227
|
-
if (tabId) {
|
|
1228
|
-
log("kuri", `created new Chrome tab: ${tabId}`);
|
|
1229
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1230
|
-
await ensureTabsDiscovered(state).catch(() => {});
|
|
1231
|
-
await waitForTabRegistration(state, tabId).catch(() => {});
|
|
1232
|
-
return tabId;
|
|
1233
|
-
}
|
|
1234
|
-
} catch (err) {
|
|
1235
|
-
if (attempt === KURI_TAB_CREATE_RETRIES - 1) {
|
|
1236
|
-
log("kuri", `Chrome tab creation failed: ${err instanceof Error ? err.message : err}`);
|
|
1237
|
-
return "";
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
await new Promise((resolve) => setTimeout(resolve, KURI_CDP_POLL_INTERVAL_MS));
|
|
1241
|
-
}
|
|
1242
|
-
return "";
|
|
1243
|
-
}
|
|
1244
|
-
async function navigate(tabId, url, state = defaultBrokerState) {
|
|
1245
|
-
await kuriGet(state, "/navigate", { tab_id: tabId, url });
|
|
1246
|
-
}
|
|
1247
|
-
async function evaluate(tabId, expression, state = defaultBrokerState) {
|
|
1248
|
-
let raw;
|
|
1249
|
-
if (expression.length > 2000) {
|
|
1250
|
-
const url = kuriUrl(state, "/evaluate", { tab_id: tabId, expression });
|
|
1251
|
-
const controller = new AbortController;
|
|
1252
|
-
const timeout = setTimeout(() => controller.abort(), KURI_REQUEST_TIMEOUT_MS);
|
|
1253
|
-
try {
|
|
1254
|
-
const res = await fetch(url, {
|
|
1255
|
-
method: "POST",
|
|
1256
|
-
headers: { "Content-Type": "text/plain" },
|
|
1257
|
-
body: expression,
|
|
1258
|
-
signal: controller.signal
|
|
1259
|
-
});
|
|
1260
|
-
const text = await res.text();
|
|
1261
|
-
try {
|
|
1262
|
-
raw = JSON.parse(text);
|
|
1263
|
-
} catch {
|
|
1264
|
-
raw = text;
|
|
1265
|
-
}
|
|
1266
|
-
} finally {
|
|
1267
|
-
clearTimeout(timeout);
|
|
1268
|
-
}
|
|
1269
|
-
} else {
|
|
1270
|
-
raw = await kuriGet(state, "/evaluate", { tab_id: tabId, expression });
|
|
1271
|
-
}
|
|
1272
|
-
const inner = raw?.result?.result;
|
|
1273
|
-
if (!inner)
|
|
1274
|
-
return raw;
|
|
1275
|
-
if (inner.type === "undefined")
|
|
1276
|
-
return;
|
|
1277
|
-
if ("value" in inner)
|
|
1278
|
-
return inner.value;
|
|
1279
|
-
return inner.description ?? raw;
|
|
1280
|
-
}
|
|
1281
|
-
async function getCookies(tabId, state = defaultBrokerState) {
|
|
1282
|
-
const raw = await kuriGet(state, "/cookies", { tab_id: tabId });
|
|
1283
|
-
return raw?.result?.cookies ?? [];
|
|
1284
|
-
}
|
|
1285
|
-
async function setCookieViaCDP(wsUrl, cookie) {
|
|
1286
|
-
return new Promise((resolve) => {
|
|
1287
|
-
const timer = setTimeout(() => {
|
|
1288
|
-
resolve(false);
|
|
1289
|
-
}, 3000);
|
|
1290
|
-
try {
|
|
1291
|
-
const ws = new (__require("ws"))(wsUrl);
|
|
1292
|
-
ws.on("open", () => {
|
|
1293
|
-
ws.send(JSON.stringify({
|
|
1294
|
-
id: 1,
|
|
1295
|
-
method: "Network.setCookie",
|
|
1296
|
-
params: {
|
|
1297
|
-
...cookie,
|
|
1298
|
-
url: `https://${cookie.domain.replace(/^\./, "")}/`
|
|
1299
|
-
}
|
|
1300
|
-
}));
|
|
1301
|
-
});
|
|
1302
|
-
ws.on("message", (data) => {
|
|
1303
|
-
clearTimeout(timer);
|
|
1304
|
-
try {
|
|
1305
|
-
const msg = JSON.parse(data.toString());
|
|
1306
|
-
if (msg.id === 1) {
|
|
1307
|
-
ws.close();
|
|
1308
|
-
resolve(msg.result?.success ?? false);
|
|
1309
|
-
}
|
|
1310
|
-
} catch {
|
|
1311
|
-
ws.close();
|
|
1312
|
-
resolve(false);
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
ws.on("error", () => {
|
|
1316
|
-
clearTimeout(timer);
|
|
1317
|
-
resolve(false);
|
|
1318
|
-
});
|
|
1319
|
-
} catch {
|
|
1320
|
-
clearTimeout(timer);
|
|
1321
|
-
resolve(false);
|
|
1322
|
-
}
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
async function resolveCdpDebuggerUrlForTab(tabId) {
|
|
1326
|
-
const portsToTry = Array.from(new Set([kuriCdpPort, 9222, 9223, 9224, 9225].filter((port) => typeof port === "number")));
|
|
1327
|
-
for (const port of portsToTry) {
|
|
1328
|
-
try {
|
|
1329
|
-
const res = await fetch(`http://127.0.0.1:${port}/json`, { signal: AbortSignal.timeout(1000) });
|
|
1330
|
-
if (!res.ok)
|
|
1331
|
-
continue;
|
|
1332
|
-
const pages = await res.json();
|
|
1333
|
-
const page = pages.find((candidate) => candidate.id === tabId);
|
|
1334
|
-
if (page?.webSocketDebuggerUrl) {
|
|
1335
|
-
if (kuriCdpPort !== port) {
|
|
1336
|
-
kuriCdpPort = port;
|
|
1337
|
-
log("kuri", `updated CDP port from tab discovery: ${port}`);
|
|
1338
|
-
}
|
|
1339
|
-
return page.webSocketDebuggerUrl;
|
|
1340
|
-
}
|
|
1341
|
-
} catch {}
|
|
1342
|
-
}
|
|
1343
|
-
return null;
|
|
1344
|
-
}
|
|
1345
|
-
async function setCookie(tabId, cookie, state = defaultBrokerState) {
|
|
1346
|
-
const value = cookie.value.replace(/^"|"$/g, "");
|
|
1347
|
-
if (cookie.secure || cookie.httpOnly) {
|
|
1348
|
-
try {
|
|
1349
|
-
const debuggerUrl = await resolveCdpDebuggerUrlForTab(tabId);
|
|
1350
|
-
if (debuggerUrl) {
|
|
1351
|
-
const success = await setCookieViaCDP(debuggerUrl, {
|
|
1352
|
-
name: cookie.name,
|
|
1353
|
-
value,
|
|
1354
|
-
domain: cookie.domain,
|
|
1355
|
-
path: cookie.path || "/",
|
|
1356
|
-
secure: cookie.secure ?? false,
|
|
1357
|
-
httpOnly: cookie.httpOnly ?? false,
|
|
1358
|
-
sameSite: cookie.sameSite || "Lax",
|
|
1359
|
-
...cookie.expires && cookie.expires > 0 ? { expires: cookie.expires } : {}
|
|
1360
|
-
});
|
|
1361
|
-
if (success)
|
|
1362
|
-
return;
|
|
1363
|
-
log("kuri", `CDP cookie set failed for ${cookie.name} on ${cookie.domain}; falling back to /cookies`);
|
|
1364
|
-
} else {
|
|
1365
|
-
log("kuri", `no CDP websocket for tab ${tabId}; falling back to /cookies for secure cookie ${cookie.name}`);
|
|
1366
|
-
}
|
|
1367
|
-
} catch {}
|
|
1368
|
-
}
|
|
1369
|
-
await kuriGet(state, "/cookies", {
|
|
1370
|
-
tab_id: tabId,
|
|
1371
|
-
name: cookie.name,
|
|
1372
|
-
value,
|
|
1373
|
-
domain: cookie.domain,
|
|
1374
|
-
...cookie.path ? { path: cookie.path } : {}
|
|
1375
|
-
});
|
|
1376
|
-
}
|
|
1377
|
-
async function networkEnable(tabId, state = defaultBrokerState) {
|
|
1378
|
-
await kuriGet(state, "/network", { tab_id: tabId, mode: "enable" });
|
|
1379
|
-
}
|
|
1380
|
-
async function getCurrentUrl(tabId, state = defaultBrokerState) {
|
|
1381
|
-
const result = await evaluate(tabId, "window.location.href", state);
|
|
1382
|
-
return typeof result === "string" ? result : "";
|
|
1383
|
-
}
|
|
1384
|
-
async function action(tabId, actionType, ref, value, state = defaultBrokerState) {
|
|
1385
|
-
const params = { tab_id: tabId, action: actionType, ref };
|
|
1386
|
-
if (value !== undefined)
|
|
1387
|
-
params.value = value;
|
|
1388
|
-
return kuriGet(state, "/action", params);
|
|
1389
|
-
}
|
|
1390
|
-
async function click(tabId, ref, state = defaultBrokerState) {
|
|
1391
|
-
await scrollIntoView(tabId, ref, state);
|
|
1392
|
-
return action(tabId, "click", ref, undefined, state);
|
|
1393
|
-
}
|
|
1394
|
-
async function fill(tabId, ref, value, state = defaultBrokerState) {
|
|
1395
|
-
await click(tabId, ref, state);
|
|
1396
|
-
const result = await action(tabId, "fill", ref, value, state);
|
|
1397
|
-
const currentValue = await evaluate(tabId, `(() => {
|
|
1398
|
-
const active = document.activeElement;
|
|
1399
|
-
return active && "value" in active ? active.value : undefined;
|
|
1400
|
-
})()`, state);
|
|
1401
|
-
if (currentValue !== value) {
|
|
1402
|
-
return evaluate(tabId, `(function() {
|
|
1403
|
-
const active = document.activeElement;
|
|
1404
|
-
if (!active || !("value" in active)) return false;
|
|
1405
|
-
active.value = ${JSON.stringify(value)};
|
|
1406
|
-
active.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1407
|
-
active.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1408
|
-
return active.value;
|
|
1409
|
-
})()`, state);
|
|
1410
|
-
}
|
|
1411
|
-
return result;
|
|
1412
|
-
}
|
|
1413
|
-
async function scrollIntoView(tabId, ref, state = defaultBrokerState) {
|
|
1414
|
-
return kuriGet(state, "/scrollintoview", { tab_id: tabId, ref });
|
|
1415
|
-
}
|
|
1416
|
-
async function authProfileSave(tabId, name, state = defaultBrokerState) {
|
|
1417
|
-
return kuriGet(state, "/auth/profile/save", { tab_id: tabId, name });
|
|
1418
|
-
}
|
|
1419
|
-
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
|
+
var KURI_DEFAULT_PORT = 7700, defaultBrokerState, brokerClients;
|
|
1420
715
|
var init_client2 = __esm(() => {
|
|
1421
716
|
init_logger();
|
|
1422
717
|
init_paths();
|
|
@@ -1721,210 +1016,6 @@ var init_token_resolver = __esm(() => {
|
|
|
1721
1016
|
init_token_sources();
|
|
1722
1017
|
});
|
|
1723
1018
|
|
|
1724
|
-
// ../../src/auth/agent-mail.ts
|
|
1725
|
-
var exports_agent_mail = {};
|
|
1726
|
-
__export(exports_agent_mail, {
|
|
1727
|
-
waitForVerificationEmail: () => waitForVerificationEmail,
|
|
1728
|
-
tryAgentMailAuth: () => tryAgentMailAuth,
|
|
1729
|
-
isAgentMailAvailable: () => isAgentMailAvailable,
|
|
1730
|
-
getOrCreateSiteInbox: () => getOrCreateSiteInbox,
|
|
1731
|
-
extractVerificationLink: () => extractVerificationLink,
|
|
1732
|
-
extractOtpFromEmail: () => extractOtpFromEmail,
|
|
1733
|
-
autonomousEmailLogin: () => autonomousEmailLogin
|
|
1734
|
-
});
|
|
1735
|
-
import { AgentMailClient } from "agentmail";
|
|
1736
|
-
function getClient() {
|
|
1737
|
-
if (_client)
|
|
1738
|
-
return _client;
|
|
1739
|
-
const apiKey = process.env.AGENTMAIL_API_KEY;
|
|
1740
|
-
if (!apiKey)
|
|
1741
|
-
throw new Error("AGENTMAIL_API_KEY not set — cannot use agent mail");
|
|
1742
|
-
_client = new AgentMailClient({ apiKey });
|
|
1743
|
-
return _client;
|
|
1744
|
-
}
|
|
1745
|
-
async function getStoredInbox(domain) {
|
|
1746
|
-
const key = `${VAULT_PREFIX}${getRegistrableDomain(domain)}`;
|
|
1747
|
-
const raw = await getCredential(key);
|
|
1748
|
-
if (!raw)
|
|
1749
|
-
return null;
|
|
1750
|
-
try {
|
|
1751
|
-
return JSON.parse(raw);
|
|
1752
|
-
} catch {
|
|
1753
|
-
return null;
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
async function storeInbox(domain, inbox) {
|
|
1757
|
-
const key = `${VAULT_PREFIX}${getRegistrableDomain(domain)}`;
|
|
1758
|
-
await storeCredential(key, JSON.stringify(inbox));
|
|
1759
|
-
log("agent-mail", `stored inbox ${inbox.email} for ${domain}`);
|
|
1760
|
-
}
|
|
1761
|
-
async function getOrCreateSiteInbox(domain) {
|
|
1762
|
-
const stored = await getStoredInbox(domain);
|
|
1763
|
-
if (stored) {
|
|
1764
|
-
log("agent-mail", `reusing stored inbox ${stored.email} for ${domain}`);
|
|
1765
|
-
return { inboxId: stored.inboxId, email: stored.email };
|
|
1766
|
-
}
|
|
1767
|
-
const client = getClient();
|
|
1768
|
-
const safeDomain = getRegistrableDomain(domain).replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
1769
|
-
const username = `unbrowse-${safeDomain}`;
|
|
1770
|
-
const clientId = `unbrowse-login-${safeDomain}`;
|
|
1771
|
-
try {
|
|
1772
|
-
const inbox = await client.inboxes.create({
|
|
1773
|
-
username,
|
|
1774
|
-
domain: "agentmail.to",
|
|
1775
|
-
displayName: `Unbrowse Agent - ${domain}`,
|
|
1776
|
-
clientId
|
|
1777
|
-
});
|
|
1778
|
-
const result = { inboxId: inbox.inboxId, email: inbox.email };
|
|
1779
|
-
await storeInbox(domain, { ...result, domain, createdAt: new Date().toISOString() });
|
|
1780
|
-
log("agent-mail", `created inbox ${inbox.email} for ${domain}`);
|
|
1781
|
-
return result;
|
|
1782
|
-
} catch (err) {
|
|
1783
|
-
log("agent-mail", `create failed (likely exists), listing to find match: ${err instanceof Error ? err.message : err}`);
|
|
1784
|
-
try {
|
|
1785
|
-
const list = await client.inboxes.list({ limit: 100 });
|
|
1786
|
-
const match = list.inboxes.find((i) => i.clientId === clientId || i.email === `${username}@agentmail.to`);
|
|
1787
|
-
if (match) {
|
|
1788
|
-
const result = { inboxId: match.inboxId, email: match.email };
|
|
1789
|
-
await storeInbox(domain, { ...result, domain, createdAt: new Date().toISOString() });
|
|
1790
|
-
return result;
|
|
1791
|
-
}
|
|
1792
|
-
} catch {}
|
|
1793
|
-
throw new Error(`Failed to create or find AgentMail inbox for ${domain}`);
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
async function waitForVerificationEmail(inboxId, fromDomain, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
1797
|
-
const client = getClient();
|
|
1798
|
-
const start2 = Date.now();
|
|
1799
|
-
const normalizedDomain = fromDomain.toLowerCase();
|
|
1800
|
-
log("agent-mail", `polling inbox ${inboxId} for email from *@${normalizedDomain} (timeout: ${timeoutMs}ms)`);
|
|
1801
|
-
while (Date.now() - start2 < timeoutMs) {
|
|
1802
|
-
try {
|
|
1803
|
-
const response = await client.inboxes.messages.list(inboxId, {
|
|
1804
|
-
limit: 10,
|
|
1805
|
-
labels: ["received"]
|
|
1806
|
-
});
|
|
1807
|
-
for (const item of response.messages ?? []) {
|
|
1808
|
-
const msg = await client.inboxes.messages.get(inboxId, item.messageId);
|
|
1809
|
-
const from = typeof msg.from === "string" ? msg.from : (msg.from ?? []).join(", ");
|
|
1810
|
-
if (from.toLowerCase().includes(normalizedDomain)) {
|
|
1811
|
-
log("agent-mail", `found verification email from ${from}: "${msg.subject}"`);
|
|
1812
|
-
return {
|
|
1813
|
-
messageId: msg.messageId,
|
|
1814
|
-
threadId: msg.threadId,
|
|
1815
|
-
subject: msg.subject ?? "",
|
|
1816
|
-
from,
|
|
1817
|
-
text: msg.extractedText ?? msg.text ?? "",
|
|
1818
|
-
html: msg.extractedHtml ?? msg.html ?? "",
|
|
1819
|
-
receivedAt: msg.createdAt
|
|
1820
|
-
};
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
} catch (err) {
|
|
1824
|
-
log("agent-mail", `poll error: ${err instanceof Error ? err.message : err}`);
|
|
1825
|
-
}
|
|
1826
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
1827
|
-
}
|
|
1828
|
-
log("agent-mail", `timeout waiting for email from ${normalizedDomain}`);
|
|
1829
|
-
return null;
|
|
1830
|
-
}
|
|
1831
|
-
function extractOtpFromEmail(text) {
|
|
1832
|
-
const codePhrase = text.match(/(?:verification|confirm|security|login|one[- ]time|otp)\s*(?:code|pin|number)[:\s]+(\w{4,8})/i);
|
|
1833
|
-
if (codePhrase)
|
|
1834
|
-
return codePhrase[1];
|
|
1835
|
-
const codeIs = text.match(/(?:code|otp|pin)[:\s]+(\w{4,8})/i);
|
|
1836
|
-
if (codeIs)
|
|
1837
|
-
return codeIs[1];
|
|
1838
|
-
const six = text.match(/\b(\d{6})\b/);
|
|
1839
|
-
if (six)
|
|
1840
|
-
return six[1];
|
|
1841
|
-
const four = text.match(/\b(\d{4})\b/);
|
|
1842
|
-
if (four)
|
|
1843
|
-
return four[1];
|
|
1844
|
-
return null;
|
|
1845
|
-
}
|
|
1846
|
-
function extractVerificationLink(text, html) {
|
|
1847
|
-
const htmlLink = html.match(/href="(https?:\/\/[^"]*(?:verify|confirm|activate|magic|token|auth|callback|validate|approve|email)[^"]*)"/i);
|
|
1848
|
-
if (htmlLink)
|
|
1849
|
-
return htmlLink[1];
|
|
1850
|
-
const textLink = text.match(/(https?:\/\/\S*(?:verify|confirm|activate|magic|token|auth|callback|validate|approve|email)\S*)/i);
|
|
1851
|
-
if (textLink)
|
|
1852
|
-
return textLink[1];
|
|
1853
|
-
const anyHtmlLink = html.match(/href="(https?:\/\/[^"]+)"/);
|
|
1854
|
-
if (anyHtmlLink)
|
|
1855
|
-
return anyHtmlLink[1];
|
|
1856
|
-
const anyTextLink = text.match(/(https?:\/\/\S+)/);
|
|
1857
|
-
if (anyTextLink)
|
|
1858
|
-
return anyTextLink[1];
|
|
1859
|
-
return null;
|
|
1860
|
-
}
|
|
1861
|
-
async function autonomousEmailLogin(domain) {
|
|
1862
|
-
const inbox = await getOrCreateSiteInbox(domain);
|
|
1863
|
-
const client = getClient();
|
|
1864
|
-
log("agent-mail", `session ready for ${domain} — email: ${inbox.email}`);
|
|
1865
|
-
return {
|
|
1866
|
-
email: inbox.email,
|
|
1867
|
-
inboxId: inbox.inboxId,
|
|
1868
|
-
domain,
|
|
1869
|
-
waitForOtp: async (timeoutMs = DEFAULT_TIMEOUT_MS) => {
|
|
1870
|
-
const email = await waitForVerificationEmail(inbox.inboxId, domain, timeoutMs);
|
|
1871
|
-
if (!email)
|
|
1872
|
-
return null;
|
|
1873
|
-
const otp = extractOtpFromEmail(email.text);
|
|
1874
|
-
if (otp)
|
|
1875
|
-
log("agent-mail", `extracted OTP: ${otp} from ${email.subject}`);
|
|
1876
|
-
return otp;
|
|
1877
|
-
},
|
|
1878
|
-
waitForLink: async (timeoutMs = DEFAULT_TIMEOUT_MS) => {
|
|
1879
|
-
const email = await waitForVerificationEmail(inbox.inboxId, domain, timeoutMs);
|
|
1880
|
-
if (!email)
|
|
1881
|
-
return null;
|
|
1882
|
-
const link = extractVerificationLink(email.text, email.html);
|
|
1883
|
-
if (link)
|
|
1884
|
-
log("agent-mail", `extracted verification link from ${email.subject}`);
|
|
1885
|
-
return link;
|
|
1886
|
-
},
|
|
1887
|
-
waitForEmail: (timeoutMs = DEFAULT_TIMEOUT_MS) => waitForVerificationEmail(inbox.inboxId, domain, timeoutMs),
|
|
1888
|
-
sendEmail: async (to, subject, body) => {
|
|
1889
|
-
const result = await client.inboxes.messages.send(inbox.inboxId, {
|
|
1890
|
-
to: [to],
|
|
1891
|
-
subject,
|
|
1892
|
-
text: body
|
|
1893
|
-
});
|
|
1894
|
-
log("agent-mail", `sent email to ${to}: "${subject}" (${result.messageId})`);
|
|
1895
|
-
return { messageId: result.messageId, threadId: result.threadId };
|
|
1896
|
-
},
|
|
1897
|
-
replyTo: async (messageId, body) => {
|
|
1898
|
-
const result = await client.inboxes.messages.reply(inbox.inboxId, messageId, {
|
|
1899
|
-
text: body
|
|
1900
|
-
});
|
|
1901
|
-
log("agent-mail", `replied to ${messageId} (${result.messageId})`);
|
|
1902
|
-
return { messageId: result.messageId, threadId: result.threadId };
|
|
1903
|
-
}
|
|
1904
|
-
};
|
|
1905
|
-
}
|
|
1906
|
-
function isAgentMailAvailable() {
|
|
1907
|
-
return Boolean(process.env.AGENTMAIL_API_KEY);
|
|
1908
|
-
}
|
|
1909
|
-
async function tryAgentMailAuth(domain) {
|
|
1910
|
-
if (!isAgentMailAvailable()) {
|
|
1911
|
-
log("agent-mail", `skipping — AGENTMAIL_API_KEY not set`);
|
|
1912
|
-
return null;
|
|
1913
|
-
}
|
|
1914
|
-
try {
|
|
1915
|
-
return await autonomousEmailLogin(domain);
|
|
1916
|
-
} catch (err) {
|
|
1917
|
-
log("agent-mail", `auth failed for ${domain}: ${err instanceof Error ? err.message : err}`);
|
|
1918
|
-
return null;
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
var POLL_INTERVAL_MS = 3000, DEFAULT_TIMEOUT_MS = 90000, _client = null, VAULT_PREFIX = "agentmail:";
|
|
1922
|
-
var init_agent_mail = __esm(async () => {
|
|
1923
|
-
init_logger();
|
|
1924
|
-
init_domain();
|
|
1925
|
-
await init_vault();
|
|
1926
|
-
});
|
|
1927
|
-
|
|
1928
1019
|
// ../../src/auth/browser-cookies.ts
|
|
1929
1020
|
var exports_browser_cookies = {};
|
|
1930
1021
|
__export(exports_browser_cookies, {
|
|
@@ -2292,74 +1383,6 @@ var init_browser_cookies = __esm(() => {
|
|
|
2292
1383
|
});
|
|
2293
1384
|
|
|
2294
1385
|
// ../../src/auth/index.ts
|
|
2295
|
-
function formatAuthError(error) {
|
|
2296
|
-
if (error instanceof Error && error.message)
|
|
2297
|
-
return error.message;
|
|
2298
|
-
if (typeof error === "string")
|
|
2299
|
-
return error;
|
|
2300
|
-
try {
|
|
2301
|
-
return JSON.stringify(error);
|
|
2302
|
-
} catch {
|
|
2303
|
-
return String(error);
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
function normalizeAuthDomain(domain) {
|
|
2307
|
-
return domain.toLowerCase().replace(/^www\./, "");
|
|
2308
|
-
}
|
|
2309
|
-
function shouldImportBrowserCookies() {
|
|
2310
|
-
const raw = process.env.UNBROWSE_IMPORT_BROWSER_COOKIES?.trim();
|
|
2311
|
-
if (!raw)
|
|
2312
|
-
return true;
|
|
2313
|
-
return !DISABLE_BROWSER_COOKIE_IMPORT.test(raw);
|
|
2314
|
-
}
|
|
2315
|
-
function isLikelyAuthenticatedCookie(targetDomain, cookie) {
|
|
2316
|
-
const cookieName = cookie.name.toLowerCase();
|
|
2317
|
-
const registrableDomain = getRegistrableDomain(normalizeAuthDomain(targetDomain));
|
|
2318
|
-
const requiredCookieNames = DOMAIN_AUTH_COOKIE_NAMES[registrableDomain];
|
|
2319
|
-
if (requiredCookieNames)
|
|
2320
|
-
return requiredCookieNames.includes(cookieName);
|
|
2321
|
-
return GENERIC_AUTH_COOKIE_NAMES.test(cookieName);
|
|
2322
|
-
}
|
|
2323
|
-
function getAuthenticatedCookiesForDomain(targetDomain, cookies) {
|
|
2324
|
-
return cookies.filter((cookie) => isDomainMatch(cookie.domain, targetDomain) && isLikelyAuthenticatedCookie(targetDomain, cookie));
|
|
2325
|
-
}
|
|
2326
|
-
async function importBrowserCookiesIntoTab(tabId, domain) {
|
|
2327
|
-
if (!shouldImportBrowserCookies())
|
|
2328
|
-
return 0;
|
|
2329
|
-
try {
|
|
2330
|
-
const { extractBrowserCookies: extractBrowserCookies2, findBestBrowserSession: findBestBrowserSession2 } = await Promise.resolve().then(() => (init_browser_cookies(), exports_browser_cookies));
|
|
2331
|
-
const bestSession = findBestBrowserSession2(domain);
|
|
2332
|
-
const cookies = bestSession ? bestSession.cookies : extractBrowserCookies2(domain).cookies;
|
|
2333
|
-
let imported = 0;
|
|
2334
|
-
for (const cookie of cookies) {
|
|
2335
|
-
try {
|
|
2336
|
-
await setCookie(tabId, cookie);
|
|
2337
|
-
imported += 1;
|
|
2338
|
-
} catch (error) {
|
|
2339
|
-
log("auth", `browser_cookie_import_failed domain=${normalizeAuthDomain(domain)} tab_id=${tabId} cookie=${cookie.name} error=${formatAuthError(error)}`);
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
if (imported > 0) {
|
|
2343
|
-
log("auth", `browser_cookie_imported domain=${normalizeAuthDomain(domain)} tab_id=${tabId} count=${imported}`);
|
|
2344
|
-
}
|
|
2345
|
-
return imported;
|
|
2346
|
-
} catch (error) {
|
|
2347
|
-
log("auth", `browser_cookie_extract_failed domain=${normalizeAuthDomain(domain)} tab_id=${tabId} error=${formatAuthError(error)}`);
|
|
2348
|
-
return 0;
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
async function saveAuthProfileBestEffort(tabId, domain, context = "auth") {
|
|
2352
|
-
const profileName = normalizeAuthDomain(domain);
|
|
2353
|
-
if (!profileName || profileName === "unknown")
|
|
2354
|
-
return false;
|
|
2355
|
-
try {
|
|
2356
|
-
await authProfileSave(tabId, profileName);
|
|
2357
|
-
return true;
|
|
2358
|
-
} catch (error) {
|
|
2359
|
-
log("auth", `${context} auth_profile_failed op=save domain=${profileName} tab_id=${tabId} error=${formatAuthError(error)}`);
|
|
2360
|
-
return false;
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
1386
|
async function extractBrowserAuth(domain, opts) {
|
|
2364
1387
|
const { extractBrowserCookies: extractBrowserCookies2 } = await Promise.resolve().then(() => (init_browser_cookies(), exports_browser_cookies));
|
|
2365
1388
|
const result = extractBrowserCookies2(domain, opts);
|
|
@@ -2446,301 +1469,12 @@ async function getAuthCookies(domain) {
|
|
|
2446
1469
|
}
|
|
2447
1470
|
return null;
|
|
2448
1471
|
}
|
|
2449
|
-
var DISABLE_BROWSER_COOKIE_IMPORT, GENERIC_AUTH_COOKIE_NAMES, DOMAIN_AUTH_COOKIE_NAMES;
|
|
2450
1472
|
var init_auth = __esm(async () => {
|
|
2451
1473
|
init_client2();
|
|
2452
1474
|
init_domain();
|
|
2453
1475
|
init_logger();
|
|
2454
1476
|
init_supervisor();
|
|
2455
|
-
await
|
|
2456
|
-
init_vault(),
|
|
2457
|
-
init_agent_mail()
|
|
2458
|
-
]);
|
|
2459
|
-
DISABLE_BROWSER_COOKIE_IMPORT = /^(0|false|no|off)$/i;
|
|
2460
|
-
GENERIC_AUTH_COOKIE_NAMES = /(^|[_\-.])(sess(?:ion)?(?:id)?|auth|token|csrf|xsrf|jwt|sid|sso|remember|logged[_-]?in|connect\.sid)([_\-.]|$)/i;
|
|
2461
|
-
DOMAIN_AUTH_COOKIE_NAMES = {
|
|
2462
|
-
"linkedin.com": ["li_at"]
|
|
2463
|
-
};
|
|
2464
|
-
});
|
|
2465
|
-
|
|
2466
|
-
// ../../src/auth/email-provider.ts
|
|
2467
|
-
class AgentMailProvider {
|
|
2468
|
-
name = "agentmail";
|
|
2469
|
-
get configured() {
|
|
2470
|
-
return Boolean(process.env.AGENTMAIL_API_KEY);
|
|
2471
|
-
}
|
|
2472
|
-
async getAddress(domain) {
|
|
2473
|
-
const { getOrCreateSiteInbox: getOrCreateSiteInbox2 } = await init_agent_mail().then(() => exports_agent_mail);
|
|
2474
|
-
const inbox = await getOrCreateSiteInbox2(domain);
|
|
2475
|
-
return inbox.email;
|
|
2476
|
-
}
|
|
2477
|
-
async send(to, subject, body) {
|
|
2478
|
-
const { autonomousEmailLogin: autonomousEmailLogin2 } = await init_agent_mail().then(() => exports_agent_mail);
|
|
2479
|
-
const session = await autonomousEmailLogin2("general");
|
|
2480
|
-
const result = await session.sendEmail(to, subject, body);
|
|
2481
|
-
return { messageId: result.messageId };
|
|
2482
|
-
}
|
|
2483
|
-
async waitForMessage(fromDomain, timeoutMs = 90000) {
|
|
2484
|
-
const { autonomousEmailLogin: autonomousEmailLogin2, waitForVerificationEmail: waitForVerificationEmail2 } = await init_agent_mail().then(() => exports_agent_mail);
|
|
2485
|
-
const session = await autonomousEmailLogin2(fromDomain);
|
|
2486
|
-
const email = await waitForVerificationEmail2(session.inboxId, fromDomain, timeoutMs);
|
|
2487
|
-
return email;
|
|
2488
|
-
}
|
|
2489
|
-
}
|
|
2490
|
-
|
|
2491
|
-
class GmailProvider {
|
|
2492
|
-
name = "gmail";
|
|
2493
|
-
get configured() {
|
|
2494
|
-
try {
|
|
2495
|
-
const { existsSync: existsSync12 } = __require("fs");
|
|
2496
|
-
const { homedir: homedir7 } = __require("os");
|
|
2497
|
-
const { join: join9 } = __require("path");
|
|
2498
|
-
return existsSync12(join9(homedir7(), ".config", "gcloud", "application_default_credentials.json"));
|
|
2499
|
-
} catch {
|
|
2500
|
-
return false;
|
|
2501
|
-
}
|
|
2502
|
-
}
|
|
2503
|
-
async getAddress(_domain) {
|
|
2504
|
-
const { execSync: execSync3 } = __require("child_process");
|
|
2505
|
-
try {
|
|
2506
|
-
const email = execSync3("gcloud config get-value account 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
2507
|
-
if (email && email.includes("@"))
|
|
2508
|
-
return email;
|
|
2509
|
-
} catch {}
|
|
2510
|
-
throw new Error("Gmail not configured — run: gcloud auth login");
|
|
2511
|
-
}
|
|
2512
|
-
async send(_to, _subject, _body) {
|
|
2513
|
-
throw new Error("Gmail send not yet wired — use AgentMail or the gws-gmail-send skill directly");
|
|
2514
|
-
}
|
|
2515
|
-
async waitForMessage(_fromDomain, _timeoutMs = 90000) {
|
|
2516
|
-
throw new Error("Gmail inbox polling not yet wired — use AgentMail or the gws-gmail skill directly");
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2519
|
-
function getEmailProvider(purpose = "general") {
|
|
2520
|
-
if (purpose === "register") {
|
|
2521
|
-
const agentmail = providers.find((p) => p.name === "agentmail" && p.configured);
|
|
2522
|
-
if (agentmail)
|
|
2523
|
-
return agentmail;
|
|
2524
|
-
}
|
|
2525
|
-
if (purpose === "login") {
|
|
2526
|
-
const gmail = providers.find((p) => p.name === "gmail" && p.configured);
|
|
2527
|
-
if (gmail)
|
|
2528
|
-
return gmail;
|
|
2529
|
-
}
|
|
2530
|
-
return providers.find((p) => p.configured) ?? null;
|
|
2531
|
-
}
|
|
2532
|
-
var providers;
|
|
2533
|
-
var init_email_provider = __esm(() => {
|
|
2534
|
-
providers = [
|
|
2535
|
-
new GmailProvider,
|
|
2536
|
-
new AgentMailProvider
|
|
2537
|
-
];
|
|
2538
|
-
});
|
|
2539
|
-
|
|
2540
|
-
// ../../src/auth/autonomous-login.ts
|
|
2541
|
-
async function detectEmailInput(tabId) {
|
|
2542
|
-
const ref = await evaluate(tabId, `(() => {
|
|
2543
|
-
// Priority order: type=email, name/id containing email, placeholder containing email
|
|
2544
|
-
const byType = document.querySelector('input[type="email"]');
|
|
2545
|
-
if (byType) return byType.getAttribute("data-ref") || "input[type=email]";
|
|
2546
|
-
|
|
2547
|
-
const byName = document.querySelector('input[name*="email" i], input[id*="email" i]');
|
|
2548
|
-
if (byName) return byName.getAttribute("data-ref") || 'input[name*="email" i]';
|
|
2549
|
-
|
|
2550
|
-
const byPlaceholder = document.querySelector('input[placeholder*="email" i]');
|
|
2551
|
-
if (byPlaceholder) return byPlaceholder.getAttribute("data-ref") || 'input[placeholder*="email" i]';
|
|
2552
|
-
|
|
2553
|
-
// Generic text input on a login page (single input + submit button pattern)
|
|
2554
|
-
const inputs = document.querySelectorAll('input[type="text"], input:not([type])');
|
|
2555
|
-
const submit = document.querySelector('button[type="submit"], input[type="submit"], button:not([type])');
|
|
2556
|
-
if (inputs.length === 1 && submit) {
|
|
2557
|
-
return inputs[0].getAttribute("data-ref") || 'input[type="text"]';
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
return null;
|
|
2561
|
-
})()`);
|
|
2562
|
-
return typeof ref === "string" ? ref : null;
|
|
2563
|
-
}
|
|
2564
|
-
async function detectOtpInput(tabId) {
|
|
2565
|
-
const ref = await evaluate(tabId, `(() => {
|
|
2566
|
-
// OTP-specific inputs
|
|
2567
|
-
const byAutocomplete = document.querySelector('input[autocomplete="one-time-code"]');
|
|
2568
|
-
if (byAutocomplete) return byAutocomplete.getAttribute("data-ref") || 'input[autocomplete="one-time-code"]';
|
|
2569
|
-
|
|
2570
|
-
// Name/id/placeholder containing otp, code, verification, pin
|
|
2571
|
-
const byName = document.querySelector(
|
|
2572
|
-
'input[name*="otp" i], input[name*="code" i], input[name*="verification" i], input[name*="pin" i], ' +
|
|
2573
|
-
'input[id*="otp" i], input[id*="code" i], input[id*="verification" i], ' +
|
|
2574
|
-
'input[placeholder*="code" i], input[placeholder*="verification" i], input[placeholder*="otp" i]'
|
|
2575
|
-
);
|
|
2576
|
-
if (byName) return byName.getAttribute("data-ref") || 'input[name*="code" i]';
|
|
2577
|
-
|
|
2578
|
-
// Numeric input with maxlength 4-8 (common OTP pattern)
|
|
2579
|
-
const numericInputs = document.querySelectorAll('input[type="number"], input[type="tel"], input[inputmode="numeric"]');
|
|
2580
|
-
for (const el of numericInputs) {
|
|
2581
|
-
const ml = parseInt(el.getAttribute("maxlength") || "0");
|
|
2582
|
-
if (ml >= 4 && ml <= 8) return el.getAttribute("data-ref") || 'input[inputmode="numeric"]';
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
// Single short text input that appeared after email submit
|
|
2586
|
-
const textInputs = document.querySelectorAll('input[type="text"], input:not([type])');
|
|
2587
|
-
for (const el of textInputs) {
|
|
2588
|
-
const ml = parseInt(el.getAttribute("maxlength") || "0");
|
|
2589
|
-
if (ml >= 4 && ml <= 8) return el.getAttribute("data-ref") || 'input[type="text"]';
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
return null;
|
|
2593
|
-
})()`);
|
|
2594
|
-
return typeof ref === "string" ? ref : null;
|
|
2595
|
-
}
|
|
2596
|
-
async function detectSubmitButton(tabId) {
|
|
2597
|
-
const ref = await evaluate(tabId, `(() => {
|
|
2598
|
-
const submit = document.querySelector(
|
|
2599
|
-
'button[type="submit"], input[type="submit"], ' +
|
|
2600
|
-
'button:not([type]):not([aria-label*="close" i]):not([aria-label*="cancel" i])'
|
|
2601
|
-
);
|
|
2602
|
-
if (submit) return submit.getAttribute("data-ref") || 'button[type="submit"]';
|
|
2603
|
-
|
|
2604
|
-
// Look for buttons with login/register/continue/verify text
|
|
2605
|
-
const buttons = document.querySelectorAll('button, a[role="button"]');
|
|
2606
|
-
for (const btn of buttons) {
|
|
2607
|
-
const text = btn.textContent?.toLowerCase() || "";
|
|
2608
|
-
if (/logs*in|signs*in|register|signs*up|continue|submit|verify|next/i.test(text)) {
|
|
2609
|
-
return btn.getAttribute("data-ref") || null;
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
return null;
|
|
2613
|
-
})()`);
|
|
2614
|
-
return typeof ref === "string" ? ref : null;
|
|
2615
|
-
}
|
|
2616
|
-
async function checkLoginSuccess(tabId, domain) {
|
|
2617
|
-
const cookies = await getCookies(tabId);
|
|
2618
|
-
const domainCookies = cookies.filter((c) => isDomainMatch(c.domain, domain));
|
|
2619
|
-
const authCookies = getAuthenticatedCookiesForDomain(domain, domainCookies);
|
|
2620
|
-
return authCookies.length > 0;
|
|
2621
|
-
}
|
|
2622
|
-
async function autonomousLogin(loginUrl, domain) {
|
|
2623
|
-
const start2 = Date.now();
|
|
2624
|
-
const targetDomain = domain ?? (() => {
|
|
2625
|
-
try {
|
|
2626
|
-
return new URL(loginUrl).hostname.replace(/^www\./, "");
|
|
2627
|
-
} catch {
|
|
2628
|
-
return loginUrl;
|
|
2629
|
-
}
|
|
2630
|
-
})();
|
|
2631
|
-
const elapsed = () => Date.now() - start2;
|
|
2632
|
-
const emailProvider = getEmailProvider("register");
|
|
2633
|
-
if (!emailProvider) {
|
|
2634
|
-
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() };
|
|
2635
|
-
}
|
|
2636
|
-
log("autonomous-login", `starting for ${targetDomain} — url: ${loginUrl}`);
|
|
2637
|
-
let tabId;
|
|
2638
|
-
try {
|
|
2639
|
-
await start();
|
|
2640
|
-
tabId = await getDefaultTab();
|
|
2641
|
-
await networkEnable(tabId);
|
|
2642
|
-
} catch (err) {
|
|
2643
|
-
return { success: false, method: "failed", domain: targetDomain, cookies_stored: 0, error: `Kuri start failed: ${err}`, duration_ms: elapsed() };
|
|
2644
|
-
}
|
|
2645
|
-
try {
|
|
2646
|
-
await navigate(tabId, loginUrl);
|
|
2647
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
2648
|
-
let emailRef = await detectEmailInput(tabId);
|
|
2649
|
-
if (!emailRef) {
|
|
2650
|
-
const regUrl = loginUrl.replace(/login|signin|sign-in/i, "register").replace(/register/i, "signup");
|
|
2651
|
-
if (regUrl !== loginUrl) {
|
|
2652
|
-
await navigate(tabId, regUrl);
|
|
2653
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
2654
|
-
emailRef = await detectEmailInput(tabId);
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
if (!emailRef) {
|
|
2658
|
-
log("autonomous-login", `no email input found on ${loginUrl}`);
|
|
2659
|
-
return { success: false, method: "failed", domain: targetDomain, cookies_stored: 0, error: "no email input field detected", duration_ms: elapsed() };
|
|
2660
|
-
}
|
|
2661
|
-
let agentEmail;
|
|
2662
|
-
try {
|
|
2663
|
-
agentEmail = await emailProvider.getAddress(targetDomain);
|
|
2664
|
-
} catch (err) {
|
|
2665
|
-
return { success: false, method: "failed", domain: targetDomain, cookies_stored: 0, error: `Email provider (${emailProvider.name}) failed: ${err}`, duration_ms: elapsed() };
|
|
2666
|
-
}
|
|
2667
|
-
log("autonomous-login", `filling email: ${agentEmail} (via ${emailProvider.name}) into ${emailRef}`);
|
|
2668
|
-
await fill(tabId, emailRef, agentEmail);
|
|
2669
|
-
const submitRef = await detectSubmitButton(tabId);
|
|
2670
|
-
if (submitRef) {
|
|
2671
|
-
await click(tabId, submitRef);
|
|
2672
|
-
} else {
|
|
2673
|
-
await evaluate(tabId, `document.querySelector('${emailRef}')?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))`);
|
|
2674
|
-
}
|
|
2675
|
-
await new Promise((r) => setTimeout(r, POST_SUBMIT_SETTLE_MS));
|
|
2676
|
-
const otpRef = await detectOtpInput(tabId);
|
|
2677
|
-
if (otpRef) {
|
|
2678
|
-
log("autonomous-login", `OTP input detected (${otpRef}), waiting for verification email via ${emailProvider.name}...`);
|
|
2679
|
-
const verificationEmail = await emailProvider.waitForMessage(targetDomain, OTP_FILL_TIMEOUT_MS);
|
|
2680
|
-
const otp = verificationEmail ? extractOtpFromEmail(verificationEmail.text) : null;
|
|
2681
|
-
if (!otp) {
|
|
2682
|
-
return { success: false, method: "failed", domain: targetDomain, email: agentEmail, cookies_stored: 0, error: "OTP email not received within timeout", duration_ms: elapsed() };
|
|
2683
|
-
}
|
|
2684
|
-
log("autonomous-login", `filling OTP: ${otp}`);
|
|
2685
|
-
await fill(tabId, otpRef, otp);
|
|
2686
|
-
const otpSubmitRef = await detectSubmitButton(tabId);
|
|
2687
|
-
if (otpSubmitRef) {
|
|
2688
|
-
await click(tabId, otpSubmitRef);
|
|
2689
|
-
}
|
|
2690
|
-
await new Promise((r) => setTimeout(r, POST_SUBMIT_SETTLE_MS));
|
|
2691
|
-
} else {
|
|
2692
|
-
log("autonomous-login", `no OTP input, trying magic link flow via ${emailProvider.name}...`);
|
|
2693
|
-
const verificationEmail = await emailProvider.waitForMessage(targetDomain, OTP_FILL_TIMEOUT_MS);
|
|
2694
|
-
const link = verificationEmail ? extractVerificationLink(verificationEmail.text, verificationEmail.html) : null;
|
|
2695
|
-
if (link) {
|
|
2696
|
-
log("autonomous-login", `navigating to magic link`);
|
|
2697
|
-
await navigate(tabId, link);
|
|
2698
|
-
await new Promise((r) => setTimeout(r, POST_SUBMIT_SETTLE_MS));
|
|
2699
|
-
} else {
|
|
2700
|
-
log("autonomous-login", `no magic link received, checking if already authenticated...`);
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
const success = await checkLoginSuccess(tabId, targetDomain);
|
|
2704
|
-
if (success) {
|
|
2705
|
-
const cookies = await getCookies(tabId);
|
|
2706
|
-
const domainCookies = cookies.filter((c) => isDomainMatch(c.domain, targetDomain));
|
|
2707
|
-
const storableCookies = domainCookies.map((c) => ({
|
|
2708
|
-
name: c.name,
|
|
2709
|
-
value: c.value,
|
|
2710
|
-
domain: c.domain,
|
|
2711
|
-
path: c.path,
|
|
2712
|
-
secure: c.secure,
|
|
2713
|
-
httpOnly: c.httpOnly,
|
|
2714
|
-
sameSite: c.sameSite,
|
|
2715
|
-
expires: c.expires
|
|
2716
|
-
}));
|
|
2717
|
-
const vaultKey = `auth:${getRegistrableDomain(targetDomain)}`;
|
|
2718
|
-
await storeCredential(vaultKey, JSON.stringify({ cookies: storableCookies }));
|
|
2719
|
-
await saveAuthProfileBestEffort(tabId, targetDomain, "autonomous_login");
|
|
2720
|
-
log("autonomous-login", `login succeeded for ${targetDomain} — stored ${storableCookies.length} cookies (${elapsed()}ms)`);
|
|
2721
|
-
return {
|
|
2722
|
-
success: true,
|
|
2723
|
-
method: otpRef ? "agent-mail-otp" : "agent-mail-link",
|
|
2724
|
-
domain: targetDomain,
|
|
2725
|
-
email: agentEmail,
|
|
2726
|
-
cookies_stored: storableCookies.length,
|
|
2727
|
-
duration_ms: elapsed()
|
|
2728
|
-
};
|
|
2729
|
-
}
|
|
2730
|
-
return { success: false, method: "failed", domain: targetDomain, email: agentEmail, cookies_stored: 0, error: "login flow completed but no auth cookies detected", duration_ms: elapsed() };
|
|
2731
|
-
} finally {}
|
|
2732
|
-
}
|
|
2733
|
-
var OTP_FILL_TIMEOUT_MS = 120000, POST_SUBMIT_SETTLE_MS = 3000;
|
|
2734
|
-
var init_autonomous_login = __esm(async () => {
|
|
2735
|
-
init_client2();
|
|
2736
|
-
init_logger();
|
|
2737
|
-
init_email_provider();
|
|
2738
|
-
init_domain();
|
|
2739
|
-
await __promiseAll([
|
|
2740
|
-
init_agent_mail(),
|
|
2741
|
-
init_auth(),
|
|
2742
|
-
init_vault()
|
|
2743
|
-
]);
|
|
1477
|
+
await init_vault();
|
|
2744
1478
|
});
|
|
2745
1479
|
|
|
2746
1480
|
// ../../src/auth/runtime.ts
|
|
@@ -2802,18 +1536,6 @@ class LocalAuthRuntime {
|
|
|
2802
1536
|
return true;
|
|
2803
1537
|
}
|
|
2804
1538
|
} catch {}
|
|
2805
|
-
const url = loginUrl ?? `https://${domain}/login`;
|
|
2806
|
-
try {
|
|
2807
|
-
const result = await autonomousLogin(url, domain);
|
|
2808
|
-
if (result.success) {
|
|
2809
|
-
this.setSession(domain, "autonomous-login", 3600000);
|
|
2810
|
-
log("auth-runtime", `loginIfNeeded resolved via autonomous login for ${domain} (${result.method}, ${result.duration_ms}ms)`);
|
|
2811
|
-
return true;
|
|
2812
|
-
}
|
|
2813
|
-
log("auth-runtime", `autonomous login failed for ${domain}: ${result.error}`);
|
|
2814
|
-
} catch (err) {
|
|
2815
|
-
log("auth-runtime", `autonomous login error for ${domain}: ${err instanceof Error ? err.message : err}`);
|
|
2816
|
-
}
|
|
2817
1539
|
return false;
|
|
2818
1540
|
}
|
|
2819
1541
|
setSession(domain, token, ttlMs = 3600000) {
|
|
@@ -2823,10 +1545,7 @@ class LocalAuthRuntime {
|
|
|
2823
1545
|
var authRuntime;
|
|
2824
1546
|
var init_runtime = __esm(async () => {
|
|
2825
1547
|
init_logger();
|
|
2826
|
-
await
|
|
2827
|
-
init_auth(),
|
|
2828
|
-
init_autonomous_login()
|
|
2829
|
-
]);
|
|
1548
|
+
await init_auth();
|
|
2830
1549
|
authRuntime = new LocalAuthRuntime;
|
|
2831
1550
|
});
|
|
2832
1551
|
|
|
@@ -3032,7 +1751,6 @@ var init_execution = __esm(async () => {
|
|
|
3032
1751
|
init_vault(),
|
|
3033
1752
|
init_auth(),
|
|
3034
1753
|
init_runtime(),
|
|
3035
|
-
init_autonomous_login(),
|
|
3036
1754
|
init_indexer(),
|
|
3037
1755
|
init_orchestrator()
|
|
3038
1756
|
]);
|
|
@@ -3358,186 +2076,6 @@ function checkWalletConfigured2() {
|
|
|
3358
2076
|
}
|
|
3359
2077
|
var init_wallet2 = () => {};
|
|
3360
2078
|
|
|
3361
|
-
// ../../src/auth/bootstrap-agentmail.ts
|
|
3362
|
-
var exports_bootstrap_agentmail = {};
|
|
3363
|
-
__export(exports_bootstrap_agentmail, {
|
|
3364
|
-
bootstrapAgentMailKey: () => bootstrapAgentMailKey
|
|
3365
|
-
});
|
|
3366
|
-
import os6 from "node:os";
|
|
3367
|
-
import fs2 from "node:fs";
|
|
3368
|
-
import path9 from "node:path";
|
|
3369
|
-
async function bootstrapAgentMailKey() {
|
|
3370
|
-
log("bootstrap-agentmail", "starting — opening console.agentmail.to");
|
|
3371
|
-
try {
|
|
3372
|
-
await start();
|
|
3373
|
-
const tabId = await getDefaultTab();
|
|
3374
|
-
await networkEnable(tabId);
|
|
3375
|
-
await importBrowserCookiesIntoTab(tabId, "agentmail.to");
|
|
3376
|
-
await importBrowserCookiesIntoTab(tabId, "clerk.agentmail.to");
|
|
3377
|
-
await importBrowserCookiesIntoTab(tabId, "github.com");
|
|
3378
|
-
await navigate(tabId, CONSOLE_URL);
|
|
3379
|
-
await new Promise((r) => setTimeout(r, SETTLE_MS));
|
|
3380
|
-
let currentUrl = await getCurrentUrl(tabId);
|
|
3381
|
-
let onDashboard = typeof currentUrl === "string" && currentUrl.includes("/dashboard");
|
|
3382
|
-
if (!onDashboard) {
|
|
3383
|
-
log("bootstrap-agentmail", "not logged in — looking for social OAuth");
|
|
3384
|
-
const clickResult = await evaluate(tabId, `(() => {
|
|
3385
|
-
// Clerk's social button class pattern
|
|
3386
|
-
const socialBtns = document.querySelectorAll('.cl-socialButtonsBlockButton, [data-provider]');
|
|
3387
|
-
for (const btn of socialBtns) {
|
|
3388
|
-
const text = (btn.textContent || '').toLowerCase();
|
|
3389
|
-
const provider = btn.getAttribute('data-provider') || '';
|
|
3390
|
-
// Match any social provider — Google, GitHub, etc.
|
|
3391
|
-
if (text || provider) {
|
|
3392
|
-
btn.click();
|
|
3393
|
-
return 'clicked:' + (provider || text.slice(0, 20));
|
|
3394
|
-
}
|
|
3395
|
-
}
|
|
3396
|
-
// Fallback: look for any button with a known OAuth provider name
|
|
3397
|
-
const btns = document.querySelectorAll('button, a');
|
|
3398
|
-
for (const btn of btns) {
|
|
3399
|
-
const text = (btn.textContent || '').toLowerCase();
|
|
3400
|
-
if (text.includes('google') || text.includes('github') || text.includes('continue with')) {
|
|
3401
|
-
btn.click();
|
|
3402
|
-
return 'clicked:' + text.slice(0, 30);
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
return 'not_found';
|
|
3406
|
-
})()`);
|
|
3407
|
-
const clickStr = typeof clickResult === "string" ? clickResult : String(clickResult);
|
|
3408
|
-
log("bootstrap-agentmail", `social button result: ${clickStr}`);
|
|
3409
|
-
if (clickStr.startsWith("clicked")) {
|
|
3410
|
-
log("bootstrap-agentmail", "clicked social 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", "OAuth completed — on dashboard");
|
|
3418
|
-
break;
|
|
3419
|
-
}
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
if (!onDashboard) {
|
|
3423
|
-
log("bootstrap-agentmail", "could not auto-login — manual setup needed");
|
|
3424
|
-
const pageInfo = await evaluate(tabId, `JSON.stringify({
|
|
3425
|
-
url: window.location.href,
|
|
3426
|
-
title: document.title,
|
|
3427
|
-
visible_buttons: Array.from(document.querySelectorAll('button,a'))
|
|
3428
|
-
.map(e => (e.textContent || '').trim().slice(0, 30))
|
|
3429
|
-
.filter(t => t)
|
|
3430
|
-
.slice(0, 10),
|
|
3431
|
-
})`).catch(() => "{}");
|
|
3432
|
-
return {
|
|
3433
|
-
success: false,
|
|
3434
|
-
method: "manual",
|
|
3435
|
-
error: `Could not auto-login to AgentMail console. Page state: ${pageInfo}. Sign up at https://console.agentmail.to and create an API key manually, then: export AGENTMAIL_API_KEY=<key>`
|
|
3436
|
-
};
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
log("bootstrap-agentmail", "on dashboard — navigating to API keys");
|
|
3440
|
-
await navigate(tabId, `${DASHBOARD_URL}/api-keys`);
|
|
3441
|
-
await new Promise((r) => setTimeout(r, SETTLE_MS));
|
|
3442
|
-
const createResult = await evaluate(tabId, `(() => {
|
|
3443
|
-
const btns = document.querySelectorAll('button, a');
|
|
3444
|
-
for (const btn of btns) {
|
|
3445
|
-
const text = (btn.textContent || '').toLowerCase();
|
|
3446
|
-
if (text.includes('create') && (text.includes('key') || text.includes('api'))) {
|
|
3447
|
-
btn.click();
|
|
3448
|
-
return 'clicked';
|
|
3449
|
-
}
|
|
3450
|
-
}
|
|
3451
|
-
return 'not_found';
|
|
3452
|
-
})()`);
|
|
3453
|
-
if (createResult === "clicked") {
|
|
3454
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
3455
|
-
await evaluate(tabId, `(() => {
|
|
3456
|
-
const input = document.querySelector('input[placeholder*="name" i], input[name*="name" i], input[type="text"]');
|
|
3457
|
-
if (input) {
|
|
3458
|
-
input.value = 'unbrowse-agent';
|
|
3459
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
3460
|
-
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
3461
|
-
}
|
|
3462
|
-
})()`);
|
|
3463
|
-
await evaluate(tabId, `(() => {
|
|
3464
|
-
const btns = document.querySelectorAll('button');
|
|
3465
|
-
for (const btn of btns) {
|
|
3466
|
-
const text = (btn.textContent || '').toLowerCase();
|
|
3467
|
-
if (text.includes('create') || text.includes('confirm') || text.includes('generate')) {
|
|
3468
|
-
btn.click();
|
|
3469
|
-
return 'clicked';
|
|
3470
|
-
}
|
|
3471
|
-
}
|
|
3472
|
-
})()`);
|
|
3473
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
3474
|
-
}
|
|
3475
|
-
const apiKey = await evaluate(tabId, `(() => {
|
|
3476
|
-
// Look for the key in common patterns
|
|
3477
|
-
// 1. Input/textarea with the key value
|
|
3478
|
-
const inputs = document.querySelectorAll('input[readonly], input[type="text"], textarea, code, pre');
|
|
3479
|
-
for (const el of inputs) {
|
|
3480
|
-
const val = el.value || el.textContent || '';
|
|
3481
|
-
// AgentMail keys typically start with 'am_' or are long alphanumeric strings
|
|
3482
|
-
if (/^(am_|sk_|key_)/.test(val) || (val.length > 30 && /^[a-zA-Z0-9_-]+$/.test(val))) {
|
|
3483
|
-
return val.trim();
|
|
3484
|
-
}
|
|
3485
|
-
}
|
|
3486
|
-
// 2. Look in the page text for key patterns
|
|
3487
|
-
const text = document.body.innerText;
|
|
3488
|
-
const match = text.match(/(am_[a-zA-Z0-9_-]{20,}|sk_[a-zA-Z0-9_-]{20,})/);
|
|
3489
|
-
if (match) return match[1];
|
|
3490
|
-
return null;
|
|
3491
|
-
})()`);
|
|
3492
|
-
if (apiKey && typeof apiKey === "string" && apiKey.length > 10) {
|
|
3493
|
-
log("bootstrap-agentmail", `extracted API key: ${apiKey.substring(0, 8)}...`);
|
|
3494
|
-
await storeCredential("agentmail:api_key", apiKey);
|
|
3495
|
-
persistApiKeyToShell(apiKey);
|
|
3496
|
-
process.env.AGENTMAIL_API_KEY = apiKey;
|
|
3497
|
-
return {
|
|
3498
|
-
success: true,
|
|
3499
|
-
api_key: apiKey,
|
|
3500
|
-
method: onDashboard ? "existing-session" : "github-oauth"
|
|
3501
|
-
};
|
|
3502
|
-
}
|
|
3503
|
-
return {
|
|
3504
|
-
success: false,
|
|
3505
|
-
error: "Reached dashboard but could not extract API key. Create one manually at https://console.agentmail.to/dashboard/api-keys"
|
|
3506
|
-
};
|
|
3507
|
-
} finally {
|
|
3508
|
-
try {
|
|
3509
|
-
await stop();
|
|
3510
|
-
} catch {}
|
|
3511
|
-
}
|
|
3512
|
-
}
|
|
3513
|
-
function persistApiKeyToShell(apiKey) {
|
|
3514
|
-
const shell = process.env.SHELL ?? "/bin/zsh";
|
|
3515
|
-
const rcFile = shell.includes("zsh") ? path9.join(os6.homedir(), ".zshrc") : path9.join(os6.homedir(), ".bashrc");
|
|
3516
|
-
try {
|
|
3517
|
-
const content = fs2.existsSync(rcFile) ? fs2.readFileSync(rcFile, "utf-8") : "";
|
|
3518
|
-
if (content.includes("AGENTMAIL_API_KEY")) {
|
|
3519
|
-
log("bootstrap-agentmail", `${rcFile} already has AGENTMAIL_API_KEY — not overwriting`);
|
|
3520
|
-
return;
|
|
3521
|
-
}
|
|
3522
|
-
fs2.appendFileSync(rcFile, `
|
|
3523
|
-
# AgentMail API key (added by unbrowse setup)
|
|
3524
|
-
export AGENTMAIL_API_KEY="${apiKey}"
|
|
3525
|
-
`);
|
|
3526
|
-
log("bootstrap-agentmail", `wrote AGENTMAIL_API_KEY to ${rcFile}`);
|
|
3527
|
-
} catch (err) {
|
|
3528
|
-
log("bootstrap-agentmail", `failed to write to ${rcFile}: ${err}`);
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
3531
|
-
var CONSOLE_URL = "https://console.agentmail.to", DASHBOARD_URL = "https://console.agentmail.to/dashboard", SETTLE_MS = 3000, AUTH_TIMEOUT_MS = 120000;
|
|
3532
|
-
var init_bootstrap_agentmail = __esm(async () => {
|
|
3533
|
-
init_client2();
|
|
3534
|
-
init_logger();
|
|
3535
|
-
await __promiseAll([
|
|
3536
|
-
init_vault(),
|
|
3537
|
-
init_auth()
|
|
3538
|
-
]);
|
|
3539
|
-
});
|
|
3540
|
-
|
|
3541
2079
|
// ../../src/version.ts
|
|
3542
2080
|
var exports_version = {};
|
|
3543
2081
|
__export(exports_version, {
|
|
@@ -3663,210 +2201,6 @@ var init_version2 = __esm(() => {
|
|
|
3663
2201
|
RELEASE_MANIFEST_SIGNATURE2 = BUILD_RELEASE_MANIFEST_SIGNATURE?.trim() || "";
|
|
3664
2202
|
});
|
|
3665
2203
|
|
|
3666
|
-
// ../../src/auth/agent-mail.ts
|
|
3667
|
-
var exports_agent_mail2 = {};
|
|
3668
|
-
__export(exports_agent_mail2, {
|
|
3669
|
-
waitForVerificationEmail: () => waitForVerificationEmail2,
|
|
3670
|
-
tryAgentMailAuth: () => tryAgentMailAuth2,
|
|
3671
|
-
isAgentMailAvailable: () => isAgentMailAvailable2,
|
|
3672
|
-
getOrCreateSiteInbox: () => getOrCreateSiteInbox2,
|
|
3673
|
-
extractVerificationLink: () => extractVerificationLink2,
|
|
3674
|
-
extractOtpFromEmail: () => extractOtpFromEmail2,
|
|
3675
|
-
autonomousEmailLogin: () => autonomousEmailLogin2
|
|
3676
|
-
});
|
|
3677
|
-
import { AgentMailClient as AgentMailClient2 } from "agentmail";
|
|
3678
|
-
function getClient2() {
|
|
3679
|
-
if (_client2)
|
|
3680
|
-
return _client2;
|
|
3681
|
-
const apiKey = process.env.AGENTMAIL_API_KEY;
|
|
3682
|
-
if (!apiKey)
|
|
3683
|
-
throw new Error("AGENTMAIL_API_KEY not set — cannot use agent mail");
|
|
3684
|
-
_client2 = new AgentMailClient2({ apiKey });
|
|
3685
|
-
return _client2;
|
|
3686
|
-
}
|
|
3687
|
-
async function getStoredInbox2(domain) {
|
|
3688
|
-
const key = `${VAULT_PREFIX2}${getRegistrableDomain(domain)}`;
|
|
3689
|
-
const raw = await getCredential(key);
|
|
3690
|
-
if (!raw)
|
|
3691
|
-
return null;
|
|
3692
|
-
try {
|
|
3693
|
-
return JSON.parse(raw);
|
|
3694
|
-
} catch {
|
|
3695
|
-
return null;
|
|
3696
|
-
}
|
|
3697
|
-
}
|
|
3698
|
-
async function storeInbox2(domain, inbox) {
|
|
3699
|
-
const key = `${VAULT_PREFIX2}${getRegistrableDomain(domain)}`;
|
|
3700
|
-
await storeCredential(key, JSON.stringify(inbox));
|
|
3701
|
-
log("agent-mail", `stored inbox ${inbox.email} for ${domain}`);
|
|
3702
|
-
}
|
|
3703
|
-
async function getOrCreateSiteInbox2(domain) {
|
|
3704
|
-
const stored = await getStoredInbox2(domain);
|
|
3705
|
-
if (stored) {
|
|
3706
|
-
log("agent-mail", `reusing stored inbox ${stored.email} for ${domain}`);
|
|
3707
|
-
return { inboxId: stored.inboxId, email: stored.email };
|
|
3708
|
-
}
|
|
3709
|
-
const client = getClient2();
|
|
3710
|
-
const safeDomain = getRegistrableDomain(domain).replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
3711
|
-
const username = `unbrowse-${safeDomain}`;
|
|
3712
|
-
const clientId = `unbrowse-login-${safeDomain}`;
|
|
3713
|
-
try {
|
|
3714
|
-
const inbox = await client.inboxes.create({
|
|
3715
|
-
username,
|
|
3716
|
-
domain: "agentmail.to",
|
|
3717
|
-
displayName: `Unbrowse Agent - ${domain}`,
|
|
3718
|
-
clientId
|
|
3719
|
-
});
|
|
3720
|
-
const result = { inboxId: inbox.inboxId, email: inbox.email };
|
|
3721
|
-
await storeInbox2(domain, { ...result, domain, createdAt: new Date().toISOString() });
|
|
3722
|
-
log("agent-mail", `created inbox ${inbox.email} for ${domain}`);
|
|
3723
|
-
return result;
|
|
3724
|
-
} catch (err) {
|
|
3725
|
-
log("agent-mail", `create failed (likely exists), listing to find match: ${err instanceof Error ? err.message : err}`);
|
|
3726
|
-
try {
|
|
3727
|
-
const list = await client.inboxes.list({ limit: 100 });
|
|
3728
|
-
const match = list.inboxes.find((i) => i.clientId === clientId || i.email === `${username}@agentmail.to`);
|
|
3729
|
-
if (match) {
|
|
3730
|
-
const result = { inboxId: match.inboxId, email: match.email };
|
|
3731
|
-
await storeInbox2(domain, { ...result, domain, createdAt: new Date().toISOString() });
|
|
3732
|
-
return result;
|
|
3733
|
-
}
|
|
3734
|
-
} catch {}
|
|
3735
|
-
throw new Error(`Failed to create or find AgentMail inbox for ${domain}`);
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
|
-
async function waitForVerificationEmail2(inboxId, fromDomain, timeoutMs = DEFAULT_TIMEOUT_MS2) {
|
|
3739
|
-
const client = getClient2();
|
|
3740
|
-
const start2 = Date.now();
|
|
3741
|
-
const normalizedDomain = fromDomain.toLowerCase();
|
|
3742
|
-
log("agent-mail", `polling inbox ${inboxId} for email from *@${normalizedDomain} (timeout: ${timeoutMs}ms)`);
|
|
3743
|
-
while (Date.now() - start2 < timeoutMs) {
|
|
3744
|
-
try {
|
|
3745
|
-
const response = await client.inboxes.messages.list(inboxId, {
|
|
3746
|
-
limit: 10,
|
|
3747
|
-
labels: ["received"]
|
|
3748
|
-
});
|
|
3749
|
-
for (const item of response.messages ?? []) {
|
|
3750
|
-
const msg = await client.inboxes.messages.get(inboxId, item.messageId);
|
|
3751
|
-
const from = typeof msg.from === "string" ? msg.from : (msg.from ?? []).join(", ");
|
|
3752
|
-
if (from.toLowerCase().includes(normalizedDomain)) {
|
|
3753
|
-
log("agent-mail", `found verification email from ${from}: "${msg.subject}"`);
|
|
3754
|
-
return {
|
|
3755
|
-
messageId: msg.messageId,
|
|
3756
|
-
threadId: msg.threadId,
|
|
3757
|
-
subject: msg.subject ?? "",
|
|
3758
|
-
from,
|
|
3759
|
-
text: msg.extractedText ?? msg.text ?? "",
|
|
3760
|
-
html: msg.extractedHtml ?? msg.html ?? "",
|
|
3761
|
-
receivedAt: msg.createdAt
|
|
3762
|
-
};
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
} catch (err) {
|
|
3766
|
-
log("agent-mail", `poll error: ${err instanceof Error ? err.message : err}`);
|
|
3767
|
-
}
|
|
3768
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
3769
|
-
}
|
|
3770
|
-
log("agent-mail", `timeout waiting for email from ${normalizedDomain}`);
|
|
3771
|
-
return null;
|
|
3772
|
-
}
|
|
3773
|
-
function extractOtpFromEmail2(text) {
|
|
3774
|
-
const codePhrase = text.match(/(?:verification|confirm|security|login|one[- ]time|otp)\s*(?:code|pin|number)[:\s]+(\w{4,8})/i);
|
|
3775
|
-
if (codePhrase)
|
|
3776
|
-
return codePhrase[1];
|
|
3777
|
-
const codeIs = text.match(/(?:code|otp|pin)[:\s]+(\w{4,8})/i);
|
|
3778
|
-
if (codeIs)
|
|
3779
|
-
return codeIs[1];
|
|
3780
|
-
const six = text.match(/\b(\d{6})\b/);
|
|
3781
|
-
if (six)
|
|
3782
|
-
return six[1];
|
|
3783
|
-
const four = text.match(/\b(\d{4})\b/);
|
|
3784
|
-
if (four)
|
|
3785
|
-
return four[1];
|
|
3786
|
-
return null;
|
|
3787
|
-
}
|
|
3788
|
-
function extractVerificationLink2(text, html) {
|
|
3789
|
-
const htmlLink = html.match(/href="(https?:\/\/[^"]*(?:verify|confirm|activate|magic|token|auth|callback|validate|approve|email)[^"]*)"/i);
|
|
3790
|
-
if (htmlLink)
|
|
3791
|
-
return htmlLink[1];
|
|
3792
|
-
const textLink = text.match(/(https?:\/\/\S*(?:verify|confirm|activate|magic|token|auth|callback|validate|approve|email)\S*)/i);
|
|
3793
|
-
if (textLink)
|
|
3794
|
-
return textLink[1];
|
|
3795
|
-
const anyHtmlLink = html.match(/href="(https?:\/\/[^"]+)"/);
|
|
3796
|
-
if (anyHtmlLink)
|
|
3797
|
-
return anyHtmlLink[1];
|
|
3798
|
-
const anyTextLink = text.match(/(https?:\/\/\S+)/);
|
|
3799
|
-
if (anyTextLink)
|
|
3800
|
-
return anyTextLink[1];
|
|
3801
|
-
return null;
|
|
3802
|
-
}
|
|
3803
|
-
async function autonomousEmailLogin2(domain) {
|
|
3804
|
-
const inbox = await getOrCreateSiteInbox2(domain);
|
|
3805
|
-
const client = getClient2();
|
|
3806
|
-
log("agent-mail", `session ready for ${domain} — email: ${inbox.email}`);
|
|
3807
|
-
return {
|
|
3808
|
-
email: inbox.email,
|
|
3809
|
-
inboxId: inbox.inboxId,
|
|
3810
|
-
domain,
|
|
3811
|
-
waitForOtp: async (timeoutMs = DEFAULT_TIMEOUT_MS2) => {
|
|
3812
|
-
const email = await waitForVerificationEmail2(inbox.inboxId, domain, timeoutMs);
|
|
3813
|
-
if (!email)
|
|
3814
|
-
return null;
|
|
3815
|
-
const otp = extractOtpFromEmail2(email.text);
|
|
3816
|
-
if (otp)
|
|
3817
|
-
log("agent-mail", `extracted OTP: ${otp} from ${email.subject}`);
|
|
3818
|
-
return otp;
|
|
3819
|
-
},
|
|
3820
|
-
waitForLink: async (timeoutMs = DEFAULT_TIMEOUT_MS2) => {
|
|
3821
|
-
const email = await waitForVerificationEmail2(inbox.inboxId, domain, timeoutMs);
|
|
3822
|
-
if (!email)
|
|
3823
|
-
return null;
|
|
3824
|
-
const link = extractVerificationLink2(email.text, email.html);
|
|
3825
|
-
if (link)
|
|
3826
|
-
log("agent-mail", `extracted verification link from ${email.subject}`);
|
|
3827
|
-
return link;
|
|
3828
|
-
},
|
|
3829
|
-
waitForEmail: (timeoutMs = DEFAULT_TIMEOUT_MS2) => waitForVerificationEmail2(inbox.inboxId, domain, timeoutMs),
|
|
3830
|
-
sendEmail: async (to, subject, body) => {
|
|
3831
|
-
const result = await client.inboxes.messages.send(inbox.inboxId, {
|
|
3832
|
-
to: [to],
|
|
3833
|
-
subject,
|
|
3834
|
-
text: body
|
|
3835
|
-
});
|
|
3836
|
-
log("agent-mail", `sent email to ${to}: "${subject}" (${result.messageId})`);
|
|
3837
|
-
return { messageId: result.messageId, threadId: result.threadId };
|
|
3838
|
-
},
|
|
3839
|
-
replyTo: async (messageId, body) => {
|
|
3840
|
-
const result = await client.inboxes.messages.reply(inbox.inboxId, messageId, {
|
|
3841
|
-
text: body
|
|
3842
|
-
});
|
|
3843
|
-
log("agent-mail", `replied to ${messageId} (${result.messageId})`);
|
|
3844
|
-
return { messageId: result.messageId, threadId: result.threadId };
|
|
3845
|
-
}
|
|
3846
|
-
};
|
|
3847
|
-
}
|
|
3848
|
-
function isAgentMailAvailable2() {
|
|
3849
|
-
return Boolean(process.env.AGENTMAIL_API_KEY);
|
|
3850
|
-
}
|
|
3851
|
-
async function tryAgentMailAuth2(domain) {
|
|
3852
|
-
if (!isAgentMailAvailable2()) {
|
|
3853
|
-
log("agent-mail", `skipping — AGENTMAIL_API_KEY not set`);
|
|
3854
|
-
return null;
|
|
3855
|
-
}
|
|
3856
|
-
try {
|
|
3857
|
-
return await autonomousEmailLogin2(domain);
|
|
3858
|
-
} catch (err) {
|
|
3859
|
-
log("agent-mail", `auth failed for ${domain}: ${err instanceof Error ? err.message : err}`);
|
|
3860
|
-
return null;
|
|
3861
|
-
}
|
|
3862
|
-
}
|
|
3863
|
-
var POLL_INTERVAL_MS2 = 3000, DEFAULT_TIMEOUT_MS2 = 90000, _client2 = null, VAULT_PREFIX2 = "agentmail:";
|
|
3864
|
-
var init_agent_mail2 = __esm(async () => {
|
|
3865
|
-
init_logger();
|
|
3866
|
-
init_domain();
|
|
3867
|
-
await init_vault();
|
|
3868
|
-
});
|
|
3869
|
-
|
|
3870
2204
|
// ../../src/auth/browser-cookies.ts
|
|
3871
2205
|
var exports_browser_cookies2 = {};
|
|
3872
2206
|
__export(exports_browser_cookies2, {
|
|
@@ -5907,11 +4241,7 @@ async function runSetup(options) {
|
|
|
5907
4241
|
browser_engine: browser,
|
|
5908
4242
|
opencode: writeOpenCodeCommand(options?.opencode ?? "auto", cwd),
|
|
5909
4243
|
update_hints: configureUpdateHintHooks(import.meta.url, installSource),
|
|
5910
|
-
wallet
|
|
5911
|
-
agent_mail: {
|
|
5912
|
-
configured: Boolean(process.env.AGENTMAIL_API_KEY),
|
|
5913
|
-
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"
|
|
5914
|
-
}
|
|
4244
|
+
wallet
|
|
5915
4245
|
};
|
|
5916
4246
|
}
|
|
5917
4247
|
|
|
@@ -6115,8 +4445,8 @@ function parseArgs(argv) {
|
|
|
6115
4445
|
}
|
|
6116
4446
|
return { command, args: positional, flags };
|
|
6117
4447
|
}
|
|
6118
|
-
async function api2(method,
|
|
6119
|
-
let target = `${BASE_URL}${
|
|
4448
|
+
async function api2(method, path9, body) {
|
|
4449
|
+
let target = `${BASE_URL}${path9}`;
|
|
6120
4450
|
let requestBody = body;
|
|
6121
4451
|
if (method === "GET" && body && typeof body === "object") {
|
|
6122
4452
|
const params = new URLSearchParams;
|
|
@@ -6446,8 +4776,8 @@ async function cmdResolve(flags) {
|
|
|
6446
4776
|
throw error;
|
|
6447
4777
|
}
|
|
6448
4778
|
}
|
|
6449
|
-
function drillPath(data,
|
|
6450
|
-
const segments =
|
|
4779
|
+
function drillPath(data, path9) {
|
|
4780
|
+
const segments = path9.split(/\./).flatMap((s) => {
|
|
6451
4781
|
const m = s.match(/^(.+)\[\]$/);
|
|
6452
4782
|
return m ? [m[1], "[]"] : [s];
|
|
6453
4783
|
});
|
|
@@ -6474,9 +4804,9 @@ function drillPath(data, path10) {
|
|
|
6474
4804
|
}
|
|
6475
4805
|
return values;
|
|
6476
4806
|
}
|
|
6477
|
-
function resolveDotPath(obj,
|
|
4807
|
+
function resolveDotPath(obj, path9) {
|
|
6478
4808
|
let cur = obj;
|
|
6479
|
-
for (const key of
|
|
4809
|
+
for (const key of path9.split(".")) {
|
|
6480
4810
|
if (cur == null || typeof cur !== "object")
|
|
6481
4811
|
return;
|
|
6482
4812
|
cur = cur[key];
|
|
@@ -6493,8 +4823,8 @@ function applyExtract(items, extractSpec) {
|
|
|
6493
4823
|
return items.map((item) => {
|
|
6494
4824
|
const row = {};
|
|
6495
4825
|
let hasValue = false;
|
|
6496
|
-
for (const { alias, path:
|
|
6497
|
-
const val = resolveDotPath(item,
|
|
4826
|
+
for (const { alias, path: path9 } of fields) {
|
|
4827
|
+
const val = resolveDotPath(item, path9);
|
|
6498
4828
|
row[alias] = val ?? null;
|
|
6499
4829
|
if (val != null)
|
|
6500
4830
|
hasValue = true;
|
|
@@ -6856,21 +5186,6 @@ async function cmdSetup(flags) {
|
|
|
6856
5186
|
info("Set up a wallet to start earning:");
|
|
6857
5187
|
info(" npx @crossmint/lobster-cli setup");
|
|
6858
5188
|
}
|
|
6859
|
-
if (!report.agent_mail.configured) {
|
|
6860
|
-
info("AgentMail not configured \u2014 attempting to bootstrap via console.agentmail.to...");
|
|
6861
|
-
try {
|
|
6862
|
-
const { bootstrapAgentMailKey: bootstrapAgentMailKey2 } = await init_bootstrap_agentmail().then(() => exports_bootstrap_agentmail);
|
|
6863
|
-
const result = await bootstrapAgentMailKey2();
|
|
6864
|
-
if (result.success) {
|
|
6865
|
-
info(`AgentMail API key obtained (${result.method}) \u2014 autonomous email login enabled`);
|
|
6866
|
-
report.agent_mail.configured = true;
|
|
6867
|
-
} else {
|
|
6868
|
-
info(`AgentMail bootstrap: ${result.error}`);
|
|
6869
|
-
}
|
|
6870
|
-
} catch (err) {
|
|
6871
|
-
info(`AgentMail bootstrap failed: ${err instanceof Error ? err.message : err}`);
|
|
6872
|
-
}
|
|
6873
|
-
}
|
|
6874
5189
|
const hasGcloud = (() => {
|
|
6875
5190
|
try {
|
|
6876
5191
|
const { existsSync: existsSync19 } = __require("fs");
|
|
@@ -6881,16 +5196,8 @@ async function cmdSetup(flags) {
|
|
|
6881
5196
|
return false;
|
|
6882
5197
|
}
|
|
6883
5198
|
})();
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
emailProviders.push("Gmail (via GWS)");
|
|
6887
|
-
if (report.agent_mail.configured)
|
|
6888
|
-
emailProviders.push("AgentMail");
|
|
6889
|
-
if (emailProviders.length > 0) {
|
|
6890
|
-
info(`Email providers: ${emailProviders.join(", ")} \u2014 autonomous login enabled`);
|
|
6891
|
-
} else {
|
|
6892
|
-
info("No email provider configured \u2014 agents can't auto-register on gated sites.");
|
|
6893
|
-
info("Options: export AGENTMAIL_API_KEY=<key> (https://agentmail.to) or gcloud auth login (Gmail)");
|
|
5199
|
+
if (hasGcloud) {
|
|
5200
|
+
info("Email provider: Gmail (via GWS) \u2014 autonomous login enabled");
|
|
6894
5201
|
}
|
|
6895
5202
|
await recordInstallTelemetryEvent("setup", {
|
|
6896
5203
|
hostType,
|
|
@@ -7627,77 +5934,6 @@ async function cmdSync(flags) {
|
|
|
7627
5934
|
async function cmdClose(flags) {
|
|
7628
5935
|
output(await api2("POST", "/v1/browse/close", typeof flags.session === "string" ? { session_id: flags.session } : undefined), false);
|
|
7629
5936
|
}
|
|
7630
|
-
async function cmdLoginAuto(args, flags) {
|
|
7631
|
-
const urlOrDomain = args[0] ?? flags.url ?? flags.domain;
|
|
7632
|
-
if (!urlOrDomain)
|
|
7633
|
-
return die("usage: unbrowse login-auto <domain-or-url> [--wait-otp | --wait-link | --send-to <email>]");
|
|
7634
|
-
const domain = (() => {
|
|
7635
|
-
try {
|
|
7636
|
-
return new URL(urlOrDomain.startsWith("http") ? urlOrDomain : `https://${urlOrDomain}`).hostname.replace(/^www\./, "");
|
|
7637
|
-
} catch {
|
|
7638
|
-
return urlOrDomain;
|
|
7639
|
-
}
|
|
7640
|
-
})();
|
|
7641
|
-
const { autonomousEmailLogin: autonomousEmailLogin3, isAgentMailAvailable: isAgentMailAvailable3 } = await init_agent_mail2().then(() => exports_agent_mail2);
|
|
7642
|
-
if (!isAgentMailAvailable3()) {
|
|
7643
|
-
info(`[login-auto] AGENTMAIL_API_KEY not set \u2014 attempting auto-bootstrap via console.agentmail.to`);
|
|
7644
|
-
try {
|
|
7645
|
-
const { bootstrapAgentMailKey: bootstrapAgentMailKey2 } = await init_bootstrap_agentmail().then(() => exports_bootstrap_agentmail);
|
|
7646
|
-
const bootstrap = await bootstrapAgentMailKey2();
|
|
7647
|
-
if (bootstrap.success && bootstrap.api_key) {
|
|
7648
|
-
process.env.AGENTMAIL_API_KEY = bootstrap.api_key;
|
|
7649
|
-
info(`[login-auto] bootstrapped AgentMail key via ${bootstrap.method ?? "browser"}`);
|
|
7650
|
-
} else {
|
|
7651
|
-
return die(`AGENTMAIL_API_KEY not set and bootstrap failed: ${bootstrap.error ?? "unknown"}. Get a key at https://agentmail.to and run: export AGENTMAIL_API_KEY=<key>`);
|
|
7652
|
-
}
|
|
7653
|
-
} catch (err) {
|
|
7654
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
7655
|
-
return die(`AGENTMAIL_API_KEY not set and bootstrap errored: ${msg}. Get a key at https://agentmail.to and run: export AGENTMAIL_API_KEY=<key>`);
|
|
7656
|
-
}
|
|
7657
|
-
}
|
|
7658
|
-
info(`[login-auto] creating agent email for ${domain}...`);
|
|
7659
|
-
const session = await autonomousEmailLogin3(domain);
|
|
7660
|
-
info(`[login-auto] email: ${session.email}`);
|
|
7661
|
-
const sendTo = flags["send-to"];
|
|
7662
|
-
if (sendTo) {
|
|
7663
|
-
const subject = flags.subject ?? `Verify ${domain}`;
|
|
7664
|
-
const body = flags.body ?? `This is an automated verification email from Unbrowse agent for ${domain}.`;
|
|
7665
|
-
info(`[login-auto] sending email to ${sendTo}...`);
|
|
7666
|
-
const result = await session.sendEmail(sendTo, subject, body);
|
|
7667
|
-
output({ email: session.email, sent_to: sendTo, message_id: result.messageId, domain });
|
|
7668
|
-
return;
|
|
7669
|
-
}
|
|
7670
|
-
if (flags["wait-otp"]) {
|
|
7671
|
-
const timeout = Number(flags.timeout) || 90000;
|
|
7672
|
-
info(`[login-auto] waiting for OTP from ${domain} (${Math.round(timeout / 1000)}s timeout)...`);
|
|
7673
|
-
const otp = await session.waitForOtp(timeout);
|
|
7674
|
-
if (otp) {
|
|
7675
|
-
info(`[login-auto] OTP: ${otp}`);
|
|
7676
|
-
output({ email: session.email, otp, domain });
|
|
7677
|
-
} else {
|
|
7678
|
-
info(`[login-auto] no OTP received`);
|
|
7679
|
-
output({ email: session.email, otp: null, domain, error: "timeout" });
|
|
7680
|
-
}
|
|
7681
|
-
return;
|
|
7682
|
-
}
|
|
7683
|
-
if (flags["wait-link"]) {
|
|
7684
|
-
const timeout = Number(flags.timeout) || 90000;
|
|
7685
|
-
info(`[login-auto] waiting for verification link from ${domain} (${Math.round(timeout / 1000)}s timeout)...`);
|
|
7686
|
-
const link = await session.waitForLink(timeout);
|
|
7687
|
-
if (link) {
|
|
7688
|
-
info(`[login-auto] link: ${link}`);
|
|
7689
|
-
output({ email: session.email, link, domain });
|
|
7690
|
-
} else {
|
|
7691
|
-
info(`[login-auto] no verification link received`);
|
|
7692
|
-
output({ email: session.email, link: null, domain, error: "timeout" });
|
|
7693
|
-
}
|
|
7694
|
-
return;
|
|
7695
|
-
}
|
|
7696
|
-
info(`[login-auto] use this email to register/login on ${domain}`);
|
|
7697
|
-
info(`[login-auto] then: unbrowse login-auto ${domain} --wait-otp`);
|
|
7698
|
-
info(`[login-auto] or: unbrowse login-auto ${domain} --wait-link`);
|
|
7699
|
-
output({ email: session.email, inbox_id: session.inboxId, domain });
|
|
7700
|
-
}
|
|
7701
5937
|
async function cmdSessionsScan(flags) {
|
|
7702
5938
|
const domain = flags.domain;
|
|
7703
5939
|
const { scanAllBrowserSessions: scanAllBrowserSessions3, findBestBrowserSession: findBestBrowserSession3 } = await Promise.resolve().then(() => (init_browser_cookies2(), exports_browser_cookies2));
|
|
@@ -7997,8 +6233,6 @@ async function main() {
|
|
|
7997
6233
|
return cmdEarnings(flags);
|
|
7998
6234
|
if (command === "sessions-scan")
|
|
7999
6235
|
return cmdSessionsScan(flags);
|
|
8000
|
-
if (command === "login-auto")
|
|
8001
|
-
return cmdLoginAuto(args, flags);
|
|
8002
6236
|
const KNOWN_COMMANDS = new Set([
|
|
8003
6237
|
"health",
|
|
8004
6238
|
"mcp",
|
|
@@ -8050,8 +6284,7 @@ async function main() {
|
|
|
8050
6284
|
"corpus-test",
|
|
8051
6285
|
"corpus-run",
|
|
8052
6286
|
"sessions-scan",
|
|
8053
|
-
"cache-clear"
|
|
8054
|
-
"login-auto"
|
|
6287
|
+
"cache-clear"
|
|
8055
6288
|
]);
|
|
8056
6289
|
if (!KNOWN_COMMANDS.has(command)) {
|
|
8057
6290
|
const pack = findSitePack(command);
|
|
@@ -8161,8 +6394,6 @@ async function main() {
|
|
|
8161
6394
|
return cmdCorpusRun(flags);
|
|
8162
6395
|
case "sessions-scan":
|
|
8163
6396
|
return cmdSessionsScan(flags);
|
|
8164
|
-
case "login-auto":
|
|
8165
|
-
return cmdLoginAuto(args, flags);
|
|
8166
6397
|
default:
|
|
8167
6398
|
info(`Unknown command: ${command}`);
|
|
8168
6399
|
printHelp();
|