token-larper 1.6.1 → 1.7.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 +9 -2
- package/bin/cli.js +142 -23
- package/package.json +1 -1
- package/src/server.ts +9 -7
package/README.md
CHANGED
|
@@ -99,8 +99,15 @@ You can run Token Larper instantly without cloning:
|
|
|
99
99
|
```bash
|
|
100
100
|
npx token-larper
|
|
101
101
|
# or
|
|
102
|
-
bunx
|
|
102
|
+
bunx token-larper
|
|
103
103
|
```
|
|
104
|
+
It starts in the background, opens the dashboard, and keeps running after you close the terminal. On Windows, quit it from the tray icon. On any platform:
|
|
105
|
+
```bash
|
|
106
|
+
bunx token-larper stop # stop it
|
|
107
|
+
bunx token-larper --foreground # run in the terminal with logs instead
|
|
108
|
+
bunx token-larper --help # all options
|
|
109
|
+
```
|
|
110
|
+
The background server writes its output to `server.log` in the data folder.
|
|
104
111
|
|
|
105
112
|
---
|
|
106
113
|
|
|
@@ -132,7 +139,7 @@ bun run tray
|
|
|
132
139
|
```
|
|
133
140
|
> **Shortcut**: You can also simply **double-click** `Start-TokenLarper.vbs` in File Explorer!
|
|
134
141
|
|
|
135
|
-
*
|
|
142
|
+
* The **Burning t.** icon will appear in your notification tray.
|
|
136
143
|
* Hover over the icon to see your real-time token count and cost estimate.
|
|
137
144
|
* Left-click the icon for a compact usage popup with an **Open dashboard** button. Right-click it to sync or configure boot auto-start.
|
|
138
145
|
|
package/bin/cli.js
CHANGED
|
@@ -1,36 +1,155 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { closeSync, mkdirSync, openSync, readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const rootDir = join(__dirname, "..");
|
|
9
10
|
const serverScript = join(rootDir, "src", "server.ts");
|
|
11
|
+
const port = Number(process.env.PORT || 4269);
|
|
12
|
+
const args = process.argv.slice(2);
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
const HELP = `Token Larper: a local dashboard for AI coding token usage.
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
Usage:
|
|
17
|
+
token-larper Start in the background and open the dashboard
|
|
18
|
+
token-larper --foreground Run in this terminal and show logs (Ctrl+C stops it)
|
|
19
|
+
token-larper --no-open Start without opening the browser
|
|
20
|
+
token-larper stop Stop the running copy
|
|
21
|
+
|
|
22
|
+
Environment: PORT (default 4269), TOKEN_LARPER_DATA_DIR, NO_TRAY=1`;
|
|
23
|
+
|
|
24
|
+
// Same folder as src/paths.ts; the background server's log goes there.
|
|
25
|
+
function dataDir() {
|
|
26
|
+
if (process.env.TOKEN_LARPER_DATA_DIR) return resolve(process.env.TOKEN_LARPER_DATA_DIR);
|
|
27
|
+
const home = homedir();
|
|
28
|
+
if (process.platform === "win32") return join(process.env.LOCALAPPDATA || join(home, "AppData", "Local"), "TokenLarper");
|
|
29
|
+
if (process.platform === "darwin") return join(home, "Library", "Application Support", "TokenLarper");
|
|
30
|
+
return join(process.env.XDG_DATA_HOME || join(home, ".local", "share"), "token-larper");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function bunPath() {
|
|
34
|
+
if (typeof Bun !== "undefined") return process.execPath;
|
|
35
|
+
if (spawnSync("bun", ["--version"], { stdio: "ignore" }).status === 0) return "bun";
|
|
36
|
+
console.error("🔥 Token Larper requires Bun (v1.1+) to run.");
|
|
37
|
+
console.error("\nInstall Bun with one command:");
|
|
38
|
+
if (process.platform === "win32") {
|
|
39
|
+
console.error(' powershell -c "irm bun.sh/install.ps1 | iex"');
|
|
25
40
|
} else {
|
|
26
|
-
console.error("
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
console.error(" curl -fsSL https://bun.sh/install | bash");
|
|
42
|
+
}
|
|
43
|
+
console.error("\nMore info: https://github.com/YarooqH/token-larper\n");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function isRunning(p) {
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(`http://127.0.0.1:${p}/api/tray-status`, { signal: AbortSignal.timeout(800) });
|
|
50
|
+
return res.ok;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function openBrowser(url) {
|
|
57
|
+
const [cmd, cmdArgs] =
|
|
58
|
+
process.platform === "win32" ? ["cmd", ["/c", "start", "", url]]
|
|
59
|
+
: process.platform === "darwin" ? ["open", [url]]
|
|
60
|
+
: ["xdg-open", [url]];
|
|
61
|
+
spawn(cmd, cmdArgs, { stdio: "ignore", detached: true, windowsHide: true }).unref();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
65
|
+
|
|
66
|
+
async function stop() {
|
|
67
|
+
try {
|
|
68
|
+
await fetch(`http://127.0.0.1:${port}/api/shutdown`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body: "{}",
|
|
72
|
+
signal: AbortSignal.timeout(2000),
|
|
73
|
+
});
|
|
74
|
+
} catch {
|
|
75
|
+
console.log(`Token Larper isn't running on port ${port}.`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (let i = 0; i < 20 && (await isRunning(port)); i++) await sleep(250);
|
|
79
|
+
console.log("Stopped Token Larper.");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function foreground() {
|
|
83
|
+
if (typeof Bun !== "undefined") return import(serverScript);
|
|
84
|
+
const child = spawnSync("bun", [serverScript], { stdio: "inherit", cwd: rootDir });
|
|
85
|
+
process.exit(child.status ?? 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function background(open) {
|
|
89
|
+
const bun = bunPath();
|
|
90
|
+
const dir = dataDir();
|
|
91
|
+
mkdirSync(dir, { recursive: true });
|
|
92
|
+
const logFile = join(dir, "server.log");
|
|
93
|
+
const log = openSync(logFile, "w");
|
|
94
|
+
|
|
95
|
+
// The server moves to the next free port if another program has this one, and takes
|
|
96
|
+
// over from an older Token Larper on the same port (see src/server.ts).
|
|
97
|
+
const child = spawn(bun, [serverScript], {
|
|
98
|
+
cwd: rootDir,
|
|
99
|
+
detached: true,
|
|
100
|
+
windowsHide: true,
|
|
101
|
+
stdio: ["ignore", log, log],
|
|
102
|
+
env: { ...process.env, TOKEN_LARPER_NO_BROWSER: "1" },
|
|
103
|
+
});
|
|
104
|
+
let exited = false;
|
|
105
|
+
child.on("exit", () => (exited = true));
|
|
106
|
+
child.unref();
|
|
107
|
+
closeSync(log);
|
|
108
|
+
|
|
109
|
+
const readLog = () => {
|
|
110
|
+
try {
|
|
111
|
+
return readFileSync(logFile, "utf8");
|
|
112
|
+
} catch {
|
|
113
|
+
return "";
|
|
32
114
|
}
|
|
33
|
-
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Ready once the server says where it is listening. A copy of the same version that
|
|
118
|
+
// is already running says so too ("already running at ..."), then the new one exits.
|
|
119
|
+
let url = null;
|
|
120
|
+
for (let i = 0; i < 120 && !url; i++) {
|
|
121
|
+
await sleep(250);
|
|
122
|
+
url = readLog().match(/running at (http:\/\/localhost:\d+)/)?.[1] ?? null;
|
|
123
|
+
if (!url && exited) break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!url) {
|
|
127
|
+
console.error("🔥 Token Larper didn't start.");
|
|
128
|
+
const tail = readLog().trim().split(/\r?\n/).slice(-15).join("\n");
|
|
129
|
+
if (tail) console.error(`\n${tail}\n`);
|
|
130
|
+
console.error(`Full log: ${logFile}`);
|
|
131
|
+
console.error("Run with --foreground to see the server output live.");
|
|
34
132
|
process.exit(1);
|
|
35
133
|
}
|
|
134
|
+
|
|
135
|
+
console.log(`🔥 Token Larper is running at ${url}`);
|
|
136
|
+
console.log(" It keeps running after you close this window.");
|
|
137
|
+
console.log(
|
|
138
|
+
process.platform === "win32"
|
|
139
|
+
? " Quit it from the tray icon, or run: bunx token-larper stop"
|
|
140
|
+
: " Stop it with: bunx token-larper stop"
|
|
141
|
+
);
|
|
142
|
+
if (open) openBrowser(url);
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
147
|
+
console.log(HELP);
|
|
148
|
+
} else if (args[0] === "stop") {
|
|
149
|
+
await stop();
|
|
150
|
+
} else if (args.includes("--foreground") || args.includes("-f")) {
|
|
151
|
+
bunPath();
|
|
152
|
+
await foreground();
|
|
153
|
+
} else {
|
|
154
|
+
await background(!args.includes("--no-open") && process.env.TOKEN_LARPER_NO_BROWSER !== "1");
|
|
36
155
|
}
|
package/package.json
CHANGED
package/src/server.ts
CHANGED
|
@@ -64,18 +64,20 @@ function openDashboard(port: number) {
|
|
|
64
64
|
|
|
65
65
|
/** The version of a Token Larper already on the port, "0.0.0" for one too old to say, or null if none. */
|
|
66
66
|
async function runningVersion(port: number): Promise<string | null> {
|
|
67
|
+
// /api/version answers instantly; versions before 1.5.0 don't have it, so fall back to
|
|
68
|
+
// /api/tray-status, which can take a few seconds while usage data loads.
|
|
67
69
|
try {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/version`, { signal: AbortSignal.timeout(1500) });
|
|
71
|
+
const body = (await res.json().catch(() => null)) as { current?: unknown } | null;
|
|
72
|
+
if (res.ok && typeof body?.current === "string") return body.current;
|
|
70
73
|
} catch {
|
|
71
|
-
|
|
74
|
+
// Nothing on the port, or it is busy; the next check decides.
|
|
72
75
|
}
|
|
73
76
|
try {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
return typeof body.current === "string" ? body.current : "0.0.0";
|
|
77
|
+
const status = await fetch(`http://127.0.0.1:${port}/api/tray-status`, { signal: AbortSignal.timeout(5000) });
|
|
78
|
+
return status.ok ? "0.0.0" : null;
|
|
77
79
|
} catch {
|
|
78
|
-
return
|
|
80
|
+
return null;
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
|