@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.
Files changed (129) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +69 -0
  4. package/PRIVACY.md +8 -3
  5. package/README.md +78 -15
  6. package/bin/banker.js +154 -32
  7. package/bin/lib/codex-hooks.mjs +123 -20
  8. package/bin/lib/codex-names.mjs +4 -0
  9. package/bin/lib/codex-routes.mjs +204 -0
  10. package/bin/lib/command-skill.cjs +50 -0
  11. package/bin/lib/telemetry-config.mjs +2 -1
  12. package/codex/command-skills/front-qa/SKILL.md +56 -0
  13. package/codex/command-skills/setup/SKILL.md +87 -0
  14. package/codex/hooks.json +29 -0
  15. package/codex/manifest.json +18 -15
  16. package/codex/skills/graceful-pause/SKILL.md +2 -2
  17. package/codex/skills/progress/scripts/progress.mjs +44 -14
  18. package/codex/skills/teamwork/SKILL.md +137 -0
  19. package/codex/skills/teamwork/scripts/teamwork.mjs +957 -0
  20. package/codex/transform-matrix.md +5 -3
  21. package/commands/setup.md +2 -2
  22. package/hooks/obsidize-hook.mjs +75 -32
  23. package/hooks/telemetry-count-codex.mjs +303 -35
  24. package/hooks/update-fetch.mjs +2 -1
  25. package/hooks/update-notify.mjs +146 -13
  26. package/package.json +3 -1
  27. package/skills/3d-intro-build/SKILL.md +101 -20
  28. package/skills/3d-intro-build/references/NOTICE.md +9 -2
  29. package/skills/3d-intro-build/references/assemble.mjs +113 -22
  30. package/skills/3d-intro-build/references/azure-adapter.mjs +110 -24
  31. package/skills/3d-intro-build/references/clip-post.mjs +443 -0
  32. package/skills/3d-intro-build/references/dive-mode.mjs +299 -0
  33. package/skills/3d-intro-build/references/intro-fixes.css +11 -0
  34. package/skills/3d-intro-build/references/prompts.md +51 -4
  35. package/skills/3d-intro-build/references/scrub-engine.js +1 -0
  36. package/skills/3d-intro-build/references/step-nav.css +29 -0
  37. package/skills/3d-intro-build/references/step-nav.js +277 -106
  38. package/skills/3d-intro-build/references/verify-intro.mjs +387 -32
  39. package/skills/3d-intro-build/references/verify.md +6 -0
  40. package/skills/3d-intro-build/references/video-pool.mjs +142 -12
  41. package/skills/3d-intro-setup/SKILL.md +20 -7
  42. package/skills/3d-intro-setup/references/azure-adapter.mjs +110 -24
  43. package/skills/3d-intro-setup/references/video-pool.mjs +142 -12
  44. package/skills/all-in-one/SKILL.md +125 -86
  45. package/skills/anti-drm/SKILL.md +1 -1
  46. package/skills/anti-drm/scripts/instruction-toggle.mjs +57 -4
  47. package/skills/arch-diagram/build_pptx_template.py +47 -28
  48. package/skills/audit-web-page/SKILL.md +1 -1
  49. package/skills/audit-web-page/harness.py +0 -1
  50. package/skills/lineage/lineage.py +1435 -533
  51. package/skills/motion-graphic-make/SKILL.md +1 -1
  52. package/skills/obsidizer/SKILL.md +20 -5
  53. package/skills/omc-patch/SKILL.md +1 -1
  54. package/skills/payload-mon/SKILL.md +2 -2
  55. package/skills/play-qa/SKILL.md +7 -5
  56. package/skills/progress-mon/README.md +162 -0
  57. package/skills/progress-mon/SKILL.md +165 -0
  58. package/skills/progress-mon/assets/fixture/README.md +1 -0
  59. package/skills/progress-mon/assets/fixture/arch.json +33 -0
  60. package/skills/progress-mon/assets/fixture/commits.json +7 -0
  61. package/skills/progress-mon/assets/fixture/config.json +30 -0
  62. package/skills/progress-mon/assets/fixture/events.jsonl +7 -0
  63. package/skills/progress-mon/assets/fixture/flow.json +44 -0
  64. package/skills/progress-mon/assets/fixture/items.json +14 -0
  65. package/skills/progress-mon/assets/fixture/req-spec.json +31 -0
  66. package/skills/progress-mon/assets/fixture/reqs.json +11 -0
  67. package/skills/progress-mon/assets/fixture/variants/arch-auto/arch.json +58 -0
  68. package/skills/progress-mon/assets/fixture/variants/check-auto/arch.json +15 -0
  69. package/skills/progress-mon/assets/fixture/variants/check-overlap/arch.json +14 -0
  70. package/skills/progress-mon/assets/fixture/variants/history-manual/config.json +13 -0
  71. package/skills/progress-mon/assets/fixture/variants/history-manual/items.json +7 -0
  72. package/skills/progress-mon/assets/fixture/variants/workflow-wide/flow.json +35 -0
  73. package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a01.jsonl +1 -0
  74. package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a02.jsonl +1 -0
  75. package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a03.jsonl +1 -0
  76. package/skills/progress-mon/assets/fixture/workflows/wf_ex1/agent-a04.jsonl +1 -0
  77. package/skills/progress-mon/assets/fixture/workflows/wf_ex1/journal.jsonl +7 -0
  78. package/skills/progress-mon/references/arch.md +280 -0
  79. package/skills/progress-mon/references/authoring.md +199 -0
  80. package/skills/progress-mon/references/config.md +115 -0
  81. package/skills/progress-mon/references/flow.md +75 -0
  82. package/skills/progress-mon/references/inventory.md +58 -0
  83. package/skills/progress-mon/references/items.md +74 -0
  84. package/skills/progress-mon/references/req-spec.md +74 -0
  85. package/skills/progress-mon/references/reqs.md +30 -0
  86. package/skills/progress-mon/scripts/index.js +31 -0
  87. package/skills/progress-mon/scripts/pm-check.mjs +526 -0
  88. package/skills/progress-mon/scripts/pm-config.mjs +341 -0
  89. package/skills/progress-mon/scripts/pm-data.mjs +394 -0
  90. package/skills/progress-mon/scripts/pm-html.mjs +77 -0
  91. package/skills/progress-mon/scripts/pm-memory.mjs +308 -0
  92. package/skills/progress-mon/scripts/pm-render.mjs +1644 -0
  93. package/skills/progress-mon/scripts/pm-server.mjs +735 -0
  94. package/skills/progress-mon/scripts/pm-sources.mjs +501 -0
  95. package/skills/progress-mon/scripts/progress-mon.mjs +167 -0
  96. package/skills/ralph-qa/SKILL.md +105 -16
  97. package/skills/ralph-qa/references/gate-evidence.mjs +457 -0
  98. package/skills/ralph-qa/references/payload-scan.mjs +19 -2
  99. package/skills/ralph-qa/references/run-folder.mjs +299 -0
  100. package/skills/ralph-qa/references/seat-payload.mjs +60 -13
  101. package/skills/ralph-qa/references/seat-run.mjs +159 -155
  102. package/skills/remains/SKILL.md +2 -1
  103. package/skills/remains/scripts/boxes.mjs +6 -2
  104. package/skills/rfp-author/scripts/md2docx.py +63 -34
  105. package/skills/setup-bypass-permissions/SKILL.md +2 -2
  106. package/skills/setup-bypass-permissions/scripts/codex-config.mjs +19 -8
  107. package/skills/setup-bypass-permissions/scripts/fallback/bypass-permissions.py +66 -15
  108. package/skills/setup-mcp/SKILL.md +1 -1
  109. package/skills/setup-omc-hud/SKILL.md +2 -1
  110. package/skills/setup-omc-hud/assets/codex-config.mjs +19 -8
  111. package/skills/smart-compact/SKILL.md +1 -1
  112. package/skills/smart-compact/scripts/codex-gate.mjs +2 -1
  113. package/skills/theme-selection/SKILL.md +1 -1
  114. package/skills/tone-compact/SKILL.md +1 -1
  115. package/skills/tone-compact/scripts/instruction-toggle.mjs +57 -4
  116. package/skills/ultra-chain/SKILL.md +34 -16
  117. package/skills/ultra-chain/references/chain-ledger.mjs +111 -3
  118. package/skills/ultra-init/SKILL.md +30 -22
  119. package/skills/ultra-ui-qa/SKILL.md +1 -1
  120. package/skills/ultraqa/NOTICE.md +34 -0
  121. package/skills/ultraqa/SKILL.md +196 -0
  122. package/skills/ultraqa/references/gate-evidence.mjs +457 -0
  123. package/skills/ultraqa/references/run-folder.mjs +299 -0
  124. package/skills/ultraqa/references/tmux-drive.md +52 -0
  125. package/skills/ultraqa/references/uqa-run.mjs +339 -0
  126. package/skills/update-banker/SKILL.md +32 -7
  127. package/skills/vertical-pptx/SKILL.md +1 -1
  128. package/skills/vertical-pptx/build_a4p.js +20 -2
  129. 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 = sys.argv[3] if len(sys.argv) > 3 else "Malgun Gothic" # 한글 폰트(서버에 설치된 것으로 교체 가능)
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('w:rFonts'))
28
+ rfonts = rpr.find(qn("w:rFonts"))
26
29
  if rfonts is None:
