@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
|
@@ -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";
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
채워진 예시는 NODES/EDGES/OUT/TITLE 를 대상 시스템 값으로 교체해 사용.
|
|
8
8
|
검증: 빌드 후 재오픈해 슬라이드/커넥터(p:cxnSp)/화살촉(a:tailEnd)/경계 확인. soffice 無 → 픽셀렌더 Unknown.
|
|
9
9
|
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
|
|
10
13
|
from pptx import Presentation
|
|
11
14
|
from pptx.util import Inches, Pt
|
|
12
15
|
from pptx.dml.color import RGBColor
|
|
@@ -14,36 +17,40 @@ from pptx.enum.shapes import MSO_SHAPE, MSO_CONNECTOR
|
|
|
14
17
|
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
|
15
18
|
from pptx.oxml.ns import qn
|
|
16
19
|
|
|
17
|
-
FONT =
|
|
18
|
-
OUT =
|
|
19
|
-
TITLE =
|
|
20
|
+
FONT = "맑은 고딕"
|
|
21
|
+
OUT = "output/architecture/diagram.pptx"
|
|
22
|
+
TITLE = "다이어그램 제목"
|
|
20
23
|
|
|
21
|
-
SHAPES = {
|
|
22
|
-
|
|
24
|
+
SHAPES = {
|
|
25
|
+
"rrect": MSO_SHAPE.ROUNDED_RECTANGLE,
|
|
26
|
+
"db": MSO_SHAPE.CAN,
|
|
27
|
+
"cube": MSO_SHAPE.CUBE,
|
|
28
|
+
"cloud": MSO_SHAPE.CLOUD,
|
|
29
|
+
}
|
|
23
30
|
|
|
24
31
|
# 색상 팔레트 예시: 서비스 파랑/보라/주황, DB 초록, 보안 분홍, HW 회색, 플랫폼 주황
|
|
25
32
|
# key: (label, sub, x, y, w, h, fill_hex, shape) — 단위 inch, 슬라이드 16:9 = 13.333 x 7.5
|
|
26
33
|
NODES = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
"FE": ("Frontend", ":8080", 0.4, 2.9, 2.1, 0.85, "E1F5FF", "rrect"),
|
|
35
|
+
"BE": ("Backend", ":8000", 3.1, 2.9, 2.2, 0.85, "F3E5F5", "rrect"),
|
|
36
|
+
"DB": ("Database", ":5432", 6.0, 2.9, 2.1, 0.85, "E8F5E9", "db"),
|
|
30
37
|
}
|
|
31
|
-
EDGES = [(
|
|
38
|
+
EDGES = [("FE", "BE"), ("BE", "DB")] # (parent, child)
|
|
32
39
|
|
|
33
40
|
|
|
34
|
-
def set_run(r, text, size, bold=False, color=
|
|
41
|
+
def set_run(r, text, size, bold=False, color="222222"):
|
|
35
42
|
r.text = text
|
|
36
43
|
r.font.size = Pt(size)
|
|
37
44
|
r.font.bold = bold
|
|
38
45
|
r.font.color.rgb = RGBColor.from_string(color)
|
|
39
46
|
r.font.name = FONT
|
|
40
47
|
rPr = r._r.get_or_add_rPr()
|
|
41
|
-
for tag in (
|
|
48
|
+
for tag in ("a:latin", "a:ea", "a:cs"): # ea 필수 — 누락 시 한글 tofu 위험
|
|
42
49
|
el = rPr.find(qn(tag))
|
|
43
50
|
if el is None:
|
|
44
51
|
el = rPr.makeelement(qn(tag), {})
|
|
45
52
|
rPr.append(el)
|
|
46
|
-
el.set(
|
|
53
|
+
el.set("typeface", FONT)
|
|
47
54
|
|
|
48
55
|
|
|
49
56
|
prs = Presentation()
|
|
@@ -52,39 +59,51 @@ prs.slide_height = Inches(7.5)
|
|
|
52
59
|
slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank
|
|
53
60
|
|
|
54
61
|
tb = slide.shapes.add_textbox(Inches(0.4), Inches(0.25), Inches(12.5), Inches(0.6))
|
|
55
|
-
p = tb.text_frame.paragraphs[0]
|
|
56
|
-
|
|
62
|
+
p = tb.text_frame.paragraphs[0]
|
|
63
|
+
p.alignment = PP_ALIGN.CENTER
|
|
64
|
+
set_run(p.add_run(), TITLE, 24, True, "1A1A1A")
|
|
57
65
|
|
|
58
66
|
rects = {}
|
|
59
67
|
for key, (label, sub, x, y, w, h, fill, shp) in NODES.items():
|
|
60
68
|
sp = slide.shapes.add_shape(SHAPES[shp], Inches(x), Inches(y), Inches(w), Inches(h))
|
|
61
|
-
sp.fill.solid()
|
|
62
|
-
sp.
|
|
69
|
+
sp.fill.solid()
|
|
70
|
+
sp.fill.fore_color.rgb = RGBColor.from_string(fill)
|
|
71
|
+
sp.line.color.rgb = RGBColor.from_string("9E9E9E")
|
|
72
|
+
sp.line.width = Pt(1)
|
|
63
73
|
try:
|
|
64
74
|
sp.shadow.inherit = False
|
|
65
75
|
except Exception:
|
|
66
76
|
pass
|
|
67
|
-
tf = sp.text_frame
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
tf = sp.text_frame
|
|
78
|
+
tf.word_wrap = True
|
|
79
|
+
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
|
|
80
|
+
pp = tf.paragraphs[0]
|
|
81
|
+
pp.alignment = PP_ALIGN.CENTER
|
|
82
|
+
set_run(pp.add_run(), label, 12.5, True, "1A1A1A")
|
|
70
83
|
if sub:
|
|
71
|
-
p2 = tf.add_paragraph()
|
|
72
|
-
|
|
84
|
+
p2 = tf.add_paragraph()
|
|
85
|
+
p2.alignment = PP_ALIGN.CENTER
|
|
86
|
+
set_run(p2.add_run(), sub, 9, False, "555555")
|
|
73
87
|
rects[key] = (x, y, w, h)
|
|
74
88
|
|
|
75
89
|
|
|
76
90
|
def arrow(x1, y1, x2, y2):
|
|
77
|
-
c = slide.shapes.add_connector(
|
|
78
|
-
|
|
91
|
+
c = slide.shapes.add_connector(
|
|
92
|
+
MSO_CONNECTOR.STRAIGHT, Inches(x1), Inches(y1), Inches(x2), Inches(y2)
|
|
93
|
+
)
|
|
94
|
+
c.line.color.rgb = RGBColor.from_string("5A5A5A")
|
|
95
|
+
c.line.width = Pt(1.5)
|
|
79
96
|
ln = c._element.spPr.get_or_add_ln() # 화살촉: ln 얻어 a:tailEnd append
|
|
80
|
-
ln.append(
|
|
97
|
+
ln.append(
|
|
98
|
+
ln.makeelement(qn("a:tailEnd"), {"type": "triangle", "w": "med", "len": "med"})
|
|
99
|
+
)
|
|
81
100
|
|
|
82
101
|
|
|
83
102
|
for a, b in EDGES: # parent.right-center -> child.left-center (좌->우)
|
|
84
|
-
ax, ay, aw, ah = rects[a]
|
|
103
|
+
ax, ay, aw, ah = rects[a]
|
|
104
|
+
bx, by, bw, bh = rects[b]
|
|
85
105
|
arrow(ax + aw, ay + ah / 2, bx, by + bh / 2)
|
|
86
106
|
|
|
87
|
-
|
|
88
|
-
os.makedirs(os.path.dirname(OUT) or '.', exist_ok=True)
|
|
107
|
+
os.makedirs(os.path.dirname(OUT) or ".", exist_ok=True)
|
|
89
108
|
prs.save(OUT)
|
|
90
|
-
print(
|
|
109
|
+
print("saved", OUT)
|
|
@@ -14,7 +14,7 @@ description: "가동 중인 웹 페이지를 Playwright 로 열어 버튼, DOM,
|
|
|
14
14
|
|
|
15
15
|
이 스킬은 playwright 브라우저가 있어야 동작한다. **진행 전 설치 여부를 먼저 확인**하고, 없으면 **설치부터 안내**한다(추정·임의 재설치 금지):
|
|
16
16
|
- 확인: `ls "${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}" 2>/dev/null | grep -qi chromium && echo ok`
|
|
17
|
-
- 미설치면 먼저 `setup-playwright` 스킬로 설치(Claude Code: `/banker:setup` → setup-playwright / Codex: `banker-setup-playwright`). 설치·검증 후 이 스킬을 이어서 진행한다.
|
|
17
|
+
- 미설치면 먼저 `setup-playwright` 스킬로 설치(Claude Code: `/banker:setup` → setup-playwright / Codex: `banker-setup-playwright`, 플러그인 설치면 `banker:setup-playwright`). 설치·검증 후 이 스킬을 이어서 진행한다.
|
|
18
18
|
|
|
19
19
|
## 언제
|
|
20
20
|
|