@kaydash9999/banker-plugins 0.16.2 → 0.17.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 +44 -0
- package/README.md +15 -10
- package/bin/banker.js +2 -2
- package/codex/manifest.json +2 -1
- package/codex/transform-matrix.md +2 -1
- package/commands/setup.md +2 -2
- package/hooks/progress-plan.mjs +716 -0
- package/hooks/progress.mjs +378 -34
- package/hooks/register.mjs +24 -1
- package/package.json +1 -1
- package/skills/3d-intro-build/references/assemble.mjs +10 -2
- package/skills/3d-intro-build/references/curate.mjs +10 -2
- package/skills/3d-intro-build/references/preview-lib.mjs +2 -0
- package/skills/3d-intro-build/references/serve.mjs +10 -2
- package/skills/3d-intro-build/references/verify-intro.mjs +10 -2
- package/skills/motion-graphic-make/references/curate.mjs +10 -2
- package/skills/motion-graphic-make/references/preview-lib.mjs +2 -0
- package/skills/motion-graphic-make/references/serve.mjs +10 -2
- package/skills/obsidizer/obsidize.mjs +10 -2
- package/skills/omc-patch/SKILL.md +96 -21
- package/skills/omc-patch/scripts/omc-patch.mjs +164 -62
- package/skills/omc-patch/scripts/probe-pm.mjs +192 -0
- package/skills/ultra-chain/SKILL.md +475 -0
- package/skills/ultra-chain/references/capture.mjs +232 -0
- package/skills/ultra-chain/references/chain-ledger.mjs +491 -0
- package/skills/ultra-chain/references/chain-report.mjs +257 -0
- package/skills/ultra-chain/references/preview-lib.mjs +258 -0
- package/skills/ultra-chain/references/serve.mjs +139 -0
- package/skills/vertical-pptx/verify_a4p.mjs +10 -2
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Screenshots for /banker:ultra-chain's visual review. Renders each target (a local HTML file or an http(s)
|
|
3
|
+
// URL) in headless Chromium and writes <out>/<name>.png, so the mock and the real result are captured the
|
|
4
|
+
// same way on Claude Code and Codex. Playwright comes from the global npm root; a project's own node_modules
|
|
5
|
+
// only with --playwright-from-project, because loading it runs that project's code. A bare import is not used:
|
|
6
|
+
// it would climb from this script's folder into a project that installed banker inside itself.
|
|
7
|
+
// URLs are loopback-only (localhost, 127.0.0.1, [::1]) unless --allow-remote; network paths (\\host, //host)
|
|
8
|
+
// are refused.
|
|
9
|
+
//
|
|
10
|
+
// node capture.mjs --out <folder> [--viewport 1280x800] [--full] [--wait <ms>] [--playwright-from-project]
|
|
11
|
+
// [--allow-remote] <name>=<file or URL>...
|
|
12
|
+
// node capture.mjs --check [--playwright-from-project] Playwright and Chromium start, nothing captured
|
|
13
|
+
// node capture.mjs --help
|
|
14
|
+
//
|
|
15
|
+
// Prints one JSON line: [{ name, target, png, ok, error? }] (with --check: { playwright: true, browser: true }).
|
|
16
|
+
// Exit 0 all written, 1 a target failed, 2 usage, 3 Playwright or its browser is missing.
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import { existsSync, mkdirSync, realpathSync, statSync } from "node:fs";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { join, resolve } from "node:path";
|
|
22
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
23
|
+
|
|
24
|
+
export class UsageError extends Error {}
|
|
25
|
+
export class NoBrowserError extends Error {}
|
|
26
|
+
|
|
27
|
+
const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
|
|
28
|
+
const VIEWPORT = /^(\d{2,5})x(\d{2,5})$/;
|
|
29
|
+
|
|
30
|
+
function viewportOf(v) {
|
|
31
|
+
const m = VIEWPORT.exec(String(v));
|
|
32
|
+
const [w, h] = m ? [Number(m[1]), Number(m[2])] : [0, 0];
|
|
33
|
+
if (!(w >= 100 && w <= 10000 && h >= 100 && h <= 10000)) throw new UsageError(`--viewport 는 <폭>x<높이>, 각 100~10000 입니다(예: 1280x800): ${v}`);
|
|
34
|
+
return { width: w, height: h };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// <name>=<target>. The name becomes <name>.png, so it may not hold a path separator.
|
|
38
|
+
function targetOf(arg) {
|
|
39
|
+
const at = arg.indexOf("=");
|
|
40
|
+
const name = at > 0 ? arg.slice(0, at) : "";
|
|
41
|
+
if (!NAME.test(name)) throw new UsageError(`대상은 <이름>=<파일 또는 URL> 이고 이름은 영문, 숫자, ., _, - 입니다: ${arg}`);
|
|
42
|
+
return { name, target: arg.slice(at + 1) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const FLAGS = {
|
|
46
|
+
"--out": (o, v) => { o.out = v; },
|
|
47
|
+
"--viewport": (o, v) => { o.viewport = viewportOf(v); },
|
|
48
|
+
"--wait": (o, v) => {
|
|
49
|
+
o.wait = Number(v);
|
|
50
|
+
if (!Number.isInteger(o.wait) || o.wait < 0 || o.wait > 60000) throw new UsageError("--wait 은 0~60000 ms 입니다");
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const SWITCHES = { "--full": "full", "--check": "check", "--playwright-from-project": "fromProject", "--allow-remote": "allowRemote" };
|
|
55
|
+
|
|
56
|
+
export const USAGE = `usage:
|
|
57
|
+
node capture.mjs --out <folder> [--viewport 1280x800] [--full] [--wait <ms>] [--playwright-from-project]
|
|
58
|
+
[--allow-remote] <name>=<file or URL>...
|
|
59
|
+
node capture.mjs --check [--playwright-from-project]
|
|
60
|
+
--out PNG 를 쓸 폴더. <name>.png 로 쓴다
|
|
61
|
+
--viewport 창의 폭x높이(픽셀). 기본 1280x800
|
|
62
|
+
--full 창 높이가 아니라 페이지 전체를 찍는다. CLI 출력, 문서, 창보다 긴 보기는 이것으로 찍는다
|
|
63
|
+
--wait 불러온 뒤 기다릴 ms. 기본 300
|
|
64
|
+
--check Playwright 와 Chromium 이 뜨는지만 본다
|
|
65
|
+
--playwright-from-project 프로젝트의 node_modules 에서 Playwright 를 읽는다(그 프로젝트 코드가 돈다)
|
|
66
|
+
--allow-remote loopback 이 아닌 URL 도 찍는다
|
|
67
|
+
exit: 0 모두 씀, 1 대상 하나라도 실패, 2 사용법, 3 Playwright 나 브라우저 없음
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
function readArgs(argv) {
|
|
71
|
+
const o = { out: null, viewport: { width: 1280, height: 800 }, full: false, wait: 300, fromProject: false, check: false, allowRemote: false, targets: [] };
|
|
72
|
+
for (let i = 0; i < argv.length; i++) {
|
|
73
|
+
const a = argv[i];
|
|
74
|
+
if (SWITCHES[a]) o[SWITCHES[a]] = true;
|
|
75
|
+
else if (FLAGS[a]) {
|
|
76
|
+
if (argv[i + 1] === undefined) throw new UsageError(`${a} 뒤에 값이 필요합니다`);
|
|
77
|
+
FLAGS[a](o, argv[++i]);
|
|
78
|
+
} else if (a.startsWith("--")) throw new UsageError(`모르는 옵션 ${a}`);
|
|
79
|
+
else o.targets.push(targetOf(a));
|
|
80
|
+
}
|
|
81
|
+
return o;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function validate(o) {
|
|
85
|
+
if (o.check) {
|
|
86
|
+
if (o.targets.length || o.out) throw new UsageError("--check 는 --out 과 대상 없이 씁니다");
|
|
87
|
+
return o;
|
|
88
|
+
}
|
|
89
|
+
if (!o.out) throw new UsageError("--out <폴더> 가 필요합니다");
|
|
90
|
+
if (!o.targets.length) throw new UsageError("찍을 대상이 없습니다");
|
|
91
|
+
if (new Set(o.targets.map((t) => t.name)).size !== o.targets.length) throw new UsageError("같은 이름의 대상이 둘 이상입니다");
|
|
92
|
+
return o;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const parseArgs = (argv) => validate(readArgs(argv));
|
|
96
|
+
|
|
97
|
+
// An http(s) URL as given, or an existing local file as a file:// URL. Nothing else (no javascript:, data:).
|
|
98
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
99
|
+
|
|
100
|
+
function httpUrl(target, allowRemote) {
|
|
101
|
+
let url;
|
|
102
|
+
try {
|
|
103
|
+
url = new URL(target);
|
|
104
|
+
} catch {
|
|
105
|
+
throw new UsageError(`URL 이 아닙니다: ${target}`);
|
|
106
|
+
}
|
|
107
|
+
if (!allowRemote && !LOOPBACK_HOSTS.has(url.hostname)) throw new UsageError(`loopback 이 아닌 주소는 --allow-remote 를 줄 때만 찍습니다: ${target}`);
|
|
108
|
+
return url.href;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function toUrl(target, cwd = process.cwd(), allowRemote = false) {
|
|
112
|
+
if (/^https?:\/\//i.test(target)) return httpUrl(target, allowRemote);
|
|
113
|
+
if (/^[\\/]{2}/.test(target)) throw new UsageError(`네트워크 경로는 찍지 않습니다: ${target}`);
|
|
114
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]+:/.test(target) && !/^[A-Za-z]:[\\/]/.test(target)) {
|
|
115
|
+
throw new UsageError(`http, https URL 이나 로컬 파일만 찍습니다: ${target}`);
|
|
116
|
+
}
|
|
117
|
+
const file = resolve(cwd, target);
|
|
118
|
+
if (!existsSync(file) || !statSync(file).isFile()) throw new UsageError(`파일이 없습니다: ${file}`);
|
|
119
|
+
return pathToFileURL(file).href;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Playwright (same rule as 3d-intro-build/references/verify-intro.mjs) ──────
|
|
123
|
+
|
|
124
|
+
function resolveFrom(base) {
|
|
125
|
+
try { return createRequire(join(base, "noop.js")).resolve("playwright"); } catch { return null; }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// npm's global root, asked from the home folder: from the project, cmd.exe on Windows would take an
|
|
129
|
+
// npm.cmd from that folder first. On Windows the command goes as one string (no argument list with
|
|
130
|
+
// shell: true, which Node 24 deprecates).
|
|
131
|
+
function globalRoot() {
|
|
132
|
+
const opts = { encoding: "utf8", cwd: homedir() };
|
|
133
|
+
const r = process.platform === "win32" ? spawnSync("npm.cmd root -g", { ...opts, shell: true }) : spawnSync("npm", ["root", "-g"], opts);
|
|
134
|
+
return String(r.stdout || "").trim();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// A broken install reads as missing Playwright (exit 3), not as a failed capture.
|
|
138
|
+
async function importPlaywright(entry) {
|
|
139
|
+
try {
|
|
140
|
+
const mod = await import(pathToFileURL(entry).href);
|
|
141
|
+
return mod.chromium ? mod : mod.default;
|
|
142
|
+
} catch (e) {
|
|
143
|
+
throw new NoBrowserError(`playwright 패키지를 불러오지 못했습니다(${entry}): ${String(e.message || e).split("\n")[0]}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function loadPlaywright({ fromProject = false } = {}) {
|
|
148
|
+
const fromHere = fromProject ? resolveFrom(process.cwd()) : null;
|
|
149
|
+
const root = fromHere ? null : globalRoot();
|
|
150
|
+
const entry = fromHere || (root && resolveFrom(root));
|
|
151
|
+
if (entry) return importPlaywright(entry);
|
|
152
|
+
throw new NoBrowserError("playwright 패키지가 없습니다. setup-playwright 스킬로 설치하거나 `npm i -g playwright && npx playwright install chromium` 을 실행하십시오.");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function launch(load, fromProject) {
|
|
156
|
+
const pw = await load({ fromProject });
|
|
157
|
+
try {
|
|
158
|
+
return await pw.chromium.launch();
|
|
159
|
+
} catch (e) {
|
|
160
|
+
throw new NoBrowserError(`Chromium 을 띄우지 못했습니다(브라우저 미설치일 수 있음: npx playwright install chromium): ${String(e.message || e).split("\n")[0]}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function shoot(browser, o, t) {
|
|
165
|
+
const png = join(o.out, `${t.name}.png`);
|
|
166
|
+
const page = await browser.newPage({ viewport: o.viewport });
|
|
167
|
+
try {
|
|
168
|
+
await page.goto(t.url, { waitUntil: "load", timeout: 30000 });
|
|
169
|
+
if (o.wait) await page.waitForTimeout(o.wait);
|
|
170
|
+
await page.screenshot({ path: png, fullPage: o.full });
|
|
171
|
+
return { name: t.name, target: t.target, png, ok: true };
|
|
172
|
+
} catch (e) {
|
|
173
|
+
return { name: t.name, target: t.target, png, ok: false, error: String(e.message || e).split("\n")[0] };
|
|
174
|
+
} finally {
|
|
175
|
+
await page.close();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function capture(o, load = loadPlaywright) {
|
|
180
|
+
const targets = o.targets.map((t) => ({ ...t, url: toUrl(t.target, process.cwd(), o.allowRemote) }));
|
|
181
|
+
mkdirSync(o.out, { recursive: true });
|
|
182
|
+
const browser = await launch(load, o.fromProject);
|
|
183
|
+
try {
|
|
184
|
+
const results = [];
|
|
185
|
+
for (const t of targets) results.push(await shoot(browser, o, t));
|
|
186
|
+
return results;
|
|
187
|
+
} finally {
|
|
188
|
+
await browser.close();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Playwright loads and Chromium starts and closes: the check 0. preflight runs on every OS.
|
|
193
|
+
async function check(o, load) {
|
|
194
|
+
const browser = await launch(load, o.fromProject);
|
|
195
|
+
await browser.close();
|
|
196
|
+
return { playwright: true, browser: true };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const askedHelp = (argv) => argv.includes("--help") || argv.includes("-h");
|
|
200
|
+
|
|
201
|
+
export async function main(argv, out = process.stdout, err = process.stderr, load = loadPlaywright) {
|
|
202
|
+
if (askedHelp(argv)) {
|
|
203
|
+
out.write(USAGE);
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
const o = parseArgs(argv);
|
|
208
|
+
if (o.check) {
|
|
209
|
+
out.write(`${JSON.stringify(await check(o, load))}\n`);
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
const results = await capture(o, load);
|
|
213
|
+
out.write(`${JSON.stringify(results)}\n`);
|
|
214
|
+
return results.every((r) => r.ok) ? 0 : 1;
|
|
215
|
+
} catch (e) {
|
|
216
|
+
err.write(`capture: ${e.message}\n`);
|
|
217
|
+
if (e instanceof UsageError) return 2;
|
|
218
|
+
return e instanceof NoBrowserError ? 3 : 1;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Run as a command, also when reached through a symlinked path: Node resolves
|
|
223
|
+
// symlinks for import.meta.url but not for process.argv[1].
|
|
224
|
+
const invokedDirectly = () => {
|
|
225
|
+
try {
|
|
226
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
227
|
+
} catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
if (invokedDirectly()) main(process.argv.slice(2)).then((code) => { process.exitCode = code; });
|