@kaydash9999/banker-plugins 0.20.5 → 0.21.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.
Files changed (41) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +24 -0
  4. package/PRIVACY.md +21 -1
  5. package/README.md +22 -14
  6. package/bin/banker.js +144 -11
  7. package/bin/lib/codex-hooks.mjs +249 -0
  8. package/bin/lib/telemetry-config.mjs +2 -0
  9. package/codex/manifest.json +13 -10
  10. package/codex/skills/graceful-pause/SKILL.md +30 -0
  11. package/codex/skills/progress/SKILL.md +42 -0
  12. package/codex/skills/progress/scripts/progress-plan.mjs +722 -0
  13. package/codex/skills/progress/scripts/progress.mjs +295 -0
  14. package/codex/transform-matrix.md +18 -13
  15. package/hooks/obsidize-hook.mjs +116 -4
  16. package/hooks/progress-plan.mjs +1 -1
  17. package/hooks/telemetry-count-codex.mjs +222 -0
  18. package/hooks/update-notify.mjs +31 -11
  19. package/package.json +1 -1
  20. package/skills/append_wiki/SKILL.md +34 -8
  21. package/skills/compact-wiki/SKILL.md +37 -6
  22. package/skills/deep-research/SKILL.md +6 -0
  23. package/skills/lfm/SKILL.md +148 -0
  24. package/skills/lineage/SKILL.md +14 -4
  25. package/skills/lineage/lineage.py +168 -4
  26. package/skills/obsidizer/SKILL.md +29 -10
  27. package/skills/obsidizer/obsidize.mjs +42 -11
  28. package/skills/omc-patch/SKILL.md +6 -0
  29. package/skills/payload-mon/SKILL.md +2 -0
  30. package/skills/rfp-author/SKILL.md +5 -3
  31. package/skills/rfp-author/references/vendor-rfp-structure.md +1 -1
  32. package/skills/setup-bypass-permissions/SKILL.md +27 -9
  33. package/skills/setup-bypass-permissions/scripts/codex-bypass.mjs +135 -0
  34. package/skills/setup-bypass-permissions/scripts/codex-config.mjs +210 -0
  35. package/skills/setup-omc-hud/SKILL.md +33 -3
  36. package/skills/setup-omc-hud/assets/codex-config.mjs +210 -0
  37. package/skills/setup-omc-hud/assets/codex-status-line.mjs +90 -0
  38. package/skills/smart-compact/SKILL.md +58 -12
  39. package/skills/smart-compact/scripts/codex-gate.mjs +384 -0
  40. package/skills/ultra-chain/SKILL.md +8 -4
  41. package/skills/ultra-interview/SKILL.md +13 -2
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code skills + commands — QA, audit, docs, architecture, dependency setup, and wiki; the banker plugin marketplace.",
9
- "version": "0.20.5"
9
+ "version": "0.21.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "banker",
3
- "version": "0.20.5",
3
+ "version": "0.21.0",
4
4
  "description": "Claude Code plugin: QA/audit/docs/architecture skills + commands, a /banker:setup installer for OS dependencies (playwright, etc.), and wiki compaction.",
5
5
  "author": {
6
6
  "name": "smallOpenSource",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.21.0] - 2026-10-28
4
+
5
+ ### Added
6
+ - 새 스킬 `lfm`(learn from master): 어떤 분야의 최고 전문가 그룹이 실제로 일하는 방식을 그들이 공개한 1차 자료로 조사한다. 근거 있는 그룹 3~5곳, 관점별 자료(작업 순서, 역할, 품질 기준, 검토, 도구, 실패와 대응), 공통 관행과 반대 근거, 계획 적용 항목(`M-1` …의 적용, 조정, 제외와 이유)을 낸다. 웹 조사를 못 하면 모델 지식으로 채우지 않고 "웹 조사 불가" 로 끝난다. Claude Code 와 Codex(Linux 0.144.5, Windows 0.160.1)에서 실제로 불러 확인했다.
7
+ - `ultra-interview`, `ultra-chain`, `deep-research`, `rfp-author` 의 리서치가 그 분야 전문가 그룹의 작업 방식도 조사해 계획에 쓴다. ultra-chain 은 1단계에서 `01-masters.md` 를 남기고, 3단계 계획이 `M-n` 마다 판정과 이유를 적으며 계획 검토자가 이를 본다. rfp-author 는 조사 결과를 내부 추적용 `references/expert-practices.md` 에만 두고 벤더 발송본에는 넣지 않는다.
8
+ - Codex 에서 모든 구성요소가 동작한다.
9
+ - 명령 `front-qa`, `setup` 이 Codex 스킬(`$banker-front-qa`, `$banker-setup`)로도 설치된다. Codex 0.144 이상에는 사용자 프롬프트가 없다.
10
+ - Codex 전용 스킬 `graceful-pause`(작업 중 보내면 실행 중인 도구 호출이 끝난 뒤 멈춤)와 `progress`(지금 세션의 계획 파일 트리, `update_plan` 단계, 최근 도구 호출. tmux 안이면 `--pane` 으로 오른쪽 칸 갱신).
11
+ - `banker setup --codex` 가 Codex 훅을 설치한다: 업데이트 알림(`SessionStart`), 사용량 카운팅(`Stop`, 턴마다 한 번). 사본은 `$CODEX_HOME/hooks-custom/banker/`, 묶음은 이벤트 맨 뒤, Windows 는 PowerShell 형식. `banker hooks status|enable|disable`, `banker uninstall --codex`. Codex 를 다시 열 때 Hooks need review 나 `/hooks` 에서 신뢰해야 돈다.
12
+ - `obsidizer --enable` 이 Codex 에서 `omx_wiki/.obsidizer` 와 `PostToolUse`(matcher `Bash`) 훅으로 동작한다. `omx wiki` 의 ingest, add 셸 호출 뒤에만, CLI 출력 JSON 이 이름 붙인 일반 페이지만, 검사한 `omx_wiki/` 를 cwd 로 고정한 정규화기(`--page --vault=<dev>:<ino>`)로 고친다.
13
+ - `lineage` 3.1.1 이 Codex 기록(rollout)을 읽는다(0.144 와 0.160 형식). Codex 안에서는 `CODEX_THREAD_ID` 로 지금 세션을 찾고, `--all-sessions` 는 이 폴더의 rollout 을 하위 에이전트 기록을 빼고 합친다.
14
+ - `setup-bypass-permissions` 에 대상 `codex`(`config.toml` 의 `approval_policy = "never"`, `sandbox_mode = "danger-full-access"`, 켜기 전 값 기록과 `off` 원복, 조직 정책과 기본 프로필 확인).
15
+ - `setup-omc-hud` 의 Codex 대상이 `[tui] status_line` 을 OMX 프리셋으로 켜고 끈다(없을 때만 넣고 banker 가 넣은 줄만 뺌).
16
+ - Codex 기록 형식이 바뀌어도 결과가 조용히 비지 않는다: lineage 는 사용자 턴 0 이면 Codex 버전과 함께 exit 2, progress 는 경고 줄, 카운팅 훅은 다음 세션 시작 때 경고 한 번. `banker doctor` 가 최신 rollout 의 형식을 점검한다(Codex 를 업데이트한 뒤 확인용).
17
+
18
+ ### Changed
19
+ - `banker setup --codex` 가 `CODEX_HOME` 을 따른다(상대 경로는 Codex 처럼 지금 폴더 기준). Codex 홈이 지금 폴더나 그 저장소 안이면 샌드박스가 고칠 수 있어 훅을 설치하지 않는다.
20
+ - Codex 사용량 카운팅은 이 Codex 에 설치된 banker 스킬 이름만 센다. 체크인 형식 `{version, os, counts}` 는 그대로이고 세션 id 와 턴 id 는 보내지 않는다(PRIVACY.md 에 Codex 절).
21
+ - 위키(`append_wiki`, `compact-wiki`)는 Codex 에서 `omx wiki` CLI 와 `omx_wiki/` 를 쓰고, Windows PowerShell 5.1 의 인자 전달을 맞춘다. `smart-compact` 는 Codex 에서 토큰 사용량을 rollout 으로 재는 훅을 쓴다. `payload-mon`, `omc-patch`, `setup-omc-hud`, `smart-compact` 는 `~/.codex` 나 `~/.claude` 쓰기를 명령별 승인이나 사용자의 `!` 명령으로 하고, 설정 폴더를 통째로 여는 `--add-dir` 은 권하지 않는다.
22
+
23
+ ### Notes
24
+ - Codex 에서 지금도 Claude Code 와 다른 것: `payload-mon` 은 Codex 자신의 요청 크기를 재지 않고, `omc-patch` 는 OMX 를 패치하지 않으며, `progress` 는 상시 패널이 아니고(Windows 는 옆 칸 없음), `graceful-pause` 는 실행 중인 도구 호출이 끝난 뒤 멈춘다.
25
+ - Windows 에서 SSH 로 접속한 세션은 Codex 샌드박스가 어떤 명령도 실행하지 못한다(Codex 쪽 제약). 데스크톱 세션에서는 샌드박스를 켠 채 확인했다.
26
+
3
27
  ## [0.20.5] - 2026-10-21
