@tkpdx01/ccc 1.6.5 → 1.6.6
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 +4 -2
- package/index.js +4 -2
- package/package.json +1 -1
- package/src/commands/apply.js +14 -1
- package/src/commands/help.js +1 -0
- package/src/commands/index.js +1 -1
- package/src/commands/new.js +1 -0
- package/src/commands/resettodefault.js +42 -0
- package/src/profiles.js +198 -3
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ ccc delete [profile] # Delete profile
|
|
|
40
40
|
ccc sync [profile] # Sync from template, preserve credentials
|
|
41
41
|
ccc sync --all # Sync all profiles
|
|
42
42
|
ccc apply [profile] # Write profile config to ~/.claude or ~/.codex
|
|
43
|
+
ccc resettodefault # Restore pre-apply ~/.codex and clean OPENAI env exports
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
### WebDAV Cloud Sync
|
|
@@ -69,7 +70,8 @@ Each profile is a directory containing `auth.json` + `config.toml`. Launched via
|
|
|
69
70
|
CODEX_HOME=~/.ccc/codex-profiles/<name>/ codex
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
`ccc <profile>` 启动仍是进程级环境变量,不污染全局。
|
|
74
|
+
`ccc apply`(Codex)会同步 `OPENAI_BASE_URL` / `OPENAI_API_KEY` 到 shell rc,并可用 `ccc resettodefault` 回滚。
|
|
73
75
|
|
|
74
76
|
### Storage
|
|
75
77
|
|
|
@@ -89,9 +91,9 @@ No global environment variables are modified — everything is process-scoped.
|
|
|
89
91
|
- **Dual CLI support** — Claude Code + OpenAI Codex in one tool
|
|
90
92
|
- **Unified index** — All profiles sorted together, launch by number
|
|
91
93
|
- **Apply command** — Push a profile's config to `~/.claude` or `~/.codex`
|
|
94
|
+
- **Reset to default** — Restore pre-apply Codex config and shell env exports
|
|
92
95
|
- **Template sync** — Update from main settings, keep credentials
|
|
93
96
|
- **Cloud sync** — E2E encrypted WebDAV sync across devices
|
|
94
|
-
- **Zero env pollution** — API keys stored in config files, not shell env
|
|
95
97
|
|
|
96
98
|
## Security
|
|
97
99
|
|
package/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
deleteCommand,
|
|
18
18
|
syncCommand,
|
|
19
19
|
applyCommand,
|
|
20
|
+
resetToDefaultCommand,
|
|
20
21
|
webdavCommand,
|
|
21
22
|
helpCommand
|
|
22
23
|
} from './src/commands/index.js';
|
|
@@ -27,7 +28,7 @@ const program = new Command();
|
|
|
27
28
|
program
|
|
28
29
|
.name('ccc')
|
|
29
30
|
.description('Claude Code / Codex Settings Launcher - 管理多个 Claude Code 和 Codex 配置文件')
|
|
30
|
-
.version('1.6.
|
|
31
|
+
.version('1.6.6');
|
|
31
32
|
|
|
32
33
|
// 注册所有命令
|
|
33
34
|
listCommand(program);
|
|
@@ -38,6 +39,7 @@ editCommand(program);
|
|
|
38
39
|
deleteCommand(program);
|
|
39
40
|
syncCommand(program);
|
|
40
41
|
applyCommand(program);
|
|
42
|
+
resetToDefaultCommand(program);
|
|
41
43
|
webdavCommand(program);
|
|
42
44
|
helpCommand(program);
|
|
43
45
|
|
|
@@ -51,7 +53,7 @@ program
|
|
|
51
53
|
|
|
52
54
|
if (profile) {
|
|
53
55
|
// 检查是否是子命令
|
|
54
|
-
if (['list', 'ls', 'use', 'show', 'new', 'edit', 'delete', 'rm', 'sync', 'apply', 'webdav', 'help'].includes(profile)) {
|
|
56
|
+
if (['list', 'ls', 'use', 'show', 'new', 'edit', 'delete', 'rm', 'sync', 'apply', 'resettodefault', 'webdav', 'help'].includes(profile)) {
|
|
55
57
|
return; // 让子命令处理
|
|
56
58
|
}
|
|
57
59
|
|
package/package.json
CHANGED
package/src/commands/apply.js
CHANGED
|
@@ -69,8 +69,21 @@ export function applyCommand(program) {
|
|
|
69
69
|
result = applyClaudeProfile(profileInfo.name);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
const success = profileInfo.type === 'codex'
|
|
73
|
+
? Boolean(result && (result.success ?? result))
|
|
74
|
+
: Boolean(result);
|
|
75
|
+
|
|
76
|
+
if (success) {
|
|
73
77
|
console.log(chalk.green(`\n✓ ${typeLabel} 配置 "${profileInfo.name}" 已应用到 ${targetDir}`));
|
|
78
|
+
|
|
79
|
+
if (profileInfo.type === 'codex' && result?.envSync?.filePath) {
|
|
80
|
+
const home = process.env.HOME || '';
|
|
81
|
+
const rcPathDisplay = home && result.envSync.filePath.startsWith(home)
|
|
82
|
+
? `~${result.envSync.filePath.slice(home.length)}`
|
|
83
|
+
: result.envSync.filePath;
|
|
84
|
+
console.log(chalk.gray(` 已同步 OPENAI_BASE_URL / OPENAI_API_KEY 到 ${rcPathDisplay}`));
|
|
85
|
+
console.log(chalk.gray(` 当前终端可执行: source ${rcPathDisplay}`));
|
|
86
|
+
}
|
|
74
87
|
} else {
|
|
75
88
|
console.log(chalk.red(`\n✗ 应用失败`));
|
|
76
89
|
process.exit(1);
|
package/src/commands/help.js
CHANGED
|
@@ -19,6 +19,7 @@ export function showHelp() {
|
|
|
19
19
|
console.log(chalk.gray(' ccc sync [profile] ') + '从模板同步配置(保留 API 凭证)');
|
|
20
20
|
console.log(chalk.gray(' ccc sync --all ') + '同步所有配置');
|
|
21
21
|
console.log(chalk.gray(' ccc apply [profile] ') + '将配置应用到默认目录(~/.claude 或 ~/.codex)');
|
|
22
|
+
console.log(chalk.gray(' ccc resettodefault ') + '恢复 apply 前的 ~/.codex 配置并移除 OPENAI 环境变量');
|
|
22
23
|
console.log(chalk.gray(' ccc edit [profile] ') + '编辑配置');
|
|
23
24
|
console.log(chalk.gray(' ccc delete, rm [name] ') + '删除配置');
|
|
24
25
|
console.log(chalk.gray(' ccc help ') + '显示此帮助信息');
|
package/src/commands/index.js
CHANGED
|
@@ -6,6 +6,6 @@ export { editCommand } from './edit.js';
|
|
|
6
6
|
export { deleteCommand } from './delete.js';
|
|
7
7
|
export { syncCommand } from './sync.js';
|
|
8
8
|
export { applyCommand } from './apply.js';
|
|
9
|
+
export { resetToDefaultCommand } from './resettodefault.js';
|
|
9
10
|
export { webdavCommand } from './webdav.js';
|
|
10
11
|
export { helpCommand, showHelp } from './help.js';
|
|
11
|
-
|
package/src/commands/new.js
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { resetCodexDefaultProfile } from '../profiles.js';
|
|
4
|
+
|
|
5
|
+
export function resetToDefaultCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('resettodefault')
|
|
8
|
+
.description('恢复 apply 前的 ~/.codex 配置,并移除 OPENAI 相关环境变量')
|
|
9
|
+
.action(async () => {
|
|
10
|
+
const { confirm } = await inquirer.prompt([
|
|
11
|
+
{
|
|
12
|
+
type: 'confirm',
|
|
13
|
+
name: 'confirm',
|
|
14
|
+
message: '恢复 ~/.codex 到 apply 前状态,并清理 OPENAI 环境变量?',
|
|
15
|
+
default: false
|
|
16
|
+
}
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
if (!confirm) {
|
|
20
|
+
console.log(chalk.yellow('已取消'));
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const result = resetCodexDefaultProfile();
|
|
25
|
+
if (!result.success) {
|
|
26
|
+
if (result.reason === 'no_backup') {
|
|
27
|
+
console.log(chalk.yellow('未找到可恢复的备份(请先执行一次 ccc apply <codex-profile>)'));
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
console.log(chalk.red('恢复失败:备份状态文件损坏'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const home = process.env.HOME || '';
|
|
35
|
+
const rcPathDisplay = home && result.shellRcPath.startsWith(home)
|
|
36
|
+
? `~${result.shellRcPath.slice(home.length)}`
|
|
37
|
+
: result.shellRcPath;
|
|
38
|
+
|
|
39
|
+
console.log(chalk.green('✓ 已恢复 ~/.codex 原始配置'));
|
|
40
|
+
console.log(chalk.green(`✓ 已清理/还原 ${rcPathDisplay} 中的 OPENAI 环境变量`));
|
|
41
|
+
});
|
|
42
|
+
}
|
package/src/profiles.js
CHANGED
|
@@ -338,6 +338,10 @@ export function clearDefaultProfile() {
|
|
|
338
338
|
|
|
339
339
|
const OPENAI_DEFAULT_BASE_URL = 'https://api.openai.com/v1';
|
|
340
340
|
const CCC_OPENAI_COMPAT_PROVIDER = 'ccc_openai';
|
|
341
|
+
const CODEX_RESET_DIR = path.join(CODEX_HOME_PATH, '.ccc-reset-default');
|
|
342
|
+
const CODEX_RESET_AUTH_BACKUP = path.join(CODEX_RESET_DIR, 'auth.json.original');
|
|
343
|
+
const CODEX_RESET_CONFIG_BACKUP = path.join(CODEX_RESET_DIR, 'config.toml.original');
|
|
344
|
+
const CODEX_RESET_META_PATH = path.join(CODEX_RESET_DIR, 'meta.json');
|
|
341
345
|
|
|
342
346
|
function normalizeBaseUrl(baseUrl) {
|
|
343
347
|
return (baseUrl || '').trim().replace(/\/+$/, '');
|
|
@@ -362,6 +366,132 @@ function upsertTomlKey(block, key, valueLiteral) {
|
|
|
362
366
|
return `${block.trimEnd()}\n${key} = ${valueLiteral}\n`;
|
|
363
367
|
}
|
|
364
368
|
|
|
369
|
+
function escapeRegExp(value) {
|
|
370
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function escapeShellSingleQuote(value) {
|
|
374
|
+
return String(value ?? '').replace(/'/g, `'\"'\"'`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function getPreferredShellRcPath() {
|
|
378
|
+
const shellPath = (process.env.SHELL || '').toLowerCase();
|
|
379
|
+
if (shellPath.includes('zsh')) {
|
|
380
|
+
return path.join(os.homedir(), '.zshrc');
|
|
381
|
+
}
|
|
382
|
+
if (shellPath.includes('bash')) {
|
|
383
|
+
return path.join(os.homedir(), '.bashrc');
|
|
384
|
+
}
|
|
385
|
+
return path.join(os.homedir(), '.profile');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function stripShellExport(content, key) {
|
|
389
|
+
const pattern = new RegExp(`^\\s*export\\s+${escapeRegExp(key)}=.*(?:\\r?\\n)?`, 'gm');
|
|
390
|
+
return content.replace(pattern, '');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function extractShellExportLine(content, key) {
|
|
394
|
+
const pattern = new RegExp(`^\\s*export\\s+${escapeRegExp(key)}=.*$`, 'm');
|
|
395
|
+
const match = content.match(pattern);
|
|
396
|
+
return match ? match[0] : '';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function upsertShellExport(content, key, rawValue) {
|
|
400
|
+
if (rawValue === undefined || rawValue === null || rawValue === '') {
|
|
401
|
+
return { content, changed: false };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const line = `export ${key}='${escapeShellSingleQuote(rawValue)}'`;
|
|
405
|
+
let nextContent = stripShellExport(content, key);
|
|
406
|
+
if (nextContent && !nextContent.endsWith('\n')) {
|
|
407
|
+
nextContent += '\n';
|
|
408
|
+
}
|
|
409
|
+
nextContent += `${line}\n`;
|
|
410
|
+
return { content: nextContent, changed: nextContent !== content };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function upsertShellExportLine(content, key, line) {
|
|
414
|
+
let nextContent = stripShellExport(content, key);
|
|
415
|
+
if (!line) {
|
|
416
|
+
return { content: nextContent, changed: nextContent !== content };
|
|
417
|
+
}
|
|
418
|
+
if (nextContent && !nextContent.endsWith('\n')) {
|
|
419
|
+
nextContent += '\n';
|
|
420
|
+
}
|
|
421
|
+
nextContent += `${line}\n`;
|
|
422
|
+
return { content: nextContent, changed: nextContent !== content };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function ensureCodexResetBackup(shellRcPath) {
|
|
426
|
+
if (fs.existsSync(CODEX_RESET_META_PATH)) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (!fs.existsSync(CODEX_RESET_DIR)) {
|
|
431
|
+
fs.mkdirSync(CODEX_RESET_DIR, { recursive: true });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const authPath = path.join(CODEX_HOME_PATH, 'auth.json');
|
|
435
|
+
const configPath = path.join(CODEX_HOME_PATH, 'config.toml');
|
|
436
|
+
const authExisted = fs.existsSync(authPath);
|
|
437
|
+
const configExisted = fs.existsSync(configPath);
|
|
438
|
+
|
|
439
|
+
if (authExisted) {
|
|
440
|
+
fs.copyFileSync(authPath, CODEX_RESET_AUTH_BACKUP);
|
|
441
|
+
}
|
|
442
|
+
if (configExisted) {
|
|
443
|
+
fs.copyFileSync(configPath, CODEX_RESET_CONFIG_BACKUP);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const rcContent = fs.existsSync(shellRcPath) ? fs.readFileSync(shellRcPath, 'utf-8') : '';
|
|
447
|
+
const meta = {
|
|
448
|
+
version: 1,
|
|
449
|
+
createdAt: new Date().toISOString(),
|
|
450
|
+
authExisted,
|
|
451
|
+
configExisted,
|
|
452
|
+
shellRcPath,
|
|
453
|
+
originalExports: {
|
|
454
|
+
OPENAI_BASE_URL: extractShellExportLine(rcContent, 'OPENAI_BASE_URL'),
|
|
455
|
+
OPENAI_API_KEY: extractShellExportLine(rcContent, 'OPENAI_API_KEY')
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
fs.writeFileSync(CODEX_RESET_META_PATH, JSON.stringify(meta, null, 2) + '\n');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function syncCodexEnvToShell(baseUrl, apiKey, shellRcPath) {
|
|
463
|
+
const rcPath = shellRcPath || getPreferredShellRcPath();
|
|
464
|
+
const current = fs.existsSync(rcPath) ? fs.readFileSync(rcPath, 'utf-8') : '';
|
|
465
|
+
|
|
466
|
+
let next = current;
|
|
467
|
+
let changed = false;
|
|
468
|
+
|
|
469
|
+
const baseUrlResult = upsertShellExport(next, 'OPENAI_BASE_URL', baseUrl);
|
|
470
|
+
next = baseUrlResult.content;
|
|
471
|
+
changed = changed || baseUrlResult.changed;
|
|
472
|
+
|
|
473
|
+
const apiKeyResult = upsertShellExport(next, 'OPENAI_API_KEY', apiKey);
|
|
474
|
+
next = apiKeyResult.content;
|
|
475
|
+
changed = changed || apiKeyResult.changed;
|
|
476
|
+
|
|
477
|
+
if (changed) {
|
|
478
|
+
fs.writeFileSync(rcPath, next);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return { filePath: rcPath, changed };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function normalizeCodexAuthForApply(auth) {
|
|
485
|
+
if (!auth || typeof auth !== 'object' || Array.isArray(auth)) {
|
|
486
|
+
return auth;
|
|
487
|
+
}
|
|
488
|
+
const apiKey = typeof auth.OPENAI_API_KEY === 'string' ? auth.OPENAI_API_KEY : '';
|
|
489
|
+
if (apiKey) {
|
|
490
|
+
return { auth_mode: 'apikey', OPENAI_API_KEY: apiKey };
|
|
491
|
+
}
|
|
492
|
+
return auth;
|
|
493
|
+
}
|
|
494
|
+
|
|
365
495
|
function ensureCodexOpenAICompatConfig(configToml, baseUrl) {
|
|
366
496
|
if (!isCustomOpenAIBaseUrl(baseUrl)) {
|
|
367
497
|
return configToml;
|
|
@@ -571,6 +701,13 @@ export function applyCodexProfile(name) {
|
|
|
571
701
|
const profile = readCodexProfile(name);
|
|
572
702
|
if (!profile) return false;
|
|
573
703
|
|
|
704
|
+
const shellRcPath = getPreferredShellRcPath();
|
|
705
|
+
const baseUrl = extractBaseUrlFromConfigToml(profile.configToml);
|
|
706
|
+
const apiKey = profile.auth?.OPENAI_API_KEY || '';
|
|
707
|
+
|
|
708
|
+
// 首次 apply 时备份 ~/.codex 与 shell env 现场,供 resettodefault 回滚
|
|
709
|
+
ensureCodexResetBackup(shellRcPath);
|
|
710
|
+
|
|
574
711
|
if (!fs.existsSync(CODEX_HOME_PATH)) {
|
|
575
712
|
fs.mkdirSync(CODEX_HOME_PATH, { recursive: true });
|
|
576
713
|
}
|
|
@@ -578,17 +715,75 @@ export function applyCodexProfile(name) {
|
|
|
578
715
|
// 写入 auth.json
|
|
579
716
|
fs.writeFileSync(
|
|
580
717
|
path.join(CODEX_HOME_PATH, 'auth.json'),
|
|
581
|
-
JSON.stringify(profile.auth, null, 2) + '\n'
|
|
718
|
+
JSON.stringify(normalizeCodexAuthForApply(profile.auth), null, 2) + '\n'
|
|
582
719
|
);
|
|
583
720
|
|
|
584
721
|
// 写入 config.toml(如果有内容)
|
|
585
722
|
if (profile.configToml && profile.configToml.trim()) {
|
|
586
|
-
const baseUrl = extractBaseUrlFromConfigToml(profile.configToml);
|
|
587
723
|
const compatConfig = ensureCodexOpenAICompatConfig(profile.configToml, baseUrl);
|
|
588
724
|
fs.writeFileSync(path.join(CODEX_HOME_PATH, 'config.toml'), compatConfig);
|
|
589
725
|
}
|
|
590
726
|
|
|
591
|
-
|
|
727
|
+
const envSync = syncCodexEnvToShell(baseUrl, apiKey, shellRcPath);
|
|
728
|
+
|
|
729
|
+
return { success: true, envSync };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// 恢复 apply 前的 ~/.codex 配置,并移除/还原相关 OPENAI 环境变量
|
|
733
|
+
export function resetCodexDefaultProfile() {
|
|
734
|
+
if (!fs.existsSync(CODEX_RESET_META_PATH)) {
|
|
735
|
+
return { success: false, reason: 'no_backup' };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
let meta;
|
|
739
|
+
try {
|
|
740
|
+
meta = JSON.parse(fs.readFileSync(CODEX_RESET_META_PATH, 'utf-8'));
|
|
741
|
+
} catch {
|
|
742
|
+
return { success: false, reason: 'invalid_backup' };
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (!fs.existsSync(CODEX_HOME_PATH)) {
|
|
746
|
+
fs.mkdirSync(CODEX_HOME_PATH, { recursive: true });
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const authPath = path.join(CODEX_HOME_PATH, 'auth.json');
|
|
750
|
+
const configPath = path.join(CODEX_HOME_PATH, 'config.toml');
|
|
751
|
+
|
|
752
|
+
if (meta.authExisted && fs.existsSync(CODEX_RESET_AUTH_BACKUP)) {
|
|
753
|
+
fs.copyFileSync(CODEX_RESET_AUTH_BACKUP, authPath);
|
|
754
|
+
} else if (fs.existsSync(authPath)) {
|
|
755
|
+
fs.unlinkSync(authPath);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (meta.configExisted && fs.existsSync(CODEX_RESET_CONFIG_BACKUP)) {
|
|
759
|
+
fs.copyFileSync(CODEX_RESET_CONFIG_BACKUP, configPath);
|
|
760
|
+
} else if (fs.existsSync(configPath)) {
|
|
761
|
+
fs.unlinkSync(configPath);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const shellRcPath = meta.shellRcPath || getPreferredShellRcPath();
|
|
765
|
+
const rcExists = fs.existsSync(shellRcPath);
|
|
766
|
+
const rcBefore = rcExists ? fs.readFileSync(shellRcPath, 'utf-8') : '';
|
|
767
|
+
let rcAfter = rcBefore;
|
|
768
|
+
|
|
769
|
+
const originalBaseUrlLine = meta.originalExports?.OPENAI_BASE_URL || '';
|
|
770
|
+
const originalApiKeyLine = meta.originalExports?.OPENAI_API_KEY || '';
|
|
771
|
+
|
|
772
|
+
rcAfter = upsertShellExportLine(rcAfter, 'OPENAI_BASE_URL', originalBaseUrlLine).content;
|
|
773
|
+
rcAfter = upsertShellExportLine(rcAfter, 'OPENAI_API_KEY', originalApiKeyLine).content;
|
|
774
|
+
|
|
775
|
+
const envChanged = rcAfter !== rcBefore;
|
|
776
|
+
if (envChanged || (!rcExists && (originalBaseUrlLine || originalApiKeyLine))) {
|
|
777
|
+
fs.writeFileSync(shellRcPath, rcAfter);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
fs.rmSync(CODEX_RESET_DIR, { recursive: true, force: true });
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
success: true,
|
|
784
|
+
shellRcPath,
|
|
785
|
+
envChanged
|
|
786
|
+
};
|
|
592
787
|
}
|
|
593
788
|
|
|
594
789
|
// ============================================================
|