@kaydash9999/banker-plugins 0.20.1 → 0.20.5
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 +195 -1
- package/PRIVACY.md +13 -3
- package/README.md +27 -4
- package/bin/banker.js +84 -40
- package/bin/lib/telemetry-config.mjs +31 -16
- package/codex/manifest.json +1 -1
- package/codex/transform-matrix.md +1 -1
- package/commands/setup.md +23 -0
- package/hooks/progress.mjs +143 -38
- package/hooks/register.mjs +13 -3
- package/hooks/update-checkin.mjs +15 -7
- package/hooks/update-fetch.mjs +15 -9
- package/hooks/update-notify.mjs +2 -9
- package/package.json +1 -1
- package/skills/3d-intro-build/SKILL.md +11 -5
- package/skills/3d-intro-build/references/azure-adapter.mjs +374 -81
- package/skills/3d-intro-build/references/curate.mjs +48 -14
- package/skills/3d-intro-build/references/preview-lib.mjs +51 -15
- package/skills/3d-intro-build/references/prompts.md +4 -4
- package/skills/3d-intro-build/references/serve.mjs +6 -1
- package/skills/3d-intro-build/references/verify-intro.mjs +16 -9
- package/skills/3d-intro-build/references/video-pool.mjs +250 -135
- package/skills/3d-intro-setup/SKILL.md +41 -10
- package/skills/3d-intro-setup/references/azure-adapter.mjs +374 -81
- package/skills/3d-intro-setup/references/video-pool.mjs +250 -135
- package/skills/anti-drm/SKILL.md +6 -3
- package/skills/anti-drm/scripts/anti-drm.mjs +49 -12
- package/skills/anti-drm/scripts/instruction-toggle.mjs +105 -20
- package/skills/audit-mock/SKILL.md +1 -1
- package/skills/audit-security/SKILL.md +64 -2
- package/skills/deep-research/SKILL.md +5 -0
- package/skills/docs-setup/SKILL.md +37 -7
- package/skills/lineage/SKILL.md +12 -12
- package/skills/lineage/lineage.py +399 -300
- package/skills/motion-graphic-make/SKILL.md +1 -1
- package/skills/motion-graphic-make/references/curate.mjs +48 -14
- package/skills/motion-graphic-make/references/preview-lib.mjs +51 -15
- package/skills/motion-graphic-make/references/serve.mjs +6 -1
- package/skills/motion-graphic-setup/SKILL.md +58 -13
- package/skills/obsidizer/SKILL.md +1 -1
- package/skills/obsidizer/obsidize.mjs +8 -1
- package/skills/omc-patch/SKILL.md +80 -23
- package/skills/omc-patch/scripts/omc-patch.mjs +460 -116
- package/skills/payload-mon/SKILL.md +5 -2
- package/skills/payload-mon/scripts/payload-mon.mjs +137 -30
- package/skills/payload-mon/scripts/payload-size.mjs +4 -1
- package/skills/pdf-vision-extract/SKILL.md +11 -6
- package/skills/ralph-qa/SKILL.md +125 -151
- package/skills/ralph-qa/references/gemini-seat.mjs +67 -19
- package/skills/ralph-qa/references/payload-scan.mjs +22 -7
- package/skills/ralph-qa/references/seat-payload.mjs +646 -0
- package/skills/ralph-qa/references/seat-run.mjs +956 -0
- package/skills/ralph-qa/references/seat-spawn.mjs +205 -0
- package/skills/ralph-qa/references/verifier-probe.mjs +57 -16
- package/skills/setup-harness-factory/SKILL.md +17 -1
- package/skills/setup-insane-search/SKILL.md +16 -0
- package/skills/setup-java/SKILL.md +20 -1
- package/skills/setup-lsp/SKILL.md +56 -13
- package/skills/setup-mcp/SKILL.md +73 -4
- package/skills/setup-node/SKILL.md +31 -6
- package/skills/setup-omc/SKILL.md +24 -0
- package/skills/setup-omc-hud/SKILL.md +13 -5
- package/skills/setup-omc-hud/assets/apply-hud.mjs +381 -27
- package/skills/setup-omc-hud/assets/omc-hud-custom.mjs +81 -14
- package/skills/setup-omc-hud/scripts/claude-update-last.mjs +5 -3
- package/skills/setup-playwright/SKILL.md +45 -2
- package/skills/setup-pwsh/SKILL.md +19 -0
- package/skills/setup-python/SKILL.md +45 -7
- package/skills/setup-sandbox/SKILL.md +30 -5
- package/skills/setup-stitch/SKILL.md +22 -0
- package/skills/setup-tmux/SKILL.md +24 -4
- package/skills/tone-compact/SKILL.md +2 -1
- package/skills/tone-compact/scripts/instruction-toggle.mjs +105 -20
- package/skills/ultra-chain/SKILL.md +4 -3
- package/skills/ultra-chain/references/preview-lib.mjs +51 -15
- package/skills/ultra-chain/references/serve.mjs +6 -1
- package/skills/ultra-interview/SKILL.md +1 -0
- package/skills/update-banker/SKILL.md +19 -3
- package/skills/vertical-pptx/SKILL.md +6 -3
- package/skills/vertical-pptx-setup/SKILL.md +76 -20
|
@@ -40,9 +40,10 @@ node "<이 스킬 디렉터리 절대경로>/scripts/payload-mon.mjs" <on|off|st
|
|
|
40
40
|
|
|
41
41
|
- `on`: Claude Code 설정 폴더(`$CLAUDE_CONFIG_DIR`, 없으면 `~/.claude`)의 `hud/omc-hud-custom.mjs` 에서, ctx 를 넣는 줄 바로 뒤에 `// >>> payload-mon >>>` 블록 하나를 넣는다.
|
|
42
42
|
블록이 불러오는 추정 모듈은 래퍼 옆 `hud/payload-mon/payload-size.mjs` 에 사본으로 둔다. 플러그인 설치 경로는 버전마다 바뀌어 업데이트 뒤에 사라지기 때문이다.
|
|
43
|
+
래퍼 파일이 다른 곳을 가리키는 링크면 사본은 그 파일 옆에 둔다. 래퍼 v4 가 그 자리에서 사본을 먼저 찾기 때문이다.
|
|
43
44
|
이미 켜져 있으면 래퍼를 바꾸지 않고, 모듈 사본이 이 버전과 다를 때만 사본을 맞춘다. 예전 방식(블록 2개)으로 켜져 있었다면 새 방식으로 바꾼다.
|
|
44
45
|
바꾸기 전 내용은 `omc-hud-custom.mjs.payload-mon.bak` 에 남는다.
|
|
45
|
-
- `off`: 그 블록만 빼서 래퍼를 켜기 전 상태로 되돌리고, 모듈 사본과 추정치 캐시를 지운다. 래퍼 v4 에서는 모듈 사본이 없어져 `PL:` 칸도 사라진다.
|
|
46
|
+
- `off`: 그 블록만 빼서 래퍼를 켜기 전 상태로 되돌리고, 모듈 사본과 추정치 캐시를 지운다. 래퍼 v4 에서는 모듈 사본이 없어져 `PL:` 칸도 사라진다. 사본 폴더가 링크면 사본을 지우지 않고 그렇다고 알린다(`PL:` 칸이 남는다).
|
|
46
47
|
- `status`: 켜짐·꺼짐, 모듈 사본이 이 버전과 같은지, statusLine 이 이 래퍼를 쓰는지, 현재 세션의 추정치를 보여 준다.
|
|
47
48
|
- 같은 래퍼에 자기 표시 블록을 넣는 다른 도구가 있어도 서로의 블록을 건드리지 않는다.
|
|
48
49
|
|
|
@@ -59,7 +60,9 @@ node "<이 스킬 디렉터리 절대경로>/scripts/payload-mon.mjs" <on|off|st
|
|
|
59
60
|
플러그인 파일을 손으로 고치지 말고, banker 를 최신으로 올린 뒤(`update-banker`) 다시 시도한다. 그래도 안 되면 banker 이슈로 알린다.
|
|
60
61
|
- "node --check 실패": 블록을 넣은 결과가 문법 검사를 통과하지 못했다(예: 래퍼가 ctx 를 넣는 줄을 비동기가 아닌 함수 안으로 옮김). 파일은 그대로다.
|
|
61
62
|
- "래퍼에 쓸 권한이 없습니다", 또는 `status` 의 "HUD 래퍼를 읽을 수 없음": 래퍼 파일의 권한을 확인한다. 파일은 그대로다.
|
|
62
|
-
- "모듈 사본을 쓰지 못했습니다": 래퍼 옆 `
|
|
63
|
+
- "모듈 사본을 쓰지 못했습니다": 래퍼 옆(래퍼 파일이 링크면 링크가 가리키는 파일 옆) `payload-mon/` 에 쓸 수 없다(권한, 같은 이름의 파일, 디스크 공간). 래퍼는 그대로다.
|
|
64
|
+
`사본 폴더가 링크입니다` 면 그 폴더가 다른 곳을 가리키는 링크다. 이 도구는 링크 너머에 쓰거나 지우지 않는다. 링크를 일반 폴더로 바꾼 뒤 다시 켠다.
|
|
65
|
+
`사본 폴더가 쓰는 동안 바뀌었습니다` 면 쓰는 사이 그 폴더가 다른 것(링크, 다른 폴더)으로 바뀌었거나, 바뀌었다 돌아와 사본이 폴더에 없다. 바뀐 자리의 사본은 이 도구가 쓴 그 파일이고 바이트도 같을 때만 거뒀다(원래 거기 있던 다른 래퍼의 사본은 둔다). 바뀌었다 돌아온 사이 다른 곳에 간 사본은 거두지 못한다. 폴더를 확인한 뒤 다시 켠다.
|
|
63
66
|
- "표시(>>> / <<<)의 짝이 맞지 않습니다": 래퍼에 payload-mon 표시 줄이 한쪽만 남았다. `on`·`off` 모두 거부하므로, 손으로 고치지 말라는 원칙의 유일한 예외다.
|
|
64
67
|
`setup-omc-hud` 로 HUD 를 재설치하거나, 래퍼에서 짝 없이 남은 `// >>> payload-mon >>>` 또는 `// <<< payload-mon <<<` 줄을 지운 뒤 다시 실행한다.
|
|
65
68
|
- HUD 를 재설치한 뒤 payload 가 안 보이면 `on` 을 다시 실행한다. `setup-omc-hud` 는 설치할 때마다 이 스킬을 늘 켠다(래퍼 v4 의 `PL:` 칸이 모듈 사본을 씀).
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
* install lives under a versioned path that the next update retires, and an
|
|
26
26
|
* import pointing there would go dark without a word. `on` writes the copy and
|
|
27
27
|
* refreshes it when this version's module differs; `status` says when it does.
|
|
28
|
+
* When the wrapper file itself is a symlink, the copy goes beside the file it
|
|
29
|
+
* points at, where wrapper v4 looks first. A copy folder (payload-mon/) that is
|
|
30
|
+
* itself a link is neither written nor emptied through: `on` refuses it, and
|
|
31
|
+
* also refuses (taking its copy back) when the folder changes while it writes.
|
|
32
|
+
*
|
|
33
|
+
* The block takes the ctx line's line ending, so a CRLF wrapper stays CRLF, and
|
|
34
|
+
* a block whose line endings an editor rewrote still counts as on. A wrapper
|
|
35
|
+
* that starts with a BOM is taken as it is.
|
|
28
36
|
*
|
|
29
37
|
* `on` converges on the current block and is a no-op when it is in place. It
|
|
30
38
|
* writes only into the OMC custom HUD (signature-gated); `off` removes only
|
|
@@ -39,6 +47,7 @@ import {
|
|
|
39
47
|
accessSync,
|
|
40
48
|
constants,
|
|
41
49
|
existsSync,
|
|
50
|
+
lstatSync,
|
|
42
51
|
mkdirSync,
|
|
43
52
|
mkdtempSync,
|
|
44
53
|
readdirSync,
|
|
@@ -56,7 +65,10 @@ import { CACHE_DIR, clearCache, estimatePayloadBytes, REQUEST_LIMIT_BYTES, SHOW_
|
|
|
56
65
|
|
|
57
66
|
const OPEN = "// >>> payload-mon >>>";
|
|
58
67
|
const CLOSE = "// <<< payload-mon <<<";
|
|
59
|
-
const BLOCKS = /^[ \t]*\/\/ >>> payload-mon >>>[\s\S]*?\/\/ <<< payload-mon <<<[ \t]*\r?\n/gm;
|
|
68
|
+
export const BLOCKS = /^[ \t]*\/\/ >>> payload-mon >>>[\s\S]*?\/\/ <<< payload-mon <<<[ \t]*\r?\n/gm;
|
|
69
|
+
// The closing line. No block reaches past the last one, so the pattern runs only on the text before it:
|
|
70
|
+
// otherwise every stray opening line scans to the end again (a 2MB file of them took 28s).
|
|
71
|
+
export const CLOSE_LINE = /\/\/ <<< payload-mon <<<[ \t]*\r?\n/g;
|
|
60
72
|
// The block goes right after the line that adds ctx.
|
|
61
73
|
const CTX_LINE = /^ {2}if \(f\.ctx\) colored\.push\(colorCtx\(f\.ctx\)\);\r?\n/m;
|
|
62
74
|
// The statusline JSON the block reads, in either layout.
|
|
@@ -86,13 +98,29 @@ const DEFAULTS = {
|
|
|
86
98
|
sessionId: process.env.CLAUDE_CODE_SESSION_ID,
|
|
87
99
|
};
|
|
88
100
|
|
|
89
|
-
//
|
|
90
|
-
|
|
101
|
+
// The file a wrapper that is itself a symlink (dotfiles) points at; any other path as it is. A wrapper
|
|
102
|
+
// reached through a linked folder keeps the path as configured.
|
|
103
|
+
function linkTarget(hudFile) {
|
|
104
|
+
try {
|
|
105
|
+
return lstatSync(hudFile).isSymbolicLink() ? realpathSync(hudFile) : hudFile;
|
|
106
|
+
} catch {
|
|
107
|
+
return hudFile;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Where the block's module lives: beside the wrapper, outside any versioned path. For a wrapper file
|
|
111
|
+
// that is a link, beside the file it points at: wrapper v4 resolves its own path through the link and
|
|
112
|
+
// looks there for its PL segment.
|
|
113
|
+
const moduleCopy = (hudFile) => join(dirname(linkTarget(hudFile)), "payload-mon", "payload-size.mjs");
|
|
114
|
+
// Where 0.20.1 put the copy for such a link; `off` clears it too.
|
|
115
|
+
const besideLink = (hudFile) => join(dirname(hudFile), "payload-mon", "payload-size.mjs");
|
|
91
116
|
const moduleUrl = (hudFile) => pathToFileURL(moduleCopy(hudFile)).href;
|
|
92
117
|
|
|
118
|
+
// The ctx line's line ending, which the block takes so that a CRLF wrapper stays CRLF.
|
|
119
|
+
const eolOf = (src) => (CTX_LINE.exec(src)?.[0].endsWith("\r\n") ? "\r\n" : "\n");
|
|
120
|
+
|
|
93
121
|
// Right after ctx, colored like the 5h/wk rates: % on the ctx scale, MB in cyan.
|
|
94
122
|
// Its names are block-scoped, so none can clash with the wrapper's own.
|
|
95
|
-
const placeBlock = (url) =>
|
|
123
|
+
const placeBlock = (url, eol = "\n") =>
|
|
96
124
|
[
|
|
97
125
|
" " + OPEN,
|
|
98
126
|
" // payload-mon: estimated API request payload against the 32MB request limit, shown",
|
|
@@ -110,12 +138,19 @@ const placeBlock = (url) =>
|
|
|
110
138
|
" }",
|
|
111
139
|
" " + CLOSE,
|
|
112
140
|
"",
|
|
113
|
-
].join(
|
|
141
|
+
].join(eol);
|
|
114
142
|
|
|
115
|
-
const insertBlock = (base, url) => base.replace(CTX_LINE, (line) => line + placeBlock(url));
|
|
143
|
+
const insertBlock = (base, url) => base.replace(CTX_LINE, (line) => line + placeBlock(url, eolOf(line)));
|
|
116
144
|
|
|
117
145
|
const count = (text, needle) => text.split(needle).length - 1;
|
|
118
146
|
|
|
147
|
+
// The text without its blocks, the pattern run only up to the last closing line (see CLOSE_LINE).
|
|
148
|
+
function withoutBlocks(src) {
|
|
149
|
+
let end = -1;
|
|
150
|
+
for (const m of src.matchAll(CLOSE_LINE)) end = m.index + m[0].length;
|
|
151
|
+
return end < 0 ? src : src.slice(0, end).replace(BLOCKS, "") + src.slice(end);
|
|
152
|
+
}
|
|
153
|
+
|
|
119
154
|
// The wrapper's text, null when there is no such file. Any other read failure
|
|
120
155
|
// (permissions, say) is not "missing" and propagates.
|
|
121
156
|
const readText = (file) => {
|
|
@@ -137,18 +172,21 @@ const readBytes = (file) => {
|
|
|
137
172
|
|
|
138
173
|
const refusal = (why, hudFile) => ({ ok: false, message: `${why}. 래퍼를 그대로 두었습니다: ${hudFile}` });
|
|
139
174
|
|
|
140
|
-
// The block exactly as `on` writes it for this URL,
|
|
141
|
-
|
|
142
|
-
|
|
175
|
+
// The line ending ("\n" or "\r\n") of the block that sits, exactly as `on` writes it for this URL,
|
|
176
|
+
// once, right after the ctx line; "" when it does not. Either ending counts as in place, so a block
|
|
177
|
+
// whose line endings an editor rewrote is still on.
|
|
178
|
+
function blockEol(src, url) {
|
|
179
|
+
if (count(src, OPEN) !== 1 || count(src, CLOSE) !== 1) return "";
|
|
143
180
|
const ctx = CTX_LINE.exec(src);
|
|
144
|
-
|
|
181
|
+
if (ctx === null) return "";
|
|
182
|
+
return ["\n", "\r\n"].find((eol) => src.startsWith(placeBlock(url, eol), ctx.index + ctx[0].length)) ?? "";
|
|
145
183
|
}
|
|
146
184
|
|
|
147
185
|
// Why the block cannot go into this wrapper, or "" when it can. Markers that do
|
|
148
186
|
// not pair up would let the block pattern swallow the code between them.
|
|
149
187
|
function misfit(src) {
|
|
150
188
|
if (count(src, OPEN) !== count(src, CLOSE)) return "payload-mon 표시(>>> / <<<)의 짝이 맞지 않습니다";
|
|
151
|
-
const base = src
|
|
189
|
+
const base = withoutBlocks(src);
|
|
152
190
|
if (!base.includes("omc-hud.mjs") || !base.includes("const colorRate")) return "OMC 커스텀 HUD 래퍼가 아닙니다";
|
|
153
191
|
if (!INPUT_DECL.test(base)) return "삽입 위치를 찾지 못했습니다(statusline 입력을 담는 input 변수가 없음)";
|
|
154
192
|
if (!CTX_LINE.test(base)) return "삽입 위치를 찾지 못했습니다(ctx를 넣는 줄이 없음)";
|
|
@@ -163,6 +201,16 @@ function copyState(o) {
|
|
|
163
201
|
return source !== null && copy.equals(source) ? "current" : "stale";
|
|
164
202
|
}
|
|
165
203
|
|
|
204
|
+
// Whether the folder holding `file` is a link. The copy folder is this tool's, and a link there
|
|
205
|
+
// would take the copy, or its removal, to wherever it points.
|
|
206
|
+
function linkedFolder(file) {
|
|
207
|
+
try {
|
|
208
|
+
return lstatSync(dirname(file)).isSymbolicLink();
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
166
214
|
// Replaces file through a fresh temp file ("wx") and a rename: a link planted at
|
|
167
215
|
// either name is replaced rather than written through, and a write that fails
|
|
168
216
|
// leaves the old file as it was.
|
|
@@ -171,24 +219,62 @@ function writeFresh(file, data, mode) {
|
|
|
171
219
|
rmSync(tmp, { force: true });
|
|
172
220
|
try {
|
|
173
221
|
writeFileSync(tmp, data, { flag: "wx", mode });
|
|
222
|
+
const id = fileId(tmp);
|
|
174
223
|
renameSync(tmp, file);
|
|
224
|
+
return id;
|
|
175
225
|
} catch (e) {
|
|
176
226
|
rmSync(tmp, { force: true });
|
|
177
227
|
throw e;
|
|
178
228
|
}
|
|
179
229
|
}
|
|
180
230
|
|
|
231
|
+
// A file's identity, exact: NTFS file IDs pass 2^53, where a Number loses the low bits.
|
|
232
|
+
const fileId = (p) => {
|
|
233
|
+
const s = lstatSync(p, { bigint: true });
|
|
234
|
+
return `${s.dev}:${s.ino}`;
|
|
235
|
+
};
|
|
236
|
+
// The copy folder's identity, to tell afterwards whether it is still the folder that was written into.
|
|
237
|
+
const folderStamp = (dir) => {
|
|
238
|
+
const s = lstatSync(dir, { bigint: true });
|
|
239
|
+
return `${s.dev}:${s.ino}:${s.isSymbolicLink()}`;
|
|
240
|
+
};
|
|
241
|
+
// After the write: the copy folder must still be the one stamped before, holding these bytes. Another program
|
|
242
|
+
// may have swapped it (for a link, say), so the copy went wherever it led, or put it back after the write, so
|
|
243
|
+
// the copy is not in it. Then the write is refused, and the copy is taken back from where the folder now leads
|
|
244
|
+
// when the file there is the one written (`written`, from writeFresh) and still ours byte for byte: a copy that
|
|
245
|
+
// was there before, or one another program wrote over, stays.
|
|
246
|
+
// Node has no openat, so a copy that went elsewhere through a swap that was put back stays where it went, and
|
|
247
|
+
// a swap after this check goes unseen.
|
|
248
|
+
function checkFolder(copy, stamp, data, written) {
|
|
249
|
+
const dir = dirname(copy);
|
|
250
|
+
if (folderStamp(dir) === stamp && readBytes(copy)?.equals(data)) return;
|
|
251
|
+
try {
|
|
252
|
+
const landed = join(realpathSync(dir), basename(copy));
|
|
253
|
+
if (fileId(landed) === written && readFileSync(landed).equals(data)) rmSync(landed, { force: true });
|
|
254
|
+
} catch {
|
|
255
|
+
/* nothing there to take back */
|
|
256
|
+
}
|
|
257
|
+
throw new Error(`사본 폴더가 쓰는 동안 바뀌었습니다: ${dir}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
181
260
|
// Brings the copy up to this version's module; true when it had to write. The
|
|
182
261
|
// statusline imports it on every refresh, so no other account may change it
|
|
183
262
|
// (reading it is harmless). When the write fails, a folder made here is taken
|
|
184
263
|
// away again: mkdirSync names a folder only when it created it, and rmdir
|
|
185
264
|
// leaves one that something else has meanwhile put a file in.
|
|
186
265
|
function syncModule(o) {
|
|
187
|
-
if (copyState(o) === "current") return false;
|
|
188
266
|
const copy = moduleCopy(o.hudFile);
|
|
267
|
+
if (linkedFolder(copy)) throw new Error(`사본 폴더가 링크입니다: ${dirname(copy)}`);
|
|
268
|
+
if (copyState(o) === "current") return false;
|
|
269
|
+
// Read first: a module that cannot be read leaves no folder behind.
|
|
270
|
+
const data = readFileSync(o.moduleSource);
|
|
189
271
|
const made = mkdirSync(dirname(copy), { recursive: true, mode: 0o755 }) !== undefined;
|
|
190
272
|
try {
|
|
191
|
-
|
|
273
|
+
const stamp = folderStamp(dirname(copy));
|
|
274
|
+
// From the same lstat: a folder that became a link since the check above is refused before the write.
|
|
275
|
+
if (stamp.endsWith(":true")) throw new Error(`사본 폴더가 링크입니다: ${dirname(copy)}`);
|
|
276
|
+
const written = writeFresh(copy, data, 0o644);
|
|
277
|
+
checkFolder(copy, stamp, data, written);
|
|
192
278
|
} catch (e) {
|
|
193
279
|
if (made) {
|
|
194
280
|
try {
|
|
@@ -202,12 +288,14 @@ function syncModule(o) {
|
|
|
202
288
|
return true;
|
|
203
289
|
}
|
|
204
290
|
|
|
205
|
-
// The first lines of node's complaint about `text` as an ES module, "" if it parses.
|
|
291
|
+
// The first lines of node's complaint about `text` as an ES module, "" if it parses. A leading BOM
|
|
292
|
+
// is left out: node runs a wrapper that starts with one, but `node --check` rejects a BOM before a
|
|
293
|
+
// shebang (Node 20, 24).
|
|
206
294
|
function syntaxError(text) {
|
|
207
295
|
const dir = mkdtempSync(join(tmpdir(), "payload-mon-"));
|
|
208
296
|
try {
|
|
209
297
|
const file = join(dir, "check.mjs");
|
|
210
|
-
writeFileSync(file, text);
|
|
298
|
+
writeFileSync(file, text.replace(/^\uFEFF/, ""));
|
|
211
299
|
const r = spawnSync(process.execPath, ["--check", file], { encoding: "utf8" });
|
|
212
300
|
return r.status === 0 ? "" : String(r.stderr || r.error).trim().split("\n").slice(0, 3).join(" | ");
|
|
213
301
|
} finally {
|
|
@@ -257,7 +345,7 @@ const showsPL = (src) => src !== null && PL_SEGMENT.test(src);
|
|
|
257
345
|
// Where the size shows, named only while it does: the block in place and a
|
|
258
346
|
// module copy to import. "" otherwise, so status never promises a segment.
|
|
259
347
|
function displayWhere(o, src) {
|
|
260
|
-
if (src === null ||
|
|
348
|
+
if (src === null || blockEol(src, moduleUrl(o.hudFile)) === "" || copyState(o) === "missing") return "";
|
|
261
349
|
return showsPL(src) ? "PL 칸에 늘 표시" : `${SHOW_FROM_BYTES / MB}MB부터 표시`;
|
|
262
350
|
}
|
|
263
351
|
|
|
@@ -265,7 +353,8 @@ export function turnOn(opts = {}) {
|
|
|
265
353
|
const o = { ...DEFAULTS, ...opts };
|
|
266
354
|
const src = readText(o.hudFile);
|
|
267
355
|
if (src === null) return { ok: false, message: `HUD 래퍼가 없습니다: ${o.hudFile}` };
|
|
268
|
-
|
|
356
|
+
// In place with the wrapper's own line ending; a block whose ending differs is written again.
|
|
357
|
+
if (blockEol(src, moduleUrl(o.hudFile)) === eolOf(src)) {
|
|
269
358
|
try {
|
|
270
359
|
const refreshed = syncModule(o);
|
|
271
360
|
return { ok: true, message: (refreshed ? "모듈 사본을 이 버전으로 맞췄습니다." : "이미 켜져 있습니다 (변경 없음).") + notShownNote(o) };
|
|
@@ -278,43 +367,52 @@ export function turnOn(opts = {}) {
|
|
|
278
367
|
const done = showsPL(src)
|
|
279
368
|
? "켰습니다. 다음 statusline 갱신부터 PL 칸에 요청 크기가 늘 표시됩니다."
|
|
280
369
|
: "켰습니다. 다음 statusline 갱신부터 8MB 이상이면 ctx 옆에 payload가 표시됩니다.";
|
|
281
|
-
const after = insertBlock(src
|
|
370
|
+
const after = insertBlock(withoutBlocks(src), moduleUrl(o.hudFile));
|
|
282
371
|
return commit(o.hudFile, src, after, done + notShownNote(o), () => syncModule(o));
|
|
283
372
|
}
|
|
284
373
|
|
|
285
374
|
function removeBlocks(o, src) {
|
|
286
375
|
if (src === null) return { ok: true, message: `HUD 래퍼가 없어 끌 것이 없습니다: ${o.hudFile}` };
|
|
287
376
|
if (count(src, OPEN) !== count(src, CLOSE)) return refusal("payload-mon 표시(>>> / <<<)의 짝이 맞지 않습니다", o.hudFile);
|
|
288
|
-
const out = src
|
|
377
|
+
const out = withoutBlocks(src);
|
|
289
378
|
if (out === src) return { ok: true, message: "이미 꺼져 있습니다 (래퍼 변경 없음)." };
|
|
290
379
|
return commit(o.hudFile, src, out, "껐습니다. 다음 statusline 갱신부터 payload가 표시되지 않습니다.");
|
|
291
380
|
}
|
|
292
381
|
|
|
293
382
|
// What off leaves behind otherwise: the cache (when this account controls its
|
|
294
383
|
// folder), and the module copy with its folder (only when nothing else is in
|
|
295
|
-
// it).
|
|
384
|
+
// it), also the one 0.20.1 left beside a linked wrapper, but nothing through a copy folder that is a link.
|
|
385
|
+
// None failing undoes off.
|
|
386
|
+
// Returns the copy folders it left alone because they are links.
|
|
296
387
|
function removeLeftovers(o) {
|
|
297
|
-
const
|
|
298
|
-
|
|
388
|
+
const all = [...new Set([moduleCopy(o.hudFile), besideLink(o.hudFile)])];
|
|
389
|
+
const copies = all.filter((copy) => !linkedFolder(copy));
|
|
390
|
+
for (const remove of [() => clearCache(o.cacheDir), ...copies.map((copy) => () => rmSync(copy, { force: true }))]) {
|
|
299
391
|
try {
|
|
300
392
|
remove();
|
|
301
393
|
} catch {
|
|
302
394
|
/* stale numbers or an unused copy; off stands */
|
|
303
395
|
}
|
|
304
396
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
397
|
+
for (const copy of copies) {
|
|
398
|
+
try {
|
|
399
|
+
rmdirSync(dirname(copy));
|
|
400
|
+
} catch {
|
|
401
|
+
/* absent, or holds something that is not ours */
|
|
402
|
+
}
|
|
309
403
|
}
|
|
404
|
+
return all.filter((copy) => !copies.includes(copy)).map((copy) => dirname(copy));
|
|
310
405
|
}
|
|
311
406
|
|
|
312
407
|
export function turnOff(opts = {}) {
|
|
313
408
|
const o = { ...DEFAULTS, ...opts };
|
|
314
409
|
const res = removeBlocks(o, readText(o.hudFile));
|
|
315
|
-
if (res.ok)
|
|
316
|
-
|
|
410
|
+
if (!res.ok) return res;
|
|
411
|
+
// A copy kept in a linked folder still feeds wrapper v4's PL segment, so off says so.
|
|
412
|
+
const kept = removeLeftovers(o);
|
|
413
|
+
return kept.length ? { ...res, message: `${res.message} ${KEPT_NOTE}: ${kept.join(", ")}` } : res;
|
|
317
414
|
}
|
|
415
|
+
const KEPT_NOTE = "사본 폴더가 링크라 모듈 사본은 지우지 않았습니다(래퍼 v4 의 PL 칸이 계속 보일 수 있음)";
|
|
318
416
|
|
|
319
417
|
// The estimate for the session that runs this, "" if unknown, ending with where
|
|
320
418
|
// it shows when `where` names it:
|
|
@@ -346,8 +444,17 @@ function stateOf(src, o) {
|
|
|
346
444
|
if (count(src, OPEN) + count(src, CLOSE) === 0) return "꺼짐";
|
|
347
445
|
// on and off both refuse this, so pointing at on would be a dead end.
|
|
348
446
|
if (count(src, OPEN) !== count(src, CLOSE)) return "표시(>>> / <<<)의 짝이 맞지 않습니다 (on·off 모두 거부: HUD 재설치 또는 남은 표시 줄 정리)";
|
|
349
|
-
if (
|
|
350
|
-
return "켜짐" +
|
|
447
|
+
if (blockEol(src, moduleUrl(o.hudFile)) === "") return "켜짐이 불완전하거나 오래됨 (on 으로 복구)";
|
|
448
|
+
return "켜짐" + copyNote(o);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// What status adds about the module copy. In a copy folder that is a link, on refuses to write it, so
|
|
452
|
+
// pointing at on would be a dead end.
|
|
453
|
+
function copyNote(o) {
|
|
454
|
+
const state = copyState(o);
|
|
455
|
+
const copy = moduleCopy(o.hudFile);
|
|
456
|
+
if (state !== "current" && linkedFolder(copy)) return `, 단 사본 폴더가 링크라 모듈 사본을 쓰지 않음 (링크를 일반 폴더로 바꾼 뒤 on): ${dirname(copy)}`;
|
|
457
|
+
return COPY_NOTE[state];
|
|
351
458
|
}
|
|
352
459
|
|
|
353
460
|
// What status says about the wrapper. A wrapper it cannot read is reported, not
|
|
@@ -211,7 +211,10 @@ export function clearCache(dir = CACHE_DIR) {
|
|
|
211
211
|
export function estimatePayloadBytes(transcriptPath, cacheDir = CACHE_DIR) {
|
|
212
212
|
const fd = openSync(transcriptPath, "r");
|
|
213
213
|
try {
|
|
214
|
-
|
|
214
|
+
// The file number as a string: NTFS file IDs pass 2^53, where a Number loses the low bits, and JSON has no BigInt.
|
|
215
|
+
const st = fstatSync(fd, { bigint: true });
|
|
216
|
+
const size = Number(st.size);
|
|
217
|
+
const ino = String(st.ino);
|
|
215
218
|
const file = cacheFile(cacheDir, transcriptPath);
|
|
216
219
|
const c = loadCache(file);
|
|
217
220
|
// Resume only a cache of these rules, for this file, which has not shrunk since.
|
|
@@ -35,7 +35,7 @@ Target: **long edge = 2400 px** (just under cap, leaves headroom).
|
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
ls -lh <pdf-path>
|
|
38
|
-
python3 -c "import fitz; d=fitz.open('<pdf-path>'); print('pages=', d.page_count)"
|
|
38
|
+
(cd ~ && python3 -c "import fitz; d=fitz.open('<absolute-pdf-path>'); print('pages=', d.page_count)") # from home: -c, -m and a stdin script import from the current folder first
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
### 2. Choose output directory
|
|
@@ -49,7 +49,7 @@ Convention: place PNGs **next to** existing design assets, in a subfolder named
|
|
|
49
49
|
PyMuPDF is the most reliable single-dependency path (no Poppler, no ImageMagick).
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
python3 - <<'PY'
|
|
52
|
+
(cd ~ && python3 - <<'PY'
|
|
53
53
|
import fitz, os
|
|
54
54
|
SRC = "<absolute-pdf-path>"
|
|
55
55
|
OUT = "<absolute-output-dir>"
|
|
@@ -63,9 +63,12 @@ for i, page in enumerate(doc, start=1):
|
|
|
63
63
|
pix.save(f"{OUT}/page-{i:03d}.png")
|
|
64
64
|
print(f"[{i}/{doc.page_count}] page-{i:03d}.png {pix.width}x{pix.height}")
|
|
65
65
|
PY
|
|
66
|
+
)
|
|
66
67
|
```
|
|
67
68
|
|
|
68
|
-
Install if missing: `python3 -m pip install --user pymupdf`
|
|
69
|
+
Install if missing: `(cd ~ && python3 -m pip install --user pymupdf)`
|
|
70
|
+
|
|
71
|
+
Offline (no network): do not install; look for the fallback tools of section 4 (`pdftoppm`, ImageMagick) instead and say "오프라인이라 설치하지 않음(필요한 것: pymupdf)". pip retries 5 times with a 15 s timeout by default before it fails.
|
|
69
72
|
|
|
70
73
|
### 4. Fallback toolchain
|
|
71
74
|
|
|
@@ -83,7 +86,7 @@ If Python/PyMuPDF unavailable:
|
|
|
83
86
|
|
|
84
87
|
```bash
|
|
85
88
|
ls <OUT> | wc -l # page count matches PDF
|
|
86
|
-
python3 -c "from PIL import Image; im=Image.open('<
|
|
89
|
+
(cd ~ && python3 -c "from PIL import Image; im=Image.open('<absolute-output-dir>/page-001.png'); print(im.size)")
|
|
87
90
|
```
|
|
88
91
|
|
|
89
92
|
Expect long edge ≈ 2400 px.
|
|
@@ -110,10 +113,12 @@ For multiple pages in one turn, batch multiple `Read` calls in parallel.
|
|
|
110
113
|
For a hybrid pipeline (visual + searchable text), also extract markdown:
|
|
111
114
|
|
|
112
115
|
```bash
|
|
113
|
-
python3 -m pip install --user pymupdf4llm
|
|
114
|
-
python3 -c "import pymupdf4llm; print(pymupdf4llm.to_markdown('<pdf>'))" > <OUT>/text.md
|
|
116
|
+
(cd ~ && python3 -m pip install --user pymupdf4llm)
|
|
117
|
+
(cd ~ && python3 -c "import pymupdf4llm; print(pymupdf4llm.to_markdown('<absolute-pdf-path>'))") > <OUT>/text.md
|
|
115
118
|
```
|
|
116
119
|
|
|
120
|
+
The install needs the network too; offline, leave the markdown companion out and say so.
|
|
121
|
+
|
|
117
122
|
Then `grep` the `.md` for labels and cross-reference with the PNGs.
|
|
118
123
|
|
|
119
124
|
## Example invocation history
|