perplexity-user-mcp 0.8.39 → 0.8.44

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.
Files changed (133) hide show
  1. package/README.md +5 -0
  2. package/dist/attachments.d.ts +10 -20
  3. package/dist/browser-window.d.ts +1 -0
  4. package/dist/cf-warmup.d.ts +32 -0
  5. package/dist/checks/browser.d.ts +12 -100
  6. package/dist/checks/config.d.ts +25 -91
  7. package/dist/checks/ide.d.ts +31 -89
  8. package/dist/checks/mcp.d.ts +12 -61
  9. package/dist/checks/native-deps.d.ts +46 -131
  10. package/dist/checks/network.d.ts +13 -71
  11. package/dist/checks/probe.d.ts +20 -92
  12. package/dist/checks/profiles.d.ts +13 -99
  13. package/dist/checks/profiles.mjs +35 -0
  14. package/dist/checks/runtime.d.ts +24 -89
  15. package/dist/checks/vault.d.ts +13 -142
  16. package/dist/checks/vault.mjs +6 -11
  17. package/dist/{chunk-T6ARJK2P.mjs → chunk-2B5OQUXR.mjs} +6 -6
  18. package/dist/{chunk-2FPGJKCA.mjs → chunk-2EE7MNP2.mjs} +2 -2
  19. package/dist/{chunk-NJX4RBO6.mjs → chunk-2OVLCZHU.mjs} +28 -3
  20. package/dist/{chunk-WDIW33DA.mjs → chunk-3LUO5ATM.mjs} +1 -1
  21. package/dist/{chunk-B65IJQZJ.mjs → chunk-6E6XTHTG.mjs} +1 -1
  22. package/dist/{chunk-S677V2JU.mjs → chunk-C5I7KXHK.mjs} +32 -2
  23. package/dist/{chunk-TDXETAQT.mjs → chunk-DKEJZ4FI.mjs} +1 -1
  24. package/dist/{chunk-U7QPUNRH.mjs → chunk-DXR6EEZH.mjs} +26 -7
  25. package/dist/{chunk-HJIXH6CL.mjs → chunk-E3GRJXXJ.mjs} +2 -0
  26. package/dist/{chunk-RK4EBZJ3.mjs → chunk-E75J42W5.mjs} +11 -8
  27. package/dist/{chunk-452DK6OS.mjs → chunk-FNHYUE22.mjs} +2 -2
  28. package/dist/{chunk-D254EFYB.mjs → chunk-GBI2U336.mjs} +1 -1
  29. package/dist/chunk-GPUGKWXH.mjs +17 -0
  30. package/dist/{chunk-HNSPNCFH.mjs → chunk-KSNV3ZVY.mjs} +1 -1
  31. package/dist/{chunk-XTRJSV72.mjs → chunk-LGH5BSUY.mjs} +1 -1
  32. package/dist/{chunk-KJFX2ZXR.mjs → chunk-NMKNEEZB.mjs} +1 -1
  33. package/dist/{chunk-FKQ3HP4Q.mjs → chunk-TIWHN4IW.mjs} +1 -1
  34. package/dist/{chunk-V4U3JM4R.mjs → chunk-TSLRTZYR.mjs} +1 -1
  35. package/dist/{chunk-DQQISMYN.mjs → chunk-V4LHDNWJ.mjs} +2 -2
  36. package/dist/{chunk-C3HPFFTD.mjs → chunk-WHVJ724K.mjs} +84 -44
  37. package/dist/cli.d.ts +14 -1298
  38. package/dist/cli.mjs +35 -27
  39. package/dist/client.d.ts +27 -24
  40. package/dist/client.mjs +6 -6
  41. package/dist/cloud-sync.d.ts +65 -42
  42. package/dist/cloud-sync.mjs +8 -8
  43. package/dist/config.d.ts +35 -39
  44. package/dist/config.mjs +3 -3
  45. package/dist/cookie-jar.d.ts +77 -0
  46. package/dist/daemon/attach.d.ts +10 -11
  47. package/dist/daemon/attach.mjs +19 -17
  48. package/dist/daemon/audit.d.ts +5 -7
  49. package/dist/daemon/audit.mjs +2 -2
  50. package/dist/daemon/client-http.d.ts +10 -16
  51. package/dist/daemon/client-http.mjs +17 -17
  52. package/dist/daemon/index.d.ts +17 -14
  53. package/dist/daemon/index.mjs +18 -18
  54. package/dist/daemon/install-tunnel.d.ts +8 -34
  55. package/dist/daemon/install-tunnel.mjs +2 -2
  56. package/dist/daemon/launcher.d.ts +24 -29
  57. package/dist/daemon/launcher.mjs +16 -16
  58. package/dist/daemon/local-tokens.d.ts +23 -0
  59. package/dist/daemon/lockfile.d.ts +10 -12
  60. package/dist/daemon/lockfile.mjs +2 -2
  61. package/dist/daemon/oauth-consent-cache.d.ts +86 -0
  62. package/dist/daemon/oauth-provider.d.ts +132 -0
  63. package/dist/daemon/public-pages.d.ts +9 -0
  64. package/dist/daemon/security.d.ts +52 -0
  65. package/dist/daemon/server.d.ts +12 -83
  66. package/dist/daemon/server.mjs +11 -11
  67. package/dist/daemon/token.d.ts +7 -9
  68. package/dist/daemon/token.mjs +2 -2
  69. package/dist/daemon/tunnel-providers/cloudflared-named-setup.d.ts +140 -0
  70. package/dist/daemon/tunnel-providers/cloudflared-named.d.ts +45 -0
  71. package/dist/daemon/tunnel-providers/cloudflared-quick.d.ts +8 -0
  72. package/dist/daemon/tunnel-providers/index.d.ts +16 -327
  73. package/dist/daemon/tunnel-providers/index.mjs +3 -3
  74. package/dist/daemon/tunnel-providers/ngrok-config.d.ts +18 -0
  75. package/dist/daemon/tunnel-providers/ngrok.d.ts +68 -0
  76. package/dist/daemon/tunnel-providers/types.d.ts +56 -0
  77. package/dist/daemon/tunnel.d.ts +5 -7
  78. package/dist/debug-tracer.d.ts +2 -0
  79. package/dist/doctor-report.d.ts +17 -22
  80. package/dist/doctor.d.ts +12 -44
  81. package/dist/doctor.mjs +2 -2
  82. package/dist/export.d.ts +11 -18
  83. package/dist/export.mjs +4 -4
  84. package/dist/format.d.ts +52 -0
  85. package/dist/fs-utils.d.ts +8 -0
  86. package/dist/health-check.d.ts +1 -108
  87. package/dist/health-check.mjs +3 -3
  88. package/dist/history-store.d.ts +29 -65
  89. package/dist/history-store.mjs +2 -2
  90. package/dist/impit-login-runner.d.ts +1 -469
  91. package/dist/impit-login-runner.mjs +4 -4
  92. package/dist/index.d.ts +25 -149
  93. package/dist/index.mjs +22 -20
  94. package/dist/is-main-module.d.ts +9 -0
  95. package/dist/login-runner.d.ts +1 -333
  96. package/dist/login-runner.mjs +13 -13
  97. package/dist/login.d.ts +5 -0
  98. package/dist/logout.d.ts +2 -28
  99. package/dist/logout.mjs +3 -2
  100. package/dist/manual-login-runner.d.ts +1 -150
  101. package/dist/manual-login-runner.mjs +11 -11
  102. package/dist/{native-deps-IE4B55EL.mjs → native-deps-FCSYDL4W.mjs} +4 -4
  103. package/dist/native-deps.d.ts +36 -0
  104. package/dist/package-version.d.ts +1 -0
  105. package/dist/profiles.d.ts +41 -41
  106. package/dist/profiles.mjs +1 -1
  107. package/dist/prompts.d.ts +2 -0
  108. package/dist/redact.d.ts +14 -142
  109. package/dist/refresh.d.ts +11 -16
  110. package/dist/refresh.mjs +4 -4
  111. package/dist/reinit-watcher.d.ts +15 -24
  112. package/dist/reinit-watcher.mjs +2 -2
  113. package/dist/resources.d.ts +5 -0
  114. package/dist/safe-write.d.ts +16 -0
  115. package/dist/session-metadata.d.ts +45 -0
  116. package/dist/tool-config.d.ts +10 -0
  117. package/dist/tools.d.ts +23 -0
  118. package/dist/tty-prompt.d.ts +18 -34
  119. package/dist/vault.d.ts +114 -34
  120. package/dist/vault.mjs +6 -4
  121. package/dist/viewer-detect.d.ts +2 -4
  122. package/dist/viewers.d.ts +13 -18
  123. package/dist/viewers.mjs +1 -1
  124. package/package.json +3 -3
  125. package/dist/cloud-sync.d-Cqt6y18U.d.ts +0 -42
  126. package/dist/doctor.d-CXmUqOXX.d.ts +0 -43
  127. package/dist/history-store.d-BzjBF2m3.d.ts +0 -65
  128. package/dist/native-deps-BNThFHxa.d.ts +0 -175
  129. package/dist/profiles.d-DqS1oZWr.d.ts +0 -41
  130. package/dist/session-metadata-B9aV_n5g.d.ts +0 -148
  131. package/dist/vault.d-BSJWDLhp.d.ts +0 -37
  132. package/dist/viewer-detect.d-HWGnyFAA.d.ts +0 -4
  133. package/dist/viewers.d-BGCK6sw6.d.ts +0 -10
