ccconfig 1.5.0 → 1.5.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
@@ -264,6 +264,46 @@ Do you want to set ANTHROPIC_SMALL_FAST_MODEL? (y/N) [n]:
264
264
  - Use `ccconfig start work` to launch Claude Code with the updated profile
265
265
  - Or use `ccconfig use work` to activate it in current shell
266
266
 
267
+ ### Copy Configuration (fork)
268
+
269
+ If you need to create a new configuration based on an existing one, use the `fork` command:
270
+
271
+ ```bash
272
+ # Fork a configuration interactively
273
+ ccconfig fork work
274
+
275
+ # The tool will:
276
+ # 1. Ask for a new configuration name
277
+ # 2. Copy all environment variables from the source
278
+ # 3. Allow you to update values (press Enter to keep current value)
279
+ ```
280
+
281
+ **Example:**
282
+ ```bash
283
+ $ ccconfig fork work
284
+ Please enter source configuration name to copy from: work
285
+ Please enter new configuration name: work-dev
286
+ Creating configuration 'work-dev' from 'work'...
287
+ Press Enter to keep current value/default, or enter new value to update
288
+
289
+ ANTHROPIC_BASE_URL [https://api.company.com]: https://dev-api.company.com
290
+ ANTHROPIC_AUTH_TOKEN [sk-ant-api...]: <press Enter to keep>
291
+ ANTHROPIC_API_KEY [sk-...]: <press Enter to keep>
292
+ ANTHROPIC_MODEL [claude-sonnet-4-5-20250929]: <press Enter to keep>
293
+
294
+ ✓ Configuration 'work-dev' created from 'work'
295
+ Environment variables:
296
+ ANTHROPIC_BASE_URL=https://dev-api.company.com
297
+ ANTHROPIC_AUTH_TOKEN=sk-ant-api...
298
+ ANTHROPIC_API_KEY=sk-...
299
+ ANTHROPIC_MODEL=claude-sonnet-4-5-20250929
300
+
301
+ Run the following command to activate:
302
+ ccconfig use work-dev
303
+ ```
304
+
305
+ This is useful when you need similar configurations with slight variations (e.g., production vs development endpoints).
306
+
267
307
  ### Shell Completion
268
308
 
269
309
  ccconfig supports shell completion for commands, profile names, and options. This makes it easier to discover and use commands.
package/README_zh.md CHANGED
@@ -264,6 +264,46 @@ Do you want to set ANTHROPIC_SMALL_FAST_MODEL? (y/N) [n]:
264
264
  - 使用 `ccconfig start work` 以更新后的配置启动 Claude Code
265
265
  - 或使用 `ccconfig use work` 在当前 shell 中激活配置
266
266
 
267
+ ### 复制配置 (fork)
268
+
269
+ 如果您需要基于现有配置创建新配置,使用 `fork` 命令:
270
+
271
+ ```bash
272
+ # 交互式复制配置
273
+ ccconfig fork work
274
+
275
+ # 工具会:
276
+ # 1. 要求输入新配置名称
277
+ # 2. 从源配置复制所有环境变量
278
+ # 3. 允许您更新值(按 Enter 保持当前值)
279
+ ```
280
+
281
+ **示例:**
282
+ ```bash
283
+ $ ccconfig fork work
284
+ Please enter source configuration name to copy from: work
285
+ Please enter new configuration name: work-dev
286
+ Creating configuration 'work-dev' from 'work'...
287
+ Press Enter to keep current value/default, or enter new value to update
288
+
289
+ ANTHROPIC_BASE_URL [https://api.company.com]: https://dev-api.company.com
290
+ ANTHROPIC_AUTH_TOKEN [sk-ant-api...]: <按 Enter 保持不变>
291
+ ANTHROPIC_API_KEY [sk-...]: <按 Enter 保持不变>
292
+ ANTHROPIC_MODEL [claude-sonnet-4-5-20250929]: <按 Enter 保持不变>
293
+
294
+ ✓ Configuration 'work-dev' created from 'work'
295
+ Environment variables:
296
+ ANTHROPIC_BASE_URL=https://dev-api.company.com
297
+ ANTHROPIC_AUTH_TOKEN=sk-ant-api...
298
+ ANTHROPIC_API_KEY=sk-...
299
+ ANTHROPIC_MODEL=claude-sonnet-4-5-20250929
300
+
301
+ Run the following command to activate:
302
+ ccconfig use work-dev
303
+ ```
304
+
305
+ 当您需要相似配置但略有差异时很有用(例如生产环境与开发环境端点)。
306
+
267
307
  ### Shell 自动补全
