@yeongjaeyou/claude-code-config 0.12.0 → 0.14.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.
@@ -13,10 +13,42 @@ Common guidelines for all Claude Code commands and development workflow.
13
13
  - Do not use emoji in code or documentation
14
14
  - Never add Claude attribution (e.g., "Generated with Claude", "Co-Authored-By: Claude") to commits, PRs, or issues
15
15
 
16
+ ### Writing Style (Anti-AI)
17
+ Write like a human, not a chatbot. Applies to ALL text: responses, documentation, comments, commit messages.
18
+
19
+ **NEVER use these AI-ish patterns:**
20
+ - Filler openers: "Certainly!", "Of course!", "Absolutely!", "I'd be happy to", "Great question!"
21
+ - Excessive affirmation: "That's a great idea", "You're absolutely right", "Excellent point"
22
+ - Redundant summaries: "To summarize...", "In conclusion...", "To recap..."
23
+ - Over-explanation: Explaining obvious things, restating the question
24
+ - Hedging phrases: "I think maybe...", "It might be possible that..."
25
+ - Hollow transitions: "Now, let's...", "Moving on to...", "Next, we'll..."
26
+ - Colon headers: "**항목:** 설명", "**Topic:** content" - just write naturally without label-colon-content structure
27
+
28
+ **DO:**
29
+ - Get to the point immediately
30
+ - Be direct and concise
31
+ - Use natural, conversational tone
32
+ - Skip pleasantries unless genuinely warranted
33
+
16
34
  ### Uncertainty Handling
17
35
  - If you are uncertain, confused, or lack clarity about the requirements or approach, **STOP immediately** and ask the user for clarification
18
36
  - Do not proceed with assumptions or guesses that could lead to incorrect implementations
19
- - If any instruction/requirement is unclear, ambiguous, or potentially incorrect, use the `AskUserQuestion` tool (do not ask inline in the response text)
37
+ - **NEVER** ask questions inline in response text - always use `AskUserQuestion` tool
38
+
39
+ ### Question Policy (MANDATORY)
40
+ - **ALL questions MUST use `AskUserQuestion` tool** - no exceptions
41
+ - Never ask questions as plain text in responses
42
+ - This includes:
43
+ - Clarification questions
44
+ - Option/choice selection
45
+ - Confirmation requests
46
+ - Any user input needed
47
+
48
+ **Why:**
49
+ - Plain text questions get buried in long responses
50
+ - Tool-based questions provide clear UI separation
51
+ - Ensures user doesn't miss important decisions
20
52
 
21
53
  ### User Confirmation Required
22
54
  Always confirm with the user before:
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+ # Claude Code OSC Notification Script
3
+ # /dev/tty로 출력하여 터미널에 직접 전달
4
+
5
+ # stdin에서 JSON 읽기 (timeout으로 blocking 방지)
6
+ INPUT=$(timeout 1 cat 2>/dev/null || true)
7
+
8
+ # JSON 파싱
9
+ if command -v jq &>/dev/null && [ -n "$INPUT" ]; then
10
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
11
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
12
+ EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null)
13
+ NOTIF_TYPE=$(echo "$INPUT" | jq -r '.notification_type // empty' 2>/dev/null)
14
+ MESSAGE=$(echo "$INPUT" | jq -r '.message // empty' 2>/dev/null)
15
+
16
+ FOLDER=$(basename "${CWD:-$PWD}" 2>/dev/null)
17
+ SHORT_ID="${SESSION_ID:0:8}"
18
+
19
+ # 제목 구성
20
+ TITLE="Claude"
21
+ [ -n "$FOLDER" ] && TITLE="$TITLE - $FOLDER"
22
+ [ -n "$SHORT_ID" ] && TITLE="$TITLE [$SHORT_ID]"
23
+
24
+ # 본문 구성
25
+ case "$EVENT" in
26
+ "Stop")
27
+ BODY="Task completed"
28
+ ;;
29
+ "Notification")
30
+ case "$NOTIF_TYPE" in
31
+ "permission_prompt") BODY="Permission needed" ;;
32
+ "idle_prompt") BODY="Waiting for input" ;;
33
+ *) BODY="${MESSAGE:-$NOTIF_TYPE}" ;;
34
+ esac
35
+ ;;
36
+ *)
37
+ BODY="${MESSAGE:-Notification}"
38
+ ;;
39
+ esac
40
+ else
41
+ TITLE="${1:-Claude Code}"
42
+ BODY="${2:-Task completed}"
43
+ fi
44
+
45
+ # OSC 777 알림 출력 (OSC 9는 중복 알림 발생하므로 제외)
46
+ # /dev/tty 사용 가능 시 직접 출력, 아니면 stdout
47
+ {
48
+ printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY"
49
+ } > /dev/tty 2>/dev/null || {
50
+ printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY"
51
+ }
52
+
53
+ exit 0
package/README.md CHANGED
@@ -267,6 +267,27 @@ cp node_modules/@yeongjaeyou/claude-code-config/.mcp.json .
267
267
  - Hugging Face model/dataset/Spaces search via `huggingface_hub` API
