codex-slot 0.1.2 → 0.1.4
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 +7 -3
- package/dist/app/service-lifecycle-service.js +79 -7
- package/dist/cli.js +9 -9
- package/dist/codex-config.js +3 -14
- package/dist/config.js +36 -11
- package/dist/service-control.js +7 -1
- package/package.json +2 -2
- package/dist/app/config-service.js +0 -61
- package/dist/config-command.js +0 -44
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ codex-slot import current ~
|
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
`import` copies the official login state into `~/.cslot/homes/<name>` instead of referencing the source HOME directly.
|
|
40
|
-
`current` is only an example slot name, not a built-in account.
|
|
40
|
+
`current` is only an example slot name, not a built-in account or workspace.
|
|
41
41
|
|
|
42
42
|
2. Check the latest usage:
|
|
43
43
|
|
|
@@ -70,7 +70,9 @@ codex-slot start
|
|
|
70
70
|
codex-slot start --port 4399
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`start` will automatically write the required provider config into `~/.codex/config.toml
|
|
73
|
+
`start` will automatically write the required provider config into `~/.codex/config.toml`.
|
|
74
|
+
It prefers port `4399` by default and will switch to the next available port automatically when `4399` is busy:
|
|
75
|
+
Each start also generates a fresh local `api_key` and syncs it into the managed provider config.
|
|
74
76
|
|
|
75
77
|
```bash
|
|
76
78
|
codex-slot start
|
|
@@ -127,7 +129,7 @@ Instead it:
|
|
|
127
129
|
```toml
|
|
128
130
|
[model_providers.cslot]
|
|
129
131
|
name = "cslot"
|
|
130
|
-
base_url = "http://127.0.0.1:
|
|
132
|
+
base_url = "http://127.0.0.1:4399/v1"
|
|
131
133
|
http_headers = { Authorization = "Bearer <your-local-api-key>" }
|
|
132
134
|
wire_api = "responses"
|
|
133
135
|
```
|
|
@@ -138,6 +140,8 @@ Behavior:
|
|
|
138
140
|
- On `cslot stop`, the original `model_provider` line and original `[model_providers.cslot]` block are restored from the saved snapshot
|
|
139
141
|
- Other providers and settings in `config.toml` are left untouched
|
|
140
142
|
- If you start with `--port`, the port is saved to `~/.cslot/config.yaml`
|
|
143
|
+
- If you start without `--port`, `4399` is preferred first and the next free port is chosen automatically on conflict
|
|
144
|
+
- Every `start` rotates the local `api_key`, and the new value is written to both `~/.cslot/config.yaml` and the managed provider block
|
|
141
145
|
|
|
142
146
|
## Data Directory
|
|
143
147
|
|
|
@@ -7,6 +7,7 @@ exports.getRunningPid = getRunningPid;
|
|
|
7
7
|
exports.startManagedService = startManagedService;
|
|
8
8
|
exports.stopManagedService = stopManagedService;
|
|
9
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_net_1 = __importDefault(require("node:net"));
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
const node_child_process_1 = require("node:child_process");
|
|
12
13
|
const codex_config_1 = require("../codex-config");
|
|
@@ -57,6 +58,59 @@ function resolveServeEntrypoint() {
|
|
|
57
58
|
args: [`${serveBasePath}.js`]
|
|
58
59
|
};
|
|
59
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* 检查指定地址与端口当前是否可绑定,用于启动前规避端口冲突。
|
|
63
|
+
*
|
|
64
|
+
* @param host 监听地址。
|
|
65
|
+
* @param port 待检查端口。
|
|
66
|
+
* @returns Promise,可绑定时返回 `true`,被占用或校验失败时返回 `false`。
|
|
67
|
+
* @throws 无显式抛出。
|
|
68
|
+
*/
|
|
69
|
+
function isPortAvailable(host, port) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const server = node_net_1.default.createServer();
|
|
72
|
+
server.once("error", () => {
|
|
73
|
+
resolve(false);
|
|
74
|
+
});
|
|
75
|
+
server.once("listening", () => {
|
|
76
|
+
server.close(() => resolve(true));
|
|
77
|
+
});
|
|
78
|
+
server.listen(port, host);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 为后台服务挑选最终启动端口。
|
|
83
|
+
*
|
|
84
|
+
* 规则:
|
|
85
|
+
* 1. 若用户显式指定 `--port`,则严格使用该端口,冲突时直接报错。
|
|
86
|
+
* 2. 若未显式指定端口,则优先使用 4399。
|
|
87
|
+
* 3. 若默认候选端口冲突,则从候选端口开始向上查找下一个可用端口。
|
|
88
|
+
*
|
|
89
|
+
* @param host 监听地址。
|
|
90
|
+
* @param currentPort 当前配置中的端口。
|
|
91
|
+
* @param portOverride 用户显式指定的端口文本。
|
|
92
|
+
* @returns Promise,成功时返回最终端口与是否发生自动切换。
|
|
93
|
+
* @throws 当显式指定端口冲突或找不到可用端口时抛出异常。
|
|
94
|
+
*/
|
|
95
|
+
async function resolveStartPort(host, currentPort, portOverride) {
|
|
96
|
+
if (portOverride) {
|
|
97
|
+
const port = (0, cli_helpers_1.parsePort)(portOverride);
|
|
98
|
+
if (!(await isPortAvailable(host, port))) {
|
|
99
|
+
throw new Error(`端口已被占用: ${port}`);
|
|
100
|
+
}
|
|
101
|
+
return { port, autoSwitched: false };
|
|
102
|
+
}
|
|
103
|
+
const preferredPort = currentPort === 4389 ? 4399 : currentPort;
|
|
104
|
+
for (let candidate = preferredPort; candidate < preferredPort + 50; candidate += 1) {
|
|
105
|
+
if (await isPortAvailable(host, candidate)) {
|
|
106
|
+
return {
|
|
107
|
+
port: candidate,
|
|
108
|
+
autoSwitched: candidate !== preferredPort
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`未找到可用端口,起始端口: ${preferredPort}`);
|
|
113
|
+
}
|
|
60
114
|
/**
|
|
61
115
|
* 启动后台服务,并在需要时将端口写回本地配置。
|
|
62
116
|
*
|
|
@@ -64,23 +118,39 @@ function resolveServeEntrypoint() {
|
|
|
64
118
|
* @returns 启动结果,包含是否已在运行、最终端口、PID 和日志路径。
|
|
65
119
|
* @throws 当端口非法、接管配置失败或子进程启动失败时抛出异常。
|
|
66
120
|
*/
|
|
67
|
-
function startManagedService(portOverride) {
|
|
121
|
+
async function startManagedService(portOverride) {
|
|
68
122
|
const config = (0, config_1.loadConfig)();
|
|
69
|
-
const port =
|
|
70
|
-
|
|
123
|
+
const { port, autoSwitched } = await resolveStartPort(config.server.host, config.server.port, portOverride);
|
|
124
|
+
const hasExplicitPortOverride = typeof portOverride === "string" && portOverride.length > 0;
|
|
125
|
+
const runningPid = getRunningPid();
|
|
126
|
+
if (runningPid && hasExplicitPortOverride && config.server.port !== port) {
|
|
71
127
|
config.server.port = port;
|
|
72
128
|
(0, config_1.saveConfig)(config);
|
|
73
129
|
}
|
|
74
|
-
const runningPid = getRunningPid();
|
|
75
130
|
if (runningPid) {
|
|
76
131
|
return {
|
|
77
132
|
alreadyRunning: true,
|
|
78
133
|
pid: runningPid,
|
|
79
134
|
port,
|
|
80
|
-
logPath: (0, config_1.getServiceLogPath)()
|
|
135
|
+
logPath: (0, config_1.getServiceLogPath)(),
|
|
136
|
+
autoSwitched: false,
|
|
137
|
+
apiKeyRotated: false
|
|
81
138
|
};
|
|
82
139
|
}
|
|
83
|
-
(
|
|
140
|
+
if (hasExplicitPortOverride && config.server.port !== port) {
|
|
141
|
+
config.server.port = port;
|
|
142
|
+
(0, config_1.saveConfig)(config);
|
|
143
|
+
}
|
|
144
|
+
// 每次真正启动服务前都轮换一次本地 api_key,并让受管 config.toml 使用同一新值。
|
|
145
|
+
const persistedConfig = (0, config_1.rotateServerApiKey)(config);
|
|
146
|
+
const runtimeConfig = {
|
|
147
|
+
...persistedConfig,
|
|
148
|
+
server: {
|
|
149
|
+
...persistedConfig.server,
|
|
150
|
+
port
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
(0, codex_config_1.applyManagedCodexConfig)(undefined, { config: runtimeConfig });
|
|
84
154
|
const logPath = (0, config_1.getServiceLogPath)();
|
|
85
155
|
const logFd = node_fs_1.default.openSync(logPath, "a");
|
|
86
156
|
const serveEntrypoint = resolveServeEntrypoint();
|
|
@@ -94,7 +164,9 @@ function startManagedService(portOverride) {
|
|
|
94
164
|
alreadyRunning: false,
|
|
95
165
|
pid: child.pid ?? 0,
|
|
96
166
|
port,
|
|
97
|
-
logPath
|
|
167
|
+
logPath,
|
|
168
|
+
autoSwitched,
|
|
169
|
+
apiKeyRotated: true
|
|
98
170
|
};
|
|
99
171
|
}
|
|
100
172
|
/**
|
package/dist/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ function configureRootProgram(program) {
|
|
|
30
30
|
" cslot status --no-interactive",
|
|
31
31
|
"",
|
|
32
32
|
`${(0, text_1.bi)("说明", "Notes")}:`,
|
|
33
|
-
` ${(0, text_1.bi)("`import current ~` 里的 current
|
|
33
|
+
` ${(0, text_1.bi)("`import current ~` 里的 current 只是示例槽位名,不是内置账号或工作空间。", "`current` in `import current ~` is only an example slot name, not a built-in account or workspace.")}`
|
|
34
34
|
].join("\n"));
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
@@ -44,19 +44,19 @@ function registerAccountCommands(program) {
|
|
|
44
44
|
program
|
|
45
45
|
.command("add")
|
|
46
46
|
.description((0, text_1.bi)("登录并新增一个账号或工作空间", "Login and add a managed slot"))
|
|
47
|
-
.argument("<name>", (0, text_1.bi)("
|
|
47
|
+
.argument("<name>", (0, text_1.bi)("账号或工作空间标识(本地槽位名)", "Managed slot name"))
|
|
48
48
|
.action(async (name) => {
|
|
49
49
|
await (0, account_commands_1.handleAccountLogin)(name);
|
|
50
50
|
});
|
|
51
51
|
program
|
|
52
52
|
.command("del")
|
|
53
|
-
.description((0, text_1.bi)("
|
|
54
|
-
.argument("[name]", (0, text_1.bi)("
|
|
53
|
+
.description((0, text_1.bi)("删除一个已录入账号或工作空间", "Remove a managed slot"))
|
|
54
|
+
.argument("[name]", (0, text_1.bi)("账号或工作空间标识(本地槽位名),留空时列出全部", "Managed slot name"))
|
|
55
55
|
.action(account_commands_1.handleAccountRemoveCommand);
|
|
56
56
|
program
|
|
57
57
|
.command("import")
|
|
58
58
|
.description((0, text_1.bi)("导入当前或指定 HOME 下的官方 codex 登录态", "Import official Codex auth state from the current or specified HOME"))
|
|
59
|
-
.argument("<name>", (0, text_1.bi)("
|
|
59
|
+
.argument("<name>", (0, text_1.bi)("账号或工作空间标识(本地槽位名,例如 work/current)", "Managed slot name, for example work/current"))
|
|
60
60
|
.argument("[codexHome]", (0, text_1.bi)("已有 HOME 目录,默认当前用户 HOME", "Source HOME, defaults to the current user HOME"))
|
|
61
61
|
.addHelpText("after", [
|
|
62
62
|
"",
|
|
@@ -66,9 +66,9 @@ function registerAccountCommands(program) {
|
|
|
66
66
|
.action(account_commands_1.handleAccountImport);
|
|
67
67
|
program
|
|
68
68
|
.command("rename")
|
|
69
|
-
.description((0, text_1.bi)("
|
|
70
|
-
.argument("<oldName>", (0, text_1.bi)("
|
|
71
|
-
.argument("<newName>", (0, text_1.bi)("
|
|
69
|
+
.description((0, text_1.bi)("重命名一个已录入账号或工作空间", "Rename a managed slot"))
|
|
70
|
+
.argument("<oldName>", (0, text_1.bi)("原账号或工作空间标识", "Old managed slot name"))
|
|
71
|
+
.argument("<newName>", (0, text_1.bi)("新账号或工作空间标识", "New managed slot name"))
|
|
72
72
|
.action(account_commands_1.handleAccountRename);
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
@@ -93,7 +93,7 @@ function registerRuntimeCommands(program) {
|
|
|
93
93
|
.addHelpText("after", [
|
|
94
94
|
"",
|
|
95
95
|
`${(0, text_1.bi)("说明", "Notes")}:`,
|
|
96
|
-
` ${(0, text_1.bi)("start 会自动接管 `~/.codex/config.toml
|
|
96
|
+
` ${(0, text_1.bi)("start 会自动接管 `~/.codex/config.toml`;默认优先使用 4399,冲突时自动顺延;每次启动都会重新生成本地 api_key;指定端口时会写入该端口;stop 会恢复接管前内容。", "`start` will manage `~/.codex/config.toml` automatically; it prefers 4399 by default, switches to the next free port on conflict, generates a fresh local api_key on every start, writes the specified port when provided, and `stop` restores the previous content.")}`,
|
|
97
97
|
].join("\n"))
|
|
98
98
|
.action(async (options) => {
|
|
99
99
|
await (0, service_control_1.handleStart)(options.port);
|
package/dist/codex-config.js
CHANGED
|
@@ -4,10 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getDefaultCodexConfigPath = getDefaultCodexConfigPath;
|
|
7
|
-
exports.generateServerApiKey = generateServerApiKey;
|
|
8
7
|
exports.applyManagedCodexConfig = applyManagedCodexConfig;
|
|
9
8
|
exports.deactivateManagedCodexConfig = deactivateManagedCodexConfig;
|
|
10
|
-
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
11
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
11
|
const config_1 = require("./config");
|
|
@@ -25,14 +23,6 @@ const PROVIDER_BLOCK_END_MARKER = "# <<< cslot provider:cslot <<<";
|
|
|
25
23
|
function getDefaultCodexConfigPath() {
|
|
26
24
|
return node_path_1.default.join(process.env.HOME ?? "", ".codex", "config.toml");
|
|
27
25
|
}
|
|
28
|
-
/**
|
|
29
|
-
* 生成随机本地 API Key,避免继续使用固定默认值。
|
|
30
|
-
*
|
|
31
|
-
* @returns 新的 API Key 字符串,仅包含十六进制字符。
|
|
32
|
-
*/
|
|
33
|
-
function generateServerApiKey() {
|
|
34
|
-
return `cslot-${node_crypto_1.default.randomBytes(18).toString("hex")}`;
|
|
35
|
-
}
|
|
36
26
|
/**
|
|
37
27
|
* 原子方式写入目标文件,避免写入过程中留下半截配置。
|
|
38
28
|
*
|
|
@@ -75,8 +65,7 @@ function buildManagedModelProviderBlock(eol) {
|
|
|
75
65
|
* @param eol 目标文件当前使用的换行符。
|
|
76
66
|
* @returns 带标记的 provider 配置块文本。
|
|
77
67
|
*/
|
|
78
|
-
function buildManagedProviderBlock(eol) {
|
|
79
|
-
const config = (0, config_1.loadConfig)();
|
|
68
|
+
function buildManagedProviderBlock(eol, config) {
|
|
80
69
|
return [
|
|
81
70
|
PROVIDER_BLOCK_START_MARKER,
|
|
82
71
|
"[model_providers.cslot]",
|
|
@@ -283,9 +272,10 @@ function applyManagedCodexConfig(targetPathOrDir, options) {
|
|
|
283
272
|
original_model_provider_block: originalModelProviderLine?.value ?? null,
|
|
284
273
|
original_cslot_provider_block: originalProviderSection?.value ?? null
|
|
285
274
|
};
|
|
275
|
+
const config = options?.config ?? (0, config_1.loadConfig)();
|
|
286
276
|
let nextContent = baseContent;
|
|
287
277
|
const managedModelProviderBlock = buildManagedModelProviderBlock(eol);
|
|
288
|
-
const managedProviderBlock = buildManagedProviderBlock(eol);
|
|
278
|
+
const managedProviderBlock = buildManagedProviderBlock(eol, config);
|
|
289
279
|
// 先处理 provider 表块,再处理 model_provider 行,避免前面的插入导致后续偏移失效。
|
|
290
280
|
if (originalProviderSection) {
|
|
291
281
|
nextContent =
|
|
@@ -321,7 +311,6 @@ function applyManagedCodexConfig(targetPathOrDir, options) {
|
|
|
321
311
|
writeFileAtomic(targetFile, nextContent);
|
|
322
312
|
(0, state_1.setManagedCodexConfigState)(snapshot);
|
|
323
313
|
if (!options?.silent) {
|
|
324
|
-
const config = (0, config_1.loadConfig)();
|
|
325
314
|
console.log((0, text_1.bi)(`已写入: ${targetFile}`, `Written to: ${targetFile}`));
|
|
326
315
|
console.log(`base_url=http://${config.server.host}:${config.server.port}/v1`);
|
|
327
316
|
console.log(`api_key=${config.server.api_key}`);
|
package/dist/config.js
CHANGED
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateServerApiKey = generateServerApiKey;
|
|
6
7
|
exports.getCslotHome = getCslotHome;
|
|
7
8
|
exports.getConfigPath = getConfigPath;
|
|
8
9
|
exports.getPidPath = getPidPath;
|
|
@@ -10,10 +11,11 @@ exports.getServiceLogPath = getServiceLogPath;
|
|
|
10
11
|
exports.expandHome = expandHome;
|
|
11
12
|
exports.loadConfig = loadConfig;
|
|
12
13
|
exports.saveConfig = saveConfig;
|
|
14
|
+
exports.rotateServerApiKey = rotateServerApiKey;
|
|
13
15
|
exports.getManagedHome = getManagedHome;
|
|
14
16
|
exports.upsertAccount = upsertAccount;
|
|
15
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
16
17
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
18
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
17
19
|
const node_os_1 = __importDefault(require("node:os"));
|
|
18
20
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
21
|
const yaml_1 = __importDefault(require("yaml"));
|
|
@@ -31,13 +33,13 @@ const configSchema = zod_1.z.object({
|
|
|
31
33
|
server: zod_1.z
|
|
32
34
|
.object({
|
|
33
35
|
host: zod_1.z.string().default("127.0.0.1"),
|
|
34
|
-
port: zod_1.z.number().int().default(
|
|
36
|
+
port: zod_1.z.number().int().default(4399),
|
|
35
37
|
api_key: zod_1.z.string().default("cslot-defaultkey"),
|
|
36
38
|
body_limit_mb: zod_1.z.number().positive().default(512)
|
|
37
39
|
})
|
|
38
40
|
.default({
|
|
39
41
|
host: "127.0.0.1",
|
|
40
|
-
port:
|
|
42
|
+
port: 4399,
|
|
41
43
|
api_key: "cslot-defaultkey",
|
|
42
44
|
body_limit_mb: 512
|
|
43
45
|
}),
|
|
@@ -55,11 +57,14 @@ const configSchema = zod_1.z.object({
|
|
|
55
57
|
accounts: zod_1.z.array(managedAccountSchema).default([])
|
|
56
58
|
});
|
|
57
59
|
/**
|
|
58
|
-
*
|
|
60
|
+
* 生成新的本地服务 API Key。
|
|
61
|
+
*
|
|
62
|
+
* 该 key 仅用于本地代理服务与受管 `~/.codex/config.toml` 之间的鉴权,
|
|
63
|
+
* 不会影响上游官方 access token。
|
|
59
64
|
*
|
|
60
65
|
* @returns 随机生成的本地 API Key。
|
|
61
66
|
*/
|
|
62
|
-
function
|
|
67
|
+
function generateServerApiKey() {
|
|
63
68
|
return `cslot-${node_crypto_1.default.randomBytes(18).toString("hex")}`;
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
@@ -124,12 +129,12 @@ function expandHome(input) {
|
|
|
124
129
|
function loadConfig() {
|
|
125
130
|
const configPath = getConfigPath();
|
|
126
131
|
if (!node_fs_1.default.existsSync(configPath)) {
|
|
127
|
-
const defaultApiKey =
|
|
132
|
+
const defaultApiKey = generateServerApiKey();
|
|
128
133
|
const defaultConfig = {
|
|
129
134
|
version: 1,
|
|
130
135
|
server: {
|
|
131
136
|
host: "127.0.0.1",
|
|
132
|
-
port:
|
|
137
|
+
port: 4399,
|
|
133
138
|
api_key: defaultApiKey,
|
|
134
139
|
body_limit_mb: 512
|
|
135
140
|
},
|
|
@@ -149,12 +154,13 @@ function loadConfig() {
|
|
|
149
154
|
let changed = JSON.stringify(parsed) !== JSON.stringify(normalized);
|
|
150
155
|
if ((!parsed || typeof parsed !== "object" || !("server" in parsed)) ||
|
|
151
156
|
!(parsed.server && typeof parsed.server === "object" && "api_key" in parsed.server)) {
|
|
152
|
-
normalized.server.api_key =
|
|
157
|
+
normalized.server.api_key = generateServerApiKey();
|
|
153
158
|
changed = true;
|
|
154
159
|
}
|
|
155
|
-
//
|
|
156
|
-
if (normalized.server.api_key === "local-only-key"
|
|
157
|
-
normalized.server.api_key
|
|
160
|
+
// 兼容历史默认值,统一迁移到新的随机本地 key。
|
|
161
|
+
if (normalized.server.api_key === "local-only-key" ||
|
|
162
|
+
normalized.server.api_key === "cslot-defaultkey") {
|
|
163
|
+
normalized.server.api_key = generateServerApiKey();
|
|
158
164
|
changed = true;
|
|
159
165
|
}
|
|
160
166
|
// 当旧配置缺少新字段时,将补全后的配置回写,便于用户直接编辑查看。
|
|
@@ -175,6 +181,25 @@ function saveConfig(config) {
|
|
|
175
181
|
const text = yaml_1.default.stringify(config);
|
|
176
182
|
node_fs_1.default.writeFileSync(configPath, text, "utf8");
|
|
177
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* 刷新本地代理服务 API Key,并将结果写回配置文件。
|
|
186
|
+
*
|
|
187
|
+
* 业务语义:
|
|
188
|
+
* 1. 每次真正启动本地代理前都重新生成一个新的本地 key。
|
|
189
|
+
* 2. 该 key 会同时驱动本地服务鉴权与 `~/.codex/config.toml` 中的 provider 头。
|
|
190
|
+
* 3. 若调用方已经持有最新配置对象,可直接传入,避免重复读取磁盘。
|
|
191
|
+
*
|
|
192
|
+
* @param config 可选的当前配置对象;未传入时会自动从磁盘读取。
|
|
193
|
+
* @returns 已写回磁盘的最新配置对象,其中 `server.api_key` 一定是新值。
|
|
194
|
+
* @throws 当配置读写失败时抛出文件系统错误。
|
|
195
|
+
*/
|
|
196
|
+
function rotateServerApiKey(config) {
|
|
197
|
+
const nextConfig = config ?? loadConfig();
|
|
198
|
+
// 每次启动前轮换本地鉴权 key,避免长期复用同一个静态口令。
|
|
199
|
+
nextConfig.server.api_key = generateServerApiKey();
|
|
200
|
+
saveConfig(nextConfig);
|
|
201
|
+
return nextConfig;
|
|
202
|
+
}
|
|
178
203
|
/**
|
|
179
204
|
* 根据账号标识生成其独立的 HOME 目录。
|
|
180
205
|
*
|
package/dist/service-control.js
CHANGED
|
@@ -14,7 +14,7 @@ const text_1 = require("./text");
|
|
|
14
14
|
*/
|
|
15
15
|
async function handleStart(portOverride) {
|
|
16
16
|
const config = (0, config_1.loadConfig)();
|
|
17
|
-
const result = (0, service_lifecycle_service_1.startManagedService)(portOverride);
|
|
17
|
+
const result = await (0, service_lifecycle_service_1.startManagedService)(portOverride);
|
|
18
18
|
if (result.alreadyRunning) {
|
|
19
19
|
console.log((0, text_1.bi)(`服务已在运行,PID=${result.pid}`, `Service is already running. PID=${result.pid}`));
|
|
20
20
|
if (portOverride) {
|
|
@@ -23,6 +23,12 @@ async function handleStart(portOverride) {
|
|
|
23
23
|
}
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
+
if (result.autoSwitched) {
|
|
27
|
+
console.log((0, text_1.bi)(`默认端口 4399 已被占用,已自动切换到 ${result.port}`, `Default port 4399 is busy. Automatically switched to ${result.port}`));
|
|
28
|
+
}
|
|
29
|
+
if (result.apiKeyRotated) {
|
|
30
|
+
console.log((0, text_1.bi)("本次启动已重新生成本地 api_key,并同步写入受管配置。", "A new local api_key was generated for this start and synced to the managed config."));
|
|
31
|
+
}
|
|
26
32
|
console.log((0, text_1.bi)(`服务已启动: http://${config.server.host}:${result.port}`, `Service started: http://${config.server.host}:${result.port}`));
|
|
27
33
|
console.log(`PID: ${result.pid}`);
|
|
28
34
|
console.log((0, text_1.bi)(`日志: ${result.logPath}`, `Log: ${result.logPath}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-slot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "本地 Codex 多账号切换与状态管理工具",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"clean": "rm -rf dist",
|
|
16
|
-
"build": "tsc -p tsconfig.json && chmod +x dist/cli.js dist/serve.js",
|
|
16
|
+
"build": "npm run clean && tsc -p tsconfig.json && chmod +x dist/cli.js dist/serve.js",
|
|
17
17
|
"prepublishOnly": "npm run build",
|
|
18
18
|
"dev": "tsx src/cli.ts",
|
|
19
19
|
"check": "tsc --noEmit -p tsconfig.json"
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.describeConfig = describeConfig;
|
|
4
|
-
exports.mutateConfig = mutateConfig;
|
|
5
|
-
exports.applyCodexProviderConfig = applyCodexProviderConfig;
|
|
6
|
-
const config_1 = require("../config");
|
|
7
|
-
const codex_config_1 = require("../codex-config");
|
|
8
|
-
const cli_helpers_1 = require("../cli-helpers");
|
|
9
|
-
/**
|
|
10
|
-
* 返回当前本地配置的只读快照,供 CLI 或其他调用方展示。
|
|
11
|
-
*
|
|
12
|
-
* @returns 当前配置快照与默认 Codex 配置路径。
|
|
13
|
-
* @throws 当配置读取失败时抛出异常。
|
|
14
|
-
*/
|
|
15
|
-
function describeConfig() {
|
|
16
|
-
return {
|
|
17
|
-
config: (0, config_1.loadConfig)(),
|
|
18
|
-
codexConfigPath: (0, codex_config_1.getDefaultCodexConfigPath)()
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* 按输入选项修改本地配置,并返回本次变更摘要。
|
|
23
|
-
*
|
|
24
|
-
* @param input 配置修改输入;字段缺失表示不修改该项。
|
|
25
|
-
* @returns 最新配置快照与已应用的变更列表。
|
|
26
|
-
* @throws 当端口非法或配置写入失败时抛出异常。
|
|
27
|
-
*/
|
|
28
|
-
function mutateConfig(input) {
|
|
29
|
-
const config = (0, config_1.loadConfig)();
|
|
30
|
-
const updates = [];
|
|
31
|
-
if (input.host) {
|
|
32
|
-
config.server.host = input.host;
|
|
33
|
-
updates.push(`host=${config.server.host}`);
|
|
34
|
-
}
|
|
35
|
-
if (input.port) {
|
|
36
|
-
config.server.port = (0, cli_helpers_1.parsePort)(input.port);
|
|
37
|
-
updates.push(`port=${config.server.port}`);
|
|
38
|
-
}
|
|
39
|
-
if (input.apiKey) {
|
|
40
|
-
config.server.api_key = input.apiKey;
|
|
41
|
-
updates.push("api_key=已手动设置");
|
|
42
|
-
}
|
|
43
|
-
else if (input.regenApiKey) {
|
|
44
|
-
config.server.api_key = (0, codex_config_1.generateServerApiKey)();
|
|
45
|
-
updates.push("api_key=已重新生成");
|
|
46
|
-
}
|
|
47
|
-
if (updates.length > 0) {
|
|
48
|
-
(0, config_1.saveConfig)(config);
|
|
49
|
-
}
|
|
50
|
-
return { config, updates };
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* 将当前受管 provider 配置写入指定的 Codex `config.toml`。
|
|
54
|
-
*
|
|
55
|
-
* @param targetPath 可选目标路径;未传时写入默认全局路径。
|
|
56
|
-
* @returns 实际写入的目标文件路径。
|
|
57
|
-
* @throws 当配置接管写入失败时抛出异常。
|
|
58
|
-
*/
|
|
59
|
-
function applyCodexProviderConfig(targetPath) {
|
|
60
|
-
return (0, codex_config_1.applyManagedCodexConfig)(targetPath);
|
|
61
|
-
}
|
package/dist/config-command.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleConfig = handleConfig;
|
|
4
|
-
const config_service_1 = require("./app/config-service");
|
|
5
|
-
const text_1 = require("./text");
|
|
6
|
-
/**
|
|
7
|
-
* 处理 `config` 子命令,支持查看、修改本地配置并按需写入 `codex config.toml`。
|
|
8
|
-
*
|
|
9
|
-
* 业务含义:
|
|
10
|
-
* 1. 将 `server.port`、`server.host`、`server.api_key` 的维护入口集中到一个命令中。
|
|
11
|
-
* 2. 支持显式轮换本地 API Key,避免继续使用固定默认值。
|
|
12
|
-
* 3. 支持在不启动代理服务的情况下预写入 Codex provider 配置,便于用户单独检查。
|
|
13
|
-
*
|
|
14
|
-
* @param options CLI 解析后的配置项;字段缺失表示用户未修改对应值。
|
|
15
|
-
* @returns 无返回值。
|
|
16
|
-
* @throws 当端口非法或配置持久化失败时抛出异常。
|
|
17
|
-
*/
|
|
18
|
-
function handleConfig(options) {
|
|
19
|
-
const { config, updates } = (0, config_service_1.mutateConfig)(options);
|
|
20
|
-
if (updates.length > 0) {
|
|
21
|
-
console.log((0, text_1.bi)("本地配置已更新:", "Local config updated:"));
|
|
22
|
-
for (const line of updates) {
|
|
23
|
-
console.log(`- ${line}`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
if (options.applyCodex !== undefined) {
|
|
27
|
-
const applyTarget = typeof options.applyCodex === "string" ? options.applyCodex : undefined;
|
|
28
|
-
(0, config_service_1.applyCodexProviderConfig)(applyTarget);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
if (updates.length === 0) {
|
|
32
|
-
const description = (0, config_service_1.describeConfig)();
|
|
33
|
-
console.log((0, text_1.bi)("当前本地配置:", "Current local config:"));
|
|
34
|
-
console.log(`- host=${description.config.server.host}`);
|
|
35
|
-
console.log(`- port=${description.config.server.port}`);
|
|
36
|
-
console.log(`- api_key=${description.config.server.api_key}`);
|
|
37
|
-
console.log(`- codex_config=${description.codexConfigPath}`);
|
|
38
|
-
console.log("");
|
|
39
|
-
console.log((0, text_1.bi)("提示:", "Notes:"));
|
|
40
|
-
console.log(`- ${(0, text_1.bi)("`import current ~` 里的 current 只是示例槽位名,不是系统内置账号。", "`current` in `import current ~` is only an example slot name, not a built-in account.")}`);
|
|
41
|
-
console.log(`- ${(0, text_1.bi)("用 `cslot config --regen-api-key` 可轮换本地 API Key。", "Use `cslot config --regen-api-key` to rotate the local API key.")}`);
|
|
42
|
-
console.log(`- ${(0, text_1.bi)("用 `cslot config --apply-codex` 可单独预写入 codex provider 配置。", "Use `cslot config --apply-codex` to write the managed Codex provider without starting the proxy.")}`);
|
|
43
|
-
}
|
|
44
|
-
}
|