codex-slot 0.1.0 → 0.1.2

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
@@ -4,7 +4,7 @@
4
4
 
5
5
  [中文文档](./docs/zh-CN.md)
6
6
 
7
- ## Features
7
+ ## Overview
8
8
 
9
9
  - Reuse the official `~/.codex` login state
10
10
  - Manage multiple accounts or workspaces as separate slots
@@ -30,15 +30,16 @@ GitHub installation from the repository URL is not supported.
30
30
 
31
31
  ## Quick Start
32
32
 
33
- Import your current Codex login state:
33
+ 1. Import your current Codex login state:
34
34
 
35
35
  ```bash
36
36
  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
41
 
41
- Check latest usage:
42
+ 2. Check the latest usage:
42
43
 
43
44
  ```bash
44
45
  codex-slot status
@@ -62,7 +63,7 @@ If you only want a non-interactive snapshot of the current state:
62
63
  codex-slot status --no-interactive
63
64
  ```
64
65
 
65
- Start the local proxy:
66
+ 3. Start the local proxy:
66
67
 
67
68
  ```bash
68
69
  codex-slot start
@@ -80,12 +81,34 @@ codex-slot start
80
81
  ```bash
81
82
  codex-slot add <name>
82
83
  codex-slot del <name>
84
+ codex-slot rename <oldName> <newName>
83
85
  codex-slot import <name> [HOME]
84
86
  codex-slot status
85
87
  codex-slot start [--port <port>]
86
88
  codex-slot stop
87
89
  ```
88
90
 
91
+ Common patterns:
92
+
93
+ - `cslot import work ~/workspace-home`
94
+ - `cslot rename work work-main`
95
+ - `cslot start`
96
+
97
+ ## Architecture
98
+
99
+ The project is intentionally split by responsibility:
100
+
101
+ - `src/cli.ts`: CLI bootstrap and command registration only
102
+ - `src/account-commands.ts`: account import, login, remove command handlers
103
+ - `src/account-commands.ts`: also owns slot rename command handling
104
+ - `src/service-control.ts`: background service lifecycle management
105
+ - `src/status-command.ts`: usage refresh output and interactive toggle UI
106
+ - `src/codex-config.ts`: managed `~/.codex/config.toml` apply/restore logic
107
+ - `src/account-store.ts`, `src/usage-sync.ts`, `src/scheduler.ts`, `src/status.ts`: core domain and runtime logic
108
+ - `src/text.ts`: shared bilingual text and locale-independent formatting helpers
109
+
110
+ This keeps the CLI entry thin while preserving stable behavior in the lower-level modules.
111
+
89
112
  ## How `status` Works
90
113
 
91
114
  `codex-slot status` does not render stale data from the official `registry.json` cache.
@@ -99,23 +122,22 @@ Instead it:
99
122
 
100
123
  ## Managed Codex Config
101
124
 
102
- `codex-slot start` writes or updates a provider block like this, based on the current `~/.cslot/config.yaml`:
125
+ `codex-slot start` writes or updates a managed provider block like this, based on the current `~/.cslot/config.yaml`:
103
126
 
104
127
  ```toml
105
128
  [model_providers.cslot]
106
129
  name = "cslot"
107
130
  base_url = "http://127.0.0.1:4389/v1"
108
- http_headers = { Authorization = "Bearer cslot-defaultkey" }
131
+ http_headers = { Authorization = "Bearer <your-local-api-key>" }
109
132
  wire_api = "responses"
110
133
  ```
111
134
 
112
135
  Behavior:
113
136
 
114
- - If global `model_provider` or `# model_provider = ...` exists, it is normalized to `model_provider = "cslot"`
115
- - If `[model_providers.cslot]` already exists, only that provider block is replaced with the fresh one above
137
+ - A managed marker block is inserted for `model_provider = "cslot"` and `[model_providers.cslot]`
138
+ - On `cslot stop`, the original `model_provider` line and original `[model_providers.cslot]` block are restored from the saved snapshot
116
139
  - Other providers and settings in `config.toml` are left untouched
117
140
  - If you start with `--port`, the port is saved to `~/.cslot/config.yaml`
118
- - `cslot stop` comments out the active `model_provider = "cslot"` line and keeps the rest of the file unchanged
119
141
 
120
142
  ## Data Directory
121
143
 
@@ -126,8 +148,6 @@ Behavior:
126
148
  - `~/.cslot/cslot.pid`