268
268
  - Download and analyze source code in temp directory (`/tmp/`)
269
269
 
270
+ ## Notification Hooks
271
+
272
+ Desktop notifications for Claude Code events using OSC escape sequences.
273
+
274
+ ### Quick Setup
275
+
276
+ ```bash
277
+ # 1. Install Terminal Notification extension in VSCode
278
+ # 2. Global install includes notify_osc.sh and auto-registers hooks
279
+ npx @yeongjaeyou/claude-code-config --global
280
+ ```
281
+
282
+ ### Supported Events
283
+
284
+ | Event | Description |
285
+ |-------|-------------|
286
+ | Stop | Task completion |
287
+ | Notification | Permission requests, idle prompts |
288
+
289
+ See [docs/notification-setup.md](docs/notification-setup.md) for detailed setup guide.
290
+
270
291
  ## License
271
292
 
272
293
  MIT License
package/bin/cli.js CHANGED
@@ -3,13 +3,17 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
- const readline = require('readline');
7
6
 
8
7
  const pkg = require('../package.json');
9
- const args = process.argv.slice(2);
8
+ const {
9
+ INSTALL_FOLDERS,
10
+ askQuestion,
11
+ checkExistingFolders,
12
+ installFolders,
13
+ mergeSettingsJson
14
+ } = require('../lib/cli-utils');
10
15
 
11
- // Installation target folders (non-user data only)
12
- const INSTALL_FOLDERS = ['commands', 'agents', 'skills', 'guidelines', 'hooks'];
16
+ const args = process.argv.slice(2);
13
17
 
14
18
  // Parse options
15
19
  const isGlobal = args.includes('--global') || args.includes('-g');
@@ -57,189 +61,6 @@ const dest = isGlobal
57
61
  ? path.join(os.homedir(), '.claude')
58
62
  : path.join(process.cwd(), '.claude');
59
63
 
