claude-pro-minmax 1.3.0 → 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 +43 -36
- package/README.md +43 -36
- package/install.sh +7 -2
- package/lib/cli.js +32 -4
- package/lib/rtk.js +203 -1
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -16,14 +16,14 @@ CPMM은 모델 라우팅, 출력 제어, 로컬 안전장치로 리셋 전까지
|
|
|
16
16
|
|
|
17
17
|
> **설치 완료했다면 여기서 시작하세요: [사용자 가이드](docs/USER-MANUAL.ko.md)**
|
|
18
18
|
>
|
|
19
|
-
> **New in v1.3.
|
|
19
|
+
> **New in v1.3.1:** RTK를 이미 켜둔 사용자에 대해 `cpmm setup`이 관리된 hook 순서와 timeout을 자동 복원합니다.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
> [!TIP]
|
|
24
24
|
> **🚀 3초 요약: 왜 이걸 써야 하나요?**
|
|
25
25
|
> 1. **배치 실행:** `/do`로 구현-검증을 한 흐름에서 처리하고, 필요할 때만 `/do-sonnet`/`/do-opus`로 승격합니다.
|
|
26
|
-
> 2. **출력 비용 제어:** 응답 예산, CLI 필터링, 그리고 optional RTK로 Bash
|
|
26
|
+
> 2. **출력 비용 제어:** 응답 예산, CLI 필터링, 그리고 optional RTK로 Bash 출력이 Claude 입력 컨텍스트를 불필요하게 키우지 않도록 합니다.
|
|
27
27
|
> 3. **로컬 안전장치:** 로컬 Hook + 원자적 롤백으로 실패 시 빠르게 복구합니다.
|
|
28
28
|
|
|
29
29
|
---
|
|
@@ -52,7 +52,7 @@ cpmm setup
|
|
|
52
52
|
cpmm doctor
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
> **v1.3.
|
|
55
|
+
> **v1.3.1 참고:** `cpmm setup`은 지원 환경에서 RTK 설치를 계속 시도합니다. RTK 활성화는 여전히 opt-in이지만, 한 번 켠 뒤에는 CPMM이 관리된 hook 순서와 timeout을 자동 복원합니다.
|
|
56
56
|
|
|
57
57
|
의존성 정책:
|
|
58
58
|
- `required`: `jq`, `mgrep`, `tmux`
|
|
@@ -101,7 +101,45 @@ cpmm doctor
|
|
|
101
101
|
프로젝트 초기화 팁:
|
|
102
102
|
- `claude` 실행 전에 `project-templates/`를 참고해 프로젝트를 초기화하세요. (설치기는 `project-templates`를 `~/.claude`로 복사하지 않습니다.)
|
|
103
103
|
|
|
104
|
-
### 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. 고급 (선택)
|
|
105
143
|
<details>
|
|
106
144
|
<summary>Perplexity, 언어, 수동 설치 보기</summary>
|
|
107
145
|
|
|
@@ -154,37 +192,6 @@ node bin/cpmm.js setup
|
|
|
154
192
|
|
|
155
193
|
</details>
|
|
156
194
|
|
|
157
|
-
### 6. Bash 출력 최적화 (RTK)
|
|
158
|
-
|
|
159
|
-
RTK는 CPMM이 지원하는 **선택적 출력 최적화 계층**입니다. `cpmm setup`은 RTK 바이너리 설치를 시도하지만, RTK hook은 기본 활성화하지 않습니다.
|
|
160
|
-
|
|
161
|
-
권장 opt-in 절차:
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
rtk init -g --hook-only
|
|
165
|
-
cpmm doctor
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
권장 `PreToolUse` 순서 (`~/.claude/settings.json`):
|
|
169
|
-
- 먼저 CPMM safety hook: `~/.claude/scripts/hooks/critical-action-check.sh` with `timeout: 5`
|
|
170
|
-
- 그 다음 RTK rewrite hook: `~/.claude/hooks/rtk-rewrite.sh` with `timeout: 10`
|
|
171
|
-
|
|
172
|
-
업데이트 참고:
|
|
173
|
-
- `cpmm setup`은 업데이트 시 `~/.claude/settings.json`을 다시 씁니다.
|
|
174
|
-
- RTK를 opt-in으로 사용 중이면 CPMM 업데이트 후 hook 순서와 timeout을 다시 확인하고, `cpmm doctor`를 다시 실행하세요.
|
|
175
|
-
|
|
176
|
-
권장 검증:
|
|
177
|
-
- `/hooks`에서 CPMM hook과 RTK hook이 모두 로드되는지 확인
|
|
178
|
-
- 위험 명령이 여전히 CPMM에서 먼저 차단되는지 확인
|
|
179
|
-
- `cpmm doctor` 실행
|
|
180
|
-
- 실제 Bash-heavy 세션 후 `rtk gain --quota --tier pro` 확인
|
|
181
|
-
|
|
182
|
-
롤백:
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
rtk init -g --uninstall
|
|
186
|
-
```
|
|
187
|
-
|
|
188
195
|
---
|
|
189
196
|
|
|
190
197
|
## 🚀 빠른 시작 (Quick Start)
|
|
@@ -461,7 +468,7 @@ claude-pro-minmax
|
|
|
461
468
|
|
|
462
469
|
A: Anthropic의 정확한 quota 알고리즘은 공개되지 않았습니다. 세 가지 축으로 최적화합니다:
|
|
463
470
|
- **저비용 모델 우선 경로**: 기본 구현은 Haiku 중심으로 시작하고 필요 시에만 Sonnet/Opus로 승격합니다.
|
|
464
|
-
- **출력 비용 인식**:
|
|
471
|
+
- **출력 비용 인식**: 출력이 많은 턴일수록 비용 부담이 커지는 경향이 있으므로, 응답 예산과 필터링으로 payload를 줄입니다.
|
|
465
472
|
- **작업 흐름 단순화**: `/do`와 `/plan`을 상황에 맞게 분리해 불필요한 고비용 턴을 줄입니다.
|
|
466
473
|
|
|
467
474
|
근거 실측값은 [docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.ko.md](docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.ko.md)를 참고하세요.
|
package/README.md
CHANGED
|
@@ -16,14 +16,14 @@ CPMM helps Pro users complete more verified tasks before reset through model rou
|
|
|
16
16
|
|
|
17
17
|
> **Already installed? Start here: [User Guide](docs/USER-MANUAL.md)**
|
|
18
18
|
>
|
|
19
|
-
> **New in v1.3.
|
|
19
|
+
> **New in v1.3.1:** `cpmm setup` now restores the managed RTK hook order and timeout for users who already enabled RTK.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
> [!TIP]
|
|
24
24
|
> **🚀 3-Second Summary: Why use this?**
|
|
25
25
|
> 1. **Batch Execution:** Use `/do` to keep implementation and verification in one flow, and escalate to `/do-sonnet`/`/do-opus` only when needed.
|
|
26
|
-
> 2. **Output Cost Control:** Use response budgets, CLI filtering, and optional RTK
|
|
26
|
+
> 2. **Output Cost Control:** Use response budgets, CLI filtering, and optional RTK to keep Bash output from inflating Claude input context.
|
|
27
27
|
> 3. **Local Safety Rails:** Local hooks and atomic rollback help you recover quickly on failure.
|
|
28
28
|
|
|
29
29
|
---
|
|
@@ -52,7 +52,7 @@ cpmm setup
|
|
|
52
52
|
cpmm doctor
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
> **v1.3.
|
|
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
56
|
|
|
57
57
|
Dependency policy:
|
|
58
58
|
- `required`: `jq`, `mgrep`, `tmux`
|
|
@@ -101,7 +101,45 @@ Dependency policy:
|
|
|
101
101
|
Project initialization tip:
|
|
102
102
|
- Before running `claude`, initialize your project with templates in `project-templates/` (not copied into `~/.claude`).
|
|
103
103
|
|
|
104
|
-
### 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)
|
|
105
143
|
<details>
|
|
106
144
|
<summary>Perplexity, language, manual install</summary>
|
|
107
145
|
|
|
@@ -154,37 +192,6 @@ node bin/cpmm.js setup
|
|
|
154
192
|
|
|
155
193
|
</details>
|
|
156
194
|
|
|
157
|
-
### 6. Bash Output Optimization (RTK)
|
|
158
|
-
|
|
159
|
-
CPMM supports RTK as an **optional output-optimization layer** for Bash-heavy workflows. `cpmm setup` attempts to install the RTK binary, but CPMM does **not** enable the RTK hook by default.
|
|
160
|
-
|
|
161
|
-
Recommended opt-in flow:
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
rtk init -g --hook-only
|
|
165
|
-
cpmm doctor
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Recommended `PreToolUse` order in `~/.claude/settings.json`:
|
|
169
|
-
- CPMM safety hook first: `~/.claude/scripts/hooks/critical-action-check.sh` with `timeout: 5`
|
|
170
|
-
- RTK rewrite hook second: `~/.claude/hooks/rtk-rewrite.sh` with `timeout: 10`
|
|
171
|
-
|
|
172
|
-
Update note:
|
|
173
|
-
- `cpmm setup` rewrites `~/.claude/settings.json` on update.
|
|
174
|
-
- If you opt into RTK, re-check hook order and timeout after each CPMM update, then run `cpmm doctor`.
|
|
175
|
-
|
|
176
|
-
Recommended verification:
|
|
177
|
-
- Run `/hooks` and confirm both CPMM and RTK hooks are loaded
|
|
178
|
-
- Confirm dangerous commands are still blocked by CPMM
|
|
179
|
-
- Run `cpmm doctor`
|
|
180
|
-
- After real Bash-heavy sessions, inspect `rtk gain --quota --tier pro`
|
|
181
|
-
|
|
182
|
-
Rollback:
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
rtk init -g --uninstall
|
|
186
|
-
```
|
|
187
|
-
|
|
188
195
|
---
|
|
189
196
|
|
|
190
197
|
## 🚀 Quick Start
|
|
@@ -461,7 +468,7 @@ To add a new runtime, copy and implement `scripts/runtime/adapters/_template.sh`
|
|
|
461
468
|
|
|
462
469
|
A: Anthropic's exact quota algorithm is not public. Optimization is based on three pillars:
|
|
463
470
|
- **Low-cost model-first path**: Start implementation with Haiku, and escalate to Sonnet/Opus only when needed.
|
|
464
|
-
- **Output-cost awareness**: Output
|
|
471
|
+
- **Output-cost awareness**: Output-heavy turns tend to cost more, so response budgets and filtering help keep payloads smaller.
|
|
465
472
|
- **Workflow simplification**: Use `/do` and `/plan` by task type to avoid unnecessary high-cost turns.
|
|
466
473
|
|
|
467
474
|
For measured evidence, see [docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.md](docs/CORE_STRATEGY_EXPERIMENT_ARCHIVE.md).
|
package/install.sh
CHANGED
|
@@ -304,8 +304,13 @@ fi
|
|
|
304
304
|
if [ "$HAS_EXISTING_RTK_HOOK" = true ]; then
|
|
305
305
|
echo "RTK Update Note:"
|
|
306
306
|
echo " An RTK hook was detected before this CPMM update."
|
|
307
|
-
|
|
308
|
-
|
|
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
|
|
309
314
|
echo ""
|
|
310
315
|
fi
|
|
311
316
|
echo "Language:"
|
package/lib/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
attemptRtkInstall,
|
|
10
10
|
getRtkManualInstallHints,
|
|
11
11
|
inspectRtkStatus,
|
|
12
|
+
reconcileManagedRtkHook,
|
|
12
13
|
} = require("./rtk");
|
|
13
14
|
|
|
14
15
|
const DEPS = [
|
|
@@ -181,9 +182,26 @@ function printRtkSetupResult(result) {
|
|
|
181
182
|
|
|
182
183
|
console.log(" Optional activation:");
|
|
183
184
|
console.log(" 1) rtk init -g --hook-only");
|
|
184
|
-
console.log(" 2)
|
|
185
|
-
console.log(" 3)
|
|
186
|
-
console.log("
|
|
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");
|
|
187
205
|
console.log("");
|
|
188
206
|
}
|
|
189
207
|
|
|
@@ -240,6 +258,7 @@ function printRtkDoctorStatus(status) {
|
|
|
240
258
|
} else {
|
|
241
259
|
const rendered = status.timeoutValue == null ? "missing" : String(status.timeoutValue);
|
|
242
260
|
console.log(` ${colorize("⚠", "33")} RTK timeout ${rendered} (recommended: 10)`);
|
|
261
|
+
console.log(" Fix: re-run cpmm setup to restore the managed timeout");
|
|
243
262
|
}
|
|
244
263
|
|
|
245
264
|
console.log("");
|
|
@@ -248,6 +267,9 @@ function printRtkDoctorStatus(status) {
|
|
|
248
267
|
|
|
249
268
|
function runSetup() {
|
|
250
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;
|
|
251
273
|
|
|
252
274
|
const results = checkDeps();
|
|
253
275
|
const missing = results.filter((r) => r.required && !r.installed);
|
|
@@ -285,6 +307,12 @@ function runSetup() {
|
|
|
285
307
|
return 1;
|
|
286
308
|
}
|
|
287
309
|
|
|
310
|
+
const rtkReconcile = reconcileManagedRtkHook({
|
|
311
|
+
enabledBeforeSetup: shouldRestoreManagedRtkHook,
|
|
312
|
+
settingsPath,
|
|
313
|
+
});
|
|
314
|
+
printRtkReconcileResult(rtkReconcile);
|
|
315
|
+
|
|
288
316
|
console.log("Setup complete.");
|
|
289
317
|
return 0;
|
|
290
318
|
}
|
|
@@ -297,7 +325,7 @@ function runInstallScript() {
|
|
|
297
325
|
console.log("Configuring CPMM...\n");
|
|
298
326
|
const result = spawnSync("bash", [scriptPath], {
|
|
299
327
|
stdio: "inherit",
|
|
300
|
-
env: { ...process.env },
|
|
328
|
+
env: { ...process.env, CPMM_SETUP_WRAPPER: "1" },
|
|
301
329
|
});
|
|
302
330
|
if (result.status !== 0) {
|
|
303
331
|
console.error("Config setup had issues. Run 'bash install.sh' manually if needed.");
|
package/lib/rtk.js
CHANGED
|
@@ -9,6 +9,8 @@ const RTK_INSTALL_URL = "https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads
|
|
|
9
9
|
const RTK_DOCS_URL = "https://github.com/rtk-ai/rtk/blob/main/docs/INSTALL.md";
|
|
10
10
|
const CPMM_HOOK_NAME = "critical-action-check.sh";
|
|
11
11
|
const RTK_HOOK_NAME = "rtk-rewrite.sh";
|
|
12
|
+
const RTK_HOOK_COMMAND = "~/.claude/hooks/rtk-rewrite.sh";
|
|
13
|
+
const RTK_HOOK_TIMEOUT = 10;
|
|
12
14
|
|
|
13
15
|
function commandExists(cmd) {
|
|
14
16
|
try {
|
|
@@ -30,6 +32,10 @@ function runCommand(bin, args, label) {
|
|
|
30
32
|
return true;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
function cloneJson(value) {
|
|
36
|
+
return JSON.parse(JSON.stringify(value));
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
function getRtkManualInstallHints() {
|
|
34
40
|
return [
|
|
35
41
|
"RTK is optional. CPMM setup will continue without it.",
|
|
@@ -114,6 +120,198 @@ function flattenBashHooks(settings) {
|
|
|
114
120
|
return flattened;
|
|
115
121
|
}
|
|
116
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
|
+
|
|
117
315
|
function inspectRtkStatus(settingsPath) {
|
|
118
316
|
const result = {
|
|
119
317
|
binaryInstalled: commandExists("rtk"),
|
|
@@ -162,7 +360,7 @@ function inspectRtkStatus(settingsPath) {
|
|
|
162
360
|
|
|
163
361
|
const rtkHook = hooks[rtkIndex];
|
|
164
362
|
result.timeoutValue = typeof rtkHook.timeout === "number" ? rtkHook.timeout : null;
|
|
165
|
-
result.timeoutOk = result.timeoutValue ===
|
|
363
|
+
result.timeoutOk = result.timeoutValue === RTK_HOOK_TIMEOUT;
|
|
166
364
|
if (!result.timeoutOk) {
|
|
167
365
|
result.warnings.push("rtk_timeout_not_10");
|
|
168
366
|
}
|
|
@@ -173,7 +371,11 @@ function inspectRtkStatus(settingsPath) {
|
|
|
173
371
|
module.exports = {
|
|
174
372
|
RTK_DOCS_URL,
|
|
175
373
|
RTK_INSTALL_URL,
|
|
374
|
+
RTK_HOOK_COMMAND,
|
|
375
|
+
RTK_HOOK_TIMEOUT,
|
|
176
376
|
attemptRtkInstall,
|
|
177
377
|
getRtkManualInstallHints,
|
|
178
378
|
inspectRtkStatus,
|
|
379
|
+
normalizeManagedRtkHookSettings,
|
|
380
|
+
reconcileManagedRtkHook,
|
|
179
381
|
};
|
package/package.json
CHANGED