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.
Files changed (125) hide show
  1. package/README.md +192 -0
  2. package/dist/attachments.d.ts +20 -0
  3. package/dist/attachments.mjs +43 -0
  4. package/dist/checks/browser.d.ts +100 -0
  5. package/dist/checks/browser.mjs +89 -0
  6. package/dist/checks/config.d.ts +91 -0
  7. package/dist/checks/config.mjs +88 -0
  8. package/dist/checks/ide.d.ts +89 -0
  9. package/dist/checks/ide.mjs +80 -0
  10. package/dist/checks/mcp.d.ts +61 -0
  11. package/dist/checks/mcp.mjs +56 -0
  12. package/dist/checks/native-deps.d.ts +131 -0
  13. package/dist/checks/native-deps.mjs +115 -0
  14. package/dist/checks/network.d.ts +71 -0
  15. package/dist/checks/network.mjs +70 -0
  16. package/dist/checks/probe.d.ts +93 -0
  17. package/dist/checks/probe.mjs +82 -0
  18. package/dist/checks/profiles.d.ts +99 -0
  19. package/dist/checks/profiles.mjs +90 -0
  20. package/dist/checks/runtime.d.ts +89 -0
  21. package/dist/checks/runtime.mjs +90 -0
  22. package/dist/checks/vault.d.ts +101 -0
  23. package/dist/checks/vault.mjs +90 -0
  24. package/dist/chunk-3B276PGG.mjs +115 -0
  25. package/dist/chunk-4UEJOM6W.mjs +9 -0
  26. package/dist/chunk-6EP2BLTV.mjs +205 -0
  27. package/dist/chunk-6YMQVLFX.mjs +146 -0
  28. package/dist/chunk-7JL36EBH.mjs +118 -0
  29. package/dist/chunk-DPGMKSSA.mjs +57 -0
  30. package/dist/chunk-H4BUAPPO.mjs +1950 -0
  31. package/dist/chunk-HMKLWVXB.mjs +109 -0
  32. package/dist/chunk-HTUAQRKH.mjs +125 -0
  33. package/dist/chunk-HU5B4FXS.mjs +139 -0
  34. package/dist/chunk-KCXM2M4B.mjs +1006 -0
  35. package/dist/chunk-LKJMLGFP.mjs +237 -0
  36. package/dist/chunk-LZPLNZ5U.mjs +67 -0
  37. package/dist/chunk-MTDFKNXX.mjs +19 -0
  38. package/dist/chunk-OF4DMAPJ.mjs +511 -0
  39. package/dist/chunk-PE23RMXY.mjs +43 -0
  40. package/dist/chunk-Q2VY4R5F.mjs +175 -0
  41. package/dist/chunk-S5VD7WTU.mjs +2540 -0
  42. package/dist/chunk-SVPRB62V.mjs +106 -0
  43. package/dist/chunk-TQLCLE4L.mjs +345 -0
  44. package/dist/chunk-U3DGFLXZ.mjs +43 -0
  45. package/dist/chunk-X45O6YD3.mjs +688 -0
  46. package/dist/chunk-XKSWCEGI.mjs +168 -0
  47. package/dist/chunk-Z7DAACGZ.mjs +534 -0
  48. package/dist/chunk-ZQFUZPLO.mjs +257 -0
  49. package/dist/cli.d.ts +952 -0
  50. package/dist/cli.mjs +827 -0
  51. package/dist/client.d.ts +355 -0
  52. package/dist/client.mjs +27 -0
  53. package/dist/cloud-sync.d-Cqt6y18U.d.ts +42 -0
  54. package/dist/cloud-sync.d.ts +42 -0
  55. package/dist/cloud-sync.mjs +17 -0
  56. package/dist/config.d.ts +186 -0
  57. package/dist/config.mjs +54 -0
  58. package/dist/daemon/attach.d.ts +36 -0
  59. package/dist/daemon/attach.mjs +25 -0
  60. package/dist/daemon/audit.d.ts +23 -0
  61. package/dist/daemon/audit.mjs +12 -0
  62. package/dist/daemon/client-http.d.ts +42 -0
  63. package/dist/daemon/client-http.mjs +29 -0
  64. package/dist/daemon/index.d.ts +14 -0
  65. package/dist/daemon/index.mjs +110 -0
  66. package/dist/daemon/install-tunnel.d.ts +46 -0
  67. package/dist/daemon/install-tunnel.mjs +14 -0
  68. package/dist/daemon/launcher.d.ts +163 -0
  69. package/dist/daemon/launcher.mjs +50 -0
  70. package/dist/daemon/lockfile.d.ts +29 -0
  71. package/dist/daemon/lockfile.mjs +18 -0
  72. package/dist/daemon/server.d.ts +159 -0
  73. package/dist/daemon/server.mjs +20 -0
  74. package/dist/daemon/token.d.ts +17 -0
  75. package/dist/daemon/token.mjs +17 -0
  76. package/dist/daemon/tunnel-providers/index.d.ts +330 -0
  77. package/dist/daemon/tunnel-providers/index.mjs +57 -0
  78. package/dist/daemon/tunnel.d.ts +23 -0
  79. package/dist/daemon/tunnel.mjs +9 -0
  80. package/dist/doctor-report.d.ts +24 -0
  81. package/dist/doctor-report.mjs +14 -0
  82. package/dist/doctor.d-CXmUqOXX.d.ts +43 -0
  83. package/dist/doctor.d.ts +44 -0
  84. package/dist/doctor.mjs +16 -0
  85. package/dist/export.d.ts +19 -0
  86. package/dist/export.mjs +15 -0
  87. package/dist/health-check.d.ts +108 -0
  88. package/dist/health-check.mjs +92 -0
  89. package/dist/history-store.d-BzjBF2m3.d.ts +65 -0
  90. package/dist/history-store.d.ts +65 -0
  91. package/dist/history-store.mjs +48 -0
  92. package/dist/impit-login-runner.d.ts +469 -0
  93. package/dist/impit-login-runner.mjs +685 -0
  94. package/dist/index.d.ts +159 -0
  95. package/dist/index.mjs +236 -0
  96. package/dist/login-runner.d.ts +333 -0
  97. package/dist/login-runner.mjs +320 -0
  98. package/dist/logout.d.ts +28 -0
  99. package/dist/logout.mjs +45 -0
  100. package/dist/manual-login-runner.d.ts +150 -0
  101. package/dist/manual-login-runner.mjs +146 -0
  102. package/dist/native-deps-BNThFHxa.d.ts +175 -0
  103. package/dist/native-deps-YNKXITRY.mjs +139 -0
  104. package/dist/profiles.d-DqS1oZWr.d.ts +41 -0
  105. package/dist/profiles.d.ts +41 -0
  106. package/dist/profiles.mjs +33 -0
  107. package/dist/redact.d.ts +159 -0
  108. package/dist/redact.mjs +11 -0
  109. package/dist/refresh.d.ts +118 -0
  110. package/dist/refresh.mjs +21 -0
  111. package/dist/reinit-watcher.d.ts +15 -0
  112. package/dist/reinit-watcher.mjs +8 -0
  113. package/dist/session-metadata-B9aV_n5g.d.ts +148 -0
  114. package/dist/tty-prompt.d.ts +44 -0
  115. package/dist/tty-prompt.mjs +39 -0
  116. package/dist/vault.d-BtRSLZiM.d.ts +8 -0
  117. package/dist/vault.d.ts +37 -0
  118. package/dist/vault.mjs +21 -0
  119. package/dist/viewer-detect.d-HWGnyFAA.d.ts +4 -0
  120. package/dist/viewer-detect.d.ts +4 -0
  121. package/dist/viewer-detect.mjs +37 -0
  122. package/dist/viewers.d-BGCK6sw6.d.ts +10 -0
  123. package/dist/viewers.d.ts +18 -0
  124. package/dist/viewers.mjs +122 -0
  125. package/package.json +152 -0
