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
package/dist/logout.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Vault
|
|
3
|
+
} from "./chunk-TQLCLE4L.mjs";
|
|
4
|
+
import "./chunk-MTDFKNXX.mjs";
|
|
5
|
+
import {
|
|
6
|
+
createProfile,
|
|
7
|
+
getActiveName,
|
|
8
|
+
getProfile,
|
|
9
|
+
getProfilePaths,
|
|
10
|
+
listProfiles,
|
|
11
|
+
setActive
|
|
12
|
+
} from "./chunk-XKSWCEGI.mjs";
|
|
13
|
+
import "./chunk-4UEJOM6W.mjs";
|
|
14
|
+
|
|
15
|
+
// src/logout.js
|
|
16
|
+
import { existsSync, writeFileSync, rmSync } from "fs";
|
|
17
|
+
async function softLogout(name) {
|
|
18
|
+
const vault = new Vault();
|
|
19
|
+
await vault.delete(name, "cookies").catch(() => {
|
|
20
|
+
});
|
|
21
|
+
const paths = getProfilePaths(name);
|
|
22
|
+
const meta = getProfile(name);
|
|
23
|
+
if (meta) {
|
|
24
|
+
delete meta.lastLogin;
|
|
25
|
+
writeFileSync(paths.meta, JSON.stringify(meta, null, 2) + "\n");
|
|
26
|
+
}
|
|
27
|
+
if (existsSync(paths.dir)) writeFileSync(paths.reinit, String(Date.now()));
|
|
28
|
+
}
|
|
29
|
+
async function hardLogout(name) {
|
|
30
|
+
const paths = getProfilePaths(name);
|
|
31
|
+
if (existsSync(paths.dir)) rmSync(paths.dir, { recursive: true, force: true });
|
|
32
|
+
if (getActiveName() === name) {
|
|
33
|
+
const remaining = listProfiles();
|
|
34
|
+
if (remaining.length > 0) {
|
|
35
|
+
setActive(remaining[0].name);
|
|
36
|
+
} else {
|
|
37
|
+
createProfile("default");
|
|
38
|
+
setActive("default");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
hardLogout,
|
|
44
|
+
softLogout
|
|
45
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
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 } from './session-metadata-B9aV_n5g.js';
|
|
8
|
+
|
|
9
|
+
// Headed login runner. User drives the browser; we poll cookies until the
|
|
10
|
+
// session token appears, then write the vault + meta + models-cache +
|
|
11
|
+
// .reinit sentinel, and exit.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const ORIGIN = process.env.PERPLEXITY_ORIGIN || "https://www.perplexity.ai";
|
|
15
|
+
// Perplexity's login flow lives at /account (the `/login` path doesn't exist
|
|
16
|
+
// on www.perplexity.ai). Integration tests override via env var to point at
|
|
17
|
+
// the mock server's /login route.
|
|
18
|
+
const LOGIN_PATH = process.env.PERPLEXITY_LOGIN_PATH || "/account";
|
|
19
|
+
const POLL_MS = Number(process.env.PERPLEXITY_POLL_MS ?? 2000);
|
|
20
|
+
const MAX_WAIT_MS = 180_000;
|
|
21
|
+
const CF_TIMEOUT_MS = Number(process.env.PERPLEXITY_CF_TIMEOUT_MS ?? 20_000);
|
|
22
|
+
|
|
23
|
+
function resolveProfile() {
|
|
24
|
+
return process.env.PERPLEXITY_PROFILE || getActiveName() || "default";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const isTest = !!process.env.PERPLEXITY_TEST_AUTO_LOGIN_EMAIL || !!process.env.PERPLEXITY_TEST_BROWSER_CLOSE_AFTER_MS;
|
|
28
|
+
|
|
29
|
+
function ipc(msg) { if (process.send) process.send(msg); }
|
|
30
|
+
function emit(obj) { process.stdout.write(JSON.stringify(obj) + "\n"); }
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
const PROFILE = resolveProfile();
|
|
34
|
+
|
|
35
|
+
let executablePath;
|
|
36
|
+
let channel;
|
|
37
|
+
if (!isTest) {
|
|
38
|
+
try {
|
|
39
|
+
({ path: executablePath, channel } = await resolveBrowserExecutable());
|
|
40
|
+
} catch (err) {
|
|
41
|
+
emit({ ok: false, reason: "chrome_missing", error: redact(String(err?.message ?? err)) });
|
|
42
|
+
process.exit(4);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const browser = await chromium.launch({
|
|
47
|
+
headless: isTest,
|
|
48
|
+
...(executablePath ? { executablePath } : {}),
|
|
49
|
+
...(channel && ["chrome", "msedge", "chromium"].includes(channel) ? { channel } : {}),
|
|
50
|
+
}); // isTest = headless in CI; humans see headed.
|
|
51
|
+
const ctx = await browser.newContext({ ignoreHTTPSErrors: true });
|
|
52
|
+
const page = await ctx.newPage();
|
|
53
|
+
|
|
54
|
+
let cfClosed = false;
|
|
55
|
+
browser.on("disconnected", () => { cfClosed = true; });
|
|
56
|
+
|
|
57
|
+
// Navigate to the login page so the page has a same-origin context for
|
|
58
|
+
// subsequent credentialed fetch() calls. Going to ORIGIN's root path can
|
|
59
|
+
// land on a marketing page that may not set a usable document origin for
|
|
60
|
+
// in-page fetch. Path is env-var-configurable for the mock server tests.
|
|
61
|
+
try { await page.goto(`${ORIGIN}${LOGIN_PATH}`, { waitUntil: "domcontentloaded", timeout: 30_000 }); }
|
|
62
|
+
catch { /* continue; next phase checks CF */ }
|
|
63
|
+
if (!isTest) await page.bringToFront().catch(() => {});
|
|
64
|
+
|
|
65
|
+
// CF resolve check
|
|
66
|
+
const cfStart = Date.now();
|
|
67
|
+
while (Date.now() - cfStart < CF_TIMEOUT_MS) {
|
|
68
|
+
try {
|
|
69
|
+
const title = await page.title();
|
|
70
|
+
if (!/just a moment/i.test(title)) break;
|
|
71
|
+
} catch { break; }
|
|
72
|
+
await page.waitForTimeout(500);
|
|
73
|
+
}
|
|
74
|
+
if (Date.now() - cfStart >= CF_TIMEOUT_MS) {
|
|
75
|
+
const title = await page.title().catch(() => "");
|
|
76
|
+
if (/just a moment/i.test(title)) {
|
|
77
|
+
await browser.close().catch(() => {});
|
|
78
|
+
emit({ ok: false, reason: "cf_blocked" });
|
|
79
|
+
process.exit(3);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
ipc({ phase: "awaiting_user" });
|
|
84
|
+
if (!isTest) await page.bringToFront().catch(() => {});
|
|
85
|
+
|
|
86
|
+
// Test hook: auto-drive the login so CI doesn't need a human.
|
|
87
|
+
if (process.env.PERPLEXITY_TEST_AUTO_LOGIN_EMAIL) {
|
|
88
|
+
const email = process.env.PERPLEXITY_TEST_AUTO_LOGIN_EMAIL;
|
|
89
|
+
await page.evaluate(async ({ origin, email }) => {
|
|
90
|
+
await fetch(`${origin}/login/email`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email }) });
|
|
91
|
+
await fetch(`${origin}/login/otp`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, otp: "123456" }) });
|
|
92
|
+
}, { origin: ORIGIN, email });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Test hook: force a browser close to exercise the cancelled path.
|
|
96
|
+
if (process.env.PERPLEXITY_TEST_BROWSER_CLOSE_AFTER_MS) {
|
|
97
|
+
setTimeout(() => browser.close().catch(() => {}), Number(process.env.PERPLEXITY_TEST_BROWSER_CLOSE_AFTER_MS));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const started = Date.now();
|
|
101
|
+
let sessionCookie = null;
|
|
102
|
+
while (Date.now() - started < MAX_WAIT_MS) {
|
|
103
|
+
if (cfClosed) {
|
|
104
|
+
emit({ ok: false, reason: "cancelled" });
|
|
105
|
+
process.exit(2);
|
|
106
|
+
}
|
|
107
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
108
|
+
const cookies = await ctx.cookies().catch(() => []);
|
|
109
|
+
sessionCookie = cookies.find((c) => c.name === "__Secure-next-auth.session-token");
|
|
110
|
+
if (sessionCookie) break;
|
|
111
|
+
}
|
|
112
|
+
if (!sessionCookie) {
|
|
113
|
+
await browser.close().catch(() => {});
|
|
114
|
+
emit({ ok: false, reason: "timeout" });
|
|
115
|
+
process.exit(2);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const allCookies = await ctx.cookies();
|
|
119
|
+
const metadata = await collectSessionMetadata(page, ORIGIN, { sessionTimeoutMs: 10_000 });
|
|
120
|
+
|
|
121
|
+
const vault = new Vault();
|
|
122
|
+
await vault.set(PROFILE, "cookies", JSON.stringify(allCookies));
|
|
123
|
+
if (metadata.sessionData?.user?.email) await vault.set(PROFILE, "email", metadata.sessionData.user.email);
|
|
124
|
+
if (metadata.sessionData?.user?.id) await vault.set(PROFILE, "userId", metadata.sessionData.user.id);
|
|
125
|
+
|
|
126
|
+
const paths = getProfilePaths(PROFILE);
|
|
127
|
+
if (!existsSync(paths.dir)) mkdirSync(paths.dir, { recursive: true });
|
|
128
|
+
writeFileSync(paths.modelsCache, JSON.stringify(metadata.cache, null, 2));
|
|
129
|
+
|
|
130
|
+
recordLoginSuccess(PROFILE, { tier: metadata.tier, loginMode: "manual", lastLogin: new Date().toISOString() });
|
|
131
|
+
|
|
132
|
+
writeFileSync(paths.reinit, String(Date.now()));
|
|
133
|
+
|
|
134
|
+
await browser.close().catch(() => {});
|
|
135
|
+
emit({ ok: true, tier: metadata.tier, modelCount: Object.keys(metadata.models?.models ?? {}).length });
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
main().catch((err) => {
|
|
140
|
+
const msg = err?.message ?? err;
|
|
141
|
+
const stack = err?.stack;
|
|
142
|
+
emit({
|
|
143
|
+
ok: false,
|
|
144
|
+
reason: "crash",
|
|
145
|
+
error: redact(String(msg ?? "unknown error")),
|
|
146
|
+
detail: redact(String(msg ?? "unknown error")),
|
|
147
|
+
...(stack ? { stack: redact(String(stack)) } : {}),
|
|
148
|
+
});
|
|
149
|
+
process.exit(5);
|
|
150
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectSessionMetadata
|
|
3
|
+
} from "./chunk-HU5B4FXS.mjs";
|
|
4
|
+
import {
|
|
5
|
+
redact
|
|
6
|
+
} from "./chunk-HMKLWVXB.mjs";
|
|
7
|
+
import {
|
|
8
|
+
resolveBrowserExecutable
|
|
9
|
+
} from "./chunk-LKJMLGFP.mjs";
|
|
10
|
+
import {
|
|
11
|
+
Vault
|
|
12
|
+
} from "./chunk-TQLCLE4L.mjs";
|
|
13
|
+
import "./chunk-MTDFKNXX.mjs";
|
|
14
|
+
import {
|
|
15
|
+
getActiveName,
|
|
16
|
+
getProfilePaths,
|
|
17
|
+
recordLoginSuccess
|
|
18
|
+
} from "./chunk-XKSWCEGI.mjs";
|
|
19
|
+
import "./chunk-4UEJOM6W.mjs";
|
|
20
|
+
|
|
21
|
+
// src/manual-login-runner.js
|
|
22
|
+
import { writeFileSync, existsSync, mkdirSync } from "fs";
|
|
23
|
+
import { chromium } from "patchright";
|
|
24
|
+
var ORIGIN = process.env.PERPLEXITY_ORIGIN || "https://www.perplexity.ai";
|
|
25
|
+
var LOGIN_PATH = process.env.PERPLEXITY_LOGIN_PATH || "/account";
|
|
26
|
+
var POLL_MS = Number(process.env.PERPLEXITY_POLL_MS ?? 2e3);
|
|
27
|
+
var MAX_WAIT_MS = 18e4;
|
|
28
|
+
var CF_TIMEOUT_MS = Number(process.env.PERPLEXITY_CF_TIMEOUT_MS ?? 2e4);
|
|
29
|
+
function resolveProfile() {
|
|
30
|
+
return process.env.PERPLEXITY_PROFILE || getActiveName() || "default";
|
|
31
|
+
}
|
|
32
|
+
var isTest = !!process.env.PERPLEXITY_TEST_AUTO_LOGIN_EMAIL || !!process.env.PERPLEXITY_TEST_BROWSER_CLOSE_AFTER_MS;
|
|
33
|
+
function ipc(msg) {
|
|
34
|
+
if (process.send) process.send(msg);
|
|
35
|
+
}
|
|
36
|
+
function emit(obj) {
|
|
37
|
+
process.stdout.write(JSON.stringify(obj) + "\n");
|
|
38
|
+
}
|
|
39
|
+
async function main() {
|
|
40
|
+
const PROFILE = resolveProfile();
|
|
41
|
+
let executablePath;
|
|
42
|
+
let channel;
|
|
43
|
+
if (!isTest) {
|
|
44
|
+
try {
|
|
45
|
+
({ path: executablePath, channel } = await resolveBrowserExecutable());
|
|
46
|
+
} catch (err) {
|
|
47
|
+
emit({ ok: false, reason: "chrome_missing", error: redact(String(err?.message ?? err)) });
|
|
48
|
+
process.exit(4);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const browser = await chromium.launch({
|
|
52
|
+
headless: isTest,
|
|
53
|
+
...executablePath ? { executablePath } : {},
|
|
54
|
+
...channel && ["chrome", "msedge", "chromium"].includes(channel) ? { channel } : {}
|
|
55
|
+
});
|
|
56
|
+
const ctx = await browser.newContext({ ignoreHTTPSErrors: true });
|
|
57
|
+
const page = await ctx.newPage();
|
|
58
|
+
let cfClosed = false;
|
|
59
|
+
browser.on("disconnected", () => {
|
|
60
|
+
cfClosed = true;
|
|
61
|
+
});
|
|
62
|
+
try {
|
|
63
|
+
await page.goto(`${ORIGIN}${LOGIN_PATH}`, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
if (!isTest) await page.bringToFront().catch(() => {
|
|
67
|
+
});
|
|
68
|
+
const cfStart = Date.now();
|
|
69
|
+
while (Date.now() - cfStart < CF_TIMEOUT_MS) {
|
|
70
|
+
try {
|
|
71
|
+
const title = await page.title();
|
|
72
|
+
if (!/just a moment/i.test(title)) break;
|
|
73
|
+
} catch {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
await page.waitForTimeout(500);
|
|
77
|
+
}
|
|
78
|
+
if (Date.now() - cfStart >= CF_TIMEOUT_MS) {
|
|
79
|
+
const title = await page.title().catch(() => "");
|
|
80
|
+
if (/just a moment/i.test(title)) {
|
|
81
|
+
await browser.close().catch(() => {
|
|
82
|
+
});
|
|
83
|
+
emit({ ok: false, reason: "cf_blocked" });
|
|
84
|
+
process.exit(3);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
ipc({ phase: "awaiting_user" });
|
|
88
|
+
if (!isTest) await page.bringToFront().catch(() => {
|
|
89
|
+
});
|
|
90
|
+
if (process.env.PERPLEXITY_TEST_AUTO_LOGIN_EMAIL) {
|
|
91
|
+
const email = process.env.PERPLEXITY_TEST_AUTO_LOGIN_EMAIL;
|
|
92
|
+
await page.evaluate(async ({ origin, email: email2 }) => {
|
|
93
|
+
await fetch(`${origin}/login/email`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: email2 }) });
|
|
94
|
+
await fetch(`${origin}/login/otp`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: email2, otp: "123456" }) });
|
|
95
|
+
}, { origin: ORIGIN, email });
|
|
96
|
+
}
|
|
97
|
+
if (process.env.PERPLEXITY_TEST_BROWSER_CLOSE_AFTER_MS) {
|
|
98
|
+
setTimeout(() => browser.close().catch(() => {
|
|
99
|
+
}), Number(process.env.PERPLEXITY_TEST_BROWSER_CLOSE_AFTER_MS));
|
|
100
|
+
}
|
|
101
|
+
const started = Date.now();
|
|
102
|
+
let sessionCookie = null;
|
|
103
|
+
while (Date.now() - started < MAX_WAIT_MS) {
|
|
104
|
+
if (cfClosed) {
|
|
105
|
+
emit({ ok: false, reason: "cancelled" });
|
|
106
|
+
process.exit(2);
|
|
107
|
+
}
|
|
108
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
109
|
+
const cookies = await ctx.cookies().catch(() => []);
|
|
110
|
+
sessionCookie = cookies.find((c) => c.name === "__Secure-next-auth.session-token");
|
|
111
|
+
if (sessionCookie) break;
|
|
112
|
+
}
|
|
113
|
+
if (!sessionCookie) {
|
|
114
|
+
await browser.close().catch(() => {
|
|
115
|
+
});
|
|
116
|
+
emit({ ok: false, reason: "timeout" });
|
|
117
|
+
process.exit(2);
|
|
118
|
+
}
|
|
119
|
+
const allCookies = await ctx.cookies();
|
|
120
|
+
const metadata = await collectSessionMetadata(page, ORIGIN, { sessionTimeoutMs: 1e4 });
|
|
121
|
+
const vault = new Vault();
|
|
122
|
+
await vault.set(PROFILE, "cookies", JSON.stringify(allCookies));
|
|
123
|
+
if (metadata.sessionData?.user?.email) await vault.set(PROFILE, "email", metadata.sessionData.user.email);
|
|
124
|
+
if (metadata.sessionData?.user?.id) await vault.set(PROFILE, "userId", metadata.sessionData.user.id);
|
|
125
|
+
const paths = getProfilePaths(PROFILE);
|
|
126
|
+
if (!existsSync(paths.dir)) mkdirSync(paths.dir, { recursive: true });
|
|
127
|
+
writeFileSync(paths.modelsCache, JSON.stringify(metadata.cache, null, 2));
|
|
128
|
+
recordLoginSuccess(PROFILE, { tier: metadata.tier, loginMode: "manual", lastLogin: (/* @__PURE__ */ new Date()).toISOString() });
|
|
129
|
+
writeFileSync(paths.reinit, String(Date.now()));
|
|
130
|
+
await browser.close().catch(() => {
|
|
131
|
+
});
|
|
132
|
+
emit({ ok: true, tier: metadata.tier, modelCount: Object.keys(metadata.models?.models ?? {}).length });
|
|
133
|
+
process.exit(0);
|
|
134
|
+
}
|
|
135
|
+
main().catch((err) => {
|
|
136
|
+
const msg = err?.message ?? err;
|
|
137
|
+
const stack = err?.stack;
|
|
138
|
+
emit({
|
|
139
|
+
ok: false,
|
|
140
|
+
reason: "crash",
|
|
141
|
+
error: redact(String(msg ?? "unknown error")),
|
|
142
|
+
detail: redact(String(msg ?? "unknown error")),
|
|
143
|
+
...stack ? { stack: redact(String(stack)) } : {}
|
|
144
|
+
});
|
|
145
|
+
process.exit(5);
|
|
146
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync, readFileSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getImpitRuntimeDir } from './refresh.js';
|
|
5
|
+
import './config.js';
|
|
6
|
+
import 'patchright';
|
|
7
|
+
|
|
8
|
+
// Speed Boost (impit) install / uninstall helpers shared by the CLI and
|
|
9
|
+
// the VS Code extension. Mirrors the behavior of
|
|
10
|
+
// packages/extension/src/native-deps.ts but lives in the npm package so
|
|
11
|
+
// `npx perplexity-user-mcp install-speed-boost` doesn't require the
|
|
12
|
+
// extension to be installed.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const STATE_MARKER = "native-deps-state.json";
|
|
16
|
+
|
|
17
|
+
function stateFile() {
|
|
18
|
+
return join(getImpitRuntimeDir(), STATE_MARKER);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeState(state) {
|
|
22
|
+
const dir = getImpitRuntimeDir();
|
|
23
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
24
|
+
writeFileSync(stateFile(), JSON.stringify(state, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readState() {
|
|
28
|
+
const f = stateFile();
|
|
29
|
+
if (!existsSync(f)) return null;
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(readFileSync(f, "utf8"));
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Ensure a minimal package.json exists in the runtime dir so `npm install`
|
|
39
|
+
* lands there cleanly (prevents npm walking up to the user's home and
|
|
40
|
+
* polluting a parent package.json). Mirrors the extension's helper.
|
|
41
|
+
*/
|
|
42
|
+
function ensureRuntimePackageJson() {
|
|
43
|
+
const dir = getImpitRuntimeDir();
|
|
44
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
45
|
+
const pkgPath = join(dir, "package.json");
|
|
46
|
+
if (!existsSync(pkgPath)) {
|
|
47
|
+
writeFileSync(
|
|
48
|
+
pkgPath,
|
|
49
|
+
JSON.stringify(
|
|
50
|
+
{
|
|
51
|
+
name: "perplexity-native-deps",
|
|
52
|
+
version: "1.0.0",
|
|
53
|
+
private: true,
|
|
54
|
+
description: "Runtime native dependencies for the Perplexity MCP CLI/extension.",
|
|
55
|
+
},
|
|
56
|
+
null,
|
|
57
|
+
2,
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return dir;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @returns {{ installed: boolean; version: string | null; installedAt: string | null; runtimeDir: string }}
|
|
66
|
+
*/
|
|
67
|
+
function getImpitStatus() {
|
|
68
|
+
const dir = getImpitRuntimeDir();
|
|
69
|
+
const marker = join(dir, "node_modules", "impit", "package.json");
|
|
70
|
+
if (!existsSync(marker)) {
|
|
71
|
+
return { installed: false, version: null, installedAt: null, runtimeDir: dir };
|
|
72
|
+
}
|
|
73
|
+
let version = null;
|
|
74
|
+
try {
|
|
75
|
+
version = JSON.parse(readFileSync(marker, "utf8")).version ?? null;
|
|
76
|
+
} catch {
|
|
77
|
+
// marker exists but unreadable — still count as installed
|
|
78
|
+
}
|
|
79
|
+
const state = readState();
|
|
80
|
+
return {
|
|
81
|
+
installed: true,
|
|
82
|
+
version,
|
|
83
|
+
installedAt: state?.installedAt ?? null,
|
|
84
|
+
runtimeDir: dir,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Install impit into ~/.perplexity-mcp/native-deps/ via `npm install`.
|
|
90
|
+
* Does NOT depend on the user's project — uses --prefix into our own
|
|
91
|
+
* runtime dir and ensures a package.json exists there.
|
|
92
|
+
*
|
|
93
|
+
* @param {{ log?: (line: string) => void }} [opts]
|
|
94
|
+
* @returns {Promise<{ ok: boolean; version?: string; error?: string }>}
|
|
95
|
+
*/
|
|
96
|
+
async function installImpit(opts = {}) {
|
|
97
|
+
const log = opts.log ?? (() => undefined);
|
|
98
|
+
const dir = ensureRuntimePackageJson();
|
|
99
|
+
log(`Installing impit into ${dir} via npm...`);
|
|
100
|
+
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
const child = spawn("npm", ["install", "impit@latest", "--no-audit", "--no-fund", "--loglevel=error"], {
|
|
103
|
+
cwd: dir,
|
|
104
|
+
shell: process.platform === "win32",
|
|
105
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
let stderrBuf = "";
|
|
109
|
+
|
|
110
|
+
child.stdout?.on("data", (chunk) => {
|
|
111
|
+
for (const line of chunk.toString().split(/\r?\n/)) if (line.trim()) log(`npm: ${line}`);
|
|
112
|
+
});
|
|
113
|
+
child.stderr?.on("data", (chunk) => {
|
|
114
|
+
const text = chunk.toString();
|
|
115
|
+
stderrBuf += text;
|
|
116
|
+
for (const line of text.split(/\r?\n/)) if (line.trim()) log(`npm: ${line}`);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
child.on("error", (err) => {
|
|
120
|
+
log(`npm spawn error: ${err.message}`);
|
|
121
|
+
resolve({
|
|
122
|
+
ok: false,
|
|
123
|
+
error:
|
|
124
|
+
err.message.includes("ENOENT")
|
|
125
|
+
? "`npm` not found on PATH. Install Node.js (which ships with npm) and try again."
|
|
126
|
+
: err.message,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
child.on("close", (code) => {
|
|
131
|
+
if (code !== 0) {
|
|
132
|
+
resolve({
|
|
133
|
+
ok: false,
|
|
134
|
+
error: `npm exited with code ${code}. stderr: ${stderrBuf.slice(0, 400) || "(empty)"}`,
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const status = getImpitStatus();
|
|
139
|
+
if (!status.installed) {
|
|
140
|
+
resolve({
|
|
141
|
+
ok: false,
|
|
142
|
+
error: "npm reported success but impit package.json not found in node_modules. Check npm output above.",
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
writeState({
|
|
147
|
+
version: status.version ?? "unknown",
|
|
148
|
+
installedAt: new Date().toISOString(),
|
|
149
|
+
});
|
|
150
|
+
log(`impit ${status.version ?? ""} installed successfully.`);
|
|
151
|
+
resolve({ ok: true, version: status.version ?? undefined });
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Remove the entire native-deps runtime directory.
|
|
158
|
+
*
|
|
159
|
+
* @param {{ log?: (line: string) => void }} [opts]
|
|
160
|
+
* @returns {{ ok: boolean; error?: string }}
|
|
161
|
+
*/
|
|
162
|
+
function uninstallImpit(opts = {}) {
|
|
163
|
+
const log = opts.log ?? (() => undefined);
|
|
164
|
+
const dir = getImpitRuntimeDir();
|
|
165
|
+
if (!existsSync(dir)) return { ok: true };
|
|
166
|
+
try {
|
|
167
|
+
rmSync(dir, { recursive: true, force: true });
|
|
168
|
+
log(`Removed ${dir}.`);
|
|
169
|
+
return { ok: true };
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return { ok: false, error: err.message };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { getImpitStatus, installImpit, uninstallImpit };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getImpitRuntimeDir
|
|
3
|
+
} from "./chunk-Z7DAACGZ.mjs";
|
|
4
|
+
import "./chunk-LKJMLGFP.mjs";
|
|
5
|
+
import "./chunk-TQLCLE4L.mjs";
|
|
6
|
+
import "./chunk-MTDFKNXX.mjs";
|
|
7
|
+
import "./chunk-XKSWCEGI.mjs";
|
|
8
|
+
import "./chunk-4UEJOM6W.mjs";
|
|
9
|
+
|
|
10
|
+
// src/native-deps.js
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
12
|
+
import { spawn } from "child_process";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
var STATE_MARKER = "native-deps-state.json";
|
|
15
|
+
function stateFile() {
|
|
16
|
+
return join(getImpitRuntimeDir(), STATE_MARKER);
|
|
17
|
+
}
|
|
18
|
+
function writeState(state) {
|
|
19
|
+
const dir = getImpitRuntimeDir();
|
|
20
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
21
|
+
writeFileSync(stateFile(), JSON.stringify(state, null, 2));
|
|
22
|
+
}
|
|
23
|
+
function readState() {
|
|
24
|
+
const f = stateFile();
|
|
25
|
+
if (!existsSync(f)) return null;
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(readFileSync(f, "utf8"));
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function ensureRuntimePackageJson() {
|
|
33
|
+
const dir = getImpitRuntimeDir();
|
|
34
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
35
|
+
const pkgPath = join(dir, "package.json");
|
|
36
|
+
if (!existsSync(pkgPath)) {
|
|
37
|
+
writeFileSync(
|
|
38
|
+
pkgPath,
|
|
39
|
+
JSON.stringify(
|
|
40
|
+
{
|
|
41
|
+
name: "perplexity-native-deps",
|
|
42
|
+
version: "1.0.0",
|
|
43
|
+
private: true,
|
|
44
|
+
description: "Runtime native dependencies for the Perplexity MCP CLI/extension."
|
|
45
|
+
},
|
|
46
|
+
null,
|
|
47
|
+
2
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return dir;
|
|
52
|
+
}
|
|
53
|
+
function getImpitStatus() {
|
|
54
|
+
const dir = getImpitRuntimeDir();
|
|
55
|
+
const marker = join(dir, "node_modules", "impit", "package.json");
|
|
56
|
+
if (!existsSync(marker)) {
|
|
57
|
+
return { installed: false, version: null, installedAt: null, runtimeDir: dir };
|
|
58
|
+
}
|
|
59
|
+
let version = null;
|
|
60
|
+
try {
|
|
61
|
+
version = JSON.parse(readFileSync(marker, "utf8")).version ?? null;
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
const state = readState();
|
|
65
|
+
return {
|
|
66
|
+
installed: true,
|
|
67
|
+
version,
|
|
68
|
+
installedAt: state?.installedAt ?? null,
|
|
69
|
+
runtimeDir: dir
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function installImpit(opts = {}) {
|
|
73
|
+
const log = opts.log ?? (() => void 0);
|
|
74
|
+
const dir = ensureRuntimePackageJson();
|
|
75
|
+
log(`Installing impit into ${dir} via npm...`);
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
const child = spawn("npm", ["install", "impit@latest", "--no-audit", "--no-fund", "--loglevel=error"], {
|
|
78
|
+
cwd: dir,
|
|
79
|
+
shell: process.platform === "win32",
|
|
80
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
81
|
+
});
|
|
82
|
+
let stderrBuf = "";
|
|
83
|
+
child.stdout?.on("data", (chunk) => {
|
|
84
|
+
for (const line of chunk.toString().split(/\r?\n/)) if (line.trim()) log(`npm: ${line}`);
|
|
85
|
+
});
|
|
86
|
+
child.stderr?.on("data", (chunk) => {
|
|
87
|
+
const text = chunk.toString();
|
|
88
|
+
stderrBuf += text;
|
|
89
|
+
for (const line of text.split(/\r?\n/)) if (line.trim()) log(`npm: ${line}`);
|
|
90
|
+
});
|
|
91
|
+
child.on("error", (err) => {
|
|
92
|
+
log(`npm spawn error: ${err.message}`);
|
|
93
|
+
resolve({
|
|
94
|
+
ok: false,
|
|
95
|
+
error: err.message.includes("ENOENT") ? "`npm` not found on PATH. Install Node.js (which ships with npm) and try again." : err.message
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
child.on("close", (code) => {
|
|
99
|
+
if (code !== 0) {
|
|
100
|
+
resolve({
|
|
101
|
+
ok: false,
|
|
102
|
+
error: `npm exited with code ${code}. stderr: ${stderrBuf.slice(0, 400) || "(empty)"}`
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const status = getImpitStatus();
|
|
107
|
+
if (!status.installed) {
|
|
108
|
+
resolve({
|
|
109
|
+
ok: false,
|
|
110
|
+
error: "npm reported success but impit package.json not found in node_modules. Check npm output above."
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
writeState({
|
|
115
|
+
version: status.version ?? "unknown",
|
|
116
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
117
|
+
});
|
|
118
|
+
log(`impit ${status.version ?? ""} installed successfully.`);
|
|
119
|
+
resolve({ ok: true, version: status.version ?? void 0 });
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function uninstallImpit(opts = {}) {
|
|
124
|
+
const log = opts.log ?? (() => void 0);
|
|
125
|
+
const dir = getImpitRuntimeDir();
|
|
126
|
+
if (!existsSync(dir)) return { ok: true };
|
|
127
|
+
try {
|
|
128
|
+
rmSync(dir, { recursive: true, force: true });
|
|
129
|
+
log(`Removed ${dir}.`);
|
|
130
|
+
return { ok: true };
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return { ok: false, error: err.message };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
getImpitStatus,
|
|
137
|
+
installImpit,
|
|
138
|
+
uninstallImpit
|
|
139
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
interface ProfileMeta {
|
|
2
|
+
name: string;
|
|
3
|
+
displayName: string;
|
|
4
|
+
createdAt: string;
|
|
5
|
+
loginMode?: string;
|
|
6
|
+
tier?: string;
|
|
7
|
+
lastLogin?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ProfilePaths {
|
|
11
|
+
dir: string;
|
|
12
|
+
meta: string;
|
|
13
|
+
vault: string;
|
|
14
|
+
vaultPlain: string;
|
|
15
|
+
browserData: string;
|
|
16
|
+
modelsCache: string;
|
|
17
|
+
history: string;
|
|
18
|
+
attachments: string;
|
|
19
|
+
researches: string;
|
|
20
|
+
reinit: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare function getConfigDir(): string;
|
|
24
|
+
declare function getProfilesDir(): string;
|
|
25
|
+
declare function getProfilePaths(name: string): ProfilePaths;
|
|
26
|
+
declare function validateName(name: string): string | null;
|
|
27
|
+
declare function createProfile(name: string, opts?: { displayName?: string; loginMode?: string }): ProfileMeta;
|
|
28
|
+
declare function listProfiles(): ProfileMeta[];
|
|
29
|
+
declare function getProfile(name: string): ProfileMeta | null;
|
|
30
|
+
declare function deleteProfile(name: string): void;
|
|
31
|
+
declare function getActiveName(): string | null;
|
|
32
|
+
declare function getActive(): ProfileMeta | null;
|
|
33
|
+
declare function setActive(name: string): void;
|
|
34
|
+
declare function suggestNextDefaultName(): string;
|
|
35
|
+
declare function renameProfile(oldName: string, newName: string): void;
|
|
36
|
+
declare function recordLoginSuccess(
|
|
37
|
+
name: string,
|
|
38
|
+
opts: { tier: string; loginMode: string; lastLogin: string }
|
|
39
|
+
): ProfileMeta;
|
|
40
|
+
|
|
41
|
+
export { type ProfileMeta, type ProfilePaths, createProfile, deleteProfile, getActive, getActiveName, getConfigDir, getProfile, getProfilePaths, getProfilesDir, listProfiles, recordLoginSuccess, renameProfile, setActive, suggestNextDefaultName, validateName };
|