claude-init 1.0.21 → 1.0.24

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.
@@ -6,8 +6,11 @@
6
6
  "deny": [
7
7
  "Read(**/.env*)",
8
8
  "Edit(**/.env*)",
9
- "Write(**/.env*)",
10
- "Bash(**:.env*)"
9
+ "Write(**/.env*)"
10
+ ],
11
+ "defaultMode": "acceptEdits",
12
+ "additionalDirectories": [
13
+ "/tmp"
11
14
  ]
12
15
  },
13
16
  "statusLine": {
@@ -11,7 +11,6 @@
11
11
  "Bash(git add:*)"
12
12
  ],
13
13
  "deny": [],
14
- "ask": [],
15
14
  "additionalDirectories": [
16
15
  "/tmp"
17
16
  ]
package/CLAUDE.md CHANGED
@@ -1,4 +1 @@
1
- + During you interaction with the user, if you find anything reusable in this project (e.g. version of a library, model name), especially about a fix to a mistake you made or a correction you received, you should take note in the `Lessons` section in the `CLAUDE.md` file so you will not make the same mistake again.
2
- + You should also use the `CLAUDE.md` file as a scratchpad to organize your thoughts. Especially when you receive a new task, you should first review the content of the scratchpad, clear old different task but keep lessons learned, then explain the task, and plan the steps you need to take to complete the task. You can use todo markers to indicate the progress, e.g.
3
- [X] Task 1
4
- [ ] Task 2
1
+ + During you interaction with the user, if you find anything reusable in this project (e.g. version of a library, model name), especially about a fix to a mistake you made or a correction you received, you should take note in the `Lessons` section in the `CLAUDE.md` file so you will not make the same mistake again.
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
- - **Non-destructive**: Never overwrites existing files, only adds missing content
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
 
@@ -55,14 +57,100 @@ Running `npx claude-init` sets up your current directory with:
55
57
 
56
58
  #### ⚙️ .claude/settings.json
57
59
  - **If missing**: Creates with default Claude settings
58
- - **If exists**: Skips to preserve your custom settings
60
+ - **If exists**: Prompts whether to overwrite with template version
61
+ - **Interactive prompt**: If the file exists, you'll be asked whether to overwrite it
62
+ - Answer **Yes** to replace with fresh template
63
+ - Answer **No** (default) to preserve your custom settings
59
64
 
60
- #### 📋 .claude/commands & .claude/agents
65
+ #### 📋 .claude/commands
61
66
  - **If missing**: Creates with all template files
62
- - **If exists**: Only adds missing files, preserves existing ones
67
+ - **If exists**: Only adds missing files by default
68
+ - **Interactive prompt**: If existing `.md` files are detected, you'll be asked whether to overwrite them with template versions
69
+ - Answer **Yes** to replace your customized `.md` files with fresh templates (while still adding any missing files)
70
+ - Answer **No** (default) to keep your existing `.md` files and only add missing files
71
+
72
+ #### 🤖 .claude/agents
73
+ - **Interactive prompt**: You'll be asked whether to install `.claude/agents` files
74
+ - Answer **Yes** (default) to install/update agent files
75
+ - Answer **No** to skip agents entirely
76
+
77
+ ### Interactive Prompts
78
+
79
+ When running interactively (in a terminal), `claude-init` will ask you:
80
+
81
+ 1. **Overwrite existing .claude/settings.json?** (only if the file already exists)
82
+ - Prompt: `Overwrite existing .claude/settings.json with template version? (y/N):`
83
+ - Default: **No** (preserves your custom settings)
84
+ - If **Yes**: Replaces your settings file with fresh template
85
+ - If **No**: Keeps your existing settings file
86
+
87
+ 2. **Overwrite existing .claude/commands/\*.md?** (only if you have existing `.md` files that match templates)
88
+ - Prompt: `Overwrite existing .claude/commands/*.md with template versions? (y/N):`
89
+ - Default: **No** (preserves your customizations)
90
+ - If **Yes**: Replaces existing `.md` files with templates, still adds missing files
91
+ - If **No**: Keeps your existing files, only adds missing files
92
+
93
+ 3. **Install .claude/agents?**
94
+ - Prompt: `Install .claude/agents files? (Y/n):`
95
+ - Default: **Yes** (installs agents)
96
+ - If **Yes**: Installs/updates agents as normal
97
+ - If **No**: Skips agents entirely (shown in summary)
98
+
99
+ **Non-interactive mode (CI/scripts)**: When `process.stdin.isTTY` is false (e.g., in CI pipelines), prompts are skipped and defaults are used:
100
+ - `.claude/settings.json` is **not** overwritten (preserves custom settings)
101
+ - `.claude/commands/*.md` files are **not** overwritten (preserves customizations)
102
+ - `.claude/agents` **is** installed (matches current behavior)
63
103
 