27
- rfonts = OxmlElement('w:rFonts'); rpr.append(rfonts)
28
- for a in ('w:ascii', 'w:hAnsi', 'w:eastAsia', 'w:cs'): # eastAsia 필수(누락 시 한글 깨짐)
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'(\*\*[^*]+\*\*)', text):
42
+ for part in re.split(r"(\*\*[^*]+\*\*)", text):
33
43
  if not part:
34
44
  continue
35
- if part.startswith('**') and part.endswith('**'):
36
- p.add_run(part[2:-2].replace('`', '')).bold = True
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':?-{1,}:?', c) for c in nonempty)
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('w:tblLayout'))
75
+ lay = tblPr.find(qn("w:tblLayout"))
64
76
  if lay is None:
65
- lay = OxmlElement('w:tblLayout'); tblPr.append(lay)
66
- lay.set(qn('w:type'), 'fixed') # 고정 레이아웃(지정 폭 준수)
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('w:tblGrid'))
81
+ grid = t._tbl.find(qn("w:tblGrid"))
69
82
  if grid is not None:
70
- for i, gc in enumerate(grid.findall(qn('w:gridCol'))):
83
+ for i, gc in enumerate(grid.findall(qn("w:gridCol"))):
71
84
  if i < len(widths):
72
- gc.set(qn('w:w'), str(int(widths[i] * 1440))) # inch→twips, 고정레이아웃 실폭(★ 이게 핵심)
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['Normal'])
94
+ set_korean(doc.styles["Normal"])
79
95
 
