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,93 @@
|
|
|
1
|
+
const CATEGORY = "probe";
|
|
2
|
+
|
|
3
|
+
async function defaultSearch({ timeoutMs }) {
|
|
4
|
+
const { PerplexityClient } = await import('../client.d.ts');
|
|
5
|
+
const client = new PerplexityClient();
|
|
6
|
+
// Force the headless-only path so the probe never opens a visible browser
|
|
7
|
+
// window. The headed Cloudflare bootstrap was leaving Chrome windows
|
|
8
|
+
// dangling on every Deep check / probe re-run when init() or close() failed
|
|
9
|
+
// mid-flight; the probe is a smoke test for the headless search path that
|
|
10
|
+
// tools actually use, so skipping the headed phase is the right scope. The
|
|
11
|
+
// env var is restored in a finally so concurrent tool-call clients aren't
|
|
12
|
+
// affected.
|
|
13
|
+
const HEADLESS_KEY = "PERPLEXITY_HEADLESS_ONLY";
|
|
14
|
+
const prevHeadlessOnly = process.env[HEADLESS_KEY];
|
|
15
|
+
process.env[HEADLESS_KEY] = "1";
|
|
16
|
+
const t0 = Date.now();
|
|
17
|
+
try {
|
|
18
|
+
// init() is INSIDE the try so the finally always runs shutdown(), even
|
|
19
|
+
// when init throws (browser launch failure, CF timeout, etc.). Previously
|
|
20
|
+
// a failed init left both the persistent context AND the freshly-spawned
|
|
21
|
+
// chrome.exe dangling, which is how visible browser windows were piling
|
|
22
|
+
// up across repeated Deep check clicks.
|
|
23
|
+
await client.init();
|
|
24
|
+
const authenticated = client.authenticated;
|
|
25
|
+
const result = await client.search({
|
|
26
|
+
query: "What is the capital of France? Cite at least one web source.",
|
|
27
|
+
modelPreference: "turbo",
|
|
28
|
+
mode: "concise",
|
|
29
|
+
sources: ["web"],
|
|
30
|
+
language: "en-US",
|
|
31
|
+
});
|
|
32
|
+
const elapsedMs = Date.now() - t0;
|
|
33
|
+
return {
|
|
34
|
+
answer: result.answer ?? "",
|
|
35
|
+
sources: result.sources ?? [],
|
|
36
|
+
elapsedMs,
|
|
37
|
+
authenticated,
|
|
38
|
+
threadUrl: result.threadUrl ?? null,
|
|
39
|
+
};
|
|
40
|
+
} finally {
|
|
41
|
+
await client.shutdown().catch(() => {});
|
|
42
|
+
if (prevHeadlessOnly === undefined) delete process.env[HEADLESS_KEY];
|
|
43
|
+
else process.env[HEADLESS_KEY] = prevHeadlessOnly;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function run(opts = {}) {
|
|
48
|
+
if (!opts.probe) {
|
|
49
|
+
return [{ category: CATEGORY, name: "probe-search", status: "skip", message: "skipped (pass --probe to enable)" }];
|
|
50
|
+
}
|
|
51
|
+
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
52
|
+
const search = opts.searchOverride ?? defaultSearch;
|
|
53
|
+
try {
|
|
54
|
+
const result = await search({ timeoutMs });
|
|
55
|
+
if (!result.sources || result.sources.length === 0) {
|
|
56
|
+
if (result.authenticated && (result.answer?.trim() || result.threadUrl)) {
|
|
57
|
+
return [{
|
|
58
|
+
category: CATEGORY,
|
|
59
|
+
name: "probe-search",
|
|
60
|
+
status: "warn",
|
|
61
|
+
message: `probe search completed without citations (latency ${result.elapsedMs}ms)`,
|
|
62
|
+
hint: "Session appears authenticated, but Perplexity returned no sources for the probe query. Retry once before treating this as an auth failure.",
|
|
63
|
+
}];
|
|
64
|
+
}
|
|
65
|
+
return [{
|
|
66
|
+
category: CATEGORY,
|
|
67
|
+
name: "probe-search",
|
|
68
|
+
status: "fail",
|
|
69
|
+
message: `probe returned no sources (latency ${result.elapsedMs}ms)`,
|
|
70
|
+
hint: result.authenticated
|
|
71
|
+
? "Perplexity returned no citations for the probe query. Retry once; if it persists, inspect the extension logs."
|
|
72
|
+
: "Session may be anonymous — run login, then --probe again.",
|
|
73
|
+
}];
|
|
74
|
+
}
|
|
75
|
+
return [{
|
|
76
|
+
category: CATEGORY,
|
|
77
|
+
name: "probe-search",
|
|
78
|
+
status: "pass",
|
|
79
|
+
message: `live search returned ${result.sources.length} source(s) in ${result.elapsedMs}ms`,
|
|
80
|
+
detail: { latencyMs: result.elapsedMs, sourceCount: result.sources.length },
|
|
81
|
+
}];
|
|
82
|
+
} catch (err) {
|
|
83
|
+
return [{
|
|
84
|
+
category: CATEGORY,
|
|
85
|
+
name: "probe-search",
|
|
86
|
+
status: "fail",
|
|
87
|
+
message: `probe failed: ${err.message}`,
|
|
88
|
+
hint: "Check network / auth — run `doctor` without --probe to see which category regressed.",
|
|
89
|
+
}];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { run };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import "../chunk-4UEJOM6W.mjs";
|
|
2
|
+
|
|
3
|
+
// src/checks/probe.js
|
|
4
|
+
var CATEGORY = "probe";
|
|
5
|
+
async function defaultSearch({ timeoutMs }) {
|
|
6
|
+
const { PerplexityClient } = await import("../client.mjs");
|
|
7
|
+
const client = new PerplexityClient();
|
|
8
|
+
const HEADLESS_KEY = "PERPLEXITY_HEADLESS_ONLY";
|
|
9
|
+
const prevHeadlessOnly = process.env[HEADLESS_KEY];
|
|
10
|
+
process.env[HEADLESS_KEY] = "1";
|
|
11
|
+
const t0 = Date.now();
|
|
12
|
+
try {
|
|
13
|
+
await client.init();
|
|
14
|
+
const authenticated = client.authenticated;
|
|
15
|
+
const result = await client.search({
|
|
16
|
+
query: "What is the capital of France? Cite at least one web source.",
|
|
17
|
+
modelPreference: "turbo",
|
|
18
|
+
mode: "concise",
|
|
19
|
+
sources: ["web"],
|
|
20
|
+
language: "en-US"
|
|
21
|
+
});
|
|
22
|
+
const elapsedMs = Date.now() - t0;
|
|
23
|
+
return {
|
|
24
|
+
answer: result.answer ?? "",
|
|
25
|
+
sources: result.sources ?? [],
|
|
26
|
+
elapsedMs,
|
|
27
|
+
authenticated,
|
|
28
|
+
threadUrl: result.threadUrl ?? null
|
|
29
|
+
};
|
|
30
|
+
} finally {
|
|
31
|
+
await client.shutdown().catch(() => {
|
|
32
|
+
});
|
|
33
|
+
if (prevHeadlessOnly === void 0) delete process.env[HEADLESS_KEY];
|
|
34
|
+
else process.env[HEADLESS_KEY] = prevHeadlessOnly;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function run(opts = {}) {
|
|
38
|
+
if (!opts.probe) {
|
|
39
|
+
return [{ category: CATEGORY, name: "probe-search", status: "skip", message: "skipped (pass --probe to enable)" }];
|
|
40
|
+
}
|
|
41
|
+
const timeoutMs = opts.timeoutMs ?? 1e4;
|
|
42
|
+
const search = opts.searchOverride ?? defaultSearch;
|
|
43
|
+
try {
|
|
44
|
+
const result = await search({ timeoutMs });
|
|
45
|
+
if (!result.sources || result.sources.length === 0) {
|
|
46
|
+
if (result.authenticated && (result.answer?.trim() || result.threadUrl)) {
|
|
47
|
+
return [{
|
|
48
|
+
category: CATEGORY,
|
|
49
|
+
name: "probe-search",
|
|
50
|
+
status: "warn",
|
|
51
|
+
message: `probe search completed without citations (latency ${result.elapsedMs}ms)`,
|
|
52
|
+
hint: "Session appears authenticated, but Perplexity returned no sources for the probe query. Retry once before treating this as an auth failure."
|
|
53
|
+
}];
|
|
54
|
+
}
|
|
55
|
+
return [{
|
|
56
|
+
category: CATEGORY,
|
|
57
|
+
name: "probe-search",
|
|
58
|
+
status: "fail",
|
|
59
|
+
message: `probe returned no sources (latency ${result.elapsedMs}ms)`,
|
|
60
|
+
hint: result.authenticated ? "Perplexity returned no citations for the probe query. Retry once; if it persists, inspect the extension logs." : "Session may be anonymous \u2014 run login, then --probe again."
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
return [{
|
|
64
|
+
category: CATEGORY,
|
|
65
|
+
name: "probe-search",
|
|
66
|
+
status: "pass",
|
|
67
|
+
message: `live search returned ${result.sources.length} source(s) in ${result.elapsedMs}ms`,
|
|
68
|
+
detail: { latencyMs: result.elapsedMs, sourceCount: result.sources.length }
|
|
69
|
+
}];
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return [{
|
|
72
|
+
category: CATEGORY,
|
|
73
|
+
name: "probe-search",
|
|
74
|
+
status: "fail",
|
|
75
|
+
message: `probe failed: ${err.message}`,
|
|
76
|
+
hint: "Check network / auth \u2014 run `doctor` without --probe to see which category regressed."
|
|
77
|
+
}];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
run
|
|
82
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const CATEGORY = "profiles";
|
|
5
|
+
const STALE_CACHE_DAYS = 7;
|
|
6
|
+
|
|
7
|
+
async function run(opts = {}) {
|
|
8
|
+
const dir = opts.configDir;
|
|
9
|
+
const results = [];
|
|
10
|
+
const profilesDir = join(dir, "profiles");
|
|
11
|
+
|
|
12
|
+
if (!existsSync(profilesDir)) {
|
|
13
|
+
results.push({ category: CATEGORY, name: "profile-count", status: "warn", message: "No profiles dir." });
|
|
14
|
+
return results;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const listing = readdirSync(profilesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
18
|
+
const names = opts.allProfiles
|
|
19
|
+
? listing
|
|
20
|
+
: opts.profile
|
|
21
|
+
? [opts.profile]
|
|
22
|
+
: listing;
|
|
23
|
+
|
|
24
|
+
if (names.length === 0) {
|
|
25
|
+
results.push({
|
|
26
|
+
category: CATEGORY,
|
|
27
|
+
name: "profile-count",
|
|
28
|
+
status: "warn",
|
|
29
|
+
message: "No profiles found. Run `login` to create one.",
|
|
30
|
+
});
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
33
|
+
results.push({
|
|
34
|
+
category: CATEGORY,
|
|
35
|
+
name: "profile-count",
|
|
36
|
+
status: "pass",
|
|
37
|
+
message: `${names.length} profile(s): ${names.join(", ")}`,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
for (const name of names) {
|
|
41
|
+
const pdir = join(profilesDir, name);
|
|
42
|
+
const meta = join(pdir, "meta.json");
|
|
43
|
+
try {
|
|
44
|
+
JSON.parse(readFileSync(meta, "utf8"));
|
|
45
|
+
results.push({ category: CATEGORY, name: `${name}/meta`, status: "pass", message: "valid" });
|
|
46
|
+
} catch {
|
|
47
|
+
results.push({
|
|
48
|
+
category: CATEGORY,
|
|
49
|
+
name: `${name}/meta`,
|
|
50
|
+
status: "fail",
|
|
51
|
+
message: `${name}/meta.json missing or corrupt`,
|
|
52
|
+
hint: `Delete and re-run login for profile '${name}'.`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const enc = join(pdir, "vault.enc");
|
|
57
|
+
const plain = join(pdir, "vault.json");
|
|
58
|
+
if (existsSync(enc)) {
|
|
59
|
+
results.push({ category: CATEGORY, name: `${name}/vault`, status: "pass", message: "encrypted" });
|
|
60
|
+
} else if (existsSync(plain)) {
|
|
61
|
+
results.push({
|
|
62
|
+
category: CATEGORY,
|
|
63
|
+
name: `${name}/vault`,
|
|
64
|
+
status: "warn",
|
|
65
|
+
message: "plaintext opt-out (security.encryptCookies=false)",
|
|
66
|
+
hint: "Consider re-running login without --plain-cookies for encrypted storage.",
|
|
67
|
+
});
|
|
68
|
+
} else {
|
|
69
|
+
results.push({
|
|
70
|
+
category: CATEGORY,
|
|
71
|
+
name: `${name}/vault`,
|
|
72
|
+
status: "warn",
|
|
73
|
+
message: "no vault file — profile never logged in",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const cache = join(pdir, "models-cache.json");
|
|
78
|
+
if (!existsSync(cache)) {
|
|
79
|
+
results.push({ category: CATEGORY, name: `${name}/models-cache`, status: "skip", message: "no cache yet" });
|
|
80
|
+
} else {
|
|
81
|
+
const ageDays = (Date.now() - statSync(cache).mtime.getTime()) / (24 * 3600 * 1000);
|
|
82
|
+
if (ageDays > STALE_CACHE_DAYS) {
|
|
83
|
+
results.push({
|
|
84
|
+
category: CATEGORY,
|
|
85
|
+
name: `${name}/models-cache`,
|
|
86
|
+
status: "warn",
|
|
87
|
+
message: `models cache is ${Math.round(ageDays)} days old`,
|
|
88
|
+
hint: "Open the dashboard and click 'Fetch live' to refresh.",
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
results.push({ category: CATEGORY, name: `${name}/models-cache`, status: "pass", message: `${Math.round(ageDays)}d old` });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { run };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import "../chunk-4UEJOM6W.mjs";
|
|
2
|
+
|
|
3
|
+
// src/checks/profiles.js
|
|
4
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
var CATEGORY = "profiles";
|
|
7
|
+
var STALE_CACHE_DAYS = 7;
|
|
8
|
+
async function run(opts = {}) {
|
|
9
|
+
const dir = opts.configDir;
|
|
10
|
+
const results = [];
|
|
11
|
+
const profilesDir = join(dir, "profiles");
|
|
12
|
+
if (!existsSync(profilesDir)) {
|
|
13
|
+
results.push({ category: CATEGORY, name: "profile-count", status: "warn", message: "No profiles dir." });
|
|
14
|
+
return results;
|
|
15
|
+
}
|
|
16
|
+
const listing = readdirSync(profilesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
17
|
+
const names = opts.allProfiles ? listing : opts.profile ? [opts.profile] : listing;
|
|
18
|
+
if (names.length === 0) {
|
|
19
|
+
results.push({
|
|
20
|
+
category: CATEGORY,
|
|
21
|
+
name: "profile-count",
|
|
22
|
+
status: "warn",
|
|
23
|
+
message: "No profiles found. Run `login` to create one."
|
|
24
|
+
});
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
results.push({
|
|
28
|
+
category: CATEGORY,
|
|
29
|
+
name: "profile-count",
|
|
30
|
+
status: "pass",
|
|
31
|
+
message: `${names.length} profile(s): ${names.join(", ")}`
|
|
32
|
+
});
|
|
33
|
+
for (const name of names) {
|
|
34
|
+
const pdir = join(profilesDir, name);
|
|
35
|
+
const meta = join(pdir, "meta.json");
|
|
36
|
+
try {
|
|
37
|
+
JSON.parse(readFileSync(meta, "utf8"));
|
|
38
|
+
results.push({ category: CATEGORY, name: `${name}/meta`, status: "pass", message: "valid" });
|
|
39
|
+
} catch {
|
|
40
|
+
results.push({
|
|
41
|
+
category: CATEGORY,
|
|
42
|
+
name: `${name}/meta`,
|
|
43
|
+
status: "fail",
|
|
44
|
+
message: `${name}/meta.json missing or corrupt`,
|
|
45
|
+
hint: `Delete and re-run login for profile '${name}'.`
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const enc = join(pdir, "vault.enc");
|
|
49
|
+
const plain = join(pdir, "vault.json");
|
|
50
|
+
if (existsSync(enc)) {
|
|
51
|
+
results.push({ category: CATEGORY, name: `${name}/vault`, status: "pass", message: "encrypted" });
|
|
52
|
+
} else if (existsSync(plain)) {
|
|
53
|
+
results.push({
|
|
54
|
+
category: CATEGORY,
|
|
55
|
+
name: `${name}/vault`,
|
|
56
|
+
status: "warn",
|
|
57
|
+
message: "plaintext opt-out (security.encryptCookies=false)",
|
|
58
|
+
hint: "Consider re-running login without --plain-cookies for encrypted storage."
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
results.push({
|
|
62
|
+
category: CATEGORY,
|
|
63
|
+
name: `${name}/vault`,
|
|
64
|
+
status: "warn",
|
|
65
|
+
message: "no vault file \u2014 profile never logged in"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const cache = join(pdir, "models-cache.json");
|
|
69
|
+
if (!existsSync(cache)) {
|
|
70
|
+
results.push({ category: CATEGORY, name: `${name}/models-cache`, status: "skip", message: "no cache yet" });
|
|
71
|
+
} else {
|
|
72
|
+
const ageDays = (Date.now() - statSync(cache).mtime.getTime()) / (24 * 3600 * 1e3);
|
|
73
|
+
if (ageDays > STALE_CACHE_DAYS) {
|
|
74
|
+
results.push({
|
|
75
|
+
category: CATEGORY,
|
|
76
|
+
name: `${name}/models-cache`,
|
|
77
|
+
status: "warn",
|
|
78
|
+
message: `models cache is ${Math.round(ageDays)} days old`,
|
|
79
|
+
hint: "Open the dashboard and click 'Fetch live' to refresh."
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
results.push({ category: CATEGORY, name: `${name}/models-cache`, status: "pass", message: `${Math.round(ageDays)}d old` });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
run
|
|
90
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const CATEGORY = "runtime";
|
|
7
|
+
|
|
8
|
+
function parseMajor(v) {
|
|
9
|
+
const m = /^v?(\d+)\./.exec(v);
|
|
10
|
+
return m ? Number(m[1]) : NaN;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function defaultGitShaResolver(cwd) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const child = spawn("git", ["rev-parse", "--short", "HEAD"], { cwd, timeout: 2000 });
|
|
16
|
+
let out = "";
|
|
17
|
+
child.stdout?.on("data", (d) => { out += d.toString(); });
|
|
18
|
+
child.on("close", (code) => resolve(code === 0 ? out.trim() : null));
|
|
19
|
+
child.on("error", () => resolve(null));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getPackageJsonCandidates(baseDir) {
|
|
24
|
+
const candidates = [];
|
|
25
|
+
if (baseDir) {
|
|
26
|
+
candidates.push(join(baseDir, "mcp", "package.json"));
|
|
27
|
+
candidates.push(join(baseDir, "package.json"));
|
|
28
|
+
}
|
|
29
|
+
const metaUrl = import.meta.url ?? null;
|
|
30
|
+
if (metaUrl) {
|
|
31
|
+
try {
|
|
32
|
+
candidates.push(fileURLToPath(new URL("../../package.json", metaUrl)));
|
|
33
|
+
} catch {}
|
|
34
|
+
try {
|
|
35
|
+
candidates.push(fileURLToPath(new URL("../package.json", metaUrl)));
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
return candidates;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function run(opts = {}) {
|
|
42
|
+
const results = [];
|
|
43
|
+
const nodeVersion = opts.nodeVersionOverride ?? process.version;
|
|
44
|
+
const major = parseMajor(nodeVersion);
|
|
45
|
+
results.push({
|
|
46
|
+
category: CATEGORY,
|
|
47
|
+
name: "node-version",
|
|
48
|
+
status: major >= 20 ? "pass" : "fail",
|
|
49
|
+
message: `Node.js ${nodeVersion}`,
|
|
50
|
+
hint: major >= 20 ? undefined : "Upgrade to Node 20 or later (https://nodejs.org).",
|
|
51
|
+
});
|
|
52
|
+
results.push({ category: CATEGORY, name: "platform", status: "pass", message: `${process.platform} ${process.arch}` });
|
|
53
|
+
results.push({ category: CATEGORY, name: "arch", status: "pass", message: process.arch });
|
|
54
|
+
|
|
55
|
+
let version = "0.0.0";
|
|
56
|
+
for (const pkgPath of getPackageJsonCandidates(opts.baseDir)) {
|
|
57
|
+
try {
|
|
58
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
59
|
+
if (pkg.name === "perplexity-user-mcp" && pkg.version) {
|
|
60
|
+
version = pkg.version;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
results.push({
|
|
66
|
+
category: CATEGORY,
|
|
67
|
+
name: "package-version",
|
|
68
|
+
status: "pass",
|
|
69
|
+
message: `perplexity-user-mcp ${version}`,
|
|
70
|
+
detail: { version },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const gitDir = opts.gitDirOverride ?? join(process.cwd(), ".git");
|
|
74
|
+
const resolver = opts.gitShaResolverOverride ?? defaultGitShaResolver;
|
|
75
|
+
if (!existsSync(gitDir)) {
|
|
76
|
+
results.push({ category: CATEGORY, name: "git-sha", status: "skip", message: "not a git checkout" });
|
|
77
|
+
} else {
|
|
78
|
+
const sha = await resolver(process.cwd());
|
|
79
|
+
if (sha) {
|
|
80
|
+
results.push({ category: CATEGORY, name: "git-sha", status: "pass", message: sha });
|
|
81
|
+
} else {
|
|
82
|
+
results.push({ category: CATEGORY, name: "git-sha", status: "skip", message: "git not on PATH" });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { run };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import "../chunk-4UEJOM6W.mjs";
|
|
2
|
+
|
|
3
|
+
// src/checks/runtime.js
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
var CATEGORY = "runtime";
|
|
9
|
+
function parseMajor(v) {
|
|
10
|
+
const m = /^v?(\d+)\./.exec(v);
|
|
11
|
+
return m ? Number(m[1]) : NaN;
|
|
12
|
+
}
|
|
13
|
+
function defaultGitShaResolver(cwd) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const child = spawn("git", ["rev-parse", "--short", "HEAD"], { cwd, timeout: 2e3 });
|
|
16
|
+
let out = "";
|
|
17
|
+
child.stdout?.on("data", (d) => {
|
|
18
|
+
out += d.toString();
|
|
19
|
+
});
|
|
20
|
+
child.on("close", (code) => resolve(code === 0 ? out.trim() : null));
|
|
21
|
+
child.on("error", () => resolve(null));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function getPackageJsonCandidates(baseDir) {
|
|
25
|
+
const candidates = [];
|
|
26
|
+
if (baseDir) {
|
|
27
|
+
candidates.push(join(baseDir, "mcp", "package.json"));
|
|
28
|
+
candidates.push(join(baseDir, "package.json"));
|
|
29
|
+
}
|
|
30
|
+
const metaUrl = import.meta.url ?? null;
|
|
31
|
+
if (metaUrl) {
|
|
32
|
+
try {
|
|
33
|
+
candidates.push(fileURLToPath(new URL("../../package.json", metaUrl)));
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
candidates.push(fileURLToPath(new URL("../package.json", metaUrl)));
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return candidates;
|
|
42
|
+
}
|
|
43
|
+
async function run(opts = {}) {
|
|
44
|
+
const results = [];
|
|
45
|
+
const nodeVersion = opts.nodeVersionOverride ?? process.version;
|
|
46
|
+
const major = parseMajor(nodeVersion);
|
|
47
|
+
results.push({
|
|
48
|
+
category: CATEGORY,
|
|
49
|
+
name: "node-version",
|
|
50
|
+
status: major >= 20 ? "pass" : "fail",
|
|
51
|
+
message: `Node.js ${nodeVersion}`,
|
|
52
|
+
hint: major >= 20 ? void 0 : "Upgrade to Node 20 or later (https://nodejs.org)."
|
|
53
|
+
});
|
|
54
|
+
results.push({ category: CATEGORY, name: "platform", status: "pass", message: `${process.platform} ${process.arch}` });
|
|
55
|
+
results.push({ category: CATEGORY, name: "arch", status: "pass", message: process.arch });
|
|
56
|
+
let version = "0.0.0";
|
|
57
|
+
for (const pkgPath of getPackageJsonCandidates(opts.baseDir)) {
|
|
58
|
+
try {
|
|
59
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
60
|
+
if (pkg.name === "perplexity-user-mcp" && pkg.version) {
|
|
61
|
+
version = pkg.version;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
results.push({
|
|
68
|
+
category: CATEGORY,
|
|
69
|
+
name: "package-version",
|
|
70
|
+
status: "pass",
|
|
71
|
+
message: `perplexity-user-mcp ${version}`,
|
|
72
|
+
detail: { version }
|
|
73
|
+
});
|
|
74
|
+
const gitDir = opts.gitDirOverride ?? join(process.cwd(), ".git");
|
|
75
|
+
const resolver = opts.gitShaResolverOverride ?? defaultGitShaResolver;
|
|
76
|
+
if (!existsSync(gitDir)) {
|
|
77
|
+
results.push({ category: CATEGORY, name: "git-sha", status: "skip", message: "not a git checkout" });
|
|
78
|
+
} else {
|
|
79
|
+
const sha = await resolver(process.cwd());
|
|
80
|
+
if (sha) {
|
|
81
|
+
results.push({ category: CATEGORY, name: "git-sha", status: "pass", message: sha });
|
|
82
|
+
} else {
|
|
83
|
+
results.push({ category: CATEGORY, name: "git-sha", status: "skip", message: "git not on PATH" });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
run
|
|
90
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const CATEGORY = "vault";
|
|
5
|
+
|
|
6
|
+
async function tryKeychain() {
|
|
7
|
+
try {
|
|
8
|
+
const mod = await import('keytar');
|
|
9
|
+
const keytar = mod.default ?? mod;
|
|
10
|
+
const hex = await keytar.getPassword("perplexity-user-mcp", "vault-master-key");
|
|
11
|
+
return { available: true, hasKey: !!hex };
|
|
12
|
+
} catch {
|
|
13
|
+
return { available: false, hasKey: false };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function keychainExpected() {
|
|
18
|
+
return process.platform === "win32" || process.platform === "darwin" ||
|
|
19
|
+
(process.platform === "linux" && !process.env.CI);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function run(opts = {}) {
|
|
23
|
+
const results = [];
|
|
24
|
+
const dir = opts.configDir;
|
|
25
|
+
const profile = opts.profile ?? "default";
|
|
26
|
+
const enc = join(dir, "profiles", profile, "vault.enc");
|
|
27
|
+
const plain = join(dir, "profiles", profile, "vault.json");
|
|
28
|
+
const envPass = process.env.PERPLEXITY_VAULT_PASSPHRASE;
|
|
29
|
+
const kc = await tryKeychain();
|
|
30
|
+
|
|
31
|
+
// Encryption mode (separate from unseal path so plaintext opt-out is always a warn, not a skip).
|
|
32
|
+
if (existsSync(plain)) {
|
|
33
|
+
results.push({
|
|
34
|
+
category: CATEGORY,
|
|
35
|
+
name: "encryption",
|
|
36
|
+
status: "warn",
|
|
37
|
+
message: "plaintext vault.json (security.encryptCookies=false)",
|
|
38
|
+
hint: "Re-run login without --plain-cookies to enable AES-256-GCM at rest.",
|
|
39
|
+
});
|
|
40
|
+
} else if (existsSync(enc)) {
|
|
41
|
+
results.push({ category: CATEGORY, name: "encryption", status: "pass", message: "AES-256-GCM (vault.enc)" });
|
|
42
|
+
} else {
|
|
43
|
+
results.push({ category: CATEGORY, name: "encryption", status: "skip", message: "no vault yet" });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Unseal path resolution, matching vault.js getMasterKey() priority.
|
|
47
|
+
if (kc.hasKey) {
|
|
48
|
+
results.push({ category: CATEGORY, name: "unseal-path", status: "pass", message: "OS keychain holds master key" });
|
|
49
|
+
if (envPass) {
|
|
50
|
+
results.push({
|
|
51
|
+
category: CATEGORY,
|
|
52
|
+
name: "keychain-preferred",
|
|
53
|
+
status: "warn",
|
|
54
|
+
message: "PERPLEXITY_VAULT_PASSPHRASE is also set — keychain wins, but consider removing the env var",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
} else if (envPass) {
|
|
58
|
+
const hasKc = kc.available;
|
|
59
|
+
results.push({
|
|
60
|
+
category: CATEGORY,
|
|
61
|
+
name: "unseal-path",
|
|
62
|
+
status: "pass",
|
|
63
|
+
message: `env var ${hasKc ? "(keychain available but empty)" : "(keychain unavailable — expected on headless Linux)"}`,
|
|
64
|
+
});
|
|
65
|
+
if (hasKc) {
|
|
66
|
+
results.push({
|
|
67
|
+
category: CATEGORY,
|
|
68
|
+
name: "keychain-preferred",
|
|
69
|
+
status: "warn",
|
|
70
|
+
message: "keychain is available — moving the master key there would remove the passphrase from IDE config files",
|
|
71
|
+
hint: "Run `npx perplexity-user-mcp login` once with the env var unset; the key will be written to keychain.",
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
results.push({ category: CATEGORY, name: "keychain-preferred", status: "skip", message: "keychain not applicable" });
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// No keychain, no env var.
|
|
78
|
+
if (!existsSync(enc) && !existsSync(plain)) {
|
|
79
|
+
results.push({ category: CATEGORY, name: "unseal-path", status: "skip", message: "no vault to unseal yet" });
|
|
80
|
+
} else if (existsSync(plain)) {
|
|
81
|
+
results.push({ category: CATEGORY, name: "unseal-path", status: "pass", message: "plaintext — no key required" });
|
|
82
|
+
} else {
|
|
83
|
+
const ttyLikely = process.stdin?.isTTY === true;
|
|
84
|
+
results.push({
|
|
85
|
+
category: CATEGORY,
|
|
86
|
+
name: "unseal-path",
|
|
87
|
+
status: ttyLikely ? "warn" : "fail",
|
|
88
|
+
message: ttyLikely
|
|
89
|
+
? "no keychain, no env var — TTY prompt will be required on next use"
|
|
90
|
+
: "vault locked: no keychain, no env var, no TTY",
|
|
91
|
+
hint: keychainExpected()
|
|
92
|
+
? "Install libsecret+gnome-keyring (Linux), or set PERPLEXITY_VAULT_PASSPHRASE."
|
|
93
|
+
: "Set PERPLEXITY_VAULT_PASSPHRASE in your MCP config env.",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return results;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { run };
|