@kaydash9999/banker-plugins 0.26.1 → 0.28.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 (72) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +39 -0
  4. package/README.md +25 -10
  5. package/bin/lib/codex-hooks.mjs +7 -1
  6. package/codex/command-skills/front-qa/SKILL.md +3 -1
  7. package/codex/hooks.json +15 -5
  8. package/codex/manifest.json +4 -0
  9. package/codex/skills/ralph/SKILL.md +272 -0
  10. package/codex/skills/ralph/scripts/gate-evidence.mjs +457 -0
  11. package/codex/skills/ralph/scripts/ralph-state.mjs +622 -0
  12. package/codex/skills/ralph/scripts/run-folder.mjs +299 -0
  13. package/codex/transform-matrix.md +5 -4
  14. package/commands/front-qa.md +3 -1
  15. package/hooks/ralph-continue-codex.mjs +59 -0
  16. package/package.json +1 -1
  17. package/skills/all-in-one/SKILL.md +22 -8
  18. package/skills/ralph-qa/references/gate-evidence.mjs +3 -3
  19. package/skills/ralph-qa/references/run-folder.mjs +3 -3
  20. package/skills/ultra-chain/SKILL.md +11 -4
  21. package/skills/ultra-init/SKILL.md +9 -7
  22. package/skills/ultraqa/references/gate-evidence.mjs +3 -3
  23. package/skills/ultraqa/references/run-folder.mjs +3 -3
  24. package/skills/video-gen/SKILL.md +141 -0
  25. package/skills/video-gen/assets/example.json +36 -0
  26. package/skills/video-gen/scripts/vg-catalog.mjs +23 -0
  27. package/skills/video-gen/scripts/vg-deck.mjs +72 -0
  28. package/skills/video-gen/scripts/vg-draw.mjs +231 -0
  29. package/skills/video-gen/scripts/vg-engine.mjs +74 -0
  30. package/skills/video-gen/scripts/vg-fx.mjs +106 -0
  31. package/skills/video-gen/scripts/vg-model.mjs +236 -0
  32. package/skills/video-gen/scripts/vg-render.mjs +151 -0
  33. package/skills/video-gen/scripts/vg-scenes.mjs +226 -0
  34. package/skills/video-gen/scripts/video-gen.mjs +212 -0
  35. package/skills/video-gen/vendor/README.md +25 -0
  36. package/skills/video-gen/vendor/aimotion/LICENSE.md +21 -0
  37. package/skills/video-gen/vendor/canvaskit/LICENSE.md +29 -0
  38. package/skills/video-gen/vendor/canvaskit/THIRD-PARTY-NOTICES.md +1366 -0
  39. package/skills/video-gen/vendor/canvaskit/canvaskit.js +270 -0
  40. package/skills/video-gen/vendor/canvaskit/canvaskit.wasm +0 -0
  41. package/skills/video-gen/vendor/fonts/NotoSans-Bold.ttf +0 -0
  42. package/skills/video-gen/vendor/fonts/NotoSans-Regular.ttf +0 -0
  43. package/skills/video-gen/vendor/fonts/NotoSansKR-Bold.otf +0 -0
  44. package/skills/video-gen/vendor/fonts/NotoSansKR-Regular.otf +0 -0
  45. package/skills/video-gen/vendor/fonts/OFL-noto-sans-kr.md +94 -0
  46. package/skills/video-gen/vendor/fonts/OFL-noto-sans.md +93 -0
  47. package/skills/video-gen/vendor/h264-mp4-encoder/LICENSE.md +21 -0
  48. package/skills/video-gen/vendor/h264-mp4-encoder/MPL-1.1.md +469 -0
  49. package/skills/video-gen/vendor/h264-mp4-encoder/h264-mp4-encoder.node.js +2 -0
  50. package/skills/visual-ralph/SKILL.md +9 -4
  51. package/skills/wbs-gen/SKILL.md +127 -0
  52. package/skills/wbs-gen/assets/example.json +58 -0
  53. package/skills/wbs-gen/scripts/wbs-gen.mjs +238 -0
  54. package/skills/wbs-gen/scripts/wg-dates.mjs +106 -0
  55. package/skills/wbs-gen/scripts/wg-holidays.mjs +89 -0
  56. package/skills/wbs-gen/scripts/wg-html.mjs +108 -0
  57. package/skills/wbs-gen/scripts/wg-plan.mjs +230 -0
  58. package/skills/wbs-gen/scripts/wg-xlsx.mjs +328 -0
  59. package/skills/wbs-gen/scripts/wg-zip.mjs +83 -0
  60. package/skills/wide-pptx/SKILL.md +121 -0
  61. package/skills/wide-pptx/assets/example.json +63 -0
  62. package/skills/wide-pptx/scripts/wide-pptx.mjs +185 -0
  63. package/skills/wide-pptx/scripts/wp-html.mjs +98 -0
  64. package/skills/wide-pptx/scripts/wp-model.mjs +236 -0
  65. package/skills/wide-pptx/scripts/wp-pptx.mjs +219 -0
  66. package/skills/wide-pptx/scripts/wp-svg.mjs +114 -0
  67. package/skills/wide-pptx/vendor/LICENSE-jszip.md +651 -0
  68. package/skills/wide-pptx/vendor/LICENSE-pako.md +21 -0
  69. package/skills/wide-pptx/vendor/LICENSE-pptxgenjs.md +21 -0
  70. package/skills/wide-pptx/vendor/README.md +12 -0
  71. package/skills/wide-pptx/vendor/THIRD-PARTY-NOTICES.md +142 -0
  72. package/skills/wide-pptx/vendor/pptxgen.bundle.js +96 -0
@@ -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.26.1"
9
+ "version": "0.28.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "banker",
3
- "version": "0.26.1",
3
+ "version": "0.28.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,44 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.28.0] - 2026-11-10
4
+
5
+ ### Added
6
+ - 새 스킬 `wide-pptx`: 발표용 16:9 슬라이드를 한 JSON 덱에서 PPTX 와 단일 HTML 덱으로 같은 배치로 만든다.
7
+ - 슬라이드 종류 9개(표지, 구역, 글머리, 2단, 표, 차트, 그림, 인용, 끝)와 발표자 노트. `example`, `check`(넘침 어림 경고), `build` 명령.
8
+ - PptxGenJS 4.0.1 번들을 스킬에 넣고(sha256 고정, 라이선스 동봉) Node `vm` 의 별도 컨텍스트에서 불러 쓴다. 호스트의 내장 객체를 바꾸지 않는다.
9
+ - 글꼴은 한글 Noto Sans KR, 그 밖 Noto Sans. 테마, 차트, 표의 글꼴과 언어(ko-KR)를 쓴 뒤에 고친다.
10
+ - HTML 덱은 자체 스크립트와 SVG 차트로 동작하고 16:9 로 인쇄된다. 설치와 내려받기가 없다.
11
+ - 새 스킬 `wbs-gen`: WBS 를 간트 차트 XLSX 와 같은 격자의 단일 HTML 표로 만든다.
12
+ - XLSX 는 순수 Node 로 직접 쓴다(자체 ZIP, CRC-32). 막대는 셀 채움, 틀 고정, 인쇄 제목, A3 가로 한 장 너비. 셀 안의 한글과 라틴 글자는 서식 조각으로 글꼴을 나눈다.
13
+ - 영업일과 주차를 계산한다. 공휴일은 온라인이면 Google 공개 캘린더(대한민국)에서 `공휴일` 만 읽어 사용자가 확인하고, 닿지 못하면 종료 코드 3 으로 끝나 사용자가 `월/일, 월/일` 로 입력한다.
14
+ - 기간은 5년까지, 두 해에 걸치는 `월/일` 은 오류로 묻는다. 출력은 모두 만든 뒤 쓰고, `--force` 없이 덮어쓰지 않는다.
15
+ - 새 스킬 `video-gen`: 만들 수 있는 영상 목록을 보이고 고르게 한 뒤, 빠진 내용은 인터뷰로 받아 MP4 를 만든다.
16
+ - 내장 종류 9개(제목, 키네틱 타이포, 글머리, 차트, 카운터, 이름 자막, 인용, 사진, 크레디트)와 장면 전환 4개(cut, fade, wipe, stripes). `wide-pptx` 덱을 장면으로 바꾸고, 정지 화면으로 미리 확인한다.
17
+ - 렌더 엔진을 스킬에 넣었다. Skia CanvasKit 0.42.0(WASM)이 프레임을 그리고 h264-mp4-encoder 1.0.12(WASM)가 MP4 를 쓴다. Chrome, ffmpeg, 네이티브 모듈, 설치, 내려받기 없이 모든 OS 에서 같게 렌더한다.
18
+ - 글꼴은 Noto Sans 와 Noto Sans KR 을 넣었고, 글자마다 맞는 글꼴을 고른다. 프레임은 시간만의 함수라 정지 화면과 영상 프레임이 같다. 짧은 장면도 끝 프레임에 모든 내용이 보인다.
19
+ - 동작 도우미 일부는 aimotion(MIT)에서 옮겼다. libmp4v2(MPL 1.1)의 소스 묶음은 GitHub 저장소 `third_party/` 에 둔다(npm 묶음에는 없음).
20
+ - 다른 종류는 `motion-graphic-make`, `3d-intro-build`, `motion-sites` 로 넘긴다.
21
+
22
+ ### Fixed
23
+ - README: Codex 훅 신뢰 안내에 신뢰해야 도는 훅으로 banker `ralph` 계속(Stop 훅)을 더했다.
24
+
25
+ ## [0.27.0] - 2026-11-06
26
+
27
+ ### Added
28
+ - Codex 전용 새 스킬 `ralph`(npm 길 `$banker-ralph`, 플러그인 길 `$banker:ralph`): oh-my-codex 0.21 이 `$ralph` 를 없애(`$ultragoal` 을 가리키는 스텁) banker 스킬이 Codex 에서 구현 단계를 부르지 못하던 것을 대신한다. OMX 없이 동작한다.
29
+ - 반복마다 구현, 슬롭 점검, 게이트 실행, 새 맥락 검토자 판정을 차례로 한다. 게이트는 ralph-qa 의 실행기(`gate-evidence.mjs`, 바이트 동일 사본)가 돌린다.
30
+ - 끝남은 `scripts/ralph-state.mjs done` 만 정한다. 게이트 기록, 검토자 APPROVE, 슬롭 점검이 모두 지금 작업 트리 지문과 맞아야 한다. 같은 트리에 받은 REJECT 는 다음 반복에서도 남는다. 판정은 검토자가 본 지문으로만 기록한다.
31
+ - 시작 전부터 있던 실패(기준선)는 그대로 두면 끝나지 않는다. 이 작업과 상관없는 실패만 `accept-baseline` 으로 까닭을 적고, 그 뒤에 받은 APPROVE 로만 끝낸다. 반복 0 은 깨끗한 시작 트리에서 돈 것만 기준선이 된다.
32
+ - 기록은 저장소 밖 게이트 폴더에 두고 잠금 안에서 바꾼다. 저장소 안에 추적하지 않는 파일을 만들지 않는다.
33
+ - Codex Stop 훅 `ralph-continue-codex`: Claude Code 의 OMC ralph 처럼, 실행을 시작한 세션이 그 실행을 `done` 이나 `stop` 으로 끝내기 전에는 턴을 이어 간다. Codex goal 설정이 없어도 된다.
34
+ - 하위 에이전트와 다른 세션은 막지 않는다. 기록 변화 없이 다섯 번 막은 뒤, 또는 한 실행에서 모두 100번 막은 뒤에는 놓아준다.
35
+ - 새 세션에서 이어 갈 때는 `adopt` 로 옮긴다. `BANKER_NO_RALPH_CONTINUE` 로 끈다.
36
+ - npm 길(`banker setup --codex`)과 Codex 플러그인(`codex/hooks.json` 의 Stop 두 번째 묶음) 모두에 들어간다. 훅 코드가 바뀌어 Codex 가 banker 훅의 신뢰를 한 번 다시 묻는다.
37
+
38
+ ### Changed
39
+ - `all-in-one`, `ultra-init`, `ultra-chain`, `visual-ralph`, `/banker:front-qa`: Codex 에서 구현 단계를 OMX `$ralph` 대신 banker `ralph` 로 부른다. Codex 의 `$ralplan` 에는 계획만 쓰고 멈추라고 넘긴다. OMX 0.21 의 ralplan 은 끝에 스스로 `$ultragoal` 로 넘어가기 때문이다. Claude Code 쪽은 바뀌지 않는다.
40
+ - `ralph-qa`, `ultraqa`: 공유 실행기 `gate-evidence.mjs`, `run-folder.mjs` 의 머리 주석에 Codex ralph 사본을 적는다(동작 변경 없음).
41
+
3
42
  ## [0.26.1] - 2026-11-05
4
43
 
5
44
  ### Changed
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별) 설치를 아우르는 **스킬 72개 + 커맨드 2개**(총 74개 구성요소. Codex 는 Claude Code 전용 `ultraqa` 를 뺀 스킬 71개와 Codex 전용 스킬 3개)
12
+ - QA·보안 감사·문서·아키텍처·위키·미디어 작업과 의존성·개발환경(OS별) 설치를 아우르는 **스킬 75개 + 커맨드 2개**(총 77개 구성요소. Codex 는 Claude Code 전용 `ultraqa` 를 뺀 스킬 74개와 Codex 전용 스킬 4개)
13
13
  - Claude Code 즉시 명령 `/graceful-pause`, `/progress`
