demian-cli 1.0.8 → 1.1.0

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 CHANGED
@@ -116,8 +116,11 @@ Create `~/.demian/config.jsond`:
116
116
  "openai": {
117
117
  "type": "openai-compatible",
118
118
  "baseURL": "https://api.openai.com/v1",
119
+ "apiKey": "",
119
120
  "apiKeyEnv": "OPENAI_API_KEY",
120
- "model": "gpt-5.5"
121
+ "modelProfiles": [
122
+ { "name": "main", "displayName": "GPT 5.5", "model": "gpt-5.5" }
123
+ ]
121
124
  }
122
125
  }
123
126
  }
@@ -130,6 +133,11 @@ export OPENAI_API_KEY="..."
130
133
  demian --provider openai --model gpt-5.5
131
134
  ```
132
135
 
136
+ Providers that can expose many local or self-hosted models, such as `lmstudio`,
137
+ `ollama-local`, `ollama-cloud`, `llamacpp`, and `vllm`, also use
138
+ `modelProfiles`. Add one array entry per model you want to show in the model
139
+ selector.
140
+
133
141
  ### Local Model Example: Ollama
134
142
 
135
143
  ```sh
@@ -307,12 +315,15 @@ Demian supports Brave, Tavily, and Exa through the `web_search` tool.
307
315
  "defaultProvider": "brave",
308
316
  "providers": {
309
317
  "brave": {
318
+ "apiKey": "",
310
319
  "apiKeyEnv": "BRAVE_SEARCH_API_KEY"
311
320
  },
312
321
  "tavily": {
322
+ "apiKey": "",
313
323
  "apiKeyEnv": "TAVILY_API_KEY"
314
324
  },
315
325
  "exa": {
326
+ "apiKey": "",
316
327
  "apiKeyEnv": "EXA_API_KEY"
317
328
  }
318
329
  }
@@ -334,7 +345,7 @@ Demian uses both user-level and project-level storage:
334
345
  - `.demian/goals/`: active and archived goal state.
335
346
  - `.demian/`: transcripts and workspace-scoped runtime files.
336
347
 
337
- Do not store API keys in shared project config. Prefer `apiKeyEnv`.
348
+ Leave `apiKey` empty and prefer `apiKeyEnv` for shared project config. Paste into `apiKey` only for local private config.
338
349
 
339
350
  ## Troubleshooting
340
351
 
