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 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:4389/v1"
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 = portOverride ? (0, cli_helpers_1.parsePort)(portOverride) : config.server.port;
70
- if (portOverride) {
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
- (0, codex_config_1.applyManagedCodexConfig)();
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 只是示例槽位名,不是内置账号。", "`current` in `import current ~` is only an example slot name, not a built-in account.")}`
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)("账号标识(本地槽位名)", "Local slot name"))
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)("删除一个已录入账号", "Remove a managed slot"))
54
- .argument("[name]", (0, text_1.bi)("账号标识(本地槽位名),留空时列出全部", "Local slot name"))
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)("账号标识(本地槽位名,例如 work/current)", "Local slot name, for example work/current"))
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)("重命名一个已录入账号", "Rename a managed slot"))
70
- .argument("<oldName>", (0, text_1.bi)("原槽位名", "Old slot name"))
71
- .argument("<newName>", (0, text_1.bi)("新槽位名", "New slot name"))
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`,并在指定端口时自动写入该端口;stop 会恢复接管前内容。", "`start` will manage `~/.codex/config.toml` automatically, write the specified port when provided, and `stop` will restore the previous content.")}`,
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);
@@ -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(4389),
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: 4389,
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
- * 生成默认的本地 API Key,用于首次初始化配置时避免使用固定常量。
60
+ * 生成新的本地服务 API Key
61
+ *
62
+ * 该 key 仅用于本地代理服务与受管 `~/.codex/config.toml` 之间的鉴权,
63
+ * 不会影响上游官方 access token。
59
64
  *
60
65
  * @returns 随机生成的本地 API Key。
61
66
  */
62
- function generateDefaultLocalApiKey() {
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 = generateDefaultLocalApiKey();
132
+ const defaultApiKey = generateServerApiKey();
128
133
  const defaultConfig = {
129
134
  version: 1,
130
135
  server: {
131
136
  host: "127.0.0.1",
132
- port: 4389,
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 = generateDefaultLocalApiKey();
157
+ normalized.server.api_key = generateServerApiKey();
153
158
  changed = true;
154
159
  }
155
- // 兼容历史默认值,统一迁移到新的简短本地 key。
156
- if (normalized.server.api_key === "local-only-key") {
157
- normalized.server.api_key = "cslot-defaultkey";
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
  *
@@ -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.2",
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
- }
@@ -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
- }