skalpel 2.0.14 → 2.0.15
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/dist/cli/index.js +471 -245
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/proxy-runner.js +531 -77
- package/dist/cli/proxy-runner.js.map +1 -1
- package/dist/index.cjs +1526 -1053
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1517 -1037
- package/dist/index.js.map +1 -1
- package/dist/proxy/index.cjs +567 -94
- package/dist/proxy/index.cjs.map +1 -1
- package/dist/proxy/index.d.cts +1 -1
- package/dist/proxy/index.d.ts +1 -1
- package/dist/proxy/index.js +560 -80
- package/dist/proxy/index.js.map +1 -1
- package/package.json +4 -2
package/dist/cli/index.js
CHANGED
|
@@ -1,4 +1,109 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/proxy/dispatcher.ts
|
|
8
|
+
import { Agent } from "undici";
|
|
9
|
+
var skalpelDispatcher;
|
|
10
|
+
var init_dispatcher = __esm({
|
|
11
|
+
"src/proxy/dispatcher.ts"() {
|
|
12
|
+
"use strict";
|
|
13
|
+
skalpelDispatcher = new Agent({
|
|
14
|
+
keepAliveTimeout: 1e4,
|
|
15
|
+
keepAliveMaxTimeout: 6e4,
|
|
16
|
+
connections: 100,
|
|
17
|
+
pipelining: 1
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// src/proxy/envelope.ts
|
|
23
|
+
var init_envelope = __esm({
|
|
24
|
+
"src/proxy/envelope.ts"() {
|
|
25
|
+
"use strict";
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// src/proxy/recovery.ts
|
|
30
|
+
import { createHash } from "crypto";
|
|
31
|
+
var MUTEX_MAX_ENTRIES, LruMutexMap, refreshMutex;
|
|
32
|
+
var init_recovery = __esm({
|
|
33
|
+
"src/proxy/recovery.ts"() {
|
|
34
|
+
"use strict";
|
|
35
|
+
MUTEX_MAX_ENTRIES = 1024;
|
|
36
|
+
LruMutexMap = class extends Map {
|
|
37
|
+
set(key, value) {
|
|
38
|
+
if (this.has(key)) {
|
|
39
|
+
super.delete(key);
|
|
40
|
+
} else if (this.size >= MUTEX_MAX_ENTRIES) {
|
|
41
|
+
const oldest = this.keys().next().value;
|
|
42
|
+
if (oldest !== void 0) super.delete(oldest);
|
|
43
|
+
}
|
|
44
|
+
return super.set(key, value);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
refreshMutex = new LruMutexMap();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// src/proxy/fetch-error.ts
|
|
52
|
+
var init_fetch_error = __esm({
|
|
53
|
+
"src/proxy/fetch-error.ts"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// src/proxy/streaming.ts
|
|
59
|
+
var HOP_BY_HOP, STRIP_HEADERS;
|
|
60
|
+
var init_streaming = __esm({
|
|
61
|
+
"src/proxy/streaming.ts"() {
|
|
62
|
+
"use strict";
|
|
63
|
+
init_dispatcher();
|
|
64
|
+
init_handler();
|
|
65
|
+
init_envelope();
|
|
66
|
+
init_recovery();
|
|
67
|
+
init_fetch_error();
|
|
68
|
+
HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
69
|
+
"connection",
|
|
70
|
+
"keep-alive",
|
|
71
|
+
"proxy-authenticate",
|
|
72
|
+
"proxy-authorization",
|
|
73
|
+
"te",
|
|
74
|
+
"trailer",
|
|
75
|
+
"transfer-encoding",
|
|
76
|
+
"upgrade"
|
|
77
|
+
]);
|
|
78
|
+
STRIP_HEADERS = /* @__PURE__ */ new Set([
|
|
79
|
+
...HOP_BY_HOP,
|
|
80
|
+
"content-encoding",
|
|
81
|
+
"content-length"
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// src/proxy/ws-client.ts
|
|
87
|
+
import { EventEmitter } from "events";
|
|
88
|
+
import WebSocket2 from "ws";
|
|
89
|
+
var init_ws_client = __esm({
|
|
90
|
+
"src/proxy/ws-client.ts"() {
|
|
91
|
+
"use strict";
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// src/proxy/handler.ts
|
|
96
|
+
var init_handler = __esm({
|
|
97
|
+
"src/proxy/handler.ts"() {
|
|
98
|
+
"use strict";
|
|
99
|
+
init_streaming();
|
|
100
|
+
init_dispatcher();
|
|
101
|
+
init_envelope();
|
|
102
|
+
init_ws_client();
|
|
103
|
+
init_recovery();
|
|
104
|
+
init_fetch_error();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
2
107
|
|
|
3
108
|
// src/cli/index.ts
|
|
4
109
|
import { Command } from "commander";
|
|
@@ -207,6 +312,8 @@ ${envContent}`);
|
|
|
207
312
|
import * as fs4 from "fs";
|
|
208
313
|
import * as path4 from "path";
|
|
209
314
|
import * as os2 from "os";
|
|
315
|
+
import net from "net";
|
|
316
|
+
import WebSocket from "ws";
|
|
210
317
|
|
|
211
318
|
// src/cli/agents/detect.ts
|
|
212
319
|
import { execSync } from "child_process";
|
|
@@ -362,6 +469,70 @@ function checkCodexConfig(config) {
|
|
|
362
469
|
message: `missing TOML: ${missing.map((m) => m.split("\n")[0]).join("; ")}`
|
|
363
470
|
};
|
|
364
471
|
}
|
|
472
|
+
async function checkCodexWebSocket(config) {
|
|
473
|
+
const tcpOk = await new Promise((resolve2) => {
|
|
474
|
+
const sock = net.connect({ host: "127.0.0.1", port: config.openaiPort, timeout: 1e3 });
|
|
475
|
+
const done = (ok) => {
|
|
476
|
+
sock.removeAllListeners();
|
|
477
|
+
try {
|
|
478
|
+
sock.destroy();
|
|
479
|
+
} catch {
|
|
480
|
+
}
|
|
481
|
+
resolve2(ok);
|
|
482
|
+
};
|
|
483
|
+
sock.once("connect", () => done(true));
|
|
484
|
+
sock.once("error", () => done(false));
|
|
485
|
+
sock.once("timeout", () => done(false));
|
|
486
|
+
});
|
|
487
|
+
if (!tcpOk) {
|
|
488
|
+
return {
|
|
489
|
+
name: "Codex WebSocket",
|
|
490
|
+
status: "skipped",
|
|
491
|
+
message: "WebSocket: SKIPPED (proxy not running)"
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return new Promise((resolve2) => {
|
|
495
|
+
const url = `ws://localhost:${config.openaiPort}/v1/responses`;
|
|
496
|
+
let settled = false;
|
|
497
|
+
const ws = new WebSocket(url, ["skalpel-codex-v1"]);
|
|
498
|
+
const settle = (result) => {
|
|
499
|
+
if (settled) return;
|
|
500
|
+
settled = true;
|
|
501
|
+
try {
|
|
502
|
+
ws.close();
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
resolve2(result);
|
|
506
|
+
};
|
|
507
|
+
const timeout = setTimeout(() => {
|
|
508
|
+
settle({
|
|
509
|
+
name: "Codex WebSocket",
|
|
510
|
+
status: "fail",
|
|
511
|
+
message: "WebSocket: FAIL handshake timeout after 5s"
|
|
512
|
+
});
|
|
513
|
+
}, 5e3);
|
|
514
|
+
ws.once("open", () => {
|
|
515
|
+
clearTimeout(timeout);
|
|
516
|
+
settle({ name: "Codex WebSocket", status: "ok", message: "WebSocket: OK" });
|
|
517
|
+
});
|
|
518
|
+
ws.once("error", (err) => {
|
|
519
|
+
clearTimeout(timeout);
|
|
520
|
+
settle({
|
|
521
|
+
name: "Codex WebSocket",
|
|
522
|
+
status: "fail",
|
|
523
|
+
message: `WebSocket: FAIL ${err.message}`
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
ws.once("unexpected-response", (_req, res) => {
|
|
527
|
+
clearTimeout(timeout);
|
|
528
|
+
settle({
|
|
529
|
+
name: "Codex WebSocket",
|
|
530
|
+
status: "fail",
|
|
531
|
+
message: `WebSocket: FAIL unexpected HTTP ${res.statusCode}`
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
}
|
|
365
536
|
async function checkCodexProxyProbe(config) {
|
|
366
537
|
const url = `http://localhost:${config.openaiPort}/v1/responses`;
|
|
367
538
|
try {
|
|
@@ -508,7 +679,8 @@ async function runDoctor() {
|
|
|
508
679
|
const config = { openaiPort };
|
|
509
680
|
checks.push(checkCodexConfig(config));
|
|
510
681
|
checks.push(await checkCodexProxyProbe(config));
|
|
511
|
-
|
|
682
|
+
checks.push(await checkCodexWebSocket(config));
|
|
683
|
+
const icons = { ok: "+", warn: "!", fail: "x", error: "x", skipped: "-" };
|
|
512
684
|
for (const check of checks) {
|
|
513
685
|
const icon = icons[check.status];
|
|
514
686
|
print2(` [${icon}] ${check.name}: ${check.message}`);
|
|
@@ -711,7 +883,7 @@ async function runReplay(filePaths) {
|
|
|
711
883
|
|
|
712
884
|
// src/cli/start.ts
|
|
713
885
|
import { spawn } from "child_process";
|
|
714
|
-
import
|
|
886
|
+
import path12 from "path";
|
|
715
887
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
716
888
|
|
|
717
889
|
// src/proxy/config.ts
|
|
@@ -848,6 +1020,20 @@ function removePid(pidFile) {
|
|
|
848
1020
|
}
|
|
849
1021
|
}
|
|
850
1022
|
|
|
1023
|
+
// src/proxy/health-check.ts
|
|
1024
|
+
async function isProxyAlive(port, timeoutMs = 2e3) {
|
|
1025
|
+
const controller = new AbortController();
|
|
1026
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1027
|
+
try {
|
|
1028
|
+
const res = await fetch(`http://localhost:${port}/health`, { signal: controller.signal });
|
|
1029
|
+
return res.ok;
|
|
1030
|
+
} catch {
|
|
1031
|
+
return false;
|
|
1032
|
+
} finally {
|
|
1033
|
+
clearTimeout(timer);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
851
1037
|
// src/cli/service/install.ts
|
|
852
1038
|
import fs8 from "fs";
|
|
853
1039
|
import path9 from "path";
|
|
@@ -1190,212 +1376,32 @@ function uninstallService() {
|
|
|
1190
1376
|
}
|
|
1191
1377
|
}
|
|
1192
1378
|
|
|
1193
|
-
// src/cli/start.ts
|
|
1194
|
-
function print5(msg) {
|
|
1195
|
-
console.log(msg);
|
|
1196
|
-
}
|
|
1197
|
-
async function runStart() {
|
|
1198
|
-
const config = loadConfig();
|
|
1199
|
-
if (!config.apiKey) {
|
|
1200
|
-
print5(' Error: No API key configured. Run "skalpel init" or set SKALPEL_API_KEY.');
|
|
1201
|
-
process.exit(1);
|
|
1202
|
-
}
|
|
1203
|
-
const existingPid = readPid(config.pidFile);
|
|
1204
|
-
if (existingPid !== null) {
|
|
1205
|
-
print5(` Proxy is already running (pid=${existingPid}).`);
|
|
1206
|
-
return;
|
|
1207
|
-
}
|
|
1208
|
-
if (isServiceInstalled()) {
|
|
1209
|
-
startService();
|
|
1210
|
-
print5(` Skalpel proxy started via system service on ports ${config.anthropicPort}, ${config.openaiPort}, and ${config.cursorPort}`);
|
|
1211
|
-
return;
|
|
1212
|
-
}
|
|
1213
|
-
const dirname = path10.dirname(fileURLToPath2(import.meta.url));
|
|
1214
|
-
const runnerScript = path10.resolve(dirname, "proxy-runner.js");
|
|
1215
|
-
const child = spawn(process.execPath, [runnerScript], {
|
|
1216
|
-
detached: true,
|
|
1217
|
-
stdio: "ignore"
|
|
1218
|
-
});
|
|
1219
|
-
child.unref();
|
|
1220
|
-
print5(` Skalpel proxy started on ports ${config.anthropicPort}, ${config.openaiPort}, and ${config.cursorPort}`);
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
// src/proxy/server.ts
|
|
1224
|
-
import http from "http";
|
|
1225
|
-
|
|
1226
|
-
// src/proxy/dispatcher.ts
|
|
1227
|
-
import { Agent } from "undici";
|
|
1228
|
-
var skalpelDispatcher = new Agent({
|
|
1229
|
-
keepAliveTimeout: 1e4,
|
|
1230
|
-
keepAliveMaxTimeout: 6e4,
|
|
1231
|
-
connections: 100,
|
|
1232
|
-
pipelining: 1
|
|
1233
|
-
});
|
|
1234
|
-
|
|
1235
|
-
// src/proxy/recovery.ts
|
|
1236
|
-
import { createHash } from "crypto";
|
|
1237
|
-
var MUTEX_MAX_ENTRIES = 1024;
|
|
1238
|
-
var LruMutexMap = class extends Map {
|
|
1239
|
-
set(key, value) {
|
|
1240
|
-
if (this.has(key)) {
|
|
1241
|
-
super.delete(key);
|
|
1242
|
-
} else if (this.size >= MUTEX_MAX_ENTRIES) {
|
|
1243
|
-
const oldest = this.keys().next().value;
|
|
1244
|
-
if (oldest !== void 0) super.delete(oldest);
|
|
1245
|
-
}
|
|
1246
|
-
return super.set(key, value);
|
|
1247
|
-
}
|
|
1248
|
-
};
|
|
1249
|
-
var refreshMutex = new LruMutexMap();
|
|
1250
|
-
|
|
1251
|
-
// src/proxy/streaming.ts
|
|
1252
|
-
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
1253
|
-
"connection",
|
|
1254
|
-
"keep-alive",
|
|
1255
|
-
"proxy-authenticate",
|
|
1256
|
-
"proxy-authorization",
|
|
1257
|
-
"te",
|
|
1258
|
-
"trailer",
|
|
1259
|
-
"transfer-encoding",
|
|
1260
|
-
"upgrade"
|
|
1261
|
-
]);
|
|
1262
|
-
var STRIP_HEADERS = /* @__PURE__ */ new Set([
|
|
1263
|
-
...HOP_BY_HOP,
|
|
1264
|
-
"content-encoding",
|
|
1265
|
-
"content-length"
|
|
1266
|
-
]);
|
|
1267
|
-
|
|
1268
|
-
// src/proxy/logger.ts
|
|
1269
|
-
import fs9 from "fs";
|
|
1270
|
-
import path11 from "path";
|
|
1271
|
-
var MAX_SIZE = 5 * 1024 * 1024;
|
|
1272
|
-
|
|
1273
|
-
// src/proxy/server.ts
|
|
1274
|
-
var proxyStartTime = 0;
|
|
1275
|
-
function stopProxy(config) {
|
|
1276
|
-
const pid = readPid(config.pidFile);
|
|
1277
|
-
if (pid === null) return false;
|
|
1278
|
-
try {
|
|
1279
|
-
process.kill(pid, "SIGTERM");
|
|
1280
|
-
} catch {
|
|
1281
|
-
}
|
|
1282
|
-
removePid(config.pidFile);
|
|
1283
|
-
return true;
|
|
1284
|
-
}
|
|
1285
|
-
function getProxyStatus(config) {
|
|
1286
|
-
const pid = readPid(config.pidFile);
|
|
1287
|
-
return {
|
|
1288
|
-
running: pid !== null,
|
|
1289
|
-
pid,
|
|
1290
|
-
uptime: proxyStartTime > 0 ? Date.now() - proxyStartTime : 0,
|
|
1291
|
-
anthropicPort: config.anthropicPort,
|
|
1292
|
-
openaiPort: config.openaiPort,
|
|
1293
|
-
cursorPort: config.cursorPort
|
|
1294
|
-
};
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// src/cli/stop.ts
|
|
1298
|
-
function print6(msg) {
|
|
1299
|
-
console.log(msg);
|
|
1300
|
-
}
|
|
1301
|
-
async function runStop() {
|
|
1302
|
-
const config = loadConfig();
|
|
1303
|
-
if (isServiceInstalled()) {
|
|
1304
|
-
stopService();
|
|
1305
|
-
}
|
|
1306
|
-
const stopped = stopProxy(config);
|
|
1307
|
-
if (stopped) {
|
|
1308
|
-
print6(" Skalpel proxy stopped.");
|
|
1309
|
-
} else {
|
|
1310
|
-
print6(" Proxy is not running.");
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
// src/cli/status.ts
|
|
1315
|
-
function print7(msg) {
|
|
1316
|
-
console.log(msg);
|
|
1317
|
-
}
|
|
1318
|
-
async function runStatus() {
|
|
1319
|
-
const config = loadConfig();
|
|
1320
|
-
const status = getProxyStatus(config);
|
|
1321
|
-
print7("");
|
|
1322
|
-
print7(" Skalpel Proxy Status");
|
|
1323
|
-
print7(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1324
|
-
print7(` Status: ${status.running ? "running" : "stopped"}`);
|
|
1325
|
-
if (status.pid !== null) {
|
|
1326
|
-
print7(` PID: ${status.pid}`);
|
|
1327
|
-
}
|
|
1328
|
-
print7(` Anthropic: port ${status.anthropicPort}`);
|
|
1329
|
-
print7(` OpenAI: port ${status.openaiPort}`);
|
|
1330
|
-
print7(` Cursor: port ${status.cursorPort}`);
|
|
1331
|
-
print7(` Config: ${config.configFile}`);
|
|
1332
|
-
print7("");
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
// src/cli/logs.ts
|
|
1336
|
-
import fs10 from "fs";
|
|
1337
|
-
function print8(msg) {
|
|
1338
|
-
console.log(msg);
|
|
1339
|
-
}
|
|
1340
|
-
async function runLogs(options) {
|
|
1341
|
-
const config = loadConfig();
|
|
1342
|
-
const logFile = config.logFile;
|
|
1343
|
-
const lineCount = parseInt(options.lines ?? "50", 10);
|
|
1344
|
-
if (!fs10.existsSync(logFile)) {
|
|
1345
|
-
print8(` No log file found at ${logFile}`);
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
const content = fs10.readFileSync(logFile, "utf-8");
|
|
1349
|
-
const lines = content.trimEnd().split("\n");
|
|
1350
|
-
const tail = lines.slice(-lineCount);
|
|
1351
|
-
for (const line of tail) {
|
|
1352
|
-
print8(line);
|
|
1353
|
-
}
|
|
1354
|
-
if (options.follow) {
|
|
1355
|
-
let position = fs10.statSync(logFile).size;
|
|
1356
|
-
fs10.watchFile(logFile, { interval: 500 }, () => {
|
|
1357
|
-
try {
|
|
1358
|
-
const stat = fs10.statSync(logFile);
|
|
1359
|
-
if (stat.size > position) {
|
|
1360
|
-
const fd = fs10.openSync(logFile, "r");
|
|
1361
|
-
const buf = Buffer.alloc(stat.size - position);
|
|
1362
|
-
fs10.readSync(fd, buf, 0, buf.length, position);
|
|
1363
|
-
fs10.closeSync(fd);
|
|
1364
|
-
process.stdout.write(buf.toString("utf-8"));
|
|
1365
|
-
position = stat.size;
|
|
1366
|
-
}
|
|
1367
|
-
} catch {
|
|
1368
|
-
}
|
|
1369
|
-
});
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
1379
|
// src/cli/agents/configure.ts
|
|
1374
|
-
import
|
|
1375
|
-
import
|
|
1380
|
+
import fs9 from "fs";
|
|
1381
|
+
import path10 from "path";
|
|
1376
1382
|
import os7 from "os";
|
|
1377
1383
|
var CURSOR_API_BASE_URL_KEY = "openai.apiBaseUrl";
|
|
1378
1384
|
var DIRECT_MODE_BASE_URL = "https://api.skalpel.ai";
|
|
1379
1385
|
var CODEX_DIRECT_PROVIDER_ID = "skalpel";
|
|
1380
1386
|
var CODEX_PROXY_PROVIDER_ID = "skalpel-proxy";
|
|
1381
1387
|
function ensureDir(dir) {
|
|
1382
|
-
|
|
1388
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1383
1389
|
}
|
|
1384
1390
|
function createBackup(filePath) {
|
|
1385
|
-
if (
|
|
1386
|
-
|
|
1391
|
+
if (fs9.existsSync(filePath)) {
|
|
1392
|
+
fs9.copyFileSync(filePath, `${filePath}.skalpel-backup`);
|
|
1387
1393
|
}
|
|
1388
1394
|
}
|
|
1389
1395
|
function readJsonFile(filePath) {
|
|
1390
1396
|
try {
|
|
1391
|
-
return JSON.parse(
|
|
1397
|
+
return JSON.parse(fs9.readFileSync(filePath, "utf-8"));
|
|
1392
1398
|
} catch {
|
|
1393
1399
|
return null;
|
|
1394
1400
|
}
|
|
1395
1401
|
}
|
|
1396
1402
|
function configureClaudeCode(agent, proxyConfig, direct = false) {
|
|
1397
|
-
const configPath = agent.configPath ??
|
|
1398
|
-
const configDir =
|
|
1403
|
+
const configPath = agent.configPath ?? path10.join(os7.homedir(), ".claude", "settings.json");
|
|
1404
|
+
const configDir = path10.dirname(configPath);
|
|
1399
1405
|
ensureDir(configDir);
|
|
1400
1406
|
createBackup(configPath);
|
|
1401
1407
|
const config = readJsonFile(configPath) ?? {};
|
|
@@ -1410,11 +1416,11 @@ function configureClaudeCode(agent, proxyConfig, direct = false) {
|
|
|
1410
1416
|
env.ANTHROPIC_BASE_URL = `http://localhost:${proxyConfig.anthropicPort}`;
|
|
1411
1417
|
delete env.ANTHROPIC_CUSTOM_HEADERS;
|
|
1412
1418
|
}
|
|
1413
|
-
|
|
1419
|
+
fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1414
1420
|
}
|
|
1415
1421
|
function readTomlFile(filePath) {
|
|
1416
1422
|
try {
|
|
1417
|
-
return
|
|
1423
|
+
return fs9.readFileSync(filePath, "utf-8");
|
|
1418
1424
|
} catch {
|
|
1419
1425
|
return "";
|
|
1420
1426
|
}
|
|
@@ -1503,9 +1509,9 @@ function removeCodexProxyProvider(content) {
|
|
|
1503
1509
|
return before.length > 0 && rest.length > 0 ? before + "\n" + rest : before + rest;
|
|
1504
1510
|
}
|
|
1505
1511
|
function configureCodex(agent, proxyConfig, direct = false) {
|
|
1506
|
-
const configDir = process.platform === "win32" ?
|
|
1507
|
-
const configPath = agent.configPath ??
|
|
1508
|
-
ensureDir(
|
|
1512
|
+
const configDir = process.platform === "win32" ? path10.join(os7.homedir(), "AppData", "Roaming", "codex") : path10.join(os7.homedir(), ".codex");
|
|
1513
|
+
const configPath = agent.configPath ?? path10.join(configDir, "config.toml");
|
|
1514
|
+
ensureDir(path10.dirname(configPath));
|
|
1509
1515
|
createBackup(configPath);
|
|
1510
1516
|
let content = readTomlFile(configPath);
|
|
1511
1517
|
if (direct) {
|
|
@@ -1518,15 +1524,15 @@ function configureCodex(agent, proxyConfig, direct = false) {
|
|
|
1518
1524
|
content = upsertCodexProxyProvider(content, proxyConfig.openaiPort);
|
|
1519
1525
|
content = removeCodexDirectProvider(content);
|
|
1520
1526
|
}
|
|
1521
|
-
|
|
1527
|
+
fs9.writeFileSync(configPath, content);
|
|
1522
1528
|
}
|
|
1523
1529
|
function getCursorConfigDir() {
|
|
1524
1530
|
if (process.platform === "darwin") {
|
|
1525
|
-
return
|
|
1531
|
+
return path10.join(os7.homedir(), "Library", "Application Support", "Cursor", "User");
|
|
1526
1532
|
} else if (process.platform === "win32") {
|
|
1527
|
-
return
|
|
1533
|
+
return path10.join(process.env.APPDATA ?? path10.join(os7.homedir(), "AppData", "Roaming"), "Cursor", "User");
|
|
1528
1534
|
}
|
|
1529
|
-
return
|
|
1535
|
+
return path10.join(os7.homedir(), ".config", "Cursor", "User");
|
|
1530
1536
|
}
|
|
1531
1537
|
function configureCursor(agent, proxyConfig, direct = false) {
|
|
1532
1538
|
if (direct) {
|
|
@@ -1534,8 +1540,8 @@ function configureCursor(agent, proxyConfig, direct = false) {
|
|
|
1534
1540
|
return;
|
|
1535
1541
|
}
|
|
1536
1542
|
const configDir = getCursorConfigDir();
|
|
1537
|
-
const configPath = agent.configPath ??
|
|
1538
|
-
ensureDir(
|
|
1543
|
+
const configPath = agent.configPath ?? path10.join(configDir, "settings.json");
|
|
1544
|
+
ensureDir(path10.dirname(configPath));
|
|
1539
1545
|
createBackup(configPath);
|
|
1540
1546
|
const config = readJsonFile(configPath) ?? {};
|
|
1541
1547
|
const existingUrl = config[CURSOR_API_BASE_URL_KEY];
|
|
@@ -1544,7 +1550,7 @@ function configureCursor(agent, proxyConfig, direct = false) {
|
|
|
1544
1550
|
saveConfig(proxyConfig);
|
|
1545
1551
|
}
|
|
1546
1552
|
config[CURSOR_API_BASE_URL_KEY] = `http://localhost:${proxyConfig.cursorPort}`;
|
|
1547
|
-
|
|
1553
|
+
fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1548
1554
|
}
|
|
1549
1555
|
function configureAgent(agent, proxyConfig, direct = false) {
|
|
1550
1556
|
switch (agent.name) {
|
|
@@ -1560,8 +1566,8 @@ function configureAgent(agent, proxyConfig, direct = false) {
|
|
|
1560
1566
|
}
|
|
1561
1567
|
}
|
|
1562
1568
|
function unconfigureClaudeCode(agent) {
|
|
1563
|
-
const configPath = agent.configPath ??
|
|
1564
|
-
if (!
|
|
1569
|
+
const configPath = agent.configPath ?? path10.join(os7.homedir(), ".claude", "settings.json");
|
|
1570
|
+
if (!fs9.existsSync(configPath)) return;
|
|
1565
1571
|
const config = readJsonFile(configPath);
|
|
1566
1572
|
if (config === null) {
|
|
1567
1573
|
console.warn(` [!] Could not parse ${configPath} \u2014 skipping to avoid data loss. Remove ANTHROPIC_BASE_URL manually if needed.`);
|
|
@@ -1575,42 +1581,42 @@ function unconfigureClaudeCode(agent) {
|
|
|
1575
1581
|
delete config.env;
|
|
1576
1582
|
}
|
|
1577
1583
|
}
|
|
1578
|
-
|
|
1584
|
+
fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1579
1585
|
const backupPath = `${configPath}.skalpel-backup`;
|
|
1580
|
-
if (
|
|
1581
|
-
|
|
1586
|
+
if (fs9.existsSync(backupPath)) {
|
|
1587
|
+
fs9.unlinkSync(backupPath);
|
|
1582
1588
|
}
|
|
1583
1589
|
}
|
|
1584
1590
|
function unconfigureCodex(agent) {
|
|
1585
|
-
const configDir = process.platform === "win32" ?
|
|
1586
|
-
const configPath = agent.configPath ??
|
|
1587
|
-
if (
|
|
1591
|
+
const configDir = process.platform === "win32" ? path10.join(os7.homedir(), "AppData", "Roaming", "codex") : path10.join(os7.homedir(), ".codex");
|
|
1592
|
+
const configPath = agent.configPath ?? path10.join(configDir, "config.toml");
|
|
1593
|
+
if (fs9.existsSync(configPath)) {
|
|
1588
1594
|
let content = readTomlFile(configPath);
|
|
1589
1595
|
content = removeTomlKey(content, "openai_base_url");
|
|
1590
1596
|
content = removeTomlKey(content, "model_provider");
|
|
1591
1597
|
content = removeCodexDirectProvider(content);
|
|
1592
1598
|
content = removeCodexProxyProvider(content);
|
|
1593
|
-
|
|
1599
|
+
fs9.writeFileSync(configPath, content);
|
|
1594
1600
|
}
|
|
1595
1601
|
const backupPath = `${configPath}.skalpel-backup`;
|
|
1596
|
-
if (
|
|
1597
|
-
|
|
1602
|
+
if (fs9.existsSync(backupPath)) {
|
|
1603
|
+
fs9.unlinkSync(backupPath);
|
|
1598
1604
|
}
|
|
1599
1605
|
}
|
|
1600
1606
|
function unconfigureCursor(agent) {
|
|
1601
1607
|
const configDir = getCursorConfigDir();
|
|
1602
|
-
const configPath = agent.configPath ??
|
|
1603
|
-
if (!
|
|
1608
|
+
const configPath = agent.configPath ?? path10.join(configDir, "settings.json");
|
|
1609
|
+
if (!fs9.existsSync(configPath)) return;
|
|
1604
1610
|
const config = readJsonFile(configPath);
|
|
1605
1611
|
if (config === null) {
|
|
1606
1612
|
console.warn(` [!] Could not parse ${configPath} \u2014 skipping to avoid data loss. Remove ${CURSOR_API_BASE_URL_KEY} manually if needed.`);
|
|
1607
1613
|
return;
|
|
1608
1614
|
}
|
|
1609
1615
|
delete config[CURSOR_API_BASE_URL_KEY];
|
|
1610
|
-
|
|
1616
|
+
fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1611
1617
|
const backupPath = `${configPath}.skalpel-backup`;
|
|
1612
|
-
if (
|
|
1613
|
-
|
|
1618
|
+
if (fs9.existsSync(backupPath)) {
|
|
1619
|
+
fs9.unlinkSync(backupPath);
|
|
1614
1620
|
}
|
|
1615
1621
|
}
|
|
1616
1622
|
function unconfigureAgent(agent) {
|
|
@@ -1628,8 +1634,8 @@ function unconfigureAgent(agent) {
|
|
|
1628
1634
|
}
|
|
1629
1635
|
|
|
1630
1636
|
// src/cli/agents/shell.ts
|
|
1631
|
-
import
|
|
1632
|
-
import
|
|
1637
|
+
import fs10 from "fs";
|
|
1638
|
+
import path11 from "path";
|
|
1633
1639
|
import os8 from "os";
|
|
1634
1640
|
var BEGIN_MARKER = "# BEGIN SKALPEL PROXY - do not edit manually";
|
|
1635
1641
|
var END_MARKER = "# END SKALPEL PROXY";
|
|
@@ -1638,21 +1644,21 @@ var PS_END_MARKER = "# END SKALPEL PROXY";
|
|
|
1638
1644
|
function getUnixProfilePaths() {
|
|
1639
1645
|
const home = os8.homedir();
|
|
1640
1646
|
const candidates = [
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1647
|
+
path11.join(home, ".bashrc"),
|
|
1648
|
+
path11.join(home, ".zshrc"),
|
|
1649
|
+
path11.join(home, ".bash_profile"),
|
|
1650
|
+
path11.join(home, ".profile")
|
|
1645
1651
|
];
|
|
1646
|
-
return candidates.filter((p) =>
|
|
1652
|
+
return candidates.filter((p) => fs10.existsSync(p));
|
|
1647
1653
|
}
|
|
1648
1654
|
function getPowerShellProfilePath() {
|
|
1649
1655
|
if (process.platform !== "win32") return null;
|
|
1650
1656
|
if (process.env.PROFILE) return process.env.PROFILE;
|
|
1651
|
-
const docsDir =
|
|
1652
|
-
const psProfile =
|
|
1653
|
-
const wpProfile =
|
|
1654
|
-
if (
|
|
1655
|
-
if (
|
|
1657
|
+
const docsDir = path11.join(os8.homedir(), "Documents");
|
|
1658
|
+
const psProfile = path11.join(docsDir, "PowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
1659
|
+
const wpProfile = path11.join(docsDir, "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
1660
|
+
if (fs10.existsSync(psProfile)) return psProfile;
|
|
1661
|
+
if (fs10.existsSync(wpProfile)) return wpProfile;
|
|
1656
1662
|
return psProfile;
|
|
1657
1663
|
}
|
|
1658
1664
|
function generateUnixBlock(proxyConfig) {
|
|
@@ -1673,13 +1679,13 @@ function generatePowerShellBlock(proxyConfig) {
|
|
|
1673
1679
|
}
|
|
1674
1680
|
function createBackup2(filePath) {
|
|
1675
1681
|
const backupPath = `${filePath}.skalpel-backup`;
|
|
1676
|
-
|
|
1682
|
+
fs10.copyFileSync(filePath, backupPath);
|
|
1677
1683
|
}
|
|
1678
1684
|
function updateProfileFile(filePath, block, beginMarker, endMarker) {
|
|
1679
|
-
if (
|
|
1685
|
+
if (fs10.existsSync(filePath)) {
|
|
1680
1686
|
createBackup2(filePath);
|
|
1681
1687
|
}
|
|
1682
|
-
let content =
|
|
1688
|
+
let content = fs10.existsSync(filePath) ? fs10.readFileSync(filePath, "utf-8") : "";
|
|
1683
1689
|
const beginIdx = content.indexOf(beginMarker);
|
|
1684
1690
|
const endIdx = content.indexOf(endMarker);
|
|
1685
1691
|
if (beginIdx !== -1 && endIdx !== -1) {
|
|
@@ -1692,15 +1698,15 @@ function updateProfileFile(filePath, block, beginMarker, endMarker) {
|
|
|
1692
1698
|
content = block + "\n";
|
|
1693
1699
|
}
|
|
1694
1700
|
}
|
|
1695
|
-
|
|
1701
|
+
fs10.writeFileSync(filePath, content);
|
|
1696
1702
|
}
|
|
1697
1703
|
function configureShellEnvVars(_agents, proxyConfig) {
|
|
1698
1704
|
const modified = [];
|
|
1699
1705
|
if (process.platform === "win32") {
|
|
1700
1706
|
const psProfile = getPowerShellProfilePath();
|
|
1701
1707
|
if (psProfile) {
|
|
1702
|
-
const dir =
|
|
1703
|
-
|
|
1708
|
+
const dir = path11.dirname(psProfile);
|
|
1709
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
1704
1710
|
const block = generatePowerShellBlock(proxyConfig);
|
|
1705
1711
|
updateProfileFile(psProfile, block, PS_BEGIN_MARKER, PS_END_MARKER);
|
|
1706
1712
|
modified.push(psProfile);
|
|
@@ -1719,28 +1725,28 @@ function removeShellEnvVars() {
|
|
|
1719
1725
|
const restored = [];
|
|
1720
1726
|
const home = os8.homedir();
|
|
1721
1727
|
const allProfiles = [
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1728
|
+
path11.join(home, ".bashrc"),
|
|
1729
|
+
path11.join(home, ".zshrc"),
|
|
1730
|
+
path11.join(home, ".bash_profile"),
|
|
1731
|
+
path11.join(home, ".profile")
|
|
1726
1732
|
];
|
|
1727
1733
|
if (process.platform === "win32") {
|
|
1728
1734
|
const psProfile = getPowerShellProfilePath();
|
|
1729
1735
|
if (psProfile) allProfiles.push(psProfile);
|
|
1730
1736
|
}
|
|
1731
1737
|
for (const profilePath of allProfiles) {
|
|
1732
|
-
if (!
|
|
1733
|
-
const content =
|
|
1738
|
+
if (!fs10.existsSync(profilePath)) continue;
|
|
1739
|
+
const content = fs10.readFileSync(profilePath, "utf-8");
|
|
1734
1740
|
const beginIdx = content.indexOf(BEGIN_MARKER);
|
|
1735
1741
|
const endIdx = content.indexOf(END_MARKER);
|
|
1736
1742
|
if (beginIdx === -1 || endIdx === -1) continue;
|
|
1737
1743
|
const before = content.slice(0, beginIdx);
|
|
1738
1744
|
const after = content.slice(endIdx + END_MARKER.length);
|
|
1739
1745
|
const cleaned = (before.replace(/\n+$/, "") + after.replace(/^\n+/, "\n")).trimEnd() + "\n";
|
|
1740
|
-
|
|
1746
|
+
fs10.writeFileSync(profilePath, cleaned);
|
|
1741
1747
|
const backupPath = `${profilePath}.skalpel-backup`;
|
|
1742
|
-
if (
|
|
1743
|
-
|
|
1748
|
+
if (fs10.existsSync(backupPath)) {
|
|
1749
|
+
fs10.unlinkSync(backupPath);
|
|
1744
1750
|
}
|
|
1745
1751
|
restored.push(profilePath);
|
|
1746
1752
|
}
|
|
@@ -1753,6 +1759,226 @@ function removeShellBlock() {
|
|
|
1753
1759
|
return removeShellEnvVars();
|
|
1754
1760
|
}
|
|
1755
1761
|
|
|
1762
|
+
// src/cli/start.ts
|
|
1763
|
+
function print5(msg) {
|
|
1764
|
+
console.log(msg);
|
|
1765
|
+
}
|
|
1766
|
+
function reconfigureAgents(config) {
|
|
1767
|
+
const direct = config.mode === "direct";
|
|
1768
|
+
const agents = detectAgents();
|
|
1769
|
+
for (const agent of agents) {
|
|
1770
|
+
if (agent.installed) {
|
|
1771
|
+
try {
|
|
1772
|
+
configureAgent(agent, config, direct);
|
|
1773
|
+
} catch {
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
try {
|
|
1778
|
+
configureShellEnvVars(agents.filter((a) => a.installed), config);
|
|
1779
|
+
} catch {
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
async function runStart() {
|
|
1783
|
+
const config = loadConfig();
|
|
1784
|
+
if (!config.apiKey) {
|
|
1785
|
+
print5(' Error: No API key configured. Run "skalpel init" or set SKALPEL_API_KEY.');
|
|
1786
|
+
process.exit(1);
|
|
1787
|
+
}
|
|
1788
|
+
const existingPid = readPid(config.pidFile);
|
|
1789
|
+
if (existingPid !== null) {
|
|
1790
|
+
print5(` Proxy is already running (pid=${existingPid}).`);
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
const alive = await isProxyAlive(config.anthropicPort);
|
|
1794
|
+
if (alive) {
|
|
1795
|
+
print5(" Proxy is already running (detected via health check).");
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
if (isServiceInstalled()) {
|
|
1799
|
+
startService();
|
|
1800
|
+
reconfigureAgents(config);
|
|
1801
|
+
print5(` Skalpel proxy started via system service on ports ${config.anthropicPort}, ${config.openaiPort}, and ${config.cursorPort}`);
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
const dirname = path12.dirname(fileURLToPath2(import.meta.url));
|
|
1805
|
+
const runnerScript = path12.resolve(dirname, "proxy-runner.js");
|
|
1806
|
+
const child = spawn(process.execPath, [runnerScript], {
|
|
1807
|
+
detached: true,
|
|
1808
|
+
stdio: "ignore"
|
|
1809
|
+
});
|
|
1810
|
+
child.unref();
|
|
1811
|
+
reconfigureAgents(config);
|
|
1812
|
+
print5(` Skalpel proxy started on ports ${config.anthropicPort}, ${config.openaiPort}, and ${config.cursorPort}`);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// src/cli/stop.ts
|
|
1816
|
+
import { execSync as execSync5 } from "child_process";
|
|
1817
|
+
|
|
1818
|
+
// src/proxy/server.ts
|
|
1819
|
+
init_handler();
|
|
1820
|
+
import http from "http";
|
|
1821
|
+
|
|
1822
|
+
// src/proxy/logger.ts
|
|
1823
|
+
import fs11 from "fs";
|
|
1824
|
+
import path13 from "path";
|
|
1825
|
+
var MAX_SIZE = 5 * 1024 * 1024;
|
|
1826
|
+
|
|
1827
|
+
// src/proxy/ws-server.ts
|
|
1828
|
+
import { WebSocketServer } from "ws";
|
|
1829
|
+
var wss = new WebSocketServer({ noServer: true });
|
|
1830
|
+
|
|
1831
|
+
// src/proxy/server.ts
|
|
1832
|
+
var proxyStartTime = 0;
|
|
1833
|
+
function stopProxy(config) {
|
|
1834
|
+
const pid = readPid(config.pidFile);
|
|
1835
|
+
if (pid === null) return false;
|
|
1836
|
+
try {
|
|
1837
|
+
process.kill(pid, "SIGTERM");
|
|
1838
|
+
} catch {
|
|
1839
|
+
}
|
|
1840
|
+
removePid(config.pidFile);
|
|
1841
|
+
return true;
|
|
1842
|
+
}
|
|
1843
|
+
async function getProxyStatus(config) {
|
|
1844
|
+
const pid = readPid(config.pidFile);
|
|
1845
|
+
if (pid !== null) {
|
|
1846
|
+
return {
|
|
1847
|
+
running: true,
|
|
1848
|
+
pid,
|
|
1849
|
+
uptime: proxyStartTime > 0 ? Date.now() - proxyStartTime : 0,
|
|
1850
|
+
anthropicPort: config.anthropicPort,
|
|
1851
|
+
openaiPort: config.openaiPort,
|
|
1852
|
+
cursorPort: config.cursorPort
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
const alive = await isProxyAlive(config.anthropicPort);
|
|
1856
|
+
return {
|
|
1857
|
+
running: alive,
|
|
1858
|
+
pid: null,
|
|
1859
|
+
uptime: 0,
|
|
1860
|
+
anthropicPort: config.anthropicPort,
|
|
1861
|
+
openaiPort: config.openaiPort,
|
|
1862
|
+
cursorPort: config.cursorPort
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// src/cli/stop.ts
|
|
1867
|
+
function print6(msg) {
|
|
1868
|
+
console.log(msg);
|
|
1869
|
+
}
|
|
1870
|
+
async function runStop() {
|
|
1871
|
+
const config = loadConfig();
|
|
1872
|
+
if (isServiceInstalled()) {
|
|
1873
|
+
stopService();
|
|
1874
|
+
}
|
|
1875
|
+
const stopped = stopProxy(config);
|
|
1876
|
+
if (stopped) {
|
|
1877
|
+
print6(" Skalpel proxy stopped.");
|
|
1878
|
+
} else {
|
|
1879
|
+
const alive = await isProxyAlive(config.anthropicPort);
|
|
1880
|
+
if (alive) {
|
|
1881
|
+
let killedViaPort = false;
|
|
1882
|
+
if (process.platform === "darwin" || process.platform === "linux") {
|
|
1883
|
+
try {
|
|
1884
|
+
const pids = execSync5(`lsof -ti :${config.anthropicPort}`, { timeout: 3e3 }).toString().trim().split("\n").filter(Boolean);
|
|
1885
|
+
for (const p of pids) {
|
|
1886
|
+
const pid = parseInt(p, 10);
|
|
1887
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
1888
|
+
try {
|
|
1889
|
+
process.kill(pid, "SIGTERM");
|
|
1890
|
+
} catch {
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
killedViaPort = true;
|
|
1895
|
+
} catch {
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (killedViaPort) {
|
|
1899
|
+
print6(" Skalpel proxy stopped (found via port detection).");
|
|
1900
|
+
} else {
|
|
1901
|
+
print6(" Proxy appears to be running but could not be stopped automatically.");
|
|
1902
|
+
print6(` Try: kill $(lsof -ti :${config.anthropicPort})`);
|
|
1903
|
+
}
|
|
1904
|
+
} else {
|
|
1905
|
+
print6(" Proxy is not running.");
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
const agents = detectAgents();
|
|
1909
|
+
for (const agent of agents) {
|
|
1910
|
+
if (agent.installed) {
|
|
1911
|
+
try {
|
|
1912
|
+
unconfigureAgent(agent);
|
|
1913
|
+
} catch {
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
try {
|
|
1918
|
+
removeShellEnvVars();
|
|
1919
|
+
} catch {
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// src/cli/status.ts
|
|
1924
|
+
function print7(msg) {
|
|
1925
|
+
console.log(msg);
|
|
1926
|
+
}
|
|
1927
|
+
async function runStatus() {
|
|
1928
|
+
const config = loadConfig();
|
|
1929
|
+
const status = await getProxyStatus(config);
|
|
1930
|
+
print7("");
|
|
1931
|
+
print7(" Skalpel Proxy Status");
|
|
1932
|
+
print7(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1933
|
+
print7(` Status: ${status.running ? "running" : "stopped"}`);
|
|
1934
|
+
if (status.pid !== null) {
|
|
1935
|
+
print7(` PID: ${status.pid}`);
|
|
1936
|
+
}
|
|
1937
|
+
print7(` Anthropic: port ${status.anthropicPort}`);
|
|
1938
|
+
print7(` OpenAI: port ${status.openaiPort}`);
|
|
1939
|
+
print7(` Cursor: port ${status.cursorPort}`);
|
|
1940
|
+
print7(` Config: ${config.configFile}`);
|
|
1941
|
+
print7("");
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
// src/cli/logs.ts
|
|
1945
|
+
import fs12 from "fs";
|
|
1946
|
+
function print8(msg) {
|
|
1947
|
+
console.log(msg);
|
|
1948
|
+
}
|
|
1949
|
+
async function runLogs(options) {
|
|
1950
|
+
const config = loadConfig();
|
|
1951
|
+
const logFile = config.logFile;
|
|
1952
|
+
const lineCount = parseInt(options.lines ?? "50", 10);
|
|
1953
|
+
if (!fs12.existsSync(logFile)) {
|
|
1954
|
+
print8(` No log file found at ${logFile}`);
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
const content = fs12.readFileSync(logFile, "utf-8");
|
|
1958
|
+
const lines = content.trimEnd().split("\n");
|
|
1959
|
+
const tail = lines.slice(-lineCount);
|
|
1960
|
+
for (const line of tail) {
|
|
1961
|
+
print8(line);
|
|
1962
|
+
}
|
|
1963
|
+
if (options.follow) {
|
|
1964
|
+
let position = fs12.statSync(logFile).size;
|
|
1965
|
+
fs12.watchFile(logFile, { interval: 500 }, () => {
|
|
1966
|
+
try {
|
|
1967
|
+
const stat = fs12.statSync(logFile);
|
|
1968
|
+
if (stat.size > position) {
|
|
1969
|
+
const fd = fs12.openSync(logFile, "r");
|
|
1970
|
+
const buf = Buffer.alloc(stat.size - position);
|
|
1971
|
+
fs12.readSync(fd, buf, 0, buf.length, position);
|
|
1972
|
+
fs12.closeSync(fd);
|
|
1973
|
+
process.stdout.write(buf.toString("utf-8"));
|
|
1974
|
+
position = stat.size;
|
|
1975
|
+
}
|
|
1976
|
+
} catch {
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1756
1982
|
// src/cli/config-cmd.ts
|
|
1757
1983
|
function print9(msg) {
|
|
1758
1984
|
console.log(msg);
|