268
308
 
269
309
  ccconfig 支持命令、配置名称和选项的 shell 自动补全,让您更容易发现和使用命令。
package/ccconfig.js CHANGED
@@ -89,7 +89,7 @@ function ensureProfileAvailable(
89
89
 
90
90
  // All supported commands
91
91
  const COMMANDS = [
92
- 'list', 'ls', 'add', 'update', 'use', 'start', 'safe-start', 'remove', 'rm',
92
+ 'list', 'ls', 'add', 'update', 'fork', 'use', 'start', 'safe-start', 'remove', 'rm',
93
93
  'current', 'mode', 'env', 'completion'
94
94
  ];
95
95
 
@@ -180,46 +180,61 @@ class ReadlineHelper {
180
180
 
181
181
  async ask(question, defaultValue = '', options = {}) {
182
182
  this.ensureInterface();
183
- const {brackets = 'parentheses'} = options;
183
+ const {brackets = 'parentheses', prefill = false} = options;
184
184
  const left = brackets === 'square' ? '[' : '(';
185
185
  const right = brackets === 'square' ? ']' : ')';
186
- const suffix = defaultValue ? ` ${left}${defaultValue}${right}` : '';
186
+ // Don't show value in brackets if it will be pre-filled
187
+ const suffix = (defaultValue && !prefill) ? ` ${left}${defaultValue}${right}` : '';
187
188
 
188
189
  return new Promise(resolve => {
189
190
  this.rl.question(`${question}${suffix}: `, answer => {
190
191
  const trimmed = answer.trim();
191
192
  resolve(trimmed || defaultValue);
192
193
  });
194
+ // Pre-fill input with default value if prefill option is enabled
195
+ if (prefill && defaultValue) {
196
+ this.rl.write(defaultValue);
197
+ }
193
198
  });
194
199
  }
195
200
 
196
201
  async askEnvVars(existingEnv = {}) {
202
+ const hasExisting = key => !!existingEnv[key];
203
+
197
204
  const baseUrl = await this.ask(
198
205
  'ANTHROPIC_BASE_URL (press Enter to keep current/default)',
199
- existingEnv.ANTHROPIC_BASE_URL || 'https://api.anthropic.com',
200
- {brackets: existingEnv.ANTHROPIC_BASE_URL ? 'square' : 'parentheses'});
206
+ existingEnv.ANTHROPIC_BASE_URL || 'https://api.anthropic.com', {
207
+ brackets: hasExisting('ANTHROPIC_BASE_URL') ? 'square' : 'parentheses',
208
+ prefill: hasExisting('ANTHROPIC_BASE_URL')
209
+ });
201
210
 
202
211
  const authToken = await this.ask(
203
212
  'ANTHROPIC_AUTH_TOKEN (press Enter to keep current/set empty)',
204
213
  existingEnv.ANTHROPIC_AUTH_TOKEN || '', {
205
- brackets: existingEnv.ANTHROPIC_AUTH_TOKEN ? 'square' : 'parentheses'
214
+ brackets: hasExisting('ANTHROPIC_AUTH_TOKEN') ? 'square' : 'parentheses',
215
+ prefill: hasExisting('ANTHROPIC_AUTH_TOKEN')
206
216
  });
207
217
 
208
218
  const apiKey = await this.ask(
209
219
  'ANTHROPIC_API_KEY (press Enter to keep current/set empty)',
210
- existingEnv.ANTHROPIC_API_KEY || '',
211
- {brackets: existingEnv.ANTHROPIC_API_KEY ? 'square' : 'parentheses'});
220
+ existingEnv.ANTHROPIC_API_KEY || '', {
221
+ brackets: hasExisting('ANTHROPIC_API_KEY') ? 'square' : 'parentheses',
222
+ prefill: hasExisting('ANTHROPIC_API_KEY')
223
+ });
212
224
 
213
225
  const model = await this.ask(
214
226
  'ANTHROPIC_MODEL (press Enter to skip/keep current)',
215
- existingEnv.ANTHROPIC_MODEL || '',
216
- {brackets: existingEnv.ANTHROPIC_MODEL ? 'square' : 'parentheses'});
227
+ existingEnv.ANTHROPIC_MODEL || '', {
228
+ brackets: hasExisting('ANTHROPIC_MODEL') ? 'square' : 'parentheses',
229
+ prefill: hasExisting('ANTHROPIC_MODEL')
230
+ });
217
231
 