package/dist/cli.mjs CHANGED
@@ -23639,6 +23639,7 @@ var defaultConfig = {
23639
23639
  defaultProvider: "brave",
23640
23640
  providers: {
23641
23641
  brave: {
23642
+ apiKey: "",
23642
23643
  apiKeyEnv: "BRAVE_SEARCH_API_KEY",
23643
23644
  endpoint: "https://api.search.brave.com/res/v1/web/search",
23644
23645
  count: 10,
@@ -23647,12 +23648,14 @@ var defaultConfig = {
23647
23648
  safeSearch: "moderate"
23648
23649
  },
23649
23650
  tavily: {
23651
+ apiKey: "",
23650
23652
  apiKeyEnv: "TAVILY_API_KEY",
23651
23653
  endpoint: "https://api.tavily.com/search",
23652
23654
  maxResults: 5,
23653
23655
  searchDepth: "basic"
23654
23656
  },
23655
23657
  exa: {
23658
+ apiKey: "",
23656
23659
  apiKeyEnv: "EXA_API_KEY",
23657
23660
  endpoint: "https://api.exa.ai/search",
23658
23661
  numResults: 5,
@@ -23707,6 +23710,7 @@ var defaultConfig = {
23707
23710
  openai: {
23708
23711
  type: "openai-compatible",
23709
23712
  baseURL: "https://api.openai.com/v1",
23713
+ apiKey: "",
23710
23714
  apiKeyEnv: "OPENAI_API_KEY",
23711
23715
  catalog: {
23712
23716
  type: "openai-models",
@@ -23716,6 +23720,7 @@ var defaultConfig = {
23716
23720
  anthropic: {
23717
23721
  type: "anthropic",
23718
23722
  baseURL: "https://api.anthropic.com/v1",
23723
+ apiKey: "",
23719
23724
  apiKeyEnv: "ANTHROPIC_API_KEY",
23720
23725
  catalog: {
23721
23726
  type: "anthropic-models",
@@ -23725,6 +23730,7 @@ var defaultConfig = {
23725
23730
  gemini: {
23726
23731
  type: "openai-compatible",
23727
23732
  baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
23733
+ apiKey: "",
23728
23734
  apiKeyEnv: "GEMINI_API_KEY",
23729
23735
  apiKeyEnvAliases: ["GOOGLE_API_KEY"],
23730
23736
  catalog: {
@@ -23735,6 +23741,7 @@ var defaultConfig = {
23735
23741
  groq: {
23736
23742
  type: "openai-compatible",
23737
23743
  baseURL: "https://api.groq.com/openai/v1",
23744
+ apiKey: "",
23738
23745
  apiKeyEnv: "GROQ_API_KEY",
23739
23746
  catalog: {
23740
23747
  type: "groq-models",
@@ -23750,6 +23757,7 @@ var defaultConfig = {
23750
23757
  displayName: "Azure example",
23751
23758
  model: "azure-deployment-name",
23752
23759
  baseURL: "https://example.openai.azure.com/openai/v1",
23760
+ apiKey: "",
23753
23761
  apiKeyEnv: "AZURE_OPENAI_API_KEY"
23754
23762
  }
23755
23763
  ]
@@ -23758,6 +23766,7 @@ var defaultConfig = {
23758
23766
  type: "openai-compatible",
23759
23767
  baseURL: "http://localhost:1234/v1",
23760
23768
  apiKey: "lm-studio",
23769
+ modelProfiles: [],
23761
23770
  catalog: {
23762
23771
  type: "openai-compatible-models",
23763
23772
  endpoint: "http://localhost:1234/v1/models"
@@ -23767,6 +23776,7 @@ var defaultConfig = {
23767
23776
  type: "openai-compatible",
23768
23777
  baseURL: "http://localhost:11434/v1",
23769
23778
  apiKey: "ollama",
23779
+ modelProfiles: [],
23770
23780
  quirks: { omitTemperature: true },
23771
23781
  catalog: {
23772
23782
  type: "openai-compatible-models",
@@ -23776,7 +23786,9 @@ var defaultConfig = {
23776
23786
  "ollama-cloud": {
23777
23787
  type: "ollama",
23778
23788
  baseURL: "https://ollama.com/api",
23789
+ apiKey: "",
23779
23790
  apiKeyEnv: "OLLAMA_API_KEY",
23791
+ modelProfiles: [],
23780
23792
  catalog: {
23781
23793
  type: "ollama-tags",
23782
23794
  endpoint: "https://ollama.com/api/tags"
@@ -23786,6 +23798,7 @@ var defaultConfig = {
23786
23798
  type: "openai-compatible",
23787
23799
  baseURL: "http://localhost:8080/v1",
23788
23800
  apiKey: "llama.cpp",
23801
+ modelProfiles: [],
23789
23802
  catalog: {
23790
23803
  type: "openai-compatible-models",
23791
23804
  endpoint: "http://localhost:8080/v1/models"
@@ -23795,6 +23808,7 @@ var defaultConfig = {
23795
23808
  type: "openai-compatible",
23796
23809
  baseURL: "http://localhost:8000/v1",
23797
23810
  apiKey: "vllm",
23811
+ modelProfiles: [],
23798
23812
  catalog: {
23799
23813
  type: "openai-compatible-models",
23800
23814
  endpoint: "http://localhost:8000/v1/models"
@@ -24127,11 +24141,18 @@ function providerModelProfiles(providerName, providerConfig) {
24127
24141
  return out;
24128
24142
  }
24129
24143
  function providerModelOptions(providerName, providerConfig) {
24130
- return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => profile.displayName ?? profile.name ?? profile.model);
24144
+ return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => displayModelForProfile(profile));
24131
24145
  }
24132
24146
  function defaultModelForProvider(providerName, providerConfig) {
24133
24147
  return providerModelProfiles(providerName, providerConfig)[0]?.model ?? "";
24134
24148
  }
24149
+ function displayModelForProfile(profile) {
24150
+ return profile.displayName?.trim() || profile.name?.trim() || profile.model;
24151
+ }
24152
+ function defaultDisplayModelForProvider(providerName, providerConfig) {
24153
+ const profile = providerModelProfiles(providerName, providerConfig)[0];
24154
+ return profile ? displayModelForProfile(profile) : defaultModelForProvider(providerName, providerConfig);
24155
+ }
24135
24156
  function resolveConfiguredApiKey(providerConfig) {
24136
24157
  if (providerConfig.apiKey) return { value: providerConfig.apiKey };
24137
24158
  for (const envName of [providerConfig.apiKeyEnv, ...providerConfig.apiKeyEnvAliases ?? []]) {
@@ -24168,7 +24189,7 @@ function mergeModelProfile(providerConfig, profile) {
24168
24189
  ...providerConfig,
24169
24190
  ...definedOnly({
24170
24191
  baseURL: profile.baseURL,
24171
- apiKey: profile.apiKey,
24192
+ apiKey: nonEmptyString(profile.apiKey),
24172
24193
  apiKeyEnv: profile.apiKeyEnv,
24173
24194
  apiKeyEnvAliases: profile.apiKeyEnvAliases,
24174
24195
  auth: profile.auth,
@@ -24183,6 +24204,9 @@ function mergeModelProfile(providerConfig, profile) {
24183
24204
  function definedOnly(input2) {
24184
24205
  return Object.fromEntries(Object.entries(input2).filter(([, value]) => value !== void 0));
24185
24206
  }
24207
+ function nonEmptyString(value) {
24208
+ return typeof value === "string" && value.length > 0 ? value : void 0;
24209
+ }
24186
24210
  function fallbackModelForProvider(providerName, providerConfig) {
24187
24211
  if (typeof providerConfig.model === "string" && providerConfig.model.trim()) return providerConfig.model.trim();
24188
24212
  if (providerName === "openai") return "gpt-5.5";
@@ -24439,18 +24463,21 @@ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
24439
24463
  openai: {
24440
24464
  type: "openai-compatible",
24441
24465
  baseURL: "https://api.openai.com/v1",
24466
+ apiKey: "",
24442
24467
  apiKeyEnv: "OPENAI_API_KEY",
24443
24468
  catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
24444
24469
  },
24445
24470
  anthropic: {
24446
24471
  type: "anthropic",
24447
24472
  baseURL: "https://api.anthropic.com/v1",
24473
+ apiKey: "",
24448
24474
  apiKeyEnv: "ANTHROPIC_API_KEY",
24449
24475
  catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
24450
24476
  },
24451
24477
  gemini: {
24452
24478
  type: "openai-compatible",
24453
24479
  baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
24480
+ apiKey: "",
24454
24481
  apiKeyEnv: "GEMINI_API_KEY",
24455
24482
  apiKeyEnvAliases: ["GOOGLE_API_KEY"],
24456
24483
  catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
@@ -24458,6 +24485,7 @@ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
24458
24485
  groq: {
24459
24486
  type: "openai-compatible",
24460
24487
  baseURL: "https://api.groq.com/openai/v1",
24488
+ apiKey: "",
24461
24489
  apiKeyEnv: "GROQ_API_KEY",
24462
24490
  catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
24463
24491
  },
@@ -24470,6 +24498,7 @@ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
24470
24498
  displayName: "Azure example",
24471
24499
  model: "azure-deployment-name",
24472
24500
  baseURL: "https://example.openai.azure.com/openai/v1",
24501
+ apiKey: "",
24473
24502
  apiKeyEnv: "AZURE_OPENAI_API_KEY"
24474
24503
  }
24475
24504
  ]
@@ -24478,31 +24507,37 @@ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
24478
24507
  type: "openai-compatible",
24479
24508
  baseURL: "http://localhost:1234/v1",
24480
24509
  apiKey: "lm-studio",
24510
+ modelProfiles: [],
24481
24511
  catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
24482
24512
  },
24483
24513
  "ollama-local": {
24484
24514
  type: "openai-compatible",
24485
24515
  baseURL: "http://localhost:11434/v1",
24486
24516
  apiKey: "ollama",
24517
+ modelProfiles: [],
24487
24518
  quirks: { omitTemperature: true },
24488
24519
  catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
24489
24520
  },
24490
24521
  "ollama-cloud": {
24491
24522
  type: "ollama",
24492
24523
  baseURL: "https://ollama.com/api",
24524
+ apiKey: "",
24493
24525
  apiKeyEnv: "OLLAMA_API_KEY",
24526
+ modelProfiles: [],
24494
24527
  catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
24495
24528
  },
24496
24529
  llamacpp: {
24497
24530
  type: "openai-compatible",
24498
24531
  baseURL: "http://localhost:8080/v1",
24499
24532
  apiKey: "llama.cpp",
24533
+ modelProfiles: [],
24500
24534
  catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
24501
24535
  },
24502
24536
  vllm: {
24503
24537
  type: "openai-compatible",
24504
24538
  baseURL: "http://localhost:8000/v1",
24505
24539
  apiKey: "vllm",
24540
+ modelProfiles: [],
24506
24541
  catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
24507
24542
  },
24508
24543
  codex: {
@@ -24574,7 +24609,7 @@ async function addModelProfile(options) {
24574
24609
  displayName,
24575
24610
  model: options.model,
24576
24611
  ...options.baseURL ? { baseURL: options.baseURL } : {},
24577
- ...options.apiKey ? { apiKey: options.apiKey } : {},
24612
+ ...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
24578
24613
  ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
24579
24614
  };
24580
24615
  if (existingByName >= 0) profiles[existingByName] = next;
@@ -24596,18 +24631,15 @@ async function readConfigObject(filePath) {
24596
24631
  }
24597
24632
  function providerPreset(options) {
24598
24633
  const preset = options.preset ?? options.name;
24599
- const auth = {
24600
- ...options.apiKey ? { apiKey: options.apiKey } : {},
24601
- ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
24602
- };
24634
+ const auth = apiKeyAuthFields(options);
24603
24635
  const openAIAuth = options.authHeader ? { auth: { type: "api-key", header: options.authHeader } } : {};
24604
- if (preset === "openai") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.openai.com/v1", apiKeyEnv: "OPENAI_API_KEY", ...auth, ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
24605
- if (preset === "anthropic") return { type: "anthropic", baseURL: options.baseURL ?? "https://api.anthropic.com/v1", apiKeyEnv: "ANTHROPIC_API_KEY", ...auth, catalog: { type: "anthropic-models", endpoint: `${(options.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
24636
+ if (preset === "openai") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
24637
+ if (preset === "anthropic") return { type: "anthropic", baseURL: options.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
24606
24638
  if (preset === "gemini") {
24607
24639
  const geminiBase = options.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
24608
- return { type: "openai-compatible", baseURL: geminiBase, apiKeyEnv: "GEMINI_API_KEY", apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...auth, ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
24640
+ return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
24609
24641
  }
24610
- if (preset === "groq") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.groq.com/openai/v1", apiKeyEnv: "GROQ_API_KEY", ...auth, ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
24642
+ if (preset === "groq") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
24611
24643
  if (preset === "azure") {
24612
24644
  return {
24613
24645
  type: "openai-compatible",
@@ -24619,16 +24651,17 @@ function providerPreset(options) {
24619
24651
  displayName: "Azure example",
24620
24652
  model: "azure-deployment-name",
24621
24653
  baseURL: options.baseURL ?? "https://example.openai.azure.com/openai/v1",
24654
+ ...options.apiKey ? {} : { apiKey: "" },
24622
24655
  apiKeyEnv: options.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
24623
24656
  }
24624
24657
  ]
24625
24658
  };
24626
24659
  }
24627
- if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:1234/v1", apiKey: options.apiKey ?? "lm-studio", catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
24628
- if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:11434/v1", apiKey: options.apiKey ?? "ollama", quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
24629
- if (preset === "ollama-cloud") return { type: "ollama", baseURL: options.baseURL ?? "https://ollama.com/api", ...auth, catalog: { type: "ollama-tags", endpoint: `${(options.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
24630
- if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8080/v1", apiKey: options.apiKey ?? "llama.cpp", catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
24631
- if (preset === "vllm") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8000/v1", apiKey: options.apiKey ?? "vllm", catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
24660
+ if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:1234/v1", apiKey: options.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
24661
+ if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:11434/v1", apiKey: options.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
24662
+ if (preset === "ollama-cloud") return { type: "ollama", baseURL: options.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
24663
+ if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8080/v1", apiKey: options.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
24664
+ if (preset === "vllm") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8000/v1", apiKey: options.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
24632
24665
  if (preset === "codex") return { type: "codex", baseURL: options.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
24633
24666
  if (preset === "claudecode") return { type: "claudecode", runtime: "agent-sdk", cliPath: "~/.local/bin/claude", cwdMode: "session", historyPolicy: "passthrough-resume", onInvalidResume: "fresh", attachmentFallback: "block", allowSubagents: false, sanitizeApiKeyEnv: true, authPreflight: true, useBareMode: false, usageLedgerScope: "process", sessionLock: true, abortPolicy: "record-only", catalog: { type: "claudecode-supported-models" } };
24634
24667
  if ((options.type ?? preset) === "openai-compatible") {
@@ -24637,14 +24670,20 @@ function providerPreset(options) {
24637
24670
  return {
24638
24671
  type: "openai-compatible",
24639
24672
  baseURL,
24640
- ...options.apiKey ? { apiKey: options.apiKey } : {},
24641
- ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {},
24673
+ ...auth,
24642
24674
  ...openAIAuth,
24643
24675
  catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
24644
24676
  };
24645
24677
  }
24646
24678
  throw new Error(`Unknown provider preset: ${preset}`);
24647
24679
  }
24680
+ function apiKeyAuthFields(options, defaultApiKeyEnv) {
24681
+ const apiKeyEnv = options.apiKeyEnv ?? defaultApiKeyEnv;
24682
+ return {
24683
+ ...options.apiKey !== void 0 || apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
24684
+ ...apiKeyEnv ? { apiKeyEnv } : {}
24685
+ };
24686
+ }
24648
24687
  async function writeJsonAtomic(filePath, content) {
24649
24688
  await mkdir6(path14.dirname(filePath), { recursive: true, mode: 448 });
24650
24689
  const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
@@ -28019,7 +28058,7 @@ async function runExaSearch(config, options) {
28019
28058
  return searchResult("exa", options.query, results, json.value);
28020
28059
  }
28021
28060
  function resolveApiKey(provider, config) {
28022
- const value = config.apiKey ?? (config.apiKeyEnv ? process.env[config.apiKeyEnv] : void 0);
28061
+ const value = config.apiKey || (config.apiKeyEnv ? process.env[config.apiKeyEnv] : void 0);
28023
28062
  if (value) return { ok: true, value };
28024
28063
  return toolFail(`Missing API key for web_search provider "${provider}". Set webSearch.providers.${provider}.apiKey or set ${config.apiKeyEnv ?? "the configured apiKeyEnv"}.`);
28025
28064
  }
@@ -31090,7 +31129,7 @@ function providerOptions(config, catalogs = {}) {
31090
31129
  isDefault: model.isDefault
31091
31130
  })) : providerModelProfiles(name, provider);
31092
31131
  const modelProfiles = profiles.map((profile) => {
31093
- const displayName = profile.displayName ?? profile.name ?? profile.model;
31132
+ const displayName = displayModelForProfile(profile);
31094
31133
  return {
31095
31134
  ...profile,
31096
31135
  label: available ? displayName : unavailableLabel(displayName),
@@ -31098,7 +31137,7 @@ function providerOptions(config, catalogs = {}) {
31098
31137
  };
31099
31138
  });
31100
31139
  const models = modelProfiles.length ? [...new Set(modelProfiles.map((item) => item.model).filter((item) => typeof item === "string" && item.trim()))] : providerModelOptions(name, provider);
31101
- const defaultModel = defaultModelForProvider(name, provider);
31140
+ const defaultModel = defaultDisplayModelForProvider(name, provider);
31102
31141
  return {
31103
31142
  name,
31104
31143
  label: available ? name : unavailableLabel(name),
@@ -31144,13 +31183,18 @@ function createInteractiveModelSelection(config, flags = {}, saved) {
31144
31183
  }
31145
31184
  const usesSavedProvider = !flags.provider && savedProviderExists && providerName === savedProviderName;
31146
31185
  const savedModel = usesSavedProvider && saved?.model ? saved.model : void 0;
31147
- const model = flags.model ?? savedModel ?? defaultModelForProvider(providerName, providerConfig);
31186
+ const profiles = providerModelProfiles(providerName, providerConfig);
31187
+ const savedProfile = savedModel ? profiles.find((profile) => profile.model === savedModel || profile.displayName === savedModel || profile.name === savedModel) : void 0;
31188
+ const defaultModel = displayModelForProfile(profiles[0] ?? { name: "", displayName: "", model: "" }) || defaultDisplayModelForProvider(providerName, providerConfig);
31189
+ const flagProfile = flags.model ? profiles.find((profile) => profile.model === flags.model || profile.displayName === flags.model || profile.name === flags.model) : void 0;
31190
+ const selectedProfile = flags.model ? flagProfile : savedModel ? savedProfile : profiles[0];
31191
+ const model = flags.model ?? (savedProfile ? displayModelForProfile(savedProfile) : savedModel) ?? defaultModel;
31148
31192
  return {
31149
31193
  providerName,
31150
31194
  providerSource: flags.provider ? "flag" : usesSavedProvider ? "saved" : "config",
31151
31195
  model,
31152
31196
  modelSource: flags.model ? "flag" : savedModel ? "saved" : "config",
31153
- modelProfileName: providerModelProfiles(providerName, providerConfig).find((profile) => profile.model === model || profile.displayName === model || profile.name === model)?.name
31197
+ modelProfileName: selectedProfile?.name
31154
31198
  };
31155
31199
  }
31156
31200
  function selectProvider(config, selection, providerName) {
@@ -31161,7 +31205,7 @@ function selectProvider(config, selection, providerName) {
31161
31205
  ...selection,
31162
31206
  providerName,
31163
31207
  providerSource: "interactive",
31164
- model: profile?.model ?? defaultModelForProvider(providerName, providerConfig),
31208
+ model: profile ? displayModelForProfile(profile) : defaultDisplayModelForProvider(providerName, providerConfig),
31165
31209
  modelSource: "config",
31166
31210
  modelProfileName: profile?.name
31167
31211
  };
package/dist/index.mjs CHANGED
@@ -23641,6 +23641,7 @@ var defaultConfig = {
23641
23641
  defaultProvider: "brave",
23642
23642
  providers: {
23643
23643
  brave: {
23644
+ apiKey: "",
23644
23645
  apiKeyEnv: "BRAVE_SEARCH_API_KEY",
23645
23646
  endpoint: "https://api.search.brave.com/res/v1/web/search",
23646
23647
  count: 10,
@@ -23649,12 +23650,14 @@ var defaultConfig = {
23649
23650
  safeSearch: "moderate"
23650
23651
  },
23651
23652
  tavily: {
23653
+ apiKey: "",
23652
23654
  apiKeyEnv: "TAVILY_API_KEY",
23653
23655
  endpoint: "https://api.tavily.com/search",
23654
23656
  maxResults: 5,
23655
23657
  searchDepth: "basic"
23656
23658
  },
23657
23659
  exa: {
23660
+ apiKey: "",
23658
23661
  apiKeyEnv: "EXA_API_KEY",
23659
23662
  endpoint: "https://api.exa.ai/search",
23660
23663
  numResults: 5,
@@ -23709,6 +23712,7 @@ var defaultConfig = {
23709
23712
  openai: {
23710
23713
  type: "openai-compatible",
23711
23714
  baseURL: "https://api.openai.com/v1",
23715
+ apiKey: "",
23712
23716
  apiKeyEnv: "OPENAI_API_KEY",
23713
23717
  catalog: {
23714
23718
  type: "openai-models",
@@ -23718,6 +23722,7 @@ var defaultConfig = {
23718
23722
  anthropic: {
23719
23723
  type: "anthropic",
23720
23724
  baseURL: "https://api.anthropic.com/v1",
23725
+ apiKey: "",
23721
23726
  apiKeyEnv: "ANTHROPIC_API_KEY",
23722
23727
  catalog: {
23723
23728
  type: "anthropic-models",
@@ -23727,6 +23732,7 @@ var defaultConfig = {
23727
23732
  gemini: {
23728
23733
  type: "openai-compatible",
23729
23734
  baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
23735
+ apiKey: "",
23730
23736
  apiKeyEnv: "GEMINI_API_KEY",
23731
23737
  apiKeyEnvAliases: ["GOOGLE_API_KEY"],
23732
23738
  catalog: {
@@ -23737,6 +23743,7 @@ var defaultConfig = {
23737
23743
  groq: {
23738
23744
  type: "openai-compatible",
23739
23745
  baseURL: "https://api.groq.com/openai/v1",
23746
+ apiKey: "",
23740
23747
  apiKeyEnv: "GROQ_API_KEY",
23741
23748
  catalog: {
23742
23749
  type: "groq-models",
@@ -23752,6 +23759,7 @@ var defaultConfig = {
23752
23759
  displayName: "Azure example",
23753
23760
  model: "azure-deployment-name",
23754
23761
  baseURL: "https://example.openai.azure.com/openai/v1",
23762
+ apiKey: "",
23755
23763
  apiKeyEnv: "AZURE_OPENAI_API_KEY"
23756
23764
  }
23757
23765
  ]
@@ -23760,6 +23768,7 @@ var defaultConfig = {
23760
23768
  type: "openai-compatible",
23761
23769
  baseURL: "http://localhost:1234/v1",
23762
23770
  apiKey: "lm-studio",
23771
+ modelProfiles: [],
23763
23772
  catalog: {
23764
23773
  type: "openai-compatible-models",
23765
23774
  endpoint: "http://localhost:1234/v1/models"
@@ -23769,6 +23778,7 @@ var defaultConfig = {
23769
23778
  type: "openai-compatible",
23770
23779
  baseURL: "http://localhost:11434/v1",
23771
23780
  apiKey: "ollama",
23781
+ modelProfiles: [],
23772
23782
  quirks: { omitTemperature: true },
23773
23783
  catalog: {
23774
23784
  type: "openai-compatible-models",
@@ -23778,7 +23788,9 @@ var defaultConfig = {
23778
23788
  "ollama-cloud": {
23779
23789
  type: "ollama",
23780
23790
  baseURL: "https://ollama.com/api",
23791
+ apiKey: "",
23781
23792
  apiKeyEnv: "OLLAMA_API_KEY",
23793
+ modelProfiles: [],
23782
23794
  catalog: {
23783
23795
  type: "ollama-tags",
23784
23796
  endpoint: "https://ollama.com/api/tags"
@@ -23788,6 +23800,7 @@ var defaultConfig = {
23788
23800
  type: "openai-compatible",
23789
23801
  baseURL: "http://localhost:8080/v1",
23790
23802
  apiKey: "llama.cpp",
23803
+ modelProfiles: [],
23791
23804
  catalog: {
23792
23805
  type: "openai-compatible-models",
23793
23806
  endpoint: "http://localhost:8080/v1/models"
@@ -23797,6 +23810,7 @@ var defaultConfig = {
23797
23810
  type: "openai-compatible",
23798
23811
  baseURL: "http://localhost:8000/v1",
23799
23812
  apiKey: "vllm",
23813
+ modelProfiles: [],
23800
23814
  catalog: {
23801
23815
  type: "openai-compatible-models",
23802
23816
  endpoint: "http://localhost:8000/v1/models"
@@ -24129,11 +24143,18 @@ function providerModelProfiles(providerName, providerConfig) {
24129
24143
  return out;
24130
24144
  }
24131
24145
  function providerModelOptions(providerName, providerConfig) {
24132
- return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => profile.displayName ?? profile.name ?? profile.model);
24146
+ return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => displayModelForProfile(profile));
24133
24147
  }
24134
24148
  function defaultModelForProvider(providerName, providerConfig) {
24135
24149
  return providerModelProfiles(providerName, providerConfig)[0]?.model ?? "";
24136
24150
  }
24151
+ function displayModelForProfile(profile) {
24152
+ return profile.displayName?.trim() || profile.name?.trim() || profile.model;
24153
+ }
24154
+ function defaultDisplayModelForProvider(providerName, providerConfig) {
24155
+ const profile = providerModelProfiles(providerName, providerConfig)[0];
24156
+ return profile ? displayModelForProfile(profile) : defaultModelForProvider(providerName, providerConfig);
24157
+ }
24137
24158
  function resolveConfiguredApiKey(providerConfig) {
24138
24159
  if (providerConfig.apiKey) return { value: providerConfig.apiKey };
24139
24160
  for (const envName of [providerConfig.apiKeyEnv, ...providerConfig.apiKeyEnvAliases ?? []]) {
@@ -24211,7 +24232,7 @@ function mergeModelProfile(providerConfig, profile) {
24211
24232
  ...providerConfig,
24212
24233
  ...definedOnly({
24213
24234
  baseURL: profile.baseURL,
24214
- apiKey: profile.apiKey,
24235
+ apiKey: nonEmptyString(profile.apiKey),
24215
24236
  apiKeyEnv: profile.apiKeyEnv,
24216
24237
  apiKeyEnvAliases: profile.apiKeyEnvAliases,
24217
24238
  auth: profile.auth,
@@ -24226,6 +24247,9 @@ function mergeModelProfile(providerConfig, profile) {
24226
24247
  function definedOnly(input2) {
24227
24248
  return Object.fromEntries(Object.entries(input2).filter(([, value]) => value !== void 0));
24228
24249
  }
24250
+ function nonEmptyString(value) {
24251
+ return typeof value === "string" && value.length > 0 ? value : void 0;
24252
+ }
24229
24253
  function fallbackModelForProvider(providerName, providerConfig) {
24230
24254
  if (typeof providerConfig.model === "string" && providerConfig.model.trim()) return providerConfig.model.trim();
24231
24255
  if (providerName === "openai") return "gpt-5.5";
@@ -27220,7 +27244,7 @@ async function runExaSearch(config, options) {
27220
27244
  return searchResult("exa", options.query, results, json.value);
27221
27245
  }
27222
27246
  function resolveApiKey(provider, config) {
27223
- const value = config.apiKey ?? (config.apiKeyEnv ? process.env[config.apiKeyEnv] : void 0);
27247
+ const value = config.apiKey || (config.apiKeyEnv ? process.env[config.apiKeyEnv] : void 0);
27224
27248
  if (value) return { ok: true, value };
27225
27249
  return toolFail(`Missing API key for web_search provider "${provider}". Set webSearch.providers.${provider}.apiKey or set ${config.apiKeyEnv ?? "the configured apiKeyEnv"}.`);
27226
27250
  }
@@ -29490,7 +29514,9 @@ export {
29490
29514
  defaultConfig,
29491
29515
  defaultDecisionForPermissionPreset,
29492
29516
  defaultDemianStorageDir,
29517
+ defaultDisplayModelForProvider,
29493
29518
  defaultModelForProvider,
29519
+ displayModelForProfile,
29494
29520
  estimateMessageTokens,
29495
29521
  estimateMessagesTokens,
29496
29522
  estimateToolSchemaTokens,