skalpel 2.0.11 → 2.0.12
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 +2 -7
- package/dist/cli/index.js +7 -110
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/proxy-runner.js +4 -32
- package/dist/cli/proxy-runner.js.map +1 -1
- package/dist/index.cjs +5 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -34
- package/dist/index.js.map +1 -1
- package/dist/proxy/index.cjs +5 -34
- package/dist/proxy/index.cjs.map +1 -1
- package/dist/proxy/index.d.cts +0 -3
- package/dist/proxy/index.d.ts +0 -3
- package/dist/proxy/index.js +5 -34
- package/dist/proxy/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,9 +26,9 @@ npx skalpel --api-key sk-skalpel-YOUR_KEY --auto
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
This will:
|
|
29
|
-
- Start the local proxy on
|
|
29
|
+
- Start the local proxy on port 18100 (Anthropic)
|
|
30
30
|
- Detect coding agents on your machine
|
|
31
|
-
- Configure Claude Code
|
|
31
|
+
- Configure Claude Code automatically
|
|
32
32
|
- Set shell environment variables in your `.bashrc`/`.zshrc`
|
|
33
33
|
- Begin optimizing API traffic immediately
|
|
34
34
|
|
|
@@ -68,7 +68,6 @@ Environment variables are added for tools that read them directly:
|
|
|
68
68
|
```bash
|
|
69
69
|
# BEGIN SKALPEL PROXY - do not edit manually
|
|
70
70
|
export ANTHROPIC_BASE_URL="http://localhost:18100"
|
|
71
|
-
export OPENAI_BASE_URL="http://localhost:18101"
|
|
72
71
|
# END SKALPEL PROXY
|
|
73
72
|
```
|
|
74
73
|
|
|
@@ -127,10 +126,6 @@ The proxy adds these headers to every request forwarded to the Skalpel backend:
|
|
|
127
126
|
|
|
128
127
|
The original `x-api-key` header (your Anthropic key) is forwarded as-is.
|
|
129
128
|
|
|
130
|
-
## Codex Support
|
|
131
|
-
|
|
132
|
-
Skalpel also supports OpenAI Codex on port 18101. The same `--auto` flow detects and configures both agents if present.
|
|
133
|
-
|
|
134
129
|
## SDK Integration
|
|
135
130
|
|
|
136
131
|
For programmatic use in your own applications:
|
package/dist/cli/index.js
CHANGED
|
@@ -250,35 +250,8 @@ function detectClaudeCode() {
|
|
|
250
250
|
}
|
|
251
251
|
return agent;
|
|
252
252
|
}
|
|
253
|
-
function detectCodex() {
|
|
254
|
-
const agent = {
|
|
255
|
-
name: "codex",
|
|
256
|
-
installed: false,
|
|
257
|
-
version: null,
|
|
258
|
-
configPath: null
|
|
259
|
-
};
|
|
260
|
-
const binaryPath = tryExec(`${whichCommand()} codex`);
|
|
261
|
-
const hasBinary = binaryPath !== null && binaryPath.length > 0;
|
|
262
|
-
const codexConfigDir = process.platform === "win32" ? path3.join(os.homedir(), "AppData", "Roaming", "codex") : path3.join(os.homedir(), ".codex");
|
|
263
|
-
const hasConfigDir = fs3.existsSync(codexConfigDir);
|
|
264
|
-
agent.installed = hasBinary || hasConfigDir;
|
|
265
|
-
if (hasBinary) {
|
|
266
|
-
const versionOutput = tryExec("codex --version");
|
|
267
|
-
if (versionOutput) {
|
|
268
|
-
const match = versionOutput.match(/(\d+\.\d+[\w.-]*)/);
|
|
269
|
-
agent.version = match ? match[1] : versionOutput;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
const configFile = path3.join(codexConfigDir, "config.toml");
|
|
273
|
-
if (fs3.existsSync(configFile)) {
|
|
274
|
-
agent.configPath = configFile;
|
|
275
|
-
} else if (hasConfigDir) {
|
|
276
|
-
agent.configPath = configFile;
|
|
277
|
-
}
|
|
278
|
-
return agent;
|
|
279
|
-
}
|
|
280
253
|
function detectAgents() {
|
|
281
|
-
return [detectClaudeCode()
|
|
254
|
+
return [detectClaudeCode()];
|
|
282
255
|
}
|
|
283
256
|
|
|
284
257
|
// src/cli/doctor.ts
|
|
@@ -596,7 +569,6 @@ var DEFAULTS = {
|
|
|
596
569
|
remoteBaseUrl: "https://api.skalpel.ai",
|
|
597
570
|
anthropicDirectUrl: "https://api.anthropic.com",
|
|
598
571
|
anthropicPort: 18100,
|
|
599
|
-
openaiPort: 18101,
|
|
600
572
|
logLevel: "info",
|
|
601
573
|
logFile: "~/.skalpel/logs/proxy.log",
|
|
602
574
|
pidFile: "~/.skalpel/proxy.pid",
|
|
@@ -615,7 +587,6 @@ function loadConfig(configPath) {
|
|
|
615
587
|
remoteBaseUrl: fileConfig.remoteBaseUrl ?? DEFAULTS.remoteBaseUrl,
|
|
616
588
|
anthropicDirectUrl: fileConfig.anthropicDirectUrl ?? DEFAULTS.anthropicDirectUrl,
|
|
617
589
|
anthropicPort: fileConfig.anthropicPort ?? DEFAULTS.anthropicPort,
|
|
618
|
-
openaiPort: fileConfig.openaiPort ?? DEFAULTS.openaiPort,
|
|
619
590
|
logLevel: fileConfig.logLevel ?? DEFAULTS.logLevel,
|
|
620
591
|
logFile: expandHome(fileConfig.logFile ?? DEFAULTS.logFile),
|
|
621
592
|
pidFile: expandHome(fileConfig.pidFile ?? DEFAULTS.pidFile),
|
|
@@ -749,8 +720,6 @@ function generateLaunchdPlist(config, proxyRunnerPath) {
|
|
|
749
720
|
<dict>
|
|
750
721
|
<key>SKALPEL_ANTHROPIC_PORT</key>
|
|
751
722
|
<string>${config.anthropicPort}</string>
|
|
752
|
-
<key>SKALPEL_OPENAI_PORT</key>
|
|
753
|
-
<string>${config.openaiPort}</string>
|
|
754
723
|
</dict>
|
|
755
724
|
</dict>
|
|
756
725
|
</plist>`;
|
|
@@ -766,7 +735,6 @@ ExecStart=${process.execPath} ${proxyRunnerPath}
|
|
|
766
735
|
Restart=always
|
|
767
736
|
RestartSec=5
|
|
768
737
|
Environment=SKALPEL_ANTHROPIC_PORT=${config.anthropicPort}
|
|
769
|
-
Environment=SKALPEL_OPENAI_PORT=${config.openaiPort}
|
|
770
738
|
|
|
771
739
|
[Install]
|
|
772
740
|
WantedBy=default.target`;
|
|
@@ -1026,47 +994,11 @@ function configureClaudeCode(agent, proxyConfig) {
|
|
|
1026
994
|
config.env.ANTHROPIC_BASE_URL = `http://localhost:${proxyConfig.anthropicPort}`;
|
|
1027
995
|
fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1028
996
|
}
|
|
1029
|
-
function readTomlFile(filePath) {
|
|
1030
|
-
try {
|
|
1031
|
-
return fs9.readFileSync(filePath, "utf-8");
|
|
1032
|
-
} catch {
|
|
1033
|
-
return "";
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
function setTomlKey(content, key, value) {
|
|
1037
|
-
const pattern = new RegExp(`^${key.replace(".", "\\.")}\\s*=.*$`, "m");
|
|
1038
|
-
const line = `${key} = "${value}"`;
|
|
1039
|
-
if (pattern.test(content)) {
|
|
1040
|
-
return content.replace(pattern, line);
|
|
1041
|
-
}
|
|
1042
|
-
const sectionMatch = content.match(/^\[/m);
|
|
1043
|
-
if (sectionMatch && sectionMatch.index !== void 0) {
|
|
1044
|
-
return content.slice(0, sectionMatch.index) + line + "\n" + content.slice(sectionMatch.index);
|
|
1045
|
-
}
|
|
1046
|
-
const separator = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
1047
|
-
return content + separator + line + "\n";
|
|
1048
|
-
}
|
|
1049
|
-
function removeTomlKey(content, key) {
|
|
1050
|
-
const pattern = new RegExp(`^${key.replace(".", "\\.")}\\s*=.*\\n?`, "gm");
|
|
1051
|
-
return content.replace(pattern, "");
|
|
1052
|
-
}
|
|
1053
|
-
function configureCodex(agent, proxyConfig) {
|
|
1054
|
-
const configDir = process.platform === "win32" ? path10.join(os7.homedir(), "AppData", "Roaming", "codex") : path10.join(os7.homedir(), ".codex");
|
|
1055
|
-
const configPath = agent.configPath ?? path10.join(configDir, "config.toml");
|
|
1056
|
-
ensureDir(path10.dirname(configPath));
|
|
1057
|
-
createBackup(configPath);
|
|
1058
|
-
let content = readTomlFile(configPath);
|
|
1059
|
-
content = setTomlKey(content, "openai_base_url", `http://localhost:${proxyConfig.openaiPort}`);
|
|
1060
|
-
fs9.writeFileSync(configPath, content);
|
|
1061
|
-
}
|
|
1062
997
|
function configureAgent(agent, proxyConfig) {
|
|
1063
998
|
switch (agent.name) {
|
|
1064
999
|
case "claude-code":
|
|
1065
1000
|
configureClaudeCode(agent, proxyConfig);
|
|
1066
1001
|
break;
|
|
1067
|
-
case "codex":
|
|
1068
|
-
configureCodex(agent, proxyConfig);
|
|
1069
|
-
break;
|
|
1070
1002
|
}
|
|
1071
1003
|
}
|
|
1072
1004
|
function unconfigureClaudeCode(agent) {
|
|
@@ -1089,27 +1021,11 @@ function unconfigureClaudeCode(agent) {
|
|
|
1089
1021
|
fs9.unlinkSync(backupPath);
|
|
1090
1022
|
}
|
|
1091
1023
|
}
|
|
1092
|
-
function unconfigureCodex(agent) {
|
|
1093
|
-
const configDir = process.platform === "win32" ? path10.join(os7.homedir(), "AppData", "Roaming", "codex") : path10.join(os7.homedir(), ".codex");
|
|
1094
|
-
const configPath = agent.configPath ?? path10.join(configDir, "config.toml");
|
|
1095
|
-
if (fs9.existsSync(configPath)) {
|
|
1096
|
-
let content = readTomlFile(configPath);
|
|
1097
|
-
content = removeTomlKey(content, "openai_base_url");
|
|
1098
|
-
fs9.writeFileSync(configPath, content);
|
|
1099
|
-
}
|
|
1100
|
-
const backupPath = `${configPath}.skalpel-backup`;
|
|
1101
|
-
if (fs9.existsSync(backupPath)) {
|
|
1102
|
-
fs9.unlinkSync(backupPath);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
1024
|
function unconfigureAgent(agent) {
|
|
1106
1025
|
switch (agent.name) {
|
|
1107
1026
|
case "claude-code":
|
|
1108
1027
|
unconfigureClaudeCode(agent);
|
|
1109
1028
|
break;
|
|
1110
|
-
case "codex":
|
|
1111
|
-
unconfigureCodex(agent);
|
|
1112
|
-
break;
|
|
1113
1029
|
}
|
|
1114
1030
|
}
|
|
1115
1031
|
|
|
@@ -1145,7 +1061,6 @@ function generateUnixBlock(proxyConfig) {
|
|
|
1145
1061
|
return [
|
|
1146
1062
|
BEGIN_MARKER,
|
|
1147
1063
|
`export ANTHROPIC_BASE_URL="http://localhost:${proxyConfig.anthropicPort}"`,
|
|
1148
|
-
`export OPENAI_BASE_URL="http://localhost:${proxyConfig.openaiPort}"`,
|
|
1149
1064
|
END_MARKER
|
|
1150
1065
|
].join("\n");
|
|
1151
1066
|
}
|
|
@@ -1153,7 +1068,6 @@ function generatePowerShellBlock(proxyConfig) {
|
|
|
1153
1068
|
return [
|
|
1154
1069
|
PS_BEGIN_MARKER,
|
|
1155
1070
|
`$env:ANTHROPIC_BASE_URL = "http://localhost:${proxyConfig.anthropicPort}"`,
|
|
1156
|
-
`$env:OPENAI_BASE_URL = "http://localhost:${proxyConfig.openaiPort}"`,
|
|
1157
1071
|
PS_END_MARKER
|
|
1158
1072
|
].join("\n");
|
|
1159
1073
|
}
|
|
@@ -1268,7 +1182,7 @@ async function runStart() {
|
|
|
1268
1182
|
}
|
|
1269
1183
|
if (isServiceInstalled()) {
|
|
1270
1184
|
startService();
|
|
1271
|
-
print5(` Skalpel proxy started via system service on
|
|
1185
|
+
print5(` Skalpel proxy started via system service on port ${config.anthropicPort}`);
|
|
1272
1186
|
configureAgentsForProxy(config);
|
|
1273
1187
|
return;
|
|
1274
1188
|
}
|
|
@@ -1279,7 +1193,7 @@ async function runStart() {
|
|
|
1279
1193
|
stdio: "ignore"
|
|
1280
1194
|
});
|
|
1281
1195
|
child.unref();
|
|
1282
|
-
print5(` Skalpel proxy started on
|
|
1196
|
+
print5(` Skalpel proxy started on port ${config.anthropicPort}`);
|
|
1283
1197
|
configureAgentsForProxy(config);
|
|
1284
1198
|
}
|
|
1285
1199
|
function configureAgentsForProxy(config) {
|
|
@@ -1341,8 +1255,7 @@ function getProxyStatus(config) {
|
|
|
1341
1255
|
running: pid !== null,
|
|
1342
1256
|
pid,
|
|
1343
1257
|
uptime: proxyStartTime > 0 ? Date.now() - proxyStartTime : 0,
|
|
1344
|
-
anthropicPort: config.anthropicPort
|
|
1345
|
-
openaiPort: config.openaiPort
|
|
1258
|
+
anthropicPort: config.anthropicPort
|
|
1346
1259
|
};
|
|
1347
1260
|
}
|
|
1348
1261
|
|
|
@@ -1438,7 +1351,6 @@ async function runStatus() {
|
|
|
1438
1351
|
}
|
|
1439
1352
|
}
|
|
1440
1353
|
print7(` Anthropic: port ${status.anthropicPort}`);
|
|
1441
|
-
print7(` OpenAI: port ${status.openaiPort}`);
|
|
1442
1354
|
print7(` Config: ${config.configFile}`);
|
|
1443
1355
|
print7("");
|
|
1444
1356
|
}
|
|
@@ -1502,7 +1414,6 @@ async function runConfig(subcommand, args) {
|
|
|
1502
1414
|
"apiKey",
|
|
1503
1415
|
"remoteBaseUrl",
|
|
1504
1416
|
"anthropicPort",
|
|
1505
|
-
"openaiPort",
|
|
1506
1417
|
"logLevel",
|
|
1507
1418
|
"logFile",
|
|
1508
1419
|
"pidFile"
|
|
@@ -1513,7 +1424,7 @@ async function runConfig(subcommand, args) {
|
|
|
1513
1424
|
process.exit(1);
|
|
1514
1425
|
}
|
|
1515
1426
|
const updated = { ...config };
|
|
1516
|
-
if (key === "anthropicPort"
|
|
1427
|
+
if (key === "anthropicPort") {
|
|
1517
1428
|
const parsed = parseInt(value, 10);
|
|
1518
1429
|
if (isNaN(parsed) || parsed < 1 || parsed > 65535) {
|
|
1519
1430
|
print9(` Invalid port number: ${value}`);
|
|
@@ -1679,7 +1590,6 @@ async function runWizard(options) {
|
|
|
1679
1590
|
print11("");
|
|
1680
1591
|
let agentsToConfigure = installedAgents.filter((a) => {
|
|
1681
1592
|
if (options?.skipClaude && a.name === "claude-code") return false;
|
|
1682
|
-
if (options?.skipCodex && a.name === "codex") return false;
|
|
1683
1593
|
return true;
|
|
1684
1594
|
});
|
|
1685
1595
|
if (agentsToConfigure.length > 0 && !isAuto) {
|
|
@@ -1726,19 +1636,6 @@ async function runWizard(options) {
|
|
|
1726
1636
|
print11(` [!] Proxy not responding yet. It may take a moment to start.`);
|
|
1727
1637
|
print11(' Run "npx skalpel status" to check later, or "npx skalpel start" to start manually.');
|
|
1728
1638
|
}
|
|
1729
|
-
try {
|
|
1730
|
-
const controller = new AbortController();
|
|
1731
|
-
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
1732
|
-
const healthUrl = `http://localhost:${proxyConfig.openaiPort}/health`;
|
|
1733
|
-
const res = await fetch(healthUrl, { signal: controller.signal });
|
|
1734
|
-
clearTimeout(timeout);
|
|
1735
|
-
if (res.ok) {
|
|
1736
|
-
print11(` [+] OpenAI proxy (port ${proxyConfig.openaiPort}): healthy`);
|
|
1737
|
-
} else {
|
|
1738
|
-
print11(` [!] OpenAI proxy (port ${proxyConfig.openaiPort}): HTTP ${res.status}`);
|
|
1739
|
-
}
|
|
1740
|
-
} catch {
|
|
1741
|
-
}
|
|
1742
1639
|
print11("");
|
|
1743
1640
|
print11(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1744
1641
|
print11("");
|
|
@@ -1747,7 +1644,7 @@ async function runWizard(options) {
|
|
|
1747
1644
|
if (agentsToConfigure.length > 0) {
|
|
1748
1645
|
print11(" Configured agents: " + agentsToConfigure.map((a) => a.name).join(", "));
|
|
1749
1646
|
}
|
|
1750
|
-
print11(" Proxy
|
|
1647
|
+
print11(" Proxy port: Anthropic=" + proxyConfig.anthropicPort);
|
|
1751
1648
|
print11("");
|
|
1752
1649
|
print11(' Run "npx skalpel status" to check proxy status');
|
|
1753
1650
|
print11(' Run "npx skalpel doctor" for a full health check');
|
|
@@ -1903,7 +1800,7 @@ function clearNpxCache() {
|
|
|
1903
1800
|
var require3 = createRequire2(import.meta.url);
|
|
1904
1801
|
var pkg2 = require3("../../package.json");
|
|
1905
1802
|
var program = new Command();
|
|
1906
|
-
program.name("skalpel").description("Skalpel AI CLI \u2014 optimize your OpenAI and Anthropic API calls").version(pkg2.version).option("--api-key <key>", "Skalpel API key for non-interactive setup").option("--auto", "Run setup in non-interactive mode").option("--skip-claude", "Skip Claude Code configuration").
|
|
1803
|
+
program.name("skalpel").description("Skalpel AI CLI \u2014 optimize your OpenAI and Anthropic API calls").version(pkg2.version).option("--api-key <key>", "Skalpel API key for non-interactive setup").option("--auto", "Run setup in non-interactive mode").option("--skip-claude", "Skip Claude Code configuration").action((options) => runWizard(options));
|
|
1907
1804
|
program.command("init").description("Initialize Skalpel in your project").action(runInit);
|
|
1908
1805
|
program.command("doctor").description("Check Skalpel configuration health").action(runDoctor);
|
|
1909
1806
|
program.command("benchmark").description("Run performance benchmarks").action(runBenchmark);
|