60
- /**
61
- * Ask user a question and get answer
62
- * @param {string} question - Question content
63
- * @returns {Promise<string>} - User answer
64
- */
65
- function askQuestion(question) {
66
- // Detect non-interactive environment (CI/CD, pipelines, etc.)
67
- if (!process.stdin.isTTY) {
68
- console.log('Non-interactive environment detected. Using default (update).');
69
- return Promise.resolve('update');
70
- }
71
-
72
- const rl = readline.createInterface({
73
- input: process.stdin,
74
- output: process.stdout
75
- });
76
-
77
- return new Promise((resolve) => {
78
- rl.question(question, (answer) => {
79
- rl.close();
80
- resolve(answer.trim().toLowerCase());
81
- });
82
- });
83
- }
84
-
85
- /**
86
- * Check existing installation folders
87
- * @returns {string[]} - List of existing folders
88
- */
89
- function checkExistingFolders() {
90
- const existing = [];
91
- for (const folder of INSTALL_FOLDERS) {
92
- const folderPath = path.join(dest, folder);
93
- if (fs.existsSync(folderPath)) {
94
- existing.push(folder);
95
- }
96
- }
97
- return existing;
98
- }
99
-
100
- /**
101
- * Copy folder recursively
102
- * @param {string} src - Source path
103
- * @param {string} dst - Destination path
104
- * @param {string} mode - Installation mode: 'merge' | 'update'
105
- */
106
- function copyFolder(src, dst, mode = 'merge') {
107
- if (!fs.existsSync(src)) {
108
- return;
109
- }
110
-
111
- // Create destination folder
112
- if (!fs.existsSync(dst)) {
113
- try {
114
- fs.mkdirSync(dst, { recursive: true });
115
- } catch (err) {
116
- throw new Error(`Failed to create folder: ${dst} - ${err.message}`);
117
- }
118
- }
119
-
120
- const entries = fs.readdirSync(src, { withFileTypes: true });
121
-
122
- for (const entry of entries) {
123
- const srcPath = path.join(src, entry.name);
124
- const dstPath = path.join(dst, entry.name);
125
-
126
- if (entry.isDirectory()) {
127
- copyFolder(srcPath, dstPath, mode);
128
- } else {
129
- // In merge mode, keep existing files (skip if exists)
130
- if (mode === 'merge' && fs.existsSync(dstPath)) {
131
- continue;
132
- }
133
- // In update mode, overwrite existing files + add new files
134
- // Custom files (only in dest) are preserved since we don't delete them
135
- try {
136
- fs.copyFileSync(srcPath, dstPath);
137
- // Set execute permission for shell scripts
138
- if (entry.name.endsWith('.sh')) {
139
- fs.chmodSync(dstPath, 0o755);
140
- }
141
- } catch (err) {
142
- throw new Error(`Failed to copy file: ${srcPath} -> ${dstPath} - ${err.message}`);
143
- }
144
- }
145
- }
146
- }
147
-
148
- /**
149
- * Selectively copy installation target folders
150
- * @param {string} mode - 'merge' | 'update'
151
- */
152
- function installFolders(mode) {
153
- for (const folder of INSTALL_FOLDERS) {
154
- const srcFolder = path.join(source, folder);
155
- const dstFolder = path.join(dest, folder);
156
-
157
- if (!fs.existsSync(srcFolder)) {
158
- continue;
159
- }
160
-
161
- // Both modes now preserve custom files
162
- // - merge: skip existing files, add new files only
163
- // - update: overwrite existing files, add new files, keep custom files
164
- copyFolder(srcFolder, dstFolder, mode);
165
- }
166
- }
167
-
168
- // Default hook configuration for inject-guidelines
169
- const DEFAULT_HOOKS_CONFIG = {
170
- UserPromptSubmit: [
171
- {
172
- matcher: '',
173
- hooks: [
174
- {
175
- type: 'command',
176
- command: 'bash .claude/hooks/inject-guidelines.sh'
177
- }
178
- ]
179
- }
180
- ]
181
- };
182
-
183
- /**
184
- * Check if a hook with the same command already exists
185
- * @param {Array} hooks - Existing hooks array
186
- * @param {string} command - Command to check
187
- * @returns {boolean}
188
- */
189
- function hookExists(hooks, command) {
190
- if (!Array.isArray(hooks)) return false;
191
- return hooks.some(hook => {
192
- if (!hook.hooks || !Array.isArray(hook.hooks)) return false;
193
- return hook.hooks.some(h => h.command === command);
194
- });
195
- }
196
-
197
- /**
198
- * Merge settings.json with default hooks configuration
199
- * Preserves existing user settings and only adds new hooks
200
- */
201
- function mergeSettingsJson() {
202
- const settingsPath = path.join(dest, 'settings.json');
203
- let settings = {};
204
-
205
- // Load existing settings if exists
206
- if (fs.existsSync(settingsPath)) {
207
- try {
208
- const content = fs.readFileSync(settingsPath, 'utf8');
209
- settings = JSON.parse(content);
210
- } catch (err) {
211
- console.log('Warning: Could not parse existing settings.json, creating new one.');
212
- settings = {};
213
- }
214
- }
215
-
216
- // Initialize hooks object if not exists
217
- if (!settings.hooks) {
218
- settings.hooks = {};
219
- }
220
-
221
- // Initialize UserPromptSubmit array if not exists
222
- if (!settings.hooks.UserPromptSubmit) {
223
- settings.hooks.UserPromptSubmit = [];
224
- }
225
-
226
- // Add default hooks if not already present
227
- const targetCommand = DEFAULT_HOOKS_CONFIG.UserPromptSubmit[0].hooks[0].command;
228
- if (!hookExists(settings.hooks.UserPromptSubmit, targetCommand)) {
229
- settings.hooks.UserPromptSubmit.push(DEFAULT_HOOKS_CONFIG.UserPromptSubmit[0]);
230
- console.log('Added inject-guidelines hook to settings.json');
231
- } else {
232
- console.log('inject-guidelines hook already exists in settings.json');
233
- }
234
-
235
- // Write merged settings
236
- try {
237
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
238
- } catch (err) {
239
- throw new Error(`Failed to write settings.json: ${err.message}`);
240
- }
241
- }
242
-
243
64
  /**
244
65
  * Main function
245
66
  */