218
232
  const smallFastModel = await this.ask(
219
233
  'ANTHROPIC_SMALL_FAST_MODEL (press Enter to skip/keep current)',
220
234
  existingEnv.ANTHROPIC_SMALL_FAST_MODEL || '', {
221
- brackets: existingEnv.ANTHROPIC_SMALL_FAST_MODEL ? 'square' :
222
- 'parentheses'
235
+ brackets:
236
+ hasExisting('ANTHROPIC_SMALL_FAST_MODEL') ? 'square' : 'parentheses',
237
+ prefill: hasExisting('ANTHROPIC_SMALL_FAST_MODEL')
223
238
  });
224
239
 
225
240
  const envVars = {
@@ -819,6 +834,88 @@ async function update(name) {
819
834
  }
820
835
  }
821
836
 
837
+ /**
838
+ * Fork (copy) existing configuration
839
+ */
840
+ async function fork(sourceName) {
841
+ initIfNeeded();
842
+ requireInteractive('forking configurations');
843
+
844
+ const helper = new ReadlineHelper();
845
+
846
+ try {
847
+ if (!sourceName) {
848
+ sourceName = await helper.ask('Please enter source configuration name to copy from');
849
+ }
850
+
851
+ validateConfigName(sourceName);
852
+
853
+ const {profile: sourceProfile} = ensureProfileAvailable(sourceName, {
854
+ allowEmptyEnv: true,
855
+ onEmptyProfiles: () => {
856
+ console.error('Error: Configuration file does not exist');
857
+ },
858
+ onMissingProfile: () => {
859
+ console.error(`Error: Configuration '${sourceName}' does not exist`);
860
+ console.error('');
861
+ console.error('Run ccconfig list to see available configurations');
862
+ }
863
+ });
864
+
865
+ const profiles = loadProfiles() || {profiles: {}};
866
+ const profilesMap = getProfilesMap(profiles);
867
+
868
+ // Ask for new name with validation loop (default to source name)
869
+ let newName = '';
870
+ while (true) {
871
+ newName = await helper.ask(
872
+ `Please enter new configuration name`, sourceName);
873
+
874
+ // Validate name format
875
+ try {
876
+ validateConfigName(newName);
877
+ } catch (error) {
878
+ // validateConfigName calls process.exit, but just in case
879
+ continue;
880
+ }
881
+
882
+ // Check if name already exists
883
+ if (profilesMap[newName]) {
884
+ console.error(`Error: Configuration '${newName}' already exists`);
885
+ console.error('Please choose a different name.');
886
+ console.error('');
887
+ continue;
888
+ }
889
+
890
+ // Name is valid and unique
891
+ break;
892
+ }
893
+
894
+ console.log(`Creating configuration '${newName}' from '${sourceName}'...`);
895
+ console.log('Press Enter to keep current value/default, or enter new value to update');
896
+ console.log('');
897
+
898
+ // Get environment variables (inherited from source)
899
+ const existingEnv = sourceProfile.env || {};
900
+ const envVars = await helper.askEnvVars(existingEnv);
901
+
902
+ // Now save everything at once
903
+ profilesMap[newName] = {env: envVars};
904
+ saveProfiles(profiles);
905
+
906
+ console.log(`✓ Configuration '${newName}' created from '${sourceName}'`);
907
+ console.log('');
908
+ console.log('Environment variables:');
909
+ displayEnvVars(envVars);
910
+ console.log('');
911
+ console.log('Run the following command to activate:');
912
+ console.log(` ccconfig use ${newName}`);
913
+
914
+ } finally {
915
+ helper.close();
916
+ }
917
+ }
918
+
822
919
  /**
823
920
  * Remove configuration
824
921
  */