14
14
 
15
15
  □ 설치하면 스킬과 커맨드가 `/banker:<이름>` 네임스페이스로 노출됩니다.
@@ -49,7 +49,7 @@ codex plugin add banker@banker-plugins
49
49
  ```
50
50
 
51
51
  스킬을 `$banker:audit-security` 처럼 `$banker:<name>` 으로 호출합니다.\
52
- 훅(업데이트 알림, 사용량 카운팅)은 Codex 를 다시 열 때 뜨는 Hooks need review 창이나 `/hooks` 에서 신뢰해야 돕니다.\
52
+ 훅(업데이트 알림, 사용량 카운팅, banker `ralph` 계속)은 Codex 를 다시 열 때 뜨는 Hooks need review 창이나 `/hooks` 에서 신뢰해야 돕니다.\
53
53
  Codex 는 세션을 시작할 때 마켓플레이스를 스스로 올려 새 판을 받습니다. 바로 올리려면 `codex plugin marketplace upgrade banker-plugins` 를 실행합니다.
54
54
 
55
55
  2) npm 전역 설치 후 `banker setup`
@@ -101,6 +101,7 @@ Claude Code는 `/banker:<이름>`, Codex는 `$banker-<이름>`(플러그인 설
101
101
  | `/banker:setup` | 구성요소·의존성 설치 오케스트레이터 (multi-select) |
102
102
  | `/graceful-pause` | 작업 중에도 바로 실행되는 정지 요청. 지금 단계만 끝내고 멈춘 뒤 보고하고 지시를 기다림 (Claude Code 2.1.289 이상, mod. Codex 는 작업 중에 `$banker-graceful-pause` 를 보내면 실행 중인 도구 호출이 끝난 뒤 멈춤) |
103
103
  | `$banker-teamwork` (Codex 전용 스킬) | 같은 기기의 Codex CLI 세션끼리 피어 협업을 켜고 끔(`on` 기본, `off`, `status`). 켜면 열린 세션과 새 세션이 모두 팀으로 동작하고, 역할은 각 세션에 말로 주며, 요청과 결과는 Codex 내장 `codex queue` 로 오감. 보낼 대상은 역할, `/rename` 으로 정한 세션 이름, 짧은ID 로 지정. 플러그인 길은 `$banker:teamwork` |
104
+ | `$banker-ralph` (Codex 전용 스킬) | 작업이 실제로 끝날 때까지 구현, 게이트 실행, 슬롭 점검, 새 맥락 검토를 반복. 실행기가 돌린 게이트 기록, 검토자 APPROVE, 슬롭 점검이 모두 지금 작업 트리와 맞을 때만 `RALPH COMPLETE`. Claude Code 의 ralph 처럼 실행이 끝나기 전에는 banker Stop 훅이 턴을 이어 감(OMX 와 Codex goal 설정 불필요). OMX 0.21 에서 없어진 `$ralph` 를 대신함. `all-in-one`, `ultra-init`, `ultra-chain`, `visual-ralph`, `front-qa` 가 Codex 에서 이것을 부름. 플러그인 길은 `$banker:ralph` |
104
105
  | `/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` 으로 오른쪽 칸에 갱신) |
105
106
 
106
107
  Claude Code 2.1.296 은 Claude 5 모델(Opus 5.5 등)에 작업 목록 도구(TaskCreate, TodoWrite)를 주지 않습니다. 그래서 `/progress` 를 켜면 모델에게 할 일 도구 `mcp__banker__todo` 를 줍니다(다음 요청부터). 모델이 여러 단계 작업을 시작할 때 할 일 목록을 보내면 그 목록이 단계가 됩니다. 한 단계로 끝나는 일에는 쓰지 않으므로 그때는 요청이 단계입니다. 내장 작업 목록 도구가 있는 세션(옛 모델, `CLAUDE_CODE_ENABLE_TODO_TOOLS=1`)에는 이 도구를 더하지 않습니다. 도구는 `/progress off` 뒤에도 세션이 끝날 때까지 남고, 그동안 부르면 패널이 꺼져 있다고 답합니다.
@@ -125,6 +126,8 @@ Claude Code 2.1.296 은 Claude 5 모델(Opus 5.5 등)에 작업 목록 도구(Ta
125
126
  |---|---|
126
127
  | `arch-diagram` | 시스템 아키텍처 구성도 (PlantUML + 편집가능 PPTX) |
127
128
  | `vertical-pptx` | A4 세로(210x297mm) 규격 PPTX 생성·점검·수리, 16:9 덱의 A4 세로 변환 |
129
+ | `wide-pptx` | 발표용 16:9 슬라이드를 한 JSON 덱에서 PPTX 와 단일 HTML 덱으로 같은 배치로 만듦. 슬라이드 종류 9개(표지, 글머리, 2단, 차트, 표, 그림, 인용, 구역, 끝)와 발표자 노트. 번들한 PptxGenJS 4.0.1 을 써 설치와 내려받기 없이 동작하고, 글꼴은 한글 Noto Sans KR, 그 밖 Noto Sans |
130
+ | `wbs-gen` | WBS 를 간트 차트 XLSX(셀 채움 막대, 인쇄 설정)와 같은 격자의 단일 HTML 표로 만듦. 영업일과 주차를 계산하고 공휴일은 온라인이면 Google 공개 캘린더에서 읽어 사용자가 확인, 닿지 못하면 사용자가 `월/일, 월/일` 로 입력. XLSX 는 순수 Node 로 직접 써 설치가 필요 없음 |
128
131
  | `make-notion-guide` | API 호출 가이드를 노션 양식 문서로 작성 |
129
132
  | `pdf-vision-extract` | 비주얼 PDF를 고해상도 PNG로 변환(비전 입력) |
130
133
  | `nothing-design` | Nothing 스타일 UI 디자인 적용 |
@@ -180,6 +183,7 @@ Claude Code 2.1.296 은 Claude 5 모델(Opus 5.5 등)에 작업 목록 도구(Ta
180
183
 
181
184
  | 스킬 | 설명 |
182
185
  |---|---|
186
+ | `video-gen` | 만들 수 있는 영상 목록을 보이고 고르게 한 뒤, 빠진 내용은 인터뷰로 받아 MP4 를 만듦. 내장 종류 9개(제목, 키네틱 타이포, 글머리, 차트, 카운터, 이름 자막, 인용, 사진, 크레디트)와 장면 전환 4개, `wide-pptx` 덱 변환, 정지 화면 확인. 번들한 WASM 엔진(Skia CanvasKit, H.264 MP4 인코더)과 Noto 글꼴로 Chrome, ffmpeg, 설치, 내려받기 없이 모든 OS 에서 같게 렌더. 다른 종류는 `motion-graphic-make`, `3d-intro-build`, `motion-sites` 로 넘김 |
183
187
  | `motion-graphic-make` | 10초 내외 내레이션 없는 무료 모션 그래픽 제작 (hyperframes 위임). 계획 승인, 스냅숏과 렌더 검토 페이지, 독립 검증, 사이트 배치는 선택 |
184
188
  | `motion-sites` | MotionSites 의 `motionsites-web-builder` 스킬을 짧은 이름으로 부름. 첫 실행 때 원본 플러그인을 MotionSites 마켓플레이스에서 설치하고 Playwright 와 Chromium 을 갖춘 뒤 프롬프트를 그대로 넘김. 원본은 banker 에 복사하지 않음(예시와 미디어 재배포 금지), 첫 실행에 네트워크 필요 |
185
189
  | `3d-intro-build` | 스크롤-스크럽 3D 인트로 사이트 제작 (Azure gpt-image 스틸 + WAN 키 풀 영상, 소진 시 Sora-2 폴백, 유료). 단계별 검토 페이지, 엄격한 CSP 에서 도는 페이지, 실제 브라우저 독립 검증. 영상 모드는 chain, dive 나가기 없음(기본), dive 나가기 있음 가운데 고름. 장면 단위 이동(장면 시작점에 멈춤, Home, End, 장면 점은 바로 이동)은 기본 켬이고 끌 수 있음, 기존 사이트 통합은 선택 |
@@ -233,14 +237,14 @@ banker uninstall # 제거
233
237
 
234
238
  - `--scope project` 로 프로젝트 로컬(`./.codex`)에 설치하고, `--dry-run` 으로 미리 볼 수 있습니다.
235
239
  - non-root 전용입니다(전역 sudo 설치 시 root 소유 파일을 방지). postinstall이 없으므로 `banker setup` 을 직접 실행합니다.
236
- - Codex에는 스킬 71개와 Codex 전용 스킬 3개(`graceful-pause`, `progress`, `teamwork`)가 `~/.codex/skills/banker-<name>/` 에, 커맨드 2개가 `~/.codex/prompts/banker-<name>.md` 에 설치됩니다(`codex/manifest.json`). \
240
+ - Codex에는 스킬 71개와 Codex 전용 스킬 4개(`graceful-pause`, `progress`, `ralph`, `teamwork`)가 `~/.codex/skills/banker-<name>/` 에, 커맨드 2개가 `~/.codex/prompts/banker-<name>.md` 에 설치됩니다(`codex/manifest.json`). \
237
241
  `CODEX_HOME` 을 설정했으면 `~/.codex` 대신 그 폴더에 설치합니다(Codex 가 읽는 곳). \
238
242
  `lineage`, `progress`, 사용량 카운팅은 Codex 의 대화 기록(rollout)을 읽습니다. Codex 는 이 형식을 공개 약속으로 내놓지 않아 업데이트로 바뀔 수 있습니다. \
239
243
  Codex 를 업데이트한 뒤에는 `banker doctor` 로 형식을 점검합니다. 읽지 못하면 lineage 는 빈 페이지 대신 오류로 멈추고, progress 는 경고 줄을 보이며, 카운팅은 다음 세션 시작 때 경고를 한 번 보입니다. \
240
244
  Codex 0.144 이상에는 사용자 프롬프트가 없으므로 커맨드 2개는 스킬로도 설치되어 `$banker-front-qa`, `$banker-setup` 으로 부릅니다. \
241
245
  디렉터리명과 일치하도록 프론트매터 `name:` 이 `banker-<name>` 으로 재작성되어 Codex가 `banker-<name>` 으로 인식합니다.
242
246
  - Codex 플러그인 설치(`codex plugin add banker@banker-plugins`)는 저장소를 `~/.codex/plugins/cache/banker-plugins/banker/<판>/` 에 두고 `.codex-plugin/plugin.json` 을 읽습니다. \
243
- 스킬 71개, Codex 전용 스킬 3개, 커맨드 2개(스킬로)가 `$banker:<name>` 으로 보이고, 업데이트 알림과 사용량 카운팅 훅은 `codex/hooks.json` 에서 옵니다. \
247
+ 스킬 71개, Codex 전용 스킬 4개, 커맨드 2개(스킬로)가 `$banker:<name>` 으로 보이고, 업데이트 알림, 사용량 카운팅, banker `ralph` 계속 훅은 `codex/hooks.json` 에서 옵니다. \
244
248
  Codex 는 세션을 시작할 때 마켓플레이스를 스스로 올려 새 판을 설치합니다. 훅 명령 앞에는 훅이 실행하는 파일의 해시가 붙어 있어, 훅 코드가 바뀐 판에서는 `/hooks` 가 다시 신뢰를 묻고 신뢰하기 전에는 훅이 돌지 않습니다. 훅 코드가 그대로인 판은 신뢰도 그대로입니다. 스킬 파일은 신뢰 확인 없이 새 판으로 바뀝니다. \
245
249
  obsidizer 훅은 npm 설치와 같이 `obsidizer --enable` 할 때만 사용자 범위에 설치됩니다(셸 호출마다 node 를 띄우므로). \
246
250
  훅은 Codex 샌드박스 밖에서 돌므로, PATH 의 node 가 임시 폴더나 작업 폴더 안에 있으면(샌드박스가 바꿀 수 있는 곳) 아무것도 돌리지 않고 그 이유를 세션 시작 알림으로 보입니다. 작업 폴더가 홈 폴더 자체이면 홈 아래의 node(nvm 등)는 그대로 씁니다. 훅 스크립트, 플러그인 폴더, node 가 `config.toml` 의 `[sandbox_workspace_write] writable_roots` 아래(그 항목 맨 위 `.codex` 제외)면 훅은 다른 일을 하지 않고 그 이유를 한 줄로 알립니다. 이 검사는 훅 파일 안에서 하므로, 샌드박스가 이미 그 파일을 고친 뒤에는 막지 못합니다. writable_roots 에 Codex 홈이나 node 가 든 폴더를 넣지 마십시오. \
@@ -268,7 +272,8 @@ banker uninstall # 제거
268
272
  - `payload-mon` 은 Codex에서 실행해도 같은 머신의 Claude Code HUD 래퍼만 켜고 끕니다. \
269
273
  32MB 요청 한도는 Claude Code 의 것이라 Codex 자신의 요청 크기는 재지 않습니다.
270
274
  - `omc-patch` 도 Codex에서 실행하면 같은 머신의 Claude Code OMC 플러그인 캐시(`~/.claude/plugins/`)만 다룹니다. Codex 의 OMX 는 건드리지 않습니다.
271
- - Codex에선 OMC 대신 **oh-my-codex(OMX)** 의 동명 스킬(ralplan·ralph·hud 등)과 `codex mcp`·내장 `/copy` 를 사용합니다(Codex는 `omx setup` 전제).
275
+ - Codex에선 OMC 대신 **oh-my-codex(OMX)** 의 동명 스킬(ralplan·hud 등)과 `codex mcp`·내장 `/copy` 를 사용합니다(Codex는 `omx setup` 전제). \
276
+ `ralph` 만 예외입니다. OMX 0.21 이 `$ralph` 를 없앴으므로 banker 스킬은 Codex 에서 banker 의 Codex 전용 `ralph`(`$banker-ralph`, 플러그인 길 `$banker:ralph`)를 부릅니다.
272
277
  - `all-in-one`·`ultra-init` 의 마지막 QA 게이트는 두 런타임 모두 banker `ralph-qa` 입니다. 독립 검토자 3명 이상이 실행기가 돌린 `--qa` 목표의 게이트 기록으로 판정합니다.
273
278
  - 설치기는 `~/.codex/AGENTS.md` 를 건드리지 않습니다(omx가 재생성하므로 `~/.codex/skills/` 자동 검색에 의존). \
274
279
  예외는 사용자가 켠 `tone-compact` 와 `anti-drm` 입니다. 각각 그 파일 끝에 `USER:OMX:POLICY` 로 감싼 블록 1개를 넣고, `off` 로 뺍니다. \
@@ -285,7 +290,7 @@ banker uninstall # 제거
285
290
  |---|---|---|
286
291
  | Claude Code | 플러그인 레지스트리 | `claude plugin marketplace add` + 설치 → `/banker:*` (`settings.json` 직접수정 없음) |
287
292
  | Codex CLI | `~/.codex/skills/banker-*/`, `~/.codex/prompts/banker-*.md` | 기존 `banker-*` 정리 후 복사(멱등). `AGENTS.md`·`config.toml` 미변경 |
288
- | 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`)이 지금 폴더나 그 저장소 안이면 샌드박스가 고칠 수 있어 훅을 설치하지 않음 |
293
+ | Codex CLI | `~/.codex/hooks.json`, `~/.codex/hooks-custom/banker/` | 업데이트 알림(`SessionStart`), 사용량 카운팅(`Stop`), banker `ralph` 계속(`Stop`, 실행을 시작한 세션이 그 실행을 끝내기 전에는 턴을 이어 감, `BANKER_NO_RALPH_CONTINUE` 로 끔) 훅. 사본은 작업 폴더 밖에 두고, 이벤트마다 맨 뒤에 묶음 1개를 더하며, 쓰기 전 `hooks.json.banker.bak` 백업. `banker hooks status`, `banker hooks enable <이름>`, `banker hooks disable <이름>` 으로 확인하고 켜고 끔. Codex 를 다시 열 때 Hooks need review 창이나 `/hooks` 에서 신뢰해야 돎. Codex 홈(`CODEX_HOME`)이 지금 폴더나 그 저장소 안이면 샌드박스가 고칠 수 있어 훅을 설치하지 않음 |
289
294
 