@@ -276,7 +97,7 @@ async function main() {
276
97
  }
277
98
 
278
99
  // Check existing installation folders
279
- const existingFolders = checkExistingFolders();
100
+ const existingFolders = checkExistingFolders(dest);
280
101
  let installMode = 'update';
281
102
 
282
103
  if (existingFolders.length > 0) {
@@ -315,10 +136,10 @@ async function main() {
315
136
  }
316
137
 
317
138
  // Install folders
318
- installFolders(installMode);
139
+ installFolders(source, dest, installMode);
319
140
 
320
141
  // Merge settings.json with hooks configuration
321
- mergeSettingsJson();
142
+ mergeSettingsJson(dest, isGlobal);
322
143
 
323
144
  console.log('');
324
145
  console.log('.claude/ folder installed successfully!');
@@ -337,6 +158,13 @@ async function main() {
337
158
  if (isGlobal) {
338
159
  console.log('Global installation complete.');
339
160
  console.log('Claude Code commands are now available in all projects.');
161
+ console.log('');
162
+ console.log('Desktop notifications enabled:');
163
+ console.log(' - Stop: Task completion alerts');
164
+ console.log(' - Notification: Permission/idle prompts');
165
+ console.log('');
166
+ console.log('Note: Install "Terminal Notification" VSCode extension for alerts.');
167
+ console.log('See docs/notification-setup.md for detailed setup guide.');
340
168
  }
341
169
  }
342
170
 
@@ -0,0 +1,271 @@
1
+ /**
2
+ * CLI Utility Functions
3
+ * Modularized from bin/cli.js for testability
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const readline = require('readline');
9
+
10
+ // Installation target folders (non-user data only)
11
+ const INSTALL_FOLDERS = ['commands', 'agents', 'skills', 'guidelines', 'hooks'];
12
+
13
+ // Default hook configuration for inject-guidelines
14
+ const DEFAULT_HOOKS_CONFIG = {
15
+ UserPromptSubmit: [
16
+ {
17
+ matcher: '',
18
+ hooks: [
19
+ {
20
+ type: 'command',
21
+ command: 'bash .claude/hooks/inject-guidelines.sh'
22
+ }
23
+ ]
24
+ }
25
+ ]
26
+ };
27
+
28
+ // Global-only notification hooks (OSC desktop alerts)
29
+ const GLOBAL_NOTIFICATION_HOOKS = {
30
+ Stop: [
31
+ {
32
+ hooks: [
33
+ {
34
+ type: 'command',
35
+ command: '~/.claude/hooks/notify_osc.sh',
36
+ timeout: 10
37
+ }
38
+ ]
39
+ }
40
+ ],
41
+ Notification: [
42
+ {
43
+ matcher: '',
44
+ hooks: [
45
+ {
46
+ type: 'command',
47
+ command: '~/.claude/hooks/notify_osc.sh',
48
+ timeout: 10
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ };
54
+
55
+ /**
56
+ * Ask user a question and get answer
57
+ * @param {string} question - Question content
58
+ * @param {Object} options - Options for readline
59
+ * @param {NodeJS.ReadStream} options.input - Input stream (default: process.stdin)
60
+ * @param {NodeJS.WriteStream} options.output - Output stream (default: process.stdout)
61
+ * @returns {Promise<string>} - User answer
62
+ */
63
+ function askQuestion(question, options = {}) {
64
+ const input = options.input || process.stdin;
65
+ const output = options.output || process.stdout;
66
+
67
+ // Detect non-interactive environment (CI/CD, pipelines, etc.)
68
+ if (!input.isTTY) {
69
+ console.log('Non-interactive environment detected. Using default (update).');
70
+ return Promise.resolve('update');
71
+ }
72
+
73
+ const rl = readline.createInterface({
74
+ input,
75
+ output
76
+ });
77
+
78
+ return new Promise((resolve) => {
79
+ rl.question(question, (answer) => {
80
+ rl.close();
81
+ resolve(answer.trim().toLowerCase());
82
+ });
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Check existing installation folders
88
+ * @param {string} dest - Destination path
89
+ * @returns {string[]} - List of existing folders
90
+ */
91
+ function checkExistingFolders(dest) {
92
+ const existing = [];
93
+ for (const folder of INSTALL_FOLDERS) {
94
+ const folderPath = path.join(dest, folder);
95
+ if (fs.existsSync(folderPath)) {
96
+ existing.push(folder);
97
+ }
98
+ }
99
+ return existing;
100
+ }
101
+
102
+ /**
103
+ * Copy folder recursively
104
+ * @param {string} src - Source path
105
+ * @param {string} dst - Destination path
106
+ * @param {string} mode - Installation mode: 'merge' | 'update'
107
+ */
108
+ function copyFolder(src, dst, mode = 'merge') {
109
+ if (!fs.existsSync(src)) {
110
+ return;
111
+ }
112
+
113
+ // Create destination folder
114
+ if (!fs.existsSync(dst)) {
115
+ try {
116
+ fs.mkdirSync(dst, { recursive: true });
117
+ } catch (err) {
118
+ throw new Error(`Failed to create folder: ${dst} - ${err.message}`);
119
+ }
120
+ }
121
+
122
+ const entries = fs.readdirSync(src, { withFileTypes: true });
123
+
124
+ for (const entry of entries) {
125
+ const srcPath = path.join(src, entry.name);
126
+ const dstPath = path.join(dst, entry.name);
127
+
128
+ if (entry.isDirectory()) {
129
+ copyFolder(srcPath, dstPath, mode);
130
+ } else {
131
+ // In merge mode, keep existing files (skip if exists)
132
+ if (mode === 'merge' && fs.existsSync(dstPath)) {
133
+ continue;
134
+ }
135
+ // In update mode, overwrite existing files + add new files
136
+ // Custom files (only in dest) are preserved since we don't delete them
137
+ try {
138
+ fs.copyFileSync(srcPath, dstPath);
139
+ // Set execute permission for shell scripts
140
+ if (entry.name.endsWith('.sh')) {
141
+ fs.chmodSync(dstPath, 0o755);
142
+ }
143
+ } catch (err) {
144
+ throw new Error(`Failed to copy file: ${srcPath} -> ${dstPath} - ${err.message}`);
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Selectively copy installation target folders
152
+ * @param {string} source - Source base path
153
+ * @param {string} dest - Destination base path
154
+ * @param {string} mode - 'merge' | 'update'
155
+ */
156
+ function installFolders(source, dest, mode) {
157
+ for (const folder of INSTALL_FOLDERS) {
158
+ const srcFolder = path.join(source, folder);
159
+ const dstFolder = path.join(dest, folder);
160
+
161
+ if (!fs.existsSync(srcFolder)) {
162
+ continue;
163
+ }
164
+
165
+ // Both modes now preserve custom files
166
+ // - merge: skip existing files, add new files only
167
+ // - update: overwrite existing files, add new files, keep custom files
168
+ copyFolder(srcFolder, dstFolder, mode);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Check if a hook with the same command already exists
174
+ * @param {Array} hooks - Existing hooks array
175
+ * @param {string} command - Command to check
176
+ * @returns {boolean}
177
+ */
178
+ function hookExists(hooks, command) {
179
+ if (!Array.isArray(hooks)) return false;
180
+ return hooks.some(hook => {
181
+ if (!hook.hooks || !Array.isArray(hook.hooks)) return false;
182
+ return hook.hooks.some(h => h.command === command);
183
+ });
184
+ }
185
+
186
+ /**
187
+ * Merge settings.json with default hooks configuration
188
+ * Preserves existing user settings and only adds new hooks
189
+ * @param {string} dest - Destination path (.claude folder)
190
+ * @param {boolean} isGlobal - Whether this is a global installation
191
+ * @param {Object} options - Optional overrides for testing
192
+ * @param {Object} options.defaultHooksConfig - Override DEFAULT_HOOKS_CONFIG
193
+ * @param {Object} options.globalNotificationHooks - Override GLOBAL_NOTIFICATION_HOOKS
194
+ */
195
+ function mergeSettingsJson(dest, isGlobal, options = {}) {
196
+ const defaultHooksConfig = options.defaultHooksConfig || DEFAULT_HOOKS_CONFIG;
197
+ const globalNotificationHooks = options.globalNotificationHooks || GLOBAL_NOTIFICATION_HOOKS;
198
+
199
+ const settingsPath = path.join(dest, 'settings.json');
200
+ let settings = {};
201
+
202
+ // Load existing settings if exists
203
+ if (fs.existsSync(settingsPath)) {
204
+ try {
205
+ const content = fs.readFileSync(settingsPath, 'utf8');
206
+ settings = JSON.parse(content);
207
+ } catch (err) {
208
+ console.log('Warning: Could not parse existing settings.json, creating new one.');
209
+ settings = {};
210
+ }
211
+ }
212
+
213
+ // Initialize hooks object if not exists
214
+ if (!settings.hooks) {
215
+ settings.hooks = {};
216
+ }
217
+
218
+ // Initialize UserPromptSubmit array if not exists
219
+ if (!settings.hooks.UserPromptSubmit) {
220
+ settings.hooks.UserPromptSubmit = [];
221
+ }
222
+
223
+ // Add default hooks if not already present
224
+ const targetCommand = defaultHooksConfig.UserPromptSubmit[0].hooks[0].command;
225
+ if (!hookExists(settings.hooks.UserPromptSubmit, targetCommand)) {
226
+ settings.hooks.UserPromptSubmit.push(defaultHooksConfig.UserPromptSubmit[0]);
227
+ console.log('Added inject-guidelines hook to settings.json');
228
+ } else {
229
+ console.log('inject-guidelines hook already exists in settings.json');
230
+ }
231
+
232
+ // Add notification hooks for global install only
233
+ if (isGlobal) {
234
+ for (const [event, hookConfigs] of Object.entries(globalNotificationHooks)) {
235
+ if (!settings.hooks[event]) {
236
+ settings.hooks[event] = [];
237
+ }
238
+ const notifCommand = hookConfigs[0].hooks[0].command;
239
+ if (!hookExists(settings.hooks[event], notifCommand)) {
240
+ settings.hooks[event].push(hookConfigs[0]);
241
+ console.log(`Added ${event} notification hook to settings.json`);
242
+ } else {
243
+ console.log(`${event} notification hook already exists in settings.json`);
244
+ }
245
+ }
246
+ }
247
+
248
+ // Write merged settings
249
+ try {
250
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
251
+ } catch (err) {
252
+ throw new Error(`Failed to write settings.json: ${err.message}`);
253
+ }
254
+
255
+ return settings;
256
+ }
257
+
258
+ module.exports = {
259
+ // Constants
260
+ INSTALL_FOLDERS,
261
+ DEFAULT_HOOKS_CONFIG,
262
+ GLOBAL_NOTIFICATION_HOOKS,
263
+
264
+ // Functions
265
+ askQuestion,
266
+ checkExistingFolders,
267
+ copyFolder,
268
+ installFolders,
269
+ hookExists,
270
+ mergeSettingsJson
271
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeongjaeyou/claude-code-config",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Claude Code CLI custom commands, agents, and skills",
5
5
  "bin": {
6
6
  "claude-code-config": "./bin/cli.js"
@@ -8,8 +8,14 @@
8
8
  "files": [
9
9
  ".claude",
10
10
  ".mcp.json",
11
- "bin"
11
+ "bin",
12
+ "lib"
12
13
  ],
14
+ "scripts": {
15
+ "test": "jest",
16
+ "test:watch": "jest --watch",
17
+ "test:coverage": "jest --coverage"
18
+ },
13
19
  "keywords": [
14
20
  "claude-code",
15
21
  "cli",
@@ -29,5 +35,12 @@
29
35
  "homepage": "https://github.com/YoungjaeDev/claude-code-config#readme",
30
36
  "publishConfig": {
31
37
  "access": "public"
38
+ },
39
+ "devDependencies": {
40
+ "jest": "^29.7.0",
41
+ "mock-fs": "^5.4.1"
42
+ },
43
+ "engines": {
44
+ "node": ">=14.0.0"
32
45
  }
33
46
  }