@@ -1,131 +1,46 @@
1
- import { createRequire } from 'node:module';
2
- import { readFileSync, existsSync } from 'node:fs';
3
- import { homedir } from 'node:os';
4
- import { join } from 'node:path';
5
-
6
- const CATEGORY = "native-deps";
7
-
8
- /**
9
- * Build a `require` that resolves as if invoked from `baseDir`. Falls back
10
- * to this module's own file when no baseDir is supplied. The fallback works
11
- * in plain Node ESM (CLI mode). In tsup-bundled CJS, `import.meta.url` is
12
- * polyfilled to undefined, so the extension MUST pass a baseDir derived
13
- * from `vscode.ExtensionContext.extensionUri` (e.g. `<extensionUri>/dist`).
14
- */
15
- function makeRequire(baseDir) {
16
- if (baseDir) {
17
- return createRequire(join(baseDir, "_resolver.js"));
18
- }
19
- const self = import.meta.url ?? null;
20
- if (!self) return null;
21
- try {
22
- // Resolve against this module's own file, which works in native Node ESM.
23
- return createRequire(self);
24
- } catch {
25
- return null;
26
- }
27
- }
28
-
29
- function resolveGotScrapingChain(baseDir) {
30
- const req = makeRequire(baseDir);
31
- if (!req) throw new Error("no resolver context (pass baseDir)");
32
- const hg = req.resolve("header-generator");
33
- const hgReq = createRequire(hg);
34
- const dp = hgReq.resolve("dot-prop");
35
- const dpReq = createRequire(dp);
36
- const io = dpReq.resolve("is-obj");
37
- return { hg, dp, io };
38
- }
39
-
40
- function getImpitRuntimeDirFallback() {
41
- return join(process.env.PERPLEXITY_CONFIG_DIR || join(homedir(), ".perplexity-mcp"), "native-deps");
42
- }
43
-
44
- async function run(opts = {}) {
45
- const results = [];
46
- const baseDir = opts.baseDir;
47
-
48
- // patchright — required
49
- try {
50
- const req = makeRequire(baseDir);
51
- if (!req) throw new Error("no resolver context");
52
- const pkgPath = req.resolve("patchright/package.json");
53
- const { version } = JSON.parse(readFileSync(pkgPath, "utf8"));
54
- results.push({ category: CATEGORY, name: "patchright", status: "pass", message: `patchright ${version}` });
55
- } catch {
56
- results.push({
57
- category: CATEGORY,
58
- name: "patchright",
59
- status: "fail",
60
- message: "patchright not resolvable",
61
- hint: "Run `pnpm install` or reinstall the VSIX.",
62
- });
63
- }
64
-
65
- // got-scraping packaging chain — carry-over #5 detector
66
- const resolveChain = opts.resolveChainOverride ?? (() => resolveGotScrapingChain(baseDir));
67
- try {
68
- const chain = resolveChain();
69
- results.push({
70
- category: CATEGORY,
71
- name: "got-scraping-chain",
72
- status: "pass",
73
- message: "header-generator -> dot-prop -> is-obj resolves",
74
- detail: chain,
75
- });
76
- } catch (err) {
77
- results.push({
78
- category: CATEGORY,
79
- name: "got-scraping-chain",
80
- status: "warn",
81
- message: "got-scraping packaging chain is broken — runtime will fall back to browser tier",
82
- detail: { chainError: err.message },
83
- hint: "Add the missing package to rootPackages in packages/extension/scripts/prepare-package-deps.mjs and rebuild the VSIX.",
84
- });
85
- }
86
-
87
- // impit (optional speed boost)
88
- let impitStatus = opts.impitStatusOverride;
89
- if (!impitStatus) {
90
- try {
91
- const { getImpitRuntimeDir } = await import('../refresh.d.ts');
92
- const runtimeDir = typeof getImpitRuntimeDir === "function" ? getImpitRuntimeDir() : getImpitRuntimeDirFallback();
93
- const marker = join(runtimeDir, "native-deps-state.json");
94
- const pkgPath = join(runtimeDir, "node_modules", "impit", "package.json");
95
- const state = existsSync(marker) ? JSON.parse(readFileSync(marker, "utf8")) : null;
96
- if (existsSync(pkgPath)) {
97
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
98
- impitStatus = {
99
- installed: true,
100
- version: pkg.version ?? state?.version ?? null,
101
- installedAt: state?.installedAt ?? null,
102
- };
103
- } else {
104
- impitStatus = { installed: false, version: state?.version ?? null, installedAt: state?.installedAt ?? null };
105
- }
106
- } catch {
107
- impitStatus = { installed: false, version: null };
108
- }
109
- }
110
- if (impitStatus.installed) {
111
- results.push({
112
- category: CATEGORY,
113
- name: "impit",
114
- status: "pass",
115
- message: `impit ${impitStatus.version ?? "(unknown version)"}${impitStatus.installedAt ? ` - installed ${impitStatus.installedAt}` : ""}`,
116
- });
117
- } else {
118
- results.push({
119
- category: CATEGORY,
120
- name: "impit",
121
- status: "skip",
122
- message: "not installed (optional — skips browser launches for sync, hydrate, retrieve, export, models, login)",
123
- hint: "Install with: `npx perplexity-user-mcp install-speed-boost` — or click 'Install Speed Boost' in the VS Code extension dashboard.",
124
- action: { label: "Install Speed Boost", commandId: "Perplexity.installSpeedBoost" },
125
- });
126
- }
127
-
128
- return results;
129
- }
130
-
131
- export { run };
1
+ export function run(opts?: {}): Promise<({
2
+ category: string;
3
+ name: string;
4
+ status: string;
5
+ message: string;
6
+ hint?: undefined;
7
+ detail?: undefined;
8
+ action?: undefined;
9
+ } | {
10
+ category: string;
11
+ name: string;
12
+ status: string;
13
+ message: string;
14
+ hint: string;
15
+ detail?: undefined;
16
+ action?: undefined;
17
+ } | {
18
+ category: string;
19
+ name: string;
20
+ status: string;
21
+ message: string;
22
+ detail: any;
23
+ hint?: undefined;
24
+ action?: undefined;
25
+ } | {
26
+ category: string;
27
+ name: string;
28
+ status: string;
29
+ message: string;
30
+ detail: {
31
+ chainError: any;
32
+ };
33
+ hint: string;
34
+ action?: undefined;
35
+ } | {
36
+ category: string;
37
+ name: string;
38
+ status: string;
39
+ message: string;
40
+ hint: string;
41
+ action: {
42
+ label: string;
43
+ commandId: string;
44
+ };
45
+ detail?: undefined;
46
+ })[]>;
@@ -1,71 +1,13 @@
1
- import { lookup } from 'node:dns/promises';
2
- import { request } from 'node:https';
3
-
4
- const CATEGORY = "network";
5
- const DEFAULT_HOST = "www.perplexity.ai";
6
-
7
- function httpsHead(url, { timeoutMs = 5000 } = {}) {
8
- return new Promise((resolve, reject) => {
9
- const req = request(url, { method: "HEAD", timeout: timeoutMs }, (res) => {
10
- resolve({ statusCode: res.statusCode, headers: res.headers });
11
- res.resume();
12
- });
13
- req.on("timeout", () => { req.destroy(new Error("HTTPS HEAD timeout")); });
14
- req.on("error", reject);
15
- req.end();
16
- });
17
- }
18
-
19
- async function run(opts = {}) {
20
- const results = [];
21
- const host = opts.host ?? DEFAULT_HOST;
22
- const dns = opts.dnsLookupOverride ?? lookup;
23
- const head = opts.httpsHeadOverride ?? ((u) => httpsHead(u));
24
-
25
- let addr = null;
26
- try {
27
- addr = await dns(host);
28
- results.push({ category: CATEGORY, name: "dns", status: "pass", message: `${host} -> ${addr.address}` });
29
- } catch (err) {
30
- results.push({
31
- category: CATEGORY,
32
- name: "dns",
33
- status: "fail",
34
- message: `DNS lookup failed: ${err.message}`,
35
- hint: "Check internet connection / proxy / VPN.",
36
- });
37
- return results;
38
- }
39
-
40
- let headRes;
41
- try {
42
- headRes = await head(`https://${host}${process.env.PERPLEXITY_LOGIN_PATH || "/account"}`);
43
- results.push({ category: CATEGORY, name: "https", status: "pass", message: `HEAD / status ${headRes.statusCode}` });
44
- } catch (err) {
45
- results.push({
46
- category: CATEGORY,
47
- name: "https",
48
- status: "fail",
49
- message: `HTTPS failed: ${err.message}`,
50
- hint: "TLS/MITM proxy? Corporate firewall?",
51
- });
52
- return results;
53
- }
54
-
55
- const isCf = String(headRes.headers?.server ?? "").toLowerCase().includes("cloudflare") && headRes.statusCode === 503;
56
- if (isCf) {
57
- results.push({
58
- category: CATEGORY,
59
- name: "cf-challenge",
60
- status: "warn",
61
- message: "Cloudflare challenge active — logins may need manual captcha.",
62
- hint: "Try login --mode manual if auto mode fails.",
63
- });
64
- } else {
65
- results.push({ category: CATEGORY, name: "cf-challenge", status: "pass", message: "no challenge detected" });
66
- }
67
-
68
- return results;
69
- }
70
-
71
- export { run };
1
+ export function run(opts?: {}): Promise<({
2
+ category: string;
3
+ name: string;
4
+ status: string;
5
+ message: string;
6
+ hint?: undefined;
7
+ } | {
8
+ category: string;
9
+ name: string;
10
+ status: string;
11
+ message: string;
12
+ hint: string;
13
+ })[]>;
@@ -1,93 +1,21 @@
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,
1
+ export function run(opts?: {}): Promise<{
2
+ category: string;
3
+ name: string;
4
+ status: string;
5
+ message: string;
6
+ }[] | {
7
+ category: string;
8
+ name: string;
9
+ status: string;
10
+ message: string;
11
+ hint: string;
12
+ }[] | {
13
+ category: string;
14
+ name: string;
15
+ status: string;
16
+ message: string;
17
+ detail: {
18
+ latencyMs: any;
19
+ sourceCount: any;
39
20
  };
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 };
21
+ }[]>;
@@ -1,99 +1,13 @@
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 };
1
+ export function run(opts?: {}): Promise<({
2
+ category: string;
3
+ name: string;
4
+ status: string;
5
+ message: string;
6
+ hint?: undefined;
7
+ } | {
8
+ category: string;
9
+ name: string;
10
+ status: string;
11
+ message: string;
12
+ hint: string;
13
+ })[]>;
@@ -82,6 +82,41 @@ async function run(opts = {}) {
82
82
  results.push({ category: CATEGORY, name: `${name}/models-cache`, status: "pass", message: `${Math.round(ageDays)}d old` });
83
83
  }
