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/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# perplexity-user-mcp
|
|
2
|
+
|
|
3
|
+
Perplexity AI MCP server. Runs a persistent Chromium session via [patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright) to serve MCP tools for search, reasoning, research, and Computer mode from your existing Perplexity account.
|
|
4
|
+
|
|
5
|
+
Also available as the bundled MCP inside the [Perplexity Internal VS Code extension](https://github.com/Automations-Project/VSCode-Perplexity-MCP); this package is the same server, runnable standalone.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g perplexity-user-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
or run on demand with `npx`:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx perplexity-user-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The server speaks MCP over stdio, so normally you point your MCP client (Claude Desktop, Cursor, Windsurf, Cline, Claude Code, Amp, Codex CLI) at the binary rather than invoking it directly.
|
|
20
|
+
|
|
21
|
+
## First run & login
|
|
22
|
+
|
|
23
|
+
Login is interactive — Perplexity emails a one-time code that you have to paste back into the prompt. The MCP `perplexity_login` tool only returns instructions, because MCP tool calls cannot display interactive prompts. You log in either through the CLI or, if you have the VS Code extension, through its dashboard.
|
|
24
|
+
|
|
25
|
+
Quick start from a fresh machine:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g perplexity-user-mcp
|
|
29
|
+
npx perplexity-user-mcp install-speed-boost # optional, strongly recommended
|
|
30
|
+
npx perplexity-user-mcp login --mode auto --email me@example.com
|
|
31
|
+
# terminal will prompt for the OTP from your email
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`--mode auto` runs the email + OTP flow over HTTP (impit-backed if Speed Boost is installed; otherwise driven through the browser). `--mode manual` opens a visible browser window so you can sign in with Google, GitHub, or Apple SSO.
|
|
35
|
+
|
|
36
|
+
Session state lives at `~/.perplexity-mcp/` (cookies, profile, models cache). Delete that directory to start over, or use `npx perplexity-user-mcp logout --purge`.
|
|
37
|
+
|
|
38
|
+
Programmatic library use is supported but logging in still happens via the CLI command — the library exposes the runner spawn helpers but does not provide an interactive prompt.
|
|
39
|
+
|
|
40
|
+
## Speed Boost (impit)
|
|
41
|
+
|
|
42
|
+
Speed Boost is an optional Rust-backed HTTP client (`impit`) that lets the server skip the browser for the tools that only need a cookie jar. Once installed it is auto-detected and used transparently — there is no env var to flip on.
|
|
43
|
+
|
|
44
|
+
Tools that benefit from Speed Boost:
|
|
45
|
+
|
|
46
|
+
- `perplexity_sync_cloud`
|
|
47
|
+
- `perplexity_hydrate_cloud_entry`
|
|
48
|
+
- `perplexity_retrieve`
|
|
49
|
+
- `perplexity_login` (auto-used when installed; opt out with `PERPLEXITY_DISABLE_IMPIT_LOGIN=1` or `--no-impit` on `login`)
|
|
50
|
+
- `perplexity_export`
|
|
51
|
+
- `perplexity_models`
|
|
52
|
+
|
|
53
|
+
Tools that still use the browser: `perplexity_search`, `perplexity_reason`, `perplexity_research`, `perplexity_compute`, `perplexity_ask`. (Search-via-impit is an opt-in pilot.)
|
|
54
|
+
|
|
55
|
+
Install:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx perplexity-user-mcp install-speed-boost
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Uninstall:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx perplexity-user-mcp uninstall-speed-boost
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
There is no opt-out for the cloud-sync / hydrate / retrieve / export / models impit paths — every one of them falls back to the browser automatically on any impit failure (Cloudflare challenge, network error, parse error, etc.). The opt-out env vars only apply to login.
|
|
68
|
+
|
|
69
|
+
Speed Boost lives at `~/.perplexity-mcp/native-deps/node_modules/impit/`. You can remove it with the uninstall command above or with `rm -rf ~/.perplexity-mcp/native-deps/`.
|
|
70
|
+
|
|
71
|
+
## Browser requirement
|
|
72
|
+
|
|
73
|
+
The server automates a real browser to reach Perplexity (Cloudflare-protected). Any of these work out of the box, probed in the order listed:
|
|
74
|
+
|
|
75
|
+
1. **Google Chrome** *(recommended — best Cloudflare compatibility)*
|
|
76
|
+
2. **Microsoft Edge** (all three platforms)
|
|
77
|
+
3. **System Chromium** (mainly Linux)
|
|
78
|
+
4. **Brave Browser** (Chromium-based — works unchanged)
|
|
79
|
+
5. **Patchright's bundled Chromium**, downloaded with:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx patchright install chromium
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
If none of those are installed the server exits at startup with instructions.
|
|
86
|
+
|
|
87
|
+
### Picking a specific browser
|
|
88
|
+
|
|
89
|
+
All overrides are optional and evaluated at call time:
|
|
90
|
+
|
|
91
|
+
| Variable | Effect |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `PERPLEXITY_BROWSER_PATH` | Absolute path to an executable. Takes precedence over auto-detection. |
|
|
94
|
+
| `PERPLEXITY_BROWSER_CHANNEL` | `chrome` \| `msedge` \| `chromium`. Controls which Patchright channel is used. |
|
|
95
|
+
| `PERPLEXITY_CHROME_PATH` | Legacy alias for `PERPLEXITY_BROWSER_PATH`. Still honored. |
|
|
96
|
+
|
|
97
|
+
## CLI commands
|
|
98
|
+
|
|
99
|
+
Most-used commands. Run `npx perplexity-user-mcp --help` for the full list (daemon, tunnel providers, etc.).
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx perplexity-user-mcp # start MCP stdio server
|
|
103
|
+
npx perplexity-user-mcp login [--profile X] [--mode auto|manual] [--plain-cookies]
|
|
104
|
+
npx perplexity-user-mcp logout [--profile X] [--purge]
|
|
105
|
+
npx perplexity-user-mcp status [--profile X] [--all]
|
|
106
|
+
npx perplexity-user-mcp doctor [--profile X] [--probe] [--all] [--report]
|
|
107
|
+
npx perplexity-user-mcp install-browser
|
|
108
|
+
npx perplexity-user-mcp install-speed-boost
|
|
109
|
+
npx perplexity-user-mcp uninstall-speed-boost
|
|
110
|
+
npx perplexity-user-mcp add-account [--name X] [--email Y] [--mode auto|manual] [--plain-cookies]
|
|
111
|
+
npx perplexity-user-mcp switch-account <name>
|
|
112
|
+
npx perplexity-user-mcp list-accounts
|
|
113
|
+
npx perplexity-user-mcp export <id> --format pdf|md|docx [--out path]
|
|
114
|
+
npx perplexity-user-mcp open <id> [--viewer obsidian|typora|logseq|system]
|
|
115
|
+
npx perplexity-user-mcp rebuild-history-index [--profile X]
|
|
116
|
+
npx perplexity-user-mcp sync-cloud [--profile X] [--page-size N] [--verbose]
|
|
117
|
+
npx perplexity-user-mcp daemon start [--port N] [--tunnel]
|
|
118
|
+
npx perplexity-user-mcp --version
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## MCP client configuration
|
|
122
|
+
|
|
123
|
+
Example `mcp.json` entry (Cursor, Windsurf, Claude Code format):
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"mcpServers": {
|
|
128
|
+
"perplexity": {
|
|
129
|
+
"command": "npx",
|
|
130
|
+
"args": ["-y", "perplexity-user-mcp"]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Claude Desktop (`claude_desktop_config.json`) uses the same shape.
|
|
137
|
+
|
|
138
|
+
## Environment variables
|
|
139
|
+
|
|
140
|
+
| Variable | Purpose |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `PERPLEXITY_CONFIG_DIR` | Override `~/.perplexity-mcp` config/profile location. |
|
|
143
|
+
| `PERPLEXITY_BROWSER_PATH` | Explicit browser executable path (skips auto-detection). |
|
|
144
|
+
| `PERPLEXITY_BROWSER_CHANNEL` | `chrome` / `msedge` / `chromium`. Channel passed to Patchright. |
|
|
145
|
+
| `PERPLEXITY_CHROME_PATH` | **Legacy** alias for `PERPLEXITY_BROWSER_PATH`. Still honored. |
|
|
146
|
+
| `PERPLEXITY_HEADLESS_ONLY` | `1` to skip the headed Turnstile bootstrap and rely on cached `cf_clearance`. Useful on servers; fails if no clearance is cached yet. |
|
|
147
|
+
| `PERPLEXITY_SESSION_TOKEN` | Pre-supplied `__Secure-next-auth.session-token` (skips interactive login). |
|
|
148
|
+
| `PERPLEXITY_CSRF_TOKEN` | Optional companion to `PERPLEXITY_SESSION_TOKEN`. |
|
|
149
|
+
| `PERPLEXITY_DISABLE_IMPIT_LOGIN` | `1` to force browser-driven login even when Speed Boost is installed. |
|
|
150
|
+
| `PERPLEXITY_VAULT_PASSPHRASE` | Env-var master-key fallback for headless Linux (no keychain). |
|
|
151
|
+
|
|
152
|
+
## Tools exposed over MCP
|
|
153
|
+
|
|
154
|
+
- `perplexity_search` — fast web search with citations
|
|
155
|
+
- `perplexity_reason` — step-by-step reasoning (Pro tier)
|
|
156
|
+
- `perplexity_research` — deep multi-section reports (Pro tier)
|
|
157
|
+
- `perplexity_ask` — flexible queries with explicit model/mode/follow-up control
|
|
158
|
+
- `perplexity_compute` — Computer mode / ASI (requires Computer-mode access — typically Max)
|
|
159
|
+
- `perplexity_models` — list models, account tier, rate limits
|
|
160
|
+
- `perplexity_retrieve` — poll a pending research/compute task
|
|
161
|
+
- `perplexity_export` — export a saved history entry as PDF, Markdown, or DOCX (uses Perplexity's native export endpoint with a local Markdown fallback)
|
|
162
|
+
- `perplexity_sync_cloud` — sync Perplexity cloud thread history into the local history store
|
|
163
|
+
- `perplexity_hydrate_cloud_entry` — hydrate a single cloud-backed history entry on demand
|
|
164
|
+
- `perplexity_list_researches` / `perplexity_get_research` — saved research history
|
|
165
|
+
- `perplexity_login` — returns login instructions (interactive login runs via the CLI / extension)
|
|
166
|
+
- `perplexity_doctor` — run diagnostic checks across browser, profile, auth, and network and return a Markdown report (pass `probe:true` for a live search probe)
|
|
167
|
+
|
|
168
|
+
## Library use
|
|
169
|
+
|
|
170
|
+
Subpath exports are published for embedding the same runtime inside other Node tooling:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { PerplexityClient } from "perplexity-user-mcp/client";
|
|
174
|
+
import { CONFIG_DIR, BROWSER_DATA_DIR } from "perplexity-user-mcp/config";
|
|
175
|
+
import { readHistory } from "perplexity-user-mcp";
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Logging in still goes through the CLI (`npx perplexity-user-mcp login`) — the library does not expose an interactive prompt.
|
|
179
|
+
|
|
180
|
+
## Requirements
|
|
181
|
+
|
|
182
|
+
- Node.js >= 20
|
|
183
|
+
- A browser runtime — any of: real Chrome, Microsoft Edge, Brave, system Chromium, or patchright's bundled Chromium (see the [Browser requirement](#browser-requirement) section)
|
|
184
|
+
- An active Perplexity account (free tier works; Pro/Max unlock reason/research/compute)
|
|
185
|
+
|
|
186
|
+
## Issues
|
|
187
|
+
|
|
188
|
+
Bug reports and feature requests: <https://github.com/Automations-Project/VSCode-Perplexity-MCP/issues>.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT — see [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface DownloadedAttachment {
|
|
2
|
+
filename: string;
|
|
3
|
+
path: string;
|
|
4
|
+
sizeBytes: number;
|
|
5
|
+
mimeType?: string;
|
|
6
|
+
kind: "image" | "file";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare const MAX_ATTACHMENT_BYTES: number;
|
|
10
|
+
declare function sanitizeAttachmentFilename(name: string): string;
|
|
11
|
+
declare function inferAttachmentKind(mimeType?: string): "image" | "file";
|
|
12
|
+
declare function downloadAttachment(options: {
|
|
13
|
+
download: (url: string, targetPath: string) => Promise<unknown>;
|
|
14
|
+
url: string;
|
|
15
|
+
attachmentsDir: string;
|
|
16
|
+
filename?: string;
|
|
17
|
+
mimeType?: string;
|
|
18
|
+
}): Promise<DownloadedAttachment>;
|
|
19
|
+
|
|
20
|
+
export { type DownloadedAttachment, MAX_ATTACHMENT_BYTES, downloadAttachment, inferAttachmentKind, sanitizeAttachmentFilename };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "./chunk-4UEJOM6W.mjs";
|
|
2
|
+
|
|
3
|
+
// src/attachments.js
|
|
4
|
+
import { mkdirSync, statSync } from "fs";
|
|
5
|
+
import { basename, join } from "path";
|
|
6
|
+
var MAX_ATTACHMENT_BYTES = 50 * 1024 * 1024;
|
|
7
|
+
function sanitizeAttachmentFilename(name) {
|
|
8
|
+
const cleaned = String(name ?? "").replace(/[<>:"/\\|?*\u0000-\u001F]/g, "-").replace(/\s+/g, " ").trim();
|
|
9
|
+
return cleaned || "attachment";
|
|
10
|
+
}
|
|
11
|
+
function inferAttachmentKind(mimeType) {
|
|
12
|
+
return String(mimeType ?? "").startsWith("image/") ? "image" : "file";
|
|
13
|
+
}
|
|
14
|
+
async function downloadAttachment(options) {
|
|
15
|
+
const {
|
|
16
|
+
download,
|
|
17
|
+
url,
|
|
18
|
+
attachmentsDir,
|
|
19
|
+
filename,
|
|
20
|
+
mimeType
|
|
21
|
+
} = options;
|
|
22
|
+
mkdirSync(attachmentsDir, { recursive: true });
|
|
23
|
+
const safeFilename = sanitizeAttachmentFilename(filename || basename(new URL(url).pathname));
|
|
24
|
+
const targetPath = join(attachmentsDir, safeFilename);
|
|
25
|
+
await download(url, targetPath);
|
|
26
|
+
const sizeBytes = statSync(targetPath).size;
|
|
27
|
+
if (sizeBytes > MAX_ATTACHMENT_BYTES) {
|
|
28
|
+
throw new Error(`Attachment '${safeFilename}' exceeds the 50 MB inline cap.`);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
filename: safeFilename,
|
|
32
|
+
path: targetPath,
|
|
33
|
+
sizeBytes,
|
|
34
|
+
mimeType: mimeType ?? void 0,
|
|
35
|
+
kind: inferAttachmentKind(mimeType)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
MAX_ATTACHMENT_BYTES,
|
|
40
|
+
downloadAttachment,
|
|
41
|
+
inferAttachmentKind,
|
|
42
|
+
sanitizeAttachmentFilename
|
|
43
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const CATEGORY = "browser";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read the Chrome-family browser's version string without launching the
|
|
7
|
+
* browser GUI.
|
|
8
|
+
*
|
|
9
|
+
* Why this is non-trivial: on Windows, `chrome.exe --version` invoked from a
|
|
10
|
+
* non-console parent (the VS Code extension host) forks the browser process —
|
|
11
|
+
* the original exits with code 0 and empty stdout (which is why the doctor
|
|
12
|
+
* report's `chrome-family` message was blank), and the forked children stay
|
|
13
|
+
* alive as visible Chrome windows. Every `runDoctor()` call (Run, Deep check,
|
|
14
|
+
* Capture diagnostics, Export) was therefore spawning a permanent visible
|
|
15
|
+
* window.
|
|
16
|
+
*
|
|
17
|
+
* Fix: on Windows, query the PE header's ProductVersion via PowerShell's
|
|
18
|
+
* `Get-Item ... .VersionInfo.ProductVersion` — no browser launch, returns
|
|
19
|
+
* the same string the user sees in File Properties → Details. On macOS /
|
|
20
|
+
* Linux, `--version` is a true CLI app contract and remains safe.
|
|
21
|
+
*/
|
|
22
|
+
function probeVersion(path) {
|
|
23
|
+
if (process.platform === "win32") {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
// Single-quote escape: PowerShell single-quoted strings need '' to
|
|
26
|
+
// represent a literal '. -LiteralPath bypasses wildcard expansion so
|
|
27
|
+
// bracketed install paths (e.g. Program Files (x86)) are safe.
|
|
28
|
+
const escaped = path.replace(/'/g, "''");
|
|
29
|
+
const child = spawn(
|
|
30
|
+
"powershell.exe",
|
|
31
|
+
[
|
|
32
|
+
"-NoProfile",
|
|
33
|
+
"-NonInteractive",
|
|
34
|
+
"-Command",
|
|
35
|
+
`(Get-Item -LiteralPath '${escaped}').VersionInfo.ProductVersion`,
|
|
36
|
+
],
|
|
37
|
+
{ timeout: 3000, windowsHide: true },
|
|
38
|
+
);
|
|
39
|
+
let out = "";
|
|
40
|
+
let err = "";
|
|
41
|
+
child.stdout?.on("data", (d) => { out += d.toString(); });
|
|
42
|
+
child.stderr?.on("data", (d) => { err += d.toString(); });
|
|
43
|
+
child.on("close", (code) => {
|
|
44
|
+
const trimmed = out.trim();
|
|
45
|
+
if (code === 0 && trimmed) resolve(trimmed);
|
|
46
|
+
else reject(new Error(err.trim() || `version probe exited ${code}`));
|
|
47
|
+
});
|
|
48
|
+
child.on("error", reject);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const child = spawn(path, ["--version"], { timeout: 2000 });
|
|
53
|
+
let out = "";
|
|
54
|
+
let err = "";
|
|
55
|
+
child.stdout?.on("data", (d) => { out += d.toString(); });
|
|
56
|
+
child.stderr?.on("data", (d) => { err += d.toString(); });
|
|
57
|
+
child.on("close", (code) => {
|
|
58
|
+
if (code === 0) resolve(out.trim() || err.trim());
|
|
59
|
+
else reject(new Error(err.trim() || `version probe exited ${code}`));
|
|
60
|
+
});
|
|
61
|
+
child.on("error", reject);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function run(opts = {}) {
|
|
66
|
+
const results = [];
|
|
67
|
+
const findChrome = opts.findChromeOverride ?? (await import('../config.d.ts')).findChromeExecutable;
|
|
68
|
+
const versionProbe = opts.versionProbeOverride ?? probeVersion;
|
|
69
|
+
|
|
70
|
+
let chromePath = null;
|
|
71
|
+
try { chromePath = findChrome(); } catch { chromePath = null; }
|
|
72
|
+
|
|
73
|
+
if (!chromePath) {
|
|
74
|
+
results.push({
|
|
75
|
+
category: CATEGORY,
|
|
76
|
+
name: "chrome-family",
|
|
77
|
+
status: "fail",
|
|
78
|
+
message: "No Chrome / Edge / Chromium binary found on PATH or in standard locations.",
|
|
79
|
+
hint: "Run `npx perplexity-user-mcp install-browser` (Phase 4) or install Chrome/Edge manually.",
|
|
80
|
+
});
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const v = await versionProbe(chromePath);
|
|
86
|
+
results.push({ category: CATEGORY, name: "chrome-family", status: "pass", message: v });
|
|
87
|
+
} catch (err) {
|
|
88
|
+
results.push({ category: CATEGORY, name: "chrome-family", status: "pass", message: chromePath });
|
|
89
|
+
results.push({
|
|
90
|
+
category: CATEGORY,
|
|
91
|
+
name: "chrome-version",
|
|
92
|
+
status: "warn",
|
|
93
|
+
message: `version probe failed: ${err.message}`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return results;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export { run };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import "../chunk-4UEJOM6W.mjs";
|
|
2
|
+
|
|
3
|
+
// src/checks/browser.js
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
var CATEGORY = "browser";
|
|
6
|
+
function probeVersion(path) {
|
|
7
|
+
if (process.platform === "win32") {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const escaped = path.replace(/'/g, "''");
|
|
10
|
+
const child = spawn(
|
|
11
|
+
"powershell.exe",
|
|
12
|
+
[
|
|
13
|
+
"-NoProfile",
|
|
14
|
+
"-NonInteractive",
|
|
15
|
+
"-Command",
|
|
16
|
+
`(Get-Item -LiteralPath '${escaped}').VersionInfo.ProductVersion`
|
|
17
|
+
],
|
|
18
|
+
{ timeout: 3e3, windowsHide: true }
|
|
19
|
+
);
|
|
20
|
+
let out = "";
|
|
21
|
+
let err = "";
|
|
22
|
+
child.stdout?.on("data", (d) => {
|
|
23
|
+
out += d.toString();
|
|
24
|
+
});
|
|
25
|
+
child.stderr?.on("data", (d) => {
|
|
26
|
+
err += d.toString();
|
|
27
|
+
});
|
|
28
|
+
child.on("close", (code) => {
|
|
29
|
+
const trimmed = out.trim();
|
|
30
|
+
if (code === 0 && trimmed) resolve(trimmed);
|
|
31
|
+
else reject(new Error(err.trim() || `version probe exited ${code}`));
|
|
32
|
+
});
|
|
33
|
+
child.on("error", reject);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const child = spawn(path, ["--version"], { timeout: 2e3 });
|
|
38
|
+
let out = "";
|
|
39
|
+
let err = "";
|
|
40
|
+
child.stdout?.on("data", (d) => {
|
|
41
|
+
out += d.toString();
|
|
42
|
+
});
|
|
43
|
+
child.stderr?.on("data", (d) => {
|
|
44
|
+
err += d.toString();
|
|
45
|
+
});
|
|
46
|
+
child.on("close", (code) => {
|
|
47
|
+
if (code === 0) resolve(out.trim() || err.trim());
|
|
48
|
+
else reject(new Error(err.trim() || `version probe exited ${code}`));
|
|
49
|
+
});
|
|
50
|
+
child.on("error", reject);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function run(opts = {}) {
|
|
54
|
+
const results = [];
|
|
55
|
+
const findChrome = opts.findChromeOverride ?? (await import("../config.mjs")).findChromeExecutable;
|
|
56
|
+
const versionProbe = opts.versionProbeOverride ?? probeVersion;
|
|
57
|
+
let chromePath = null;
|
|
58
|
+
try {
|
|
59
|
+
chromePath = findChrome();
|
|
60
|
+
} catch {
|
|
61
|
+
chromePath = null;
|
|
62
|
+
}
|
|
63
|
+
if (!chromePath) {
|
|
64
|
+
results.push({
|
|
65
|
+
category: CATEGORY,
|
|
66
|
+
name: "chrome-family",
|
|
67
|
+
status: "fail",
|
|
68
|
+
message: "No Chrome / Edge / Chromium binary found on PATH or in standard locations.",
|
|
69
|
+
hint: "Run `npx perplexity-user-mcp install-browser` (Phase 4) or install Chrome/Edge manually."
|
|
70
|
+
});
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const v = await versionProbe(chromePath);
|
|
75
|
+
results.push({ category: CATEGORY, name: "chrome-family", status: "pass", message: v });
|
|
76
|
+
} catch (err) {
|
|
77
|
+
results.push({ category: CATEGORY, name: "chrome-family", status: "pass", message: chromePath });
|
|
78
|
+
results.push({
|
|
79
|
+
category: CATEGORY,
|
|
80
|
+
name: "chrome-version",
|
|
81
|
+
status: "warn",
|
|
82
|
+
message: `version probe failed: ${err.message}`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return results;
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
run
|
|
89
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const CATEGORY = "config";
|
|
5
|
+
|
|
6
|
+
async function run(opts = {}) {
|
|
7
|
+
const dir = opts.configDir;
|
|
8
|
+
const results = [];
|
|
9
|
+
|
|
10
|
+
if (!existsSync(dir)) {
|
|
11
|
+
results.push({
|
|
12
|
+
category: CATEGORY,
|
|
13
|
+
name: "config-dir",
|
|
14
|
+
status: "fail",
|
|
15
|
+
message: `Config dir not found: ${dir}`,
|
|
16
|
+
hint: "Run `npx perplexity-user-mcp login` to initialize it.",
|
|
17
|
+
});
|
|
18
|
+
return results;
|
|
19
|
+
}
|
|
20
|
+
results.push({
|
|
21
|
+
category: CATEGORY,
|
|
22
|
+
name: "config-dir",
|
|
23
|
+
status: "pass",
|
|
24
|
+
message: `Config dir present at ${dir}`,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (process.platform !== "win32") {
|
|
28
|
+
const mode = statSync(dir).mode & 0o777;
|
|
29
|
+
if (mode & 0o077) {
|
|
30
|
+
results.push({
|
|
31
|
+
category: CATEGORY,
|
|
32
|
+
name: "config-perms",
|
|
33
|
+
status: "warn",
|
|
34
|
+
message: `Config dir is world/group readable (mode 0${mode.toString(8)})`,
|
|
35
|
+
hint: "Run `chmod 700 ~/.perplexity-mcp`.",
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
results.push({ category: CATEGORY, name: "config-perms", status: "pass", message: "0700" });
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
results.push({ category: CATEGORY, name: "config-perms", status: "skip", message: "NTFS ACL (see icacls)" });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const activePath = join(dir, "active");
|
|
45
|
+
if (!existsSync(activePath)) {
|
|
46
|
+
results.push({
|
|
47
|
+
category: CATEGORY,
|
|
48
|
+
name: "active-pointer",
|
|
49
|
+
status: "warn",
|
|
50
|
+
message: "No active profile set.",
|
|
51
|
+
hint: "Add an account to create and activate your first profile.",
|
|
52
|
+
action: { label: "Add account", commandId: "Perplexity.addAccount" },
|
|
53
|
+
});
|
|
54
|
+
} else {
|
|
55
|
+
const name = readFileSync(activePath, "utf8").trim();
|
|
56
|
+
const metaPath = join(dir, "profiles", name, "meta.json");
|
|
57
|
+
if (!existsSync(metaPath)) {
|
|
58
|
+
results.push({
|
|
59
|
+
category: CATEGORY,
|
|
60
|
+
name: "active-pointer",
|
|
61
|
+
status: "fail",
|
|
62
|
+
message: `active -> '${name}' but profile does not exist`,
|
|
63
|
+
hint: "Run `npx perplexity-user-mcp list-accounts` and pick a real profile.",
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
results.push({ category: CATEGORY, name: "active-pointer", status: "pass", message: name });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const cfgJson = join(dir, "config.json");
|
|
71
|
+
if (!existsSync(cfgJson)) {
|
|
72
|
+
results.push({ category: CATEGORY, name: "config-json", status: "skip", message: "optional file absent" });
|
|
73
|
+
} else {
|
|
74
|
+
try {
|
|
75
|
+
JSON.parse(readFileSync(cfgJson, "utf8"));
|
|
76
|
+
results.push({ category: CATEGORY, name: "config-json", status: "pass", message: "valid" });
|
|
77
|
+
} catch (err) {
|
|
78
|
+
results.push({
|
|
79
|
+
category: CATEGORY,
|
|
80
|
+
name: "config-json",
|
|
81
|
+
status: "warn",
|
|
82
|
+
message: `config.json malformed: ${err.message}`,
|
|
83
|
+
hint: "Delete or fix the file — doctor reads it for reporting.githubIssueButton etc.",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { run };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import "../chunk-4UEJOM6W.mjs";
|
|
2
|
+
|
|
3
|
+
// src/checks/config.js
|
|
4
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
var CATEGORY = "config";
|
|
7
|
+
async function run(opts = {}) {
|
|
8
|
+
const dir = opts.configDir;
|
|
9
|
+
const results = [];
|
|
10
|
+
if (!existsSync(dir)) {
|
|
11
|
+
results.push({
|
|
12
|
+
category: CATEGORY,
|
|
13
|
+
name: "config-dir",
|
|
14
|
+
status: "fail",
|
|
15
|
+
message: `Config dir not found: ${dir}`,
|
|
16
|
+
hint: "Run `npx perplexity-user-mcp login` to initialize it."
|
|
17
|
+
});
|
|
18
|
+
return results;
|
|
19
|
+
}
|
|
20
|
+
results.push({
|
|
21
|
+
category: CATEGORY,
|
|
22
|
+
name: "config-dir",
|
|
23
|
+
status: "pass",
|
|
24
|
+
message: `Config dir present at ${dir}`
|
|
25
|
+
});
|
|
26
|
+
if (process.platform !== "win32") {
|
|
27
|
+
const mode = statSync(dir).mode & 511;
|
|
28
|
+
if (mode & 63) {
|
|
29
|
+
results.push({
|
|
30
|
+
category: CATEGORY,
|
|
31
|
+
name: "config-perms",
|
|
32
|
+
status: "warn",
|
|
33
|
+
message: `Config dir is world/group readable (mode 0${mode.toString(8)})`,
|
|
34
|
+
hint: "Run `chmod 700 ~/.perplexity-mcp`."
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
results.push({ category: CATEGORY, name: "config-perms", status: "pass", message: "0700" });
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
results.push({ category: CATEGORY, name: "config-perms", status: "skip", message: "NTFS ACL (see icacls)" });
|
|
41
|
+
}
|
|
42
|
+
const activePath = join(dir, "active");
|
|
43
|
+
if (!existsSync(activePath)) {
|
|
44
|
+
results.push({
|
|
45
|
+
category: CATEGORY,
|
|
46
|
+
name: "active-pointer",
|
|
47
|
+
status: "warn",
|
|
48
|
+
message: "No active profile set.",
|
|
49
|
+
hint: "Add an account to create and activate your first profile.",
|
|
50
|
+
action: { label: "Add account", commandId: "Perplexity.addAccount" }
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
const name = readFileSync(activePath, "utf8").trim();
|
|
54
|
+
const metaPath = join(dir, "profiles", name, "meta.json");
|
|
55
|
+
if (!existsSync(metaPath)) {
|
|
56
|
+
results.push({
|
|
57
|
+
category: CATEGORY,
|
|
58
|
+
name: "active-pointer",
|
|
59
|
+
status: "fail",
|
|
60
|
+
message: `active -> '${name}' but profile does not exist`,
|
|
61
|
+
hint: "Run `npx perplexity-user-mcp list-accounts` and pick a real profile."
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
results.push({ category: CATEGORY, name: "active-pointer", status: "pass", message: name });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const cfgJson = join(dir, "config.json");
|
|
68
|
+
if (!existsSync(cfgJson)) {
|
|
69
|
+
results.push({ category: CATEGORY, name: "config-json", status: "skip", message: "optional file absent" });
|
|
70
|
+
} else {
|
|
71
|
+
try {
|
|
72
|
+
JSON.parse(readFileSync(cfgJson, "utf8"));
|
|
73
|
+
results.push({ category: CATEGORY, name: "config-json", status: "pass", message: "valid" });
|
|
74
|
+
} catch (err) {
|
|
75
|
+
results.push({
|
|
76
|
+
category: CATEGORY,
|
|
77
|
+
name: "config-json",
|
|
78
|
+
status: "warn",
|
|
79
|
+
message: `config.json malformed: ${err.message}`,
|
|
80
|
+
hint: "Delete or fix the file \u2014 doctor reads it for reporting.githubIssueButton etc."
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
run
|
|
88
|
+
};
|