@@ -1685,7 +1782,7 @@ _ccconfig_completions() {
1685
1782
  ;;
1686
1783
  2)
1687
1784
  case "\${prev}" in
1688
- use|start|safe-start|update|remove|rm)
1785
+ use|start|safe-start|update|fork|remove|rm)
1689
1786
  COMPREPLY=( $(compgen -W "\${profiles}" -- \${cur}) )
1690
1787
  ;;
1691
1788
  mode)
@@ -1694,6 +1791,9 @@ _ccconfig_completions() {
1694
1791
  env)
1695
1792
  COMPREPLY=( $(compgen -W "bash zsh fish sh powershell pwsh dotenv" -- \${cur}) )
1696
1793
  ;;
1794
+ completion)
1795
+ COMPREPLY=( $(compgen -W "bash zsh fish powershell pwsh" -- \${cur}) )
1796
+ ;;
1697
1797
  esac
1698
1798
  ;;
1699
1799
  3)
@@ -1722,6 +1822,7 @@ _ccconfig() {
1722
1822
  'ls:List all configurations'
1723
1823
  'add:Add new configuration'
1724
1824
  'update:Update existing configuration'
1825
+ 'fork:Copy existing configuration and update'
1725
1826
  'use:Switch to specified configuration'
1726
1827
  'start:Start Claude Code (auto-approve mode)'
1727
1828
  'safe-start:Start Claude Code (safe mode, requires confirmation)'
@@ -1746,7 +1847,7 @@ _ccconfig() {
1746
1847
  ;;
1747
1848
  3)
1748
1849
  case $words[2] in
1749
- use|start|safe-start|update|remove|rm)
1850
+ use|start|safe-start|update|fork|remove|rm)
1750
1851
  _describe 'profile' profiles
1751
1852
  ;;
1752
1853
  mode)
@@ -1755,6 +1856,11 @@ _ccconfig() {
1755
1856
  env)
1756
1857
  _describe 'format' formats
1757
1858
  ;;
1859
+ completion)
1860
+ local -a shells
1861
+ shells=('bash' 'zsh' 'fish' 'powershell' 'pwsh')
1862
+ _describe 'shell' shells
1863
+ ;;
1758
1864
  esac
1759
1865
  ;;
1760
1866
  4)
@@ -1782,6 +1888,7 @@ complete -c ccconfig -f -n "__fish_use_subcommand" -a "list" -d "List all config
1782
1888
  complete -c ccconfig -f -n "__fish_use_subcommand" -a "ls" -d "List all configurations"
1783
1889
  complete -c ccconfig -f -n "__fish_use_subcommand" -a "add" -d "Add new configuration"
1784
1890
  complete -c ccconfig -f -n "__fish_use_subcommand" -a "update" -d "Update existing configuration"
1891
+ complete -c ccconfig -f -n "__fish_use_subcommand" -a "fork" -d "Copy existing configuration and update"
1785
1892
  complete -c ccconfig -f -n "__fish_use_subcommand" -a "use" -d "Switch to specified configuration"
1786
1893
  complete -c ccconfig -f -n "__fish_use_subcommand" -a "start" -d "Start Claude Code (auto-approve mode)"
1787
1894
  complete -c ccconfig -f -n "__fish_use_subcommand" -a "safe-start" -d "Start Claude Code (safe mode, requires confirmation)"