290
295
  **플러그인 자체 훅 — `hooks/hooks.json` (설치 시 Claude Code에서 자동 발화)**
291
296
 
@@ -326,7 +331,8 @@ banker uninstall # 제거
326
331
  - Claude Code (마켓플레이스 경로): 스킬이 `/banker:*` 로 동작.
327
332
  - 또는 Node.js ≥ 16.7 (npm 전역 설치 경로): `banker` CLI 제공.
328
333
  - 일부 스킬은 별도 의존성이 필요하며 `/banker:setup` 으로 설치합니다. 의존성이 없으면 각 스킬이 실행 전에 설치부터 안내합니다.
329
- - `all-in-one`, `ultra-chain`, `ultra-init`, `/banker:front-qa`: oh-my-claudecode(OMC) 5 와 같은 버전대의 `omc` CLI(`ralph` 가 `omc ralph verify` 를 부르므로 4.x CLI 로는 멈춤). Codex에서는 OMX. 마지막 QA 의 `ralph-qa` 는 banker 에 들어 있습니다.
334
+ - `all-in-one`, `ultra-chain`, `ultra-init`, `/banker:front-qa`: oh-my-claudecode(OMC) 5 와 같은 버전대의 `omc` CLI(`ralph` 가 `omc ralph verify` 를 부르므로 4.x CLI 로는 멈춤). Codex에서는 계획 단계에 OMX(`$ralplan`)가 필요하고, 구현 단계는 banker 의 Codex 전용 `ralph` 라 OMX 가 필요 없습니다. 마지막 QA 의 `ralph-qa` 는 banker 에 들어 있습니다.
335
+ - `ralph`(Codex 전용): git 저장소. 기록은 저장소 밖 게이트 폴더에 두므로 `workspace-write` 샌드박스에서는 실행기 호출에 승인이 필요할 수 있습니다.
330
336
  - `audit-web-page`, `play-qa`, `ultra-ui-qa`: playwright. `3d-intro-build` 의 독립 검증(`verify-intro.mjs`)도 Node 용 playwright 패키지와 Chromium 을 씁니다(`npm i -g playwright && npx playwright install chromium`).
331
337
  - `payload-mon`: OMC 커스텀 HUD 래퍼(`setup-omc-hud` 로 설치).
332
338
  - `omc-patch`: Node 18 이상과 `git`(OMC 마켓플레이스 고정과 확인에 사용).
@@ -458,8 +464,9 @@ banker setup
458
464
  - `npx` 로 부르는 도구(`motion-graphic-make` 의 hyperframes 등): 전역 설치가 없으면 패키지를 받으려 해, 네트워크가 없으면 약 70초 뒤 실패합니다. 오프라인에서는 전역 설치한 실행 파일을 부릅니다.
459
465
  - 유료 API 를 부르는 `3d-intro-build`(요청마다 시간 제한), `ralph-qa` 의 외부 좌석(codex, gemini, opencode, 좌석마다 600초)
460
466
  - `build-testbox`: `run` 은 내려받지 않고, 없는 이미지, 브라우저, 엔진의 칸은 `unverified` 와 setup 할 항목으로 끝납니다. `setup` 은 `--yes` 확인 뒤에만 받고, 원천에 닿지 못하면 받지 않고 "네트워크 필요" 로 끝납니다.
467
+ - `wbs-gen` 의 공휴일 조회(Google 공개 캘린더): 닿지 못하면 기다리지 않고 종료 코드 3 으로 끝나고, 스킬이 사용자에게 공휴일을 `월/일, 월/일` 로 받습니다. 공휴일 표를 지어내지 않습니다.
461
468
  - `vertical-pptx`, `pdf-vision-extract` 가 빠진 도구를 설치하라고 안내할 때, `nothing-design` 의 글꼴(Google Fonts. 받지 못하면 대체 글꼴로 보임)
462
- - 네트워크를 쓰지 않는 스킬(예: `lineage`, `setup-omc-hud`, `tone-compact`, `anti-drm`, `payload-mon`, `init-dir`, `git-init`, `tune-tmux`)은 네트워크 없이 그대로 씁니다.
469
+ - 네트워크를 쓰지 않는 스킬(예: `lineage`, `setup-omc-hud`, `tone-compact`, `anti-drm`, `payload-mon`, `init-dir`, `git-init`, `tune-tmux`, `wide-pptx`, `video-gen`)은 네트워크 없이 그대로 씁니다.
463
470
 
464
471
  ## 라이선스 / 서드파티
465
472
 
