@lkangd/cc-env 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.claude/settings.json +6 -0
  2. package/.claude/settings.local.json +3 -0
  3. package/.nvmrc +1 -0
  4. package/dist/cli.js +266 -0
  5. package/dist/commands/debug.js +17 -0
  6. package/dist/commands/init.js +64 -0
  7. package/dist/commands/preset/create.js +61 -0
  8. package/dist/commands/preset/delete.js +25 -0
  9. package/dist/commands/preset/edit.js +15 -0
  10. package/dist/commands/preset/list.js +16 -0
  11. package/dist/commands/preset/show.js +16 -0
  12. package/dist/commands/restore.js +65 -0
  13. package/dist/commands/run.js +80 -0
  14. package/dist/core/errors.js +11 -0
  15. package/dist/core/find-claude.js +64 -0
  16. package/dist/core/format.js +23 -0
  17. package/dist/core/fs.js +12 -0
  18. package/dist/core/gitignore.js +23 -0
  19. package/dist/core/lock.js +25 -0
  20. package/dist/core/logger.js +8 -0
  21. package/dist/core/mask.js +13 -0
  22. package/dist/core/paths.js +32 -0
  23. package/dist/core/process-env.js +4 -0
  24. package/dist/core/schema.js +38 -0
  25. package/dist/core/spawn.js +26 -0
  26. package/dist/flows/init-flow.js +35 -0
  27. package/dist/flows/preset-create-flow.js +80 -0
  28. package/dist/flows/restore-flow.js +75 -0
  29. package/dist/ink/init-app.js +54 -0
  30. package/dist/ink/preset-create-app.js +271 -0
  31. package/dist/ink/preset-delete-app.js +47 -0
  32. package/dist/ink/preset-list-app.js +27 -0
  33. package/dist/ink/preset-show-app.js +27 -0
  34. package/dist/ink/restore-app.js +102 -0
  35. package/dist/ink/run-preset-select-app.js +31 -0
  36. package/dist/ink/summary.js +28 -0
  37. package/dist/services/claude-settings-env-service.js +55 -0
  38. package/dist/services/config-service.js +26 -0
  39. package/dist/services/history-service.js +39 -0
  40. package/dist/services/preset-service.js +61 -0
  41. package/dist/services/project-env-service.js +90 -0
  42. package/dist/services/project-state-service.js +26 -0
  43. package/dist/services/runtime-env-service.js +13 -0
  44. package/dist/services/settings-env-service.js +36 -0
  45. package/dist/services/shell-env-service.js +77 -0
  46. package/docs/product-specs/index.draft.md +106 -0
  47. package/docs/product-specs/index.md +911 -0
  48. package/docs/product-specs/optional.md +42 -0
  49. package/docs/references/claude-code-env.md +224 -0
  50. package/docs/superpowers/plans/2026-04-24-cc-env-init-shell-migration.md +1331 -0
  51. package/docs/superpowers/plans/2026-04-24-cc-env.md +1666 -0
  52. package/docs/superpowers/plans/2026-04-26-preset-create-interactive-refactor.md +1432 -0
  53. package/docs/superpowers/specs/2026-04-24-cc-env-design.md +438 -0
  54. package/docs/superpowers/specs/2026-04-24-cc-env-init-shell-migration-design.md +181 -0
  55. package/docs/superpowers/specs/2026-04-26-preset-create-interactive-refactor-design.md +78 -0
  56. package/package.json +55 -0
  57. package/src/cli.ts +337 -0
  58. package/src/commands/init.ts +139 -0
  59. package/src/commands/preset/create.ts +96 -0
  60. package/src/commands/preset/delete.ts +62 -0
  61. package/src/commands/preset/show.ts +51 -0
  62. package/src/commands/restore.ts +150 -0
  63. package/src/commands/run.ts +158 -0
  64. package/src/core/errors.ts +13 -0
  65. package/src/core/find-claude.ts +70 -0
  66. package/src/core/format.ts +29 -0
  67. package/src/core/fs.ts +18 -0
  68. package/src/core/gitignore.ts +26 -0
  69. package/src/core/logger.ts +11 -0
  70. package/src/core/mask.ts +17 -0
  71. package/src/core/paths.ts +41 -0
  72. package/src/core/process-env.ts +11 -0
  73. package/src/core/schema.ts +55 -0
  74. package/src/core/spawn.ts +36 -0
  75. package/src/flows/init-flow.ts +61 -0
  76. package/src/flows/preset-create-flow.ts +129 -0
  77. package/src/flows/restore-flow.ts +144 -0
  78. package/src/ink/init-app.tsx +110 -0
  79. package/src/ink/preset-create-app.tsx +451 -0
  80. package/src/ink/preset-delete-app.tsx +114 -0
  81. package/src/ink/preset-show-app.tsx +76 -0
  82. package/src/ink/restore-app.tsx +230 -0
  83. package/src/ink/run-preset-select-app.tsx +83 -0
  84. package/src/ink/summary.tsx +91 -0
  85. package/src/services/claude-settings-env-service.ts +72 -0
  86. package/src/services/history-service.ts +48 -0
  87. package/src/services/preset-service.ts +72 -0
  88. package/src/services/project-env-service.ts +128 -0
  89. package/src/services/project-state-service.ts +31 -0
  90. package/src/services/settings-env-service.ts +40 -0
  91. package/src/services/shell-env-service.ts +112 -0
  92. package/src/types.d.ts +19 -0
  93. package/tests/cli/help.test.ts +133 -0
  94. package/tests/cli/init.test.ts +76 -0
  95. package/tests/cli/restore.test.ts +172 -0
  96. package/tests/commands/create.test.ts +263 -0
  97. package/tests/commands/output.test.ts +119 -0
  98. package/tests/commands/run.test.ts +218 -0
  99. package/tests/core/gitignore.test.ts +98 -0
  100. package/tests/core/paths.test.ts +24 -0
  101. package/tests/core/schema-mask.test.ts +182 -0
  102. package/tests/core/spawn.test.ts +47 -0
  103. package/tests/flows/init-flow.test.ts +40 -0
  104. package/tests/flows/preset-create-flow.test.ts +225 -0
  105. package/tests/flows/restore-flow.test.ts +157 -0
  106. package/tests/integration/init-restore.test.ts +406 -0
  107. package/tests/services/claude-shell.test.ts +183 -0
  108. package/tests/services/storage.test.ts +143 -0
  109. package/tsconfig.build.json +9 -0
  110. package/tsconfig.json +22 -0
  111. package/vitest.config.ts +8 -0
