2026-11-10 01:24:28 +08:00
|
|
|
|
// cli.go 管理 CLI 版 cursor-agent 的代理设置注入。
|
|
|
|
|
|
// 通过在数据目录生成 wrapper 脚本和 preload 脚本,
|
|
|
|
|
|
// 将 CLI 的 cursor.sh 连接重定向到后端 HTTPS+HTTP/2 监听器。
|
|
|
|
|
|
package cursor
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-11-10 09:59:34 +08:00
|
|
|
|
"cursor/internal/securefile"
|
|
|
|
|
|
_ "embed"
|
2026-11-10 01:24:28 +08:00
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"net"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"runtime"
|
2026-11-10 09:59:34 +08:00
|
|
|
|
"strconv"
|
2026-11-10 01:24:28 +08:00
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
|
|
"cursor/internal/appdata"
|
|
|
|
|
|
"cursor/internal/logger"
|
|
|
|
|
|
localruntime "cursor/internal/runtime"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// CLIWrapperDir 返回 wrapper 脚本存放目录。
|
|
|
|
|
|
func CLIWrapperDir() string {
|
|
|
|
|
|
return filepath.Join(appdata.RootDir(), "cli")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CLIWrapperPath 返回当前平台的 wrapper 脚本完整路径。
|
|
|
|
|
|
func CLIWrapperPath() string {
|
|
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
|
|
return filepath.Join(CLIWrapperDir(), "agent.cmd")
|
|
|
|
|
|
}
|
|
|
|
|
|
return filepath.Join(CLIWrapperDir(), "agent")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DetectCLIInstallPath 尝试检测 CLI 版 cursor-agent 的安装路径。
|
|
|
|
|
|
func DetectCLIInstallPath() string {
|
|
|
|
|
|
homeDir, err := os.UserHomeDir()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
candidates := make([]string, 0, 4)
|
|
|
|
|
|
switch runtime.GOOS {
|
|
|
|
|
|
case "windows":
|
|
|
|
|
|
localAppData := strings.TrimSpace(os.Getenv("LOCALAPPDATA"))
|
|
|
|
|
|
if localAppData == "" {
|
|
|
|
|
|
localAppData = filepath.Join(homeDir, "AppData", "Local")
|
|
|
|
|
|
}
|
|
|
|
|
|
candidates = append(candidates,
|
|
|
|
|
|
filepath.Join(localAppData, "cursor-agent", "agent.cmd"),
|
|
|
|
|
|
)
|
|
|
|
|
|
case "darwin":
|
|
|
|
|
|
candidates = append(candidates,
|
|
|
|
|
|
filepath.Join(homeDir, ".cursor-agent", "agent"),
|
|
|
|
|
|
filepath.Join(homeDir, ".local", "bin", "cursor-agent"),
|
|
|
|
|
|
)
|
|
|
|
|
|
case "linux":
|
|
|
|
|
|
candidates = append(candidates,
|
|
|
|
|
|
filepath.Join(homeDir, ".cursor-agent", "agent"),
|
|
|
|
|
|
filepath.Join(homeDir, ".local", "bin", "cursor-agent"),
|
|
|
|
|
|
"/usr/local/bin/cursor-agent",
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, c := range candidates {
|
|
|
|
|
|
if info, err := os.Stat(c); err == nil && !info.IsDir() {
|
|
|
|
|
|
return c
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 09:59:34 +08:00
|
|
|
|
const cliPreloadFilename = "tls-redirect.cjs"
|
2026-11-10 01:24:28 +08:00
|
|
|
|
|
2026-11-10 09:59:34 +08:00
|
|
|
|
//go:embed cli_preload.cjs
|
|
|
|
|
|
var cliPreloadScript string
|
2026-11-10 01:24:28 +08:00
|
|
|
|
|
|
|
|
|
|
// writeCLIPreloadScript 将 preload 脚本写入 wrapper 目录,返回文件路径。
|
|
|
|
|
|
func writeCLIPreloadScript() (string, error) {
|
|
|
|
|
|
wrapperDir := CLIWrapperDir()
|
|
|
|
|
|
if err := os.MkdirAll(wrapperDir, 0o755); err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("创建 preload 目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
preloadPath := filepath.Join(wrapperDir, cliPreloadFilename)
|
2026-11-10 09:59:34 +08:00
|
|
|
|
if err := securefile.Write(preloadPath, []byte(cliPreloadScript)); err != nil {
|
2026-11-10 01:24:28 +08:00
|
|
|
|
return "", fmt.Errorf("写入 preload 脚本失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return preloadPath, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// WriteCLIProxyWrapper 在数据目录生成 wrapper 脚本。
|
|
|
|
|
|
//
|
|
|
|
|
|
// wrapper 设置环境变量后调用真实 CLI:
|
|
|
|
|
|
// - CLI_HTTPS_PORT → 后端 HTTPS+HTTP/2 监听端口(preload 脚本读取)
|
|
|
|
|
|
// - NODE_EXTRA_CA_CERTS → CA 证书路径
|
|
|
|
|
|
// - NODE_OPTIONS → --require preload 脚本,patch net.connect/tls.connect
|
|
|
|
|
|
// - CURSOR_AUTH_TOKEN → 注入 token,跳过浏览器登录
|
|
|
|
|
|
//
|
|
|
|
|
|
// 不再设置 HTTP_PROXY/HTTPS_PROXY — 跳过 goproxy MITM,
|
|
|
|
|
|
// 由 preload 脚本在 TCP 层直接重定向到后端 HTTPS 监听器。
|
|
|
|
|
|
func WriteCLIProxyWrapper(cliHTTPSAddr, caCertPath string) error {
|
|
|
|
|
|
cliHTTPSAddr = strings.TrimSpace(cliHTTPSAddr)
|
|
|
|
|
|
caCertPath = strings.TrimSpace(caCertPath)
|
|
|
|
|
|
if cliHTTPSAddr == "" {
|
|
|
|
|
|
return fmt.Errorf("CLI HTTPS 监听地址为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取端口号
|
|
|
|
|
|
_, port, err := net.SplitHostPort(cliHTTPSAddr)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("解析 CLI HTTPS 监听地址失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cliPath := DetectCLIInstallPath()
|
|
|
|
|
|
if cliPath == "" {
|
|
|
|
|
|
return fmt.Errorf("未检测到 CLI 版 cursor-agent 安装路径")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wrapperDir := CLIWrapperDir()
|
|
|
|
|
|
if err := os.MkdirAll(wrapperDir, 0o755); err != nil {
|
|
|
|
|
|
return fmt.Errorf("创建 wrapper 目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
preloadPath, err := writeCLIPreloadScript()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
authToken := localruntime.InjectAuthToken
|
|
|
|
|
|
|
|
|
|
|
|
var content string
|
|
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
|
|
content = buildWindowsWrapper(port, caCertPath, authToken, cliPath, preloadPath)
|
2026-11-10 09:59:34 +08:00
|
|
|
|
if err := securefile.Write(filepath.Join(wrapperDir, "launch.ps1"), append([]byte{0xef, 0xbb, 0xbf}, []byte(buildWindowsLauncher(port, caCertPath, authToken, cliPath, preloadPath))...)); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-11-10 01:24:28 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
content = buildUnixWrapper(port, caCertPath, authToken, cliPath, preloadPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wrapperPath := CLIWrapperPath()
|
2026-11-10 09:59:34 +08:00
|
|
|
|
if err := securefile.Write(wrapperPath, []byte(content)); err != nil {
|
2026-11-10 01:24:28 +08:00
|
|
|
|
return fmt.Errorf("写入 wrapper 脚本失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 09:59:34 +08:00
|
|
|
|
if err := os.Chmod(wrapperPath, 0700); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-11-10 01:24:28 +08:00
|
|
|
|
logger.Infof(
|
|
|
|
|
|
"writeCLIProxyWrapper: path=%s https_port=%s ca=%s cli=%s preload=%s",
|
|
|
|
|
|
wrapperPath, port, caCertPath, cliPath, preloadPath,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ClearCLIProxyWrapper 移除 wrapper 脚本、preload 脚本和终端初始化脚本。
|
|
|
|
|
|
func ClearCLIProxyWrapper() error {
|
2026-11-10 09:59:34 +08:00
|
|
|
|
for _, ownedName := range []string{"launch.ps1", "tls-bypass.cjs"} {
|
|
|
|
|
|
if err := os.Remove(filepath.Join(CLIWrapperDir(), ownedName)); err != nil && !os.IsNotExist(err) {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-11-10 01:24:28 +08:00
|
|
|
|
wrapperPath := CLIWrapperPath()
|
|
|
|
|
|
if _, err := os.Stat(wrapperPath); err != nil {
|
|
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if err := os.Remove(wrapperPath); err != nil {
|
|
|
|
|
|
return fmt.Errorf("移除 wrapper 脚本失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
preloadPath := filepath.Join(CLIWrapperDir(), cliPreloadFilename)
|
|
|
|
|
|
if _, err := os.Stat(preloadPath); err != nil {
|
|
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if err := os.Remove(preloadPath); err != nil {
|
|
|
|
|
|
return fmt.Errorf("移除 preload 脚本失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理 Windows 终端初始化批处理脚本
|
|
|
|
|
|
terminalInitPath := filepath.Join(CLIWrapperDir(), "cli-terminal-init.bat")
|
|
|
|
|
|
if _, err := os.Stat(terminalInitPath); err != nil {
|
|
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if err := os.Remove(terminalInitPath); err != nil {
|
|
|
|
|
|
return fmt.Errorf("移除终端初始化脚本失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.Infof("clearCLIProxyWrapper: removed wrapper=%s preload=%s terminal_init=%s", wrapperPath, preloadPath, terminalInitPath)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CLIWrapperExists 检查 wrapper 是否已存在。
|
|
|
|
|
|
func CLIWrapperExists() bool {
|
|
|
|
|
|
_, err := os.Stat(CLIWrapperPath())
|
|
|
|
|
|
return err == nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CLIAuthFilePath 返回 CLI 版 auth.json 的路径。
|
|
|
|
|
|
// Windows: %APPDATA%\Cursor\auth.json; Unix: ~/.cursor/auth.json
|
|
|
|
|
|
func CLIAuthFilePath() string {
|
|
|
|
|
|
switch runtime.GOOS {
|
|
|
|
|
|
case "windows":
|
|
|
|
|
|
appData := strings.TrimSpace(os.Getenv("APPDATA"))
|
|
|
|
|
|
if appData == "" {
|
|
|
|
|
|
homeDir, _ := os.UserHomeDir()
|
|
|
|
|
|
appData = filepath.Join(homeDir, "AppData", "Roaming")
|
|
|
|
|
|
}
|
|
|
|
|
|
return filepath.Join(appData, "Cursor", "auth.json")
|
|
|
|
|
|
default:
|
|
|
|
|
|
homeDir, _ := os.UserHomeDir()
|
|
|
|
|
|
return filepath.Join(homeDir, ".cursor", "auth.json")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// cliAuthData 对应 CLI auth.json 的 JSON 结构。
|
|
|
|
|
|
type cliAuthData struct {
|
|
|
|
|
|
AccessToken string `json:"accessToken"`
|
|
|
|
|
|
RefreshToken string `json:"refreshToken"`
|
|
|
|
|
|
APIKey *string `json:"apiKey"`
|
|
|
|
|
|
BedrockCredentials *string `json:"bedrockCredentials"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// cliAuthBackupPath 返回真实凭据的备份路径(auth.json.local-cursor-backup)。
|
|
|
|
|
|
func cliAuthBackupPath() string {
|
|
|
|
|
|
return CLIAuthFilePath() + ".local-cursor-backup"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// WriteCLIAuthFile 写入 mock auth.json,使 CLI 的 FileCredentialStore.getAllCredentials() 通过认证检查。
|
|
|
|
|
|
// accessToken/refreshToken 使用 InjectAuthToken(有效 JWT,exp 在 2099 年)。
|
|
|
|
|
|
// 覆盖前会备份用户的真实凭据(仅首次,之后保留最早那份原始备份),
|
|
|
|
|
|
// ClearCLIAuthFile 时若存在备份则恢复,避免销毁用户真实登录态。
|
|
|
|
|
|
func WriteCLIAuthFile() error {
|
|
|
|
|
|
authPath := CLIAuthFilePath()
|
|
|
|
|
|
dir := filepath.Dir(authPath)
|
|
|
|
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
|
|
|
|
return fmt.Errorf("创建 auth.json 目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 首次覆盖前备份真实凭据;备份已存在说明此前已备份过,不覆盖最早的原件。
|
|
|
|
|
|
backupPath := cliAuthBackupPath()
|
|
|
|
|
|
if raw, err := os.ReadFile(authPath); err == nil {
|
|
|
|
|
|
if _, statErr := os.Stat(backupPath); os.IsNotExist(statErr) {
|
|
|
|
|
|
if isMockAuthData(raw) {
|
|
|
|
|
|
logger.Infof("writeCLIAuthFile: existing auth.json is mock, skip backup path=%s", authPath)
|
|
|
|
|
|
} else if writeErr := os.WriteFile(backupPath, raw, 0o600); writeErr != nil {
|
|
|
|
|
|
return fmt.Errorf("备份原 auth.json 失败: %w", writeErr)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logger.Infof("writeCLIAuthFile: backed up original auth.json to %s", backupPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data := cliAuthData{
|
|
|
|
|
|
AccessToken: localruntime.InjectAuthToken,
|
|
|
|
|
|
RefreshToken: localruntime.InjectAuthToken,
|
|
|
|
|
|
APIKey: nil,
|
|
|
|
|
|
BedrockCredentials: nil,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
content, err := json.MarshalIndent(data, "", " ")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("序列化 auth.json 失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := os.WriteFile(authPath, content, 0o600); err != nil {
|
|
|
|
|
|
return fmt.Errorf("写入 auth.json 失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.Infof("writeCLIAuthFile: path=%s", authPath)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// isMockAuthData 判断 auth.json 内容是否为本工具写入的 mock 凭据。
|
|
|
|
|
|
func isMockAuthData(raw []byte) bool {
|
|
|
|
|
|
var data cliAuthData
|
|
|
|
|
|
if err := json.Unmarshal(raw, &data); err != nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return data.AccessToken == localruntime.InjectAuthToken
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ClearCLIAuthFile 移除 mock auth.json。
|
|
|
|
|
|
// 若存在真实凭据备份则恢复之;仅当 auth.json 是 mock 内容时才删除。
|
|
|
|
|
|
func ClearCLIAuthFile() error {
|
|
|
|
|
|
authPath := CLIAuthFilePath()
|
|
|
|
|
|
raw, err := os.ReadFile(authPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
backupPath := cliAuthBackupPath()
|
|
|
|
|
|
backupExists := false
|
|
|
|
|
|
if _, statErr := os.Stat(backupPath); statErr == nil {
|
|
|
|
|
|
backupExists = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if isMockAuthData(raw) {
|
|
|
|
|
|
if backupExists {
|
|
|
|
|
|
// 恢复用户真实凭据。
|
|
|
|
|
|
if restoreErr := os.Rename(backupPath, authPath); restoreErr != nil {
|
|
|
|
|
|
return fmt.Errorf("恢复原 auth.json 失败: %w", restoreErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
logger.Infof("clearCLIAuthFile: restored original auth.json from %s", backupPath)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := os.Remove(authPath); err != nil {
|
|
|
|
|
|
return fmt.Errorf("移除 auth.json 失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
logger.Infof("clearCLIAuthFile: removed mock auth.json path=%s", authPath)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 内容不是 mock(用户在此期间重新登录过),保留现状并不动备份。
|
|
|
|
|
|
logger.Infof("clearCLIAuthFile: auth.json is not mock, leave untouched path=%s", authPath)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func buildWindowsWrapper(port, caCertPath, authToken, cliPath, preloadPath string) string {
|
2026-11-10 09:59:34 +08:00
|
|
|
|
return "@echo off\r\nsetlocal DisableDelayedExpansion\r\n\"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoProfile -ExecutionPolicy Bypass -File \"%~dp0launch.ps1\" %*\r\n"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func buildWindowsLauncher(port, caCertPath, authToken, cliPath, preloadPath string) string {
|
|
|
|
|
|
psScript := filepath.Join(filepath.Dir(cliPath), "cursor-agent.ps1")
|
|
|
|
|
|
return fmt.Sprintf(`$ErrorActionPreference='Stop'
|
|
|
|
|
|
$env:HTTP_PROXY=''
|
|
|
|
|
|
$env:HTTPS_PROXY=''
|
|
|
|
|
|
$env:CLI_HTTPS_PORT=%s
|
|
|
|
|
|
$env:LOCAL_CURSOR_ORIGINAL_CA_FILE=$env:NODE_EXTRA_CA_CERTS
|
|
|
|
|
|
$env:NODE_EXTRA_CA_CERTS=%s
|
|
|
|
|
|
if($env:LOCAL_CURSOR_ORIGINAL_CA_FILE -eq $env:NODE_EXTRA_CA_CERTS){$env:LOCAL_CURSOR_ORIGINAL_CA_FILE=''}
|
|
|
|
|
|
Remove-Item Env:NODE_TLS_REJECT_UNAUTHORIZED -ErrorAction SilentlyContinue
|
|
|
|
|
|
if($env:NODE_OPTIONS -match 'tls-bypass[.]cjs'){$env:NODE_OPTIONS=''}
|
|
|
|
|
|
$env:NODE_OPTIONS=($env:NODE_OPTIONS + ' ' + %s).Trim()
|
|
|
|
|
|
$env:CURSOR_AUTH_TOKEN=%s
|
|
|
|
|
|
$env:CURSOR_INVOKED_AS='agent.cmd'
|
|
|
|
|
|
& %s @args
|
|
|
|
|
|
exit $LASTEXITCODE
|
|
|
|
|
|
`, psLiteral(port), psLiteral(caCertPath), psLiteral("--require="+strconv.Quote(preloadPath)), psLiteral(authToken), psLiteral(psScript))
|
2026-11-10 01:24:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 09:59:34 +08:00
|
|
|
|
func psLiteral(s string) string { return "'" + strings.ReplaceAll(s, "'", "''") + "'" }
|
|
|
|
|
|
func shellLiteral(s string) string { return "'" + strings.ReplaceAll(s, "'", `'"'"'`) + "'" }
|
|
|
|
|
|
|
2026-11-10 01:24:28 +08:00
|
|
|
|
func buildUnixWrapper(port, caCertPath, authToken, cliPath, preloadPath string) string {
|
|
|
|
|
|
return fmt.Sprintf(`#!/bin/sh
|
2026-11-10 09:59:34 +08:00
|
|
|
|
unset HTTP_PROXY HTTPS_PROXY NODE_TLS_REJECT_UNAUTHORIZED
|
|
|
|
|
|
export CLI_HTTPS_PORT=%s
|
|
|
|
|
|
export LOCAL_CURSOR_ORIGINAL_CA_FILE="${NODE_EXTRA_CA_CERTS-}"
|
|
|
|
|
|
export NODE_EXTRA_CA_CERTS=%s
|
|
|
|
|
|
if [ "$LOCAL_CURSOR_ORIGINAL_CA_FILE" = "$NODE_EXTRA_CA_CERTS" ]; then unset LOCAL_CURSOR_ORIGINAL_CA_FILE; fi
|
|
|
|
|
|
case "${NODE_OPTIONS-}" in *tls-bypass.cjs*) unset NODE_OPTIONS;; esac
|
|
|
|
|
|
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }"%s
|
|
|
|
|
|
export CURSOR_AUTH_TOKEN=%s
|
|
|
|
|
|
exec %s "$@"
|
|
|
|
|
|
`, shellLiteral(port), shellLiteral(caCertPath), shellLiteral("--require="+strconv.Quote(preloadPath)), shellLiteral(authToken), shellLiteral(cliPath))
|
2026-11-10 01:24:28 +08:00
|
|
|
|
}
|