@juho0719/cckit 0.2.1 → 0.2.4

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.
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ # Agent 툴 실행 추적 — 여러 에이전트 동시 지원
3
+ # @hook-event PreToolUse|Agent|pre
4
+ # @hook-event PostToolUse|Agent|post
5
+
6
+ AGENT_STATE_DIR="/tmp/claude-agents"
7
+ mkdir -p "$AGENT_STATE_DIR"
8
+
9
+ EVENT="${1:-post}"
10
+ INPUT=$(cat)
11
+
12
+ # subagent_type + description으로 고유 파일명 생성
13
+ AGENT_ID=$(echo "$INPUT" | python3 -c "
14
+ import sys, json, hashlib
15
+ try:
16
+ d = json.load(sys.stdin)
17
+ ti = d.get('tool_input', {})
18
+ key = (ti.get('subagent_type', '') or '') + '|' + (ti.get('description', '') or '')
19
+ print(hashlib.md5(key.encode()).hexdigest()[:12])
20
+ except:
21
+ print('')
22
+ " 2>/dev/null)
23
+
24
+ if [ -z "$AGENT_ID" ]; then
25
+ exit 0
26
+ fi
27
+
28
+ if [ "$EVENT" = "pre" ]; then
29
+ SUBAGENT_TYPE=$(echo "$INPUT" | python3 -c "
30
+ import sys, json
31
+ try:
32
+ d = json.load(sys.stdin)
33
+ print(d.get('tool_input', {}).get('subagent_type', '') or '')
34
+ except:
35
+ print('')
36
+ " 2>/dev/null)
37
+
38
+ DESCRIPTION=$(echo "$INPUT" | python3 -c "
39
+ import sys, json
40
+ try:
41
+ d = json.load(sys.stdin)
42
+ desc = d.get('tool_input', {}).get('description', '') or ''
43
+ print(desc[:50] if len(desc) > 50 else desc)
44
+ except:
45
+ print('')
46
+ " 2>/dev/null)
47
+
48
+ if [ -n "$SUBAGENT_TYPE" ]; then
49
+ echo "${SUBAGENT_TYPE}|${DESCRIPTION}" > "${AGENT_STATE_DIR}/${AGENT_ID}"
50
+ fi
51
+ else
52
+ rm -f "${AGENT_STATE_DIR}/${AGENT_ID}"
53
+ fi
@@ -0,0 +1,123 @@
1
+ #!/bin/bash
2
+ # Auto commit & push hook - runs when Claude Code finishes a response
3
+ # @hook-event Stop||
4
+ # Compatible with bash 3.2 (macOS default)
5
+
6
+ # Get the working directory from the hook input
7
+ WORK_DIR=$(echo "$CLAUDE_HOOK_INPUT" | jq -r '.cwd // empty' 2>/dev/null)
8
+ if [ -z "$WORK_DIR" ]; then
9
+ WORK_DIR="$(pwd)"
10
+ fi
11
+
12
+ cd "$WORK_DIR" 2>/dev/null || exit 0
13
+
14
+ # Check if we're in a git repo
15
+ if ! git rev-parse --is-inside-work-tree &>/dev/null; then
16
+ exit 0
17
+ fi
18
+
19
+ # Check if there are any changes
20
+ if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
21
+ exit 0
22
+ fi
23
+
24
+ BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
25
+ if [ -z "$BRANCH" ]; then
26
+ exit 0
27
+ fi
28
+
29
+ # Stage all changes
30
+ git add -A
31
+
32
+ # ── 변경 파일 목록 수집 ───────────────────────────────
33
+ ADDED_FILES=$(git diff --cached --diff-filter=A --name-only)
34
+ MODIFIED_FILES=$(git diff --cached --diff-filter=M --name-only)
35
+ DELETED_FILES=$(git diff --cached --diff-filter=D --name-only)
36
+
37
+ ALL_FILES=$(git diff --cached --name-only)
38
+ ADDED_COUNT=$(echo "$ADDED_FILES" | grep -c . || true)
39
+ MODIFIED_COUNT=$(echo "$MODIFIED_FILES" | grep -c . || true)
40
+ DELETED_COUNT=$(echo "$DELETED_FILES" | grep -c . || true)
41
+
42
+ # ── 커밋 타입 감지 ────────────────────────────────────
43
+ # test: 파일이 모두 테스트/설정 관련이면 test/chore
44
+ HAS_TEST=$(echo "$ALL_FILES" | grep -E "(\.test\.|\.spec\.|__tests__|e2e/)" | head -1)
45
+ HAS_CONFIG=$(echo "$ALL_FILES" | grep -E "\.(config|json|yaml|yml|env|toml)$" | head -1)
46
+ HAS_ROADMAP=$(echo "$ALL_FILES" | grep -E "roadmap|ROADMAP|\.md$" | head -1)
47
+ HAS_SRC=$(echo "$ALL_FILES" | grep -E "^src/" | head -1)
48
+
49
+ if [ -n "$HAS_TEST" ] && [ -z "$HAS_SRC" ]; then
50
+ COMMIT_TYPE="test"
51
+ elif [ -n "$HAS_CONFIG" ] && [ -z "$HAS_SRC" ]; then
52
+ COMMIT_TYPE="chore"
53
+ elif [ -n "$HAS_ROADMAP" ] && [ -z "$HAS_SRC" ]; then
54
+ COMMIT_TYPE="docs"
55
+ else
56
+ COMMIT_TYPE="feat"
57
+ fi
58
+
59
+ # ── 스코프: 가장 많이 변경된 2단계 디렉토리 ────────────
60
+ SCOPE=$(echo "$ALL_FILES" \
61
+ | awk -F'/' 'NF>=2 {print $1"/"$2} NF==1 {print $1}' \
62
+ | sort | uniq -c | sort -rn \
63
+ | awk 'NR==1{print $2}')
64
+ [ -z "$SCOPE" ] && SCOPE="root"
65
+
66
+ # ── Subject: 주요 파일명 나열 (확장자 제거, 최대 3개) ───
67
+ key_files() {
68
+ echo "$1" | while IFS= read -r f; do
69
+ [ -n "$f" ] && basename "$f" | sed 's/\.[^.]*$//'
70
+ done | head -3 | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g'
71
+ }
72
+
73
+ ADDED_NAMES=$(key_files "$ADDED_FILES")
74
+ MODIFIED_NAMES=$(key_files "$MODIFIED_FILES")
75
+ DELETED_NAMES=$(key_files "$DELETED_FILES")
76
+
77
+ SUBJECT_PARTS=""
78
+ [ -n "$ADDED_NAMES" ] && SUBJECT_PARTS="${SUBJECT_PARTS}add ${ADDED_NAMES}"
79
+ [ -n "$MODIFIED_NAMES" ] && SUBJECT_PARTS="${SUBJECT_PARTS:+$SUBJECT_PARTS; }update ${MODIFIED_NAMES}"
80
+ [ -n "$DELETED_NAMES" ] && SUBJECT_PARTS="${SUBJECT_PARTS:+$SUBJECT_PARTS; }remove ${DELETED_NAMES}"
81
+
82
+ # 파일이 4개 이상이면 개수도 병기
83
+ TOTAL=$((ADDED_COUNT + MODIFIED_COUNT + DELETED_COUNT))
84
+ if [ "$TOTAL" -gt 3 ]; then
85
+ SUBJECT_PARTS="${SUBJECT_PARTS} (+$((TOTAL - 3)) more)"
86
+ fi
87
+
88
+ SUBJECT="${COMMIT_TYPE}(${SCOPE}): ${SUBJECT_PARTS}"
89
+
90
+ # ── Body: 전체 파일 목록 ─────────────────────────────
91
+ BODY=""
92
+ if [ -n "$ADDED_FILES" ]; then
93
+ BODY="${BODY}New files:\n"
94
+ while IFS= read -r f; do
95
+ [ -n "$f" ] && BODY="${BODY} + ${f}\n"
96
+ done <<< "$ADDED_FILES"
97
+ fi
98
+ if [ -n "$MODIFIED_FILES" ]; then
99
+ [ -n "$BODY" ] && BODY="${BODY}\n"
100
+ BODY="${BODY}Modified:\n"
101
+ while IFS= read -r f; do
102
+ [ -n "$f" ] && BODY="${BODY} ~ ${f}\n"
103
+ done <<< "$MODIFIED_FILES"
104
+ fi
105
+ if [ -n "$DELETED_FILES" ]; then
106
+ [ -n "$BODY" ] && BODY="${BODY}\n"
107
+ BODY="${BODY}Deleted:\n"
108
+ while IFS= read -r f; do
109
+ [ -n "$f" ] && BODY="${BODY} - ${f}\n"
110
+ done <<< "$DELETED_FILES"
111
+ fi
112
+
113
+ FULL_MSG="${SUBJECT}\n\n${BODY}"
114
+
115
+ git commit -m "$(printf "%b" "$FULL_MSG")" --no-verify 2>/dev/null
116
+
117
+ # Push to remote if available
118
+ REMOTE=$(git remote 2>/dev/null | head -1)
119
+ if [ -n "$REMOTE" ]; then
120
+ git push "$REMOTE" "$BRANCH" --no-verify 2>/dev/null
121
+ fi
122
+
123
+ exit 0
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ # 새 요청 시작 시 agent 상태 파일 초기화
3
+ # @hook-event UserPromptSubmit||
4
+
5
+ rm -f /tmp/claude-agents/* 2>/dev/null
6
+ exit 0
@@ -44,6 +44,5 @@ process.stdin.on('end', () => {
44
44
  // Invalid input — pass through
45
45
  }
46
46
 
47
- process.stdout.write(data);
48
47
  process.exit(0);
49
48
  });
@@ -32,7 +32,6 @@ process.stdin.on("end", () => {
32
32
  if (filePath && /\.(ts|tsx)$/.test(filePath)) {
33
33
  const resolvedPath = path.resolve(filePath);
34
34
  if (!fs.existsSync(resolvedPath)) {
35
- process.stdout.write(data);
36
35
  process.exit(0);
37
36
  }
38
37
  // Find nearest tsconfig.json by walking up (max 20 levels to prevent infinite loop)
@@ -91,6 +90,5 @@ process.stdin.on("end", () => {
91
90
  // Invalid input — pass through
92
91
  }
93
92
 
94
- process.stdout.write(data);
95
93
  process.exit(0);
96
94
  });
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+ # Skill 툴 실행 추적
3
+ # @hook-event PreToolUse|Skill|pre
4
+ # @hook-event PostToolUse|Skill|post
5
+
6
+ SKILL_STATE_FILE="/tmp/claude-active-skill.txt"
7
+
8
+ EVENT="${1:-post}"
9
+ INPUT=$(cat)
10
+
11
+ if [ "$EVENT" = "pre" ]; then
12
+ SKILL_NAME=$(echo "$INPUT" | python3 -c "
13
+ import sys, json
14
+ try:
15
+ d = json.load(sys.stdin)
16
+ print(d.get('tool_input', {}).get('skill', '') or '')
17
+ except:
18
+ print('')
19
+ " 2>/dev/null)
20
+
21
+ if [ -n "$SKILL_NAME" ]; then
22
+ echo "$SKILL_NAME" > "$SKILL_STATE_FILE"
23
+ fi
24
+ else
25
+ rm -f "$SKILL_STATE_FILE"
26
+ fi
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # 응답 완료 시 macOS 알림 발송 — 마지막 커밋 메시지를 요약으로 표시
3
+ # @hook-event Stop||
4
+
5
+ WORK_DIR=$(echo "$CLAUDE_HOOK_INPUT" | jq -r '.cwd // empty' 2>/dev/null)
6
+ if [ -z "$WORK_DIR" ]; then
7
+ WORK_DIR="$(pwd)"
8
+ fi
9
+
10
+ # 마지막 git 커밋 메시지를 알림 본문으로 사용
11
+ MSG=""
12
+ if git -C "$WORK_DIR" rev-parse --is-inside-work-tree &>/dev/null; then
13
+ MSG=$(git -C "$WORK_DIR" log --oneline -1 --format="%s" 2>/dev/null)
14
+ fi
15
+
16
+ if [ -z "$MSG" ]; then
17
+ MSG="응답이 완료됐습니다."
18
+ fi
19
+
20
+ osascript -e "display notification \"${MSG}\" with title \"Claude Code\" subtitle \"응답 완료\" sound name \"Glass\""
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env bash
2
+ # Docs:
3
+ # - https://code.claude.com/docs/en/statusline
4
+ # - https://code.claude.com/docs/en/settings
5
+ #
6
+ # Notes:
7
+ # - Claude Code reruns the statusline when conversation state changes, debounced at about 300ms.
8
+ # - `agent.name` is officially provided.
9
+ # - Active skills are not officially exposed in the statusline JSON, so `skills` below is a best-effort
10
+ # inference from recent `/skill-name` or `$skill-name` mentions in the transcript.
11
+
12
+ if ! command -v jq >/dev/null 2>&1; then
13
+ printf '[--] | 📁 -- | default\n'
14
+ printf '◉ CTX unavailable | 🤖 main | 🧩 --\n'
15
+ exit 0
16
+ fi
17
+
18
+ input=$(cat 2>/dev/null)
19
+ [ -n "$input" ] || exit 0
20
+
21
+ repeat_char() {
22
+ local count="$1"
23
+ local char="$2"
24
+ local out=""
25
+ while [ "$count" -gt 0 ]; do
26
+ out="${out}${char}"
27
+ count=$((count - 1))
28
+ done
29
+ printf '%s' "$out"
30
+ }
31
+
32
+ shorten() {
33
+ local text="$1"
34
+ local max="${2:-24}"
35
+ if [ "${#text}" -le "$max" ]; then
36
+ printf '%s' "$text"
37
+ else
38
+ printf '%s…' "${text:0:max-1}"
39
+ fi
40
+ }
41
+
42
+ current_dir_name() {
43
+ local path="$1"
44
+ path="${path%/}"
45
+
46
+ if [ -z "$path" ]; then
47
+ printf '/'
48
+ elif [ "$path" = "$HOME" ]; then
49
+ printf '~'
50
+ else
51
+ printf '%s' "${path##*/}"
52
+ fi
53
+ }
54
+
55
+ current_effort() {
56
+ if [ "${MAX_THINKING_TOKENS:-}" = "0" ]; then
57
+ printf 'off'
58
+ elif [ -n "${CLAUDE_CODE_EFFORT_LEVEL:-}" ]; then
59
+ printf '%s' "$CLAUDE_CODE_EFFORT_LEVEL"
60
+ elif [ "${CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING:-}" = "1" ] && [ -n "${MAX_THINKING_TOKENS:-}" ]; then
61
+ printf 'think:%s' "$MAX_THINKING_TOKENS"
62
+ elif [ "${CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING:-}" = "1" ]; then
63
+ printf 'fixed'
64
+ else
65
+ printf 'default'
66
+ fi
67
+ }
68
+
69
+ infer_skills() {
70
+ local transcript="$1"
71
+ local skills
72
+
73
+ if [ -z "$transcript" ] || [ ! -f "$transcript" ]; then
74
+ printf '%s' '--'
75
+ return
76
+ fi
77
+
78
+ skills=$(
79
+ tail -n 200 "$transcript" 2>/dev/null |
80
+ jq -r '.. | .text? // empty' 2>/dev/null |
81
+ grep -Eo '(^|[[:space:]])(\$|/)[A-Za-z][A-Za-z0-9_-]+' |
82
+ sed -E 's/^[[:space:]]+//; s#^[/$]##' |
83
+ grep -Evi '^(add-dir|agents|bug|clear|compact|config|cost|doctor|help|init|login|logout|mcp|memory|model|permissions|plugin|plugins|pr-comments|review|status|statusline|terminal-setup|vim)$' |
84
+ awk '!seen[$0]++' |
85
+ head -n 3 |
86
+ awk 'BEGIN{ORS=""} {if (NR>1) printf ", "; printf $0}'
87
+ )
88
+
89
+ printf '%s' "${skills:---}"
90
+ }
91
+
92
+ ctx_color() {
93
+ local pct="$1"
94
+ if [ "$pct" -ge 85 ]; then
95
+ printf '\033[38;2;255;107;107m'
96
+ elif [ "$pct" -ge 60 ]; then
97
+ printf '\033[38;2;255;184;77m'
98
+ else
99
+ printf '\033[38;2;92;224;163m'
100
+ fi
101
+ }
102
+
103
+ IFS=$'\t' read -r model dir agent transcript ctx_size cur_in cur_cache_create cur_cache_read used_fallback worktree_branch < <(
104
+ printf '%s' "$input" | jq -r '[
105
+ .model.display_name // .model.id // "--",
106
+ .workspace.current_dir // .cwd // "",
107
+ .agent.name // "main",
108
+ .transcript_path // "",
109
+ (.context_window.context_window_size // 0),
110
+ (.context_window.current_usage.input_tokens? // 0),
111
+ (.context_window.current_usage.cache_creation_input_tokens? // 0),
112
+ (.context_window.current_usage.cache_read_input_tokens? // 0),
113
+ (.context_window.used_percentage // 0 | floor),
114
+ .worktree.branch // ""
115
+ ] | @tsv'
116
+ )
117
+
118
+ dir="${dir:-$PWD}"
119
+ model="${model:---}"
120
+ agent="${agent:-main}"
121
+
122
+ used_tokens=$((cur_in + cur_cache_create + cur_cache_read))
123
+ if [ "$ctx_size" -gt 0 ] && [ "$used_tokens" -gt 0 ]; then
124
+ used_pct=$((used_tokens * 100 / ctx_size))
125
+ else
126
+ used_pct="${used_fallback:-0}"
127
+ fi
128
+
129
+ case "$used_pct" in
130
+ ''|*[!0-9]*)
131
+ used_pct=0
132
+ ;;
133
+ esac
134
+
135
+ [ "$used_pct" -lt 0 ] && used_pct=0
136
+ [ "$used_pct" -gt 100 ] && used_pct=100
137
+
138
+ branch="$worktree_branch"
139
+ if [ -z "$branch" ] && [ -n "$dir" ] && git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
140
+ branch="$(git -C "$dir" symbolic-ref --quiet --short HEAD 2>/dev/null || git -C "$dir" rev-parse --short HEAD 2>/dev/null)"
141
+ fi
142
+
143
+ skills="$(infer_skills "$transcript")"
144
+ effort="$(current_effort)"
145
+
146
+ dir_label="$(shorten "$(current_dir_name "$dir")" 24)"
147
+ branch_label="$(shorten "$branch" 26)"
148
+ agent_label="$(shorten "$agent" 18)"
149
+ skills_label="$(shorten "$skills" 32)"
150
+
151
+ bar_width=20
152
+ filled=$((used_pct * bar_width / 100))
153
+ empty=$((bar_width - filled))
154
+
155
+ RESET=$'\033[0m'
156
+ BOLD=$'\033[1m'
157
+ DIM=$'\033[2m'
158
+
159
+ MODEL_C=$'\033[1;38;2;110;200;255m'
160
+ DIR_C=$'\033[38;2;142;180;255m'
161
+ BRANCH_C=$'\033[38;2;120;230;160m'
162
+ EFFORT_C=$'\033[38;2;255;196;107m'
163
+ AGENT_C=$'\033[38;2;94;234;212m'
164
+ SKILL_C=$'\033[38;2;255;121;198m'
165
+ SEP_C=$'\033[38;2;108;114;134m'
166
+ FRAME_C=$'\033[38;2;96;103;122m'
167
+ TRACK_C=$'\033[38;2;62;68;84m'
168
+ LABEL_C=$'\033[38;2;145;215;255m'
169
+ PCT_C="$(ctx_color "$used_pct")"
170
+
171
+ filled_bar="$(repeat_char "$filled" '█')"
172
+ empty_bar="$(repeat_char "$empty" '░')"
173
+
174
+ line1="${MODEL_C}[${model}]${RESET} ${SEP_C}|${RESET} ${DIR_C}📁 ${dir_label}${RESET}"
175
+ if [ -n "$branch_label" ]; then
176
+ line1="${line1} ${SEP_C}|${RESET} ${BRANCH_C}⎇ ${branch_label}${RESET}"
177
+ fi
178
+ line1="${line1} ${SEP_C}|${RESET} ${EFFORT_C}${effort}${RESET}"
179
+
180
+ line2="${LABEL_C}◉ CTX ${FRAME_C}▕${PCT_C}${filled_bar}${TRACK_C}${empty_bar}${FRAME_C}▏ ${PCT_C}${used_pct}%${RESET}"
181
+ line2="${line2} ${SEP_C}|${RESET} ${AGENT_C}🤖 ${agent_label}${RESET}"
182
+ line2="${line2} ${SEP_C}|${RESET} ${SKILL_C}🧩 ${skills_label}${RESET}"
183
+
184
+ printf '%b\n' "$line1"
185
+ printf '%b\n' "$line2"
186
+
@@ -72,17 +72,47 @@ async function getCommands() {
72
72
  }
73
73
  async function getHooks() {
74
74
  const dir = join(getAssetsDir(), "hooks");
75
- const files = (await readdir(dir)).filter((f) => f.endsWith(".js"));
75
+ const allFiles = await readdir(dir);
76
+ const files = allFiles.filter((f) => f.endsWith(".js") || f.endsWith(".sh"));
76
77
  const items = [];
77
78
  for (const file of files) {
78
79
  const content = await readFile(join(dir, file), "utf-8");
79
- const descMatch = content.match(/\*\s+PostToolUse Hook:\s*(.+)/);
80
- const description = descMatch ? descMatch[1].trim() : file;
81
- const hookType = "PostToolUse";
82
- let matcher = "Edit";
83
- if (file.includes("typecheck")) matcher = "Edit|Write";
84
- if (file.includes("format")) matcher = "Edit|Write";
85
- items.push({ name: file, description, file, hookType, matcher });
80
+ if (file.endsWith(".js")) {
81
+ const descMatch = content.match(/\*\s+PostToolUse Hook:\s*(.+)/);
82
+ const description = descMatch ? descMatch[1].trim() : file;
83
+ const hookType = "PostToolUse";
84
+ let matcher = "Edit";
85
+ if (file.includes("typecheck")) matcher = "Edit|Write";
86
+ if (file.includes("format")) matcher = "Edit|Write";
87
+ items.push({ name: file, description, file, hookType, matcher });
88
+ } else {
89
+ const lines = content.split("\n");
90
+ const descLine = lines.find(
91
+ (l) => l.startsWith("#") && !l.startsWith("#!") && !l.includes("@hook-event")
92
+ );
93
+ const description = descLine ? descLine.replace(/^#\s*/, "") : file;
94
+ const eventRegex = /^# @hook-event ([^|]+)\|([^|]*)\|?(.*)$/gm;
95
+ const registrations = [];
96
+ let match;
97
+ while ((match = eventRegex.exec(content)) !== null) {
98
+ registrations.push({
99
+ hookType: match[1].trim(),
100
+ matcher: match[2].trim(),
101
+ args: match[3].trim() || void 0
102
+ });
103
+ }
104
+ if (registrations.length === 0) continue;
105
+ const [first] = registrations;
106
+ items.push({
107
+ name: file,
108
+ description,
109
+ file,
110
+ hookType: first.hookType,
111
+ matcher: first.matcher,
112
+ args: first.args,
113
+ registrations: registrations.length > 1 ? registrations : void 0
114
+ });
115
+ }
86
116
  }
87
117
  return items;
88
118
  }
@@ -0,0 +1,37 @@
1
+ import {
2
+ backupIfExists
3
+ } from "./chunk-K25UZZVG.js";
4
+ import {
5
+ copyFileUtil,
6
+ ensureDir
7
+ } from "./chunk-3GUKEMND.js";
8
+ import {
9
+ getAssetsDir
10
+ } from "./chunk-5XOKKPAA.js";
11
+
12
+ // src/installers/rules.ts
13
+ import { join } from "path";
14
+ import { readdir } from "fs/promises";
15
+ async function installRules(categories, targetDir) {
16
+ const srcRulesDir = join(getAssetsDir(), "rules");
17
+ for (const category of categories) {
18
+ const srcCatDir = join(srcRulesDir, category);
19
+ let files;
20
+ try {
21
+ files = (await readdir(srcCatDir)).filter((f) => f.endsWith(".md"));
22
+ } catch {
23
+ continue;
24
+ }
25
+ const destCatDir = join(targetDir, "rules", category);
26
+ await ensureDir(destCatDir);
27
+ for (const file of files) {
28
+ const dest = join(destCatDir, file);
29
+ await backupIfExists(dest);
30
+ await copyFileUtil(join(srcCatDir, file), dest);
31
+ }
32
+ }
33
+ }
34
+
35
+ export {
36
+ installRules
37
+ };
@@ -11,7 +11,7 @@ import {
11
11
 
12
12
  // src/installers/hooks.ts
13
13
  import { join } from "path";
14
- import { readFile, writeFile, access } from "fs/promises";
14
+ import { readFile, writeFile, access, chmod } from "fs/promises";
15
15
  import { constants } from "fs";
16
16
  async function mergeHookSettings(settingsPath, hookType, matcher, command) {
17
17
  let settings = {};
@@ -44,8 +44,18 @@ async function installHooks(hooks, targetDir) {
44
44
  const dest = join(hooksDir, hook.file);
45
45
  await backupIfExists(dest);
46
46
  await copyFileUtil(join(srcDir, hook.file), dest);
47
- const hookCommand = `node ${dest}`;
48
- await mergeHookSettings(settingsPath, hook.hookType, hook.matcher, hookCommand);
47
+ if (hook.file.endsWith(".sh")) {
48
+ await chmod(dest, 493);
49
+ }
50
+ const runtime = hook.file.endsWith(".sh") ? "bash" : "node";
51
+ const regs = hook.registrations ?? [
52
+ { hookType: hook.hookType, matcher: hook.matcher, args: hook.args }
53
+ ];
54
+ for (const reg of regs) {
55
+ const argsStr = reg.args ? ` ${reg.args}` : "";
56
+ const hookCommand = `${runtime} "$CLAUDE_PROJECT_DIR"/.claude/hooks/${hook.file}${argsStr}`;
57
+ await mergeHookSettings(settingsPath, reg.hookType, reg.matcher, hookCommand);
58
+ }
49
59
  }
50
60
  }
51
61
 
@@ -12,7 +12,7 @@ import {
12
12
  getMcpServers,
13
13
  getRuleCategories,
14
14
  getSkills
15
- } from "./chunk-OLLOS3GG.js";
15
+ } from "./chunk-E3INXQNO.js";
16
16
  import {
17
17
  installAgents
18
18
  } from "./chunk-EYY2IZ7N.js";
@@ -24,13 +24,18 @@ import {
24
24
  } from "./chunk-3Y26YU4R.js";
25
25
  import {
26
26
  installHooks
27
- } from "./chunk-3UNN3IBE.js";
28
- import "./chunk-K25UZZVG.js";
29
- import "./chunk-3GUKEMND.js";
27
+ } from "./chunk-W7RWPDBH.js";
30
28
  import {
31
29
  installRules
32
- } from "./chunk-RMUKD7CW.js";
30
+ } from "./chunk-ID643AV4.js";
33
31
  import {
32
+ backupIfExists
33
+ } from "./chunk-K25UZZVG.js";
34
+ import {
35
+ copyFileUtil
36
+ } from "./chunk-3GUKEMND.js";
37
+ import {
38
+ getAssetsDir,
34
39
  getGlobalDir,
35
40
  getProjectDir
36
41
  } from "./chunk-5XOKKPAA.js";
@@ -39,7 +44,20 @@ import {
39
44
  import { checkbox, select, input, confirm } from "@inquirer/prompts";
40
45
  import chalk from "chalk";
41
46
  import ora from "ora";
47
+ import { join as join2 } from "path";
48
+
49
+ // src/installers/statusline.ts
42
50
  import { join } from "path";
51
+ import { chmod } from "fs/promises";
52
+ async function installStatusline() {
53
+ const src = join(getAssetsDir(), "statusline", "statusline.sh");
54
+ const dest = join(getGlobalDir(), "statusline.sh");
55
+ await backupIfExists(dest);
56
+ await copyFileUtil(src, dest);
57
+ await chmod(dest, 493);
58
+ }
59
+
60
+ // src/cli.ts
43
61
  function printBanner() {
44
62
  console.log(
45
63
  chalk.cyan.bold(`
@@ -79,7 +97,8 @@ async function runCli() {
79
97
  { name: "Hooks - Post-tool hooks", value: "hooks" },
80
98
  { name: "Rules - CLAUDE.md rules", value: "rules" },
81
99
  { name: "MCPs - MCP server configs", value: "mcps" },
82
- { name: "ClaudeMd - Behavioral guidelines", value: "claudemd" }
100
+ { name: "ClaudeMd - Behavioral guidelines", value: "claudemd" },
101
+ { name: "Statusline - Terminal statusline script", value: "statusline" }
83
102
  ]
84
103
  });
85
104
  if (selectedCategories.length === 0) {
@@ -95,7 +114,8 @@ async function runCli() {
95
114
  ruleCategories: [],
96
115
  mcps: [],
97
116
  mcpEnvValues: /* @__PURE__ */ new Map(),
98
- claudemds: []
117
+ claudemds: [],
118
+ statusline: false
99
119
  };
100
120
  const skippedMcps = /* @__PURE__ */ new Map();
101
121
  if (selectedCategories.includes("agents")) {
@@ -164,6 +184,9 @@ async function runCli() {
164
184
  }))
165
185
  });
166
186
  }
187
+ if (selectedCategories.includes("statusline")) {
188
+ plan.statusline = true;
189
+ }
167
190
  if (selectedCategories.includes("mcps")) {
168
191
  const allMcps = await getMcpServers();
169
192
  plan.mcps = await checkbox({
@@ -213,9 +236,10 @@ async function runCli() {
213
236
  if (plan.hooks.length) console.log(` Hooks : ${plan.hooks.map((h) => h.name).join(", ")}`);
214
237
  if (plan.ruleCategories.length) console.log(` Rules : ${plan.ruleCategories.join(", ")}`);
215
238
  if (plan.mcps.length) console.log(` MCPs : ${plan.mcps.map((m) => m.name).join(", ")}`);
216
- if (plan.claudemds.length) console.log(` ClaudeMd: ${plan.claudemds.map((c) => c.name).join(", ")}`);
239
+ if (plan.claudemds.length) console.log(` ClaudeMd : ${plan.claudemds.map((c) => c.name).join(", ")}`);
240
+ if (plan.statusline) console.log(` Statusline: statusline.sh \u2192 ~/.claude/statusline.sh`);
217
241
  console.log("");
218
- const totalItems = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcps.length + plan.claudemds.length;
242
+ const totalItems = plan.agents.length + plan.skills.length + plan.commands.length + plan.hooks.length + plan.ruleCategories.length + plan.mcps.length + plan.claudemds.length + (plan.statusline ? 1 : 0);
219
243
  if (totalItems === 0) {
220
244
  console.log(chalk.yellow(" Nothing selected. Exiting."));
221
245
  return;
@@ -246,8 +270,7 @@ async function runCli() {
246
270
  }
247
271
  if (plan.ruleCategories.length) {
248
272
  spinner.text = "Installing rules...";
249
- const claudeMd = join(targetDir, "CLAUDE.md");
250
- await installRules(plan.ruleCategories, claudeMd);
273
+ await installRules(plan.ruleCategories, targetDir);
251
274
  }
252
275
  if (plan.mcps.length) {
253
276
  spinner.text = "Installing MCP servers...";
@@ -257,15 +280,20 @@ async function runCli() {
257
280
  spinner.text = "Installing claudemd...";
258
281
  await installClaudemd(plan.claudemds, targetDir);
259
282
  }
283
+ if (plan.statusline) {
284
+ spinner.text = "Installing statusline...";
285
+ await installStatusline();
286
+ }
260
287
  spinner.succeed(chalk.green("Installation complete!"));
261
288
  console.log("");
262
- if (plan.agents.length) console.log(` ${chalk.green("\u2713")} ${plan.agents.length} agent(s) \u2192 ${join(targetDir, "agents")}`);
263
- if (plan.skills.length) console.log(` ${chalk.green("\u2713")} ${plan.skills.length} skill(s) \u2192 ${join(targetDir, "skills")}`);
264
- if (plan.commands.length) console.log(` ${chalk.green("\u2713")} ${plan.commands.length} command(s) \u2192 ${join(targetDir, "commands")}`);
265
- if (plan.hooks.length) console.log(` ${chalk.green("\u2713")} ${plan.hooks.length} hook(s) \u2192 ${join(targetDir, "hooks")}`);
266
- if (plan.ruleCategories.length) console.log(` ${chalk.green("\u2713")} Rules appended \u2192 ${join(targetDir, "CLAUDE.md")}`);
289
+ if (plan.agents.length) console.log(` ${chalk.green("\u2713")} ${plan.agents.length} agent(s) \u2192 ${join2(targetDir, "agents")}`);
290
+ if (plan.skills.length) console.log(` ${chalk.green("\u2713")} ${plan.skills.length} skill(s) \u2192 ${join2(targetDir, "skills")}`);
291
+ if (plan.commands.length) console.log(` ${chalk.green("\u2713")} ${plan.commands.length} command(s) \u2192 ${join2(targetDir, "commands")}`);
292
+ if (plan.hooks.length) console.log(` ${chalk.green("\u2713")} ${plan.hooks.length} hook(s) \u2192 ${join2(targetDir, "hooks")}`);
293
+ if (plan.ruleCategories.length) console.log(` ${chalk.green("\u2713")} Rules copied \u2192 ${join2(targetDir, "rules")}`);
267
294
  if (plan.mcps.length) console.log(` ${chalk.green("\u2713")} ${plan.mcps.length} MCP server(s) \u2192 ${scope === "global" ? "~/.claude.json" : "./.claude.json"}`);
268
- if (plan.claudemds.length) console.log(` ${chalk.green("\u2713")} ClaudeMd appended \u2192 ${join(targetDir, "CLAUDE.md")}`);
295
+ if (plan.claudemds.length) console.log(` ${chalk.green("\u2713")} ClaudeMd appended \u2192 ${join2(targetDir, "CLAUDE.md")}`);
296
+ if (plan.statusline) console.log(` ${chalk.green("\u2713")} Statusline installed \u2192 ~/.claude/statusline.sh`);
269
297
  console.log("");
270
298
  if (skippedMcps.size > 0) {
271
299
  console.log(chalk.yellow(" \u26A0 The following MCP servers have placeholder env vars:"));
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  installHooks
3
- } from "./chunk-3UNN3IBE.js";
3
+ } from "./chunk-W7RWPDBH.js";
4
4
  import "./chunk-K25UZZVG.js";
5
5
  import "./chunk-3GUKEMND.js";
6
6
  import "./chunk-5XOKKPAA.js";
package/dist/index.js CHANGED
@@ -60,18 +60,18 @@ Installing all to ${targetDir}...
60
60
  { installMcps },
61
61
  { installClaudemd }
62
62
  ] = await Promise.all([
63
- import("./registry-BU55RMHU.js"),
64
- import("./registry-BU55RMHU.js"),
65
- import("./registry-BU55RMHU.js"),
66
- import("./registry-BU55RMHU.js"),
67
- import("./registry-BU55RMHU.js"),
68
- import("./registry-BU55RMHU.js"),
69
- import("./registry-BU55RMHU.js"),
63
+ import("./registry-KRLOB4TH.js"),
64
+ import("./registry-KRLOB4TH.js"),
65
+ import("./registry-KRLOB4TH.js"),
66
+ import("./registry-KRLOB4TH.js"),
67
+ import("./registry-KRLOB4TH.js"),
68
+ import("./registry-KRLOB4TH.js"),
69
+ import("./registry-KRLOB4TH.js"),
70
70
  import("./agents-AEKT67A6.js"),
71
71
  import("./skills-ULMW3UCM.js"),
72
72
  import("./commands-P5LILVZ5.js"),
73
- import("./hooks-IIG2XK4I.js"),
74
- import("./rules-2CPBVNNJ.js"),
73
+ import("./hooks-A2WQ2LGG.js"),
74
+ import("./rules-EFSJ3L3A.js"),
75
75
  import("./mcps-67Q7TBGW.js"),
76
76
  import("./claudemd-KKQ2DL7P.js")
77
77
  ]);
@@ -93,8 +93,7 @@ Installing all to ${targetDir}...
93
93
  spinner.text = "Installing hooks...";
94
94
  await installHooks(hooks, targetDir);
95
95
  spinner.text = "Installing rules...";
96
- const claudeMd = join2(targetDir, "CLAUDE.md");
97
- await installRules(ruleCategories.map((rc) => rc.category), claudeMd);
96
+ await installRules(ruleCategories.map((rc) => rc.category), targetDir);
98
97
  spinner.text = "Installing MCP servers...";
99
98
  await installMcps(mcps, scope, /* @__PURE__ */ new Map());
100
99
  spinner.text = "Installing claudemd...";
@@ -125,7 +124,7 @@ async function main() {
125
124
  return;
126
125
  }
127
126
  if (args.includes("--uninstall") || args.includes("-u")) {
128
- const { runUninstallCli } = await import("./uninstall-cli-VCOZGDBM.js");
127
+ const { runUninstallCli } = await import("./uninstall-cli-GLYJG5V2.js");
129
128
  await runUninstallCli();
130
129
  return;
131
130
  }
@@ -135,7 +134,7 @@ async function main() {
135
134
  await installAll(scope);
136
135
  return;
137
136
  }
138
- const { runCli } = await import("./cli-D2Q5QUO7.js");
137
+ const { runCli } = await import("./cli-KZYBSIXO.js");
139
138
  await runCli();
140
139
  }
141
140
  main().catch((err) => {
@@ -6,7 +6,7 @@ import {
6
6
  getMcpServers,
7
7
  getRuleCategories,
8
8
  getSkills
9
- } from "./chunk-OLLOS3GG.js";
9
+ } from "./chunk-E3INXQNO.js";
10
10
  import "./chunk-5XOKKPAA.js";
11
11
  export {
12
12
  getAgents,
@@ -0,0 +1,9 @@
1
+ import {
2
+ installRules
3
+ } from "./chunk-ID643AV4.js";
4
+ import "./chunk-K25UZZVG.js";
5
+ import "./chunk-3GUKEMND.js";
6
+ import "./chunk-5XOKKPAA.js";
7
+ export {
8
+ installRules
9
+ };
@@ -6,7 +6,7 @@ import {
6
6
  getMcpServers,
7
7
  getRuleCategories,
8
8
  getSkills
9
- } from "./chunk-OLLOS3GG.js";
9
+ } from "./chunk-E3INXQNO.js";
10
10
  import {
11
11
  getAssetsDir,
12
12
  getGlobalDir,
@@ -17,7 +17,6 @@ import {
17
17
  import { checkbox, select, confirm } from "@inquirer/prompts";
18
18
  import chalk from "chalk";
19
19
  import ora from "ora";
20
- import { join as join9 } from "path";
21
20
 
22
21
  // src/scanner.ts
23
22
  import { readdir, readFile, access } from "fs/promises";
@@ -76,38 +75,17 @@ async function getInstalledHooks(targetDir) {
76
75
  return registryHooks.filter((h) => installedFiles.includes(h.file));
77
76
  }
78
77
  async function getInstalledRuleCategories(targetDir) {
79
- const claudeMdPath = join(targetDir, "CLAUDE.md");
80
- let content = "";
78
+ const rulesDir = join(targetDir, "rules");
79
+ let installedDirs = [];
81
80
  try {
82
- await access(claudeMdPath, constants.F_OK);
83
- content = await readFile(claudeMdPath, "utf-8");
81
+ const entries = await readdir(rulesDir, { withFileTypes: true });
82
+ installedDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
84
83
  } catch {
85
84
  return [];
86
85
  }
87
- const installedHeaders = /* @__PURE__ */ new Set();
88
- for (const line of content.split("\n")) {
89
- const match = line.match(/^(#{1,2}\s+.+)/);
90
- if (match) installedHeaders.add(match[1].trim());
91
- }
92
86
  const allCategories = await getRuleCategories();
93
- const installedCategories = [];
94
- for (const rc of allCategories) {
95
- const catDir = join(getAssetsDir(), "rules", rc.category);
96
- let found = false;
97
- for (const file of rc.files) {
98
- try {
99
- const fileContent = await readFile(join(catDir, file), "utf-8");
100
- const headerMatch = fileContent.match(/^(#{1,2}\s+.+)/m);
101
- if (headerMatch && installedHeaders.has(headerMatch[1].trim())) {
102
- found = true;
103
- break;
104
- }
105
- } catch {
106
- }
107
- }
108
- if (found) installedCategories.push(rc.category);
109
- }
110
- return installedCategories;
87
+ const registryCategoryNames = new Set(allCategories.map((rc) => rc.category));
88
+ return installedDirs.filter((d) => registryCategoryNames.has(d));
111
89
  }
112
90
  async function getInstalledClaudemd(targetDir) {
113
91
  const claudeMdPath = join(targetDir, "CLAUDE.md");
@@ -206,7 +184,7 @@ async function uninstallCommands(commands, targetDir) {
206
184
  import { join as join5 } from "path";
207
185
  import { rm as rm4, readFile as readFile2, writeFile, access as access5 } from "fs/promises";
208
186
  import { constants as constants5 } from "fs";
209
- async function removeHookFromSettings(settingsPath, hookCommand) {
187
+ async function removeHookFromSettings(settingsPath, hookFileName) {
210
188
  let settings = {};
211
189
  try {
212
190
  await access5(settingsPath, constants5.F_OK);
@@ -216,11 +194,12 @@ async function removeHookFromSettings(settingsPath, hookCommand) {
216
194
  return;
217
195
  }
218
196
  if (!settings.hooks) return;
197
+ const suffix = `/hooks/${hookFileName}`;
219
198
  for (const hookType of Object.keys(settings.hooks)) {
220
199
  const matchers = settings.hooks[hookType];
221
200
  for (let i = matchers.length - 1; i >= 0; i--) {
222
201
  const matcher = matchers[i];
223
- matcher.hooks = matcher.hooks.filter((h) => h.command !== hookCommand);
202
+ matcher.hooks = matcher.hooks.filter((h) => !h.command.includes(suffix));
224
203
  if (matcher.hooks.length === 0) {
225
204
  matchers.splice(i, 1);
226
205
  }
@@ -239,8 +218,7 @@ async function uninstallHooks(hooks, targetDir) {
239
218
  const settingsPath = join5(targetDir, "settings.json");
240
219
  for (const hook of hooks) {
241
220
  const filePath = join5(hooksDir, hook.file);
242
- const hookCommand = `node ${filePath}`;
243
- await removeHookFromSettings(settingsPath, hookCommand);
221
+ await removeHookFromSettings(settingsPath, hook.file);
244
222
  try {
245
223
  await access5(filePath, constants5.F_OK);
246
224
  await rm4(filePath);
@@ -250,69 +228,47 @@ async function uninstallHooks(hooks, targetDir) {
250
228
  }
251
229
 
252
230
  // src/uninstallers/rules.ts
253
- import { readFile as readFile3, writeFile as writeFile2, access as access6 } from "fs/promises";
254
- import { constants as constants6 } from "fs";
231
+ import { readdir as readdir2, rm as rm5 } from "fs/promises";
255
232
  import { join as join6 } from "path";
256
- async function uninstallRules(categories, claudeMdPath) {
257
- let existing = "";
258
- try {
259
- await access6(claudeMdPath, constants6.F_OK);
260
- existing = await readFile3(claudeMdPath, "utf-8");
261
- } catch {
262
- return;
263
- }
264
- const headersToRemove = /* @__PURE__ */ new Set();
233
+ async function uninstallRules(categories, targetDir) {
234
+ const assetsRulesDir = join6(getAssetsDir(), "rules");
265
235
  for (const category of categories) {
266
- const catDir = join6(getAssetsDir(), "rules", category);
267
- let files;
236
+ const srcCatDir = join6(assetsRulesDir, category);
237
+ let assetFiles;
268
238
  try {
269
- const { readdir: readdir2 } = await import("fs/promises");
270
- files = (await readdir2(catDir)).filter((f) => f.endsWith(".md"));
239
+ assetFiles = (await readdir2(srcCatDir)).filter((f) => f.endsWith(".md"));
271
240
  } catch {
272
241
  continue;
273
242
  }
274
- for (const file of files) {
243
+ const destCatDir = join6(targetDir, "rules", category);
244
+ for (const file of assetFiles) {
275
245
  try {
276
- const content = await readFile3(join6(catDir, file), "utf-8");
277
- const headerMatch = content.match(/^(#{1,2}\s+.+)/m);
278
- if (headerMatch) {
279
- headersToRemove.add(headerMatch[1].trim());
280
- }
246
+ await rm5(join6(destCatDir, file));
281
247
  } catch {
282
248
  }
283
249
  }
284
- }
285
- if (headersToRemove.size === 0) return;
286
- const lines = existing.split("\n");
287
- const result = [];
288
- let inRemovedSection = false;
289
- let currentDepth = 0;
290
- for (const line of lines) {
291
- const headerMatch = line.match(/^(#{1,2})\s+/);
292
- if (headerMatch) {
293
- const depth = headerMatch[1].length;
294
- const trimmed = line.trim();
295
- if (headersToRemove.has(trimmed)) {
296
- inRemovedSection = true;
297
- currentDepth = depth;
298
- continue;
299
- }
300
- if (inRemovedSection && depth <= currentDepth) {
301
- inRemovedSection = false;
250
+ try {
251
+ const remaining = await readdir2(destCatDir);
252
+ if (remaining.length === 0) {
253
+ await rm5(destCatDir, { recursive: true });
302
254
  }
255
+ } catch {
303
256
  }
304
- if (!inRemovedSection) {
305
- result.push(line);
257
+ }
258
+ const rulesDir = join6(targetDir, "rules");
259
+ try {
260
+ const remaining = await readdir2(rulesDir);
261
+ if (remaining.length === 0) {
262
+ await rm5(rulesDir, { recursive: true });
306
263
  }
264
+ } catch {
307
265
  }
308
- const cleaned = result.join("\n").replace(/\n{3,}/g, "\n\n");
309
- await writeFile2(claudeMdPath, cleaned, "utf-8");
310
266
  }
311
267
 
312
268
  // src/uninstallers/mcps.ts
313
269
  import { join as join7 } from "path";
314
- import { readFile as readFile4, writeFile as writeFile3, access as access7 } from "fs/promises";
315
- import { constants as constants7 } from "fs";
270
+ import { readFile as readFile3, writeFile as writeFile2, access as access6 } from "fs/promises";
271
+ import { constants as constants6 } from "fs";
316
272
  import { homedir as homedir2 } from "os";
317
273
  function getClaudeJsonPath2(scope) {
318
274
  if (scope === "global") {
@@ -324,8 +280,8 @@ async function uninstallMcps(serverNames, scope) {
324
280
  const filePath = getClaudeJsonPath2(scope);
325
281
  let claudeJson = {};
326
282
  try {
327
- await access7(filePath, constants7.F_OK);
328
- const content = await readFile4(filePath, "utf-8");
283
+ await access6(filePath, constants6.F_OK);
284
+ const content = await readFile3(filePath, "utf-8");
329
285
  claudeJson = JSON.parse(content);
330
286
  } catch {
331
287
  return;
@@ -334,26 +290,26 @@ async function uninstallMcps(serverNames, scope) {
334
290
  for (const name of serverNames) {
335
291
  delete claudeJson.mcpServers[name];
336
292
  }
337
- await writeFile3(filePath, JSON.stringify(claudeJson, null, 2) + "\n", "utf-8");
293
+ await writeFile2(filePath, JSON.stringify(claudeJson, null, 2) + "\n", "utf-8");
338
294
  }
339
295
 
340
296
  // src/uninstallers/claudemd.ts
341
- import { readFile as readFile5, writeFile as writeFile4, access as access8 } from "fs/promises";
342
- import { constants as constants8 } from "fs";
297
+ import { readFile as readFile4, writeFile as writeFile3, access as access7 } from "fs/promises";
298
+ import { constants as constants7 } from "fs";
343
299
  import { join as join8 } from "path";
344
300
  async function uninstallClaudemd(items, targetDir) {
345
301
  const claudeMdPath = join8(targetDir, "CLAUDE.md");
346
302
  let existing = "";
347
303
  try {
348
- await access8(claudeMdPath, constants8.F_OK);
349
- existing = await readFile5(claudeMdPath, "utf-8");
304
+ await access7(claudeMdPath, constants7.F_OK);
305
+ existing = await readFile4(claudeMdPath, "utf-8");
350
306
  } catch {
351
307
  return;
352
308
  }
353
309
  const headersToRemove = /* @__PURE__ */ new Set();
354
310
  for (const item of items) {
355
311
  try {
356
- const raw = await readFile5(join8(getAssetsDir(), "claudemd", item.file), "utf-8");
312
+ const raw = await readFile4(join8(getAssetsDir(), "claudemd", item.file), "utf-8");
357
313
  const content = raw.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
358
314
  const headerMatch = content.match(/^(#{1,2}\s+.+)/m);
359
315
  if (headerMatch) {
@@ -386,7 +342,7 @@ async function uninstallClaudemd(items, targetDir) {
386
342
  }
387
343
  }
388
344
  const cleaned = result.join("\n").replace(/\n{3,}/g, "\n\n");
389
- await writeFile4(claudeMdPath, cleaned, "utf-8");
345
+ await writeFile3(claudeMdPath, cleaned, "utf-8");
390
346
  }
391
347
 
392
348
  // src/uninstall-cli.ts
@@ -613,8 +569,7 @@ async function runUninstallCli() {
613
569
  }
614
570
  if (plan.ruleCategories.length) {
615
571
  spinner.text = "Removing rules...";
616
- const claudeMd = join9(targetDir, "CLAUDE.md");
617
- await uninstallRules(plan.ruleCategories, claudeMd);
572
+ await uninstallRules(plan.ruleCategories, targetDir);
618
573
  }
619
574
  if (plan.mcpNames.length) {
620
575
  spinner.text = "Removing MCP servers...";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juho0719/cckit",
3
- "version": "0.2.1",
3
+ "version": "0.2.4",
4
4
  "description": "Claude Code Harness Installer - Interactive CLI for installing Claude Code agents, skills, commands, hooks, rules, and MCP servers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,44 +0,0 @@
1
- import {
2
- getAssetsDir
3
- } from "./chunk-5XOKKPAA.js";
4
-
5
- // src/installers/rules.ts
6
- import { join } from "path";
7
- import { readFile, writeFile, access } from "fs/promises";
8
- import { constants } from "fs";
9
- async function installRules(categories, targetClaudeMd) {
10
- const rulesDir = join(getAssetsDir(), "rules");
11
- let existing = "";
12
- try {
13
- await access(targetClaudeMd, constants.F_OK);
14
- existing = await readFile(targetClaudeMd, "utf-8");
15
- } catch {
16
- }
17
- const toAppend = [];
18
- for (const category of categories) {
19
- const catDir = join(rulesDir, category);
20
- let files;
21
- try {
22
- const { readdir } = await import("fs/promises");
23
- files = (await readdir(catDir)).filter((f) => f.endsWith(".md"));
24
- } catch {
25
- continue;
26
- }
27
- for (const file of files) {
28
- const content = await readFile(join(catDir, file), "utf-8");
29
- const headerMatch = content.match(/^#{1,2}\s+(.+)/m);
30
- if (headerMatch) {
31
- const header = headerMatch[0];
32
- if (existing.includes(header)) continue;
33
- }
34
- toAppend.push(content.trim());
35
- }
36
- }
37
- if (toAppend.length === 0) return;
38
- const appendContent = (existing.endsWith("\n") ? "" : "\n") + toAppend.join("\n\n") + "\n";
39
- await writeFile(targetClaudeMd, existing + appendContent, "utf-8");
40
- }
41
-
42
- export {
43
- installRules
44
- };
@@ -1,7 +0,0 @@
1
- import {
2
- installRules
3
- } from "./chunk-RMUKD7CW.js";
4
- import "./chunk-5XOKKPAA.js";
5
- export {
6
- installRules
7
- };