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 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. **출력 비용 제어:** 응답 예산과 CLI 필터링으로 불필요한 출력 토큰을 줄입니다.
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
- - 자동 설치 지원: `npm` (mgrep), `brew` (macOS), Linux 패키지 매니저 `apt-get`, `dnf`, `pacman`, `apk`
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`에만 `settings.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
- - **출력 비용 인식**: Output 토큰은 Input 대비 단가가 높아 응답 예산/필터링으로 payload를 줄입니다.
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 and CLI filtering to reduce unnecessary output tokens.
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 supports `npm` (mgrep), `brew` (macOS), and Linux package managers `apt-get`, `dnf`, `pacman`, `apk`
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` only — `settings.json` is overwritten on update.
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. Advanced (Optional)
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 tokens are priced higher than input, so response budgets/filtering reduce payload.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-pro-minmax",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "Quota-first routing and safety layer for Claude Code Pro workflows.",
5
5
  "author": "donghoon <move-hoon@users.noreply.github.com>",
6
6
  "license": "MIT",
@@ -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
  | 코드 | 동작 |
@@ -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
-