84
84
  }
85
+ const daemonStatusPath = join(pdir, "daemon-status.json");
86
+ if (!existsSync(daemonStatusPath)) {
87
+ results.push({ category: CATEGORY, name: `${name}/daemon-status`, status: "skip", message: "no daemon status file (stdio-only or not yet started)" });
88
+ } else {
89
+ try {
90
+ const ds = JSON.parse(readFileSync(daemonStatusPath, "utf8"));
91
+ if (!ds.authenticated) {
92
+ results.push({
93
+ category: CATEGORY,
94
+ name: `${name}/daemon-status`,
95
+ status: "warn",
96
+ message: `daemon last init: ${ds.lastInit} \u2014 authenticated: false (tier: ${ds.tier})${ds.error ? `, error: ${ds.error}` : ""}`,
97
+ hint: "Open the extension dashboard and click 'Refresh state' to trigger a daemon reinit."
98
+ });
99
+ } else {
100
+ results.push({
101
+ category: CATEGORY,
102
+ name: `${name}/daemon-status`,
103
+ status: "pass",
104
+ message: `authenticated as ${ds.tier}, last init: ${ds.lastInit} (${ds.initDurationMs}ms)`
105
+ });
106
+ }
107
+ } catch {
108
+ results.push({ category: CATEGORY, name: `${name}/daemon-status`, status: "warn", message: "daemon-status.json is corrupt or unreadable" });
109
+ }
110
+ }
111
+ const loginBrowserDataPath = join(pdir, "login-browser-data");
112
+ if (existsSync(loginBrowserDataPath)) {
113
+ results.push({
114
+ category: CATEGORY,
115
+ name: `${name}/login-browser-data`,
116
+ status: "info",
117
+ message: "login-browser-data directory present (leftover from a past login session; safe to ignore)"
118
+ });
119
+ }
85
120
  }
86
121
  return results;
87
122
  }
@@ -1,89 +1,24 @@
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 };
1
+ export function run(opts?: {}): Promise<({
2
+ category: string;
3
+ name: string;
4
+ status: string;
5
+ message: string;
6
+ hint: string | undefined;
7
+ detail?: undefined;
8
+ } | {
9
+ category: string;
10
+ name: string;
11
+ status: string;
12
+ message: string;
13
+ detail: {
14
+ version: string;
15
+ };
16
+ hint?: undefined;
17
+ } | {
18
+ category: string;
19
+ name: string;
20
+ status: string;
21
+ message: any;
22
+ hint?: undefined;
23
+ detail?: undefined;
24
+ })[]>;