perplexity-user-mcp 0.8.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/dist/attachments.d.ts +20 -0
- package/dist/attachments.mjs +43 -0
- package/dist/checks/browser.d.ts +100 -0
- package/dist/checks/browser.mjs +89 -0
- package/dist/checks/config.d.ts +91 -0
- package/dist/checks/config.mjs +88 -0
- package/dist/checks/ide.d.ts +89 -0
- package/dist/checks/ide.mjs +80 -0
- package/dist/checks/mcp.d.ts +61 -0
- package/dist/checks/mcp.mjs +56 -0
- package/dist/checks/native-deps.d.ts +131 -0
- package/dist/checks/native-deps.mjs +115 -0
- package/dist/checks/network.d.ts +71 -0
- package/dist/checks/network.mjs +70 -0
- package/dist/checks/probe.d.ts +93 -0
- package/dist/checks/probe.mjs +82 -0
- package/dist/checks/profiles.d.ts +99 -0
- package/dist/checks/profiles.mjs +90 -0
- package/dist/checks/runtime.d.ts +89 -0
- package/dist/checks/runtime.mjs +90 -0
- package/dist/checks/vault.d.ts +101 -0
- package/dist/checks/vault.mjs +90 -0
- package/dist/chunk-3B276PGG.mjs +115 -0
- package/dist/chunk-4UEJOM6W.mjs +9 -0
- package/dist/chunk-6EP2BLTV.mjs +205 -0
- package/dist/chunk-6YMQVLFX.mjs +146 -0
- package/dist/chunk-7JL36EBH.mjs +118 -0
- package/dist/chunk-DPGMKSSA.mjs +57 -0
- package/dist/chunk-H4BUAPPO.mjs +1950 -0
- package/dist/chunk-HMKLWVXB.mjs +109 -0
- package/dist/chunk-HTUAQRKH.mjs +125 -0
- package/dist/chunk-HU5B4FXS.mjs +139 -0
- package/dist/chunk-KCXM2M4B.mjs +1006 -0
- package/dist/chunk-LKJMLGFP.mjs +237 -0
- package/dist/chunk-LZPLNZ5U.mjs +67 -0
- package/dist/chunk-MTDFKNXX.mjs +19 -0
- package/dist/chunk-OF4DMAPJ.mjs +511 -0
- package/dist/chunk-PE23RMXY.mjs +43 -0
- package/dist/chunk-Q2VY4R5F.mjs +175 -0
- package/dist/chunk-S5VD7WTU.mjs +2540 -0
- package/dist/chunk-SVPRB62V.mjs +106 -0
- package/dist/chunk-TQLCLE4L.mjs +345 -0
- package/dist/chunk-U3DGFLXZ.mjs +43 -0
- package/dist/chunk-X45O6YD3.mjs +688 -0
- package/dist/chunk-XKSWCEGI.mjs +168 -0
- package/dist/chunk-Z7DAACGZ.mjs +534 -0
- package/dist/chunk-ZQFUZPLO.mjs +257 -0
- package/dist/cli.d.ts +952 -0
- package/dist/cli.mjs +827 -0
- package/dist/client.d.ts +355 -0
- package/dist/client.mjs +27 -0
- package/dist/cloud-sync.d-Cqt6y18U.d.ts +42 -0
- package/dist/cloud-sync.d.ts +42 -0
- package/dist/cloud-sync.mjs +17 -0
- package/dist/config.d.ts +186 -0
- package/dist/config.mjs +54 -0
- package/dist/daemon/attach.d.ts +36 -0
- package/dist/daemon/attach.mjs +25 -0
- package/dist/daemon/audit.d.ts +23 -0
- package/dist/daemon/audit.mjs +12 -0
- package/dist/daemon/client-http.d.ts +42 -0
- package/dist/daemon/client-http.mjs +29 -0
- package/dist/daemon/index.d.ts +14 -0
- package/dist/daemon/index.mjs +110 -0
- package/dist/daemon/install-tunnel.d.ts +46 -0
- package/dist/daemon/install-tunnel.mjs +14 -0
- package/dist/daemon/launcher.d.ts +163 -0
- package/dist/daemon/launcher.mjs +50 -0
- package/dist/daemon/lockfile.d.ts +29 -0
- package/dist/daemon/lockfile.mjs +18 -0
- package/dist/daemon/server.d.ts +159 -0
- package/dist/daemon/server.mjs +20 -0
- package/dist/daemon/token.d.ts +17 -0
- package/dist/daemon/token.mjs +17 -0
- package/dist/daemon/tunnel-providers/index.d.ts +330 -0
- package/dist/daemon/tunnel-providers/index.mjs +57 -0
- package/dist/daemon/tunnel.d.ts +23 -0
- package/dist/daemon/tunnel.mjs +9 -0
- package/dist/doctor-report.d.ts +24 -0
- package/dist/doctor-report.mjs +14 -0
- package/dist/doctor.d-CXmUqOXX.d.ts +43 -0
- package/dist/doctor.d.ts +44 -0
- package/dist/doctor.mjs +16 -0
- package/dist/export.d.ts +19 -0
- package/dist/export.mjs +15 -0
- package/dist/health-check.d.ts +108 -0
- package/dist/health-check.mjs +92 -0
- package/dist/history-store.d-BzjBF2m3.d.ts +65 -0
- package/dist/history-store.d.ts +65 -0
- package/dist/history-store.mjs +48 -0
- package/dist/impit-login-runner.d.ts +469 -0
- package/dist/impit-login-runner.mjs +685 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.mjs +236 -0
- package/dist/login-runner.d.ts +333 -0
- package/dist/login-runner.mjs +320 -0
- package/dist/logout.d.ts +28 -0
- package/dist/logout.mjs +45 -0
- package/dist/manual-login-runner.d.ts +150 -0
- package/dist/manual-login-runner.mjs +146 -0
- package/dist/native-deps-BNThFHxa.d.ts +175 -0
- package/dist/native-deps-YNKXITRY.mjs +139 -0
- package/dist/profiles.d-DqS1oZWr.d.ts +41 -0
- package/dist/profiles.d.ts +41 -0
- package/dist/profiles.mjs +33 -0
- package/dist/redact.d.ts +159 -0
- package/dist/redact.mjs +11 -0
- package/dist/refresh.d.ts +118 -0
- package/dist/refresh.mjs +21 -0
- package/dist/reinit-watcher.d.ts +15 -0
- package/dist/reinit-watcher.mjs +8 -0
- package/dist/session-metadata-B9aV_n5g.d.ts +148 -0
- package/dist/tty-prompt.d.ts +44 -0
- package/dist/tty-prompt.mjs +39 -0
- package/dist/vault.d-BtRSLZiM.d.ts +8 -0
- package/dist/vault.d.ts +37 -0
- package/dist/vault.mjs +21 -0
- package/dist/viewer-detect.d-HWGnyFAA.d.ts +4 -0
- package/dist/viewer-detect.d.ts +4 -0
- package/dist/viewer-detect.mjs +37 -0
- package/dist/viewers.d-BGCK6sw6.d.ts +10 -0
- package/dist/viewers.d.ts +18 -0
- package/dist/viewers.mjs +122 -0
- package/package.json +152 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { chromium } from 'patchright';
|
|
3
|
+
import { Vault } from './vault.d-BtRSLZiM.js';
|
|
4
|
+
import { resolveBrowserExecutable } from './config.js';
|
|
5
|
+
import { recordLoginSuccess, getActiveName, getProfilePaths } from './profiles.d-DqS1oZWr.js';
|
|
6
|
+
import { redact } from './redact.js';
|
|
7
|
+
import { c as collectSessionMetadata, b as buildRuntimeEndpoints, p as pageRequest } from './session-metadata-B9aV_n5g.js';
|
|
8
|
+
|
|
9
|
+
async function minimizePageWindow(page) {
|
|
10
|
+
try {
|
|
11
|
+
const context = page?.context?.();
|
|
12
|
+
if (!context || typeof context.newCDPSession !== "function") return false;
|
|
13
|
+
const session = await context.newCDPSession(page);
|
|
14
|
+
try {
|
|
15
|
+
const { windowId } = await session.send("Browser.getWindowForTarget");
|
|
16
|
+
await session.send("Browser.setWindowBounds", {
|
|
17
|
+
windowId,
|
|
18
|
+
bounds: { windowState: "minimized" },
|
|
19
|
+
});
|
|
20
|
+
return true;
|
|
21
|
+
} finally {
|
|
22
|
+
await session.detach().catch(() => {});
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Auto-OTP login runner. Parent provides email via PERPLEXITY_EMAIL; we
|
|
30
|
+
// drive the real Perplexity email+OTP flow (NextAuth on the live site,
|
|
31
|
+
// legacy /login/* on the local mock), prompt for the six-digit code via
|
|
32
|
+
// IPC, and persist the resulting session into the profile vault.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const ORIGIN = process.env.PERPLEXITY_ORIGIN || "https://www.perplexity.ai";
|
|
36
|
+
const LOGIN_PATH = process.env.PERPLEXITY_LOGIN_PATH || "/account";
|
|
37
|
+
const EMAIL = process.env.PERPLEXITY_EMAIL;
|
|
38
|
+
const OTP_TIMEOUT_MS = Number(process.env.PERPLEXITY_OTP_TIMEOUT_MS ?? 300_000);
|
|
39
|
+
const CF_TIMEOUT_MS = Number(process.env.PERPLEXITY_CF_TIMEOUT_MS ?? 20_000);
|
|
40
|
+
const MAX_RETRIES = 2;
|
|
41
|
+
|
|
42
|
+
function resolveProfile() {
|
|
43
|
+
return process.env.PERPLEXITY_PROFILE || getActiveName() || "default";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isLocalOrigin(origin) {
|
|
47
|
+
return /^https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?(?:\/|$)/i.test(origin);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ipc(msg) { if (process.send) process.send(msg); }
|
|
51
|
+
function emit(obj) { process.stdout.write(JSON.stringify(obj) + "\n"); }
|
|
52
|
+
|
|
53
|
+
async function awaitOtp() {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const timer = setTimeout(() => {
|
|
56
|
+
process.removeListener("message", handler);
|
|
57
|
+
reject(new Error("otp_timeout"));
|
|
58
|
+
}, OTP_TIMEOUT_MS);
|
|
59
|
+
const handler = (m) => {
|
|
60
|
+
if (m && typeof m.otp === "string") {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
process.removeListener("message", handler);
|
|
63
|
+
resolve(m.otp);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
process.on("message", handler);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function main() {
|
|
71
|
+
if (!EMAIL) {
|
|
72
|
+
emit({ ok: false, reason: "no_email" });
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const PROFILE = resolveProfile();
|
|
77
|
+
const localOrigin = isLocalOrigin(ORIGIN);
|
|
78
|
+
|
|
79
|
+
let executablePath;
|
|
80
|
+
let channel;
|
|
81
|
+
if (!localOrigin) {
|
|
82
|
+
try {
|
|
83
|
+
({ path: executablePath, channel } = await resolveBrowserExecutable());
|
|
84
|
+
} catch (err) {
|
|
85
|
+
emit({ ok: false, reason: "chrome_missing", error: redact(String(err?.message ?? err)) });
|
|
86
|
+
process.exit(4);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const browser = await chromium.launch({
|
|
91
|
+
headless: localOrigin,
|
|
92
|
+
...(executablePath ? { executablePath } : {}),
|
|
93
|
+
...(channel && ["chrome", "msedge", "chromium"].includes(channel) ? { channel } : {}),
|
|
94
|
+
args: localOrigin ? [] : ["--start-minimized"],
|
|
95
|
+
});
|
|
96
|
+
const ctx = await browser.newContext({ ignoreHTTPSErrors: true });
|
|
97
|
+
const page = await ctx.newPage();
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await page.goto(`${ORIGIN}${LOGIN_PATH}`, { waitUntil: "domcontentloaded", timeout: 30_000 });
|
|
101
|
+
} catch {}
|
|
102
|
+
if (!localOrigin) await minimizePageWindow(page);
|
|
103
|
+
|
|
104
|
+
const ready = await waitForLoginReady(page);
|
|
105
|
+
if (!ready) {
|
|
106
|
+
const title = await page.title().catch(() => "");
|
|
107
|
+
await browser.close().catch(() => {});
|
|
108
|
+
emit({ ok: false, reason: /just a moment/i.test(title) ? "cf_blocked" : "auto_unsupported" });
|
|
109
|
+
process.exit(/just a moment/i.test(title) ? 3 : 2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const liveAttempt = await startLiveEmailFlow(page);
|
|
113
|
+
let authFlow = liveAttempt;
|
|
114
|
+
if (liveAttempt.kind === "unsupported") {
|
|
115
|
+
authFlow = await startLegacyMockFlow(page);
|
|
116
|
+
}
|
|
117
|
+
if (!localOrigin) await minimizePageWindow(page);
|
|
118
|
+
|
|
119
|
+
if (authFlow.kind === "sso_required") {
|
|
120
|
+
await browser.close().catch(() => {});
|
|
121
|
+
emit({ ok: false, reason: "sso_required" });
|
|
122
|
+
process.exit(2);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (authFlow.kind === "unsupported") {
|
|
126
|
+
await browser.close().catch(() => {});
|
|
127
|
+
emit({ ok: false, reason: "auto_unsupported", detail: authFlow.detail });
|
|
128
|
+
process.exit(2);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (authFlow.kind === "email_rejected") {
|
|
132
|
+
await browser.close().catch(() => {});
|
|
133
|
+
emit({ ok: false, reason: "email_rejected", detail: authFlow.detail });
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
138
|
+
ipc({ phase: "awaiting_otp", attempt });
|
|
139
|
+
let otp;
|
|
140
|
+
try {
|
|
141
|
+
otp = await awaitOtp();
|
|
142
|
+
} catch {
|
|
143
|
+
await browser.close().catch(() => {});
|
|
144
|
+
emit({ ok: false, reason: "otp_timeout" });
|
|
145
|
+
process.exit(2);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const submitResp = await submitOtp(page, authFlow, otp);
|
|
149
|
+
if (submitResp.ok) {
|
|
150
|
+
const allCookies = await ctx.cookies();
|
|
151
|
+
const metadata = await collectSessionMetadata(page, ORIGIN, {
|
|
152
|
+
sessionData: submitResp.sessionData,
|
|
153
|
+
sessionTimeoutMs: 10_000,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const vault = new Vault();
|
|
157
|
+
await vault.set(PROFILE, "cookies", JSON.stringify(allCookies));
|
|
158
|
+
if (metadata.sessionData?.user?.email) await vault.set(PROFILE, "email", metadata.sessionData.user.email);
|
|
159
|
+
if (metadata.sessionData?.user?.id) await vault.set(PROFILE, "userId", metadata.sessionData.user.id);
|
|
160
|
+
|
|
161
|
+
const paths = getProfilePaths(PROFILE);
|
|
162
|
+
if (!existsSync(paths.dir)) mkdirSync(paths.dir, { recursive: true });
|
|
163
|
+
writeFileSync(paths.modelsCache, JSON.stringify(metadata.cache, null, 2));
|
|
164
|
+
recordLoginSuccess(PROFILE, { tier: metadata.tier, loginMode: "auto", lastLogin: new Date().toISOString() });
|
|
165
|
+
writeFileSync(paths.reinit, String(Date.now()));
|
|
166
|
+
|
|
167
|
+
await browser.close().catch(() => {});
|
|
168
|
+
emit({ ok: true, tier: metadata.tier, modelCount: Object.keys(metadata.models?.models ?? {}).length });
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (authFlow.kind === "live") {
|
|
173
|
+
await page.goto(authFlow.verifyUrl, { waitUntil: "domcontentloaded", timeout: 30_000 }).catch(() => {});
|
|
174
|
+
if (!localOrigin) await minimizePageWindow(page);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (attempt === MAX_RETRIES) {
|
|
178
|
+
await browser.close().catch(() => {});
|
|
179
|
+
emit({ ok: false, reason: "otp_rejected" });
|
|
180
|
+
process.exit(2);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function waitForLoginReady(page) {
|
|
186
|
+
const started = Date.now();
|
|
187
|
+
while (Date.now() - started < CF_TIMEOUT_MS) {
|
|
188
|
+
if (await page.locator('input[type="email"]').count().catch(() => 0)) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
const title = await page.title().catch(() => "");
|
|
192
|
+
if (title && !/just a moment/i.test(title)) {
|
|
193
|
+
const body = await page.locator("body").innerText().catch(() => "");
|
|
194
|
+
if (/continue with email/i.test(body) || /single sign-on/i.test(body) || /continue/i.test(body)) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
await page.waitForTimeout(500);
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function startLiveEmailFlow(page) {
|
|
204
|
+
const endpoints = buildRuntimeEndpoints(ORIGIN);
|
|
205
|
+
const csrf = await pageRequest(page, endpoints.csrf);
|
|
206
|
+
if (!(csrf.ok && csrf.contentType.includes("json") && csrf.json?.csrfToken)) {
|
|
207
|
+
return {
|
|
208
|
+
kind: "unsupported",
|
|
209
|
+
detail: { step: "csrf", status: csrf.status, contentType: csrf.contentType, error: csrf.error ?? undefined },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const sso = await pageRequest(page, endpoints.ssoDetails, {
|
|
214
|
+
method: "POST",
|
|
215
|
+
headers: { "content-type": "application/json" },
|
|
216
|
+
body: JSON.stringify({ email: EMAIL }),
|
|
217
|
+
});
|
|
218
|
+
if (sso.ok && sso.json?.organization) {
|
|
219
|
+
return { kind: "sso_required" };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const redirectUrl = `${ORIGIN}/account?login-source=settings`;
|
|
223
|
+
const signIn = await pageRequest(page, endpoints.signInEmail, {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: { "content-type": "application/json" },
|
|
226
|
+
body: JSON.stringify({
|
|
227
|
+
email: EMAIL,
|
|
228
|
+
useNumericOtp: "true",
|
|
229
|
+
csrfToken: csrf.json.csrfToken,
|
|
230
|
+
callbackUrl: `${redirectUrl}#locale=en-US`,
|
|
231
|
+
json: "true",
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (!(signIn.ok && signIn.contentType.includes("json") && signIn.json?.url)) {
|
|
236
|
+
return {
|
|
237
|
+
kind: signIn.status >= 400 && signIn.status < 500 ? "email_rejected" : "unsupported",
|
|
238
|
+
detail: { step: "signin_email", status: signIn.status, contentType: signIn.contentType, error: signIn.error ?? undefined },
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const verifyUrl = new URL(signIn.json.url, ORIGIN);
|
|
243
|
+
verifyUrl.searchParams.set("email", EMAIL);
|
|
244
|
+
verifyUrl.searchParams.set("redirectUrl", redirectUrl);
|
|
245
|
+
await page.goto(verifyUrl.toString(), { waitUntil: "domcontentloaded", timeout: 30_000 }).catch(() => {});
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
kind: "live",
|
|
249
|
+
redirectUrl,
|
|
250
|
+
verifyUrl: verifyUrl.toString(),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function startLegacyMockFlow(page) {
|
|
255
|
+
const emailResp = await pageRequest(page, `${ORIGIN}/login/email`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: { "content-type": "application/json" },
|
|
258
|
+
body: JSON.stringify({ email: EMAIL }),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (emailResp.redirected && (emailResp.url || "").includes("/sso")) {
|
|
262
|
+
return { kind: "sso_required" };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const looksUnsupported =
|
|
266
|
+
emailResp.status === 404 ||
|
|
267
|
+
emailResp.status === 405 ||
|
|
268
|
+
emailResp.status >= 500 ||
|
|
269
|
+
!emailResp.contentType.includes("json");
|
|
270
|
+
|
|
271
|
+
if (looksUnsupported) {
|
|
272
|
+
return {
|
|
273
|
+
kind: "unsupported",
|
|
274
|
+
detail: { step: "legacy_email", status: emailResp.status, contentType: emailResp.contentType, error: emailResp.error ?? undefined },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!emailResp.ok) {
|
|
279
|
+
return { kind: "email_rejected", detail: emailResp.status };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { kind: "legacy" };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function submitOtp(page, flow, otp) {
|
|
286
|
+
if (flow.kind === "legacy") {
|
|
287
|
+
const submitResp = await pageRequest(page, `${ORIGIN}/login/otp`, {
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: { "content-type": "application/json" },
|
|
290
|
+
body: JSON.stringify({ email: EMAIL, otp }),
|
|
291
|
+
});
|
|
292
|
+
if (submitResp.status !== 200) {
|
|
293
|
+
return { ok: false };
|
|
294
|
+
}
|
|
295
|
+
const metadata = await collectSessionMetadata(page, ORIGIN, { sessionTimeoutMs: 2_000 });
|
|
296
|
+
return { ok: !!metadata.sessionData?.user?.id, sessionData: metadata.sessionData };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const endpoints = buildRuntimeEndpoints(ORIGIN);
|
|
300
|
+
const redirectResp = await pageRequest(page, endpoints.otpRedirectLink, {
|
|
301
|
+
method: "POST",
|
|
302
|
+
headers: { "content-type": "application/json" },
|
|
303
|
+
body: JSON.stringify({
|
|
304
|
+
email: EMAIL,
|
|
305
|
+
otp,
|
|
306
|
+
redirectUrl: flow.redirectUrl,
|
|
307
|
+
emailLoginMethod: "web-otp",
|
|
308
|
+
loginSource: null,
|
|
309
|
+
}),
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
if (!(redirectResp.ok && redirectResp.contentType.includes("json") && redirectResp.json?.redirect)) {
|
|
313
|
+
return { ok: false };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const callbackUrl = new URL(redirectResp.json.redirect, ORIGIN).toString();
|
|
317
|
+
await page.goto(callbackUrl, { waitUntil: "domcontentloaded", timeout: 30_000 }).catch(() => {});
|
|
318
|
+
const metadata = await collectSessionMetadata(page, ORIGIN, { sessionTimeoutMs: 5_000 });
|
|
319
|
+
return { ok: !!metadata.sessionData?.user?.id, sessionData: metadata.sessionData };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
main().catch((err) => {
|
|
323
|
+
const msg = err?.message ?? err;
|
|
324
|
+
const stack = err?.stack;
|
|
325
|
+
emit({
|
|
326
|
+
ok: false,
|
|
327
|
+
reason: "crash",
|
|
328
|
+
error: redact(String(msg ?? "unknown error")),
|
|
329
|
+
detail: redact(String(msg ?? "unknown error")),
|
|
330
|
+
...(stack ? { stack: redact(String(stack)) } : {}),
|
|
331
|
+
});
|
|
332
|
+
process.exit(5);
|
|
333
|
+
});
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildRuntimeEndpoints,
|
|
3
|
+
collectSessionMetadata,
|
|
4
|
+
pageRequest
|
|
5
|
+
} from "./chunk-HU5B4FXS.mjs";
|
|
6
|
+
import {
|
|
7
|
+
redact
|
|
8
|
+
} from "./chunk-HMKLWVXB.mjs";
|
|
9
|
+
import {
|
|
10
|
+
resolveBrowserExecutable
|
|
11
|
+
} from "./chunk-LKJMLGFP.mjs";
|
|
12
|
+
import {
|
|
13
|
+
Vault
|
|
14
|
+
} from "./chunk-TQLCLE4L.mjs";
|
|
15
|
+
import "./chunk-MTDFKNXX.mjs";
|
|
16
|
+
import {
|
|
17
|
+
getActiveName,
|
|
18
|
+
getProfilePaths,
|
|
19
|
+
recordLoginSuccess
|
|
20
|
+
} from "./chunk-XKSWCEGI.mjs";
|
|
21
|
+
import "./chunk-4UEJOM6W.mjs";
|
|
22
|
+
|
|
23
|
+
// src/login-runner.js
|
|
24
|
+
import { writeFileSync, existsSync, mkdirSync } from "fs";
|
|
25
|
+
import { chromium } from "patchright";
|
|
26
|
+
|
|
27
|
+
// src/browser-window.js
|
|
28
|
+
async function minimizePageWindow(page) {
|
|
29
|
+
try {
|
|
30
|
+
const context = page?.context?.();
|
|
31
|
+
if (!context || typeof context.newCDPSession !== "function") return false;
|
|
32
|
+
const session = await context.newCDPSession(page);
|
|
33
|
+
try {
|
|
34
|
+
const { windowId } = await session.send("Browser.getWindowForTarget");
|
|
35
|
+
await session.send("Browser.setWindowBounds", {
|
|
36
|
+
windowId,
|
|
37
|
+
bounds: { windowState: "minimized" }
|
|
38
|
+
});
|
|
39
|
+
return true;
|
|
40
|
+
} finally {
|
|
41
|
+
await session.detach().catch(() => {
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/login-runner.js
|
|
50
|
+
var ORIGIN = process.env.PERPLEXITY_ORIGIN || "https://www.perplexity.ai";
|
|
51
|
+
var LOGIN_PATH = process.env.PERPLEXITY_LOGIN_PATH || "/account";
|
|
52
|
+
var EMAIL = process.env.PERPLEXITY_EMAIL;
|
|
53
|
+
var OTP_TIMEOUT_MS = Number(process.env.PERPLEXITY_OTP_TIMEOUT_MS ?? 3e5);
|
|
54
|
+
var CF_TIMEOUT_MS = Number(process.env.PERPLEXITY_CF_TIMEOUT_MS ?? 2e4);
|
|
55
|
+
var MAX_RETRIES = 2;
|
|
56
|
+
function resolveProfile() {
|
|
57
|
+
return process.env.PERPLEXITY_PROFILE || getActiveName() || "default";
|
|
58
|
+
}
|
|
59
|
+
function isLocalOrigin(origin) {
|
|
60
|
+
return /^https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?(?:\/|$)/i.test(origin);
|
|
61
|
+
}
|
|
62
|
+
function ipc(msg) {
|
|
63
|
+
if (process.send) process.send(msg);
|
|
64
|
+
}
|
|
65
|
+
function emit(obj) {
|
|
66
|
+
process.stdout.write(JSON.stringify(obj) + "\n");
|
|
67
|
+
}
|
|
68
|
+
async function awaitOtp() {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const timer = setTimeout(() => {
|
|
71
|
+
process.removeListener("message", handler);
|
|
72
|
+
reject(new Error("otp_timeout"));
|
|
73
|
+
}, OTP_TIMEOUT_MS);
|
|
74
|
+
const handler = (m) => {
|
|
75
|
+
if (m && typeof m.otp === "string") {
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
process.removeListener("message", handler);
|
|
78
|
+
resolve(m.otp);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
process.on("message", handler);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function main() {
|
|
85
|
+
if (!EMAIL) {
|
|
86
|
+
emit({ ok: false, reason: "no_email" });
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const PROFILE = resolveProfile();
|
|
90
|
+
const localOrigin = isLocalOrigin(ORIGIN);
|
|
91
|
+
let executablePath;
|
|
92
|
+
let channel;
|
|
93
|
+
if (!localOrigin) {
|
|
94
|
+
try {
|
|
95
|
+
({ path: executablePath, channel } = await resolveBrowserExecutable());
|
|
96
|
+
} catch (err) {
|
|
97
|
+
emit({ ok: false, reason: "chrome_missing", error: redact(String(err?.message ?? err)) });
|
|
98
|
+
process.exit(4);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const browser = await chromium.launch({
|
|
102
|
+
headless: localOrigin,
|
|
103
|
+
...executablePath ? { executablePath } : {},
|
|
104
|
+
...channel && ["chrome", "msedge", "chromium"].includes(channel) ? { channel } : {},
|
|
105
|
+
args: localOrigin ? [] : ["--start-minimized"]
|
|
106
|
+
});
|
|
107
|
+
const ctx = await browser.newContext({ ignoreHTTPSErrors: true });
|
|
108
|
+
const page = await ctx.newPage();
|
|
109
|
+
try {
|
|
110
|
+
await page.goto(`${ORIGIN}${LOGIN_PATH}`, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
if (!localOrigin) await minimizePageWindow(page);
|
|
114
|
+
const ready = await waitForLoginReady(page);
|
|
115
|
+
if (!ready) {
|
|
116
|
+
const title = await page.title().catch(() => "");
|
|
117
|
+
await browser.close().catch(() => {
|
|
118
|
+
});
|
|
119
|
+
emit({ ok: false, reason: /just a moment/i.test(title) ? "cf_blocked" : "auto_unsupported" });
|
|
120
|
+
process.exit(/just a moment/i.test(title) ? 3 : 2);
|
|
121
|
+
}
|
|
122
|
+
const liveAttempt = await startLiveEmailFlow(page);
|
|
123
|
+
let authFlow = liveAttempt;
|
|
124
|
+
if (liveAttempt.kind === "unsupported") {
|
|
125
|
+
authFlow = await startLegacyMockFlow(page);
|
|
126
|
+
}
|
|
127
|
+
if (!localOrigin) await minimizePageWindow(page);
|
|
128
|
+
if (authFlow.kind === "sso_required") {
|
|
129
|
+
await browser.close().catch(() => {
|
|
130
|
+
});
|
|
131
|
+
emit({ ok: false, reason: "sso_required" });
|
|
132
|
+
process.exit(2);
|
|
133
|
+
}
|
|
134
|
+
if (authFlow.kind === "unsupported") {
|
|
135
|
+
await browser.close().catch(() => {
|
|
136
|
+
});
|
|
137
|
+
emit({ ok: false, reason: "auto_unsupported", detail: authFlow.detail });
|
|
138
|
+
process.exit(2);
|
|
139
|
+
}
|
|
140
|
+
if (authFlow.kind === "email_rejected") {
|
|
141
|
+
await browser.close().catch(() => {
|
|
142
|
+
});
|
|
143
|
+
emit({ ok: false, reason: "email_rejected", detail: authFlow.detail });
|
|
144
|
+
process.exit(2);
|
|
145
|
+
}
|
|
146
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
147
|
+
ipc({ phase: "awaiting_otp", attempt });
|
|
148
|
+
let otp;
|
|
149
|
+
try {
|
|
150
|
+
otp = await awaitOtp();
|
|
151
|
+
} catch {
|
|
152
|
+
await browser.close().catch(() => {
|
|
153
|
+
});
|
|
154
|
+
emit({ ok: false, reason: "otp_timeout" });
|
|
155
|
+
process.exit(2);
|
|
156
|
+
}
|
|
157
|
+
const submitResp = await submitOtp(page, authFlow, otp);
|
|
158
|
+
if (submitResp.ok) {
|
|
159
|
+
const allCookies = await ctx.cookies();
|
|
160
|
+
const metadata = await collectSessionMetadata(page, ORIGIN, {
|
|
161
|
+
sessionData: submitResp.sessionData,
|
|
162
|
+
sessionTimeoutMs: 1e4
|
|
163
|
+
});
|
|
164
|
+
const vault = new Vault();
|
|
165
|
+
await vault.set(PROFILE, "cookies", JSON.stringify(allCookies));
|
|
166
|
+
if (metadata.sessionData?.user?.email) await vault.set(PROFILE, "email", metadata.sessionData.user.email);
|
|
167
|
+
if (metadata.sessionData?.user?.id) await vault.set(PROFILE, "userId", metadata.sessionData.user.id);
|
|
168
|
+
const paths = getProfilePaths(PROFILE);
|
|
169
|
+
if (!existsSync(paths.dir)) mkdirSync(paths.dir, { recursive: true });
|
|
170
|
+
writeFileSync(paths.modelsCache, JSON.stringify(metadata.cache, null, 2));
|
|
171
|
+
recordLoginSuccess(PROFILE, { tier: metadata.tier, loginMode: "auto", lastLogin: (/* @__PURE__ */ new Date()).toISOString() });
|
|
172
|
+
writeFileSync(paths.reinit, String(Date.now()));
|
|
173
|
+
await browser.close().catch(() => {
|
|
174
|
+
});
|
|
175
|
+
emit({ ok: true, tier: metadata.tier, modelCount: Object.keys(metadata.models?.models ?? {}).length });
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
if (authFlow.kind === "live") {
|
|
179
|
+
await page.goto(authFlow.verifyUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
180
|
+
});
|
|
181
|
+
if (!localOrigin) await minimizePageWindow(page);
|
|
182
|
+
}
|
|
183
|
+
if (attempt === MAX_RETRIES) {
|
|
184
|
+
await browser.close().catch(() => {
|
|
185
|
+
});
|
|
186
|
+
emit({ ok: false, reason: "otp_rejected" });
|
|
187
|
+
process.exit(2);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function waitForLoginReady(page) {
|
|
192
|
+
const started = Date.now();
|
|
193
|
+
while (Date.now() - started < CF_TIMEOUT_MS) {
|
|
194
|
+
if (await page.locator('input[type="email"]').count().catch(() => 0)) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
const title = await page.title().catch(() => "");
|
|
198
|
+
if (title && !/just a moment/i.test(title)) {
|
|
199
|
+
const body = await page.locator("body").innerText().catch(() => "");
|
|
200
|
+
if (/continue with email/i.test(body) || /single sign-on/i.test(body) || /continue/i.test(body)) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
await page.waitForTimeout(500);
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
async function startLiveEmailFlow(page) {
|
|
209
|
+
const endpoints = buildRuntimeEndpoints(ORIGIN);
|
|
210
|
+
const csrf = await pageRequest(page, endpoints.csrf);
|
|
211
|
+
if (!(csrf.ok && csrf.contentType.includes("json") && csrf.json?.csrfToken)) {
|
|
212
|
+
return {
|
|
213
|
+
kind: "unsupported",
|
|
214
|
+
detail: { step: "csrf", status: csrf.status, contentType: csrf.contentType, error: csrf.error ?? void 0 }
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const sso = await pageRequest(page, endpoints.ssoDetails, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: { "content-type": "application/json" },
|
|
220
|
+
body: JSON.stringify({ email: EMAIL })
|
|
221
|
+
});
|
|
222
|
+
if (sso.ok && sso.json?.organization) {
|
|
223
|
+
return { kind: "sso_required" };
|
|
224
|
+
}
|
|
225
|
+
const redirectUrl = `${ORIGIN}/account?login-source=settings`;
|
|
226
|
+
const signIn = await pageRequest(page, endpoints.signInEmail, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: { "content-type": "application/json" },
|
|
229
|
+
body: JSON.stringify({
|
|
230
|
+
email: EMAIL,
|
|
231
|
+
useNumericOtp: "true",
|
|
232
|
+
csrfToken: csrf.json.csrfToken,
|
|
233
|
+
callbackUrl: `${redirectUrl}#locale=en-US`,
|
|
234
|
+
json: "true"
|
|
235
|
+
})
|
|
236
|
+
});
|
|
237
|
+
if (!(signIn.ok && signIn.contentType.includes("json") && signIn.json?.url)) {
|
|
238
|
+
return {
|
|
239
|
+
kind: signIn.status >= 400 && signIn.status < 500 ? "email_rejected" : "unsupported",
|
|
240
|
+
detail: { step: "signin_email", status: signIn.status, contentType: signIn.contentType, error: signIn.error ?? void 0 }
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const verifyUrl = new URL(signIn.json.url, ORIGIN);
|
|
244
|
+
verifyUrl.searchParams.set("email", EMAIL);
|
|
245
|
+
verifyUrl.searchParams.set("redirectUrl", redirectUrl);
|
|
246
|
+
await page.goto(verifyUrl.toString(), { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
247
|
+
});
|
|
248
|
+
return {
|
|
249
|
+
kind: "live",
|
|
250
|
+
redirectUrl,
|
|
251
|
+
verifyUrl: verifyUrl.toString()
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
async function startLegacyMockFlow(page) {
|
|
255
|
+
const emailResp = await pageRequest(page, `${ORIGIN}/login/email`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: { "content-type": "application/json" },
|
|
258
|
+
body: JSON.stringify({ email: EMAIL })
|
|
259
|
+
});
|
|
260
|
+
if (emailResp.redirected && (emailResp.url || "").includes("/sso")) {
|
|
261
|
+
return { kind: "sso_required" };
|
|
262
|
+
}
|
|
263
|
+
const looksUnsupported = emailResp.status === 404 || emailResp.status === 405 || emailResp.status >= 500 || !emailResp.contentType.includes("json");
|
|
264
|
+
if (looksUnsupported) {
|
|
265
|
+
return {
|
|
266
|
+
kind: "unsupported",
|
|
267
|
+
detail: { step: "legacy_email", status: emailResp.status, contentType: emailResp.contentType, error: emailResp.error ?? void 0 }
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (!emailResp.ok) {
|
|
271
|
+
return { kind: "email_rejected", detail: emailResp.status };
|
|
272
|
+
}
|
|
273
|
+
return { kind: "legacy" };
|
|
274
|
+
}
|
|
275
|
+
async function submitOtp(page, flow, otp) {
|
|
276
|
+
if (flow.kind === "legacy") {
|
|
277
|
+
const submitResp = await pageRequest(page, `${ORIGIN}/login/otp`, {
|
|
278
|
+
method: "POST",
|
|
279
|
+
headers: { "content-type": "application/json" },
|
|
280
|
+
body: JSON.stringify({ email: EMAIL, otp })
|
|
281
|
+
});
|
|
282
|
+
if (submitResp.status !== 200) {
|
|
283
|
+
return { ok: false };
|
|
284
|
+
}
|
|
285
|
+
const metadata2 = await collectSessionMetadata(page, ORIGIN, { sessionTimeoutMs: 2e3 });
|
|
286
|
+
return { ok: !!metadata2.sessionData?.user?.id, sessionData: metadata2.sessionData };
|
|
287
|
+
}
|
|
288
|
+
const endpoints = buildRuntimeEndpoints(ORIGIN);
|
|
289
|
+
const redirectResp = await pageRequest(page, endpoints.otpRedirectLink, {
|
|
290
|
+
method: "POST",
|
|
291
|
+
headers: { "content-type": "application/json" },
|
|
292
|
+
body: JSON.stringify({
|
|
293
|
+
email: EMAIL,
|
|
294
|
+
otp,
|
|
295
|
+
redirectUrl: flow.redirectUrl,
|
|
296
|
+
emailLoginMethod: "web-otp",
|
|
297
|
+
loginSource: null
|
|
298
|
+
})
|
|
299
|
+
});
|
|
300
|
+
if (!(redirectResp.ok && redirectResp.contentType.includes("json") && redirectResp.json?.redirect)) {
|
|
301
|
+
return { ok: false };
|
|
302
|
+
}
|
|
303
|
+
const callbackUrl = new URL(redirectResp.json.redirect, ORIGIN).toString();
|
|
304
|
+
await page.goto(callbackUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
305
|
+
});
|
|
306
|
+
const metadata = await collectSessionMetadata(page, ORIGIN, { sessionTimeoutMs: 5e3 });
|
|
307
|
+
return { ok: !!metadata.sessionData?.user?.id, sessionData: metadata.sessionData };
|
|
308
|
+
}
|
|
309
|
+
main().catch((err) => {
|
|
310
|
+
const msg = err?.message ?? err;
|
|
311
|
+
const stack = err?.stack;
|
|
312
|
+
emit({
|
|
313
|
+
ok: false,
|
|
314
|
+
reason: "crash",
|
|
315
|
+
error: redact(String(msg ?? "unknown error")),
|
|
316
|
+
detail: redact(String(msg ?? "unknown error")),
|
|
317
|
+
...stack ? { stack: redact(String(stack)) } : {}
|
|
318
|
+
});
|
|
319
|
+
process.exit(5);
|
|
320
|
+
});
|
package/dist/logout.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { Vault } from './vault.d-BtRSLZiM.js';
|
|
3
|
+
import { getActiveName, setActive, getProfilePaths, listProfiles, getProfile } from './profiles.d-DqS1oZWr.js';
|
|
4
|
+
|
|
5
|
+
async function softLogout(name) {
|
|
6
|
+
const vault = new Vault();
|
|
7
|
+
await vault.delete(name, "cookies").catch(() => {});
|
|
8
|
+
const paths = getProfilePaths(name);
|
|
9
|
+
const meta = getProfile(name);
|
|
10
|
+
if (meta) {
|
|
11
|
+
delete meta.lastLogin;
|
|
12
|
+
writeFileSync(paths.meta, JSON.stringify(meta, null, 2) + "\n");
|
|
13
|
+
}
|
|
14
|
+
if (existsSync(paths.dir)) writeFileSync(paths.reinit, String(Date.now()));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function hardLogout(name) {
|
|
18
|
+
const paths = getProfilePaths(name);
|
|
19
|
+
if (existsSync(paths.dir)) rmSync(paths.dir, { recursive: true, force: true });
|
|
20
|
+
if (getActiveName() === name) {
|
|
21
|
+
const remaining = listProfiles();
|
|
22
|
+
if (remaining.length > 0) {
|
|
23
|
+
setActive(remaining[0].name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { hardLogout, softLogout };
|