@nightrunners/nightmaxxing 0.1.0 → 0.1.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/README.md CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  CLI for [nightmaxxing](https://maxxing.nrght.eu) — the social leaderboard
4
4
  for LLM token usage. Parses your local agent usage (Claude Code, Codex,
5
- OpenCode, Gemini CLI, Copilot CLI, Hermes, Pi) via
6
- [ccusage](https://github.com/ryoppippi/ccusage) and pushes daily aggregates
7
- to your public profile.
5
+ OpenCode, Gemini CLI, Copilot CLI, Hermes, Pi, Oh My Pi, Grok Build CLI,
6
+ Antigravity, ZCode, Amp, Qwen Code, Kimi CLI, Kilo Code, Goose, Droid,
7
+ Codebuff, OpenClaw) via [ccusage](https://github.com/ryoppippi/ccusage) and
8
+ pushes daily aggregates to your public profile.
8
9
 
9
10
  ## Usage
10
11
 
@@ -23,13 +24,28 @@ You can also install globally with `bun add -g --trust @nightrunners/nightmaxxin
23
24
  The background service uses the global `nightmaxxing` binary and syncs every
24
25
  5 minutes. It auto-updates through the package manager that installed the
25
26
  global binary (bun, npm, pnpm, or yarn) when that package manager can be
26
- detected.
27
+ detected. Custom log roots (`CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `HERMES_HOME`,
28
+ the other agents' data-directory variables such as `OPENCODE_DATA_DIR`,
29
+ `GEMINI_DATA_DIR`, `COPILOT_HOME`, `PI_AGENT_DIR`, `GROK_HOME` or
30
+ `AMP_DATA_DIR`, and `XDG_DATA_HOME`/`XDG_CONFIG_HOME`) are captured at
31
+ `nightmaxxing service install` or `nightmaxxing service repair`; rerun one of
32
+ those after changing them. Without `HERMES_HOME`, Hermes usage includes every
33
+ named profile under `~/.hermes/profiles/`.
27
34
  Use `nightmaxxing service status` for the last run and `nightmaxxing service
28
35
  doctor` to inspect scheduler files, auth, auto-update, locks, and recent logs.
36
+ Each doctor check is `OK`, `INFO` (fine, but worth knowing: never synced yet, a
37
+ sync running right now), `WARN` (syncs still run, but something is off) or
38
+ `FAIL` (scheduled syncs can't happen); every `WARN` and `FAIL` line ends with
39
+ the command that fixes it. `service doctor` exits 1 when any check is `WARN` or
40
+ `FAIL` and 0 otherwise, so scripts and CI can gate on it. With `--json`, the
41
+ report's `health` is `ok`, `warn` or `fail` (`status` only says the doctor ran).
29
42
 
30
43
  Run `sync` as often as you like, from as many machines as you like —
31
44
  profiles aggregate across devices. Useful flags: `--dry-run`,
32
- `--since YYYY-MM-DD`, `--sources claude,codex`, `--json`.
45
+ `--since YYYY-MM-DD`, `--sources claude,codex`, `--json`. `sync` exits
46
+ non-zero when no agent's usage could be collected; if only some agents fail,
47
+ it still uploads the rest and exits 0 with `"status": "partial"` in `--json`
48
+ output.
33
49
 
34
50
  ### What gets uploaded (privacy)
35
51
 
@@ -25,6 +25,11 @@ function detectArch(value = os.arch()) {
25
25
  );
26
26
  }
27
27
 
28
+ // The published package's `bin` points at bin/nightmaxxing, a copy of this
29
+ // launcher. Preinstall caches the verified native binary beside it under this
30
+ // name so the launcher can exec it without re-running CPU detection.
31
+ const CACHED_BINARY_NAME = "nightmaxxing.exe";
32
+
28
33
  function binaryName(platform = detectPlatform()) {
29
34
  platform = detectPlatform(platform);
30
35
  return platform === "windows" ? "nightmaxxing.exe" : "nightmaxxing";
@@ -168,6 +173,24 @@ function resolveBinary(packageName, sourceBinary = binaryName(), options = {}) {
168
173
  return binaryPath;
169
174
  }
170
175
 
176
+ function cachedBinaryPaths(packageDir = __dirname) {
177
+ return [
178
+ path.join(packageDir, "bin", CACHED_BINARY_NAME),
179
+ path.join(packageDir, CACHED_BINARY_NAME),
180
+ ];
181
+ }
182
+
183
+ function findCachedBinary(packageDir = __dirname) {
184
+ for (const binaryPath of cachedBinaryPaths(packageDir)) {
185
+ try {
186
+ if (fs.statSync(binaryPath).isFile()) return binaryPath;
187
+ } catch {
188
+ continue;
189
+ }
190
+ }
191
+ return null;
192
+ }
193
+
171
194
  function findNativeBinary(options = {}) {
172
195
  const packages = options.packages ?? nativePackageNames(options);
173
196
  const sourceBinary = options.sourceBinary ?? binaryName(options.platform);
@@ -206,16 +229,52 @@ function recoveryMessage(options = {}) {
206
229
  ].join("\n");
207
230
  }
208
231
 
232
+ function relayedSignals(platform = detectPlatform()) {
233
+ // Windows delivers console Ctrl+C/Ctrl+Break to every process attached to the
234
+ // console, and ChildProcess#kill there is TerminateProcess, so the launcher
235
+ // only stays alive and lets the native binary handle the event itself.
236
+ return platform === "windows"
237
+ ? { forward: false, signals: ["SIGINT", "SIGBREAK"] }
238
+ : { forward: true, signals: ["SIGHUP", "SIGINT", "SIGQUIT", "SIGTERM"] };
239
+ }
240
+
209
241
  function runBinary(target, argv = process.argv.slice(2)) {
210
- const result = childProcess.spawnSync(target, argv, {
242
+ // Listen before spawning: the child can be up and visible to a supervisor
243
+ // before spawn() returns, and a signal that lands before process.on() would
244
+ // kill the launcher with the default action instead of reaching the child.
245
+ // Listeners run on a later tick, so `child` is always assigned by then.
246
+ let child;
247
+ const { forward, signals } = relayedSignals();
248
+ const listeners = signals.map((signal) => {
249
+ const listener = () => {
250
+ if (forward) child.kill(signal);
251
+ };
252
+ process.on(signal, listener);
253
+ return [signal, listener];
254
+ });
255
+ child = childProcess.spawn(target, argv, {
211
256
  stdio: "inherit",
212
257
  windowsHide: true,
213
258
  });
214
- if (result.error) {
215
- console.error(result.error.message);
259
+ const removeListeners = () => {
260
+ for (const [signal, listener] of listeners) process.removeListener(signal, listener);
261
+ };
262
+
263
+ child.on("error", (error) => {
264
+ removeListeners();
265
+ console.error(error.message);
216
266
  process.exit(1);
217
- }
218
- process.exit(typeof result.status === "number" ? result.status : 1);
267
+ });
268
+ child.on("exit", (code, signal) => {
269
+ removeListeners();
270
+ if (signal !== null) {
271
+ // Die the same way so shells and supervisors see the real signal.
272
+ process.kill(process.pid, signal);
273
+ setTimeout(() => process.exit(128 + (os.constants.signals[signal] ?? 0)), 100);
274
+ return;
275
+ }
276
+ process.exit(code ?? 1);
277
+ });
219
278
  }
220
279
 
221
280
  function runNativeBinary(options = {}) {
@@ -225,6 +284,12 @@ function runNativeBinary(options = {}) {
225
284
  return;
226
285
  }
227
286
 
287
+ const cachedBinary = findCachedBinary(options.packageDir);
288
+ if (cachedBinary !== null) {
289
+ runBinary(cachedBinary, options.argv);
290
+ return;
291
+ }
292
+
228
293
  const nativeBinary = findNativeBinary(options);
229
294
  if (nativeBinary === null) {
230
295
  console.error(recoveryMessage(options));
@@ -238,15 +303,19 @@ if (require.main === module) {
238
303
  }
239
304
 
240
305
  module.exports = {
306
+ CACHED_BINARY_NAME,
241
307
  binaryName,
308
+ cachedBinaryPaths,
242
309
  detectArch,
243
310
  detectPlatform,
311
+ findCachedBinary,
244
312
  findNativeBinary,
245
313
  isMusl,
246
314
  nativePackageNames,
247
315
  packageJsonPaths,
248
316
  readPackageJson,
249
317
  recoveryMessage,
318
+ relayedSignals,
250
319
  resolveBinary,
251
320
  runNativeBinary,
252
321
  supportsAvx2,
@@ -12,6 +12,7 @@ const require = createRequire(import.meta.url);
12
12
  const launcher = require("./native-bin-launcher.cjs");
13
13
 
14
14
  const {
15
+ CACHED_BINARY_NAME,
15
16
  binaryName,
16
17
  detectArch,
17
18
  detectPlatform,
@@ -22,7 +23,9 @@ const {
22
23
  supportsAvx2,
23
24
  } = launcher;
24
25
 
25
- const targetBinary = path.join(__dirname, "bin", "nightmaxxing.exe");
26
+ const targetBinary = path.join(__dirname, "bin", CACHED_BINARY_NAME);
27
+ const launcherBinary = path.join(__dirname, "bin", "nightmaxxing");
28
+ const launcherSource = path.join(__dirname, "native-bin-launcher.cjs");
26
29
  let packageJsonCache;
27
30
 
28
31
  function copyBinary(source, target = targetBinary) {
@@ -91,21 +94,58 @@ function verifyBinary(target = targetBinary) {
91
94
  return result.status === 0;
92
95
  }
93
96
 
97
+ // npm and bun link POSIX bins as symlinks, so replacing the JS launcher behind
98
+ // the link makes every call exec the native binary directly. Everywhere else the
99
+ // link is a generated shim that pins the runtime by reading the file when it is
100
+ // linked (Windows cmd/ps1/.bunx shims, pnpm's shell shims) — often before this
101
+ // script runs — and would then run node on a native binary. Those keep the JS
102
+ // launcher, which execs the cached binary.
103
+ function canReplaceLauncher(
104
+ platform = detectPlatform(),
105
+ userAgent = process.env.npm_config_user_agent ?? "",
106
+ ) {
107
+ return platform !== "windows" && /^(npm|bun)\//.test(userAgent);
108
+ }
109
+
110
+ function replaceLauncher(source = targetBinary, target = launcherBinary) {
111
+ try {
112
+ copyBinary(source, target);
113
+ if (verifyBinary(target)) return true;
114
+ } catch {
115
+ // Fall through and restore the launcher.
116
+ }
117
+ try {
118
+ fs.rmSync(target, { force: true });
119
+ fs.copyFileSync(launcherSource, target);
120
+ fs.chmodSync(target, 0o755);
121
+ } catch {
122
+ // Leave whatever is there; the cached binary is still in place.
123
+ }
124
+ return false;
125
+ }
126
+
94
127
  function installNativeBinary() {
95
128
  const packages = nativePackageNames();
96
129
  const sourceBinary = binaryName();
97
130
 
98
131
  for (const packageName of packages) {
132
+ let installed = false;
99
133
  try {
100
134
  copyBinary(resolveBinary(packageName, sourceBinary, { packageDir: __dirname }));
101
- if (verifyBinary()) return;
135
+ installed = verifyBinary();
102
136
  } catch {
103
- if (installPackage(packageName, sourceBinary) && verifyBinary()) return;
137
+ installed = installPackage(packageName, sourceBinary) && verifyBinary();
138
+ }
139
+ if (installed) {
140
+ if (canReplaceLauncher()) replaceLauncher();
141
+ return;
104
142
  }
143
+ // The launcher trusts whatever is cached, so never leave a binary that failed.
144
+ fs.rmSync(targetBinary, { force: true });
105
145
  }
106
146
 
107
147
  throw new Error(
108
- `It seems your package manager failed to install the right Nightmaxxing native package. Try manually installing ${packages
148
+ `It seems your package manager failed to install the right nightmaxxing native package. Try manually installing ${packages
109
149
  .map((packageName) => JSON.stringify(packageName))
110
150
  .join(" or ")}.`,
111
151
  );
@@ -126,6 +166,7 @@ if (isMainModule()) {
126
166
 
127
167
  export {
128
168
  binaryName,
169
+ canReplaceLauncher,
129
170
  copyBinary,
130
171
  detectArch,
131
172
  detectPlatform,
@@ -134,6 +175,7 @@ export {
134
175
  isMusl,
135
176
  nativePackageNames,
136
177
  readPackageJson,
178
+ replaceLauncher,
137
179
  supportsAvx2,
138
180
  verifyBinary,
139
181
  };
@@ -25,6 +25,11 @@ function detectArch(value = os.arch()) {
25
25
  );
26
26
  }
27
27
 
28
+ // The published package's `bin` points at bin/nightmaxxing, a copy of this
29
+ // launcher. Preinstall caches the verified native binary beside it under this
30
+ // name so the launcher can exec it without re-running CPU detection.
31
+ const CACHED_BINARY_NAME = "nightmaxxing.exe";
32
+
28
33
  function binaryName(platform = detectPlatform()) {
29
34
  platform = detectPlatform(platform);
30
35
  return platform === "windows" ? "nightmaxxing.exe" : "nightmaxxing";
@@ -168,6 +173,24 @@ function resolveBinary(packageName, sourceBinary = binaryName(), options = {}) {
168
173
  return binaryPath;
169
174
  }
170
175
 
176
+ function cachedBinaryPaths(packageDir = __dirname) {
177
+ return [
178
+ path.join(packageDir, "bin", CACHED_BINARY_NAME),
179
+ path.join(packageDir, CACHED_BINARY_NAME),
180
+ ];
181
+ }
182
+
183
+ function findCachedBinary(packageDir = __dirname) {
184
+ for (const binaryPath of cachedBinaryPaths(packageDir)) {
185
+ try {
186
+ if (fs.statSync(binaryPath).isFile()) return binaryPath;
187
+ } catch {
188
+ continue;
189
+ }
190
+ }
191
+ return null;
192
+ }
193
+
171
194
  function findNativeBinary(options = {}) {
172
195
  const packages = options.packages ?? nativePackageNames(options);
173
196
  const sourceBinary = options.sourceBinary ?? binaryName(options.platform);
@@ -206,16 +229,52 @@ function recoveryMessage(options = {}) {
206
229
  ].join("\n");
207
230
  }
208
231
 
232
+ function relayedSignals(platform = detectPlatform()) {
233
+ // Windows delivers console Ctrl+C/Ctrl+Break to every process attached to the
234
+ // console, and ChildProcess#kill there is TerminateProcess, so the launcher
235
+ // only stays alive and lets the native binary handle the event itself.
236
+ return platform === "windows"
237
+ ? { forward: false, signals: ["SIGINT", "SIGBREAK"] }
238
+ : { forward: true, signals: ["SIGHUP", "SIGINT", "SIGQUIT", "SIGTERM"] };
239
+ }
240
+
209
241
  function runBinary(target, argv = process.argv.slice(2)) {
210
- const result = childProcess.spawnSync(target, argv, {
242
+ // Listen before spawning: the child can be up and visible to a supervisor
243
+ // before spawn() returns, and a signal that lands before process.on() would
244
+ // kill the launcher with the default action instead of reaching the child.
245
+ // Listeners run on a later tick, so `child` is always assigned by then.
246
+ let child;
247
+ const { forward, signals } = relayedSignals();
248
+ const listeners = signals.map((signal) => {
249
+ const listener = () => {
250
+ if (forward) child.kill(signal);
251
+ };
252
+ process.on(signal, listener);
253
+ return [signal, listener];
254
+ });
255
+ child = childProcess.spawn(target, argv, {
211
256
  stdio: "inherit",
212
257
  windowsHide: true,
213
258
  });
214
- if (result.error) {
215
- console.error(result.error.message);
259
+ const removeListeners = () => {
260
+ for (const [signal, listener] of listeners) process.removeListener(signal, listener);
261
+ };
262
+
263
+ child.on("error", (error) => {
264
+ removeListeners();
265
+ console.error(error.message);
216
266
  process.exit(1);
217
- }
218
- process.exit(typeof result.status === "number" ? result.status : 1);
267
+ });
268
+ child.on("exit", (code, signal) => {
269
+ removeListeners();
270
+ if (signal !== null) {
271
+ // Die the same way so shells and supervisors see the real signal.
272
+ process.kill(process.pid, signal);
273
+ setTimeout(() => process.exit(128 + (os.constants.signals[signal] ?? 0)), 100);
274
+ return;
275
+ }
276
+ process.exit(code ?? 1);
277
+ });
219
278
  }
220
279
 
221
280
  function runNativeBinary(options = {}) {
@@ -225,6 +284,12 @@ function runNativeBinary(options = {}) {
225
284
  return;
226
285
  }
227
286
 
287
+ const cachedBinary = findCachedBinary(options.packageDir);
288
+ if (cachedBinary !== null) {
289
+ runBinary(cachedBinary, options.argv);
290
+ return;
291
+ }
292
+
228
293
  const nativeBinary = findNativeBinary(options);
229
294
  if (nativeBinary === null) {
230
295
  console.error(recoveryMessage(options));
@@ -238,15 +303,19 @@ if (require.main === module) {
238
303
  }
239
304
 
240
305
  module.exports = {
306
+ CACHED_BINARY_NAME,
241
307
  binaryName,
308
+ cachedBinaryPaths,
242
309
  detectArch,
243
310
  detectPlatform,
311
+ findCachedBinary,
244
312
  findNativeBinary,
245
313
  isMusl,
246
314
  nativePackageNames,
247
315
  packageJsonPaths,
248
316
  readPackageJson,
249
317
  recoveryMessage,
318
+ relayedSignals,
250
319
  resolveBinary,
251
320
  runNativeBinary,
252
321
  supportsAvx2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nightrunners/nightmaxxing",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI for Nightmaxxing — sync your LLM token usage to the leaderboard.",
5
5
  "keywords": [
6
6
  "ccusage",
@@ -17,7 +17,7 @@
17
17
  "directory": "apps/cli"
18
18
  },
19
19
  "bin": {
20
- "nightmaxxing": "./bin/nightmaxxing.exe"
20
+ "nightmaxxing": "./bin/nightmaxxing"
21
21
  },
22
22
  "scripts": {
23
23
  "preinstall": "bun ./install-native.mjs || node ./install-native.mjs"
@@ -42,17 +42,17 @@
42
42
  "access": "public"
43
43
  },
44
44
  "optionalDependencies": {
45
- "@nightrunners/nightmaxxing-linux-arm64": "0.1.0",
46
- "@nightrunners/nightmaxxing-linux-x64": "0.1.0",
47
- "@nightrunners/nightmaxxing-linux-x64-baseline": "0.1.0",
48
- "@nightrunners/nightmaxxing-linux-arm64-musl": "0.1.0",
49
- "@nightrunners/nightmaxxing-linux-x64-musl": "0.1.0",
50
- "@nightrunners/nightmaxxing-linux-x64-baseline-musl": "0.1.0",
51
- "@nightrunners/nightmaxxing-darwin-arm64": "0.1.0",
52
- "@nightrunners/nightmaxxing-darwin-x64": "0.1.0",
53
- "@nightrunners/nightmaxxing-darwin-x64-baseline": "0.1.0",
54
- "@nightrunners/nightmaxxing-windows-arm64": "0.1.0",
55
- "@nightrunners/nightmaxxing-windows-x64": "0.1.0",
56
- "@nightrunners/nightmaxxing-windows-x64-baseline": "0.1.0"
45
+ "@nightrunners/nightmaxxing-linux-arm64": "0.1.2",
46
+ "@nightrunners/nightmaxxing-linux-x64": "0.1.2",
47
+ "@nightrunners/nightmaxxing-linux-x64-baseline": "0.1.2",
48
+ "@nightrunners/nightmaxxing-linux-arm64-musl": "0.1.2",
49
+ "@nightrunners/nightmaxxing-linux-x64-musl": "0.1.2",
50
+ "@nightrunners/nightmaxxing-linux-x64-baseline-musl": "0.1.2",
51
+ "@nightrunners/nightmaxxing-darwin-arm64": "0.1.2",
52
+ "@nightrunners/nightmaxxing-darwin-x64": "0.1.2",
53
+ "@nightrunners/nightmaxxing-darwin-x64-baseline": "0.1.2",
54
+ "@nightrunners/nightmaxxing-windows-arm64": "0.1.2",
55
+ "@nightrunners/nightmaxxing-windows-x64": "0.1.2",
56
+ "@nightrunners/nightmaxxing-windows-x64-baseline": "0.1.2"
57
57
  }
58
58
  }