@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
|
@@ -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,29 +15,44 @@
|
|
|
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 unless edited by hand), 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
|
-
* folders 0755; the other statusLine fields (padding, ...) stay.
|
|
28
|
+
* folders 0755; the other statusLine fields (padding, ...) stay. settings.json goes
|
|
29
|
+
* in as a new file through a rename (a link keeps pointing at it), in place where
|
|
30
|
+
* that cannot be done or would part a hard link or change its owner or group, and
|
|
31
|
+
* is left as it was when the disk is too full for the new file.
|
|
32
|
+
* At the end it runs the installed formatter once with no input and reports
|
|
33
|
+
* whether OMC's HUD loads and which version draws (the render check); that never
|
|
34
|
+
* changes the exit code. APPLY_HUD_CHECK_TIMEOUT_MS (tests) shortens its 25s limit.
|
|
26
35
|
* Exit 1 means nothing changed; exit 2 means an unexpected error stopped it partway.
|
|
27
36
|
*/
|
|
37
|
+
import { spawnSync } from "node:child_process";
|
|
28
38
|
import {
|
|
29
39
|
accessSync,
|
|
30
40
|
chmodSync,
|
|
31
41
|
constants,
|
|
32
42
|
copyFileSync,
|
|
33
43
|
existsSync,
|
|
44
|
+
lstatSync,
|
|
34
45
|
mkdirSync,
|
|
35
46
|
readdirSync,
|
|
36
47
|
readFileSync,
|
|
48
|
+
realpathSync,
|
|
49
|
+
renameSync,
|
|
50
|
+
rmSync,
|
|
51
|
+
statSync,
|
|
37
52
|
writeFileSync,
|
|
38
53
|
} from "node:fs";
|
|
39
54
|
import { homedir } from "node:os";
|
|
40
|
-
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
55
|
+
import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
41
56
|
import { fileURLToPath } from "node:url";
|
|
42
57
|
|
|
43
58
|
const SELF = dirname(fileURLToPath(import.meta.url));
|
|
@@ -62,6 +77,27 @@ const configDst = join(OMC_DIR, "hud-config.json");
|
|
|
62
77
|
// PowerShell on Windows) that treats backslashes as escapes.
|
|
63
78
|
const cmdPath = wrapperDst.split(sep).join("/");
|
|
64
79
|
const log = (m) => process.stdout.write(m + "\n");
|
|
80
|
+
// Characters that could act on a terminal or pass for a line of their own are shown as "?", as
|
|
81
|
+
// omc-patch.mjs (UNSAFE_CH) shows file values in output an agent reads.
|
|
82
|
+
const UNSAFE = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{Co}\p{Cn}\p{Variation_Selector}\p{Default_Ignorable_Code_Point}\u{2800}\u{a0}\u{1680}\u{2000}-\u{200a}\u{202f}\u{205f}\u{3000}]/gu;
|
|
83
|
+
const shown = (s) => String(s).replace(UNSAFE, "?");
|
|
84
|
+
// The old statusLine command as the report shows it: its first word (the program; a NAME=value it sets is skipped) and
|
|
85
|
+
// its length. settings.json can hold tokens (see step 0) and the line goes into a report; masking the parts that look
|
|
86
|
+
// like secrets missed every form no rule named (curl -u, sshpass -p, mysql -pPW), so the rest stays in the backup.
|
|
87
|
+
const ASSIGN = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
88
|
+
// Shell words: runs of plain characters, quoted pieces and backslash escapes (a quote or a space after a backslash
|
|
89
|
+
// stays in its word, inside double quotes too), a quote left open running to the end, so a value in quotes stays one
|
|
90
|
+
// word with its name.
|
|
91
|
+
const WORDS = /(?:"(?:[^"\\]|\\.)*"?|'[^']*'?|\\[\s\S]?|[^\s"'\\])+/g;
|
|
92
|
+
function priorShown(command) {
|
|
93
|
+
const text = String(command).trim();
|
|
94
|
+
const words = text.match(WORDS) || [];
|
|
95
|
+
const first = words.findIndex((w) => !ASSIGN.test(w));
|
|
96
|
+
// a first word that looks like a setting or an address may hold a secret itself
|
|
97
|
+
const name = first === -1 ? "(no program)" : /[=@]|:\/\//.test(words[first]) ? "(program hidden)" : words[first];
|
|
98
|
+
const head = `${name}${first !== -1 && first < words.length - 1 ? " …" : ""}`;
|
|
99
|
+
return `${JSON.stringify(shown(head))} (${text.length} characters)`;
|
|
100
|
+
}
|
|
65
101
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
66
102
|
const refuse = (lines) => {
|
|
67
103
|
for (const l of lines) log(l);
|
|
@@ -72,18 +108,66 @@ const refuse = (lines) => {
|
|
|
72
108
|
// file as it was. Refuse rather than replace: a fresh file would drop every
|
|
73
109
|
// other setting. A BOM (Windows editors) is dropped when the file is written
|
|
74
110
|
// back; the backup keeps the original bytes.
|
|
111
|
+
// Read as Claude Code (2.1.303) reads it: white space only is {}, and JSON
|
|
112
|
+
// with comments or trailing commas is invalid. A refusal says where the JSON
|
|
113
|
+
// breaks (from Node's "at position N") but never quotes it: Node's own message
|
|
114
|
+
// does, and the file can hold tokens.
|
|
115
|
+
function jsonErrorAt(text, message) {
|
|
116
|
+
const m = /at position (\d+)/.exec(message);
|
|
117
|
+
if (!m) return "";
|
|
118
|
+
const before = text.slice(0, Number(m[1]));
|
|
119
|
+
return `line ${before.split("\n").length}, column ${before.length - before.lastIndexOf("\n")}`;
|
|
120
|
+
}
|
|
121
|
+
const kindOf = (v) => (v === null ? "null" : Array.isArray(v) ? "an array" : `a ${typeof v}`);
|
|
122
|
+
function readSettings(raw) {
|
|
123
|
+
const text = raw.replace(/^\uFEFF/, "");
|
|
124
|
+
if (text.trim() === "") return { settings: {}, empty: true };
|
|
125
|
+
let value;
|
|
126
|
+
try {
|
|
127
|
+
value = JSON.parse(text);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
const at = jsonErrorAt(text, String(e && e.message));
|
|
130
|
+
return {
|
|
131
|
+
refusal:
|
|
132
|
+
`[x] settings.json is not valid JSON${at ? ` (${at})` : ""}; nothing changed. ` +
|
|
133
|
+
"Claude Code cannot read it either: comments and trailing commas are not allowed. Fix it and re-run.",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return { settings: value, empty: false };
|
|
137
|
+
return { refusal: `[x] settings.json holds ${kindOf(value)}, not a JSON object; nothing changed. Fix it and re-run.` };
|
|
138
|
+
}
|
|
139
|
+
// A target that is there but no regular file (a folder, a FIFO, a device), or a folder the install writes into that
|
|
140
|
+
// is there but no folder, is refused before anything is read or written: reading or writing a FIFO waits for good,
|
|
141
|
+
// and the others stopped the install half-way. Links are followed, as the reads and writes follow them; a target
|
|
142
|
+
// that is not there (a broken link too) is written new.
|
|
143
|
+
function oddTarget() {
|
|
144
|
+
for (const p of [settingsPath, loaderDst, libDst, wrapperDst, configDst]) {
|
|
145
|
+
try {
|
|
146
|
+
if (!statSync(p).isFile()) return `${shown(p)} is not a regular file (a folder, a FIFO or a device)`;
|
|
147
|
+
} catch {
|
|
148
|
+
/* not there: written new */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const d of [CONFIG_DIR, HUD_DIR, join(HUD_DIR, "lib"), OMC_DIR]) {
|
|
152
|
+
try {
|
|
153
|
+
if (!statSync(d).isDirectory()) return `${shown(d)} is not a folder`;
|
|
154
|
+
} catch {
|
|
155
|
+
/* not there: made */
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
160
|
+
const odd = oddTarget();
|
|
161
|
+
if (odd) refuse([`[x] ${odd}; nothing changed. Move it aside and re-run.`]);
|
|
162
|
+
|
|
75
163
|
let settings = {};
|
|
76
164
|
let settingsRaw = null;
|
|
77
165
|
if (existsSync(settingsPath)) {
|
|
78
166
|
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
|
-
}
|
|
167
|
+
const read = readSettings(settingsRaw);
|
|
168
|
+
if (read.refusal) refuse([read.refusal]);
|
|
169
|
+
settings = read.settings;
|
|
170
|
+
if (read.empty) log("[..] settings.json is empty; read as {} as Claude Code does");
|
|
87
171
|
}
|
|
88
172
|
|
|
89
173
|
// Inside the command's double quotes these still act: a quote ends the path, $ and `
|
|
@@ -166,10 +250,88 @@ function writeOwned(file, data) {
|
|
|
166
250
|
if (process.platform !== "win32") chmodSync(file, FILE_MODE);
|
|
167
251
|
}
|
|
168
252
|
|
|
253
|
+
// The marker blocks payload-mon and claude-update-last add to the installed formatter, matched as those
|
|
254
|
+
// tools match them (payload-mon.mjs BLOCKS, claude-update-last.mjs BLOCKS). Read as latin1, which maps
|
|
255
|
+
// every byte to one character, so text outside the blocks compares byte for byte.
|
|
256
|
+
const TOOL_BLOCKS = [
|
|
257
|
+
/^[ \t]*\/\/ >>> payload-mon >>>[\s\S]*?\/\/ <<< payload-mon <<<[ \t]*\r?\n/gm,
|
|
258
|
+
/^[ \t]*\/\/ >>> claude-update-last >>>(?:(?!\/\/ >>> claude-update-last >>>)[\s\S])*?\/\/ <<< claude-update-last <<<[ \t]*(?:\r?\n|(?![\s\S]))/gm,
|
|
259
|
+
];
|
|
260
|
+
// payload-mon's closing line (payload-mon.mjs CLOSE_LINE). No block reaches past the last one, so its
|
|
261
|
+
// pattern runs only on the text before it: otherwise every stray opening line scans to the end again
|
|
262
|
+
// (a 2MB file of them took 28s).
|
|
263
|
+
const PM_CLOSE = /\/\/ <<< payload-mon <<<[ \t]*\r?\n/g;
|
|
264
|
+
// The text without the tools' blocks, and the blocks it held.
|
|
265
|
+
function splitToolBlocks(text) {
|
|
266
|
+
const blocks = [];
|
|
267
|
+
const take = (t, pattern) =>
|
|
268
|
+
t.replace(pattern, (block) => {
|
|
269
|
+
blocks.push(block);
|
|
270
|
+
return "";
|
|
271
|
+
});
|
|
272
|
+
let end = -1;
|
|
273
|
+
for (const m of text.matchAll(PM_CLOSE)) end = m.index + m[0].length;
|
|
274
|
+
const rest = end < 0 ? text : take(text.slice(0, end), TOOL_BLOCKS[0]) + text.slice(end);
|
|
275
|
+
return { body: take(rest, TOOL_BLOCKS[1]), blocks };
|
|
276
|
+
}
|
|
277
|
+
// The blocks as the tools write them (payload-mon.mjs placeBlock, claude-update-last.mjs blockText), with
|
|
278
|
+
// LF line endings and payload-mon's import of its module copy as "<module copy>" (where the copy sits is the
|
|
279
|
+
// tool's to choose; a URL to any other file is an edit). A block that differs from both was edited by hand. When a tool's block changes, the test "payload-mon 블록만 다른 래퍼와 바뀌지 않은 settings.json 은
|
|
280
|
+
// 다시 설치해도 백업하지 않음" fails until this copy follows.
|
|
281
|
+
const TOOL_BLOCK_TEXTS = [
|
|
282
|
+
[
|
|
283
|
+
" // >>> payload-mon >>>",
|
|
284
|
+
" // payload-mon: estimated API request payload against the 32MB request limit, shown",
|
|
285
|
+
" // like ctx from 8MB (25%) on. `payload-mon off` removes this block; a missing or",
|
|
286
|
+
" // broken module costs only this segment, never the statusline.",
|
|
287
|
+
" try {",
|
|
288
|
+
" const transcript = JSON.parse(input)?.transcript_path;",
|
|
289
|
+
" if (transcript) {",
|
|
290
|
+
" const { estimatePayloadBytes, payloadSegment } = await <module copy>;",
|
|
291
|
+
" const segment = payloadSegment(estimatePayloadBytes(transcript));",
|
|
292
|
+
" if (segment) colored.push(colorRate(segment));",
|
|
293
|
+
" }",
|
|
294
|
+
" } catch {",
|
|
295
|
+
" /* no stdin JSON, unreadable transcript or module error */",
|
|
296
|
+
" }",
|
|
297
|
+
" // <<< payload-mon <<<",
|
|
298
|
+
"",
|
|
299
|
+
].join("\n"),
|
|
300
|
+
[
|
|
301
|
+
" // >>> claude-update-last >>>",
|
|
302
|
+
" // claude-update-last (setup-omc-hud): OMC's Claude Code update notice",
|
|
303
|
+
" // (\"[Claude#X] -> Y claude update\") goes after every other segment.",
|
|
304
|
+
" // `claude-update-last off` removes this block; on any error the order stays as it was.",
|
|
305
|
+
" try {",
|
|
306
|
+
" const notice = /^\\[Claude(?:#[^\\]]*)?\\]\\s*->\\s*\\S+\\s+claude\\s+update\\b/;",
|
|
307
|
+
" const plain = (s) => stripAnsi(String(s)).trim();",
|
|
308
|
+
" const found = segs.find((s) => notice.test(plain(s)));",
|
|
309
|
+
" if (found) {",
|
|
310
|
+
" const moved = A(\"2\", plain(found));",
|
|
311
|
+
" for (let i = colored.length - 1; i >= 0; i--) if (notice.test(plain(colored[i]))) colored.splice(i, 1);",
|
|
312
|
+
" colored.push(moved);",
|
|
313
|
+
" }",
|
|
314
|
+
" } catch {",
|
|
315
|
+
" /* keep the order the wrapper chose */",
|
|
316
|
+
" }",
|
|
317
|
+
" // <<< claude-update-last <<<",
|
|
318
|
+
"",
|
|
319
|
+
].join("\n"),
|
|
320
|
+
];
|
|
321
|
+
const toolMade = (block) =>
|
|
322
|
+
TOOL_BLOCK_TEXTS.includes(block.replace(/\r\n/g, "\n").replace(/await import\("file:\/\/[^"\\]*\/payload-mon\/payload-size\.mjs"\)/, "await <module copy>"));
|
|
323
|
+
const sameBytes = (a, b) => a.equals(b);
|
|
324
|
+
// Blocks as the tools write them are no reason for a backup: `payload-mon on` puts its block back, and
|
|
325
|
+
// wrapper v4 needs no claude-update-last block. A block edited by hand is one.
|
|
326
|
+
function sameFormatter(a, b) {
|
|
327
|
+
const old = splitToolBlocks(a.toString("latin1"));
|
|
328
|
+
return old.body === splitToolBlocks(b.toString("latin1")).body && old.blocks.every(toolMade);
|
|
329
|
+
}
|
|
330
|
+
|
|
169
331
|
// Keep a timestamped copy, with its bytes and mode, of a file that differs from what replaces it.
|
|
170
|
-
function backupIfDifferent(dst, next, label) {
|
|
332
|
+
function backupIfDifferent(dst, next, label, same = sameBytes) {
|
|
171
333
|
if (!existsSync(dst)) return;
|
|
172
|
-
if (readFileSync(dst)
|
|
334
|
+
if (same(readFileSync(dst), next)) return;
|
|
173
335
|
const keep = dst + ".backup." + stamp;
|
|
174
336
|
copyFileSync(dst, keep);
|
|
175
337
|
log(`[ok] Backed up ${label} -> ${keep}`);
|
|
@@ -188,12 +350,12 @@ function installLoader() {
|
|
|
188
350
|
}
|
|
189
351
|
|
|
190
352
|
// 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
|
|
353
|
+
// Files that differ from the templates (local edits) are kept as timestamped
|
|
354
|
+
// backups first; the blocks payload-mon / claude-update-last add, as they write them, do not count.
|
|
193
355
|
function installTemplates() {
|
|
194
356
|
const wrapperSrc = readFileSync(join(SELF, "omc-hud-custom.mjs"));
|
|
195
357
|
const configSrc = readFileSync(join(SELF, "hud-config.json"));
|
|
196
|
-
backupIfDifferent(wrapperDst, wrapperSrc, "previous formatter");
|
|
358
|
+
backupIfDifferent(wrapperDst, wrapperSrc, "previous formatter", sameFormatter);
|
|
197
359
|
backupIfDifferent(configDst, configSrc, "previous HUD config");
|
|
198
360
|
writeOwned(wrapperDst, wrapperSrc);
|
|
199
361
|
writeOwned(configDst, configSrc);
|
|
@@ -201,23 +363,214 @@ function installTemplates() {
|
|
|
201
363
|
log("[ok] HUD config -> " + configDst);
|
|
202
364
|
}
|
|
203
365
|
|
|
366
|
+
// settings.json goes in as a new file through a rename, so a stop midway never leaves it half written.
|
|
367
|
+
// One that is a link (dotfiles) keeps its link, and the file it points at is the one replaced. A file
|
|
368
|
+
// left at the temporary name (an earlier run with this pid, or a link put there) is removed first and
|
|
369
|
+
// never written through. It is written in place as before where the new file cannot go in (a folder
|
|
370
|
+
// this account may not write, a file another program holds open, a file system that refuses the
|
|
371
|
+
// rename) or the link leads nowhere yet, and where a rename would part a hard link (its other name
|
|
372
|
+
// would keep the old text) or the new file has another owner or group. Only a full disk stops it, as
|
|
373
|
+
// writing in place could then cut settings.json short; the new file is written before that choice, so
|
|
374
|
+
// a hard link or another owner finds a full disk first too.
|
|
375
|
+
const FULL_DISK = new Set(["ENOSPC", "EDQUOT"]);
|
|
376
|
+
const isLink = (p) => {
|
|
377
|
+
try {
|
|
378
|
+
return lstatSync(p).isSymbolicLink();
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
const removeTmp = (tmp) => {
|
|
384
|
+
try {
|
|
385
|
+
rmSync(tmp, { force: true });
|
|
386
|
+
} catch {
|
|
387
|
+
/* a folder at that name: left alone */
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
// Whether the file, as `old` was, must keep its inode: it has a second name (a hard link), or the new file
|
|
391
|
+
// `tmp` got another owner or group than it had.
|
|
392
|
+
function keepsInode(old, tmp) {
|
|
393
|
+
if (!old) return false;
|
|
394
|
+
const now = statSync(tmp);
|
|
395
|
+
return old.nlink > 1 || now.uid !== old.uid || now.gid !== old.gid;
|
|
396
|
+
}
|
|
397
|
+
function writeSettings(file, text) {
|
|
398
|
+
let target = file;
|
|
399
|
+
let old;
|
|
400
|
+
let mode;
|
|
401
|
+
try {
|
|
402
|
+
target = realpathSync(file);
|
|
403
|
+
old = statSync(target);
|
|
404
|
+
mode = old.mode & 0o777;
|
|
405
|
+
} catch {
|
|
406
|
+
if (isLink(file)) return writeFileSync(file, text);
|
|
407
|
+
}
|
|
408
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
409
|
+
try {
|
|
410
|
+
rmSync(tmp, { force: true });
|
|
411
|
+
writeFileSync(tmp, text, { flag: "wx", mode });
|
|
412
|
+
if (mode !== undefined && process.platform !== "win32") chmodSync(tmp, mode);
|
|
413
|
+
if (!keepsInode(old, tmp)) return renameSync(tmp, target);
|
|
414
|
+
} catch (e) {
|
|
415
|
+
if (FULL_DISK.has(e.code)) throw e;
|
|
416
|
+
} finally {
|
|
417
|
+
removeTmp(tmp);
|
|
418
|
+
}
|
|
419
|
+
writeFileSync(file, text);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// How many other names settings.json has (a hard link, a `cp -al` snapshot, say), 0 when it cannot be read.
|
|
423
|
+
// writeSettings writes such a file in place, so those names show the new text too.
|
|
424
|
+
function otherNames(file) {
|
|
425
|
+
try {
|
|
426
|
+
return statSync(file).nlink - 1;
|
|
427
|
+
} catch {
|
|
428
|
+
return 0;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
204
432
|
// 4. Point settings.json statusLine at the custom formatter; its other fields (padding, ...) stay.
|
|
433
|
+
// A settings.json that already says exactly this is neither backed up nor written.
|
|
205
434
|
const objectOr = (v) => (v && typeof v === "object" && !Array.isArray(v) ? v : {});
|
|
206
435
|
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
436
|
// Quoted so a config dir with spaces (C:/Users/First Last/.claude) still runs.
|
|
213
437
|
const command = `node "${cmdPath}"`;
|
|
214
438
|
const prior = objectOr(settings.statusLine);
|
|
215
439
|
if (typeof prior.command === "string" && prior.command && prior.command !== command) {
|
|
216
|
-
log(`[..] statusLine ran ${
|
|
440
|
+
log(`[..] statusLine ran ${priorShown(prior.command)}; it now runs the custom HUD (the old value is in the backup)`);
|
|
217
441
|
}
|
|
218
442
|
settings.statusLine = { ...prior, type: "command", command };
|
|
219
|
-
|
|
443
|
+
const next = JSON.stringify(settings, null, 2) + "\n";
|
|
444
|
+
if (next === settingsRaw) {
|
|
445
|
+
log(`[ok] statusLine -> ${command} (settings.json unchanged, no backup)`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (settingsRaw !== null) {
|
|
449
|
+
const backup = settingsPath + ".backup." + stamp;
|
|
450
|
+
copyFileSync(settingsPath, backup); // the original bytes and mode
|
|
451
|
+
log("[ok] Backed up settings.json -> " + backup);
|
|
452
|
+
}
|
|
453
|
+
const others = otherNames(settingsPath);
|
|
454
|
+
writeSettings(settingsPath, next);
|
|
220
455
|
log("[ok] statusLine -> " + command);
|
|
456
|
+
if (others > 0) {
|
|
457
|
+
log(`[..] settings.json has ${others} other name${others > 1 ? "s" : ""} (a hard link); it was written in place, so they show the new text too (the old text is in the backup)`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// 5. Render check: run the installed formatter once with no input, as the statusLine command does,
|
|
462
|
+
// with `node` from PATH. With no input OMC prints its diagnostic, whose first line names the version
|
|
463
|
+
// that loaded ("[OMC] HUD v5.7.4 | preset: full"), and uses no network. Reported only: the install
|
|
464
|
+
// is done either way. The 25s limit outwaits the formatter, which gives up on its own within 20s
|
|
465
|
+
// and then ends its fallback child; killing it sooner would leave that child running.
|
|
466
|
+
// APPLY_HUD_CHECK_TIMEOUT_MS shortens the limit for tests.
|
|
467
|
+
const CHECK_MS = Number(process.env.APPLY_HUD_CHECK_TIMEOUT_MS) || 25000;
|
|
468
|
+
const BLAME =
|
|
469
|
+
" 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.";
|
|
470
|
+
const clip = (s, n = 200) => shown(s).slice(0, n);
|
|
471
|
+
// No input, and the config folder as working folder: OMC's loader looks in the working folder's
|
|
472
|
+
// node_modules and runs git from it, and Windows looks there first for a bare program name, so the
|
|
473
|
+
// folder the installer was started in (a project, say) must not be it.
|
|
474
|
+
const RUN_OPTS = { input: "", encoding: "utf8", timeout: CHECK_MS, windowsHide: true, cwd: CONFIG_DIR };
|
|
475
|
+
|
|
476
|
+
// node as the statusLine shell finds it, but only in PATH folders given as absolute paths: spawning the
|
|
477
|
+
// bare name would on Windows try the working folder first, where a project's own node.exe could run.
|
|
478
|
+
// Like the shell, it passes over a file it may not run.
|
|
479
|
+
function findNode(pathEnv, name = process.platform === "win32" ? "node.exe" : "node") {
|
|
480
|
+
for (const entry of (pathEnv || "").split(delimiter)) {
|
|
481
|
+
const dir = entry.replace(/^"(.*)"$/, "$1");
|
|
482
|
+
if (!dir || !isAbsolute(dir)) continue;
|
|
483
|
+
const file = join(dir, name);
|
|
484
|
+
try {
|
|
485
|
+
if (statSync(file).isFile()) {
|
|
486
|
+
accessSync(file, constants.X_OK);
|
|
487
|
+
return file;
|
|
488
|
+
}
|
|
489
|
+
} catch {
|
|
490
|
+
/* not in this folder, or not runnable */
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const NO_NODE = '[!!] Render check: no "node" on PATH; the statusLine command runs "node", so the statusline will not show.';
|
|
497
|
+
// Claude Code runs statusLine with Git Bash when it finds it, else with PowerShell. Of the two only
|
|
498
|
+
// PowerShell runs node.cmd for "node", and this check cannot tell which it will be, so it does not run.
|
|
499
|
+
const NODE_CMD_ONLY =
|
|
500
|
+
'[..] Render check: skipped; PATH has node.cmd but no node.exe, and Git Bash, which Claude Code runs statusLine with when it finds it, does not run node.cmd as "node".';
|
|
501
|
+
const noNodeLine = (pathEnv) => (process.platform === "win32" && findNode(pathEnv, "node.cmd") ? NODE_CMD_ONLY : NO_NODE);
|
|
502
|
+
|
|
503
|
+
function omcVersion(root) {
|
|
504
|
+
if (!root) return null;
|
|
505
|
+
try {
|
|
506
|
+
const v = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
|
|
507
|
+
return typeof v === "string" ? v : null;
|
|
508
|
+
} catch {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// The first line of a Node error ("Error: ...", "TypeError [ERR_X]: ..."), "" when there is none.
|
|
514
|
+
const errorLine = (text) => /^\w*Error\b.*$/m.exec(text || "")?.[0] ?? "";
|
|
515
|
+
|
|
516
|
+
// Why an OMC version does not load: its HUD's own error when run directly.
|
|
517
|
+
function loadReason(node, root) {
|
|
518
|
+
const hud = join(root, "dist", "hud", "index.js");
|
|
519
|
+
if (!existsSync(hud)) return "no dist/hud/index.js";
|
|
520
|
+
const r = spawnSync(node, [hud], RUN_OPTS);
|
|
521
|
+
const line = errorLine(r.stderr);
|
|
522
|
+
return line ? clip(line) : "";
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const NO_OUTPUT = "[..] Render check: OMC printed nothing with no input, so this check cannot tell whether the statusline draws.";
|
|
526
|
+
|
|
527
|
+
// What the formatter printed, as report lines.
|
|
528
|
+
function judgeRender(out, node) {
|
|
529
|
+
const banner = /^\[OMC\] HUD v(\S+) \| preset: /m.exec(out);
|
|
530
|
+
if (banner) {
|
|
531
|
+
const installed = omcVersion(pluginRoot);
|
|
532
|
+
// Both version strings come from OMC (its output, its package.json): shown through clip like its lines.
|
|
533
|
+
const [drew, want] = [clip(banner[1], 40), clip(installed, 40)];
|
|
534
|
+
if (!installed || installed === banner[1]) return [`[ok] Render check: the statusline loads OMC ${drew}.`];
|
|
535
|
+
const why = loadReason(node, pluginRoot);
|
|
536
|
+
// Without an error from the installed version, all this check saw is which version drew.
|
|
537
|
+
if (!why) return [`[..] Render check: the statusline loads OMC ${drew}, not the installed ${want}.`];
|
|
538
|
+
return [`[!!] Render check: the statusline loads OMC ${drew}, not the installed ${want}; ${want} does not load (${why}).`, BLAME];
|
|
539
|
+
}
|
|
540
|
+
// The formatter says this when OMC printed nothing; with no input that alone does not say it cannot draw.
|
|
541
|
+
if (/^\[OMC HUD\] no output$/m.test(out)) return [NO_OUTPUT];
|
|
542
|
+
// OMC's line ends with the reason, after a path that can be long: keep it whole within reason.
|
|
543
|
+
const failed = /^\[OMC HUD\] .*$/m.exec(out);
|
|
544
|
+
if (failed) return [`[!!] Render check: OMC's HUD does not load: ${clip(failed[0], 1000)}`, BLAME];
|
|
545
|
+
// A loader that cannot load prints Node's error, header lines first; its module path can be long.
|
|
546
|
+
const error = errorLine(out);
|
|
547
|
+
if (error) return [`[!!] Render check: the statusline prints an error instead of a line: ${clip(error, 1000)}`];
|
|
548
|
+
return [`[..] Render check: unrecognized output: ${clip(out.split("\n")[0])}`];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// A statusline command that exited non-zero: its Node error line when stderr has one, else the first line.
|
|
552
|
+
function failedLine(r) {
|
|
553
|
+
const stderr = (r.stderr || "").trim();
|
|
554
|
+
const err = errorLine(stderr) || stderr.split("\n")[0];
|
|
555
|
+
return `[!!] Render check: the statusline command failed (exit ${r.status ?? r.signal})${err ? `: ${clip(err)}` : ""}`;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function renderReport() {
|
|
559
|
+
const node = findNode(process.env.PATH);
|
|
560
|
+
if (!node) return [noNodeLine(process.env.PATH)];
|
|
561
|
+
const r = spawnSync(node, [wrapperDst], RUN_OPTS);
|
|
562
|
+
if (r.error && r.error.code === "ETIMEDOUT") return [`[!!] Render check: the statusline gave no answer in ${CHECK_MS / 1000}s.`];
|
|
563
|
+
if (r.error) throw r.error;
|
|
564
|
+
if (r.status !== 0) return [failedLine(r)];
|
|
565
|
+
return judgeRender((r.stdout || "").replace(/\x1b\[[0-9;]*m/g, ""), node);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function renderCheck() {
|
|
569
|
+
try {
|
|
570
|
+
for (const line of renderReport()) log(line);
|
|
571
|
+
} catch (e) {
|
|
572
|
+
log(`[..] Render check: skipped (${e.code || e.message})`);
|
|
573
|
+
}
|
|
221
574
|
}
|
|
222
575
|
|
|
223
576
|
try {
|
|
@@ -226,9 +579,10 @@ try {
|
|
|
226
579
|
pointStatusLine();
|
|
227
580
|
} catch (e) {
|
|
228
581
|
log(`[x] Stopped partway (${e.code || e.message}); some files may have changed.`);
|
|
229
|
-
log(`
|
|
582
|
+
log(` Earlier versions it replaced, apart from the loader and blocks as the tools wrote them, are in <file>.backup.${stamp}.`);
|
|
230
583
|
process.exit(2);
|
|
231
584
|
}
|
|
232
585
|
|
|
586
|
+
renderCheck();
|
|
233
587
|
log("");
|
|
234
588
|
log("Done. Restart Claude Code to load the new statusline.");
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
* segment each: 🤖1|🔧14|⚡3. A count OMC leaves out (zero) stays out.
|
|
19
19
|
* - per-model usage badge (fable:0%(1h31m)) -> Fa:0%/1h31m, after the rate windows.
|
|
20
20
|
* - PL:<MB, 1 decimal>M always shown before se. It reads payload-mon's module
|
|
21
|
-
* copy
|
|
22
|
-
*
|
|
21
|
+
* copy, payload-mon/payload-size.mjs, from the first of these folders that
|
|
22
|
+
* holds it as a regular file: this file's (the one a statusLine link points
|
|
23
|
+
* at), the link's, the config folder's hud/, then the folder import.meta.url
|
|
24
|
+
* names (the link's under --preserve-symlinks); without it PL is left out.
|
|
23
25
|
* - cwd group split on "|"; branch shown without "branch:" after the account.
|
|
24
26
|
* - OMC#X.Y.Z label no longer shown, nor OMC's update hint lines
|
|
25
27
|
* ("[!] omc X - paste: ! ...", "[!] claude X - paste: ! claude update").
|
|
@@ -68,14 +70,43 @@
|
|
|
68
70
|
* We wait for the first complete line instead.
|
|
69
71
|
*/
|
|
70
72
|
import { spawnSync } from "node:child_process";
|
|
71
|
-
import { readFileSync } from "node:fs";
|
|
73
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
72
74
|
import { homedir } from "node:os";
|
|
73
|
-
import { dirname, join } from "node:path";
|
|
75
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
74
76
|
import { Readable } from "node:stream";
|
|
75
77
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
76
78
|
|
|
77
79
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
78
|
-
|
|
80
|
+
// The statusLine command names this file. When that name is a link (dotfiles, say), node runs the
|
|
81
|
+
// file it points at: the installer puts omc-hud.mjs beside the link and payload-mon puts its copy
|
|
82
|
+
// beside the file, so both folders are looked in. The command's name counts only when it leads to
|
|
83
|
+
// this file (another script may have imported this one), and the file's own folder comes from
|
|
84
|
+
// realpath: with --preserve-symlinks-main, import.meta.url names the link.
|
|
85
|
+
function hereDirs() {
|
|
86
|
+
const dirs = { named: null, real: __dirname, self: __dirname };
|
|
87
|
+
try {
|
|
88
|
+
const real = realpathSync(fileURLToPath(import.meta.url));
|
|
89
|
+
dirs.real = dirname(real);
|
|
90
|
+
const named = process.argv[1] ? resolve(process.argv[1]) : "";
|
|
91
|
+
if (named && realpathSync(named) === real) dirs.named = dirname(named);
|
|
92
|
+
} catch {
|
|
93
|
+
/* keep this file's own folder */
|
|
94
|
+
}
|
|
95
|
+
return dirs;
|
|
96
|
+
}
|
|
97
|
+
const HERE = hereDirs();
|
|
98
|
+
const isFile = (p) => {
|
|
99
|
+
try {
|
|
100
|
+
return statSync(p).isFile();
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
// The first of these folders (each once, in this order) that holds the file as a regular file, "" when none
|
|
106
|
+
// does: a folder at that name is no module, and opening a FIFO waits for a writer for good.
|
|
107
|
+
function findSibling(dirs, ...parts) {
|
|
108
|
+
return [...new Set(dirs.filter(Boolean))].map((d) => join(d, ...parts)).find(isFile) ?? "";
|
|
109
|
+
}
|
|
79
110
|
|
|
80
111
|
const STDIN_TIMEOUT_MS = 2000; // Claude Code pipes the payload immediately
|
|
81
112
|
const RENDER_TIMEOUT_MS = 10000; // see timer discipline note above
|
|
@@ -122,10 +153,39 @@ function emit(s) {
|
|
|
122
153
|
// that file in $CLAUDE_CONFIG_DIR when set, otherwise in the home folder.
|
|
123
154
|
function claudeJsonDir() {
|
|
124
155
|
const v = (process.env.CLAUDE_CONFIG_DIR || "").trim();
|
|
125
|
-
|
|
156
|
+
return v ? expandDir(v) : homedir();
|
|
157
|
+
}
|
|
158
|
+
// Where Claude Code was started (the statusline JSON's workspace.project_dir, else the working
|
|
159
|
+
// folder). A relative CLAUDE_CONFIG_DIR is read from there: Claude Code reads it from its own working
|
|
160
|
+
// folder, which is the same one until it moves (into a worktree, say).
|
|
161
|
+
function startDir() {
|
|
162
|
+
try {
|
|
163
|
+
const d = JSON.parse(input)?.workspace?.project_dir;
|
|
164
|
+
if (typeof d === "string" && isAbsolute(d)) return d;
|
|
165
|
+
} catch {
|
|
166
|
+
/* no or bad stdin JSON */
|
|
167
|
+
}
|
|
168
|
+
return process.cwd();
|
|
169
|
+
}
|
|
170
|
+
function expandDir(v) {
|
|
126
171
|
if (v === "~") return homedir();
|
|
127
172
|
if (v.startsWith("~/") || v.startsWith("~\\")) return join(homedir(), v.slice(2));
|
|
128
|
-
return v;
|
|
173
|
+
return resolve(startDir(), v);
|
|
174
|
+
}
|
|
175
|
+
// Claude Code's config folder. Its hud/ is where the installer puts the loader: the third place
|
|
176
|
+
// siblings are looked for, as when another script imported this file through a link. "" when the
|
|
177
|
+
// home folder cannot be told (no HOME and no account entry) or the working folder is gone: the
|
|
178
|
+
// other places still count.
|
|
179
|
+
function configDir() {
|
|
180
|
+
const v = (process.env.CLAUDE_CONFIG_DIR || "").trim();
|
|
181
|
+
return v ? expandDir(v) : join(homedir(), ".claude");
|
|
182
|
+
}
|
|
183
|
+
function configHud() {
|
|
184
|
+
try {
|
|
185
|
+
return join(configDir(), "hud");
|
|
186
|
+
} catch {
|
|
187
|
+
return "";
|
|
188
|
+
}
|
|
129
189
|
}
|
|
130
190
|
function loginAccount() {
|
|
131
191
|
try {
|
|
@@ -175,6 +235,7 @@ function readStdinBounded(ms) {
|
|
|
175
235
|
}
|
|
176
236
|
|
|
177
237
|
const input = await readStdinBounded(STDIN_TIMEOUT_MS);
|
|
238
|
+
const HUD = findSibling([HERE.named, HERE.real, configHud(), HERE.self], "omc-hud.mjs");
|
|
178
239
|
|
|
179
240
|
// Current reasoning effort level (low/medium/high/xhigh) from the statusline
|
|
180
241
|
// payload (effort.level) — shown next to the model. Empty if absent.
|
|
@@ -278,13 +339,18 @@ for (const s of [process.stdout, process.stderr]) {
|
|
|
278
339
|
}
|
|
279
340
|
|
|
280
341
|
let raw = "";
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
342
|
+
if (!HUD) {
|
|
343
|
+
// No regular file at any place looked in: nothing to run. Named where the installer puts it.
|
|
344
|
+
emit(`[OMC HUD] omc-hud.mjs not found: ${join(HERE.named || HERE.real, "omc-hud.mjs")}\n`);
|
|
345
|
+
} else {
|
|
346
|
+
try {
|
|
347
|
+
raw = (await renderInProcess(RENDER_TIMEOUT_MS)).replace(/\r/g, "");
|
|
348
|
+
} catch {
|
|
349
|
+
raw = ""; // fall through to the child-process path
|
|
350
|
+
}
|
|
285
351
|
}
|
|
286
352
|
|
|
287
|
-
if (!raw.trim()) {
|
|
353
|
+
if (HUD && !raw.trim()) {
|
|
288
354
|
// In-process rendering produced nothing usable. Fall back to the original
|
|
289
355
|
// out-of-process path: it costs an extra node, but a statusline that renders
|
|
290
356
|
// beats one that does not — e.g. if a future OMC version stops being
|
|
@@ -481,8 +547,9 @@ try {
|
|
|
481
547
|
// without it (payload-mon off) or without a transcript, PL is left out.
|
|
482
548
|
try {
|
|
483
549
|
const transcript = JSON.parse(input)?.transcript_path;
|
|
484
|
-
|
|
485
|
-
|
|
550
|
+
const copy = transcript ? findSibling([HERE.real, HERE.named, configHud(), HERE.self], "payload-mon", "payload-size.mjs") : "";
|
|
551
|
+
if (copy) {
|
|
552
|
+
const { estimatePayloadBytes, REQUEST_LIMIT_BYTES } = await import(pathToFileURL(copy).href);
|
|
486
553
|
const bytes = estimatePayloadBytes(transcript);
|
|
487
554
|
if (Number.isFinite(bytes)) {
|
|
488
555
|
for (let i = colored.length - 1; i >= 0; i--) if (/^payload:/.test(stripAnsi(colored[i]))) colored.splice(i, 1);
|