@herbertgao/pi-extensions 2026.9.10 → 2026.9.12
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 +1 -0
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -2
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +2 -2
- package/node_modules/@herbertgao/resume-from/package.json +1 -1
- package/node_modules/@herbertgao/sol-pi/README.md +3 -3
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
- package/node_modules/@herbertgao/sol-pi/agents-install.md +4 -4
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +6 -6
- package/node_modules/@herbertgao/sol-pi/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/dist/index.ts +39 -89
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -87
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +8 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +40 -37
- package/node_modules/@narumitw/pi-caffeinate/README.md +21 -66
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +10 -41
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +50 -51
- package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +637 -663
- package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +114 -120
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +29 -29
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +108 -126
- package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +124 -128
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
- package/node_modules/pi-mcp-adapter/README.md +75 -7
- package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
- package/node_modules/pi-mcp-adapter/cli.js +56 -5
- package/node_modules/pi-mcp-adapter/commands.ts +96 -5
- package/node_modules/pi-mcp-adapter/config.ts +73 -6
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
- package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
- package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
- package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
- package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
- package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
- package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
- package/node_modules/pi-mcp-adapter/index.ts +129 -6
- package/node_modules/pi-mcp-adapter/init.ts +6 -19
- package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
- package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
- package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
- package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +17 -9
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
- package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
- package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
- package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
- package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
- package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
- package/node_modules/pi-mcp-adapter/types.ts +53 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
- package/node_modules/pi-mcp-adapter/utils.ts +16 -0
- package/node_modules/pi-multi-account/CHANGELOG.md +1209 -0
- package/node_modules/pi-multi-account/CONTRIBUTING.md +61 -0
- package/node_modules/pi-multi-account/LICENSE +21 -0
- package/node_modules/pi-multi-account/README.md +197 -0
- package/node_modules/pi-multi-account/SECURITY.md +27 -0
- package/node_modules/pi-multi-account/auth-file-transaction.ts +56 -0
- package/node_modules/pi-multi-account/child-usability.ts +233 -0
- package/node_modules/pi-multi-account/compaction-summary.ts +32 -0
- package/node_modules/pi-multi-account/completion-route-planner.ts +224 -0
- package/node_modules/pi-multi-account/context-guard.ts +420 -0
- package/node_modules/pi-multi-account/cursor/LICENSE +21 -0
- package/node_modules/pi-multi-account/cursor/NOTICE +2 -0
- package/node_modules/pi-multi-account/cursor/auth.ts +165 -0
- package/node_modules/pi-multi-account/cursor/bridge-handle.ts +155 -0
- package/node_modules/pi-multi-account/cursor/conversation-registry.ts +104 -0
- package/node_modules/pi-multi-account/cursor/cursor-models-raw.json +611 -0
- package/node_modules/pi-multi-account/cursor/cursor-shared.ts +192 -0
- package/node_modules/pi-multi-account/cursor/h2-bridge.mjs +175 -0
- package/node_modules/pi-multi-account/cursor/index.ts +572 -0
- package/node_modules/pi-multi-account/cursor/message-parsing.ts +323 -0
- package/node_modules/pi-multi-account/cursor/prompt-usage.ts +53 -0
- package/node_modules/pi-multi-account/cursor/proto/agent_pb.ts +15294 -0
- package/node_modules/pi-multi-account/cursor/proxy.ts +2510 -0
- package/node_modules/pi-multi-account/cursor/session-lifecycle.ts +40 -0
- package/node_modules/pi-multi-account/cursor/sse-keepalive.ts +24 -0
- package/node_modules/pi-multi-account/cursor/stream-lifecycle.ts +193 -0
- package/node_modules/pi-multi-account/cursor/upstream-watchdog.ts +88 -0
- package/node_modules/pi-multi-account/cursor-bridge.ts +240 -0
- package/node_modules/pi-multi-account/cursor-model-name.ts +12 -0
- package/node_modules/pi-multi-account/index.ts +11825 -0
- package/node_modules/pi-multi-account/model-catalog.ts +354 -0
- package/node_modules/pi-multi-account/package.json +101 -0
- package/node_modules/pi-multi-account/pi-contract.ts +281 -0
- package/node_modules/pi-multi-account/provider-payload-stream.ts +44 -0
- package/node_modules/pi-multi-account/provider-priority.ts +189 -0
- package/node_modules/pi-multi-account/slot-proxy-auth.ts +167 -0
- package/node_modules/pi-multi-account/slot-proxy.ts +344 -0
- package/node_modules/pi-multi-account/state-file-transaction.ts +67 -0
- package/node_modules/pi-multi-account/usage.ts +1099 -0
- package/node_modules/pi-typesafe/README.md +6 -2
- package/node_modules/pi-typesafe/dist/client.d.ts +11 -0
- package/node_modules/pi-typesafe/dist/client.js +45 -10
- package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/package.json +2 -2
- package/node_modules/pi-web-access/CHANGELOG.md +36 -0
- package/node_modules/pi-web-access/README.md +75 -18
- package/node_modules/pi-web-access/anysearch.ts +4 -15
- package/node_modules/pi-web-access/bocha.ts +3 -22
- package/node_modules/pi-web-access/brave.ts +3 -21
- package/node_modules/pi-web-access/brightdata.ts +5 -32
- package/node_modules/pi-web-access/content-find.ts +168 -53
- package/node_modules/pi-web-access/curator-page.ts +4 -1
- package/node_modules/pi-web-access/curator-run.ts +2 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +24620 -0
- package/node_modules/pi-web-access/domain-filter-normalization.ts +14 -0
- package/node_modules/pi-web-access/duckduckgo.ts +3 -21
- package/node_modules/pi-web-access/extract.ts +3 -1
- package/node_modules/pi-web-access/firecrawl.ts +5 -29
- package/node_modules/pi-web-access/gemini-search.ts +81 -32
- package/node_modules/pi-web-access/index.ts +149 -148
- package/node_modules/pi-web-access/jina-search.ts +4 -15
- package/node_modules/pi-web-access/kagi.ts +4 -13
- package/node_modules/pi-web-access/kimi-search.ts +5 -30
- package/node_modules/pi-web-access/mistral-search.ts +1 -15
- package/node_modules/pi-web-access/ollama.ts +2 -7
- package/node_modules/pi-web-access/openai-search.ts +174 -36
- package/node_modules/pi-web-access/opencode-session-headers.ts +24 -0
- package/node_modules/pi-web-access/package.json +10 -4
- package/node_modules/pi-web-access/page-query.ts +10 -2
- package/node_modules/pi-web-access/parallel.ts +1 -15
- package/node_modules/pi-web-access/pdf-extract.ts +3 -0
- package/node_modules/pi-web-access/querit.ts +5 -29
- package/node_modules/pi-web-access/search-answer-formatting.ts +11 -0
- package/node_modules/pi-web-access/search-result-count-normalization.ts +4 -0
- package/node_modules/pi-web-access/search1api.ts +5 -29
- package/node_modules/pi-web-access/searchinfinity.ts +5 -29
- package/node_modules/pi-web-access/searxng.ts +3 -21
- package/node_modules/pi-web-access/serpapi.ts +5 -28
- package/node_modules/pi-web-access/serpbase.ts +3 -22
- package/node_modules/pi-web-access/serpdive.ts +3 -21
- package/node_modules/pi-web-access/serper.ts +5 -28
- package/node_modules/pi-web-access/serply.ts +197 -0
- package/node_modules/pi-web-access/source-check.ts +11 -47
- package/node_modules/pi-web-access/summary-review.ts +7 -3
- package/node_modules/pi-web-access/tavily.ts +3 -21
- package/node_modules/pi-web-access/tinyfish.ts +5 -29
- package/node_modules/pi-web-access/utils.ts +9 -1
- package/node_modules/pi-web-access/valyu.ts +5 -28
- package/node_modules/pi-web-access/xai-search.ts +1 -15
- package/node_modules/pi-web-access/xcrawl.ts +5 -32
- package/package.json +23 -16
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor OAuth authentication via PKCE.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Generate PKCE verifier + challenge
|
|
6
|
+
* 2. Open browser to cursor.com/loginDeepControl
|
|
7
|
+
* 3. Poll api2.cursor.sh/auth/poll until tokens arrive
|
|
8
|
+
* 4. Refresh via api2.cursor.sh/auth/exchange_user_api_key
|
|
9
|
+
*
|
|
10
|
+
* Based on https://github.com/ephraimduncan/opencode-cursor by Ephraim Duncan.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const CURSOR_LOGIN_URL = "https://cursor.com/loginDeepControl";
|
|
14
|
+
const CURSOR_POLL_URL = "https://api2.cursor.sh/auth/poll";
|
|
15
|
+
const CURSOR_REFRESH_URL = "https://api2.cursor.sh/auth/exchange_user_api_key";
|
|
16
|
+
|
|
17
|
+
const POLL_MAX_ATTEMPTS = 150;
|
|
18
|
+
const POLL_BASE_DELAY = 1000;
|
|
19
|
+
const POLL_MAX_DELAY = 10_000;
|
|
20
|
+
const POLL_BACKOFF_MULTIPLIER = 1.2;
|
|
21
|
+
|
|
22
|
+
// ── PKCE ──
|
|
23
|
+
|
|
24
|
+
async function generatePKCE(): Promise<{ verifier: string; challenge: string }> {
|
|
25
|
+
const verifierBytes = new Uint8Array(96);
|
|
26
|
+
crypto.getRandomValues(verifierBytes);
|
|
27
|
+
const verifier = Buffer.from(verifierBytes).toString("base64url");
|
|
28
|
+
|
|
29
|
+
const data = new TextEncoder().encode(verifier);
|
|
30
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
31
|
+
const challenge = Buffer.from(hashBuffer).toString("base64url");
|
|
32
|
+
|
|
33
|
+
return { verifier, challenge };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Login params ──
|
|
37
|
+
|
|
38
|
+
export interface CursorAuthParams {
|
|
39
|
+
verifier: string;
|
|
40
|
+
challenge: string;
|
|
41
|
+
uuid: string;
|
|
42
|
+
loginUrl: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function generateCursorAuthParams(): Promise<CursorAuthParams> {
|
|
46
|
+
const { verifier, challenge } = await generatePKCE();
|
|
47
|
+
const uuid = crypto.randomUUID();
|
|
48
|
+
|
|
49
|
+
const params = new URLSearchParams({
|
|
50
|
+
challenge,
|
|
51
|
+
uuid,
|
|
52
|
+
mode: "login",
|
|
53
|
+
redirectTarget: "cli",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const loginUrl = `${CURSOR_LOGIN_URL}?${params.toString()}`;
|
|
57
|
+
return { verifier, challenge, uuid, loginUrl };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Poll for auth completion ──
|
|
61
|
+
|
|
62
|
+
export async function pollCursorAuth(
|
|
63
|
+
uuid: string,
|
|
64
|
+
verifier: string,
|
|
65
|
+
): Promise<{ accessToken: string; refreshToken: string }> {
|
|
66
|
+
let delay = POLL_BASE_DELAY;
|
|
67
|
+
let consecutiveErrors = 0;
|
|
68
|
+
|
|
69
|
+
for (let attempt = 0; attempt < POLL_MAX_ATTEMPTS; attempt++) {
|
|
70
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(
|
|
74
|
+
`${CURSOR_POLL_URL}?uuid=${uuid}&verifier=${verifier}`,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (response.status === 404) {
|
|
78
|
+
consecutiveErrors = 0;
|
|
79
|
+
delay = Math.min(delay * POLL_BACKOFF_MULTIPLIER, POLL_MAX_DELAY);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (response.ok) {
|
|
84
|
+
const data = (await response.json()) as {
|
|
85
|
+
accessToken: string;
|
|
86
|
+
refreshToken: string;
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
accessToken: data.accessToken,
|
|
90
|
+
refreshToken: data.refreshToken,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
throw new Error(`Poll failed: ${response.status}`);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
consecutiveErrors++;
|
|
97
|
+
if (consecutiveErrors >= 3) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
"Too many consecutive errors during Cursor auth polling",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw new Error("Cursor authentication polling timeout");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Token refresh ──
|
|
109
|
+
|
|
110
|
+
export interface CursorCredentials {
|
|
111
|
+
access: string;
|
|
112
|
+
refresh: string;
|
|
113
|
+
expires: number;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function refreshCursorToken(
|
|
117
|
+
refreshToken: string,
|
|
118
|
+
): Promise<CursorCredentials> {
|
|
119
|
+
const response = await fetch(CURSOR_REFRESH_URL, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
Authorization: `Bearer ${refreshToken}`,
|
|
123
|
+
"Content-Type": "application/json",
|
|
124
|
+
},
|
|
125
|
+
body: "{}",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
const error = await response.text();
|
|
130
|
+
throw new Error(`Cursor token refresh failed: ${error}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const data = (await response.json()) as {
|
|
134
|
+
accessToken: string;
|
|
135
|
+
refreshToken: string;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
access: data.accessToken,
|
|
140
|
+
refresh: data.refreshToken || refreshToken,
|
|
141
|
+
expires: getTokenExpiry(data.accessToken),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── JWT expiry extraction ──
|
|
146
|
+
|
|
147
|
+
export function getTokenExpiry(token: string): number {
|
|
148
|
+
try {
|
|
149
|
+
const parts = token.split(".");
|
|
150
|
+
if (parts.length !== 3 || !parts[1]) {
|
|
151
|
+
return Date.now() + 3600 * 1000;
|
|
152
|
+
}
|
|
153
|
+
const decoded = JSON.parse(
|
|
154
|
+
atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")),
|
|
155
|
+
);
|
|
156
|
+
if (
|
|
157
|
+
decoded &&
|
|
158
|
+
typeof decoded === "object" &&
|
|
159
|
+
typeof decoded.exp === "number"
|
|
160
|
+
) {
|
|
161
|
+
return decoded.exp * 1000 - 5 * 60 * 1000;
|
|
162
|
+
}
|
|
163
|
+
} catch {}
|
|
164
|
+
return Date.now() + 3600 * 1000;
|
|
165
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The handle around one bridge child process.
|
|
3
|
+
*
|
|
4
|
+
* Cursor talks over a long-lived HTTP/2 stream, and the conversation is genuinely
|
|
5
|
+
* bidirectional: while the model streams its answer, Cursor keeps asking us for blobs and we
|
|
6
|
+
* keep answering on the same pipe. So the pipe can be closed from our side — a cancelled turn,
|
|
7
|
+
* a session teardown, a compaction that starts a new conversation — while replies to Cursor's
|
|
8
|
+
* requests are still in flight.
|
|
9
|
+
*
|
|
10
|
+
* A write into a closed pipe does not fail where it is written. Node reports
|
|
11
|
+
* ERR_STREAM_WRITE_AFTER_END on a later tick, as an `error` event on the stream, which walks
|
|
12
|
+
* straight past the try/catch at the call site and, with no listener, becomes an
|
|
13
|
+
* uncaughtException that takes Pi down with it. That is exactly what happened after a
|
|
14
|
+
* compaction closed the bridge and Cursor asked for one more blob.
|
|
15
|
+
*
|
|
16
|
+
* Hence two rules, both enforced here rather than at each of the dozen call sites:
|
|
17
|
+
* - once ended, the handle is not alive and silently drops writes;
|
|
18
|
+
* - every stream we own carries an error listener, so nothing the bridge does can ever
|
|
19
|
+
* surface as an uncaught exception in the host process.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface BridgeStreams {
|
|
23
|
+
stdin: {
|
|
24
|
+
write(chunk: Uint8Array): unknown;
|
|
25
|
+
end(): unknown;
|
|
26
|
+
on(event: "error", listener: (error: Error) => void): unknown;
|
|
27
|
+
readonly writableEnded?: boolean;
|
|
28
|
+
readonly destroyed?: boolean;
|
|
29
|
+
} | null;
|
|
30
|
+
stdout: {
|
|
31
|
+
on(event: "data" | "error", listener: (payload: never) => void): unknown;
|
|
32
|
+
} | null;
|
|
33
|
+
on(event: "exit" | "error", listener: (payload: never) => void): unknown;
|
|
34
|
+
kill(signal?: NodeJS.Signals | number): unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BridgeHandle {
|
|
38
|
+
proc: { kill(signal?: NodeJS.Signals | number): unknown };
|
|
39
|
+
readonly alive: boolean;
|
|
40
|
+
write(data: Uint8Array): void;
|
|
41
|
+
end(): void;
|
|
42
|
+
destroy(): void;
|
|
43
|
+
onData(cb: (chunk: Buffer) => void): void;
|
|
44
|
+
onClose(cb: (code: number) => void): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Length-prefix a frame the way the bridge process expects it. */
|
|
48
|
+
export function lpEncode(data: Uint8Array): Buffer {
|
|
49
|
+
const buf = Buffer.alloc(4 + data.length);
|
|
50
|
+
buf.writeUInt32BE(data.length, 0);
|
|
51
|
+
buf.set(data, 4);
|
|
52
|
+
return buf;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createBridgeHandle(
|
|
56
|
+
proc: BridgeStreams,
|
|
57
|
+
hooks: { debug?: (event: string, data: Record<string, unknown>) => void } = {},
|
|
58
|
+
): BridgeHandle {
|
|
59
|
+
const debug = hooks.debug ?? (() => {});
|
|
60
|
+
const cbs = {
|
|
61
|
+
data: null as ((chunk: Buffer) => void) | null,
|
|
62
|
+
close: null as ((code: number) => void) | null,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
let exited = false;
|
|
66
|
+
let ended = false;
|
|
67
|
+
let exitCode = 1;
|
|
68
|
+
|
|
69
|
+
// The safety net. A late write, a broken pipe, a bridge that could not be spawned at all —
|
|
70
|
+
// none of them may reach the host's uncaughtException handler.
|
|
71
|
+
proc.stdin?.on("error", (error: Error) => {
|
|
72
|
+
debug("bridge.stdin_error", { error: String(error) });
|
|
73
|
+
});
|
|
74
|
+
proc.stdout?.on("error", ((error: Error) => {
|
|
75
|
+
debug("bridge.stdout_error", { error: String(error) });
|
|
76
|
+
}) as never);
|
|
77
|
+
proc.on("error", ((error: Error) => {
|
|
78
|
+
debug("bridge.process_error", { error: String(error) });
|
|
79
|
+
}) as never);
|
|
80
|
+
|
|
81
|
+
let pending = Buffer.alloc(0);
|
|
82
|
+
proc.stdout?.on("data", ((chunk: Buffer) => {
|
|
83
|
+
pending = Buffer.concat([pending, chunk]);
|
|
84
|
+
while (pending.length >= 4) {
|
|
85
|
+
const len = pending.readUInt32BE(0);
|
|
86
|
+
if (pending.length < 4 + len) break;
|
|
87
|
+
const payload = pending.subarray(4, 4 + len);
|
|
88
|
+
pending = pending.subarray(4 + len);
|
|
89
|
+
cbs.data?.(Buffer.from(payload));
|
|
90
|
+
}
|
|
91
|
+
}) as never);
|
|
92
|
+
|
|
93
|
+
proc.on("exit", ((code: number | null) => {
|
|
94
|
+
exited = true;
|
|
95
|
+
exitCode = code ?? 1;
|
|
96
|
+
debug("bridge.exit", { exitCode });
|
|
97
|
+
cbs.close?.(exitCode);
|
|
98
|
+
}) as never);
|
|
99
|
+
|
|
100
|
+
const writable = (): boolean => {
|
|
101
|
+
if (exited || ended) return false;
|
|
102
|
+
const stdin = proc.stdin;
|
|
103
|
+
if (!stdin) return false;
|
|
104
|
+
return !stdin.writableEnded && !stdin.destroyed;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const end = (): void => {
|
|
108
|
+
if (ended) return;
|
|
109
|
+
if (writable()) {
|
|
110
|
+
try {
|
|
111
|
+
proc.stdin!.write(lpEncode(new Uint8Array(0)));
|
|
112
|
+
proc.stdin!.end();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
debug("bridge.end_failed", { error: String(error) });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
ended = true;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
proc,
|
|
122
|
+
// Ended counts as dead: a caller that asks `alive` is deciding whether to keep talking on
|
|
123
|
+
// this pipe, and the answer for a pipe we closed ourselves is no.
|
|
124
|
+
get alive() { return !exited && !ended; },
|
|
125
|
+
write(data: Uint8Array) {
|
|
126
|
+
if (!writable()) {
|
|
127
|
+
debug("bridge.write_after_end", { bytes: data.length, exited, ended });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
proc.stdin!.write(lpEncode(data));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
debug("bridge.write_failed", { error: String(error) });
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
end,
|
|
137
|
+
destroy() {
|
|
138
|
+
end();
|
|
139
|
+
if (exited) return;
|
|
140
|
+
try {
|
|
141
|
+
proc.kill();
|
|
142
|
+
} catch (error) {
|
|
143
|
+
debug("bridge.kill_failed", { error: String(error) });
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
onData(cb: (chunk: Buffer) => void) { cbs.data = cb; },
|
|
147
|
+
onClose(cb: (code: number) => void) {
|
|
148
|
+
if (exited) {
|
|
149
|
+
queueMicrotask(() => cb(exitCode));
|
|
150
|
+
} else {
|
|
151
|
+
cbs.close = cb;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a Cursor conversation lives between requests.
|
|
3
|
+
*
|
|
4
|
+
* Cursor is stateful: once we hold a checkpoint for a conversation, the bridge replays that
|
|
5
|
+
* checkpoint instead of the message list Pi sent, and Cursor answers from its own copy of the
|
|
6
|
+
* history. That is fine while Pi and Cursor agree on what the history is — and wrong the moment
|
|
7
|
+
* Pi compacts, because Pi's short summary never reaches Cursor and Cursor's `usedTokens` keeps
|
|
8
|
+
* reporting the full pre-compaction size back to Pi.
|
|
9
|
+
*
|
|
10
|
+
* So the registry owns two things: the stored conversations, and a per-conversation generation
|
|
11
|
+
* counter. Bumping the generation mints a new Cursor conversation id, which is how a compaction
|
|
12
|
+
* is made to actually take effect — the checkpoint is dropped AND the old server-side
|
|
13
|
+
* conversation is left behind rather than resumed under the same id.
|
|
14
|
+
*
|
|
15
|
+
* It is a separate module from proxy.ts on purpose: proxy.ts pulls in generated protobuf code
|
|
16
|
+
* with non-erasable `enum`s, which Node's type stripping refuses to load, so nothing in it can
|
|
17
|
+
* be unit-tested directly.
|
|
18
|
+
*/
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
|
|
21
|
+
export interface StoredConversation {
|
|
22
|
+
conversationId: string;
|
|
23
|
+
checkpoint: Uint8Array | null;
|
|
24
|
+
sessionScoped: boolean;
|
|
25
|
+
blobStore: Map<string, Uint8Array>;
|
|
26
|
+
lastAccessMs: number;
|
|
27
|
+
/**
|
|
28
|
+
* How many completed turns of Pi's transcript this conversation has actually seen. Cursor
|
|
29
|
+
* answers from its own copy of the history, so anything that happened while the session was
|
|
30
|
+
* on another provider is invisible to it — and rotation moves the session between accounts
|
|
31
|
+
* constantly. See {@link isStaleForTranscript}.
|
|
32
|
+
*/
|
|
33
|
+
turnsCovered: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Has the session moved on without Cursor?
|
|
38
|
+
*
|
|
39
|
+
* Between two consecutive requests on this conversation the transcript grows by at most one
|
|
40
|
+
* completed turn — the one we just answered. A bigger jump means turns were completed
|
|
41
|
+
* somewhere Cursor never saw: a failover to another provider and back, a branch, another
|
|
42
|
+
* client. Its checkpoint is then a conversation with a hole in it, and resuming from it hands
|
|
43
|
+
* the model a past that is missing whatever happened while it was away.
|
|
44
|
+
*
|
|
45
|
+
* Rebuilding costs tokens; resuming a stale checkpoint costs correctness, silently. So the
|
|
46
|
+
* doubt is resolved toward rebuilding.
|
|
47
|
+
*/
|
|
48
|
+
export function isStaleForTranscript(stored: StoredConversation, completedTurns: number): boolean {
|
|
49
|
+
if (!stored.checkpoint) return false;
|
|
50
|
+
return completedTurns > stored.turnsCovered + 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const conversationStates = new Map<string, StoredConversation>();
|
|
54
|
+
|
|
55
|
+
/** How many times this conversation has been restarted (compaction, mostly). */
|
|
56
|
+
const conversationGenerations = new Map<string, number>();
|
|
57
|
+
|
|
58
|
+
export function deriveBridgeKeyFromSessionId(sessionId: string): string {
|
|
59
|
+
return createHash("sha256").update(`bridge:${sessionId}`).digest("hex").slice(0, 16);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function deriveConversationKeyFromSessionId(sessionId: string): string {
|
|
63
|
+
return createHash("sha256").update(`conv:${sessionId}`).digest("hex").slice(0, 16);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function conversationGeneration(convKey: string): number {
|
|
67
|
+
return conversationGenerations.get(convKey) ?? 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Stable per (conversation, generation) so a retried request rejoins the same Cursor
|
|
72
|
+
* conversation, while a post-compaction request starts a new one.
|
|
73
|
+
*/
|
|
74
|
+
export function deterministicConversationId(convKey: string, generation = conversationGeneration(convKey)): string {
|
|
75
|
+
const seed = generation > 0 ? `cursor-conv-id:${convKey}#${generation}` : `cursor-conv-id:${convKey}`;
|
|
76
|
+
const hex = createHash("sha256").update(seed).digest("hex").slice(0, 32);
|
|
77
|
+
return [
|
|
78
|
+
hex.slice(0, 8), hex.slice(8, 12),
|
|
79
|
+
`4${hex.slice(13, 16)}`,
|
|
80
|
+
`${(0x8 | (parseInt(hex[16], 16) & 0x3)).toString(16)}${hex.slice(17, 20)}`,
|
|
81
|
+
hex.slice(20, 32),
|
|
82
|
+
].join("-");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Forget everything Cursor is holding for this conversation and move to a fresh id.
|
|
87
|
+
* Returns whether a conversation was actually stored, for logging.
|
|
88
|
+
*/
|
|
89
|
+
export function forgetConversation(convKey: string): boolean {
|
|
90
|
+
const existed = conversationStates.delete(convKey);
|
|
91
|
+
conversationGenerations.set(convKey, conversationGeneration(convKey) + 1);
|
|
92
|
+
return existed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Drop a conversation for good — the session itself is gone, so its generation is noise. */
|
|
96
|
+
export function dropConversation(convKey: string): boolean {
|
|
97
|
+
conversationGenerations.delete(convKey);
|
|
98
|
+
return conversationStates.delete(convKey);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function clearConversationRegistry(): void {
|
|
102
|
+
conversationStates.clear();
|
|
103
|
+
conversationGenerations.clear();
|
|
104
|
+
}
|