@@ -0,0 +1,90 @@
1
+ import "../chunk-4UEJOM6W.mjs";
2
+
3
+ // src/checks/vault.js
4
+ import { existsSync } from "fs";
5
+ import { join } from "path";
6
+ var CATEGORY = "vault";
7
+ async function tryKeychain() {
8
+ try {
9
+ const mod = await import("keytar");
10
+ const keytar = mod.default ?? mod;
11
+ const hex = await keytar.getPassword("perplexity-user-mcp", "vault-master-key");
12
+ return { available: true, hasKey: !!hex };
13
+ } catch {
14
+ return { available: false, hasKey: false };
15
+ }
16
+ }
17
+ function keychainExpected() {
18
+ return process.platform === "win32" || process.platform === "darwin" || process.platform === "linux" && !process.env.CI;
19
+ }
20
+ async function run(opts = {}) {
21
+ const results = [];
22
+ const dir = opts.configDir;
23
+ const profile = opts.profile ?? "default";
24
+ const enc = join(dir, "profiles", profile, "vault.enc");
25
+ const plain = join(dir, "profiles", profile, "vault.json");
26
+ const envPass = process.env.PERPLEXITY_VAULT_PASSPHRASE;
27
+ const kc = await tryKeychain();
28
+ if (existsSync(plain)) {
29
+ results.push({
30
+ category: CATEGORY,
31
+ name: "encryption",
32
+ status: "warn",
33
+ message: "plaintext vault.json (security.encryptCookies=false)",
34
+ hint: "Re-run login without --plain-cookies to enable AES-256-GCM at rest."
35
+ });
36
+ } else if (existsSync(enc)) {
37
+ results.push({ category: CATEGORY, name: "encryption", status: "pass", message: "AES-256-GCM (vault.enc)" });
38
+ } else {
39
+ results.push({ category: CATEGORY, name: "encryption", status: "skip", message: "no vault yet" });
40
+ }
41
+ if (kc.hasKey) {
42
+ results.push({ category: CATEGORY, name: "unseal-path", status: "pass", message: "OS keychain holds master key" });
43
+ if (envPass) {
44
+ results.push({
45
+ category: CATEGORY,
46
+ name: "keychain-preferred",
47
+ status: "warn",
48
+ message: "PERPLEXITY_VAULT_PASSPHRASE is also set \u2014 keychain wins, but consider removing the env var"
49
+ });
50
+ }
51
+ } else if (envPass) {
52
+ const hasKc = kc.available;
53
+ results.push({
54
+ category: CATEGORY,
55
+ name: "unseal-path",
56
+ status: "pass",
57
+ message: `env var ${hasKc ? "(keychain available but empty)" : "(keychain unavailable \u2014 expected on headless Linux)"}`
58
+ });
59
+ if (hasKc) {
60
+ results.push({
61
+ category: CATEGORY,
62
+ name: "keychain-preferred",
63
+ status: "warn",
64
+ message: "keychain is available \u2014 moving the master key there would remove the passphrase from IDE config files",
65
+ hint: "Run `npx perplexity-user-mcp login` once with the env var unset; the key will be written to keychain."
66
+ });
67
+ } else {
68
+ results.push({ category: CATEGORY, name: "keychain-preferred", status: "skip", message: "keychain not applicable" });
69
+ }
70
+ } else {
71
+ if (!existsSync(enc) && !existsSync(plain)) {
72
+ results.push({ category: CATEGORY, name: "unseal-path", status: "skip", message: "no vault to unseal yet" });
73
+ } else if (existsSync(plain)) {
74
+ results.push({ category: CATEGORY, name: "unseal-path", status: "pass", message: "plaintext \u2014 no key required" });
75
+ } else {
76
+ const ttyLikely = process.stdin?.isTTY === true;
77
+ results.push({
78
+ category: CATEGORY,
79
+ name: "unseal-path",
80
+ status: ttyLikely ? "warn" : "fail",
81
+ message: ttyLikely ? "no keychain, no env var \u2014 TTY prompt will be required on next use" : "vault locked: no keychain, no env var, no TTY",
82
+ hint: keychainExpected() ? "Install libsecret+gnome-keyring (Linux), or set PERPLEXITY_VAULT_PASSPHRASE." : "Set PERPLEXITY_VAULT_PASSPHRASE in your MCP config env."
83
+ });
84
+ }
85
+ }
86
+ return results;
87
+ }
88
+ export {
89
+ run
90
+ };
@@ -0,0 +1,115 @@
1
+ import {
2
+ getConfigDir
3
+ } from "./chunk-XKSWCEGI.mjs";
4
+
5
+ // src/daemon/install-tunnel.ts
6
+ import { createHash } from "crypto";
7
+ import { gunzipSync } from "zlib";
8
+ import { chmodSync, mkdirSync, renameSync, writeFileSync } from "fs";
9
+ import { join } from "path";
10
+
11
+ // src/daemon/cloudflared-pins.json
12
+ var cloudflared_pins_default = {
13
+ version: "2026.3.0",
14
+ assets: {
15
+ "darwin-arm64": {
16
+ filename: "cloudflared-darwin-arm64.tgz",
17
+ sha256: "2aae4f69b0fc1c671b8353b4f594cbd902cd1e360c8eed2b8cad4602cb1546fb"
18
+ },
19
+ "darwin-x64": {
20
+ filename: "cloudflared-darwin-amd64.tgz",
21
+ sha256: "0f30140c4a5e213d22f951ef4c964cac5fb6a5f061ba6eba5ea932999f7c0394"
22
+ },
23
+ "linux-arm64": {
24
+ filename: "cloudflared-linux-arm64",
25
+ sha256: "0755ba4cbab59980e6148367fcf53a8f3ec85a97deefd63c2420cf7850769bee"
26
+ },
27
+ "linux-x64": {
28
+ filename: "cloudflared-linux-amd64",
29
+ sha256: "4a9e50e6d6d798e90fcd01933151a90bf7edd99a0a55c28ad18f2e16263a5c30"
30
+ },
31
+ "windows-x64": {
32
+ filename: "cloudflared-windows-amd64.exe",
33
+ sha256: "59b12880b24af581cf5b1013db601c7d843b9b097e9c78aa5957c7f39f741885"
34
+ }
35
+ }
36
+ };
37
+
38
+ // src/daemon/install-tunnel.ts
39
+ async function installCloudflared(options = {}) {
40
+ const configDir = options.configDir ?? getConfigDir();
41
+ const assetKey = resolvePinnedAssetKey(options.platform ?? process.platform, options.arch ?? process.arch);
42
+ const asset = cloudflared_pins_default.assets[assetKey];
43
+ const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/download/${cloudflared_pins_default.version}/${asset.filename}`;
44
+ const response = await (options.fetchImpl ?? fetch)(downloadUrl);
45
+ if (!response.ok) {
46
+ throw new Error(`Failed to download cloudflared (${response.status} ${response.statusText}).`);
47
+ }
48
+ const archiveBuffer = Buffer.from(await response.arrayBuffer());
49
+ const sha256 = createHash("sha256").update(archiveBuffer).digest("hex");
50
+ if (sha256 !== asset.sha256) {
51
+ throw new Error(`cloudflared checksum mismatch for ${asset.filename}. Expected ${asset.sha256}, received ${sha256}.`);
52
+ }
53
+ const binaryBuffer = asset.filename.endsWith(".tgz") ? extractCloudflaredBinaryFromTarGz(archiveBuffer) : archiveBuffer;
54
+ const binaryPath = getTunnelBinaryPath(configDir, options.platform ?? process.platform);
55
+ const tempPath = `${binaryPath}.tmp`;
56
+ mkdirSync(join(configDir, "bin"), { recursive: true });
57
+ writeFileSync(tempPath, binaryBuffer, { mode: 493 });
58
+ renameSync(tempPath, binaryPath);
59
+ if ((options.platform ?? process.platform) !== "win32") {
60
+ chmodSync(binaryPath, 493);
61
+ }
62
+ return {
63
+ binaryPath,
64
+ version: cloudflared_pins_default.version,
65
+ assetKey,
66
+ filename: asset.filename,
67
+ sha256
68
+ };
69
+ }
70
+ function getPinnedCloudflaredVersion() {
71
+ return cloudflared_pins_default.version;
72
+ }
73
+ function getTunnelBinaryPath(configDir = getConfigDir(), platform = process.platform) {
74
+ return join(configDir, "bin", platform === "win32" ? "cloudflared.exe" : "cloudflared");
75
+ }
76
+ function resolvePinnedAssetKey(platform, arch) {
77
+ if (platform === "darwin" && arch === "arm64") return "darwin-arm64";
78
+ if (platform === "darwin" && arch === "x64") return "darwin-x64";
79
+ if (platform === "linux" && arch === "arm64") return "linux-arm64";
80
+ if (platform === "linux" && arch === "x64") return "linux-x64";
81
+ if (platform === "win32" && arch === "x64") return "windows-x64";
82
+ throw new Error(`Unsupported cloudflared platform: ${platform}/${arch}`);
83
+ }
84
+ function extractCloudflaredBinaryFromTarGz(buffer) {
85
+ const tarBuffer = gunzipSync(buffer);
86
+ let offset = 0;
87
+ while (offset + 512 <= tarBuffer.length) {
88
+ const header = tarBuffer.subarray(offset, offset + 512);
89
+ if (header.every((value) => value === 0)) {
90
+ break;
91
+ }
92
+ const name = readTarString(header.subarray(0, 100));
93
+ const sizeText = readTarString(header.subarray(124, 136)).trim();
94
+ const size = Number.parseInt(sizeText || "0", 8);
95
+ const dataStart = offset + 512;
96
+ const dataEnd = dataStart + size;
97
+ if (name === "cloudflared" || name.endsWith("/cloudflared")) {
98
+ return tarBuffer.subarray(dataStart, dataEnd);
99
+ }
100
+ offset = dataStart + Math.ceil(size / 512) * 512;
101
+ }
102
+ throw new Error("Downloaded cloudflared archive did not contain the cloudflared binary.");
103
+ }
104
+ function readTarString(buffer) {
105
+ const nullIndex = buffer.indexOf(0);
106
+ const end = nullIndex === -1 ? buffer.length : nullIndex;
107
+ return buffer.subarray(0, end).toString("utf8");
108
+ }
109
+
110
+ export {
111
+ installCloudflared,
112
+ getPinnedCloudflaredVersion,
113
+ getTunnelBinaryPath,
114
+ resolvePinnedAssetKey
115
+ };
@@ -0,0 +1,9 @@
1
+ var __glob = (map) => (path) => {
2
+ var fn = map[path];
3
+ if (fn) return fn();
4
+ throw new Error("Module not found in bundle: " + path);
5
+ };
6
+
7
+ export {
8
+ __glob
9
+ };
@@ -0,0 +1,205 @@
1
+ import {
2
+ getConfigDir
3
+ } from "./chunk-XKSWCEGI.mjs";
4
+
5
+ // src/daemon/lockfile.ts
6
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
7
+ import { dirname, join } from "path";
8
+ function getLockfilePath(configDir = getConfigDir()) {
9
+ return join(configDir, "daemon.lock");
10
+ }
11
+ function acquire(record, options = {}) {
12
+ const lockPath = options.lockPath ?? getLockfilePath();
13
+ const normalized = normalizeRecord(record);
14
+ mkdirSync(dirname(lockPath), { recursive: true });
15
+ for (let attempt = 0; attempt < 2; attempt++) {
16
+ let fd;
17
+ try {
18
+ fd = openSync(lockPath, "wx");
19
+ } catch (error) {
20
+ if (isExistsError(error)) {
21
+ if (attempt === 0 && tryReclaimStale(lockPath)) {
22
+ continue;
23
+ }
24
+ return false;
25
+ }
26
+ throw error;
27
+ }
28
+ let wrote = false;
29
+ try {
30
+ writeFileSync(fd, serialize(normalized), "utf8");
31
+ wrote = true;
32
+ } finally {
33
+ closeSync(fd);
34
+ if (!wrote) {
35
+ rmSync(lockPath, { force: true });
36
+ }
37
+ }
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+ function tryReclaimStale(lockPath) {
43
+ let existing = null;
44
+ try {
45
+ existing = read({ lockPath });
46
+ } catch {
47
+ try {
48
+ rmSync(lockPath, { force: true });
49
+ return true;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+ if (!isStale(existing)) {
55
+ return false;
56
+ }
57
+ try {
58
+ rmSync(lockPath, { force: true });
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+ function read(options = {}) {
65
+ const lockPath = options.lockPath ?? getLockfilePath();
66
+ if (!existsSync(lockPath)) {
67
+ return null;
68
+ }
69
+ const parsed = JSON.parse(readFileSync(lockPath, "utf8"));
70
+ return normalizeRecord(parsed);
71
+ }
72
+ function release(options = {}) {
73
+ const lockPath = options.lockPath ?? getLockfilePath();
74
+ if (!existsSync(lockPath)) {
75
+ return false;
76
+ }
77
+ if (options.expectedUuid) {
78
+ const current = read({ lockPath });
79
+ if (!current || current.uuid !== options.expectedUuid) {
80
+ return false;
81
+ }
82
+ }
83
+ rmSync(lockPath, { force: true });
84
+ return true;
85
+ }
86
+ function replace(record, options = {}) {
87
+ const lockPath = options.lockPath ?? getLockfilePath();
88
+ const normalized = normalizeRecord(record);
89
+ if (options.expectedUuid) {
90
+ const current = read({ lockPath });
91
+ if (!current || current.uuid !== options.expectedUuid) {
92
+ return false;
93
+ }
94
+ }
95
+ mkdirSync(dirname(lockPath), { recursive: true });
96
+ const tempPath = `${lockPath}.tmp`;
97
+ writeFileSync(tempPath, serialize(normalized), "utf8");
98
+ renameSync(tempPath, lockPath);
99
+ return true;
100
+ }
101
+ function isStale(record, options = {}) {
102
+ if (!record) {
103
+ return true;
104
+ }
105
+ if (!Number.isInteger(record.pid) || record.pid <= 0) {
106
+ return true;
107
+ }
108
+ if (!record.uuid || typeof record.uuid !== "string") {
109
+ return true;
110
+ }
111
+ if (options.echoedUuid && options.echoedUuid !== record.uuid) {
112
+ return true;
113
+ }
114
+ return !isProcessAlive(record.pid);
115
+ }
116
+ function normalizeRecord(value) {
117
+ if (!value || typeof value !== "object") {
118
+ throw new Error("Daemon lockfile must contain a JSON object.");
119
+ }
120
+ const record = value;
121
+ const pid = asPositiveInteger(record.pid, "pid");
122
+ const port = asPort(record.port);
123
+ const uuid = asRequiredString(record.uuid, "uuid");
124
+ const bearerToken = asRequiredString(record.bearerToken, "bearerToken");
125
+ const version = asRequiredString(record.version, "version");
126
+ const startedAt = asRequiredString(record.startedAt, "startedAt");
127
+ return {
128
+ pid,
129
+ port,
130
+ uuid,
131
+ bearerToken,
132
+ version,
133
+ startedAt,
134
+ cloudflaredPid: asOptionalInteger(record.cloudflaredPid, "cloudflaredPid"),
135
+ tunnelUrl: asOptionalString(record.tunnelUrl, "tunnelUrl")
136
+ };
137
+ }
138
+ function asPositiveInteger(value, name) {
139
+ if (!Number.isInteger(value) || Number(value) <= 0) {
140
+ throw new Error(`Daemon lockfile field '${name}' must be a positive integer.`);
141
+ }
142
+ return Number(value);
143
+ }
144
+ function asPort(value) {
145
+ if (!Number.isInteger(value) || Number(value) < 0 || Number(value) > 65535) {
146
+ throw new Error("Daemon lockfile field 'port' must be an integer between 0 and 65535.");
147
+ }
148
+ return Number(value);
149
+ }
150
+ function asRequiredString(value, name) {
151
+ if (typeof value !== "string" || value.length === 0) {
152
+ throw new Error(`Daemon lockfile field '${name}' must be a non-empty string.`);
153
+ }
154
+ return value;
155
+ }
156
+ function asOptionalInteger(value, name) {
157
+ if (value === void 0) {
158
+ return void 0;
159
+ }
160
+ if (value === null) {
161
+ return null;
162
+ }
163
+ if (!Number.isInteger(value) || Number(value) <= 0) {
164
+ throw new Error(`Daemon lockfile field '${name}' must be a positive integer when present.`);
165
+ }
166
+ return Number(value);
167
+ }
168
+ function asOptionalString(value, name) {
169
+ if (value === void 0) {
170
+ return void 0;
171
+ }
172
+ if (value === null) {
173
+ return null;
174
+ }
175
+ if (typeof value !== "string") {
176
+ throw new Error(`Daemon lockfile field '${name}' must be a string when present.`);
177
+ }
178
+ return value;
179
+ }
180
+ function serialize(record) {
181
+ return JSON.stringify(record, null, 2) + "\n";
182
+ }
183
+ function isExistsError(error) {
184
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
185
+ }
186
+ function isProcessAlive(pid) {
187
+ try {
188
+ process.kill(pid, 0);
189
+ return true;
190
+ } catch (error) {
191
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "EPERM") {
192
+ return true;
193
+ }
194
+ return false;
195
+ }
196
+ }
197
+
198
+ export {
199
+ getLockfilePath,
200
+ acquire,
201
+ read,
202
+ release,
203
+ replace,
204
+ isStale
205
+ };
@@ -0,0 +1,146 @@
1
+ // src/daemon/tunnel.ts
2
+ import { spawn } from "child_process";
3
+ import { createInterface } from "readline";
4
+ import { execFile as execFileCallback } from "child_process";
5
+ import { promisify } from "util";
6
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
7
+ import { dirname, join } from "path";
8
+ var execFile = promisify(execFileCallback);
9
+ var NEUTRAL_CONFIG_FILENAME = "quick-tunnel.yml";
10
+ var NEUTRAL_CONFIG_BODY = "# perplexity-user-mcp Quick Tunnel \u2014 routing handled by --url flag.\nno-autoupdate: true\n";
11
+ function ensureNeutralConfig(binaryPath) {
12
+ try {
13
+ const configPath = join(dirname(binaryPath), NEUTRAL_CONFIG_FILENAME);
14
+ if (!existsSync(configPath)) {
15
+ mkdirSync(dirname(configPath), { recursive: true });
16
+ writeFileSync(configPath, NEUTRAL_CONFIG_BODY, { encoding: "utf8" });
17
+ }
18
+ return configPath;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function startTunnel(options) {
24
+ const neutralConfigPath = ensureNeutralConfig(options.command);
25
+ const spawnArgs = [
26
+ ...options.args ?? [],
27
+ "tunnel",
28
+ "--no-autoupdate",
29
+ ...neutralConfigPath ? ["--config", neutralConfigPath] : [],
30
+ "--url",
31
+ `http://127.0.0.1:${options.port}`
32
+ ];
33
+ const child = spawn(
34
+ options.command,
35
+ spawnArgs,
36
+ {
37
+ stdio: ["ignore", "pipe", "pipe"],
38
+ detached: false,
39
+ windowsHide: true,
40
+ env: options.env
41
+ }
42
+ );
43
+ let stopping = false;
44
+ let settled = false;
45
+ let resolveExited;
46
+ const exited = new Promise((resolve) => {
47
+ resolveExited = resolve;
48
+ });
49
+ let state = {
50
+ status: "starting",
51
+ url: null,
52
+ pid: child.pid ?? null,
53
+ error: null
54
+ };
55
+ const updateState = (next) => {
56
+ state = next;
57
+ options.onStateChange?.(state);
58
+ };
59
+ updateState(state);
60
+ let resolveReady;
61
+ let rejectReady;
62
+ const waitUntilReady = new Promise((resolve, reject) => {
63
+ resolveReady = resolve;
64
+ rejectReady = reject;
65
+ });
66
+ const handleLine = (line) => {
67
+ const url = extractTunnelUrl(line);
68
+ if (!url || settled) {
69
+ return;
70
+ }
71
+ settled = true;
72
+ updateState({
73
+ status: "enabled",
74
+ url,
75
+ pid: child.pid ?? null,
76
+ error: null
77
+ });
78
+ resolveReady(url);
79
+ };
80
+ createInterface({ input: child.stderr }).on("line", handleLine);
81
+ createInterface({ input: child.stdout }).on("line", handleLine);
82
+ child.on("error", (error) => {
83
+ if (!settled) {
84
+ settled = true;
85
+ rejectReady(error);
86
+ }
87
+ updateState({
88
+ status: stopping ? "disabled" : "crashed",
89
+ url: null,
90
+ pid: child.pid ?? null,
91
+ error: error.message
92
+ });
93
+ });
94
+ child.on("exit", (code, signal) => {
95
+ if (!settled) {
96
+ settled = true;
97
+ rejectReady(new Error(`cloudflared exited before publishing a tunnel URL (code=${code ?? "null"} signal=${signal ?? "null"}).`));
98
+ }
99
+ updateState({
100
+ status: stopping ? "disabled" : "crashed",
101
+ url: stopping ? null : state.url,
102
+ pid: null,
103
+ error: stopping ? null : `cloudflared exited (code=${code ?? "null"} signal=${signal ?? "null"}).`
104
+ });
105
+ resolveExited();
106
+ });
107
+ const stop = async () => {
108
+ if (stopping) {
109
+ return;
110
+ }
111
+ stopping = true;
112
+ if (child.exitCode !== null || child.killed) {
113
+ updateState({
114
+ status: "disabled",
115
+ url: null,
116
+ pid: null,
117
+ error: null
118
+ });
119
+ return;
120
+ }
121
+ if (process.platform === "win32") {
122
+ await execFile("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
123
+ windowsHide: true
124
+ }).catch(() => void 0);
125
+ await exited;
126
+ return;
127
+ }
128
+ child.kill("SIGTERM");
129
+ await exited;
130
+ };
131
+ return {
132
+ pid: child.pid ?? 0,
133
+ waitUntilReady,
134
+ stop,
135
+ getState: () => state
136
+ };
137
+ }
138
+ function extractTunnelUrl(line) {
139
+ const match = line.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/iu);
140
+ return match?.[0] ?? null;
141
+ }
142
+
143
+ export {
144
+ startTunnel,
145
+ extractTunnelUrl
146
+ };
@@ -0,0 +1,118 @@
1
+ import {
2
+ getConfigDir
3
+ } from "./chunk-XKSWCEGI.mjs";
4
+ import {
5
+ __glob
6
+ } from "./chunk-4UEJOM6W.mjs";
7
+
8
+ // import("./checks/**/*.js") in src/doctor.js
9
+ var globImport_checks_js = __glob({
10
+ "./checks/browser.js": () => import("./checks/browser.mjs"),
11
+ "./checks/config.js": () => import("./checks/config.mjs"),
12
+ "./checks/ide.js": () => import("./checks/ide.mjs"),
13
+ "./checks/mcp.js": () => import("./checks/mcp.mjs"),
14
+ "./checks/native-deps.js": () => import("./checks/native-deps.mjs"),
15
+ "./checks/network.js": () => import("./checks/network.mjs"),
16
+ "./checks/probe.js": () => import("./checks/probe.mjs"),
17
+ "./checks/profiles.js": () => import("./checks/profiles.mjs"),
18
+ "./checks/runtime.js": () => import("./checks/runtime.mjs"),
19
+ "./checks/vault.js": () => import("./checks/vault.mjs")
20
+ });
21
+
22
+ // src/doctor.js
23
+ var CATEGORIES = [
24
+ "runtime",
25
+ "config",
26
+ "profiles",
27
+ "vault",
28
+ "browser",
29
+ "native-deps",
30
+ "network",
31
+ "ide",
32
+ "mcp",
33
+ "probe"
34
+ ];
35
+ var RANK = { skip: 0, pass: 1, warn: 2, fail: 3 };
36
+ var INV = ["skip", "pass", "warn", "fail"];
37
+ function rollupStatus(statuses) {
38
+ if (statuses.length === 0) return "skip";
39
+ const rank = statuses.reduce((acc, s) => Math.max(acc, RANK[s] ?? 0), 0);
40
+ return INV[rank];
41
+ }
42
+ function exitCodeFor(report) {
43
+ return report.overall === "fail" ? 10 : 0;
44
+ }
45
+ async function loadCheck(name) {
46
+ const mod = await globImport_checks_js(`./checks/${name}.js`);
47
+ return mod.run;
48
+ }
49
+ async function runAll(opts = {}) {
50
+ const t0 = Date.now();
51
+ const configDir = opts.configDir ?? getConfigDir();
52
+ const { getActiveName } = await import("./profiles.mjs");
53
+ const activeProfile = opts.profile ?? getActiveName() ?? "default";
54
+ const probe = !!opts.probe;
55
+ const allProfiles = !!opts.allProfiles;
56
+ const tasks = CATEGORIES.map(async (cat) => {
57
+ if (cat === "probe" && !probe && !opts.injected?.[cat]) {
58
+ return [cat, [{ category: "probe", name: "probe-search", status: "skip", message: "skipped (use --probe to enable)" }]];
59
+ }
60
+ if (opts.injected?.[cat]) return [cat, opts.injected[cat]];
61
+ try {
62
+ const runFn = await loadCheck(cat);
63
+ const results = await runFn({
64
+ configDir,
65
+ profile: activeProfile,
66
+ allProfiles,
67
+ probe,
68
+ ideStatuses: opts.ideStatuses,
69
+ baseDir: opts.baseDir
70
+ });
71
+ return [cat, results];
72
+ } catch (err) {
73
+ return [cat, [{ category: cat, name: `${cat}-runner`, status: "fail", message: `check crashed: ${err.message}` }]];
74
+ }
75
+ });
76
+ const settled = await Promise.all(tasks);
77
+ const byCategory = {};
78
+ for (const [cat, checks] of settled) {
79
+ const rollup = rollupStatus(checks.map((c) => c.status));
80
+ byCategory[cat] = { status: rollup, checks };
81
+ }
82
+ const overall = rollupStatus(Object.values(byCategory).map((b) => b.status));
83
+ return {
84
+ overall,
85
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
86
+ durationMs: Date.now() - t0,
87
+ activeProfile,
88
+ probeRan: probe,
89
+ byCategory
90
+ };
91
+ }
92
+ function formatReportMarkdown(report) {
93
+ const lines = [];
94
+ const dot = { pass: "[OK]", warn: "[!]", fail: "[X]", skip: "[-]" };
95
+ lines.push(`# Perplexity Doctor report -- ${dot[report.overall]} **${report.overall.toUpperCase()}**`);
96
+ lines.push(`Generated ${report.generatedAt} in ${report.durationMs}ms`);
97
+ lines.push(`Active profile: \`${report.activeProfile}\`${report.probeRan ? " (probe ran)" : ""}`);
98
+ lines.push("");
99
+ for (const cat of CATEGORIES) {
100
+ const bucket = report.byCategory[cat];
101
+ if (!bucket) continue;
102
+ lines.push(`## ${dot[bucket.status]} ${cat} -- ${bucket.status}`);
103
+ for (const c of bucket.checks) {
104
+ lines.push(`- ${dot[c.status]} **${c.name}** -- ${c.message}${c.hint ? `
105
+ - *Hint:* ${c.hint}` : ""}`);
106
+ }
107
+ lines.push("");
108
+ }
109
+ return lines.join("\n");
110
+ }
111
+
112
+ export {
113
+ CATEGORIES,
114
+ rollupStatus,
115
+ exitCodeFor,
116
+ runAll,
117
+ formatReportMarkdown
118
+ };