@proxysoul/soulforge 2.13.2 → 2.14.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.
@@ -129,6 +129,9 @@ function createWorkerHandler(handlers, onInit, onDispose) {
129
129
  const allHandlers = {
130
130
  ...handlers,
131
131
  __memoryUsage: () => {
132
+ try {
133
+ Bun.gc(true);
134
+ } catch {}
132
135
  const usage = process.memoryUsage();
133
136
  return {
134
137
  heapUsed: usage.heapUsed,
@@ -41832,7 +41835,9 @@ var init_ui = __esm(() => {
41832
41835
  floatingTerminal: false,
41833
41836
  updateModal: false,
41834
41837
  mcpSettings: false,
41835
- tabNamePopup: false
41838
+ hearthSettings: false,
41839
+ tabNamePopup: false,
41840
+ uiDemo: false
41836
41841
  };
41837
41842
  useUIStore = create()(subscribeWithSelector((set2) => ({
41838
41843
  modals: {
@@ -41850,7 +41855,7 @@ var init_ui = __esm(() => {
41850
41855
  reasoningExpanded: {},
41851
41856
  suspended: false,
41852
41857
  editorSplit: 60,
41853
- lockIn: false,
41858
+ lockIn: true,
41854
41859
  openModal: (name21) => set2(() => ({
41855
41860
  modals: {
41856
41861
  ...INITIAL_MODALS,
@@ -49515,7 +49520,7 @@ var package_default;
49515
49520
  var init_package = __esm(() => {
49516
49521
  package_default = {
49517
49522
  name: "@proxysoul/soulforge",
49518
- version: "2.13.2",
49523
+ version: "2.14.1",
49519
49524
  description: "Graph-powered code intelligence \u2014 multi-agent coding with codebase-aware AI",
49520
49525
  repository: {
49521
49526
  type: "git",
@@ -49532,7 +49537,8 @@ var init_package = __esm(() => {
49532
49537
  author: "proxySoul",
49533
49538
  bin: {
49534
49539
  soulforge: "./dist/bin.sh",
49535
- sf: "./dist/bin.sh"
49540
+ sf: "./dist/bin.sh",
49541
+ "soulforge-remote": "./dist/soulforge-remote.sh"
49536
49542
  },
49537
49543
  engines: {
49538
49544
  bun: ">=1.2.0"
@@ -49566,14 +49572,14 @@ var init_package = __esm(() => {
49566
49572
  },
49567
49573
  devDependencies: {
49568
49574
  "@babel/core": "7.29.0",
49569
- "@biomejs/biome": "2.4.12",
49575
+ "@biomejs/biome": "2.4.13",
49570
49576
  "@types/babel__core": "7.20.5",
49571
- "@types/bun": "1.3.12",
49577
+ "@types/bun": "1.3.13",
49572
49578
  "@types/linkify-it": "5.0.0",
49573
49579
  "@types/node": "25.6.0",
49574
49580
  "@types/react": "19.2.14",
49575
49581
  "babel-plugin-react-compiler": "1.0.0",
49576
- "bun-types": "1.3.12",
49582
+ "bun-types": "1.3.13",
49577
49583
  typescript: "6.0.3"
49578
49584
  },
49579
49585
  dependencies: {
@@ -49588,25 +49594,26 @@ var init_package = __esm(() => {
49588
49594
  "@ai-sdk/openai": "3.0.53",
49589
49595
  "@ai-sdk/openai-compatible": "^2.0.41",
49590
49596
  "@ai-sdk/xai": "3.0.83",
49591
- "@anthropic-ai/sdk": "0.90.0",
49592
- "@llmgateway/ai-sdk-provider": "3.6.0",
49597
+ "@anthropic-ai/sdk": "0.91.1",
49598
+ "@llmgateway/ai-sdk-provider": "3.7.0",
49593
49599
  "@modelcontextprotocol/sdk": "^1.29.0",
49594
49600
  "@mozilla/readability": "0.6.0",
49595
- "@openrouter/ai-sdk-provider": "2.8.0",
49596
- "@opentui/react": "0.1.100",
49601
+ "@openrouter/ai-sdk-provider": "2.8.1",
49602
+ "@opentui/react": "0.2.0",
49597
49603
  ai: "6.0.168",
49598
- "ghostty-opentui": "1.4.10",
49604
+ "ghostty-opentui": "1.4.11",
49599
49605
  isbinaryfile: "6.0.0",
49600
49606
  jsonrepair: "^3.14.0",
49601
49607
  linkedom: "0.18.12",
49602
49608
  "linkify-it": "5.0.0",
49603
- marked: "18.0.1",
49609
+ marked: "18.0.2",
49604
49610
  neovim: "5.4.0",
49605
49611
  react: "19.2.5",
49606
49612
  shiki: "4.0.2",
49607
49613
  "strip-ansi": "7.2.0",
49608
49614
  "tree-sitter-wasms": "0.1.13",
49609
49615
  "ts-morph": "28.0.0",
49616
+ undici: "^8.1.0",
49610
49617
  "vercel-minimax-ai-provider": "^0.0.2",
49611
49618
  "web-tree-sitter": "0.25.10",
49612
49619
  zod: "4.3.6",
@@ -49634,26 +49641,36 @@ async function exchangeToken(githubToken) {
49634
49641
  if (cachedBearer && Date.now() / 1000 < cachedBearer.expiresAt - 60) {
49635
49642
  return cachedBearer.token;
49636
49643
  }
49637
- const res = await fetch(TOKEN_EXCHANGE, {
49638
- headers: {
49639
- Authorization: `Token ${githubToken}`,
49640
- "User-Agent": `SoulForge/${CURRENT_VERSION}`
49641
- },
49642
- signal: AbortSignal.timeout(1e4)
49643
- });
49644
- if (!res.ok) {
49645
- cachedBearer = null;
49646
- const body = await res.text().catch(() => "");
49647
- throw new Error(`Copilot token exchange failed (${String(res.status)})${body ? `: ${body.slice(0, 200)}` : ""}`);
49648
- }
49649
- const data = await res.json();
49650
- if (!data.token)
49651
- throw new Error("Copilot token exchange returned empty token");
49652
- cachedBearer = {
49653
- token: data.token,
49654
- expiresAt: data.expires_at
49655
- };
49656
- return data.token;
49644
+ if (bearerInflight)
49645
+ return bearerInflight;
49646
+ bearerInflight = (async () => {
49647
+ try {
49648
+ const res = await fetch(TOKEN_EXCHANGE, {
49649
+ headers: {
49650
+ Authorization: `Token ${githubToken}`,
49651
+ "User-Agent": `SoulForge/${CURRENT_VERSION}`
49652
+ },
49653
+ signal: AbortSignal.timeout(1e4)
49654
+ });
49655
+ if (!res.ok) {
49656
+ cachedBearer = null;
49657
+ const body = await res.text().catch(() => "");
49658
+ const hint = res.status === 401 || res.status === 403 ? " \u2014 your GitHub OAuth token is invalid or expired. Re-run the login flow in VS Code/JetBrains and copy the fresh oauth_token from ~/.config/github-copilot/apps.json." : res.status >= 500 ? " \u2014 GitHub is having issues, try again in a moment." : "";
49659
+ throw new Error(`Copilot token exchange failed (${String(res.status)})${body ? `: ${body.slice(0, 200)}` : ""}${hint}`);
49660
+ }
49661
+ const data = await res.json();
49662
+ if (!data.token)
49663
+ throw new Error("Copilot token exchange returned empty token");
49664
+ cachedBearer = {
49665
+ token: data.token,
49666
+ expiresAt: data.expires_at
49667
+ };
49668
+ return data.token;
49669
+ } finally {
49670
+ bearerInflight = null;
49671
+ }
49672
+ })();
49673
+ return bearerInflight;
49657
49674
  }
49658
49675
  function invalidateBearer() {
49659
49676
  cachedBearer = null;
@@ -49664,16 +49681,19 @@ function getGitHubToken() {
49664
49681
  return stored;
49665
49682
  throw new Error("GitHub Copilot requires an OAuth token. Sign in via VS Code or JetBrains, then copy oauth_token from ~/.config/github-copilot/apps.json and save it with /keys or --set-key copilot.");
49666
49683
  }
49667
- function needsResponsesApi(modelId) {
49668
- const id = modelId.toLowerCase();
49669
- if (id.includes("codex"))
49670
- return true;
49671
- if (id.startsWith("gpt-5"))
49672
- return true;
49673
- return false;
49674
- }
49675
- function isAnthropicModel(modelId) {
49676
- return modelId.toLowerCase().startsWith("claude");
49684
+ function detectInitiator(body) {
49685
+ if (typeof body !== "string")
49686
+ return "user";
49687
+ try {
49688
+ const parsed = JSON.parse(body);
49689
+ if (Array.isArray(parsed.messages)) {
49690
+ for (const m of parsed.messages) {
49691
+ if (m?.role === "assistant" || m?.role === "tool")
49692
+ return "agent";
49693
+ }
49694
+ }
49695
+ } catch {}
49696
+ return "user";
49677
49697
  }
49678
49698
  function createCopilotFetch(githubToken) {
49679
49699
  return async (url2, init) => {
@@ -49684,38 +49704,41 @@ function createCopilotFetch(githubToken) {
49684
49704
  invalidateBearer();
49685
49705
  bearer = await exchangeToken(githubToken);
49686
49706
  }
49687
- const headers = new Headers(init?.headers);
49688
- headers.set("Authorization", `Bearer ${bearer}`);
49707
+ const buildHeaders = (token) => {
49708
+ const h = new Headers(init?.headers);
49709
+ h.set("Authorization", `Bearer ${token}`);
49710
+ h.set("X-Request-Id", crypto.randomUUID());
49711
+ h.set("X-Initiator", detectInitiator(init?.body));
49712
+ return h;
49713
+ };
49689
49714
  const res = await fetch(url2, {
49690
49715
  ...init,
49691
- headers
49716
+ headers: buildHeaders(bearer)
49692
49717
  });
49693
49718
  if (res.status === 401) {
49694
49719
  invalidateBearer();
49695
49720
  const retryBearer = await exchangeToken(githubToken);
49696
- const retryHeaders = new Headers(init?.headers);
49697
- retryHeaders.set("Authorization", `Bearer ${retryBearer}`);
49698
49721
  return fetch(url2, {
49699
49722
  ...init,
49700
- headers: retryHeaders
49723
+ headers: buildHeaders(retryBearer)
49701
49724
  });
49702
49725
  }
49703
49726
  return res;
49704
49727
  };
49705
49728
  }
49729
+ function assertChatCompletionsSupported(modelId) {
49730
+ const endpoints = supportedEndpoints.get(modelId);
49731
+ if (!endpoints || endpoints.length === 0)
49732
+ return;
49733
+ const hasChat = endpoints.some((e) => e.includes("chat") || e.includes("completions"));
49734
+ if (hasChat)
49735
+ return;
49736
+ throw new Error(`Copilot model "${modelId}" only supports ${endpoints.join(", ")} \u2014 ` + "SoulForge routes Copilot through /chat/completions. " + "Try claude-sonnet-4.6, gpt-4.1, or another chat-compatible model.");
49737
+ }
49706
49738
  function createCopilotModel(modelId) {
49739
+ assertChatCompletionsSupported(modelId);
49707
49740
  const githubToken = getGitHubToken();
49708
49741
  const copilotFetch = createCopilotFetch(githubToken);
49709
- if (isAnthropicModel(modelId)) {
49710
- return createAnthropic({
49711
- baseURL: COPILOT_API,
49712
- apiKey: "copilot",
49713
- headers: {
49714
- ...COPILOT_HEADERS
49715
- },
49716
- fetch: copilotFetch
49717
- })(modelId);
49718
- }
49719
49742
  const client = createOpenAI({
49720
49743
  baseURL: COPILOT_API,
49721
49744
  apiKey: "copilot",
@@ -49724,22 +49747,23 @@ function createCopilotModel(modelId) {
49724
49747
  },
49725
49748
  fetch: copilotFetch
49726
49749
  });
49727
- if (needsResponsesApi(modelId)) {
49728
- return client.responses(modelId);
49729
- }
49730
49750
  return client.chat(modelId);
49731
49751
  }
49732
- var ENV_VAR = "COPILOT_API_KEY", COPILOT_API = "https://api.githubcopilot.com", TOKEN_EXCHANGE = "https://api.github.com/copilot_internal/v2/token", COPILOT_HEADERS, cachedBearer = null, copilot;
49752
+ var ENV_VAR = "COPILOT_API_KEY", COPILOT_API = "https://api.githubcopilot.com", TOKEN_EXCHANGE = "https://api.github.com/copilot_internal/v2/token", COPILOT_CHAT_VERSION = "0.26.7", COPILOT_API_VERSION = "2025-04-01", COPILOT_HEADERS, cachedBearer = null, bearerInflight = null, supportedEndpoints, copilot;
49733
49753
  var init_copilot = __esm(() => {
49734
- init_dist6();
49735
49754
  init_dist8();
49736
49755
  init_secrets();
49737
49756
  init_version();
49738
49757
  COPILOT_HEADERS = {
49739
- "Editor-Version": `SoulForge/${CURRENT_VERSION}`,
49740
- "Editor-Plugin-Version": `SoulForge/${CURRENT_VERSION}`,
49741
- "Copilot-Integration-Id": "vscode-chat"
49742
- };
49758
+ "Editor-Version": "vscode/1.95.0",
49759
+ "Editor-Plugin-Version": `copilot-chat/${COPILOT_CHAT_VERSION}`,
49760
+ "Copilot-Integration-Id": "vscode-chat",
49761
+ "User-Agent": `GitHubCopilotChat/${COPILOT_CHAT_VERSION}`,
49762
+ "OpenAI-Intent": "conversation-panel",
49763
+ "X-GitHub-Api-Version": COPILOT_API_VERSION,
49764
+ "X-VSCode-User-Agent-Library-Version": "electron-fetch"
49765
+ };
49766
+ supportedEndpoints = new Map;
49743
49767
  copilot = {
49744
49768
  id: "copilot",
49745
49769
  name: "GitHub Copilot",
@@ -49774,6 +49798,9 @@ var init_copilot = __esm(() => {
49774
49798
  continue;
49775
49799
  if (result.some((r) => r.id === m.id))
49776
49800
  continue;
49801
+ if (Array.isArray(m.supported_endpoints)) {
49802
+ supportedEndpoints.set(m.id, m.supported_endpoints);
49803
+ }
49777
49804
  result.push({
49778
49805
  id: m.id,
49779
49806
  name: m.id
@@ -57851,7 +57878,8 @@ function createLLMGateway(options = {}) {
57851
57878
  provider: "llmgateway.image",
57852
57879
  url: ({ path }) => `${baseURL}${path}`,
57853
57880
  headers: getHeaders,
57854
- fetch: options.fetch
57881
+ fetch: options.fetch,
57882
+ extraBody: options.extraBody
57855
57883
  });
57856
57884
  const createLanguageModel = (modelId, settings) => {
57857
57885
  if (new.target) {
@@ -59105,13 +59133,19 @@ var __defProp3, __defProps, __getOwnPropDescs, __getOwnPropSymbols, __hasOwnProp
59105
59133
  if (options.aspectRatio != null) {
59106
59134
  body.aspect_ratio = options.aspectRatio;
59107
59135
  }
59136
+ if (options.quality != null) {
59137
+ body.quality = options.quality;
59138
+ }
59139
+ const providerOptions = options.providerOptions || {};
59140
+ const llmgatewayOptions = providerOptions.llmgateway || {};
59141
+ const requestBody = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, body), this.config.extraBody), this.settings.extraBody), llmgatewayOptions);
59108
59142
  const { value: response, responseHeaders } = await postJsonToApi2({
59109
59143
  url: this.config.url({
59110
59144
  path: hasFiles ? "/images/edits" : "/images/generations",
59111
59145
  modelId: this.modelId
59112
59146
  }),
59113
59147
  headers: combineHeaders2(this.config.headers(), options.headers),
59114
- body,
59148
+ body: requestBody,
59115
59149
  failedResponseHandler: llmgatewayFailedResponseHandler,
59116
59150
  successfulResponseHandler: createJsonResponseHandler2(LLMGatewayImageResponseSchema),
59117
59151
  abortSignal: options.abortSignal,
@@ -71066,9 +71100,9 @@ function convertToOpenRouterChatMessages(prompt) {
71066
71100
  const parsedProviderOptions = OpenRouterProviderOptionsSchema.safeParse(providerOptions);
71067
71101
  const messageReasoningDetails = parsedProviderOptions.success ? (_e = (_d = parsedProviderOptions.data) == null ? undefined : _d.openrouter) == null ? undefined : _e.reasoning_details : undefined;
71068
71102
  const messageAnnotations = parsedProviderOptions.success ? (_g = (_f = parsedProviderOptions.data) == null ? undefined : _f.openrouter) == null ? undefined : _g.annotations : undefined;
71069
- const candidateReasoningDetails = messageReasoningDetails && Array.isArray(messageReasoningDetails) && messageReasoningDetails.length > 0 ? messageReasoningDetails : findFirstReasoningDetails(content);
71103
+ const candidateReasoningDetails = messageReasoningDetails && Array.isArray(messageReasoningDetails) ? messageReasoningDetails : findFirstReasoningDetails(content);
71070
71104
  let finalReasoningDetails;
71071
- if (candidateReasoningDetails && candidateReasoningDetails.length > 0) {
71105
+ if (candidateReasoningDetails) {
71072
71106
  const validDetails = candidateReasoningDetails.filter((detail) => {
71073
71107
  var _a173;
71074
71108
  if (detail.type !== "reasoning.text") {
@@ -71092,9 +71126,9 @@ function convertToOpenRouterChatMessages(prompt) {
71092
71126
  uniqueDetails.push(detail);
71093
71127
  }
71094
71128
  }
71095
- finalReasoningDetails = uniqueDetails.length > 0 ? uniqueDetails : undefined;
71129
+ finalReasoningDetails = uniqueDetails;
71096
71130
  }
71097
- const effectiveReasoning = reasoning && finalReasoningDetails ? reasoning : undefined;
71131
+ const effectiveReasoning = reasoning && finalReasoningDetails && finalReasoningDetails.length > 0 ? reasoning : undefined;
71098
71132
  messages.push({
71099
71133
  role: "assistant",
71100
71134
  content: text2,
@@ -72537,11 +72571,11 @@ var __defProp4, __defProps2, __getOwnPropDescs2, __getOwnPropSymbols2, __hasOwnP
72537
72571
  controller.enqueue({
72538
72572
  type: "reasoning-end",
72539
72573
  id: reasoningId || generateId5(),
72540
- providerMetadata: accumulatedReasoningDetails.length > 0 ? {
72574
+ providerMetadata: {
72541
72575
  openrouter: {
72542
72576
  reasoning_details: accumulatedReasoningDetails
72543
72577
  }
72544
- } : undefined
72578
+ }
72545
72579
  });
72546
72580
  reasoningStarted = false;
72547
72581
  }
@@ -72772,11 +72806,11 @@ var __defProp4, __defProps2, __getOwnPropDescs2, __getOwnPropSymbols2, __hasOwnP
72772
72806
  controller.enqueue({
72773
72807
  type: "reasoning-end",
72774
72808
  id: reasoningId || generateId5(),
72775
- providerMetadata: accumulatedReasoningDetails.length > 0 ? {
72809
+ providerMetadata: {
72776
72810
  openrouter: {
72777
72811
  reasoning_details: accumulatedReasoningDetails
72778
72812
  }
72779
- } : undefined
72813
+ }
72780
72814
  });
72781
72815
  }
72782
72816
  if (textStarted) {
@@ -72791,9 +72825,7 @@ var __defProp4, __defProps2, __getOwnPropDescs2, __getOwnPropSymbols2, __hasOwnP
72791
72825
  if (provider !== undefined) {
72792
72826
  openrouterMetadata.provider = provider;
72793
72827
  }
72794
- if (accumulatedReasoningDetails.length > 0) {
72795
- openrouterMetadata.reasoning_details = accumulatedReasoningDetails;
72796
- }
72828
+ openrouterMetadata.reasoning_details = accumulatedReasoningDetails;
72797
72829
  if (accumulatedFileAnnotations.length > 0) {
72798
72830
  openrouterMetadata.annotations = accumulatedFileAnnotations;
72799
72831
  }
@@ -73245,7 +73277,7 @@ var __defProp4, __defProps2, __getOwnPropDescs2, __getOwnPropSymbols2, __hasOwnP
73245
73277
  usage
73246
73278
  };
73247
73279
  }
73248
- }, DEFAULT_IMAGE_MEDIA_TYPE = "image/png", webSearchInputSchema2, webSearch2, VERSION22 = "2.8.0", VideoGenerationSubmitResponseSchema, VideoGenerationPollResponseSchema, DEFAULT_POLL_INTERVAL_MS = 2000, DEFAULT_MAX_POLL_TIME_MS = 600000, OpenRouterVideoModel = class {
73280
+ }, DEFAULT_IMAGE_MEDIA_TYPE = "image/png", webSearchInputSchema2, webSearch2, VERSION22 = "2.8.1", VideoGenerationSubmitResponseSchema, VideoGenerationPollResponseSchema, DEFAULT_POLL_INTERVAL_MS = 2000, DEFAULT_MAX_POLL_TIME_MS = 600000, OpenRouterVideoModel = class {
73249
73281
  constructor(modelId, settings, config2) {
73250
73282
  this.specificationVersion = "v3";
73251
73283
  this.provider = "openrouter";
@@ -74331,11 +74363,162 @@ var init_openrouter = __esm(() => {
74331
74363
  };
74332
74364
  });
74333
74365
 
74366
+ // src/core/proxy/key-resolver.ts
74367
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
74368
+ import { homedir as homedir5 } from "os";
74369
+ import { join as join7 } from "path";
74370
+ function candidateConfigPaths() {
74371
+ const paths = [process.env.PROXY_CONFIG_PATH, join7(homedir5(), ".soulforge", "proxy", "config.yaml"), "/opt/homebrew/etc/cliproxyapi.conf", "/opt/homebrew/etc/cliproxyapi/config.yaml", "/usr/local/etc/cliproxyapi.conf", "/usr/local/etc/cliproxyapi/config.yaml", "/etc/cliproxyapi.conf", "/etc/cliproxyapi/config.yaml", join7(homedir5(), ".config", "cliproxyapi", "config.yaml"), join7(homedir5(), ".cli-proxy-api", "config.yaml")];
74372
+ return paths.filter((p) => typeof p === "string" && p.length > 0);
74373
+ }
74374
+ function isPlausibleKey(value) {
74375
+ const s = value.trim();
74376
+ if (!s)
74377
+ return false;
74378
+ if (PLACEHOLDER_PATTERNS.some((re) => re.test(s)))
74379
+ return false;
74380
+ if (BCRYPT_RE.test(s))
74381
+ return false;
74382
+ if (SHA256_HEX_RE.test(s))
74383
+ return false;
74384
+ return true;
74385
+ }
74386
+ function parseApiKeys(content) {
74387
+ const lines = content.split(`
74388
+ `);
74389
+ const keys = [];
74390
+ let inList = false;
74391
+ let listIndent = -1;
74392
+ for (const rawLine of lines) {
74393
+ const line = rawLine.replace(/\r$/, "");
74394
+ const stripped = line.replace(/^\s*/, "");
74395
+ if (stripped.startsWith("#"))
74396
+ continue;
74397
+ if (!inList) {
74398
+ if (/^api-keys\s*:\s*$/.test(stripped) && !line.startsWith(" ") && !line.startsWith("\t")) {
74399
+ inList = true;
74400
+ listIndent = -1;
74401
+ }
74402
+ continue;
74403
+ }
74404
+ if (stripped === "")
74405
+ continue;
74406
+ const indentMatch = line.match(/^(\s+)/);
74407
+ const indent = indentMatch?.[1]?.length ?? 0;
74408
+ if (indent === 0)
74409
+ break;
74410
+ if (listIndent === -1)
74411
+ listIndent = indent;
74412
+ if (indent < listIndent)
74413
+ break;
74414
+ const item = line.slice(indent).match(/^-\s+(?:"([^"]*)"|'([^']*)'|(.+?))\s*$/);
74415
+ if (!item)
74416
+ continue;
74417
+ const v = item[1] ?? item[2] ?? item[3] ?? "";
74418
+ keys.push(v);
74419
+ }
74420
+ return keys;
74421
+ }
74422
+ function discoverApiKeys() {
74423
+ const out = [];
74424
+ const seen = new Set;
74425
+ for (const path of candidateConfigPaths()) {
74426
+ try {
74427
+ if (!existsSync2(path))
74428
+ continue;
74429
+ const content = readFileSync2(path, "utf-8");
74430
+ for (const k of parseApiKeys(content)) {
74431
+ if (!isPlausibleKey(k))
74432
+ continue;
74433
+ if (seen.has(k))
74434
+ continue;
74435
+ seen.add(k);
74436
+ out.push({
74437
+ key: k,
74438
+ source: path
74439
+ });
74440
+ }
74441
+ } catch {}
74442
+ }
74443
+ return out;
74444
+ }
74445
+ function primaryConfigPath() {
74446
+ for (const path of candidateConfigPaths()) {
74447
+ try {
74448
+ if (!existsSync2(path))
74449
+ continue;
74450
+ const content = readFileSync2(path, "utf-8");
74451
+ if (/^api-keys\s*:\s*$/m.test(content))
74452
+ return path;
74453
+ } catch {}
74454
+ }
74455
+ return null;
74456
+ }
74457
+ function getActiveProxyApiKey() {
74458
+ return cached2;
74459
+ }
74460
+ function setActiveProxyApiKey(key) {
74461
+ cached2 = key;
74462
+ cachedIsProbed = true;
74463
+ }
74464
+ function candidateApiKeys() {
74465
+ const out = [];
74466
+ const push = (v) => {
74467
+ if (!v)
74468
+ return;
74469
+ const s = v.trim();
74470
+ if (!s)
74471
+ return;
74472
+ if (!out.includes(s))
74473
+ out.push(s);
74474
+ };
74475
+ push(process.env.PROXY_API_KEY);
74476
+ push("soulforge");
74477
+ for (const d of discoverApiKeys())
74478
+ push(d.key);
74479
+ return out;
74480
+ }
74481
+ var PLACEHOLDER_PATTERNS, BCRYPT_RE, SHA256_HEX_RE, cached2, cachedIsProbed = false;
74482
+ var init_key_resolver = __esm(() => {
74483
+ PLACEHOLDER_PATTERNS = [/^your[-_]?(?:api[-_]?)?key/i, /^changeme$/i, /^replace[-_ ]?me/i, /^xxx+$/i];
74484
+ BCRYPT_RE = /^\$2[aby]\$/;
74485
+ SHA256_HEX_RE = /^[0-9a-f]{64}$/i;
74486
+ cached2 = process.env.PROXY_API_KEY?.trim() || "soulforge";
74487
+ });
74488
+
74334
74489
  // src/core/setup/install.ts
74335
74490
  import { execSync } from "child_process";
74336
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync } from "fs";
74337
- import { homedir as homedir5, platform } from "os";
74338
- import { join as join7 } from "path";
74491
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync } from "fs";
74492
+ import { homedir as homedir6, platform } from "os";
74493
+ import { join as join8 } from "path";
74494
+ async function fetchLatestProxyVersion(timeoutMs = 5000) {
74495
+ try {
74496
+ const ctl = new AbortController;
74497
+ const t = setTimeout(() => ctl.abort(), timeoutMs);
74498
+ const res = await fetch(PROXY_RELEASES_URL, {
74499
+ signal: ctl.signal,
74500
+ headers: {
74501
+ Accept: "application/vnd.github+json"
74502
+ }
74503
+ });
74504
+ clearTimeout(t);
74505
+ if (!res.ok)
74506
+ return null;
74507
+ const data = await res.json();
74508
+ return data.tag_name?.replace(/^v/, "")?.trim() || null;
74509
+ } catch {
74510
+ return null;
74511
+ }
74512
+ }
74513
+ async function resolveProxyVersion() {
74514
+ const env = process.env.SOULFORGE_PROXY_VERSION?.trim();
74515
+ if (env)
74516
+ return env;
74517
+ const latest = await fetchLatestProxyVersion();
74518
+ if (latest)
74519
+ return latest;
74520
+ return FALLBACK_PROXY_VERSION;
74521
+ }
74339
74522
  function getPlatformKey() {
74340
74523
  const key = `${process.platform}-${process.arch}`;
74341
74524
  if (key !== "darwin-arm64" && key !== "darwin-x64" && key !== "linux-x64" && key !== "linux-arm64") {
@@ -74347,22 +74530,22 @@ async function installBinary(config2) {
74347
74530
  ensureDirs();
74348
74531
  const key = getPlatformKey();
74349
74532
  const asset = config2.getAsset(key);
74350
- const extractDir = join7(INSTALLS_DIR, `${config2.name}-${config2.version}`);
74351
- if (!existsSync2(asset.binPath)) {
74533
+ const extractDir = join8(INSTALLS_DIR, `${config2.name}-${config2.version}`);
74534
+ if (!existsSync3(asset.binPath)) {
74352
74535
  await downloadAndExtract(asset.url, extractDir);
74353
74536
  }
74354
- if (!existsSync2(asset.binPath)) {
74537
+ if (!existsSync3(asset.binPath)) {
74355
74538
  throw new Error(`${config2.name} binary not found after extraction at ${asset.binPath}`);
74356
74539
  }
74357
74540
  execSync(`chmod +x "${asset.binPath}"`, {
74358
74541
  stdio: "ignore"
74359
74542
  });
74360
- createSymlink(asset.binPath, join7(BIN_DIR, config2.binName));
74361
- return join7(BIN_DIR, config2.binName);
74543
+ createSymlink(asset.binPath, join8(BIN_DIR, config2.binName));
74544
+ return join8(BIN_DIR, config2.binName);
74362
74545
  }
74363
74546
  function getVendoredPath(binary) {
74364
- const binLink = join7(BIN_DIR, binary);
74365
- return existsSync2(binLink) ? binLink : null;
74547
+ const binLink = join8(BIN_DIR, binary);
74548
+ return existsSync3(binLink) ? binLink : null;
74366
74549
  }
74367
74550
  function ensureDirs() {
74368
74551
  mkdirSync2(BIN_DIR, {
@@ -74380,7 +74563,7 @@ async function downloadAndExtract(url2, extractDir) {
74380
74563
  if (!response.ok) {
74381
74564
  throw new Error(`Download failed: ${response.status} ${response.statusText} (${url2})`);
74382
74565
  }
74383
- const tmpFile = join7(extractDir, "download.tar.gz");
74566
+ const tmpFile = join8(extractDir, "download.tar.gz");
74384
74567
  const buffer = await response.arrayBuffer();
74385
74568
  await Bun.write(tmpFile, buffer);
74386
74569
  execSync(`tar xzf "${tmpFile}" -C "${extractDir}"`, {
@@ -74389,14 +74572,14 @@ async function downloadAndExtract(url2, extractDir) {
74389
74572
  unlinkSync(tmpFile);
74390
74573
  }
74391
74574
  function createSymlink(target, link) {
74392
- if (existsSync2(link)) {
74575
+ if (existsSync3(link)) {
74393
74576
  unlinkSync(link);
74394
74577
  }
74395
74578
  symlinkSync(target, link);
74396
74579
  }
74397
74580
  async function installProxy(version2) {
74398
- const v = version2 ?? PROXY_VERSION;
74399
- return installBinary({
74581
+ const v = version2 ?? await resolveProxyVersion();
74582
+ const path = await installBinary({
74400
74583
  name: "cliproxyapi",
74401
74584
  binName: "cli-proxy-api",
74402
74585
  version: v,
@@ -74405,17 +74588,21 @@ async function installProxy(version2) {
74405
74588
  const asset = `CLIProxyAPI_${v}_${suffix}.tar.gz`;
74406
74589
  return {
74407
74590
  url: `https://github.com/router-for-me/CLIProxyAPI/releases/download/v${v}/${asset}`,
74408
- binPath: join7(INSTALLS_DIR, `cliproxyapi-${v}`, "cli-proxy-api")
74591
+ binPath: join8(INSTALLS_DIR, `cliproxyapi-${v}`, "cli-proxy-api")
74409
74592
  };
74410
74593
  }
74411
74594
  });
74595
+ return {
74596
+ path,
74597
+ version: v
74598
+ };
74412
74599
  }
74413
- var SOULFORGE_DIR, BIN_DIR, INSTALLS_DIR, FONTS_DIR, PROXY_VERSION = "6.9.28", PROXY_SUFFIXES;
74600
+ var SOULFORGE_DIR, BIN_DIR, INSTALLS_DIR, FONTS_DIR, FALLBACK_PROXY_VERSION = "6.9.29", PROXY_RELEASES_URL = "https://api.github.com/repos/router-for-me/CLIProxyAPI/releases/latest", PROXY_SUFFIXES;
74414
74601
  var init_install = __esm(() => {
74415
- SOULFORGE_DIR = join7(homedir5(), ".soulforge");
74416
- BIN_DIR = join7(SOULFORGE_DIR, "bin");
74417
- INSTALLS_DIR = join7(SOULFORGE_DIR, "installs");
74418
- FONTS_DIR = join7(SOULFORGE_DIR, "fonts");
74602
+ SOULFORGE_DIR = join8(homedir6(), ".soulforge");
74603
+ BIN_DIR = join8(SOULFORGE_DIR, "bin");
74604
+ INSTALLS_DIR = join8(SOULFORGE_DIR, "installs");
74605
+ FONTS_DIR = join8(SOULFORGE_DIR, "fonts");
74419
74606
  PROXY_SUFFIXES = {
74420
74607
  "darwin-arm64": "darwin_arm64",
74421
74608
  "darwin-x64": "darwin_amd64",
@@ -74426,80 +74613,51 @@ var init_install = __esm(() => {
74426
74613
 
74427
74614
  // src/core/proxy/lifecycle.ts
74428
74615
  import { execFileSync, execSync as execSync2, spawn as spawn4 } from "child_process";
74429
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
74430
- import { homedir as homedir6 } from "os";
74431
- import { join as join8 } from "path";
74616
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
74617
+ import { homedir as homedir7 } from "os";
74618
+ import { join as join9 } from "path";
74432
74619
  function setState(state, error48 = null) {
74433
74620
  currentState = state;
74434
74621
  lastError = error48;
74435
74622
  for (const fn of stateListeners)
74436
74623
  fn(state, error48);
74437
74624
  }
74438
- function getInstalledProxyVersion() {
74439
- try {
74440
- if (existsSync3(VERSION_FILE)) {
74441
- const v = readFileSync2(VERSION_FILE, "utf-8").trim();
74442
- if (v)
74443
- return v;
74444
- }
74445
- } catch {}
74446
- return PROXY_VERSION;
74447
- }
74448
74625
  function saveInstalledProxyVersion(version2) {
74449
74626
  mkdirSync3(PROXY_CONFIG_DIR, {
74450
74627
  recursive: true
74451
74628
  });
74452
74629
  writeFileSync2(VERSION_FILE, version2);
74453
74630
  }
74454
- function hasConflictingKeys(content) {
74455
- for (const line of content.split(`
74456
- `)) {
74457
- if (line.length === 0 || line[0] === "#" || line[0] === " " || line[0] === "\t")
74458
- continue;
74459
- const colon = line.indexOf(":");
74460
- if (colon === -1)
74461
- continue;
74462
- const key = line.slice(0, colon).trim();
74463
- if (PERF_KEYS.includes(key))
74464
- return true;
74465
- }
74466
- return false;
74631
+ function stripLegacyPerfBlock(content) {
74632
+ if (!content.includes(LEGACY_PERF_MARKER_PREFIX))
74633
+ return content;
74634
+ const lines = content.split(`
74635
+ `);
74636
+ const start = lines.findIndex((l) => l.startsWith(LEGACY_PERF_MARKER_PREFIX));
74637
+ if (start === -1)
74638
+ return content;
74639
+ let end = start + 1;
74640
+ while (end < lines.length && lines[end]?.trim() !== "")
74641
+ end++;
74642
+ lines.splice(start, end - start);
74643
+ return lines.join(`
74644
+ `);
74467
74645
  }
74468
74646
  function ensureConfig() {
74469
74647
  mkdirSync3(PROXY_CONFIG_DIR, {
74470
74648
  recursive: true
74471
74649
  });
74472
- if (!existsSync3(PROXY_CONFIG_PATH)) {
74473
- writeFileSync2(PROXY_CONFIG_PATH, ["host: 127.0.0.1", "port: 8317", 'auth-dir: "~/.cli-proxy-api"', "api-keys:", ' - "soulforge"', "", PERF_BLOCK, ""].join(`
74650
+ if (!existsSync4(PROXY_CONFIG_PATH)) {
74651
+ writeFileSync2(PROXY_CONFIG_PATH, ["host: 127.0.0.1", "port: 8317", 'auth-dir: "~/.cli-proxy-api"', "api-keys:", ' - "soulforge"', ""].join(`
74474
74652
  `));
74475
74653
  return;
74476
74654
  }
74477
74655
  try {
74478
- const existing = readFileSync2(PROXY_CONFIG_PATH, "utf-8");
74479
- if (existing.includes(PERF_MARKER))
74480
- return;
74481
- let cleaned = existing;
74482
- if (existing.includes(PERF_MARKER_PREFIX)) {
74483
- const lines = existing.split(`
74484
- `);
74485
- const start = lines.findIndex((l) => l.startsWith(PERF_MARKER_PREFIX));
74486
- if (start !== -1) {
74487
- let end = start + 1;
74488
- while (end < lines.length && lines[end]?.trim() !== "")
74489
- end++;
74490
- lines.splice(start, end - start);
74491
- cleaned = lines.join(`
74492
- `);
74493
- }
74656
+ const existing = readFileSync3(PROXY_CONFIG_PATH, "utf-8");
74657
+ const cleaned = stripLegacyPerfBlock(existing);
74658
+ if (cleaned !== existing) {
74659
+ writeFileSync2(PROXY_CONFIG_PATH, cleaned);
74494
74660
  }
74495
- if (hasConflictingKeys(cleaned))
74496
- return;
74497
- const sep = cleaned.endsWith(`
74498
- `) ? "" : `
74499
- `;
74500
- writeFileSync2(PROXY_CONFIG_PATH, `${cleaned}${sep}
74501
- ${PERF_BLOCK}
74502
- `);
74503
74661
  } catch {}
74504
74662
  }
74505
74663
  function commandExists(cmd) {
@@ -74512,24 +74670,76 @@ function commandExists(cmd) {
74512
74670
  return false;
74513
74671
  }
74514
74672
  }
74673
+ function getBinaryVersion(binary) {
74674
+ const parse5 = (s) => {
74675
+ const m = s.match(/Version:\s*(\d+\.\d+\.\d+)/);
74676
+ return m?.[1] ?? null;
74677
+ };
74678
+ try {
74679
+ const out = execFileSync(binary, ["-help"], {
74680
+ encoding: "utf-8",
74681
+ timeout: 2000,
74682
+ stdio: ["ignore", "pipe", "pipe"]
74683
+ });
74684
+ return parse5(out);
74685
+ } catch (err) {
74686
+ const e = err;
74687
+ const stdout = typeof e.stdout === "string" ? e.stdout : e.stdout?.toString() ?? "";
74688
+ const stderr = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString() ?? "";
74689
+ return parse5(stdout + stderr);
74690
+ }
74691
+ }
74692
+ function compareVersions(a, b) {
74693
+ const ap = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
74694
+ const bp = b.split(".").map((n) => Number.parseInt(n, 10) || 0);
74695
+ const len = Math.max(ap.length, bp.length);
74696
+ for (let i = 0;i < len; i++) {
74697
+ const diff = (ap[i] ?? 0) - (bp[i] ?? 0);
74698
+ if (diff !== 0)
74699
+ return diff;
74700
+ }
74701
+ return 0;
74702
+ }
74515
74703
  function getProxyBinary() {
74704
+ const systemBinary = commandExists("cli-proxy-api") ? "cli-proxy-api" : commandExists("cliproxyapi") ? "cliproxyapi" : null;
74516
74705
  const vendored = getVendoredPath("cli-proxy-api");
74517
- if (vendored)
74518
- return vendored;
74519
- if (commandExists("cli-proxy-api"))
74520
- return "cli-proxy-api";
74521
- if (commandExists("cliproxyapi"))
74522
- return "cliproxyapi";
74523
- return null;
74706
+ if (systemBinary && vendored) {
74707
+ const sysVersion = getBinaryVersion(systemBinary);
74708
+ const vendoredVersion = getBinaryVersion(vendored);
74709
+ if (sysVersion && vendoredVersion) {
74710
+ if (compareVersions(sysVersion, vendoredVersion) >= 0)
74711
+ return systemBinary;
74712
+ logBackgroundError("CLIProxyAPI", `system binary v${sysVersion} is older than vendored v${vendoredVersion} \u2014 using vendored`);
74713
+ return vendored;
74714
+ }
74715
+ return systemBinary;
74716
+ }
74717
+ return systemBinary ?? vendored;
74718
+ }
74719
+ function portIsOccupied() {
74720
+ const portMatch = PROXY_URL.match(/:([0-9]+)/);
74721
+ if (!portMatch)
74722
+ return false;
74723
+ const port = portMatch[1];
74724
+ try {
74725
+ const out = execFileSync("lsof", ["-ti", `tcp:${port}`], {
74726
+ encoding: "utf-8",
74727
+ timeout: 3000,
74728
+ stdio: ["ignore", "pipe", "ignore"]
74729
+ }).trim();
74730
+ return out.length > 0;
74731
+ } catch {
74732
+ return false;
74733
+ }
74524
74734
  }
74525
- async function healthCheck() {
74735
+ async function healthCheck(key) {
74526
74736
  try {
74527
74737
  const controller = new AbortController;
74528
74738
  const timeout = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
74529
74739
  const res = await fetch(`${PROXY_URL}/models`, {
74530
74740
  signal: controller.signal,
74531
74741
  headers: {
74532
- Authorization: `Bearer ${PROXY_API_KEY}`
74742
+ Authorization: `Bearer ${key}`
74533
74743
  }
74534
74744
  });
74535
74745
  clearTimeout(timeout);
@@ -74542,6 +74752,33 @@ async function healthCheck() {
74542
74752
  return "unreachable";
74543
74753
  }
74544
74754
  }
74755
+ async function probeForWorkingKey() {
74756
+ let sawAuthRequired = false;
74757
+ let sawUnreachable = false;
74758
+ for (const candidate of candidateApiKeys()) {
74759
+ const r = await healthCheck(candidate);
74760
+ if (r === "ok")
74761
+ return {
74762
+ key: candidate,
74763
+ state: "ok"
74764
+ };
74765
+ if (r === "auth-required")
74766
+ sawAuthRequired = true;
74767
+ if (r === "unreachable")
74768
+ sawUnreachable = true;
74769
+ }
74770
+ if (sawAuthRequired)
74771
+ return {
74772
+ state: "auth-required"
74773
+ };
74774
+ if (sawUnreachable)
74775
+ return {
74776
+ state: "unreachable"
74777
+ };
74778
+ return {
74779
+ state: "unreachable"
74780
+ };
74781
+ }
74545
74782
  async function ensureProxy() {
74546
74783
  if (currentState === "starting") {
74547
74784
  return {
@@ -74549,32 +74786,43 @@ async function ensureProxy() {
74549
74786
  error: "Proxy is already starting"
74550
74787
  };
74551
74788
  }
74552
- const installed = getInstalledProxyVersion();
74553
- const needsUpgrade = installed !== PROXY_VERSION;
74554
- if (needsUpgrade) {
74555
- stopProxy();
74556
- killProxyOnPort();
74557
- }
74558
- const health = await healthCheck();
74559
- if (health === "ok" && !needsUpgrade) {
74789
+ const probe = await probeForWorkingKey();
74790
+ if (probe.state === "ok") {
74791
+ setActiveProxyApiKey(probe.key);
74560
74792
  setState("running");
74561
74793
  return {
74562
74794
  ok: true
74563
74795
  };
74564
74796
  }
74565
- if (health === "auth-required") {
74797
+ if (probe.state === "auth-required") {
74798
+ const cfg = primaryConfigPath();
74799
+ const discovered = discoverApiKeys();
74800
+ if (cfg && discovered.length === 0) {
74801
+ const msg = `Proxy rejected every candidate API key. Edit ${cfg} (replace placeholder in \`api-keys:\`) or set PROXY_API_KEY, then restart the proxy.`;
74802
+ setState("needs-auth", msg);
74803
+ return {
74804
+ ok: false,
74805
+ error: msg
74806
+ };
74807
+ }
74566
74808
  setState("needs-auth", "Authentication required \u2014 run /proxy login");
74567
74809
  return {
74568
74810
  ok: false,
74569
74811
  error: "Authentication required \u2014 run /proxy login"
74570
74812
  };
74571
74813
  }
74814
+ if (portIsOccupied()) {
74815
+ logBackgroundError("CLIProxyAPI", "orphan process on port \u2014 clearing");
74816
+ killProxyOnPort();
74817
+ await new Promise((r) => setTimeout(r, 200));
74818
+ }
74572
74819
  setState("starting");
74573
74820
  let binary = getProxyBinary();
74574
74821
  if (!binary) {
74575
74822
  try {
74576
- binary = await installProxy();
74577
- saveInstalledProxyVersion(PROXY_VERSION);
74823
+ const installed = await installProxy();
74824
+ binary = installed.path;
74825
+ saveInstalledProxyVersion(installed.version);
74578
74826
  } catch (err) {
74579
74827
  const msg = toErrorMessage(err);
74580
74828
  setState("error", `Failed to install CLIProxyAPI: ${msg}`);
@@ -74620,7 +74868,7 @@ async function ensureProxy() {
74620
74868
  }
74621
74869
  for (let i = 0;i < STARTUP_POLL_ATTEMPTS; i++) {
74622
74870
  await new Promise((r) => setTimeout(r, STARTUP_POLL_MS));
74623
- const status = await healthCheck();
74871
+ const status = await healthCheck(getActiveProxyApiKey());
74624
74872
  if (status === "ok") {
74625
74873
  setState("running");
74626
74874
  return {
@@ -74657,7 +74905,7 @@ function stopProxy() {
74657
74905
  killProxyOnPort();
74658
74906
  setState("stopped");
74659
74907
  }
74660
- function killProxyOnPort() {
74908
+ function killProxyOnPort(force = false) {
74661
74909
  const portMatch = PROXY_URL.match(/:([0-9]+)/);
74662
74910
  if (!portMatch)
74663
74911
  return;
@@ -74666,13 +74914,15 @@ function killProxyOnPort() {
74666
74914
  try {
74667
74915
  out = execFileSync("lsof", ["-ti", `tcp:${port}`], {
74668
74916
  encoding: "utf-8",
74669
- timeout: 3000
74917
+ timeout: 3000,
74918
+ stdio: ["ignore", "pipe", "ignore"]
74670
74919
  }).trim();
74671
74920
  } catch {
74672
74921
  try {
74673
74922
  out = execFileSync("fuser", [`${port}/tcp`], {
74674
74923
  encoding: "utf-8",
74675
- timeout: 3000
74924
+ timeout: 3000,
74925
+ stdio: ["ignore", "pipe", "ignore"]
74676
74926
  }).trim();
74677
74927
  } catch {
74678
74928
  return;
@@ -74680,46 +74930,43 @@ function killProxyOnPort() {
74680
74930
  }
74681
74931
  if (!out)
74682
74932
  return;
74933
+ const signal = force ? "SIGKILL" : "SIGTERM";
74683
74934
  for (const token of out.split(/[\s\n]+/)) {
74684
74935
  const pid = Number.parseInt(token.trim(), 10);
74685
74936
  if (pid > 0 && pid !== process.pid) {
74686
74937
  try {
74687
- process.kill(pid, "SIGTERM");
74938
+ process.kill(pid, signal);
74688
74939
  } catch {}
74689
74940
  }
74690
74941
  }
74691
74942
  }
74692
- var proxyProcess = null, PROXY_URL, PROXY_API_KEY, PROXY_CONFIG_DIR, PROXY_CONFIG_PATH, HEALTH_TIMEOUT_MS = 2000, STARTUP_POLL_MS = 500, STARTUP_POLL_ATTEMPTS = 10, currentState = "stopped", lastError = null, stateListeners, VERSION_FILE, PERF_MARKER_PREFIX = "# soulforge-perf-defaults", PERF_MARKER_VERSION = 1, PERF_MARKER, PERF_KEYS, PERF_BLOCK, AUTH_DIR, VERSION_CACHE_TTL;
74943
+ var proxyProcess = null, PROXY_URL, PROXY_CONFIG_DIR, PROXY_CONFIG_PATH, HEALTH_TIMEOUT_MS = 2000, STARTUP_POLL_MS = 500, STARTUP_POLL_ATTEMPTS = 10, currentState = "stopped", lastError = null, stateListeners, VERSION_FILE, LEGACY_PERF_MARKER_PREFIX = "# soulforge-perf-defaults", AUTH_DIR, VERSION_CACHE_TTL;
74693
74944
  var init_lifecycle = __esm(() => {
74694
74945
  init_errors4();
74695
74946
  init_process_tracker();
74696
74947
  init_install();
74948
+ init_key_resolver();
74697
74949
  PROXY_URL = process.env.PROXY_API_URL || "http://127.0.0.1:8317/v1";
74698
- PROXY_API_KEY = process.env.PROXY_API_KEY || "soulforge";
74699
- PROXY_CONFIG_DIR = join8(homedir6(), ".soulforge", "proxy");
74700
- PROXY_CONFIG_PATH = join8(PROXY_CONFIG_DIR, "config.yaml");
74950
+ PROXY_CONFIG_DIR = join9(homedir7(), ".soulforge", "proxy");
74951
+ PROXY_CONFIG_PATH = join9(PROXY_CONFIG_DIR, "config.yaml");
74701
74952
  stateListeners = new Set;
74702
- VERSION_FILE = join8(PROXY_CONFIG_DIR, "version");
74703
- PERF_MARKER = `${PERF_MARKER_PREFIX} v${String(PERF_MARKER_VERSION)}`;
74704
- PERF_KEYS = ["request-retry", "max-retry-interval", "max-retry-credentials", "streaming", "nonstream-keepalive-interval"];
74705
- PERF_BLOCK = [PERF_MARKER, "request-retry: 1", "max-retry-interval: 10", "max-retry-credentials: 2", "streaming:", " keepalive-seconds: 15", " bootstrap-retries: 1", "nonstream-keepalive-interval: 30"].join(`
74706
- `);
74707
- AUTH_DIR = join8(homedir6(), ".cli-proxy-api");
74953
+ VERSION_FILE = join9(PROXY_CONFIG_DIR, "version");
74954
+ AUTH_DIR = join9(homedir7(), ".cli-proxy-api");
74708
74955
  VERSION_CACHE_TTL = 10 * 60 * 1000;
74709
74956
  });
74710
74957
 
74711
74958
  // src/core/llm/providers/proxy.ts
74712
- function isAnthropicModel2(modelId) {
74959
+ function isAnthropicModel(modelId) {
74713
74960
  return modelId.toLowerCase().startsWith("claude");
74714
74961
  }
74715
- var baseURL, apiKey, proxy;
74962
+ var baseURL, proxy;
74716
74963
  var init_proxy = __esm(() => {
74717
74964
  init_dist6();
74718
74965
  init_dist8();
74966
+ init_key_resolver();
74719
74967
  init_lifecycle();
74720
74968
  init_context_windows();
74721
74969
  baseURL = process.env.PROXY_API_URL || "http://127.0.0.1:8317/v1";
74722
- apiKey = process.env.PROXY_API_KEY || "soulforge";
74723
74970
  proxy = {
74724
74971
  id: "proxy",
74725
74972
  name: "Proxy",
@@ -74728,7 +74975,8 @@ var init_proxy = __esm(() => {
74728
74975
  asciiIcon: "\u26E8",
74729
74976
  grouped: true,
74730
74977
  createModel(modelId) {
74731
- if (isAnthropicModel2(modelId)) {
74978
+ const apiKey = getActiveProxyApiKey();
74979
+ if (isAnthropicModel(modelId)) {
74732
74980
  return createAnthropic({
74733
74981
  baseURL,
74734
74982
  apiKey
@@ -74838,8 +75086,8 @@ var init_vercel_gateway = __esm(() => {
74838
75086
  description: "Vercel AI Gateway",
74839
75087
  grouped: true,
74840
75088
  createModel(modelId) {
74841
- const apiKey2 = getProviderApiKey("AI_GATEWAY_API_KEY");
74842
- if (!apiKey2) {
75089
+ const apiKey = getProviderApiKey("AI_GATEWAY_API_KEY");
75090
+ if (!apiKey) {
74843
75091
  throw new Error("AI_GATEWAY_API_KEY is not set");
74844
75092
  }
74845
75093
  return gateway(modelId);
@@ -77807,21 +78055,21 @@ var init_xai = __esm(() => {
77807
78055
  asciiIcon: "X",
77808
78056
  description: "Grok models",
77809
78057
  createModel(modelId) {
77810
- const apiKey2 = getProviderApiKey("XAI_API_KEY");
77811
- if (!apiKey2) {
78058
+ const apiKey = getProviderApiKey("XAI_API_KEY");
78059
+ if (!apiKey) {
77812
78060
  throw new Error("XAI_API_KEY is not set");
77813
78061
  }
77814
78062
  return createXai({
77815
- apiKey: apiKey2
78063
+ apiKey
77816
78064
  })(modelId);
77817
78065
  },
77818
78066
  async fetchModels() {
77819
- const apiKey2 = getProviderApiKey("XAI_API_KEY");
77820
- if (!apiKey2)
78067
+ const apiKey = getProviderApiKey("XAI_API_KEY");
78068
+ if (!apiKey)
77821
78069
  return null;
77822
78070
  const res = await fetch("https://api.x.ai/v1/models", {
77823
78071
  headers: {
77824
- Authorization: `Bearer ${apiKey2}`
78072
+ Authorization: `Bearer ${apiKey}`
77825
78073
  }
77826
78074
  });
77827
78075
  if (!res.ok)
@@ -78120,9 +78368,9 @@ var init_summarize = __esm(() => {
78120
78368
  });
78121
78369
 
78122
78370
  // src/core/workers/io.worker.ts
78123
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync3, statSync } from "fs";
78371
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync } from "fs";
78124
78372
  import { readFile, rename, writeFile as writeFile2 } from "fs/promises";
78125
- import { extname, join as join9 } from "path";
78373
+ import { extname, join as join10 } from "path";
78126
78374
 
78127
78375
  // node_modules/isbinaryfile/lib/index.js
78128
78376
  import { open, stat } from "fs/promises";
@@ -78496,7 +78744,7 @@ var handlers = {
78496
78744
  const dir = sessionDir;
78497
78745
  const sessionMeta = meta3;
78498
78746
  const entries = tabEntries;
78499
- if (!existsSync4(dir)) {
78747
+ if (!existsSync5(dir)) {
78500
78748
  mkdirSync4(dir, {
78501
78749
  recursive: true,
78502
78750
  mode: 448
@@ -78524,8 +78772,8 @@ var handlers = {
78524
78772
  const metaJson = JSON.stringify(updatedMeta, null, 2);
78525
78773
  const lines = allMessages.map((m) => JSON.stringify(m)).join(`
78526
78774
  `);
78527
- const metaPath = join9(dir, "meta.json");
78528
- const jsonlPath = join9(dir, "messages.jsonl");
78775
+ const metaPath = join10(dir, "meta.json");
78776
+ const jsonlPath = join10(dir, "messages.jsonl");
78529
78777
  const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
78530
78778
  const metaTmp = `${metaPath}.${suffix}.tmp`;
78531
78779
  const jsonlTmp = `${jsonlPath}.${suffix}.tmp`;
@@ -78546,7 +78794,7 @@ var handlers = {
78546
78794
  for (const [tabId, msgs] of cores) {
78547
78795
  coreData[tabId] = msgs;
78548
78796
  }
78549
- const corePath = join9(dir, "core.json");
78797
+ const corePath = join10(dir, "core.json");
78550
78798
  const coreTmp = `${corePath}.${suffix}.tmp`;
78551
78799
  await writeFile2(coreTmp, JSON.stringify(coreData), {
78552
78800
  encoding: "utf-8",
@@ -78557,14 +78805,14 @@ var handlers = {
78557
78805
  },
78558
78806
  loadSession: async (sessionDir) => {
78559
78807
  const dir = sessionDir;
78560
- const metaPath = join9(dir, "meta.json");
78561
- if (!existsSync4(metaPath))
78808
+ const metaPath = join10(dir, "meta.json");
78809
+ if (!existsSync5(metaPath))
78562
78810
  return null;
78563
- const meta3 = JSON.parse(readFileSync3(metaPath, "utf-8"));
78564
- const jsonlPath = join9(dir, "messages.jsonl");
78811
+ const meta3 = JSON.parse(readFileSync4(metaPath, "utf-8"));
78812
+ const jsonlPath = join10(dir, "messages.jsonl");
78565
78813
  const allMessages = [];
78566
- if (existsSync4(jsonlPath)) {
78567
- const content = readFileSync3(jsonlPath, "utf-8").trim();
78814
+ if (existsSync5(jsonlPath)) {
78815
+ const content = readFileSync4(jsonlPath, "utf-8").trim();
78568
78816
  if (content) {
78569
78817
  for (const line of content.split(`
78570
78818
  `)) {
@@ -78586,11 +78834,11 @@ var handlers = {
78586
78834
  } = tab.messageRange;
78587
78835
  tabEntries.push([tab.id, allMessages.slice(startLine, endLine)]);
78588
78836
  }
78589
- const corePath = join9(dir, "core.json");
78837
+ const corePath = join10(dir, "core.json");
78590
78838
  let coreEntries;
78591
- if (existsSync4(corePath)) {
78839
+ if (existsSync5(corePath)) {
78592
78840
  try {
78593
- const coreData = JSON.parse(readFileSync3(corePath, "utf-8"));
78841
+ const coreData = JSON.parse(readFileSync4(corePath, "utf-8"));
78594
78842
  coreEntries = Object.entries(coreData);
78595
78843
  } catch {}
78596
78844
  }
@@ -78602,27 +78850,27 @@ var handlers = {
78602
78850
  },
78603
78851
  listSessions: (sessionsDir) => {
78604
78852
  const dir = sessionsDir;
78605
- if (!existsSync4(dir))
78853
+ if (!existsSync5(dir))
78606
78854
  return [];
78607
78855
  try {
78608
78856
  const entries = readdirSync4(dir);
78609
78857
  const metas = [];
78610
78858
  for (const entry of entries) {
78611
78859
  try {
78612
- const fullPath = join9(dir, entry);
78860
+ const fullPath = join10(dir, entry);
78613
78861
  const s = statSync(fullPath);
78614
78862
  if (!s.isDirectory())
78615
78863
  continue;
78616
- const metaPath = join9(fullPath, "meta.json");
78617
- if (!existsSync4(metaPath))
78864
+ const metaPath = join10(fullPath, "meta.json");
78865
+ if (!existsSync5(metaPath))
78618
78866
  continue;
78619
- const raw = readFileSync3(metaPath, "utf-8");
78867
+ const raw = readFileSync4(metaPath, "utf-8");
78620
78868
  const meta3 = JSON.parse(raw);
78621
78869
  const totalMessages = (meta3.tabs ?? []).reduce((sum, t) => sum + (t.messageRange.endLine - t.messageRange.startLine), 0);
78622
78870
  let sizeBytes = 0;
78623
78871
  for (const file2 of ["meta.json", "messages.jsonl"]) {
78624
78872
  try {
78625
- sizeBytes += statSync(join9(fullPath, file2)).size;
78873
+ sizeBytes += statSync(join10(fullPath, file2)).size;
78626
78874
  } catch {}
78627
78875
  }
78628
78876
  metas.push({