tokenmaxxing 1.5.0 → 1.6.0
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 +27 -0
- package/package.json +4 -3
- package/src/lib/install.ts +62 -2
package/README.md
CHANGED
|
@@ -23,6 +23,33 @@ bun add -g tokenmaxxing
|
|
|
23
23
|
tokenmaxxing init
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Or with Nix (same source-run-by-Bun package; `init` still owns credentials, the `claude` shim, and settings merges). Install onto PATH first, then init — `nix run ... -- init` alone leaves supervisor shims without a stable `tokenmaxxing` on PATH after the ephemeral run exits:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
nix profile install github:anaclumos/tokenmaxxing
|
|
30
|
+
tokenmaxxing init
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
nix-darwin:
|
|
34
|
+
|
|
35
|
+
```nix
|
|
36
|
+
# flake inputs: tokenmaxxing.url = "github:anaclumos/tokenmaxxing";
|
|
37
|
+
modules = [
|
|
38
|
+
inputs.tokenmaxxing.darwinModules.withOverlay
|
|
39
|
+
{ programs.tokenmaxxing.enable = true; }
|
|
40
|
+
];
|
|
41
|
+
# then: tokenmaxxing init
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Home Manager:
|
|
45
|
+
|
|
46
|
+
```nix
|
|
47
|
+
imports = [ inputs.tokenmaxxing.homeManagerModules.default ];
|
|
48
|
+
programs.tokenmaxxing.enable = true;
|
|
49
|
+
programs.tokenmaxxing.package = inputs.tokenmaxxing.packages.${pkgs.system}.default;
|
|
50
|
+
# then: tokenmaxxing init
|
|
51
|
+
```
|
|
52
|
+
|
|
26
53
|
`init` imports the account you're already on, installs the `claude` supervisor + four `settings.json` entries (the tokenmaxxing statusLine, a subagentStatusLine, a Stop hook, a SessionStart hook), and adds the supervisor's bin dir to PATH in your shell rc (idempotent; it must sit ahead of the real `claude` to intercept it). Restart your shell, then add more accounts and go:
|
|
27
54
|
|
|
28
55
|
```sh
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenmaxxing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Automatic Claude Code account switching: pool multiple accounts and hot-swap when quota fills, resuming your session on the fresh account.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,10 +33,11 @@
|
|
|
33
33
|
"dev": "bun run src/main.ts",
|
|
34
34
|
"test": "bun test",
|
|
35
35
|
"typecheck": "tsc --noEmit",
|
|
36
|
-
"prepublishOnly": "bun run typecheck && bun run test"
|
|
36
|
+
"prepublishOnly": "bun run typecheck && bun run test",
|
|
37
|
+
"nix:bun": "bunx bun2nix@2.1.2 -o bun.nix"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@types/bun": "
|
|
40
|
+
"@types/bun": "1.3.14",
|
|
40
41
|
"typescript": "^5.6.0"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
package/src/lib/install.ts
CHANGED
|
@@ -33,14 +33,65 @@ export function isBinDirAhead(): boolean {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Optional "1"/"true"/"yes" flag; unset/empty → undefined (feature off).
|
|
37
|
+
const EnvFlagSchema = z.enum(["1", "true", "yes"]).optional().catch(undefined);
|
|
38
|
+
|
|
39
|
+
/** True when this process is the Nix-packaged CLI (flake startScript sets
|
|
40
|
+
* TOKENMAXXING_NIX=1; store-path Bun.main is the fallback for wraps that
|
|
41
|
+
* forget the env). Env overrides parse at the read site. */
|
|
42
|
+
export function isNixPackaged(): boolean {
|
|
43
|
+
if (EnvFlagSchema.parse(process.env.TOKENMAXXING_NIX) != null) return true;
|
|
44
|
+
try {
|
|
45
|
+
return realpathSync(Bun.main).startsWith("/nix/store/");
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** True when a Nix module owns the periodic check timer; init must not write
|
|
52
|
+
* a second imperative unit. */
|
|
53
|
+
export function skipImperativeTimer(): boolean {
|
|
54
|
+
return EnvFlagSchema.parse(process.env.TOKENMAXXING_SKIP_TIMER) != null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Nix supervisor shim: prefer a PATH-stable `tokenmaxxing` (profile /
|
|
58
|
+
* current-system, excluding this binDir) so upgrades/GC of an old store
|
|
59
|
+
* generation stay reachable; fall back to bun+entry for the rare
|
|
60
|
+
* `nix run ... -- init` case where nothing is on PATH yet (works until that
|
|
61
|
+
* generation is GC'd — docs steer users to `nix profile install` first). */
|
|
62
|
+
function nixSupervisorShim(bun: string, entry: string): string {
|
|
63
|
+
return `#!/bin/sh
|
|
64
|
+
dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
|
65
|
+
old_ifs=$IFS
|
|
66
|
+
IFS=:
|
|
67
|
+
new_path=
|
|
68
|
+
for p in $PATH; do
|
|
69
|
+
[ "$p" = "$dir" ] && continue
|
|
70
|
+
if [ -n "$new_path" ]; then new_path="$new_path:$p"; else new_path="$p"; fi
|
|
71
|
+
done
|
|
72
|
+
IFS=$old_ifs
|
|
73
|
+
PATH=$new_path
|
|
74
|
+
export PATH
|
|
75
|
+
if command -v tokenmaxxing >/dev/null 2>&1; then
|
|
76
|
+
exec tokenmaxxing "$@"
|
|
77
|
+
fi
|
|
78
|
+
exec ${JSON.stringify(bun)} run ${JSON.stringify(entry)} "$@"
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
|
|
36
82
|
export function installSupervisor(): InstallOutcome {
|
|
37
83
|
mkdirSync(paths.binDir, { recursive: true });
|
|
38
84
|
const target = installedBin(); // binDir/tokenmaxxing
|
|
39
85
|
// Resolve the entry through the global-bin symlink (bun add -g links
|
|
40
86
|
// ~/.bun/bin/tokenmaxxing → the package's src/main.ts) so the shim points
|
|
41
|
-
// into the installed package tree, where its imports resolve.
|
|
87
|
+
// into the installed package tree, where its imports resolve. Nix shims
|
|
88
|
+
// prefer PATH first (see nixSupervisorShim).
|
|
42
89
|
const entry = realpathSync(Bun.main);
|
|
43
|
-
|
|
90
|
+
if (isNixPackaged()) {
|
|
91
|
+
writeFileAtomic(target, nixSupervisorShim(process.execPath, entry), 0o755);
|
|
92
|
+
} else {
|
|
93
|
+
writeFileAtomic(target, `#!/bin/sh\nexec ${JSON.stringify(process.execPath)} run ${JSON.stringify(entry)} "$@"\n`, 0o755);
|
|
94
|
+
}
|
|
44
95
|
|
|
45
96
|
// the on-PATH `claude` wrapper
|
|
46
97
|
writeFileAtomic(paths.supervisorLink, `#!/bin/sh\nexec ${JSON.stringify(target)} __supervise "$@"\n`, 0o755);
|
|
@@ -174,6 +225,10 @@ function run(cmd: string[]): boolean {
|
|
|
174
225
|
* place but activation failed (e.g. systemd user session absent over ssh) -
|
|
175
226
|
* the caller prints the manual activation step. */
|
|
176
227
|
function installCheckTimer(): boolean {
|
|
228
|
+
// Nix module owns the timer (TOKENMAXXING_SKIP_TIMER): do not write a second
|
|
229
|
+
// unit that would double-fire or clobber the declarative one.
|
|
230
|
+
if (skipImperativeTimer()) return true;
|
|
231
|
+
|
|
177
232
|
if (process.platform === "darwin") {
|
|
178
233
|
const plist = launchdPlist();
|
|
179
234
|
writeFileAtomic(
|
|
@@ -243,6 +298,9 @@ export function timerActivationHint(): string {
|
|
|
243
298
|
|
|
244
299
|
/** True when the timer unit exists AND the service manager reports it loaded. */
|
|
245
300
|
export function checkTimerHealthy(): boolean {
|
|
301
|
+
// Declarative Nix timer: init wrote nothing; doctor must not demand the
|
|
302
|
+
// imperative unit.
|
|
303
|
+
if (skipImperativeTimer()) return true;
|
|
246
304
|
if (process.platform === "darwin") {
|
|
247
305
|
const domain = launchdDomain();
|
|
248
306
|
return existsSync(launchdPlist()) && domain != null && run(["launchctl", "print", `${domain}/${LAUNCHD_LABEL}`]);
|
|
@@ -305,6 +363,8 @@ function systemdTimerActive(): "active" | "not-active" | "unavailable" {
|
|
|
305
363
|
* loaded must deactivate successfully, and an unanswerable probe (service
|
|
306
364
|
* manager unusable) reports false rather than pretending it is gone. */
|
|
307
365
|
function uninstallCheckTimer(): boolean {
|
|
366
|
+
// Nix owns the timer: do not bootout/disable the declarative unit.
|
|
367
|
+
if (skipImperativeTimer()) return true;
|
|
308
368
|
if (process.platform === "darwin") {
|
|
309
369
|
const domain = launchdDomain();
|
|
310
370
|
const loaded = launchdJobLoaded();
|