@kaydash9999/banker-plugins 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ user-invocable: false
8
8
 
9
9
  Use this built-in reference when you need detailed agent/tool/skill catalog information that does not need to live in every `CLAUDE.md` / `AGENTS.md` session.
10
10
 
11
- **런타임 — 이 문서는 양 런타임 카탈로그를 병기한다.** Claude Code는 **OMC(oh-my-claudecode)**, Codex CLI는 대응 프레임워크 **OMX(oh-my-codex)** 를 쓴다. 아래 §Agent Catalog ~ §Commit Protocol 은 **OMC(Claude)** 기준이고, 말미의 **§OMX (Codex) Catalog** 에 OMX 대응 카탈로그를 실측 정리했다. 이름이 대체로 대응하나 1:1은 아닐 수 있으니 설치 환경(`~/.claude` ↔ `~/.codex`) 기준으로 확인하라.
11
+ **런타임 — 이 문서는 양 런타임 카탈로그를 병기한다.** Claude Code는 **OMC(oh-my-claudecode)**, Codex CLI는 대응 프레임워크 **OMX(oh-my-codex)** 를 쓴다. 아래 §Agent Catalog ~ §Commit Protocol 은 **OMC(Claude) 5.6.1** 기준이고, 말미의 **§OMX (Codex) Catalog** 에 OMX 대응 카탈로그를 실측 정리했다. 이름이 대체로 대응하나 1:1은 아닐 수 있으니 설치 환경(`~/.claude` ↔ `~/.codex`) 기준으로 확인하라. **OMC 5.0.0 은 워크플로 17개를 별칭 없이 삭제했다**(§Retired in OMC 5). 삭제된 이름을 부르면 실패한다.
12
12
 
13
13
  ## Agent Catalog
14
14
 
@@ -22,7 +22,7 @@ Prefix: `oh-my-claudecode:`. See `agents/*.md` for full prompts.
22
22
  - `executor` (sonnet) — implementation and refactoring
23
23
  - `verifier` (sonnet) — completion evidence and validation
24
24
  - `tracer` (sonnet) — trace gathering and evidence capture
25
- - `security-reviewer` (sonnet) — trust boundaries and vulnerabilities
25
+ - `security-reviewer` (opus) — trust boundaries and vulnerabilities
26
26
  - `code-reviewer` (opus) — comprehensive code review
27
27
  - `test-engineer` (sonnet) — testing strategy and regression coverage
28
28
  - `designer` (sonnet) — UX and interaction design
@@ -39,20 +39,32 @@ Prefix: `oh-my-claudecode:`. See `agents/*.md` for full prompts.
39
39
  - `haiku` — quick lookups, lightweight inspection, narrow docs work
40
40
  - `sonnet` — standard implementation, debugging, and review
41
41
  - `opus` — architecture, deep analysis, consensus planning, and high-risk review
42
+ - `fable` — Claude Fable 5, above Opus, when passed explicitly
43
+
44
+ The session model chosen with `/model` drives only the main loop; delegated agents run on their pinned tier unless `model` is passed or `agents.<name>.model` overrides it.
42
45
 
43
46
  ## Tools Reference
44
47
 
45
48
  ### External AI / orchestration
46
- - `/team N:executor "task"`
49
+ - `/team N:executor "task"` (`team-exec` worker type: executor, debugger, designer, codex, gemini, antigravity)
47
50
  - `omc team N:codex|gemini "..."`
48
- - `omc ask <claude|codex|gemini>`
49
- - `/ccg`
51
+ - `omc ask <claude|codex|gemini|antigravity|grok|cursor>` (skill: `/oh-my-claudecode:ask`)
52
+ - Multi-model synthesis (formerly `/ccg`, removed in 5.0.0): `/oh-my-claudecode:ask codex` + `/oh-my-claudecode:ask antigravity`, then synthesize, or `/team`
53
+
54
+ ### OMC CLI (OMC 5)
55
+ - `omc ralph verify [--write-baseline] [--session <id>]` — feedback baseline and gate that `ralph` runs itself; the global CLI must match the plugin (a 4.x CLI lacks it, and `ralph` then stops)
56
+ - `omc ultragoal create-goals|complete-goals|checkpoint|record-review-blockers|status` — durable multi-goal ledger under `.omc/ultragoal/`
50
57
 
51
58
  ### OMC state
52
- - `state_read`, `state_write`, `state_clear`, `state_list_active`, `state_get_status`
59
+ - `state_read`, `state_write`, `state_clear`, `state_list_active`, `state_get_status`, `state_migrate_non_git`
60
+
61
+ ### Wiki, shared memory, traces
62
+ - `wiki_ingest`, `wiki_query`, `wiki_list`, `wiki_read`, `wiki_lint`, `wiki_add`, `wiki_delete`
63
+ - `shared_memory_read`, `shared_memory_write`, `shared_memory_list`, `shared_memory_delete`, `shared_memory_cleanup`
64
+ - `trace_timeline`, `trace_summary`, `session_search`
53
65
 
54
66
  ### Team runtime
55
- - `TeamCreate`, `TeamDelete`, `SendMessage`, `TaskCreate`, `TaskList`, `TaskGet`, `TaskUpdate`
67
+ - Claude Code 2.1.178+ removed native `TeamCreate`/`TeamDelete`. With `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` each session has one implicit team: spawn teammates directly with the Agent/Task tool (`name="worker-N"`) and coordinate through `SendMessage` and the shared task list (`TaskCreate`, `TaskList`, `TaskGet`, `TaskUpdate`)
56
68
 
57
69
  ### Notepad
58
70
  - `notepad_read`, `notepad_write_priority`, `notepad_write_working`, `notepad_write_manual`
