@kaydash9999/banker-plugins 0.21.0 → 0.22.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +28 -0
- package/PRIVACY.md +7 -3
- package/README.md +41 -4
- package/bin/banker.js +38 -12
- package/bin/lib/codex-hooks.mjs +7 -2
- package/bin/lib/codex-routes.mjs +112 -0
- package/bin/lib/command-skill.cjs +19 -0
- package/bin/lib/telemetry-config.mjs +2 -1
- package/codex/command-skills/front-qa/SKILL.md +56 -0
- package/codex/command-skills/setup/SKILL.md +87 -0
- package/codex/hooks.json +29 -0
- package/codex/skills/graceful-pause/SKILL.md +2 -2
- package/commands/setup.md +2 -2
- package/hooks/telemetry-count-codex.mjs +112 -25
- package/hooks/update-notify.mjs +82 -11
- package/package.json +1 -1
- package/skills/3d-intro-build/references/verify-intro.mjs +15 -1
- package/skills/anti-drm/SKILL.md +1 -1
- package/skills/anti-drm/scripts/instruction-toggle.mjs +57 -4
- package/skills/arch-diagram/build_pptx_template.py +47 -28
- package/skills/audit-web-page/SKILL.md +1 -1
- package/skills/audit-web-page/harness.py +0 -1
- package/skills/lineage/lineage.py +1435 -533
- package/skills/motion-graphic-make/SKILL.md +1 -1
- package/skills/obsidizer/SKILL.md +20 -5
- package/skills/payload-mon/SKILL.md +1 -1
- package/skills/play-qa/SKILL.md +1 -1
- package/skills/ralph-qa/SKILL.md +1 -1
- package/skills/remains/SKILL.md +1 -1
- package/skills/rfp-author/scripts/md2docx.py +63 -34
- package/skills/setup-bypass-permissions/SKILL.md +2 -2
- package/skills/setup-bypass-permissions/scripts/fallback/bypass-permissions.py +66 -15
- package/skills/smart-compact/SKILL.md +1 -1
- package/skills/smart-compact/scripts/codex-gate.mjs +2 -1
- package/skills/theme-selection/SKILL.md +1 -1
- package/skills/tone-compact/SKILL.md +1 -1
- package/skills/tone-compact/scripts/instruction-toggle.mjs +57 -4
- package/skills/ultra-chain/SKILL.md +2 -2
- package/skills/ultra-ui-qa/SKILL.md +1 -1
- package/skills/update-banker/SKILL.md +31 -6
- package/skills/vertical-pptx/SKILL.md +1 -1
- package/skills/vertical-pptx/build_a4p.js +20 -2
- package/skills/vertical-pptx/build_a4p.py +560 -359
|
@@ -14,7 +14,7 @@ description: "10초 안팎의 내레이션 없는 모션 그래픽(키네틱 타
|
|
|
14
14
|
이 스킬은 hyperframes CLI 가 설치, 정상 동작해야 한다.
|
|
15
15
|
**진행 전 가용성을 먼저 확인**하고, 문제가 있으면 **설치부터 안내**한다(추정, 임의 재설치 금지):
|
|
16
16
|
- 확인: `npx hyperframes doctor`(오프라인에서는 전역 설치한 `hyperframes doctor`. 깔린 것이 없으면 `npx` 가 패키지를 받으려 해 네트워크가 없으면 약 70초 뒤 실패한다)
|
|
17
|
-
- 실패/미설치면 먼저 `motion-graphic-setup` 스킬로 설치(Claude Code: `/banker:setup` 에서 motion-graphic-setup / Codex: `banker-motion-graphic-setup`). 설치, 검증 후 이 스킬을 이어서 진행한다.
|
|
17
|
+
- 실패/미설치면 먼저 `motion-graphic-setup` 스킬로 설치(Claude Code: `/banker:setup` 에서 motion-graphic-setup / Codex: `banker-motion-graphic-setup`, 플러그인 설치면 `banker:motion-graphic-setup`). 설치, 검증 후 이 스킬을 이어서 진행한다.
|
|
18
18
|
- 독립 검증에서 배치한 페이지를 잴 때는 Node 용 playwright 패키지와 Chromium 이 필요하다(`npm i -g playwright && npx playwright install chromium`).
|
|
19
19
|
|
|
20
20
|
## hyperframes 란 (요약)
|
|
@@ -82,21 +82,36 @@ banker의 **첫 플러그인 선언 hook**(`hooks/hooks.json`)이 OMC 위키 쓰
|
|
|
82
82
|
없음). 플래그가 없으면 hook은 즉시 exit 0. 방금 banker를 설치/갱신한 세션이면 완료 보고에
|
|
83
83
|
**`/reload-plugins`** 실행을 안내한다(hot-reload 경로).
|
|
84
84
|
|
|
85
|
-
Codex 에서는 플래그 자리가 `omx_wiki/.obsidizer` 이고, hook 은
|
|
85
|
+
Codex 에서는 플래그 자리가 `omx_wiki/.obsidizer` 이고, hook 은 `--enable` 할 때만 Codex 사용자 범위에 따로 설치한다.
|
|
86
|
+
hook 이 셸 호출마다 node 를 띄우므로 쓰지 않는 사람에게는 넣지 않는다. 설치 길과 관계없이 같다.
|
|
87
|
+
아래 `<banker>` 는 `"<node>" "<banker.js>"` 다. 샌드박스 밖에서 도는 명령이므로 PATH 의 `banker` 를 이름으로 부르지 않고 두 절대경로로 쓴다.
|
|
88
|
+
- `<banker.js>`: Codex 플러그인 설치(`codex plugin add`, 이 SKILL.md 가 `plugins/cache/` 아래)면 `<이 SKILL.md 가 있는 폴더>/../../bin/banker.js`
|
|
89
|
+
(플러그인 폴더의 같은 프로그램). npm 설치면 PATH 의 `banker` 가 가리키는 파일의 실제 경로
|
|
90
|
+
- macOS, Linux: `node -p "require('fs').realpathSync(process.argv[1])" "$(command -v banker)"`(npm 이 만든 링크가 `bin/banker.js` 로 풀림)
|
|
91
|
+
- Windows: `Join-Path (Split-Path (Get-Command banker.cmd).Source) 'node_modules\@kaydash9999\banker-plugins\bin\banker.js'`
|
|
92
|
+
- 그 파일이 없거나 이름이 `banker.js` 가 아니면 샌드박스 밖 실행을 요청하지 않고 그 이유를 보고한 뒤 멈춘다
|
|
93
|
+
- `<node>`: 샌드박스 안에서 `node -p "require('fs').realpathSync(process.execPath)"` 로 얻은 실제 경로
|
|
94
|
+
- 두 경로 중 하나라도 임시 폴더나 작업 폴더 안이면(샌드박스가 바꿀 수 있는 곳) 샌드박스 밖 실행을 요청하지 않고 그 이유를 보고한 뒤 멈춘다. 작업 폴더가 홈 폴더
|
|
95
|
+
자체이면 작업 폴더 규칙은 적용하지 않는다(nvm 처럼 홈 아래 둔 node)
|
|
96
|
+
- Windows 의 PowerShell 에서는 앞에 호출 연산자를 붙여 `& "<node>" "<banker.js>" hooks enable obsidize` 처럼 쓴다(없으면 구문 오류)
|
|
97
|
+
이 hook 은 그때의 사본(`~/.codex/hooks-custom/banker/`)으로 돌아, 플러그인이 새 판으로 올라가도 바뀌지 않는다. `hooks status obsidize` 가
|
|
98
|
+
`stale` 이면 `--enable` 을 다시 실행해 새 판으로 바꾼다.
|
|
86
99
|
|
|
87
100
|
- `--enable`
|
|
88
101
|
1. 작업 폴더에 `omx_wiki/` 가 없으면 만들고 `omx_wiki/.obsidizer` 를 쓴다. `omx_wiki/` 는 저장소에
|
|
89
102
|
넣는 폴더라 이 플래그도 커밋 대상이 될 수 있음을 보고에 적는다.
|
|
90
|
-
2.
|
|
91
|
-
3.
|
|
103
|
+
2. `<banker> hooks status obsidize` 로 hook 상태를 본다. `on` 이면 3을 건너뛴다.
|
|
104
|
+
3. `<banker> hooks enable obsidize` 를 실행한다. 이 명령은 `~/.codex/hooks.json` 과
|
|
92
105
|
`~/.codex/hooks-custom/banker/` 에 쓰므로 `workspace-write` 샌드박스 밖에서 돌아야 한다. 그 한 명령만
|
|
93
106
|
샌드박스 밖 실행 승인을 요청한다. `--add-dir ~/.codex` 는 권하지 않는다(세션이 훅과 신뢰 기록을 고칠 수
|
|
94
107
|
있게 됨). 승인을 요청할 수 없으면(`codex exec`, 승인 정책 `never`) 실행하지 않고, 사용자가 입력창에
|
|
95
|
-
`! banker hooks enable obsidize` 로 직접 실행하도록 안내한 뒤 멈춘다.
|
|
108
|
+
`! <banker> hooks enable obsidize`(실제 명령으로 바꿔 적음)로 직접 실행하도록 안내한 뒤 멈춘다.
|
|
96
109
|
4. Codex 를 다시 열 때 뜨는 Hooks need review 창이나 `/hooks` 에서 banker obsidize hook 을 신뢰해야
|
|
97
110
|
돈다고 보고한다. 신뢰 전에는 플래그가 있어도 정규화되지 않는다.
|
|
98
111
|
- `--disable`: `omx_wiki/.obsidizer` 를 지운다. hook 은 다른 프로젝트의 플래그도 보므로 남겨 두고, 어느
|
|
99
|
-
프로젝트에서도 쓰지 않으면
|
|
112
|
+
프로젝트에서도 쓰지 않으면 `<banker> hooks disable obsidize` 로 뺄 수 있다고 안내한다(같은 승인 규칙).
|
|
113
|
+
이 hook 은 `codex plugin remove` 로 지워지지 않는다(사용자 범위에 따로 있음). 플러그인을 지우기 전에 `<banker> hooks disable obsidize`
|
|
114
|
+
를 실행하라고 함께 안내한다.
|
|
100
115
|
|
|
101
116
|
## 변환 규칙
|
|
102
117
|
|
|
@@ -16,7 +16,7 @@ Claude Code 는 요청 본문이 32MB 를 넘으면 "Request too large (max 32MB
|
|
|
16
16
|
## 실행
|
|
17
17
|
|
|
18
18
|
이 SKILL.md 가 있는 디렉터리의 절대경로로 스크립트를 실행하고, 출력 내용을 짧게 전한다.
|
|
19
|
-
Claude Code 는 그 경로를 스킬 맨 위 "Base directory for this skill:" 로 보여 주고, Codex 는 `~/.codex/skills/banker-payload-mon/`
|
|
19
|
+
Claude Code 는 그 경로를 스킬 맨 위 "Base directory for this skill:" 로 보여 주고, Codex 는 스킬 목록의 이 SKILL.md 경로(file)로 보여 준다. npm 설치는 `~/.codex/skills/banker-payload-mon/`(프로젝트 범위 설치면 `./.codex/skills/banker-payload-mon/`), 플러그인 설치(`codex plugin add`)는 `~/.codex/plugins/cache/<마켓플레이스>/banker/<판>/skills/payload-mon/` 이다.
|
|
20
20
|
`~` 는 따옴표 안에서 펼쳐지지 않으니 절대경로를 쓴다.
|
|
21
21
|
|
|
22
22
|
```bash
|
package/skills/play-qa/SKILL.md
CHANGED
|
@@ -10,7 +10,7 @@ description: "Godot 로 만든 HTML5 웹 게임을 실제 브라우저에서 Pla
|
|
|
10
10
|
## 0. 전제조건: playwright (없으면 설치부터)
|
|
11
11
|
이 스킬은 playwright 브라우저가 있어야 동작한다. **진행 전 설치 여부를 먼저 확인**하고, 없으면 **설치부터 안내**한다(추정·임의 재설치 금지):
|
|
12
12
|
- 확인: `ls "${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}" 2>/dev/null | grep -qi chromium && echo ok`
|
|
13
|
-
- 미설치면 먼저 `setup-playwright` 스킬로 설치(Claude Code: `/banker:setup` → setup-playwright / Codex: `banker-setup-playwright`). 설치·검증 후 이 스킬을 이어서 진행한다.
|
|
13
|
+
- 미설치면 먼저 `setup-playwright` 스킬로 설치(Claude Code: `/banker:setup` → setup-playwright / Codex: `banker-setup-playwright`, 플러그인 설치면 `banker:setup-playwright`). 설치·검증 후 이 스킬을 이어서 진행한다.
|
|
14
14
|
|
|
15
15
|
## 언제
|
|
16
16
|
- 오너가 "네가 직접 플레이/qa해봐", "게임 테스트해봐" 류로 위임.
|
package/skills/ralph-qa/SKILL.md
CHANGED
|
@@ -112,7 +112,7 @@ node "<이 스킬 디렉터리 절대경로>/references/verifier-probe.mjs" --ru
|
|
|
112
112
|
외부 좌석 CLI 는 에이전트다. 읽기 도구로 읽은 파일도 그 프로바이더로 간다. 그래서 좌석마다 페이로드 사본 하나만 든 폴더에서 띄우고, 답은 좌석 폴더 밖의 다른 폴더로 받는다.
|
|
113
113
|
|
|
114
114
|
폴더, 페이로드, 비밀 검사, 좌석 명령, 시간 상한, 정리는 실행기 `references/seat-run.mjs` 가 한다. **좌석 명령과 git 사슬을 손으로 짜지 않는다.** 실행기가 멈추면 출력 JSON 의 `stop` 을 아래 멈춤 표대로 처리한다.
|
|
115
|
-
`<R>` 은 `<이 스킬 디렉터리 절대경로>/references` 다(Codex 설치본은 `~/.codex/skills/banker-ralph-qa/references`, `CODEX_HOME` 이 있으면 그
|
|
115
|
+
`<R>` 은 `<이 스킬 디렉터리 절대경로>/references` 다(Codex 의 npm 설치본은 `~/.codex/skills/banker-ralph-qa/references`, 플러그인 설치본은 `~/.codex/plugins/cache/<마켓플레이스>/banker/<판>/skills/ralph-qa/references`. `CODEX_HOME` 이 있으면 `~/.codex` 대신 그 폴더). 호출 사이에 셸 변수가 남지 않으므로 호출마다 실제 경로를 적는다.
|
|
116
116
|
명령마다 stdout 에 JSON 하나를 내고, 같은 JSON 을 `<d>/result-<명령>.md` 에도 쓴다(멈춘 send 는 `result-send-stop.md`). 종료 코드는 0 끝남, 1 비밀처럼 보이는 줄, 2 인자나 실행기 오류, 3 멈춤, 4 512 KiB 초과, 5 send 가 아직 돎이다.
|
|
117
117
|
반복마다 새 폴더를 만든다.
|
|
118
118
|
|
package/skills/remains/SKILL.md
CHANGED
|
@@ -41,7 +41,7 @@ argument-hint: "[기준 커밋] [--no-boxes]"
|
|
|
41
41
|
|
|
42
42
|
## 4. 테스트박스 (등록돼 있으면)
|
|
43
43
|
|
|
44
|
-
박스 작업은 이 스킬 폴더의 `scripts/boxes.mjs` 가 한다(Codex
|
|
44
|
+
박스 작업은 이 스킬 폴더의 `scripts/boxes.mjs` 가 한다(Codex 의 npm 설치는 `~/.codex/skills/banker-remains/scripts/boxes.mjs`, 플러그인 설치는 `~/.codex/plugins/cache/<마켓플레이스>/banker/<판>/skills/remains/scripts/boxes.mjs`). Node 만 쓰고, 이 머신에 `ssh`, `scp`, `git`, 박스에 `tar` 가 있어야 한다. `--no-boxes` 를 받으면 이 단계를 건너뛰고 확인 범위에 그렇게 적는다.
|
|
45
45
|
|
|
46
46
|
1. `node <스킬 폴더>/scripts/boxes.mjs list` 로 등록을 본다. 등록 파일이 없으면 확인 범위에 "테스트박스 미등록" 이라 적고 넘어간다.
|
|
47
47
|
2. `node <스킬 폴더>/scripts/boxes.mjs probe` 로 접속을 확인한다.
|
|
@@ -16,30 +16,42 @@ if len(sys.argv) < 3:
|
|
|
16
16
|
sys.exit(2)
|
|
17
17
|
SRC = sys.argv[1]
|
|
18
18
|
OUT = sys.argv[2]
|
|
19
|
-
FONT =
|
|
19
|
+
FONT = (
|
|
20
|
+
sys.argv[3] if len(sys.argv) > 3 else "Malgun Gothic"
|
|
21
|
+
) # 한글 폰트(서버에 설치된 것으로 교체 가능)
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
def set_korean(style):
|
|
22
25
|
style.font.name = FONT
|
|
23
26
|
style.font.size = Pt(10)
|
|
24
27
|
rpr = style.element.get_or_add_rPr()
|
|
25
|
-
rfonts = rpr.find(qn(
|
|
28
|
+
rfonts = rpr.find(qn("w:rFonts"))
|
|
26
29
|
if rfonts is None:
|
|
27
|
-
rfonts = OxmlElement(
|
|
28
|
-
|
|
30
|
+
rfonts = OxmlElement("w:rFonts")
|
|
31
|
+
rpr.append(rfonts)
|
|
32
|
+
for a in (
|
|
33
|
+
"w:ascii",
|
|
34
|
+
"w:hAnsi",
|
|
35
|
+
"w:eastAsia",
|
|
36
|
+
"w:cs",
|
|
37
|
+
): # eastAsia 필수(누락 시 한글 깨짐)
|
|
29
38
|
rfonts.set(qn(a), FONT)
|
|
30
39
|
|
|
40
|
+
|
|
31
41
|
def add_runs(p, text):
|
|
32
|
-
for part in re.split(r
|
|
42
|
+
for part in re.split(r"(\*\*[^*]+\*\*)", text):
|
|
33
43
|
if not part:
|
|
34
44
|
continue
|
|
35
|
-
if part.startswith(
|
|
36
|
-
p.add_run(part[2:-2].replace(
|
|
45
|
+
if part.startswith("**") and part.endswith("**"):
|
|
46
|
+
p.add_run(part[2:-2].replace("`", "")).bold = True
|
|
37
47
|
else:
|
|
38
|
-
p.add_run(part.replace(
|
|
48
|
+
p.add_run(part.replace("`", ""))
|
|
49
|
+
|
|
39
50
|
|
|
40
51
|
def is_sep(cells):
|
|
41
52
|
nonempty = [c.strip() for c in cells if c.strip()]
|
|
42
|
-
return bool(nonempty) and all(re.fullmatch(r
|
|
53
|
+
return bool(nonempty) and all(re.fullmatch(r":?-{1,}:?", c) for c in nonempty)
|
|
54
|
+
|
|
43
55
|
|
|
44
56
|
def set_table_widths(t):
|
|
45
57
|
# 균등 너비 금지: 맨 왼쪽 컬럼은 내용에 맞춰 최소폭(개행 없이), 나머지 컬럼이 잔여 폭 분배
|
|
@@ -60,66 +72,77 @@ def set_table_widths(t):
|
|
|
60
72
|
t.autofit = False
|
|
61
73
|
t.allow_autofit = False
|
|
62
74
|
tblPr = t._tbl.tblPr
|
|
63
|
-
lay = tblPr.find(qn(
|
|
75
|
+
lay = tblPr.find(qn("w:tblLayout"))
|
|
64
76
|
if lay is None:
|
|
65
|
-
lay = OxmlElement(
|
|
66
|
-
|
|
77
|
+
lay = OxmlElement("w:tblLayout")
|
|
78
|
+
tblPr.append(lay)
|
|
79
|
+
lay.set(qn("w:type"), "fixed") # 고정 레이아웃(지정 폭 준수)
|
|
67
80
|
widths = [col0] + [rest] * (ncol - 1)
|
|
68
|
-
grid = t._tbl.find(qn(
|
|
81
|
+
grid = t._tbl.find(qn("w:tblGrid"))
|
|
69
82
|
if grid is not None:
|
|
70
|
-
for i, gc in enumerate(grid.findall(qn(
|
|
83
|
+
for i, gc in enumerate(grid.findall(qn("w:gridCol"))):
|
|
71
84
|
if i < len(widths):
|
|
72
|
-
gc.set(
|
|
85
|
+
gc.set(
|
|
86
|
+
qn("w:w"), str(int(widths[i] * 1440))
|
|
87
|
+
) # inch→twips, 고정레이아웃 실폭(★ 이게 핵심)
|
|
73
88
|
for i, col in enumerate(t.columns):
|
|
74
89
|
for cell in col.cells:
|
|
75
90
|
cell.width = Inches(widths[i])
|
|
76
91
|
|
|
92
|
+
|
|
77
93
|
doc = Document()
|
|
78
|
-
set_korean(doc.styles[
|
|
94
|
+
set_korean(doc.styles["Normal"])
|
|
79
95
|
|
|
80
|
-
lines = open(SRC, encoding=
|
|
96
|
+
lines = open(SRC, encoding="utf-8").read().split("\n")
|
|
81
97
|
i, n = 0, len(lines)
|
|
82
98
|
while i < n:
|
|
83
99
|
s = lines[i].strip()
|
|
84
100
|
if not s:
|
|
85
|
-
i += 1
|
|
86
|
-
|
|
101
|
+
i += 1
|
|
102
|
+
continue
|
|
103
|
+
if s.startswith("|"):
|
|
87
104
|
block = []
|
|
88
|
-
while i < n and lines[i].strip().startswith(
|
|
89
|
-
block.append(lines[i].strip())
|
|
105
|
+
while i < n and lines[i].strip().startswith("|"):
|
|
106
|
+
block.append(lines[i].strip())
|
|
107
|
+
i += 1
|
|
108
|
+
|
|
90
109
|
def split_row(tl):
|
|
91
|
-
return [c.strip() for c in tl.strip(
|
|
110
|
+
return [c.strip() for c in tl.strip("|").split("|")]
|
|
111
|
+
|
|
92
112
|
rows = [split_row(tl) for tl in block]
|
|
93
113
|
rows = [r for r in rows if not is_sep(r)]
|
|
94
114
|
if rows:
|
|
95
115
|
ncol = max(len(r) for r in rows)
|
|
96
|
-
t = doc.add_table(rows=0, cols=ncol)
|
|
116
|
+
t = doc.add_table(rows=0, cols=ncol)
|
|
117
|
+
t.style = "Table Grid"
|
|
97
118
|
for ri, r in enumerate(rows):
|
|
98
119
|
rc = t.add_row().cells
|
|
99
120
|
for ci in range(ncol):
|
|
100
121
|
para = rc[ci].paragraphs[0]
|
|
101
|
-
add_runs(para, r[ci] if ci < len(r) else
|
|
122
|
+
add_runs(para, r[ci] if ci < len(r) else "")
|
|
102
123
|
if ri == 0:
|
|
103
124
|
for run in para.runs:
|
|
104
125
|
run.bold = True
|
|
105
126
|
set_table_widths(t)
|
|
106
127
|
doc.add_paragraph() # spacer after table (표와 다음 단락 겹침 방지)
|
|
107
128
|
continue
|
|
108
|
-
if s.startswith(
|
|
129
|
+
if s.startswith("### "):
|
|
109
130
|
doc.add_heading(s[4:], level=2)
|
|
110
|
-
elif s.startswith(
|
|
131
|
+
elif s.startswith("## "):
|
|
111
132
|
doc.add_heading(s[3:], level=1)
|
|
112
|
-
elif s.startswith(
|
|
113
|
-
h0 = doc.add_heading(
|
|
133
|
+
elif s.startswith("# "):
|
|
134
|
+
h0 = doc.add_heading("", level=0)
|
|
135
|
+
add_runs(h0, s[2:])
|
|
114
136
|
for r in h0.runs:
|
|
115
137
|
r.font.size = Pt(20)
|
|
116
|
-
elif s.startswith(
|
|
117
|
-
p = doc.add_paragraph()
|
|
138
|
+
elif s.startswith("> "):
|
|
139
|
+
p = doc.add_paragraph()
|
|
140
|
+
add_runs(p, s[2:])
|
|
118
141
|
for run in p.runs:
|
|
119
142
|
run.italic = True
|
|
120
143
|
run.font.size = Pt(8)
|
|
121
|
-
elif s.startswith(
|
|
122
|
-
add_runs(doc.add_paragraph(style=
|
|
144
|
+
elif s.startswith("- "):
|
|
145
|
+
add_runs(doc.add_paragraph(style="List Bullet"), s[2:])
|
|
123
146
|
else:
|
|
124
147
|
add_runs(doc.add_paragraph(), s)
|
|
125
148
|
i += 1
|
|
@@ -128,9 +151,15 @@ while i < n:
|
|
|
128
151
|
fp = doc.sections[0].footer.paragraphs[0]
|
|
129
152
|
fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
130
153
|
run = fp.add_run()
|
|
131
|
-
for tag, kw in (
|
|
154
|
+
for tag, kw in (
|
|
155
|
+
("w:fldChar", {"w:fldCharType": "begin"}),
|
|
156
|
+
(None, None),
|
|
157
|
+
("w:fldChar", {"w:fldCharType": "end"}),
|
|
158
|
+
):
|
|
132
159
|
if tag is None:
|
|
133
|
-
el = OxmlElement(
|
|
160
|
+
el = OxmlElement("w:instrText")
|
|
161
|
+
el.set(qn("xml:space"), "preserve")
|
|
162
|
+
el.text = "PAGE"
|
|
134
163
|
else:
|
|
135
164
|
el = OxmlElement(tag)
|
|
136
165
|
for k, v in kw.items():
|
|
@@ -28,7 +28,7 @@ disable-model-invocation: true
|
|
|
28
28
|
(대상 codex) 확인만 줄이고 샌드박스는 남기려면 이 스킬 대신 `approval_policy = "never"` 만 두는 방법이 있습니다. 그때 작업 폴더 밖 쓰기와 네트워크는 실패합니다.
|
|
29
29
|
대상이 `claude` 면 Codex 에서 실행해도 바뀌는 것은 같은 머신의 Claude Code 설정입니다.
|
|
30
30
|
대상이 `codex` 면 이 계정의 모든 Codex 세션이 명령을 묻지 않고 샌드박스 없이 실행합니다(`codex --dangerously-bypass-approvals-and-sandbox` 와 같음). `codex exec` 같은 비대화형 실행도 같습니다. 샌드박스가 꺼져 작업 폴더 밖 파일과 네트워크에도 제한이 없습니다.
|
|
31
|
-
되돌리려면 `/banker:setup-bypass-permissions off` 를 실행하십시오. Codex 대상은 `$banker-setup-bypass-permissions off codex` 입니다.
|
|
31
|
+
되돌리려면 `/banker:setup-bypass-permissions off` 를 실행하십시오. Codex 대상은 `$banker-setup-bypass-permissions off codex`(플러그인 설치면 `$banker:setup-bypass-permissions off codex`) 입니다.
|
|
32
32
|
|
|
33
33
|
## 대상
|
|
34
34
|
|
|
@@ -52,7 +52,7 @@ disable-model-invocation: true
|
|
|
52
52
|
## 절차
|
|
53
53
|
|
|
54
54
|
`<스킬 폴더>` 는 이 SKILL.md 가 있는 폴더의 절대경로다.
|
|
55
|
-
Codex 에서는 설치된 `banker-setup-bypass-permissions`
|
|
55
|
+
Codex 에서는 설치된 `banker-setup-bypass-permissions` 폴더다(플러그인 설치면 스킬 목록의 이 SKILL.md 경로가 든 폴더).
|
|
56
56
|
|
|
57
57
|
1. 확인 (`on` 일 때만)
|
|
58
58
|
- 위 경고 전부를 일반 출력으로 먼저 보여 준다.
|
|
@@ -21,6 +21,7 @@ Exit status: 0 done, or nothing to do. 1 refused. 2 unexpected error. 3 this acc
|
|
|
21
21
|
the file or its folder. Every result ends with the line `settings file: <path>` on stdout.
|
|
22
22
|
Python 3.6 or later.
|
|
23
23
|
"""
|
|
24
|
+
|
|
24
25
|
import errno
|
|
25
26
|
import json
|
|
26
27
|
import os
|
|
@@ -43,7 +44,9 @@ def settings_path():
|
|
|
43
44
|
|
|
44
45
|
def policy_home():
|
|
45
46
|
# The override exists for this skill's tests alone.
|
|
46
|
-
if os.environ.get("BANKER_BYPASS_TEST") == "1" and os.environ.get(
|
|
47
|
+
if os.environ.get("BANKER_BYPASS_TEST") == "1" and os.environ.get(
|
|
48
|
+
"BANKER_BYPASS_POLICY_HOME"
|
|
49
|
+
):
|
|
47
50
|
return os.environ["BANKER_BYPASS_POLICY_HOME"]
|
|
48
51
|
return POLICY_HOMES.get(sys.platform, "/etc/claude-code")
|
|
49
52
|
|
|
@@ -59,7 +62,11 @@ ABSENT = (errno.ENOENT, errno.ENOTDIR, errno.EISDIR)
|
|
|
59
62
|
|
|
60
63
|
|
|
61
64
|
def unreadable_policy(path):
|
|
62
|
-
return Refused(
|
|
65
|
+
return Refused(
|
|
66
|
+
"this machine's managed policy cannot be read ("
|
|
67
|
+
+ path
|
|
68
|
+
+ "); ask the administrator whether it allows the mode"
|
|
69
|
+
)
|
|
63
70
|
|
|
64
71
|
|
|
65
72
|
def policy_files():
|
|
@@ -73,7 +80,12 @@ def policy_files():
|
|
|
73
80
|
if e.errno not in ABSENT:
|
|
74
81
|
raise unreadable_policy(drop)
|
|
75
82
|
names = []
|
|
76
|
-
files += [
|
|
83
|
+
files += [
|
|
84
|
+
os.path.join(drop, n)
|
|
85
|
+
for n in sorted(
|
|
86
|
+
n for n in names if n.endswith(".json") and not n.startswith(".")
|
|
87
|
+
)
|
|
88
|
+
]
|
|
77
89
|
return files
|
|
78
90
|
|
|
79
91
|
|
|
@@ -108,6 +120,7 @@ def load(path):
|
|
|
108
120
|
raise Refused("settings.json is not valid UTF-8")
|
|
109
121
|
if not text.strip():
|
|
110
122
|
return {}, raw
|
|
123
|
+
|
|
111
124
|
def reject(token):
|
|
112
125
|
raise ValueError("the constant " + token + " is not JSON")
|
|
113
126
|
|
|
@@ -134,7 +147,9 @@ def replace(path, data, mode):
|
|
|
134
147
|
except OSError as e:
|
|
135
148
|
if e.errno not in (errno.EBUSY, errno.EXDEV):
|
|
136
149
|
raise
|
|
137
|
-
with open(
|
|
150
|
+
with open(
|
|
151
|
+
path, "wb"
|
|
152
|
+
) as target: # a bind-mounted single file refuses the rename: write in place
|
|
138
153
|
target.write(data)
|
|
139
154
|
os.remove(tmp)
|
|
140
155
|
except BaseException:
|
|
@@ -148,21 +163,34 @@ def save(path, data, raw):
|
|
|
148
163
|
real = os.path.realpath(path)
|
|
149
164
|
mode = os.stat(real).st_mode & 0o777 if os.path.exists(real) else 0o600
|
|
150
165
|
bom = BOM if raw is not None and raw.startswith(BOM) else b""
|
|
151
|
-
replace(
|
|
166
|
+
replace(
|
|
167
|
+
real,
|
|
168
|
+
bom + (json.dumps(data, indent=2, ensure_ascii=False) + "\n").encode("utf-8"),
|
|
169
|
+
mode,
|
|
170
|
+
)
|
|
152
171
|
|
|
153
172
|
|
|
154
173
|
def check_on(path, data):
|
|
155
174
|
# Claude Code lets root use the mode inside a sandbox it recognises: IS_SANDBOX exactly "1", or
|
|
156
175
|
# CLAUDE_CODE_BUBBLEWRAP set to a true value (2.1.289 reads 1, true, yes, on; 0 or false is not one).
|
|
157
|
-
bubblewrap = os.environ.get("CLAUDE_CODE_BUBBLEWRAP", "").strip().lower() in (
|
|
176
|
+
bubblewrap = os.environ.get("CLAUDE_CODE_BUBBLEWRAP", "").strip().lower() in (
|
|
177
|
+
"1",
|
|
178
|
+
"true",
|
|
179
|
+
"yes",
|
|
180
|
+
"on",
|
|
181
|
+
)
|
|
158
182
|
sandboxed = os.environ.get("IS_SANDBOX") == "1" or bubblewrap
|
|
159
183
|
if hasattr(os, "geteuid") and os.geteuid() == 0 and not sandboxed:
|
|
160
|
-
raise Refused(
|
|
184
|
+
raise Refused(
|
|
185
|
+
"Claude Code will not start in bypassPermissions as root; IS_SANDBOX=1 marks a real sandbox"
|
|
186
|
+
)
|
|
161
187
|
blocked = policy_block()
|
|
162
188
|
if blocked:
|
|
163
189
|
raise Refused("managed policy forbids bypassPermissions: " + blocked)
|
|
164
190
|
if forbids(data):
|
|
165
|
-
raise Refused(
|
|
191
|
+
raise Refused(
|
|
192
|
+
"settings.json itself sets permissions.disableBypassPermissionsMode"
|
|
193
|
+
)
|
|
166
194
|
|
|
167
195
|
|
|
168
196
|
def forget_record(path):
|
|
@@ -176,7 +204,9 @@ def forget_record(path):
|
|
|
176
204
|
|
|
177
205
|
def turn_on(path):
|
|
178
206
|
if not os.path.isdir(os.path.dirname(path)):
|
|
179
|
-
raise Refused(
|
|
207
|
+
raise Refused(
|
|
208
|
+
"there is no Claude Code config folder at " + os.path.dirname(path)
|
|
209
|
+
)
|
|
180
210
|
data, raw = load(path)
|
|
181
211
|
check_on(path, data)
|
|
182
212
|
if data.get("permissions", {}).get("defaultMode") == MODE:
|
|
@@ -197,13 +227,21 @@ def turn_off(path):
|
|
|
197
227
|
data, raw = load(path)
|
|
198
228
|
current = data.get("permissions", {}).get("defaultMode")
|
|
199
229
|
if current != MODE:
|
|
200
|
-
print(
|
|
230
|
+
print(
|
|
231
|
+
"defaultMode = "
|
|
232
|
+
+ str(current)
|
|
233
|
+
+ " (not bypassPermissions, nothing changed)"
|
|
234
|
+
)
|
|
201
235
|
return 0
|
|
202
|
-
if not os.access(
|
|
236
|
+
if not os.access(
|
|
237
|
+
path, os.W_OK
|
|
238
|
+
): # a read-only settings file is a lock: off honours it as on does
|
|
203
239
|
raise PermissionError(errno.EACCES, "this account may not write", path)
|
|
204
240
|
del data["permissions"]["defaultMode"]
|
|
205
241
|
save(path, data, raw)
|
|
206
|
-
forget_record(
|
|
242
|
+
forget_record(
|
|
243
|
+
path
|
|
244
|
+
) # only after the write: if it failed, the record is still the way back
|
|
207
245
|
print("defaultMode removed (Claude Code's default start mode)")
|
|
208
246
|
return 0
|
|
209
247
|
|
|
@@ -212,7 +250,10 @@ def outcome(argv, path):
|
|
|
212
250
|
actions = {("on", "--yes"): turn_on, ("off",): turn_off}
|
|
213
251
|
action = actions.get(tuple(argv[1:]))
|
|
214
252
|
if action is None:
|
|
215
|
-
print(
|
|
253
|
+
print(
|
|
254
|
+
"refused: usage is `bypass-permissions.py on --yes` or `bypass-permissions.py off`",
|
|
255
|
+
file=sys.stderr,
|
|
256
|
+
)
|
|
216
257
|
return 1
|
|
217
258
|
try:
|
|
218
259
|
return action(path)
|
|
@@ -221,12 +262,22 @@ def outcome(argv, path):
|
|
|
221
262
|
return 1
|
|
222
263
|
except OSError as e:
|
|
223
264
|
if e.errno in (errno.EACCES, errno.EPERM, errno.EROFS):
|
|
224
|
-
print(
|
|
265
|
+
print(
|
|
266
|
+
"write blocked: " + str(e) + "; settings.json left as it was",
|
|
267
|
+
file=sys.stderr,
|
|
268
|
+
)
|
|
225
269
|
return 3
|
|
226
270
|
print("unexpected error: " + str(e) + "; check settings.json", file=sys.stderr)
|
|
227
271
|
return 2
|
|
228
272
|
except Exception as e: # anything else stopped it partway too
|
|
229
|
-
print(
|
|
273
|
+
print(
|
|
274
|
+
"unexpected error: "
|
|
275
|
+
+ type(e).__name__
|
|
276
|
+
+ ": "
|
|
277
|
+
+ str(e)
|
|
278
|
+
+ "; check settings.json",
|
|
279
|
+
file=sys.stderr,
|
|
280
|
+
)
|
|
230
281
|
return 2
|
|
231
282
|
|
|
232
283
|
|
|
@@ -98,7 +98,7 @@ description: "컨텍스트 사용률이 임계(기본 50%)를 넘으면 `append_
|
|
|
98
98
|
|
|
99
99
|
1~3은 어시스턴트가 자동 수행. 4는 유저가 실행한다. 안내는 2에서 끝내고 3 뒤에는 아무것도 붙이지 않는다.
|
|
100
100
|
(Claude=`/banker:append_wiki`·`/banker:ready-compact`,
|
|
101
|
-
Codex=`$banker-append_wiki`·`$banker-ready-compact`; `/copy`·compaction은 양쪽 TUI 내장.)
|
|
101
|
+
Codex=`$banker-append_wiki`·`$banker-ready-compact`, 플러그인 설치면 `$banker:append_wiki`·`$banker:ready-compact`; `/copy`·compaction은 양쪽 TUI 내장.)
|
|
102
102
|
|
|
103
103
|
## 활성화 (idempotent · compose-safe)
|
|
104
104
|
`smart-compact [--threshold N]` (기본 N=50). 아래는 Claude Code 절차이고, Codex 는 위 Codex 절의 `codex-gate.mjs on` 으로 대신한다:
|
|
@@ -138,7 +138,8 @@ export const percent = (u) => Math.round((u.used * 100) / u.window);
|
|
|
138
138
|
export function nudge(pct, threshold) {
|
|
139
139
|
return `smart-compact: 컨텍스트 사용률 ${pct}% (임계 ${threshold}%). 이번 응답에서 위생 체인을 실행한다: `
|
|
140
140
|
+ "$banker-append_wiki, 그다음 한 줄 안내(다음 메시지가 resume 프롬프트이고 그 뒤 /copy, /compact, 붙여 넣기 순서), "
|
|
141
|
-
+ "마지막으로 $banker-ready-compact.
|
|
141
|
+
+ "마지막으로 $banker-ready-compact. banker 를 Codex 플러그인으로 설치했으면 $banker:append_wiki, $banker:ready-compact 다. "
|
|
142
|
+
+ "/copy, /compact, 붙여 넣기는 사용자가 한다.";
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
const text = (o, key) => (o && typeof o[key] === "string" ? o[key] : "");
|
|
@@ -24,7 +24,7 @@ argument-hint: "list | css | json | widget | check [--design-md PATH] [--only SL
|
|
|
24
24
|
| 런타임 | 스킬 디렉터리 |
|
|
25
25
|
|---|---|
|
|
26
26
|
| Claude Code | 이 파일 맨 위에 넣어 준 "Base directory for this skill:" 줄의 경로 |
|
|
27
|
-
| Codex | `~/.codex/skills/banker-theme-selection/`
|
|
27
|
+
| Codex | Codex 스킬 목록이 보이는 이 SKILL.md 경로(file)의 폴더. npm 설치는 `~/.codex/skills/banker-theme-selection/`(프로젝트 범위 설치면 `./.codex/skills/banker-theme-selection/`), 플러그인 설치(`codex plugin add`)는 `~/.codex/plugins/cache/<마켓플레이스>/banker/<판>/skills/theme-selection/`. 절대경로로 씀 |
|
|
28
28
|
|
|
29
29
|
- `~` 는 따옴표 안에서 펼쳐지지 않으니 절대경로를 쓴다.
|
|
30
30
|
2. 프로젝트에 `DESIGN.md` 가 있으면 `--design-md` 로 함께 먹인다. 없으면 번들 74종만 쓴다.
|
|
@@ -24,7 +24,7 @@ argument-hint: "on | off | status"
|
|
|
24
24
|
| 런타임 | `--runtime` | 스킬 디렉터리 |
|
|
25
25
|
|---|---|---|
|
|
26
26
|
| Claude Code | `claude` | 스킬 맨 위 "Base directory for this skill:" 경로 |
|
|
27
|
-
| Codex | `codex` | `~/.codex/skills/banker-tone-compact/`
|
|
27
|
+
| Codex | `codex` | Codex 스킬 목록이 보이는 이 SKILL.md 경로(file)의 폴더. npm 설치는 `~/.codex/skills/banker-tone-compact/`(프로젝트 범위 설치면 `./.codex/skills/banker-tone-compact/`), 플러그인 설치(`codex plugin add`)는 `~/.codex/plugins/cache/<마켓플레이스>/banker/<판>/skills/tone-compact/`. 절대경로로 씀 |
|
|
28
28
|
|
|
29
29
|
3. 결과 처리
|
|
30
30
|
|
|
@@ -186,7 +186,7 @@ export function writeFresh(file, data, mode = 0o644) {
|
|
|
186
186
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
187
187
|
rmSync(tmp, { force: true });
|
|
188
188
|
try {
|
|
189
|
-
|
|
189
|
+
writeNew(tmp, data, mode);
|
|
190
190
|
renameOver(tmp, file);
|
|
191
191
|
} catch (e) {
|
|
192
192
|
rmSync(tmp, { force: true });
|
|
@@ -194,6 +194,48 @@ export function writeFresh(file, data, mode = 0o644) {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
// Opens a file that is not there yet, as "wx" does, and makes sure the name is the file made. macOS and Linux refuse
|
|
198
|
+
// any name that is there, a link to nothing among them (EEXIST). Windows follows a link to nothing and makes the file
|
|
199
|
+
// it leads to: then that file is removed again when it is still the empty one made, and the open is refused with
|
|
200
|
+
// EEXIST as elsewhere. A look before the open catches such a link; this catches one planted between the look and the
|
|
201
|
+
// open. A look that fails after the open leaves the file to the caller, as a plain "wx" would.
|
|
202
|
+
export function openNew(path, mode) {
|
|
203
|
+
const fd = openSync(path, "wx", mode);
|
|
204
|
+
let made = null;
|
|
205
|
+
let st;
|
|
206
|
+
try {
|
|
207
|
+
made = fstatSync(fd, { bigint: true });
|
|
208
|
+
st = lstatSync(path, { bigint: true });
|
|
209
|
+
} catch {
|
|
210
|
+
return fd;
|
|
211
|
+
}
|
|
212
|
+
if (!st.isSymbolicLink() && (made.ino === 0n || sameInode(made, st))) return fd;
|
|
213
|
+
closeSync(fd);
|
|
214
|
+
if (st.isSymbolicLink()) dropFollowed(path, made);
|
|
215
|
+
throw Object.assign(new Error(`EEXIST: file already exists, open '${path}'`), { code: "EEXIST", syscall: "open", path });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Removes the file a "wx" open made through the link at path, when it is still the empty file that open made.
|
|
219
|
+
function dropFollowed(path, made) {
|
|
220
|
+
try {
|
|
221
|
+
const target = realpathSync(path);
|
|
222
|
+
const st = statSync(target, { bigint: true });
|
|
223
|
+
if (sameInode(made, st) && st.size === 0n) rmSync(target, { force: true });
|
|
224
|
+
} catch {
|
|
225
|
+
// gone already, or the link changed again: nothing of ours to take back
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Writes data to a file that is not there yet (openNew) and closes it.
|
|
230
|
+
function writeNew(path, data, mode) {
|
|
231
|
+
const fd = openNew(path, mode);
|
|
232
|
+
try {
|
|
233
|
+
writeFileSync(fd, data);
|
|
234
|
+
} finally {
|
|
235
|
+
closeSync(fd);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
197
239
|
// Windows does not rename a file over a link to a folder: there the link goes first, and the folder it
|
|
198
240
|
// leads to stays.
|
|
199
241
|
function renameOver(tmp, file) {
|
|
@@ -510,13 +552,14 @@ function writeBlocked(changes) {
|
|
|
510
552
|
// whatever the umask allows more. A file left empty is removed, unless it is a
|
|
511
553
|
// link or has other names (a hard link), whose other names would keep the old
|
|
512
554
|
// text: it is emptied instead. Codex skips an empty file anyway.
|
|
513
|
-
// A file that was not there when it was read is created only if it still is not (
|
|
555
|
+
// A file that was not there when it was read is created only if it still is not (openNew): a name that appeared in
|
|
514
556
|
// between, a link planted there among them, is not written through.
|
|
515
557
|
function apply(t, changes) {
|
|
516
558
|
for (const { file, after } of changes) {
|
|
517
559
|
if (file.bytes !== null) writeFresh(`${file.path}.${t.slug}.bak`, file.bytes, 0o600);
|
|
518
560
|
if (after === "" && !isLink(file.path) && !hasOtherNames(file.path)) rmSync(file.path, { force: true });
|
|
519
|
-
else
|
|
561
|
+
else if (file.bytes === null) writeNew(file.path, after, 0o644);
|
|
562
|
+
else writeFileSync(file.path, after, { mode: 0o644, flag: "w" });
|
|
520
563
|
}
|
|
521
564
|
}
|
|
522
565
|
|
|
@@ -568,9 +611,19 @@ const SLEEP = new Int32Array(new SharedArrayBuffer(4));
|
|
|
568
611
|
// when it may still be there (no stats or no inode number to prove it is that file, or the share could not say).
|
|
569
612
|
// Any other error (permissions, EIO) throws, after the same taking away.
|
|
570
613
|
function tryLock(path, token) {
|
|
614
|
+
// A name that is there in any form is held. Windows opens "wx" through a link to nothing and makes the file it leads
|
|
615
|
+
// to; macOS and Linux answer EEXIST for it below, and openNew takes back a file Windows made through a link planted
|
|
616
|
+
// after this look. A look that fails leaves the answer to the open, as before.
|
|
617
|
+
let there = false;
|
|
618
|
+
try {
|
|
619
|
+
there = lstatSync(path, { throwIfNoEntry: false }) !== undefined;
|
|
620
|
+
} catch {
|
|
621
|
+
// ESTALE and the like: the open below answers
|
|
622
|
+
}
|
|
623
|
+
if (there) return "held";
|
|
571
624
|
let fd;
|
|
572
625
|
try {
|
|
573
|
-
fd =
|
|
626
|
+
fd = openNew(path, 0o600);
|
|
574
627
|
} catch (e) {
|
|
575
628
|
if (e.code === "EEXIST") return "held";
|
|
576
629
|
if (e.code === "ESTALE") return "stale";
|
|
@@ -65,7 +65,7 @@ ultra-chain 이 직접 하는 일은 여섯 가지다.
|
|
|
65
65
|
| 이어 하기 메모 | OMC 작업 노트에 덧붙이기(`notepad_write_working`). 우선 노트는 통째로 바뀌므로 쓰지 않음 | `.omx/notepad.md` 에 한 줄 덧붙이기 |
|
|
66
66
|
| 실행 기록 기본 폴더 | `.omc/ultra-chain/` | `.omx/ultra-chain/` |
|
|
67
67
|
|
|
68
|
-
- Codex 에는 Skill 도구가 없다. 위 표의 `$이름` 은 그 스킬의 `SKILL.md` 를 읽고 그대로 따른다는 뜻이다. banker 스킬은 `<S>/../banker-<이름>/SKILL.md`, OMX 스킬은 `~/.codex/skills/<이름>/SKILL.md` 다. 그 스킬이 상태 기록을 요구하면 그 지시대로 직접 기록한다. 큰 `SKILL.md`(ralph-qa 는 약 71KB)는 셸 출력 한도에서 잘리니 나눠 읽는다.
|
|
68
|
+
- Codex 에는 Skill 도구가 없다. 위 표의 `$이름` 은 그 스킬의 `SKILL.md` 를 읽고 그대로 따른다는 뜻이다. banker 스킬은 npm 설치면 `<S>/../banker-<이름>/SKILL.md`, Codex 플러그인 설치(`<S>` 가 `plugins/cache/` 아래, 스킬 이름 `banker:<이름>`)면 `<S>/../<이름>/SKILL.md` 다. 표의 `$banker-<이름>` 은 플러그인 설치에서 `$banker:<이름>` 이다. OMX 스킬은 `~/.codex/skills/<이름>/SKILL.md` 다. 그 스킬이 상태 기록을 요구하면 그 지시대로 직접 기록한다. 큰 `SKILL.md`(ralph-qa 는 약 71KB)는 셸 출력 한도에서 잘리니 나눠 읽는다.
|
|
69
69
|
- Codex 의 하위 에이전트(critic, 시각 판정)는 저자 대화를 넘기지 않는다. multi-agent v2 는 `fork_turns="none"` 을 주고, v1 은 `fork_context` 를 주지 않는다. 판정을 받으면 `close_agent` 로 닫는다.
|
|
70
70
|
- Codex 환경 지시가 역할(`agent_type`)을 요구하면, 고정 모델이 세션 모델과 같은 역할을 쓰고 지시 앞머리에 파일 쓰기 금지를 적는다. 그런 역할이 없으면 역할 없이 띄운다. 쓴 역할과 모델, 강도를 판정 기록의 `note` 에 적는다.
|
|
71
71
|
- Codex 의 오래 도는 명령은 기다리는 시간을 박스 `--timeout` 보다 길게 둔다. 기다리다 끊기면 박스 정리와 결과 JSON 이 남지 않는다.
|
|
@@ -424,7 +424,7 @@ node "<S>/references/chain-report.mjs" "<run>"
|
|
|
424
424
|
|
|
425
425
|
압축(compaction)이나 중단 뒤에는 다음 순서로 잇는다.
|
|
426
426
|
|
|
427
|
-
1. 이 스킬을 다시 부른다: `--run=<실행 폴더> --from=<단계>`(Claude Code `Skill("banker:ultra-chain")`, Codex `$banker-ultra-chain`). 지시와 `<S>` 를 되찾기 위해서다. 실행 폴더는 이어 하기 메모나 압축 요약에서 찾는다.
|
|
427
|
+
1. 이 스킬을 다시 부른다: `--run=<실행 폴더> --from=<단계>`(Claude Code `Skill("banker:ultra-chain")`, Codex `$banker-ultra-chain`, 플러그인 설치면 `$banker:ultra-chain`). 지시와 `<S>` 를 되찾기 위해서다. 실행 폴더는 이어 하기 메모나 압축 요약에서 찾는다.
|
|
428
428
|
2. `node "<S>/references/chain-ledger.mjs" show "<run>"` 로 `current`(다시 시작할 단계), 단계별 `lastVerdict`(마지막 판정과 blocker), `maxRound` 를 본다. 멈추거나 실패한 실행은 그 단계가 `current` 다. 원래 플래그는 `task.md` 첫 줄과 `preflight` 기록에 있다.
|
|
429
429
|
3. 끊길 때 돌던 작업(박스 시험, 검토 좌석, 서버)이 끝났는지 확인한다. 끝나지 않았으면 알림을 기다리거나 멈춘다. 끊기기 전 실행의 늦은 판정은 쓰지 않는다.
|
|
430
430
|
4. 돌던 ralph-qa 실행은 되살리지 않는다. 좌석과 끈끈한 반대는 한 실행 안에서만 유지되기 때문이다.
|
|
@@ -11,7 +11,7 @@ description: "웹 UI 를 Playwright 로 렌더해 디자인 원본(PDF 나 스
|
|
|
11
11
|
## 0. 전제조건: playwright (없으면 설치부터)
|
|
12
12
|
이 스킬은 playwright 브라우저가 있어야 동작한다. **진행 전 설치 여부를 먼저 확인**하고, 없으면 **설치부터 안내**한다(추정·임의 재설치 금지):
|
|
13
13
|
- 확인: `ls "${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}" 2>/dev/null | grep -qi chromium && echo ok`
|
|
14
|
-
- 미설치면 먼저 `setup-playwright` 스킬로 설치(Claude Code: `/banker:setup` → setup-playwright / Codex: `banker-setup-playwright`). 설치·검증 후 이 스킬을 이어서 진행한다.
|
|
14
|
+
- 미설치면 먼저 `setup-playwright` 스킬로 설치(Claude Code: `/banker:setup` → setup-playwright / Codex: `banker-setup-playwright`, 플러그인 설치면 `banker:setup-playwright`). 설치·검증 후 이 스킬을 이어서 진행한다.
|
|
15
15
|
|
|
16
16
|
## When to use
|
|
17
17
|
- "ultra ui qa", "UI 대조", "디자인 PDF와 대조", "내부개념/어색한 내용 점검", "표 title·차트 크기 비교"
|