claude-pro-minmax 1.2.1 → 1.3.1
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/README.ko.md +53 -6
- package/README.md +53 -6
- package/install.sh +28 -1
- package/lib/cli.js +131 -5
- package/lib/rtk.js +381 -0
- package/package.json +1 -1
- package/scripts/hooks/README.ko.md +10 -0
- package/scripts/hooks/README.md +10 -1
package/README.ko.md
CHANGED
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
CPMM은 모델 라우팅, 출력 제어, 로컬 안전장치로 리셋 전까지 더 많은 검증된 작업을 완료하도록 돕습니다.
|
|
16
16
|
|
|
17
17
|
> **설치 완료했다면 여기서 시작하세요: [사용자 가이드](docs/USER-MANUAL.ko.md)**
|
|
18
|
+
>
|
|
19
|
+
> **New in v1.3.1:** RTK를 이미 켜둔 사용자에 대해 `cpmm setup`이 관리된 hook 순서와 timeout을 자동 복원합니다.
|
|
18
20
|
|
|
19
21
|
---
|
|
20
22
|
|
|
21
23
|
> [!TIP]
|
|
22
24
|
> **🚀 3초 요약: 왜 이걸 써야 하나요?**
|
|
23
25
|
> 1. **배치 실행:** `/do`로 구현-검증을 한 흐름에서 처리하고, 필요할 때만 `/do-sonnet`/`/do-opus`로 승격합니다.
|
|
24
|
-
> 2. **출력 비용 제어:** 응답
|
|
26
|
+
> 2. **출력 비용 제어:** 응답 예산, CLI 필터링, 그리고 optional RTK로 Bash 출력이 Claude 입력 컨텍스트를 불필요하게 키우지 않도록 합니다.
|
|
25
27
|
> 3. **로컬 안전장치:** 로컬 Hook + 원자적 롤백으로 실패 시 빠르게 복구합니다.
|
|
26
28
|
|
|
27
29
|
---
|
|
@@ -50,16 +52,22 @@ cpmm setup
|
|
|
50
52
|
cpmm doctor
|
|
51
53
|
```
|
|
52
54
|
|
|
55
|
+
> **v1.3.1 참고:** `cpmm setup`은 지원 환경에서 RTK 설치를 계속 시도합니다. RTK 활성화는 여전히 opt-in이지만, 한 번 켠 뒤에는 CPMM이 관리된 hook 순서와 timeout을 자동 복원합니다.
|
|
56
|
+
|
|
53
57
|
의존성 정책:
|
|
54
58
|
- `required`: `jq`, `mgrep`, `tmux`
|
|
59
|
+
- `optional` (자동 설치 시도): `rtk`
|
|
55
60
|
- `optional` (확인만): `claude` (사전 설치 가정)
|
|
56
|
-
- 자동 설치
|
|
61
|
+
- 도구별 자동 설치 경로:
|
|
62
|
+
- `mgrep`: `npm`
|
|
63
|
+
- `rtk`: `brew` 또는 upstream `curl` installer
|
|
64
|
+
- `jq`, `tmux`: `brew` (macOS) 또는 Linux 패키지 매니저 `apt-get`, `dnf`, `pacman`, `apk`
|
|
57
65
|
- macOS에서 Homebrew가 없으면 설치 명령을 안내합니다
|
|
58
66
|
|
|
59
67
|
### 4. 커스텀 & 업데이트 정책
|
|
60
68
|
|
|
61
69
|
- `cpmm setup`은 누락된 의존성을 설치한 뒤, CPMM 설정(설정 파일 복사, 언어 선택, Perplexity 설정)까지 진행합니다.
|
|
62
|
-
- `cpmm doctor`는 수정 없이 의존성
|
|
70
|
+
- `cpmm doctor`는 수정 없이 의존성 상태와 RTK hook 상태를 확인합니다.
|
|
63
71
|
- 재실행 시 CPMM 관리 파일은 최신 버전으로 교체되고, 사용자 데이터는 보존됩니다.
|
|
64
72
|
|
|
65
73
|
```text
|
|
@@ -87,13 +95,51 @@ cpmm doctor
|
|
|
87
95
|
```
|
|
88
96
|
|
|
89
97
|
> **핵심 규칙 2가지:**
|
|
90
|
-
> 1. 글로벌 커스텀은 `settings.local.json
|
|
98
|
+
> 1. 글로벌 커스텀은 일반적으로 `settings.local.json`에 둡니다. `settings.json`은 CPMM 관리 대상이라 업데이트 시 덮어쓰기되므로, RTK 같은 third-party hook을 여기에 넣었다면 업데이트 후 다시 확인해야 합니다.
|
|
91
99
|
> 2. 커스텀 명령어/규칙은 프로젝트 `.claude/`에 — 글로벌 `commands/`는 CPMM이 관리합니다.
|
|
92
100
|
|
|
93
101
|
프로젝트 초기화 팁:
|
|
94
102
|
- `claude` 실행 전에 `project-templates/`를 참고해 프로젝트를 초기화하세요. (설치기는 `project-templates`를 `~/.claude`로 복사하지 않습니다.)
|
|
95
103
|
|
|
96
|
-
### 5.
|
|
104
|
+
### 5. Bash 명령 출력 필터링 (RTK)
|
|
105
|
+
|
|
106
|
+
RTK는 CPMM이 지원하는 **선택적 Bash 명령 출력 필터링 계층**입니다. `cpmm setup`은 RTK 바이너리 설치를 시도하지만, RTK hook은 기본 활성화하지 않습니다.
|
|
107
|
+
|
|
108
|
+
RTK를 이번 릴리스에 선택 통합으로 넣은 이유는, Bash-heavy 워크플로우에서 긴 명령 출력이 Claude 입력 컨텍스트로 다시 들어가기 전에 이를 줄여 주기 때문입니다. CPMM은 권장 hook 순서를 문서화하고 `cpmm doctor`로 점검하며, 그 순서에서는 CPMM의 critical-action check가 RTK rewrite hook보다 먼저 실행되어야 합니다. 다만 hook 동작을 예측 가능하고 디버깅 가능하게 유지하기 위해 default-on이 아니라 opt-in으로 제공합니다.
|
|
109
|
+
|
|
110
|
+
권장 opt-in 절차:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
rtk init -g --hook-only
|
|
114
|
+
# RTK를 켠 뒤에는 cpmm setup이 관리된 hook 순서와 timeout을 복원
|
|
115
|
+
cpmm setup
|
|
116
|
+
cpmm doctor
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
권장 `PreToolUse` 순서 (`~/.claude/settings.json`):
|
|
120
|
+
- 먼저 CPMM safety hook: `~/.claude/scripts/hooks/critical-action-check.sh` with `timeout: 5`
|
|
121
|
+
- 그 다음 RTK rewrite hook: `~/.claude/hooks/rtk-rewrite.sh` with `timeout: 10`
|
|
122
|
+
|
|
123
|
+
업데이트 참고:
|
|
124
|
+
- `cpmm setup`은 업데이트 시 `~/.claude/settings.json`을 다시 씁니다.
|
|
125
|
+
- `cpmm setup` 전에 RTK가 이미 켜져 있었다면, CPMM이 설정 재작성 뒤에 관리된 RTK hook 순서와 `timeout: 10`을 자동 복원합니다.
|
|
126
|
+
- 관리된 RTK 상태를 확인하려면 setup 뒤에 `cpmm doctor`를 실행하세요.
|
|
127
|
+
|
|
128
|
+
권장 검증:
|
|
129
|
+
- `/hooks`에서 CPMM hook과 RTK hook이 모두 로드되는지 확인
|
|
130
|
+
- 위험 명령이 여전히 CPMM에서 먼저 차단되는지 확인
|
|
131
|
+
- `cpmm doctor` 실행
|
|
132
|
+
- 실제 Bash-heavy 세션 후 `rtk gain --quota --tier pro` 확인
|
|
133
|
+
|
|
134
|
+
공개된 [RTK 통합 사용자 사례](https://github.com/move-hoon/claude-pro-minmax/issues/3)에서는 `rtk gain --quota --tier pro` 기준으로 Bash-heavy 워크플로우에서 `1,664`개 명령 동안 입력 토큰 `8.5M` 절감(`49.4%`)이 보고되었습니다. 절감률은 작업 부하와 세션 형태에 따라 달라질 수 있습니다.
|
|
135
|
+
|
|
136
|
+
롤백:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
rtk init -g --uninstall
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 6. 고급 (선택)
|
|
97
143
|
<details>
|
|
98
144
|
<summary>Perplexity, 언어, 수동 설치 보기</summary>
|
|
99
145
|
|
|
@@ -422,7 +468,7 @@ claude-pro-minmax
|
|
|
422
468
|
|
|
423
469
|
A: Anthropic의 정확한 quota 알고리즘은 공개되지 않았습니다. 세 가지 축으로 최적화합니다:
|
|
424
470
|
- **저비용 모델 우선 경로**: 기본 구현은 Haiku 중심으로 시작하고 필요 시에만 Sonnet/Opus로 승격합니다.
|
|
425
|
-
- **출력 비용 인식**:
|
|
471
|
+
- **출력 비용 인식**: 출력이 많은 턴일수록 비용 부담이 커지는 경향이 있으므로, 응답 예산과 필터링으로 payload를 줄입니다.
|
|
426
472
|
- **작업 흐름 단순화**: `/do`와 `/plan`을 상황에 맞게 분리해 불필요한 고비용 턴을 줄입니다.
|
|
427
473
|
|
|
428
474
|
근거 실측값은 [docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.ko.md](docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.ko.md)를 참고하세요.
|
|
@@ -497,6 +543,7 @@ A: CPMM은 `scripts/snapshot.sh`를 통한 **best-effort 원자적 롤백**을
|
|
|
497
543
|
## 참고 링크
|
|
498
544
|
|
|
499
545
|
- 핵심전략 근거 실험 아카이브: [핵심전략 실험 아카이브](docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.ko.md)
|
|
546
|
+
- 방향성 비교를 위한 외부 역분석 사례: [claude-limits](https://she-llac.com/claude-limits) (Claude 플랜 usage/credits 동작을 설명하려는 비공식 분석)
|
|
500
547
|
- 공식 문서:
|
|
501
548
|
- [Anthropic Pricing](https://docs.anthropic.com/en/docs/about-claude/pricing)
|
|
502
549
|
- [Usage Limit Best Practices](https://support.claude.com/en/articles/9797557-usage-limit-best-practices)
|
package/README.md
CHANGED
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
CPMM helps Pro users complete more verified tasks before reset through model routing, output control, and local safety rails.
|
|
16
16
|
|
|
17
17
|
> **Already installed? Start here: [User Guide](docs/USER-MANUAL.md)**
|
|
18
|
+
>
|
|
19
|
+
> **New in v1.3.1:** `cpmm setup` now restores the managed RTK hook order and timeout for users who already enabled RTK.
|
|
18
20
|
|
|
19
21
|
---
|
|
20
22
|
|
|
21
23
|
> [!TIP]
|
|
22
24
|
> **🚀 3-Second Summary: Why use this?**
|
|
23
25
|
> 1. **Batch Execution:** Use `/do` to keep implementation and verification in one flow, and escalate to `/do-sonnet`/`/do-opus` only when needed.
|
|
24
|
-
> 2. **Output Cost Control:** Use response budgets
|
|
26
|
+
> 2. **Output Cost Control:** Use response budgets, CLI filtering, and optional RTK to keep Bash output from inflating Claude input context.
|
|
25
27
|
> 3. **Local Safety Rails:** Local hooks and atomic rollback help you recover quickly on failure.
|
|
26
28
|
|
|
27
29
|
---
|
|
@@ -50,16 +52,22 @@ cpmm setup
|
|
|
50
52
|
cpmm doctor
|
|
51
53
|
```
|
|
52
54
|
|
|
55
|
+
> **v1.3.1 note:** `cpmm setup` still attempts RTK installation when supported. RTK activation remains opt-in, but once enabled CPMM now restores the managed hook order and timeout automatically.
|
|
56
|
+
|
|
53
57
|
Dependency policy:
|
|
54
58
|
- `required`: `jq`, `mgrep`, `tmux`
|
|
59
|
+
- `optional` (auto-install attempt): `rtk`
|
|
55
60
|
- `optional` (check only): `claude` (assumed pre-installed)
|
|
56
|
-
- auto-install
|
|
61
|
+
- auto-install paths by tool:
|
|
62
|
+
- `mgrep`: `npm`
|
|
63
|
+
- `rtk`: `brew` or upstream `curl` installer
|
|
64
|
+
- `jq`, `tmux`: `brew` (macOS) or Linux package managers `apt-get`, `dnf`, `pacman`, `apk`
|
|
57
65
|
- on macOS without Homebrew, setup prints the Homebrew install command
|
|
58
66
|
|
|
59
67
|
### 4. Customization & Update Policy
|
|
60
68
|
|
|
61
69
|
- `cpmm setup` installs missing dependencies, then configures CPMM (copies config files, language selection, Perplexity setup).
|
|
62
|
-
- `cpmm doctor` checks dependency status without modifying anything.
|
|
70
|
+
- `cpmm doctor` checks dependency status and RTK hook health without modifying anything.
|
|
63
71
|
- Re-running `cpmm setup` replaces CPMM-managed files with the latest version while preserving user data.
|
|
64
72
|
|
|
65
73
|
```text
|
|
@@ -87,13 +95,51 @@ Dependency policy:
|
|
|
87
95
|
```
|
|
88
96
|
|
|
89
97
|
> **Two key rules:**
|
|
90
|
-
> 1. Global customization goes in `settings.local.json
|
|
98
|
+
> 1. Global customization generally goes in `settings.local.json`. `settings.json` is CPMM-managed and overwritten on update; if you opt into RTK or other third-party hooks there, re-check them after updates.
|
|
91
99
|
> 2. Custom commands/rules go in project `.claude/` — global `commands/` is managed by CPMM.
|
|
92
100
|
|
|
93
101
|
Project initialization tip:
|
|
94
102
|
- Before running `claude`, initialize your project with templates in `project-templates/` (not copied into `~/.claude`).
|
|
95
103
|
|
|
96
|
-
### 5.
|
|
104
|
+
### 5. Bash Command-Output Filtering (RTK)
|
|
105
|
+
|
|
106
|
+
CPMM supports RTK as an **optional Bash command-output filtering layer** for Bash-heavy workflows. `cpmm setup` attempts to install the RTK binary, but CPMM does **not** enable the RTK hook by default.
|
|
107
|
+
|
|
108
|
+
We’re shipping RTK as an official optional integration because it reduces noisy command output before that output expands Claude’s input context in Bash-heavy workflows. CPMM documents and validates the recommended hook order, where CPMM’s critical-action check should run before RTK’s rewrite hook. The integration remains opt-in so hook behavior stays predictable and easier to debug.
|
|
109
|
+
|
|
110
|
+
Recommended opt-in flow:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
rtk init -g --hook-only
|
|
114
|
+
# if RTK was enabled, cpmm setup restores the managed hook order + timeout
|
|
115
|
+
cpmm setup
|
|
116
|
+
cpmm doctor
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Recommended `PreToolUse` order in `~/.claude/settings.json`:
|
|
120
|
+
- CPMM safety hook first: `~/.claude/scripts/hooks/critical-action-check.sh` with `timeout: 5`
|
|
121
|
+
- RTK rewrite hook second: `~/.claude/hooks/rtk-rewrite.sh` with `timeout: 10`
|
|
122
|
+
|
|
123
|
+
Update note:
|
|
124
|
+
- `cpmm setup` rewrites `~/.claude/settings.json` on update.
|
|
125
|
+
- If RTK was already enabled before `cpmm setup`, CPMM restores the managed RTK hook order and `timeout: 10` automatically after rewriting settings.
|
|
126
|
+
- Run `cpmm doctor` after setup if you want to verify the managed RTK state.
|
|
127
|
+
|
|
128
|
+
Recommended verification:
|
|
129
|
+
- Run `/hooks` and confirm both CPMM and RTK hooks are loaded
|
|
130
|
+
- Confirm dangerous commands are still blocked by CPMM
|
|
131
|
+
- Run `cpmm doctor`
|
|
132
|
+
- After real Bash-heavy sessions, inspect `rtk gain --quota --tier pro`
|
|
133
|
+
|
|
134
|
+
Public example: in a [community RTK integration report](https://github.com/move-hoon/claude-pro-minmax/issues/3), `rtk gain --quota --tier pro` reported `8.5M` input tokens saved (`49.4%`) across `1,664` commands in a Bash-heavy workflow. Savings vary by workload and session shape.
|
|
135
|
+
|
|
136
|
+
Rollback:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
rtk init -g --uninstall
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 6. Advanced (Optional)
|
|
97
143
|
<details>
|
|
98
144
|
<summary>Perplexity, language, manual install</summary>
|
|
99
145
|
|
|
@@ -422,7 +468,7 @@ To add a new runtime, copy and implement `scripts/runtime/adapters/_template.sh`
|
|
|
422
468
|
|
|
423
469
|
A: Anthropic's exact quota algorithm is not public. Optimization is based on three pillars:
|
|
424
470
|
- **Low-cost model-first path**: Start implementation with Haiku, and escalate to Sonnet/Opus only when needed.
|
|
425
|
-
- **Output-cost awareness**: Output
|
|
471
|
+
- **Output-cost awareness**: Output-heavy turns tend to cost more, so response budgets and filtering help keep payloads smaller.
|
|
426
472
|
- **Workflow simplification**: Use `/do` and `/plan` by task type to avoid unnecessary high-cost turns.
|
|
427
473
|
|
|
428
474
|
For measured evidence, see [docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.md](docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.md).
|
|
@@ -497,6 +543,7 @@ If rollback did not fully restore clean state:
|
|
|
497
543
|
## References
|
|
498
544
|
|
|
499
545
|
- Archived experiment evidence for core strategy: [Core Strategy Experiment Archive](docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.md)
|
|
546
|
+
- Independent reverse-engineering case study for direction-checking: [claude-limits](https://she-llac.com/claude-limits) (unofficial analysis of Claude plan usage/credits behavior)
|
|
500
547
|
- Official pricing and usage docs:
|
|
501
548
|
- [Anthropic Pricing](https://docs.anthropic.com/en/docs/about-claude/pricing)
|
|
502
549
|
- [Usage Limit Best Practices](https://support.claude.com/en/articles/9797557-usage-limit-best-practices)
|
package/install.sh
CHANGED
|
@@ -47,6 +47,11 @@ else
|
|
|
47
47
|
fi
|
|
48
48
|
|
|
49
49
|
# Backup existing ~/.claude (Fresh Install Only)
|
|
50
|
+
HAS_EXISTING_RTK_HOOK=false
|
|
51
|
+
if [ -f "$HOME/.claude/settings.json" ] && grep -q "rtk-rewrite.sh" "$HOME/.claude/settings.json" 2>/dev/null; then
|
|
52
|
+
HAS_EXISTING_RTK_HOOK=true
|
|
53
|
+
fi
|
|
54
|
+
|
|
50
55
|
if [ "$IS_UPDATE" = false ] && [ -d "$HOME/.claude" ]; then
|
|
51
56
|
if [ -d "$HOME/.claude.pre-cpmm" ]; then
|
|
52
57
|
echo "⚠️ ~/.claude.pre-cpmm already exists, skipping backup."
|
|
@@ -283,9 +288,31 @@ echo " > /dplan Analyze complex architecture"
|
|
|
283
288
|
echo " > /do Implement the login page"
|
|
284
289
|
echo ""
|
|
285
290
|
echo "Dependency Check:"
|
|
286
|
-
echo " cpmm setup # install missing deps (jq, mgrep, tmux)"
|
|
291
|
+
echo " cpmm setup # install missing deps (jq, mgrep, tmux) + attempt optional RTK install"
|
|
287
292
|
echo " cpmm doctor # check status only"
|
|
288
293
|
echo ""
|
|
294
|
+
if command -v rtk >/dev/null 2>&1; then
|
|
295
|
+
echo "RTK (Optional Integration):"
|
|
296
|
+
echo " Installed: rtk"
|
|
297
|
+
echo " Enable hook: rtk init -g --hook-only"
|
|
298
|
+
echo " Recommended Bash hook order in ~/.claude/settings.json:"
|
|
299
|
+
echo " 1) ~/.claude/scripts/hooks/critical-action-check.sh (timeout: 5)"
|
|
300
|
+
echo " 2) ~/.claude/hooks/rtk-rewrite.sh (timeout: 10)"
|
|
301
|
+
echo " Rollback: rtk init -g --uninstall"
|
|
302
|
+
echo ""
|
|
303
|
+
fi
|
|
304
|
+
if [ "$HAS_EXISTING_RTK_HOOK" = true ]; then
|
|
305
|
+
echo "RTK Update Note:"
|
|
306
|
+
echo " An RTK hook was detected before this CPMM update."
|
|
307
|
+
if [ "${CPMM_SETUP_WRAPPER:-}" = "1" ]; then
|
|
308
|
+
echo " cpmm setup will restore the managed RTK hook order and timeout after rewriting settings."
|
|
309
|
+
echo " Run: cpmm doctor"
|
|
310
|
+
else
|
|
311
|
+
echo " Re-run cpmm setup to restore the managed RTK hook order and timeout."
|
|
312
|
+
echo " Or re-check ~/.claude/settings.json manually, then run: cpmm doctor"
|
|
313
|
+
fi
|
|
314
|
+
echo ""
|
|
315
|
+
fi
|
|
289
316
|
echo "Language:"
|
|
290
317
|
echo " To change language: edit ~/.claude/rules/language.md"
|
|
291
318
|
echo " To use English: rm ~/.claude/rules/language.md"
|
package/lib/cli.js
CHANGED
|
@@ -4,6 +4,13 @@ const { execSync, spawnSync } = require("node:child_process");
|
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const fs = require("node:fs");
|
|
6
6
|
const pkg = require("../package.json");
|
|
7
|
+
const {
|
|
8
|
+
RTK_DOCS_URL,
|
|
9
|
+
attemptRtkInstall,
|
|
10
|
+
getRtkManualInstallHints,
|
|
11
|
+
inspectRtkStatus,
|
|
12
|
+
reconcileManagedRtkHook,
|
|
13
|
+
} = require("./rtk");
|
|
7
14
|
|
|
8
15
|
const DEPS = [
|
|
9
16
|
{
|
|
@@ -154,8 +161,115 @@ function printStatus(results) {
|
|
|
154
161
|
console.log("");
|
|
155
162
|
}
|
|
156
163
|
|
|
164
|
+
function printRtkSetupResult(result) {
|
|
165
|
+
if (!result) return;
|
|
166
|
+
|
|
167
|
+
console.log("RTK Integration:");
|
|
168
|
+
switch (result.outcome) {
|
|
169
|
+
case "already_installed":
|
|
170
|
+
console.log(` ${colorize("✓", "32")} rtk [optional] already installed`);
|
|
171
|
+
break;
|
|
172
|
+
case "installed":
|
|
173
|
+
console.log(` ${colorize("✓", "32")} rtk [optional] installed (${result.method})`);
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
console.log(` ${colorize("⚠", "33")} rtk [optional] ${result.message}`);
|
|
177
|
+
for (const line of getRtkManualInstallHints()) {
|
|
178
|
+
console.log(` ${line}`);
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log(" Optional activation:");
|
|
184
|
+
console.log(" 1) rtk init -g --hook-only");
|
|
185
|
+
console.log(" 2) Re-run cpmm setup to restore the CPMM-managed hook order + timeout");
|
|
186
|
+
console.log(" 3) Re-run: cpmm doctor");
|
|
187
|
+
console.log("");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function printRtkReconcileResult(result) {
|
|
191
|
+
if (!result || !result.attempted) return;
|
|
192
|
+
|
|
193
|
+
if (result.ok) {
|
|
194
|
+
const suffix = result.changed ? "restored" : "already canonical";
|
|
195
|
+
console.log("RTK Hook Management:");
|
|
196
|
+
console.log(` ${colorize("✓", "32")} managed hook ${suffix} (CPMM hook first, RTK timeout 10)`);
|
|
197
|
+
console.log("");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log("RTK Hook Management:");
|
|
202
|
+
console.log(` ${colorize("⚠", "33")} managed hook ${result.message || "unable to restore RTK hook state automatically"}`);
|
|
203
|
+
console.log(" Run: rtk init -g --hook-only");
|
|
204
|
+
console.log(" Then: cpmm setup");
|
|
205
|
+
console.log("");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function printRtkDoctorStatus(status) {
|
|
209
|
+
console.log("\nRTK Integration:");
|
|
210
|
+
console.log(
|
|
211
|
+
` ${status.binaryInstalled ? colorize("✓", "32") : colorize("○", "33")} rtk binary ${
|
|
212
|
+
status.binaryInstalled ? "installed" : "not installed (optional)"
|
|
213
|
+
}`,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (status.failReason === "missing_settings_json") {
|
|
217
|
+
console.log(` ${colorize("✗", "31")} settings.json missing (~/.claude/settings.json)`);
|
|
218
|
+
console.log(" Fix: re-run cpmm setup");
|
|
219
|
+
console.log("");
|
|
220
|
+
return { failed: true };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (status.failReason === "invalid_settings_json") {
|
|
224
|
+
console.log(` ${colorize("✗", "31")} settings.json invalid JSON`);
|
|
225
|
+
console.log(" Fix: repair ~/.claude/settings.json, then re-run cpmm doctor");
|
|
226
|
+
console.log("");
|
|
227
|
+
return { failed: true };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!status.cpmmHookFound) {
|
|
231
|
+
console.log(` ${colorize("✗", "31")} CPMM hook critical-action-check.sh missing`);
|
|
232
|
+
console.log(" Fix: re-run cpmm setup");
|
|
233
|
+
console.log("");
|
|
234
|
+
return { failed: true };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.log(` ${colorize("✓", "32")} CPMM hook critical-action-check.sh detected`);
|
|
238
|
+
|
|
239
|
+
if (!status.hookEnabled) {
|
|
240
|
+
console.log(` ${colorize("○", "33")} RTK hook not enabled (opt-in)`);
|
|
241
|
+
console.log(` Enable: rtk init -g --hook-only`);
|
|
242
|
+
console.log(` Docs: ${RTK_DOCS_URL}`);
|
|
243
|
+
console.log("");
|
|
244
|
+
return { failed: false };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (status.orderOk) {
|
|
248
|
+
console.log(` ${colorize("✓", "32")} Hook order CPMM safety hook precedes RTK hook`);
|
|
249
|
+
} else {
|
|
250
|
+
console.log(` ${colorize("✗", "31")} Hook order RTK hook precedes CPMM safety hook`);
|
|
251
|
+
console.log(" Fix order in ~/.claude/settings.json:");
|
|
252
|
+
console.log(" 1) ~/.claude/scripts/hooks/critical-action-check.sh (timeout: 5)");
|
|
253
|
+
console.log(" 2) ~/.claude/hooks/rtk-rewrite.sh (timeout: 10)");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (status.timeoutOk) {
|
|
257
|
+
console.log(` ${colorize("✓", "32")} RTK timeout 10`);
|
|
258
|
+
} else {
|
|
259
|
+
const rendered = status.timeoutValue == null ? "missing" : String(status.timeoutValue);
|
|
260
|
+
console.log(` ${colorize("⚠", "33")} RTK timeout ${rendered} (recommended: 10)`);
|
|
261
|
+
console.log(" Fix: re-run cpmm setup to restore the managed timeout");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
console.log("");
|
|
265
|
+
return { failed: !status.orderOk };
|
|
266
|
+
}
|
|
267
|
+
|
|
157
268
|
function runSetup() {
|
|
158
269
|
console.log(`CPMM v${pkg.version} - Setup`);
|
|
270
|
+
const settingsPath = path.join(process.env.HOME || process.env.USERPROFILE || "", ".claude", "settings.json");
|
|
271
|
+
const preSetupRtkStatus = inspectRtkStatus(settingsPath);
|
|
272
|
+
const shouldRestoreManagedRtkHook = preSetupRtkStatus.hookEnabled;
|
|
159
273
|
|
|
160
274
|
const results = checkDeps();
|
|
161
275
|
const missing = results.filter((r) => r.required && !r.installed);
|
|
@@ -185,11 +299,20 @@ function runSetup() {
|
|
|
185
299
|
printStatus(results);
|
|
186
300
|
}
|
|
187
301
|
|
|
302
|
+
const rtkResult = attemptRtkInstall();
|
|
303
|
+
printRtkSetupResult(rtkResult);
|
|
304
|
+
|
|
188
305
|
// Run install.sh for config files, language, and Perplexity setup
|
|
189
306
|
if (!runInstallScript()) {
|
|
190
307
|
return 1;
|
|
191
308
|
}
|
|
192
309
|
|
|
310
|
+
const rtkReconcile = reconcileManagedRtkHook({
|
|
311
|
+
enabledBeforeSetup: shouldRestoreManagedRtkHook,
|
|
312
|
+
settingsPath,
|
|
313
|
+
});
|
|
314
|
+
printRtkReconcileResult(rtkReconcile);
|
|
315
|
+
|
|
193
316
|
console.log("Setup complete.");
|
|
194
317
|
return 0;
|
|
195
318
|
}
|
|
@@ -202,7 +325,7 @@ function runInstallScript() {
|
|
|
202
325
|
console.log("Configuring CPMM...\n");
|
|
203
326
|
const result = spawnSync("bash", [scriptPath], {
|
|
204
327
|
stdio: "inherit",
|
|
205
|
-
env: { ...process.env },
|
|
328
|
+
env: { ...process.env, CPMM_SETUP_WRAPPER: "1" },
|
|
206
329
|
});
|
|
207
330
|
if (result.status !== 0) {
|
|
208
331
|
console.error("Config setup had issues. Run 'bash install.sh' manually if needed.");
|
|
@@ -217,13 +340,16 @@ function runDoctor() {
|
|
|
217
340
|
const results = checkDeps();
|
|
218
341
|
printStatus(results);
|
|
219
342
|
|
|
343
|
+
const rtkStatus = inspectRtkStatus(path.join(process.env.HOME || process.env.USERPROFILE || "", ".claude", "settings.json"));
|
|
344
|
+
const rtkDoctor = printRtkDoctorStatus(rtkStatus);
|
|
345
|
+
|
|
220
346
|
const missing = results.filter((r) => r.required && !r.installed);
|
|
221
|
-
if (missing.length > 0) {
|
|
347
|
+
if (missing.length > 0 || rtkDoctor.failed) {
|
|
222
348
|
console.log(`Fix: cpmm setup`);
|
|
223
349
|
return 1;
|
|
224
350
|
}
|
|
225
351
|
|
|
226
|
-
console.log(colorize("All checks passed.", "32"));
|
|
352
|
+
console.log(colorize("All required checks passed.", "32"));
|
|
227
353
|
return 0;
|
|
228
354
|
}
|
|
229
355
|
|
|
@@ -231,8 +357,8 @@ function printHelp() {
|
|
|
231
357
|
console.log(`CPMM v${pkg.version}
|
|
232
358
|
|
|
233
359
|
Usage:
|
|
234
|
-
cpmm setup Install deps + configure CPMM (language, Perplexity)
|
|
235
|
-
cpmm doctor Check dependency status
|
|
360
|
+
cpmm setup Install deps + configure CPMM (language, Perplexity, optional RTK)
|
|
361
|
+
cpmm doctor Check dependency and RTK hook status
|
|
236
362
|
cpmm --help Show this help
|
|
237
363
|
cpmm --version Show version
|
|
238
364
|
`);
|
package/lib/rtk.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const { execSync, spawnSync } = require("node:child_process");
|
|
7
|
+
|
|
8
|
+
const RTK_INSTALL_URL = "https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh";
|
|
9
|
+
const RTK_DOCS_URL = "https://github.com/rtk-ai/rtk/blob/main/docs/INSTALL.md";
|
|
10
|
+
const CPMM_HOOK_NAME = "critical-action-check.sh";
|
|
11
|
+
const RTK_HOOK_NAME = "rtk-rewrite.sh";
|
|
12
|
+
const RTK_HOOK_COMMAND = "~/.claude/hooks/rtk-rewrite.sh";
|
|
13
|
+
const RTK_HOOK_TIMEOUT = 10;
|
|
14
|
+
|
|
15
|
+
function commandExists(cmd) {
|
|
16
|
+
try {
|
|
17
|
+
execSync(`command -v ${cmd}`, { stdio: "ignore" });
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runCommand(bin, args, label) {
|
|
25
|
+
const rendered = [bin, ...args].join(" ");
|
|
26
|
+
console.log(` $ ${rendered}`);
|
|
27
|
+
const result = spawnSync(bin, args, { stdio: "inherit", env: { ...process.env } });
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
console.error(` FAIL: ${label} (exit ${result.status})`);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cloneJson(value) {
|
|
36
|
+
return JSON.parse(JSON.stringify(value));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getRtkManualInstallHints() {
|
|
40
|
+
return [
|
|
41
|
+
"RTK is optional. CPMM setup will continue without it.",
|
|
42
|
+
"Install RTK manually with one of these upstream-supported methods:",
|
|
43
|
+
" - brew install rtk",
|
|
44
|
+
` - curl -fsSL ${RTK_INSTALL_URL} | sh`,
|
|
45
|
+
` - Docs: ${RTK_DOCS_URL}`,
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function attemptRtkInstall() {
|
|
50
|
+
if (commandExists("rtk")) {
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
outcome: "already_installed",
|
|
54
|
+
method: "existing",
|
|
55
|
+
message: "RTK already installed.",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (commandExists("brew")) {
|
|
60
|
+
const ok = runCommand("brew", ["install", "rtk"], "rtk (brew)");
|
|
61
|
+
if (ok) {
|
|
62
|
+
return {
|
|
63
|
+
ok: true,
|
|
64
|
+
outcome: "installed",
|
|
65
|
+
method: "brew",
|
|
66
|
+
message: "RTK installed via Homebrew.",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (commandExists("curl")) {
|
|
72
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "cpmm-rtk-"));
|
|
73
|
+
const scriptPath = path.join(tempDir, "install.sh");
|
|
74
|
+
try {
|
|
75
|
+
const downloaded = runCommand("curl", ["-fsSL", RTK_INSTALL_URL, "-o", scriptPath], "rtk installer download");
|
|
76
|
+
const installed = downloaded ? runCommand("sh", [scriptPath], "rtk installer") : false;
|
|
77
|
+
return {
|
|
78
|
+
ok: installed,
|
|
79
|
+
outcome: installed ? "installed" : "manual_action_required",
|
|
80
|
+
method: "curl",
|
|
81
|
+
message: installed ? "RTK installed via upstream installer." : "RTK upstream installer failed.",
|
|
82
|
+
};
|
|
83
|
+
} finally {
|
|
84
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
outcome: "manual_action_required",
|
|
91
|
+
method: "manual",
|
|
92
|
+
message: "No supported automatic RTK installer found.",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function matcherIncludesBash(matcher) {
|
|
97
|
+
return String(matcher || "")
|
|
98
|
+
.split("|")
|
|
99
|
+
.map((token) => token.trim())
|
|
100
|
+
.includes("Bash");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function flattenBashHooks(settings) {
|
|
104
|
+
const entries = Array.isArray(settings?.hooks?.PreToolUse) ? settings.hooks.PreToolUse : [];
|
|
105
|
+
const flattened = [];
|
|
106
|
+
|
|
107
|
+
entries.forEach((entry, entryIndex) => {
|
|
108
|
+
if (!matcherIncludesBash(entry?.matcher)) return;
|
|
109
|
+
const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
110
|
+
hooks.forEach((hook, hookIndex) => {
|
|
111
|
+
flattened.push({
|
|
112
|
+
entryIndex,
|
|
113
|
+
hookIndex,
|
|
114
|
+
command: String(hook?.command || ""),
|
|
115
|
+
timeout: hook?.timeout,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return flattened;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeManagedRtkHookSettings(settings) {
|
|
124
|
+
const source = settings && typeof settings === "object" ? settings : {};
|
|
125
|
+
const next = cloneJson(source);
|
|
126
|
+
if (!next.hooks || typeof next.hooks !== "object") next.hooks = {};
|
|
127
|
+
const preToolUse = Array.isArray(next.hooks.PreToolUse) ? next.hooks.PreToolUse : [];
|
|
128
|
+
next.hooks.PreToolUse = preToolUse;
|
|
129
|
+
|
|
130
|
+
let cpmmEntryIndex = -1;
|
|
131
|
+
let cpmmHookIndex = -1;
|
|
132
|
+
|
|
133
|
+
preToolUse.forEach((entry, entryIndex) => {
|
|
134
|
+
if (!matcherIncludesBash(entry?.matcher)) return;
|
|
135
|
+
const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
136
|
+
const foundIndex = hooks.findIndex((hook) => String(hook?.command || "").includes(CPMM_HOOK_NAME));
|
|
137
|
+
if (foundIndex !== -1 && cpmmEntryIndex === -1) {
|
|
138
|
+
cpmmEntryIndex = entryIndex;
|
|
139
|
+
cpmmHookIndex = foundIndex;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (cpmmEntryIndex === -1 || cpmmHookIndex === -1) {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
failReason: "missing_cpmm_hook",
|
|
147
|
+
changed: false,
|
|
148
|
+
settings: next,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
next.hooks.PreToolUse = preToolUse
|
|
153
|
+
.map((entry, entryIndex) => {
|
|
154
|
+
if (!matcherIncludesBash(entry?.matcher)) return entry;
|
|
155
|
+
const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
156
|
+
const filteredHooks = hooks.filter((hook) => !String(hook?.command || "").includes(RTK_HOOK_NAME));
|
|
157
|
+
return {
|
|
158
|
+
...entry,
|
|
159
|
+
hooks: filteredHooks,
|
|
160
|
+
__cpmmEntry: entryIndex === cpmmEntryIndex,
|
|
161
|
+
};
|
|
162
|
+
})
|
|
163
|
+
.filter((entry) => {
|
|
164
|
+
if (!matcherIncludesBash(entry?.matcher)) return true;
|
|
165
|
+
if (entry.__cpmmEntry) return true;
|
|
166
|
+
return Array.isArray(entry?.hooks) && entry.hooks.length > 0;
|
|
167
|
+
})
|
|
168
|
+
.map((entry) => {
|
|
169
|
+
const { __cpmmEntry, ...rest } = entry;
|
|
170
|
+
return rest;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const canonicalEntry = next.hooks.PreToolUse.find((entry) => {
|
|
174
|
+
if (!matcherIncludesBash(entry?.matcher)) return false;
|
|
175
|
+
const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
176
|
+
return hooks.some((hook) => String(hook?.command || "").includes(CPMM_HOOK_NAME));
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (!canonicalEntry) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
failReason: "missing_cpmm_hook",
|
|
183
|
+
changed: false,
|
|
184
|
+
settings: next,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const canonicalHooks = Array.isArray(canonicalEntry.hooks) ? canonicalEntry.hooks : [];
|
|
189
|
+
const canonicalCpmmIndex = canonicalHooks.findIndex((hook) => String(hook?.command || "").includes(CPMM_HOOK_NAME));
|
|
190
|
+
if (canonicalCpmmIndex === -1) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
failReason: "missing_cpmm_hook",
|
|
194
|
+
changed: false,
|
|
195
|
+
settings: next,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const desiredRtkHook = {
|
|
200
|
+
type: "command",
|
|
201
|
+
command: RTK_HOOK_COMMAND,
|
|
202
|
+
timeout: RTK_HOOK_TIMEOUT,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
canonicalHooks.splice(canonicalCpmmIndex + 1, 0, desiredRtkHook);
|
|
206
|
+
canonicalEntry.hooks = canonicalHooks;
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
ok: true,
|
|
210
|
+
changed: JSON.stringify(source) !== JSON.stringify(next),
|
|
211
|
+
settings: next,
|
|
212
|
+
failReason: null,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function writeSettingsJsonAtomic(settingsPath, settings) {
|
|
217
|
+
const tempPath = `${settingsPath}.tmp`;
|
|
218
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
219
|
+
fs.renameSync(tempPath, settingsPath);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function ensureRtkHookArtifact() {
|
|
223
|
+
const hookPath = path.join(os.homedir(), ".claude", "hooks", RTK_HOOK_NAME);
|
|
224
|
+
if (fs.existsSync(hookPath)) {
|
|
225
|
+
return { ok: true, hookPath, initialized: false };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const ok = runCommand("rtk", ["init", "-g", "--hook-only", "--no-patch"], "rtk hook init");
|
|
229
|
+
if (!ok || !fs.existsSync(hookPath)) {
|
|
230
|
+
return {
|
|
231
|
+
ok: false,
|
|
232
|
+
hookPath,
|
|
233
|
+
initialized: ok,
|
|
234
|
+
failReason: "missing_rtk_hook_artifact",
|
|
235
|
+
message: "RTK hook artifact could not be created.",
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { ok: true, hookPath, initialized: true };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function reconcileManagedRtkHook({ enabledBeforeSetup, settingsPath }) {
|
|
243
|
+
const result = {
|
|
244
|
+
attempted: Boolean(enabledBeforeSetup),
|
|
245
|
+
changed: false,
|
|
246
|
+
ok: true,
|
|
247
|
+
message: null,
|
|
248
|
+
failReason: null,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
if (!enabledBeforeSetup) {
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!commandExists("rtk")) {
|
|
256
|
+
return {
|
|
257
|
+
...result,
|
|
258
|
+
ok: false,
|
|
259
|
+
failReason: "rtk_binary_missing",
|
|
260
|
+
message: "RTK binary is not available, so CPMM could not restore the managed RTK hook.",
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!fs.existsSync(settingsPath)) {
|
|
265
|
+
return {
|
|
266
|
+
...result,
|
|
267
|
+
ok: false,
|
|
268
|
+
failReason: "missing_settings_json",
|
|
269
|
+
message: "CPMM settings.json is missing after setup.",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const hookArtifact = ensureRtkHookArtifact();
|
|
274
|
+
if (!hookArtifact.ok) {
|
|
275
|
+
return {
|
|
276
|
+
...result,
|
|
277
|
+
ok: false,
|
|
278
|
+
failReason: hookArtifact.failReason || "missing_rtk_hook_artifact",
|
|
279
|
+
message: hookArtifact.message || "RTK hook artifact could not be created.",
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let parsed;
|
|
284
|
+
try {
|
|
285
|
+
parsed = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
286
|
+
} catch {
|
|
287
|
+
return {
|
|
288
|
+
...result,
|
|
289
|
+
ok: false,
|
|
290
|
+
failReason: "invalid_settings_json",
|
|
291
|
+
message: "CPMM settings.json is invalid JSON after setup.",
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const normalized = normalizeManagedRtkHookSettings(parsed);
|
|
296
|
+
if (!normalized.ok) {
|
|
297
|
+
return {
|
|
298
|
+
...result,
|
|
299
|
+
ok: false,
|
|
300
|
+
failReason: normalized.failReason,
|
|
301
|
+
message: "CPMM could not find its Bash safety hook while restoring RTK.",
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (normalized.changed) {
|
|
306
|
+
writeSettingsJsonAtomic(settingsPath, normalized.settings);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
...result,
|
|
311
|
+
changed: normalized.changed,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function inspectRtkStatus(settingsPath) {
|
|
316
|
+
const result = {
|
|
317
|
+
binaryInstalled: commandExists("rtk"),
|
|
318
|
+
settingsPath,
|
|
319
|
+
cpmmHookFound: false,
|
|
320
|
+
hookEnabled: false,
|
|
321
|
+
orderOk: null,
|
|
322
|
+
timeoutValue: null,
|
|
323
|
+
timeoutOk: null,
|
|
324
|
+
failReason: null,
|
|
325
|
+
warnings: [],
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
if (!fs.existsSync(settingsPath)) {
|
|
329
|
+
result.failReason = "missing_settings_json";
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
let parsed;
|
|
334
|
+
try {
|
|
335
|
+
parsed = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
336
|
+
} catch {
|
|
337
|
+
result.failReason = "invalid_settings_json";
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const hooks = flattenBashHooks(parsed);
|
|
342
|
+
const cpmmIndex = hooks.findIndex((hook) => hook.command.includes(CPMM_HOOK_NAME));
|
|
343
|
+
if (cpmmIndex === -1) {
|
|
344
|
+
result.failReason = "missing_cpmm_hook";
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
result.cpmmHookFound = true;
|
|
349
|
+
|
|
350
|
+
const rtkIndex = hooks.findIndex((hook) => hook.command.includes(RTK_HOOK_NAME));
|
|
351
|
+
if (rtkIndex === -1) {
|
|
352
|
+
return result;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
result.hookEnabled = true;
|
|
356
|
+
result.orderOk = cpmmIndex < rtkIndex;
|
|
357
|
+
if (!result.orderOk) {
|
|
358
|
+
result.failReason = "rtk_precedes_cpmm";
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const rtkHook = hooks[rtkIndex];
|
|
362
|
+
result.timeoutValue = typeof rtkHook.timeout === "number" ? rtkHook.timeout : null;
|
|
363
|
+
result.timeoutOk = result.timeoutValue === RTK_HOOK_TIMEOUT;
|
|
364
|
+
if (!result.timeoutOk) {
|
|
365
|
+
result.warnings.push("rtk_timeout_not_10");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
module.exports = {
|
|
372
|
+
RTK_DOCS_URL,
|
|
373
|
+
RTK_INSTALL_URL,
|
|
374
|
+
RTK_HOOK_COMMAND,
|
|
375
|
+
RTK_HOOK_TIMEOUT,
|
|
376
|
+
attemptRtkInstall,
|
|
377
|
+
getRtkManualInstallHints,
|
|
378
|
+
inspectRtkStatus,
|
|
379
|
+
normalizeManagedRtkHookSettings,
|
|
380
|
+
reconcileManagedRtkHook,
|
|
381
|
+
};
|
package/package.json
CHANGED
|
@@ -77,6 +77,16 @@ flowchart TB
|
|
|
77
77
|
|
|
78
78
|
**비용 설명:** 모든 Hook은 머신에서 로컬로 실행되어 API 호출이 없습니다. Hook이 Claude에게 메시지를 표시할 때만(예: 명령 차단, 컴팩션 제안) 해당 메시지가 입력 토큰을 소비하며, 이러한 메시지는 의도적으로 간결합니다.
|
|
79
79
|
|
|
80
|
+
## RTK 순서
|
|
81
|
+
|
|
82
|
+
RTK를 opt-in으로 사용할 때는 CPMM의 Bash safety hook를 먼저 두고, RTK rewrite hook를 그 뒤에 둡니다.
|
|
83
|
+
|
|
84
|
+
권장 순서 (`~/.claude/settings.json`):
|
|
85
|
+
- `~/.claude/scripts/hooks/critical-action-check.sh` with `timeout: 5`
|
|
86
|
+
- `~/.claude/hooks/rtk-rewrite.sh` with `timeout: 10`
|
|
87
|
+
|
|
88
|
+
이 순서를 유지해야 CPMM의 위험 명령 차단이 RTK rewrite보다 먼저 실행됩니다.
|
|
89
|
+
|
|
80
90
|
## 종료 코드
|
|
81
91
|
|
|
82
92
|
| 코드 | 동작 |
|
package/scripts/hooks/README.md
CHANGED
|
@@ -77,6 +77,16 @@ flowchart TB
|
|
|
77
77
|
|
|
78
78
|
**Cost Explanation:** All hooks run locally on your machine without API calls. Only when a hook displays a message to Claude (e.g., blocking a command, suggesting compaction) does that message consume input tokens—and these messages are intentionally brief.
|
|
79
79
|
|
|
80
|
+
## RTK Ordering
|
|
81
|
+
|
|
82
|
+
If you opt into RTK, keep CPMM's Bash safety hook first and RTK's rewrite hook second.
|
|
83
|
+
|
|
84
|
+
Recommended order in `~/.claude/settings.json`:
|
|
85
|
+
- `~/.claude/scripts/hooks/critical-action-check.sh` with `timeout: 5`
|
|
86
|
+
- `~/.claude/hooks/rtk-rewrite.sh` with `timeout: 10`
|
|
87
|
+
|
|
88
|
+
This preserves CPMM's critical-action block before RTK rewrites the Bash command.
|
|
89
|
+
|
|
80
90
|
## Exit Codes
|
|
81
91
|
|
|
82
92
|
| Code | Behavior |
|
|
@@ -115,4 +125,3 @@ Output to **stderr** is shown to Claude when blocking (exit 2). Note: stdout JSO
|
|
|
115
125
|
2. Make executable: `chmod +x script.sh`
|
|
116
126
|
3. Add to `settings.json` or agent frontmatter
|
|
117
127
|
4. Test with various inputs
|
|
118
|
-
|