kentutai-app 1.0.9 → 1.1.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/cli/cli.js +0 -49
- package/cli/src/cli/api/client.js +547 -0
- package/cli/src/cli/menus/apiKeys.js +233 -0
- package/cli/src/cli/menus/cliTools.js +618 -0
- package/cli/src/cli/menus/combos.js +477 -0
- package/cli/src/cli/menus/providers.js +845 -0
- package/cli/src/cli/menus/settings.js +207 -0
- package/cli/src/cli/terminalUI.js +121 -0
- package/cli/src/cli/tray/autostart.js +306 -0
- package/cli/src/cli/tray/icon.ico +0 -0
- package/cli/src/cli/tray/icon.png +0 -0
- package/cli/src/cli/tray/tray.js +322 -0
- package/cli/src/cli/tray/tray.ps1 +79 -0
- package/cli/src/cli/tray/trayWin.js +89 -0
- package/cli/src/cli/utils/clipboard.js +30 -0
- package/cli/src/cli/utils/display.js +42 -0
- package/cli/src/cli/utils/endpoint.js +32 -0
- package/cli/src/cli/utils/format.js +125 -0
- package/cli/src/cli/utils/input.js +130 -0
- package/cli/src/cli/utils/menuHelper.js +156 -0
- package/cli/src/cli/utils/modelSelector.js +136 -0
- package/package.json +1 -1
package/cli/cli.js
CHANGED
|
@@ -7,7 +7,6 @@ const os = require("os");
|
|
|
7
7
|
|
|
8
8
|
const pkg = require("./package.json");
|
|
9
9
|
|
|
10
|
-
// ─── Configuration ────────────────────────────────────────────────────
|
|
11
10
|
const DEFAULT_PORT = 20123;
|
|
12
11
|
const DEFAULT_HOST = "0.0.0.0";
|
|
13
12
|
|
|
@@ -17,7 +16,6 @@ let noBrowser = false;
|
|
|
17
16
|
let showLog = false;
|
|
18
17
|
let trayMode = false;
|
|
19
18
|
|
|
20
|
-
// Parse arguments
|
|
21
19
|
const args = process.argv.slice(2);
|
|
22
20
|
for (let i = 0; i < args.length; i++) {
|
|
23
21
|
if (args[i] === "--port" || args[i] === "-p") {
|
|
@@ -52,7 +50,6 @@ Options:
|
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
// ─── Standalone server path ───────────────────────────────────────────
|
|
56
53
|
const standaloneDir = path.join(__dirname, "app");
|
|
57
54
|
const serverPath = path.join(standaloneDir, "server.js");
|
|
58
55
|
|
|
@@ -62,8 +59,6 @@ if (!fs.existsSync(serverPath)) {
|
|
|
62
59
|
process.exit(1);
|
|
63
60
|
}
|
|
64
61
|
|
|
65
|
-
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
66
|
-
|
|
67
62
|
function openBrowser(url) {
|
|
68
63
|
const cmd = process.platform === "darwin" ? "open"
|
|
69
64
|
: process.platform === "win32" ? "start"
|
|
@@ -94,8 +89,6 @@ function killProcessOnPort(port) {
|
|
|
94
89
|
});
|
|
95
90
|
}
|
|
96
91
|
|
|
97
|
-
// ─── Start Server ─────────────────────────────────────────────────────
|
|
98
|
-
|
|
99
92
|
function startServer() {
|
|
100
93
|
const displayHost = host === DEFAULT_HOST ? "localhost" : host;
|
|
101
94
|
const url = `http://${displayHost}:${port}/dashboard`;
|
|
@@ -118,7 +111,6 @@ function startServer() {
|
|
|
118
111
|
process.exit(code || 0);
|
|
119
112
|
});
|
|
120
113
|
|
|
121
|
-
// Cleanup on exit
|
|
122
114
|
const cleanup = () => {
|
|
123
115
|
try { server.kill(); } catch {}
|
|
124
116
|
};
|
|
@@ -149,14 +141,11 @@ async function checkForUpdate() {
|
|
|
149
141
|
} catch { return null; }
|
|
150
142
|
}
|
|
151
143
|
|
|
152
|
-
// ─── Main ─────────────────────────────────────────────────────────────
|
|
153
|
-
|
|
154
144
|
async function main() {
|
|
155
145
|
await killProcessOnPort(port);
|
|
156
146
|
|
|
157
147
|
const { server, url } = startServer();
|
|
158
148
|
|
|
159
|
-
// Tray mode: no interactive menu
|
|
160
149
|
if (trayMode) {
|
|
161
150
|
console.log(`\n ${pkg.name} v${pkg.version}`);
|
|
162
151
|
console.log(` Server: ${url}`);
|
|
@@ -164,14 +153,11 @@ async function main() {
|
|
|
164
153
|
return;
|
|
165
154
|
}
|
|
166
155
|
|
|
167
|
-
// Wait for server to be ready
|
|
168
156
|
const { selectMenu, pause } = require("./src/cli/utils/input");
|
|
169
157
|
const { clearScreen } = require("./src/cli/utils/display");
|
|
170
158
|
|
|
171
|
-
// Wait a bit for server startup
|
|
172
159
|
await new Promise(r => setTimeout(r, 3000));
|
|
173
160
|
|
|
174
|
-
// If --no-browser, just show info and wait
|
|
175
161
|
if (noBrowser) {
|
|
176
162
|
console.log(`\n ${pkg.name} v${pkg.version}`);
|
|
177
163
|
console.log(` Server: ${url}`);
|
|
@@ -208,12 +194,10 @@ async function main() {
|
|
|
208
194
|
`\ud83d\ude80 Server: ${serverUrl}`
|
|
209
195
|
);
|
|
210
196
|
|
|
211
|
-
// Handle choice
|
|
212
197
|
const adjustedChoice = latestVersion ? choice : choice + 1;
|
|
213
198
|
|
|
214
199
|
if (adjustedChoice === 0 && latestVersion) {
|
|
215
200
|
console.log("\n Updating...");
|
|
216
|
-
const { execSync } = require("child_process");
|
|
217
201
|
try {
|
|
218
202
|
execSync(`npm install -g ${pkg.name}`, { stdio: "inherit" });
|
|
219
203
|
console.log("\n Update complete! Please restart the application.");
|
|
@@ -251,39 +235,6 @@ async function main() {
|
|
|
251
235
|
}
|
|
252
236
|
}
|
|
253
237
|
}
|
|
254
|
-
} else if (adjustedChoice === 1 || (adjustedChoice === 0 && !latestVersion)) {
|
|
255
|
-
// Web UI
|
|
256
|
-
openBrowser(url);
|
|
257
|
-
await pause("\nPress Enter to go back to menu...");
|
|
258
|
-
} else if (adjustedChoice === 2) {
|
|
259
|
-
// Terminal UI
|
|
260
|
-
try {
|
|
261
|
-
const { startTerminalUI } = require("./src/cli/terminalUI");
|
|
262
|
-
await startTerminalUI(port);
|
|
263
|
-
} catch (err) {
|
|
264
|
-
console.error("\n Terminal UI error:", err.message);
|
|
265
|
-
await pause("\nPress Enter to continue...");
|
|
266
|
-
}
|
|
267
|
-
} else if (adjustedChoice === 3) {
|
|
268
|
-
// Hide to Tray
|
|
269
|
-
clearScreen();
|
|
270
|
-
console.log(`\n \ud83d\udd14 ${pkg.name} is running in background`);
|
|
271
|
-
console.log(` Server: ${serverUrl}`);
|
|
272
|
-
console.log(` PID: ${server.pid}`);
|
|
273
|
-
console.log(`\n Press Ctrl+C to stop.\n`);
|
|
274
|
-
// Keep process alive, remove stdin listeners
|
|
275
|
-
process.stdin.removeAllListeners("keypress");
|
|
276
|
-
if (process.stdin.isTTY) try { process.stdin.setRawMode(false); } catch {}
|
|
277
|
-
return;
|
|
278
|
-
} else {
|
|
279
|
-
// Exit
|
|
280
|
-
console.log("\nExiting...");
|
|
281
|
-
server.kill();
|
|
282
|
-
setTimeout(() => process.exit(0), 100);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
238
|
|
|
288
239
|
main().catch((err) => {
|
|
289
240
|
console.error("Fatal:", err.message);
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const https = require("https");
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const { machineIdSync } = require("node-machine-id");
|
|
8
|
+
|
|
9
|
+
// Default configuration
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
host: "localhost",
|
|
12
|
+
port: 20123,
|
|
13
|
+
protocol: "http:",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const CLI_TOKEN_HEADER = "x-9r-cli-token";
|
|
17
|
+
const CLI_TOKEN_SALT = "kt-cli-auth";
|
|
18
|
+
const APP_NAME = "kentutai";
|
|
19
|
+
|
|
20
|
+
function getDataDir() {
|
|
21
|
+
if (process.env.DATA_DIR) return process.env.DATA_DIR;
|
|
22
|
+
if (process.platform === "win32") {
|
|
23
|
+
return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), APP_NAME);
|
|
24
|
+
}
|
|
25
|
+
return path.join(os.homedir(), `.${APP_NAME}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MACHINE_ID_FILE = path.join(getDataDir(), "machine-id");
|
|
29
|
+
const AUTH_DIR = path.join(getDataDir(), "auth");
|
|
30
|
+
const CLI_SECRET_FILE = path.join(AUTH_DIR, "cli-secret");
|
|
31
|
+
|
|
32
|
+
let config = { ...DEFAULT_CONFIG };
|
|
33
|
+
let cachedCliToken = null;
|
|
34
|
+
let cachedCliSecret = null;
|
|
35
|
+
|
|
36
|
+
// Read raw machineId from shared file (written by server) → guarantees token match
|
|
37
|
+
function loadRawMachineId() {
|
|
38
|
+
try {
|
|
39
|
+
const raw = fs.readFileSync(MACHINE_ID_FILE, "utf8").trim();
|
|
40
|
+
if (raw) return raw;
|
|
41
|
+
} catch {}
|
|
42
|
+
try { return machineIdSync(); } catch { return ""; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Random secret shared with server via file → token unpredictable from machineId alone.
|
|
46
|
+
function loadCliSecret() {
|
|
47
|
+
if (cachedCliSecret) return cachedCliSecret;
|
|
48
|
+
try {
|
|
49
|
+
cachedCliSecret = fs.readFileSync(CLI_SECRET_FILE, "utf8").trim();
|
|
50
|
+
if (cachedCliSecret) return cachedCliSecret;
|
|
51
|
+
} catch {}
|
|
52
|
+
cachedCliSecret = crypto.randomBytes(32).toString("hex");
|
|
53
|
+
try {
|
|
54
|
+
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
|
55
|
+
fs.writeFileSync(CLI_SECRET_FILE, cachedCliSecret, { mode: 0o600 });
|
|
56
|
+
} catch {}
|
|
57
|
+
return cachedCliSecret;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getCliToken() {
|
|
61
|
+
if (cachedCliToken !== null) return cachedCliToken;
|
|
62
|
+
const raw = loadRawMachineId();
|
|
63
|
+
const secret = loadCliSecret();
|
|
64
|
+
cachedCliToken = raw ? crypto.createHash("sha256").update(raw + CLI_TOKEN_SALT + secret).digest("hex").substring(0, 16) : "";
|
|
65
|
+
return cachedCliToken;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Configure API client
|
|
70
|
+
* @param {Object} options - Configuration options
|
|
71
|
+
* @param {string} options.host - API host
|
|
72
|
+
* @param {number} options.port - API port
|
|
73
|
+
* @param {string} options.protocol - Protocol (http: or https:)
|
|
74
|
+
*/
|
|
75
|
+
function configure(options = {}) {
|
|
76
|
+
config = { ...config, ...options };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Make HTTP request to API
|
|
81
|
+
* @param {string} method - HTTP method
|
|
82
|
+
* @param {string} path - API path
|
|
83
|
+
* @param {Object} body - Request body (optional)
|
|
84
|
+
* @returns {Promise<Object>} Response with { success, data/error }
|
|
85
|
+
*/
|
|
86
|
+
function makeRequest(method, path, body = null) {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
const httpModule = config.protocol === "https:" ? https : http;
|
|
89
|
+
|
|
90
|
+
const options = {
|
|
91
|
+
hostname: config.host,
|
|
92
|
+
port: config.port,
|
|
93
|
+
path: path,
|
|
94
|
+
method: method,
|
|
95
|
+
headers: {
|
|
96
|
+
"Content-Type": "application/json",
|
|
97
|
+
[CLI_TOKEN_HEADER]: getCliToken(),
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Add Content-Length for POST/PUT requests
|
|
102
|
+
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
103
|
+
const bodyString = JSON.stringify(body);
|
|
104
|
+
options.headers["Content-Length"] = Buffer.byteLength(bodyString);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const req = httpModule.request(options, (res) => {
|
|
108
|
+
let data = "";
|
|
109
|
+
|
|
110
|
+
res.on("data", (chunk) => {
|
|
111
|
+
data += chunk;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
res.on("end", () => {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = data ? JSON.parse(data) : {};
|
|
117
|
+
|
|
118
|
+
// Check if response indicates error
|
|
119
|
+
if (res.statusCode >= 400 || parsed.error) {
|
|
120
|
+
resolve({
|
|
121
|
+
success: false,
|
|
122
|
+
error: parsed.error || `HTTP ${res.statusCode}`,
|
|
123
|
+
statusCode: res.statusCode,
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
resolve({
|
|
127
|
+
success: true,
|
|
128
|
+
data: parsed,
|
|
129
|
+
statusCode: res.statusCode,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
resolve({
|
|
134
|
+
success: false,
|
|
135
|
+
error: `Failed to parse response: ${err.message}`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
req.on("error", (err) => {
|
|
142
|
+
resolve({
|
|
143
|
+
success: false,
|
|
144
|
+
error: `Network error: ${err.message}`,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
req.on("timeout", () => {
|
|
149
|
+
req.destroy();
|
|
150
|
+
resolve({
|
|
151
|
+
success: false,
|
|
152
|
+
error: "Request timeout",
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Set timeout (30 seconds)
|
|
157
|
+
req.setTimeout(30000);
|
|
158
|
+
|
|
159
|
+
// Write body if present
|
|
160
|
+
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
161
|
+
req.write(JSON.stringify(body));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
req.end();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// PROVIDERS API
|
|
170
|
+
// ============================================================================
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get all providers
|
|
174
|
+
* @returns {Promise<Object>} { success, data: { connections } }
|
|
175
|
+
*/
|
|
176
|
+
async function getProviders() {
|
|
177
|
+
return makeRequest("GET", "/api/providers");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get provider by ID
|
|
182
|
+
* @param {string} id - Provider ID
|
|
183
|
+
* @returns {Promise<Object>} { success, data: { connection } }
|
|
184
|
+
*/
|
|
185
|
+
async function getProviderById(id) {
|
|
186
|
+
return makeRequest("GET", `/api/providers/${id}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Test provider connection
|
|
191
|
+
* @param {string} id - Provider ID
|
|
192
|
+
* @returns {Promise<Object>} { success, data: { valid, error } }
|
|
193
|
+
*/
|
|
194
|
+
async function testProvider(id) {
|
|
195
|
+
return makeRequest("POST", `/api/providers/${id}/test`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Delete provider
|
|
200
|
+
* @param {string} id - Provider ID
|
|
201
|
+
* @returns {Promise<Object>} { success, data: { message } }
|
|
202
|
+
*/
|
|
203
|
+
async function deleteProvider(id) {
|
|
204
|
+
return makeRequest("DELETE", `/api/providers/${id}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get provider models
|
|
209
|
+
* @param {string} id - Provider ID
|
|
210
|
+
* @returns {Promise<Object>} { success, data: { provider, connectionId, models } }
|
|
211
|
+
*/
|
|
212
|
+
async function getProviderModels(id) {
|
|
213
|
+
return makeRequest("GET", `/api/providers/${id}/models`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// OAUTH API
|
|
218
|
+
// ============================================================================
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get OAuth authorization URL
|
|
222
|
+
* @param {string} provider - Provider ID
|
|
223
|
+
* @returns {Promise<Object>} { success, data: { authUrl, codeVerifier, state, redirectUri } }
|
|
224
|
+
*/
|
|
225
|
+
async function getOAuthAuthUrl(provider) {
|
|
226
|
+
// Codex requires fixed port 1455 and path /auth/callback
|
|
227
|
+
const redirectUri = provider === "codex"
|
|
228
|
+
? "http://localhost:1455/auth/callback"
|
|
229
|
+
: "http://localhost:20123/callback";
|
|
230
|
+
return makeRequest("GET", `/api/oauth/${provider}/authorize?redirect_uri=${encodeURIComponent(redirectUri)}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Exchange OAuth authorization code for token
|
|
235
|
+
* @param {string} provider - Provider ID
|
|
236
|
+
* @param {Object} data - { code, redirectUri, codeVerifier, state }
|
|
237
|
+
* @returns {Promise<Object>} { success, data }
|
|
238
|
+
*/
|
|
239
|
+
async function exchangeOAuthCode(provider, data) {
|
|
240
|
+
return makeRequest("POST", `/api/oauth/${provider}/exchange`, data);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get OAuth device code
|
|
245
|
+
* @param {string} provider - Provider ID
|
|
246
|
+
* @returns {Promise<Object>} { success, data: { device_code, user_code, verification_uri, verification_uri_complete, codeVerifier, extraData } }
|
|
247
|
+
*/
|
|
248
|
+
async function getOAuthDeviceCode(provider) {
|
|
249
|
+
return makeRequest("GET", `/api/oauth/${provider}/device-code`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Poll OAuth token using device code
|
|
254
|
+
* @param {string} provider - Provider ID
|
|
255
|
+
* @param {Object} data - { deviceCode, codeVerifier, extraData }
|
|
256
|
+
* @returns {Promise<Object>} { success, data: { pending } }
|
|
257
|
+
*/
|
|
258
|
+
async function pollOAuthToken(provider, data) {
|
|
259
|
+
return makeRequest("POST", `/api/oauth/${provider}/poll`, data);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create API key provider connection
|
|
264
|
+
* @param {Object} data - { provider, name, apiKey }
|
|
265
|
+
* @returns {Promise<Object>} { success, data }
|
|
266
|
+
*/
|
|
267
|
+
async function createApiKeyProvider(data) {
|
|
268
|
+
return makeRequest("POST", "/api/providers", data);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Update provider connection
|
|
273
|
+
* @param {string} id - Connection ID
|
|
274
|
+
* @param {Object} data - { name, priority, defaultModel, isActive }
|
|
275
|
+
* @returns {Promise<Object>} { success, data: { connection } }
|
|
276
|
+
*/
|
|
277
|
+
async function updateConnection(id, data) {
|
|
278
|
+
return makeRequest("PUT", `/api/providers/${id}`, data);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// API KEYS API
|
|
283
|
+
// ============================================================================
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get all API keys
|
|
287
|
+
* @returns {Promise<Object>} { success, data: { keys } }
|
|
288
|
+
*/
|
|
289
|
+
async function getApiKeys() {
|
|
290
|
+
return makeRequest("GET", "/api/keys");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Create new API key
|
|
295
|
+
* @param {string} name - Key name
|
|
296
|
+
* @returns {Promise<Object>} { success, data: { key, name, id, machineId } }
|
|
297
|
+
*/
|
|
298
|
+
async function createApiKey(name) {
|
|
299
|
+
return makeRequest("POST", "/api/keys", { name });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Delete API key
|
|
304
|
+
* @param {string} id - Key ID
|
|
305
|
+
* @returns {Promise<Object>} { success, data: { success } }
|
|
306
|
+
*/
|
|
307
|
+
async function deleteApiKey(id) {
|
|
308
|
+
return makeRequest("DELETE", `/api/keys/${id}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// COMBOS API
|
|
313
|
+
// ============================================================================
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get all combos
|
|
317
|
+
* @returns {Promise<Object>} { success, data: { combos } }
|
|
318
|
+
*/
|
|
319
|
+
async function getCombos() {
|
|
320
|
+
return makeRequest("GET", "/api/combos");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get combo by ID
|
|
325
|
+
* @param {string} id - Combo ID
|
|
326
|
+
* @returns {Promise<Object>} { success, data: combo }
|
|
327
|
+
*/
|
|
328
|
+
async function getComboById(id) {
|
|
329
|
+
return makeRequest("GET", `/api/combos/${id}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Create new combo
|
|
334
|
+
* @param {Object} data - Combo data { name, models }
|
|
335
|
+
* @returns {Promise<Object>} { success, data: combo }
|
|
336
|
+
*/
|
|
337
|
+
async function createCombo(data) {
|
|
338
|
+
return makeRequest("POST", "/api/combos", data);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Update combo
|
|
343
|
+
* @param {string} id - Combo ID
|
|
344
|
+
* @param {Object} data - Update data { name?, models? }
|
|
345
|
+
* @returns {Promise<Object>} { success, data: combo }
|
|
346
|
+
*/
|
|
347
|
+
async function updateCombo(id, data) {
|
|
348
|
+
return makeRequest("PUT", `/api/combos/${id}`, data);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Delete combo
|
|
353
|
+
* @param {string} id - Combo ID
|
|
354
|
+
* @returns {Promise<Object>} { success, data: { success } }
|
|
355
|
+
*/
|
|
356
|
+
async function deleteCombo(id) {
|
|
357
|
+
return makeRequest("DELETE", `/api/combos/${id}`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ============================================================================
|
|
361
|
+
// CLI TOOLS API
|
|
362
|
+
// ============================================================================
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get CLI tool settings
|
|
366
|
+
* @param {string} tool - Tool name: claude | codex | droid | openclaw
|
|
367
|
+
* @returns {Promise<Object>} { success, data: { installed, has9Router, ... } }
|
|
368
|
+
*/
|
|
369
|
+
async function getCliToolSettings(tool) {
|
|
370
|
+
return makeRequest("GET", `/api/cli-tools/${tool}-settings`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Apply CLI tool settings (POST)
|
|
375
|
+
* @param {string} tool - Tool name: claude | codex | droid | openclaw
|
|
376
|
+
* @param {Object} body - Payload depends on tool
|
|
377
|
+
* @returns {Promise<Object>} { success, data }
|
|
378
|
+
*/
|
|
379
|
+
async function applyCliToolSettings(tool, body) {
|
|
380
|
+
return makeRequest("POST", `/api/cli-tools/${tool}-settings`, body);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Reset CLI tool settings (DELETE)
|
|
385
|
+
* @param {string} tool - Tool name: claude | codex | droid | openclaw
|
|
386
|
+
* @returns {Promise<Object>} { success, data }
|
|
387
|
+
*/
|
|
388
|
+
async function resetCliToolSettings(tool) {
|
|
389
|
+
return makeRequest("DELETE", `/api/cli-tools/${tool}-settings`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ============================================================================
|
|
393
|
+
// SETTINGS API
|
|
394
|
+
// ============================================================================
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Get settings
|
|
398
|
+
* @returns {Promise<Object>} { success, data: settings }
|
|
399
|
+
*/
|
|
400
|
+
async function getSettings() {
|
|
401
|
+
return makeRequest("GET", "/api/settings");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Update settings
|
|
406
|
+
* @param {Object} data - Settings data
|
|
407
|
+
* @returns {Promise<Object>} { success, data: settings }
|
|
408
|
+
*/
|
|
409
|
+
async function updateSettings(data) {
|
|
410
|
+
return makeRequest("PATCH", "/api/settings", data);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// MODELS API
|
|
415
|
+
// ============================================================================
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get all models (internal API)
|
|
419
|
+
* @returns {Promise<Object>} { success, data: { models } }
|
|
420
|
+
*/
|
|
421
|
+
async function getModels() {
|
|
422
|
+
return makeRequest("GET", "/api/models");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Get available models from active providers + combos (OpenAI compatible)
|
|
427
|
+
* @returns {Promise<Object>} { success, data: { object, data: [...models] } }
|
|
428
|
+
*/
|
|
429
|
+
async function getAvailableModels() {
|
|
430
|
+
return makeRequest("GET", "/v1/models");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ============================================================================
|
|
434
|
+
// PROVIDER NODES API (custom providers)
|
|
435
|
+
// ============================================================================
|
|
436
|
+
|
|
437
|
+
async function getProviderNodes() {
|
|
438
|
+
return makeRequest("GET", "/api/provider-nodes");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function createProviderNode(data) {
|
|
442
|
+
return makeRequest("POST", "/api/provider-nodes", data);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function updateProviderNode(id, data) {
|
|
446
|
+
return makeRequest("PUT", `/api/provider-nodes/${id}`, data);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function deleteProviderNode(id) {
|
|
450
|
+
return makeRequest("DELETE", `/api/provider-nodes/${id}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function validateProviderNode(data) {
|
|
454
|
+
return makeRequest("POST", "/api/provider-nodes/validate", data);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ============================================================================
|
|
458
|
+
// TUNNEL API
|
|
459
|
+
// ============================================================================
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Get tunnel status
|
|
463
|
+
* @returns {Promise<Object>} { success, data: { enabled, tunnelUrl, shortId, running } }
|
|
464
|
+
*/
|
|
465
|
+
async function getTunnelStatus() {
|
|
466
|
+
return makeRequest("GET", "/api/tunnel/status");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Enable tunnel
|
|
471
|
+
* @returns {Promise<Object>} { success, data: { tunnelUrl, shortId } }
|
|
472
|
+
*/
|
|
473
|
+
async function enableTunnel() {
|
|
474
|
+
return makeRequest("POST", "/api/tunnel/enable");
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Disable tunnel
|
|
479
|
+
* @returns {Promise<Object>} { success, data: { success } }
|
|
480
|
+
*/
|
|
481
|
+
async function disableTunnel() {
|
|
482
|
+
return makeRequest("POST", "/api/tunnel/disable");
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ============================================================================
|
|
486
|
+
// EXPORTS
|
|
487
|
+
// ============================================================================
|
|
488
|
+
|
|
489
|
+
module.exports = {
|
|
490
|
+
configure,
|
|
491
|
+
|
|
492
|
+
// Providers
|
|
493
|
+
getProviders,
|
|
494
|
+
getProviderById,
|
|
495
|
+
testProvider,
|
|
496
|
+
deleteProvider,
|
|
497
|
+
getProviderModels,
|
|
498
|
+
|
|
499
|
+
// Connection aliases
|
|
500
|
+
testConnection: testProvider,
|
|
501
|
+
deleteConnection: deleteProvider,
|
|
502
|
+
updateConnection,
|
|
503
|
+
|
|
504
|
+
// OAuth
|
|
505
|
+
getOAuthAuthUrl,
|
|
506
|
+
exchangeOAuthCode,
|
|
507
|
+
getOAuthDeviceCode,
|
|
508
|
+
pollOAuthToken,
|
|
509
|
+
createApiKeyProvider,
|
|
510
|
+
|
|
511
|
+
// API Keys
|
|
512
|
+
getApiKeys,
|
|
513
|
+
createApiKey,
|
|
514
|
+
deleteApiKey,
|
|
515
|
+
|
|
516
|
+
// Combos
|
|
517
|
+
getCombos,
|
|
518
|
+
getComboById,
|
|
519
|
+
createCombo,
|
|
520
|
+
updateCombo,
|
|
521
|
+
deleteCombo,
|
|
522
|
+
|
|
523
|
+
// CLI Tools
|
|
524
|
+
getCliToolSettings,
|
|
525
|
+
applyCliToolSettings,
|
|
526
|
+
resetCliToolSettings,
|
|
527
|
+
|
|
528
|
+
// Settings
|
|
529
|
+
getSettings,
|
|
530
|
+
updateSettings,
|
|
531
|
+
|
|
532
|
+
// Tunnel
|
|
533
|
+
getTunnelStatus,
|
|
534
|
+
enableTunnel,
|
|
535
|
+
disableTunnel,
|
|
536
|
+
|
|
537
|
+
// Models
|
|
538
|
+
getModels,
|
|
539
|
+
getAvailableModels,
|
|
540
|
+
|
|
541
|
+
// Provider Nodes (custom providers)
|
|
542
|
+
getProviderNodes,
|
|
543
|
+
createProviderNode,
|
|
544
|
+
updateProviderNode,
|
|
545
|
+
deleteProviderNode,
|
|
546
|
+
validateProviderNode,
|
|
547
|
+
};
|