80
- lines = open(SRC, encoding='utf-8').read().split('\n')
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; continue
86
- if s.startswith('|'):
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()); i += 1
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('|').split('|')]
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); t.style = 'Table Grid'
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('', level=0); add_runs(h0, s[2:])
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(); add_runs(p, s[2:])
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='List Bullet'), s[2:])
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 (('w:fldChar', {'w:fldCharType': 'begin'}), (None, None), ('w:fldChar', {'w:fldCharType': 'end'})):
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('w:instrText'); el.set(qn('xml:space'), 'preserve'); el.text = 'PAGE'
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
- while (ls.length && ls[ls.length - 1] === '') ls.pop();
171
- return [...ls, ...(ls.length ? [''] : []), `[${table}]`, ...line, ''].join(nl);
172
- }
173
- // 다음 표 머리에 붙은 주석(`# MCP servers` 처럼 머리 바로 위의 주석 줄)과 그 앞 빈 줄은 건너뛰고 넣는다.
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
- if (range[1] < ls.length) while (at > range[0] && ls[at - 1].trim().startsWith('#')) at--;
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
- ls.splice(at, 0, ...line);
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("BANKER_BYPASS_POLICY_HOME"):
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("this machine's managed policy cannot be read (" + path + "); ask the administrator whether it allows the mode")
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 += [os.path.join(drop, n) for n in sorted(n for n in names if n.endswith(".json") and not n.startswith("."))]
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(path, "wb") as target: # a bind-mounted single file refuses the rename: write in place
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(real, bom + (json.dumps(data, indent=2, ensure_ascii=False) + "\n").encode("utf-8"), mode)
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 ("1", "true", "yes", "on")
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("Claude Code will not start in bypassPermissions as root; IS_SANDBOX=1 marks a real sandbox")
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("settings.json itself sets permissions.disableBypassPermissionsMode")
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("there is no Claude Code config folder at " + os.path.dirname(path))
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("defaultMode = " + str(current) + " (not bypassPermissions, nothing changed)")
230
+ print(
231
+ "defaultMode = "
232
+ + str(current)
233
+ + " (not bypassPermissions, nothing changed)"
234
+ )
201
235
  return 0
202
- if not os.access(path, os.W_OK): # a read-only settings file is a lock: off honours it as on does
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(path) # only after the write: if it failed, the record is still the way back
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("refused: usage is `bypass-permissions.py on --yes` or `bypass-permissions.py off`", file=sys.stderr)
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("write blocked: " + str(e) + "; settings.json left as it was", file=sys.stderr)
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("unexpected error: " + type(e).__name__ + ": " + str(e) + "; check settings.json", file=sys.stderr)
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 사본에서 실행할 때 경로는 `<이 스킬 디렉터리 절대경로>/../banker-payload-mon/scripts/payload-mon.mjs` 다.
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
- while (ls.length && ls[ls.length - 1] === '') ls.pop();
171
- return [...ls, ...(ls.length ? [''] : []), `[${table}]`, ...line, ''].join(nl);
172
- }
173
- // 다음 표 머리에 붙은 주석(`# MCP servers` 처럼 머리 바로 위의 주석 줄)과 그 앞 빈 줄은 건너뛰고 넣는다.
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
- if (range[1] < ls.length) while (at > range[0] && ls[at - 1].trim().startsWith('#')) at--;
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
- ls.splice(at, 0, ...line);
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. /copy, /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/` 의 절대경로. 프로젝트 범위 설치면 `./.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/` 의 절대경로. 프로젝트 범위 설치면 `./.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
- writeFileSync(tmp, data, { flag: "wx", mode });
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 ("wx"): a name that appeared in
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 writeFileSync(file.path, after, { mode: 0o644, flag: file.bytes === null ? "wx" : "w" });
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 = openSync(path, "wx", 0o600);
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";