127
149
  - `~/.cslot/logs/service.log`
128
150
 
129
- If you previously used `~/.codexsw`, it is migrated automatically.
130
-
131
151
  ## Limit Handling
132
152
 
133
153
  - Weekly limit: blocked until the weekly reset time
@@ -136,8 +156,8 @@ If you previously used `~/.codexsw`, it is migrated automatically.
136
156
 
137
157
  ## Repository
138
158
 
139
- - GitHub: https://github.com/openxiaobu/cslot
140
- - Issues: https://github.com/openxiaobu/cslot/issues
159
+ - GitHub: https://github.com/openxiaobu/codex-slot
160
+ - Issues: https://github.com/openxiaobu/codex-slot/issues
141
161
 
142
162
  ## Development
143
163
 
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleAccountImport = handleAccountImport;
4
+ exports.handleAccountLogin = handleAccountLogin;
5
+ exports.handleAccountRemove = handleAccountRemove;
6
+ exports.handleAccountRemoveCommand = handleAccountRemoveCommand;
7
+ exports.handleAccountRename = handleAccountRename;
8
+ const account_service_1 = require("./app/account-service");
9
+ const text_1 = require("./text");
10
+ /**
11
+ * 将已有的 Codex HOME 目录中的登录态复制到 cslot 自己的隔离目录并纳入管理。
12
+ *
13
+ * @param name 本地账号标识,等同于配置中的槽位名。
14
+ * @param codexHome 现有 HOME 目录;若未传则默认使用当前用户 HOME。
15
+ * @returns 无返回值。
16
+ * @throws 当源目录缺少必要认证文件时抛出异常。
17
+ */
18
+ function handleAccountImport(name, codexHome) {
19
+ const { account, sourceHome } = (0, account_service_1.importAccount)(name, codexHome);
20
+ console.log((0, text_1.bi)(`账号已导入: ${account.id}`, `Account imported: ${account.id}`));
21
+ console.log((0, text_1.bi)(`来源 HOME: ${sourceHome}`, `Source HOME: ${sourceHome}`));
22
+ console.log((0, text_1.bi)(`已复制到: ${account.codex_home}`, `Copied to: ${account.codex_home}`));
23
+ }
24
+ /**
25
+ * 执行隔离登录流程,将账号录入到 cslot 管理目录。
26
+ *
27
+ * @param name 本地账号标识,等同于配置中的槽位名。
28
+ * @returns Promise,成功时输出账号目录信息。
29
+ * @throws 当登录流程失败或认证状态不完整时抛出异常。
30
+ */
31
+ async function handleAccountLogin(name) {
32
+ const home = await (0, account_service_1.loginAccount)(name);
33
+ console.log((0, text_1.bi)(`登录完成,账号目录: ${home}`, `Login completed. Account home: ${home}`));
34
+ }
35
+ /**
36
+ * 删除配置中的账号项。
37
+ *
38
+ * @param name 本地账号标识,等同于配置中的槽位名。
39
+ * @returns 无返回值。
40
+ * @throws 当账号不存在时抛出异常。
41
+ */
42
+ function handleAccountRemove(name) {
43
+ const removed = (0, account_service_1.removeAccount)(name);
44
+ if (!removed) {
45
+ throw new Error((0, text_1.bi)(`未找到账号 ${name}`, `Account not found: ${name}`));
46
+ }
47
+ console.log((0, text_1.bi)(`已删除账号配置: ${removed.id}`, `Removed account config: ${removed.id}`));
48
+ }
49
+ /**
50
+ * `del` 子命令入口:在未提供 name 时先展示当前已录入账号列表,便于选择。
51
+ *
52
+ * @param name 可选的账号标识;留空时仅打印账号列表和删除示例。
53
+ * @returns 无返回值。
54
+ * @throws 当指定账号不存在时透传删除异常。
55
+ */
56
+ function handleAccountRemoveCommand(name) {
57
+ if (!name) {
58
+ const accounts = (0, account_service_1.listAccounts)();
59
+ if (accounts.length === 0) {
60
+ console.log((0, text_1.bi)("当前没有已录入账号。", "No managed accounts found."));
61
+ return;
62
+ }
63
+ console.log((0, text_1.bi)("当前已录入账号(name):", "Managed accounts (name):"));
64
+ for (const account of accounts) {
65
+ if (account.email) {
66
+ console.log(`- ${account.id} (${account.email})`);
67
+ }
68
+ else {
69
+ console.log(`- ${account.id}`);
70
+ }
71
+ }
72
+ console.log("");
73
+ console.log((0, text_1.bi)("请使用以下命令删除指定账号,例如:", "Use the following command to remove a specific account, for example:"));
74
+ console.log(" codex-slot del <name>");
75
+ return;
76
+ }
77
+ handleAccountRemove(name);
78
+ }
79
+ /**
80
+ * 重命名已有受管槽位。
81
+ *
82
+ * @param oldName 原槽位名。
83
+ * @param newName 新槽位名。
84
+ * @returns 无返回值。
85
+ * @throws 当旧槽位不存在、新槽位已存在或目录迁移失败时抛出异常。
86
+ */
87
+ function handleAccountRename(oldName, newName) {
88
+ const renamed = (0, account_service_1.renameAccount)(oldName, newName);
89
+ console.log((0, text_1.bi)(`已重命名账号: ${oldName} -> ${newName}`, `Renamed account: ${oldName} -> ${newName}`));
90
+ console.log((0, text_1.bi)(`当前目录: ${renamed.codex_home}`, `Current home: ${renamed.codex_home}`));
91
+ }
@@ -16,6 +16,7 @@ exports.findManagedAccount = findManagedAccount;
16
16
  const node_fs_1 = __importDefault(require("node:fs"));
