tokmon 0.28.0 → 0.28.1
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 +1 -1
- package/dist/{bootstrap-ink-5LQMSU4B.js → bootstrap-ink-L7QSJBMS.js} +119 -9
- package/dist/{chunk-QOA6KOJQ.js → chunk-3RTWFGGD.js} +1 -1
- package/dist/{chunk-AMD4PXDG.js → chunk-4HU4EI5T.js} +1 -1
- package/dist/{chunk-IWKSA64G.js → chunk-CVKCVHS7.js} +4 -3
- package/dist/{chunk-KCY53RUE.js → chunk-FMP3P2WV.js} +2 -2
- package/dist/{chunk-E2YXYU73.js → chunk-HP5UZCXP.js} +29 -0
- package/dist/{chunk-JK3U54G7.js → chunk-S33XIUAW.js} +11 -11
- package/dist/{chunk-QM5E5RJZ.js → chunk-XDA5RJST.js} +1 -1
- package/dist/{cli-command-HXKWB6IP.js → cli-command-75LP4IDS.js} +5 -5
- package/dist/cli.js +6 -6
- package/dist/{config-2PXUENQL.js → config-HXFJTNLM.js} +5 -1
- package/dist/{daemon-JCQF645I.js → daemon-CL4FJW26.js} +5 -5
- package/dist/{daemon-handle-2GVZT55B.js → daemon-handle-QQLJE46Y.js} +3 -3
- package/dist/server-EUO7CWYH.js +11 -0
- package/dist/web/assets/{Area-DgGVYaNT.js → Area-CMXvOw33.js} +1 -1
- package/dist/web/assets/{analytics-D5EvgF4d.js → analytics-DqdZXOJL.js} +2 -2
- package/dist/web/assets/{breakdown-CGdJkAHx.js → breakdown-kOSSJ3mI.js} +1 -1
- package/dist/web/assets/{chart-D4KHj2-J.js → chart-cZoLgpmG.js} +1 -1
- package/dist/web/assets/{explore-B2QnGzfO.js → explore-vcsM8K9I.js} +1 -1
- package/dist/web/assets/index-BErCXT7f.css +1 -0
- package/dist/web/assets/index-DH3dOnbg.js +83 -0
- package/dist/web/assets/{models-D7w7JJCX.js → models-B5ikm83_.js} +2 -2
- package/dist/web/assets/{overview-BPz0j397.js → overview-DD1kP8CP.js} +2 -2
- package/dist/web/assets/{panel-Da3rkvLu.js → panel-DTQBwExW.js} +1 -1
- package/dist/web/assets/{primitives-B8eGD8S_.js → primitives-C36qJlA2.js} +1 -1
- package/dist/web/assets/settings-sheet-_i2zTQjf.js +1 -0
- package/dist/web/assets/{share-sheet-BXrrMoLk.js → share-sheet-BLHmrFbm.js} +2 -2
- package/dist/web/assets/{timeline-CiGI4EmA.js → timeline-B4uG-Emo.js} +1 -1
- package/dist/web/assets/{use-dialog-trap-Dhs8W7s1.js → use-dialog-trap-bfaXEMz9.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/server-BJOXAGRI.js +0 -11
- package/dist/web/assets/index-C0txIEHK.js +0 -83
- package/dist/web/assets/index-NnKaHxPO.css +0 -1
- package/dist/web/assets/settings-sheet-D6tnWqmt.js +0 -1
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ tokmon serve --port 8080
|
|
|
105
105
|
tokmon serve --no-open # don't auto-open the browser
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
It binds to `127.0.0.1` by default and reads the same local daemon state — nothing leaves your machine. The browser connects directly over a same-origin WebSocket; dashboard URLs have no token or login step. Optional LAN access can be enabled in settings, with an explicit unsafe-access warning, and takes effect after the daemon restarts. The dashboard renders instantly from a cached snapshot, then streams live updates and goes idle when no tab is open. Press `R` or use the visible **Refresh** control to update it. Filter by provider, model, account, and period, flip between dark and light, and export any panel — or a summary card — as a PNG with the **Share** button.
|
|
108
|
+
It binds to `127.0.0.1` by default and reads the same local daemon state — nothing leaves your machine. The browser connects directly over a same-origin WebSocket; dashboard URLs have no token or login step. Optional LAN access can be enabled in settings, with an explicit unsafe-access warning, and takes effect after the daemon restarts. When publishing the dashboard through an ingress or reverse proxy, add each exact DNS name to **Allowed hosts** in settings (for example, `tokmon.example.com`); IP hosts remain allowed in LAN mode. The dashboard renders instantly from a cached snapshot, then streams live updates and goes idle when no tab is open. Press `R` or use the visible **Refresh** control to update it. Filter by provider, model, account, and period, flip between dark and light, and export any panel — or a summary card — as a PNG with the **Share** button.
|
|
109
109
|
|
|
110
110
|
### Overview
|
|
111
111
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createDaemonRpcClient
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3RTWFGGD.js";
|
|
5
5
|
import {
|
|
6
6
|
accountsByProvider,
|
|
7
7
|
buildAccounts,
|
|
8
8
|
fetchPeak,
|
|
9
9
|
modelColor
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-FMP3P2WV.js";
|
|
11
11
|
import {
|
|
12
12
|
PROVIDERS,
|
|
13
13
|
PROVIDER_ORDER,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
time,
|
|
25
25
|
tokens,
|
|
26
26
|
withTimeout
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-CVKCVHS7.js";
|
|
28
28
|
import {
|
|
29
29
|
COLOR_PALETTE,
|
|
30
30
|
DEFAULTS,
|
|
@@ -33,13 +33,14 @@ import {
|
|
|
33
33
|
getTrackedAccountRows,
|
|
34
34
|
isValidTimezone,
|
|
35
35
|
loadConfig,
|
|
36
|
+
normalizeAllowedHost,
|
|
36
37
|
normalizeConfig,
|
|
37
38
|
pickAccentColor,
|
|
38
39
|
redactEmail,
|
|
39
40
|
sanitizeTyped,
|
|
40
41
|
saveConfigSync,
|
|
41
42
|
snapshotCacheFile
|
|
42
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-HP5UZCXP.js";
|
|
43
44
|
import {
|
|
44
45
|
glyphs
|
|
45
46
|
} from "./chunk-RF4GGQGM.js";
|
|
@@ -589,7 +590,7 @@ import { Box as Box4, Text as Text4 } from "ink";
|
|
|
589
590
|
import { memo as memo3 } from "react";
|
|
590
591
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
591
592
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
592
|
-
var GENERAL_ROWS =
|
|
593
|
+
var GENERAL_ROWS = 11;
|
|
593
594
|
var SETTINGS_TABS = ["general", "providers", "accounts"];
|
|
594
595
|
var SETTINGS_TAB_LABELS = {
|
|
595
596
|
general: "General",
|
|
@@ -604,6 +605,9 @@ var SettingsView = memo3(function SettingsView2({
|
|
|
604
605
|
tzEdit,
|
|
605
606
|
tzCaret,
|
|
606
607
|
tzError,
|
|
608
|
+
allowedHostsEdit,
|
|
609
|
+
allowedHostsCaret,
|
|
610
|
+
allowedHostsError,
|
|
607
611
|
resolvedTz,
|
|
608
612
|
accountForm,
|
|
609
613
|
activeAccountId,
|
|
@@ -612,6 +616,7 @@ var SettingsView = memo3(function SettingsView2({
|
|
|
612
616
|
}) {
|
|
613
617
|
if (accountForm) return /* @__PURE__ */ jsx3(AccountFormView, { form: accountForm, accounts: config.accounts });
|
|
614
618
|
const editingTz = tzEdit !== null;
|
|
619
|
+
const editingAllowedHosts = allowedHostsEdit !== null;
|
|
615
620
|
const tzDisplay = config.timezone === null ? `System (${resolvedTz})` : config.timezone;
|
|
616
621
|
const tabFocused = cursor < 0;
|
|
617
622
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
|
|
@@ -685,8 +690,17 @@ var SettingsView = memo3(function SettingsView2({
|
|
|
685
690
|
] })
|
|
686
691
|
] }),
|
|
687
692
|
/* @__PURE__ */ jsx3(Row, { cursor, idx: 8, label: "Network access", children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: config.allowNetworkAccess ? "red" : "green", children: config.allowNetworkAccess ? "LAN (unsafe)" : "local only" }) }),
|
|
693
|
+
/* @__PURE__ */ jsx3(Row, { cursor, idx: 9, label: "Allowed hosts", children: editingAllowedHosts ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
694
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "[" }),
|
|
695
|
+
/* @__PURE__ */ jsx3(CaretText, { value: allowedHostsEdit ?? "", caret: allowedHostsCaret, color: "cyan" }),
|
|
696
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "]" })
|
|
697
|
+
] }) : /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: config.allowedHosts.join(", ") || "none" }) }),
|
|
698
|
+
cursor === 9 && allowedHostsError && /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
|
|
699
|
+
" ",
|
|
700
|
+
allowedHostsError
|
|
701
|
+
] }),
|
|
688
702
|
config.allowNetworkAccess && /* @__PURE__ */ jsx3(Text3, { color: "red", children: " Warning: dashboard data and settings will be exposed to your local network after daemon restart." }),
|
|
689
|
-
/* @__PURE__ */ jsxs3(Row, { cursor, idx:
|
|
703
|
+
/* @__PURE__ */ jsxs3(Row, { cursor, idx: 10, label: "Reset times", children: [
|
|
690
704
|
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
691
705
|
glyphs().caretL,
|
|
692
706
|
" "
|
|
@@ -779,6 +793,12 @@ var SettingsView = memo3(function SettingsView2({
|
|
|
779
793
|
" Enter save ",
|
|
780
794
|
glyphs().middot,
|
|
781
795
|
" Esc cancel"
|
|
796
|
+
] }) : editingAllowedHosts ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
797
|
+
"comma-separated exact DNS names ",
|
|
798
|
+
glyphs().middot,
|
|
799
|
+
" Enter save ",
|
|
800
|
+
glyphs().middot,
|
|
801
|
+
" Esc cancel"
|
|
782
802
|
] }) : activeTab === "providers" ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
783
803
|
glyphs().arrowU,
|
|
784
804
|
glyphs().arrowD,
|
|
@@ -1992,6 +2012,56 @@ function handleTimezoneEditor(input, key, editor, text, updateConfig) {
|
|
|
1992
2012
|
if (clean) text.insert(clean);
|
|
1993
2013
|
}
|
|
1994
2014
|
}
|
|
2015
|
+
function handleAllowedHostsEditor(input, key, editor, text, updateConfig) {
|
|
2016
|
+
const { value, setValue, setError, setCaret, valueRef, caretRef } = editor;
|
|
2017
|
+
if (value === null) return;
|
|
2018
|
+
if (key.escape) {
|
|
2019
|
+
setValue(null);
|
|
2020
|
+
setError(null);
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
if (key.return) {
|
|
2024
|
+
const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
2025
|
+
const normalized = parts.map(normalizeAllowedHost);
|
|
2026
|
+
if (normalized.some((host) => host === null)) {
|
|
2027
|
+
setError("Use exact DNS names without a scheme, port, path, or wildcard");
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
updateConfig((config) => ({ ...config, allowedHosts: [...new Set(normalized)] }));
|
|
2031
|
+
setValue(null);
|
|
2032
|
+
setError(null);
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
if (key.leftArrow) {
|
|
2036
|
+
setCaret((caret) => clampCaret(caret - 1, value.length));
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
if (key.rightArrow) {
|
|
2040
|
+
setCaret((caret) => clampCaret(caret + 1, value.length));
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
if (key.ctrl && input === "a") {
|
|
2044
|
+
setCaret(0);
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
if (key.ctrl && input === "e") {
|
|
2048
|
+
setCaret(value.length);
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
if (key.backspace || key.delete) {
|
|
2052
|
+
const result = spliceBackspace(valueRef.current, caretRef.current);
|
|
2053
|
+
valueRef.current = result.value;
|
|
2054
|
+
caretRef.current = result.caret;
|
|
2055
|
+
setValue(result.value);
|
|
2056
|
+
setCaret(result.caret);
|
|
2057
|
+
setError(null);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
if (text.isPrintable(input, key)) {
|
|
2061
|
+
const clean = sanitizeTyped(input);
|
|
2062
|
+
if (clean) text.insert(clean);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
1995
2065
|
function handleSearchEditor(input, key, table, text) {
|
|
1996
2066
|
const { search, setSearch, setSearchMode, setSearchCaret, searchValueRef, searchCaretRef } = table;
|
|
1997
2067
|
if (key.return || key.escape) {
|
|
@@ -2064,6 +2134,8 @@ function handleSettings(input, key, ctx) {
|
|
|
2064
2134
|
setCursor(-1);
|
|
2065
2135
|
timezoneEditor.setValue(null);
|
|
2066
2136
|
timezoneEditor.setError(null);
|
|
2137
|
+
ctx.allowedHostsEditor.setValue(null);
|
|
2138
|
+
ctx.allowedHostsEditor.setError(null);
|
|
2067
2139
|
};
|
|
2068
2140
|
const rowCount = tab === "general" ? GENERAL_ROWS : tab === "providers" ? PROVIDER_ORDER.length : trackedAccounts.length + 1;
|
|
2069
2141
|
if (key.tab) {
|
|
@@ -2194,7 +2266,14 @@ function handleSettings(input, key, ctx) {
|
|
|
2194
2266
|
updateConfig((current) => ({ ...current, allowNetworkAccess: !current.allowNetworkAccess }));
|
|
2195
2267
|
return;
|
|
2196
2268
|
}
|
|
2197
|
-
if (index === 9 &&
|
|
2269
|
+
if (index === 9 && inputKey.return) {
|
|
2270
|
+
const initial = config.allowedHosts.join(", ");
|
|
2271
|
+
context.allowedHostsEditor.setValue(initial);
|
|
2272
|
+
context.allowedHostsEditor.setCaret(initial.length);
|
|
2273
|
+
context.allowedHostsEditor.setError(null);
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
if (index === 10 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2198
2277
|
updateConfig((current) => ({ ...current, resetDisplay: current.resetDisplay === "relative" ? "absolute" : "relative" }));
|
|
2199
2278
|
}
|
|
2200
2279
|
}
|
|
@@ -2357,7 +2436,7 @@ function handleTerminalFocusInput(input) {
|
|
|
2357
2436
|
return true;
|
|
2358
2437
|
}
|
|
2359
2438
|
function isRefreshAllShortcut(input, mode) {
|
|
2360
|
-
return (input === "r" || input === "R") && !mode.unavailable && !mode.showPicker && !mode.editingAccount && !mode.editingTimezone && !mode.editingSearch;
|
|
2439
|
+
return (input === "r" || input === "R") && !mode.unavailable && !mode.showPicker && !mode.editingAccount && !mode.editingTimezone && !mode.editingAllowedHosts && !mode.editingSearch;
|
|
2361
2440
|
}
|
|
2362
2441
|
function handleOnboarding(input, key, ctx) {
|
|
2363
2442
|
const { onboarding, global } = ctx;
|
|
@@ -2386,11 +2465,13 @@ function handleOnboarding(input, key, ctx) {
|
|
|
2386
2465
|
function handleKey(input, key, ctx) {
|
|
2387
2466
|
const editingAccount = ctx.settings.show && ctx.accountEditor.form !== null;
|
|
2388
2467
|
const editingTimezone = ctx.settings.show && ctx.timezoneEditor.value !== null;
|
|
2468
|
+
const editingAllowedHosts = ctx.settings.show && ctx.allowedHostsEditor.value !== null;
|
|
2389
2469
|
const editingSearch = ctx.table.tab === 1 && ctx.table.searchMode;
|
|
2390
2470
|
if (isRefreshAllShortcut(input, {
|
|
2391
2471
|
showPicker: ctx.onboarding.show,
|
|
2392
2472
|
editingAccount,
|
|
2393
2473
|
editingTimezone,
|
|
2474
|
+
editingAllowedHosts,
|
|
2394
2475
|
editingSearch,
|
|
2395
2476
|
unavailable: !ctx.global.configReady
|
|
2396
2477
|
})) {
|
|
@@ -2409,6 +2490,10 @@ function handleKey(input, key, ctx) {
|
|
|
2409
2490
|
handleTimezoneEditor(input, key, ctx.timezoneEditor, ctx.textInput, ctx.global.updateConfig);
|
|
2410
2491
|
return;
|
|
2411
2492
|
}
|
|
2493
|
+
if (editingAllowedHosts) {
|
|
2494
|
+
handleAllowedHostsEditor(input, key, ctx.allowedHostsEditor, ctx.textInput, ctx.global.updateConfig);
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2412
2497
|
if (editingSearch) {
|
|
2413
2498
|
handleSearchEditor(input, key, ctx.table, ctx.textInput);
|
|
2414
2499
|
return;
|
|
@@ -3142,6 +3227,9 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3142
3227
|
const [tzEdit, setTzEdit] = useState10(null);
|
|
3143
3228
|
const [tzError, setTzError] = useState10(null);
|
|
3144
3229
|
const [tzCaret, setTzCaret] = useState10(0);
|
|
3230
|
+
const [allowedHostsEdit, setAllowedHostsEdit] = useState10(null);
|
|
3231
|
+
const [allowedHostsError, setAllowedHostsError] = useState10(null);
|
|
3232
|
+
const [allowedHostsCaret, setAllowedHostsCaret] = useState10(0);
|
|
3145
3233
|
const [searchCaret, setSearchCaret] = useState10(0);
|
|
3146
3234
|
const [onboardSel, setOnboardSel] = useState10(null);
|
|
3147
3235
|
const [onboardCursor, setOnboardCursor] = useState10(0);
|
|
@@ -3169,6 +3257,8 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3169
3257
|
const dashPageCountRef = useRef8(1);
|
|
3170
3258
|
const tzValueRef = useRef8("");
|
|
3171
3259
|
const tzCaretRef = useRef8(0);
|
|
3260
|
+
const allowedHostsValueRef = useRef8("");
|
|
3261
|
+
const allowedHostsCaretRef = useRef8(0);
|
|
3172
3262
|
const searchValueRef = useRef8("");
|
|
3173
3263
|
const searchCaretRef = useRef8(0);
|
|
3174
3264
|
const accountsKey = useMemo2(() => accounts.map(acctKey).join("|"), [accounts]);
|
|
@@ -3274,6 +3364,8 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3274
3364
|
dashPageCountRef.current = dashPageCount;
|
|
3275
3365
|
tzValueRef.current = tzEdit ?? "";
|
|
3276
3366
|
tzCaretRef.current = clampCaret(tzCaret, (tzEdit ?? "").length);
|
|
3367
|
+
allowedHostsValueRef.current = allowedHostsEdit ?? "";
|
|
3368
|
+
allowedHostsCaretRef.current = clampCaret(allowedHostsCaret, (allowedHostsEdit ?? "").length);
|
|
3277
3369
|
searchValueRef.current = search;
|
|
3278
3370
|
searchCaretRef.current = clampCaret(searchCaret, search.length);
|
|
3279
3371
|
const isPrintable = (input, key) => !!input && !key.ctrl && !key.meta && !isPasteInput(input);
|
|
@@ -3291,6 +3383,13 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3291
3383
|
setTzEdit(r.value);
|
|
3292
3384
|
setTzCaret(r.caret);
|
|
3293
3385
|
setTzError(null);
|
|
3386
|
+
} else if (showSettings && allowedHostsEdit !== null) {
|
|
3387
|
+
const r = spliceInsert(allowedHostsValueRef.current, allowedHostsCaretRef.current, text);
|
|
3388
|
+
allowedHostsValueRef.current = r.value;
|
|
3389
|
+
allowedHostsCaretRef.current = r.caret;
|
|
3390
|
+
setAllowedHostsEdit(r.value);
|
|
3391
|
+
setAllowedHostsCaret(r.caret);
|
|
3392
|
+
setAllowedHostsError(null);
|
|
3294
3393
|
} else if (tab === 1 && searchMode) {
|
|
3295
3394
|
const r = spliceInsert(searchValueRef.current, searchCaretRef.current, text);
|
|
3296
3395
|
searchValueRef.current = r.value;
|
|
@@ -3461,7 +3560,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3461
3560
|
if (webStartingRef.current) return;
|
|
3462
3561
|
webStartingRef.current = true;
|
|
3463
3562
|
try {
|
|
3464
|
-
const { startWebServer } = await import("./server-
|
|
3563
|
+
const { startWebServer } = await import("./server-EUO7CWYH.js");
|
|
3465
3564
|
const ctrl = await startWebServer({ config: cfg, log: false });
|
|
3466
3565
|
webRef.current = ctrl;
|
|
3467
3566
|
openUrl(ctrl.url);
|
|
@@ -3535,6 +3634,14 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3535
3634
|
valueRef: tzValueRef,
|
|
3536
3635
|
caretRef: tzCaretRef
|
|
3537
3636
|
},
|
|
3637
|
+
allowedHostsEditor: {
|
|
3638
|
+
value: allowedHostsEdit,
|
|
3639
|
+
setValue: setAllowedHostsEdit,
|
|
3640
|
+
setError: setAllowedHostsError,
|
|
3641
|
+
setCaret: setAllowedHostsCaret,
|
|
3642
|
+
valueRef: allowedHostsValueRef,
|
|
3643
|
+
caretRef: allowedHostsCaretRef
|
|
3644
|
+
},
|
|
3538
3645
|
textInput: { isPrintable, insert: insertText },
|
|
3539
3646
|
settings: {
|
|
3540
3647
|
show: showSettings,
|
|
@@ -3674,6 +3781,9 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3674
3781
|
tzEdit,
|
|
3675
3782
|
tzCaret,
|
|
3676
3783
|
tzError,
|
|
3784
|
+
allowedHostsEdit,
|
|
3785
|
+
allowedHostsCaret,
|
|
3786
|
+
allowedHostsError,
|
|
3677
3787
|
resolvedTz: tz,
|
|
3678
3788
|
accountForm,
|
|
3679
3789
|
activeAccountId: cfg.activeAccountId,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
TOKMON_WS_METHODS,
|
|
4
4
|
TOKMON_WS_PATH,
|
|
5
5
|
TokmonRpcGroup
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-CVKCVHS7.js";
|
|
7
7
|
|
|
8
8
|
// src/client/daemon-rpc-client.ts
|
|
9
9
|
import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Stream } from "effect";
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
envDir,
|
|
6
6
|
expandHome,
|
|
7
7
|
isValidTimezone
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HP5UZCXP.js";
|
|
9
9
|
|
|
10
10
|
// src/async.ts
|
|
11
11
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -744,8 +744,8 @@ import { Schema } from "effect";
|
|
|
744
744
|
import * as Rpc from "effect/unstable/rpc/Rpc";
|
|
745
745
|
import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
|
|
746
746
|
var TOKMON_WS_PATH = "/ws";
|
|
747
|
-
var TOKMON_PROTOCOL_VERSION =
|
|
748
|
-
var TOKMON_CAPABILITIES = ["config-cas", "config-revision"];
|
|
747
|
+
var TOKMON_PROTOCOL_VERSION = 3;
|
|
748
|
+
var TOKMON_CAPABILITIES = ["config-cas", "config-revision", "allowed-hosts"];
|
|
749
749
|
var TOKMON_WS_METHODS = {
|
|
750
750
|
getConfig: "tokmon.getConfig",
|
|
751
751
|
setConfig: "tokmon.setConfig",
|
|
@@ -787,6 +787,7 @@ var ConfigSchema = Schema.Struct({
|
|
|
787
787
|
defaultFocus: Schema.Literals(["all", "last"]),
|
|
788
788
|
ascii: Schema.Literals(["auto", "on", "off"]),
|
|
789
789
|
allowNetworkAccess: Schema.Boolean,
|
|
790
|
+
allowedHosts: Schema.Array(Schema.String),
|
|
790
791
|
resetDisplay: Schema.Literals(["relative", "absolute"]),
|
|
791
792
|
knownProviders: Schema.Array(ProviderIdSchema)
|
|
792
793
|
});
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
identityFromIdToken,
|
|
6
6
|
readClaudeIdentity,
|
|
7
7
|
readJson
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-CVKCVHS7.js";
|
|
9
9
|
import {
|
|
10
10
|
expandHome,
|
|
11
11
|
slugify
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-HP5UZCXP.js";
|
|
13
13
|
|
|
14
14
|
// src/accounts.ts
|
|
15
15
|
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
@@ -26,6 +26,7 @@ var DEFAULTS = {
|
|
|
26
26
|
defaultFocus: "all",
|
|
27
27
|
ascii: "auto",
|
|
28
28
|
allowNetworkAccess: false,
|
|
29
|
+
allowedHosts: [],
|
|
29
30
|
resetDisplay: "relative",
|
|
30
31
|
knownProviders: []
|
|
31
32
|
};
|
|
@@ -147,6 +148,27 @@ function sameJson(a, b) {
|
|
|
147
148
|
return false;
|
|
148
149
|
}
|
|
149
150
|
}
|
|
151
|
+
function normalizeAllowedHost(value) {
|
|
152
|
+
if (typeof value !== "string") return null;
|
|
153
|
+
const candidate = value.trim();
|
|
154
|
+
if (!candidate) return null;
|
|
155
|
+
try {
|
|
156
|
+
const url = new URL(`http://${candidate}`);
|
|
157
|
+
if (url.username || url.password || url.port || url.pathname !== "/" || url.search || url.hash) return null;
|
|
158
|
+
const hostname = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
159
|
+
if (!hostname || hostname.length > 253 || hostname.includes("*")) return null;
|
|
160
|
+
const labels = hostname.split(".");
|
|
161
|
+
if (labels.some((label) => !label || label.length > 63 || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(label))) return null;
|
|
162
|
+
return hostname;
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function normalizeAllowedHosts(value) {
|
|
168
|
+
if (!Array.isArray(value)) return [];
|
|
169
|
+
const hosts = value.map(normalizeAllowedHost).filter((host) => host !== null);
|
|
170
|
+
return [...new Set(hosts)];
|
|
171
|
+
}
|
|
150
172
|
function repairConfig(input) {
|
|
151
173
|
const reasons = [];
|
|
152
174
|
const parsed = isRecord(input) ? input : {};
|
|
@@ -192,6 +214,10 @@ function repairConfig(input) {
|
|
|
192
214
|
if (parsed.knownProviders !== void 0 && !Array.isArray(parsed.knownProviders)) {
|
|
193
215
|
reasons.push("knownProviders was not an array");
|
|
194
216
|
}
|
|
217
|
+
const allowedHosts = normalizeAllowedHosts(parsed.allowedHosts);
|
|
218
|
+
if (parsed.allowedHosts !== void 0 && !sameJson(parsed.allowedHosts, allowedHosts)) {
|
|
219
|
+
reasons.push("allowedHosts contained invalid or duplicate hostnames");
|
|
220
|
+
}
|
|
195
221
|
const activeAccountId = typeof parsed.activeAccountId === "string" && (accountIds.has(parsed.activeAccountId) || PROVIDER_IDS.includes(parsed.activeAccountId)) ? parsed.activeAccountId : null;
|
|
196
222
|
if (parsed.activeAccountId !== void 0 && parsed.activeAccountId !== null && activeAccountId === null) {
|
|
197
223
|
reasons.push("activeAccountId did not match a known account/provider");
|
|
@@ -217,6 +243,7 @@ function repairConfig(input) {
|
|
|
217
243
|
defaultFocus: parsed.defaultFocus === "last" ? "last" : "all",
|
|
218
244
|
ascii: parsed.ascii === "on" ? "on" : parsed.ascii === "off" ? "off" : "auto",
|
|
219
245
|
allowNetworkAccess: parsed.allowNetworkAccess === true,
|
|
246
|
+
allowedHosts,
|
|
220
247
|
resetDisplay: parsed.resetDisplay === "absolute" ? "absolute" : "relative",
|
|
221
248
|
knownProviders
|
|
222
249
|
};
|
|
@@ -389,6 +416,8 @@ export {
|
|
|
389
416
|
getTrackedAccountRows,
|
|
390
417
|
clampNum,
|
|
391
418
|
isValidTimezone,
|
|
419
|
+
normalizeAllowedHost,
|
|
420
|
+
normalizeAllowedHosts,
|
|
392
421
|
repairConfig,
|
|
393
422
|
normalizeConfig,
|
|
394
423
|
slugify,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
colorHex,
|
|
12
12
|
fetchPeak,
|
|
13
13
|
namedHex
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-FMP3P2WV.js";
|
|
15
15
|
import {
|
|
16
16
|
PROVIDERS,
|
|
17
17
|
TOKMON_CAPABILITIES,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
detectProviders,
|
|
23
23
|
resolveTimezone,
|
|
24
24
|
withTimeout
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-CVKCVHS7.js";
|
|
26
26
|
import {
|
|
27
27
|
cacheDir,
|
|
28
28
|
expandHome,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
normalizeConfig,
|
|
31
31
|
saveConfig,
|
|
32
32
|
snapshotCacheFile
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-HP5UZCXP.js";
|
|
34
34
|
|
|
35
35
|
// src/web/server.ts
|
|
36
36
|
import { createServer } from "http";
|
|
@@ -783,11 +783,11 @@ function isLoopbackHostHeader(value) {
|
|
|
783
783
|
const hostname = hostnameFromHost(value);
|
|
784
784
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
785
785
|
}
|
|
786
|
-
function isAllowedHostHeader(value, allowNetworkAccess) {
|
|
786
|
+
function isAllowedHostHeader(value, allowNetworkAccess, allowedHosts = []) {
|
|
787
787
|
if (isLoopbackHostHeader(value)) return true;
|
|
788
788
|
if (!allowNetworkAccess) return false;
|
|
789
789
|
const hostname = hostnameFromHost(value);
|
|
790
|
-
return hostname !== null && isIP(hostname) !== 0;
|
|
790
|
+
return hostname !== null && (isIP(hostname) !== 0 || allowedHosts.includes(hostname));
|
|
791
791
|
}
|
|
792
792
|
function isSameOriginRequest(req) {
|
|
793
793
|
const origin = header(req, "origin");
|
|
@@ -801,8 +801,8 @@ function isSameOriginRequest(req) {
|
|
|
801
801
|
return false;
|
|
802
802
|
}
|
|
803
803
|
}
|
|
804
|
-
function isAllowedLocalRequest(req, allowNetworkAccess) {
|
|
805
|
-
return isAllowedHostHeader(header(req, "host"), allowNetworkAccess) && isSameOriginRequest(req);
|
|
804
|
+
function isAllowedLocalRequest(req, allowNetworkAccess, allowedHosts = []) {
|
|
805
|
+
return isAllowedHostHeader(header(req, "host"), allowNetworkAccess, allowedHosts) && isSameOriginRequest(req);
|
|
806
806
|
}
|
|
807
807
|
|
|
808
808
|
// src/web/ws.ts
|
|
@@ -890,7 +890,7 @@ async function mountWsRpc(server, deps) {
|
|
|
890
890
|
)
|
|
891
891
|
);
|
|
892
892
|
const onUpgrade = (req, socket, head) => {
|
|
893
|
-
if (!isAllowedLocalRequest(req, deps.state.config.allowNetworkAccess)) {
|
|
893
|
+
if (!isAllowedLocalRequest(req, deps.state.config.allowNetworkAccess, deps.state.config.allowedHosts)) {
|
|
894
894
|
rejectUpgrade(socket);
|
|
895
895
|
return;
|
|
896
896
|
}
|
|
@@ -945,8 +945,8 @@ function tokenMatches(given, expected) {
|
|
|
945
945
|
const token = Buffer.from(expected);
|
|
946
946
|
return actual.length === token.length && timingSafeEqual(actual, token);
|
|
947
947
|
}
|
|
948
|
-
function guardHost(req, res,
|
|
949
|
-
if (isAllowedHostHeader(header2(req, "host"), allowNetworkAccess)) return true;
|
|
948
|
+
function guardHost(req, res, config) {
|
|
949
|
+
if (isAllowedHostHeader(header2(req, "host"), config.allowNetworkAccess, config.allowedHosts)) return true;
|
|
950
950
|
sendJson(res, 403, { error: "forbidden" });
|
|
951
951
|
return false;
|
|
952
952
|
}
|
|
@@ -962,7 +962,7 @@ function createRouter(engine, state, vite, webRoot, wsToken, version) {
|
|
|
962
962
|
const url = req.url || "/";
|
|
963
963
|
const path = url.split("?")[0];
|
|
964
964
|
const method = req.method || "GET";
|
|
965
|
-
if (!guardHost(req, res, state.config
|
|
965
|
+
if (!guardHost(req, res, state.config)) return;
|
|
966
966
|
if (path === "/api/data") {
|
|
967
967
|
engine.touch();
|
|
968
968
|
sendJson(res, 200, engine.snapshot() ?? { pending: true });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
cacheDir
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HP5UZCXP.js";
|
|
5
5
|
|
|
6
6
|
// src/web/lockfile.ts
|
|
7
7
|
import { closeSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
attachOrSpawn
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-4HU4EI5T.js";
|
|
5
|
+
import "./chunk-XDA5RJST.js";
|
|
6
6
|
import "./chunk-SMPY52EV.js";
|
|
7
7
|
import {
|
|
8
8
|
createDaemonRpcClient
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-3RTWFGGD.js";
|
|
10
10
|
import {
|
|
11
11
|
PROVIDERS,
|
|
12
12
|
antigravityStateDb,
|
|
@@ -26,11 +26,11 @@ import {
|
|
|
26
26
|
startOfMonth,
|
|
27
27
|
startOfWeek,
|
|
28
28
|
withTimeout
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-CVKCVHS7.js";
|
|
30
30
|
import {
|
|
31
31
|
PROVIDER_IDS,
|
|
32
32
|
configLocation
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-HP5UZCXP.js";
|
|
34
34
|
|
|
35
35
|
// src/provider-locations.ts
|
|
36
36
|
import { access } from "fs/promises";
|
package/dist/cli.js
CHANGED
|
@@ -41,7 +41,7 @@ function validateServeArgs(serveArgs) {
|
|
|
41
41
|
}
|
|
42
42
|
async function main() {
|
|
43
43
|
if (subcommand && ["usage", "models", "query", "providers", "snapshot", "config"].includes(subcommand)) {
|
|
44
|
-
const { runQueryCommand } = await import("./cli-command-
|
|
44
|
+
const { runQueryCommand } = await import("./cli-command-75LP4IDS.js");
|
|
45
45
|
try {
|
|
46
46
|
const output = await runQueryCommand(
|
|
47
47
|
subcommand,
|
|
@@ -60,14 +60,14 @@ Run tokmon ${subcommand} --help for usage.
|
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
if (subcommand === "__daemon") {
|
|
63
|
-
const { runDaemon } = await import("./daemon-
|
|
63
|
+
const { runDaemon } = await import("./daemon-CL4FJW26.js");
|
|
64
64
|
await runDaemon(args.slice(1), { foreground: false });
|
|
65
65
|
process.exitCode ??= 0;
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
if (subcommand === "serve" || subcommand === "web") {
|
|
69
69
|
validateServeArgs(args.slice(1));
|
|
70
|
-
const { runDaemon } = await import("./daemon-
|
|
70
|
+
const { runDaemon } = await import("./daemon-CL4FJW26.js");
|
|
71
71
|
await runDaemon(args.slice(1), { foreground: true });
|
|
72
72
|
process.exitCode ??= 0;
|
|
73
73
|
return;
|
|
@@ -115,9 +115,9 @@ Run tokmon ${subcommand} --help for usage.
|
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
const { loadConfig } = await import("./config-
|
|
118
|
+
const { loadConfig } = await import("./config-HXFJTNLM.js");
|
|
119
119
|
const { resolveGlyphs, setGlyphs } = await import("./glyphs-NKCSZLGO.js");
|
|
120
|
-
const { attachOrSpawn } = await import("./daemon-handle-
|
|
120
|
+
const { attachOrSpawn } = await import("./daemon-handle-QQLJE46Y.js");
|
|
121
121
|
const config = await loadConfig();
|
|
122
122
|
setGlyphs(resolveGlyphs({
|
|
123
123
|
flag: asciiFlag,
|
|
@@ -128,7 +128,7 @@ Run tokmon ${subcommand} --help for usage.
|
|
|
128
128
|
}));
|
|
129
129
|
const daemon = await attachOrSpawn();
|
|
130
130
|
const mode = daemon.kind === "spawned" ? "connected" : "degraded";
|
|
131
|
-
const { bootstrapInk } = await import("./bootstrap-ink-
|
|
131
|
+
const { bootstrapInk } = await import("./bootstrap-ink-L7QSJBMS.js");
|
|
132
132
|
await bootstrapInk({ interval, config, daemon, mode });
|
|
133
133
|
}
|
|
134
134
|
void main().catch((error) => {
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
generateAccountId,
|
|
16
16
|
getTrackedAccountRows,
|
|
17
17
|
loadConfig,
|
|
18
|
+
normalizeAllowedHost,
|
|
19
|
+
normalizeAllowedHosts,
|
|
18
20
|
normalizeConfig,
|
|
19
21
|
pickAccentColor,
|
|
20
22
|
redactEmail,
|
|
@@ -24,7 +26,7 @@ import {
|
|
|
24
26
|
saveConfigSync,
|
|
25
27
|
slugify,
|
|
26
28
|
snapshotCacheFile
|
|
27
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-HP5UZCXP.js";
|
|
28
30
|
export {
|
|
29
31
|
ACCENT_COLORS,
|
|
30
32
|
COLOR_PALETTE,
|
|
@@ -41,6 +43,8 @@ export {
|
|
|
41
43
|
generateAccountId,
|
|
42
44
|
getTrackedAccountRows,
|
|
43
45
|
loadConfig,
|
|
46
|
+
normalizeAllowedHost,
|
|
47
|
+
normalizeAllowedHosts,
|
|
44
48
|
normalizeConfig,
|
|
45
49
|
pickAccentColor,
|
|
46
50
|
redactEmail,
|
|
@@ -8,20 +8,20 @@ import {
|
|
|
8
8
|
unlinkLock,
|
|
9
9
|
verifyLock,
|
|
10
10
|
writeLock
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XDA5RJST.js";
|
|
12
12
|
import {
|
|
13
13
|
startWebServer
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-S33XIUAW.js";
|
|
15
15
|
import {
|
|
16
16
|
appVersion
|
|
17
17
|
} from "./chunk-SMPY52EV.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-FMP3P2WV.js";
|
|
19
19
|
import {
|
|
20
20
|
flushDisk
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-CVKCVHS7.js";
|
|
22
22
|
import {
|
|
23
23
|
loadConfig
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-HP5UZCXP.js";
|
|
25
25
|
|
|
26
26
|
// src/web/daemon.ts
|
|
27
27
|
import { randomBytes } from "crypto";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
attachOrSpawn
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-4HU4EI5T.js";
|
|
5
|
+
import "./chunk-XDA5RJST.js";
|
|
6
6
|
import "./chunk-SMPY52EV.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-HP5UZCXP.js";
|
|
8
8
|
export {
|
|
9
9
|
attachOrSpawn
|
|
10
10
|
};
|