tokmon 0.19.8 → 0.20.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.
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/config.ts
4
+ import { readFile, writeFile, mkdir, rename } from "fs/promises";
5
+ import { mkdirSync, renameSync, writeFileSync } from "fs";
6
+ import { join, isAbsolute } from "path";
7
+ import { homedir } from "os";
8
+
9
+ // src/config-schema.ts
10
+ var DEFAULTS = {
11
+ interval: 2,
12
+ billingInterval: 5,
13
+ clearScreen: true,
14
+ timezone: null,
15
+ accounts: [],
16
+ activeAccountId: null,
17
+ disabledProviders: [],
18
+ onboarded: false,
19
+ dashboardLayout: "grid",
20
+ defaultFocus: "all",
21
+ ascii: "auto",
22
+ knownProviders: []
23
+ };
24
+ var LEGACY_KNOWN = ["claude", "codex", "cursor"];
25
+ var ACCENT_COLORS = ["cyan", "magenta", "green", "yellow", "blue", "red"];
26
+ var PROVIDER_ORDER = ["claude", "codex", "cursor", "copilot", "pi", "opencode", "antigravity", "gemini"];
27
+ var PROVIDER_IDS = [...PROVIDER_ORDER];
28
+ var COLOR_PALETTE = [
29
+ "cyan",
30
+ "magenta",
31
+ "green",
32
+ "yellow",
33
+ "blue",
34
+ "red",
35
+ "cyanBright",
36
+ "magentaBright",
37
+ "greenBright"
38
+ ];
39
+ var PROVIDER_META = {
40
+ claude: { name: "Claude", color: "green" },
41
+ codex: { name: "Codex", color: "cyan" },
42
+ cursor: { name: "Cursor", color: "magenta" },
43
+ copilot: { name: "Copilot", color: "white" },
44
+ pi: { name: "Pi", color: "blue" },
45
+ opencode: { name: "opencode", color: "yellow" },
46
+ antigravity: { name: "Antigravity", color: "red" },
47
+ gemini: { name: "Gemini", color: "greenBright" }
48
+ };
49
+ function getTrackedAccountRows(config, trackedProviders = PROVIDER_ORDER.filter((pid) => !config.disabledProviders.includes(pid)), autoAccounts) {
50
+ const tracked = new Set(trackedProviders);
51
+ const configuredIds = /* @__PURE__ */ new Set();
52
+ const configuredKeys = /* @__PURE__ */ new Set();
53
+ const rowIds = /* @__PURE__ */ new Set();
54
+ const rowKeys = /* @__PURE__ */ new Set();
55
+ const rows = [];
56
+ const keyFor = (providerId, homeDir) => `${providerId}:${homeDir && homeDir !== "~" ? homeDir : "~"}`;
57
+ const rememberRow = (row) => {
58
+ rowIds.add(row.id);
59
+ rowKeys.add(keyFor(row.providerId, row.homeDir));
60
+ rows.push(row);
61
+ };
62
+ config.accounts.forEach((account, explicitIndex) => {
63
+ const meta = PROVIDER_META[account.providerId];
64
+ configuredIds.add(account.id);
65
+ configuredKeys.add(keyFor(account.providerId, account.homeDir));
66
+ rememberRow({
67
+ id: account.id,
68
+ providerId: account.providerId,
69
+ name: account.name,
70
+ homeDir: account.homeDir || "~",
71
+ color: account.color || meta.color,
72
+ source: "configured",
73
+ explicitId: account.id,
74
+ explicitIndex
75
+ });
76
+ });
77
+ if (autoAccounts) {
78
+ for (const account of autoAccounts) {
79
+ if (config.disabledProviders.includes(account.providerId)) continue;
80
+ const key = keyFor(account.providerId, account.homeDir);
81
+ if (configuredIds.has(account.id) || configuredKeys.has(key) || rowIds.has(account.id) || rowKeys.has(key)) continue;
82
+ const meta = PROVIDER_META[account.providerId];
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: "auto"
90
+ });
91
+ }
92
+ }
93
+ for (const providerId of PROVIDER_ORDER) {
94
+ if (config.disabledProviders.includes(providerId)) continue;
95
+ if (!tracked.has(providerId)) continue;
96
+ const key = keyFor(providerId, "~");
97
+ if (configuredIds.has(providerId) || configuredKeys.has(key) || rowIds.has(providerId) || rowKeys.has(key)) continue;
98
+ const meta = PROVIDER_META[providerId];
99
+ rememberRow({
100
+ id: providerId,
101
+ providerId,
102
+ name: meta.name,
103
+ homeDir: "~",
104
+ color: meta.color,
105
+ source: "auto"
106
+ });
107
+ }
108
+ return rows;
109
+ }
110
+ function clampNum(v, fallback, min) {
111
+ return typeof v === "number" && Number.isFinite(v) && v >= min ? v : fallback;
112
+ }
113
+ function isValidTimezone(tz) {
114
+ try {
115
+ new Intl.DateTimeFormat("en-CA", { timeZone: tz });
116
+ return true;
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+ function normalizeConfig(parsed) {
122
+ try {
123
+ const accounts = (Array.isArray(parsed.accounts) ? parsed.accounts : []).map((a) => ({ ...a, providerId: a.providerId ?? "claude" })).filter((a) => typeof a?.id === "string" && typeof a?.name === "string" && PROVIDER_IDS.includes(a.providerId));
124
+ return {
125
+ ...DEFAULTS,
126
+ interval: clampNum(parsed.interval, DEFAULTS.interval, 1),
127
+ billingInterval: clampNum(parsed.billingInterval, DEFAULTS.billingInterval, 1),
128
+ clearScreen: typeof parsed.clearScreen === "boolean" ? parsed.clearScreen : DEFAULTS.clearScreen,
129
+ timezone: typeof parsed.timezone === "string" && parsed.timezone.trim() && isValidTimezone(parsed.timezone.trim()) ? parsed.timezone : null,
130
+ accounts,
131
+ activeAccountId: typeof parsed.activeAccountId === "string" ? parsed.activeAccountId : null,
132
+ disabledProviders: (Array.isArray(parsed.disabledProviders) ? parsed.disabledProviders : []).filter((p) => PROVIDER_IDS.includes(p)),
133
+ onboarded: parsed.onboarded === true,
134
+ dashboardLayout: parsed.dashboardLayout === "single" ? "single" : "grid",
135
+ defaultFocus: parsed.defaultFocus === "last" ? "last" : "all",
136
+ ascii: parsed.ascii === "on" ? "on" : parsed.ascii === "off" ? "off" : "auto",
137
+ knownProviders: Array.isArray(parsed.knownProviders) ? parsed.knownProviders.filter((p) => PROVIDER_IDS.includes(p)) : parsed.onboarded === true ? [...LEGACY_KNOWN] : []
138
+ };
139
+ } catch {
140
+ return { ...DEFAULTS };
141
+ }
142
+ }
143
+ function slugify(value) {
144
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
145
+ }
146
+ function generateAccountId(name, existing) {
147
+ const base = slugify(name) || "account";
148
+ const taken = new Set(existing.map((a) => a.id));
149
+ if (!taken.has(base)) return base;
150
+ for (let i = 2; i < 1e3; i++) {
151
+ const candidate = `${base}_${i}`;
152
+ if (!taken.has(candidate)) return candidate;
153
+ }
154
+ return `${base}_${Date.now()}`;
155
+ }
156
+ function pickAccentColor(existing) {
157
+ const used = new Set(existing.map((a) => a.color).filter(Boolean));
158
+ for (const c of ACCENT_COLORS) {
159
+ if (!used.has(c)) return c;
160
+ }
161
+ return ACCENT_COLORS[existing.length % ACCENT_COLORS.length];
162
+ }
163
+ function sanitizeTyped(input) {
164
+ if (!input) return "";
165
+ 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, "");
166
+ }
167
+
168
+ // src/config.ts
169
+ function envDir(name) {
170
+ const v = process.env[name];
171
+ return v && v.trim() && isAbsolute(v.trim()) ? v.trim() : void 0;
172
+ }
173
+ function configDir() {
174
+ if (process.platform === "win32") {
175
+ return join(envDir("APPDATA") ?? join(homedir(), "AppData", "Roaming"), "tokmon");
176
+ }
177
+ return join(envDir("XDG_CONFIG_HOME") ?? join(homedir(), ".config"), "tokmon");
178
+ }
179
+ function configLocation() {
180
+ return join(configDir(), "config.json");
181
+ }
182
+ function cacheDir() {
183
+ if (process.platform === "win32") {
184
+ return join(envDir("LOCALAPPDATA") ?? envDir("APPDATA") ?? join(homedir(), "AppData", "Local"), "tokmon", "cache");
185
+ }
186
+ if (process.platform === "darwin") {
187
+ return join(homedir(), "Library", "Caches", "tokmon");
188
+ }
189
+ return join(envDir("XDG_CACHE_HOME") ?? join(homedir(), ".cache"), "tokmon");
190
+ }
191
+ async function loadConfig() {
192
+ let raw;
193
+ try {
194
+ raw = await readFile(configLocation(), "utf-8");
195
+ } catch {
196
+ return { ...DEFAULTS };
197
+ }
198
+ let parsed;
199
+ try {
200
+ parsed = JSON.parse(raw);
201
+ } catch {
202
+ try {
203
+ await writeFile(configLocation() + ".bak", raw);
204
+ } catch {
205
+ }
206
+ return { ...DEFAULTS };
207
+ }
208
+ return normalizeConfig(parsed);
209
+ }
210
+ var saveQueue = Promise.resolve();
211
+ function configJson(config) {
212
+ return JSON.stringify(config, null, 2) + "\n";
213
+ }
214
+ function saveConfig(config) {
215
+ saveQueue = saveQueue.then(async () => {
216
+ try {
217
+ const dir = configDir();
218
+ await mkdir(dir, { recursive: true });
219
+ const tmp = join(dir, `config.json.${process.pid}.tmp`);
220
+ await writeFile(tmp, configJson(config));
221
+ await rename(tmp, configLocation());
222
+ } catch {
223
+ }
224
+ });
225
+ return saveQueue;
226
+ }
227
+ function saveConfigSync(config) {
228
+ try {
229
+ const dir = configDir();
230
+ mkdirSync(dir, { recursive: true });
231
+ const tmp = join(dir, `config.json.${process.pid}.tmp`);
232
+ writeFileSync(tmp, configJson(config));
233
+ renameSync(tmp, configLocation());
234
+ } catch {
235
+ }
236
+ }
237
+ function expandHome(p) {
238
+ if (!p) return homedir();
239
+ if (p === "~" || p === "~/" || p === "~\\") return homedir();
240
+ if (p.startsWith("~/") || p.startsWith("~\\")) return join(homedir(), p.slice(2));
241
+ return p;
242
+ }
243
+ function findAccount(config, id) {
244
+ if (!id) return null;
245
+ return config.accounts.find((a) => a.id === id) ?? null;
246
+ }
247
+
248
+ export {
249
+ DEFAULTS,
250
+ ACCENT_COLORS,
251
+ PROVIDER_IDS,
252
+ COLOR_PALETTE,
253
+ PROVIDER_META,
254
+ getTrackedAccountRows,
255
+ clampNum,
256
+ isValidTimezone,
257
+ normalizeConfig,
258
+ slugify,
259
+ generateAccountId,
260
+ pickAccentColor,
261
+ sanitizeTyped,
262
+ envDir,
263
+ configLocation,
264
+ cacheDir,
265
+ loadConfig,
266
+ saveConfig,
267
+ saveConfigSync,
268
+ expandHome,
269
+ findAccount
270
+ };
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/glyphs.ts
4
+ var GLYPHS_UNICODE = {
5
+ spark: ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"],
6
+ barFull: "\u2501",
7
+ barEmpty: "\u2500",
8
+ rule: "\u2500",
9
+ spinner: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
10
+ dot: "\u25CF",
11
+ dotSel: "\u25C9",
12
+ radioOff: "\u25CB",
13
+ dotAll: "\u2726",
14
+ caretR: "\u25B8",
15
+ caretL: "\u25C2",
16
+ play: "\u25B6",
17
+ arrowU: "\u2191",
18
+ arrowD: "\u2193",
19
+ arrowL: "\u2190",
20
+ arrowR: "\u2192",
21
+ shift: "\u21E7",
22
+ vbar: "\u258C",
23
+ treeMid: "\u251C\u2500",
24
+ treeEnd: "\u2514\u2500",
25
+ boxMark: "\u2502",
26
+ check: "\u2713",
27
+ warn: "\u26A0",
28
+ ellipsis: "\u2026",
29
+ middot: "\xB7",
30
+ emDash: "\u2014",
31
+ eur: "\u20AC",
32
+ gbp: "\xA3",
33
+ border: "round"
34
+ };
35
+ var GLYPHS_ASCII = {
36
+ spark: [".", ":", "-", "=", "+", "*", "#", "@"],
37
+ barFull: "#",
38
+ barEmpty: "-",
39
+ rule: "-",
40
+ spinner: ["|", "/", "-", "\\"],
41
+ dot: "*",
42
+ dotSel: "*",
43
+ radioOff: "o",
44
+ dotAll: "+",
45
+ caretR: ">",
46
+ caretL: "<",
47
+ play: ">",
48
+ arrowU: "^",
49
+ arrowD: "v",
50
+ arrowL: "<",
51
+ arrowR: ">",
52
+ shift: "^",
53
+ vbar: "|",
54
+ treeMid: "+-",
55
+ treeEnd: "`-",
56
+ boxMark: "|",
57
+ check: "x",
58
+ warn: "!",
59
+ ellipsis: "...",
60
+ middot: "-",
61
+ emDash: "-",
62
+ eur: "EUR",
63
+ gbp: "GBP",
64
+ border: "classic"
65
+ };
66
+ function detectUnicode(env, isTTY, platform) {
67
+ if (!isTTY) return false;
68
+ if (env.TERM === "dumb") return false;
69
+ if (platform === "win32") {
70
+ return Boolean(env.WT_SESSION || env.ConEmuANSI === "ON" || env.TERM_PROGRAM === "vscode" || /xterm/i.test(env.TERM ?? ""));
71
+ }
72
+ const loc = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
73
+ if (loc && /\.(iso|latin|ascii|cp\d|koi|gbk|big5)/i.test(loc)) return false;
74
+ if (/^(C|POSIX)$/i.test(loc)) return false;
75
+ return true;
76
+ }
77
+ function resolveGlyphs(opts) {
78
+ let ascii;
79
+ if (opts.flag === "on") ascii = true;
80
+ else if (opts.flag === "off") ascii = false;
81
+ else {
82
+ const e = (opts.env.TOKMON_ASCII ?? "").toLowerCase();
83
+ if (/^(1|true|on|yes)$/.test(e)) ascii = true;
84
+ else if (/^(0|false|off|no)$/.test(e)) ascii = false;
85
+ else if (opts.config === "on") ascii = true;
86
+ else if (opts.config === "off") ascii = false;
87
+ else ascii = !detectUnicode(opts.env, opts.isTTY, opts.platform);
88
+ }
89
+ return ascii ? GLYPHS_ASCII : GLYPHS_UNICODE;
90
+ }
91
+ var active = GLYPHS_UNICODE;
92
+ function setGlyphs(set) {
93
+ active = set;
94
+ }
95
+ function glyphs() {
96
+ return active;
97
+ }
98
+
99
+ export {
100
+ GLYPHS_UNICODE,
101
+ GLYPHS_ASCII,
102
+ detectUnicode,
103
+ resolveGlyphs,
104
+ setGlyphs,
105
+ glyphs
106
+ };