codex-endpoint-switcher 1.0.0
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 +70 -0
- package/bin/codex-switcher.js +89 -0
- package/install-web-access.ps1 +61 -0
- package/open-web-console.cmd +3 -0
- package/open-web-console.vbs +2 -0
- package/package.json +74 -0
- package/remove-web-access.ps1 +17 -0
- package/src/main/main.js +74 -0
- package/src/main/preload.js +28 -0
- package/src/main/profile-manager.js +439 -0
- package/src/renderer/index.html +140 -0
- package/src/renderer/renderer.js +340 -0
- package/src/renderer/styles.css +507 -0
- package/src/web/launcher.js +248 -0
- package/src/web/server.js +132 -0
- package/start-web-background.cmd +3 -0
- package/start-web-background.vbs +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Codex Endpoint Switcher
|
|
2
|
+
|
|
3
|
+
用于切换 Codex 的 `URL`、`Key` 和连接备注。
|
|
4
|
+
|
|
5
|
+
切换时只会更新:
|
|
6
|
+
|
|
7
|
+
- `~/.codex/config.toml` 中当前 `model_provider` 对应段落的 `base_url`
|
|
8
|
+
- `~/.codex/auth.json` 中的 `OPENAI_API_KEY`
|
|
9
|
+
|
|
10
|
+
其它 Codex 配置保持不变。
|
|
11
|
+
|
|
12
|
+
## 使用方式
|
|
13
|
+
|
|
14
|
+
### 1. 临时运行
|
|
15
|
+
|
|
16
|
+
如果这个包已经发布到 npm,可以直接:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx codex-endpoint-switcher open
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. 全局安装
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g codex-endpoint-switcher
|
|
26
|
+
codex-switcher open
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. 本地打包后安装
|
|
30
|
+
|
|
31
|
+
先在项目目录执行:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm run pack:npm
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
然后安装生成的 `.tgz`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g .\codex-endpoint-switcher-1.0.0.tgz
|
|
41
|
+
codex-switcher open
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## CLI 命令
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
codex-switcher open
|
|
48
|
+
codex-switcher start
|
|
49
|
+
codex-switcher status
|
|
50
|
+
codex-switcher restart
|
|
51
|
+
codex-switcher install-access
|
|
52
|
+
codex-switcher remove-access
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
说明:
|
|
56
|
+
|
|
57
|
+
- `open`:启动本地网页服务并打开浏览器
|
|
58
|
+
- `start`:仅在后台启动本地网页服务
|
|
59
|
+
- `status`:查看当前服务状态
|
|
60
|
+
- `restart`:重启本地网页服务
|
|
61
|
+
- `install-access`:创建桌面快捷方式和开机启动项
|
|
62
|
+
- `remove-access`:移除桌面快捷方式和开机启动项
|
|
63
|
+
|
|
64
|
+
## 网页地址
|
|
65
|
+
|
|
66
|
+
默认地址:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
http://localhost:3186
|
|
70
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const { spawn } = require("node:child_process");
|
|
5
|
+
const { handleCommand } = require("../src/web/launcher");
|
|
6
|
+
|
|
7
|
+
const projectRoot = path.resolve(__dirname, "..");
|
|
8
|
+
const installScriptPath = path.join(projectRoot, "install-web-access.ps1");
|
|
9
|
+
const removeScriptPath = path.join(projectRoot, "remove-web-access.ps1");
|
|
10
|
+
|
|
11
|
+
function printUsage() {
|
|
12
|
+
console.log(`
|
|
13
|
+
用法:
|
|
14
|
+
codex-switcher open
|
|
15
|
+
codex-switcher start
|
|
16
|
+
codex-switcher status
|
|
17
|
+
codex-switcher restart
|
|
18
|
+
codex-switcher install-access
|
|
19
|
+
codex-switcher remove-access
|
|
20
|
+
|
|
21
|
+
说明:
|
|
22
|
+
open 启动本地网页服务并打开浏览器
|
|
23
|
+
start 仅在后台启动本地网页服务
|
|
24
|
+
status 查看服务状态
|
|
25
|
+
restart 重启本地网页服务
|
|
26
|
+
install-access 创建桌面快捷方式与开机启动项
|
|
27
|
+
remove-access 删除桌面快捷方式与开机启动项
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function runPowerShellScript(scriptPath) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const child = spawn(
|
|
34
|
+
"powershell.exe",
|
|
35
|
+
["-ExecutionPolicy", "Bypass", "-File", scriptPath],
|
|
36
|
+
{
|
|
37
|
+
cwd: projectRoot,
|
|
38
|
+
stdio: "inherit",
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
child.on("exit", (code) => {
|
|
43
|
+
if (code === 0) {
|
|
44
|
+
resolve();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
reject(new Error(`脚本执行失败,退出码:${code}`));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
child.on("error", reject);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function main() {
|
|
56
|
+
const command = process.argv[2] || "open";
|
|
57
|
+
|
|
58
|
+
switch (command) {
|
|
59
|
+
case "open":
|
|
60
|
+
case "start":
|
|
61
|
+
case "status":
|
|
62
|
+
case "restart": {
|
|
63
|
+
await handleCommand(command === "start" ? "ensure" : command);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
case "install-access": {
|
|
67
|
+
await runPowerShellScript(installScriptPath);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
case "remove-access": {
|
|
71
|
+
await runPowerShellScript(removeScriptPath);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
case "help":
|
|
75
|
+
case "--help":
|
|
76
|
+
case "-h": {
|
|
77
|
+
printUsage();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
default:
|
|
81
|
+
printUsage();
|
|
82
|
+
throw new Error(`不支持的命令:${command}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main().catch((error) => {
|
|
87
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
$ErrorActionPreference = "Stop"
|
|
2
|
+
|
|
3
|
+
$projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
4
|
+
$ws = New-Object -ComObject WScript.Shell
|
|
5
|
+
$startupPath = $ws.SpecialFolders("Startup")
|
|
6
|
+
$desktopPath = [Environment]::GetFolderPath("Desktop")
|
|
7
|
+
|
|
8
|
+
$desktopShortcutPath = Join-Path $desktopPath "Codex 网页控制台.lnk"
|
|
9
|
+
$startupShortcutPath = Join-Path $startupPath "Codex 网页控制台后台服务.lnk"
|
|
10
|
+
$desktopVbsPath = Join-Path $projectRoot "open-web-console.vbs"
|
|
11
|
+
$startupVbsPath = Join-Path $projectRoot "start-web-background.vbs"
|
|
12
|
+
$iconPath = Join-Path $projectRoot "release\\CodexProfileDesktop-1.0.0-portable.exe"
|
|
13
|
+
|
|
14
|
+
function Backup-ExistingFile {
|
|
15
|
+
param(
|
|
16
|
+
[Parameter(Mandatory = $true)]
|
|
17
|
+
[string]$Path
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if (-not (Test-Path $Path)) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
25
|
+
$backupPath = "$Path.bak-$timestamp"
|
|
26
|
+
Copy-Item -Path $Path -Destination $backupPath -Force
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Set-Shortcut {
|
|
30
|
+
param(
|
|
31
|
+
[Parameter(Mandatory = $true)]
|
|
32
|
+
[string]$ShortcutPath,
|
|
33
|
+
|
|
34
|
+
[Parameter(Mandatory = $true)]
|
|
35
|
+
[string]$Arguments,
|
|
36
|
+
|
|
37
|
+
[Parameter(Mandatory = $true)]
|
|
38
|
+
[string]$Description
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
Backup-ExistingFile -Path $ShortcutPath
|
|
42
|
+
|
|
43
|
+
$shortcut = $ws.CreateShortcut($ShortcutPath)
|
|
44
|
+
$shortcut.TargetPath = "wscript.exe"
|
|
45
|
+
$shortcut.Arguments = "`"$Arguments`""
|
|
46
|
+
$shortcut.WorkingDirectory = $projectRoot
|
|
47
|
+
$shortcut.WindowStyle = 7
|
|
48
|
+
$shortcut.Description = $Description
|
|
49
|
+
|
|
50
|
+
if (Test-Path $iconPath) {
|
|
51
|
+
$shortcut.IconLocation = "$iconPath,0"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
$shortcut.Save()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Set-Shortcut -ShortcutPath $desktopShortcutPath -Arguments $desktopVbsPath -Description "打开 Codex 本地网页控制台"
|
|
58
|
+
Set-Shortcut -ShortcutPath $startupShortcutPath -Arguments $startupVbsPath -Description "开机后台启动 Codex 本地网页服务"
|
|
59
|
+
|
|
60
|
+
Write-Output "已创建桌面快捷方式:$desktopShortcutPath"
|
|
61
|
+
Write-Output "已创建开机启动项:$startupShortcutPath"
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codex-endpoint-switcher",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "用于切换 Codex URL 和 Key 的本地网页控制台与 npm CLI",
|
|
5
|
+
"main": "src/main/main.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codex-switcher": "bin/codex-switcher.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/**/*",
|
|
11
|
+
"src/**/*",
|
|
12
|
+
"install-web-access.ps1",
|
|
13
|
+
"remove-web-access.ps1",
|
|
14
|
+
"open-web-console.cmd",
|
|
15
|
+
"open-web-console.vbs",
|
|
16
|
+
"start-web-background.cmd",
|
|
17
|
+
"start-web-background.vbs"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"start": "electron .",
|
|
21
|
+
"dev": "electron .",
|
|
22
|
+
"start:web": "node src/web/server.js",
|
|
23
|
+
"dev:web": "node src/web/server.js",
|
|
24
|
+
"cli": "node bin/codex-switcher.js",
|
|
25
|
+
"pack:npm": "npm pack",
|
|
26
|
+
"build:portable": "electron-builder --win portable",
|
|
27
|
+
"build:installer": "electron-builder --win nsis"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"codex",
|
|
31
|
+
"electron",
|
|
32
|
+
"desktop"
|
|
33
|
+
],
|
|
34
|
+
"author": "Codex",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"preferGlobal": true,
|
|
37
|
+
"build": {
|
|
38
|
+
"appId": "local.codex.profile.desktop",
|
|
39
|
+
"productName": "CodexProfileDesktop",
|
|
40
|
+
"asar": true,
|
|
41
|
+
"directories": {
|
|
42
|
+
"output": "release"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"src/**/*",
|
|
46
|
+
"package.json"
|
|
47
|
+
],
|
|
48
|
+
"win": {
|
|
49
|
+
"target": [
|
|
50
|
+
{
|
|
51
|
+
"target": "portable",
|
|
52
|
+
"arch": [
|
|
53
|
+
"x64"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"artifactName": "CodexProfileDesktop-${version}-portable.${ext}"
|
|
58
|
+
},
|
|
59
|
+
"portable": {
|
|
60
|
+
"artifactName": "CodexProfileDesktop-${version}-portable.${ext}"
|
|
61
|
+
},
|
|
62
|
+
"nsis": {
|
|
63
|
+
"oneClick": false,
|
|
64
|
+
"allowToChangeInstallationDirectory": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"express": "^5.1.0"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"electron": "^41.0.3",
|
|
72
|
+
"electron-builder": "^26.8.1"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
$ErrorActionPreference = "Stop"
|
|
2
|
+
|
|
3
|
+
$ws = New-Object -ComObject WScript.Shell
|
|
4
|
+
$startupPath = $ws.SpecialFolders("Startup")
|
|
5
|
+
$desktopPath = [Environment]::GetFolderPath("Desktop")
|
|
6
|
+
|
|
7
|
+
$paths = @(
|
|
8
|
+
(Join-Path $desktopPath "Codex 网页控制台.lnk"),
|
|
9
|
+
(Join-Path $startupPath "Codex 网页控制台后台服务.lnk")
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
foreach ($targetPath in $paths) {
|
|
13
|
+
if (Test-Path $targetPath) {
|
|
14
|
+
Remove-Item -Path $targetPath -Force
|
|
15
|
+
Write-Output "已移除:$targetPath"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/main/main.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const { app, BrowserWindow, ipcMain, shell } = require("electron");
|
|
3
|
+
const profileManager = require("./profile-manager");
|
|
4
|
+
|
|
5
|
+
function createWindow() {
|
|
6
|
+
const mainWindow = new BrowserWindow({
|
|
7
|
+
width: 1260,
|
|
8
|
+
height: 860,
|
|
9
|
+
minWidth: 1120,
|
|
10
|
+
minHeight: 760,
|
|
11
|
+
backgroundColor: "#f5eee6",
|
|
12
|
+
autoHideMenuBar: true,
|
|
13
|
+
title: "Codex 配置切换器",
|
|
14
|
+
webPreferences: {
|
|
15
|
+
preload: path.join(__dirname, "preload.js"),
|
|
16
|
+
contextIsolation: true,
|
|
17
|
+
nodeIntegration: false,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function registerHandlers() {
|
|
25
|
+
ipcMain.handle("endpoints:list", async () => {
|
|
26
|
+
return profileManager.listEndpoints();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
ipcMain.handle("endpoints:current", async () => {
|
|
30
|
+
return profileManager.getCurrentEndpointSummary();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
ipcMain.handle("endpoints:create", async (_event, payload) => {
|
|
34
|
+
return profileManager.createEndpoint(payload);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
ipcMain.handle("endpoints:update", async (_event, payload) => {
|
|
38
|
+
return profileManager.updateEndpoint(payload.id, payload);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
ipcMain.handle("endpoints:delete", async (_event, payload) => {
|
|
42
|
+
return profileManager.deleteEndpoint(payload.id);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
ipcMain.handle("endpoints:switch", async (_event, payload) => {
|
|
46
|
+
return profileManager.switchEndpoint(payload.id);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
ipcMain.handle("paths:get", async () => {
|
|
50
|
+
return profileManager.getManagedPaths();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
ipcMain.handle("shell:openPath", async (_event, targetPath) => {
|
|
54
|
+
const openError = await shell.openPath(targetPath);
|
|
55
|
+
return openError || "";
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
app.whenReady().then(() => {
|
|
60
|
+
registerHandlers();
|
|
61
|
+
createWindow();
|
|
62
|
+
|
|
63
|
+
app.on("activate", () => {
|
|
64
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
65
|
+
createWindow();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
app.on("window-all-closed", () => {
|
|
71
|
+
if (process.platform !== "darwin") {
|
|
72
|
+
app.quit();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const { contextBridge, ipcRenderer } = require("electron");
|
|
2
|
+
|
|
3
|
+
contextBridge.exposeInMainWorld("codexDesktop", {
|
|
4
|
+
listEndpoints() {
|
|
5
|
+
return ipcRenderer.invoke("endpoints:list");
|
|
6
|
+
},
|
|
7
|
+
getCurrentConfig() {
|
|
8
|
+
return ipcRenderer.invoke("endpoints:current");
|
|
9
|
+
},
|
|
10
|
+
createEndpoint(payload) {
|
|
11
|
+
return ipcRenderer.invoke("endpoints:create", payload);
|
|
12
|
+
},
|
|
13
|
+
updateEndpoint(payload) {
|
|
14
|
+
return ipcRenderer.invoke("endpoints:update", payload);
|
|
15
|
+
},
|
|
16
|
+
deleteEndpoint(payload) {
|
|
17
|
+
return ipcRenderer.invoke("endpoints:delete", payload);
|
|
18
|
+
},
|
|
19
|
+
switchEndpoint(payload) {
|
|
20
|
+
return ipcRenderer.invoke("endpoints:switch", payload);
|
|
21
|
+
},
|
|
22
|
+
getPaths() {
|
|
23
|
+
return ipcRenderer.invoke("paths:get");
|
|
24
|
+
},
|
|
25
|
+
openPath(targetPath) {
|
|
26
|
+
return ipcRenderer.invoke("shell:openPath", targetPath);
|
|
27
|
+
},
|
|
28
|
+
});
|