skalpel 2.0.13 → 2.0.14
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 +118 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/proxy-runner.js +62 -0
- package/dist/cli/proxy-runner.js.map +1 -1
- package/dist/index.cjs +62 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -1
- package/dist/proxy/index.cjs +62 -0
- package/dist/proxy/index.cjs.map +1 -1
- package/dist/proxy/index.js +62 -0
- package/dist/proxy/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -319,6 +319,69 @@ function detectAgents() {
|
|
|
319
319
|
function print2(msg) {
|
|
320
320
|
console.log(msg);
|
|
321
321
|
}
|
|
322
|
+
function codexConfigPath() {
|
|
323
|
+
return process.platform === "win32" ? path4.join(os2.homedir(), "AppData", "Roaming", "codex", "config.toml") : path4.join(os2.homedir(), ".codex", "config.toml");
|
|
324
|
+
}
|
|
325
|
+
function checkCodexConfig(config) {
|
|
326
|
+
const cfgPath = codexConfigPath();
|
|
327
|
+
if (!fs4.existsSync(cfgPath)) {
|
|
328
|
+
return {
|
|
329
|
+
name: "Codex config",
|
|
330
|
+
status: "warn",
|
|
331
|
+
message: `${cfgPath} not found \u2014 run "npx skalpel" to configure Codex`
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
let content = "";
|
|
335
|
+
try {
|
|
336
|
+
content = fs4.readFileSync(cfgPath, "utf-8");
|
|
337
|
+
} catch {
|
|
338
|
+
return {
|
|
339
|
+
name: "Codex config",
|
|
340
|
+
status: "warn",
|
|
341
|
+
message: `cannot read ${cfgPath}`
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const requiredLines = [
|
|
345
|
+
`openai_base_url = "http://localhost:${config.openaiPort}"`,
|
|
346
|
+
`model_provider = "skalpel-proxy"`,
|
|
347
|
+
`[model_providers.skalpel-proxy]`,
|
|
348
|
+
`wire_api = "responses"`,
|
|
349
|
+
`base_url = "http://localhost:${config.openaiPort}/v1"`
|
|
350
|
+
];
|
|
351
|
+
const missing = requiredLines.filter((line) => !content.includes(line));
|
|
352
|
+
if (missing.length === 0) {
|
|
353
|
+
return {
|
|
354
|
+
name: "Codex config",
|
|
355
|
+
status: "ok",
|
|
356
|
+
message: `skalpel-proxy provider pinned (wire_api=responses) on port ${config.openaiPort}`
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
name: "Codex config",
|
|
361
|
+
status: "fail",
|
|
362
|
+
message: `missing TOML: ${missing.map((m) => m.split("\n")[0]).join("; ")}`
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
async function checkCodexProxyProbe(config) {
|
|
366
|
+
const url = `http://localhost:${config.openaiPort}/v1/responses`;
|
|
367
|
+
try {
|
|
368
|
+
const res = await fetch(url, {
|
|
369
|
+
method: "POST",
|
|
370
|
+
headers: { "Content-Type": "application/json", Authorization: "Bearer sk-codex-placeholder-skalpel" },
|
|
371
|
+
body: JSON.stringify({ model: "gpt-5-codex", input: "ping", stream: false }),
|
|
372
|
+
signal: AbortSignal.timeout(5e3)
|
|
373
|
+
});
|
|
374
|
+
if (res.status === 405) {
|
|
375
|
+
return { name: "Codex proxy probe", status: "error", message: "backend rejected POST \u2014 run the fix in docs/codex-integration-fix.md" };
|
|
376
|
+
}
|
|
377
|
+
if (res.status === 401 || res.status >= 200 && res.status < 300) {
|
|
378
|
+
return { name: "Codex proxy probe", status: "ok", message: `POST /v1/responses returned ${res.status}` };
|
|
379
|
+
}
|
|
380
|
+
return { name: "Codex proxy probe", status: "warn", message: `unexpected status ${res.status}` };
|
|
381
|
+
} catch {
|
|
382
|
+
return { name: "Codex proxy probe", status: "warn", message: "proxy not reachable (is it running?)" };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
322
385
|
function loadConfigApiKey() {
|
|
323
386
|
try {
|
|
324
387
|
const configPath = path4.join(os2.homedir(), ".skalpel", "config.json");
|
|
@@ -436,12 +499,21 @@ async function runDoctor() {
|
|
|
436
499
|
checks.push({ name: agent.name, status: "warn", message: "Not installed" });
|
|
437
500
|
}
|
|
438
501
|
}
|
|
439
|
-
|
|
502
|
+
let openaiPort = 18101;
|
|
503
|
+
try {
|
|
504
|
+
const raw = JSON.parse(fs4.readFileSync(skalpelConfigPath, "utf-8"));
|
|
505
|
+
if (typeof raw.openaiPort === "number") openaiPort = raw.openaiPort;
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
const config = { openaiPort };
|
|
509
|
+
checks.push(checkCodexConfig(config));
|
|
510
|
+
checks.push(await checkCodexProxyProbe(config));
|
|
511
|
+
const icons = { ok: "+", warn: "!", fail: "x", error: "x" };
|
|
440
512
|
for (const check of checks) {
|
|
441
513
|
const icon = icons[check.status];
|
|
442
514
|
print2(` [${icon}] ${check.name}: ${check.message}`);
|
|
443
515
|
}
|
|
444
|
-
const failures = checks.filter((c) => c.status === "fail");
|
|
516
|
+
const failures = checks.filter((c) => c.status === "fail" || c.status === "error");
|
|
445
517
|
const warnings = checks.filter((c) => c.status === "warn");
|
|
446
518
|
print2("");
|
|
447
519
|
if (failures.length > 0) {
|
|
@@ -1305,6 +1377,7 @@ import os7 from "os";
|
|
|
1305
1377
|
var CURSOR_API_BASE_URL_KEY = "openai.apiBaseUrl";
|
|
1306
1378
|
var DIRECT_MODE_BASE_URL = "https://api.skalpel.ai";
|
|
1307
1379
|
var CODEX_DIRECT_PROVIDER_ID = "skalpel";
|
|
1380
|
+
var CODEX_PROXY_PROVIDER_ID = "skalpel-proxy";
|
|
1308
1381
|
function ensureDir(dir) {
|
|
1309
1382
|
fs11.mkdirSync(dir, { recursive: true });
|
|
1310
1383
|
}
|
|
@@ -1396,6 +1469,39 @@ function removeCodexDirectProvider(content) {
|
|
|
1396
1469
|
const rest = content.slice(end).replace(/^\n+/, "");
|
|
1397
1470
|
return before.length > 0 && rest.length > 0 ? before + "\n" + rest : before + rest;
|
|
1398
1471
|
}
|
|
1472
|
+
function buildCodexProxyProviderBlock(port) {
|
|
1473
|
+
return [
|
|
1474
|
+
`[model_providers.skalpel-proxy]`,
|
|
1475
|
+
`name = "Skalpel Proxy"`,
|
|
1476
|
+
`base_url = "http://localhost:${port}/v1"`,
|
|
1477
|
+
`wire_api = "responses"`,
|
|
1478
|
+
`env_key = "OPENAI_API_KEY"`
|
|
1479
|
+
].join("\n");
|
|
1480
|
+
}
|
|
1481
|
+
function upsertCodexProxyProvider(content, port) {
|
|
1482
|
+
const sectionHeader = `[model_providers.${CODEX_PROXY_PROVIDER_ID}]`;
|
|
1483
|
+
const block = buildCodexProxyProviderBlock(port);
|
|
1484
|
+
const idx = content.indexOf(sectionHeader);
|
|
1485
|
+
if (idx === -1) {
|
|
1486
|
+
const separator = content.length > 0 && !content.endsWith("\n") ? "\n\n" : content.length > 0 ? "\n" : "";
|
|
1487
|
+
return content + separator + block + "\n";
|
|
1488
|
+
}
|
|
1489
|
+
const after = content.slice(idx + sectionHeader.length);
|
|
1490
|
+
const nextHeaderMatch = after.match(/\n\[[^\]]+\]/);
|
|
1491
|
+
const end = nextHeaderMatch && nextHeaderMatch.index !== void 0 ? idx + sectionHeader.length + nextHeaderMatch.index : content.length;
|
|
1492
|
+
return content.slice(0, idx) + block + content.slice(end);
|
|
1493
|
+
}
|
|
1494
|
+
function removeCodexProxyProvider(content) {
|
|
1495
|
+
const sectionHeader = `[model_providers.${CODEX_PROXY_PROVIDER_ID}]`;
|
|
1496
|
+
const idx = content.indexOf(sectionHeader);
|
|
1497
|
+
if (idx === -1) return content;
|
|
1498
|
+
const after = content.slice(idx + sectionHeader.length);
|
|
1499
|
+
const nextHeaderMatch = after.match(/\n\[[^\]]+\]/);
|
|
1500
|
+
const end = nextHeaderMatch && nextHeaderMatch.index !== void 0 ? idx + sectionHeader.length + nextHeaderMatch.index : content.length;
|
|
1501
|
+
const before = content.slice(0, idx).replace(/\n+$/, "");
|
|
1502
|
+
const rest = content.slice(end).replace(/^\n+/, "");
|
|
1503
|
+
return before.length > 0 && rest.length > 0 ? before + "\n" + rest : before + rest;
|
|
1504
|
+
}
|
|
1399
1505
|
function configureCodex(agent, proxyConfig, direct = false) {
|
|
1400
1506
|
const configDir = process.platform === "win32" ? path12.join(os7.homedir(), "AppData", "Roaming", "codex") : path12.join(os7.homedir(), ".codex");
|
|
1401
1507
|
const configPath = agent.configPath ?? path12.join(configDir, "config.toml");
|
|
@@ -1408,7 +1514,8 @@ function configureCodex(agent, proxyConfig, direct = false) {
|
|
|
1408
1514
|
content = upsertCodexDirectProvider(content, proxyConfig.apiKey);
|
|
1409
1515
|
} else {
|
|
1410
1516
|
content = setTomlKey(content, "openai_base_url", `http://localhost:${proxyConfig.openaiPort}`);
|
|
1411
|
-
content =
|
|
1517
|
+
content = setTomlKey(content, "model_provider", CODEX_PROXY_PROVIDER_ID);
|
|
1518
|
+
content = upsertCodexProxyProvider(content, proxyConfig.openaiPort);
|
|
1412
1519
|
content = removeCodexDirectProvider(content);
|
|
1413
1520
|
}
|
|
1414
1521
|
fs11.writeFileSync(configPath, content);
|
|
@@ -1482,6 +1589,7 @@ function unconfigureCodex(agent) {
|
|
|
1482
1589
|
content = removeTomlKey(content, "openai_base_url");
|
|
1483
1590
|
content = removeTomlKey(content, "model_provider");
|
|
1484
1591
|
content = removeCodexDirectProvider(content);
|
|
1592
|
+
content = removeCodexProxyProvider(content);
|
|
1485
1593
|
fs11.writeFileSync(configPath, content);
|
|
1486
1594
|
}
|
|
1487
1595
|
const backupPath = `${configPath}.skalpel-backup`;
|
|
@@ -1901,6 +2009,13 @@ async function runWizard(options) {
|
|
|
1901
2009
|
print11(` Configured ${agent.name}${agent.configPath ? ` (${agent.configPath})` : ""}`);
|
|
1902
2010
|
}
|
|
1903
2011
|
print11("");
|
|
2012
|
+
const codexConfigured = agentsToConfigure.some((a) => a.name === "codex");
|
|
2013
|
+
if (codexConfigured && !process.env.OPENAI_API_KEY) {
|
|
2014
|
+
print11(" [!] Codex expects OPENAI_API_KEY to be set. The Skalpel proxy ignores the value,");
|
|
2015
|
+
print11(" so any non-empty string works, e.g.:");
|
|
2016
|
+
print11(" export OPENAI_API_KEY=sk-codex-placeholder-skalpel");
|
|
2017
|
+
print11("");
|
|
2018
|
+
}
|
|
1904
2019
|
}
|
|
1905
2020
|
print11(" Installing proxy as system service...");
|
|
1906
2021
|
try {
|