claude-init 1.0.21 → 1.0.23
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/.claude/settings.json +3 -2
- package/README.md +80 -3
- package/package.json +1 -1
- package/src/cli.js +67 -13
- package/src/fileManager.js +55 -24
- package/src/prompt.js +44 -0
package/.claude/settings.json
CHANGED
package/README.md
CHANGED
|
@@ -5,9 +5,11 @@ A CLI tool to initialize Claude development environment with standardized config
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Smart Setup**: Automatically detects existing files and only creates/updates what's needed
|
|
8
|
-
- **
|
|
8
|
+
- **Interactive Prompts**: Asks before overwriting existing command files or installing agents
|
|
9
|
+
- **Non-destructive by Default**: Preserves existing files unless you choose to overwrite
|
|
9
10
|
- **Progress Feedback**: Clear visual indicators of what's being created, updated, or skipped
|
|
10
11
|
- **Cross-platform**: Works on Windows, macOS, and Linux
|
|
12
|
+
- **CI-friendly**: Non-interactive mode with sensible defaults when not running in a TTY
|
|
11
13
|
|
|
12
14
|
## Requirements
|
|
13
15
|
|
|
@@ -57,12 +59,87 @@ Running `npx claude-init` sets up your current directory with:
|
|
|
57
59
|
- **If missing**: Creates with default Claude settings
|
|
58
60
|
- **If exists**: Skips to preserve your custom settings
|
|
59
61
|
|
|
60
|
-
#### 📋 .claude/commands
|
|
62
|
+
#### 📋 .claude/commands
|
|
61
63
|
- **If missing**: Creates with all template files
|
|
62
|
-
- **If exists**: Only adds missing files
|
|
64
|
+
- **If exists**: Only adds missing files by default
|
|
65
|
+
- **Interactive prompt**: If existing `.md` files are detected, you'll be asked whether to overwrite them with template versions
|
|
66
|
+
- Answer **Yes** to replace your customized `.md` files with fresh templates (while still adding any missing files)
|
|
67
|
+
- Answer **No** (default) to keep your existing `.md` files and only add missing files
|
|
68
|
+
|
|
69
|
+
#### 🤖 .claude/agents
|
|
70
|
+
- **Interactive prompt**: You'll be asked whether to install `.claude/agents` files
|
|
71
|
+
- Answer **Yes** (default) to install/update agent files
|
|
72
|
+
- Answer **No** to skip agents entirely
|
|
73
|
+
|
|
74
|
+
### Interactive Prompts
|
|
75
|
+
|
|
76
|
+
When running interactively (in a terminal), `claude-init` will ask you:
|
|
77
|
+
|
|
78
|
+
1. **Overwrite existing .claude/commands/\*.md?** (only if you have existing `.md` files that match templates)
|
|
79
|
+
- Prompt: `Overwrite existing .claude/commands/*.md with template versions? (y/N):`
|
|
80
|
+
- Default: **No** (preserves your customizations)
|
|
81
|
+
- If **Yes**: Replaces existing `.md` files with templates, still adds missing files
|
|
82
|
+
- If **No**: Keeps your existing files, only adds missing files
|
|
83
|
+
|
|
84
|
+
2. **Install .claude/agents?**
|
|
85
|
+
- Prompt: `Install .claude/agents files? (Y/n):`
|
|
86
|
+
- Default: **Yes** (installs agents)
|
|
87
|
+
- If **Yes**: Installs/updates agents as normal
|
|
88
|
+
- If **No**: Skips agents entirely (shown in summary)
|
|
89
|
+
|
|
90
|
+
**Non-interactive mode (CI/scripts)**: When `process.stdin.isTTY` is false (e.g., in CI pipelines), prompts are skipped and defaults are used:
|
|
91
|
+
- `.claude/commands/*.md` files are **not** overwritten (preserves customizations)
|
|
92
|
+
- `.claude/agents` **is** installed (matches current behavior)
|
|
63
93
|
|
|
64
94
|
## Example Output
|
|
65
95
|
|
|
96
|
+
### Interactive Run (First Time)
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
🚀 Claude Environment Initializer
|
|
100
|
+
Initializing in: /path/to/your/project
|
|
101
|
+
|
|
102
|
+
Install .claude/agents files? (Y/n): y
|
|
103
|
+
|
|
104
|
+
✓ Created new CLAUDE.md
|
|
105
|
+
✓ Created .devcontainer directory
|
|
106
|
+
✓ Created .claude/settings.json
|
|
107
|
+
✓ Created .claude/commands with 5 files
|
|
108
|
+
✓ Created .claude/agents with 3 files
|
|
109
|
+
|
|
110
|
+
📊 Summary:
|
|
111
|
+
Created: 5 items
|
|
112
|
+
Files added: 8
|
|
113
|
+
|
|
114
|
+
✨ Claude environment is ready!
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Interactive Run (With Existing Files)
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
🚀 Claude Environment Initializer
|
|
121
|
+
Initializing in: /path/to/your/project
|
|
122
|
+
|
|
123
|
+
Overwrite existing .claude/commands/*.md with template versions? (y/N): n
|
|
124
|
+
Install .claude/agents files? (Y/n): n
|
|
125
|
+
|
|
126
|
+
- CLAUDE.md already contains template content
|
|
127
|
+
- Directory .devcontainer already exists
|
|
128
|
+
- File .claude/settings.json already exists
|
|
129
|
+
↻ Added 1 missing files to .claude/commands (added: 1)
|
|
130
|
+
- All files in .claude/agents are up to date
|
|
131
|
+
|
|
132
|
+
📊 Summary:
|
|
133
|
+
Updated: 1 items
|
|
134
|
+
Skipped: 3 items (already exist)
|
|
135
|
+
Files added: 1
|
|
136
|
+
- .claude/agents skipped by user
|
|
137
|
+
|
|
138
|
+
✅ Claude environment is already up to date!
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Non-Interactive Run (Original Output)
|
|
142
|
+
|
|
66
143
|
```
|
|
67
144
|
🚀 Claude Environment Initializer
|
|
68
145
|
Initializing in: /path/to/your/project
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
-
import
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import {
|
|
4
6
|
handleClaudeMarkdown,
|
|
5
7
|
handleDirectoryMirror,
|
|
6
8
|
handleSelectiveFileCopy,
|
|
7
9
|
handleSingleFile
|
|
8
10
|
} from './fileManager.js';
|
|
11
|
+
import { promptYesNo } from './prompt.js';
|
|
12
|
+
import { getTemplatesPath, getFilesInDirectory } from './utils.js';
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* Format action result with appropriate colors and icons
|
|
12
16
|
*/
|
|
13
17
|
function formatResult(result) {
|
|
14
|
-
const { action, details, filesAdded } = result;
|
|
15
|
-
|
|
18
|
+
const { action, details, filesAdded, filesOverwritten } = result;
|
|
19
|
+
|
|
16
20
|
switch (action) {
|
|
17
21
|
case 'created':
|
|
18
22
|
return chalk.green(`✓ ${details}`);
|
|
19
23
|
case 'updated':
|
|
20
|
-
|
|
24
|
+
let fileInfo = '';
|
|
25
|
+
if (filesAdded !== undefined || filesOverwritten !== undefined) {
|
|
26
|
+
const parts = [];
|
|
27
|
+
if (filesAdded !== undefined && filesAdded > 0) {
|
|
28
|
+
parts.push(`added: ${filesAdded}`);
|
|
29
|
+
}
|
|
30
|
+
if (filesOverwritten !== undefined && filesOverwritten > 0) {
|
|
31
|
+
parts.push(`overwritten: ${filesOverwritten}`);
|
|
32
|
+
}
|
|
33
|
+
if (parts.length > 0) {
|
|
34
|
+
fileInfo = ` (${parts.join(', ')})`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
21
37
|
return chalk.yellow(`↻ ${details}${fileInfo}`);
|
|
22
38
|
case 'skipped':
|
|
23
39
|
return chalk.gray(`- ${details}`);
|
|
@@ -31,11 +47,38 @@ function formatResult(result) {
|
|
|
31
47
|
*/
|
|
32
48
|
export async function cli() {
|
|
33
49
|
const targetDir = process.cwd();
|
|
34
|
-
|
|
50
|
+
|
|
35
51
|
console.log(chalk.blue.bold('🚀 Claude Environment Initializer'));
|
|
36
52
|
console.log(chalk.gray(`Initializing in: ${targetDir}`));
|
|
37
53
|
console.log();
|
|
38
|
-
|
|
54
|
+
|
|
55
|
+
// Determine if we should prompt for overwriting .claude/commands/*.md
|
|
56
|
+
let overwriteCommandsMd = false;
|
|
57
|
+
const commandsTargetPath = path.join(targetDir, '.claude/commands');
|
|
58
|
+
const commandsTemplatePath = path.join(getTemplatesPath(), '.claude/commands');
|
|
59
|
+
|
|
60
|
+
if (await fs.pathExists(commandsTargetPath) && await fs.pathExists(commandsTemplatePath)) {
|
|
61
|
+
const templateFiles = await getFilesInDirectory(commandsTemplatePath);
|
|
62
|
+
const targetFiles = await getFilesInDirectory(commandsTargetPath);
|
|
63
|
+
|
|
64
|
+
// Check if there are any overlapping .md files
|
|
65
|
+
const mdTemplateFiles = templateFiles.filter(f => f.endsWith('.md'));
|
|
66
|
+
const hasOverlappingMd = mdTemplateFiles.some(f => targetFiles.includes(f));
|
|
67
|
+
|
|
68
|
+
if (hasOverlappingMd) {
|
|
69
|
+
overwriteCommandsMd = await promptYesNo(
|
|
70
|
+
'Overwrite existing .claude/commands/*.md with template versions?',
|
|
71
|
+
false
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Determine if we should install .claude/agents
|
|
77
|
+
const includeAgents = await promptYesNo(
|
|
78
|
+
'Install .claude/agents files?',
|
|
79
|
+
true
|
|
80
|
+
);
|
|
81
|
+
|
|
39
82
|
const tasks = [
|
|
40
83
|
{
|
|
41
84
|
name: 'CLAUDE.md',
|
|
@@ -51,13 +94,20 @@ export async function cli() {
|
|
|
51
94
|
},
|
|
52
95
|
{
|
|
53
96
|
name: '.claude/commands',
|
|
54
|
-
handler: () => handleSelectiveFileCopy(targetDir, '.claude/commands'
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
handler: () => handleSelectiveFileCopy(targetDir, '.claude/agents')
|
|
97
|
+
handler: () => handleSelectiveFileCopy(targetDir, '.claude/commands', {
|
|
98
|
+
overwriteExisting: overwriteCommandsMd,
|
|
99
|
+
filter: (relPath) => relPath.endsWith('.md')
|
|
100
|
+
})
|
|
59
101
|
}
|
|
60
102
|
];
|
|
103
|
+
|
|
104
|
+
// Conditionally add .claude/agents task
|
|
105
|
+
if (includeAgents) {
|
|
106
|
+
tasks.push({
|
|
107
|
+
name: '.claude/agents',
|
|
108
|
+
handler: () => handleSelectiveFileCopy(targetDir, '.claude/agents')
|
|
109
|
+
});
|
|
110
|
+
}
|
|
61
111
|
|
|
62
112
|
let totalActions = { created: 0, updated: 0, skipped: 0, filesAdded: 0 };
|
|
63
113
|
|
|
@@ -99,9 +149,13 @@ export async function cli() {
|
|
|
99
149
|
if (totalActions.filesAdded > 0) {
|
|
100
150
|
console.log(chalk.cyan(` Files added: ${totalActions.filesAdded}`));
|
|
101
151
|
}
|
|
102
|
-
|
|
152
|
+
|
|
153
|
+
if (!includeAgents) {
|
|
154
|
+
console.log(chalk.gray(` - .claude/agents skipped by user`));
|
|
155
|
+
}
|
|
156
|
+
|
|
103
157
|
console.log();
|
|
104
|
-
|
|
158
|
+
|
|
105
159
|
if (totalActions.created > 0 || totalActions.updated > 0) {
|
|
106
160
|
console.log(chalk.green.bold('✨ Claude environment is ready!'));
|
|
107
161
|
} else {
|
package/src/fileManager.js
CHANGED
|
@@ -70,53 +70,84 @@ export async function handleDirectoryMirror(targetDir, relativePath) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* Handle selective file copying (add missing files only)
|
|
73
|
+
* Handle selective file copying (add missing files only, optionally overwrite existing)
|
|
74
|
+
* @param {string} targetDir - The target directory
|
|
75
|
+
* @param {string} relativePath - The relative path to copy
|
|
76
|
+
* @param {Object} options - Options for the copy operation
|
|
77
|
+
* @param {boolean} options.overwriteExisting - Whether to overwrite existing files (default: false)
|
|
78
|
+
* @param {function} options.filter - Function to filter which files to overwrite (default: all files)
|
|
74
79
|
*/
|
|
75
|
-
export async function handleSelectiveFileCopy(targetDir, relativePath) {
|
|
80
|
+
export async function handleSelectiveFileCopy(targetDir, relativePath, options = {}) {
|
|
81
|
+
const { overwriteExisting = false, filter = () => true } = options;
|
|
76
82
|
const targetPath = path.join(targetDir, relativePath);
|
|
77
83
|
const templatePath = path.join(getTemplatesPath(), relativePath);
|
|
78
|
-
|
|
84
|
+
|
|
79
85
|
if (!(await fs.pathExists(targetPath))) {
|
|
80
86
|
// Directory doesn't exist, create it with all template files
|
|
81
87
|
await fs.copy(templatePath, targetPath);
|
|
82
88
|
const files = await getFilesInDirectory(templatePath);
|
|
83
|
-
return {
|
|
84
|
-
action: 'created',
|
|
89
|
+
return {
|
|
90
|
+
action: 'created',
|
|
85
91
|
details: `Created ${relativePath} with ${files.length} files`,
|
|
86
92
|
filesAdded: files.length
|
|
87
93
|
};
|
|
88
94
|
}
|
|
89
|
-
|
|
90
|
-
// Directory exists, check for missing files
|
|
95
|
+
|
|
96
|
+
// Directory exists, check for missing files and optionally overwrite existing
|
|
91
97
|
const templateFiles = await getFilesInDirectory(templatePath);
|
|
92
98
|
const targetFiles = await getFilesInDirectory(targetPath);
|
|
93
99
|
const missingFiles = templateFiles.filter(file => !targetFiles.includes(file));
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
details: `All files in ${relativePath} are up to date`,
|
|
99
|
-
filesAdded: 0
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
100
|
+
|
|
101
|
+
let filesAdded = 0;
|
|
102
|
+
let filesOverwritten = 0;
|
|
103
|
+
|
|
103
104
|
// Copy missing files
|
|
104
|
-
let copiedCount = 0;
|
|
105
105
|
for (const missingFile of missingFiles) {
|
|
106
106
|
const sourcePath = path.join(templatePath, missingFile);
|
|
107
107
|
const destPath = path.join(targetPath, missingFile);
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
// Ensure destination directory exists
|
|
110
110
|
await fs.ensureDir(path.dirname(destPath));
|
|
111
111
|
await fs.copy(sourcePath, destPath);
|
|
112
|
-
|
|
112
|
+
filesAdded++;
|
|
113
113
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
|
|
115
|
+
// Optionally overwrite existing files that match filter
|
|
116
|
+
if (overwriteExisting) {
|
|
117
|
+
const existingFiles = templateFiles.filter(file => targetFiles.includes(file) && filter(file));
|
|
118
|
+
|
|
119
|
+
for (const existingFile of existingFiles) {
|
|
120
|
+
const sourcePath = path.join(templatePath, existingFile);
|
|
121
|
+
const destPath = path.join(targetPath, existingFile);
|
|
122
|
+
|
|
123
|
+
// Ensure destination directory exists
|
|
124
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
125
|
+
await fs.copy(sourcePath, destPath);
|
|
126
|
+
filesOverwritten++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (filesAdded === 0 && filesOverwritten === 0) {
|
|
131
|
+
return {
|
|
132
|
+
action: 'skipped',
|
|
133
|
+
details: `All files in ${relativePath} are up to date`,
|
|
134
|
+
filesAdded: 0
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = {
|
|
139
|
+
action: 'updated',
|
|
140
|
+
filesAdded
|
|
119
141
|
};
|
|
142
|
+
|
|
143
|
+
if (overwriteExisting && filesOverwritten > 0) {
|
|
144
|
+
result.filesOverwritten = filesOverwritten;
|
|
145
|
+
result.details = `Added ${filesAdded} missing files, overwritten ${filesOverwritten} files in ${relativePath}`;
|
|
146
|
+
} else {
|
|
147
|
+
result.details = `Added ${filesAdded} missing files to ${relativePath}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result;
|
|
120
151
|
}
|
|
121
152
|
|
|
122
153
|
/**
|
package/src/prompt.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Prompts the user for a yes/no answer.
|
|
5
|
+
* @param {string} question - The question to ask
|
|
6
|
+
* @param {boolean} defaultAnswer - The default answer if user presses Enter or if non-TTY
|
|
7
|
+
* @returns {Promise<boolean>} - True for yes, false for no
|
|
8
|
+
*/
|
|
9
|
+
export async function promptYesNo(question, defaultAnswer) {
|
|
10
|
+
// If not TTY, return default immediately
|
|
11
|
+
if (!process.stdin.isTTY) {
|
|
12
|
+
return defaultAnswer;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const rl = readline.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const defaultHint = defaultAnswer ? 'Y/n' : 'y/N';
|
|
22
|
+
rl.question(`${question} (${defaultHint}): `, (answer) => {
|
|
23
|
+
rl.close();
|
|
24
|
+
|
|
25
|
+
const normalized = answer.trim().toLowerCase();
|
|
26
|
+
|
|
27
|
+
// Empty answer uses default
|
|
28
|
+
if (normalized === '') {
|
|
29
|
+
resolve(defaultAnswer);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Accept y, yes, n, no
|
|
34
|
+
if (normalized === 'y' || normalized === 'yes') {
|
|
35
|
+
resolve(true);
|
|
36
|
+
} else if (normalized === 'n' || normalized === 'no') {
|
|
37
|
+
resolve(false);
|
|
38
|
+
} else {
|
|
39
|
+
// Invalid input, use default
|
|
40
|
+
resolve(defaultAnswer);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|