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 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"
@@ -0,0 +1,3 @@
1
+ @echo off
2
+ cd /d "%~dp0"
3
+ node src\web\launcher.js open
@@ -0,0 +1,2 @@
1
+ Set shell = CreateObject("WScript.Shell")
2
+ shell.Run "cmd /c """ & Replace(WScript.ScriptFullName, "open-web-console.vbs", "open-web-console.cmd") & """", 0, False
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
+ }
@@ -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
+ });