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 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
- const icons = { ok: "+", warn: "!", fail: "x" };
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 = removeTomlKey(content, "model_provider");
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 {