@kaydash9999/banker-plugins 0.19.5 → 0.20.1

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.
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * apply-hud.mjs — cross-platform installer for the custom OMC HUD statusline.
4
+ *
5
+ * Installs into ${CLAUDE_CONFIG_DIR:-~/.claude}:
6
+ * - hud/omc-hud.mjs (OMC loader, copied from the installed plugin)
7
+ * - hud/lib/config-dir.mjs (loader dependency)
8
+ * - hud/omc-hud-custom.mjs (the custom compact/colored formatter)
9
+ * - .omc/hud-config.json (HUD preset/elements)
10
+ * - settings.json (statusLine -> the custom formatter; existing file backed up)
11
+ *
12
+ * Runs identically on Linux, macOS, and Windows (only needs node + an OMC install).
13
+ * Run it directly with `node apply-hud.mjs` on every OS; the templates are read
14
+ * from this file's folder, so nothing is downloaded.
15
+ *
16
+ * Based on smallOpenSource/omc_hud d3cedd5 (MIT, see LICENSE-omc_hud). Changes:
17
+ * statusLine path quoted; a settings.json that is not a JSON object is refused
18
+ * before any file changes (the original replaced it); UTF-8 BOM accepted;
19
+ * CLAUDE_CONFIG_DIR trimmed, "~" expanded and made absolute; formatter and HUD
20
+ * config backed up when they differ; pre-releases sort below their release; no
21
+ * folder is made when OMC is missing. Also refused before any change: a config
22
+ * folder path the statusLine shell would act on, a file or folder it cannot
23
+ * write, an OMC folder without both loader files, a relative OMC_PLUGIN_ROOT.
24
+ * Backups keep the original bytes and mode; the files it writes are 0644 and new
25
+ * folders 0755; the other statusLine fields (padding, ...) stay.
26
+ * Exit 1 means nothing changed; exit 2 means an unexpected error stopped it partway.
27
+ */
28
+ import {
29
+ accessSync,
30
+ chmodSync,
31
+ constants,
32
+ copyFileSync,
33
+ existsSync,
34
+ mkdirSync,
35
+ readdirSync,
36
+ readFileSync,
37
+ writeFileSync,
38
+ } from "node:fs";
39
+ import { homedir } from "node:os";
40
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
41
+ import { fileURLToPath } from "node:url";
42
+
43
+ const SELF = dirname(fileURLToPath(import.meta.url));
44
+ // Same rules as OMC's getClaudeConfigDir: trim, expand a leading "~", then make
45
+ // it absolute so statusLine works from any cwd.
46
+ function configDir() {
47
+ const v = (process.env.CLAUDE_CONFIG_DIR || "").trim();
48
+ if (!v) return join(homedir(), ".claude");
49
+ if (v === "~") return homedir();
50
+ if (v.startsWith("~/") || v.startsWith("~\\")) return join(homedir(), v.slice(2));
51
+ return resolve(v);
52
+ }
53
+ const CONFIG_DIR = configDir().replace(/[\\/]+$/, "");
54
+ const HUD_DIR = join(CONFIG_DIR, "hud");
55
+ const OMC_DIR = join(CONFIG_DIR, ".omc");
56
+ const settingsPath = join(CONFIG_DIR, "settings.json");
57
+ const loaderDst = join(HUD_DIR, "omc-hud.mjs");
58
+ const libDst = join(HUD_DIR, "lib", "config-dir.mjs");
59
+ const wrapperDst = join(HUD_DIR, "omc-hud-custom.mjs");
60
+ const configDst = join(OMC_DIR, "hud-config.json");
61
+ // An absolute forward-slash path: Claude Code runs statusLine through a shell (Git Bash or
62
+ // PowerShell on Windows) that treats backslashes as escapes.
63
+ const cmdPath = wrapperDst.split(sep).join("/");
64
+ const log = (m) => process.stdout.write(m + "\n");
65
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
66
+ const refuse = (lines) => {
67
+ for (const l of lines) log(l);
68
+ process.exit(1);
69
+ };
70
+
71
+ // 0. Check settings.json before touching anything, so a refusal leaves every
72
+ // file as it was. Refuse rather than replace: a fresh file would drop every
73
+ // other setting. A BOM (Windows editors) is dropped when the file is written
74
+ // back; the backup keeps the original bytes.
75
+ let settings = {};
76
+ let settingsRaw = null;
77
+ if (existsSync(settingsPath)) {
78
+ settingsRaw = readFileSync(settingsPath, "utf8");
79
+ try {
80
+ settings = JSON.parse(settingsRaw.replace(/^\uFEFF/, ""));
81
+ } catch {
82
+ settings = undefined;
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
+ }
87
+ }
88
+
89
+ // Inside the command's double quotes these still act: a quote ends the path, $ and `
90
+ // run what follows, %VAR% expands, a backslash escapes, a line break ends the command.
91
+ if (/["`$%\\\r\n]/.test(cmdPath)) {
92
+ refuse([
93
+ '[x] The config folder path has a character the statusLine shell would act on (" ` $ % \\ or a line break); nothing changed.',
94
+ " Use a config folder without it (CLAUDE_CONFIG_DIR).",
95
+ ]);
96
+ }
97
+
98
+ // 1. Resolve the installed OMC plugin root (for the canonical HUD loader template).
99
+ function resolvePluginRoot() {
100
+ const lib = (r) => join(r, "scripts", "lib");
101
+ // The loader needs both files; a folder holding only one would leave hud/ half replaced.
102
+ const complete = (r) => existsSync(join(lib(r), "hud-wrapper-template.txt")) && existsSync(join(lib(r), "config-dir.mjs"));
103
+ const fromEnv = process.env.OMC_PLUGIN_ROOT;
104
+ // Only an absolute path: a relative one would be read from the project folder the command runs in.
105
+ if (fromEnv && isAbsolute(fromEnv) && complete(fromEnv)) return fromEnv;
106
+ const base = join(CONFIG_DIR, "plugins", "cache", "omc", "oh-my-claudecode");
107
+ if (!existsSync(base)) return null;
108
+ const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([A-Za-z0-9.-]+))?$/;
109
+ // Newest first; a pre-release sorts below its release (5.7.4-beta < 5.7.4).
110
+ const newer = (a, b) => {
111
+ const x = a.match(SEMVER);
112
+ const y = b.match(SEMVER);
113
+ for (let i = 1; i <= 3; i++) if (+x[i] !== +y[i]) return +y[i] - +x[i];
114
+ if (!x[4] !== !y[4]) return x[4] ? 1 : -1;
115
+ return (y[4] || "").localeCompare(x[4] || "", undefined, { numeric: true });
116
+ };
117
+ const versions = readdirSync(base)
118
+ .filter((n) => SEMVER.test(n))
119
+ .sort(newer);
120
+ for (const v of versions) {
121
+ const r = join(base, v);
122
+ if (complete(r)) return r;
123
+ }
124
+ return null;
125
+ }
126
+
127
+ const pluginRoot = resolvePluginRoot();
128
+ if (!pluginRoot && !existsSync(loaderDst)) {
129
+ refuse([
130
+ "[x] OMC plugin not installed and no existing hud/omc-hud.mjs found.",
131
+ " Install OMC first, then re-run:",
132
+ " /plugin install oh-my-claudecode (or set OMC_PLUGIN_ROOT to an OMC version folder)",
133
+ ]);
134
+ }
135
+
136
+ // Every file this run writes or backs up, checked before the first write so that a
137
+ // refusal changes nothing; a new file or backup needs its folder, or the nearest one
138
+ // that exists, to take it.
139
+ function unwritable() {
140
+ for (const p of [settingsPath, loaderDst, libDst, wrapperDst, configDst]) {
141
+ if (!existsSync(p)) continue;
142
+ try {
143
+ accessSync(p, constants.W_OK);
144
+ } catch (e) {
145
+ return `${p} (${e.code})`;
146
+ }
147
+ }
148
+ for (const dir of [CONFIG_DIR, HUD_DIR, join(HUD_DIR, "lib"), OMC_DIR]) {
149
+ let d = dir;
150
+ while (!existsSync(d) && dirname(d) !== d) d = dirname(d);
151
+ try {
152
+ accessSync(d, constants.W_OK);
153
+ } catch (e) {
154
+ return `${d} (${e.code})`;
155
+ }
156
+ }
157
+ return "";
158
+ }
159
+ const blocked = unwritable();
160
+ if (blocked) refuse([`[x] Cannot write ${blocked}; nothing changed. Fix the permission and re-run.`]);
161
+
162
+ // The statusline runs or reads these on every refresh, so no other account may change them.
163
+ const FILE_MODE = 0o644;
164
+ function writeOwned(file, data) {
165
+ writeFileSync(file, data, { mode: FILE_MODE });
166
+ if (process.platform !== "win32") chmodSync(file, FILE_MODE);
167
+ }
168
+
169
+ // Keep a timestamped copy, with its bytes and mode, of a file that differs from what replaces it.
170
+ function backupIfDifferent(dst, next, label) {
171
+ if (!existsSync(dst)) return;
172
+ if (readFileSync(dst).equals(next)) return;
173
+ const keep = dst + ".backup." + stamp;
174
+ copyFileSync(dst, keep);
175
+ log(`[ok] Backed up ${label} -> ${keep}`);
176
+ }
177
+
178
+ // 2. Ensure target directories exist, then copy the loader.
179
+ function installLoader() {
180
+ for (const d of [join(HUD_DIR, "lib"), OMC_DIR]) mkdirSync(d, { recursive: true, mode: 0o755 });
181
+ if (!pluginRoot) {
182
+ log("[..] OMC plugin root not found; keeping existing hud/omc-hud.mjs");
183
+ return;
184
+ }
185
+ writeOwned(loaderDst, readFileSync(join(pluginRoot, "scripts", "lib", "hud-wrapper-template.txt")));
186
+ writeOwned(libDst, readFileSync(join(pluginRoot, "scripts", "lib", "config-dir.mjs")));
187
+ log("[ok] OMC HUD loader installed from " + pluginRoot);
188
+ }
189
+
190
+ // 3. Install the custom formatter and HUD config from the bundled templates.
191
+ // Files that differ from the templates (local edits, blocks added by
192
+ // payload-mon / claude-update-last) are kept as timestamped backups first.
193
+ function installTemplates() {
194
+ const wrapperSrc = readFileSync(join(SELF, "omc-hud-custom.mjs"));
195
+ const configSrc = readFileSync(join(SELF, "hud-config.json"));
196
+ backupIfDifferent(wrapperDst, wrapperSrc, "previous formatter");
197
+ backupIfDifferent(configDst, configSrc, "previous HUD config");
198
+ writeOwned(wrapperDst, wrapperSrc);
199
+ writeOwned(configDst, configSrc);
200
+ log("[ok] Custom formatter -> " + wrapperDst);
201
+ log("[ok] HUD config -> " + configDst);
202
+ }
203
+
204
+ // 4. Point settings.json statusLine at the custom formatter; its other fields (padding, ...) stay.
205
+ const objectOr = (v) => (v && typeof v === "object" && !Array.isArray(v) ? v : {});
206
+ 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
+ // Quoted so a config dir with spaces (C:/Users/First Last/.claude) still runs.
213
+ const command = `node "${cmdPath}"`;
214
+ const prior = objectOr(settings.statusLine);
215
+ if (typeof prior.command === "string" && prior.command && prior.command !== command) {
216
+ log(`[..] statusLine ran ${JSON.stringify(prior.command)}; it now runs the custom HUD (the old value is in the backup)`);
217
+ }
218
+ settings.statusLine = { ...prior, type: "command", command };
219
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
220
+ log("[ok] statusLine -> " + command);
221
+ }
222
+
223
+ try {
224
+ installLoader();
225
+ installTemplates();
226
+ pointStatusLine();
227
+ } catch (e) {
228
+ log(`[x] Stopped partway (${e.code || e.message}); some files may have changed.`);
229
+ log(` A replaced file's earlier version is in <file>.backup.${stamp}.`);
230
+ process.exit(2);
231
+ }
232
+
233
+ log("");
234
+ log("Done. Restart Claude Code to load the new statusline.");
@@ -0,0 +1,53 @@
1
+ {
2
+ "preset": "full",
3
+ "labels": {
4
+ "thinking": "think"
5
+ },
6
+ "elements": {
7
+ "omcLabel": true,
8
+ "updateNotification": true,
9
+ "model": true,
10
+ "modelFormat": "versioned",
11
+ "cwd": true,
12
+ "cwdFormat": "absolute",
13
+ "useHyperlinks": false,
14
+ "gitRepo": false,
15
+ "gitBranch": true,
16
+ "gitStatus": false,
17
+ "ralph": true,
18
+ "autopilot": true,
19
+ "prdStory": true,
20
+ "activeSkills": false,
21
+ "lastSkill": false,
22
+ "contextBar": true,
23
+ "agents": true,
24
+ "agentsFormat": "multiline",
25
+ "backgroundTasks": true,
26
+ "todos": true,
27
+ "thinking": true,
28
+ "thinkingFormat": "text",
29
+ "permissionStatus": false,
30
+ "apiKeySource": false,
31
+ "profile": false,
32
+ "promptTime": false,
33
+ "sessionHealth": true,
34
+ "showSessionDuration": true,
35
+ "showHealthIndicator": false,
36
+ "useBars": false,
37
+ "showCallCounts": true,
38
+ "callCountsFormat": "emoji",
39
+ "safeMode": true,
40
+ "maxOutputLines": 8
41
+ },
42
+ "thresholds": {
43
+ "contextWarning": 70,
44
+ "contextCompactSuggestion": 80,
45
+ "contextCritical": 85,
46
+ "ralphWarning": 7
47
+ },
48
+ "staleTaskThresholdMinutes": 30,
49
+ "contextLimitWarning": {
50
+ "threshold": 80,
51
+ "autoCompact": false
52
+ }
53
+ }