17
17
  const node_path_1 = __importDefault(require("node:path"));
18
18
  const config_1 = require("./config");
19
+ const text_1 = require("./text");
19
20
  /**
20
21
  * 读取指定账号 HOME 下的 `.codex` 目录。
21
22
  *
@@ -87,10 +88,10 @@ function cloneCodexAuthState(sourceHome, targetHome) {
87
88
  const sourceAccountsDir = node_path_1.default.join(sourceCodexDir, "accounts");
88
89
  const sourceRegistryPath = node_path_1.default.join(sourceAccountsDir, "registry.json");
89
90
  if (!node_fs_1.default.existsSync(sourceAuthPath)) {
90
- throw new Error(`来源目录缺少 auth.json: ${sourceAuthPath}`);
91
+ throw new Error((0, text_1.bi)(`来源目录缺少 auth.json: ${sourceAuthPath}`, `Source directory is missing auth.json: ${sourceAuthPath}`));
91
92
  }
92
93
  if (!node_fs_1.default.existsSync(sourceRegistryPath)) {
93
- throw new Error(`来源目录缺少 registry.json: ${sourceRegistryPath}`);
94
+ throw new Error((0, text_1.bi)(`来源目录缺少 registry.json: ${sourceRegistryPath}`, `Source directory is missing registry.json: ${sourceRegistryPath}`));
94
95
  }
95
96
  node_fs_1.default.mkdirSync(targetCodexDir, { recursive: true });
96
97
  node_fs_1.default.mkdirSync(node_path_1.default.join(targetCodexDir, "accounts"), { recursive: true });
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.importAccount = importAccount;
7
+ exports.loginAccount = loginAccount;
8
+ exports.removeAccount = removeAccount;
9
+ exports.listAccounts = listAccounts;
10
+ exports.renameAccount = renameAccount;
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ const account_store_1 = require("../account-store");
13
+ const config_1 = require("../config");
14
+ const login_1 = require("../login");
15
+ const state_1 = require("../state");
16
+ const text_1 = require("../text");
17
+ /**
18
+ * 导入指定 HOME 下的官方 Codex 登录态到受管槽位。
19
+ *
20
+ * @param slotName 本地槽位名。
21
+ * @param codexHome 可选源 HOME;未传时默认当前用户 HOME。
22
+ * @returns 导入后的受管账号配置与源路径信息。
23
+ * @throws 当源目录缺少必要认证文件或写入失败时抛出异常。
24
+ */
25
+ function importAccount(slotName, codexHome) {
26
+ const sourceHome = codexHome ? (0, config_1.expandHome)(codexHome) : process.env.HOME ?? "";
27
+ const managedHome = (0, config_1.getManagedHome)(slotName);
28
+ (0, account_store_1.cloneCodexAuthState)(sourceHome, managedHome);
29
+ return {
30
+ account: (0, account_store_1.registerManagedAccount)(slotName, managedHome),
31
+ sourceHome
32
+ };
33
+ }
34
+ /**
35
+ * 通过隔离 HOME 调起官方 `codex login` 完成单账号登录。
36
+ *
37
+ * @param slotName 本地槽位名。
38
+ * @returns Promise,成功时返回登录后的账号 HOME 目录。
39
+ * @throws 当登录失败或登录态不完整时抛出异常。
40
+ */
41
+ async function loginAccount(slotName) {
42
+ return await (0, login_1.loginManagedAccount)(slotName);
43
+ }
44
+ /**
45
+ * 删除指定受管槽位。
46
+ *
47
+ * @param slotName 本地槽位名。
48
+ * @returns 被删除的账号配置;不存在时返回 `null`。
49
+ * @throws 无显式抛出。
50
+ */
51
+ function removeAccount(slotName) {
52
+ return (0, account_store_1.removeManagedAccount)(slotName);
53
+ }
54
+ /**
55
+ * 列出当前所有受管账号配置。
56
+ *
57
+ * @returns 当前配置中的账号列表。
58
+ * @throws 当配置读取失败时抛出异常。
59
+ */
60
+ function listAccounts() {
61
+ return (0, config_1.loadConfig)().accounts;
62
+ }
63
+ /**
64
+ * 重命名受管槽位,并同步迁移与账号标识绑定的本地状态。
65
+ *
66
+ * 业务规则:
67
+ * 1. 若账号 HOME 使用默认槽位目录,则一并重命名目录路径。
68
+ * 2. 若账号 HOME 是自定义路径,则仅更新账号标识,不强改目录。
69
+ * 3. `state.json` 中与账号标识绑定的 usage/block 缓存会同步迁移。
70
+ *
71
+ * @param oldName 原槽位名。
72
+ * @param newName 新槽位名。
73
+ * @returns 重命名后的账号配置。
74
+ * @throws 当旧槽位不存在、新槽位已存在或目录迁移失败时抛出异常。
75
+ */
76
+ function renameAccount(oldName, newName) {
77
+ const config = (0, config_1.loadConfig)();
78
+ const index = config.accounts.findIndex((item) => item.id === oldName);
79
+ if (index < 0) {
80
+ throw new Error((0, text_1.bi)(`未找到账号 ${oldName}`, `Account not found: ${oldName}`));
81
+ }
82
+ if (config.accounts.some((item) => item.id === newName)) {
83
+ throw new Error((0, text_1.bi)(`账号 ${newName} 已存在`, `Account already exists: ${newName}`));
84
+ }
85
+ const currentAccount = config.accounts[index];
86
+ const defaultOldHome = (0, config_1.getManagedHome)(oldName);
87
+ const defaultNewHome = (0, config_1.getManagedHome)(newName);
88
+ let nextHome = currentAccount.codex_home;
89
+ if (currentAccount.codex_home === defaultOldHome) {
90
+ if (node_fs_1.default.existsSync(defaultNewHome)) {
91
+ throw new Error((0, text_1.bi)(`目标槽位目录已存在: ${defaultNewHome}`, `Target slot directory already exists: ${defaultNewHome}`));
92
+ }
93
+ // 只有默认槽位目录才一起迁移,避免误移动用户手工指定的 HOME。
94
+ if (node_fs_1.default.existsSync(defaultOldHome)) {
95
+ node_fs_1.default.renameSync(defaultOldHome, defaultNewHome);
96
+ }
97
+ nextHome = defaultNewHome;
98
+ }
99
+ const renamedAccount = {
100
+ ...currentAccount,
101
+ id: newName,
102
+ name: newName,
103
+ codex_home: nextHome
104
+ };
105
+ config.accounts[index] = renamedAccount;
106
+ (0, config_1.saveConfig)(config);
107
+ const state = (0, state_1.loadState)();
108
+ if (state.account_blocks[oldName]) {
109
+ state.account_blocks[newName] = state.account_blocks[oldName];
110
+ delete state.account_blocks[oldName];
111
+ }
112
+ if (state.usage_cache[oldName]) {
113
+ state.usage_cache[newName] = {
114
+ ...state.usage_cache[oldName],
115
+ accountId: newName
116
+ };
117
+ delete state.usage_cache[oldName];
118
+ }
119
+ (0, state_1.saveState)(state);
120
+ return renamedAccount;
121
+ }
@@ -0,0 +1,61 @@
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
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getRunningPid = getRunningPid;
7
+ exports.startManagedService = startManagedService;
8
+ exports.stopManagedService = stopManagedService;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const node_child_process_1 = require("node:child_process");
12
+ const codex_config_1 = require("../codex-config");
13
+ const cli_helpers_1 = require("../cli-helpers");
14
+ const config_1 = require("../config");
15
+ /**
16
+ * 判断后台服务当前是否在运行。
17
+ *
18
+ * @returns 运行中的 PID;未运行时返回 `null`。
19
+ * @throws 无显式抛出。
20
+ */
21
+ function getRunningPid() {
22
+ const pidPath = (0, config_1.getPidPath)();
23
+ if (!node_fs_1.default.existsSync(pidPath)) {
24
+ return null;
25
+ }
26
+ const raw = node_fs_1.default.readFileSync(pidPath, "utf8").trim();
27
+ const pid = Number(raw);
28
+ if (!Number.isInteger(pid) || pid <= 0) {
29
+ node_fs_1.default.rmSync(pidPath, { force: true });
30
+ return null;
31
+ }
32
+ try {
33
+ process.kill(pid, 0);
34
+ return pid;
35
+ }
36
+ catch {
37
+ node_fs_1.default.rmSync(pidPath, { force: true });
38
+ return null;
39
+ }
40
+ }
41
+ /**
42
+ * 解析后台服务启动入口,兼容源码运行与构建产物运行两种场景。
43
+ *
44
+ * @returns 可直接传给 `spawn` 的命令与参数前缀。
45
+ * @throws 无显式抛出。
46
+ */
47
+ function resolveServeEntrypoint() {
48
+ const serveBasePath = node_path_1.default.resolve(__dirname, "..", "serve");
49
+ if (node_path_1.default.extname(__filename) === ".ts") {
50
+ return {
51
+ command: process.platform === "win32" ? "npx.cmd" : "npx",
52
+ args: ["tsx", `${serveBasePath}.ts`]
53
+ };
54
+ }
55
+ return {
56
+ command: process.execPath,
57
+ args: [`${serveBasePath}.js`]
58
+ };
59
+ }
60
+ /**
61
+ * 启动后台服务,并在需要时将端口写回本地配置。
62
+ *
63
+ * @param portOverride 可选端口文本;传入时会先校验并落盘到配置。
64
+ * @returns 启动结果,包含是否已在运行、最终端口、PID 和日志路径。
65
+ * @throws 当端口非法、接管配置失败或子进程启动失败时抛出异常。
66
+ */
67
+ function startManagedService(portOverride) {
68
+ const config = (0, config_1.loadConfig)();
69
+ const port = portOverride ? (0, cli_helpers_1.parsePort)(portOverride) : config.server.port;
70
+ if (portOverride) {
71
+ config.server.port = port;
72
+ (0, config_1.saveConfig)(config);
73
+ }
74
+ const runningPid = getRunningPid();
75
+ if (runningPid) {
76
+ return {
77
+ alreadyRunning: true,
78
+ pid: runningPid,
79
+ port,
80
+ logPath: (0, config_1.getServiceLogPath)()
81
+ };
82
+ }
83
+ (0, codex_config_1.applyManagedCodexConfig)();
84
+ const logPath = (0, config_1.getServiceLogPath)();
85
+ const logFd = node_fs_1.default.openSync(logPath, "a");
86
+ const serveEntrypoint = resolveServeEntrypoint();
87
+ const child = (0, node_child_process_1.spawn)(serveEntrypoint.command, [...serveEntrypoint.args, "--port", String(port)], {
88
+ detached: true,
89
+ stdio: ["ignore", logFd, logFd]
90
+ });
91
+ child.unref();
92
+ node_fs_1.default.writeFileSync((0, config_1.getPidPath)(), `${child.pid}\n`, "utf8");
93
+ return {
94
+ alreadyRunning: false,
95
+ pid: child.pid ?? 0,
96
+ port,
97
+ logPath
98
+ };
99
+ }
100
+ /**
101
+ * 停止后台服务,并恢复被接管的 Codex 配置。
102
+ *
103
+ * @returns 停止结果;若服务未运行则仅执行配置恢复。
104
+ * @throws 当进程终止失败时透传底层异常。
105
+ */
106
+ function stopManagedService() {
107
+ const pid = getRunningPid();
108
+ if (!pid) {
109
+ (0, codex_config_1.deactivateManagedCodexConfig)();
110
+ return { stoppedPid: null };
111
+ }
112
+ process.kill(pid, "SIGTERM");
113
+ node_fs_1.default.rmSync((0, config_1.getPidPath)(), { force: true });
114
+ (0, codex_config_1.deactivateManagedCodexConfig)();
115
+ return { stoppedPid: pid };
116
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refreshStatusSnapshot = refreshStatusSnapshot;
4
+ exports.getStatusSnapshot = getStatusSnapshot;
5
+ exports.persistAccountEnabledState = persistAccountEnabledState;
6
+ const config_1 = require("../config");
7
+ const scheduler_1 = require("../scheduler");
8
+ const status_1 = require("../status");
9
+ const usage_sync_1 = require("../usage-sync");
10
+ /**
11
+ * 刷新全部账号额度并返回最新状态快照。
12
+ *
13
+ * @returns Promise,成功时返回状态列表、摘要和当前选中账号名。
14
+ * @throws 当额度刷新失败时透传底层异常。
15
+ */
16
+ async function refreshStatusSnapshot() {
17
+ await (0, usage_sync_1.refreshAllAccountUsage)();
18
+ return getStatusSnapshot();
19
+ }
20
+ /**
21
+ * 读取当前状态快照但不主动刷新远端额度。
22
+ *
23
+ * @returns 当前状态列表、摘要和当前选中账号名。
24
+ * @throws 当本地配置或状态读取失败时抛出异常。
25
+ */
26
+ function getStatusSnapshot() {
27
+ const statuses = (0, status_1.collectAccountStatuses)();
28
+ const selected = (0, scheduler_1.pickBestAccount)();
29
+ return {
30
+ statuses,
31
+ selectedName: selected?.account.name ?? null,
32
+ summary: (0, status_1.summarizeAccountStatuses)(statuses)
33
+ };
34
+ }
35
+ /**
36
+ * 将交互式界面中的启用状态修改写回配置文件。
37
+ *
38
+ * @param accounts 用户在交互界面中调整后的账号数组。
39
+ * @returns 无返回值。
40
+ * @throws 当配置写入失败时抛出异常。
41
+ */
42
+ function persistAccountEnabledState(accounts) {
43
+ const latest = (0, config_1.loadConfig)();
44
+ for (const account of accounts) {
45
+ const index = latest.accounts.findIndex((item) => item.id === account.id);
46
+ if (index >= 0) {
47
+ latest.accounts[index].enabled = account.enabled;
48
+ }
49
+ }
50
+ (0, config_1.saveConfig)(latest);
51
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getCliVersion = getCliVersion;
7
+ exports.parsePort = parsePort;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const text_1 = require("./text");
11
+ /**
12
+ * 读取当前 CLI 的发布版本号,优先与 npm 包元数据保持一致。
13
+ *
14
+ * @returns 当前包版本号;当 `package.json` 不可读或字段缺失时返回 `0.0.0`。
15
+ * @throws 无显式抛出;内部异常会被吞掉并回退到默认版本号。
16
+ */
17
+ function getCliVersion() {
18
+ try {
19
+ const packageJsonPath = node_path_1.default.resolve(__dirname, "../package.json");
20
+ const packageJsonContent = node_fs_1.default.readFileSync(packageJsonPath, "utf8");
21
+ const packageJson = JSON.parse(packageJsonContent);
22
+ if (typeof packageJson.version === "string" && packageJson.version.trim().length > 0) {
23
+ return packageJson.version;
24
+ }
25
+ }
26
+ catch {
27
+ // 读取失败时使用保底版本,避免 `-V` 命令直接异常退出。
28
+ }
29
+ return "0.0.0";
30
+ }
31
+ /**
32
+ * 校验并解析端口参数,避免将非法值写入配置或用于启动服务。
33
+ *
34
+ * @param rawPort 命令行传入的端口文本。
35
+ * @returns 合法的监听端口。
36
+ * @throws 当端口为空、非数字或超出合法范围时抛出异常。
37
+ */
38
+ function parsePort(rawPort) {
39
+ const port = Number(rawPort);
40
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
41
+ throw new Error((0, text_1.bi)(`非法端口: ${rawPort}`, `Invalid port: ${rawPort}`));
42
+ }
43
+ return port;
44
+ }