@kaydash9999/banker-plugins 0.21.0 → 0.23.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 +69 -0
- package/PRIVACY.md +8 -3
- package/README.md +78 -15
- package/bin/banker.js +154 -32
- package/bin/lib/codex-hooks.mjs +123 -20
- package/bin/lib/codex-names.mjs +4 -0
- package/bin/lib/codex-routes.mjs +204 -0
- package/bin/lib/command-skill.cjs +50 -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/manifest.json +18 -15
- package/codex/skills/graceful-pause/SKILL.md +2 -2
- package/codex/skills/progress/scripts/progress.mjs +44 -14
- package/codex/skills/teamwork/SKILL.md +137 -0
- package/codex/skills/teamwork/scripts/teamwork.mjs +957 -0
- package/codex/transform-matrix.md +5 -3
- package/commands/setup.md +2 -2
- package/hooks/obsidize-hook.mjs +75 -32
- package/hooks/telemetry-count-codex.mjs +303 -35
- package/hooks/update-fetch.mjs +2 -1
- package/hooks/update-notify.mjs +146 -13
- package/package.json +3 -1
- package/skills/3d-intro-build/SKILL.md +101 -20
- package/skills/3d-intro-build/references/NOTICE.md +9 -2
- package/skills/3d-intro-build/references/assemble.mjs +113 -22
- package/skills/3d-intro-build/references/azure-adapter.mjs +110 -24
- package/skills/3d-intro-build/references/clip-post.mjs +443 -0
- package/skills/3d-intro-build/references/dive-mode.mjs +299 -0
- package/skills/3d-intro-build/references/intro-fixes.css +11 -0
- package/skills/3d-intro-build/references/prompts.md +51 -4
- package/skills/3d-intro-build/references/scrub-engine.js +1 -0
- package/skills/3d-intro-build/references/step-nav.css +29 -0
- package/skills/3d-intro-build/references/step-nav.js +277 -106
- package/skills/3d-intro-build/references/verify-intro.mjs +387 -32
- package/skills/3d-intro-build/references/verify.md +6 -0
- package/skills/3d-intro-build/references/video-pool.mjs +142 -12
- package/skills/3d-intro-setup/SKILL.md +20 -7
- package/skills/3d-intro-setup/references/azure-adapter.mjs +110 -24
- package/skills/3d-intro-setup/references/video-pool.mjs +142 -12
- package/skills/all-in-one/SKILL.md +125 -86
- 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/omc-patch/SKILL.md +1 -1
- package/skills/payload-mon/SKILL.md +2 -2
- package/skills/play-qa/SKILL.md +7 -5
- package/skills/progress-mon/README.md +162 -0
- package/skills/progress-mon/SKILL.md +165 -0
- package/skills/progress-mon/assets/fixture/README.md +1 -0
- package/skills/progress-mon/assets/fixture/arch.json +33 -0
- package/skills/progress-mon/assets/fixture/commits.json +7 -0
- package/skills/progress-mon/assets/fixture/config.json +30 -0
- package/skills/progress-mon/assets/fixture/events.jsonl +7 -0
- package/skills/progress-mon/assets/fixture/flow.json +44 -0
- package/skills/progress-mon/assets/fixture/items.json +14 -0
- package/skills/progress-mon/assets/fixture/req-spec.json +31 -0
- package/skills/progress-mon/assets/fixture/reqs.json +11 -0
- package/skills/progress-mon/assets/fixture/variants/arch-auto/arch.json +58 -0
- package/skills/progress-mon/assets/fixture/variants/check-auto/arch.json +15 -0
- package/skills/progress-mon/assets/fixture/variants/check-overlap/arch.json +14 -0
- package/skills/progress-mon/assets/fixture/variants/history-manual/config.json +13 -0
- package/skills/progress-mon/assets/fixture/variants/history-manual/items.json +7 -0
- package/skills/progress-mon/assets/fixture/variants/workflow-wide/flow.json +35 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a01.jsonl +1 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a02.jsonl +1 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a03.jsonl +1 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a04.jsonl +1 -0
- package/skills/progress-mon/assets/fixture/workflows/wf_ex1/journal.jsonl +7 -0
- package/skills/progress-mon/references/arch.md +280 -0
- package/skills/progress-mon/references/authoring.md +199 -0
- package/skills/progress-mon/references/config.md +115 -0
- package/skills/progress-mon/references/flow.md +75 -0
- package/skills/progress-mon/references/inventory.md +58 -0
- package/skills/progress-mon/references/items.md +74 -0
- package/skills/progress-mon/references/req-spec.md +74 -0
- package/skills/progress-mon/references/reqs.md +30 -0
- package/skills/progress-mon/scripts/index.js +31 -0
- package/skills/progress-mon/scripts/pm-check.mjs +526 -0
- package/skills/progress-mon/scripts/pm-config.mjs +341 -0
- package/skills/progress-mon/scripts/pm-data.mjs +394 -0
- package/skills/progress-mon/scripts/pm-html.mjs +77 -0
- package/skills/progress-mon/scripts/pm-memory.mjs +308 -0
- package/skills/progress-mon/scripts/pm-render.mjs +1644 -0
- package/skills/progress-mon/scripts/pm-server.mjs +735 -0
- package/skills/progress-mon/scripts/pm-sources.mjs +501 -0
- package/skills/progress-mon/scripts/progress-mon.mjs +167 -0
- package/skills/ralph-qa/SKILL.md +105 -16
- package/skills/ralph-qa/references/gate-evidence.mjs +457 -0
- package/skills/ralph-qa/references/payload-scan.mjs +19 -2
- package/skills/ralph-qa/references/run-folder.mjs +299 -0
- package/skills/ralph-qa/references/seat-payload.mjs +60 -13
- package/skills/ralph-qa/references/seat-run.mjs +159 -155
- package/skills/remains/SKILL.md +2 -1
- package/skills/remains/scripts/boxes.mjs +6 -2
- package/skills/rfp-author/scripts/md2docx.py +63 -34
- package/skills/setup-bypass-permissions/SKILL.md +2 -2
- package/skills/setup-bypass-permissions/scripts/codex-config.mjs +19 -8
- package/skills/setup-bypass-permissions/scripts/fallback/bypass-permissions.py +66 -15
- package/skills/setup-mcp/SKILL.md +1 -1
- package/skills/setup-omc-hud/SKILL.md +2 -1
- package/skills/setup-omc-hud/assets/codex-config.mjs +19 -8
- 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 +34 -16
- package/skills/ultra-chain/references/chain-ledger.mjs +111 -3
- package/skills/ultra-init/SKILL.md +30 -22
- package/skills/ultra-ui-qa/SKILL.md +1 -1
- package/skills/ultraqa/NOTICE.md +34 -0
- package/skills/ultraqa/SKILL.md +196 -0
- package/skills/ultraqa/references/gate-evidence.mjs +457 -0
- package/skills/ultraqa/references/run-folder.mjs +299 -0
- package/skills/ultraqa/references/tmux-drive.md +52 -0
- package/skills/ultraqa/references/uqa-run.mjs +339 -0
- package/skills/update-banker/SKILL.md +32 -7
- 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
|
@@ -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
|
- 위 경고 전부를 일반 출력으로 먼저 보여 준다.
|
|
@@ -166,16 +166,27 @@ export function setKey(text, table, key, rawValue, comment = '') {
|
|
|
166
166
|
return ls.join(nl);
|
|
167
167
|
}
|
|
168
168
|
const range = tableRange(scan(text), table);
|
|
169
|
-
if (!range)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
if (!range) return withNewTable(ls, table, line).join(nl);
|
|
170
|
+
const [at, after] = placeIn(ls, range);
|
|
171
|
+
ls.splice(at, 0, ...line, ...after);
|
|
172
|
+
return ls.join(nl);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function withNewTable(ls, table, line) {
|
|
176
|
+
while (ls.length && ls[ls.length - 1] === '') ls.pop();
|
|
177
|
+
return [...ls, ...(ls.length ? [''] : []), `[${table}]`, ...line, ''];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 표 안에서 새 키를 넣을 줄 번호와 키 뒤에 더할 줄. 다음 표 머리에 붙은 주석(`# MCP servers` 처럼 머리 바로 위의 주석
|
|
181
|
+
// 줄)과 그 앞 빈 줄은 건너뛴다. 건너뛴 주석이 파일 첫 줄까지 닿으면 파일 머리 주석으로 보고 그 뒤에 넣고, 키와 표 머리
|
|
182
|
+
// 사이에 빈 줄 하나를 둔다(파일 첫 줄부터 표 설명 주석인 파일도 이렇게 되지만 값의 뜻은 같다).
|
|
183
|
+
function placeIn(ls, range) {
|
|
174
184
|
let at = range[1];
|
|
175
|
-
|
|
185
|
+
const beforeHead = range[1] < ls.length;
|
|
186
|
+
if (beforeHead) while (at > range[0] && ls[at - 1].trim().startsWith('#')) at--;
|
|
187
|
+
if (beforeHead && at === 0 && range[1] > 0) return [range[1], ['']];
|
|
176
188
|
while (at > range[0] && ls[at - 1].trim() === '') at--;
|
|
177
|
-
|
|
178
|
-
return ls.join(nl);
|
|
189
|
+
return [at, []];
|
|
179
190
|
}
|
|
180
191
|
|
|
181
192
|
// 키를 뺀다. comment 와 같은 주석 줄이 바로 위에 있으면 그것도 뺀다.
|
|
@@ -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
|
|
|
@@ -6,7 +6,7 @@ description: "범용 MCP 서버(context7, sequential-thinking, filesystem, git,
|
|
|
6
6
|
# setup-mcp: 범용 MCP 서버 설치
|
|
7
7
|
|
|
8
8
|
context7·sequential-thinking·filesystem·git·fetch - 특정 도구에 종속되지 않는 5종 표준 MCP 서버를
|
|
9
|
-
Claude Code·Codex 양쪽에 배선한다. **`setup-omc`(OMC/OMX 프레임워크 자체 설치, 자기 도구를 따로 들고 옴)와도,
|
|
9
|
+
Claude Code·Codex 양쪽에 배선한다. **`setup-omc`(Codex 는 `setup-omx`. OMC/OMX 프레임워크 자체 설치, 자기 도구를 따로 들고 옴)와도,
|
|
10
10
|
`setup-lsp`(`lsp_bridge` 전용)와도 다르다** - 이 스킬은 그 외 범용 standalone 서버만 다룬다. Serena 는
|
|
11
11
|
프로젝트 스코프(`--project` 가 홈 디렉터리면 인덱싱 과부하)라 여기서 제외한다. 설치는 **멱등**(있으면
|
|
12
12
|
remove 후 재-add). 답변은 한글(기술 토큰 영문).
|
|
@@ -122,7 +122,8 @@ node "<이 스킬 디렉터리 절대경로>\assets\apply-hud.mjs"
|
|
|
122
122
|
node "<이 스킬 디렉터리 절대경로>/../payload-mon/scripts/payload-mon.mjs" on
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
- Codex
|
|
125
|
+
- Codex npm 사본(`banker-setup-omx-hud`)에서 실행할 때 경로는 `<이 스킬 디렉터리 절대경로>/../banker-payload-mon/scripts/payload-mon.mjs` 다.
|
|
126
|
+
- Codex 플러그인 사본(`codex/renamed-skills/setup-omx-hud`)에서 실행할 때 경로는 `<이 스킬 디렉터리 절대경로>/../../../skills/payload-mon/scripts/payload-mon.mjs` 다.
|
|
126
127
|
- 이유: `PL:` 칸은 payload-mon 이 래퍼 옆(`hud/payload-mon/`)에 두는 크기 계산 모듈을 쓴다. 모듈이 없으면 `PL:` 이 나오지 않는다. 래퍼 파일이 링크면 링크가 가리키는 파일 옆에 둔다. 래퍼는 이 모듈을 링크가 가리키는 파일 옆에서, 로더(`omc-hud.mjs`)는 statusLine 명령의 경로 옆에서 먼저 찾는다.
|
|
127
128
|
- 출력 `켰습니다. 다음 statusline 갱신부터 PL 칸에 요청 크기가 늘 표시됩니다.` 를 확인한다. `이미 켜져 있습니다`, `모듈 사본을 이 버전으로 맞췄습니다` 도 정상이다.
|
|
128
129
|
|
|
@@ -166,16 +166,27 @@ export function setKey(text, table, key, rawValue, comment = '') {
|
|
|
166
166
|
return ls.join(nl);
|
|
167
167
|
}
|
|
168
168
|
const range = tableRange(scan(text), table);
|
|
169
|
-
if (!range)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
if (!range) return withNewTable(ls, table, line).join(nl);
|
|
170
|
+
const [at, after] = placeIn(ls, range);
|
|
171
|
+
ls.splice(at, 0, ...line, ...after);
|
|
172
|
+
return ls.join(nl);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function withNewTable(ls, table, line) {
|
|
176
|
+
while (ls.length && ls[ls.length - 1] === '') ls.pop();
|
|
177
|
+
return [...ls, ...(ls.length ? [''] : []), `[${table}]`, ...line, ''];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 표 안에서 새 키를 넣을 줄 번호와 키 뒤에 더할 줄. 다음 표 머리에 붙은 주석(`# MCP servers` 처럼 머리 바로 위의 주석
|
|
181
|
+
// 줄)과 그 앞 빈 줄은 건너뛴다. 건너뛴 주석이 파일 첫 줄까지 닿으면 파일 머리 주석으로 보고 그 뒤에 넣고, 키와 표 머리
|
|
182
|
+
// 사이에 빈 줄 하나를 둔다(파일 첫 줄부터 표 설명 주석인 파일도 이렇게 되지만 값의 뜻은 같다).
|
|
183
|
+
function placeIn(ls, range) {
|
|
174
184
|
let at = range[1];
|
|
175
|
-
|
|
185
|
+
const beforeHead = range[1] < ls.length;
|
|
186
|
+
if (beforeHead) while (at > range[0] && ls[at - 1].trim().startsWith('#')) at--;
|
|
187
|
+
if (beforeHead && at === 0 && range[1] > 0) return [range[1], ['']];
|
|
176
188
|
while (at > range[0] && ls[at - 1].trim() === '') at--;
|
|
177
|
-
|
|
178
|
-
return ls.join(nl);
|
|
189
|
+
return [at, []];
|
|
179
190
|
}
|
|
180
191
|
|
|
181
192
|
// 키를 뺀다. comment 와 같은 주석 줄이 바로 위에 있으면 그것도 뺀다.
|
|
@@ -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";
|