@kaydash9999/banker-plugins 0.20.1 → 0.20.2
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 +16 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/skills/payload-mon/SKILL.md +2 -1
- package/skills/payload-mon/scripts/payload-mon.mjs +52 -21
- package/skills/setup-omc-hud/SKILL.md +8 -3
- package/skills/setup-omc-hud/assets/apply-hud.mjs +164 -25
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "banker",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "Claude Code plugin: QA/audit/docs/architecture skills + commands, a /banker:setup installer for OS dependencies (playwright, etc.), and wiki compaction.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "smallOpenSource",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.20.2] - 2026-10-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `setup-omc-hud` 설치 스크립트가 끝에 상태표시줄 래퍼를 입력 없이 한 번 돌려, OMC 를 불러오는지와 어느 판이 그리는지 알린다(`Render check`). 새 OMC 판이 불러오기에 실패해 옛 판으로 넘어가면 그 이유와 함께 알린다. 네트워크는 쓰지 않고, 설치 결과(종료 코드)는 바꾸지 않는다.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- 유지보수자용: payload-mon `status` 의 모듈 사본 없음 분기와 래퍼 v4 판별 꼴(주석의 `PL:` 글자)을 시험하고, 난수 대체 시험의 주석을 바로잡는다.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `setup-omc-hud` 를 다시 설치할 때마다 래퍼와 `settings.json` 의 백업이 하나씩 쌓이던 것. payload-mon, claude-update-last 블록만 다른 래퍼와 바뀌는 내용이 없는 `settings.json` 은 이제 백업하지 않는다.
|
|
13
|
+
- `setup-omc-hud` 설치 스크립트가 빈 `settings.json` 을 "JSON 객체가 아님" 으로 거부하던 것. Claude Code 처럼 `{}` 로 읽는다.
|
|
14
|
+
- `setup-omc-hud` 설치 스크립트가 주석이나 끝 쉼표가 든 `settings.json` 을 거부할 때 이유를 말하지 않던 것. 이제 그 이유와, Claude Code 도 이 파일을 읽지 못한다는 것을 알린다. Node 가 위치를 알려 주면 줄과 칸도 적는다. 파일 내용은 출력에 넣지 않는다.
|
|
15
|
+
- `payload-mon` 의 `on`, `off` 가 BOM 으로 시작하는 래퍼를 "node --check 실패" 로 거부하던 것. 그 래퍼는 node 로 정상 실행된다.
|
|
16
|
+
- `payload-mon` 블록이 CRLF 래퍼에 LF 줄끝으로 들어가던 것. 이제 래퍼의 줄끝을 따른다. 편집기가 래퍼 전체를 CRLF 로 저장한 뒤 `status` 가 "켜짐이 불완전" 이라 하던 것도 고친다. Windows 에서 Git 기본 설정으로 받은 설치본이 이런 꼴일 수 있다.
|
|
17
|
+
- 래퍼 파일만 다른 폴더로 링크하면(dotfiles 등) 래퍼 v4 의 `PL:` 칸이 보이지 않는데 `on` 과 `status` 는 PL 칸을 안내하던 것. 모듈 사본을 링크가 가리키는 파일 옆에 둔다. 0.20.1 로 켠 이런 설치는 `status` 가 불완전으로 알리고, `on` 한 번으로 고쳐진다.
|
|
18
|
+
|
|
3
19
|
## [0.20.1] - 2026-10-19
|
|
4
20
|
|
|
5
21
|
### Changed
|
package/README.md
CHANGED
|
@@ -180,7 +180,7 @@ USER_RESOURCES 가이드의 공통 요소를 OS별·런타임별(Claude Code/Cod
|
|
|
180
180
|
| `setup-omc` | oh-my-claudecode(OMC) 설치·갱신 (Codex는 OMX) |
|
|
181
181
|
| `setup-harness-factory` | revfactory/harness 팀 아키텍처 팩토리 설치·구성·사용안내 (Codex=meta-harness) |
|
|
182
182
|
| `setup-playwright` | Playwright + headless 브라우저 (RHEL8/Rocky8·non-root·no-conda 폴백) |
|
|
183
|
-
| `setup-omc-hud` | omc_hud 상태표시줄(래퍼 v4). 스킬에 든 파일로 오프라인 설치, 모든 OS 같은
|
|
183
|
+
| `setup-omc-hud` | omc_hud 상태표시줄(래퍼 v4). 스킬에 든 파일로 오프라인 설치, 모든 OS 같은 명령, 설치 직후 렌더 확인 |
|
|
184
184
|
| `setup-insane-search` | insane-search 플러그인 설치 (Claude·Codex) |
|
|
185
185
|
| `setup-stitch` | Stitch 디자인 MCP 프록시 등록(RockyLinux8 proxy) |
|
|
186
186
|
| `docs-setup` | arch-diagram·pdf-vision-extract 의존성(pptx·pymupdf·plantuml) 설치 |
|
|
@@ -247,7 +247,7 @@ banker uninstall # 제거
|
|
|
247
247
|
| `setup-mcp` | `claude mcp add` (context7·seq-thinking·filesystem·git·fetch) | `[mcp_servers.*]` |
|
|
248
248
|
| `setup-lsp` | LSP MCP 등록 | `[mcp_servers.lsp_bridge]` |
|
|
249
249
|
| `setup-stitch` | `claude mcp add stitch` | `codex mcp add stitch` |
|
|
250
|
-
| `setup-omc-hud` | `statusLine`(`settings.json` 백업) + `hud/omc-hud-custom.mjs`, `hud/omc-hud.mjs`, `.omc/hud-config.json`(바뀐 파일은 `.backup.<시각>` 백업) + payload-mon 켜기. 옛 판이 넣은 갱신 안내 블록은 `off` 로 원복 | — (Codex는 OMX `hud`) |
|
|
250
|
+
| `setup-omc-hud` | `statusLine`(바뀔 때 `settings.json` 백업) + `hud/omc-hud-custom.mjs`, `hud/omc-hud.mjs`, `.omc/hud-config.json`(바뀐 파일은 `.backup.<시각>` 백업) + payload-mon 켜기. 옛 판이 넣은 갱신 안내 블록은 `off` 로 원복 | — (Codex는 OMX `hud`) |
|
|
251
251
|
| `setup-pwsh` | `env.CLAUDE_CODE_GIT_BASH_PATH` (병합) | — (네이티브 셸, 배선 불필요) |
|
|
252
252
|
| `setup-harness-factory` | `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` (env 영속) | — |
|
|
253
253
|
| `smart-compact` | `statusLine.command` 래핑 + `UserPromptSubmit` 훅 추가 (백업·멱등) | `~/.codex/` 대응 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaydash9999/banker-plugins",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "banker — Claude Code + Codex CLI plugin (QA, audit, docs, architecture, wiki skills). Install via Claude Code marketplace, or `npm i -g @kaydash9999/banker-plugins` then `banker setup`.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"banker": "bin/banker.js"
|
|
@@ -40,6 +40,7 @@ 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
46
|
- `off`: 그 블록만 빼서 래퍼를 켜기 전 상태로 되돌리고, 모듈 사본과 추정치 캐시를 지운다. 래퍼 v4 에서는 모듈 사본이 없어져 `PL:` 칸도 사라진다.
|
|
@@ -59,7 +60,7 @@ 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/` 에 쓸 수 없다(권한, 같은 이름의 파일, 디스크 공간). 래퍼는 그대로다.
|
|
63
64
|
- "표시(>>> / <<<)의 짝이 맞지 않습니다": 래퍼에 payload-mon 표시 줄이 한쪽만 남았다. `on`·`off` 모두 거부하므로, 손으로 고치지 말라는 원칙의 유일한 예외다.
|
|
64
65
|
`setup-omc-hud` 로 HUD 를 재설치하거나, 래퍼에서 짝 없이 남은 `// >>> payload-mon >>>` 또는 `// <<< payload-mon <<<` 줄을 지운 뒤 다시 실행한다.
|
|
65
66
|
- HUD 를 재설치한 뒤 payload 가 안 보이면 `on` 을 다시 실행한다. `setup-omc-hud` 는 설치할 때마다 이 스킬을 늘 켠다(래퍼 v4 의 `PL:` 칸이 모듈 사본을 씀).
|
|
@@ -25,6 +25,12 @@
|
|
|
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.
|
|
30
|
+
*
|
|
31
|
+
* The block takes the ctx line's line ending, so a CRLF wrapper stays CRLF, and
|
|
32
|
+
* a block whose line endings an editor rewrote still counts as on. A wrapper
|
|
33
|
+
* that starts with a BOM is taken as it is.
|
|
28
34
|
*
|
|
29
35
|
* `on` converges on the current block and is a no-op when it is in place. It
|
|
30
36
|
* writes only into the OMC custom HUD (signature-gated); `off` removes only
|
|
@@ -39,6 +45,7 @@ import {
|
|
|
39
45
|
accessSync,
|
|
40
46
|
constants,
|
|
41
47
|
existsSync,
|
|
48
|
+
lstatSync,
|
|
42
49
|
mkdirSync,
|
|
43
50
|
mkdtempSync,
|
|
44
51
|
readdirSync,
|
|
@@ -86,13 +93,29 @@ const DEFAULTS = {
|
|
|
86
93
|
sessionId: process.env.CLAUDE_CODE_SESSION_ID,
|
|
87
94
|
};
|
|
88
95
|
|
|
89
|
-
//
|
|
90
|
-
|
|
96
|
+
// The file a wrapper that is itself a symlink (dotfiles) points at; any other path as it is. A wrapper
|
|
97
|
+
// reached through a linked folder keeps the path as configured.
|
|
98
|
+
function linkTarget(hudFile) {
|
|
99
|
+
try {
|
|
100
|
+
return lstatSync(hudFile).isSymbolicLink() ? realpathSync(hudFile) : hudFile;
|
|
101
|
+
} catch {
|
|
102
|
+
return hudFile;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Where the block's module lives: beside the wrapper, outside any versioned path. For a wrapper file
|
|
106
|
+
// that is a link, beside the file it points at: wrapper v4 resolves its own path through the link and
|
|
107
|
+
// looks there for its PL segment.
|
|
108
|
+
const moduleCopy = (hudFile) => join(dirname(linkTarget(hudFile)), "payload-mon", "payload-size.mjs");
|
|
109
|
+
// Where 0.20.1 put the copy for such a link; `off` clears it too.
|
|
110
|
+
const besideLink = (hudFile) => join(dirname(hudFile), "payload-mon", "payload-size.mjs");
|
|
91
111
|
const moduleUrl = (hudFile) => pathToFileURL(moduleCopy(hudFile)).href;
|
|
92
112
|
|
|
113
|
+
// The ctx line's line ending, which the block takes so that a CRLF wrapper stays CRLF.
|
|
114
|
+
const eolOf = (src) => (CTX_LINE.exec(src)?.[0].endsWith("\r\n") ? "\r\n" : "\n");
|
|
115
|
+
|
|
93
116
|
// Right after ctx, colored like the 5h/wk rates: % on the ctx scale, MB in cyan.
|
|
94
117
|
// Its names are block-scoped, so none can clash with the wrapper's own.
|
|
95
|
-
const placeBlock = (url) =>
|
|
118
|
+
const placeBlock = (url, eol = "\n") =>
|
|
96
119
|
[
|
|
97
120
|
" " + OPEN,
|
|
98
121
|
" // payload-mon: estimated API request payload against the 32MB request limit, shown",
|
|
@@ -110,9 +133,9 @@ const placeBlock = (url) =>
|
|
|
110
133
|
" }",
|
|
111
134
|
" " + CLOSE,
|
|
112
135
|
"",
|
|
113
|
-
].join(
|
|
136
|
+
].join(eol);
|
|
114
137
|
|
|
115
|
-
const insertBlock = (base, url) => base.replace(CTX_LINE, (line) => line + placeBlock(url));
|
|
138
|
+
const insertBlock = (base, url) => base.replace(CTX_LINE, (line) => line + placeBlock(url, eolOf(line)));
|
|
116
139
|
|
|
117
140
|
const count = (text, needle) => text.split(needle).length - 1;
|
|
118
141
|
|
|
@@ -137,11 +160,14 @@ const readBytes = (file) => {
|
|
|
137
160
|
|
|
138
161
|
const refusal = (why, hudFile) => ({ ok: false, message: `${why}. 래퍼를 그대로 두었습니다: ${hudFile}` });
|
|
139
162
|
|
|
140
|
-
// The block exactly as `on` writes it for this URL,
|
|
141
|
-
|
|
142
|
-
|
|
163
|
+
// The line ending ("\n" or "\r\n") of the block that sits, exactly as `on` writes it for this URL,
|
|
164
|
+
// once, right after the ctx line; "" when it does not. Either ending counts as in place, so a block
|
|
165
|
+
// whose line endings an editor rewrote is still on.
|
|
166
|
+
function blockEol(src, url) {
|
|
167
|
+
if (count(src, OPEN) !== 1 || count(src, CLOSE) !== 1) return "";
|
|
143
168
|
const ctx = CTX_LINE.exec(src);
|
|
144
|
-
|
|
169
|
+
if (ctx === null) return "";
|
|
170
|
+
return ["\n", "\r\n"].find((eol) => src.startsWith(placeBlock(url, eol), ctx.index + ctx[0].length)) ?? "";
|
|
145
171
|
}
|
|
146
172
|
|
|
147
173
|
// Why the block cannot go into this wrapper, or "" when it can. Markers that do
|
|
@@ -202,12 +228,14 @@ function syncModule(o) {
|
|
|
202
228
|
return true;
|
|
203
229
|
}
|
|
204
230
|
|
|
205
|
-
// The first lines of node's complaint about `text` as an ES module, "" if it parses.
|
|
231
|
+
// The first lines of node's complaint about `text` as an ES module, "" if it parses. A leading BOM
|
|
232
|
+
// is left out: node runs a wrapper that starts with one, but `node --check` rejects a BOM before a
|
|
233
|
+
// shebang (Node 20, 24).
|
|
206
234
|
function syntaxError(text) {
|
|
207
235
|
const dir = mkdtempSync(join(tmpdir(), "payload-mon-"));
|
|
208
236
|
try {
|
|
209
237
|
const file = join(dir, "check.mjs");
|
|
210
|
-
writeFileSync(file, text);
|
|
238
|
+
writeFileSync(file, text.replace(/^\uFEFF/, ""));
|
|
211
239
|
const r = spawnSync(process.execPath, ["--check", file], { encoding: "utf8" });
|
|
212
240
|
return r.status === 0 ? "" : String(r.stderr || r.error).trim().split("\n").slice(0, 3).join(" | ");
|
|
213
241
|
} finally {
|
|
@@ -257,7 +285,7 @@ const showsPL = (src) => src !== null && PL_SEGMENT.test(src);
|
|
|
257
285
|
// Where the size shows, named only while it does: the block in place and a
|
|
258
286
|
// module copy to import. "" otherwise, so status never promises a segment.
|
|
259
287
|
function displayWhere(o, src) {
|
|
260
|
-
if (src === null ||
|
|
288
|
+
if (src === null || blockEol(src, moduleUrl(o.hudFile)) === "" || copyState(o) === "missing") return "";
|
|
261
289
|
return showsPL(src) ? "PL 칸에 늘 표시" : `${SHOW_FROM_BYTES / MB}MB부터 표시`;
|
|
262
290
|
}
|
|
263
291
|
|
|
@@ -265,7 +293,8 @@ export function turnOn(opts = {}) {
|
|
|
265
293
|
const o = { ...DEFAULTS, ...opts };
|
|
266
294
|
const src = readText(o.hudFile);
|
|
267
295
|
if (src === null) return { ok: false, message: `HUD 래퍼가 없습니다: ${o.hudFile}` };
|
|
268
|
-
|
|
296
|
+
// In place with the wrapper's own line ending; a block whose ending differs is written again.
|
|
297
|
+
if (blockEol(src, moduleUrl(o.hudFile)) === eolOf(src)) {
|
|
269
298
|
try {
|
|
270
299
|
const refreshed = syncModule(o);
|
|
271
300
|
return { ok: true, message: (refreshed ? "모듈 사본을 이 버전으로 맞췄습니다." : "이미 켜져 있습니다 (변경 없음).") + notShownNote(o) };
|
|
@@ -292,20 +321,22 @@ function removeBlocks(o, src) {
|
|
|
292
321
|
|
|
293
322
|
// What off leaves behind otherwise: the cache (when this account controls its
|
|
294
323
|
// folder), and the module copy with its folder (only when nothing else is in
|
|
295
|
-
// it).
|
|
324
|
+
// it), also the one 0.20.1 left beside a linked wrapper. None failing undoes off.
|
|
296
325
|
function removeLeftovers(o) {
|
|
297
|
-
const
|
|
298
|
-
for (const remove of [() => clearCache(o.cacheDir), () => rmSync(copy, { force: true })]) {
|
|
326
|
+
const copies = [...new Set([moduleCopy(o.hudFile), besideLink(o.hudFile)])];
|
|
327
|
+
for (const remove of [() => clearCache(o.cacheDir), ...copies.map((copy) => () => rmSync(copy, { force: true }))]) {
|
|
299
328
|
try {
|
|
300
329
|
remove();
|
|
301
330
|
} catch {
|
|
302
331
|
/* stale numbers or an unused copy; off stands */
|
|
303
332
|
}
|
|
304
333
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
334
|
+
for (const copy of copies) {
|
|
335
|
+
try {
|
|
336
|
+
rmdirSync(dirname(copy));
|
|
337
|
+
} catch {
|
|
338
|
+
/* absent, or holds something that is not ours */
|
|
339
|
+
}
|
|
309
340
|
}
|
|
310
341
|
}
|
|
311
342
|
|
|
@@ -346,7 +377,7 @@ function stateOf(src, o) {
|
|
|
346
377
|
if (count(src, OPEN) + count(src, CLOSE) === 0) return "꺼짐";
|
|
347
378
|
// on and off both refuse this, so pointing at on would be a dead end.
|
|
348
379
|
if (count(src, OPEN) !== count(src, CLOSE)) return "표시(>>> / <<<)의 짝이 맞지 않습니다 (on·off 모두 거부: HUD 재설치 또는 남은 표시 줄 정리)";
|
|
349
|
-
if (
|
|
380
|
+
if (blockEol(src, moduleUrl(o.hudFile)) === "") return "켜짐이 불완전하거나 오래됨 (on 으로 복구)";
|
|
350
381
|
return "켜짐" + COPY_NOTE[copyState(o)];
|
|
351
382
|
}
|
|
352
383
|
|
|
@@ -72,13 +72,14 @@ node "<이 스킬 디렉터리 절대경로>\assets\apply-hud.mjs"
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
- `[ok] statusLine -> node "<설정 폴더>/hud/omc-hud-custom.mjs"` 줄을 확인한다.
|
|
75
|
+
- `Render check` 줄을 확인한다. `[ok]` 면 정상이다. `[!!]` 나 `[..]` 면 그 줄을 그대로 보고한다. 이때도 설치는 끝났다(종료 0).
|
|
75
76
|
- 기존 statusLine 이 다른 명령이었으면 `[..] statusLine ran ...` 줄이 나온다. 보고에 그대로 넣는다.
|
|
76
77
|
- 종료 코드 1 이면 출력의 `[x]` 줄을 그대로 보고하고 멈춘다. 이때 바뀐 파일은 없다.
|
|
77
|
-
- `settings.json` 이 JSON 객체가 아니면 사용자가 고친 뒤 다시 실행한다.
|
|
78
|
+
- `settings.json` 이 JSON 문법에 맞지 않거나(주석, 끝 쉼표 포함) 객체가 아니면 사용자가 고친 뒤 다시 실행한다. 빈 파일은 Claude Code 처럼 `{}` 로 읽는다.
|
|
78
79
|
- OMC 가 없으면 OMC 를 먼저 설치한다(`setup-omc` 스킬). 이 스킬은 OMC 를 설치하지 않는다.
|
|
79
80
|
- 설정 폴더 경로에 셸이 해석하는 글자가 있으면 그 글자가 없는 폴더를 쓰게 안내한다.
|
|
80
81
|
- 쓸 수 없는 파일이나 폴더가 있으면 권한을 고친 뒤 다시 실행한다.
|
|
81
|
-
- 종료 코드 2 면 예기치 못한 오류로 중간에 멈춘 것이다. 출력을 그대로 보고하고,
|
|
82
|
+
- 종료 코드 2 면 예기치 못한 오류로 중간에 멈춘 것이다. 출력을 그대로 보고하고, 바꾼 파일의 이전 판은 `<파일>.backup.<시각>` 에 있다고 알린다. 로더와 도구 블록은 백업하지 않는다.
|
|
82
83
|
|
|
83
84
|
## 2. payload-mon 켜기
|
|
84
85
|
|
|
@@ -89,12 +90,13 @@ node "<이 스킬 디렉터리 절대경로>/../payload-mon/scripts/payload-mon.
|
|
|
89
90
|
```
|
|
90
91
|
|
|
91
92
|
- Codex 사본에서 실행할 때 경로는 `<이 스킬 디렉터리 절대경로>/../banker-payload-mon/scripts/payload-mon.mjs` 다.
|
|
92
|
-
- 이유: `PL:` 칸은 payload-mon 이 래퍼 옆(`hud/payload-mon/`)에 두는 크기 계산 모듈을 쓴다. 모듈이 없으면 `PL:` 이 나오지 않는다.
|
|
93
|
+
- 이유: `PL:` 칸은 payload-mon 이 래퍼 옆(`hud/payload-mon/`)에 두는 크기 계산 모듈을 쓴다. 모듈이 없으면 `PL:` 이 나오지 않는다. 래퍼 파일이 링크면 링크가 가리키는 파일 옆에 둔다.
|
|
93
94
|
- 출력 `켰습니다. 다음 statusline 갱신부터 PL 칸에 요청 크기가 늘 표시됩니다.` 를 확인한다. `이미 켜져 있습니다`, `모듈 사본을 이 버전으로 맞췄습니다` 도 정상이다.
|
|
94
95
|
|
|
95
96
|
## 3. 보고
|
|
96
97
|
|
|
97
98
|
- 1단계 출력의 statusLine 줄과 백업 경로
|
|
99
|
+
- 1단계의 `Render check` 줄
|
|
98
100
|
- 2단계 결과
|
|
99
101
|
- statusLine 이 바뀌었으면 Claude Code 재시작 안내
|
|
100
102
|
|
|
@@ -113,6 +115,7 @@ node "<이 스킬 디렉터리 절대경로>/scripts/claude-update-last.mjs" off
|
|
|
113
115
|
## 함정
|
|
114
116
|
|
|
115
117
|
- 다시 설치하면 래퍼와 `hud-config.json` 을 첨부 파일로 덮어쓴다. 손으로 고친 내용은 `.backup.<시각>` 에만 남는다.
|
|
118
|
+
- payload-mon, claude-update-last 블록만 다른 래퍼와 바뀌는 내용이 없는 `settings.json` 은 백업하지 않는다.
|
|
116
119
|
- `settings.json` 의 `omcHud` 값은 `hud-config.json` 의 같은 키를 덮어쓴다. 래퍼가 기대는 아래 키를 바꾸면 칸이 달라지거나 OMC 원문이 그대로 나온다.
|
|
117
120
|
- `omcLabel`, `useBars`
|
|
118
121
|
- `maxWidth`, `maxOutputLines`
|
|
@@ -132,3 +135,5 @@ node "<이 스킬 디렉터리 절대경로>/scripts/claude-update-last.mjs" off
|
|
|
132
135
|
- 설치는 네트워크를 쓰지 않는다. 다만 OMC 가 렌더할 때 네트워크로 얻는 정보(새 Claude Code 판 같은)는 오프라인에서 빠질 수 있다.
|
|
133
136
|
- 래퍼, HUD 설정, 모듈 사본은 플러그인 밖(`<설정 폴더>/hud/`, `<설정 폴더>/.omc/`)에 있다. 플러그인을 지워도 상태표시줄은 남는다.
|
|
134
137
|
- OMC 를 지울 때는 statusLine 도 지운다. OMC 로더는 플러그인을 못 찾으면 작업 폴더의 `node_modules` 에서 OMC HUD 를 찾아 불러오기 때문이다.
|
|
138
|
+
- 설치 끝의 렌더 확인은 래퍼를 입력 없이 한 번 돌려, OMC 진단의 첫 줄(`[OMC] HUD v<판> | preset: <이름>`)로 OMC 를 불러왔는지와 그린 판을 본다. 네트워크는 쓰지 않는다.
|
|
139
|
+
- 렌더 확인이 OMC 를 불러오지 못한다거나 옛 판이 그린다고 하면 OMC 쪽 문제다. `/oh-my-claudecode:omc-setup` 은 고치면서 statusLine 을 OMC 명령으로 되돌리므로, 쓴 뒤에는 이 스킬을 다시 실행한다. omc-patch 를 쓰는 PC 는 omc-patch 도 다시 적용한다.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - hud/lib/config-dir.mjs (loader dependency)
|
|
8
8
|
* - hud/omc-hud-custom.mjs (the custom compact/colored formatter)
|
|
9
9
|
* - .omc/hud-config.json (HUD preset/elements)
|
|
10
|
-
* - settings.json (statusLine -> the custom formatter;
|
|
10
|
+
* - settings.json (statusLine -> the custom formatter; backed up when it changes)
|
|
11
11
|
*
|
|
12
12
|
* Runs identically on Linux, macOS, and Windows (only needs node + an OMC install).
|
|
13
13
|
* Run it directly with `node apply-hud.mjs` on every OS; the templates are read
|
|
@@ -15,16 +15,23 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Based on smallOpenSource/omc_hud d3cedd5 (MIT, see LICENSE-omc_hud). Changes:
|
|
17
17
|
* statusLine path quoted; a settings.json that is not a JSON object is refused
|
|
18
|
-
* before any file changes (the original replaced it)
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* before any file changes (the original replaced it), naming the line and
|
|
19
|
+
* column when Node reports one but never quoting it; one holding only white
|
|
20
|
+
* space is read as {}, as Claude Code does; UTF-8 BOM accepted; CLAUDE_CONFIG_DIR trimmed, "~" expanded
|
|
21
|
+
* and made absolute; formatter and HUD config backed up when they differ (the
|
|
22
|
+
* blocks payload-mon and claude-update-last add do not count), settings.json only
|
|
23
|
+
* when it changes; pre-releases sort below their release; no
|
|
21
24
|
* folder is made when OMC is missing. Also refused before any change: a config
|
|
22
25
|
* folder path the statusLine shell would act on, a file or folder it cannot
|
|
23
26
|
* write, an OMC folder without both loader files, a relative OMC_PLUGIN_ROOT.
|
|
24
27
|
* Backups keep the original bytes and mode; the files it writes are 0644 and new
|
|
25
28
|
* folders 0755; the other statusLine fields (padding, ...) stay.
|
|
29
|
+
* At the end it runs the installed formatter once with no input and reports
|
|
30
|
+
* whether OMC's HUD loads and which version draws (the render check); that never
|
|
31
|
+
* changes the exit code. APPLY_HUD_CHECK_TIMEOUT_MS (tests) shortens its 25s limit.
|
|
26
32
|
* Exit 1 means nothing changed; exit 2 means an unexpected error stopped it partway.
|
|
27
33
|
*/
|
|
34
|
+
import { spawnSync } from "node:child_process";
|
|
28
35
|
import {
|
|
29
36
|
accessSync,
|
|
30
37
|
chmodSync,
|
|
@@ -34,10 +41,11 @@ import {
|
|
|
34
41
|
mkdirSync,
|
|
35
42
|
readdirSync,
|
|
36
43
|
readFileSync,
|
|
44
|
+
statSync,
|
|
37
45
|
writeFileSync,
|
|
38
46
|
} from "node:fs";
|
|
39
47
|
import { homedir } from "node:os";
|
|
40
|
-
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
48
|
+
import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
41
49
|
import { fileURLToPath } from "node:url";
|
|
42
50
|
|
|
43
51
|
const SELF = dirname(fileURLToPath(import.meta.url));
|
|
@@ -72,18 +80,42 @@ const refuse = (lines) => {
|
|
|
72
80
|
// file as it was. Refuse rather than replace: a fresh file would drop every
|
|
73
81
|
// other setting. A BOM (Windows editors) is dropped when the file is written
|
|
74
82
|
// back; the backup keeps the original bytes.
|
|
83
|
+
// Read as Claude Code (2.1.303) reads it: white space only is {}, and JSON
|
|
84
|
+
// with comments or trailing commas is invalid. A refusal says where the JSON
|
|
85
|
+
// breaks (from Node's "at position N") but never quotes it: Node's own message
|
|
86
|
+
// does, and the file can hold tokens.
|
|
87
|
+
function jsonErrorAt(text, message) {
|
|
88
|
+
const m = /at position (\d+)/.exec(message);
|
|
89
|
+
if (!m) return "";
|
|
90
|
+
const before = text.slice(0, Number(m[1]));
|
|
91
|
+
return `line ${before.split("\n").length}, column ${before.length - before.lastIndexOf("\n")}`;
|
|
92
|
+
}
|
|
93
|
+
const kindOf = (v) => (v === null ? "null" : Array.isArray(v) ? "an array" : `a ${typeof v}`);
|
|
94
|
+
function readSettings(raw) {
|
|
95
|
+
const text = raw.replace(/^\uFEFF/, "");
|
|
96
|
+
if (text.trim() === "") return { settings: {}, empty: true };
|
|
97
|
+
let value;
|
|
98
|
+
try {
|
|
99
|
+
value = JSON.parse(text);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
const at = jsonErrorAt(text, String(e && e.message));
|
|
102
|
+
return {
|
|
103
|
+
refusal:
|
|
104
|
+
`[x] settings.json is not valid JSON${at ? ` (${at})` : ""}; nothing changed. ` +
|
|
105
|
+
"Claude Code cannot read it either: comments and trailing commas are not allowed. Fix it and re-run.",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return { settings: value, empty: false };
|
|
109
|
+
return { refusal: `[x] settings.json holds ${kindOf(value)}, not a JSON object; nothing changed. Fix it and re-run.` };
|
|
110
|
+
}
|
|
75
111
|
let settings = {};
|
|
76
112
|
let settingsRaw = null;
|
|
77
113
|
if (existsSync(settingsPath)) {
|
|
78
114
|
settingsRaw = readFileSync(settingsPath, "utf8");
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
if (!settings || typeof settings !== "object" || Array.isArray(settings)) {
|
|
85
|
-
refuse(["[x] Existing settings.json is not a JSON object; nothing changed. Fix it and re-run."]);
|
|
86
|
-
}
|
|
115
|
+
const read = readSettings(settingsRaw);
|
|
116
|
+
if (read.refusal) refuse([read.refusal]);
|
|
117
|
+
settings = read.settings;
|
|
118
|
+
if (read.empty) log("[..] settings.json is empty; read as {} as Claude Code does");
|
|
87
119
|
}
|
|
88
120
|
|
|
89
121
|
// Inside the command's double quotes these still act: a quote ends the path, $ and `
|
|
@@ -166,10 +198,23 @@ function writeOwned(file, data) {
|
|
|
166
198
|
if (process.platform !== "win32") chmodSync(file, FILE_MODE);
|
|
167
199
|
}
|
|
168
200
|
|
|
201
|
+
// The marker blocks payload-mon and claude-update-last add to the installed formatter, matched as those
|
|
202
|
+
// tools match them (payload-mon.mjs BLOCKS, claude-update-last.mjs's block pattern). Read as latin1,
|
|
203
|
+
// which maps every byte to one character, so text outside the blocks compares byte for byte.
|
|
204
|
+
const TOOL_BLOCKS = [
|
|
205
|
+
/^[ \t]*\/\/ >>> payload-mon >>>[\s\S]*?\/\/ <<< payload-mon <<<[ \t]*\r?\n/gm,
|
|
206
|
+
/^[ \t]*\/\/ >>> claude-update-last >>>(?:(?!\/\/ >>> claude-update-last >>>)[\s\S])*?\/\/ <<< claude-update-last <<<[ \t]*(?:\r?\n|(?![\s\S]))/gm,
|
|
207
|
+
];
|
|
208
|
+
const withoutToolBlocks = (text) => TOOL_BLOCKS.reduce((t, blocks) => t.replace(blocks, ""), text);
|
|
209
|
+
const sameBytes = (a, b) => a.equals(b);
|
|
210
|
+
// Those blocks alone are no reason for a backup: `payload-mon on` puts its block back, and wrapper v4
|
|
211
|
+
// needs no claude-update-last block.
|
|
212
|
+
const sameFormatter = (a, b) => withoutToolBlocks(a.toString("latin1")) === withoutToolBlocks(b.toString("latin1"));
|
|
213
|
+
|
|
169
214
|
// Keep a timestamped copy, with its bytes and mode, of a file that differs from what replaces it.
|
|
170
|
-
function backupIfDifferent(dst, next, label) {
|
|
215
|
+
function backupIfDifferent(dst, next, label, same = sameBytes) {
|
|
171
216
|
if (!existsSync(dst)) return;
|
|
172
|
-
if (readFileSync(dst)
|
|
217
|
+
if (same(readFileSync(dst), next)) return;
|
|
173
218
|
const keep = dst + ".backup." + stamp;
|
|
174
219
|
copyFileSync(dst, keep);
|
|
175
220
|
log(`[ok] Backed up ${label} -> ${keep}`);
|
|
@@ -188,12 +233,12 @@ function installLoader() {
|
|
|
188
233
|
}
|
|
189
234
|
|
|
190
235
|
// 3. Install the custom formatter and HUD config from the bundled templates.
|
|
191
|
-
// Files that differ from the templates (local edits
|
|
192
|
-
// payload-mon / claude-update-last
|
|
236
|
+
// Files that differ from the templates (local edits) are kept as timestamped
|
|
237
|
+
// backups first; the blocks payload-mon / claude-update-last add do not count.
|
|
193
238
|
function installTemplates() {
|
|
194
239
|
const wrapperSrc = readFileSync(join(SELF, "omc-hud-custom.mjs"));
|
|
195
240
|
const configSrc = readFileSync(join(SELF, "hud-config.json"));
|
|
196
|
-
backupIfDifferent(wrapperDst, wrapperSrc, "previous formatter");
|
|
241
|
+
backupIfDifferent(wrapperDst, wrapperSrc, "previous formatter", sameFormatter);
|
|
197
242
|
backupIfDifferent(configDst, configSrc, "previous HUD config");
|
|
198
243
|
writeOwned(wrapperDst, wrapperSrc);
|
|
199
244
|
writeOwned(configDst, configSrc);
|
|
@@ -202,13 +247,9 @@ function installTemplates() {
|
|
|
202
247
|
}
|
|
203
248
|
|
|
204
249
|
// 4. Point settings.json statusLine at the custom formatter; its other fields (padding, ...) stay.
|
|
250
|
+
// A settings.json that already says exactly this is neither backed up nor written.
|
|
205
251
|
const objectOr = (v) => (v && typeof v === "object" && !Array.isArray(v) ? v : {});
|
|
206
252
|
function pointStatusLine() {
|
|
207
|
-
if (settingsRaw !== null) {
|
|
208
|
-
const backup = settingsPath + ".backup." + stamp;
|
|
209
|
-
copyFileSync(settingsPath, backup); // the original bytes and mode
|
|
210
|
-
log("[ok] Backed up settings.json -> " + backup);
|
|
211
|
-
}
|
|
212
253
|
// Quoted so a config dir with spaces (C:/Users/First Last/.claude) still runs.
|
|
213
254
|
const command = `node "${cmdPath}"`;
|
|
214
255
|
const prior = objectOr(settings.statusLine);
|
|
@@ -216,19 +257,117 @@ function pointStatusLine() {
|
|
|
216
257
|
log(`[..] statusLine ran ${JSON.stringify(prior.command)}; it now runs the custom HUD (the old value is in the backup)`);
|
|
217
258
|
}
|
|
218
259
|
settings.statusLine = { ...prior, type: "command", command };
|
|
219
|
-
|
|
260
|
+
const next = JSON.stringify(settings, null, 2) + "\n";
|
|
261
|
+
if (next === settingsRaw) {
|
|
262
|
+
log(`[ok] statusLine -> ${command} (settings.json unchanged, no backup)`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (settingsRaw !== null) {
|
|
266
|
+
const backup = settingsPath + ".backup." + stamp;
|
|
267
|
+
copyFileSync(settingsPath, backup); // the original bytes and mode
|
|
268
|
+
log("[ok] Backed up settings.json -> " + backup);
|
|
269
|
+
}
|
|
270
|
+
writeFileSync(settingsPath, next);
|
|
220
271
|
log("[ok] statusLine -> " + command);
|
|
221
272
|
}
|
|
222
273
|
|
|
274
|
+
// 5. Render check: run the installed formatter once with no input, as the statusLine command does,
|
|
275
|
+
// with `node` from PATH. With no input OMC prints its diagnostic, whose first line names the version
|
|
276
|
+
// that loaded ("[OMC] HUD v5.7.4 | preset: full"), and uses no network. Reported only: the install
|
|
277
|
+
// is done either way. The 25s limit outwaits the formatter, which gives up on its own within 20s
|
|
278
|
+
// and then ends its fallback child; killing it sooner would leave that child running.
|
|
279
|
+
// APPLY_HUD_CHECK_TIMEOUT_MS shortens the limit for tests.
|
|
280
|
+
const CHECK_MS = Number(process.env.APPLY_HUD_CHECK_TIMEOUT_MS) || 25000;
|
|
281
|
+
const BLAME =
|
|
282
|
+
" OMC's install is at fault. /oh-my-claudecode:omc-setup repairs it but points statusLine back at OMC, so run setup-omc-hud again after it.";
|
|
283
|
+
const clip = (s, n = 200) => String(s).slice(0, n);
|
|
284
|
+
// No input, and the config folder as working folder: OMC's loader looks in the working folder's
|
|
285
|
+
// node_modules and runs git from it, and Windows looks there first for a bare program name, so the
|
|
286
|
+
// folder the installer was started in (a project, say) must not be it.
|
|
287
|
+
const RUN_OPTS = { input: "", encoding: "utf8", timeout: CHECK_MS, windowsHide: true, cwd: CONFIG_DIR };
|
|
288
|
+
|
|
289
|
+
// node as the statusLine shell finds it, but only in PATH folders given as absolute paths: spawning the
|
|
290
|
+
// bare name would on Windows try the working folder first, where a project's own node.exe could run.
|
|
291
|
+
function findNode(pathEnv) {
|
|
292
|
+
const name = process.platform === "win32" ? "node.exe" : "node";
|
|
293
|
+
for (const entry of (pathEnv || "").split(delimiter)) {
|
|
294
|
+
const dir = entry.replace(/^"(.*)"$/, "$1");
|
|
295
|
+
if (!dir || !isAbsolute(dir)) continue;
|
|
296
|
+
try {
|
|
297
|
+
if (statSync(join(dir, name)).isFile()) return join(dir, name);
|
|
298
|
+
} catch {
|
|
299
|
+
/* not in this folder */
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function omcVersion(root) {
|
|
306
|
+
if (!root) return null;
|
|
307
|
+
try {
|
|
308
|
+
const v = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
|
|
309
|
+
return typeof v === "string" ? v : null;
|
|
310
|
+
} catch {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Why an OMC version does not load: its HUD's own error when run directly.
|
|
316
|
+
function loadReason(node, root) {
|
|
317
|
+
const hud = join(root, "dist", "hud", "index.js");
|
|
318
|
+
if (!existsSync(hud)) return "no dist/hud/index.js";
|
|
319
|
+
const r = spawnSync(node, [hud], RUN_OPTS);
|
|
320
|
+
const line = /^\w*Error\b.*$/m.exec(r.stderr || "");
|
|
321
|
+
return line ? clip(line[0]) : "";
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// What the formatter printed, as report lines.
|
|
325
|
+
function judgeRender(out, node) {
|
|
326
|
+
const banner = /^\[OMC\] HUD v(\S+) \| preset: /m.exec(out);
|
|
327
|
+
if (banner) {
|
|
328
|
+
const installed = omcVersion(pluginRoot);
|
|
329
|
+
if (!installed || installed === banner[1]) return [`[ok] Render check: the statusline loads OMC ${banner[1]}.`];
|
|
330
|
+
const why = loadReason(node, pluginRoot);
|
|
331
|
+
const line = `[!!] Render check: the statusline loads OMC ${banner[1]}, not the installed ${installed}; ${installed} does not load`;
|
|
332
|
+
return [`${line}${why ? ` (${why})` : ""}.`, BLAME];
|
|
333
|
+
}
|
|
334
|
+
// OMC's line ends with the reason, after a path that can be long: keep it whole within reason.
|
|
335
|
+
const failed = /^\[OMC HUD\] .*$/m.exec(out);
|
|
336
|
+
if (failed) return [`[!!] Render check: OMC's HUD does not load: ${clip(failed[0], 1000)}`, BLAME];
|
|
337
|
+
return [`[..] Render check: unrecognized output: ${clip(out.split("\n")[0])}`];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function renderReport() {
|
|
341
|
+
const node = findNode(process.env.PATH);
|
|
342
|
+
if (!node) return ['[!!] Render check: no "node" on PATH; the statusLine command runs "node", so the statusline will not show.'];
|
|
343
|
+
const r = spawnSync(node, [wrapperDst], RUN_OPTS);
|
|
344
|
+
if (r.error && r.error.code === "ETIMEDOUT") return [`[!!] Render check: the statusline gave no answer in ${CHECK_MS / 1000}s.`];
|
|
345
|
+
if (r.error) throw r.error;
|
|
346
|
+
if (r.status !== 0) {
|
|
347
|
+
const err = (r.stderr || "").trim().split("\n")[0];
|
|
348
|
+
return [`[!!] Render check: the statusline command failed (exit ${r.status ?? r.signal})${err ? `: ${clip(err)}` : ""}`];
|
|
349
|
+
}
|
|
350
|
+
return judgeRender((r.stdout || "").replace(/\x1b\[[0-9;]*m/g, ""), node);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function renderCheck() {
|
|
354
|
+
try {
|
|
355
|
+
for (const line of renderReport()) log(line);
|
|
356
|
+
} catch (e) {
|
|
357
|
+
log(`[..] Render check: skipped (${e.code || e.message})`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
223
361
|
try {
|
|
224
362
|
installLoader();
|
|
225
363
|
installTemplates();
|
|
226
364
|
pointStatusLine();
|
|
227
365
|
} catch (e) {
|
|
228
366
|
log(`[x] Stopped partway (${e.code || e.message}); some files may have changed.`);
|
|
229
|
-
log(`
|
|
367
|
+
log(` Earlier versions it replaced, apart from the loader and the tool blocks, are in <file>.backup.${stamp}.`);
|
|
230
368
|
process.exit(2);
|
|
231
369
|
}
|
|
232
370
|
|
|
371
|
+
renderCheck();
|
|
233
372
|
log("");
|
|
234
373
|
log("Done. Restart Claude Code to load the new statusline.");
|