perplexity-user-mcp 0.8.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/dist/attachments.d.ts +20 -0
- package/dist/attachments.mjs +43 -0
- package/dist/checks/browser.d.ts +100 -0
- package/dist/checks/browser.mjs +89 -0
- package/dist/checks/config.d.ts +91 -0
- package/dist/checks/config.mjs +88 -0
- package/dist/checks/ide.d.ts +89 -0
- package/dist/checks/ide.mjs +80 -0
- package/dist/checks/mcp.d.ts +61 -0
- package/dist/checks/mcp.mjs +56 -0
- package/dist/checks/native-deps.d.ts +131 -0
- package/dist/checks/native-deps.mjs +115 -0
- package/dist/checks/network.d.ts +71 -0
- package/dist/checks/network.mjs +70 -0
- package/dist/checks/probe.d.ts +93 -0
- package/dist/checks/probe.mjs +82 -0
- package/dist/checks/profiles.d.ts +99 -0
- package/dist/checks/profiles.mjs +90 -0
- package/dist/checks/runtime.d.ts +89 -0
- package/dist/checks/runtime.mjs +90 -0
- package/dist/checks/vault.d.ts +101 -0
- package/dist/checks/vault.mjs +90 -0
- package/dist/chunk-3B276PGG.mjs +115 -0
- package/dist/chunk-4UEJOM6W.mjs +9 -0
- package/dist/chunk-6EP2BLTV.mjs +205 -0
- package/dist/chunk-6YMQVLFX.mjs +146 -0
- package/dist/chunk-7JL36EBH.mjs +118 -0
- package/dist/chunk-DPGMKSSA.mjs +57 -0
- package/dist/chunk-H4BUAPPO.mjs +1950 -0
- package/dist/chunk-HMKLWVXB.mjs +109 -0
- package/dist/chunk-HTUAQRKH.mjs +125 -0
- package/dist/chunk-HU5B4FXS.mjs +139 -0
- package/dist/chunk-KCXM2M4B.mjs +1006 -0
- package/dist/chunk-LKJMLGFP.mjs +237 -0
- package/dist/chunk-LZPLNZ5U.mjs +67 -0
- package/dist/chunk-MTDFKNXX.mjs +19 -0
- package/dist/chunk-OF4DMAPJ.mjs +511 -0
- package/dist/chunk-PE23RMXY.mjs +43 -0
- package/dist/chunk-Q2VY4R5F.mjs +175 -0
- package/dist/chunk-S5VD7WTU.mjs +2540 -0
- package/dist/chunk-SVPRB62V.mjs +106 -0
- package/dist/chunk-TQLCLE4L.mjs +345 -0
- package/dist/chunk-U3DGFLXZ.mjs +43 -0
- package/dist/chunk-X45O6YD3.mjs +688 -0
- package/dist/chunk-XKSWCEGI.mjs +168 -0
- package/dist/chunk-Z7DAACGZ.mjs +534 -0
- package/dist/chunk-ZQFUZPLO.mjs +257 -0
- package/dist/cli.d.ts +952 -0
- package/dist/cli.mjs +827 -0
- package/dist/client.d.ts +355 -0
- package/dist/client.mjs +27 -0
- package/dist/cloud-sync.d-Cqt6y18U.d.ts +42 -0
- package/dist/cloud-sync.d.ts +42 -0
- package/dist/cloud-sync.mjs +17 -0
- package/dist/config.d.ts +186 -0
- package/dist/config.mjs +54 -0
- package/dist/daemon/attach.d.ts +36 -0
- package/dist/daemon/attach.mjs +25 -0
- package/dist/daemon/audit.d.ts +23 -0
- package/dist/daemon/audit.mjs +12 -0
- package/dist/daemon/client-http.d.ts +42 -0
- package/dist/daemon/client-http.mjs +29 -0
- package/dist/daemon/index.d.ts +14 -0
- package/dist/daemon/index.mjs +110 -0
- package/dist/daemon/install-tunnel.d.ts +46 -0
- package/dist/daemon/install-tunnel.mjs +14 -0
- package/dist/daemon/launcher.d.ts +163 -0
- package/dist/daemon/launcher.mjs +50 -0
- package/dist/daemon/lockfile.d.ts +29 -0
- package/dist/daemon/lockfile.mjs +18 -0
- package/dist/daemon/server.d.ts +159 -0
- package/dist/daemon/server.mjs +20 -0
- package/dist/daemon/token.d.ts +17 -0
- package/dist/daemon/token.mjs +17 -0
- package/dist/daemon/tunnel-providers/index.d.ts +330 -0
- package/dist/daemon/tunnel-providers/index.mjs +57 -0
- package/dist/daemon/tunnel.d.ts +23 -0
- package/dist/daemon/tunnel.mjs +9 -0
- package/dist/doctor-report.d.ts +24 -0
- package/dist/doctor-report.mjs +14 -0
- package/dist/doctor.d-CXmUqOXX.d.ts +43 -0
- package/dist/doctor.d.ts +44 -0
- package/dist/doctor.mjs +16 -0
- package/dist/export.d.ts +19 -0
- package/dist/export.mjs +15 -0
- package/dist/health-check.d.ts +108 -0
- package/dist/health-check.mjs +92 -0
- package/dist/history-store.d-BzjBF2m3.d.ts +65 -0
- package/dist/history-store.d.ts +65 -0
- package/dist/history-store.mjs +48 -0
- package/dist/impit-login-runner.d.ts +469 -0
- package/dist/impit-login-runner.mjs +685 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.mjs +236 -0
- package/dist/login-runner.d.ts +333 -0
- package/dist/login-runner.mjs +320 -0
- package/dist/logout.d.ts +28 -0
- package/dist/logout.mjs +45 -0
- package/dist/manual-login-runner.d.ts +150 -0
- package/dist/manual-login-runner.mjs +146 -0
- package/dist/native-deps-BNThFHxa.d.ts +175 -0
- package/dist/native-deps-YNKXITRY.mjs +139 -0
- package/dist/profiles.d-DqS1oZWr.d.ts +41 -0
- package/dist/profiles.d.ts +41 -0
- package/dist/profiles.mjs +33 -0
- package/dist/redact.d.ts +159 -0
- package/dist/redact.mjs +11 -0
- package/dist/refresh.d.ts +118 -0
- package/dist/refresh.mjs +21 -0
- package/dist/reinit-watcher.d.ts +15 -0
- package/dist/reinit-watcher.mjs +8 -0
- package/dist/session-metadata-B9aV_n5g.d.ts +148 -0
- package/dist/tty-prompt.d.ts +44 -0
- package/dist/tty-prompt.mjs +39 -0
- package/dist/vault.d-BtRSLZiM.d.ts +8 -0
- package/dist/vault.d.ts +37 -0
- package/dist/vault.mjs +21 -0
- package/dist/viewer-detect.d-HWGnyFAA.d.ts +4 -0
- package/dist/viewer-detect.d.ts +4 -0
- package/dist/viewer-detect.mjs +37 -0
- package/dist/viewers.d-BGCK6sw6.d.ts +10 -0
- package/dist/viewers.d.ts +18 -0
- package/dist/viewers.mjs +122 -0
- package/package.json +152 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
interface ProfileMeta {
|
|
2
|
+
name: string;
|
|
3
|
+
displayName: string;
|
|
4
|
+
createdAt: string;
|
|
5
|
+
loginMode?: string;
|
|
6
|
+
tier?: string;
|
|
7
|
+
lastLogin?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ProfilePaths {
|
|
11
|
+
dir: string;
|
|
12
|
+
meta: string;
|
|
13
|
+
vault: string;
|
|
14
|
+
vaultPlain: string;
|
|
15
|
+
browserData: string;
|
|
16
|
+
modelsCache: string;
|
|
17
|
+
history: string;
|
|
18
|
+
attachments: string;
|
|
19
|
+
researches: string;
|
|
20
|
+
reinit: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare function getConfigDir(): string;
|
|
24
|
+
declare function getProfilesDir(): string;
|
|
25
|
+
declare function getProfilePaths(name: string): ProfilePaths;
|
|
26
|
+
declare function validateName(name: string): string | null;
|
|
27
|
+
declare function createProfile(name: string, opts?: { displayName?: string; loginMode?: string }): ProfileMeta;
|
|
28
|
+
declare function listProfiles(): ProfileMeta[];
|
|
29
|
+
declare function getProfile(name: string): ProfileMeta | null;
|
|
30
|
+
declare function deleteProfile(name: string): void;
|
|
31
|
+
declare function getActiveName(): string | null;
|
|
32
|
+
declare function getActive(): ProfileMeta | null;
|
|
33
|
+
declare function setActive(name: string): void;
|
|
34
|
+
declare function suggestNextDefaultName(): string;
|
|
35
|
+
declare function renameProfile(oldName: string, newName: string): void;
|
|
36
|
+
declare function recordLoginSuccess(
|
|
37
|
+
name: string,
|
|
38
|
+
opts: { tier: string; loginMode: string; lastLogin: string }
|
|
39
|
+
): ProfileMeta;
|
|
40
|
+
|
|
41
|
+
export { type ProfileMeta, type ProfilePaths, createProfile, deleteProfile, getActive, getActiveName, getConfigDir, getProfile, getProfilePaths, getProfilesDir, listProfiles, recordLoginSuccess, renameProfile, setActive, suggestNextDefaultName, validateName };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createProfile,
|
|
3
|
+
deleteProfile,
|
|
4
|
+
getActive,
|
|
5
|
+
getActiveName,
|
|
6
|
+
getConfigDir,
|
|
7
|
+
getProfile,
|
|
8
|
+
getProfilePaths,
|
|
9
|
+
getProfilesDir,
|
|
10
|
+
listProfiles,
|
|
11
|
+
recordLoginSuccess,
|
|
12
|
+
renameProfile,
|
|
13
|
+
setActive,
|
|
14
|
+
suggestNextDefaultName,
|
|
15
|
+
validateName
|
|
16
|
+
} from "./chunk-XKSWCEGI.mjs";
|
|
17
|
+
import "./chunk-4UEJOM6W.mjs";
|
|
18
|
+
export {
|
|
19
|
+
createProfile,
|
|
20
|
+
deleteProfile,
|
|
21
|
+
getActive,
|
|
22
|
+
getActiveName,
|
|
23
|
+
getConfigDir,
|
|
24
|
+
getProfile,
|
|
25
|
+
getProfilePaths,
|
|
26
|
+
getProfilesDir,
|
|
27
|
+
listProfiles,
|
|
28
|
+
recordLoginSuccess,
|
|
29
|
+
renameProfile,
|
|
30
|
+
setActive,
|
|
31
|
+
suggestNextDefaultName,
|
|
32
|
+
validateName
|
|
33
|
+
};
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Security-critical: scrubs sensitive patterns from strings before logging
|
|
2
|
+
// or before sending to external destinations (GitHub issue reports, debug
|
|
3
|
+
// exports). Pattern list ordered from most-specific to least-specific to
|
|
4
|
+
// avoid accidental re-matching by a more general rule.
|
|
5
|
+
|
|
6
|
+
// Scope: this module is called on OUR OWN trusted log/debug data (logger
|
|
7
|
+
// output, doctor reports, cookies). It is NOT a user-input sanitizer.
|
|
8
|
+
// Deliberate trade-offs in the current pattern set:
|
|
9
|
+
// - Emails are matched post-URL-decode only (raw %40 encoded forms pass through)
|
|
10
|
+
// - Unix home paths assume no whitespace in usernames
|
|
11
|
+
// - Windows home paths only cover C:\Users\... (UNC paths pass through)
|
|
12
|
+
// - Long-token redaction (≥20 base64/hex chars) may over-redact legitimate
|
|
13
|
+
// long URLs or JSON values — accepted trade-off for safety over debuggability
|
|
14
|
+
// If any of these assumptions changes (e.g., we start redacting user-supplied
|
|
15
|
+
// payloads), the pattern set must be revisited.
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Canonical secret-shape regex list. Distinct from the legacy PATTERNS array
|
|
19
|
+
* below because every match emits a kind-tagged `<redacted:<kind>>` placeholder
|
|
20
|
+
* so the redactor's output is unambiguous for audit / test / grep-gate
|
|
21
|
+
* purposes. Applied BEFORE the legacy PATTERNS inside redactString so specific
|
|
22
|
+
* shapes win over the generic long-token catchall.
|
|
23
|
+
*/
|
|
24
|
+
const SECRET_PATTERNS = Object.freeze([
|
|
25
|
+
// OAuth / local prefixes come FIRST so a value like `"bearer":"pplx_at_…"`
|
|
26
|
+
// gets the specific oauth-access tag instead of the generic daemon-bearer
|
|
27
|
+
// one from the bearer-json catchall below.
|
|
28
|
+
{ name: "oauth-access", kind: "oauth-access", re: /pplx_at_[A-Za-z0-9_\-]{10,}/g },
|
|
29
|
+
{ name: "oauth-refresh", kind: "oauth-refresh", re: /pplx_rt_[A-Za-z0-9_\-]{10,}/g },
|
|
30
|
+
{ name: "oauth-code", kind: "oauth-code", re: /pplx_ac_[A-Za-z0-9_\-]{10,}/g },
|
|
31
|
+
{ name: "local-bearer", kind: "local-bearer", re: /pplx_local_[a-z0-9-]+_[A-Za-z0-9_\-]{10,}/g },
|
|
32
|
+
{ name: "daemon-bearer-json", kind: "daemon-bearer", re: /"bearerToken"\s*:\s*"[A-Za-z0-9_\-]{30,}"/g },
|
|
33
|
+
// Matches the `"bearer":"..."` shape used by daemon:bearer:reveal:response
|
|
34
|
+
// so reveal-payload logs stay leak-free. Value is only required to be
|
|
35
|
+
// 20+ safe-identifier chars — covers both raw daemon bearers and future
|
|
36
|
+
// short-lived tokens.
|
|
37
|
+
{ name: "bearer-json", kind: "daemon-bearer", re: /"bearer"\s*:\s*"[A-Za-z0-9_\-]{20,}"/g },
|
|
38
|
+
{ name: "authorization-header", kind: "bearer-header", re: /[Aa]uthorization\s*:\s*Bearer\s+[A-Za-z0-9_\-\.]{20,}/g },
|
|
39
|
+
{ name: "ngrok-authtoken", kind: "ngrok-authtoken", re: /"authtoken"\s*:\s*"\d+[A-Za-z0-9]{20,}"/g },
|
|
40
|
+
{ name: "jwt", kind: "jwt", re: /eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+/g },
|
|
41
|
+
{ name: "cf-clearance", kind: "cf-clearance", re: /cf_clearance=[^;\s]+/g },
|
|
42
|
+
{ name: "perplexity-session", kind: "perplexity-session", re: /__Secure-next-auth\.session-token=[^;\s]+/g },
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* String-only secret redactor. Applies SECRET_PATTERNS and returns a string
|
|
47
|
+
* with kind-tagged placeholders. Does NOT apply the legacy PATTERNS
|
|
48
|
+
* (emails / userIds / paths / IPs / generic long-token); call `redact()` for
|
|
49
|
+
* the full composite behavior.
|
|
50
|
+
* @param {string} input
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
function redactSecrets(input) {
|
|
54
|
+
if (typeof input !== "string") return input;
|
|
55
|
+
let out = input;
|
|
56
|
+
for (const { re, kind } of SECRET_PATTERNS) {
|
|
57
|
+
out = out.replace(re, (match) => {
|
|
58
|
+
if (match.startsWith('"bearerToken"')) return `"bearerToken":"<redacted:${kind}>"`;
|
|
59
|
+
if (match.startsWith('"bearer"')) return `"bearer":"<redacted:${kind}>"`;
|
|
60
|
+
if (/^[Aa]uthorization\s*:/i.test(match)) return match.replace(/Bearer\s+[A-Za-z0-9_\-\.]{20,}/, `Bearer <redacted:${kind}>`);
|
|
61
|
+
if (match.startsWith('"authtoken"')) return `"authtoken":"<redacted:${kind}>"`;
|
|
62
|
+
if (match.startsWith("cf_clearance=")) return `cf_clearance=<redacted:${kind}>`;
|
|
63
|
+
if (match.startsWith("__Secure-next-auth.session-token=")) return `__Secure-next-auth.session-token=<redacted:${kind}>`;
|
|
64
|
+
return `<redacted:${kind}>`;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const PATTERNS = [
|
|
71
|
+
// Emails: RFC 5322 subset. Must come before generic token rules because
|
|
72
|
+
// emails contain characters that other rules would catch.
|
|
73
|
+
{
|
|
74
|
+
re: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
75
|
+
replace: "<email>",
|
|
76
|
+
},
|
|
77
|
+
// Perplexity user IDs: user_ followed by hex/alphanum (>=8 chars).
|
|
78
|
+
{
|
|
79
|
+
re: /\buser_[A-Fa-f0-9]{8,}\b/g,
|
|
80
|
+
replace: "<userId>",
|
|
81
|
+
},
|
|
82
|
+
// Home directory paths. Must replace before the "long opaque token" rule
|
|
83
|
+
// because long path segments would otherwise trip it.
|
|
84
|
+
{
|
|
85
|
+
re: /(\/Users\/|\/home\/)[^/\s]+/g,
|
|
86
|
+
replace: "<home>",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
re: /([A-Z]:)\\Users\\[^\\]+/g,
|
|
90
|
+
replace: "<home>",
|
|
91
|
+
},
|
|
92
|
+
// IPv4
|
|
93
|
+
{
|
|
94
|
+
re: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
|
|
95
|
+
replace: "<ip>",
|
|
96
|
+
},
|
|
97
|
+
// IPv6. Three alternations cover: (1) full 8-group addresses, (2) :: compressed
|
|
98
|
+
// forms with at least one prefix group (e.g. 2001:db8::1, fe80::1), (3) leading-::
|
|
99
|
+
// forms like ::1 and ::. A function filter then requires hex letters (a-f/A-F) OR
|
|
100
|
+
// a double-colon so that pure-digit colon-separated strings like "23:59:59"
|
|
101
|
+
// (HH:MM:SS wall-clock) and ISO timestamps are left untouched.
|
|
102
|
+
{
|
|
103
|
+
re: /\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b|\b(?:[0-9a-fA-F]{1,4}:){1,7}:(?:[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){0,6})?\b|(?<!\w)::(?:[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){0,6})?(?!\w)/g,
|
|
104
|
+
replace: (match) => {
|
|
105
|
+
const hasHex = /[a-fA-F]/.test(match);
|
|
106
|
+
const hasDoubleColon = /::/.test(match);
|
|
107
|
+
return (hasHex || hasDoubleColon) ? "<ip>" : match;
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
// Long opaque tokens (base64 / hex, >=20 chars). Applied last so more
|
|
111
|
+
// specific rules win first. Captures key=value and replaces only the value.
|
|
112
|
+
{
|
|
113
|
+
re: /=([A-Za-z0-9+/=]{20,})/g,
|
|
114
|
+
replace: "=<redacted>",
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Redact sensitive patterns from a string or object graph.
|
|
120
|
+
* For objects: every string-valued leaf is redacted recursively.
|
|
121
|
+
* Arrays are handled recursively too. Primitive non-strings are returned unchanged.
|
|
122
|
+
* @template T
|
|
123
|
+
* @param {T} value
|
|
124
|
+
* @param {WeakSet<object>} [_seen]
|
|
125
|
+
* @returns {T}
|
|
126
|
+
*/
|
|
127
|
+
function redact(value, _seen) {
|
|
128
|
+
if (value == null) return value;
|
|
129
|
+
if (typeof value === "string") return redactString(value);
|
|
130
|
+
if (typeof value !== "object") return value;
|
|
131
|
+
|
|
132
|
+
// Cycle detection for objects/arrays. We use a WeakSet threaded through the
|
|
133
|
+
// recursion so siblings don't falsely collide with each other.
|
|
134
|
+
const seen = _seen ?? new WeakSet();
|
|
135
|
+
if (seen.has(value)) return "<circular>";
|
|
136
|
+
seen.add(value);
|
|
137
|
+
|
|
138
|
+
if (Array.isArray(value)) return value.map((v) => redact(v, seen));
|
|
139
|
+
|
|
140
|
+
const out = {};
|
|
141
|
+
for (const [k, v] of Object.entries(value)) out[k] = redact(v, seen);
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function redactString(s) {
|
|
146
|
+
// SECRET_PATTERNS first: specific bearer / OAuth / JWT / ngrok / cookie
|
|
147
|
+
// shapes get tagged "<redacted:<kind>>" placeholders. Then the legacy
|
|
148
|
+
// PATTERNS handle everything else (emails / userIds / home paths / IPs /
|
|
149
|
+
// generic long-token catchall). Running secrets first prevents the generic
|
|
150
|
+
// `=(20+chars)` catchall from eating a bearer with a plain `<redacted>`
|
|
151
|
+
// label when we'd rather have `<redacted:oauth-access>`.
|
|
152
|
+
let out = redactSecrets(s);
|
|
153
|
+
for (const { re, replace } of PATTERNS) {
|
|
154
|
+
out = out.replace(re, replace);
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { SECRET_PATTERNS, redact, redactSecrets };
|
package/dist/redact.mjs
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { PlaywrightCookie } from './config.js';
|
|
2
|
+
import 'patchright';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Live refresh of account info (models/config, ASI access, rate limits, experiments).
|
|
6
|
+
*
|
|
7
|
+
* Architecture — three pluggable tiers, tried in order:
|
|
8
|
+
*
|
|
9
|
+
* 1. got-scraping — pure JS HTTP client (Apify's got fork). Reorders TLS
|
|
10
|
+
* extensions + HTTP/2 SETTINGS + header ordering to look Chrome-ish.
|
|
11
|
+
* ~200ms round trip. Always available (shipped as a regular dep).
|
|
12
|
+
*
|
|
13
|
+
* 2. impit (optional) — Rust-backed JA3/JA4 impersonation via rustls-patched +
|
|
14
|
+
* reqwest. Closer to real Chrome than got-scraping. Only attempted if the
|
|
15
|
+
* user has installed it into ~/.perplexity-mcp/native-deps/ (Settings
|
|
16
|
+
* → Speed Boost). ~300-500ms.
|
|
17
|
+
*
|
|
18
|
+
* 3. browser — headless Patchright. Guaranteed to work because Chromium
|
|
19
|
+
* speaks the same TLS as whatever solved Turnstile at login time. ~3-5s.
|
|
20
|
+
*
|
|
21
|
+
* Each tier returns the same TierResult shape so the orchestrator can just
|
|
22
|
+
* walk them in order. A CF challenge (HTML response instead of JSON) counts
|
|
23
|
+
* as "try the next tier". Anything else (network error, 5xx, unparseable)
|
|
24
|
+
* also cascades.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
type RefreshTier = "got-scraping" | "impit" | "browser";
|
|
28
|
+
interface RefreshResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
source: "live" | "no-cookies" | "cf-challenge" | "failed";
|
|
31
|
+
tier: RefreshTier | null;
|
|
32
|
+
modelCount: number;
|
|
33
|
+
accountTier: "Max" | "Pro" | "Enterprise" | "Free" | "Unknown";
|
|
34
|
+
error?: string;
|
|
35
|
+
cachePath: string;
|
|
36
|
+
elapsedMs: number;
|
|
37
|
+
tierAttempts?: Array<{
|
|
38
|
+
tier: RefreshTier;
|
|
39
|
+
ok: boolean;
|
|
40
|
+
elapsedMs: number;
|
|
41
|
+
error?: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
interface RefreshOptions {
|
|
45
|
+
log?: (line: string) => void;
|
|
46
|
+
timeoutMs?: number;
|
|
47
|
+
/** Force a particular tier for testing. Omit for normal cascade. */
|
|
48
|
+
forceTier?: RefreshTier;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Minimal shape of the `impit` module that we actually call.
|
|
52
|
+
* Full types aren't declared in our tsconfig because impit is NOT a build-time
|
|
53
|
+
* dependency — it's installed at runtime by the user via "Install Speed Boost".
|
|
54
|
+
*/
|
|
55
|
+
interface ImpitModule {
|
|
56
|
+
Impit: new (opts: {
|
|
57
|
+
browser: string;
|
|
58
|
+
ignoreTlsErrors?: boolean;
|
|
59
|
+
proxyUrl?: string;
|
|
60
|
+
}) => {
|
|
61
|
+
fetch(url: string, init?: {
|
|
62
|
+
method?: string;
|
|
63
|
+
body?: string | Uint8Array;
|
|
64
|
+
headers?: Record<string, string>;
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
redirect?: "follow" | "manual" | "error";
|
|
67
|
+
}): Promise<{
|
|
68
|
+
status: number;
|
|
69
|
+
headers: Headers | Record<string, string>;
|
|
70
|
+
text(): Promise<string>;
|
|
71
|
+
json(): Promise<unknown>;
|
|
72
|
+
}>;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Dynamically import impit from the user's native-deps directory.
|
|
77
|
+
* Returns null if impit isn't installed there.
|
|
78
|
+
*
|
|
79
|
+
* Note on createRequire: when this code is bundled into the extension host
|
|
80
|
+
* (CJS via tsup), `import.meta.url` is undefined, which makes
|
|
81
|
+
* `createRequire(import.meta.url)` throw — the previous implementation
|
|
82
|
+
* silently caught that and reported "impit not installed" even when the
|
|
83
|
+
* package was on disk. Rooting `createRequire` at the impit module's own
|
|
84
|
+
* absolute path works in both CJS- and ESM-bundled output (Node accepts a
|
|
85
|
+
* file path string per the docs) and resolves impit's peer native binding
|
|
86
|
+
* from native-deps/node_modules correctly.
|
|
87
|
+
*/
|
|
88
|
+
declare function loadImpit(): Promise<ImpitModule | null>;
|
|
89
|
+
declare function isImpitAvailable(): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Browser-free authenticated fetch via impit. Used as a fast path for REST
|
|
92
|
+
* endpoints that only need cookies + a Chrome-ish TLS fingerprint
|
|
93
|
+
* (e.g. /rest/thread/list_ask_threads). Callers should fall back to the
|
|
94
|
+
* browser path on any non-success outcome.
|
|
95
|
+
*
|
|
96
|
+
* Returns null when impit isn't installed/loadable or the request threw at
|
|
97
|
+
* the network level. Returns `{ challenged: true }` when the response body
|
|
98
|
+
* looks like a Cloudflare interstitial.
|
|
99
|
+
*/
|
|
100
|
+
declare function impitFetchJson(url: string, init: {
|
|
101
|
+
method?: string;
|
|
102
|
+
body?: unknown;
|
|
103
|
+
headers?: Record<string, string>;
|
|
104
|
+
} | undefined, cookies: PlaywrightCookie[], timeoutMs?: number): Promise<{
|
|
105
|
+
status: number;
|
|
106
|
+
data: unknown;
|
|
107
|
+
challenged?: boolean;
|
|
108
|
+
} | null>;
|
|
109
|
+
declare function refreshAccountInfo(opts?: RefreshOptions): Promise<RefreshResult>;
|
|
110
|
+
declare function getModelsCacheInfo(): {
|
|
111
|
+
path: string;
|
|
112
|
+
exists: boolean;
|
|
113
|
+
mtime: Date | null;
|
|
114
|
+
ageHours: number | null;
|
|
115
|
+
};
|
|
116
|
+
declare function getImpitRuntimeDir(): string;
|
|
117
|
+
|
|
118
|
+
export { type ImpitModule, type RefreshOptions, type RefreshResult, type RefreshTier, getImpitRuntimeDir, getModelsCacheInfo, impitFetchJson, isImpitAvailable, loadImpit, refreshAccountInfo };
|
package/dist/refresh.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getImpitRuntimeDir,
|
|
3
|
+
getModelsCacheInfo,
|
|
4
|
+
impitFetchJson,
|
|
5
|
+
isImpitAvailable,
|
|
6
|
+
loadImpit,
|
|
7
|
+
refreshAccountInfo
|
|
8
|
+
} from "./chunk-Z7DAACGZ.mjs";
|
|
9
|
+
import "./chunk-LKJMLGFP.mjs";
|
|
10
|
+
import "./chunk-TQLCLE4L.mjs";
|
|
11
|
+
import "./chunk-MTDFKNXX.mjs";
|
|
12
|
+
import "./chunk-XKSWCEGI.mjs";
|
|
13
|
+
import "./chunk-4UEJOM6W.mjs";
|
|
14
|
+
export {
|
|
15
|
+
getImpitRuntimeDir,
|
|
16
|
+
getModelsCacheInfo,
|
|
17
|
+
impitFetchJson,
|
|
18
|
+
isImpitAvailable,
|
|
19
|
+
loadImpit,
|
|
20
|
+
refreshAccountInfo
|
|
21
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface ReinitWatcher {
|
|
2
|
+
dispose(): void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface WatchReinitOptions {
|
|
6
|
+
debounceMs?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare function watchReinit(
|
|
10
|
+
profileName: string,
|
|
11
|
+
callback: () => void | Promise<void>,
|
|
12
|
+
opts?: WatchReinitOptions
|
|
13
|
+
): ReinitWatcher;
|
|
14
|
+
|
|
15
|
+
export { type ReinitWatcher, type WatchReinitOptions, watchReinit };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const API_VERSION_QUERY = "version=2.18&source=default";
|
|
2
|
+
|
|
3
|
+
function buildRuntimeEndpoints(origin) {
|
|
4
|
+
const base = origin.replace(/\/+$/, "");
|
|
5
|
+
return {
|
|
6
|
+
session: `${base}/api/auth/session?${API_VERSION_QUERY}`,
|
|
7
|
+
csrf: `${base}/api/auth/csrf?${API_VERSION_QUERY}`,
|
|
8
|
+
signInEmail: `${base}/api/auth/signin/email?${API_VERSION_QUERY}`,
|
|
9
|
+
otpRedirectLink: `${base}/api/auth/otp-redirect-link`,
|
|
10
|
+
ssoDetails: `${base}/rest/enterprise/organization/login/details?${API_VERSION_QUERY}`,
|
|
11
|
+
models: `${base}/rest/models/config?config_schema=v1&${API_VERSION_QUERY}`,
|
|
12
|
+
asi: `${base}/rest/billing/asi-access-decision?${API_VERSION_QUERY}`,
|
|
13
|
+
rateLimits: `${base}/rest/rate-limit/status?${API_VERSION_QUERY}`,
|
|
14
|
+
experiments: `${base}/rest/experiments/attributes?${API_VERSION_QUERY}`,
|
|
15
|
+
userInfo: `${base}/rest/user/info?${API_VERSION_QUERY}`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function pageRequest(page, url, init = {}) {
|
|
20
|
+
return page.evaluate(async ({ url: target, init: requestInit }) => {
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(target, {
|
|
23
|
+
credentials: "include",
|
|
24
|
+
...requestInit,
|
|
25
|
+
});
|
|
26
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
27
|
+
let json = null;
|
|
28
|
+
let text = null;
|
|
29
|
+
try {
|
|
30
|
+
if (contentType.includes("json")) json = await response.json();
|
|
31
|
+
else text = (await response.text()).slice(0, 500);
|
|
32
|
+
} catch {}
|
|
33
|
+
return {
|
|
34
|
+
ok: response.ok,
|
|
35
|
+
status: response.status,
|
|
36
|
+
redirected: response.redirected,
|
|
37
|
+
url: response.url,
|
|
38
|
+
contentType,
|
|
39
|
+
json,
|
|
40
|
+
text,
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
status: 0,
|
|
46
|
+
redirected: false,
|
|
47
|
+
url: target,
|
|
48
|
+
contentType: "",
|
|
49
|
+
json: null,
|
|
50
|
+
text: null,
|
|
51
|
+
error: error?.message ?? String(error),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}, { url, init });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function pollSession(page, sessionUrl, { timeoutMs = 10_000, intervalMs = 500 } = {}) {
|
|
58
|
+
const started = Date.now();
|
|
59
|
+
while (Date.now() - started < timeoutMs) {
|
|
60
|
+
const sessionResp = await pageRequest(page, sessionUrl);
|
|
61
|
+
if (sessionResp.ok && sessionResp.json?.user?.id) {
|
|
62
|
+
return sessionResp.json;
|
|
63
|
+
}
|
|
64
|
+
await page.waitForTimeout(intervalMs);
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function deriveAccountFlags({ experiments, userInfo, asi }) {
|
|
70
|
+
const isEnterprise = userInfo?.is_enterprise === true || experiments?.server_is_enterprise === true;
|
|
71
|
+
const isMax = experiments?.server_is_max === true;
|
|
72
|
+
const canUseComputer = asi?.can_use_computer ?? false;
|
|
73
|
+
const isPro =
|
|
74
|
+
experiments?.server_is_pro === true ||
|
|
75
|
+
(canUseComputer && !isMax && !isEnterprise);
|
|
76
|
+
|
|
77
|
+
return { isPro, isMax, isEnterprise, canUseComputer };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function deriveTier(payload) {
|
|
81
|
+
const { isPro, isMax, isEnterprise } = deriveAccountFlags(payload);
|
|
82
|
+
if (isMax) return "Max";
|
|
83
|
+
if (isEnterprise) return "Enterprise";
|
|
84
|
+
if (isPro) return "Pro";
|
|
85
|
+
return "Authenticated";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function collectSessionMetadata(page, origin, opts = {}) {
|
|
89
|
+
const endpoints = buildRuntimeEndpoints(origin);
|
|
90
|
+
const sessionData =
|
|
91
|
+
opts.sessionData ??
|
|
92
|
+
await pollSession(page, endpoints.session, { timeoutMs: opts.sessionTimeoutMs ?? 10_000 });
|
|
93
|
+
|
|
94
|
+
if (!sessionData?.user?.id) {
|
|
95
|
+
return {
|
|
96
|
+
sessionData: null,
|
|
97
|
+
models: null,
|
|
98
|
+
asi: null,
|
|
99
|
+
rateLimits: null,
|
|
100
|
+
experiments: null,
|
|
101
|
+
userInfo: null,
|
|
102
|
+
tier: "Authenticated",
|
|
103
|
+
cache: {
|
|
104
|
+
modelsConfig: null,
|
|
105
|
+
rateLimits: null,
|
|
106
|
+
isPro: false,
|
|
107
|
+
isMax: false,
|
|
108
|
+
isEnterprise: false,
|
|
109
|
+
canUseComputer: false,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const [modelsResp, asiResp, rateResp, expResp, userInfoResp] = await Promise.all([
|
|
115
|
+
pageRequest(page, endpoints.models),
|
|
116
|
+
pageRequest(page, endpoints.asi),
|
|
117
|
+
pageRequest(page, endpoints.rateLimits),
|
|
118
|
+
pageRequest(page, endpoints.experiments),
|
|
119
|
+
pageRequest(page, endpoints.userInfo),
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
const payload = {
|
|
123
|
+
experiments: expResp.ok ? expResp.json : null,
|
|
124
|
+
userInfo: userInfoResp.ok ? userInfoResp.json : null,
|
|
125
|
+
asi: asiResp.ok ? asiResp.json : null,
|
|
126
|
+
};
|
|
127
|
+
const flags = deriveAccountFlags(payload);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
sessionData,
|
|
131
|
+
models: modelsResp.ok ? modelsResp.json : null,
|
|
132
|
+
asi: asiResp.ok ? asiResp.json : null,
|
|
133
|
+
rateLimits: rateResp.ok ? rateResp.json : null,
|
|
134
|
+
experiments: expResp.ok ? expResp.json : null,
|
|
135
|
+
userInfo: userInfoResp.ok ? userInfoResp.json : null,
|
|
136
|
+
tier: deriveTier(payload),
|
|
137
|
+
cache: {
|
|
138
|
+
modelsConfig: modelsResp.ok ? modelsResp.json : null,
|
|
139
|
+
rateLimits: rateResp.ok ? rateResp.json : null,
|
|
140
|
+
isPro: flags.isPro,
|
|
141
|
+
isMax: flags.isMax,
|
|
142
|
+
isEnterprise: flags.isEnterprise,
|
|
143
|
+
canUseComputer: flags.canUseComputer,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { buildRuntimeEndpoints as b, collectSessionMetadata as c, pageRequest as p };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
|
|
3
|
+
function promptSecret({ stdin = process.stdin, stderr = process.stderr, prompt = "> " } = {}) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
stderr.write(prompt);
|
|
6
|
+
const rl = createInterface({ input: stdin, output: stderr, terminal: false });
|
|
7
|
+
rl.once("line", (line) => {
|
|
8
|
+
rl.close();
|
|
9
|
+
resolve(String(line).trim());
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Minimal y/N confirmation prompt for destructive-ish CLI subcommands.
|
|
16
|
+
*
|
|
17
|
+
* - Prompt goes to stderr. Answer comes from stdin. Matches the existing
|
|
18
|
+
* `promptSecret` shape so tests can inject fakes.
|
|
19
|
+
* - Accepts `y`, `Y`, `yes`, `YES`, `Yes` as confirmation. Everything else —
|
|
20
|
+
* including empty input, EOF (Ctrl-D), and stdin close — is treated as a
|
|
21
|
+
* decline. Returns `true` on confirm, `false` on decline.
|
|
22
|
+
* - Does NOT exit the process; the caller decides whether to exit 130.
|
|
23
|
+
*/
|
|
24
|
+
function promptYesNo({ stdin = process.stdin, stderr = process.stderr, prompt = "Continue? [y/N] " } = {}) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
stderr.write(prompt);
|
|
27
|
+
const rl = createInterface({ input: stdin, output: stderr, terminal: false });
|
|
28
|
+
let settled = false;
|
|
29
|
+
const finish = (value) => {
|
|
30
|
+
if (settled) return;
|
|
31
|
+
settled = true;
|
|
32
|
+
try { rl.close(); } catch { /* ignore */ }
|
|
33
|
+
resolve(value);
|
|
34
|
+
};
|
|
35
|
+
rl.once("line", (line) => {
|
|
36
|
+
const answer = String(line).trim().toLowerCase();
|
|
37
|
+
finish(answer === "y" || answer === "yes");
|
|
38
|
+
});
|
|
39
|
+
// EOF / stream close before a line arrives → decline.
|
|
40
|
+
rl.once("close", () => finish(false));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { promptSecret, promptYesNo };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import "./chunk-4UEJOM6W.mjs";
|
|
2
|
+
|
|
3
|
+
// src/tty-prompt.js
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
function promptSecret({ stdin = process.stdin, stderr = process.stderr, prompt = "> " } = {}) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
stderr.write(prompt);
|
|
8
|
+
const rl = createInterface({ input: stdin, output: stderr, terminal: false });
|
|
9
|
+
rl.once("line", (line) => {
|
|
10
|
+
rl.close();
|
|
11
|
+
resolve(String(line).trim());
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function promptYesNo({ stdin = process.stdin, stderr = process.stderr, prompt = "Continue? [y/N] " } = {}) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
stderr.write(prompt);
|
|
18
|
+
const rl = createInterface({ input: stdin, output: stderr, terminal: false });
|
|
19
|
+
let settled = false;
|
|
20
|
+
const finish = (value) => {
|
|
21
|
+
if (settled) return;
|
|
22
|
+
settled = true;
|
|
23
|
+
try {
|
|
24
|
+
rl.close();
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
resolve(value);
|
|
28
|
+
};
|
|
29
|
+
rl.once("line", (line) => {
|
|
30
|
+
const answer = String(line).trim().toLowerCase();
|
|
31
|
+
finish(answer === "y" || answer === "yes");
|
|
32
|
+
});
|
|
33
|
+
rl.once("close", () => finish(false));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
promptSecret,
|
|
38
|
+
promptYesNo
|
|
39
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare class Vault {
|
|
2
|
+
get(profile: string, key: string): Promise<string | null>;
|
|
3
|
+
set(profile: string, key: string, value: string): Promise<void>;
|
|
4
|
+
delete(profile: string, key: string): Promise<void>;
|
|
5
|
+
deleteAll(profile: string): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export { Vault };
|