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.
Files changed (57) hide show
  1. package/README.md +5 -5
  2. package/dist/{bootstrap-ink-CHJWH5XC.js → bootstrap-ink-UD3KDTOB.js} +1113 -934
  3. package/dist/{chunk-FO7IUYCM.js → chunk-AON6GE7D.js} +368 -280
  4. package/dist/{chunk-QTHCHB7S.js → chunk-DJPUYMZM.js} +52 -18
  5. package/dist/chunk-HV6AYQDB.js +192 -0
  6. package/dist/chunk-HXDAOOSI.js +154 -0
  7. package/dist/{chunk-AQNFQRWV.js → chunk-PGQK7MOE.js} +2583 -1711
  8. package/dist/cli.js +93 -54
  9. package/dist/{config-64N7KBZ7.js → config-5CNJN3P7.js} +1 -1
  10. package/dist/daemon-AW3YHFSJ.js +193 -0
  11. package/dist/daemon-handle-EYAXNNKZ.js +126 -0
  12. package/dist/server-4CDZMLH6.js +10 -0
  13. package/dist/web/assets/Area-DGo9K-U3.js +2 -0
  14. package/dist/web/assets/analytics-BOhbhV5Z.js +2 -0
  15. package/dist/web/assets/breakdown-CO04nSN-.js +4 -0
  16. package/dist/web/assets/chart-bNxdfXxA.js +69 -0
  17. package/dist/web/assets/explore-sZnmjzAj.js +22 -0
  18. package/dist/web/assets/index-DSj-Bxov.css +1 -0
  19. package/dist/web/assets/index-DnDr1VUr.js +83 -0
  20. package/dist/web/assets/models-Cl6D8VVF.js +2 -0
  21. package/dist/web/assets/overview-C18nvaAK.js +2 -0
  22. package/dist/web/assets/panel-DTjMnHaz.js +1 -0
  23. package/dist/web/assets/primitives-Dj_9YxTF.js +1 -0
  24. package/dist/web/assets/settings-sheet-0BGb5Ahv.js +1 -0
  25. package/dist/web/assets/share-sheet-BHtt-OmT.js +2 -0
  26. package/dist/web/assets/timeline-DmzB8Z3W.js +1 -0
  27. package/dist/web/assets/use-dialog-trap-DeXFtxx8.js +1 -0
  28. package/dist/web/index.html +3 -2
  29. package/package.json +10 -6
  30. package/dist/daemon-ATXHNYTK.js +0 -213
  31. package/dist/daemon-handle-HLSKLMWU.js +0 -142
  32. package/dist/server-56C7MMEZ.js +0 -9
  33. package/dist/web/assets/breakdown-Bj0BAVYf.js +0 -4
  34. package/dist/web/assets/index-BCaPGYQA.css +0 -1
  35. package/dist/web/assets/index-Dziiggw_.js +0 -173
  36. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  37. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  38. package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  39. package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  40. package/dist/web/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
  41. package/dist/web/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
  42. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  43. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  44. package/dist/web/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  45. package/dist/web/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  46. package/dist/web/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
  47. package/dist/web/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
  48. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  49. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  50. package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  51. package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  52. package/dist/web/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
  53. package/dist/web/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
  54. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  55. package/dist/web/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  56. package/dist/web/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
  57. 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
- await writeFile(configLocation() + ".bak", raw);
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
- saveQueue = saveQueue.then(async () => {
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
- const dir = configDir();
313
- await mkdir(dir, { recursive: true });
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
- return saveQueue;
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
- const dir = configDir();
325
- mkdirSync(dir, { recursive: true });
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
+ };