4
28
 
5
29
  ### Changed
package/PRIVACY.md CHANGED
@@ -8,7 +8,7 @@
8
8
  하나는 새 버전이 나왔을 때 알려주는 **업데이트 알림**이고, 다른 하나는 유지보수자가 사용량을 파악하기 위한 **익명 사용량 카운팅**입니다.\
9
9
  두 기능 모두 기본값은 켜짐(default-on)이며, 따로 끌 수 있습니다.\
10
10
  다만 사용량 체크인은 업데이트 확인과 함께 보내므로, 업데이트 알림을 끄면(`BANKER_NO_UPDATE_CHECK`) 체크인 전송도 멈춥니다. 그때 카운팅 기록은 이 기기에만 쌓이다가 5,000줄에서 멈춥니다.\
11
- 이 기능은 Claude Code 런타임에서만 동작합니다. Codex CLI는 샌드박스가 네트워크 접근을 차단하므로 알림과 카운팅 둘 다 동작하지 않습니다.
11
+ Claude Code 와 Codex CLI 모두에서 동작합니다. Codex 에서는 `banker setup --codex` 가 Codex 훅으로 설치하며, 수집 범위는 아래 "Codex CLI" 절에 있습니다.
12
12
 
13
13
  ## 현재 상태
14
14
 
@@ -84,6 +84,26 @@ update-notifier·Homebrew 등 다른 CLI 도구가 쓰는 것과 같은 방식
84
84
  세 훅 모두 트리거될 때마다 짧게 실행되는 node 프로세스를 하나씩 기동합니다(수십 ms 수준).\
85
85
  opt-out 하더라도 훅의 등록 자체는 남고, 각 스크립트 내부의 게이트가 즉시 무동작으로 반환합니다.
86
86
 
87
+ ## Codex CLI
88
+
89
+ `banker setup --codex` 가 Codex 사용자 범위(`~/.codex/hooks.json`)에 훅 두 개를 넣습니다.\
90
+ 훅 스크립트는 `~/.codex/hooks-custom/banker/` 사본에서 돌고, Codex 를 다시 열 때 뜨는 Hooks need review 창이나 `/hooks` 에서 사용자가 신뢰해야 돌기 시작합니다.\
91
+ `banker uninstall --codex` 가 둘 다 지우고, `banker hooks disable update` 나 `banker hooks disable count` 로 하나씩 끌 수 있습니다.
92
+
93
+ - `SessionStart`: 세션 시작 시 1회, Claude Code 와 같은 캐시로 업데이트 알림을 확인합니다. 알림은 TUI 에 경고 줄로 보입니다.
94
+ - `Stop`: 턴이 끝날 때 1회, 이번 턴에 쓴 banker 스킬 이름을 셉니다. 도구 호출마다 돌지 않습니다.
95
+
96
+ Codex 카운팅이 읽는 것과 남기는 것은 다음과 같습니다.
97
+
98
+ - 읽는 것: 훅 입력이 알려 주는 이 세션의 Codex 기록 파일(rollout)에서 이번 턴 부분만. 그중 도구 호출 기록과, `$banker-<이름>` 으로 부른 스킬을 Codex 가 넣은 머리 줄의 `skills/banker-<이름>/SKILL.md` 경로만 봅니다. 이 Codex 에 설치된 banker 스킬 이름만 셉니다.
99
+ - 보지 않는 것: 사용자가 입력한 글, 모델의 답, 도구 결과, 스킬 본문.
100
+ - 로컬에 남기는 것: Claude Code 와 같은 `usage-log`, `used-skills.json` 의 스킬 이름과 시간대, 같은 턴을 두 번 세지 않게 하는 최근 턴 키(`codex-count-turns`, Codex 세션 ID 와 턴 ID, 최근 100개).
101
+ - 로컬에 남기는 것(추가): 이 훅이 기록 형식을 읽지 못한 Codex 버전(`codex-shape-miss.json`, 버전과 알림 여부만). 다음 세션 시작 때 경고를 한 번 보이는 데만 쓰고 전송하지 않습니다.
102
+ - 전송하는 것: Claude Code 와 같은 체크인 `{version, os, counts}` 입니다. 세션 ID 와 턴 ID 는 전송하지 않습니다.
103
+
104
+ Codex 훅은 Codex 샌드박스 밖에서 돌므로, 샌드박스가 네트워크를 막아도 업데이트 조회와 체크인은 Claude Code 와 같이 나갑니다.\
105
+ 끄는 방법(`BANKER_NO_TELEMETRY`, `BANKER_NO_UPDATE_CHECK`, `banker telemetry off`)도 같습니다.
106
+
87
107
  ## 전송 안전
88
108
 