64
104
  ## Example Output
65
105
 
106
+ ### Interactive Run (First Time)
107
+
108
+ ```
109
+ 🚀 Claude Environment Initializer
110
+ Initializing in: /path/to/your/project
111
+
112
+ Install .claude/agents files? (Y/n): y
113
+
114
+ ✓ Created new CLAUDE.md
115
+ ✓ Created .devcontainer directory
116
+ ✓ Created .claude/settings.json
117
+ ✓ Created .claude/commands with 5 files
118
+ ✓ Created .claude/agents with 3 files
119
+
120
+ 📊 Summary:
121
+ Created: 5 items
122
+ Files added: 8
123
+
124
+ ✨ Claude environment is ready!
125
+ ```
126
+
127
+ ### Interactive Run (With Existing Files)
128
+
129
+ ```
130
+ 🚀 Claude Environment Initializer
131
+ Initializing in: /path/to/your/project
132
+
133
+ Overwrite existing .claude/commands/*.md with template versions? (y/N): n
134
+ Install .claude/agents files? (Y/n): n
135
+ Overwrite existing .claude/settings.json with template version? (y/N): n
136
+
137
+ - CLAUDE.md already contains template content
138
+ - Directory .devcontainer already exists
139
+ - File .claude/settings.json already exists
140
+ ↻ Added 1 missing files to .claude/commands (added: 1)
141
+ - All files in .claude/agents are up to date
142
+
143
+ 📊 Summary:
144
+ Updated: 1 items
145
+ Skipped: 3 items (already exist)
146
+ Files added: 1
147
+ - .claude/agents skipped by user
148
+
149
+ ✅ Claude environment is already up to date!
150
+ ```
151
+
152
+ ### Non-Interactive Run (Original Output)
153
+
66
154
  ```
67
155
  🚀 Claude Environment Initializer
68
156
  Initializing in: /path/to/your/project
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-init",
3
- "version": "1.0.21",
3
+ "version": "1.0.24",
4
4
  "description": "Initialize Claude development environment with configurations and templates",
5
5
  "type": "module",
6
6
  "bin": {
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
- const fileInfo = filesAdded !== undefined ? ` (${filesAdded} files)` : '';
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,48 @@ 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 overwrite .claude/settings.json
56
+ let overwriteSettings = false;
57
+ const settingsPath = path.join(targetDir, '.claude/settings.json');
58
+ if (await fs.pathExists(settingsPath)) {
59
+ overwriteSettings = await promptYesNo(
60
+ 'Overwrite existing .claude/settings.json with template version?',
61
+ false
62
+ );
63
+ }
64
+
65
+ // Determine if we should prompt for overwriting .claude/commands/*.md
66
+ let overwriteCommandsMd = false;
67
+ const commandsTargetPath = path.join(targetDir, '.claude/commands');
68
+ const commandsTemplatePath = path.join(getTemplatesPath(), '.claude/commands');
69
+
70
+ if (await fs.pathExists(commandsTargetPath) && await fs.pathExists(commandsTemplatePath)) {
71
+ const templateFiles = await getFilesInDirectory(commandsTemplatePath);
72
+ const targetFiles = await getFilesInDirectory(commandsTargetPath);
73
+
74
+ // Check if there are any overlapping .md files
75
+ const mdTemplateFiles = templateFiles.filter(f => f.endsWith('.md'));
76
+ const hasOverlappingMd = mdTemplateFiles.some(f => targetFiles.includes(f));
77
+
78
+ if (hasOverlappingMd) {
79
+ overwriteCommandsMd = await promptYesNo(
80
+ 'Overwrite existing .claude/commands/*.md with template versions?',
81
+ false
82
+ );
83
+ }
84
+ }
85
+
86
+ // Determine if we should install .claude/agents
87
+ const includeAgents = await promptYesNo(
88
+ 'Install .claude/agents files?',
89
+ true
90
+ );
91
+
39
92
  const tasks = [
40
93
  {
41
94
  name: 'CLAUDE.md',
@@ -47,17 +100,24 @@ export async function cli() {
47
100
  },
48
101
  {
49
102
  name: '.claude/settings.json',
50
- handler: () => handleSingleFile(targetDir, '.claude/settings.json')
103
+ handler: () => handleSingleFile(targetDir, '.claude/settings.json', { overwrite: overwriteSettings })
51
104
  },
52
105
  {
53
106
  name: '.claude/commands',
54
- handler: () => handleSelectiveFileCopy(targetDir, '.claude/commands')
55
- },
56
- {
57
- name: '.claude/agents',
58
- handler: () => handleSelectiveFileCopy(targetDir, '.claude/agents')
107
+ handler: () => handleSelectiveFileCopy(targetDir, '.claude/commands', {
108
+ overwriteExisting: overwriteCommandsMd,
109
+ filter: (relPath) => relPath.endsWith('.md')
110
+ })
59
111
  }
60
112
  ];
113
+
114
+ // Conditionally add .claude/agents task
115
+ if (includeAgents) {
116
+ tasks.push({
117
+ name: '.claude/agents',
118
+ handler: () => handleSelectiveFileCopy(targetDir, '.claude/agents')
119
+ });
120
+ }
61
121
 
62
122
  let totalActions = { created: 0, updated: 0, skipped: 0, filesAdded: 0 };
63
123
 
@@ -99,9 +159,13 @@ export async function cli() {
99
159
  if (totalActions.filesAdded > 0) {
100
160
  console.log(chalk.cyan(` Files added: ${totalActions.filesAdded}`));
101
161
  }
102
-
162
+
163
+ if (!includeAgents) {
164
+ console.log(chalk.gray(` - .claude/agents skipped by user`));
165
+ }
166
+
103
167
  console.log();
104
-
168
+
105
169
  if (totalActions.created > 0 || totalActions.updated > 0) {
106
170
  console.log(chalk.green.bold('✨ Claude environment is ready!'));
107
171
  } else {
@@ -70,69 +70,111 @@ 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
- if (missingFiles.length === 0) {
96
- return {
97
- action: 'skipped',
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
- copiedCount++;
112
+ filesAdded++;
113
113
  }
114
-
115
- return {
116
- action: 'updated',
117
- details: `Added ${copiedCount} missing files to ${relativePath}`,
118
- filesAdded: copiedCount
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
  /**
123
- * Handle single file copying (create if not exists, skip if exists)
154
+ * Handle single file copying (create if not exists, skip if exists, optionally overwrite)
155
+ * @param {string} targetDir - The target directory
156
+ * @param {string} relativePath - The relative path of the file to copy
157
+ * @param {Object} options - Options for the copy operation
158
+ * @param {boolean} options.overwrite - Whether to overwrite existing file (default: false)
124
159
  */
125
- export async function handleSingleFile(targetDir, relativePath) {
160
+ export async function handleSingleFile(targetDir, relativePath, options = {}) {
161
+ const { overwrite = false } = options;
126
162
  const targetPath = path.join(targetDir, relativePath);
127
163
  const templatePath = path.join(getTemplatesPath(), relativePath);
128
-
164
+
129
165
  if (await fs.pathExists(targetPath)) {
130
- return { action: 'skipped', details: `File ${relativePath} already exists` };
166
+ if (!overwrite) {
167
+ return { action: 'skipped', details: `File ${relativePath} already exists` };
168
+ }
169
+
170
+ // Overwrite existing file
171
+ await fs.copy(templatePath, targetPath);
172
+ return { action: 'updated', details: `Overwrote ${relativePath} with template` };
131
173
  }
132
-
174
+
133
175
  // Ensure target directory exists
134
176
  await fs.ensureDir(path.dirname(targetPath));
135
177
  await fs.copy(templatePath, targetPath);
136
-
178
+
137
179
  return { action: 'created', details: `Created ${relativePath}` };
138
180
  }
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
+ }