tokmon 0.28.2 → 0.28.3
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 +156 -234
- package/THIRD_PARTY_NOTICES.md +62 -0
- package/dist/{bootstrap-ink-IMT3S5HU.js → bootstrap-ink-WG4WG3R2.js} +1579 -1136
- package/dist/chunk-7CH6S7BR.js +447 -0
- package/dist/{chunk-LCAHTRGP.js → chunk-CFKUZCMB.js} +56 -29
- package/dist/chunk-HNWEJ6MS.js +1094 -0
- package/dist/{chunk-WUK3MQUB.js → chunk-JZSZEHVD.js} +167 -10
- package/dist/{chunk-6ZEW2M6P.js → chunk-P57DVFNH.js} +125 -25
- package/dist/{chunk-LKYOWBD4.js → chunk-P5JN5DDX.js} +21 -42
- package/dist/chunk-TN6V7XSL.js +512 -0
- package/dist/{chunk-J3JW3RFP.js → chunk-USKOQIE3.js} +95 -13
- package/dist/chunk-UYPDMVW5.js +21 -0
- package/dist/{cli-command-DCENRCO3.js → cli-command-MD3RJRP4.js} +249 -15
- package/dist/cli.js +6 -6
- package/dist/{config-HXFJTNLM.js → config-3DGZ47F7.js} +23 -3
- package/dist/daemon-EMOA36WB.js +377 -0
- package/dist/daemon-handle-JHE5MKMI.js +11 -0
- package/dist/server-EPSH52DR.js +12 -0
- package/dist/web/assets/{Area-CMXvOw33.js → Area-stRnp8Km.js} +1 -1
- package/dist/web/assets/analytics-moZZt9L_.js +2 -0
- package/dist/web/assets/breakdown-km9w7myU.js +4 -0
- package/dist/web/assets/button-DrX04Cc2.js +1 -0
- package/dist/web/assets/{chart-cZoLgpmG.js → chart-D0CtuL2k.js} +1 -1
- package/dist/web/assets/explore-B11uNJV1.js +22 -0
- package/dist/web/assets/index-RDhXsCNG.js +83 -0
- package/dist/web/assets/index-ZmOS8p9O.css +1 -0
- package/dist/web/assets/models-DYbZnY34.js +2 -0
- package/dist/web/assets/overview-CruICbfJ.js +2 -0
- package/dist/web/assets/panel-xckM3_GC.js +1 -0
- package/dist/web/assets/settings-sheet-G-BTSIF6.js +1 -0
- package/dist/web/assets/share-sheet-weIdd4DW.js +2 -0
- package/dist/web/assets/timeline-BHgniliZ.js +1 -0
- package/dist/web/index.html +30 -4
- package/package.json +13 -4
- package/site/THIRD_PARTY_NOTICES.md +83 -0
- package/site/public/fonts/OFL.txt +93 -0
- package/dist/chunk-44ZSQEOS.js +0 -299
- package/dist/chunk-HP5UZCXP.js +0 -436
- package/dist/chunk-M7XMV36F.js +0 -275
- package/dist/daemon-6Y4O7NXS.js +0 -241
- package/dist/daemon-handle-GVCYVDPO.js +0 -10
- package/dist/server-GXXOWUVZ.js +0 -11
- package/dist/web/assets/analytics-DqdZXOJL.js +0 -2
- package/dist/web/assets/breakdown-kOSSJ3mI.js +0 -4
- package/dist/web/assets/explore-vcsM8K9I.js +0 -22
- package/dist/web/assets/index-BErCXT7f.css +0 -1
- package/dist/web/assets/index-DH3dOnbg.js +0 -83
- package/dist/web/assets/models-B5ikm83_.js +0 -2
- package/dist/web/assets/overview-DD1kP8CP.js +0 -2
- package/dist/web/assets/panel-DTQBwExW.js +0 -1
- package/dist/web/assets/primitives-C36qJlA2.js +0 -1
- package/dist/web/assets/settings-sheet-_i2zTQjf.js +0 -1
- package/dist/web/assets/share-sheet-BLHmrFbm.js +0 -2
- package/dist/web/assets/timeline-B4uG-Emo.js +0 -1
- package/dist/web/assets/use-dialog-trap-bfaXEMz9.js +0 -1
package/dist/chunk-HP5UZCXP.js
DELETED
|
@@ -1,436 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/config.ts
|
|
4
|
-
import { chmod, readFile, writeFile, mkdir, rename, unlink } from "fs/promises";
|
|
5
|
-
import { chmodSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
6
|
-
import { join, isAbsolute } from "path";
|
|
7
|
-
import { homedir } from "os";
|
|
8
|
-
|
|
9
|
-
// src/providers/types.ts
|
|
10
|
-
var PROVIDER_IDS = ["claude", "codex", "cursor", "copilot", "pi", "opencode", "antigravity", "gemini", "grok"];
|
|
11
|
-
|
|
12
|
-
// src/config-schema.ts
|
|
13
|
-
var DEFAULTS = {
|
|
14
|
-
revision: 0,
|
|
15
|
-
interval: 2,
|
|
16
|
-
billingInterval: 5,
|
|
17
|
-
clearScreen: true,
|
|
18
|
-
privacyMode: true,
|
|
19
|
-
privacyToggleKey: "p",
|
|
20
|
-
timezone: null,
|
|
21
|
-
accounts: [],
|
|
22
|
-
activeAccountId: null,
|
|
23
|
-
disabledProviders: [],
|
|
24
|
-
onboarded: false,
|
|
25
|
-
dashboardLayout: "grid",
|
|
26
|
-
defaultFocus: "all",
|
|
27
|
-
ascii: "auto",
|
|
28
|
-
allowNetworkAccess: false,
|
|
29
|
-
allowedHosts: [],
|
|
30
|
-
resetDisplay: "relative",
|
|
31
|
-
knownProviders: []
|
|
32
|
-
};
|
|
33
|
-
var LEGACY_KNOWN = ["claude", "codex", "cursor"];
|
|
34
|
-
var ACCENT_COLORS = ["cyan", "magenta", "green", "yellow", "blue", "red"];
|
|
35
|
-
var PROVIDER_ORDER = [...PROVIDER_IDS];
|
|
36
|
-
var COLOR_PALETTE = [
|
|
37
|
-
"cyan",
|
|
38
|
-
"magenta",
|
|
39
|
-
"green",
|
|
40
|
-
"yellow",
|
|
41
|
-
"blue",
|
|
42
|
-
"red",
|
|
43
|
-
"cyanBright",
|
|
44
|
-
"magentaBright",
|
|
45
|
-
"greenBright"
|
|
46
|
-
];
|
|
47
|
-
var PROVIDER_META = {
|
|
48
|
-
claude: { name: "Claude", color: "green" },
|
|
49
|
-
codex: { name: "Codex", color: "cyan" },
|
|
50
|
-
cursor: { name: "Cursor", color: "magenta" },
|
|
51
|
-
copilot: { name: "Copilot", color: "white" },
|
|
52
|
-
pi: { name: "Pi", color: "blue" },
|
|
53
|
-
opencode: { name: "opencode", color: "yellow" },
|
|
54
|
-
antigravity: { name: "Antigravity", color: "red" },
|
|
55
|
-
gemini: { name: "Gemini", color: "greenBright" },
|
|
56
|
-
grok: { name: "Grok", color: "yellowBright" }
|
|
57
|
-
};
|
|
58
|
-
var EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i;
|
|
59
|
-
var EMAIL_RE_GLOBAL = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
60
|
-
function containsEmail(value) {
|
|
61
|
-
return typeof value === "string" && EMAIL_RE.test(value);
|
|
62
|
-
}
|
|
63
|
-
function redactEmail(value) {
|
|
64
|
-
return value.replace(EMAIL_RE_GLOBAL, "[redacted]");
|
|
65
|
-
}
|
|
66
|
-
function getTrackedAccountRows(config, trackedProviders = PROVIDER_ORDER.filter((pid) => !config.disabledProviders.includes(pid)), autoAccounts) {
|
|
67
|
-
const tracked = new Set(trackedProviders);
|
|
68
|
-
const configuredIds = /* @__PURE__ */ new Set();
|
|
69
|
-
const configuredKeys = /* @__PURE__ */ new Set();
|
|
70
|
-
const rowIds = /* @__PURE__ */ new Set();
|
|
71
|
-
const rowKeys = /* @__PURE__ */ new Set();
|
|
72
|
-
const rows = [];
|
|
73
|
-
const keyFor = (providerId, homeDir) => `${providerId}:${homeDir && homeDir !== "~" ? homeDir : "~"}`;
|
|
74
|
-
const rememberRow = (row) => {
|
|
75
|
-
rowIds.add(row.id);
|
|
76
|
-
rowKeys.add(keyFor(row.providerId, row.homeDir));
|
|
77
|
-
rows.push(row);
|
|
78
|
-
};
|
|
79
|
-
config.accounts.forEach((account, explicitIndex) => {
|
|
80
|
-
const meta = PROVIDER_META[account.providerId];
|
|
81
|
-
configuredIds.add(account.id);
|
|
82
|
-
configuredKeys.add(keyFor(account.providerId, account.homeDir));
|
|
83
|
-
rememberRow({
|
|
84
|
-
id: account.id,
|
|
85
|
-
providerId: account.providerId,
|
|
86
|
-
name: account.name,
|
|
87
|
-
homeDir: account.homeDir || "~",
|
|
88
|
-
color: account.color || meta.color,
|
|
89
|
-
source: "configured",
|
|
90
|
-
explicitId: account.id,
|
|
91
|
-
explicitIndex
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
if (autoAccounts) {
|
|
95
|
-
for (const account of autoAccounts) {
|
|
96
|
-
if (config.disabledProviders.includes(account.providerId)) continue;
|
|
97
|
-
const key = keyFor(account.providerId, account.homeDir);
|
|
98
|
-
if (configuredIds.has(account.id) || configuredKeys.has(key) || rowIds.has(account.id) || rowKeys.has(key)) continue;
|
|
99
|
-
const meta = PROVIDER_META[account.providerId];
|
|
100
|
-
rememberRow({
|
|
101
|
-
id: account.id,
|
|
102
|
-
providerId: account.providerId,
|
|
103
|
-
name: account.name,
|
|
104
|
-
homeDir: account.homeDir || "~",
|
|
105
|
-
color: account.color || meta.color,
|
|
106
|
-
source: "auto"
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
for (const providerId of PROVIDER_ORDER) {
|
|
111
|
-
if (config.disabledProviders.includes(providerId)) continue;
|
|
112
|
-
if (!tracked.has(providerId)) continue;
|
|
113
|
-
const key = keyFor(providerId, "~");
|
|
114
|
-
if (configuredIds.has(providerId) || configuredKeys.has(key) || rowIds.has(providerId) || rowKeys.has(key)) continue;
|
|
115
|
-
const meta = PROVIDER_META[providerId];
|
|
116
|
-
rememberRow({
|
|
117
|
-
id: providerId,
|
|
118
|
-
providerId,
|
|
119
|
-
name: meta.name,
|
|
120
|
-
homeDir: "~",
|
|
121
|
-
color: meta.color,
|
|
122
|
-
source: "auto"
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return rows;
|
|
126
|
-
}
|
|
127
|
-
function clampNum(v, fallback, min) {
|
|
128
|
-
return typeof v === "number" && Number.isFinite(v) && v >= min ? v : fallback;
|
|
129
|
-
}
|
|
130
|
-
function isValidTimezone(tz) {
|
|
131
|
-
try {
|
|
132
|
-
new Intl.DateTimeFormat("en-CA", { timeZone: tz });
|
|
133
|
-
return true;
|
|
134
|
-
} catch {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function isRecord(value) {
|
|
139
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
140
|
-
}
|
|
141
|
-
function validProvider(value) {
|
|
142
|
-
return PROVIDER_IDS.includes(value);
|
|
143
|
-
}
|
|
144
|
-
function sameJson(a, b) {
|
|
145
|
-
try {
|
|
146
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
147
|
-
} catch {
|
|
148
|
-
return false;
|
|
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
|
-
}
|
|
172
|
-
function repairConfig(input) {
|
|
173
|
-
const reasons = [];
|
|
174
|
-
const parsed = isRecord(input) ? input : {};
|
|
175
|
-
if (parsed !== input) reasons.push("config root was not an object");
|
|
176
|
-
const rawAccounts = Array.isArray(parsed.accounts) ? parsed.accounts : [];
|
|
177
|
-
if (!Array.isArray(parsed.accounts) && parsed.accounts !== void 0) reasons.push("accounts was not an array");
|
|
178
|
-
const accounts = [];
|
|
179
|
-
const accountIds = /* @__PURE__ */ new Set();
|
|
180
|
-
rawAccounts.forEach((raw, index) => {
|
|
181
|
-
if (!isRecord(raw)) {
|
|
182
|
-
reasons.push(`accounts[${index}] was not an object`);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const providerId = raw.providerId ?? "claude";
|
|
186
|
-
if (!validProvider(providerId)) {
|
|
187
|
-
reasons.push(`accounts[${index}].providerId was invalid`);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const id = typeof raw.id === "string" && raw.id.trim() ? raw.id.trim() : "";
|
|
191
|
-
const name = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : "";
|
|
192
|
-
if (!id || !name) {
|
|
193
|
-
reasons.push(`accounts[${index}] was missing id or name`);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (accountIds.has(id)) {
|
|
197
|
-
reasons.push(`accounts[${index}].id was duplicated`);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
accountIds.add(id);
|
|
201
|
-
accounts.push({
|
|
202
|
-
id,
|
|
203
|
-
providerId,
|
|
204
|
-
name,
|
|
205
|
-
homeDir: typeof raw.homeDir === "string" && raw.homeDir.trim() ? raw.homeDir : "~",
|
|
206
|
-
...typeof raw.color === "string" && raw.color.trim() ? { color: raw.color } : {}
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
const disabledProviders = (Array.isArray(parsed.disabledProviders) ? parsed.disabledProviders : []).filter(validProvider);
|
|
210
|
-
if (parsed.disabledProviders !== void 0 && !Array.isArray(parsed.disabledProviders)) {
|
|
211
|
-
reasons.push("disabledProviders was not an array");
|
|
212
|
-
}
|
|
213
|
-
const knownProviders = Array.isArray(parsed.knownProviders) ? parsed.knownProviders.filter(validProvider) : parsed.onboarded === true ? [...LEGACY_KNOWN] : [];
|
|
214
|
-
if (parsed.knownProviders !== void 0 && !Array.isArray(parsed.knownProviders)) {
|
|
215
|
-
reasons.push("knownProviders was not an array");
|
|
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
|
-
}
|
|
221
|
-
const activeAccountId = typeof parsed.activeAccountId === "string" && (accountIds.has(parsed.activeAccountId) || PROVIDER_IDS.includes(parsed.activeAccountId)) ? parsed.activeAccountId : null;
|
|
222
|
-
if (parsed.activeAccountId !== void 0 && parsed.activeAccountId !== null && activeAccountId === null) {
|
|
223
|
-
reasons.push("activeAccountId did not match a known account/provider");
|
|
224
|
-
}
|
|
225
|
-
const timezone = typeof parsed.timezone === "string" && parsed.timezone.trim() && isValidTimezone(parsed.timezone.trim()) ? parsed.timezone.trim() : null;
|
|
226
|
-
if (parsed.timezone !== void 0 && parsed.timezone !== null && timezone === null) {
|
|
227
|
-
reasons.push("timezone was invalid");
|
|
228
|
-
}
|
|
229
|
-
const config = {
|
|
230
|
-
...DEFAULTS,
|
|
231
|
-
revision: typeof parsed.revision === "number" && Number.isSafeInteger(parsed.revision) && parsed.revision >= 0 ? parsed.revision : DEFAULTS.revision,
|
|
232
|
-
interval: clampNum(parsed.interval, DEFAULTS.interval, 1),
|
|
233
|
-
billingInterval: clampNum(parsed.billingInterval, DEFAULTS.billingInterval, 1),
|
|
234
|
-
clearScreen: typeof parsed.clearScreen === "boolean" ? parsed.clearScreen : DEFAULTS.clearScreen,
|
|
235
|
-
privacyMode: typeof parsed.privacyMode === "boolean" ? parsed.privacyMode : DEFAULTS.privacyMode,
|
|
236
|
-
privacyToggleKey: typeof parsed.privacyToggleKey === "string" && parsed.privacyToggleKey.length === 1 ? parsed.privacyToggleKey : DEFAULTS.privacyToggleKey,
|
|
237
|
-
timezone,
|
|
238
|
-
accounts,
|
|
239
|
-
activeAccountId,
|
|
240
|
-
disabledProviders,
|
|
241
|
-
onboarded: parsed.onboarded === true,
|
|
242
|
-
dashboardLayout: parsed.dashboardLayout === "single" ? "single" : "grid",
|
|
243
|
-
defaultFocus: parsed.defaultFocus === "last" ? "last" : "all",
|
|
244
|
-
ascii: parsed.ascii === "on" ? "on" : parsed.ascii === "off" ? "off" : "auto",
|
|
245
|
-
allowNetworkAccess: parsed.allowNetworkAccess === true,
|
|
246
|
-
allowedHosts,
|
|
247
|
-
resetDisplay: parsed.resetDisplay === "absolute" ? "absolute" : "relative",
|
|
248
|
-
knownProviders
|
|
249
|
-
};
|
|
250
|
-
for (const key of Object.keys(DEFAULTS)) {
|
|
251
|
-
if (!(key in parsed)) reasons.push(`missing ${key}`);
|
|
252
|
-
}
|
|
253
|
-
return { config, repaired: reasons.length > 0 || !sameJson(parsed, config), reasons };
|
|
254
|
-
}
|
|
255
|
-
function normalizeConfig(parsed) {
|
|
256
|
-
try {
|
|
257
|
-
return repairConfig(parsed).config;
|
|
258
|
-
} catch {
|
|
259
|
-
return { ...DEFAULTS };
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
function slugify(value) {
|
|
263
|
-
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
|
|
264
|
-
}
|
|
265
|
-
function generateAccountId(name, existing) {
|
|
266
|
-
const base = slugify(name) || "account";
|
|
267
|
-
const taken = new Set(existing.map((a) => a.id));
|
|
268
|
-
if (!taken.has(base)) return base;
|
|
269
|
-
for (let i = 2; i < 1e3; i++) {
|
|
270
|
-
const candidate = `${base}_${i}`;
|
|
271
|
-
if (!taken.has(candidate)) return candidate;
|
|
272
|
-
}
|
|
273
|
-
return `${base}_${Date.now()}`;
|
|
274
|
-
}
|
|
275
|
-
function pickAccentColor(existing) {
|
|
276
|
-
const used = new Set(existing.map((a) => a.color).filter(Boolean));
|
|
277
|
-
for (const c of ACCENT_COLORS) {
|
|
278
|
-
if (!used.has(c)) return c;
|
|
279
|
-
}
|
|
280
|
-
return ACCENT_COLORS[existing.length % ACCENT_COLORS.length];
|
|
281
|
-
}
|
|
282
|
-
function sanitizeTyped(input) {
|
|
283
|
-
if (!input) return "";
|
|
284
|
-
return input.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1bO./g, "").replace(/\x1b/g, "").replace(/[\x00-\x1f\x7f-\x9f]/g, "").replace(/\[20[01]~/g, "");
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// src/config.ts
|
|
288
|
-
function envDir(name) {
|
|
289
|
-
const v = process.env[name];
|
|
290
|
-
return v && v.trim() && isAbsolute(v.trim()) ? v.trim() : void 0;
|
|
291
|
-
}
|
|
292
|
-
function configDir() {
|
|
293
|
-
if (process.platform === "win32") {
|
|
294
|
-
return join(envDir("APPDATA") ?? join(homedir(), "AppData", "Roaming"), "tokmon");
|
|
295
|
-
}
|
|
296
|
-
return join(envDir("XDG_CONFIG_HOME") ?? join(homedir(), ".config"), "tokmon");
|
|
297
|
-
}
|
|
298
|
-
function configLocation() {
|
|
299
|
-
return join(configDir(), "config.json");
|
|
300
|
-
}
|
|
301
|
-
function cacheDir() {
|
|
302
|
-
if (process.platform === "win32") {
|
|
303
|
-
return join(envDir("LOCALAPPDATA") ?? envDir("APPDATA") ?? join(homedir(), "AppData", "Local"), "tokmon", "cache");
|
|
304
|
-
}
|
|
305
|
-
if (process.platform === "darwin") {
|
|
306
|
-
return join(homedir(), "Library", "Caches", "tokmon");
|
|
307
|
-
}
|
|
308
|
-
return join(envDir("XDG_CACHE_HOME") ?? join(homedir(), ".cache"), "tokmon");
|
|
309
|
-
}
|
|
310
|
-
function snapshotCacheFile() {
|
|
311
|
-
return join(cacheDir(), "web-snapshot.json");
|
|
312
|
-
}
|
|
313
|
-
async function loadConfig() {
|
|
314
|
-
let raw;
|
|
315
|
-
try {
|
|
316
|
-
raw = await readFile(configLocation(), "utf-8");
|
|
317
|
-
} catch {
|
|
318
|
-
return { ...DEFAULTS };
|
|
319
|
-
}
|
|
320
|
-
try {
|
|
321
|
-
await chmod(configDir(), 448);
|
|
322
|
-
await chmod(configLocation(), 384);
|
|
323
|
-
} catch {
|
|
324
|
-
}
|
|
325
|
-
let parsed;
|
|
326
|
-
try {
|
|
327
|
-
parsed = JSON.parse(raw);
|
|
328
|
-
} catch {
|
|
329
|
-
try {
|
|
330
|
-
const dir = configDir();
|
|
331
|
-
await mkdir(dir, { recursive: true, mode: 448 });
|
|
332
|
-
await chmod(dir, 448);
|
|
333
|
-
const backup = configLocation() + ".bak";
|
|
334
|
-
await writeFile(backup, raw, { mode: 384 });
|
|
335
|
-
await chmod(backup, 384);
|
|
336
|
-
} catch {
|
|
337
|
-
}
|
|
338
|
-
return { ...DEFAULTS };
|
|
339
|
-
}
|
|
340
|
-
const repaired = repairConfig(parsed);
|
|
341
|
-
if (repaired.repaired) {
|
|
342
|
-
try {
|
|
343
|
-
await saveConfig(repaired.config);
|
|
344
|
-
} catch {
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return repaired.config;
|
|
348
|
-
}
|
|
349
|
-
var saveQueue = Promise.resolve();
|
|
350
|
-
var tempSequence = 0;
|
|
351
|
-
function configTempFile(dir) {
|
|
352
|
-
tempSequence = (tempSequence + 1) % Number.MAX_SAFE_INTEGER;
|
|
353
|
-
return join(dir, `config.json.${process.pid}.${Date.now()}.${tempSequence}.tmp`);
|
|
354
|
-
}
|
|
355
|
-
function configJson(config) {
|
|
356
|
-
return JSON.stringify(normalizeConfig(config), null, 2) + "\n";
|
|
357
|
-
}
|
|
358
|
-
function saveConfig(config) {
|
|
359
|
-
const write = async () => {
|
|
360
|
-
const dir = configDir();
|
|
361
|
-
await mkdir(dir, { recursive: true, mode: 448 });
|
|
362
|
-
await chmod(dir, 448);
|
|
363
|
-
const tmp = configTempFile(dir);
|
|
364
|
-
try {
|
|
365
|
-
await writeFile(tmp, configJson(config), { mode: 384 });
|
|
366
|
-
await chmod(tmp, 384);
|
|
367
|
-
await rename(tmp, configLocation());
|
|
368
|
-
} catch (error) {
|
|
369
|
-
try {
|
|
370
|
-
await unlink(tmp);
|
|
371
|
-
} catch {
|
|
372
|
-
}
|
|
373
|
-
throw error;
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
const operation = saveQueue.catch(() => void 0).then(write);
|
|
377
|
-
saveQueue = operation;
|
|
378
|
-
return operation;
|
|
379
|
-
}
|
|
380
|
-
function saveConfigSync(config) {
|
|
381
|
-
const dir = configDir();
|
|
382
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
383
|
-
chmodSync(dir, 448);
|
|
384
|
-
const tmp = configTempFile(dir);
|
|
385
|
-
try {
|
|
386
|
-
writeFileSync(tmp, configJson(config), { mode: 384 });
|
|
387
|
-
chmodSync(tmp, 384);
|
|
388
|
-
renameSync(tmp, configLocation());
|
|
389
|
-
} catch (error) {
|
|
390
|
-
try {
|
|
391
|
-
unlinkSync(tmp);
|
|
392
|
-
} catch {
|
|
393
|
-
}
|
|
394
|
-
throw error;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
function expandHome(p) {
|
|
398
|
-
if (!p) return homedir();
|
|
399
|
-
if (p === "~" || p === "~/" || p === "~\\") return homedir();
|
|
400
|
-
if (p.startsWith("~/") || p.startsWith("~\\")) return join(homedir(), p.slice(2));
|
|
401
|
-
return p;
|
|
402
|
-
}
|
|
403
|
-
function findAccount(config, id) {
|
|
404
|
-
if (!id) return null;
|
|
405
|
-
return config.accounts.find((a) => a.id === id) ?? null;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
export {
|
|
409
|
-
PROVIDER_IDS,
|
|
410
|
-
DEFAULTS,
|
|
411
|
-
ACCENT_COLORS,
|
|
412
|
-
COLOR_PALETTE,
|
|
413
|
-
PROVIDER_META,
|
|
414
|
-
containsEmail,
|
|
415
|
-
redactEmail,
|
|
416
|
-
getTrackedAccountRows,
|
|
417
|
-
clampNum,
|
|
418
|
-
isValidTimezone,
|
|
419
|
-
normalizeAllowedHost,
|
|
420
|
-
normalizeAllowedHosts,
|
|
421
|
-
repairConfig,
|
|
422
|
-
normalizeConfig,
|
|
423
|
-
slugify,
|
|
424
|
-
generateAccountId,
|
|
425
|
-
pickAccentColor,
|
|
426
|
-
sanitizeTyped,
|
|
427
|
-
envDir,
|
|
428
|
-
configLocation,
|
|
429
|
-
cacheDir,
|
|
430
|
-
snapshotCacheFile,
|
|
431
|
-
loadConfig,
|
|
432
|
-
saveConfig,
|
|
433
|
-
saveConfigSync,
|
|
434
|
-
expandHome,
|
|
435
|
-
findAccount
|
|
436
|
-
};
|
package/dist/chunk-M7XMV36F.js
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
TOKMON_WS_METHODS,
|
|
4
|
-
TOKMON_WS_PATH,
|
|
5
|
-
TokmonRpcGroup
|
|
6
|
-
} from "./chunk-WUK3MQUB.js";
|
|
7
|
-
|
|
8
|
-
// src/client/daemon-rpc-client.ts
|
|
9
|
-
import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Stream } from "effect";
|
|
10
|
-
import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
|
|
11
|
-
import * as Socket from "effect/unstable/socket/Socket";
|
|
12
|
-
var TokmonRpcClient = class extends Context.Service()(
|
|
13
|
-
"tokmon/client/DaemonRpcClient/TokmonRpcClient"
|
|
14
|
-
) {
|
|
15
|
-
};
|
|
16
|
-
var dynamicImport = new Function("specifier", "return import(specifier)");
|
|
17
|
-
function toWsUrl(baseUrl) {
|
|
18
|
-
const base = typeof window === "undefined" ? void 0 : window.location.origin;
|
|
19
|
-
const url = new URL(baseUrl, base);
|
|
20
|
-
if (url.protocol === "http:") url.protocol = "ws:";
|
|
21
|
-
else if (url.protocol === "https:") url.protocol = "wss:";
|
|
22
|
-
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
|
23
|
-
throw new Error(`unsupported daemon RPC protocol: ${url.protocol}`);
|
|
24
|
-
}
|
|
25
|
-
url.hash = "";
|
|
26
|
-
url.searchParams.delete("tokmonToken");
|
|
27
|
-
url.searchParams.delete("wsToken");
|
|
28
|
-
url.pathname = TOKMON_WS_PATH;
|
|
29
|
-
return url.toString();
|
|
30
|
-
}
|
|
31
|
-
function shouldUseNodeTransport(transport) {
|
|
32
|
-
if (transport === "node") return true;
|
|
33
|
-
if (transport === "browser") return false;
|
|
34
|
-
return typeof window === "undefined";
|
|
35
|
-
}
|
|
36
|
-
async function socketLayerFor(url, transport) {
|
|
37
|
-
if (shouldUseNodeTransport(transport)) {
|
|
38
|
-
const NodeSocket = await dynamicImport("@effect/platform-node/NodeSocket");
|
|
39
|
-
return NodeSocket.layerWebSocket(url);
|
|
40
|
-
}
|
|
41
|
-
return Socket.layerWebSocket(url).pipe(
|
|
42
|
-
Layer.provide(Socket.layerWebSocketConstructorGlobal)
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
function retryPolicy(options) {
|
|
46
|
-
const baseDelay = options.reconnectBaseDelayMs ?? 250;
|
|
47
|
-
const policy = Schedule.exponential(Duration.millis(baseDelay), 1.5).pipe(
|
|
48
|
-
Schedule.either(Schedule.spaced(Duration.millis(2500)))
|
|
49
|
-
);
|
|
50
|
-
return typeof options.reconnectAttempts === "number" ? policy.pipe(Schedule.both(Schedule.recurs(options.reconnectAttempts))) : policy;
|
|
51
|
-
}
|
|
52
|
-
function createDaemonRpcClient(baseUrl, options = {}) {
|
|
53
|
-
const url = toWsUrl(baseUrl);
|
|
54
|
-
const fibers = /* @__PURE__ */ new Set();
|
|
55
|
-
let session = null;
|
|
56
|
-
let sessionPromise = null;
|
|
57
|
-
let closed = false;
|
|
58
|
-
const setConn = (state, error) => {
|
|
59
|
-
options.onConn?.(state, error);
|
|
60
|
-
};
|
|
61
|
-
const resetSession = (active) => {
|
|
62
|
-
if (active !== void 0 && active !== null && active !== session) {
|
|
63
|
-
void active.runtime.dispose().catch(() => {
|
|
64
|
-
});
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const dead = active ?? session;
|
|
68
|
-
session = null;
|
|
69
|
-
sessionPromise = null;
|
|
70
|
-
if (dead) void dead.runtime.dispose().catch(() => {
|
|
71
|
-
});
|
|
72
|
-
};
|
|
73
|
-
const makeProtocolLayer = async () => {
|
|
74
|
-
const socketLayer = await socketLayerFor(url, options.transport);
|
|
75
|
-
const connectionHooksLayer = Layer.succeed(
|
|
76
|
-
RpcClient.ConnectionHooks,
|
|
77
|
-
RpcClient.ConnectionHooks.of({
|
|
78
|
-
onConnect: Effect.sync(() => {
|
|
79
|
-
setConn("live");
|
|
80
|
-
}),
|
|
81
|
-
onDisconnect: Effect.sync(() => {
|
|
82
|
-
if (!closed) {
|
|
83
|
-
setConn("reconnecting");
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
);
|
|
88
|
-
return Layer.effect(
|
|
89
|
-
RpcClient.Protocol,
|
|
90
|
-
RpcClient.makeProtocolSocket({
|
|
91
|
-
retryPolicy: retryPolicy(options),
|
|
92
|
-
retryTransientErrors: true
|
|
93
|
-
})
|
|
94
|
-
).pipe(
|
|
95
|
-
Layer.provide(Layer.mergeAll(socketLayer, RpcSerialization.layerJson, connectionHooksLayer))
|
|
96
|
-
);
|
|
97
|
-
};
|
|
98
|
-
const ensureSession = async () => {
|
|
99
|
-
if (closed) throw new Error("daemon RPC client is closed");
|
|
100
|
-
if (session) return session;
|
|
101
|
-
if (sessionPromise) return sessionPromise;
|
|
102
|
-
setConn("connecting");
|
|
103
|
-
sessionPromise = (async () => {
|
|
104
|
-
let runtime;
|
|
105
|
-
try {
|
|
106
|
-
const protocolLayer = await makeProtocolLayer();
|
|
107
|
-
const clientLayer = Layer.effect(
|
|
108
|
-
TokmonRpcClient,
|
|
109
|
-
RpcClient.make(TokmonRpcGroup)
|
|
110
|
-
).pipe(
|
|
111
|
-
Layer.provide(protocolLayer)
|
|
112
|
-
);
|
|
113
|
-
runtime = ManagedRuntime.make(clientLayer);
|
|
114
|
-
await runtime.runPromise(TokmonRpcClient.asEffect());
|
|
115
|
-
if (closed) {
|
|
116
|
-
await runtime.dispose();
|
|
117
|
-
throw new Error("daemon RPC client is closed");
|
|
118
|
-
}
|
|
119
|
-
session = { runtime };
|
|
120
|
-
return session;
|
|
121
|
-
} catch (error) {
|
|
122
|
-
sessionPromise = null;
|
|
123
|
-
await runtime?.dispose().catch(() => {
|
|
124
|
-
});
|
|
125
|
-
if (!closed) setConn("error", error);
|
|
126
|
-
throw error;
|
|
127
|
-
}
|
|
128
|
-
})();
|
|
129
|
-
return sessionPromise;
|
|
130
|
-
};
|
|
131
|
-
const run = async (effectFor) => {
|
|
132
|
-
const active = await ensureSession();
|
|
133
|
-
try {
|
|
134
|
-
return await active.runtime.runPromise(
|
|
135
|
-
TokmonRpcClient.use((client) => effectFor(client))
|
|
136
|
-
);
|
|
137
|
-
} catch (error) {
|
|
138
|
-
if (!closed) {
|
|
139
|
-
resetSession(active);
|
|
140
|
-
setConn("error", error);
|
|
141
|
-
}
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
const subscribe = (streamFor, onValue, staleAfterFor) => {
|
|
146
|
-
if (closed) return () => {
|
|
147
|
-
};
|
|
148
|
-
let fiber = null;
|
|
149
|
-
let unsubscribed = false;
|
|
150
|
-
let retryTimer = null;
|
|
151
|
-
let watchdogTimer = null;
|
|
152
|
-
let lastValueAt = 0;
|
|
153
|
-
let staleAfterMs = Number.POSITIVE_INFINITY;
|
|
154
|
-
let watchdogRestart = false;
|
|
155
|
-
const stopFiber = () => {
|
|
156
|
-
if (!fiber) return;
|
|
157
|
-
const current = fiber;
|
|
158
|
-
fiber = null;
|
|
159
|
-
fibers.delete(current);
|
|
160
|
-
void (session?.runtime.runPromise(Fiber.interrupt(current)) ?? Effect.runPromise(Fiber.interrupt(current))).catch(() => {
|
|
161
|
-
});
|
|
162
|
-
};
|
|
163
|
-
const scheduleRetry = () => {
|
|
164
|
-
if (closed || unsubscribed || retryTimer) return;
|
|
165
|
-
retryTimer = setTimeout(() => {
|
|
166
|
-
retryTimer = null;
|
|
167
|
-
start();
|
|
168
|
-
}, options.reconnectBaseDelayMs ?? 250);
|
|
169
|
-
retryTimer.unref?.();
|
|
170
|
-
};
|
|
171
|
-
if (staleAfterFor) {
|
|
172
|
-
const checkEveryMs = Math.min(5e3, Math.max(10, (options.snapshotStaleFloorMs ?? 9e4) / 2));
|
|
173
|
-
watchdogTimer = setInterval(() => {
|
|
174
|
-
if (!fiber || lastValueAt === 0 || Date.now() - lastValueAt <= staleAfterMs) return;
|
|
175
|
-
watchdogRestart = true;
|
|
176
|
-
lastValueAt = Date.now();
|
|
177
|
-
setConn("reconnecting");
|
|
178
|
-
stopFiber();
|
|
179
|
-
}, checkEveryMs);
|
|
180
|
-
watchdogTimer.unref?.();
|
|
181
|
-
}
|
|
182
|
-
const start = () => {
|
|
183
|
-
void (async () => {
|
|
184
|
-
try {
|
|
185
|
-
const active = await ensureSession();
|
|
186
|
-
if (closed || unsubscribed) return;
|
|
187
|
-
lastValueAt = Date.now();
|
|
188
|
-
staleAfterMs = options.snapshotStaleFloorMs ?? 9e4;
|
|
189
|
-
const currentFiber = active.runtime.runFork(
|
|
190
|
-
TokmonRpcClient.use(
|
|
191
|
-
(client) => streamFor(client).pipe(
|
|
192
|
-
Stream.runForEach(
|
|
193
|
-
(value) => Effect.sync(() => {
|
|
194
|
-
lastValueAt = Date.now();
|
|
195
|
-
if (staleAfterFor) staleAfterMs = staleAfterFor(value);
|
|
196
|
-
try {
|
|
197
|
-
onValue(value);
|
|
198
|
-
} catch {
|
|
199
|
-
}
|
|
200
|
-
})
|
|
201
|
-
)
|
|
202
|
-
)
|
|
203
|
-
)
|
|
204
|
-
);
|
|
205
|
-
fiber = currentFiber;
|
|
206
|
-
fibers.add(currentFiber);
|
|
207
|
-
currentFiber.addObserver((exit) => {
|
|
208
|
-
fibers.delete(currentFiber);
|
|
209
|
-
if (fiber === currentFiber) fiber = null;
|
|
210
|
-
if (closed || unsubscribed) return;
|
|
211
|
-
const stale = watchdogRestart;
|
|
212
|
-
watchdogRestart = false;
|
|
213
|
-
resetSession(active);
|
|
214
|
-
if (stale || Exit.isSuccess(exit)) setConn("reconnecting");
|
|
215
|
-
else setConn("error", Cause.squash(exit.cause));
|
|
216
|
-
scheduleRetry();
|
|
217
|
-
});
|
|
218
|
-
} catch (error) {
|
|
219
|
-
if (!closed && !unsubscribed) {
|
|
220
|
-
resetSession();
|
|
221
|
-
setConn("error", error);
|
|
222
|
-
scheduleRetry();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
})();
|
|
226
|
-
};
|
|
227
|
-
start();
|
|
228
|
-
return () => {
|
|
229
|
-
unsubscribed = true;
|
|
230
|
-
if (retryTimer) {
|
|
231
|
-
clearTimeout(retryTimer);
|
|
232
|
-
retryTimer = null;
|
|
233
|
-
}
|
|
234
|
-
if (watchdogTimer) {
|
|
235
|
-
clearInterval(watchdogTimer);
|
|
236
|
-
watchdogTimer = null;
|
|
237
|
-
}
|
|
238
|
-
stopFiber();
|
|
239
|
-
};
|
|
240
|
-
};
|
|
241
|
-
return {
|
|
242
|
-
getConfig: () => run((client) => client[TOKMON_WS_METHODS.getConfig]({})).then((state) => state),
|
|
243
|
-
setConfig: (config) => run((client) => client[TOKMON_WS_METHODS.setConfig](config)).then((state) => state),
|
|
244
|
-
refresh: (scope = "all") => run((client) => client[TOKMON_WS_METHODS.refresh]({ scope })),
|
|
245
|
-
browseFs: (path) => run((client) => client[TOKMON_WS_METHODS.browseFs]({ path })),
|
|
246
|
-
subscribeSnapshot: (onSnapshot) => subscribe(
|
|
247
|
-
(client) => client[TOKMON_WS_METHODS.snapshot]({}).pipe(Stream.map((value) => value)),
|
|
248
|
-
onSnapshot,
|
|
249
|
-
(snapshot) => Math.max(options.snapshotStaleFloorMs ?? 9e4, snapshot.intervalMs * 3)
|
|
250
|
-
),
|
|
251
|
-
subscribeConfig: (onConfig) => subscribe((client) => client[TOKMON_WS_METHODS.config]({}).pipe(Stream.map((value) => value)), onConfig),
|
|
252
|
-
async close() {
|
|
253
|
-
if (closed) return;
|
|
254
|
-
closed = true;
|
|
255
|
-
setConn("closed");
|
|
256
|
-
const active = [...fibers];
|
|
257
|
-
fibers.clear();
|
|
258
|
-
const activeSession = session ?? await sessionPromise?.catch(() => null) ?? null;
|
|
259
|
-
await Promise.all(active.map(
|
|
260
|
-
(fiber) => (activeSession?.runtime.runPromise(Fiber.interrupt(fiber)) ?? Effect.runPromise(Fiber.interrupt(fiber))).catch(() => {
|
|
261
|
-
})
|
|
262
|
-
));
|
|
263
|
-
session = null;
|
|
264
|
-
sessionPromise = null;
|
|
265
|
-
if (activeSession) {
|
|
266
|
-
await activeSession.runtime.dispose().catch(() => {
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export {
|
|
274
|
-
createDaemonRpcClient
|
|
275
|
-
};
|