89
109
  - 업데이트 알림 조회는 GET 요청이며 페이로드가 없습니다.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [빠른 시작](#빠른-시작) · [워크플로 예시](#워크플로-사용-예시) · [구성](#구성) · [설치 상세](#설치-상세-npm--codex) · [설정 변경 지점](#설정-변경-지점-claude-code--codex) · [요구사항](#요구사항) · [업데이트 / 제거](#업데이트--제거) · [업데이트 확인](#업데이트-확인-및-사용량-카운팅) · [오프라인](#오프라인에서-쓰기) · [라이선스 / 서드파티](#라이선스--서드파티)
10
10
 
11
11
  □ banker는 다음을 포함하는 Claude Code 및 Codex CLI 플러그인입니다.
12
- - QA·보안 감사·문서·아키텍처·위키·미디어 작업과 의존성·개발환경(OS별) 설치를 아우르는 **스킬 63개 + 커맨드 2개**(총 65개 구성요소)
12
+ - QA·보안 감사·문서·아키텍처·위키·미디어 작업과 의존성·개발환경(OS별) 설치를 아우르는 **스킬 64개 + 커맨드 2개**(총 66개 구성요소)
13
13
  - Claude Code 즉시 명령 `/graceful-pause`, `/progress`
14
14
 
15
15
  □ 설치하면 스킬과 커맨드가 `/banker:<이름>` 네임스페이스로 노출됩니다.
@@ -82,8 +82,8 @@ Claude Code는 `/banker:<이름>`, Codex는 `banker-<이름>` 으로 호출합
82
82
  |---|---|
83
83
  | `/banker:front-qa` | 스펙(note) 기반 프론트엔드 구현 + parity QA |
84
84
  | `/banker:setup` | 구성요소·의존성 설치 오케스트레이터 (multi-select) |
85
- | `/graceful-pause` | 작업 중에도 바로 실행되는 정지 요청. 지금 단계만 끝내고 멈춘 뒤 보고하고 지시를 기다림 (Claude Code 2.1.289 이상, mod. Codex 미지원) |
86
- | `/progress` | 진행 상황 패널을 켜고 끔(`show` 기본 토글, `on`, `off`). 맨 위는 이 세션이 쓰거나 읽은 마지막 계획 파일(`.omc/plans/*.md`, `docs/superpowers/plans/*.md`, `~/.claude/plans/*.md`, ralph `prd.json`, ultragoal `goals.json`) 전체. Notion 토글 목록처럼 제목이 그 아래 내용을, 목록 항목이 하위 항목을, 표와 코드 블록이 행과 줄을 품고, 처음에는 큰 제목만 접힌 채 보임. 체크박스는 완료와 대기, 제목은 그 아래 체크 수(`2/5`). 그 아래 단계는 할 일 목록(Claude 의 작업 목록이나 `mcp__banker__todo`), 없으면 이 세션의 요청이고, 하위 목록은 그 단계가 진행 중일 때 한 도구 호출 한 단계까지. 작업 중에도 바로 실행됨(2.1.289 이상에서 확인) (mod. Codex 미지원) |
85
+ | `/graceful-pause` | 작업 중에도 바로 실행되는 정지 요청. 지금 단계만 끝내고 멈춘 뒤 보고하고 지시를 기다림 (Claude Code 2.1.289 이상, mod. Codex 는 작업 중에 `$banker-graceful-pause` 를 보내면 실행 중인 도구 호출이 끝난 뒤 멈춤) |
86
+ | `/progress` | 진행 상황 패널을 켜고 끔(`show` 기본 토글, `on`, `off`). 맨 위는 이 세션이 쓰거나 읽은 마지막 계획 파일(`.omc/plans/*.md`, `docs/superpowers/plans/*.md`, `~/.claude/plans/*.md`, ralph `prd.json`, ultragoal `goals.json`) 전체. Notion 토글 목록처럼 제목이 그 아래 내용을, 목록 항목이 하위 항목을, 표와 코드 블록이 행과 줄을 품고, 처음에는 큰 제목만 접힌 채 보임. 체크박스는 완료와 대기, 제목은 그 아래 체크 수(`2/5`). 그 아래 단계는 할 일 목록(Claude 의 작업 목록이나 `mcp__banker__todo`), 없으면 이 세션의 요청이고, 하위 목록은 그 단계가 진행 중일 때 한 도구 호출 한 단계까지. 작업 중에도 바로 실행됨(2.1.289 이상에서 확인) (mod. Codex 는 `$banker-progress` 스킬이 지금 세션의 계획 트리, `update_plan` 단계, 최근 도구 호출을 텍스트로 보이고, tmux 안이면 `--pane` 으로 오른쪽 칸에 갱신) |
87
87
 
88
88
  Claude Code 2.1.296 은 Claude 5 모델(Opus 5.5 등)에 작업 목록 도구(TaskCreate, TodoWrite)를 주지 않습니다. 그래서 `/progress` 를 켜면 모델에게 할 일 도구 `mcp__banker__todo` 를 줍니다(다음 요청부터). 모델이 여러 단계 작업을 시작할 때 할 일 목록을 보내면 그 목록이 단계가 됩니다. 한 단계로 끝나는 일에는 쓰지 않으므로 그때는 요청이 단계입니다. 내장 작업 목록 도구가 있는 세션(옛 모델, `CLAUDE_CODE_ENABLE_TODO_TOOLS=1`)에는 이 도구를 더하지 않습니다. 도구는 `/progress off` 뒤에도 세션이 끝날 때까지 남고, 그동안 부르면 패널이 꺼져 있다고 답합니다.
89
89
 
@@ -120,7 +120,8 @@ Claude Code 2.1.296 은 Claude 5 모델(Opus 5.5 등)에 작업 목록 도구(Ta
120
120
 
121
121
  > `obsidizer` 는 banker 최초의 플러그인 선언 hook(`hooks/hooks.json`)을 함께 설치합니다.\
122
122
  > banker가 활성화되어 있으면 이 hook은 **항상 등록**되어 위키 쓰기(`wiki_ingest`/`wiki_add`)마다 node 프로세스를 띄웁니다(플래그 확인이 그 안에서 일어나므로 `--enable` 전에도 쓰기당 약 30ms).\
123
- > `--enable` 전에는 아무것도 쓰지 않는 순수 no-op입니다.
123
+ > `--enable` 전에는 아무것도 쓰지 않는 순수 no-op입니다.\
124
+ > Codex 에는 위키 MCP 가 없어 `--enable` 이 `omx_wiki/.obsidizer` 를 쓰고 Codex 사용자 범위에 `PostToolUse`(matcher `Bash`) 훅을 설치합니다(`banker hooks enable obsidize`, 샌드박스 밖 실행 승인 필요). 이 훅은 `--enable` 한 때만 설치되고, 설치된 동안 셸 호출마다 node 프로세스를 띄워 `omx wiki` 의 ingest, add 일 때만 정규화합니다.
124
125
 
125
126
  ### 스킬: 워크플로 · 유틸
126
127
 
@@ -136,6 +137,7 @@ Claude Code 2.1.296 은 Claude 5 모델(Opus 5.5 등)에 작업 목록 도구(Ta
136
137
  | `ralph-qa` | 세션 모델로 띄운 독립 검토 에이전트 3개 이상이 합의할 때까지 검증 반복. `--codex`·`--gemini`·`--opencode` 를 주면 그 CLI 에서 가장 뛰어난 모델이 좌석으로 합류 |
137
138
  | `smart-compact` | 컨텍스트 임계 초과 시 위키·resume 저장 게이트 자동 무장 |
138
139
  | `deep-research` | 다중 소스 팬아웃 + 적대적 검증 인용 리서치 |
140
+ | `lfm` | learn from master. 그 분야 최고 전문가 그룹이 일하는 방식을 1차 자료로 조사해 계획 적용 항목으로 정리(ultra-interview, ultra-chain, deep-research, rfp-author 의 리서치에서도 씀) |
139
141
  | `ultra-interview` | 리서치 선행으로 사람 질문 최소화, 모호성 3% 이하까지 인터뷰 |
140
142
  | `interval-report` | 장기 수행 중간 보고를 `docs/intermission.md` 로 갱신(경과·잔여·단일 표) |
141
143
  | `summary-wiki` | 위키를 `docs/` 단일 파일 개조식으로 요약(지식 동기화 검토용, 읽기 전용) |
@@ -205,7 +207,11 @@ banker uninstall # 제거
205
207
 
206
208
  - `--scope project` 로 프로젝트 로컬(`./.codex`)에 설치하고, `--dry-run` 으로 미리 볼 수 있습니다.
207
209
  - non-root 전용입니다(전역 sudo 설치 시 root 소유 파일을 방지). postinstall이 없으므로 `banker setup` 을 직접 실행합니다.
208
- - Codex에는 스킬 63개가 `~/.codex/skills/banker-<name>/` 에, 커맨드 2개가 `~/.codex/prompts/banker-<name>.md` 에 설치됩니다(`codex/manifest.json`). \
210
+ - Codex에는 스킬 64개와 Codex 전용 스킬 2개(`graceful-pause`, `progress`)가 `~/.codex/skills/banker-<name>/` 에, 커맨드 2개가 `~/.codex/prompts/banker-<name>.md` 에 설치됩니다(`codex/manifest.json`). \
211
+ `CODEX_HOME` 을 설정했으면 `~/.codex` 대신 그 폴더에 설치합니다(Codex 가 읽는 곳). \
212
+ `lineage`, `progress`, 사용량 카운팅은 Codex 의 대화 기록(rollout)을 읽습니다. Codex 는 이 형식을 공개 약속으로 내놓지 않아 업데이트로 바뀔 수 있습니다. \
213
+ Codex 를 업데이트한 뒤에는 `banker doctor` 로 형식을 점검합니다. 읽지 못하면 lineage 는 빈 페이지 대신 오류로 멈추고, progress 는 경고 줄을 보이며, 카운팅은 다음 세션 시작 때 경고를 한 번 보입니다. \
214
+ Codex 0.144 이상에는 사용자 프롬프트가 없으므로 커맨드 2개는 스킬로도 설치되어 `$banker-front-qa`, `$banker-setup` 으로 부릅니다. \
209
215
  디렉터리명과 일치하도록 프론트매터 `name:` 이 `banker-<name>` 으로 재작성되어 Codex가 `banker-<name>` 으로 인식합니다.
210
216
  - OMC/Claude 에 결합됐던 오케스트레이터·설치·유틸 표면(all-in-one·ultra-chain·ultra-init·front-qa·setup·setup-omc·setup-omc-hud·setup-stitch·omc-reference·compact-copy)은 본문이 **런타임 인식**이라 Codex에서도 동작합니다.
211
217
  - `payload-mon` 은 Codex에서 실행해도 같은 머신의 Claude Code HUD 래퍼만 켜고 끕니다. \
@@ -228,6 +234,7 @@ banker uninstall # 제거
228
234
  |---|---|---|
229
235
  | Claude Code | 플러그인 레지스트리 | `claude plugin marketplace add` + 설치 → `/banker:*` (`settings.json` 직접수정 없음) |
230
236
  | Codex CLI | `~/.codex/skills/banker-*/`, `~/.codex/prompts/banker-*.md` | 기존 `banker-*` 정리 후 복사(멱등). `AGENTS.md`·`config.toml` 미변경 |
237
+ | Codex CLI | `~/.codex/hooks.json`, `~/.codex/hooks-custom/banker/` | 업데이트 알림(`SessionStart`)과 사용량 카운팅(`Stop`) 훅. 사본은 작업 폴더 밖에 두고, 이벤트마다 맨 뒤에 묶음 1개를 더하며, 쓰기 전 `hooks.json.banker.bak` 백업. `banker hooks status`, `banker hooks enable <이름>`, `banker hooks disable <이름>` 으로 확인하고 켜고 끔. Codex 를 다시 열 때 Hooks need review 창이나 `/hooks` 에서 신뢰해야 돎. Codex 홈(`CODEX_HOME`)이 지금 폴더나 그 저장소 안이면 샌드박스가 고칠 수 있어 훅을 설치하지 않음 |
231
238
 
232
239
  **플러그인 자체 훅 — `hooks/hooks.json` (설치 시 Claude Code에서 자동 발화)**
233
240
 
@@ -247,10 +254,10 @@ banker uninstall # 제거
247
254
  | `setup-mcp` | `claude mcp add` (context7·seq-thinking·filesystem·git·fetch) | `[mcp_servers.*]` |
248
255
  | `setup-lsp` | LSP MCP 등록 | `[mcp_servers.lsp_bridge]` |
249
256
  | `setup-stitch` | `claude mcp add stitch` | `codex mcp add stitch` |
250
- | `setup-omc-hud` | `statusLine`(바뀔 때 `settings.json` 백업) + `hud/omc-hud-custom.mjs`, `hud/omc-hud.mjs`, `.omc/hud-config.json`(바뀐 파일은 `.backup.<시각>` 백업) + payload-mon 켜기. 옛 판이 넣은 갱신 안내 블록은 `off` 로 원복 | — (Codex는 OMX `hud`) |
257
+ | `setup-omc-hud` | `statusLine`(바뀔 때 `settings.json` 백업) + `hud/omc-hud-custom.mjs`, `hud/omc-hud.mjs`, `.omc/hud-config.json`(바뀐 파일은 `.backup.<시각>` 백업) + payload-mon 켜기. 옛 판이 넣은 갱신 안내 블록은 `off` 로 원복 | 대상이 Codex 면 `~/.codex/config.toml` 의 `[tui] status_line` 이 없을 때만 OMX 프리셋으로 넣고 `# banker:status-line` 표시(`config.toml.banker-hud.bak` 백업). OMX 나 사용자가 둔 줄은 그대로, `off` 는 banker 줄만 뺌. Claude Code 쪽을 고르면 왼쪽과 같음 |
251
258
  | `setup-pwsh` | `env.CLAUDE_CODE_GIT_BASH_PATH` (병합) | — (네이티브 셸, 배선 불필요) |
252
259
  | `setup-harness-factory` | `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` (env 영속) | — |
253
- | `smart-compact` | `statusLine.command` 래핑 + `UserPromptSubmit` 훅 추가 (백업·멱등) | `~/.codex/` 대응 |
260
+ | `smart-compact` | `statusLine.command` 래핑 + `UserPromptSubmit` 훅 추가 (백업·멱등) | `hooks.json` 의 `UserPromptSubmit` 맨 뒤에 훅 묶음 1개 + `smart-compact.json`(임계값), `smart-compact-state.json`(세션 상태). 훅 사본 `hooks-custom/banker-smart-compact-gate.mjs`. `hooks.json` 을 바꿀 때마다 직전 내용을 `hooks.json.smart-compact.bak` 에 백업, 멱등. `off` 는 이 훅과 사본, 설정만 지움. 새 훅은 `/hooks` 에서 신뢰해야 돎 |
254
261
  | `payload-mon` | `hud/omc-hud-custom.mjs` 에 표시 블록 1개 삽입(`.payload-mon.bak` 백업·멱등·`node --check` 검증) + 래퍼 옆 `hud/payload-mon/` 에 모듈 사본 (`off` 로 원복) | — (Codex에서 실행해도 Claude Code HUD 만 다룸) |
255
262
  | `tone-compact` | `rules/banker-tone-compact.md` 생성 (`off` 로 삭제) | `AGENTS.md`(내용이 있는 `AGENTS.override.md` 우선) 끝에 `USER:OMX:POLICY` 로 감싼 블록 1개(`.tone-compact.bak` 백업·멱등, `off` 로 원복) |
256
263
  | `anti-drm` | `rules/banker-anti-drm.md` 생성 (`off` 로 삭제) + 설정 `~/.config/banker/anti-drm.json`(두 런타임 공용, `off` 뒤에도 남음, anti-drm 이 쓰지 않은 파일이 있었으면 `.bak`) | `AGENTS.md`(내용이 있는 `AGENTS.override.md` 우선) 끝에 `USER:OMX:POLICY` 로 감싼 블록 1개(`.anti-drm.bak` 백업·멱등, `off` 로 원복) |
@@ -258,9 +265,9 @@ banker uninstall # 제거
258
265
  | `remains` | 설정 파일 미변경. `~/.config/banker/test-boxes.json` 은 읽기만 함(사용자가 만들거나, 사용자가 준 정보로 확인받고 만듦). 처음 접속하는 박스의 호스트 키가 `~/.ssh/known_hosts` 에 더해짐. 테스트박스에는 임시 폴더를 만들고 끝나면 지움(시간 초과나 중단 때는 시작한 프로세스를 먼저 끝냄, 못 지우면 결과에 표시) | 동일 |
259
266
  | `motion-graphic-setup` | 설정 파일 미패치 (Node/ffmpeg 확보 + `npx hyperframes` 설치만) | 동일 (`config.toml` 미변경) |
260
267
  | `3d-intro-setup` | `settings.json` 미패치 — 크레덴셜을 untracked env 파일에 기록(`.env.3d-intro.local` 또는 `~/.config/banker/3d-intro/env`) | 동일 (`config.toml` 미변경) |
261
- | `setup-bypass-permissions` | `permissions.defaultMode` = `bypassPermissions` (켜기 전 내용은 `settings.json.bypass-permissions.bak`, 되돌리기 기록은 `settings.json.bypass-permissions.json`, `off` 로 원복. 관리 정책, 설정 파일의 금지 키, root 계정이면 거부) | Codex 에서 실행해도 같은 머신의 Claude Code 설정을 바꿈 (Codex 승인 정책은 미변경) |
268
+ | `setup-bypass-permissions` | `permissions.defaultMode` = `bypassPermissions` (켜기 전 내용은 `settings.json.bypass-permissions.bak`, 되돌리기 기록은 `settings.json.bypass-permissions.json`, `off` 로 원복. 관리 정책, 설정 파일의 금지 키, root 계정이면 거부) | 대상 `codex` 면 `~/.codex/config.toml` 최상위의 `approval_policy` = `"never"`, `sandbox_mode` = `"danger-full-access"` (켜기 전 원본 `config.toml.bypass-permissions.bak`, 되돌리기 기록 `config.toml.bypass-permissions.json`, `off` 로 원복. 조직 정책이 막으면 거부). 대상 `claude` 면 Codex 에서 실행해도 Claude Code 설정을 바꿈 |
262
269
 
263
- `obsidizer` 는 `settings.json` 대신 `<위키디렉터리>/.obsidizer` 플래그 파일로만 켜고 끕니다.
270
+ `obsidizer` 는 `settings.json` 대신 `<위키디렉터리>/.obsidizer` 플래그 파일로만 켜고 끕니다. Codex 는 `omx_wiki/.obsidizer` 플래그에 더해 `~/.codex/hooks.json` 에 훅 묶음 1개를 더합니다.
264
271
 
265
272
  ## 요구사항
266
273
 
@@ -273,8 +280,8 @@ banker uninstall # 제거
273
280
  - `omc-patch`: Node 18 이상과 `git`(OMC 마켓플레이스 고정과 확인에 사용).
274
281
  - `remains`: 테스트박스를 쓰려면 이 머신에 `ssh`, `scp`, `git`, 박스에 `tar` 와 시험에 필요한 런타임(예: Node)이 있어야 하고, SSH 키로 암호 없이 접속돼야 합니다.
275
282
  - `ultra-chain`: 위 OMC(Codex 는 OMX), 웹 조사 수단, 시각 검토의 캡처에는 Node 용 playwright 패키지와 Chromium, OS 시험에는 `remains` 와 같은 테스트박스 조건.
276
- - `/graceful-pause`: Claude Code 2.1.289 이상(mod). 그보다 오래된 Claude Code 는 명령을 등록하지 않고 한 줄로 알리며, Codex 에는 이 명령이 없습니다.
277
- - `/progress`: mod 를 지원하는 Claude Code(2.1.286 과 2.1.296 에서 확인). 작업 중 즉시 실행은 2.1.289 이상에서 확인했습니다. mod 가 없는 Claude Code(2.1.250 에서 확인)에서는 "mod 를 지원하지않는 claude code 버전입니다" 만 보이고 모델은 호출되지 않습니다. Codex 에는 이 명령이 없습니다.
283
+ - `/graceful-pause`: Claude Code 2.1.289 이상(mod). 그보다 오래된 Claude Code 는 명령을 등록하지 않고 한 줄로 알립니다. Codex 는 스킬 `$banker-graceful-pause` 입니다(0.144.5 에서 확인).
284
+ - `/progress`: mod 를 지원하는 Claude Code(2.1.286 과 2.1.296 에서 확인). 작업 중 즉시 실행은 2.1.289 이상에서 확인했습니다. mod 가 없는 Claude Code(2.1.250 에서 확인)에서는 "mod 를 지원하지않는 claude code 버전입니다" 만 보이고 모델은 호출되지 않습니다. Codex 는 스킬 `$banker-progress` 입니다(상시 패널이 아니라 부를 때마다 보임, tmux 칸은 선택).
278
285
  - `lineage`: Python 3.7+ (표준 라이브러리만 사용). RHEL8/Rocky8은 기본 `python3` 가 3.6이라 `setup-python` 등으로 3.11을 설치해 지정해야 합니다.
279
286
 
280
287
  ## 업데이트 / 제거
@@ -305,7 +312,7 @@ Codex는 재설치할 때마다 기존 `banker-*` 를 먼저 정리하므로 옛
305
312
  > 0.19.5 이하의 `setup-omc-hud` 가 옛 래퍼에 넣은 Claude 갱신 안내 블록은 제거 전에 `/banker:setup-omc-hud off` 로 뺄 수 있습니다.\
306
313
  > 이미 지웠다면 HUD를 재설치하거나, `// >>> claude-update-last >>>` 부터 `// <<< claude-update-last <<<` 까지 지우면 됩니다. 블록을 남겨 두어도 상태표시줄은 깨지지 않습니다.
307
314
 
308
- > `setup-bypass-permissions` 로 바꾼 권한 모드는 `settings.json` 에 남아, 플러그인을 지워도 모든 세션에서 확인 없이 도구가 실행됩니다. 제거 전에 `/banker:setup-bypass-permissions off` 를 실행하세요.\
315
+ > `setup-bypass-permissions` 로 바꾼 권한 모드는 `settings.json`(Codex 대상은 `~/.codex/config.toml`)에 남아, 플러그인을 지워도 모든 세션에서 확인 없이 도구가 실행됩니다. 제거 전에 `/banker:setup-bypass-permissions off` 를 실행하세요(Codex 는 `$banker-setup-bypass-permissions off codex`).\
309
316
  > 이미 지웠다면 설정 폴더(`$CLAUDE_CONFIG_DIR`, 없으면 `~/.claude`)의 `settings.json` 에서 `permissions.defaultMode` 를 지우거나 원래 값으로 되돌리면 됩니다. 옆의 `settings.json.bypass-permissions.bak` 과 `settings.json.bypass-permissions.json` 도 지우세요. 백업에는 설정의 비밀 값이 들어 있을 수 있습니다.\
310
317
  > root 계정에서 이 모드 때문에 Claude Code 가 시작되지 않으면 `claude --permission-mode default` 로 시작한 뒤 `off` 를 실행하세요.
311
318
 
@@ -343,7 +350,8 @@ export BANKER_NO_TELEMETRY=1 # 사용량 카운팅만 끄기
343
350
 
344
351
  카운팅은 `banker telemetry off` 로도 끌 수 있습니다.\
345
352
  플러그인은 기본 카운팅 엔드포인트(`banker.banker-plugins.workers.dev`)를 내장하므로 count-default-on 이라 설치 즉시 활성이며, `BANKER_TELEMETRY_ENDPOINT` 환경변수로 자가호스팅/override 할 수 있습니다.\
346
- Codex CLI는 샌드박스가 네트워크를 차단하므로 알림·카운팅 둘 다 동작하지 않습니다.
353
+ Codex CLI 에서는 `banker setup --codex` 가 같은 일을 Codex 훅 `SessionStart`(알림 확인)와 `Stop`(턴이 끝날 때 이번 턴에 쓴 banker 스킬 집계)으로 설치합니다. 도구 호출마다 돌지 않고 턴마다 한 번 돕니다. \
354
+ Codex 를 다시 열 때 Hooks need review 창이나 `/hooks` 에서 신뢰해야 돌고, 훅은 Codex 샌드박스 밖에서 돌므로 샌드박스가 네트워크를 막아도 동작합니다. 끄는 방법은 같습니다.
347
355
 
348
356
  무엇을 수집하고 누가 수신하는지는 [PRIVACY.md](PRIVACY.md)에 정리했습니다.
349
357
 
@@ -374,7 +382,7 @@ banker setup
374
382
  - 내려받는 설치 스킬(`setup-omc-hud`, `setup-bypass-permissions` 를 뺀 `setup-*`, `docs-setup`, `vertical-pptx-setup`, `motion-graphic-setup`, `3d-intro-setup`): 웹 원천(npm, PyPI, GitHub 등)은 먼저 닿는지 보고, 닿지 못하면 받지 않고 "오프라인이라 설치하지 않음" 으로 알립니다. OS 패키지 관리자(dnf, apt, brew, winget)의 단계는 그 관리자의 미러 설정을 따르므로 미리 판정하지 않고 시도합니다. 단 `setup-pwsh` 는 winget 이 유일한 원천이라 그 원천을 먼저 봅니다.
375
383
  - `update-banker`: 레지스트리에 닿지 못하면 어느 채널도 바꾸지 않고 멈춥니다.
376
384
  - `audit-security` 의 의존성 취약점 조회: 결과로 읽을 수 없는 도구는 0건이 아니라 "미커버" 로 적습니다.
377
- - `deep-research`, 이를 부르는 `ultra-chain` 1단계, `ultra-interview` 의 조사: 웹 도구가 모두 실패하면 조사하지 못했다고 알립니다.
385
+ - `deep-research`, `lfm`, 이를 부르는 `ultra-chain` 1단계, `ultra-interview` 와 `rfp-author` 의 조사: 웹 도구가 모두 실패하면 조사하지 못했다고 알립니다. `lfm` 은 모델이 아는 관행으로 표를 채우지 않고 "웹 조사 불가" 로 끝납니다.
378
386
  - `npx` 로 부르는 도구(`motion-graphic-make` 의 hyperframes 등): 전역 설치가 없으면 패키지를 받으려 해, 네트워크가 없으면 약 70초 뒤 실패합니다. 오프라인에서는 전역 설치한 실행 파일을 부릅니다.
379
387
  - 유료 API 를 부르는 `3d-intro-build`(요청마다 시간 제한), `ralph-qa` 의 외부 좌석(codex, gemini, opencode, 좌석마다 600초)
380
388
  - `vertical-pptx`, `pdf-vision-extract` 가 빠진 도구를 설치하라고 안내할 때, `nothing-design` 의 글꼴(Google Fonts. 받지 못하면 대체 글꼴로 보임)
package/bin/banker.js CHANGED
@@ -56,8 +56,9 @@ function starRepo(spawn = cp.spawnSync) {
56
56
  function parseArgs(argv) {
57
57
  const a = { cmd: null, sub: null, claude: false, codex: false, scope: 'user', scopeExplicit: false, dryRun: false, help: false, version: false };
58
58
  for (const t of argv) {
59
- if (t === 'setup' || t === 'doctor' || t === 'uninstall' || t === 'help' || t === 'telemetry') { if (!a.cmd) a.cmd = t; }
60
- else if (t === 'on' || t === 'off' || t === 'status') { if (!a.sub) a.sub = t; } // telemetry subcommand
59
+ if (t === 'setup' || t === 'doctor' || t === 'uninstall' || t === 'help' || t === 'telemetry' || t === 'hooks') { if (!a.cmd) a.cmd = t; }
60
+ else if (t === 'on' || t === 'off' || t === 'status' || t === 'enable' || t === 'disable') { if (!a.sub) a.sub = t; } // telemetry, hooks subcommand
61
+ else if (a.cmd === 'hooks' && /^[a-z]+$/.test(t)) (a.hookIds = a.hookIds || []).push(t);
61
62
  else if (t === '--claude') a.claude = true;
62
63
  else if (t === '--codex') a.codex = true;
63
64
  else if (t === '--dry-run' || t === '-n') a.dryRun = true;
@@ -81,11 +82,19 @@ function assertNotRoot() {
81
82
  }
82
83
 
83
84
  function readManifest() { return JSON.parse(fs.readFileSync(MANIFEST, 'utf8')); }
84
- function eligible(m) { return m.surfaces.filter(s => s.target === 'both'); }
85
+ // Codex gets every 'both' surface and the 'codex-only' ones (their source lives in codex/skills/, which the Claude
86
+ // plugin never loads: Claude Code has its own surface for them, e.g. the /graceful-pause and /progress mods).
87
+ function eligible(m) { return m.surfaces.filter(s => s.target === 'both' || s.target === 'codex-only'); }
88
+ function skillSource(s) { return s.target === 'codex-only' ? path.join(PKG_ROOT, 'codex', 'skills', s.name) : path.join(PKG_ROOT, 'skills', s.name); }
85
89
 
86
90
  function homeDir() { return os.homedir(); }
87
91
  function claudePluginsDir() { return path.join(homeDir(), '.claude', 'plugins'); }
88
- function codexBase(scope) { return scope === 'project' ? path.join(process.cwd(), '.codex') : path.join(homeDir(), '.codex'); }
92
+ // User scope is where Codex itself reads skills and hooks: $CODEX_HOME when set, else ~/.codex.
93
+ function codexBase(scope) {
94
+ if (scope === 'project') return path.join(process.cwd(), '.codex');
95
+ const ch = process.env.CODEX_HOME;
96
+ return ch ? path.resolve(ch) : path.join(homeDir(), '.codex'); // a relative one from here, as Codex reads it
97
+ }
89
98
 
90
99
  function have(bin) {
91
100
  // PATH scan — no shell (avoids DEP0190 + injection). Cross-OS.
@@ -153,6 +162,19 @@ function setCodexSkillName(skillMd, newName, dryRun) {
153
162
  fs.writeFileSync(skillMd, src.slice(0, fm.index) + `---\n${block}\n---` + src.slice(fm.index + fm[0].length));
154
163
  }
155
164
 
165
+ // A command as a Codex skill: the command's description becomes the skill's, under name: <name>. Codex skills get
166
+ // no $ARGUMENTS substitution, so a note says where the arguments come from.
167
+ function writeCommandSkill(src, destDir, name, dryRun) {
168
+ if (dryRun) { log(` [dry-run] write command skill ${path.relative(PKG_ROOT, src)} -> ${destDir}`); return; }
169
+ const text = fs.readFileSync(src, 'utf8');
170
+ const fm = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
171
+ const desc = fm && fm[1].match(/^description:\s*(.*)$/m);
172
+ const body = fm ? text.slice(fm[0].length) : text;
173
+ const note = '> Codex: 이 명령은 스킬로 설치됐다. 본문의 `$ARGUMENTS`, `$1` 은 사용자가 `$' + name + '` 뒤에 쓴 글이다.\n\n';
174
+ fs.mkdirSync(destDir, { recursive: true });
175
+ fs.writeFileSync(path.join(destDir, 'SKILL.md'), `---\nname: ${name}\ndescription: ${desc ? desc[1] : '""'}\n---\n\n${note}${body}`);
176
+ }
177
+
156
178
  /* ---------- Claude Code target ---------- */
157
179
  function setupClaude(dryRun) {
158
180
  log('• Claude Code:');
@@ -185,21 +207,77 @@ function setupCodex(dryRun, scope) {
185
207
  for (const s of eligible(m)) {
186
208
  if (s.type === 'skill') {
187
209
  const dest = path.join(skillsDir, `banker-${s.name}`);
188
- copyDir(path.join(PKG_ROOT, 'skills', s.name), dest, dryRun);
210
+ copyDir(skillSource(s), dest, dryRun);
189
211
  setCodexSkillName(path.join(dest, 'SKILL.md'), `banker-${s.name}`, dryRun); // dir==name (Codex discovery)
190
212
  nSkill++;
191
213
  } else if (s.type === 'command') {
192
- copyFile(path.join(PKG_ROOT, 'commands', `${s.name}.md`), path.join(promptsDir, `banker-${s.name}.md`), dryRun);
214
+ const src = path.join(PKG_ROOT, 'commands', `${s.name}.md`);
215
+ copyFile(src, path.join(promptsDir, `banker-${s.name}.md`), dryRun);
216
+ writeCommandSkill(src, path.join(skillsDir, `banker-${s.name}`), `banker-${s.name}`, dryRun);
193
217
  nCmd++;
194
218
  }
195
219
  }
196
- log(` → ${nSkill} skills -> ${path.join(base, 'skills', 'banker-*')}, ${nCmd} prompts -> ${path.join(base, 'prompts', 'banker-*.md')}`);
220
+ log(` → ${nSkill} skills -> ${path.join(base, 'skills', 'banker-*')}, ${nCmd} commands -> skills banker-* and prompts ${path.join(base, 'prompts', 'banker-*.md')}`);
221
+ log(' → Codex 0.144+ has no custom prompts, so each command is also a skill: invoke it as $banker-<name>.');
197
222
  log(' → skills are invoked as `banker-<name>` (frontmatter name rewritten to match the dir).');
198
223
  log(' → ~/.codex/AGENTS.md is NOT modified (omx regenerates it); skills auto-discovered from ~/.codex/skills/.');
199
224
  return true;
200
225
  }
201
226
 
202
227
  /* ---------- doctor ---------- */
228
+ // The newest Codex session record (rollout) under <base>/sessions/YYYY/MM/DD/, or null.
229
+ function newestRollout(base) {
230
+ let at = path.join(base, 'sessions');
231
+ for (let depth = 0; depth < 3; depth++) {
232
+ let names = [];
233
+ try { names = fs.readdirSync(at).filter(n => /^\d+$/.test(n)).sort(); } catch { return null; }
234
+ if (!names.length) return null;
235
+ at = path.join(at, names[names.length - 1]);
236
+ }
237
+ try {
238
+ const files = fs.readdirSync(at).filter(n => /^rollout-.*\.jsonl$/.test(n)).sort();
239
+ return files.length ? path.join(at, files[files.length - 1]) : null;
240
+ } catch { return null; }
241
+ }
242
+
243
+ // What lineage, progress and the usage-count hook look for in a rollout. Codex does not promise its record shape,
244
+ // so after a Codex update this tells whether banker still reads it (a miss would otherwise leave empty results).
245
+ function codexShapeReport(file) {
246
+ let fd;
247
+ try {
248
+ fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NONBLOCK || 0));
249
+ const st = fs.fstatSync(fd);
250
+ if (!st.isFile()) return null;
251
+ const read = (start, len) => { const b = Buffer.alloc(len); fs.readSync(fd, b, 0, len, start); return b.toString('utf8'); };
252
+ const first = read(0, Math.min(st.size, 8 << 20)); // session_meta carries the instructions and can be large
253
+ const head = first.split('\n')[0];
254
+ if (!first.includes('\n') && st.size > (8 << 20)) return { version: '', tooLong: true }; // the first line is past 8 MB (bytes)
255
+ if (!first.slice(head.length).trim()) return { version: 'new', fresh: true };
256
+ const body = read(Math.max(0, st.size - (32 << 20)), Math.min(st.size, 32 << 20));
257
+ let version = '';
258
+ try { const m = JSON.parse(head); version = m.type === 'session_meta' ? String((m.payload || {}).cli_version || '?') : ''; } catch {}
259
+ return {
260
+ version,
261
+ turnStart: /"task_started"[^\n]*"turn_id"|"turn_id"[^\n]*"task_started"/.test(body),
262
+ userText: /"(user_message|UserMessage)"/.test(body),
263
+ toolCall: /"type":"[a-z_]+_call"/.test(body),
264
+ };
265
+ } catch { return null; } finally { if (fd !== undefined) try { fs.closeSync(fd); } catch {} }
266
+ }
267
+
268
+ function doctorCodexShape(base) {
269
+ const file = newestRollout(base);
270
+ if (!file) { log(' Codex record shape: no session record yet (run one Codex session, then doctor again)'); return; }
271
+ const r = codexShapeReport(file);
272
+ if (r && r.tooLong) { log(` Codex record shape: ${path.basename(file)} opens with a line over 8 MB, not checked`); return; }
273
+ if (r && r.fresh) { log(` Codex record shape: ${path.basename(file)} holds no turn yet (run one Codex turn, then doctor again)`); return; }
274
+ if (!r || !r.version) { log(` ⚠ Codex record shape: ${path.basename(file)} does not open with session_meta - lineage, progress and usage counting cannot read it`); return; }
275
+ const mark = (ok) => (ok ? 'ok' : 'MISSING');
276
+ log(` Codex record shape (Codex ${r.version}, ${path.basename(file)}): turn start ${mark(r.turnStart)}, user text ${mark(r.userText)}, tool calls ${mark(r.toolCall)}`);
277
+ if (!r.turnStart || !r.userText) log(' ⚠ banker may not read this Codex version\'s records (lineage stops, progress warns, usage counting misses); update banker');
278
+ else if (!r.toolCall) log(' (no tool calls: fine for a session that used no tools)');
279
+ }
280
+
203
281
  function doctor() {
204
282
  log(`banker v${version()} (pkg: ${PKG_ROOT})`);
205
283
  log(`plugin.json version: ${(() => { try { return require(PLUGIN_JSON).version; } catch { return '?'; } })()}`);
@@ -211,7 +289,8 @@ function doctor() {
211
289
  let installed = [];
212
290
  try { installed = fs.readdirSync(sdir).filter(d => d.startsWith('banker-')); } catch {}
213
291
  log(` codex CLI: ${have('codex') ? 'found' : 'NOT found'}`);
214
- log(` installed banker skills in ~/.codex/skills: ${installed.length}`);
292
+ log(` installed banker skills in ${sdir}: ${installed.length}`);
293
+ doctorCodexShape(base);
215
294
  // Codex only discovers a skill when its dir name equals the SKILL.md `name:` — flag any mismatch.
216
295
  const mismatched = installed
217
296
  .map(d => ({ d, name: readSkillName(path.join(sdir, d, 'SKILL.md')) }))
@@ -258,6 +337,54 @@ function uninstallCodex(dryRun, scopes) {
258
337
  }
259
338
  }
260
339
 
340
+ /* ---------- Codex hooks ---------- */
341
+ // bin/lib/codex-hooks.mjs is ESM; loaded lazily like telemetry-config. Hooks go into the same Codex home as the skills.
342
+ async function loadCodexHooks() {
343
+ return import(require('url').pathToFileURL(path.join(__dirname, 'lib', 'codex-hooks.mjs')).href);
344
+ }
345
+ function codexHookPaths(ch) { return ch.codexPaths({ CODEX_HOME: codexBase('user') }); }
346
+
347
+ // Update notice and usage counting on Codex, as the Claude plugin's hooks.json gives them to Claude Code. User scope
348
+ // only: a project-scope copy would sit in a workspace, and Codex runs trusted hooks outside the sandbox.
349
+ async function setupCodexHooks(dryRun, scope) {
350
+ if (scope !== 'user') { log(' → Codex hooks (update notice, usage counting) are user-scope only; skipped for scope=project.'); return; }
351
+ const ch = await loadCodexHooks();
352
+ if (dryRun) { log(` [dry-run] Codex hooks: ${ch.DEFAULT_HOOKS.join(', ')} -> ${codexHookPaths(ch).hooks}`); return; }
353
+ const r = ch.enableHooks(codexHookPaths(ch), ch.DEFAULT_HOOKS, PKG_ROOT);
354
+ if (r.code) { warn(` Codex hooks not changed: ${r.msg}`); return; }
355
+ log(` → Codex hooks ${ch.DEFAULT_HOOKS.join(', ')} in ${codexHookPaths(ch).hooks}${r.changed.length ? ` (changed: ${r.changed.join(', ')})` : ' (already in place)'}.`);
356
+ if (r.changed.length) log(` → ${ch.TRUST_NOTE}`);
357
+ }
358
+
359
+ async function uninstallCodexHooks(dryRun) {
360
+ const ch = await loadCodexHooks();
361
+ if (dryRun) { log(` [dry-run] remove banker Codex hooks from ${codexHookPaths(ch).hooks}`); return; }
362
+ const r = ch.removeAll(codexHookPaths(ch));
363
+ if (r.code) warn(` Codex hooks not changed: ${r.msg}`);
364
+ else if (r.changed.length) log(` removed banker Codex hooks: ${r.changed.join(', ')}${r.shifted.length ? ` (later ${r.shifted.join(', ')} hooks may need trusting again in /hooks)` : ''}`);
365
+ }
366
+
367
+ // banker hooks <status|enable|disable> [ids]: status of all, or turn named hooks on or off (obsidizer --enable on Codex).
368
+ async function hooksCmd(sub, ids) {
369
+ const ch = await loadCodexHooks();
370
+ const p = codexHookPaths(ch);
371
+ const all = Object.keys(ch.HOOKS);
372
+ const want = ids && ids.length ? ids : all;
373
+ const unknown = want.filter((id) => !all.includes(id));
374
+ if (unknown.length || (sub !== 'status' && !(ids && ids.length))) { warn(`usage: banker hooks <status|enable|disable> [${all.join('|')}]`); process.exitCode = 1; return; }
375
+ if (sub === 'status' || !sub) {
376
+ const s = ch.hookStatus(p, want, PKG_ROOT);
377
+ if (s.why) { warn(`banker hooks: ${s.why}`); process.exitCode = 1; return; }
378
+ for (const id of want) log(`${id}: ${s.status[id]}`);
379
+ return;
380
+ }
381
+ const r = sub === 'enable' ? ch.enableHooks(p, want, PKG_ROOT) : ch.disableHooks(p, want);
382
+ if (r.code) { warn(`banker hooks: not changed: ${r.msg}`); process.exitCode = 1; return; }
383
+ log(`banker hooks ${sub}: ${r.changed.length ? r.changed.join(', ') : 'nothing to change'}`);
384
+ if (sub === 'enable' && r.changed.length) log(ch.TRUST_NOTE);
385
+ if (r.shifted && r.shifted.length) log(`later ${r.shifted.join(', ')} hooks may need trusting again in /hooks.`);
386
+ }
387
+
261
388
  /* ---------- telemetry config bridge ---------- */
262
389
  // bin/banker.js is CommonJS; telemetry-config is ESM (.mjs). Load it lazily via dynamic import
263
390
  // (pathToFileURL keeps the specifier valid on Windows). Only the interactive setup path and the
@@ -407,13 +534,15 @@ Usage:
407
534
  banker setup [--claude] [--codex] [--scope user|project] [--dry-run]
408
535
  banker uninstall [--claude] [--codex] [--scope user|project] [--dry-run]
409
536
  banker telemetry <on|off|status>
537
+ banker hooks <status|enable|disable> [update|count|obsidize] (Codex hooks in $CODEX_HOME/hooks.json, default ~/.codex)
410
538
  banker doctor
411
539
  banker help
412
540
 
413
541
  Notes:
414
542
  - setup/uninstall with no target flag applies to BOTH tools.
415
543
  - --claude registers the marketplace + installs banker@banker-plugins (skills as /banker:*).
416
- - --codex copies codex-eligible skills -> ~/.codex/skills/banker-* and commands -> ~/.codex/prompts/banker-*.md.
544
+ - --codex copies codex-eligible skills -> ~/.codex/skills/banker-* and commands -> ~/.codex/prompts/banker-*.md
545
+ ($CODEX_HOME instead of ~/.codex when it is set).
417
546
  - telemetry on|off toggles anonymous usage counting opt-out (default-on; config only); status prints counting + update-check state. See PRIVACY.md.
418
547
  - Never runs as root. --dry-run prints planned actions without writing.`;
419
548
 
@@ -423,11 +552,12 @@ async function main() {
423
552
  if (a.help || a.cmd === 'help' || !a.cmd) { log(HELP); return; }
424
553
  if (a.cmd === 'doctor') { doctor(); return; }
425
554
  if (a.cmd === 'telemetry') { await telemetryCmd(a.sub); return; }
555
+ if (a.cmd === 'hooks') { assertNotRoot(); await hooksCmd(a.sub, a.hookIds); return; }
426
556
  assertNotRoot();
427
557
  if (a.cmd === 'setup') {
428
558
  log(`banker setup${a.dryRun ? ' (dry-run)' : ''}: claude=${a.claude} codex=${a.codex}`);
429
559
  if (a.claude) setupClaude(a.dryRun);
430
- if (a.codex) setupCodex(a.dryRun, a.scope);
560
+ if (a.codex && setupCodex(a.dryRun, a.scope)) await setupCodexHooks(a.dryRun, a.scope);
431
561
  // Best-effort first-run prompts (interactive TTY only). A prompt failure must never fail setup.
432
562
  try { await maybeFirstRunPrompts(a); } catch { /* non-fatal */ }
433
563
  log('done.');
@@ -435,7 +565,10 @@ async function main() {
435
565
  }
436
566
  if (a.cmd === 'uninstall') {
437
567
  if (a.claude) uninstallClaude(a.dryRun);
438
- if (a.codex) uninstallCodex(a.dryRun, a.scopeExplicit ? [a.scope] : ['user', 'project']);
568
+ if (a.codex) {
569
+ uninstallCodex(a.dryRun, a.scopeExplicit ? [a.scope] : ['user', 'project']);
570
+ if (!a.scopeExplicit || a.scope === 'user') await uninstallCodexHooks(a.dryRun);
571
+ }
439
572
  log('done.');
440
573
  return;
441
574
  }