@@ -0,0 +1,39 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { atomicWriteFile, ensureParentDir } from '../core/fs.js';
4
+ import { resolveHistoryPath } from '../core/paths.js';
5
+ import { historySchema } from '../core/schema.js';
6
+ function isErrnoException(error) {
7
+ return error instanceof Error && 'code' in error;
8
+ }
9
+ export function createHistoryService(globalRoot) {
10
+ return {
11
+ async write(record) {
12
+ const stored = historySchema.parse(record);
13
+ const filePath = resolveHistoryPath(globalRoot, stored.timestamp);
14
+ await ensureParentDir(filePath);
15
+ await atomicWriteFile(filePath, `${JSON.stringify(stored, null, 2)}\n`);
16
+ return stored;
17
+ },
18
+ async list() {
19
+ const dirPath = dirname(resolveHistoryPath(globalRoot, 'placeholder'));
20
+ try {
21
+ const entries = await readdir(dirPath, { withFileTypes: true });
22
+ const fileNames = entries
23
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
24
+ .map((entry) => entry.name)
25
+ .sort((a, b) => b.localeCompare(a));
26
+ return Promise.all(fileNames.map(async (fileName) => {
27
+ const content = await readFile(join(dirPath, fileName), 'utf8');
28
+ return historySchema.parse(JSON.parse(content));
29
+ }));
30
+ }
31
+ catch (error) {
32
+ if (isErrnoException(error) && error.code === 'ENOENT') {
33
+ return [];
34
+ }
35
+ throw error;
36
+ }
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,61 @@
1
+ import { readdir, readFile, rm } from 'node:fs/promises';
2
+ import { dirname } from 'node:path';
3
+ import { CliError } from '../core/errors.js';
4
+ import { atomicWriteFile } from '../core/fs.js';
5
+ import { resolvePresetPath } from '../core/paths.js';
6
+ import { presetSchema } from '../core/schema.js';
7
+ export function createPresetService(globalRoot) {
8
+ function getPath(name) {
9
+ return resolvePresetPath(globalRoot, name);
10
+ }
11
+ return {
12
+ getPath,
13
+ async write(preset) {
14
+ const parsed = presetSchema.parse(preset);
15
+ const filePath = getPath(parsed.name);
16
+ await atomicWriteFile(filePath, `${JSON.stringify(parsed, null, 2)}\n`);
17
+ return { ...parsed, filePath };
18
+ },
19
+ async read(name) {
20
+ const filePath = getPath(name);
21
+ try {
22
+ const content = await readFile(filePath, 'utf8');
23
+ const preset = presetSchema.parse(JSON.parse(content));
24
+ return { ...preset, filePath };
25
+ }
26
+ catch (error) {
27
+ if (error.code === 'ENOENT') {
28
+ throw new CliError(`Preset not found: ${name}`);
29
+ }
30
+ throw error;
31
+ }
32
+ },
33
+ async listNames() {
34
+ const dirPath = dirname(getPath('placeholder'));
35
+ try {
36
+ const entries = await readdir(dirPath, { withFileTypes: true });
37
+ return entries
38
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
39
+ .map((entry) => entry.name.slice(0, -'.json'.length))
40
+ .sort();
41
+ }
42
+ catch (error) {
43
+ if (error.code === 'ENOENT') {
44
+ return [];
45
+ }
46
+ throw error;
47
+ }
48
+ },
49
+ async remove(name) {
50
+ const filePath = getPath(name);
51
+ try {
52
+ await rm(filePath);
53
+ }
54
+ catch (error) {
55
+ if (error.code !== 'ENOENT') {
56
+ throw error;
57
+ }
58
+ }
59
+ },
60
+ };
61
+ }
@@ -0,0 +1,90 @@
1
+ import { access, mkdir, readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { parse, stringify } from 'yaml';
4
+ import { atomicWriteFile } from '../core/fs.js';
5
+ import { CliError } from '../core/errors.js';
6
+ import { envMapSchema } from '../core/schema.js';
7
+ function parseEnvelope(value) {
8
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
9
+ const obj = value;
10
+ if (obj.env && typeof obj.env === 'object' && !Array.isArray(obj.env)) {
11
+ return {
12
+ env: envMapSchema.parse(obj.env),
13
+ name: typeof obj.name === 'string' ? obj.name : undefined,
14
+ createdAt: typeof obj.createdAt === 'string' ? obj.createdAt : undefined,
15
+ updatedAt: typeof obj.updatedAt === 'string' ? obj.updatedAt : undefined,
16
+ };
17
+ }
18
+ }
19
+ return { env: envMapSchema.parse(value) };
20
+ }
21
+ export function createProjectEnvService({ cwd }) {
22
+ const envDir = join(cwd, '.cc-env');
23
+ const jsonPath = join(envDir, 'env.json');
24
+ const yamlPath = join(envDir, 'env.yaml');
25
+ async function exists(filePath) {
26
+ try {
27
+ await access(filePath);
28
+ return true;
29
+ }
30
+ catch (error) {
31
+ if (error.code === 'ENOENT') {
32
+ return false;
33
+ }
34
+ throw error;
35
+ }
36
+ }
37
+ async function resolveMode() {
38
+ const [jsonExists, yamlExists] = await Promise.all([exists(jsonPath), exists(yamlPath)]);
39
+ if (jsonExists && yamlExists) {
40
+ throw new CliError('Project env conflict: env.json and env.yaml both exist');
41
+ }
42
+ if (yamlExists) {
43
+ return 'yaml';
44
+ }
45
+ if (jsonExists) {
46
+ return 'json';
47
+ }
48
+ return 'missing';
49
+ }
50
+ async function readRaw() {
51
+ const mode = await resolveMode();
52
+ if (mode === 'missing') {
53
+ return { env: envMapSchema.parse({}) };
54
+ }
55
+ const filePath = mode === 'yaml' ? yamlPath : jsonPath;
56
+ const content = await readFile(filePath, 'utf8');
57
+ const value = mode === 'yaml' ? parse(content) : JSON.parse(content);
58
+ return parseEnvelope(value ?? {});
59
+ }
60
+ return {
61
+ async read() {
62
+ const { env } = await readRaw();
63
+ return env;
64
+ },
65
+ async readWithMeta() {
66
+ return readRaw();
67
+ },
68
+ async write(env, meta) {
69
+ const parsedEnv = envMapSchema.parse(env);
70
+ await mkdir(envDir, { recursive: true });
71
+ const mode = await resolveMode();
72
+ if (meta?.name !== undefined) {
73
+ const envelope = { name: meta.name, env: parsedEnv };
74
+ if (meta.createdAt !== undefined)
75
+ envelope.createdAt = meta.createdAt;
76
+ if (meta.updatedAt !== undefined)
77
+ envelope.updatedAt = meta.updatedAt;
78
+ const content = `${JSON.stringify(envelope, null, 2)}\n`;
79
+ await atomicWriteFile(jsonPath, content);
80
+ return parsedEnv;
81
+ }
82
+ const filePath = mode === 'yaml' ? yamlPath : jsonPath;
83
+ const content = mode === 'yaml'
84
+ ? stringify(parsedEnv)
85
+ : `${JSON.stringify(parsedEnv, null, 2)}\n`;
86
+ await atomicWriteFile(filePath, content);
87
+ return parsedEnv;
88
+ },
89
+ };
90
+ }
@@ -0,0 +1,26 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { atomicWriteFile } from '../core/fs.js';
4
+ export function createProjectStateService(globalRoot) {
5
+ const filePath = join(globalRoot, 'project-state.json');
6
+ async function readAll() {
7
+ try {
8
+ const content = await readFile(filePath, 'utf8');
9
+ return JSON.parse(content);
10
+ }
11
+ catch {
12
+ return {};
13
+ }
14
+ }
15
+ return {
16
+ async getLastPreset(cwd) {
17
+ const state = await readAll();
18
+ return state[cwd];
19
+ },
20
+ async saveLastPreset(cwd, ref) {
21
+ const state = await readAll();
22
+ state[cwd] = ref;
23
+ await atomicWriteFile(filePath, `${JSON.stringify(state, null, 2)}\n`);
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,13 @@
1
+ import { envMapSchema } from '../core/schema.js';
2
+ export function createRuntimeEnvService() {
3
+ return {
4
+ merge({ processEnv, settingsEnv, projectEnv, presetEnv, }) {
5
+ return envMapSchema.parse({
6
+ ...processEnv,
7
+ ...settingsEnv,
8
+ ...projectEnv,
9
+ ...presetEnv,
10
+ });
11
+ },
12
+ };
13
+ }
@@ -0,0 +1,36 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { atomicWriteFile } from '../core/fs.js';
3
+ import { envMapSchema } from '../core/schema.js';
4
+ export function createSettingsEnvService({ settingsPath }) {
5
+ return {
6
+ async read() {
7
+ try {
8
+ const content = await readFile(settingsPath, 'utf8');
9
+ const json = JSON.parse(content);
10
+ return envMapSchema.parse(json.env ?? {});
11
+ }
12
+ catch (error) {
13
+ if (error.code === 'ENOENT') {
14
+ return envMapSchema.parse({});
15
+ }
16
+ throw error;
17
+ }
18
+ },
19
+ async write(env) {
20
+ const parsedEnv = envMapSchema.parse(env);
21
+ let json = {};
22
+ try {
23
+ const content = await readFile(settingsPath, 'utf8');
24
+ json = JSON.parse(content);
25
+ }
26
+ catch (error) {
27
+ if (error.code !== 'ENOENT') {
28
+ throw error;
29
+ }
30
+ }
31
+ json.env = parsedEnv;
32
+ await atomicWriteFile(settingsPath, `${JSON.stringify(json, null, 2)}\n`);
33
+ return parsedEnv;
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,77 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { atomicWriteFile } from '../core/fs.js';
3
+ import { resolveShellConfigPaths } from '../core/paths.js';
4
+ import { envMapSchema } from '../core/schema.js';
5
+ const startMarker = '# >>> cc-env >>>';
6
+ const endMarker = '# <<< cc-env <<<';
7
+ function parseManagedEnv(content) {
8
+ const match = content.match(/# >>> cc-env >>>[\s\S]*?# <<< cc-env <<</);
9
+ if (!match) {
10
+ return envMapSchema.parse({});
11
+ }
12
+ const lines = match[0]
13
+ .split('\n')
14
+ .slice(1, -1)
15
+ .filter(Boolean);
16
+ return envMapSchema.parse(Object.fromEntries(lines.map((line) => {
17
+ if (line.startsWith('set -gx ')) {
18
+ const [, key, value] = line.match(/^set -gx ([A-Z0-9_]+) "(.*)"$/) ?? [];
19
+ return [key, value];
20
+ }
21
+ const [, key, value] = line.match(/^export ([A-Z0-9_]+)="(.*)"$/) ?? [];
22
+ return [key, value];
23
+ })));
24
+ }
25
+ function renderBlock(shell, env) {
26
+ const lines = Object.entries(env)
27
+ .sort(([left], [right]) => left.localeCompare(right))
28
+ .map(([key, value]) => shell === 'fish' ? `set -gx ${key} "${value}"` : `export ${key}="${value}"`);
29
+ return [startMarker, ...lines, endMarker, ''].join('\n');
30
+ }
31
+ function replaceManagedBlock(content, block) {
32
+ const pattern = /# >>> cc-env >>>[\s\S]*?# <<< cc-env <<<\n?/;
33
+ if (pattern.test(content)) {
34
+ const result = content.replace(pattern, block);
35
+ if (block === '') {
36
+ return result.replace(/\n{3,}/g, '\n\n').replace(/\n{2,}$/, '\n');
37
+ }
38
+ return result;
39
+ }
40
+ return content.length === 0 ? block : `${content.replace(/\n?$/, '\n')}\n${block}`;
41
+ }
42
+ export function createShellEnvService({ homeDir } = {}) {
43
+ const paths = resolveShellConfigPaths(homeDir);
44
+ async function readContent(path) {
45
+ try {
46
+ return await readFile(path, 'utf8');
47
+ }
48
+ catch (error) {
49
+ if (error.code === 'ENOENT') {
50
+ return '';
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+ return {
56
+ async write(env) {
57
+ return Promise.all(Object.entries(paths).map(async ([shell, filePath]) => {
58
+ const content = await readContent(filePath);
59
+ const mergedEnv = envMapSchema.parse({
60
+ ...parseManagedEnv(content),
61
+ ...env,
62
+ });
63
+ await atomicWriteFile(filePath, replaceManagedBlock(content, renderBlock(shell, mergedEnv)));
64
+ return { shell, filePath, env: mergedEnv };
65
+ }));
66
+ },
67
+ async removeKeys(shellWrites, keys) {
68
+ await Promise.all(shellWrites.map(async ({ shell, filePath }) => {
69
+ const content = await readContent(filePath);
70
+ const current = parseManagedEnv(content);
71
+ const next = envMapSchema.parse(Object.fromEntries(Object.entries(current).filter(([key]) => !keys.includes(key))));
72
+ const block = Object.keys(next).length === 0 ? '' : renderBlock(shell, next);
73
+ await atomicWriteFile(filePath, replaceManagedBlock(content, block));
74
+ }));
75
+ },
76
+ };
77
+ }
@@ -0,0 +1,106 @@
1
+ # 从零开始构建一个基于 nodejs 的命令行工具
2
+
3
+ ## 背景
4
+
5
+ claude code cli 在启动的时候,会读取环境变量中需要的配置,以作为启动参数,比如模型的基础请求地址:ANTHROPIC_BASE_URL。这些配置一般定义在 ~/.claude/settings.json 中的 env 字段。
6
+ 现在有一个痛点,比如我有多家模型提供商,如果将配置写在 ~/.claude/settings.json 中,那么只能同时使用一家模型提供商,因此需要提供另外一种方式,让 claude code 在启动的时候可以指定提供商或者其他所有 claude code 支持的环境变量。
7
+
8
+ ## 功能描述
9
+
10
+ ### 使用方式
11
+
12
+ 在终端命令行下,提供一种如下的调用方式来启动 claude code:
13
+
14
+ ```bash
15
+ cc-env --preset=some-preset claude
16
+ ```
17
+
18
+ 运行后 cc-env 会将 some-preset(提前设置过的) 对应的一组预置的环境变量(claude code 支持),让 claude code 读到运行时配置。
19
+
20
+ ### 初始化
21
+
22
+ 使用 cc-env 进行初始化,主要做的是将 ~/.claude/settings.json 中 env 的字段,移动到全局环境变量中,调用后展示 env 中已经定义的环境变量,默认勾选中以下字段:
23
+
24
+ - ANTHROPIC_AUTH_TOKEN
25
+ - ANTHROPIC_BASE_URL
26
+ - ANTHROPIC_DEFAULT_HAIKU_MODEL
27
+ - ANTHROPIC_DEFAULT_OPUS_MODEL
28
+ - ANTHROPIC_DEFAULT_SONNET_MODEL
29
+ - ANTHROPIC_REASONING_MODEL
30
+
31
+ ```bash
32
+ cc-env --init // or cc-env -i
33
+ ```
34
+
35
+ 初始化移动过的记录需要保存在 ~/.cc-env 文件夹中,每一次初始化都要记录,以便恢复的时候,可以让用户选择。
36
+
37
+ 初始化可以重复执行,如果有字段覆盖,需要提示用户确认,一定要用户输入 Y 确认。
38
+
39
+ ### 恢复
40
+
41
+ 将初始化的移动的环境变量移动回 ~/.claude/settings.json 中 env 内。
42
+
43
+ 通过文件夹的方式展示每一次初始化记录和对应的字段给用户查看,左边栏是时间(从上到下,倒叙),右边栏是涉及变动的字段。用户选中确认后,按 Y 确认回写。
44
+
45
+ ### 创建预设
46
+
47
+ 参数你根据最合适的方式定义。
48
+
49
+ 预设默认放置在 ~/.cc-env 文件夹中,但是可以指定到当前文件夹下,如果是当前文件夹下,则自动创建 .cc-env 目录,并存放为 env.json,重复存放覆盖。
50
+
51
+ #### 展示预设列表
52
+
53
+ 通过文件夹的方式展示所有预设,左边栏是时间(从上到下,倒叙),右边栏是对应的环境变量字段。
54
+
55
+ #### 文件导入
56
+
57
+ 支持指定 claude code settings.json 文件路径,但是只会读取里面的 env 字段。
58
+
59
+ 支持 yaml 格式的文件,支持 json。
60
+
61
+ 限制:只支持 value 字段为非嵌套对象的输入,自动忽略不合法的值
62
+
63
+ #### 交互式导入
64
+
65
+ 支持直接贴多行的 yaml 的输入和 json 输入。
66
+
67
+ 限制:只支持 value 字段为非嵌套对象的输入,自动忽略不合法的值
68
+
69
+ #### 勾选时设置
70
+
71
+ 基于 claude code 官方支持 env,让用户勾选需要设置的环境变量值,确认依次录入具体的值,默认勾选中以下字段:
72
+
73
+ - ANTHROPIC_AUTH_TOKEN
74
+ - ANTHROPIC_BASE_URL
75
+ - ANTHROPIC_DEFAULT_HAIKU_MODEL
76
+ - ANTHROPIC_DEFAULT_OPUS_MODEL
77
+ - ANTHROPIC_DEFAULT_SONNET_MODEL
78
+ - ANTHROPIC_REASONING_MODEL
79
+
80
+ 限制:只支持 value 字段为非嵌套对象的输入,自动忽略不合法的值
81
+
82
+ #### 删除预设
83
+
84
+ 参数你根据最合适的方式定义。
85
+
86
+ 展示预设列表,选中确认后,输入 Y 删除
87
+
88
+ #### 编辑预设
89
+
90
+ 参数你根据最合适的方式定义。
91
+
92
+ 交互方式你来定。
93
+
94
+ #### 读取目录下的预设
95
+
96
+ cc-env 在启动时候,检查当前目录下 .cc-env/env.json 或者 .cc-env/env.yaml,如果存在,则当作较高优先级的环境变量使用。
97
+
98
+ ## 要求
99
+
100
+ ### 技术栈
101
+
102
+ 基于 nodejs 为 base,选用最佳的 cli 实现技术栈
103
+
104
+ ## 引用
105
+
106
+ ### [claude code 官方支持 env](../references/claude-code-env.md)