claude-config-cli 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.
- package/LICENSE.md +21 -0
- package/README.md +177 -0
- package/cli.js +378 -0
- package/package.json +48 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 KazooTTT
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Claude Config CLI
|
|
2
|
+
|
|
3
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
+
[![License][license-src]][license-href]
|
|
6
|
+
|
|
7
|
+
A CLI tool to manage Claude Code configuration files by scanning existing configs and applying recommended settings.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Scan Mode**: Scan multiple directories to find existing Claude config files and generate a recommended configuration
|
|
12
|
+
- **Apply Mode**: Directly apply a pre-defined recommended configuration
|
|
13
|
+
- **Merge or Overwrite**: Choose to merge with existing configs or overwrite them
|
|
14
|
+
- **Interactive CLI**: User-friendly prompts for all operations
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Using npm
|
|
20
|
+
npm install -g claude-config-cli
|
|
21
|
+
|
|
22
|
+
# Using pnpm
|
|
23
|
+
pnpm add -g claude-config-cli
|
|
24
|
+
|
|
25
|
+
# Using yarn
|
|
26
|
+
yarn global add claude-config-cli
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or use directly with npx (no installation needed):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx claude-config-cli
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### Command: `scan`
|
|
38
|
+
|
|
39
|
+
Scan directories for Claude config files and generate recommended configuration.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Scan current directory
|
|
43
|
+
claude-config scan
|
|
44
|
+
|
|
45
|
+
# Scan specific directories
|
|
46
|
+
claude-config scan ~/personal ~/work
|
|
47
|
+
|
|
48
|
+
# Preview without applying
|
|
49
|
+
claude-config scan --preview
|
|
50
|
+
|
|
51
|
+
# Specify output path
|
|
52
|
+
claude-config scan -o .claude/settings.local.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Command: `apply`
|
|
56
|
+
|
|
57
|
+
Apply the recommended Claude configuration directly.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Apply with interactive prompts
|
|
61
|
+
claude-config apply
|
|
62
|
+
|
|
63
|
+
# Preview the recommended config
|
|
64
|
+
claude-config apply --preview
|
|
65
|
+
|
|
66
|
+
# Specify output path
|
|
67
|
+
claude-config apply -o /custom/path/settings.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Options
|
|
71
|
+
|
|
72
|
+
Both commands support the following options:
|
|
73
|
+
|
|
74
|
+
- `-o, --output <path>`: Specify the output file path (default: `.claude/settings.local.json`)
|
|
75
|
+
- `-p, --preview`: Preview the configuration without applying it
|
|
76
|
+
- `-h, --help`: Display help information
|
|
77
|
+
|
|
78
|
+
### Interactive Prompts
|
|
79
|
+
|
|
80
|
+
When you run `scan` or `apply` without `--preview`, you'll be prompted to choose an action:
|
|
81
|
+
|
|
82
|
+
- **Save to new file**: Create a new config file
|
|
83
|
+
- **Merge with existing config**: Merge the recommended config with any existing config (preserves existing settings)
|
|
84
|
+
- **Overwrite existing config**: Replace any existing config with the recommended one
|
|
85
|
+
- **Cancel**: Cancel the operation
|
|
86
|
+
|
|
87
|
+
## Example Workflow
|
|
88
|
+
|
|
89
|
+
### 1. Scan and Preview
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx claude-config scan ~/personal ~/work --preview
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This will show you the recommended configuration based on all configs found in the specified directories.
|
|
96
|
+
|
|
97
|
+
### 2. Apply Recommended Config
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx claude-config apply
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You'll be prompted to choose how to apply the config. Select your preferred option and the tool will handle the rest.
|
|
104
|
+
|
|
105
|
+
### 3. Scan and Apply in One Step
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npx claude-config scan ~/projects
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
After scanning, choose how you want to apply the generated configuration.
|
|
112
|
+
|
|
113
|
+
## Recommended Configuration
|
|
114
|
+
|
|
115
|
+
The recommended configuration includes common permissions for:
|
|
116
|
+
|
|
117
|
+
- Package managers (pnpm, npm, bun)
|
|
118
|
+
- Build tools (cargo, wrangler)
|
|
119
|
+
- Git operations
|
|
120
|
+
- Development tools (node, python, tsc)
|
|
121
|
+
- MCP tools (context7, ast-grep, ide)
|
|
122
|
+
|
|
123
|
+
This configuration is based on common patterns found across many projects.
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
### Local Testing
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Install dependencies
|
|
131
|
+
pnpm install
|
|
132
|
+
|
|
133
|
+
# Test the CLI
|
|
134
|
+
node cli.js --help
|
|
135
|
+
node cli.js scan --preview
|
|
136
|
+
node cli.js apply --preview
|
|
137
|
+
|
|
138
|
+
# Run tests
|
|
139
|
+
pnpm test
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Publishing to npm
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Build (if you have a build step)
|
|
146
|
+
pnpm run build
|
|
147
|
+
|
|
148
|
+
# Publish
|
|
149
|
+
pnpm publish
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Release
|
|
153
|
+
|
|
154
|
+
This project uses [bumpp](https://github.com/antfu/bumpp) for version management.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Interactive release
|
|
158
|
+
pnpm release
|
|
159
|
+
|
|
160
|
+
# Automatic release (patch/minor/major)
|
|
161
|
+
pnpm release:patch
|
|
162
|
+
pnpm release:minor
|
|
163
|
+
pnpm release:major
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
[MIT](./LICENSE) License
|
|
169
|
+
|
|
170
|
+
<!-- Badges -->
|
|
171
|
+
|
|
172
|
+
[npm-version-src]: https://img.shields.io/npm/v/claude-config-cli?style=flat&colorA=080f12&colorB=1fa669
|
|
173
|
+
[npm-version-href]: https://npmjs.com/package/claude-config-cli
|
|
174
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/claude-config-cli?style=flat&colorA=080f12&colorB=1fa669
|
|
175
|
+
[npm-downloads-href]: https://npmjs.com/package/claude-config-cli
|
|
176
|
+
[license-src]: https://img.shields.io/github/license/kazoottt/claude-config-generate.svg?style=flat&colorA=080f12&colorB=1fa669
|
|
177
|
+
[license-href]: https://github.com/kazoottt/claude-config-generate/blob/main/LICENSE
|
package/cli.js
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
13
|
+
// 预定义的推荐配置
|
|
14
|
+
const RECOMMENDED_CONFIG = {
|
|
15
|
+
permissions: {
|
|
16
|
+
allow: [
|
|
17
|
+
"Bash(pnpm build:*)",
|
|
18
|
+
"WebSearch",
|
|
19
|
+
"Bash(find:*)",
|
|
20
|
+
"Bash(npm run build:*)",
|
|
21
|
+
"Bash(pnpm add:*)",
|
|
22
|
+
"Bash(pnpm typecheck:*)",
|
|
23
|
+
"Bash(pnpm lint:*)",
|
|
24
|
+
"Bash(cargo check:*)",
|
|
25
|
+
"Bash(wrangler d1 execute:*)",
|
|
26
|
+
"Bash(git diff:*)",
|
|
27
|
+
"Bash(xargs:*)",
|
|
28
|
+
"Bash(python:*)",
|
|
29
|
+
"Bash(mv:*)",
|
|
30
|
+
"Bash(npm run lint)",
|
|
31
|
+
"Bash(node:*)",
|
|
32
|
+
"Bash(git branch:*)",
|
|
33
|
+
"mcp__context7__resolvelibraryid",
|
|
34
|
+
"mcp__ide__getDiagnostics",
|
|
35
|
+
"Bash(npm run dev:*)",
|
|
36
|
+
"Bash(bunx:*)",
|
|
37
|
+
"Bash(pnpm run:*)",
|
|
38
|
+
"Bash(npx tsc:*)",
|
|
39
|
+
"mcp__astgrep__find_code",
|
|
40
|
+
"Bash(python3:*)",
|
|
41
|
+
"Bash(chmod:*)"
|
|
42
|
+
],
|
|
43
|
+
deny: [],
|
|
44
|
+
ask: []
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const program = new Command();
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.name('claude-config')
|
|
52
|
+
.description('CLI tool to manage Claude Code configuration files')
|
|
53
|
+
.version('1.0.0');
|
|
54
|
+
|
|
55
|
+
// 扫描命令
|
|
56
|
+
program
|
|
57
|
+
.command('scan')
|
|
58
|
+
.description('Scan directories for Claude config files and generate recommended config')
|
|
59
|
+
.argument('[dirs...]', 'Directories to scan (default: current directory)')
|
|
60
|
+
.option('-o, --output <path>', 'Output file path (default: .claude/settings.local.json)')
|
|
61
|
+
.option('-p, --preview', 'Preview the recommended config without applying')
|
|
62
|
+
.action(async (dirs, options) => {
|
|
63
|
+
const scanDirs = dirs.length > 0 ? dirs : [process.cwd()];
|
|
64
|
+
const outputPath = options.output || path.join(process.cwd(), '.claude/settings.local.json');
|
|
65
|
+
|
|
66
|
+
console.log(chalk.blue('🔍 Scanning directories for Claude config files...'));
|
|
67
|
+
scanDirs.forEach(dir => console.log(chalk.gray(` ${dir}`)));
|
|
68
|
+
|
|
69
|
+
const configFiles = findConfigFiles(scanDirs);
|
|
70
|
+
console.log(chalk.blue(`\n📊 Found ${configFiles.length} config file(s)\n`));
|
|
71
|
+
|
|
72
|
+
if (configFiles.length === 0) {
|
|
73
|
+
console.log(chalk.yellow('⚠️ No config files found. Use the "apply" command to use the recommended config.'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const stats = scanConfigs(configFiles);
|
|
78
|
+
const recommended = generateRecommendedConfig(stats);
|
|
79
|
+
|
|
80
|
+
if (options.preview) {
|
|
81
|
+
console.log(chalk.green('📋 Recommended Configuration:\n'));
|
|
82
|
+
console.log(JSON.stringify(recommended, null, 2));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Ask user what to do
|
|
87
|
+
const { action } = await inquirer.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: 'list',
|
|
90
|
+
name: 'action',
|
|
91
|
+
message: 'What would you like to do?',
|
|
92
|
+
choices: [
|
|
93
|
+
{ name: '💾 Save to new file', value: 'save' },
|
|
94
|
+
{ name: '🔀 Merge with existing config (if exists)', value: 'merge' },
|
|
95
|
+
{ name: '♻️ Overwrite existing config (if exists)', value: 'overwrite' },
|
|
96
|
+
{ name: '❌ Cancel', value: 'cancel' }
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
if (action === 'cancel') {
|
|
102
|
+
console.log(chalk.gray('Operation cancelled.'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await applyConfig(recommended, outputPath, action);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// 应用命令
|
|
110
|
+
program
|
|
111
|
+
.command('apply')
|
|
112
|
+
.description('Apply the recommended Claude config')
|
|
113
|
+
.option('-o, --output <path>', 'Output file path (default: .claude/settings.local.json)')
|
|
114
|
+
.option('-p, --preview', 'Preview the recommended config without applying')
|
|
115
|
+
.action(async (options) => {
|
|
116
|
+
const outputPath = options.output || path.join(process.cwd(), '.claude/settings.local.json');
|
|
117
|
+
|
|
118
|
+
if (options.preview) {
|
|
119
|
+
console.log(chalk.green('📋 Recommended Configuration:\n'));
|
|
120
|
+
console.log(JSON.stringify(RECOMMENDED_CONFIG, null, 2));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(chalk.green('🚀 Applying recommended Claude configuration...\n'));
|
|
125
|
+
|
|
126
|
+
const { action } = await inquirer.prompt([
|
|
127
|
+
{
|
|
128
|
+
type: 'list',
|
|
129
|
+
name: 'action',
|
|
130
|
+
message: 'How would you like to apply the config?',
|
|
131
|
+
choices: [
|
|
132
|
+
{ name: '💾 Save to new file', value: 'save' },
|
|
133
|
+
{ name: '🔀 Merge with existing config (if exists)', value: 'merge' },
|
|
134
|
+
{ name: '♻️ Overwrite existing config (if exists)', value: 'overwrite' },
|
|
135
|
+
{ name: '❌ Cancel', value: 'cancel' }
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
if (action === 'cancel') {
|
|
141
|
+
console.log(chalk.gray('Operation cancelled.'));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await applyConfig(RECOMMENDED_CONFIG, outputPath, action);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 查找配置文件
|
|
150
|
+
*/
|
|
151
|
+
function findConfigFiles(dirs) {
|
|
152
|
+
const CONFIG_PATHS = [
|
|
153
|
+
'.claude/settings.local.json',
|
|
154
|
+
'@.claude/settings.local.json',
|
|
155
|
+
'.claude.json',
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const files = [];
|
|
159
|
+
|
|
160
|
+
for (const dir of dirs) {
|
|
161
|
+
if (!fs.existsSync(dir)) continue;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
165
|
+
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
const fullPath = path.join(dir, entry.name);
|
|
168
|
+
|
|
169
|
+
// 跳过隐藏目录(除了 .claude)
|
|
170
|
+
if (entry.name.startsWith('.') && entry.name !== '.claude') {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 跳过 node_modules
|
|
175
|
+
if (entry.name === 'node_modules' || entry.name === '.git') {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 检查是否是配置文件
|
|
180
|
+
for (const configPath of CONFIG_PATHS) {
|
|
181
|
+
const configFile = path.join(fullPath, configPath);
|
|
182
|
+
if (fs.existsSync(configFile)) {
|
|
183
|
+
files.push(configFile);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 递归查找子目录
|
|
188
|
+
if (entry.isDirectory()) {
|
|
189
|
+
files.push(...findConfigFiles([fullPath]));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (err) {
|
|
193
|
+
// 忽略权限错误
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return files;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 扫描配置文件并统计
|
|
202
|
+
*/
|
|
203
|
+
function scanConfigs(configFiles) {
|
|
204
|
+
const stats = {
|
|
205
|
+
permissions: { allow: new Map(), deny: new Map(), ask: new Map() },
|
|
206
|
+
allowToRun: new Map(),
|
|
207
|
+
allowedCommands: new Map(),
|
|
208
|
+
allowedFileOperations: new Map(),
|
|
209
|
+
excludedPaths: new Map(),
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
for (const filePath of configFiles) {
|
|
213
|
+
try {
|
|
214
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
215
|
+
const config = JSON.parse(content);
|
|
216
|
+
|
|
217
|
+
// 统计 permissions
|
|
218
|
+
if (config.permissions) {
|
|
219
|
+
['allow', 'deny', 'ask'].forEach(type => {
|
|
220
|
+
if (Array.isArray(config.permissions[type])) {
|
|
221
|
+
config.permissions[type].forEach(item => {
|
|
222
|
+
const count = stats.permissions[type].get(item) || 0;
|
|
223
|
+
stats.permissions[type].set(item, count + 1);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 统计其他配置项
|
|
230
|
+
[
|
|
231
|
+
'allowToRun',
|
|
232
|
+
'allowedCommands',
|
|
233
|
+
'allowedFileOperations',
|
|
234
|
+
'excludedPaths'
|
|
235
|
+
].forEach(key => {
|
|
236
|
+
if (Array.isArray(config[key])) {
|
|
237
|
+
config[key].forEach(item => {
|
|
238
|
+
const count = stats[key].get(item) || 0;
|
|
239
|
+
stats[key].set(item, count + 1);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
} catch (err) {
|
|
245
|
+
console.error(chalk.red(`❌ Failed to parse ${filePath}`));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return stats;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 生成推荐配置(过滤特定路径)
|
|
254
|
+
*/
|
|
255
|
+
function generateRecommendedConfig(stats) {
|
|
256
|
+
const isGlobal = (item) => {
|
|
257
|
+
if (typeof item !== 'string') return true;
|
|
258
|
+
return !item.includes('/Users/') && !item.includes('/home/');
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const recommended = {};
|
|
262
|
+
|
|
263
|
+
// 收集 permissions.allow
|
|
264
|
+
const allAllow = Array.from(stats.permissions.allow.entries())
|
|
265
|
+
.filter(([item]) => isGlobal(item))
|
|
266
|
+
.filter(([_, count]) => count >= 2)
|
|
267
|
+
.sort((a, b) => b[1] - a[1])
|
|
268
|
+
.map(([item]) => item);
|
|
269
|
+
|
|
270
|
+
if (allAllow.length > 0) {
|
|
271
|
+
recommended.permissions = {
|
|
272
|
+
allow: allAllow,
|
|
273
|
+
deny: [],
|
|
274
|
+
ask: []
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 收集其他配置项
|
|
279
|
+
[
|
|
280
|
+
'allowToRun',
|
|
281
|
+
'allowedCommands',
|
|
282
|
+
'allowedFileOperations',
|
|
283
|
+
'excludedPaths'
|
|
284
|
+
].forEach(key => {
|
|
285
|
+
const items = Array.from(stats[key].entries())
|
|
286
|
+
.filter(([item]) => isGlobal(item))
|
|
287
|
+
.filter(([_, count]) => count >= 2)
|
|
288
|
+
.sort((a, b) => b[1] - a[1])
|
|
289
|
+
.map(([item]) => item);
|
|
290
|
+
|
|
291
|
+
if (items.length > 0) {
|
|
292
|
+
recommended[key] = items;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return recommended;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 应用配置
|
|
301
|
+
*/
|
|
302
|
+
async function applyConfig(newConfig, outputPath, action) {
|
|
303
|
+
const dir = path.dirname(outputPath);
|
|
304
|
+
|
|
305
|
+
// 创建目录
|
|
306
|
+
if (!fs.existsSync(dir)) {
|
|
307
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let finalConfig = newConfig;
|
|
311
|
+
|
|
312
|
+
// 处理合并模式
|
|
313
|
+
if (action === 'merge' && fs.existsSync(outputPath)) {
|
|
314
|
+
try {
|
|
315
|
+
const existing = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
|
|
316
|
+
finalConfig = mergeConfigs(existing, newConfig);
|
|
317
|
+
console.log(chalk.blue('🔀 Merging with existing configuration...'));
|
|
318
|
+
} catch (err) {
|
|
319
|
+
console.error(chalk.red('❌ Failed to parse existing config, using new config only'));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 写入文件
|
|
324
|
+
fs.writeFileSync(outputPath, JSON.stringify(finalConfig, null, 2), 'utf8');
|
|
325
|
+
|
|
326
|
+
console.log(chalk.green(`✅ Configuration ${action === 'merge' ? 'merged' : 'applied'} successfully!`));
|
|
327
|
+
console.log(chalk.gray(` Location: ${outputPath}\n`));
|
|
328
|
+
|
|
329
|
+
// 显示预览
|
|
330
|
+
console.log(chalk.blue('📋 Applied Configuration:\n'));
|
|
331
|
+
console.log(JSON.stringify(finalConfig, null, 2));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* 合并配置(去重)
|
|
336
|
+
*/
|
|
337
|
+
function mergeConfigs(existing, newConfig) {
|
|
338
|
+
const merged = { ...existing };
|
|
339
|
+
|
|
340
|
+
// 合并 permissions
|
|
341
|
+
if (newConfig.permissions) {
|
|
342
|
+
if (!merged.permissions) {
|
|
343
|
+
merged.permissions = {};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
['allow', 'deny', 'ask'].forEach(type => {
|
|
347
|
+
if (newConfig.permissions[type]) {
|
|
348
|
+
const existingItems = merged.permissions[type] || [];
|
|
349
|
+
const newItems = newConfig.permissions[type];
|
|
350
|
+
|
|
351
|
+
// 合并并去重
|
|
352
|
+
const combined = [...new Set([...existingItems, ...newItems])];
|
|
353
|
+
merged.permissions[type] = combined;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 合并数组配置项
|
|
359
|
+
[
|
|
360
|
+
'allowToRun',
|
|
361
|
+
'allowedCommands',
|
|
362
|
+
'allowedFileOperations',
|
|
363
|
+
'excludedPaths'
|
|
364
|
+
].forEach(key => {
|
|
365
|
+
if (newConfig[key]) {
|
|
366
|
+
const existingItems = merged[key] || [];
|
|
367
|
+
const newItems = newConfig[key];
|
|
368
|
+
|
|
369
|
+
// 合并并去重
|
|
370
|
+
const combined = [...new Set([...existingItems, ...newItems])];
|
|
371
|
+
merged[key] = combined;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return merged;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-config-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"packageManager": "pnpm@10.20.0",
|
|
6
|
+
"description": "CLI tool to manage Claude Code configuration files",
|
|
7
|
+
"author": "KazooTTT <work@kazoottt.top>",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "https://github.com/KazooTTT/claude-config-generate#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/KazooTTT/claude-config-generate.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": "https://github.com/KazooTTT/claude-config-generate/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude",
|
|
17
|
+
"config",
|
|
18
|
+
"cli",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"configuration"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"claude-config": "./cli.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"cli.js"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "node cli.js --help",
|
|
30
|
+
"release": "bumpp",
|
|
31
|
+
"release:next": "bumpp next --yes",
|
|
32
|
+
"release:patch": "bumpp patch --yes",
|
|
33
|
+
"release:minor": "bumpp minor --yes",
|
|
34
|
+
"release:major": "bumpp major --yes",
|
|
35
|
+
"prepack": "npm run test"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"chalk": "^5.3.0",
|
|
39
|
+
"commander": "^12.0.0",
|
|
40
|
+
"inquirer": "^9.2.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"bumpp": "^10.3.2"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|