@@ -67,35 +79,57 @@ Prefix: `oh-my-claudecode:`. See `agents/*.md` for full prompts.
67
79
 
68
80
  ## Skills Registry
69
81
 
70
- Invoke built-in workflows via `/oh-my-claudecode:<name>`.
82
+ Invoke built-in workflows via `/oh-my-claudecode:<name>` (OMC 5.6.1).
71
83
 
72
- ### Workflow skills
73
- - `autopilot` — full autonomous execution from idea to working code
74
- - `ralph` — persistence loop until completion with verification
75
- - `ultrawork` — high-throughput parallel execution
76
- - `visual-verdict` — structured visual QA verdicts
77
- - `team` — coordinated team orchestration
78
- - `ccg` — Codex + Gemini + Claude synthesis lane
79
- - `ultraqa` — QA cycle: test, verify, fix, repeat
80
- - `omc-plan` — planning workflow and `/plan`-safe alias
81
- - `ralplan` — consensus planning workflow
82
- - `sciomc` — science/research workflow
83
- - `external-context` — external docs/research workflow
84
- - `deepinit` — hierarchical AGENTS.md generation
84
+ ### Canonical workflows (Tier-0)
85
+ - `plan` → `execute` → `review` → `verify` — plan, build, review, verify. As plugin skills they are `/oh-my-claudecode:plan` and `/oh-my-claudecode:review`; a standalone install (`omc setup` copying skills into `~/.claude/skills/`) names them `omc-plan` and `omc-review`, because the bare names collide with Claude Code's own `/plan` and `/review`
86
+ - `ralplan` — consensus planning (Planner → Architect → Critic); stops at a `pending approval` plan
85
87
  - `deep-interview` — Socratic ambiguity-gated requirements workflow