@@ -476,8 +483,16 @@ banker 자체는 **MIT** ([LICENSE](LICENSE)). Owner: [smallOpenSource](https://
476
483
  - `skills/theme-selection/assets`: 번들 팔레트(`palettes.json`)는 [VoltAgent/awesome-design-md](https://github.com/VoltAgent/awesome-design-md) 의 DESIGN.md 에서 색 값을 읽어 만든 것, **MIT License**(© 2026 VoltAgent).\
477
484
  원본 라이선스 고지는 `skills/theme-selection/assets/LICENSE` 와 `NOTICE.md` 에 포함.\
478
485
  테마 이름의 상표는 각 소유자에게 있으며, banker 는 그 회사들과 관계가 없습니다.
479
-
480
- 코드로 포함(재배포)하는 서드파티는 `humanizer` 와 `scroll-world`(`3d-intro-build`) 둘이고, 데이터로 포함하는 것은 `awesome-design-md`(`theme-selection`) 하나입니다.\
486
+ - `skills/wide-pptx/vendor`: [PptxGenJS](https://github.com/gitbrent/PptxGenJS) 4.0.1 의 `pptxgen.bundle.js` 를 고치지 않고 벤더링, **MIT License**.\
487
+ 안에 든 JSZip(MIT 또는 GPLv3 중 MIT), pako(MIT), core-js 등의 고지는 같은 폴더의 `LICENSE-*.md` 와 `THIRD-PARTY-NOTICES.md` 에 포함.
488
+ - `skills/video-gen/vendor`: 아래를 고치지 않고 벤더링. 파일별 판과 sha256 은 `skills/video-gen/vendor/README.md`.
489
+ - [Skia CanvasKit](https://skia.org/docs/user/modules/canvaskit/) 0.42.0(`canvaskit-wasm`), **BSD-3-Clause**. 안에 든 ICU, FreeType, HarfBuzz, libpng, zlib 등의 고지는 `canvaskit/THIRD-PARTY-NOTICES.md`.
490
+ - [h264-mp4-encoder](https://github.com/TrevorSundberg/h264-mp4-encoder) 1.0.12, **MIT License**. 안의 minih264 는 public domain(CC0), libmp4v2 는 **Mozilla Public License 1.1**(전문 `h264-mp4-encoder/MPL-1.1.md`).\
491
+ libmp4v2 소스 묶음은 GitHub 저장소의 `third_party/libmp4v2-d49b4466.tar.gz` 에 둡니다(npm 묶음에는 넣지 않음).
492
+ - [Noto Sans](https://notofonts.github.io/), [Noto Sans KR](https://github.com/notofonts/noto-cjk) 글꼴, **SIL Open Font License 1.1**(`fonts/OFL-*.md`).
493
+ - `scripts/vg-fx.mjs` 의 동작 도우미 일부는 [kstost/cokacmotion](https://github.com/kstost/cokacmotion) 의 aimotion 에서 옮긴 것, **MIT License**(`aimotion/LICENSE.md`).
494
+
495
+ 코드로 포함(재배포)하는 서드파티는 `humanizer`, `scroll-world`(`3d-intro-build`), `omc_hud`(`setup-omc-hud`), PptxGenJS(`wide-pptx`), CanvasKit 과 h264-mp4-encoder 와 aimotion 도우미(`video-gen`)이고, 데이터로 포함하는 것은 `awesome-design-md`(`theme-selection`)와 Noto 글꼴(`video-gen`)입니다.\
481
496
  아래는 스킬이 **런타임에 의존하거나 연동**하는 외부 요소로, banker 가 재배포하지 않으며 각 라이선스·상표는 소유자에게 있습니다.
482
497
 
483
498
  **의존 라이브러리·도구 (사용 시 별도 설치, 라이선스는 각 프로젝트 소유)**
@@ -20,6 +20,7 @@ import path from 'node:path';
20
20
  export const HOOKS = {
21
21
  update: { event: 'SessionStart', script: 'hooks/update-notify.mjs', args: ['--codex'] },
22
22
  count: { event: 'Stop', script: 'hooks/telemetry-count-codex.mjs', args: [] },
23
+ ralph: { event: 'Stop', script: 'hooks/ralph-continue-codex.mjs', args: [] },
23
24
  obsidize: { event: 'PostToolUse', script: 'hooks/obsidize-hook.mjs', args: ['--codex'], matcher: 'Bash' },
24
25
  };
25
26
  // 사본으로 옮길 파일(훅 스크립트와 그 의존 파일). 상대 경로를 그대로 둬 '../bin/lib/...' 가져오기가 맞는다.
@@ -34,8 +35,13 @@ export const PAYLOAD = [
34
35
  'hooks/telemetry-count-codex.mjs',
35
36
  'hooks/obsidize-hook.mjs',
36
37
  'skills/obsidizer/obsidize.mjs',
38
+ 'hooks/ralph-continue-codex.mjs',
39
+ 'codex/skills/ralph/scripts/ralph-state.mjs',
40
+ 'codex/skills/ralph/scripts/gate-evidence.mjs',
41
+ 'codex/skills/ralph/scripts/run-folder.mjs',
37
42
  ];
38
- export const DEFAULT_HOOKS = ['update', 'count'];
43
+ // ralph: the Codex ralph keeps its turn going until its run ends (as the OMC ralph's Stop hook does in Claude Code).
44
+ export const DEFAULT_HOOKS = ['update', 'count', 'ralph'];
39
45
 
40
46
  // Codex 처럼 상대 경로 CODEX_HOME 은 지금 폴더 기준으로 푼다.
41
47
  export function codexPaths(env = process.env) {
@@ -19,7 +19,9 @@ description: "스펙 파일(기본 `docs/note.md`)을 바탕으로 프론트엔
19
19
  omc ralph verify --help 2>&1 | grep -q 'Usage: omc ralph verify' && echo "omc ok" || echo "omc CLI 없음 또는 낡음"
20
20
  ```
21
21
  - 실패하면 `/banker:setup` → oh-my-codex(OMX)(또는 `setup-omx` 스킬)로 먼저 구성한 뒤 이어서 진행. 점검을 통과하기 전에는 절차 1을 시작하지 않는다.
22
- - **런타임 매핑:** 아래 절차의 `/ralplan`·`/ralph`는 Claude Code에선 OMC 스킬, **Codex에선 oh-my-codex(OMX)의 동명 스킬**이다(Codex는 OMC 대신 OMX 전제). 각 런타임에서 사용 가능한 형태로 호출한다.
22
+ - **런타임 매핑:** 아래 절차의 `/ralplan` 은 Claude Code에선 OMC 스킬, **Codex에선 oh-my-codex(OMX)의 동명 스킬**이다(Codex는 OMC 대신 OMX 전제). `/ralph` 는 Claude Code에선 OMC 스킬, **Codex에선 banker 의 Codex 전용 `ralph`** 다(npm 길 `$banker-ralph`, 플러그인 길 `$banker:ralph`. OMX 0.21 이 `$ralph` 를 없애 OMX 것은 부르지 않음). 각 런타임에서 사용 가능한 형태로 호출한다.
23
+ - **Codex 의 2단계:** OMX 0.21 의 `$ralplan` 은 끝에 스스로 `$ultragoal` 로 넘어간다. 계획만 쓰고 멈추라고 넘긴다(`$ultragoal`, `$ralph`, `$team`, 구현으로 넘어가지 않음). 계획은 `.omx/plans/` 에 둔다.
24
+ - **Codex 의 3~6단계:** banker `ralph` 의 `SKILL.md`(스킬로 설치된 때: npm 길 `<S>/../banker-ralph/SKILL.md`, 플러그인 길 `<S>/../../skills/ralph/SKILL.md`, `<S>` 는 이 커맨드 스킬 폴더)를 따른다. 스테이지와 스펙 항목을 수용 기준으로, typecheck 와 vitest 와 parity-gate 를 게이트(게이트마다 명령 하나)로 넘긴다. 그 ralph 가 새 맥락 검토, 슬롭 점검, 회귀 확인을 함께 하고 `RALPH COMPLETE` 줄을 낸다. critic 은 ralph 의 검토자가 맡는다.
23
25
 
24
26
  ## 절차 (CLAUDE.md: 계획 없는 즉시실행 금지)
25
27
 
package/codex/hooks.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
- "description": "banker hooks for the Codex plugin (codex plugin add banker@banker-plugins), written by scripts/sync-codex-plugin.js. Codex reads this file instead of hooks/hooks.json because .codex-plugin/plugin.json names it. update notice (SessionStart, opt-out BANKER_NO_UPDATE_CHECK), usage counting (Stop, opt-out BANKER_NO_TELEMETRY). The obsidizer hook is installed only by obsidizer --enable. Codex runs them only after you trust them in /hooks, and outside its sandbox: each command runs the node PATH names (a multicall shim such as Volta or mise, or a .cmd shim on Windows: the node binary it names) only when its real path is absolute and outside the temp folder and the session folder (a session in the home folder itself excepted), from the plugin folder when that is outside them too, and otherwise says why it skipped. A hook whose script, plugin folder or node is under a config.toml [sandbox_workspace_write] writable_roots entry (not that entry's top .codex) does nothing but say why. PATH entries under a network mount outside the home folder are not read. Each command starts with a hash of the hook files (banker-hooks-rev), so new hook code asks for trust again. command is for sh, commandWindows for PowerShell; PLUGIN_ROOT is the installed plugin folder.",
2
+ "description": "banker hooks for the Codex plugin (codex plugin add banker@banker-plugins), written by scripts/sync-codex-plugin.js. Codex reads this file instead of hooks/hooks.json because .codex-plugin/plugin.json names it. update notice (SessionStart, opt-out BANKER_NO_UPDATE_CHECK), usage counting (Stop, opt-out BANKER_NO_TELEMETRY), keeping a banker ralph run going until it ends (Stop, only for the session that started it, opt-out BANKER_NO_RALPH_CONTINUE). The obsidizer hook is installed only by obsidizer --enable. Codex runs them only after you trust them in /hooks, and outside its sandbox: each command runs the node PATH names (a multicall shim such as Volta or mise, or a .cmd shim on Windows: the node binary it names) only when its real path is absolute and outside the temp folder and the session folder (a session in the home folder itself excepted), from the plugin folder when that is outside them too, and otherwise says why it skipped. A hook whose script, plugin folder or node is under a config.toml [sandbox_workspace_write] writable_roots entry (not that entry's top .codex) does nothing but say why. PATH entries under a network mount outside the home folder are not read. Each command starts with a hash of the hook files (banker-hooks-rev), so new hook code asks for trust again. command is for sh, commandWindows for PowerShell; PLUGIN_ROOT is the installed plugin folder.",
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": ": banker-hooks-rev 59265eebca3c; x() { m=$(printf '%s' \"banker hooks skipped: $1\" | command -p tr -d '\\000-\\037\"\\\\'); printf '{\"systemMessage\":\"%s\"}\\n' \"$m\"; exit 0; }; [ -n \"$PLUGIN_ROOT\" ] || x \"PLUGIN_ROOT is not set\"; p=$(pwd -P); [ -n \"$p\" ] && [ \"$p\" != / ] || x \"the session folder is /\"; u=$(cd /tmp 2>/dev/null && pwd -P); t=$(cd \"${TMPDIR:-/tmp}\" 2>/dev/null && pwd -P); h=; case \"$HOME\" in /?*) h=$(cd \"$HOME\" 2>/dev/null && pwd -P);; esac; w() { for z in \"$u\" \"$t\"; do [ -n \"$z\" ] && case \"$1/\" in \"$z\"/*) return 0;; esac; done; [ \"$p\" = \"$h\" ] && return 1; case \"$1/\" in \"$p\"/*) return 0;; esac; return 1; }; pr=$(cd -P \"$PLUGIN_ROOT\" 2>/dev/null && pwd -P) || x \"cannot enter the plugin folder\"; w \"$pr\" && x \"the plugin folder is in a temp folder or the session folder ($pr)\"; A='function dec(s, o, i, c) { o = \"\"; for (i = 1; i <= length(s); i++) { c = substr(s, i, 1); if (c == \"\\\\\" && substr(s, i + 1, 3) ~ /^[0-7][0-7][0-7]$/) { o = o sprintf(\"%c\", substr(s, i + 1, 1) * 64 + substr(s, i + 2, 1) * 8 + substr(s, i + 3, 1)); i += 3 } else o = o c }; return o } mac { i = index($0, \" on \"); j = 0; for (k = length($0); k > i; k--) if (substr($0, k, 2) == \" (\") { j = k; break }; if (!i || !j) next; m = substr($0, i + 4, j - i - 4); t = substr($0, j + 2); sub(/[,)].*/, \"\", t) } !mac { m = dec($2); t = $3 } (t ~ /^(nfs|nfs4|cifs|smbfs|smb3|afpfs|webdav|davfs|autofs|ceph|glusterfs|lustre|9p)$/ || t ~ /^fuse\\./) && m !~ /:/ { printf \"%s:\", m }'; if [ -r '/proc/self/mounts' ]; then M=$(LC_ALL=C command -p awk \"$A\" '/proc/self/mounts' 2>/dev/null); else M=$('/sbin/mount' 2>/dev/null | LC_ALL=C command -p awk -v mac=1 \"$A\" 2>/dev/null); fi; nm() { for z in $M; do case \"$1/\" in \"$z\"/*) return 0;; esac; done; return 1; }; hm() { for z in \"$h\" \"$HOME\"; do case \"$z\" in /?*) case \"$1/\" in \"$z\"/*) return 0;; esac;; esac; done; return 1; }; set -f; o=$IFS; IFS=:; a=; q=; for v in $PATH; do case \"$v\" in /*) ;; *) a=$a${a:+:}$v; continue;; esac; nm \"$v\" && ! hm \"$v\" && continue; a=$a${a:+:}$v; y=$(cd \"$v\" 2>/dev/null && pwd -P) || continue; w \"$y\" || q=$q${q:+:}$v; done; n=; [ -n \"$a\" ] && n=$(PATH=$a; command -v node) || for d in /opt/homebrew/bin /usr/local/bin; do nm \"$d\" && continue; [ -x \"$d/node\" ] && n=$d/node && break; done; IFS=$o; set +f; [ -n \"$n\" ] || x \"node is not on PATH (a Codex started from the desktop may not have the shell's PATH)\"; case \"$n\" in /*) ;; *) x \"node on PATH is not an absolute path ($n)\";; esac; PATH=${q:-/usr/bin:/bin}; export PATH; cd \"$pr\" || x \"cannot enter the plugin folder\"; rp() { realpath \"$1\" 2>/dev/null || readlink -f \"$1\" 2>/dev/null || { f=$1; i=0; while [ -L \"$f\" ]; do i=$((i+1)); [ \"$i\" -le 40 ] || return 1; l=$(readlink \"$f\") || return 1; case \"$l\" in /*) f=$l;; *) f=${f%/*}/$l;; esac; done; [ -e \"$f\" ] || return 1; d=$(cd -P \"${f%/*}/\" 2>/dev/null && pwd -P) || return 1; printf '%s\\n' \"${d%/}/${f##*/}\"; }; }; r=$(rp \"$n\") || x \"cannot resolve the real path of $n\"; k() { case \"$1\" in /*) ;; *) x \"node path is not absolute ($1)\";; esac; for d in \"$u\" \"$t\"; do [ -n \"$d\" ] && case \"$1\" in \"$d\"/*) x \"node is in a temp folder ($1)\";; esac; done; [ \"$p\" = \"$h\" ] || case \"$1\" in \"$p\"/*) x \"node is in the session folder ($1)\";; esac; }; case \"${r##*/}\" in node) k \"$r\"; e=$r;; *) k \"$n\"; k \"$r\"; e=$(\"$n\" -p 'require(\"fs\").realpathSync(process.execPath)' 2>/dev/null) || x \"the node shim $n did not name its node\"; k \"$e\";; esac; \"$e\" \"$pr/hooks/run.cjs\" \"$pr/hooks/update-notify.mjs\" --codex --plugin",
10
- "commandWindows": "$null = 'banker-hooks-rev 59265eebca3c'; function x($m) { Write-Output (ConvertTo-Json -Compress @{ systemMessage = \"banker hooks skipped: $m\" }); exit 0 }; if (-not $env:PLUGIN_ROOT) { x 'PLUGIN_ROOT is not set' }; function G($p) { try { (Get-Item -LiteralPath $p -Force -ErrorAction Stop).FullName } catch { try { [IO.Path]::GetFullPath($p) } catch { $p } } }; $sb = @{}; foreach ($l in @(& (Join-Path ([Environment]::SystemDirectory) 'subst.exe') 2>$null)) { if ($l -match '^([A-Za-z]:)\\\\: => (.+)$') { $sb[$matches[1].ToUpper()] = $matches[2] } }; function L($p) { if (-not $p) { return '' }; $p = G $p; for ($i = 0; $i -lt 32; $i++) { $d2 = if ($p.Length -ge 2) { $p.Substring(0, 2).ToUpper() } else { '' }; if ($sb.Contains($d2)) { $p = G ($sb[$d2].TrimEnd('\\') + $p.Substring(2)); continue }; $c = $p; $t = $null; while ($c) { $it = Get-Item -LiteralPath $c -Force -ErrorAction SilentlyContinue; if ($it -and $it.LinkType -in 'SymbolicLink', 'Junction' -and @($it.Target)[0]) { $t = [string]@($it.Target)[0]; break }; $c = Split-Path -LiteralPath $c }; if (-not $t) { break }; if (-not [IO.Path]::IsPathRooted($t)) { $t = Join-Path (Split-Path -LiteralPath $c) $t }; $p = G ($t.TrimEnd('\\') + $p.Substring($c.Length)) }; $p.TrimEnd('\\') }; $ds = @($env:TEMP, $env:TMP); if ($env:LOCALAPPDATA) { $ds += \"$env:LOCALAPPDATA\\Temp\" }; $w = L $PWD.Path; if ($w -ne (L $env:USERPROFILE)) { $ds += $w }; $ds = @($ds | Where-Object { $_ } | ForEach-Object { L $_ } | Where-Object { $_ }); function u($f) { $f = L $f; foreach ($d in $ds) { if ($f -eq $d -or $f.StartsWith($d + '\\', 'OrdinalIgnoreCase')) { return $true } }; $false }; function k($f) { if (u $f) { x \"node is in a temp folder or the session folder ($(L $f))\" }; L $f }; $pr = L $env:PLUGIN_ROOT; if (u $pr) { x \"the plugin folder is in a temp folder or the session folder ($pr)\" }; $nd = @(@($env:PATH -split ';') | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_.Trim().Trim('\"')) } | Where-Object { $_ -match '^[A-Za-z]:\\\\' -and ([IO.DriveInfo]::new($_.Substring(0, 1))).DriveType -ne 'Network' }); $n = $null; foreach ($d in $nd) { foreach ($e in 'node.exe', 'node.bat', 'node.cmd') { $f = Join-Path $d $e; if (Test-Path -LiteralPath $f -PathType Leaf) { $n = $f; break } }; if ($n) { break } }; if (-not $n) { x 'node is not on PATH' }; $env:PATH = ($nd | Where-Object { -not (u $_) }) -join ';'; $r = k $n; if (-not (Set-Location -LiteralPath $pr -PassThru -ErrorAction SilentlyContinue)) { x 'cannot enter the plugin folder' }; if ($r -notmatch '\\.exe$') { $e = & $r -p process.execPath 2>$null | Select-Object -Last 1; if (-not $e -or $e -notmatch '\\.exe$' -or -not (Test-Path -LiteralPath $e -PathType Leaf)) { x \"the node shim $r did not name its node\" }; $r = k $e }; & $r \"$pr\\hooks\\run.cjs\" \"$pr\\hooks\\update-notify.mjs\" --codex --plugin",
9
+ "command": ": banker-hooks-rev d1402c48baa8; x() { m=$(printf '%s' \"banker hooks skipped: $1\" | command -p tr -d '\\000-\\037\"\\\\'); printf '{\"systemMessage\":\"%s\"}\\n' \"$m\"; exit 0; }; [ -n \"$PLUGIN_ROOT\" ] || x \"PLUGIN_ROOT is not set\"; p=$(pwd -P); [ -n \"$p\" ] && [ \"$p\" != / ] || x \"the session folder is /\"; u=$(cd /tmp 2>/dev/null && pwd -P); t=$(cd \"${TMPDIR:-/tmp}\" 2>/dev/null && pwd -P); h=; case \"$HOME\" in /?*) h=$(cd \"$HOME\" 2>/dev/null && pwd -P);; esac; w() { for z in \"$u\" \"$t\"; do [ -n \"$z\" ] && case \"$1/\" in \"$z\"/*) return 0;; esac; done; [ \"$p\" = \"$h\" ] && return 1; case \"$1/\" in \"$p\"/*) return 0;; esac; return 1; }; pr=$(cd -P \"$PLUGIN_ROOT\" 2>/dev/null && pwd -P) || x \"cannot enter the plugin folder\"; w \"$pr\" && x \"the plugin folder is in a temp folder or the session folder ($pr)\"; A='function dec(s, o, i, c) { o = \"\"; for (i = 1; i <= length(s); i++) { c = substr(s, i, 1); if (c == \"\\\\\" && substr(s, i + 1, 3) ~ /^[0-7][0-7][0-7]$/) { o = o sprintf(\"%c\", substr(s, i + 1, 1) * 64 + substr(s, i + 2, 1) * 8 + substr(s, i + 3, 1)); i += 3 } else o = o c }; return o } mac { i = index($0, \" on \"); j = 0; for (k = length($0); k > i; k--) if (substr($0, k, 2) == \" (\") { j = k; break }; if (!i || !j) next; m = substr($0, i + 4, j - i - 4); t = substr($0, j + 2); sub(/[,)].*/, \"\", t) } !mac { m = dec($2); t = $3 } (t ~ /^(nfs|nfs4|cifs|smbfs|smb3|afpfs|webdav|davfs|autofs|ceph|glusterfs|lustre|9p)$/ || t ~ /^fuse\\./) && m !~ /:/ { printf \"%s:\", m }'; if [ -r '/proc/self/mounts' ]; then M=$(LC_ALL=C command -p awk \"$A\" '/proc/self/mounts' 2>/dev/null); else M=$('/sbin/mount' 2>/dev/null | LC_ALL=C command -p awk -v mac=1 \"$A\" 2>/dev/null); fi; nm() { for z in $M; do case \"$1/\" in \"$z\"/*) return 0;; esac; done; return 1; }; hm() { for z in \"$h\" \"$HOME\"; do case \"$z\" in /?*) case \"$1/\" in \"$z\"/*) return 0;; esac;; esac; done; return 1; }; set -f; o=$IFS; IFS=:; a=; q=; for v in $PATH; do case \"$v\" in /*) ;; *) a=$a${a:+:}$v; continue;; esac; nm \"$v\" && ! hm \"$v\" && continue; a=$a${a:+:}$v; y=$(cd \"$v\" 2>/dev/null && pwd -P) || continue; w \"$y\" || q=$q${q:+:}$v; done; n=; [ -n \"$a\" ] && n=$(PATH=$a; command -v node) || for d in /opt/homebrew/bin /usr/local/bin; do nm \"$d\" && continue; [ -x \"$d/node\" ] && n=$d/node && break; done; IFS=$o; set +f; [ -n \"$n\" ] || x \"node is not on PATH (a Codex started from the desktop may not have the shell's PATH)\"; case \"$n\" in /*) ;; *) x \"node on PATH is not an absolute path ($n)\";; esac; PATH=${q:-/usr/bin:/bin}; export PATH; cd \"$pr\" || x \"cannot enter the plugin folder\"; rp() { realpath \"$1\" 2>/dev/null || readlink -f \"$1\" 2>/dev/null || { f=$1; i=0; while [ -L \"$f\" ]; do i=$((i+1)); [ \"$i\" -le 40 ] || return 1; l=$(readlink \"$f\") || return 1; case \"$l\" in /*) f=$l;; *) f=${f%/*}/$l;; esac; done; [ -e \"$f\" ] || return 1; d=$(cd -P \"${f%/*}/\" 2>/dev/null && pwd -P) || return 1; printf '%s\\n' \"${d%/}/${f##*/}\"; }; }; r=$(rp \"$n\") || x \"cannot resolve the real path of $n\"; k() { case \"$1\" in /*) ;; *) x \"node path is not absolute ($1)\";; esac; for d in \"$u\" \"$t\"; do [ -n \"$d\" ] && case \"$1\" in \"$d\"/*) x \"node is in a temp folder ($1)\";; esac; done; [ \"$p\" = \"$h\" ] || case \"$1\" in \"$p\"/*) x \"node is in the session folder ($1)\";; esac; }; case \"${r##*/}\" in node) k \"$r\"; e=$r;; *) k \"$n\"; k \"$r\"; e=$(\"$n\" -p 'require(\"fs\").realpathSync(process.execPath)' 2>/dev/null) || x \"the node shim $n did not name its node\"; k \"$e\";; esac; \"$e\" \"$pr/hooks/run.cjs\" \"$pr/hooks/update-notify.mjs\" --codex --plugin",
10
+ "commandWindows": "$null = 'banker-hooks-rev d1402c48baa8'; function x($m) { Write-Output (ConvertTo-Json -Compress @{ systemMessage = \"banker hooks skipped: $m\" }); exit 0 }; if (-not $env:PLUGIN_ROOT) { x 'PLUGIN_ROOT is not set' }; function G($p) { try { (Get-Item -LiteralPath $p -Force -ErrorAction Stop).FullName } catch { try { [IO.Path]::GetFullPath($p) } catch { $p } } }; $sb = @{}; foreach ($l in @(& (Join-Path ([Environment]::SystemDirectory) 'subst.exe') 2>$null)) { if ($l -match '^([A-Za-z]:)\\\\: => (.+)$') { $sb[$matches[1].ToUpper()] = $matches[2] } }; function L($p) { if (-not $p) { return '' }; $p = G $p; for ($i = 0; $i -lt 32; $i++) { $d2 = if ($p.Length -ge 2) { $p.Substring(0, 2).ToUpper() } else { '' }; if ($sb.Contains($d2)) { $p = G ($sb[$d2].TrimEnd('\\') + $p.Substring(2)); continue }; $c = $p; $t = $null; while ($c) { $it = Get-Item -LiteralPath $c -Force -ErrorAction SilentlyContinue; if ($it -and $it.LinkType -in 'SymbolicLink', 'Junction' -and @($it.Target)[0]) { $t = [string]@($it.Target)[0]; break }; $c = Split-Path -LiteralPath $c }; if (-not $t) { break }; if (-not [IO.Path]::IsPathRooted($t)) { $t = Join-Path (Split-Path -LiteralPath $c) $t }; $p = G ($t.TrimEnd('\\') + $p.Substring($c.Length)) }; $p.TrimEnd('\\') }; $ds = @($env:TEMP, $env:TMP); if ($env:LOCALAPPDATA) { $ds += \"$env:LOCALAPPDATA\\Temp\" }; $w = L $PWD.Path; if ($w -ne (L $env:USERPROFILE)) { $ds += $w }; $ds = @($ds | Where-Object { $_ } | ForEach-Object { L $_ } | Where-Object { $_ }); function u($f) { $f = L $f; foreach ($d in $ds) { if ($f -eq $d -or $f.StartsWith($d + '\\', 'OrdinalIgnoreCase')) { return $true } }; $false }; function k($f) { if (u $f) { x \"node is in a temp folder or the session folder ($(L $f))\" }; L $f }; $pr = L $env:PLUGIN_ROOT; if (u $pr) { x \"the plugin folder is in a temp folder or the session folder ($pr)\" }; $nd = @(@($env:PATH -split ';') | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_.Trim().Trim('\"')) } | Where-Object { $_ -match '^[A-Za-z]:\\\\' -and ([IO.DriveInfo]::new($_.Substring(0, 1))).DriveType -ne 'Network' }); $n = $null; foreach ($d in $nd) { foreach ($e in 'node.exe', 'node.bat', 'node.cmd') { $f = Join-Path $d $e; if (Test-Path -LiteralPath $f -PathType Leaf) { $n = $f; break } }; if ($n) { break } }; if (-not $n) { x 'node is not on PATH' }; $env:PATH = ($nd | Where-Object { -not (u $_) }) -join ';'; $r = k $n; if (-not (Set-Location -LiteralPath $pr -PassThru -ErrorAction SilentlyContinue)) { x 'cannot enter the plugin folder' }; if ($r -notmatch '\\.exe$') { $e = & $r -p process.execPath 2>$null | Select-Object -Last 1; if (-not $e -or $e -notmatch '\\.exe$' -or -not (Test-Path -LiteralPath $e -PathType Leaf)) { x \"the node shim $r did not name its node\" }; $r = k $e }; & $r \"$pr\\hooks\\run.cjs\" \"$pr\\hooks\\update-notify.mjs\" --codex --plugin",
11
11
  "timeout": 5
12
12
  }
13
13
  ]
@@ -18,8 +18,18 @@
18
18
  "hooks": [
19
19
  {
20
20
  "type": "command",
21
- "command": ": banker-hooks-rev 59265eebca3c; x() { printf '%s\\n' \"banker hooks skipped: $1\" >&2; exit 0; }; [ -n \"$PLUGIN_ROOT\" ] || x \"PLUGIN_ROOT is not set\"; p=$(pwd -P); [ -n \"$p\" ] && [ \"$p\" != / ] || x \"the session folder is /\"; u=$(cd /tmp 2>/dev/null && pwd -P); t=$(cd \"${TMPDIR:-/tmp}\" 2>/dev/null && pwd -P); h=; case \"$HOME\" in /?*) h=$(cd \"$HOME\" 2>/dev/null && pwd -P);; esac; w() { for z in \"$u\" \"$t\"; do [ -n \"$z\" ] && case \"$1/\" in \"$z\"/*) return 0;; esac; done; [ \"$p\" = \"$h\" ] && return 1; case \"$1/\" in \"$p\"/*) return 0;; esac; return 1; }; pr=$(cd -P \"$PLUGIN_ROOT\" 2>/dev/null && pwd -P) || x \"cannot enter the plugin folder\"; w \"$pr\" && x \"the plugin folder is in a temp folder or the session folder ($pr)\"; A='function dec(s, o, i, c) { o = \"\"; for (i = 1; i <= length(s); i++) { c = substr(s, i, 1); if (c == \"\\\\\" && substr(s, i + 1, 3) ~ /^[0-7][0-7][0-7]$/) { o = o sprintf(\"%c\", substr(s, i + 1, 1) * 64 + substr(s, i + 2, 1) * 8 + substr(s, i + 3, 1)); i += 3 } else o = o c }; return o } mac { i = index($0, \" on \"); j = 0; for (k = length($0); k > i; k--) if (substr($0, k, 2) == \" (\") { j = k; break }; if (!i || !j) next; m = substr($0, i + 4, j - i - 4); t = substr($0, j + 2); sub(/[,)].*/, \"\", t) } !mac { m = dec($2); t = $3 } (t ~ /^(nfs|nfs4|cifs|smbfs|smb3|afpfs|webdav|davfs|autofs|ceph|glusterfs|lustre|9p)$/ || t ~ /^fuse\\./) && m !~ /:/ { printf \"%s:\", m }'; if [ -r '/proc/self/mounts' ]; then M=$(LC_ALL=C command -p awk \"$A\" '/proc/self/mounts' 2>/dev/null); else M=$('/sbin/mount' 2>/dev/null | LC_ALL=C command -p awk -v mac=1 \"$A\" 2>/dev/null); fi; nm() { for z in $M; do case \"$1/\" in \"$z\"/*) return 0;; esac; done; return 1; }; hm() { for z in \"$h\" \"$HOME\"; do case \"$z\" in /?*) case \"$1/\" in \"$z\"/*) return 0;; esac;; esac; done; return 1; }; set -f; o=$IFS; IFS=:; a=; q=; for v in $PATH; do case \"$v\" in /*) ;; *) a=$a${a:+:}$v; continue;; esac; nm \"$v\" && ! hm \"$v\" && continue; a=$a${a:+:}$v; y=$(cd \"$v\" 2>/dev/null && pwd -P) || continue; w \"$y\" || q=$q${q:+:}$v; done; n=; [ -n \"$a\" ] && n=$(PATH=$a; command -v node) || for d in /opt/homebrew/bin /usr/local/bin; do nm \"$d\" && continue; [ -x \"$d/node\" ] && n=$d/node && break; done; IFS=$o; set +f; [ -n \"$n\" ] || x \"node is not on PATH (a Codex started from the desktop may not have the shell's PATH)\"; case \"$n\" in /*) ;; *) x \"node on PATH is not an absolute path ($n)\";; esac; PATH=${q:-/usr/bin:/bin}; export PATH; cd \"$pr\" || x \"cannot enter the plugin folder\"; rp() { realpath \"$1\" 2>/dev/null || readlink -f \"$1\" 2>/dev/null || { f=$1; i=0; while [ -L \"$f\" ]; do i=$((i+1)); [ \"$i\" -le 40 ] || return 1; l=$(readlink \"$f\") || return 1; case \"$l\" in /*) f=$l;; *) f=${f%/*}/$l;; esac; done; [ -e \"$f\" ] || return 1; d=$(cd -P \"${f%/*}/\" 2>/dev/null && pwd -P) || return 1; printf '%s\\n' \"${d%/}/${f##*/}\"; }; }; r=$(rp \"$n\") || x \"cannot resolve the real path of $n\"; k() { case \"$1\" in /*) ;; *) x \"node path is not absolute ($1)\";; esac; for d in \"$u\" \"$t\"; do [ -n \"$d\" ] && case \"$1\" in \"$d\"/*) x \"node is in a temp folder ($1)\";; esac; done; [ \"$p\" = \"$h\" ] || case \"$1\" in \"$p\"/*) x \"node is in the session folder ($1)\";; esac; }; case \"${r##*/}\" in node) k \"$r\"; e=$r;; *) k \"$n\"; k \"$r\"; e=$(\"$n\" -p 'require(\"fs\").realpathSync(process.execPath)' 2>/dev/null) || x \"the node shim $n did not name its node\"; k \"$e\";; esac; \"$e\" \"$pr/hooks/run.cjs\" \"$pr/hooks/telemetry-count-codex.mjs\"",
22
- "commandWindows": "$null = 'banker-hooks-rev 59265eebca3c'; function x($m) { [Console]::Error.WriteLine(\"banker hooks skipped: $m\"); exit 0 }; if (-not $env:PLUGIN_ROOT) { x 'PLUGIN_ROOT is not set' }; function G($p) { try { (Get-Item -LiteralPath $p -Force -ErrorAction Stop).FullName } catch { try { [IO.Path]::GetFullPath($p) } catch { $p } } }; $sb = @{}; foreach ($l in @(& (Join-Path ([Environment]::SystemDirectory) 'subst.exe') 2>$null)) { if ($l -match '^([A-Za-z]:)\\\\: => (.+)$') { $sb[$matches[1].ToUpper()] = $matches[2] } }; function L($p) { if (-not $p) { return '' }; $p = G $p; for ($i = 0; $i -lt 32; $i++) { $d2 = if ($p.Length -ge 2) { $p.Substring(0, 2).ToUpper() } else { '' }; if ($sb.Contains($d2)) { $p = G ($sb[$d2].TrimEnd('\\') + $p.Substring(2)); continue }; $c = $p; $t = $null; while ($c) { $it = Get-Item -LiteralPath $c -Force -ErrorAction SilentlyContinue; if ($it -and $it.LinkType -in 'SymbolicLink', 'Junction' -and @($it.Target)[0]) { $t = [string]@($it.Target)[0]; break }; $c = Split-Path -LiteralPath $c }; if (-not $t) { break }; if (-not [IO.Path]::IsPathRooted($t)) { $t = Join-Path (Split-Path -LiteralPath $c) $t }; $p = G ($t.TrimEnd('\\') + $p.Substring($c.Length)) }; $p.TrimEnd('\\') }; $ds = @($env:TEMP, $env:TMP); if ($env:LOCALAPPDATA) { $ds += \"$env:LOCALAPPDATA\\Temp\" }; $w = L $PWD.Path; if ($w -ne (L $env:USERPROFILE)) { $ds += $w }; $ds = @($ds | Where-Object { $_ } | ForEach-Object { L $_ } | Where-Object { $_ }); function u($f) { $f = L $f; foreach ($d in $ds) { if ($f -eq $d -or $f.StartsWith($d + '\\', 'OrdinalIgnoreCase')) { return $true } }; $false }; function k($f) { if (u $f) { x \"node is in a temp folder or the session folder ($(L $f))\" }; L $f }; $pr = L $env:PLUGIN_ROOT; if (u $pr) { x \"the plugin folder is in a temp folder or the session folder ($pr)\" }; $nd = @(@($env:PATH -split ';') | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_.Trim().Trim('\"')) } | Where-Object { $_ -match '^[A-Za-z]:\\\\' -and ([IO.DriveInfo]::new($_.Substring(0, 1))).DriveType -ne 'Network' }); $n = $null; foreach ($d in $nd) { foreach ($e in 'node.exe', 'node.bat', 'node.cmd') { $f = Join-Path $d $e; if (Test-Path -LiteralPath $f -PathType Leaf) { $n = $f; break } }; if ($n) { break } }; if (-not $n) { x 'node is not on PATH' }; $env:PATH = ($nd | Where-Object { -not (u $_) }) -join ';'; $r = k $n; if (-not (Set-Location -LiteralPath $pr -PassThru -ErrorAction SilentlyContinue)) { x 'cannot enter the plugin folder' }; if ($r -notmatch '\\.exe$') { $e = & $r -p process.execPath 2>$null | Select-Object -Last 1; if (-not $e -or $e -notmatch '\\.exe$' -or -not (Test-Path -LiteralPath $e -PathType Leaf)) { x \"the node shim $r did not name its node\" }; $r = k $e }; & $r \"$pr\\hooks\\run.cjs\" \"$pr\\hooks\\telemetry-count-codex.mjs\"",
21
+ "command": ": banker-hooks-rev d1402c48baa8; x() { printf '%s\\n' \"banker hooks skipped: $1\" >&2; exit 0; }; [ -n \"$PLUGIN_ROOT\" ] || x \"PLUGIN_ROOT is not set\"; p=$(pwd -P); [ -n \"$p\" ] && [ \"$p\" != / ] || x \"the session folder is /\"; u=$(cd /tmp 2>/dev/null && pwd -P); t=$(cd \"${TMPDIR:-/tmp}\" 2>/dev/null && pwd -P); h=; case \"$HOME\" in /?*) h=$(cd \"$HOME\" 2>/dev/null && pwd -P);; esac; w() { for z in \"$u\" \"$t\"; do [ -n \"$z\" ] && case \"$1/\" in \"$z\"/*) return 0;; esac; done; [ \"$p\" = \"$h\" ] && return 1; case \"$1/\" in \"$p\"/*) return 0;; esac; return 1; }; pr=$(cd -P \"$PLUGIN_ROOT\" 2>/dev/null && pwd -P) || x \"cannot enter the plugin folder\"; w \"$pr\" && x \"the plugin folder is in a temp folder or the session folder ($pr)\"; A='function dec(s, o, i, c) { o = \"\"; for (i = 1; i <= length(s); i++) { c = substr(s, i, 1); if (c == \"\\\\\" && substr(s, i + 1, 3) ~ /^[0-7][0-7][0-7]$/) { o = o sprintf(\"%c\", substr(s, i + 1, 1) * 64 + substr(s, i + 2, 1) * 8 + substr(s, i + 3, 1)); i += 3 } else o = o c }; return o } mac { i = index($0, \" on \"); j = 0; for (k = length($0); k > i; k--) if (substr($0, k, 2) == \" (\") { j = k; break }; if (!i || !j) next; m = substr($0, i + 4, j - i - 4); t = substr($0, j + 2); sub(/[,)].*/, \"\", t) } !mac { m = dec($2); t = $3 } (t ~ /^(nfs|nfs4|cifs|smbfs|smb3|afpfs|webdav|davfs|autofs|ceph|glusterfs|lustre|9p)$/ || t ~ /^fuse\\./) && m !~ /:/ { printf \"%s:\", m }'; if [ -r '/proc/self/mounts' ]; then M=$(LC_ALL=C command -p awk \"$A\" '/proc/self/mounts' 2>/dev/null); else M=$('/sbin/mount' 2>/dev/null | LC_ALL=C command -p awk -v mac=1 \"$A\" 2>/dev/null); fi; nm() { for z in $M; do case \"$1/\" in \"$z\"/*) return 0;; esac; done; return 1; }; hm() { for z in \"$h\" \"$HOME\"; do case \"$z\" in /?*) case \"$1/\" in \"$z\"/*) return 0;; esac;; esac; done; return 1; }; set -f; o=$IFS; IFS=:; a=; q=; for v in $PATH; do case \"$v\" in /*) ;; *) a=$a${a:+:}$v; continue;; esac; nm \"$v\" && ! hm \"$v\" && continue; a=$a${a:+:}$v; y=$(cd \"$v\" 2>/dev/null && pwd -P) || continue; w \"$y\" || q=$q${q:+:}$v; done; n=; [ -n \"$a\" ] && n=$(PATH=$a; command -v node) || for d in /opt/homebrew/bin /usr/local/bin; do nm \"$d\" && continue; [ -x \"$d/node\" ] && n=$d/node && break; done; IFS=$o; set +f; [ -n \"$n\" ] || x \"node is not on PATH (a Codex started from the desktop may not have the shell's PATH)\"; case \"$n\" in /*) ;; *) x \"node on PATH is not an absolute path ($n)\";; esac; PATH=${q:-/usr/bin:/bin}; export PATH; cd \"$pr\" || x \"cannot enter the plugin folder\"; rp() { realpath \"$1\" 2>/dev/null || readlink -f \"$1\" 2>/dev/null || { f=$1; i=0; while [ -L \"$f\" ]; do i=$((i+1)); [ \"$i\" -le 40 ] || return 1; l=$(readlink \"$f\") || return 1; case \"$l\" in /*) f=$l;; *) f=${f%/*}/$l;; esac; done; [ -e \"$f\" ] || return 1; d=$(cd -P \"${f%/*}/\" 2>/dev/null && pwd -P) || return 1; printf '%s\\n' \"${d%/}/${f##*/}\"; }; }; r=$(rp \"$n\") || x \"cannot resolve the real path of $n\"; k() { case \"$1\" in /*) ;; *) x \"node path is not absolute ($1)\";; esac; for d in \"$u\" \"$t\"; do [ -n \"$d\" ] && case \"$1\" in \"$d\"/*) x \"node is in a temp folder ($1)\";; esac; done; [ \"$p\" = \"$h\" ] || case \"$1\" in \"$p\"/*) x \"node is in the session folder ($1)\";; esac; }; case \"${r##*/}\" in node) k \"$r\"; e=$r;; *) k \"$n\"; k \"$r\"; e=$(\"$n\" -p 'require(\"fs\").realpathSync(process.execPath)' 2>/dev/null) || x \"the node shim $n did not name its node\"; k \"$e\";; esac; \"$e\" \"$pr/hooks/run.cjs\" \"$pr/hooks/telemetry-count-codex.mjs\"",
22
+ "commandWindows": "$null = 'banker-hooks-rev d1402c48baa8'; function x($m) { [Console]::Error.WriteLine(\"banker hooks skipped: $m\"); exit 0 }; if (-not $env:PLUGIN_ROOT) { x 'PLUGIN_ROOT is not set' }; function G($p) { try { (Get-Item -LiteralPath $p -Force -ErrorAction Stop).FullName } catch { try { [IO.Path]::GetFullPath($p) } catch { $p } } }; $sb = @{}; foreach ($l in @(& (Join-Path ([Environment]::SystemDirectory) 'subst.exe') 2>$null)) { if ($l -match '^([A-Za-z]:)\\\\: => (.+)$') { $sb[$matches[1].ToUpper()] = $matches[2] } }; function L($p) { if (-not $p) { return '' }; $p = G $p; for ($i = 0; $i -lt 32; $i++) { $d2 = if ($p.Length -ge 2) { $p.Substring(0, 2).ToUpper() } else { '' }; if ($sb.Contains($d2)) { $p = G ($sb[$d2].TrimEnd('\\') + $p.Substring(2)); continue }; $c = $p; $t = $null; while ($c) { $it = Get-Item -LiteralPath $c -Force -ErrorAction SilentlyContinue; if ($it -and $it.LinkType -in 'SymbolicLink', 'Junction' -and @($it.Target)[0]) { $t = [string]@($it.Target)[0]; break }; $c = Split-Path -LiteralPath $c }; if (-not $t) { break }; if (-not [IO.Path]::IsPathRooted($t)) { $t = Join-Path (Split-Path -LiteralPath $c) $t }; $p = G ($t.TrimEnd('\\') + $p.Substring($c.Length)) }; $p.TrimEnd('\\') }; $ds = @($env:TEMP, $env:TMP); if ($env:LOCALAPPDATA) { $ds += \"$env:LOCALAPPDATA\\Temp\" }; $w = L $PWD.Path; if ($w -ne (L $env:USERPROFILE)) { $ds += $w }; $ds = @($ds | Where-Object { $_ } | ForEach-Object { L $_ } | Where-Object { $_ }); function u($f) { $f = L $f; foreach ($d in $ds) { if ($f -eq $d -or $f.StartsWith($d + '\\', 'OrdinalIgnoreCase')) { return $true } }; $false }; function k($f) { if (u $f) { x \"node is in a temp folder or the session folder ($(L $f))\" }; L $f }; $pr = L $env:PLUGIN_ROOT; if (u $pr) { x \"the plugin folder is in a temp folder or the session folder ($pr)\" }; $nd = @(@($env:PATH -split ';') | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_.Trim().Trim('\"')) } | Where-Object { $_ -match '^[A-Za-z]:\\\\' -and ([IO.DriveInfo]::new($_.Substring(0, 1))).DriveType -ne 'Network' }); $n = $null; foreach ($d in $nd) { foreach ($e in 'node.exe', 'node.bat', 'node.cmd') { $f = Join-Path $d $e; if (Test-Path -LiteralPath $f -PathType Leaf) { $n = $f; break } }; if ($n) { break } }; if (-not $n) { x 'node is not on PATH' }; $env:PATH = ($nd | Where-Object { -not (u $_) }) -join ';'; $r = k $n; if (-not (Set-Location -LiteralPath $pr -PassThru -ErrorAction SilentlyContinue)) { x 'cannot enter the plugin folder' }; if ($r -notmatch '\\.exe$') { $e = & $r -p process.execPath 2>$null | Select-Object -Last 1; if (-not $e -or $e -notmatch '\\.exe$' -or -not (Test-Path -LiteralPath $e -PathType Leaf)) { x \"the node shim $r did not name its node\" }; $r = k $e }; & $r \"$pr\\hooks\\run.cjs\" \"$pr\\hooks\\telemetry-count-codex.mjs\"",
23
+ "timeout": 5
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": ": banker-hooks-rev d1402c48baa8; x() { printf '%s\\n' \"banker hooks skipped: $1\" >&2; exit 0; }; [ -n \"$PLUGIN_ROOT\" ] || x \"PLUGIN_ROOT is not set\"; p=$(pwd -P); [ -n \"$p\" ] && [ \"$p\" != / ] || x \"the session folder is /\"; u=$(cd /tmp 2>/dev/null && pwd -P); t=$(cd \"${TMPDIR:-/tmp}\" 2>/dev/null && pwd -P); h=; case \"$HOME\" in /?*) h=$(cd \"$HOME\" 2>/dev/null && pwd -P);; esac; w() { for z in \"$u\" \"$t\"; do [ -n \"$z\" ] && case \"$1/\" in \"$z\"/*) return 0;; esac; done; [ \"$p\" = \"$h\" ] && return 1; case \"$1/\" in \"$p\"/*) return 0;; esac; return 1; }; pr=$(cd -P \"$PLUGIN_ROOT\" 2>/dev/null && pwd -P) || x \"cannot enter the plugin folder\"; w \"$pr\" && x \"the plugin folder is in a temp folder or the session folder ($pr)\"; A='function dec(s, o, i, c) { o = \"\"; for (i = 1; i <= length(s); i++) { c = substr(s, i, 1); if (c == \"\\\\\" && substr(s, i + 1, 3) ~ /^[0-7][0-7][0-7]$/) { o = o sprintf(\"%c\", substr(s, i + 1, 1) * 64 + substr(s, i + 2, 1) * 8 + substr(s, i + 3, 1)); i += 3 } else o = o c }; return o } mac { i = index($0, \" on \"); j = 0; for (k = length($0); k > i; k--) if (substr($0, k, 2) == \" (\") { j = k; break }; if (!i || !j) next; m = substr($0, i + 4, j - i - 4); t = substr($0, j + 2); sub(/[,)].*/, \"\", t) } !mac { m = dec($2); t = $3 } (t ~ /^(nfs|nfs4|cifs|smbfs|smb3|afpfs|webdav|davfs|autofs|ceph|glusterfs|lustre|9p)$/ || t ~ /^fuse\\./) && m !~ /:/ { printf \"%s:\", m }'; if [ -r '/proc/self/mounts' ]; then M=$(LC_ALL=C command -p awk \"$A\" '/proc/self/mounts' 2>/dev/null); else M=$('/sbin/mount' 2>/dev/null | LC_ALL=C command -p awk -v mac=1 \"$A\" 2>/dev/null); fi; nm() { for z in $M; do case \"$1/\" in \"$z\"/*) return 0;; esac; done; return 1; }; hm() { for z in \"$h\" \"$HOME\"; do case \"$z\" in /?*) case \"$1/\" in \"$z\"/*) return 0;; esac;; esac; done; return 1; }; set -f; o=$IFS; IFS=:; a=; q=; for v in $PATH; do case \"$v\" in /*) ;; *) a=$a${a:+:}$v; continue;; esac; nm \"$v\" && ! hm \"$v\" && continue; a=$a${a:+:}$v; y=$(cd \"$v\" 2>/dev/null && pwd -P) || continue; w \"$y\" || q=$q${q:+:}$v; done; n=; [ -n \"$a\" ] && n=$(PATH=$a; command -v node) || for d in /opt/homebrew/bin /usr/local/bin; do nm \"$d\" && continue; [ -x \"$d/node\" ] && n=$d/node && break; done; IFS=$o; set +f; [ -n \"$n\" ] || x \"node is not on PATH (a Codex started from the desktop may not have the shell's PATH)\"; case \"$n\" in /*) ;; *) x \"node on PATH is not an absolute path ($n)\";; esac; PATH=${q:-/usr/bin:/bin}; export PATH; cd \"$pr\" || x \"cannot enter the plugin folder\"; rp() { realpath \"$1\" 2>/dev/null || readlink -f \"$1\" 2>/dev/null || { f=$1; i=0; while [ -L \"$f\" ]; do i=$((i+1)); [ \"$i\" -le 40 ] || return 1; l=$(readlink \"$f\") || return 1; case \"$l\" in /*) f=$l;; *) f=${f%/*}/$l;; esac; done; [ -e \"$f\" ] || return 1; d=$(cd -P \"${f%/*}/\" 2>/dev/null && pwd -P) || return 1; printf '%s\\n' \"${d%/}/${f##*/}\"; }; }; r=$(rp \"$n\") || x \"cannot resolve the real path of $n\"; k() { case \"$1\" in /*) ;; *) x \"node path is not absolute ($1)\";; esac; for d in \"$u\" \"$t\"; do [ -n \"$d\" ] && case \"$1\" in \"$d\"/*) x \"node is in a temp folder ($1)\";; esac; done; [ \"$p\" = \"$h\" ] || case \"$1\" in \"$p\"/*) x \"node is in the session folder ($1)\";; esac; }; case \"${r##*/}\" in node) k \"$r\"; e=$r;; *) k \"$n\"; k \"$r\"; e=$(\"$n\" -p 'require(\"fs\").realpathSync(process.execPath)' 2>/dev/null) || x \"the node shim $n did not name its node\"; k \"$e\";; esac; \"$e\" \"$pr/hooks/run.cjs\" \"$pr/hooks/ralph-continue-codex.mjs\"",
32
+ "commandWindows": "$null = 'banker-hooks-rev d1402c48baa8'; function x($m) { [Console]::Error.WriteLine(\"banker hooks skipped: $m\"); exit 0 }; if (-not $env:PLUGIN_ROOT) { x 'PLUGIN_ROOT is not set' }; function G($p) { try { (Get-Item -LiteralPath $p -Force -ErrorAction Stop).FullName } catch { try { [IO.Path]::GetFullPath($p) } catch { $p } } }; $sb = @{}; foreach ($l in @(& (Join-Path ([Environment]::SystemDirectory) 'subst.exe') 2>$null)) { if ($l -match '^([A-Za-z]:)\\\\: => (.+)$') { $sb[$matches[1].ToUpper()] = $matches[2] } }; function L($p) { if (-not $p) { return '' }; $p = G $p; for ($i = 0; $i -lt 32; $i++) { $d2 = if ($p.Length -ge 2) { $p.Substring(0, 2).ToUpper() } else { '' }; if ($sb.Contains($d2)) { $p = G ($sb[$d2].TrimEnd('\\') + $p.Substring(2)); continue }; $c = $p; $t = $null; while ($c) { $it = Get-Item -LiteralPath $c -Force -ErrorAction SilentlyContinue; if ($it -and $it.LinkType -in 'SymbolicLink', 'Junction' -and @($it.Target)[0]) { $t = [string]@($it.Target)[0]; break }; $c = Split-Path -LiteralPath $c }; if (-not $t) { break }; if (-not [IO.Path]::IsPathRooted($t)) { $t = Join-Path (Split-Path -LiteralPath $c) $t }; $p = G ($t.TrimEnd('\\') + $p.Substring($c.Length)) }; $p.TrimEnd('\\') }; $ds = @($env:TEMP, $env:TMP); if ($env:LOCALAPPDATA) { $ds += \"$env:LOCALAPPDATA\\Temp\" }; $w = L $PWD.Path; if ($w -ne (L $env:USERPROFILE)) { $ds += $w }; $ds = @($ds | Where-Object { $_ } | ForEach-Object { L $_ } | Where-Object { $_ }); function u($f) { $f = L $f; foreach ($d in $ds) { if ($f -eq $d -or $f.StartsWith($d + '\\', 'OrdinalIgnoreCase')) { return $true } }; $false }; function k($f) { if (u $f) { x \"node is in a temp folder or the session folder ($(L $f))\" }; L $f }; $pr = L $env:PLUGIN_ROOT; if (u $pr) { x \"the plugin folder is in a temp folder or the session folder ($pr)\" }; $nd = @(@($env:PATH -split ';') | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_.Trim().Trim('\"')) } | Where-Object { $_ -match '^[A-Za-z]:\\\\' -and ([IO.DriveInfo]::new($_.Substring(0, 1))).DriveType -ne 'Network' }); $n = $null; foreach ($d in $nd) { foreach ($e in 'node.exe', 'node.bat', 'node.cmd') { $f = Join-Path $d $e; if (Test-Path -LiteralPath $f -PathType Leaf) { $n = $f; break } }; if ($n) { break } }; if (-not $n) { x 'node is not on PATH' }; $env:PATH = ($nd | Where-Object { -not (u $_) }) -join ';'; $r = k $n; if (-not (Set-Location -LiteralPath $pr -PassThru -ErrorAction SilentlyContinue)) { x 'cannot enter the plugin folder' }; if ($r -notmatch '\\.exe$') { $e = & $r -p process.execPath 2>$null | Select-Object -Last 1; if (-not $e -or $e -notmatch '\\.exe$' -or -not (Test-Path -LiteralPath $e -PathType Leaf)) { x \"the node shim $r did not name its node\" }; $r = k $e }; & $r \"$pr\\hooks\\run.cjs\" \"$pr\\hooks\\ralph-continue-codex.mjs\"",
23
33
  "timeout": 5
24
34
  }
25
35
  ]
@@ -81,11 +81,15 @@
81
81
  { "name": "build-testbox", "type": "skill", "target": "both", "reason": "a test environment this machine builds from its own resources: a browser matrix (Playwright Chromium and WebKit, desktop and mobile device emulation) against a loopback URL and container OSes (Rocky Linux 8, 9; macOS only as a Tart VM on Apple Silicon) run in parallel, every requested cell kept as a pass, fail, unverified or unsupported row with a reason, results.json and summary.md; detect, run, setup; pure Node, no deps", "supportingFiles": ["scripts/testbox.mjs","scripts/args.mjs","scripts/detect.mjs","scripts/browsers.mjs","scripts/browser-cell.mjs","scripts/containers.mjs","scripts/setup.mjs","scripts/report.mjs","scripts/proc.mjs"], "caveat": "Playwright (a global install, or the project's with --playwright-from-project) and docker or podman are optional; a missing one leaves its cells unverified with the reason; run downloads nothing (a missing image or browser build is unverified, never pulled); setup only lists what it would fetch and its rough size, and fetches after the user confirms and it runs again with --yes (exit 4 and network needed when the sources are unreachable); macOS runs only as a Tart VM on an Apple Silicon host (other hosts are unsupported under the Apple SLA), and the skill never downloads or starts the VM; on Codex detect and run ask for sandbox_permissions require_escalated for the container engine and the headless browsers, the server under test on a 127.0.0.1 port is the user's own (approved where it is started), and setup --yes, or a container command that uses the network, asks for network access (--network host lifts that cell's isolation, and the user is told)" },
82
82
  { "name": "tune-tmux", "type": "skill", "target": "both", "reason": "adds or removes one marked block in the tmux or psmux config file so mouse drag copies to the OS clipboard and PageUp/PageDown scroll inside tmux, without taking keys or the mouse from full-screen apps; status shows the chosen file, the lines and why any are left out, conflicts in other files tmux reads, and per-app behavior (Claude Code, Codex); first original kept, off removes only the block, --reload applies it to a running server; pure Node, no deps, no network", "supportingFiles": ["scripts/tune-tmux.mjs","scripts/tmux-config.mjs","scripts/app-notes.mjs","scripts/marker-block.mjs","scripts/psmux-caps.json"], "caveat": "needs tmux or psmux (install with setup-tmux; status alone works without them); it writes the user's config file outside the workspace (~/.tmux.conf or the XDG tmux.conf, ~/.psmux.conf for psmux), keeping the first original as <conf>.tune-tmux.bak, so on Codex on, off and --reload ask for sandbox_permissions require_escalated (no --add-dir); a config file that is a symbolic link is refused unless --follow-symlink; psmux gets a block of zero lines today (its defaults give mouse, set-clipboard and wheel copy-mode; drag to the Windows clipboard and PageUp/PageDown inside full-screen apps are unverified; outside apps PageUp needs the prefix, PageDown does not leave copy-mode at the bottom and #{alternate_on} is always 0), so on reports the unverified and broken counts with exit 0 and only takes out an old tune-tmux block left in that file; inside full-screen apps (Claude Code fullscreen, Codex Fullscreen) the app keeps PageUp/PageDown and the mouse, and tmux copy-mode cannot scroll Codex history, a limit tmux config cannot fix; it never changes Claude Code or Codex settings" },
83
83
  { "name": "make-evidence", "type": "skill", "target": "both", "reason": "writes six evidence documents for a built service from real runs and commits them under docs/evidence/ (optimize, verify, flow, scale, decisions, trace) with scripts/evidence.mjs: run records each command (quoted command line, HEAD and dirty count, both streams with common secret forms masked, exit code passed through, stdin closed, a time limit that ends the process tree, no overwrite), measured numbers are METRIC name=value lines that check matches exactly against the log a document line names, room and collect give the oracle writer (a fresh-context sub-agent with a fixed prompt) a folder outside the repository holding only the requirement document and the owner facts, worktree and bug put each seeded bug (constraint, objective, permission) into a separate worktree of HEAD and judge it by ORACLE lines against a baseline (caught, missed, undecidable) while the user's work tree is left as it was, precheck stops on staged changes and asks about uncommitted ones and config hosts that are not this machine, commit takes only docs/evidence and tests/oracle (ignored logs included, hooks not skipped); --oracle-only writes the oracle before implementation; never edits the implementation, installs, downloads or pushes", "supportingFiles": ["scripts/evidence.mjs", "scripts/me-git.mjs", "scripts/me-run.mjs", "scripts/me-precheck.mjs", "scripts/me-check.mjs", "scripts/me-oracle.mjs", "scripts/me-commit.mjs"], "caveat": "the project supplies the server, its tests and the tools; a missing tool, a server that cannot be reached or a non-local config is written as unverified, never filled from unit tests; the sub-agent's list of files read is its own report, and context the runtime adds (the session folder's CLAUDE.md or AGENTS.md, memory) is written down as a limit; on Codex worktree, bug, commit, serve, stop and runs that reach a local server or DB ask for sandbox_permissions require_escalated" },
84
+ { "name": "wide-pptx", "type": "skill", "target": "both", "reason": "makes a 16:9 presentation from one deck (JSON) as PPTX and as one self-contained HTML deck with scripts/wide-pptx.mjs: title, section, bullets, two columns, table, chart (bar, line, pie), image, quote and closing slides with speaker notes, the same fixed layout in both (13.333 x 7.5 in, 1280 x 720 px); the PPTX comes from the bundled PptxGenJS 4.0.1 (vendor/pptxgen.bundle.js, MIT, loaded in a node:vm context of its own (a loading context, not a security boundary) because the browser bundle exports JSZip alone under require), then its theme is patched to Latin Noto Sans and Hangul Noto Sans KR and chart text from Arial to the same; the HTML has its own small script (arrows, space, Home/End, click, N for notes), inline SVG charts, data-URI images and 16:9 print pages; local PNG, JPEG and GIF images only (type from the first bytes); check warns where text may overflow its box; existing outputs are kept unless --force; no install, no download", "supportingFiles": ["scripts/wide-pptx.mjs", "scripts/wp-model.mjs", "scripts/wp-pptx.mjs", "scripts/wp-html.mjs", "scripts/wp-svg.mjs", "assets/example.json", "vendor/pptxgen.bundle.js", "vendor/LICENSE-pptxgenjs.md", "vendor/LICENSE-jszip.md", "vendor/LICENSE-pako.md", "vendor/THIRD-PARTY-NOTICES.md", "vendor/README.md"], "caveat": "no font file is embedded, so a viewer without Noto Sans and Noto Sans KR shows other fonts and wraps lines differently; text longer than its box overflows (check warns, the skill splits the slide); A4 portrait print documents belong to vertical-pptx" },
85
+ { "name": "video-gen", "type": "skill", "target": "both", "reason": "shows what videos can be made, lets the user pick, interviews for missing content, then renders an H.264 MP4 with scripts/video-gen.mjs and its bundled engine: Skia CanvasKit 0.42.0 (WASM, BSD-3), h264-mp4-encoder 1.0.12 (WASM; minih264 public domain, libmp4v2 MPL 1.1 with its source kept in third_party/ and named in vendor/README.md) and Noto Sans / Noto Sans KR (OFL), so no Chrome, ffmpeg, native module, install or network on any OS; built-in kinds title, kinetic type, slides (a wide-pptx deck through from-deck), chart (bar, line), counter, lower third, quote, image (zoom, pan) and credits with cut, fade, wipe and stripes transitions; text asks for Noto Sans first so Hangul falls to Noto Sans KR; every frame is a pure function of time (still renders any frame); motion helpers partly from aimotion (kstost/cokacmotion, MIT); HTML motion graphics, AI 3D intros and motion sites hand over to motion-graphic-make, 3d-intro-build and motion-sites; existing outputs are kept unless --force", "supportingFiles": ["scripts/video-gen.mjs", "scripts/vg-model.mjs", "scripts/vg-engine.mjs", "scripts/vg-draw.mjs", "scripts/vg-scenes.mjs", "scripts/vg-render.mjs", "scripts/vg-fx.mjs", "scripts/vg-deck.mjs", "scripts/vg-catalog.mjs", "assets/example.json", "vendor/README.md", "vendor/canvaskit/canvaskit.js", "vendor/canvaskit/canvaskit.wasm", "vendor/canvaskit/LICENSE.md", "vendor/canvaskit/THIRD-PARTY-NOTICES.md", "vendor/h264-mp4-encoder/h264-mp4-encoder.node.js", "vendor/h264-mp4-encoder/LICENSE.md", "vendor/h264-mp4-encoder/MPL-1.1.md", "vendor/fonts/NotoSans-Regular.ttf", "vendor/fonts/NotoSans-Bold.ttf", "vendor/fonts/NotoSansKR-Regular.otf", "vendor/fonts/NotoSansKR-Bold.otf", "vendor/fonts/OFL-noto-sans.md", "vendor/fonts/OFL-noto-sans-kr.md", "vendor/aimotion/LICENSE.md"], "caveat": "about 20 MB of vendored engine and fonts; rendering is on the CPU; no audio; images are local PNG, JPEG, GIF or WebP up to 20 MB" },
86
+ { "name": "wbs-gen", "type": "skill", "target": "both", "reason": "draws a WBS plan (JSON) as one Gantt sheet in XLSX and self-contained HTML with scripts/wbs-gen.mjs, pure Node with no dependency: working-day columns only (weekends and holidays left out), week headers, 8 fixed columns frozen with 3 header rows, bars as plain cell fills (no conditional formatting), part colours, diamonds for milestones and constraints, red gate lines, legend and notes, A3 landscape one page wide with print titles; the XLSX package (shared strings, styles, ZIP with its own CRC-32) is written by the helper; holidays come from Google's public Korean holiday calendar (holidays only, not observances) shown to the user to confirm, or from the user as M/D, M/D when the calendar cannot be reached; existing outputs are kept unless --force", "supportingFiles": ["scripts/wbs-gen.mjs", "scripts/wg-dates.mjs", "scripts/wg-plan.mjs", "scripts/wg-xlsx.mjs", "scripts/wg-zip.mjs", "scripts/wg-html.mjs", "scripts/wg-holidays.mjs", "assets/example.json"], "caveat": "on Codex the holidays command needs the network and asks for sandbox_permissions require_escalated; refused or offline means the user types the holidays; Hangul is set in Noto Sans KR and the rest in Noto Sans (XLSX rich-text runs per script, HTML font order), no font file is embedded, so a viewer without them shows another font; plans span at most five years, and a month/day that falls in two years of the range must be written as YYYY-MM-DD" },
84
87
 
85
88
  { "name": "front-qa", "type": "command", "target": "both", "reason": "ralplan->ralph frontend-QA pipeline; Claude=OMC, Codex=OMX", "supportingFiles": [] },
86
89
  { "name": "setup", "type": "command", "target": "both", "reason": "installer orchestrator; Claude AskUserQuestion multi-select, Codex prompt-based selection", "supportingFiles": [], "codexDescription": "구성요소와 의존성을 여러 개 골라 설치하는 오케스트레이터. 런타임(Node, Python, Java), LSP, tmux, MCP, 샌드박스, OMX, Playwright 등. 권한 확인 끄기는 따로 묻고 기본으로 고르지 않으며, 골라도 실행하지 않고 직접 입력할 명령만 안내." },
87
90
  { "name": "graceful-pause", "type": "skill", "target": "codex-only", "reason": "Codex counterpart of the Claude Code /graceful-pause mod: sent mid-turn, Codex holds it until the running tool call ends and puts it into the same turn, so the session stops after the current step", "supportingFiles": [], "caveat": "source in codex/skills/graceful-pause (the Claude plugin keeps its immediate /graceful-pause mod); relies on Codex steering a message typed while a turn runs (Codex 0.144.5: delivered after the next tool call)" },
88
91
  { "name": "progress", "type": "skill", "target": "codex-only", "reason": "Codex counterpart of the Claude Code /progress mod: reads this session's rollout (found by CODEX_THREAD_ID) and prints the last plan file the session wrote or read as a tree (the same progress-plan.mjs engine, a byte-identical copy kept by scripts/sync-adapter.js), the last update_plan steps (or the requests) and the recent tool calls under the step in progress", "supportingFiles": ["scripts/progress.mjs", "scripts/progress-plan.mjs"], "caveat": "source in codex/skills/progress; not a live panel, it shows the state when called; --pane opens a tmux side pane that redraws every 2 s and --pane off closes it; inside the workspace-write sandbox tmux may be unreachable, so that one command needs approval to run outside it (or the user runs it with !); the rollout is read from its last 32 MB" },
92
+ { "name": "ralph", "type": "skill", "target": "codex-only", "reason": "the Codex ralph: a build loop that ends only when runner-run gate records, a fresh reviewer's APPROVE and the slop pass all match the work tree as it is now (ralph-state.mjs check); replaces OMX $ralph, which OMX 0.21 removed (a sunset stub pointing at $ultragoal), and needs no OMX; like the OMC ralph in Claude Code, banker's Codex Stop hook (hooks/ralph-continue-codex.mjs, ralph-state.mjs continueFor) keeps the session that started a run going until done or stop (five stops with no change let it go; opt-out BANKER_NO_RALPH_CONTINUE); all-in-one, ultra-init, ultra-chain, visual-ralph and front-qa call it on Codex", "supportingFiles": ["scripts/ralph-state.mjs", "scripts/gate-evidence.mjs", "scripts/run-folder.mjs"], "caveat": "source in codex/skills/ralph; gate-evidence.mjs and run-folder.mjs are byte-identical copies of ralph-qa's (scripts/sync-adapter.js); the record lives in the gate folder outside the repo, so under the workspace-write sandbox its calls may ask for approval; the repo must be a git work tree" },
89
93
  { "name": "teamwork", "type": "skill", "target": "codex-only", "reason": "peer work between the Codex CLI sessions of one machine, turned on and off for the whole Codex home: SessionStart and UserPromptSubmit hooks register each session and add the peer rules, and requests and results travel by Codex's own codex queue through one wrapper an approval rule lets run outside the sandbox", "supportingFiles": ["scripts/teamwork.mjs"], "caveat": "source in codex/skills/teamwork; Codex 0.149.0 or later (codex queue); after the first on, trust the hooks once in the next new session (Hooks need review); sessions open before the install must be reopened; before the hooks are trusted codex exec may stall" }
90
94
  ]
91
95
  }