@@ -1798,8 +1905,8 @@ function __ccconfig_profiles
1798
1905
  end
1799
1906
  end
1800
1907
 
1801
- # Profile name completion for use, start, safe-start, update, remove
1802
- complete -c ccconfig -f -n "__fish_seen_subcommand_from use start safe-start update remove rm" -a "(__ccconfig_profiles)"
1908
+ # Profile name completion for use, start, safe-start, update, fork, remove
1909
+ complete -c ccconfig -f -n "__fish_seen_subcommand_from use start safe-start update fork remove rm" -a "(__ccconfig_profiles)"
1803
1910
 
1804
1911
  # Mode options
1805
1912
  complete -c ccconfig -f -n "__fish_seen_subcommand_from mode" -a "settings env"
@@ -1807,6 +1914,9 @@ complete -c ccconfig -f -n "__fish_seen_subcommand_from mode" -a "settings env"
1807
1914
  # Env format options
1808
1915
  complete -c ccconfig -f -n "__fish_seen_subcommand_from env" -a "bash zsh fish sh powershell pwsh dotenv"
1809
1916
 
1917
+ # Completion shell options
1918
+ complete -c ccconfig -f -n "__fish_seen_subcommand_from completion" -a "bash zsh fish powershell pwsh"
1919
+
1810
1920
  # Flags for use command
1811
1921
  complete -c ccconfig -f -n "__fish_seen_subcommand_from use" -s p -l permanent -d "Write permanently to shell config"
1812
1922
 
@@ -1841,7 +1951,7 @@ function Get-CconfigProfiles {
1841
1951
  Register-ArgumentCompleter -Native -CommandName ccconfig -ScriptBlock {
1842
1952
  param($wordToComplete, $commandAst, $cursorPosition)
1843
1953
 
1844
- $commands = @('list', 'ls', 'add', 'update', 'use', 'start', 'safe-start', 'remove', 'rm', 'current', 'mode', 'env', 'completion')
1954
+ $commands = @('list', 'ls', 'add', 'update', 'fork', 'use', 'start', 'safe-start', 'remove', 'rm', 'current', 'mode', 'env', 'completion')
1845
1955
  $modes = @('settings', 'env')
1846
1956
  $formats = @('bash', 'zsh', 'fish', 'sh', 'powershell', 'pwsh', 'dotenv')
1847
1957
 
@@ -1863,7 +1973,7 @@ Register-ArgumentCompleter -Native -CommandName ccconfig -ScriptBlock {
1863
1973
  # Second argument completions based on command
1864
1974
  if ($position -eq 2 -or ($position -eq 3 -and $wordToComplete)) {
1865
1975
  switch ($command) {
1866
- { $_ -in 'use', 'start', 'safe-start', 'update', 'remove', 'rm' } {
1976
+ { $_ -in 'use', 'start', 'safe-start', 'update', 'fork', 'remove', 'rm' } {
1867
1977
  Get-CconfigProfiles | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
1868
1978
  [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
1869
1979
  }
@@ -1940,6 +2050,8 @@ function help() {
1940
2050
  ' add [name] Add new configuration (interactive)');
1941
2051
  console.log(
1942
2052
  ' update [name] Update existing configuration (interactive)');
2053
+ console.log(
2054
+ ' fork [source-name] Copy existing configuration and update (interactive)');
1943
2055
  console.log(
1944
2056
  ' use <name> [-p|--permanent] Switch to specified configuration');
1945
2057
  console.log(
@@ -2062,6 +2174,9 @@ async function main() {
2062
2174
  case 'update':
2063
2175
  await update(filteredArgs[1]);
2064
2176
  break;
2177
+ case 'fork':
2178
+ await fork(filteredArgs[1]);
2179
+ break;
2065
2180
  case 'remove':
2066
2181
  case 'rm':
2067
2182
  await remove(filteredArgs[1]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccconfig",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Cross-platform Claude Code configuration switching tool",
5
5
  "main": "ccconfig.js",
6
6
  "bin": {