86
- - `ai-slop-cleaner` — regression-safe cleanup workflow
88
+
89
+ ### Directly invocable workflows
90
+ - `autopilot` — full autonomous execution from idea to working code
91
+ - `ralph` — PRD-driven persistence loop with a configurable reviewer; needs the `omc` CLI (`omc ralph verify`)
92
+ - `ultragoal` — durable multi-goal plan and ledger with Claude Code `/goal` handoff
93
+ - `autoresearch` — stateful single-mission improvement loop
94
+
95
+ ### Lanes and quality
96
+ - `team` — N coordinated agents on a shared task list (`team-plan` → `team-prd` → `team-exec` → `team-verify` → `team-fix`)
97
+ - `research` — grounded, sourced investigation; `external-context` — parallel external doc lookup
98
+ - `trace` — competing-hypothesis tracing; `debug` — diagnose OMC session or repo state
99
+ - `verify` — evidence before a completion claim (gathers evidence, does not fix)
100
+ - `visual-verdict` — screenshot-to-reference QA verdict
101
+ - `tdd` — test-first at agreed seams
102
+ - `ai-slop-cleaner` — regression-safe cleanup, an opt-in lane of `review`
103
+ - `minimal-code-discipline`, `minimal-prose-discipline`, `agent-doc-discipline` — writing-time disciplines for code, replies, and agent-facing docs
104
+
105
+ ### Shipyard and repo workflows
106
+ - `drydock` (harness seed), `launch` (governed delivery pipeline), `harbor` (intake of issues and PRs), `intent` (requirements intake), `ask-navigator` (decision map), `map` (which skill owns which job), `loft` (throwaway design artifact), `refit` (environment retrospective), `architecture-survey`
107
+ - `graph` (declarative DAG runtime), `diagram`, `pr` (PR body from OMC's paper trail), `release`, `deepinit`, `self-improve`
87
108
 
88
109
  ### Utility skills
89
- - `ask`, `cancel`, `note`, `skillify`, `learner` (deprecated alias), `omc-setup`, `mcp-setup`, `hud`, `omc-doctor`, `trace`, `release`, `project-session-manager`, `skill`, `writer-memory`, `configure-notifications`
110
+ - `ask`, `cancel`, `remember`, `skillify`, `skill`, `wiki`, `omc-setup`, `omc-doctor`, `hud`, `project-session-manager` (alias `psm`), `configure-notifications`
111
+
112
+ ### Retired in OMC 5 (removed, no alias)
113
+
114
+ | Removed | Use instead |
115
+ |---|---|
116
+ | `ultrawork`, `pipeline` | `execute` (or `team` for coordinated parallel workers) |
117
+ | `ultraqa` | `verify` |
118
+ | `ultrapilot`, `swarm`, `omc-teams` | `team` (or `omc team`) |
119
+ | `merge-readiness`, `understanding-gate` | `review` |
120
+ | `deep-dive`, `sciomc` | `research` |
121
+ | `ccg` | `ask` + `team` |
122
+ | `setup` | `omc-setup` |
123
+ | `mcp-setup` | `claude mcp add <name> ...` |
124
+ | `omc-reference` | `wiki` |
125
+ | `learner`, `writer-memory` | `remember` |
126
+ | `local-build-reminder` | none |
90
127
 
91
128
  ### Keyword triggers kept compact in CLAUDE.md
92
129
  - `"autopilot"→autopilot`
93
- - `"ralph"→ralph`
94
- - `"ulw"→ultrawork`
95
- - `"ccg"→ccg`
96
130
  - `"ralplan"→ralplan`
97
131
  - `"deep interview"→deep-interview`
98
- - `"deslop" / "anti-slop"→ai-slop-cleaner`
132
+ - `"deslop" / "anti-slop"→ai-slop-cleaner` (opt-in lane of review)
99
133
  - `"deep-analyze"→analysis mode`
100
134
  - `"tdd"→TDD mode`
101
135
  - `"deepsearch"→codebase search`
@@ -146,26 +180,27 @@ Not-tested: End-to-end plugin marketplace install in a fresh Claude profile
146
180
 
147
181
  **런타임:** oh-my-codex(OMX), Codex CLI. 설치/점검 `omx setup` · `omx doctor`. 스킬은
148
182
  `~/.codex/skills/<name>/SKILL.md`(**dir == frontmatter name**)를 자동탐색. 실측 기준 oh-my-codex
149
- 0.18.16 — 버전에 따라 목록이 바뀔 수 있으니 설치 환경에서 확인.
183
+ 0.20.2 패키지 — `omx setup` 이 까는 묶음은 그 일부일 수 있고 버전에 따라 목록이 바뀌니 설치 환경에서 확인.
184
+ OMX 는 OMC 5 와 달리 `ultraqa`·`ultrawork`·`swarm`·`pipeline` 을 아직 제공한다.
150
185
 
151
186
  ### OMX Agent Prompts
152
187
  `~/.codex/prompts/*.md` (OMX 오케스트레이션이 사용). OMC 에이전트에 대응 + Codex 확장:
153
188
  - 대응: `analyst` · `architect` · `critic` · `debugger` · `executor` · `verifier` · `explore` · `planner` · `git-master` · `designer` · `qa-tester` · `security-reviewer` · `test-engineer` · `writer` · `code-reviewer` · `code-simplifier`
154
- - 확장: `vision` · `researcher` · `ux-researcher` · `product-manager` · `product-analyst` · `information-architect` · `dependency-expert` · `api-reviewer` · `performance-reviewer` · `quality-reviewer` · `quality-strategist` · `style-reviewer` · `scholastic` · `team-orchestrator` · `team-executor` · `build-fixer` · `sisyphus-lite` · `prometheus-strict-{metis,momus,oracle}`
189
+ - 확장: `vision` · `researcher` · `ux-researcher` · `product-manager` · `product-analyst` · `information-architect` · `dependency-expert` · `api-reviewer` · `performance-reviewer` · `quality-reviewer` · `quality-strategist` · `style-reviewer` · `scholastic` · `team-orchestrator` · `team-executor` · `build-fixer` · `explore-harness` · `sisyphus-lite` · `prometheus-strict-{metis,momus,oracle}`
155
190
 
156
191
  ### OMX Skills Registry
157
192
  `$<name>` 로 호출:
158
- - **오케스트레이션**: `ralph` · `ralplan` · `ultraqa` · `ultrawork` · `ultragoal` · `autopilot` · `team` · `swarm` · `pipeline` · `tdd` · `ai-slop-cleaner` · `visual-ralph` · `visual-verdict` · `ralph-init`
193
+ - **오케스트레이션**: `ralph` · `ralplan` · `plan` · `ultraqa` · `ultrawork` · `ultragoal` · `autopilot` · `team` · `swarm` · `pipeline` · `tdd` · `ai-slop-cleaner` · `visual-ralph` · `visual-verdict` · `ralph-init` · `cancel`
159
194
  - **리서치/분석**: `autoresearch` · `autoresearch-goal` · `best-practice-research` · `deepsearch` · `analyze` · `trace`
160
195
  - **자문/리뷰**: `ask`(로컬 Claude/Gemini 자문 통합 진입점; 구 `ask-claude`·`ask-gemini` 스킬도 병존) · `review` · `code-review` · `security-review`
161
- - **셋업/유틸**: `omx-setup` · `doctor` · `hud` · `wiki` · `note` · `skill` · `help` · `configure-notifications` · `ecomode` · `design` · `frontend-ui-ux` · `web-clone` · `build-fix` · `performance-goal` · `prometheus-strict` · `deep-interview` · `worker`
196
+ - **셋업/유틸**: `omx-setup` · `doctor` · `hud` · `wiki` · `note` · `skill` · `help` · `configure-notifications` · `ecomode` · `design` · `frontend-ui-ux` · `web-clone` · `build-fix` · `git-master` · `performance-goal` · `prometheus-strict` · `deep-interview` · `worker`
162
197
 
163
198
  ### OMX Interfaces
164
199
  - **스킬**: `~/.codex/skills/<name>/`(dir==name). **커맨드/프롬프트**: `~/.codex/prompts/<name>.md`(스킬 목록과 별개 표면).
165
200
  - **외부 LLM**: `$ask`(로컬 Claude/Gemini CLI 자문).
166
201
  - **MCP**: `codex mcp add <NAME> (--url <URL> | -- <COMMAND>...)`.
167
202
  - **`/copy` 등 슬래시 명령은 Codex TUI 내장**(컴파일 바이너리 — `~/.codex/skills`·`prompts`·`--help` 목록엔 안 뜸).
168
- - ⚠️ `~/.codex/AGENTS.md` 본문은 `omx setup/doctor` 가 재생성 → **직접 쓰지 말 것**(조각 소실).
203
+ - ⚠️ `~/.codex/AGENTS.md` 본문은 `omx setup/doctor` 가 재생성 → **직접 쓰지 말 것**(조각 소실). `omx setup --merge-agents` 로 다시 만들 때 남는 것은 `<!-- USER:OMX:POLICY:START -->` … `<!-- USER:OMX:POLICY:END -->` 로 감싼 블록뿐이고(banker `tone-compact` 가 이 방식), `--force` 나 덮어쓰기 확인으로 새로 쓰면 그 블록도 사라진다(백업은 `~/.omx/backups/setup/`). 비대화형 `omx setup` 은 기존 AGENTS.md 덮어쓰기를 건너뛴다.
169
204
 
170
205
  ### OMC ↔ OMX 대응
171
206
  OMC(Claude) ↔ OMX(Codex) 는 대체로 대칭이다: `claude mcp`↔`codex mcp`, `~/.claude`↔`~/.codex`,
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: payload-mon
3
+ description: "(banker) OMC HUD 상태표시줄에 현재 세션의 API 요청 payload 추정치(32MB 한도 대비)를 ctx 옆에 표시하는 기능을 켜고 끔(8MB부터 표시). 'payload-mon'/'payload 모니터 켜/꺼'/'상태표시줄 payload 표시'/'요청 크기 경고'/'32MB 한도 경고'/'payload 표시가 안 보여' 시 사용."
4
+ argument-hint: "on | off | status"
5
+ ---
6
+
7
+ # payload-mon — 상태표시줄 payload 모니터 켜기·끄기
8
+
9
+ 인자: `$ARGUMENTS` (`on`·`off`·`status` 중 하나, 비어 있으면 `status`. Codex 는 사용자 메시지에서 읽는다). 답변은 한글로 짧게.
10
+
11
+ Claude Code 는 요청 본문이 32MB 를 넘으면 "Request too large (max 32MB)" 로 거절한다.
12
+ 이 한도는 토큰이 아니라 바이트라서, ctx(컨텍스트 %)가 낮아도 이미지나 큰 도구 결과가 쌓이면 걸린다.
13
+ 이 스킬은 그 바이트를 추정해 한도의 25%(8MB)부터 ctx 옆에 `payload:28%/9.0MB` 처럼 보여 준다.
14
+
15
+ ## 실행
16
+
17
+ 이 SKILL.md 가 있는 디렉터리의 절대경로로 스크립트를 실행하고, 출력 내용을 짧게 전한다.
18
+ Claude Code 는 그 경로를 스킬 맨 위 "Base directory for this skill:" 로 보여 주고, Codex 는 `~/.codex/skills/banker-payload-mon/` 이다(프로젝트 범위 설치면 `./.codex/skills/banker-payload-mon/`).
19
+ `~` 는 따옴표 안에서 펼쳐지지 않으니 절대경로를 쓴다.
20
+
21
+ ```bash
22
+ node "<이 스킬 디렉터리 절대경로>/scripts/payload-mon.mjs" <on|off|status>
23
+ ```
24
+
25
+ - 명령에 넣는 인자는 `on`·`off`·`status` 세 단어 중 정확히 하나뿐이다. 사용자 입력을 그대로 셸에 붙이지 않는다. 그 밖의 값이면 실행하지 말고 사용법을 알린다.
26
+ - `on`·`off` 는 사용자가 켜거나 끄라고 했을 때만 실행한다. 상태만 물으면 `status` 로 충분하다.
27
+ - HUD 래퍼를 손으로 고치지 않는다. 스크립트가 대상 확인(OMC 커스텀 HUD 인지), 삽입 위치 확인, 쓰기 권한 확인, `node --check` 검증, 수정 전 백업을 한다. 손으로 고치면 이 안전장치를 건너뛴다.
28
+ - 종료 코드 0 은 완료, 1 은 아무것도 바꾸지 않았다는 뜻이니 메시지를 그대로 전한다. 2 는 예기치 못한 오류로 중간에 멈춘 것이니 메시지를 전하고 `status` 로 상태를 확인한다.
29
+ - 출력이 비어 있으면 스크립트가 실행되지 않은 것이다. 성공으로 보고하지 말고 경로부터 확인한다.
30
+
31
+ ## 런타임
32
+
33
+ - **Claude Code**: 다음 statusline 갱신부터 반영되며 재시작은 필요 없다.
34
+ - **Codex**: Codex 자신의 요청 크기는 재지 않는다. 32MB 한도와 세션 파일 형식은 Claude Code 의 것이기 때문이다.
35
+ Codex 에서 실행해도 같은 머신의 Claude Code HUD 래퍼를 켜고 끄는 것만 하고, `status` 의 "현재 세션" 줄은 Claude Code 세션에서만 나온다.
36
+ 사용자가 Codex 쪽 표시를 기대하면 이 점을 먼저 알린다.
37
+
38
+ ## 동작
39
+
40
+ - `on`: Claude Code 설정 폴더(`$CLAUDE_CONFIG_DIR`, 없으면 `~/.claude`)의 `hud/omc-hud-custom.mjs` 에서, ctx 를 넣는 줄 바로 뒤에 `// >>> payload-mon >>>` 블록 하나를 넣는다.
41
+ 블록이 불러오는 추정 모듈은 래퍼 옆 `hud/payload-mon/payload-size.mjs` 에 사본으로 둔다. 플러그인 설치 경로는 버전마다 바뀌어 업데이트 뒤에 사라지기 때문이다.
42
+ 이미 켜져 있으면 래퍼를 바꾸지 않고, 모듈 사본이 이 버전과 다를 때만 사본을 맞춘다. 예전 방식(블록 2개)으로 켜져 있었다면 새 방식으로 바꾼다.
43
+ 바꾸기 전 내용은 `omc-hud-custom.mjs.payload-mon.bak` 에 남는다.
44
+ - `off`: 그 블록만 빼서 래퍼를 켜기 전 상태로 되돌리고, 모듈 사본과 추정치 캐시를 지운다.
45
+ - `status`: 켜짐·꺼짐, 모듈 사본이 이 버전과 같은지, statusLine 이 이 래퍼를 쓰는지, 현재 세션의 추정치를 보여 준다.
46
+ - 같은 래퍼에 자기 표시 블록을 넣는 다른 도구가 있어도 서로의 블록을 건드리지 않는다.
47
+
48
+ ## 무엇을 어떻게 보여 주는가
49
+
50
+ - `ctx:21%|payload:28%/9.0MB|se:…`: 32MB 대비 %(ctx 와 같은 색: 70% 미만 초록, 85% 미만 노랑, 그 이상 빨강)와 크기(MB, 청록). 8MB 미만이면 숨긴다.
51
+ - 값은 마지막 `/compact` 이후 Claude Code 가 실제로 보내는 메시지의 바이트다. Claude Code 가 32MB 한도를 판정하는 방식과 같고, 무엇을 넣고 뺄지는 Claude Code 2.1.288 의 요청 필터를 따른다.
52
+ - 세션 파일 크기가 아니다. 71MB 짜리 세션 파일도 실제 전송분은 0.1MB 일 수 있다. 알려진 오차는 `scripts/payload-size.mjs` 머리 주석에 있다.
53
+
54
+ ## 거부되거나 안 보일 때
55
+
56
+ - "OMC 커스텀 HUD 래퍼가 아닙니다" 또는 "삽입 위치를 찾지 못했습니다": HUD 래퍼의 모양이 이 스크립트가 아는 것과 다르다. 파일은 그대로다.
57
+ 플러그인 파일을 손으로 고치지 말고, banker 를 최신으로 올린 뒤(`update-banker`) 다시 시도한다. 그래도 안 되면 banker 이슈로 알린다.
58
+ - "node --check 실패": 블록을 넣은 결과가 문법 검사를 통과하지 못했다(예: 래퍼가 ctx 를 넣는 줄을 비동기가 아닌 함수 안으로 옮김). 파일은 그대로다.
59
+ - "래퍼에 쓸 권한이 없습니다", 또는 `status` 의 "HUD 래퍼를 읽을 수 없음": 래퍼 파일의 권한을 확인한다. 파일은 그대로다.
60
+ - "모듈 사본을 쓰지 못했습니다": 래퍼 옆 `hud/payload-mon/` 에 쓸 수 없다(권한, 같은 이름의 파일, 디스크 공간). 래퍼는 그대로다.
61
+ - "표시(>>> / <<<)의 짝이 맞지 않습니다": 래퍼에 payload-mon 표시 줄이 한쪽만 남았다. `on`·`off` 모두 거부하므로, 손으로 고치지 말라는 원칙의 유일한 예외다.
62
+ `setup-omc-hud` 로 HUD 를 재설치하거나, 래퍼에서 짝 없이 남은 `// >>> payload-mon >>>` 또는 `// <<< payload-mon <<<` 줄을 지운 뒤 다시 실행한다.
63
+ - HUD 를 재설치한 뒤 payload 가 안 보이면 `on` 을 다시 실행한다. `setup-omc-hud` 로 재설치했다면 그 스킬이 다시 켠다.
64
+ - banker 업데이트 알림에 payload-mon 이 바뀌었다고 나오거나 `status` 에 "모듈 사본이 이 버전과 다름" 이 나오면, `on` 을 한 번 실행해 사본을 맞춘다.
65
+ - `status` 에 "주의: statusLine 명령에 … 보이지 않습니다" 가 나오면 statusline 이 다른 명령이다. smart-compact 처럼 HUD 를 감싸 호출하는 래퍼라면 정상 표시된다. 이 스킬은 OMC 커스텀 HUD 래퍼에만 붙는다.
66
+ - 추정치가 실제 "Request too large" 시점과 크게 어긋나면 Claude Code 가 세션 파일 형식이나 요청 필터를 바꿨을 수 있다. banker 이슈로 알린다.
@@ -0,0 +1,377 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * payload-mon: turns the statusline payload segment on or off.
4
+ *
5
+ * node payload-mon.mjs on add the payload-mon block to the OMC HUD wrapper
6
+ * node payload-mon.mjs off take it out again, with the module copy and the cache
7
+ * node payload-mon.mjs status say whether it is on, with this session's estimate
8
+ *
9
+ * The wrapper (<config>/hud/omc-hud-custom.mjs, where <config> is
10
+ * $CLAUDE_CONFIG_DIR or ~/.claude; $PAYLOAD_MON_HUD_FILE overrides the path)
11
+ * gets one marker-delimited block right after the line that adds ctx, which
12
+ * works out the segment and places it there. Blocks other tools add to the same
13
+ * wrapper with their own markers are left alone.
14
+ *
15
+ * Two wrapper layouts take the block. The older one reads stdin into `let input`
16
+ * and runs the OMC HUD through spawnSync; the current omc_hud one reads it into
17
+ * `const input`, runs the HUD in-process and keeps spawnSync for a fallback
18
+ * branch. A block anchored on spawnSync would land in that branch, so the ctx
19
+ * line is the only anchor. Earlier versions that also put a block before
20
+ * spawnSync are converted on the next `on`.
21
+ *
22
+ * The block imports a copy of payload-size.mjs kept beside the wrapper
23
+ * (<hud>/payload-mon/payload-size.mjs), not the module in this folder: a plugin
24
+ * install lives under a versioned path that the next update retires, and an
25
+ * import pointing there would go dark without a word. `on` writes the copy and
26
+ * refreshes it when this version's module differs; `status` says when it does.
27
+ *
28
+ * `on` converges on the current block and is a no-op when it is in place. It
29
+ * writes only into the OMC custom HUD (signature-gated); `off` removes only
30
+ * payload-mon's own marker blocks. Both refuse markers that do not pair up, keep
31
+ * the text from before each change in <wrapper>.payload-mon.bak, and write
32
+ * nothing that fails `node --check`.
33
+ * Exit status 1 means nothing was changed; 2 means an unexpected error stopped
34
+ * the command partway, so check `status`.
35
+ */
36
+ import { spawnSync } from "node:child_process";
37
+ import {
38
+ accessSync,
39
+ constants,
40
+ existsSync,
41
+ mkdirSync,
42
+ mkdtempSync,
43
+ readdirSync,
44
+ readFileSync,
45
+ realpathSync,
46
+ renameSync,
47
+ rmdirSync,
48
+ rmSync,
49
+ writeFileSync,
50
+ } from "node:fs";
51
+ import { homedir, tmpdir } from "node:os";
52
+ import { basename, dirname, join } from "node:path";
53
+ import { fileURLToPath, pathToFileURL } from "node:url";
54
+ import { CACHE_DIR, clearCache, estimatePayloadBytes, REQUEST_LIMIT_BYTES, SHOW_FROM_BYTES } from "./payload-size.mjs";
55
+
56
+ const OPEN = "// >>> payload-mon >>>";
57
+ const CLOSE = "// <<< payload-mon <<<";
58
+ const BLOCKS = /^[ \t]*\/\/ >>> payload-mon >>>[\s\S]*?\/\/ <<< payload-mon <<<[ \t]*\r?\n/gm;
59
+ // The block goes right after the line that adds ctx.
60
+ const CTX_LINE = /^ {2}if \(f\.ctx\) colored\.push\(colorCtx\(f\.ctx\)\);\r?\n/m;
61
+ // The statusline JSON the block reads, in either layout.
62
+ const INPUT_DECL = /^(?:let|const) input\b/m;
63
+ const MB = 1024 * 1024;
64
+
65
+ const CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
66
+
67
+ const DEFAULTS = {
68
+ hudFile: process.env.PAYLOAD_MON_HUD_FILE || join(CONFIG_DIR, "hud", "omc-hud-custom.mjs"),
69
+ moduleSource: fileURLToPath(new URL("./payload-size.mjs", import.meta.url)),
70
+ cacheDir: CACHE_DIR,
71
+ settingsFile: join(CONFIG_DIR, "settings.json"),
72
+ projectsDir: join(CONFIG_DIR, "projects"),
73
+ sessionId: process.env.CLAUDE_CODE_SESSION_ID,
74
+ };
75
+
76
+ // Where the block's module lives: beside the wrapper, outside any versioned path.
77
+ const moduleCopy = (hudFile) => join(dirname(hudFile), "payload-mon", "payload-size.mjs");
78
+ const moduleUrl = (hudFile) => pathToFileURL(moduleCopy(hudFile)).href;
79
+
80
+ // Right after ctx, colored like the 5h/wk rates: % on the ctx scale, MB in cyan.
81
+ // Its names are block-scoped, so none can clash with the wrapper's own.
82
+ const placeBlock = (url) =>
83
+ [
84
+ " " + OPEN,
85
+ " // payload-mon: estimated API request payload against the 32MB request limit, shown",
86
+ " // like ctx from 8MB (25%) on. `payload-mon off` removes this block; a missing or",
87
+ " // broken module costs only this segment, never the statusline.",
88
+ " try {",
89
+ " const transcript = JSON.parse(input)?.transcript_path;",
90
+ " if (transcript) {",
91
+ ` const { estimatePayloadBytes, payloadSegment } = await import(${JSON.stringify(url)});`,
92
+ " const segment = payloadSegment(estimatePayloadBytes(transcript));",
93
+ " if (segment) colored.push(colorRate(segment));",
94
+ " }",
95
+ " } catch {",
96
+ " /* no stdin JSON, unreadable transcript or module error */",
97
+ " }",
98
+ " " + CLOSE,
99
+ "",
100
+ ].join("\n");
101
+
102
+ const insertBlock = (base, url) => base.replace(CTX_LINE, (line) => line + placeBlock(url));
103
+
104
+ const count = (text, needle) => text.split(needle).length - 1;
105
+
106
+ // The wrapper's text, null when there is no such file. Any other read failure
107
+ // (permissions, say) is not "missing" and propagates.
108
+ const readText = (file) => {
109
+ try {
110
+ return readFileSync(file, "utf8");
111
+ } catch (e) {
112
+ if (e.code === "ENOENT" || e.code === "ENOTDIR") return null;
113
+ throw e;
114
+ }
115
+ };
116
+
117
+ const readBytes = (file) => {
118
+ try {
119
+ return readFileSync(file);
120
+ } catch {
121
+ return null;
122
+ }
123
+ };
124
+
125
+ const refusal = (why, hudFile) => ({ ok: false, message: `${why}. 래퍼를 그대로 두었습니다: ${hudFile}` });
126
+
127
+ // The block exactly as `on` writes it for this URL, once, right after the ctx line.
128
+ function inPlace(src, url) {
129
+ if (count(src, OPEN) !== 1 || count(src, CLOSE) !== 1) return false;
130
+ const ctx = CTX_LINE.exec(src);
131
+ return ctx !== null && src.startsWith(placeBlock(url), ctx.index + ctx[0].length);
132
+ }
133
+
134
+ // Why the block cannot go into this wrapper, or "" when it can. Markers that do
135
+ // not pair up would let the block pattern swallow the code between them.
136
+ function misfit(src) {
137
+ if (count(src, OPEN) !== count(src, CLOSE)) return "payload-mon 표시(>>> / <<<)의 짝이 맞지 않습니다";
138
+ const base = src.replace(BLOCKS, "");
139
+ if (!base.includes("omc-hud.mjs") || !base.includes("const colorRate")) return "OMC 커스텀 HUD 래퍼가 아닙니다";
140
+ if (!INPUT_DECL.test(base)) return "삽입 위치를 찾지 못했습니다(statusline 입력을 담는 input 변수가 없음)";
141
+ if (!CTX_LINE.test(base)) return "삽입 위치를 찾지 못했습니다(ctx를 넣는 줄이 없음)";
142
+ return "";
143
+ }
144
+
145
+ // "current", "stale" (differs from this version's module) or "missing".
146
+ function copyState(o) {
147
+ const copy = readBytes(moduleCopy(o.hudFile));
148
+ if (copy === null) return "missing";
149
+ const source = readBytes(o.moduleSource);
150
+ return source !== null && copy.equals(source) ? "current" : "stale";
151
+ }
152
+
153
+ // Replaces file through a fresh temp file ("wx") and a rename: a link planted at
154
+ // either name is replaced rather than written through, and a write that fails
155
+ // leaves the old file as it was.
156
+ function writeFresh(file, data, mode) {
157
+ const tmp = `${file}.${process.pid}.tmp`;
158
+ rmSync(tmp, { force: true });
159
+ try {
160
+ writeFileSync(tmp, data, { flag: "wx", mode });
161
+ renameSync(tmp, file);
162
+ } catch (e) {
163
+ rmSync(tmp, { force: true });
164
+ throw e;
165
+ }
166
+ }
167
+
168
+ // Brings the copy up to this version's module; true when it had to write. The
169
+ // statusline imports it on every refresh, so no other account may change it
170
+ // (reading it is harmless). When the write fails, a folder made here is taken
171
+ // away again: mkdirSync names a folder only when it created it, and rmdir
172
+ // leaves one that something else has meanwhile put a file in.
173
+ function syncModule(o) {
174
+ if (copyState(o) === "current") return false;
175
+ const copy = moduleCopy(o.hudFile);
176
+ const made = mkdirSync(dirname(copy), { recursive: true, mode: 0o755 }) !== undefined;
177
+ try {
178
+ writeFresh(copy, readFileSync(o.moduleSource), 0o644);
179
+ } catch (e) {
180
+ if (made) {
181
+ try {
182
+ rmdirSync(dirname(copy));
183
+ } catch {
184
+ /* no longer empty, so not only ours */
185
+ }
186
+ }
187
+ throw e;
188
+ }
189
+ return true;
190
+ }
191
+
192
+ // The first lines of node's complaint about `text` as an ES module, "" if it parses.
193
+ function syntaxError(text) {
194
+ const dir = mkdtempSync(join(tmpdir(), "payload-mon-"));
195
+ try {
196
+ const file = join(dir, "check.mjs");
197
+ writeFileSync(file, text);
198
+ const r = spawnSync(process.execPath, ["--check", file], { encoding: "utf8" });
199
+ return r.status === 0 ? "" : String(r.stderr || r.error).trim().split("\n").slice(0, 3).join(" | ");
200
+ } finally {
201
+ rmSync(dir, { recursive: true, force: true });
202
+ }
203
+ }
204
+
205
+ // Writes `after` over the wrapper if it parses, keeping `before` in the .bak.
206
+ // `prepare` runs once the text is known to parse and the wrapper to be writable,
207
+ // before the wrapper is touched, so a failure there leaves the wrapper as it was.
208
+ function commit(hudFile, before, after, done, prepare = () => {}) {
209
+ const err = syntaxError(after);
210
+ if (err) return { ok: false, message: `node --check 실패로 래퍼를 그대로 두었습니다: ${err}` };
211
+ try {
212
+ accessSync(hudFile, constants.W_OK);
213
+ } catch (e) {
214
+ return refusal(`래퍼에 쓸 권한이 없습니다(${e.code || e.message})`, hudFile);
215
+ }
216
+ try {
217
+ prepare();
218
+ } catch (e) {
219
+ return refusal(`모듈 사본을 쓰지 못했습니다(${e.code || e.message})`, hudFile);
220
+ }
221
+ writeFresh(`${hudFile}.payload-mon.bak`, before);
222
+ writeFileSync(hudFile, after);
223
+ return { ok: true, message: done };
224
+ }
225
+
226
+ // Whether settings.json runs this wrapper as the statusline.
227
+ function statusLineUses(settingsFile, hudFile) {
228
+ try {
229
+ const command = JSON.parse(readFileSync(settingsFile, "utf8"))?.statusLine?.command;
230
+ return typeof command === "string" && command.includes(basename(hudFile));
231
+ } catch {
232
+ return false;
233
+ }
234
+ }
235
+
236
+ const notShownNote = (o) =>
237
+ statusLineUses(o.settingsFile, o.hudFile)
238
+ ? ""
239
+ : `\n주의: statusLine 명령에 ${basename(o.hudFile)}가 보이지 않습니다. ` +
240
+ "다른 래퍼(smart-compact 등)가 HUD를 감싸 호출하면 정상 표시되고, 아니면 화면에 나타나지 않습니다.";
241
+
242
+ export function turnOn(opts = {}) {
243
+ const o = { ...DEFAULTS, ...opts };
244
+ const src = readText(o.hudFile);
245
+ if (src === null) return { ok: false, message: `HUD 래퍼가 없습니다: ${o.hudFile}` };
246
+ if (inPlace(src, moduleUrl(o.hudFile))) {
247
+ try {
248
+ const refreshed = syncModule(o);
249
+ return { ok: true, message: (refreshed ? "모듈 사본을 이 버전으로 맞췄습니다." : "이미 켜져 있습니다 (변경 없음).") + notShownNote(o) };
250
+ } catch (e) {
251
+ return { ok: false, message: `모듈 사본을 쓰지 못했습니다(${e.code || e.message}): ${moduleCopy(o.hudFile)}` };
252
+ }
253
+ }
254
+ const why = misfit(src);
255
+ if (why) return refusal(why, o.hudFile);
256
+ const done = "켰습니다. 다음 statusline 갱신부터 8MB 이상이면 ctx 옆에 payload가 표시됩니다.";
257
+ const after = insertBlock(src.replace(BLOCKS, ""), moduleUrl(o.hudFile));
258
+ return commit(o.hudFile, src, after, done + notShownNote(o), () => syncModule(o));
259
+ }
260
+
261
+ function removeBlocks(o, src) {
262
+ if (src === null) return { ok: true, message: `HUD 래퍼가 없어 끌 것이 없습니다: ${o.hudFile}` };
263
+ if (count(src, OPEN) !== count(src, CLOSE)) return refusal("payload-mon 표시(>>> / <<<)의 짝이 맞지 않습니다", o.hudFile);
264
+ const out = src.replace(BLOCKS, "");
265
+ if (out === src) return { ok: true, message: "이미 꺼져 있습니다 (래퍼 변경 없음)." };
266
+ return commit(o.hudFile, src, out, "껐습니다. 다음 statusline 갱신부터 payload가 표시되지 않습니다.");
267
+ }
268
+
269
+ // What off leaves behind otherwise: the cache (when this account controls its
270
+ // folder), and the module copy with its folder (only when nothing else is in
271
+ // it). Neither failing undoes off.
272
+ function removeLeftovers(o) {
273
+ const copy = moduleCopy(o.hudFile);
274
+ for (const remove of [() => clearCache(o.cacheDir), () => rmSync(copy, { force: true })]) {
275
+ try {
276
+ remove();
277
+ } catch {
278
+ /* stale numbers or an unused copy; off stands */
279
+ }
280
+ }
281
+ try {
282
+ rmdirSync(dirname(copy));
283
+ } catch {
284
+ /* absent, or holds something that is not ours */
285
+ }
286
+ }
287
+
288
+ export function turnOff(opts = {}) {
289
+ const o = { ...DEFAULTS, ...opts };
290
+ const res = removeBlocks(o, readText(o.hudFile));
291
+ if (res.ok) removeLeftovers(o);
292
+ return res;
293
+ }
294
+
295
+ // "현재 세션: 0.8MB / 32MB (2%), 8MB부터 표시" for the session that runs this, "" if unknown.
296
+ function sessionEstimate(o) {
297
+ if (!o.sessionId || !existsSync(o.projectsDir)) return "";
298
+ const file = `${o.sessionId}.jsonl`;
299
+ const project = readdirSync(o.projectsDir).find((d) => existsSync(join(o.projectsDir, d, file)));
300
+ if (!project) return "";
301
+ try {
302
+ const bytes = estimatePayloadBytes(join(o.projectsDir, project, file), o.cacheDir);
303
+ const pct = Math.floor((bytes / REQUEST_LIMIT_BYTES) * 100);
304
+ return `현재 세션: ${(bytes / MB).toFixed(1)}MB / 32MB (${pct}%), ${SHOW_FROM_BYTES / MB}MB부터 표시`;
305
+ } catch {
306
+ return "";
307
+ }
308
+ }
309
+
310
+ const COPY_NOTE = {
311
+ current: "",
312
+ stale: ", 단 모듈 사본이 이 버전과 다름 (on 으로 갱신)",
313
+ missing: ", 단 모듈 사본이 없어 표시되지 않음 (on 으로 복구)",
314
+ };
315
+
316
+ function stateOf(src, o) {
317
+ if (src === null) return "HUD 래퍼 없음";
318
+ if (count(src, OPEN) + count(src, CLOSE) === 0) return "꺼짐";
319
+ // on and off both refuse this, so pointing at on would be a dead end.
320
+ if (count(src, OPEN) !== count(src, CLOSE)) return "표시(>>> / <<<)의 짝이 맞지 않습니다 (on·off 모두 거부: HUD 재설치 또는 남은 표시 줄 정리)";
321
+ if (!inPlace(src, moduleUrl(o.hudFile))) return "켜짐이 불완전하거나 오래됨 (on 으로 복구)";
322
+ return "켜짐" + COPY_NOTE[copyState(o)];
323
+ }
324
+
325
+ // What status says about the wrapper. A wrapper it cannot read is reported, not
326
+ // thrown: status is where an exit 2 sends the user to look.
327
+ function describe(o) {
328
+ try {
329
+ return stateOf(readText(o.hudFile), o);
330
+ } catch (e) {
331
+ return `HUD 래퍼를 읽을 수 없음 (${e.code || e.message})`;
332
+ }
333
+ }
334
+
335
+ export function status(opts = {}) {
336
+ const o = { ...DEFAULTS, ...opts };
337
+ const lines = [`payload-mon: ${describe(o)} (${o.hudFile})`];
338
+ const note = notShownNote(o);
339
+ if (note) lines.push(note.trim());
340
+ const estimate = sessionEstimate(o);
341
+ if (estimate) lines.push(estimate);
342
+ return { ok: true, message: lines.join("\n") };
343
+ }
344
+
345
+ const COMMANDS = new Map([
346
+ ["on", turnOn],
347
+ ["off", turnOff],
348
+ ["status", status],
349
+ ]);
350
+
351
+ function main(arg) {
352
+ const run = COMMANDS.get(arg);
353
+ if (!run) {
354
+ process.stdout.write("사용법: payload-mon on | off | status\n");
355
+ return 1;
356
+ }
357
+ try {
358
+ const { ok, message } = run();
359
+ process.stdout.write(message + "\n");
360
+ return ok ? 0 : 1;
361
+ } catch (e) {
362
+ process.stdout.write(`예기치 못한 오류로 중단했습니다: ${e.message}\nstatus 로 상태를 확인하세요.\n`);
363
+ return 2;
364
+ }
365
+ }
366
+
367
+ // Run as a command, also when reached through a symlinked path: Node resolves
368
+ // symlinks for import.meta.url but not for process.argv[1].
369
+ const invokedDirectly = () => {
370
+ try {
371
+ return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
372
+ } catch {
373
+ return false;
374
+ }
375
+ };
376
+
377
+ if (invokedDirectly()) process.exitCode = main((process.argv[2] || "status").toLowerCase());