@mayank3238/keymux 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/dist/cli.cjs +870 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +840 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +2615 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +859 -0
- package/dist/index.d.ts +859 -0
- package/dist/index.js +2567 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy/index.cjs +2138 -0
- package/dist/proxy/index.cjs.map +1 -0
- package/dist/proxy/index.d.cts +38 -0
- package/dist/proxy/index.d.ts +38 -0
- package/dist/proxy/index.js +2122 -0
- package/dist/proxy/index.js.map +1 -0
- package/package.json +77 -0
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var fs2 = require('fs');
|
|
5
|
+
var path2 = require('path');
|
|
6
|
+
var os = require('os');
|
|
7
|
+
var chalk = require('chalk');
|
|
8
|
+
var http = require('http');
|
|
9
|
+
var child_process = require('child_process');
|
|
10
|
+
var net = require('net');
|
|
11
|
+
|
|
12
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
13
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
|
|
15
|
+
function _interopNamespace(e) {
|
|
16
|
+
if (e && e.__esModule) return e;
|
|
17
|
+
var n = Object.create(null);
|
|
18
|
+
if (e) {
|
|
19
|
+
Object.keys(e).forEach(function (k) {
|
|
20
|
+
if (k !== 'default') {
|
|
21
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
22
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
get: function () { return e[k]; }
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
n.default = e;
|
|
30
|
+
return Object.freeze(n);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
|
|
34
|
+
var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
|
|
35
|
+
var os__namespace = /*#__PURE__*/_interopNamespace(os);
|
|
36
|
+
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
37
|
+
var http__namespace = /*#__PURE__*/_interopNamespace(http);
|
|
38
|
+
var net__namespace = /*#__PURE__*/_interopNamespace(net);
|
|
39
|
+
|
|
40
|
+
// keymux v1.0.0 — Smart API Key Multiplexer for LLM Providers
|
|
41
|
+
|
|
42
|
+
var configPath = path2__namespace.join(os__namespace.homedir(), ".keymux", "config.json");
|
|
43
|
+
var config = readConfig();
|
|
44
|
+
var draftConfig = JSON.parse(JSON.stringify(config));
|
|
45
|
+
var flashMessage = "";
|
|
46
|
+
function readConfig() {
|
|
47
|
+
try {
|
|
48
|
+
if (fs2__namespace.existsSync(configPath)) {
|
|
49
|
+
const parsed = JSON.parse(fs2__namespace.readFileSync(configPath, "utf8"));
|
|
50
|
+
if (parsed && typeof parsed === "object") {
|
|
51
|
+
if (!parsed.keys) parsed.keys = { openrouter: [], nvidia: [], mistral: [], gemini: [], groq: [] };
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
}
|
|
57
|
+
return { defaultProvider: null, defaultModel: "", keys: { openrouter: [], nvidia: [], mistral: [], gemini: [], groq: [] } };
|
|
58
|
+
}
|
|
59
|
+
function saveConfig(cfg) {
|
|
60
|
+
try {
|
|
61
|
+
fs2__namespace.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
|
62
|
+
} catch (e) {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
var proxyPort = process.argv.includes("--port") ? parseInt(process.argv[process.argv.indexOf("--port") + 1] || "3002", 10) : 3002;
|
|
66
|
+
async function triggerReload() {
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const req = http__namespace.request({
|
|
69
|
+
hostname: "localhost",
|
|
70
|
+
port: proxyPort,
|
|
71
|
+
path: "/v1/keymux/reload",
|
|
72
|
+
method: "POST"
|
|
73
|
+
}, (res) => {
|
|
74
|
+
resolve(true);
|
|
75
|
+
});
|
|
76
|
+
req.on("error", () => {
|
|
77
|
+
resolve(false);
|
|
78
|
+
});
|
|
79
|
+
req.end();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function readUsageData() {
|
|
83
|
+
const usagePath = path2__namespace.join(os__namespace.homedir(), ".keymux", "usage.json");
|
|
84
|
+
try {
|
|
85
|
+
if (fs2__namespace.existsSync(usagePath)) {
|
|
86
|
+
const parsed = JSON.parse(fs2__namespace.readFileSync(usagePath, "utf8"));
|
|
87
|
+
if (parsed && typeof parsed === "object") {
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
}
|
|
93
|
+
return { totalInputTokens: 0, totalOutputTokens: 0, totalCacheTokens: 0, providerUsage: {}, daily: {}, sessions: 0 };
|
|
94
|
+
}
|
|
95
|
+
function formatTokens(num) {
|
|
96
|
+
if (num > 1e6) return (num / 1e6).toFixed(1) + "m";
|
|
97
|
+
if (num > 1e3) return (num / 1e3).toFixed(1) + "k";
|
|
98
|
+
return num.toString();
|
|
99
|
+
}
|
|
100
|
+
var TABS = ["Status", "Usage", "Stats", "Settings"];
|
|
101
|
+
var currentTabIdx = 0;
|
|
102
|
+
var settingsSelectionIdx = 0;
|
|
103
|
+
var expandedProviders = [];
|
|
104
|
+
var currentTreeItems = [];
|
|
105
|
+
var usageViewMode = "today";
|
|
106
|
+
var settingsView = "main";
|
|
107
|
+
var isInputMode = false;
|
|
108
|
+
var inputTarget = "";
|
|
109
|
+
var inputBuffer = "";
|
|
110
|
+
var isListening = false;
|
|
111
|
+
var orange = chalk__default.default.hex("#e27d60");
|
|
112
|
+
var lightBlue = chalk__default.default.hex("#8b9df2");
|
|
113
|
+
var gray = chalk__default.default.hex("#808080");
|
|
114
|
+
var highlight = chalk__default.default.bgHex("#8b9df2").black;
|
|
115
|
+
var whiteHighlight = chalk__default.default.bgWhite.black;
|
|
116
|
+
function renderTabs() {
|
|
117
|
+
let out = "";
|
|
118
|
+
for (let i = 0; i < TABS.length; i++) {
|
|
119
|
+
if (i === currentTabIdx) {
|
|
120
|
+
out += highlight(` ${TABS[i]} `) + " ";
|
|
121
|
+
} else {
|
|
122
|
+
out += lightBlue(TABS[i]) + " ";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
console.log(out + "");
|
|
126
|
+
}
|
|
127
|
+
async function renderStats() {
|
|
128
|
+
console.log("\n" + whiteHighlight(" Overview "));
|
|
129
|
+
console.log(chalk__default.default.gray.italic(" (Showing all-time cumulative history and lifetime session metrics)\n"));
|
|
130
|
+
const data = readUsageData();
|
|
131
|
+
let favProvider = "None";
|
|
132
|
+
let maxUsage = 0;
|
|
133
|
+
for (const [prov, count] of Object.entries(data.providerUsage || {})) {
|
|
134
|
+
if (count > maxUsage) {
|
|
135
|
+
maxUsage = count;
|
|
136
|
+
favProvider = prov;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const totalTokens = (data.totalInputTokens || 0) + (data.totalOutputTokens || 0);
|
|
140
|
+
const formattedTotal = formatTokens(totalTokens);
|
|
141
|
+
const inTokensStr = formatTokens(data.totalInputTokens || 0);
|
|
142
|
+
const outTokensStr = formatTokens(data.totalOutputTokens || 0);
|
|
143
|
+
const cacheTokensStr = formatTokens(data.totalCacheTokens || 0);
|
|
144
|
+
const sessions = data.sessions || 1;
|
|
145
|
+
const dailyDates = Object.keys(data.daily || {}).sort();
|
|
146
|
+
const activeDays = dailyDates.length;
|
|
147
|
+
let mostActiveDay = "None";
|
|
148
|
+
let maxDailyTokens = 0;
|
|
149
|
+
for (const date of dailyDates) {
|
|
150
|
+
const dayData = data.daily[date];
|
|
151
|
+
const dayTokens = (dayData.inputTokens || 0) + (dayData.outputTokens || 0);
|
|
152
|
+
if (dayTokens > maxDailyTokens) {
|
|
153
|
+
maxDailyTokens = dayTokens;
|
|
154
|
+
mostActiveDay = date;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let currentStreak = 0;
|
|
158
|
+
if (activeDays > 0) {
|
|
159
|
+
const today2 = /* @__PURE__ */ new Date();
|
|
160
|
+
for (let i = 0; i < 365; i++) {
|
|
161
|
+
const checkDate = new Date(today2);
|
|
162
|
+
checkDate.setDate(today2.getDate() - i);
|
|
163
|
+
const dateStr = checkDate.toISOString().split("T")[0];
|
|
164
|
+
if (data.daily && data.daily[dateStr]) {
|
|
165
|
+
currentStreak++;
|
|
166
|
+
} else if (i > 0) {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const numWeeks = 52;
|
|
172
|
+
const blocks = [gray("\xB7"), orange("\u2591"), orange("\u2592"), orange("\u2593"), orange("\u2588")];
|
|
173
|
+
const grid = Array.from({ length: 7 }, () => Array(numWeeks).fill(" "));
|
|
174
|
+
const today = /* @__PURE__ */ new Date();
|
|
175
|
+
const todayDayOfWeek = today.getDay();
|
|
176
|
+
for (let col = 0; col < numWeeks; col++) {
|
|
177
|
+
for (let row = 0; row < 7; row++) {
|
|
178
|
+
const weeksAgo = numWeeks - 1 - col;
|
|
179
|
+
const daysAgo = weeksAgo * 7 + (todayDayOfWeek - row);
|
|
180
|
+
if (daysAgo < 0 || daysAgo > 364) {
|
|
181
|
+
if (grid[row] && typeof col === "number") grid[row][col] = " ";
|
|
182
|
+
} else {
|
|
183
|
+
const d = /* @__PURE__ */ new Date();
|
|
184
|
+
d.setDate(today.getDate() - daysAgo);
|
|
185
|
+
const dateStr = d.toISOString().split("T")[0];
|
|
186
|
+
const dayData = data.daily && data.daily[dateStr] ? data.daily[dateStr] : null;
|
|
187
|
+
if (!dayData) {
|
|
188
|
+
if (grid[row] && typeof col === "number") grid[row][col] = blocks[0];
|
|
189
|
+
} else {
|
|
190
|
+
const tokens = (dayData.inputTokens || 0) + (dayData.outputTokens || 0);
|
|
191
|
+
let b = 1;
|
|
192
|
+
if (tokens > 1e4) b = 2;
|
|
193
|
+
if (tokens > 5e4) b = 3;
|
|
194
|
+
if (tokens > 1e5) b = 4;
|
|
195
|
+
if (grid[row] && typeof col === "number") grid[row][col] = blocks[b];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
console.log(" Sep Oct Nov Dec Jan Feb Mar Apr May Jun Jul Aug");
|
|
201
|
+
const rowLabels = [" ", "Mon", " ", "Wed", " ", "Fri", " "];
|
|
202
|
+
for (let row = 0; row < 7; row++) {
|
|
203
|
+
process.stdout.write(rowLabels[row] + " ");
|
|
204
|
+
for (let col = 0; col < numWeeks; col++) {
|
|
205
|
+
process.stdout.write(grid[row] && grid[row][col] ? grid[row][col] : " ");
|
|
206
|
+
}
|
|
207
|
+
console.log();
|
|
208
|
+
}
|
|
209
|
+
console.log(" Less " + blocks.join("") + " More");
|
|
210
|
+
console.log(orange("All time") + gray(" \xB7 Last 7 days \xB7 Last 30 days") + "");
|
|
211
|
+
console.log(`Favorite provider: ${orange(favProvider.padEnd(16))}Total tokens: ${orange(formattedTotal)}`);
|
|
212
|
+
console.log(`Sessions: ${orange(sessions.toString().padEnd(25))}Active days: ${orange(activeDays.toString())}`);
|
|
213
|
+
console.log(`Most active day: ${orange(mostActiveDay.padEnd(17))}Current streak: ${orange(currentStreak + " days")}`);
|
|
214
|
+
console.log();
|
|
215
|
+
console.log(gray(`Input ${inTokensStr} \xB7 Output ${outTokensStr} \xB7 Cache read ${cacheTokensStr}`));
|
|
216
|
+
console.log();
|
|
217
|
+
console.log();
|
|
218
|
+
console.log(gray("\u2193 stats \xB7 r to refresh"));
|
|
219
|
+
}
|
|
220
|
+
async function renderStatus() {
|
|
221
|
+
console.log("\n\n STATUS");
|
|
222
|
+
let activeMode = "auto";
|
|
223
|
+
let activeModel = "none";
|
|
224
|
+
let currentProvider = "auto";
|
|
225
|
+
try {
|
|
226
|
+
const amRes = await fetch(`http://localhost:${proxyPort}/v1/keymux/activeModel`);
|
|
227
|
+
if (amRes.ok) {
|
|
228
|
+
const am = await amRes.json();
|
|
229
|
+
activeMode = am.mode === "STRICT" ? "strict" : "auto";
|
|
230
|
+
activeModel = am.model === "auto" ? "auto (best available)" : am.model;
|
|
231
|
+
currentProvider = am.provider === "auto" ? "auto" : am.provider.toLowerCase();
|
|
232
|
+
}
|
|
233
|
+
} catch (e) {
|
|
234
|
+
}
|
|
235
|
+
let activeKey = "";
|
|
236
|
+
let lastUsedModel = "none";
|
|
237
|
+
let lastUsedProvider = "none";
|
|
238
|
+
try {
|
|
239
|
+
const lrRes = await fetch(`http://localhost:${proxyPort}/v1/keymux/lastRoute`);
|
|
240
|
+
if (lrRes.ok) {
|
|
241
|
+
const lr = await lrRes.json();
|
|
242
|
+
if (lr && lr.provider) {
|
|
243
|
+
activeKey = lr.key || "";
|
|
244
|
+
lastUsedModel = lr.model || "none";
|
|
245
|
+
lastUsedProvider = lr.provider || "none";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} catch (e) {
|
|
249
|
+
}
|
|
250
|
+
const leftColWidth = 35;
|
|
251
|
+
const printRow = (label, value) => {
|
|
252
|
+
console.log(` ${chalk__default.default.white(label.padEnd(leftColWidth))} ${chalk__default.default.white(value)}`);
|
|
253
|
+
};
|
|
254
|
+
let maskedActiveKey = "none";
|
|
255
|
+
if (activeKey) {
|
|
256
|
+
maskedActiveKey = activeKey.length > 10 ? `${activeKey.substring(0, 4)}...${activeKey.slice(-4)}` : activeKey;
|
|
257
|
+
}
|
|
258
|
+
let displayModel = activeModel;
|
|
259
|
+
let displayProvider = currentProvider;
|
|
260
|
+
if (activeMode === "auto") {
|
|
261
|
+
if (lastUsedModel && lastUsedModel !== "none") {
|
|
262
|
+
displayModel = `auto (last: ${lastUsedModel})`;
|
|
263
|
+
}
|
|
264
|
+
if (lastUsedProvider && lastUsedProvider !== "none") {
|
|
265
|
+
displayProvider = `auto (last: ${lastUsedProvider})`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
printRow("Active mode", activeMode);
|
|
269
|
+
printRow("Active model", displayModel);
|
|
270
|
+
printRow("Current provider", displayProvider);
|
|
271
|
+
printRow("Last used key", maskedActiveKey);
|
|
272
|
+
console.log("\n CONNECTIONS (Real-time health, RPM limits, latency)");
|
|
273
|
+
console.log(" " + "\u2500".repeat(60));
|
|
274
|
+
try {
|
|
275
|
+
const res = await fetch(`http://localhost:${proxyPort}/v1/keymux/stats`);
|
|
276
|
+
if (res.ok) {
|
|
277
|
+
const stats = await res.json();
|
|
278
|
+
if (stats.length === 0) {
|
|
279
|
+
console.log(chalk__default.default.dim(" No active keys found."));
|
|
280
|
+
}
|
|
281
|
+
for (const ep of stats) {
|
|
282
|
+
let providerName = ep.id?.split("-")[0] || "unk";
|
|
283
|
+
if (providerName.toLowerCase() === "openrouter") providerName = "OpenRouter";
|
|
284
|
+
else if (providerName.toLowerCase() === "nvidia") providerName = "Nvidia NIM";
|
|
285
|
+
else if (providerName.toLowerCase() === "mistral") providerName = "Mistral AI";
|
|
286
|
+
else if (providerName.toLowerCase() === "gemini") providerName = "Google Gemini";
|
|
287
|
+
else if (providerName.toLowerCase() === "groq") providerName = "Groq";
|
|
288
|
+
else providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1);
|
|
289
|
+
let kStr = ep.key || "unk";
|
|
290
|
+
if (kStr.length > 10) {
|
|
291
|
+
kStr = kStr.substring(0, 4) + "..." + kStr.slice(-4);
|
|
292
|
+
}
|
|
293
|
+
const isH = ep.isHealthy;
|
|
294
|
+
const cooldownS = ep.cooldownRemainingMs ? Math.ceil(ep.cooldownRemainingMs / 1e3) : 0;
|
|
295
|
+
let healthStr = "";
|
|
296
|
+
if (!isH && (ep.circuitState === "open" || cooldownS > 0)) {
|
|
297
|
+
healthStr = chalk__default.default.red(`BLOCKED ${cooldownS}s cdn`.padEnd(19));
|
|
298
|
+
} else if (!isH) {
|
|
299
|
+
healthStr = chalk__default.default.red("BLOCKED ".padEnd(19));
|
|
300
|
+
} else {
|
|
301
|
+
healthStr = chalk__default.default.green("ACTIVE ".padEnd(19));
|
|
302
|
+
}
|
|
303
|
+
const rpm = ep.rpm || 0;
|
|
304
|
+
const lim = ep.rpmLimit;
|
|
305
|
+
let rStr = lim && lim > 0 ? `${rpm} rpm` : `${rpm} rpm`;
|
|
306
|
+
rStr = rStr.padEnd(16);
|
|
307
|
+
const lat = ep.latency || ep.avgLatencyMs || 0;
|
|
308
|
+
let lStr = !isH && (ep.circuitState === "open" || cooldownS > 0) ? "---" : lat >= 6e4 ? `${(lat / 6e4).toFixed(1)}m` : lat >= 1e3 ? `${(lat / 1e3).toFixed(1)}s` : `${lat}ms`;
|
|
309
|
+
if (lStr === "---") {
|
|
310
|
+
lStr = chalk__default.default.gray("---");
|
|
311
|
+
} else {
|
|
312
|
+
lStr = chalk__default.default.white(lStr);
|
|
313
|
+
}
|
|
314
|
+
console.log(` ${chalk__default.default.white(providerName.padEnd(15))}${chalk__default.default.white(kStr.padEnd(16))}${healthStr}${chalk__default.default.white(rStr)}${lStr}`);
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
console.log(chalk__default.default.dim(" Proxy returned an error."));
|
|
318
|
+
}
|
|
319
|
+
} catch (e) {
|
|
320
|
+
console.log(chalk__default.default.dim(" Proxy offline or unreachable."));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function renderSettings() {
|
|
324
|
+
const isUnsaved = JSON.stringify(draftConfig) !== JSON.stringify(config);
|
|
325
|
+
const unsavedTag = isUnsaved ? chalk__default.default.yellow(" (Unsaved Changes - Press S to Save)") : "";
|
|
326
|
+
if (flashMessage) {
|
|
327
|
+
console.log(chalk__default.default.green(` \u2714 ${flashMessage}`));
|
|
328
|
+
flashMessage = "";
|
|
329
|
+
}
|
|
330
|
+
currentTreeItems = [];
|
|
331
|
+
let index = 0;
|
|
332
|
+
const leftColWidth = 35;
|
|
333
|
+
const renderRow = (label, value, isSelected) => {
|
|
334
|
+
const bg = isSelected ? chalk__default.default.bgHex("#333333").white : chalk__default.default.white;
|
|
335
|
+
const padding = Math.max(0, leftColWidth - label.length);
|
|
336
|
+
console.log(bg(` ${label}${" ".repeat(padding)} ${value}`.padEnd(80)));
|
|
337
|
+
};
|
|
338
|
+
if (settingsView === "main") {
|
|
339
|
+
console.log("\n\n SETTINGS" + unsavedTag);
|
|
340
|
+
console.log(" " + "\u2500".repeat(60));
|
|
341
|
+
currentTreeItems.push({ type: "main_mode", index: index++ });
|
|
342
|
+
const isAuto = !draftConfig.strictMode;
|
|
343
|
+
renderRow("Routing mode", isAuto ? "auto" : "strict", index - 1 === settingsSelectionIdx);
|
|
344
|
+
currentTreeItems.push({ type: "main_model", index: index++ });
|
|
345
|
+
renderRow("Active model", isAuto ? chalk__default.default.gray("auto") : draftConfig.defaultModel || "none", index - 1 === settingsSelectionIdx);
|
|
346
|
+
currentTreeItems.push({ type: "main_keys", index: index++ });
|
|
347
|
+
const totalKeys = Object.values(draftConfig.keys || {}).reduce((acc, arr) => acc + arr.length, 0);
|
|
348
|
+
renderRow("API keys", `${totalKeys} active keys`, index - 1 === settingsSelectionIdx);
|
|
349
|
+
console.log("\n\n " + chalk__default.default.gray("Enter to toggle/select \xB7 S to save \xB7 X to discard"));
|
|
350
|
+
} else if (settingsView === "models") {
|
|
351
|
+
console.log("\n\n SELECT ACTIVE MODEL");
|
|
352
|
+
console.log(" " + "\u2500".repeat(110));
|
|
353
|
+
const modelList = [
|
|
354
|
+
{ provider: "openrouter", id: "inclusionai/ling-3.0-flash-vl:free", capa: "Vision \u2022 Free tier \u2022 Multimodal logic & QA" },
|
|
355
|
+
{ provider: "groq", id: "qwen/qwen3.8-27b", capa: "Vision tower \u2022 Ultra fast LPU \u2022 Native reasoning tags" },
|
|
356
|
+
{ provider: "groq", id: "openai/gpt-oss-120b", capa: "120B \u2022 Reasoning traces \u2022 Structured output \u2022 Tool use" },
|
|
357
|
+
{ provider: "groq", id: "groq/compound", capa: "Multi-tool agent \u2022 Integrated web search & code execution" },
|
|
358
|
+
{ provider: "nvidia", id: "nvidia/nemotron-3-super-120b-a12b", capa: "120B MoE \u2022 High volume agentic reasoning traces" },
|
|
359
|
+
{ provider: "nvidia", id: "nvidia/nemotron-3-ultra-550b-a55b", capa: "550B MoE \u2022 Massive enterprise IT & logic solver" },
|
|
360
|
+
{ provider: "gemini", id: "gemini-3.5-flash-lite", capa: "Multimodal \u2022 1M context \u2022 Low cost document parser" },
|
|
361
|
+
{ provider: "mistral", id: "devstral-latest", capa: "Discontinued \u2022 Software engineering agentic workflow specialist" },
|
|
362
|
+
{ provider: "mistral", id: "codestral-2508", capa: "256K context \u2022 Pure fill-in-the-middle code completion" }
|
|
363
|
+
];
|
|
364
|
+
for (const m of modelList) {
|
|
365
|
+
currentTreeItems.push({ type: "model", provider: m.provider, id: m.id, index: index++ });
|
|
366
|
+
const isSelected = index - 1 === settingsSelectionIdx;
|
|
367
|
+
const isSaved = draftConfig.defaultModel === m.id;
|
|
368
|
+
const hasKeys = draftConfig.keys && draftConfig.keys[m.provider] && draftConfig.keys[m.provider].length > 0;
|
|
369
|
+
const modelStr = m.id.padEnd(leftColWidth);
|
|
370
|
+
const provStr = m.provider.padEnd(10);
|
|
371
|
+
const keyWarning = hasKeys ? "" : chalk__default.default.red(" \u26A0\uFE0F NO KEYS ");
|
|
372
|
+
const capaStr = chalk__default.default.gray(`${m.capa}`);
|
|
373
|
+
let line = "";
|
|
374
|
+
if (isSelected) {
|
|
375
|
+
line = chalk__default.default.bgHex("#333333").white(` ${modelStr} `) + chalk__default.default.bgHex("#333333").gray(`${provStr}`) + (hasKeys ? "" : chalk__default.default.bgHex("#333333").red(" \u26A0\uFE0F NO KEYS ")) + chalk__default.default.bgHex("#333333").gray(`${m.capa}`);
|
|
376
|
+
console.log(chalk__default.default.bgHex("#333333")(line.padEnd(120)));
|
|
377
|
+
} else {
|
|
378
|
+
const idCol = isSaved ? chalk__default.default.blue(` ${modelStr} `) : chalk__default.default.bold.white(` ${modelStr} `);
|
|
379
|
+
console.log(`${idCol}${chalk__default.default.gray(provStr)}${keyWarning}${capaStr}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
console.log("\n\n " + chalk__default.default.gray("Enter to select \xB7 Esc to return"));
|
|
383
|
+
} else if (settingsView === "keys") {
|
|
384
|
+
console.log("\n\n API SECRETS");
|
|
385
|
+
console.log(" " + "\u2500".repeat(60));
|
|
386
|
+
const providers = ["openrouter", "nvidia", "mistral", "gemini", "groq"];
|
|
387
|
+
const capitalize = (s) => {
|
|
388
|
+
if (s === "openrouter") return "OpenRouter";
|
|
389
|
+
if (s === "nvidia") return "Nvidia NIM";
|
|
390
|
+
if (s === "mistral") return "Mistral AI";
|
|
391
|
+
if (s === "gemini") return "Google Gemini";
|
|
392
|
+
if (s === "groq") return "Groq";
|
|
393
|
+
return s;
|
|
394
|
+
};
|
|
395
|
+
const maskKey = (k) => k.length > 8 ? k.substring(0, 4) + "..." + k.slice(-4) : "***";
|
|
396
|
+
for (const p of providers) {
|
|
397
|
+
const keys = draftConfig.keys?.[p] || [];
|
|
398
|
+
currentTreeItems.push({ type: "secret_provider", provider: p, index: index++ });
|
|
399
|
+
const isSelected = index - 1 === settingsSelectionIdx;
|
|
400
|
+
renderRow(capitalize(p), `${keys.length} active keys`, isSelected);
|
|
401
|
+
if (expandedProviders.includes(p)) {
|
|
402
|
+
for (const key of keys) {
|
|
403
|
+
currentTreeItems.push({ type: "key", provider: p, keyString: key, index: index++ });
|
|
404
|
+
const keySelected = index - 1 === settingsSelectionIdx;
|
|
405
|
+
const bg = keySelected ? chalk__default.default.bgHex("#333333").gray : chalk__default.default.gray;
|
|
406
|
+
console.log(bg(` \u21B3 ${maskKey(key)}${keySelected ? chalk__default.default.red(" (Press D to delete)") : ""}`.padEnd(80)));
|
|
407
|
+
}
|
|
408
|
+
currentTreeItems.push({ type: "add", provider: p, index: index++ });
|
|
409
|
+
const addSelected = index - 1 === settingsSelectionIdx;
|
|
410
|
+
if (isInputMode && inputTarget === p) {
|
|
411
|
+
console.log(` \u21B3 ${chalk__default.default.cyan("Enter new key:")} ` + chalk__default.default.bgCyan.black(` ${inputBuffer}_ `));
|
|
412
|
+
} else {
|
|
413
|
+
const bg = addSelected ? chalk__default.default.bgHex("#333333").gray : chalk__default.default.gray;
|
|
414
|
+
console.log(bg(` \u21B3 [ Press Enter to Add ]`.padEnd(80)));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
console.log("\n\n " + chalk__default.default.gray("Enter to add/toggle \xB7 D to delete key \xB7 Esc to return \xB7 S to save"));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function formatCompact(num) {
|
|
422
|
+
if (num >= 1e6) return (num / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
|
|
423
|
+
if (num >= 1e3) return (num / 1e3).toFixed(1).replace(/\.0$/, "") + "K";
|
|
424
|
+
return num.toString();
|
|
425
|
+
}
|
|
426
|
+
async function renderUsage() {
|
|
427
|
+
console.log("\n\n USAGE & ANALYTICS");
|
|
428
|
+
let usageData = { providerUsage: {} };
|
|
429
|
+
try {
|
|
430
|
+
const res = await fetch(`http://localhost:${proxyPort}/v1/keymux/usage`);
|
|
431
|
+
if (res.ok) {
|
|
432
|
+
usageData = await res.json();
|
|
433
|
+
} else {
|
|
434
|
+
usageData = { providerUsage: readUsageData().providerUsage };
|
|
435
|
+
}
|
|
436
|
+
} catch (e) {
|
|
437
|
+
usageData = { providerUsage: readUsageData().providerUsage };
|
|
438
|
+
}
|
|
439
|
+
let liveStats = [];
|
|
440
|
+
let activeKey = "";
|
|
441
|
+
try {
|
|
442
|
+
const res = await fetch(`http://localhost:${proxyPort}/v1/keymux/stats`);
|
|
443
|
+
if (res.ok) {
|
|
444
|
+
liveStats = await res.json();
|
|
445
|
+
}
|
|
446
|
+
const lrRes = await fetch(`http://localhost:${proxyPort}/v1/keymux/lastRoute`);
|
|
447
|
+
if (lrRes.ok) {
|
|
448
|
+
const lr = await lrRes.json();
|
|
449
|
+
if (lr && lr.provider) activeKey = lr.key || "";
|
|
450
|
+
}
|
|
451
|
+
} catch (e) {
|
|
452
|
+
}
|
|
453
|
+
console.log(chalk__default.default.dim(` [ ${usageViewMode === "today" ? "Last 24 Hours (Today)" : "Current Session"} ] (Press Enter to toggle)
|
|
454
|
+
`));
|
|
455
|
+
const getTokens = (usage) => typeof usage === "number" ? 0 : usage?.tokens || 0;
|
|
456
|
+
const getReqs = (usage) => typeof usage === "number" ? usage : usage?.requests || 0;
|
|
457
|
+
const currentUsageData = usageViewMode === "today" ? usageData.today || usageData.providerUsage : usageData.session || usageData.providerUsage;
|
|
458
|
+
const orUsage = currentUsageData?.openrouter;
|
|
459
|
+
const nvUsage = currentUsageData?.nvidia;
|
|
460
|
+
const miUsage = currentUsageData?.mistral;
|
|
461
|
+
const geUsage = currentUsageData?.gemini;
|
|
462
|
+
const gqUsage = currentUsageData?.groq;
|
|
463
|
+
const orTokens = getTokens(orUsage);
|
|
464
|
+
const nvTokens = getTokens(nvUsage);
|
|
465
|
+
const miTokens = getTokens(miUsage);
|
|
466
|
+
const geTokens = getTokens(geUsage);
|
|
467
|
+
const gqTokens = getTokens(gqUsage);
|
|
468
|
+
const totalTokens = orTokens + nvTokens + miTokens + geTokens + gqTokens;
|
|
469
|
+
const orPct = totalTokens > 0 ? Math.round(orTokens / totalTokens * 100) : 0;
|
|
470
|
+
const nvPct = totalTokens > 0 ? Math.round(nvTokens / totalTokens * 100) : 0;
|
|
471
|
+
const miPct = totalTokens > 0 ? Math.round(miTokens / totalTokens * 100) : 0;
|
|
472
|
+
const gePct = totalTokens > 0 ? Math.round(geTokens / totalTokens * 100) : 0;
|
|
473
|
+
const gqPct = totalTokens > 0 ? Math.round(gqTokens / totalTokens * 100) : 0;
|
|
474
|
+
const leftColWidth = 35;
|
|
475
|
+
console.log(" PROVIDER DISTRIBUTION (TOKENS)");
|
|
476
|
+
console.log(" " + "\u2500".repeat(60));
|
|
477
|
+
const printRow = (label, value) => {
|
|
478
|
+
console.log(` ${chalk__default.default.white(label.padEnd(leftColWidth))} ${chalk__default.default.white(value)}`);
|
|
479
|
+
};
|
|
480
|
+
printRow("OpenRouter", `${formatCompact(orTokens)} Tokens (${orPct}%)`);
|
|
481
|
+
printRow("Nvidia NIM", `${formatCompact(nvTokens)} Tokens (${nvPct}%)`);
|
|
482
|
+
if (miTokens > 0 || getReqs(miUsage) > 0 || draftConfig.keys?.mistral?.length) {
|
|
483
|
+
printRow("Mistral AI", `${formatCompact(miTokens)} Tokens (${miPct}%)`);
|
|
484
|
+
}
|
|
485
|
+
if (geTokens > 0 || getReqs(geUsage) > 0 || draftConfig.keys?.gemini?.length) {
|
|
486
|
+
printRow("Google Gemini", `${formatCompact(geTokens)} Tokens (${gePct}%)`);
|
|
487
|
+
}
|
|
488
|
+
if (gqTokens > 0 || getReqs(gqUsage) > 0 || draftConfig.keys?.groq?.length) {
|
|
489
|
+
printRow("Groq", `${formatCompact(gqTokens)} Tokens (${gqPct}%)`);
|
|
490
|
+
}
|
|
491
|
+
console.log("\n TRAFFIC ROUTING (REQUESTS)");
|
|
492
|
+
console.log(" " + "\u2500".repeat(60));
|
|
493
|
+
if (liveStats.length === 0) {
|
|
494
|
+
console.log(chalk__default.default.dim(" No active keys found."));
|
|
495
|
+
} else {
|
|
496
|
+
for (const stat of liveStats) {
|
|
497
|
+
const keyStr = stat.key || "";
|
|
498
|
+
let masked = keyStr;
|
|
499
|
+
if (keyStr.length > 10) {
|
|
500
|
+
masked = `${keyStr.substring(0, 4)}...${keyStr.slice(-4)}`;
|
|
501
|
+
}
|
|
502
|
+
const reqs = stat.totalRequests || 0;
|
|
503
|
+
const errs = stat.totalErrors || 0;
|
|
504
|
+
let label = masked;
|
|
505
|
+
if (activeKey && keyStr && activeKey.endsWith(keyStr.slice(-4))) {
|
|
506
|
+
label += chalk__default.default.dim(" (Active)");
|
|
507
|
+
}
|
|
508
|
+
const val = `${reqs} reqs ${errs > 0 ? chalk__default.default.red(`[${errs} errs]`) : ""}`;
|
|
509
|
+
const rawLabel = label.replace(/\x1B\[\d+m/g, "");
|
|
510
|
+
const padding = Math.max(0, leftColWidth - rawLabel.length);
|
|
511
|
+
console.log(` ${chalk__default.default.white(label)}${" ".repeat(padding)} ${chalk__default.default.white(val.trim())}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
var lastFrameBuffer = "";
|
|
516
|
+
async function render() {
|
|
517
|
+
let frameBuffer = "";
|
|
518
|
+
const originalLog = console.log;
|
|
519
|
+
const originalWrite = process.stdout.write;
|
|
520
|
+
console.log = (...args2) => {
|
|
521
|
+
frameBuffer += args2.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ") + "\n";
|
|
522
|
+
};
|
|
523
|
+
process.stdout.write = ((chunk) => {
|
|
524
|
+
frameBuffer += chunk.toString();
|
|
525
|
+
return true;
|
|
526
|
+
});
|
|
527
|
+
try {
|
|
528
|
+
renderTabs();
|
|
529
|
+
const tab = TABS[currentTabIdx];
|
|
530
|
+
if (tab === "Stats") await renderStats();
|
|
531
|
+
else if (tab === "Status") await renderStatus();
|
|
532
|
+
else if (tab === "Usage") await renderUsage();
|
|
533
|
+
else if (tab === "Settings") renderSettings();
|
|
534
|
+
console.log("" + chalk__default.default.gray("Tab to switch tabs \xB7 q to quit"));
|
|
535
|
+
} finally {
|
|
536
|
+
console.log = originalLog;
|
|
537
|
+
process.stdout.write = originalWrite;
|
|
538
|
+
}
|
|
539
|
+
if (frameBuffer !== lastFrameBuffer) {
|
|
540
|
+
process.stdout.write("\x1B[2J\x1B[H" + frameBuffer);
|
|
541
|
+
lastFrameBuffer = frameBuffer;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async function runDashboard() {
|
|
545
|
+
await render();
|
|
546
|
+
if (process.stdin.isTTY && !isListening) {
|
|
547
|
+
isListening = true;
|
|
548
|
+
let isRendering = false;
|
|
549
|
+
setInterval(async () => {
|
|
550
|
+
if (!isInputMode && !isRendering) {
|
|
551
|
+
isRendering = true;
|
|
552
|
+
try {
|
|
553
|
+
await render();
|
|
554
|
+
} finally {
|
|
555
|
+
isRendering = false;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}, 2e3);
|
|
559
|
+
process.stdin.setRawMode(true);
|
|
560
|
+
process.stdin.resume();
|
|
561
|
+
process.stdin.setEncoding("utf8");
|
|
562
|
+
process.stdin.on("data", async (key) => {
|
|
563
|
+
if (key === "" || key === "q" && !isInputMode) {
|
|
564
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
565
|
+
process.exit();
|
|
566
|
+
}
|
|
567
|
+
if (key === "r" && !isInputMode) {
|
|
568
|
+
await render();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (isInputMode) {
|
|
572
|
+
if (key === "\r" || key === "") {
|
|
573
|
+
const val = inputBuffer.trim();
|
|
574
|
+
if (val) {
|
|
575
|
+
if (!draftConfig.keys) draftConfig.keys = { openrouter: [], nvidia: [], mistral: [], gemini: [], groq: [] };
|
|
576
|
+
if (!draftConfig.keys[inputTarget]) {
|
|
577
|
+
draftConfig.keys[inputTarget] = [];
|
|
578
|
+
}
|
|
579
|
+
if (!draftConfig.keys[inputTarget].includes(val)) {
|
|
580
|
+
draftConfig.keys[inputTarget].push(val);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
isInputMode = false;
|
|
584
|
+
await render();
|
|
585
|
+
} else if (key === "\x1B") {
|
|
586
|
+
isInputMode = false;
|
|
587
|
+
await render();
|
|
588
|
+
} else if (key === "\x7F" || key === "\b") {
|
|
589
|
+
inputBuffer = inputBuffer.slice(0, -1);
|
|
590
|
+
await render();
|
|
591
|
+
} else {
|
|
592
|
+
const cleanKey = key.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
593
|
+
if (cleanKey) {
|
|
594
|
+
inputBuffer += cleanKey;
|
|
595
|
+
if (inputBuffer.length > 500) {
|
|
596
|
+
inputBuffer = inputBuffer.slice(0, 500);
|
|
597
|
+
}
|
|
598
|
+
await render();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (key === " " || key === "\x1B[C" || key === "\x1BOC") {
|
|
604
|
+
currentTabIdx = (currentTabIdx + 1) % TABS.length;
|
|
605
|
+
isInputMode = false;
|
|
606
|
+
settingsView = "main";
|
|
607
|
+
settingsSelectionIdx = 0;
|
|
608
|
+
await render();
|
|
609
|
+
} else if (key === "\x1B[D" || key === "\x1BOD") {
|
|
610
|
+
currentTabIdx = (currentTabIdx - 1 + TABS.length) % TABS.length;
|
|
611
|
+
isInputMode = false;
|
|
612
|
+
settingsView = "main";
|
|
613
|
+
settingsSelectionIdx = 0;
|
|
614
|
+
await render();
|
|
615
|
+
} else if (TABS[currentTabIdx] === "Usage" && (key === "\r" || key === "" || key === " ")) {
|
|
616
|
+
usageViewMode = usageViewMode === "today" ? "session" : "today";
|
|
617
|
+
await render();
|
|
618
|
+
} else if (TABS[currentTabIdx] === "Settings") {
|
|
619
|
+
if (key === "\x1B" || key === "\x1B[27~") {
|
|
620
|
+
if (settingsView !== "main") {
|
|
621
|
+
settingsView = "main";
|
|
622
|
+
settingsSelectionIdx = 0;
|
|
623
|
+
await render();
|
|
624
|
+
}
|
|
625
|
+
} else if (key === "\x1B[A" || key === "\x1BOA") {
|
|
626
|
+
if (settingsSelectionIdx > 0) settingsSelectionIdx--;
|
|
627
|
+
await render();
|
|
628
|
+
} else if (key === "\x1B[B" || key === "\x1BOB") {
|
|
629
|
+
if (settingsSelectionIdx < currentTreeItems.length - 1) settingsSelectionIdx++;
|
|
630
|
+
await render();
|
|
631
|
+
} else if (key === "\r" || key === "" || key === " ") {
|
|
632
|
+
const selected = currentTreeItems[settingsSelectionIdx];
|
|
633
|
+
if (!selected) return;
|
|
634
|
+
if (selected.type === "main_mode") {
|
|
635
|
+
draftConfig.strictMode = !draftConfig.strictMode;
|
|
636
|
+
if (!draftConfig.strictMode) {
|
|
637
|
+
draftConfig.defaultModel = "";
|
|
638
|
+
draftConfig.defaultProvider = "";
|
|
639
|
+
}
|
|
640
|
+
await render();
|
|
641
|
+
} else if (selected.type === "main_model") {
|
|
642
|
+
settingsView = "models";
|
|
643
|
+
settingsSelectionIdx = 0;
|
|
644
|
+
await render();
|
|
645
|
+
} else if (selected.type === "main_keys") {
|
|
646
|
+
settingsView = "keys";
|
|
647
|
+
settingsSelectionIdx = 0;
|
|
648
|
+
await render();
|
|
649
|
+
} else if (selected.type === "model") {
|
|
650
|
+
draftConfig.defaultProvider = selected.provider;
|
|
651
|
+
draftConfig.defaultModel = selected.id;
|
|
652
|
+
draftConfig.strictMode = true;
|
|
653
|
+
settingsView = "main";
|
|
654
|
+
settingsSelectionIdx = 0;
|
|
655
|
+
await render();
|
|
656
|
+
} else if (selected.type === "secret_provider") {
|
|
657
|
+
if (expandedProviders.includes(selected.provider)) {
|
|
658
|
+
expandedProviders = expandedProviders.filter((p) => p !== selected.provider);
|
|
659
|
+
} else {
|
|
660
|
+
expandedProviders.push(selected.provider);
|
|
661
|
+
}
|
|
662
|
+
await render();
|
|
663
|
+
} else if (selected.type === "add") {
|
|
664
|
+
isInputMode = true;
|
|
665
|
+
inputTarget = selected.provider;
|
|
666
|
+
inputBuffer = "";
|
|
667
|
+
await render();
|
|
668
|
+
}
|
|
669
|
+
} else if (key.toLowerCase() === "m" && !isInputMode && settingsView === "main") {
|
|
670
|
+
draftConfig.strictMode = !draftConfig.strictMode;
|
|
671
|
+
if (!draftConfig.strictMode) {
|
|
672
|
+
draftConfig.defaultModel = "";
|
|
673
|
+
draftConfig.defaultProvider = "";
|
|
674
|
+
}
|
|
675
|
+
flashMessage = draftConfig.strictMode ? "\u{1F512} Switched to STRICT Mode" : "\u26A1 Switched to AUTO Mode";
|
|
676
|
+
await render();
|
|
677
|
+
} else if ((key.toLowerCase() === "d" || key === "\x7F" || key === "\b" || key === "\x1B[3~") && !isInputMode && settingsView === "keys") {
|
|
678
|
+
const selected = currentTreeItems[settingsSelectionIdx];
|
|
679
|
+
if (selected && selected.type === "key") {
|
|
680
|
+
const providerKeys = draftConfig.keys[selected.provider];
|
|
681
|
+
if (providerKeys) {
|
|
682
|
+
const idx = providerKeys.indexOf(selected.keyString);
|
|
683
|
+
if (idx > -1) {
|
|
684
|
+
providerKeys.splice(idx, 1);
|
|
685
|
+
flashMessage = `\u{1F5D1}\uFE0F Deleted key for ${selected.provider}`;
|
|
686
|
+
if (settingsSelectionIdx >= currentTreeItems.length - 1) {
|
|
687
|
+
settingsSelectionIdx = Math.max(0, currentTreeItems.length - 2);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
await render();
|
|
693
|
+
} else if (key.toLowerCase() === "s") {
|
|
694
|
+
config = JSON.parse(JSON.stringify(draftConfig));
|
|
695
|
+
saveConfig(config);
|
|
696
|
+
await triggerReload();
|
|
697
|
+
flashMessage = "Changes saved successfully!";
|
|
698
|
+
await render();
|
|
699
|
+
} else if (key.toLowerCase() === "x") {
|
|
700
|
+
draftConfig = JSON.parse(JSON.stringify(config));
|
|
701
|
+
settingsView = "main";
|
|
702
|
+
settingsSelectionIdx = 0;
|
|
703
|
+
flashMessage = "Changes discarded.";
|
|
704
|
+
await render();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function getPidFile() {
|
|
711
|
+
const dir = path2__namespace.join(os__namespace.homedir(), ".keymux");
|
|
712
|
+
if (!fs2__namespace.existsSync(dir)) fs2__namespace.mkdirSync(dir, { recursive: true });
|
|
713
|
+
return path2__namespace.join(dir, "proxy.pid");
|
|
714
|
+
}
|
|
715
|
+
async function startDaemon(port) {
|
|
716
|
+
return new Promise((resolve, reject) => {
|
|
717
|
+
const server = net__namespace.createServer();
|
|
718
|
+
server.once("error", (err) => {
|
|
719
|
+
if (err.code === "EADDRINUSE") {
|
|
720
|
+
console.log(`Keymux proxy already running on port ${port}`);
|
|
721
|
+
resolve();
|
|
722
|
+
} else {
|
|
723
|
+
reject(err);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
server.once("listening", () => {
|
|
727
|
+
server.close(async () => {
|
|
728
|
+
try {
|
|
729
|
+
let proxyScriptPath;
|
|
730
|
+
if (typeof __dirname !== "undefined") {
|
|
731
|
+
proxyScriptPath = path2__namespace.join(__dirname, "proxy", "index.js").replace(/\\/g, "\\\\");
|
|
732
|
+
} else {
|
|
733
|
+
const url = await import('url');
|
|
734
|
+
const dirname = url.fileURLToPath(new URL(".", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))));
|
|
735
|
+
proxyScriptPath = path2__namespace.join(dirname, "proxy", "index.js").replace(/\\/g, "\\\\");
|
|
736
|
+
}
|
|
737
|
+
const child = child_process.spawn("node", ["-e", `import('${proxyScriptPath}').then(m => m.startProxyServer({ port: ${port} })).catch(console.error)`], {
|
|
738
|
+
detached: true,
|
|
739
|
+
stdio: "ignore"
|
|
740
|
+
});
|
|
741
|
+
if (child.pid) {
|
|
742
|
+
fs2__namespace.writeFileSync(getPidFile(), child.pid.toString());
|
|
743
|
+
}
|
|
744
|
+
child.unref();
|
|
745
|
+
console.log(`Keymux proxy started on port ${port}`);
|
|
746
|
+
resolve();
|
|
747
|
+
} catch (err) {
|
|
748
|
+
reject(err);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
server.listen(port);
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
async function stopDaemon(port) {
|
|
756
|
+
const pidFile = getPidFile();
|
|
757
|
+
let stopped = false;
|
|
758
|
+
if (fs2__namespace.existsSync(pidFile)) {
|
|
759
|
+
try {
|
|
760
|
+
const pid = parseInt(fs2__namespace.readFileSync(pidFile, "utf8").trim(), 10);
|
|
761
|
+
if (pid && !isNaN(pid)) {
|
|
762
|
+
try {
|
|
763
|
+
const cmd = child_process.execSync(`ps -p ${pid} -o command=`).toString();
|
|
764
|
+
if (!cmd.includes("node") && !cmd.includes("keymux")) {
|
|
765
|
+
console.log(`Process ${pid} is not a Keymux proxy. Aborting.`);
|
|
766
|
+
} else {
|
|
767
|
+
process.kill(pid, "SIGTERM");
|
|
768
|
+
console.log(`Keymux proxy (PID ${pid}) stopped gracefully.`);
|
|
769
|
+
stopped = true;
|
|
770
|
+
}
|
|
771
|
+
} catch (e) {
|
|
772
|
+
console.log(`Failed to verify process ${pid}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
} catch (e) {
|
|
776
|
+
if (e.code === "ESRCH") {
|
|
777
|
+
console.log("Proxy is not running (stale PID file).");
|
|
778
|
+
} else {
|
|
779
|
+
console.log("Failed to stop proxy via PID: " + e.message);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
try {
|
|
783
|
+
fs2__namespace.unlinkSync(pidFile);
|
|
784
|
+
} catch (e) {
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
const pkillCmd = `pkill -f "dist/proxy/index.js.*port: ${port}"`;
|
|
789
|
+
child_process.execSync(pkillCmd, { stdio: "ignore" });
|
|
790
|
+
if (!stopped) {
|
|
791
|
+
console.log(`Orphaned Keymux proxy on port ${port} forcefully stopped.`);
|
|
792
|
+
stopped = true;
|
|
793
|
+
}
|
|
794
|
+
} catch (e) {
|
|
795
|
+
}
|
|
796
|
+
if (!stopped) {
|
|
797
|
+
console.log("Keymux proxy is not running.");
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/cli.ts
|
|
802
|
+
var args = process.argv.slice(2);
|
|
803
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
804
|
+
async function runBootAnimation() {
|
|
805
|
+
const chalk2 = (await import('chalk')).default;
|
|
806
|
+
const logo = [
|
|
807
|
+
" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
808
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D",
|
|
809
|
+
" \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2554\u255D",
|
|
810
|
+
" \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2557",
|
|
811
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u255D \u2588\u2588\u2557",
|
|
812
|
+
" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D"
|
|
813
|
+
];
|
|
814
|
+
console.clear();
|
|
815
|
+
console.log("\n");
|
|
816
|
+
for (const line of logo) {
|
|
817
|
+
console.log(chalk2.cyan(line));
|
|
818
|
+
await sleep(30);
|
|
819
|
+
}
|
|
820
|
+
console.log("\n" + chalk2.green(" \u2714 Keymux Gateway initialized successfully."));
|
|
821
|
+
}
|
|
822
|
+
var helpText = `Usage: keymux [command] [options]
|
|
823
|
+
|
|
824
|
+
Commands:
|
|
825
|
+
-d, --dashboard Run the interactive dashboard
|
|
826
|
+
proxy, start Start the Keymux proxy daemon
|
|
827
|
+
stop Stop the Keymux proxy daemon
|
|
828
|
+
|
|
829
|
+
Options for proxy/start:
|
|
830
|
+
--port <port> Specify the port to run the proxy on (default: 3002)
|
|
831
|
+
|
|
832
|
+
Examples:
|
|
833
|
+
keymux -d
|
|
834
|
+
keymux start
|
|
835
|
+
keymux proxy --port 8080
|
|
836
|
+
keymux stop`;
|
|
837
|
+
async function main() {
|
|
838
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
839
|
+
console.log(helpText);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
if (args.length === 0 || args.includes("-d") || args.includes("--dashboard")) {
|
|
843
|
+
runDashboard().catch(console.error);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
const command = args[0];
|
|
847
|
+
if (command === "proxy" || command === "start") {
|
|
848
|
+
let port = 3002;
|
|
849
|
+
const portIndex = args.indexOf("--port");
|
|
850
|
+
if (portIndex !== -1 && args.length > portIndex + 1) {
|
|
851
|
+
port = parseInt(args[portIndex + 1] ?? "3002", 10);
|
|
852
|
+
}
|
|
853
|
+
await runBootAnimation();
|
|
854
|
+
await startDaemon(port);
|
|
855
|
+
} else if (command === "stop") {
|
|
856
|
+
let port = 3002;
|
|
857
|
+
const portIndex = args.indexOf("--port");
|
|
858
|
+
if (portIndex !== -1 && args.length > portIndex + 1) {
|
|
859
|
+
port = parseInt(args[portIndex + 1] ?? "3002", 10);
|
|
860
|
+
}
|
|
861
|
+
await stopDaemon(port);
|
|
862
|
+
} else {
|
|
863
|
+
console.log(`Unknown command: ${command}`);
|
|
864
|
+
console.log(helpText);
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
main().catch(console.error);
|
|
869
|
+
//# sourceMappingURL=cli.cjs.map
|
|
870
|
+
//# sourceMappingURL=cli.cjs.map
|