perplexity-user-mcp 0.8.42 → 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 (132) hide show
  1. package/dist/attachments.d.ts +10 -20
  2. package/dist/browser-window.d.ts +1 -0
  3. package/dist/cf-warmup.d.ts +32 -0
  4. package/dist/checks/browser.d.ts +12 -100
  5. package/dist/checks/config.d.ts +25 -91
  6. package/dist/checks/ide.d.ts +31 -89
  7. package/dist/checks/mcp.d.ts +12 -61
  8. package/dist/checks/native-deps.d.ts +46 -131
  9. package/dist/checks/network.d.ts +13 -71
  10. package/dist/checks/probe.d.ts +20 -92
  11. package/dist/checks/profiles.d.ts +13 -99
  12. package/dist/checks/profiles.mjs +35 -0
  13. package/dist/checks/runtime.d.ts +24 -89
  14. package/dist/checks/vault.d.ts +13 -142
  15. package/dist/checks/vault.mjs +6 -11
  16. package/dist/{chunk-T6ARJK2P.mjs → chunk-2B5OQUXR.mjs} +6 -6
  17. package/dist/{chunk-2FPGJKCA.mjs → chunk-2EE7MNP2.mjs} +2 -2
  18. package/dist/{chunk-Z4OLYVB2.mjs → chunk-2OVLCZHU.mjs} +1 -1
  19. package/dist/{chunk-WDIW33DA.mjs → chunk-3LUO5ATM.mjs} +1 -1
  20. package/dist/{chunk-B65IJQZJ.mjs → chunk-6E6XTHTG.mjs} +1 -1
  21. package/dist/{chunk-S677V2JU.mjs → chunk-C5I7KXHK.mjs} +32 -2
  22. package/dist/{chunk-TDXETAQT.mjs → chunk-DKEJZ4FI.mjs} +1 -1
  23. package/dist/{chunk-U7QPUNRH.mjs → chunk-DXR6EEZH.mjs} +26 -7
  24. package/dist/{chunk-HJIXH6CL.mjs → chunk-E3GRJXXJ.mjs} +2 -0
  25. package/dist/{chunk-RK4EBZJ3.mjs → chunk-E75J42W5.mjs} +11 -8
  26. package/dist/{chunk-452DK6OS.mjs → chunk-FNHYUE22.mjs} +2 -2
  27. package/dist/{chunk-D254EFYB.mjs → chunk-GBI2U336.mjs} +1 -1
  28. package/dist/chunk-GPUGKWXH.mjs +17 -0
  29. package/dist/{chunk-HNSPNCFH.mjs → chunk-KSNV3ZVY.mjs} +1 -1
  30. package/dist/{chunk-XTRJSV72.mjs → chunk-LGH5BSUY.mjs} +1 -1
  31. package/dist/{chunk-KJFX2ZXR.mjs → chunk-NMKNEEZB.mjs} +1 -1
  32. package/dist/{chunk-FKQ3HP4Q.mjs → chunk-TIWHN4IW.mjs} +1 -1
  33. package/dist/{chunk-V4U3JM4R.mjs → chunk-TSLRTZYR.mjs} +1 -1
  34. package/dist/{chunk-DQQISMYN.mjs → chunk-V4LHDNWJ.mjs} +2 -2
  35. package/dist/{chunk-C3HPFFTD.mjs → chunk-WHVJ724K.mjs} +84 -44
  36. package/dist/cli.d.ts +14 -1317
  37. package/dist/cli.mjs +14 -21
  38. package/dist/client.d.ts +27 -24
  39. package/dist/client.mjs +6 -6
  40. package/dist/cloud-sync.d.ts +65 -42
  41. package/dist/cloud-sync.mjs +8 -8
  42. package/dist/config.d.ts +35 -39
  43. package/dist/config.mjs +3 -3
  44. package/dist/cookie-jar.d.ts +77 -0
  45. package/dist/daemon/attach.d.ts +5 -12
  46. package/dist/daemon/attach.mjs +17 -17
  47. package/dist/daemon/audit.d.ts +5 -7
  48. package/dist/daemon/audit.mjs +2 -2
  49. package/dist/daemon/client-http.d.ts +10 -16
  50. package/dist/daemon/client-http.mjs +17 -17
  51. package/dist/daemon/index.d.ts +17 -14
  52. package/dist/daemon/index.mjs +18 -18
  53. package/dist/daemon/install-tunnel.d.ts +8 -34
  54. package/dist/daemon/install-tunnel.mjs +2 -2
  55. package/dist/daemon/launcher.d.ts +24 -29
  56. package/dist/daemon/launcher.mjs +16 -16
  57. package/dist/daemon/local-tokens.d.ts +23 -0
  58. package/dist/daemon/lockfile.d.ts +10 -12
  59. package/dist/daemon/lockfile.mjs +2 -2
  60. package/dist/daemon/oauth-consent-cache.d.ts +86 -0
  61. package/dist/daemon/oauth-provider.d.ts +132 -0
  62. package/dist/daemon/public-pages.d.ts +9 -0
  63. package/dist/daemon/security.d.ts +52 -0
  64. package/dist/daemon/server.d.ts +12 -83
  65. package/dist/daemon/server.mjs +11 -11
  66. package/dist/daemon/token.d.ts +7 -9
  67. package/dist/daemon/token.mjs +2 -2
  68. package/dist/daemon/tunnel-providers/cloudflared-named-setup.d.ts +140 -0
  69. package/dist/daemon/tunnel-providers/cloudflared-named.d.ts +45 -0
  70. package/dist/daemon/tunnel-providers/cloudflared-quick.d.ts +8 -0
  71. package/dist/daemon/tunnel-providers/index.d.ts +16 -327
  72. package/dist/daemon/tunnel-providers/index.mjs +3 -3
  73. package/dist/daemon/tunnel-providers/ngrok-config.d.ts +18 -0
  74. package/dist/daemon/tunnel-providers/ngrok.d.ts +68 -0
  75. package/dist/daemon/tunnel-providers/types.d.ts +56 -0
  76. package/dist/daemon/tunnel.d.ts +5 -7
  77. package/dist/debug-tracer.d.ts +2 -0
  78. package/dist/doctor-report.d.ts +17 -22
  79. package/dist/doctor.d.ts +12 -44
  80. package/dist/doctor.mjs +2 -2
  81. package/dist/export.d.ts +11 -18
  82. package/dist/export.mjs +4 -4
  83. package/dist/format.d.ts +52 -0
  84. package/dist/fs-utils.d.ts +8 -0
  85. package/dist/health-check.d.ts +1 -108
  86. package/dist/health-check.mjs +3 -3
  87. package/dist/history-store.d.ts +29 -65
  88. package/dist/history-store.mjs +2 -2
  89. package/dist/impit-login-runner.d.ts +1 -469
  90. package/dist/impit-login-runner.mjs +4 -4
  91. package/dist/index.d.ts +25 -149
  92. package/dist/index.mjs +22 -20
  93. package/dist/is-main-module.d.ts +9 -0
  94. package/dist/login-runner.d.ts +1 -333
  95. package/dist/login-runner.mjs +13 -13
  96. package/dist/login.d.ts +5 -0
  97. package/dist/logout.d.ts +2 -28
  98. package/dist/logout.mjs +3 -2
  99. package/dist/manual-login-runner.d.ts +1 -150
  100. package/dist/manual-login-runner.mjs +11 -11
  101. package/dist/{native-deps-IE4B55EL.mjs → native-deps-FCSYDL4W.mjs} +4 -4
  102. package/dist/native-deps.d.ts +36 -0
  103. package/dist/package-version.d.ts +1 -0
  104. package/dist/profiles.d.ts +41 -41
  105. package/dist/profiles.mjs +1 -1
  106. package/dist/prompts.d.ts +2 -0
  107. package/dist/redact.d.ts +14 -142
  108. package/dist/refresh.d.ts +11 -16
  109. package/dist/refresh.mjs +4 -4
  110. package/dist/reinit-watcher.d.ts +15 -24
  111. package/dist/reinit-watcher.mjs +2 -2
  112. package/dist/resources.d.ts +5 -0
  113. package/dist/safe-write.d.ts +16 -0
  114. package/dist/session-metadata.d.ts +45 -0
  115. package/dist/tool-config.d.ts +10 -0
  116. package/dist/tools.d.ts +23 -0
  117. package/dist/tty-prompt.d.ts +18 -34
  118. package/dist/vault.d.ts +114 -34
  119. package/dist/vault.mjs +6 -4
  120. package/dist/viewer-detect.d.ts +2 -4
  121. package/dist/viewers.d.ts +13 -18
  122. package/dist/viewers.mjs +1 -1
  123. package/package.json +2 -2
  124. package/dist/cloud-sync.d-Cqt6y18U.d.ts +0 -42
  125. package/dist/doctor.d-CXmUqOXX.d.ts +0 -43
  126. package/dist/history-store.d-BzjBF2m3.d.ts +0 -65
  127. package/dist/native-deps-BNThFHxa.d.ts +0 -175
  128. package/dist/profiles.d-DqS1oZWr.d.ts +0 -41
  129. package/dist/session-metadata-B9aV_n5g.d.ts +0 -148
  130. package/dist/vault.d-BSJWDLhp.d.ts +0 -37
  131. package/dist/viewer-detect.d-HWGnyFAA.d.ts +0 -4
  132. package/dist/viewers.d-BGCK6sw6.d.ts +0 -10
