#!/usr/bin/env node /** * pchat 补丁桥接:无 CO 卡密,直调 vendor co-chat 扩展补丁逻辑(headless)。 */ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); const mode = process.argv[2] || 'apply'; const vendorArg = process.argv.indexOf('--vendor'); const vendorRce = vendorArg >= 0 ? process.argv[vendorArg + 1] : null; const CURSOR_APP = '/Applications/Cursor.app'; const WORKBENCH = path.join(CURSOR_APP, 'Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js'); const EXTHOST = path.join(CURSOR_APP, 'Contents/Resources/app/out/vs/workbench/api/node/extensionHostProcess.js'); const EXT_CANDIDATES = [ vendorRce ? path.join(path.dirname(vendorRce), '..', 'dist/extension.js') : null, path.join(process.env.HOME || '', '.persistent-chat-local/lib/../vendor/cochat-engine/3.3.34/dist/extension.js'), path.join(__dirname, '../vendor/cochat-engine/3.3.34/dist/extension.js'), path.join(process.env.PCHAT_WORKSPACE || '/Users/karuo/Documents/个人', '.persistent-chat-v1.1/vendor/cochat-engine/3.3.34/dist/extension.js'), path.join(process.env.HOME || '', '.cursor/extensions/co-chat.co-chat-panel-3.3.34/dist/extension.js'), ].filter(Boolean).map((p) => path.normalize(p)); function emit(obj) { console.log(JSON.stringify(obj)); } function resolveExtensionPath() { for (const p of EXT_CANDIDATES) { if (fs.existsSync(p)) return p; } return null; } function installVscodeMock(extPath) { const mockPath = path.join('/tmp', 'pchat-vscode-mock.cjs'); const content = ` const path = require('path'); const fs = require('fs'); const extPath = ${JSON.stringify(extPath)}; const subs = []; module.exports = { ExtensionContext: function() { this.subscriptions = []; this.globalState = { get: () => undefined, update: async () => {}, keys: () => [] }; this.secrets = { get: async () => undefined, store: async () => {}, delete: async () => {} }; this.extensionPath = path.dirname(path.dirname(extPath)); this.extensionUri = { fsPath: this.extensionPath }; this.storagePath = path.join(require('os').tmpdir(), 'pchat-ext-storage'); this.globalStoragePath = path.join(require('os').tmpdir(), 'pchat-global-storage'); this.logPath = path.join(require('os').tmpdir(), 'pchat-log'); for (const d of [this.storagePath, this.globalStoragePath, this.logPath]) { try { fs.mkdirSync(d, { recursive: true }); } catch {} } }, workspace: { getConfiguration: (section) => ({ get: (k, d) => { const map = { 'kc.noQuotaMcp': true, 'kc.corePatchBundle': true, 'kc.endlessRetries': true }; return map[k] !== undefined ? map[k] : d; }, update: async () => {}, has: () => true, }), workspaceFolders: [], fs: {}, }, window: { showInformationMessage: async (m) => { console.error('[info]', m); return undefined; }, showErrorMessage: async (m) => { console.error('[err]', m); return undefined; }, showWarningMessage: async () => undefined, }, extensions: { all: [], getExtension: (id) => id && id.includes('co-chat') ? { exports: {}, extensionPath: path.dirname(path.dirname(extPath)), packageJSON: { version: '3.3.34' } } : undefined, }, env: { appRoot: ${JSON.stringify(path.join(CURSOR_APP, 'Contents/Resources/app'))}, uriScheme: 'vscode' }, commands: { registerCommand: () => ({ dispose: () => {} }) }, Uri: { file: (f) => ({ fsPath: f }) }, EventEmitter: class { constructor() { this.event = (cb) => { subs.push(cb); return { dispose: () => {} }; }; } fire(v) { subs.forEach((f) => f(v)); } }, }; `; fs.writeFileSync(mockPath, content); const Module = require('module'); const orig = Module._resolveFilename; Module._resolveFilename = function (request, parent, isMain, options) { if (request === 'vscode') return mockPath; return orig.call(this, request, parent, isMain, options); }; } async function tryHeadlessPatch(extPath) { installVscodeMock(extPath); process.env.CO_SKIP_LICENSE = '1'; process.env.PCHAT_NO_LICENSE = '1'; const extUrl = pathToFileURL(extPath).href; const mod = await import(extUrl); const activate = mod.activate || mod.default?.activate; if (typeof activate !== 'function') { throw new Error('extension.js 无 activate 导出'); } const vscode = require('vscode'); const ctx = new vscode.ExtensionContext(); await activate(ctx); await new Promise((r) => setTimeout(r, 5000)); return { activated: true }; } function verifyMarkers() { const wb = fs.existsSync(WORKBENCH) ? fs.readFileSync(WORKBENCH, 'utf8') : ''; const eh = fs.existsSync(EXTHOST) ? fs.readFileSync(EXTHOST, 'utf8') : ''; const markers = [ { id: 'noQuotaMcp', start: 'CO_NO_QUOTA_MCP_V1_START', src: wb }, { id: 'composerBridge', start: 'CO_COMPOSER_BRIDGE_START', src: wb }, { id: 'exthost', start: 'CO_NO_QUOTA_EXTHOST_V1_START', src: eh }, ]; const found = markers.filter((m) => m.src.includes(m.start)).map((m) => m.id); return { patchOk: found.length >= 3, found, total: markers.length }; } async function main() { if (mode === 'verify') { emit({ ok: true, ...verifyMarkers() }); return; } const extPath = resolveExtensionPath(); if (!extPath) { emit({ ok: false, error: '未找到 vendor/extension.js', candidates: EXT_CANDIDATES }); process.exitCode = 1; return; } try { await tryHeadlessPatch(extPath); } catch (e) { emit({ ok: false, error: 'headless activate 失败: ' + (e.message || e), extPath, stack: String(e.stack || '').slice(0, 500) }); process.exitCode = 1; return; } const v = verifyMarkers(); emit({ ok: v.patchOk, ...v, extPath, hint: v.patchOk ? 'W8 OK' : '补丁未注入:请 Cmd+Q 退出 Cursor 后重试 W7' }); } main().catch((e) => { emit({ ok: false, error: String(e.stack || e.message || e) }); process.exitCode = 1; });