tokmon 0.22.6 → 0.23.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 +5 -5
- package/dist/{bootstrap-ink-CHJWH5XC.js → bootstrap-ink-UD3KDTOB.js} +1113 -934
- package/dist/{chunk-FO7IUYCM.js → chunk-AON6GE7D.js} +368 -280
- package/dist/{chunk-QTHCHB7S.js → chunk-DJPUYMZM.js} +52 -18
- package/dist/chunk-HV6AYQDB.js +192 -0
- package/dist/chunk-HXDAOOSI.js +154 -0
- package/dist/{chunk-AQNFQRWV.js → chunk-PGQK7MOE.js} +2583 -1711
- package/dist/cli.js +93 -54
- package/dist/{config-64N7KBZ7.js → config-5CNJN3P7.js} +1 -1
- package/dist/daemon-AW3YHFSJ.js +193 -0
- package/dist/daemon-handle-EYAXNNKZ.js +126 -0
- package/dist/server-4CDZMLH6.js +10 -0
- package/dist/web/assets/Area-DGo9K-U3.js +2 -0
- package/dist/web/assets/analytics-BOhbhV5Z.js +2 -0
- package/dist/web/assets/breakdown-CO04nSN-.js +4 -0
- package/dist/web/assets/chart-bNxdfXxA.js +69 -0
- package/dist/web/assets/explore-sZnmjzAj.js +22 -0
- package/dist/web/assets/index-DSj-Bxov.css +1 -0
- package/dist/web/assets/index-DnDr1VUr.js +83 -0
- package/dist/web/assets/models-Cl6D8VVF.js +2 -0
- package/dist/web/assets/overview-C18nvaAK.js +2 -0
- package/dist/web/assets/panel-DTjMnHaz.js +1 -0
- package/dist/web/assets/primitives-Dj_9YxTF.js +1 -0
- package/dist/web/assets/settings-sheet-0BGb5Ahv.js +1 -0
- package/dist/web/assets/share-sheet-BHtt-OmT.js +2 -0
- package/dist/web/assets/timeline-DmzB8Z3W.js +1 -0
- package/dist/web/assets/use-dialog-trap-DeXFtxx8.js +1 -0
- package/dist/web/index.html +3 -2
- package/package.json +10 -6
- package/dist/daemon-ATXHNYTK.js +0 -213
- package/dist/daemon-handle-HLSKLMWU.js +0 -142
- package/dist/server-56C7MMEZ.js +0 -9
- package/dist/web/assets/breakdown-Bj0BAVYf.js +0 -4
- package/dist/web/assets/index-BCaPGYQA.css +0 -1
- package/dist/web/assets/index-Dziiggw_.js +0 -173
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
- package/dist/web/assets/timeline-BQpqTU-a.js +0 -1
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/config.ts
|
|
4
|
-
import { readFile, writeFile, mkdir, rename } from "fs/promises";
|
|
5
|
-
import { mkdirSync, renameSync, writeFileSync } from "fs";
|
|
4
|
+
import { chmod, readFile, writeFile, mkdir, rename, unlink } from "fs/promises";
|
|
5
|
+
import { chmodSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
6
6
|
import { join, isAbsolute } from "path";
|
|
7
7
|
import { homedir } from "os";
|
|
8
8
|
|
|
9
9
|
// src/providers/types.ts
|
|
10
|
-
var PROVIDER_IDS = ["claude", "codex", "cursor", "copilot", "pi", "opencode", "antigravity", "gemini"];
|
|
10
|
+
var PROVIDER_IDS = ["claude", "codex", "cursor", "copilot", "pi", "opencode", "antigravity", "gemini", "grok"];
|
|
11
11
|
|
|
12
12
|
// src/config-schema.ts
|
|
13
13
|
var DEFAULTS = {
|
|
14
|
+
revision: 0,
|
|
14
15
|
interval: 2,
|
|
15
16
|
billingInterval: 5,
|
|
16
17
|
clearScreen: true,
|
|
@@ -48,7 +49,8 @@ var PROVIDER_META = {
|
|
|
48
49
|
pi: { name: "Pi", color: "blue" },
|
|
49
50
|
opencode: { name: "opencode", color: "yellow" },
|
|
50
51
|
antigravity: { name: "Antigravity", color: "red" },
|
|
51
|
-
gemini: { name: "Gemini", color: "greenBright" }
|
|
52
|
+
gemini: { name: "Gemini", color: "greenBright" },
|
|
53
|
+
grok: { name: "Grok", color: "yellowBright" }
|
|
52
54
|
};
|
|
53
55
|
var EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i;
|
|
54
56
|
var EMAIL_RE_GLOBAL = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
@@ -198,6 +200,7 @@ function repairConfig(input) {
|
|
|
198
200
|
}
|
|
199
201
|
const config = {
|
|
200
202
|
...DEFAULTS,
|
|
203
|
+
revision: typeof parsed.revision === "number" && Number.isSafeInteger(parsed.revision) && parsed.revision >= 0 ? parsed.revision : DEFAULTS.revision,
|
|
201
204
|
interval: clampNum(parsed.interval, DEFAULTS.interval, 1),
|
|
202
205
|
billingInterval: clampNum(parsed.billingInterval, DEFAULTS.billingInterval, 1),
|
|
203
206
|
clearScreen: typeof parsed.clearScreen === "boolean" ? parsed.clearScreen : DEFAULTS.clearScreen,
|
|
@@ -283,12 +286,22 @@ async function loadConfig() {
|
|
|
283
286
|
} catch {
|
|
284
287
|
return { ...DEFAULTS };
|
|
285
288
|
}
|
|
289
|
+
try {
|
|
290
|
+
await chmod(configDir(), 448);
|
|
291
|
+
await chmod(configLocation(), 384);
|
|
292
|
+
} catch {
|
|
293
|
+
}
|
|
286
294
|
let parsed;
|
|
287
295
|
try {
|
|
288
296
|
parsed = JSON.parse(raw);
|
|
289
297
|
} catch {
|
|
290
298
|
try {
|
|
291
|
-
|
|
299
|
+
const dir = configDir();
|
|
300
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
301
|
+
await chmod(dir, 448);
|
|
302
|
+
const backup = configLocation() + ".bak";
|
|
303
|
+
await writeFile(backup, raw, { mode: 384 });
|
|
304
|
+
await chmod(backup, 384);
|
|
292
305
|
} catch {
|
|
293
306
|
}
|
|
294
307
|
return { ...DEFAULTS };
|
|
@@ -303,30 +316,51 @@ async function loadConfig() {
|
|
|
303
316
|
return repaired.config;
|
|
304
317
|
}
|
|
305
318
|
var saveQueue = Promise.resolve();
|
|
319
|
+
var tempSequence = 0;
|
|
320
|
+
function configTempFile(dir) {
|
|
321
|
+
tempSequence = (tempSequence + 1) % Number.MAX_SAFE_INTEGER;
|
|
322
|
+
return join(dir, `config.json.${process.pid}.${Date.now()}.${tempSequence}.tmp`);
|
|
323
|
+
}
|
|
306
324
|
function configJson(config) {
|
|
307
325
|
return JSON.stringify(normalizeConfig(config), null, 2) + "\n";
|
|
308
326
|
}
|
|
309
327
|
function saveConfig(config) {
|
|
310
|
-
|
|
328
|
+
const write = async () => {
|
|
329
|
+
const dir = configDir();
|
|
330
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
331
|
+
await chmod(dir, 448);
|
|
332
|
+
const tmp = configTempFile(dir);
|
|
311
333
|
try {
|
|
312
|
-
|
|
313
|
-
await
|
|
314
|
-
const tmp = join(dir, `config.json.${process.pid}.tmp`);
|
|
315
|
-
await writeFile(tmp, configJson(config));
|
|
334
|
+
await writeFile(tmp, configJson(config), { mode: 384 });
|
|
335
|
+
await chmod(tmp, 384);
|
|
316
336
|
await rename(tmp, configLocation());
|
|
317
|
-
} catch {
|
|
337
|
+
} catch (error) {
|
|
338
|
+
try {
|
|
339
|
+
await unlink(tmp);
|
|
340
|
+
} catch {
|
|
341
|
+
}
|
|
342
|
+
throw error;
|
|
318
343
|
}
|
|
319
|
-
}
|
|
320
|
-
|
|
344
|
+
};
|
|
345
|
+
const operation = saveQueue.catch(() => void 0).then(write);
|
|
346
|
+
saveQueue = operation;
|
|
347
|
+
return operation;
|
|
321
348
|
}
|
|
322
349
|
function saveConfigSync(config) {
|
|
350
|
+
const dir = configDir();
|
|
351
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
352
|
+
chmodSync(dir, 448);
|
|
353
|
+
const tmp = configTempFile(dir);
|
|
323
354
|
try {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const tmp = join(dir, `config.json.${process.pid}.tmp`);
|
|
327
|
-
writeFileSync(tmp, configJson(config));
|
|
355
|
+
writeFileSync(tmp, configJson(config), { mode: 384 });
|
|
356
|
+
chmodSync(tmp, 384);
|
|
328
357
|
renameSync(tmp, configLocation());
|
|
329
|
-
} catch {
|
|
358
|
+
} catch (error) {
|
|
359
|
+
try {
|
|
360
|
+
unlinkSync(tmp);
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
throw error;
|
|
330
364
|
}
|
|
331
365
|
}
|
|
332
366
|
function expandHome(p) {
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
cacheDir
|
|
4
|
+
} from "./chunk-DJPUYMZM.js";
|
|
5
|
+
|
|
6
|
+
// src/web/lockfile.ts
|
|
7
|
+
import { closeSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
8
|
+
import { isAbsolute, join } from "path";
|
|
9
|
+
function lockDir(opts = {}) {
|
|
10
|
+
const injected = opts.cachePath ?? process.env.TOKMON_DAEMON_CACHE_DIR;
|
|
11
|
+
return injected && isAbsolute(injected) ? injected : cacheDir();
|
|
12
|
+
}
|
|
13
|
+
function lockfilePath(opts = {}) {
|
|
14
|
+
return join(lockDir(opts), "daemon.json");
|
|
15
|
+
}
|
|
16
|
+
function isLoopbackUrl(value) {
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(value);
|
|
19
|
+
return url.protocol === "http:" && (url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "::1");
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function validLock(value) {
|
|
25
|
+
const lock = value;
|
|
26
|
+
return !!lock && typeof lock.pid === "number" && Number.isInteger(lock.pid) && lock.pid > 0 && typeof lock.port === "number" && Number.isInteger(lock.port) && lock.port >= 0 && lock.port <= 65535 && typeof lock.url === "string" && (lock.state === "starting" || isLoopbackUrl(lock.url)) && typeof lock.wsToken === "string" && lock.wsToken.length >= 32 && typeof lock.version === "string" && typeof lock.startedAt === "number" && typeof lock.ownerId === "string" && lock.ownerId.length >= 32 && (lock.state === "starting" || lock.state === "ready");
|
|
27
|
+
}
|
|
28
|
+
function readLock(opts = {}) {
|
|
29
|
+
try {
|
|
30
|
+
const path = lockfilePath(opts);
|
|
31
|
+
const stat = statSync(path);
|
|
32
|
+
if (!stat.isFile() || (stat.mode & 63) !== 0) return null;
|
|
33
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
34
|
+
return validLock(parsed) ? parsed : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function ensureLockDir(opts) {
|
|
40
|
+
const dir = lockDir(opts);
|
|
41
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
42
|
+
let fd;
|
|
43
|
+
try {
|
|
44
|
+
fd = openSync(dir, "r");
|
|
45
|
+
fchmodSync(fd, 448);
|
|
46
|
+
} catch {
|
|
47
|
+
} finally {
|
|
48
|
+
if (fd !== void 0) try {
|
|
49
|
+
closeSync(fd);
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function writeNew(path, lock) {
|
|
55
|
+
let fd;
|
|
56
|
+
let created = false;
|
|
57
|
+
try {
|
|
58
|
+
fd = openSync(path, "wx", 384);
|
|
59
|
+
created = true;
|
|
60
|
+
fchmodSync(fd, 384);
|
|
61
|
+
writeFileSync(fd, JSON.stringify(lock));
|
|
62
|
+
fsyncSync(fd);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
if (created) try {
|
|
66
|
+
unlinkSync(path);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
} finally {
|
|
71
|
+
if (fd !== void 0) try {
|
|
72
|
+
closeSync(fd);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function acquireLock(lock, opts = {}) {
|
|
78
|
+
try {
|
|
79
|
+
ensureLockDir(opts);
|
|
80
|
+
return writeNew(lockfilePath(opts), lock);
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function writeLock(lock, opts = {}) {
|
|
86
|
+
const current = readLock(opts);
|
|
87
|
+
if (!current || current.ownerId !== lock.ownerId || current.pid !== process.pid) return false;
|
|
88
|
+
const path = lockfilePath(opts);
|
|
89
|
+
const tmp = join(lockDir(opts), `daemon.json.${process.pid}.${lock.ownerId}.tmp`);
|
|
90
|
+
let fd;
|
|
91
|
+
try {
|
|
92
|
+
writeFileSync(tmp, JSON.stringify(lock), { mode: 384 });
|
|
93
|
+
try {
|
|
94
|
+
fd = openSync(tmp, "r");
|
|
95
|
+
fchmodSync(fd, 384);
|
|
96
|
+
fsyncSync(fd);
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
renameSync(tmp, path);
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
try {
|
|
103
|
+
unlinkSync(tmp);
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
} finally {
|
|
108
|
+
if (fd !== void 0) try {
|
|
109
|
+
closeSync(fd);
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function unlinkLock(ownerId, opts = {}) {
|
|
115
|
+
const current = readLock(opts);
|
|
116
|
+
if (!current || current.ownerId !== ownerId || current.pid !== process.pid) return false;
|
|
117
|
+
try {
|
|
118
|
+
unlinkSync(lockfilePath(opts));
|
|
119
|
+
return true;
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function reclaimDeadLock(opts = {}) {
|
|
125
|
+
const current = readLock(opts);
|
|
126
|
+
if (!current || isAlive(current.pid)) return false;
|
|
127
|
+
try {
|
|
128
|
+
unlinkSync(lockfilePath(opts));
|
|
129
|
+
return true;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function reclaimAbandonedLock(opts = {}, graceMs = 1e4) {
|
|
135
|
+
const path = lockfilePath(opts);
|
|
136
|
+
try {
|
|
137
|
+
const before = lstatSync(path);
|
|
138
|
+
if (!before.isFile()) return false;
|
|
139
|
+
let pid = null;
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
142
|
+
if (Number.isInteger(parsed.pid) && parsed.pid > 0) pid = parsed.pid;
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
if (pid !== null && isAlive(pid)) return false;
|
|
146
|
+
if (pid === null && Date.now() - before.mtimeMs < graceMs) return false;
|
|
147
|
+
const after = lstatSync(path);
|
|
148
|
+
if (after.dev !== before.dev || after.ino !== before.ino) return false;
|
|
149
|
+
unlinkSync(path);
|
|
150
|
+
return true;
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function isAlive(pid) {
|
|
156
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
157
|
+
try {
|
|
158
|
+
process.kill(pid, 0);
|
|
159
|
+
return true;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
return err.code === "EPERM";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function probeHealth(url, token, version, timeoutMs = 500) {
|
|
165
|
+
if (!isLoopbackUrl(url) || !token) return false;
|
|
166
|
+
try {
|
|
167
|
+
const res = await fetch(`${url.replace(/\/+$/, "")}/healthz`, {
|
|
168
|
+
headers: { "x-tokmon-token": token },
|
|
169
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
170
|
+
});
|
|
171
|
+
if (!res.ok) return false;
|
|
172
|
+
const body = await res.json();
|
|
173
|
+
return body.ok === true && body.owner === true && (version === void 0 || body.version === version);
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function verifyLock(lock, version, timeoutMs) {
|
|
179
|
+
if (!lock || lock.state !== "ready" || lock.version !== version || !isAlive(lock.pid)) return null;
|
|
180
|
+
return await probeHealth(lock.url, lock.wsToken, version, timeoutMs) ? lock : null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
readLock,
|
|
185
|
+
acquireLock,
|
|
186
|
+
writeLock,
|
|
187
|
+
unlinkLock,
|
|
188
|
+
reclaimDeadLock,
|
|
189
|
+
reclaimAbandonedLock,
|
|
190
|
+
isAlive,
|
|
191
|
+
verifyLock
|
|
192
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/web/static.ts
|
|
4
|
+
import { createReadStream, existsSync, readFileSync } from "fs";
|
|
5
|
+
import { stat } from "fs/promises";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { extname, join, normalize, sep } from "path";
|
|
8
|
+
var MIME = {
|
|
9
|
+
".html": "text/html; charset=utf-8",
|
|
10
|
+
".js": "text/javascript; charset=utf-8",
|
|
11
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
12
|
+
".css": "text/css; charset=utf-8",
|
|
13
|
+
".json": "application/json; charset=utf-8",
|
|
14
|
+
".svg": "image/svg+xml",
|
|
15
|
+
".png": "image/png",
|
|
16
|
+
".jpg": "image/jpeg",
|
|
17
|
+
".jpeg": "image/jpeg",
|
|
18
|
+
".gif": "image/gif",
|
|
19
|
+
".ico": "image/x-icon",
|
|
20
|
+
".woff": "font/woff",
|
|
21
|
+
".woff2": "font/woff2",
|
|
22
|
+
".ttf": "font/ttf",
|
|
23
|
+
".otf": "font/otf",
|
|
24
|
+
".map": "application/json; charset=utf-8",
|
|
25
|
+
".webmanifest": "application/manifest+json"
|
|
26
|
+
};
|
|
27
|
+
function findWebRoot() {
|
|
28
|
+
const candidates = ["./web/", "../web/", "../dist/web/", "../../dist/web/"];
|
|
29
|
+
for (const rel of candidates) {
|
|
30
|
+
try {
|
|
31
|
+
const dir = fileURLToPath(new URL(rel, import.meta.url));
|
|
32
|
+
if (existsSync(join(dir, "index.html"))) return dir.replace(/[\\/]+$/, "");
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function resolveStaticPath(webRoot, urlPath) {
|
|
39
|
+
let clean;
|
|
40
|
+
try {
|
|
41
|
+
clean = decodeURIComponent(urlPath.split("?")[0]);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const rel = normalize(clean).replace(/^(\.\.[/\\])+/, "").replace(/^[/\\]+/, "");
|
|
46
|
+
const full = join(webRoot, rel);
|
|
47
|
+
if (full !== webRoot && !full.startsWith(webRoot + sep)) return null;
|
|
48
|
+
return full;
|
|
49
|
+
}
|
|
50
|
+
function send(res, status, type, body) {
|
|
51
|
+
res.writeHead(status, {
|
|
52
|
+
"Content-Type": type,
|
|
53
|
+
"Cache-Control": "no-store",
|
|
54
|
+
"Referrer-Policy": "no-referrer",
|
|
55
|
+
"X-Content-Type-Options": "nosniff"
|
|
56
|
+
});
|
|
57
|
+
res.end(body);
|
|
58
|
+
}
|
|
59
|
+
function sendJson(res, status, data) {
|
|
60
|
+
send(res, status, "application/json; charset=utf-8", JSON.stringify(data));
|
|
61
|
+
}
|
|
62
|
+
function serveStatic(webRoot, urlPath, res) {
|
|
63
|
+
const path = urlPath.split("?")[0];
|
|
64
|
+
const filePath = resolveStaticPath(webRoot, path === "/" ? "/index.html" : path);
|
|
65
|
+
if (!filePath) {
|
|
66
|
+
send(res, 403, "text/plain", "forbidden");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
void stat(filePath).then((st) => {
|
|
70
|
+
if (st.isFile()) {
|
|
71
|
+
const type = MIME[extname(filePath).toLowerCase()] || "application/octet-stream";
|
|
72
|
+
const immutable = filePath.includes(`${sep}assets${sep}`);
|
|
73
|
+
res.writeHead(200, {
|
|
74
|
+
"Content-Type": type,
|
|
75
|
+
"Cache-Control": immutable ? "public, max-age=31536000, immutable" : "no-cache",
|
|
76
|
+
"Referrer-Policy": "no-referrer",
|
|
77
|
+
"X-Content-Type-Options": "nosniff"
|
|
78
|
+
});
|
|
79
|
+
createReadStream(filePath).pipe(res);
|
|
80
|
+
} else {
|
|
81
|
+
throw new Error("not a file");
|
|
82
|
+
}
|
|
83
|
+
}).catch(() => {
|
|
84
|
+
if (extname(path)) {
|
|
85
|
+
send(res, 404, "text/plain", "not found");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const indexPath = join(webRoot, "index.html");
|
|
89
|
+
if (!existsSync(indexPath)) {
|
|
90
|
+
send(res, 404, "text/plain", "not found");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
res.writeHead(200, {
|
|
94
|
+
"Content-Type": MIME[".html"],
|
|
95
|
+
"Cache-Control": "no-cache",
|
|
96
|
+
"Referrer-Policy": "no-referrer",
|
|
97
|
+
"X-Content-Type-Options": "nosniff"
|
|
98
|
+
});
|
|
99
|
+
createReadStream(indexPath).on("error", () => {
|
|
100
|
+
try {
|
|
101
|
+
res.destroy();
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}).pipe(res);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function appVersion() {
|
|
108
|
+
for (const rel of ["../package.json", "../../package.json"]) {
|
|
109
|
+
try {
|
|
110
|
+
const p = fileURLToPath(new URL(rel, import.meta.url));
|
|
111
|
+
const pkg = JSON.parse(readFileSync(p, "utf-8"));
|
|
112
|
+
if (typeof pkg.version === "string") return pkg.version;
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/web/open.ts
|
|
120
|
+
import { spawn } from "child_process";
|
|
121
|
+
import { appendFileSync } from "fs";
|
|
122
|
+
function browserUrl(url, wsToken) {
|
|
123
|
+
return wsToken ? `${url}#tokmonToken=${encodeURIComponent(wsToken)}` : url;
|
|
124
|
+
}
|
|
125
|
+
function openBrowser(url, wsToken) {
|
|
126
|
+
const target = browserUrl(url, wsToken);
|
|
127
|
+
if (process.env.TOKMON_OPENLOG) {
|
|
128
|
+
try {
|
|
129
|
+
appendFileSync(process.env.TOKMON_OPENLOG, target + "\n");
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
if (process.platform === "darwin") {
|
|
136
|
+
spawn("open", [target], { stdio: "ignore", detached: true }).unref();
|
|
137
|
+
} else if (process.platform === "win32") {
|
|
138
|
+
spawn("cmd", ["/c", "start", "", target], { stdio: "ignore", detached: true }).unref();
|
|
139
|
+
} else {
|
|
140
|
+
spawn("xdg-open", [target], { stdio: "ignore", detached: true }).unref();
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export {
|
|
147
|
+
findWebRoot,
|
|
148
|
+
send,
|
|
149
|
+
sendJson,
|
|
150
|
+
serveStatic,
|
|
151
|
+
appVersion,
|
|
152
|
+
browserUrl,
|
|
153
|
+
openBrowser
|
|
154
|
+
};
|