@praeviso/code-env-switch 0.1.1 → 0.1.3
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/.github/workflows/npm-publish.yml +25 -0
- package/AGENTS.md +32 -0
- package/PLAN.md +33 -0
- package/README.md +24 -0
- package/README_zh.md +24 -0
- package/bin/cli/args.js +303 -0
- package/bin/cli/help.js +77 -0
- package/bin/cli/index.js +13 -0
- package/bin/commands/add.js +81 -0
- package/bin/commands/index.js +21 -0
- package/bin/commands/launch.js +330 -0
- package/bin/commands/list.js +57 -0
- package/bin/commands/show.js +10 -0
- package/bin/commands/statusline.js +12 -0
- package/bin/commands/unset.js +20 -0
- package/bin/commands/use.js +92 -0
- package/bin/config/defaults.js +85 -0
- package/bin/config/index.js +20 -0
- package/bin/config/io.js +72 -0
- package/bin/constants.js +27 -0
- package/bin/index.js +279 -0
- package/bin/profile/display.js +78 -0
- package/bin/profile/index.js +26 -0
- package/bin/profile/match.js +40 -0
- package/bin/profile/resolve.js +79 -0
- package/bin/profile/type.js +90 -0
- package/bin/shell/detect.js +40 -0
- package/bin/shell/index.js +18 -0
- package/bin/shell/snippet.js +92 -0
- package/bin/shell/utils.js +35 -0
- package/bin/statusline/claude.js +153 -0
- package/bin/statusline/codex.js +356 -0
- package/bin/statusline/index.js +631 -0
- package/bin/types.js +5 -0
- package/bin/ui/index.js +16 -0
- package/bin/ui/interactive.js +189 -0
- package/bin/ui/readline.js +76 -0
- package/bin/usage/index.js +832 -0
- package/code-env.example.json +11 -0
- package/package.json +2 -2
- package/src/cli/args.ts +318 -0
- package/src/cli/help.ts +75 -0
- package/src/cli/index.ts +5 -0
- package/src/commands/add.ts +91 -0
- package/src/commands/index.ts +10 -0
- package/src/commands/launch.ts +395 -0
- package/src/commands/list.ts +91 -0
- package/src/commands/show.ts +12 -0
- package/src/commands/statusline.ts +18 -0
- package/src/commands/unset.ts +19 -0
- package/src/commands/use.ts +121 -0
- package/src/config/defaults.ts +88 -0
- package/src/config/index.ts +19 -0
- package/src/config/io.ts +69 -0
- package/src/constants.ts +28 -0
- package/src/index.ts +359 -0
- package/src/profile/display.ts +77 -0
- package/src/profile/index.ts +12 -0
- package/src/profile/match.ts +41 -0
- package/src/profile/resolve.ts +84 -0
- package/src/profile/type.ts +83 -0
- package/src/shell/detect.ts +30 -0
- package/src/shell/index.ts +6 -0
- package/src/shell/snippet.ts +92 -0
- package/src/shell/utils.ts +30 -0
- package/src/statusline/claude.ts +172 -0
- package/src/statusline/codex.ts +393 -0
- package/src/statusline/index.ts +920 -0
- package/src/types.ts +95 -0
- package/src/ui/index.ts +5 -0
- package/src/ui/interactive.ts +220 -0
- package/src/ui/readline.ts +85 -0
- package/src/usage/index.ts +979 -0
- package/bin/codenv.js +0 -1316
- package/src/codenv.ts +0 -1478
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
publish:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: "20"
|
|
19
|
+
registry-url: "https://registry.npmjs.org"
|
|
20
|
+
cache: "npm"
|
|
21
|
+
- run: npm ci
|
|
22
|
+
- run: npm run build
|
|
23
|
+
- run: npm publish --access public
|
|
24
|
+
env:
|
|
25
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Structure & Module Organization
|
|
4
|
+
- `src/` holds TypeScript sources. Key areas: `src/cli/` argument parsing, `src/commands/` CLI actions, `src/config/` config IO, `src/profile/` profile resolution, `src/shell/` shell integration, `src/ui/` prompts, `src/usage/` logging.
|
|
5
|
+
- `bin/` contains compiled JavaScript from `tsc`; treat it as generated output.
|
|
6
|
+
- `code-env.example.json` is the public config template; `README.md` and `README_zh.md` are user docs.
|
|
7
|
+
|
|
8
|
+
## Build, Test, and Development Commands
|
|
9
|
+
- `npm install` installs dependencies.
|
|
10
|
+
- `npm run build` compiles `src/` to `bin/` using `tsconfig.json`.
|
|
11
|
+
- `npm run lint` runs ESLint; `npm run lint:fix` auto-fixes.
|
|
12
|
+
- `npm link` (or `npm install -g .`) installs the CLI locally for manual testing.
|
|
13
|
+
- No automated test script is configured yet.
|
|
14
|
+
|
|
15
|
+
## Coding Style & Naming Conventions
|
|
16
|
+
- Follow existing formatting: 4-space indentation, double quotes, and semicolons.
|
|
17
|
+
- Use `camelCase` for variables/functions and `PascalCase` for types/interfaces.
|
|
18
|
+
- CLI commands are single verbs (`add`, `use`, `unset`); flags are `--kebab-case` (e.g., `--config`, `--shell`).
|
|
19
|
+
- Keep changes ESLint-clean.
|
|
20
|
+
|
|
21
|
+
## Testing Guidelines
|
|
22
|
+
- There is no test framework configured. If you add tests, also add a `npm test` script and document the framework in `README.md`.
|
|
23
|
+
- Prefer a top-level `tests/` folder or `src/**/__tests__` for discoverable structure.
|
|
24
|
+
|
|
25
|
+
## Commit & Pull Request Guidelines
|
|
26
|
+
- Git history shows Conventional Commit-style prefixes (e.g., `feat:`) plus simple descriptive messages; release commits use version numbers like `0.1.1`.
|
|
27
|
+
- Use short, imperative subjects and include a brief body for non-trivial changes.
|
|
28
|
+
- PRs should include: summary, rationale, manual test commands run (e.g., `npm run build`, `codenv use`), and any config or shell-rc impacts; link related issues.
|
|
29
|
+
|
|
30
|
+
## Configuration & Security Notes
|
|
31
|
+
- Config is loaded via `--config`, `CODE_ENV_CONFIG`, or `~/.config/code-env/config.json`; use `code-env.example.json` for sanitized examples.
|
|
32
|
+
- Never commit real API keys or user-specific config.
|
package/PLAN.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Statusline Plan (Simplified)
|
|
2
|
+
|
|
3
|
+
## Goals
|
|
4
|
+
- Provide a host-agnostic statusline outputter for Codex CLI and Claude Code.
|
|
5
|
+
- Show Git status, model label, and usage in a single line.
|
|
6
|
+
- Auto-enable a bottom statusline when launching via `codenv`.
|
|
7
|
+
|
|
8
|
+
## Done (implemented)
|
|
9
|
+
- `codenv statusline` outputs text/JSON with Git + model + usage.
|
|
10
|
+
- ANSI bottom-bar renderer added with forced redraw support (for Codex UI repaint).
|
|
11
|
+
- `codenv launch codex` auto-starts the renderer.
|
|
12
|
+
- `codenv launch claude` ensures `.claude/settings.json` uses a `statusLine` command object.
|
|
13
|
+
- Env knobs added for enable/disable, interval, offset/reserve, and force redraw.
|
|
14
|
+
|
|
15
|
+
## Next plan (overall)
|
|
16
|
+
### Phase 1 — Stabilize behavior
|
|
17
|
+
- Verify Codex overlay behavior across terminals; tune default interval/offset as needed.
|
|
18
|
+
- Add a simple “compatibility fallback” note for terminals that don’t support scroll regions.
|
|
19
|
+
- Confirm Claude statusLine object format across versions (no string form).
|
|
20
|
+
|
|
21
|
+
### Phase 2 — Data quality
|
|
22
|
+
- Optional: resolve model from session logs if not provided by env/stdin.
|
|
23
|
+
- Clarify usage sync strategy (per-session vs aggregate) and align with `src/usage/`.
|
|
24
|
+
|
|
25
|
+
### Phase 3 — Integrations & ergonomics
|
|
26
|
+
- Provide generic wrapper example for other CLIs (stdin JSON contract).
|
|
27
|
+
- Optional tmux statusline snippet as alternative for Codex.
|
|
28
|
+
- Add minimal config knobs to `code-env.example.json` if needed.
|
|
29
|
+
|
|
30
|
+
### Phase 4 — QA & polish
|
|
31
|
+
- Manual test checklist (bash/zsh/fish, macOS/Linux).
|
|
32
|
+
- Performance check target (<50ms typical render).
|
|
33
|
+
- Harden error handling and safe fallbacks.
|
package/README.md
CHANGED
|
@@ -65,6 +65,8 @@ npm link
|
|
|
65
65
|
|
|
66
66
|
> By default, `codenv use` only outputs shell commands. After running
|
|
67
67
|
> `codenv init`, the shell wrapper applies them automatically.
|
|
68
|
+
> The snippet also wraps `codex`/`claude` to bind sessions to profiles; use
|
|
69
|
+
> `command codex` / `command claude` to bypass.
|
|
68
70
|
|
|
69
71
|
### Common commands
|
|
70
72
|
|
|
@@ -204,6 +206,17 @@ If nothing is found, `codenv add` writes to `~/.config/code-env/config.json`.
|
|
|
204
206
|
"codex": "primary",
|
|
205
207
|
"claude": "default"
|
|
206
208
|
},
|
|
209
|
+
"codexStatusline": {
|
|
210
|
+
"command": ["codenv", "statusline", "--type", "codex", "--sync-usage"],
|
|
211
|
+
"showHints": false,
|
|
212
|
+
"updateIntervalMs": 300,
|
|
213
|
+
"timeoutMs": 1000
|
|
214
|
+
},
|
|
215
|
+
"claudeStatusline": {
|
|
216
|
+
"command": "codenv statusline --type claude --sync-usage",
|
|
217
|
+
"type": "command",
|
|
218
|
+
"padding": 0
|
|
219
|
+
},
|
|
207
220
|
"profiles": {
|
|
208
221
|
"p_a1b2c3": {
|
|
209
222
|
"name": "primary",
|
|
@@ -223,6 +236,17 @@ If nothing is found, `codenv add` writes to `~/.config/code-env/config.json`.
|
|
|
223
236
|
Notes:
|
|
224
237
|
- `unset`: global keys to clear. Type-specific defaults are applied only for the active type and won't clear the other type.
|
|
225
238
|
- `defaultProfiles`: optional; map of `codex`/`claude` to profile name or key used by `codenv auto`.
|
|
239
|
+
- `codexStatusline`: optional; config to inject Codex TUI status line settings when launching `codex`.
|
|
240
|
+
- `command`: string or string[]; command passed to Codex `tui.status_line.command`.
|
|
241
|
+
- `showHints`: boolean; whether Codex footer hints are appended when the status line is active.
|
|
242
|
+
- `updateIntervalMs`: number; update interval in ms for the status line command.
|
|
243
|
+
- `timeoutMs`: number; timeout in ms for the status line command.
|
|
244
|
+
- `configPath`: optional; override `~/.codex/config.toml` (also supports `CODE_ENV_CODEX_CONFIG_PATH`).
|
|
245
|
+
- `claudeStatusline`: optional; config to inject Claude Code statusLine settings when launching `claude`.
|
|
246
|
+
- `command`: string (or string[]; arrays are joined into a single command string).
|
|
247
|
+
- `type`: string; statusLine type (default: `command`).
|
|
248
|
+
- `padding`: number; statusLine padding (default: 0).
|
|
249
|
+
- `settingsPath`: optional; override `~/.claude/settings.json` (also supports `CODE_ENV_CLAUDE_SETTINGS_PATH`).
|
|
226
250
|
- `name`: human-facing profile name shown in `codenv list` and used by `codenv use <name>`.
|
|
227
251
|
- `type`: optional; `codex` or `claude` (alias `cc`) for `codenv use <type> <name>` matching.
|
|
228
252
|
- `note`: shown in `codenv list`.
|
package/README_zh.md
CHANGED
|
@@ -65,6 +65,8 @@ npm link
|
|
|
65
65
|
|
|
66
66
|
> 默认情况下,`codenv use` 仅输出 shell 命令;执行 `codenv init` 后,
|
|
67
67
|
> shell 包装函数会自动在当前终端生效。
|
|
68
|
+
> 该片段还会包装 `codex`/`claude` 以绑定会话到 profile;如需绕过,
|
|
69
|
+
> 可使用 `command codex` / `command claude`。
|
|
68
70
|
|
|
69
71
|
### 常用命令
|
|
70
72
|
|
|
@@ -203,6 +205,17 @@ codenv use codex primary | source
|
|
|
203
205
|
"codex": "primary",
|
|
204
206
|
"claude": "default"
|
|
205
207
|
},
|
|
208
|
+
"codexStatusline": {
|
|
209
|
+
"command": ["codenv", "statusline", "--type", "codex", "--sync-usage"],
|
|
210
|
+
"showHints": false,
|
|
211
|
+
"updateIntervalMs": 300,
|
|
212
|
+
"timeoutMs": 1000
|
|
213
|
+
},
|
|
214
|
+
"claudeStatusline": {
|
|
215
|
+
"command": "codenv statusline --type claude --sync-usage",
|
|
216
|
+
"type": "command",
|
|
217
|
+
"padding": 0
|
|
218
|
+
},
|
|
206
219
|
"profiles": {
|
|
207
220
|
"p_a1b2c3": {
|
|
208
221
|
"name": "primary",
|
|
@@ -222,6 +235,17 @@ codenv use codex primary | source
|
|
|
222
235
|
说明:
|
|
223
236
|
- `unset`:全局需要清理的环境变量。按 type 的默认清理键只会对当前 type 生效,不会影响其他 type。
|
|
224
237
|
- `defaultProfiles`:可选;`codex`/`claude` 对应的默认 profile 名称或 key,供 `codenv auto` 使用。
|
|
238
|
+
- `codexStatusline`:可选;在启动 `codex` 时写入 Codex TUI status line 配置。
|
|
239
|
+
- `command`:字符串或字符串数组;写入 Codex 的 `tui.status_line.command`。
|
|
240
|
+
- `showHints`:布尔值;状态栏激活时是否拼接 footer 提示。
|
|
241
|
+
- `updateIntervalMs`:数字;状态栏命令的刷新间隔(毫秒)。
|
|
242
|
+
- `timeoutMs`:数字;状态栏命令超时(毫秒)。
|
|
243
|
+
- `configPath`:可选;覆盖 `~/.codex/config.toml`(也可用 `CODE_ENV_CODEX_CONFIG_PATH`)。
|
|
244
|
+
- `claudeStatusline`:可选;在启动 `claude` 时写入 Claude Code statusLine 配置。
|
|
245
|
+
- `command`:字符串(或字符串数组;数组会被拼接成单个命令字符串)。
|
|
246
|
+
- `type`:字符串;statusLine 类型(默认 `command`)。
|
|
247
|
+
- `padding`:数字;statusLine padding(默认 0)。
|
|
248
|
+
- `settingsPath`:可选;覆盖 `~/.claude/settings.json`(也可用 `CODE_ENV_CLAUDE_SETTINGS_PATH`)。
|
|
225
249
|
- `name`:用于展示的 profile 名称,`codenv list` 与 `codenv use <name>` 会使用它。
|
|
226
250
|
- `type`:可选,`codex` 或 `claude`(别名 `cc`),便于用 `codenv use <type> <name>` 匹配。
|
|
227
251
|
- `note`:显示在 `codenv list` 输出中。
|
package/bin/cli/args.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseArgs = parseArgs;
|
|
4
|
+
exports.parseInitArgs = parseInitArgs;
|
|
5
|
+
exports.parseAddArgs = parseAddArgs;
|
|
6
|
+
exports.parseStatuslineArgs = parseStatuslineArgs;
|
|
7
|
+
const type_1 = require("../profile/type");
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
let configPath = null;
|
|
10
|
+
const args = [];
|
|
11
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12
|
+
const arg = argv[i];
|
|
13
|
+
if (arg === "-h" || arg === "--help") {
|
|
14
|
+
return { args: [], configPath: null, help: true };
|
|
15
|
+
}
|
|
16
|
+
if (arg === "-c" || arg === "--config") {
|
|
17
|
+
configPath = argv[i + 1];
|
|
18
|
+
i++;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg.startsWith("--config=")) {
|
|
22
|
+
configPath = arg.slice("--config=".length);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
args.push(arg);
|
|
26
|
+
}
|
|
27
|
+
return { args, configPath, help: false };
|
|
28
|
+
}
|
|
29
|
+
function parseInitArgs(args) {
|
|
30
|
+
const result = { apply: true, print: false, shell: null };
|
|
31
|
+
for (let i = 0; i < args.length; i++) {
|
|
32
|
+
const arg = args[i];
|
|
33
|
+
if (arg === "--apply") {
|
|
34
|
+
result.apply = true;
|
|
35
|
+
result.print = false;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (arg === "--print") {
|
|
39
|
+
result.print = true;
|
|
40
|
+
result.apply = false;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === "--shell") {
|
|
44
|
+
const val = args[i + 1];
|
|
45
|
+
if (!val)
|
|
46
|
+
throw new Error("Missing value for --shell.");
|
|
47
|
+
result.shell = val;
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (arg.startsWith("--shell=")) {
|
|
52
|
+
result.shell = arg.slice("--shell=".length);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`Unknown init argument: ${arg}`);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
function parseAddArgs(args) {
|
|
60
|
+
const result = {
|
|
61
|
+
profile: null,
|
|
62
|
+
pairs: [],
|
|
63
|
+
note: null,
|
|
64
|
+
removeFiles: [],
|
|
65
|
+
commands: [],
|
|
66
|
+
unset: [],
|
|
67
|
+
type: null,
|
|
68
|
+
};
|
|
69
|
+
for (let i = 0; i < args.length; i++) {
|
|
70
|
+
const arg = args[i];
|
|
71
|
+
if (!result.profile && !arg.startsWith("-")) {
|
|
72
|
+
result.profile = arg;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg === "-n" || arg === "--note") {
|
|
76
|
+
const val = args[i + 1];
|
|
77
|
+
if (!val)
|
|
78
|
+
throw new Error("Missing value for --note.");
|
|
79
|
+
result.note = val;
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (arg.startsWith("--note=")) {
|
|
84
|
+
result.note = arg.slice("--note=".length);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (arg === "-t" || arg === "--type") {
|
|
88
|
+
const val = args[i + 1];
|
|
89
|
+
if (!val)
|
|
90
|
+
throw new Error("Missing value for --type.");
|
|
91
|
+
result.type = val;
|
|
92
|
+
i++;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (arg.startsWith("--type=")) {
|
|
96
|
+
result.type = arg.slice("--type=".length);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (arg === "-r" || arg === "--remove-file") {
|
|
100
|
+
const val = args[i + 1];
|
|
101
|
+
if (!val)
|
|
102
|
+
throw new Error("Missing value for --remove-file.");
|
|
103
|
+
result.removeFiles.push(val);
|
|
104
|
+
i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (arg.startsWith("--remove-file=")) {
|
|
108
|
+
result.removeFiles.push(arg.slice("--remove-file=".length));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (arg === "-x" || arg === "--command") {
|
|
112
|
+
const val = args[i + 1];
|
|
113
|
+
if (!val)
|
|
114
|
+
throw new Error("Missing value for --command.");
|
|
115
|
+
result.commands.push(val);
|
|
116
|
+
i++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (arg.startsWith("--command=")) {
|
|
120
|
+
result.commands.push(arg.slice("--command=".length));
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (arg === "-u" || arg === "--unset") {
|
|
124
|
+
const val = args[i + 1];
|
|
125
|
+
if (!val)
|
|
126
|
+
throw new Error("Missing value for --unset.");
|
|
127
|
+
result.unset.push(val);
|
|
128
|
+
i++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (arg.startsWith("--unset=")) {
|
|
132
|
+
result.unset.push(arg.slice("--unset=".length));
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (arg.includes("=")) {
|
|
136
|
+
result.pairs.push(arg);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
throw new Error(`Unknown add argument: ${arg}`);
|
|
140
|
+
}
|
|
141
|
+
if (!result.profile) {
|
|
142
|
+
throw new Error("Missing profile name.");
|
|
143
|
+
}
|
|
144
|
+
if (result.type) {
|
|
145
|
+
const normalized = (0, type_1.normalizeType)(result.type);
|
|
146
|
+
if (!normalized) {
|
|
147
|
+
throw new Error(`Unknown type: ${result.type}`);
|
|
148
|
+
}
|
|
149
|
+
result.type = normalized;
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
function parseNumberFlag(value, flag) {
|
|
154
|
+
if (value === null || value === undefined || value === "") {
|
|
155
|
+
throw new Error(`Missing value for ${flag}.`);
|
|
156
|
+
}
|
|
157
|
+
const num = Number(value);
|
|
158
|
+
if (!Number.isFinite(num)) {
|
|
159
|
+
throw new Error(`Invalid number for ${flag}: ${value}`);
|
|
160
|
+
}
|
|
161
|
+
return num;
|
|
162
|
+
}
|
|
163
|
+
function parseStatuslineArgs(args) {
|
|
164
|
+
const result = {
|
|
165
|
+
format: "text",
|
|
166
|
+
cwd: null,
|
|
167
|
+
type: null,
|
|
168
|
+
profileKey: null,
|
|
169
|
+
profileName: null,
|
|
170
|
+
model: null,
|
|
171
|
+
usageToday: null,
|
|
172
|
+
usageTotal: null,
|
|
173
|
+
usageInput: null,
|
|
174
|
+
usageOutput: null,
|
|
175
|
+
syncUsage: false,
|
|
176
|
+
};
|
|
177
|
+
for (let i = 0; i < args.length; i++) {
|
|
178
|
+
const arg = args[i];
|
|
179
|
+
if (arg === "--format") {
|
|
180
|
+
const val = args[i + 1];
|
|
181
|
+
if (!val)
|
|
182
|
+
throw new Error("Missing value for --format.");
|
|
183
|
+
const normalized = val.toLowerCase();
|
|
184
|
+
if (normalized !== "text" && normalized !== "json") {
|
|
185
|
+
throw new Error(`Unknown format: ${val}`);
|
|
186
|
+
}
|
|
187
|
+
result.format = normalized;
|
|
188
|
+
i++;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (arg.startsWith("--format=")) {
|
|
192
|
+
const val = arg.slice("--format=".length);
|
|
193
|
+
const normalized = val.toLowerCase();
|
|
194
|
+
if (normalized !== "text" && normalized !== "json") {
|
|
195
|
+
throw new Error(`Unknown format: ${val}`);
|
|
196
|
+
}
|
|
197
|
+
result.format = normalized;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (arg === "--cwd") {
|
|
201
|
+
const val = args[i + 1];
|
|
202
|
+
if (!val)
|
|
203
|
+
throw new Error("Missing value for --cwd.");
|
|
204
|
+
result.cwd = val;
|
|
205
|
+
i++;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (arg.startsWith("--cwd=")) {
|
|
209
|
+
result.cwd = arg.slice("--cwd=".length);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (arg === "--type") {
|
|
213
|
+
const val = args[i + 1];
|
|
214
|
+
if (!val)
|
|
215
|
+
throw new Error("Missing value for --type.");
|
|
216
|
+
result.type = val;
|
|
217
|
+
i++;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (arg.startsWith("--type=")) {
|
|
221
|
+
result.type = arg.slice("--type=".length);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (arg === "--profile-key") {
|
|
225
|
+
const val = args[i + 1];
|
|
226
|
+
if (!val)
|
|
227
|
+
throw new Error("Missing value for --profile-key.");
|
|
228
|
+
result.profileKey = val;
|
|
229
|
+
i++;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (arg.startsWith("--profile-key=")) {
|
|
233
|
+
result.profileKey = arg.slice("--profile-key=".length);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (arg === "--profile-name") {
|
|
237
|
+
const val = args[i + 1];
|
|
238
|
+
if (!val)
|
|
239
|
+
throw new Error("Missing value for --profile-name.");
|
|
240
|
+
result.profileName = val;
|
|
241
|
+
i++;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (arg.startsWith("--profile-name=")) {
|
|
245
|
+
result.profileName = arg.slice("--profile-name=".length);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (arg === "--model") {
|
|
249
|
+
const val = args[i + 1];
|
|
250
|
+
if (!val)
|
|
251
|
+
throw new Error("Missing value for --model.");
|
|
252
|
+
result.model = val;
|
|
253
|
+
i++;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (arg.startsWith("--model=")) {
|
|
257
|
+
result.model = arg.slice("--model=".length);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (arg === "--usage-today") {
|
|
261
|
+
result.usageToday = parseNumberFlag(args[i + 1], "--usage-today");
|
|
262
|
+
i++;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (arg.startsWith("--usage-today=")) {
|
|
266
|
+
result.usageToday = parseNumberFlag(arg.slice("--usage-today=".length), "--usage-today");
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (arg === "--usage-total") {
|
|
270
|
+
result.usageTotal = parseNumberFlag(args[i + 1], "--usage-total");
|
|
271
|
+
i++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (arg.startsWith("--usage-total=")) {
|
|
275
|
+
result.usageTotal = parseNumberFlag(arg.slice("--usage-total=".length), "--usage-total");
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (arg === "--usage-input") {
|
|
279
|
+
result.usageInput = parseNumberFlag(args[i + 1], "--usage-input");
|
|
280
|
+
i++;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (arg.startsWith("--usage-input=")) {
|
|
284
|
+
result.usageInput = parseNumberFlag(arg.slice("--usage-input=".length), "--usage-input");
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (arg === "--usage-output") {
|
|
288
|
+
result.usageOutput = parseNumberFlag(args[i + 1], "--usage-output");
|
|
289
|
+
i++;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (arg.startsWith("--usage-output=")) {
|
|
293
|
+
result.usageOutput = parseNumberFlag(arg.slice("--usage-output=".length), "--usage-output");
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (arg === "--sync-usage") {
|
|
297
|
+
result.syncUsage = true;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
throw new Error(`Unknown statusline argument: ${arg}`);
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
}
|
package/bin/cli/help.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Help message for codenv CLI
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.printHelp = printHelp;
|
|
7
|
+
function printHelp() {
|
|
8
|
+
const msg = `codenv - switch Claude/Codex env vars
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
codenv list
|
|
12
|
+
codenv ls
|
|
13
|
+
codenv config
|
|
14
|
+
codenv auto
|
|
15
|
+
codenv use
|
|
16
|
+
codenv use <profile>
|
|
17
|
+
codenv use <type> <name>
|
|
18
|
+
codenv show <profile>
|
|
19
|
+
codenv show <type> <name>
|
|
20
|
+
codenv default <profile>
|
|
21
|
+
codenv default <type> <name>
|
|
22
|
+
codenv default --clear
|
|
23
|
+
codenv remove <profile> [<profile> ...]
|
|
24
|
+
codenv remove <type> <name> [<type> <name> ...]
|
|
25
|
+
codenv remove --all
|
|
26
|
+
codenv unset
|
|
27
|
+
codenv add <profile> KEY=VALUE [KEY=VALUE ...]
|
|
28
|
+
codenv add
|
|
29
|
+
codenv launch <codex|claude> [--] [args...]
|
|
30
|
+
codenv init
|
|
31
|
+
codenv statusline [options]
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
-c, --config <path> Path to config JSON
|
|
35
|
+
-h, --help Show help
|
|
36
|
+
|
|
37
|
+
Init options:
|
|
38
|
+
--apply Append shell helper to your shell rc (default)
|
|
39
|
+
--print Print helper snippet to stdout
|
|
40
|
+
--shell <bash|zsh|fish> Explicitly set the target shell
|
|
41
|
+
|
|
42
|
+
Add options:
|
|
43
|
+
-t, --type <codex|claude> Set profile type (alias: cc)
|
|
44
|
+
-n, --note <text> Set profile note
|
|
45
|
+
-r, --remove-file <path> Add a removeFiles entry (repeat)
|
|
46
|
+
-x, --command <cmd> Add a commands entry (repeat)
|
|
47
|
+
-u, --unset <KEY> Add a global unset key (repeat)
|
|
48
|
+
|
|
49
|
+
Statusline options:
|
|
50
|
+
--format <text|json> Output format (default: text)
|
|
51
|
+
--cwd <path> Override working directory
|
|
52
|
+
--type <type> Set profile type
|
|
53
|
+
--profile-key <key> Set profile key
|
|
54
|
+
--profile-name <name> Set profile name
|
|
55
|
+
--model <model> Set model label
|
|
56
|
+
--usage-today <n> Set today's token usage
|
|
57
|
+
--usage-total <n> Set total token usage
|
|
58
|
+
--usage-input <n> Set input token usage
|
|
59
|
+
--usage-output <n> Set output token usage
|
|
60
|
+
--sync-usage Sync usage from sessions before reading
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
codenv init
|
|
64
|
+
codenv use codex primary
|
|
65
|
+
codenv list
|
|
66
|
+
codenv default codex primary
|
|
67
|
+
codenv remove codex primary
|
|
68
|
+
codenv remove codex primary claude default
|
|
69
|
+
codenv remove --all
|
|
70
|
+
codenv launch codex -- --help
|
|
71
|
+
codenv statusline --format json
|
|
72
|
+
CODE_ENV_CONFIG=~/.config/code-env/config.json codenv use claude default
|
|
73
|
+
codenv add --type codex primary OPENAI_BASE_URL=https://api.example.com/v1 OPENAI_API_KEY=YOUR_API_KEY
|
|
74
|
+
codenv add
|
|
75
|
+
`;
|
|
76
|
+
console.log(msg);
|
|
77
|
+
}
|
package/bin/cli/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.printHelp = exports.parseStatuslineArgs = exports.parseAddArgs = exports.parseInitArgs = exports.parseArgs = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* CLI module exports
|
|
6
|
+
*/
|
|
7
|
+
var args_1 = require("./args");
|
|
8
|
+
Object.defineProperty(exports, "parseArgs", { enumerable: true, get: function () { return args_1.parseArgs; } });
|
|
9
|
+
Object.defineProperty(exports, "parseInitArgs", { enumerable: true, get: function () { return args_1.parseInitArgs; } });
|
|
10
|
+
Object.defineProperty(exports, "parseAddArgs", { enumerable: true, get: function () { return args_1.parseAddArgs; } });
|
|
11
|
+
Object.defineProperty(exports, "parseStatuslineArgs", { enumerable: true, get: function () { return args_1.parseStatuslineArgs; } });
|
|
12
|
+
var help_1 = require("./help");
|
|
13
|
+
Object.defineProperty(exports, "printHelp", { enumerable: true, get: function () { return help_1.printHelp; } });
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addConfig = addConfig;
|
|
4
|
+
const match_1 = require("../profile/match");
|
|
5
|
+
const resolve_1 = require("../profile/resolve");
|
|
6
|
+
function addConfig(config, addArgs) {
|
|
7
|
+
if (!config.profiles || typeof config.profiles !== "object") {
|
|
8
|
+
config.profiles = {};
|
|
9
|
+
}
|
|
10
|
+
let targetKey = null;
|
|
11
|
+
let matchedByName = false;
|
|
12
|
+
if (Object.prototype.hasOwnProperty.call(config.profiles, addArgs.profile)) {
|
|
13
|
+
targetKey = addArgs.profile;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const matches = (0, match_1.findProfileKeysByName)(config, addArgs.profile, addArgs.type);
|
|
17
|
+
if (matches.length === 1) {
|
|
18
|
+
targetKey = matches[0];
|
|
19
|
+
matchedByName = true;
|
|
20
|
+
}
|
|
21
|
+
else if (matches.length > 1) {
|
|
22
|
+
const hint = addArgs.type
|
|
23
|
+
? `Use profile key: ${matches.join(", ")}`
|
|
24
|
+
: `Use: codenv add --type <type> ${addArgs.profile} ... (or profile key: ${matches.join(", ")})`;
|
|
25
|
+
throw new Error(`Multiple profiles named "${addArgs.profile}". ${hint}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!targetKey) {
|
|
29
|
+
targetKey = (0, resolve_1.generateProfileKey)(config);
|
|
30
|
+
matchedByName = true;
|
|
31
|
+
}
|
|
32
|
+
if (!config.profiles[targetKey]) {
|
|
33
|
+
config.profiles[targetKey] = {};
|
|
34
|
+
}
|
|
35
|
+
const profile = config.profiles[targetKey];
|
|
36
|
+
if (!profile.env || typeof profile.env !== "object") {
|
|
37
|
+
profile.env = {};
|
|
38
|
+
}
|
|
39
|
+
if (matchedByName) {
|
|
40
|
+
profile.name = addArgs.profile;
|
|
41
|
+
}
|
|
42
|
+
if (addArgs.type) {
|
|
43
|
+
profile.type = addArgs.type;
|
|
44
|
+
}
|
|
45
|
+
for (const pair of addArgs.pairs) {
|
|
46
|
+
const idx = pair.indexOf("=");
|
|
47
|
+
if (idx <= 0)
|
|
48
|
+
throw new Error(`Invalid KEY=VALUE: ${pair}`);
|
|
49
|
+
const key = pair.slice(0, idx);
|
|
50
|
+
const value = pair.slice(idx + 1);
|
|
51
|
+
profile.env[key] = value;
|
|
52
|
+
}
|
|
53
|
+
if (addArgs.note !== null && addArgs.note !== undefined) {
|
|
54
|
+
profile.note = addArgs.note;
|
|
55
|
+
}
|
|
56
|
+
if (addArgs.removeFiles.length > 0) {
|
|
57
|
+
if (!Array.isArray(profile.removeFiles))
|
|
58
|
+
profile.removeFiles = [];
|
|
59
|
+
for (const p of addArgs.removeFiles) {
|
|
60
|
+
if (!profile.removeFiles.includes(p))
|
|
61
|
+
profile.removeFiles.push(p);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (addArgs.commands.length > 0) {
|
|
65
|
+
if (!Array.isArray(profile.commands))
|
|
66
|
+
profile.commands = [];
|
|
67
|
+
for (const cmd of addArgs.commands) {
|
|
68
|
+
if (!profile.commands.includes(cmd))
|
|
69
|
+
profile.commands.push(cmd);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (addArgs.unset.length > 0) {
|
|
73
|
+
if (!Array.isArray(config.unset))
|
|
74
|
+
config.unset = [];
|
|
75
|
+
for (const key of addArgs.unset) {
|
|
76
|
+
if (!config.unset.includes(key))
|
|
77
|
+
config.unset.push(key);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return config;
|
|
81
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.printStatusline = exports.runLaunch = exports.printUnset = exports.printShow = exports.addConfig = exports.printList = exports.printUse = exports.buildUseLines = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Commands module exports
|
|
6
|
+
*/
|
|
7
|
+
var use_1 = require("./use");
|
|
8
|
+
Object.defineProperty(exports, "buildUseLines", { enumerable: true, get: function () { return use_1.buildUseLines; } });
|
|
9
|
+
Object.defineProperty(exports, "printUse", { enumerable: true, get: function () { return use_1.printUse; } });
|
|
10
|
+
var list_1 = require("./list");
|
|
11
|
+
Object.defineProperty(exports, "printList", { enumerable: true, get: function () { return list_1.printList; } });
|
|
12
|
+
var add_1 = require("./add");
|
|
13
|
+
Object.defineProperty(exports, "addConfig", { enumerable: true, get: function () { return add_1.addConfig; } });
|
|
14
|
+
var show_1 = require("./show");
|
|
15
|
+
Object.defineProperty(exports, "printShow", { enumerable: true, get: function () { return show_1.printShow; } });
|
|
16
|
+
var unset_1 = require("./unset");
|
|
17
|
+
Object.defineProperty(exports, "printUnset", { enumerable: true, get: function () { return unset_1.printUnset; } });
|
|
18
|
+
var launch_1 = require("./launch");
|
|
19
|
+
Object.defineProperty(exports, "runLaunch", { enumerable: true, get: function () { return launch_1.runLaunch; } });
|
|
20
|
+
var statusline_1 = require("./statusline");
|
|
21
|
+
Object.defineProperty(exports, "printStatusline", { enumerable: true, get: function () { return statusline_1.printStatusline; } });
|