@@ -0,0 +1,86 @@
1
+ /**
2
+ * OAuth consent cache — remembers per-(clientId, redirectUri, resource)
3
+ * consents so the user isn't prompted every time Claude Desktop / Cursor /
4
+ * Cline refreshes an access token (which happens on a ~1h cycle).
5
+ *
6
+ * Storage: <configDir>/oauth-consent.json, 0600.
7
+ * Entries carry an absolute `expiresAt` (ms since epoch). On each check
8
+ * we lazily prune expired entries. Deleting a record is synchronous and
9
+ * best-effort (if the file is locked we lose the mutation; next write
10
+ * will overwrite it).
11
+ *
12
+ * Key structure: `(clientId, redirectUri, resource)`. After Phase 8.2 H12
13
+ * the OAuth `resource` parameter (RFC 8707) is part of the audience binding,
14
+ * so a consent granted for `https://tunnel-a.example/mcp` must NOT
15
+ * auto-approve a new authorize request for `https://tunnel-b.example/mcp`
16
+ * even though the clientId + redirectUri match. `resource === undefined` is
17
+ * a distinct key from any bound resource string — unbound consents (legacy
18
+ * / loopback clients that don't send the `resource` param) don't
19
+ * auto-approve bound-resource consents and vice-versa.
20
+ *
21
+ * Revoking a consent entry does NOT invalidate already-issued access
22
+ * tokens — those live 1h anyway; revoking the CLIENT (see
23
+ * oauth-provider.revokeClient) is the way to invalidate outstanding
24
+ * tokens. Phase 8.2's dashboard panel composes these two.
25
+ */
26
+ export interface ConsentEntry {
27
+ clientId: string;
28
+ redirectUri: string;
29
+ approvedAt: string;
30
+ expiresAt: number;
31
+ /**
32
+ * RFC 8707 resource binding. `undefined` = unbound (legacy / loopback
33
+ * clients that don't send the `resource` param at /authorize). Two entries
34
+ * with the same (clientId, redirectUri) but different `resource` values
35
+ * (including undefined-vs-string) are DISTINCT cache keys.
36
+ */
37
+ resource?: string;
38
+ }
39
+ export interface ConsentCacheOptions {
40
+ cachePath?: string;
41
+ now?: () => number;
42
+ /**
43
+ * Resource the authorize request targets. Must match `ConsentEntry.resource`
44
+ * exactly for a check hit (undefined matches undefined; a string matches
45
+ * the same string; any mismatch is a miss).
46
+ */
47
+ resource?: string;
48
+ }
49
+ export declare function getConsentCachePath(configDir?: string): string;
50
+ /**
51
+ * Record an approval for (clientId, redirectUri, resource). Overwrites any
52
+ * prior entry for the SAME triple only — entries that share clientId +
53
+ * redirectUri but have a different `resource` (including bound-vs-unbound)
54
+ * are preserved as separate consents.
55
+ */
56
+ export declare function record(clientId: string, redirectUri: string, ttlMs: number, options?: ConsentCacheOptions): ConsentEntry;
57
+ /**
58
+ * Returns true iff a non-expired consent exists for the full triple
59
+ * (clientId, redirectUri, resource). `resource === undefined` is a distinct
60
+ * key from any bound resource string. Expired entries are pruned from disk
61
+ * as a side effect.
62
+ */
63
+ export declare function check(clientId: string, redirectUri: string, options?: ConsentCacheOptions): boolean;
64
+ /**
65
+ * Returns all non-expired consents. Expired entries are pruned on read.
66
+ */
67
+ export declare function list(options?: ConsentCacheOptions): ConsentEntry[];
68
+ /**
69
+ * Revoke consent entries. Filter hierarchy:
70
+ * - `{}` (no filter) → revoke everything
71
+ * - `{ clientId }` → revoke every entry for that client (all redirects, all resources)
72
+ * - `{ clientId, redirectUri }` → revoke every entry for that (client, redirect) (all resources)
73
+ * - `{ clientId, redirectUri, resource }` → revoke only the exact triple
74
+ * - `{ clientId, resource }` → revoke every entry for that client scoped to the given resource
75
+ *
76
+ * A `resource` filter value of `undefined` means "do not filter by resource"
77
+ * (NOT "match unbound entries"). To revoke only unbound entries for a client,
78
+ * enumerate via `list()` + call revoke per-entry — the filter API deliberately
79
+ * treats `resource === undefined` as "any" to match pre-H12 callers.
80
+ *
81
+ * Returns the number of entries removed.
82
+ */
83
+ export declare function revoke(options?: ConsentCacheOptions & {
84
+ clientId?: string;
85
+ redirectUri?: string;
86
+ }): number;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * OAuth 2.1 authorization-server provider for the Perplexity MCP daemon.
3
+ *
4
+ * Implements the MCP SDK's `OAuthServerProvider` interface. Plugs into
5
+ * `mcpAuthRouter()` to expose `/authorize`, `/token`, `/register`, `/revoke`
6
+ * and the `/.well-known/*` metadata endpoints.
7
+ *
8
+ * Design:
9
+ * - Clients are persisted in `<configDir>/oauth-clients.json` (0600).
10
+ * - Authorization codes are in-memory, 2-min TTL, single-use.
11
+ * - Access tokens are in-memory, 1-hour TTL.
12
+ * - Refresh tokens rotate on each exchange.
13
+ * - `verifyAccessToken` accepts either a valid OAuth access token OR the
14
+ * daemon's static bearer, so existing loopback callers (extension host
15
+ * + CLI) keep working with no changes.
16
+ * - `authorize()` defers to a caller-supplied `requestConsent` callback
17
+ * which the daemon wires to the VS Code extension's modal. Until the
18
+ * user approves or the 2-min timeout elapses, the HTTP response is
19
+ * held open.
20
+ */
21
+ import type { Response } from "express";
22
+ import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
23
+ import type { OAuthRegisteredClientsStore } from "@modelcontextprotocol/sdk/server/auth/clients.js";
24
+ import type { AuthorizationParams, OAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/provider.js";
25
+ import type { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
26
+ import * as consentCache from "./oauth-consent-cache.js";
27
+ export interface OAuthProviderOptions {
28
+ configDir: string;
29
+ /**
30
+ * Invoked when a browser hits `/authorize`. Must resolve to `true`
31
+ * (approve) or `false` (deny). The daemon wires this to the VS Code
32
+ * modal via an SSE-based consent coordinator.
33
+ *
34
+ * `resource` is the RFC 8707 resource the authorize request targets
35
+ * (post-H12). The consent UI MUST display this value so users can spot
36
+ * cross-resource replay attempts. `undefined` means the client did not
37
+ * send a `resource` param (legacy / loopback flow).
38
+ */
39
+ requestConsent: (info: {
40
+ clientId: string;
41
+ clientName: string;
42
+ redirectUri: string;
43
+ consentId: string;
44
+ resource?: string;
45
+ }) => Promise<boolean>;
46
+ /** Live getter so rotate-token stays supported. */
47
+ getStaticBearer: () => string;
48
+ /**
49
+ * Live getter for the consent-cache TTL in ms. `0` disables the cache
50
+ * (modal fires every time). Read live per-authorize so toggling the
51
+ * setting takes effect on the next request without restarting the
52
+ * provider.
53
+ */
54
+ getConsentCacheTtlMs?: () => number;
55
+ /**
56
+ * Fires just before the authorize response is sent when the provider
57
+ * decided to auto-approve from the consent cache. server.ts uses this
58
+ * to flip the request's audit tag from `none` to `oauth-cached` so
59
+ * the audit log records the cache hit distinctly.
60
+ */
61
+ onConsentCacheHit?: (info: {
62
+ clientId: string;
63
+ redirectUri: string;
64
+ res: Response;
65
+ }) => void;
66
+ }
67
+ export interface AuthorizedClientSummary {
68
+ clientId: string;
69
+ clientName?: string;
70
+ registeredAt: number;
71
+ lastUsedAt?: string;
72
+ consentLastApprovedAt?: string;
73
+ activeTokens: number;
74
+ }
75
+ export declare class PerplexityOAuthProvider implements OAuthServerProvider {
76
+ private readonly options;
77
+ private codes;
78
+ private tokens;
79
+ private clients;
80
+ private clientsPath;
81
+ private consentCachePath;
82
+ constructor(options: OAuthProviderOptions);
83
+ get clientsStore(): OAuthRegisteredClientsStore;
84
+ authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>;
85
+ private issueAuthorizationCode;
86
+ challengeForAuthorizationCode(_client: OAuthClientInformationFull, authorizationCode: string): Promise<string>;
87
+ exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string, codeVerifier?: string, redirectUri?: string, resource?: URL | string): Promise<OAuthTokens>;
88
+ exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string, scopes?: string[], resource?: URL | string): Promise<OAuthTokens>;
89
+ verifyAccessToken(token: string, source?: "loopback" | "tunnel", expectedResource?: string): Promise<AuthInfo>;
90
+ revokeToken(_client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest): Promise<void>;
91
+ listClients(): AuthorizedClientSummary[];
92
+ revokeClient(clientId: string): boolean;
93
+ /**
94
+ * Revoke every registered OAuth client and invalidate all outstanding
95
+ * access/refresh tokens. Returns the count of clients that were removed.
96
+ * Cached consents for every revoked client are also purged so a
97
+ * future registration with a colliding id cannot silently inherit them.
98
+ */
99
+ revokeAllClients(): number;
100
+ listConsents(): consentCache.ConsentEntry[];
101
+ revokeConsent(clientId?: string, redirectUri?: string): number;
102
+ private issueTokenPair;
103
+ private loadClients;
104
+ private persistClients;
105
+ }
106
+ /**
107
+ * Coordinator for /authorize ↔ /daemon/oauth-consent round-trip.
108
+ *
109
+ * `request()` starts a new pending consent and returns a Promise that resolves
110
+ * when the extension host POSTs `/daemon/oauth-consent` with the matching id.
111
+ * Times out after the given ms (denying by default).
112
+ */
113
+ export declare class ConsentCoordinator {
114
+ private pending;
115
+ request(options: {
116
+ id: string;
117
+ clientId: string;
118
+ clientName: string;
119
+ redirectUri: string;
120
+ resource?: string;
121
+ timeoutMs: number;
122
+ onRequest: () => void;
123
+ }): Promise<boolean>;
124
+ resolve(id: string, approved: boolean): boolean;
125
+ list(): Array<{
126
+ id: string;
127
+ clientId: string;
128
+ clientName: string;
129
+ redirectUri: string;
130
+ resource?: string;
131
+ }>;
132
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Unauthenticated pages served on the daemon's HTTP surface.
3
+ *
4
+ * Deliberately minimal — no version, uptime, or tool-list leakage. The homepage
5
+ * is what a human lands on if they hit the tunnel URL in a browser. Everything
6
+ * actionable lives in the VS Code dashboard behind bearer auth.
7
+ */
8
+ export declare function getHomepageHtml(): string;
9
+ export declare function getRobotsTxt(): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Security middleware for the daemon HTTP surface.
3
+ *
4
+ * Ships in Phase 6a — everything runs BEFORE the bearer check so that both
5
+ * authenticated and unauthenticated requests are rate-limited / blocklisted.
6
+ *
7
+ * Components:
8
+ * 1. IP/UA extraction into req._pplx (typed).
9
+ * 2. Per-IP rate limit for unauthenticated requests (credentials often missing
10
+ * from /register, /authorize, public pages — bearer-rate-limit kicks in
11
+ * once authenticated).
12
+ * 3. Per-bearer rate limit (60 rpm default). Loopback-sourced requests exempt.
13
+ * 4. User-Agent blocklist — known scanners.
14
+ * 5. 401-burst tripwire — 20 401s in 60s on the tunnel fires a callback so
15
+ * the launcher can auto-disable the tunnel.
16
+ * 6. Slow-401 — every 401 response is delayed a fixed 150ms to defeat
17
+ * bearer-brute-force timing probes.
18
+ */
19
+ export interface SecurityOptions {
20
+ ratelimitRpm?: number;
21
+ tripwireWindowMs?: number;
22
+ tripwireThreshold?: number;
23
+ slow401Ms?: number;
24
+ uaBlocklist?: RegExp[];
25
+ onTripwireTriggered?: (info: {
26
+ source: "loopback" | "tunnel";
27
+ failures: number;
28
+ windowMs: number;
29
+ ip: string | null;
30
+ }) => void | Promise<void>;
31
+ }
32
+ export interface SecurityMiddlewareResult {
33
+ middleware: (req: any, res: any, next: any) => void;
34
+ record401: (info: {
35
+ source: "loopback" | "tunnel";
36
+ ip: string | null;
37
+ }) => void;
38
+ snapshot: () => SecurityDiagnostics;
39
+ }
40
+ export interface SecurityDiagnostics {
41
+ tripwireFailures: number;
42
+ tripwireWindowMs: number;
43
+ tripwireThreshold: number;
44
+ rateLimitedBearers: number;
45
+ blockedUas: number;
46
+ }
47
+ /**
48
+ * Create the security middleware stack. Call once per daemon instance.
49
+ */
50
+ export declare function createSecurity(options?: SecurityOptions): SecurityMiddlewareResult;
51
+ /** Reset the tripwire latch — called after an auto-disable so future attacks re-trip. */
52
+ export declare function resetTripwire(security: SecurityMiddlewareResult): void;
@@ -1,87 +1,17 @@
1
- import { RequestLike } from 'express';
2
- import { PerplexityClient } from '../client.js';
3
- import { readAuditTail } from './audit.js';
4
- import { DaemonTokenRecord } from './token.js';
5
- import '../config.js';
6
- import 'patchright';
7
-
8
- /**
9
- * OAuth consent cache — remembers per-(clientId, redirectUri, resource)
10
- * consents so the user isn't prompted every time Claude Desktop / Cursor /
11
- * Cline refreshes an access token (which happens on a ~1h cycle).
12
- *
13
- * Storage: <configDir>/oauth-consent.json, 0600.
14
- * Entries carry an absolute `expiresAt` (ms since epoch). On each check
15
- * we lazily prune expired entries. Deleting a record is synchronous and
16
- * best-effort (if the file is locked we lose the mutation; next write
17
- * will overwrite it).
18
- *
19
- * Key structure: `(clientId, redirectUri, resource)`. After Phase 8.2 H12
20
- * the OAuth `resource` parameter (RFC 8707) is part of the audience binding,
21
- * so a consent granted for `https://tunnel-a.example/mcp` must NOT
22
- * auto-approve a new authorize request for `https://tunnel-b.example/mcp`
23
- * even though the clientId + redirectUri match. `resource === undefined` is
24
- * a distinct key from any bound resource string — unbound consents (legacy
25
- * / loopback clients that don't send the `resource` param) don't
26
- * auto-approve bound-resource consents and vice-versa.
27
- *
28
- * Revoking a consent entry does NOT invalidate already-issued access
29
- * tokens — those live 1h anyway; revoking the CLIENT (see
30
- * oauth-provider.revokeClient) is the way to invalidate outstanding
31
- * tokens. Phase 8.2's dashboard panel composes these two.
32
- */
33
- interface ConsentEntry {
34
- clientId: string;
35
- redirectUri: string;
36
- approvedAt: string;
37
- expiresAt: number;
38
- /**
39
- * RFC 8707 resource binding. `undefined` = unbound (legacy / loopback
40
- * clients that don't send the `resource` param at /authorize). Two entries
41
- * with the same (clientId, redirectUri) but different `resource` values
42
- * (including undefined-vs-string) are DISTINCT cache keys.
43
- */
44
- resource?: string;
45
- }
46
-
47
- /**
48
- * OAuth 2.1 authorization-server provider for the Perplexity MCP daemon.
49
- *
50
- * Implements the MCP SDK's `OAuthServerProvider` interface. Plugs into
51
- * `mcpAuthRouter()` to expose `/authorize`, `/token`, `/register`, `/revoke`
52
- * and the `/.well-known/*` metadata endpoints.
53
- *
54
- * Design:
55
- * - Clients are persisted in `<configDir>/oauth-clients.json` (0600).
56
- * - Authorization codes are in-memory, 2-min TTL, single-use.
57
- * - Access tokens are in-memory, 1-hour TTL.
58
- * - Refresh tokens rotate on each exchange.
59
- * - `verifyAccessToken` accepts either a valid OAuth access token OR the
60
- * daemon's static bearer, so existing loopback callers (extension host
61
- * + CLI) keep working with no changes.
62
- * - `authorize()` defers to a caller-supplied `requestConsent` callback
63
- * which the daemon wires to the VS Code extension's modal. Until the
64
- * user approves or the 2-min timeout elapses, the HTTP response is
65
- * held open.
66
- */
67
-
68
- interface AuthorizedClientSummary {
69
- clientId: string;
70
- clientName?: string;
71
- registeredAt: number;
72
- lastUsedAt?: string;
73
- consentLastApprovedAt?: string;
74
- activeTokens: number;
75
- }
76
-
1
+ import { type RequestLike } from "express";
2
+ import { PerplexityClient } from "../client.js";
3
+ import { readAuditTail } from "./audit.js";
4
+ import { type AuthorizedClientSummary } from "./oauth-provider.js";
5
+ import type { ConsentEntry } from "./oauth-consent-cache.js";
6
+ import { type DaemonTokenRecord } from "./token.js";
77
7
  type EventPayload = Record<string, unknown>;
78
- interface DaemonTunnelHealth {
8
+ export interface DaemonTunnelHealth {
79
9
  status: "disabled" | "starting" | "enabled" | "crashed";
80
10
  url: string | null;
81
11
  pid?: number | null;
82
12
  error?: string | null;
83
13
  }
84
- interface StartDaemonServerOptions {
14
+ export interface StartDaemonServerOptions {
85
15
  host?: string;
86
16
  port?: number;
87
17
  uuid?: string;
@@ -120,7 +50,7 @@ interface StartDaemonServerOptions {
120
50
  /** When tunnel is enabled we advertise this as the OAuth issuer. */
121
51
  getTunnelUrl?: () => string | null;
122
52
  }
123
- interface StartedDaemonServer {
53
+ export interface StartedDaemonServer {
124
54
  host: string;
125
55
  port: number;
126
56
  url: string;
@@ -147,13 +77,12 @@ interface StartedDaemonServer {
147
77
  redirectUri?: string;
148
78
  }) => number;
149
79
  }
150
- declare function startDaemonServer(options?: StartDaemonServerOptions): Promise<StartedDaemonServer>;
80
+ export declare function startDaemonServer(options?: StartDaemonServerOptions): Promise<StartedDaemonServer>;
151
81
  /**
152
82
  * Canonicalize the expected resource identifier (RFC 8707) for this
153
83
  * request. Returns `<scheme>://<host>/mcp` with no trailing slash so the
154
84
  * PRM endpoint, the /authorize→/token binding, and verifyAccessToken's
155
85
  * expectedResource all agree on a single form.
156
86
  */
157
- declare function resolveRequestResource(req: RequestLike, fallback?: URL): string;
158
-
159
- export { type DaemonTunnelHealth, type StartDaemonServerOptions, type StartedDaemonServer, resolveRequestResource, startDaemonServer };
87
+ export declare function resolveRequestResource(req: RequestLike, fallback?: URL): string;
88
+ export {};
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  resolveRequestResource,
3
3
  startDaemonServer
4
- } from "../chunk-T6ARJK2P.mjs";
5
- import "../chunk-V4U3JM4R.mjs";
6
- import "../chunk-TDXETAQT.mjs";
7
- import "../chunk-2FPGJKCA.mjs";
8
- import "../chunk-C3HPFFTD.mjs";
9
- import "../chunk-DQQISMYN.mjs";
10
- import "../chunk-B65IJQZJ.mjs";
11
- import "../chunk-D254EFYB.mjs";
12
- import "../chunk-U7QPUNRH.mjs";
13
- import "../chunk-S677V2JU.mjs";
4
+ } from "../chunk-2B5OQUXR.mjs";
5
+ import "../chunk-TSLRTZYR.mjs";
6
+ import "../chunk-DKEJZ4FI.mjs";
7
+ import "../chunk-2EE7MNP2.mjs";
8
+ import "../chunk-WHVJ724K.mjs";
9
+ import "../chunk-V4LHDNWJ.mjs";
10
+ import "../chunk-6E6XTHTG.mjs";
11
+ import "../chunk-GBI2U336.mjs";
12
+ import "../chunk-DXR6EEZH.mjs";
13
+ import "../chunk-C5I7KXHK.mjs";
14
14
  import "../chunk-MTDFKNXX.mjs";
15
- import "../chunk-HJIXH6CL.mjs";
15
+ import "../chunk-E3GRJXXJ.mjs";
16
16
  import "../chunk-4UEJOM6W.mjs";
17
17
  export {
18
18
  resolveRequestResource,
@@ -1,17 +1,15 @@
1
- interface DaemonTokenRecord {
1
+ export interface DaemonTokenRecord {
2
2
  bearerToken: string;
3
3
  version: number;
4
4
  createdAt: string;
5
5
  rotatedAt: string;
6
6
  }
7
- interface TokenOptions {
7
+ export interface TokenOptions {
8
8
  tokenPath?: string;
9
9
  now?: () => string;
10
10
  }
11
- declare function getTokenPath(configDir?: string): string;
12
- declare function generateBearerToken(): string;
13
- declare function readToken(options?: TokenOptions): DaemonTokenRecord | null;
14
- declare function ensureToken(options?: TokenOptions): DaemonTokenRecord;
15
- declare function rotateToken(options?: TokenOptions): DaemonTokenRecord;
16
-
17
- export { type DaemonTokenRecord, type TokenOptions, ensureToken, generateBearerToken, getTokenPath, readToken, rotateToken };
11
+ export declare function getTokenPath(configDir?: string): string;
12
+ export declare function generateBearerToken(): string;
13
+ export declare function readToken(options?: TokenOptions): DaemonTokenRecord | null;
14
+ export declare function ensureToken(options?: TokenOptions): DaemonTokenRecord;
15
+ export declare function rotateToken(options?: TokenOptions): DaemonTokenRecord;
@@ -4,9 +4,9 @@ import {
4
4
  getTokenPath,
5
5
  readToken,
6
6
  rotateToken
7
- } from "../chunk-TDXETAQT.mjs";
7
+ } from "../chunk-DKEJZ4FI.mjs";
8
8
  import "../chunk-MTDFKNXX.mjs";
9
- import "../chunk-HJIXH6CL.mjs";
9
+ import "../chunk-E3GRJXXJ.mjs";
10
10
  import "../chunk-4UEJOM6W.mjs";
11
11
  export {
12
12
  ensureToken,
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Cloudflared named-tunnel setup helpers.
3
+ *
4
+ * Wraps the `cloudflared` CLI to drive a persistent (named) tunnel. Named
5
+ * tunnels require an origin cert (`~/.cloudflared/cert.pem`), a tunnel UUID
6
+ * + credentials file, and a YAML config that maps a hostname -> local port.
7
+ *
8
+ * This module ships the setup primitives only; provider registration lives in
9
+ * cloudflared-named.ts (Phase 8.4.2). The dashboard/CLI call these helpers
10
+ * during the one-time setup flow.
11
+ */
12
+ import { spawn as nodeSpawn } from "node:child_process";
13
+ /**
14
+ * Minimal spawn signature we rely on — matches the three-arg overload of
15
+ * `node:child_process.spawn` used for piping stdio and collecting output.
16
+ * Declared as a type alias so tests can inject a fake implementation.
17
+ */
18
+ type SpawnFn = typeof nodeSpawn;
19
+ export interface CloudflaredLoginResult {
20
+ ok: boolean;
21
+ certPath: string;
22
+ stderr?: string;
23
+ }
24
+ export interface NamedTunnelSummary {
25
+ /** cloudflared's tunnel UUID. */
26
+ uuid: string;
27
+ /** Human-readable name. */
28
+ name: string;
29
+ createdAt?: string;
30
+ connections?: number;
31
+ }
32
+ export interface CreatedTunnel extends NamedTunnelSummary {
33
+ /** Path to the credentials JSON cloudflared wrote. */
34
+ credentialsPath: string;
35
+ }
36
+ export interface NamedTunnelConfig {
37
+ uuid: string;
38
+ /** e.g. "mcp.example.com" */
39
+ hostname: string;
40
+ /** Local daemon HTTP port. */
41
+ port: number;
42
+ /** Full path to the written .yml. */
43
+ configPath: string;
44
+ credentialsPath: string;
45
+ }
46
+ export interface DeletedNamedTunnel {
47
+ uuid: string;
48
+ }
49
+ export type DeleteNamedTunnelFailureReason = "active-connections" | "unknown";
50
+ export declare class DeleteNamedTunnelError extends Error {
51
+ readonly reason: DeleteNamedTunnelFailureReason;
52
+ constructor(message: string, reason: DeleteNamedTunnelFailureReason);
53
+ }
54
+ /**
55
+ * Runs `cloudflared tunnel login`. Opens a browser. Blocks until the cert
56
+ * lands at `~/.cloudflared/cert.pem` (or throws on timeout / abort). The
57
+ * login subprocess is best-effort terminated on resolve/reject.
58
+ */
59
+ export declare function runCloudflaredLogin(options: {
60
+ configDir: string;
61
+ binaryPath?: string;
62
+ signal?: AbortSignal;
63
+ timeoutMs?: number;
64
+ /** Override the cert watch location (defaults to `$HOME/.cloudflared/cert.pem`). */
65
+ certPath?: string;
66
+ /**
67
+ * When true, pipe the cloudflared child's stderr AND stdout to the parent
68
+ * process's stderr so CLI users see the "open this URL in your browser"
69
+ * prompt (some cloudflared builds emit it on stdout, others on stderr).
70
+ * Never forwards to parent stdout — the CLI reserves stdout for --json
71
+ * machine-readable output. Default false (test hermeticity + dashboard
72
+ * flow that wraps output in notifications instead).
73
+ */
74
+ forwardOutput?: boolean;
75
+ /** Test-only dependency injection seam. */
76
+ dependencies?: {
77
+ spawn?: SpawnFn;
78
+ };
79
+ }): Promise<CloudflaredLoginResult>;
80
+ /**
81
+ * Runs `cloudflared tunnel list --output=json` and parses. Returns [] on
82
+ * "no tunnels" (exit 0 + empty list). Throws on binary/cert problems.
83
+ */
84
+ export declare function listNamedTunnels(options: {
85
+ configDir: string;
86
+ binaryPath?: string;
87
+ dependencies?: {
88
+ spawn?: SpawnFn;
89
+ };
90
+ }): Promise<NamedTunnelSummary[]>;
91
+ /**
92
+ * Runs `cloudflared tunnel create <name>`, parses the UUID + credentials
93
+ * path out of stdout, then runs `cloudflared tunnel route dns <uuid>
94
+ * <hostname>` to install the CNAME record.
95
+ */
96
+ export declare function createNamedTunnel(options: {
97
+ configDir: string;
98
+ name: string;
99
+ hostname: string;
100
+ binaryPath?: string;
101
+ signal?: AbortSignal;
102
+ dependencies?: {
103
+ spawn?: SpawnFn;
104
+ };
105
+ }): Promise<CreatedTunnel>;
106
+ /**
107
+ * Deletes a remote Cloudflare named tunnel. This is intentionally separate
108
+ * from clearNamedTunnelConfig(): remote delete can fail even with --force
109
+ * when DNS still routes traffic and active connections keep the tunnel alive.
110
+ */
111
+ export declare function deleteNamedTunnel(options: {
112
+ configDir: string;
113
+ uuid: string;
114
+ binaryPath?: string;
115
+ signal?: AbortSignal;
116
+ dependencies?: {
117
+ spawn?: SpawnFn;
118
+ };
119
+ }): Promise<DeletedNamedTunnel>;
120
+ export declare function isActiveConnectionDeleteFailure(output: string): boolean;
121
+ export declare function getNamedTunnelConfigPath(configDir: string): string;
122
+ /**
123
+ * Writes `<configDir>/cloudflared-named.yml` describing the tunnel ->
124
+ * localhost mapping. Uses the temp-file + rename pattern from ngrok-config.ts
125
+ * and locks file mode to 0600 (POSIX) / user-only ACL (Windows).
126
+ */
127
+ export declare function writeTunnelConfig(options: {
128
+ configDir: string;
129
+ uuid: string;
130
+ hostname: string;
131
+ port: number;
132
+ credentialsPath: string;
133
+ }): NamedTunnelConfig;
134
+ /**
135
+ * Reads + validates the config written by writeTunnelConfig. Returns null
136
+ * if absent or malformed.
137
+ */
138
+ export declare function readNamedTunnelConfig(configDir: string): NamedTunnelConfig | null;
139
+ export declare function clearNamedTunnelConfig(configDir: string): boolean;
140
+ export {};
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Cloudflared named-tunnel provider.
3
+ *
4
+ * Runs a persistent Cloudflare tunnel that routes a user-owned hostname
5
+ * (e.g. https://mcp.example.com) to the local daemon HTTP port. Unlike
6
+ * cloudflared-quick, the URL is stable across restarts.
7
+ *
8
+ * Setup (one-time, handled by 8.4.3 UI + 8.4.4 CLI) produces:
9
+ * - ~/.cloudflared/cert.pem (origin cert from `cloudflared login`)
10
+ * - ~/.cloudflared/<uuid>.json (tunnel credentials, written by create)
11
+ * - <configDir>/cloudflared-named.yml (managed ingress config we serialize)
12
+ *
13
+ * Port-drift rewrite (load-bearing): the managed YAML embeds the daemon's
14
+ * loopback port. The daemon picks a fresh OS-assigned port on most restarts,
15
+ * so the persisted port is almost always stale by the time start() runs. We
16
+ * rewrite the managed YAML with the current port on every start() — idempotent
17
+ * atomic writes are cheap, forgetting the rewrite routes cloudflared to a
18
+ * dead port.
19
+ *
20
+ * The managed YAML is treated as provider-owned — hand-edits to add extra
21
+ * ingress rules WILL be silently dropped on the next start() because we
22
+ * serialize only the four canonical keys (tunnel / credentials-file /
23
+ * hostname / service). Warning on drift is deferred to 8.4.3.
24
+ */
25
+ import { spawn as nodeSpawn } from "node:child_process";
26
+ import type { TunnelProvider } from "./types.js";
27
+ type SpawnFn = typeof nodeSpawn;
28
+ interface ProviderDependencies {
29
+ spawn?: SpawnFn;
30
+ homedir?: () => string;
31
+ }
32
+ /**
33
+ * Build a provider bound to a specific dependency set. The exported
34
+ * singleton uses node defaults; tests construct a one-off via
35
+ * createCloudflaredNamedProvider({ dependencies: { spawn: fakeSpawn } }).
36
+ */
37
+ export declare function createCloudflaredNamedProvider(options?: {
38
+ dependencies?: ProviderDependencies;
39
+ }): TunnelProvider;
40
+ /**
41
+ * Default singleton wired to node's real spawn. Registered in the provider
42
+ * registry; tests use createCloudflaredNamedProvider() for DI.
43
+ */
44
+ export declare const cloudflaredNamedProvider: TunnelProvider;
45
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Cloudflare Quick Tunnel provider — wraps the existing binary-based flow.
3
+ *
4
+ * Ephemeral subdomain on *.trycloudflare.com. Zero configuration required
5
+ * beyond installing the binary (handled via `daemon install-tunnel`).
6
+ */
7
+ import type { TunnelProvider } from "./types.js";
8
+ export declare const cloudflaredQuickProvider: TunnelProvider;