demian-cli 1.1.1 → 1.1.2

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.
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
2
2
 
3
3
  // src/vscode-worker.ts
4
+ import { existsSync as existsSync5 } from "node:fs";
4
5
  import { mkdir as mkdir16, rm as rm5, writeFile as writeFile15 } from "node:fs/promises";
5
6
  import path36 from "node:path";
6
7
 
@@ -762,6 +763,24 @@ function isPathLikeKey(key) {
762
763
  }
763
764
 
764
765
  // src/ui/tui/store.ts
766
+ var TUI_CONFIG_ACTION_PREFIX = "demian-config:";
767
+ var TUI_SESSION_ACTION_PREFIX = "demian-session:";
768
+ function parseTuiConfigAction(prompt) {
769
+ if (!prompt.startsWith(TUI_CONFIG_ACTION_PREFIX)) return void 0;
770
+ try {
771
+ return JSON.parse(prompt.slice(TUI_CONFIG_ACTION_PREFIX.length));
772
+ } catch {
773
+ return void 0;
774
+ }
775
+ }
776
+ function parseTuiSessionAction(prompt) {
777
+ if (!prompt.startsWith(TUI_SESSION_ACTION_PREFIX)) return void 0;
778
+ try {
779
+ return JSON.parse(prompt.slice(TUI_SESSION_ACTION_PREFIX.length));
780
+ } catch {
781
+ return void 0;
782
+ }
783
+ }
765
784
  var TuiStore = class {
766
785
  #state = {
767
786
  status: {},
@@ -770,8 +789,14 @@ var TuiStore = class {
770
789
  inputMode: "starting",
771
790
  sessionOptions: [],
772
791
  sessionCursor: 0,
792
+ sessionCancelBehavior: "exit",
773
793
  providerOptions: [],
774
794
  providerCursor: 0,
795
+ configModelCursor: 0,
796
+ configAddProviderCursor: 0,
797
+ configModelInput: "",
798
+ configProviderInput: "",
799
+ configProviderPresets: defaultConfigProviderPresets(),
775
800
  agentOptions: [],
776
801
  agentCursor: 0,
777
802
  permissionPreset: "auto",
@@ -807,6 +832,7 @@ var TuiStore = class {
807
832
  #pendingPermissionTimeout;
808
833
  #syntheticPermissionId = 0;
809
834
  #activeClaudeCodePlan;
835
+ #notifyTimer;
810
836
  snapshot() {
811
837
  return {
812
838
  ...this.#state,
@@ -814,6 +840,7 @@ var TuiStore = class {
814
840
  selection: this.#state.selection ? { ...this.#state.selection } : void 0,
815
841
  sessionOptions: this.#state.sessionOptions.map((option) => ({ ...option })),
816
842
  providerOptions: this.#state.providerOptions.map((option) => ({ ...option })),
843
+ configProviderPresets: this.#state.configProviderPresets.map((option) => ({ ...option })),
817
844
  agentOptions: this.#state.agentOptions.map((option) => ({ ...option })),
818
845
  blocks: this.#state.blocks.map((block) => ({ ...block, lines: [...block.lines], toolDetails: cloneToolRunDetails(block.toolDetails), goalWork: cloneGoalWork(block.goalWork), cowork: cloneCoworkGroup(block.cowork) })),
819
846
  warnings: [...this.#state.warnings],
@@ -1035,6 +1062,10 @@ var TuiStore = class {
1035
1062
  agent: context.agent,
1036
1063
  cwd: context.cwd
1037
1064
  };
1065
+ this.#state.configPath = context.configPath;
1066
+ this.#state.configCreated = context.configCreated;
1067
+ this.#state.configDefaultProvider = context.configDefaultProvider;
1068
+ this.#state.configMessage = context.configMessage;
1038
1069
  if (context.contextEfficiency) {
1039
1070
  this.#state.contextEfficiency = {
1040
1071
  ...this.#state.contextEfficiency ?? { selectedTools: [] },
@@ -1066,7 +1097,7 @@ var TuiStore = class {
1066
1097
  currentPermissionPreset() {
1067
1098
  return this.#state.permissionPreset;
1068
1099
  }
1069
- requestSessionSelection(options2) {
1100
+ requestSessionSelection(options2, behavior = {}) {
1070
1101
  if (this.#exitRequested) return Promise.resolve({ kind: "exit" });
1071
1102
  const normalized = options2.length > 0 ? options2.map((option) => ({ ...option })) : [{ kind: "new", title: "New session", status: "ready", currentWorkspace: true }];
1072
1103
  return new Promise((resolve) => {
@@ -1074,6 +1105,7 @@ var TuiStore = class {
1074
1105
  this.#state.inputMode = "session";
1075
1106
  this.#state.sessionOptions = normalized;
1076
1107
  this.#state.sessionCursor = 0;
1108
+ this.#state.sessionCancelBehavior = behavior.cancel ?? "exit";
1077
1109
  this.#state.promptInput = "";
1078
1110
  this.#state.promptError = void 0;
1079
1111
  this.#state.streamingText = "";
@@ -1083,6 +1115,14 @@ var TuiStore = class {
1083
1115
  this.#notify();
1084
1116
  });
1085
1117
  }
1118
+ cancelSessionSelection() {
1119
+ if (this.#state.inputMode !== "session") return false;
1120
+ if (this.#state.sessionCancelBehavior === "prompt") {
1121
+ this.#resolveSession({ kind: "cancel" });
1122
+ return false;
1123
+ }
1124
+ return true;
1125
+ }
1086
1126
  moveSessionCursor(delta) {
1087
1127
  if (this.#state.inputMode !== "session" || this.#state.sessionOptions.length === 0) return;
1088
1128
  const count = this.#state.sessionOptions.length;
@@ -1103,6 +1143,10 @@ var TuiStore = class {
1103
1143
  if (this.#state.inputMode !== "session") return;
1104
1144
  this.#resolveSession({ kind: "new" });
1105
1145
  }
1146
+ submitSessionSelectionShortcut() {
1147
+ if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
1148
+ this.#resolvePrompt(`${TUI_SESSION_ACTION_PREFIX}${JSON.stringify({ type: "select" })}`);
1149
+ }
1106
1150
  requestPrompt(initialPrompt) {
1107
1151
  if (this.#exitRequested) return Promise.resolve("");
1108
1152
  const prompt = (initialPrompt ?? "").trim();
@@ -1200,6 +1244,143 @@ var TuiStore = class {
1200
1244
  this.#state.activity = `provider ${option.name} selected`;
1201
1245
  this.#notify();
1202
1246
  }
1247
+ openConfigManager(message) {
1248
+ const canOpenFromConfig = this.#state.inputMode === "config" || this.#state.inputMode === "configModel" || this.#state.inputMode === "configModelInput" || this.#state.inputMode === "configProviderInput" || this.#state.inputMode === "configAddProvider";
1249
+ if (!canOpenFromConfig && (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim())) return;
1250
+ this.#state.inputMode = "config";
1251
+ const current = this.#state.selection?.providerName ?? this.#state.status.provider;
1252
+ this.#state.providerCursor = Math.max(0, this.#state.providerOptions.findIndex((option) => option.name === current));
1253
+ this.#state.configMessage = message ?? this.#state.configMessage;
1254
+ this.#state.settingsError = void 0;
1255
+ this.#state.activity = "configure providers and models";
1256
+ this.#notify();
1257
+ }
1258
+ openConfigProviderInput(field) {
1259
+ if (this.#state.inputMode !== "config") return;
1260
+ const provider = this.#selectedConfigProvider();
1261
+ if (!provider) return;
1262
+ this.#state.inputMode = "configProviderInput";
1263
+ this.#state.configProviderInputField = field;
1264
+ this.#state.configProviderInput = configProviderFieldValue(provider, field);
1265
+ this.#state.settingsError = void 0;
1266
+ this.#state.activity = `edit ${field} for ${provider.name}`;
1267
+ this.#notify();
1268
+ }
1269
+ appendConfigProviderInput(input2) {
1270
+ if (this.#state.inputMode !== "configProviderInput") return;
1271
+ this.#state.configProviderInput += input2;
1272
+ this.#state.settingsError = void 0;
1273
+ this.#notify();
1274
+ }
1275
+ backspaceConfigProviderInput() {
1276
+ if (this.#state.inputMode !== "configProviderInput") return;
1277
+ this.#state.configProviderInput = Array.from(this.#state.configProviderInput).slice(0, -1).join("");
1278
+ this.#state.settingsError = void 0;
1279
+ this.#notify();
1280
+ }
1281
+ submitConfigProviderInput() {
1282
+ if (this.#state.inputMode !== "configProviderInput") return;
1283
+ const provider = this.#selectedConfigProvider();
1284
+ const field = this.#state.configProviderInputField;
1285
+ if (!provider || !field) return;
1286
+ this.#submitConfigAction({ type: "updateProvider", provider: provider.name, field, value: this.#state.configProviderInput });
1287
+ }
1288
+ moveConfigProviderCursor(delta) {
1289
+ if (this.#state.inputMode !== "config" || this.#state.providerOptions.length === 0) return;
1290
+ const count = this.#state.providerOptions.length;
1291
+ this.#state.providerCursor = (this.#state.providerCursor + delta + count) % count;
1292
+ this.#notify();
1293
+ }
1294
+ submitConfigDefaultProvider() {
1295
+ if (this.#state.inputMode !== "config") return;
1296
+ const option = this.#selectedConfigProvider();
1297
+ if (!option) return;
1298
+ this.#submitConfigAction({ type: "setDefaultProvider", provider: option.name });
1299
+ }
1300
+ openConfigModelSelector() {
1301
+ if (this.#state.inputMode !== "config" && this.#state.inputMode !== "configModelInput") return;
1302
+ const option = this.#selectedConfigProvider();
1303
+ if (!option) return;
1304
+ this.#state.inputMode = "configModel";
1305
+ this.#state.configModelCursor = 0;
1306
+ this.#state.settingsError = void 0;
1307
+ this.#state.activity = `choose default model for ${option.name}`;
1308
+ this.#notify();
1309
+ }
1310
+ moveConfigModelCursor(delta) {
1311
+ if (this.#state.inputMode !== "configModel") return;
1312
+ const profiles = this.#selectedConfigProvider()?.modelProfiles ?? [];
1313
+ if (profiles.length === 0) return;
1314
+ this.#state.configModelCursor = (this.#state.configModelCursor + delta + profiles.length) % profiles.length;
1315
+ this.#notify();
1316
+ }
1317
+ submitConfigDefaultModel() {
1318
+ if (this.#state.inputMode !== "configModel") return;
1319
+ const provider = this.#selectedConfigProvider();
1320
+ const profile = provider?.modelProfiles?.[this.#state.configModelCursor];
1321
+ if (!provider || !profile?.model) return;
1322
+ this.#submitConfigAction({
1323
+ type: "setDefaultModel",
1324
+ provider: provider.name,
1325
+ profileName: profile.name,
1326
+ name: profile.name,
1327
+ displayName: profile.displayName,
1328
+ model: profile.model
1329
+ });
1330
+ }
1331
+ openConfigModelInput() {
1332
+ if (this.#state.inputMode !== "configModel" && this.#state.inputMode !== "config") return;
1333
+ if (!this.#selectedConfigProvider()) return;
1334
+ this.#state.inputMode = "configModelInput";
1335
+ this.#state.configModelInput = "";
1336
+ this.#state.settingsError = void 0;
1337
+ this.#state.activity = "add a model profile";
1338
+ this.#notify();
1339
+ }
1340
+ appendConfigModelInput(input2) {
1341
+ if (this.#state.inputMode !== "configModelInput") return;
1342
+ this.#state.configModelInput += input2;
1343
+ this.#state.settingsError = void 0;
1344
+ this.#notify();
1345
+ }
1346
+ backspaceConfigModelInput() {
1347
+ if (this.#state.inputMode !== "configModelInput") return;
1348
+ this.#state.configModelInput = Array.from(this.#state.configModelInput).slice(0, -1).join("");
1349
+ this.#state.settingsError = void 0;
1350
+ this.#notify();
1351
+ }
1352
+ submitConfigModelInput() {
1353
+ if (this.#state.inputMode !== "configModelInput") return;
1354
+ const provider = this.#selectedConfigProvider();
1355
+ const model = this.#state.configModelInput.trim();
1356
+ if (!provider || !model) {
1357
+ this.#state.settingsError = "Model ID is required.";
1358
+ this.#notify();
1359
+ return;
1360
+ }
1361
+ this.#submitConfigAction({ type: "setDefaultModel", provider: provider.name, model, displayName: model });
1362
+ }
1363
+ openConfigAddProviderSelector() {
1364
+ if (this.#state.inputMode !== "config") return;
1365
+ this.#state.inputMode = "configAddProvider";
1366
+ this.#state.configAddProviderCursor = 0;
1367
+ this.#state.settingsError = void 0;
1368
+ this.#state.activity = "add provider preset";
1369
+ this.#notify();
1370
+ }
1371
+ moveConfigAddProviderCursor(delta) {
1372
+ if (this.#state.inputMode !== "configAddProvider") return;
1373
+ const count = this.#state.configProviderPresets.length;
1374
+ if (count === 0) return;
1375
+ this.#state.configAddProviderCursor = (this.#state.configAddProviderCursor + delta + count) % count;
1376
+ this.#notify();
1377
+ }
1378
+ submitConfigAddProvider() {
1379
+ if (this.#state.inputMode !== "configAddProvider") return;
1380
+ const preset = this.#state.configProviderPresets[this.#state.configAddProviderCursor];
1381
+ if (!preset) return;
1382
+ this.#submitConfigAction({ type: "addProvider", preset: preset.preset, name: preset.name, apiKeyEnv: preset.apiKeyEnv });
1383
+ }
1203
1384
  openAgentSelector() {
1204
1385
  if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
1205
1386
  if (this.#state.agentOptions.length === 0) {
@@ -1298,9 +1479,12 @@ var TuiStore = class {
1298
1479
  this.#notify();
1299
1480
  }
1300
1481
  closeSettings() {
1301
- if (this.#state.inputMode !== "provider" && this.#state.inputMode !== "model" && this.#state.inputMode !== "agent" && this.#state.inputMode !== "permissionPreset") return;
1482
+ if (!["provider", "model", "agent", "permissionPreset", "config", "configModel", "configModelInput", "configProviderInput", "configAddProvider"].includes(this.#state.inputMode)) return;
1302
1483
  this.#state.inputMode = "prompt";
1303
1484
  this.#state.modelInput = "";
1485
+ this.#state.configModelInput = "";
1486
+ this.#state.configProviderInput = "";
1487
+ this.#state.configProviderInputField = void 0;
1304
1488
  this.#state.settingsError = void 0;
1305
1489
  this.#state.activity = "waiting for first message";
1306
1490
  this.#notify();
@@ -1429,9 +1613,12 @@ var TuiStore = class {
1429
1613
  this.#state.streamingFinalized = false;
1430
1614
  this.#activeClaudeCodePlan = this.#isClaudeCodePlanRun(event.provider) ? { text: "", providerName: event.provider, model: event.model, createdAt: event.ts } : void 0;
1431
1615
  }
1432
- if (event.type === "model.text.delta" && !isNestedInvocationEvent(event)) {
1616
+ if (event.type === "model.text.delta") {
1617
+ if (isNestedInvocationEvent(event)) return;
1433
1618
  this.#state.streamingText += event.text;
1434
1619
  this.#state.streamingFinalized = false;
1620
+ this.#notify({ deferMs: 50 });
1621
+ return;
1435
1622
  }
1436
1623
  if (event.type === "model.text" && !isNestedInvocationEvent(event)) {
1437
1624
  this.#state.streamingText = event.text;
@@ -2145,14 +2332,28 @@ var TuiStore = class {
2145
2332
  resolve(prompt);
2146
2333
  this.#notify();
2147
2334
  }
2335
+ #selectedConfigProvider() {
2336
+ return this.#state.providerOptions[this.#state.providerCursor];
2337
+ }
2338
+ #submitConfigAction(action) {
2339
+ this.#state.settingsError = void 0;
2340
+ this.#state.configModelInput = "";
2341
+ this.#state.configProviderInput = "";
2342
+ this.#state.configProviderInputField = void 0;
2343
+ this.#resolvePrompt(`${TUI_CONFIG_ACTION_PREFIX}${JSON.stringify(action)}`);
2344
+ }
2148
2345
  #resolveSession(selection) {
2149
2346
  const resolve = this.#sessionResolve;
2150
2347
  if (!resolve) return;
2151
2348
  this.#sessionResolve = void 0;
2349
+ this.#state.sessionCancelBehavior = "exit";
2152
2350
  if (selection.kind === "exit") {
2153
2351
  this.#state.inputMode = "done";
2154
2352
  this.#state.done = true;
2155
2353
  this.#state.activity = "exiting";
2354
+ } else if (selection.kind === "cancel") {
2355
+ this.#state.inputMode = "prompt";
2356
+ this.#state.activity = "waiting for next message";
2156
2357
  } else {
2157
2358
  this.#state.inputMode = "starting";
2158
2359
  this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
@@ -2264,7 +2465,23 @@ var TuiStore = class {
2264
2465
  }
2265
2466
  };
2266
2467
  }
2267
- #notify() {
2468
+ #notify(options2 = {}) {
2469
+ const deferMs = options2.deferMs ?? 0;
2470
+ if (deferMs > 0) {
2471
+ if (this.#notifyTimer) return;
2472
+ this.#notifyTimer = setTimeout(() => {
2473
+ this.#notifyTimer = void 0;
2474
+ this.#emitNotify();
2475
+ }, deferMs);
2476
+ return;
2477
+ }
2478
+ if (this.#notifyTimer) {
2479
+ clearTimeout(this.#notifyTimer);
2480
+ this.#notifyTimer = void 0;
2481
+ }
2482
+ this.#emitNotify();
2483
+ }
2484
+ #emitNotify() {
2268
2485
  for (const listener of this.#listeners) listener();
2269
2486
  }
2270
2487
  };
@@ -2427,6 +2644,25 @@ function cloneContextEfficiency(context) {
2427
2644
  lastCompaction: context.lastCompaction ? { ...context.lastCompaction } : void 0
2428
2645
  };
2429
2646
  }
2647
+ function configProviderFieldValue(provider, field) {
2648
+ if (field === "baseURL") return provider.baseURL ?? "";
2649
+ if (field === "apiKeyEnv") return provider.apiKeyEnv ?? "";
2650
+ return provider.auth?.header ?? "";
2651
+ }
2652
+ function defaultConfigProviderPresets() {
2653
+ return [
2654
+ { label: "OpenAI", preset: "openai", name: "openai", apiKeyEnv: "OPENAI_API_KEY", detail: "OpenAI API" },
2655
+ { label: "Anthropic", preset: "anthropic", name: "anthropic", apiKeyEnv: "ANTHROPIC_API_KEY", detail: "Anthropic API" },
2656
+ { label: "Gemini", preset: "gemini", name: "gemini", apiKeyEnv: "GEMINI_API_KEY", detail: "Google Gemini OpenAI-compatible API" },
2657
+ { label: "Groq", preset: "groq", name: "groq", apiKeyEnv: "GROQ_API_KEY", detail: "Groq Cloud" },
2658
+ { label: "Codex", preset: "codex", name: "codex", detail: "ChatGPT Codex OAuth" },
2659
+ { label: "Claude Code", preset: "claudecode", name: "claudecode", detail: "Local Claude Code runtime" },
2660
+ { label: "Ollama local", preset: "ollama-local", name: "ollama-local", detail: "http://localhost:11434" },
2661
+ { label: "LM Studio", preset: "lmstudio", name: "lmstudio", detail: "http://localhost:1234" },
2662
+ { label: "llama.cpp", preset: "llamacpp", name: "llamacpp", detail: "http://localhost:8080" },
2663
+ { label: "vLLM", preset: "vllm", name: "vllm", detail: "http://localhost:8000" }
2664
+ ];
2665
+ }
2430
2666
  function cloneToolRunDetails(details) {
2431
2667
  if (!details) return void 0;
2432
2668
  return {
@@ -2527,7 +2763,8 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
2527
2763
  }
2528
2764
 
2529
2765
  // src/ui/tui/controller.ts
2530
- import path33 from "node:path";
2766
+ import path34 from "node:path";
2767
+ import { existsSync as existsSync4 } from "node:fs";
2531
2768
 
2532
2769
  // src/agents/types.ts
2533
2770
  function normalizeAgent(input2) {
@@ -26966,10 +27203,380 @@ function claudeCodeRuntimePolicyHash(config) {
26966
27203
  }
26967
27204
  var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision", "allowedTools", "disallowedTools", "tools", "allowSubagents"];
26968
27205
 
26969
- // src/transcript.ts
26970
- import { mkdir as mkdir6, appendFile, writeFile as writeFile5 } from "node:fs/promises";
27206
+ // src/config-scaffold.ts
27207
+ import { chmod as chmod3, mkdir as mkdir6, readFile as readFile7, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
26971
27208
  import os8 from "node:os";
26972
27209
  import path14 from "node:path";
27210
+ function defaultUserConfigPath() {
27211
+ return path14.join(os8.homedir(), ".demian", "config.json");
27212
+ }
27213
+ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
27214
+ return {
27215
+ version: 2,
27216
+ defaultProvider,
27217
+ providers: {
27218
+ openai: {
27219
+ type: "openai-compatible",
27220
+ baseURL: "https://api.openai.com/v1",
27221
+ apiKey: "",
27222
+ apiKeyEnv: "OPENAI_API_KEY",
27223
+ catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
27224
+ },
27225
+ anthropic: {
27226
+ type: "anthropic",
27227
+ baseURL: "https://api.anthropic.com/v1",
27228
+ apiKey: "",
27229
+ apiKeyEnv: "ANTHROPIC_API_KEY",
27230
+ catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
27231
+ },
27232
+ gemini: {
27233
+ type: "openai-compatible",
27234
+ baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
27235
+ apiKey: "",
27236
+ apiKeyEnv: "GEMINI_API_KEY",
27237
+ apiKeyEnvAliases: ["GOOGLE_API_KEY"],
27238
+ catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
27239
+ },
27240
+ groq: {
27241
+ type: "openai-compatible",
27242
+ baseURL: "https://api.groq.com/openai/v1",
27243
+ apiKey: "",
27244
+ apiKeyEnv: "GROQ_API_KEY",
27245
+ catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
27246
+ },
27247
+ azure: {
27248
+ type: "openai-compatible",
27249
+ auth: { type: "api-key", header: "api-key" },
27250
+ modelProfiles: [
27251
+ {
27252
+ name: "azure-example",
27253
+ displayName: "Azure example",
27254
+ model: "azure-deployment-name",
27255
+ baseURL: "https://example.openai.azure.com/openai/v1",
27256
+ apiKey: "",
27257
+ apiKeyEnv: "AZURE_OPENAI_API_KEY"
27258
+ }
27259
+ ]
27260
+ },
27261
+ lmstudio: {
27262
+ type: "openai-compatible",
27263
+ baseURL: "http://localhost:1234/v1",
27264
+ apiKey: "lm-studio",
27265
+ modelProfiles: [],
27266
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
27267
+ },
27268
+ "ollama-local": {
27269
+ type: "openai-compatible",
27270
+ baseURL: "http://localhost:11434/v1",
27271
+ apiKey: "ollama",
27272
+ modelProfiles: [],
27273
+ quirks: { omitTemperature: true },
27274
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
27275
+ },
27276
+ "ollama-cloud": {
27277
+ type: "ollama",
27278
+ baseURL: "https://ollama.com/api",
27279
+ apiKey: "",
27280
+ apiKeyEnv: "OLLAMA_API_KEY",
27281
+ modelProfiles: [],
27282
+ catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
27283
+ },
27284
+ llamacpp: {
27285
+ type: "openai-compatible",
27286
+ baseURL: "http://localhost:8080/v1",
27287
+ apiKey: "llama.cpp",
27288
+ modelProfiles: [],
27289
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
27290
+ },
27291
+ vllm: {
27292
+ type: "openai-compatible",
27293
+ baseURL: "http://localhost:8000/v1",
27294
+ apiKey: "vllm",
27295
+ modelProfiles: [],
27296
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
27297
+ },
27298
+ codex: {
27299
+ type: "codex",
27300
+ baseURL: "https://chatgpt.com/backend-api/codex",
27301
+ authStore: "auto",
27302
+ allowApiKeyFallback: false,
27303
+ promptCacheKey: "root-session",
27304
+ catalog: { type: "codex-oauth-models" }
27305
+ },
27306
+ claudecode: {
27307
+ type: "claudecode",
27308
+ runtime: "agent-sdk",
27309
+ cliPath: "~/.local/bin/claude",
27310
+ cwdMode: "session",
27311
+ historyPolicy: "passthrough-resume",
27312
+ onInvalidResume: "fresh",
27313
+ attachmentFallback: "block",
27314
+ allowSubagents: false,
27315
+ sanitizeApiKeyEnv: true,
27316
+ authPreflight: true,
27317
+ useBareMode: false,
27318
+ usageLedgerScope: "process",
27319
+ sessionLock: true,
27320
+ abortPolicy: "record-only",
27321
+ catalog: { type: "claudecode-supported-models" }
27322
+ }
27323
+ }
27324
+ };
27325
+ }
27326
+ async function createUserConfig(options2 = {}) {
27327
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27328
+ const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
27329
+ `;
27330
+ if (options2.print) return { path: filePath, created: false, content };
27331
+ const existed = await exists(filePath);
27332
+ if (existed && !options2.force) return { path: filePath, created: false, content: await readFile7(filePath, "utf8"), existed: true };
27333
+ await writeJsonAtomic(filePath, content);
27334
+ return { path: filePath, created: true, content, existed };
27335
+ }
27336
+ async function addProvider(options2) {
27337
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27338
+ const config = await readConfigObject(filePath);
27339
+ const providers = objectValue(config.providers);
27340
+ const name = options2.name;
27341
+ if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
27342
+ providers[name] = providerPreset(options2);
27343
+ config.providers = providers;
27344
+ const content = `${JSON.stringify(config, null, 2)}
27345
+ `;
27346
+ await writeJsonAtomic(filePath, content);
27347
+ return { path: filePath, created: true, content };
27348
+ }
27349
+ async function addModelProfile(options2) {
27350
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27351
+ const config = await readConfigObject(filePath);
27352
+ const providers = objectValue(config.providers);
27353
+ const provider = objectValue(providers[options2.provider]);
27354
+ const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
27355
+ const displayName = options2.displayName ?? options2.name;
27356
+ const previousName = normalizeOptionalName(options2.previousName);
27357
+ const existingByPreviousName = previousName ? profiles.findIndex((entry) => entry.name === previousName) : -1;
27358
+ const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
27359
+ const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
27360
+ const updateIndex = existingByPreviousName >= 0 ? existingByPreviousName : existingByName;
27361
+ if (existingByName >= 0 && existingByName !== updateIndex) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Pick a different profile name.`);
27362
+ if (existingByName >= 0 && existingByPreviousName < 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
27363
+ if (existingByDisplay >= 0 && existingByDisplay !== updateIndex && !options2.force) {
27364
+ throw new Error(`Profile displayName "${displayName}" already exists on provider ${options2.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
27365
+ }
27366
+ const next = {
27367
+ name: options2.name,
27368
+ displayName,
27369
+ model: options2.model,
27370
+ ...options2.baseURL ? { baseURL: options2.baseURL } : {},
27371
+ ...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
27372
+ ...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
27373
+ };
27374
+ if (updateIndex >= 0) profiles[updateIndex] = next;
27375
+ else profiles.push(next);
27376
+ provider.modelProfiles = profiles;
27377
+ providers[options2.provider] = provider;
27378
+ config.providers = providers;
27379
+ const content = `${JSON.stringify(config, null, 2)}
27380
+ `;
27381
+ await writeJsonAtomic(filePath, content);
27382
+ return { path: filePath, created: true, content };
27383
+ }
27384
+ async function updateConfigDefaults(options2) {
27385
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27386
+ const config = await readConfigObject(filePath);
27387
+ const defaultProvider = normalizeOptionalName(options2.defaultProvider);
27388
+ const defaultAgent = normalizeOptionalName(options2.defaultAgent);
27389
+ if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
27390
+ if (defaultProvider) config.defaultProvider = defaultProvider;
27391
+ if (defaultAgent) config.defaultAgent = defaultAgent;
27392
+ const content = `${JSON.stringify(config, null, 2)}
27393
+ `;
27394
+ await writeJsonAtomic(filePath, content);
27395
+ return { path: filePath, created: true, content };
27396
+ }
27397
+ async function setDefaultModelProfile(options2) {
27398
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27399
+ const config = await readConfigObject(filePath);
27400
+ const providers = objectValue(config.providers);
27401
+ const provider = providers[options2.provider];
27402
+ if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
27403
+ const providerConfig = { ...provider };
27404
+ const profiles = Array.isArray(providerConfig.modelProfiles) ? [...providerConfig.modelProfiles] : [];
27405
+ const requestedName = normalizeOptionalName(options2.profileName ?? options2.name);
27406
+ const requestedModel = normalizeOptionalName(options2.model);
27407
+ const requestedDisplayName = normalizeOptionalName(options2.displayName) ?? requestedModel ?? requestedName;
27408
+ let index = profiles.findIndex((entry) => requestedName && entry.name === requestedName);
27409
+ if (index < 0 && requestedModel) index = profiles.findIndex((entry) => entry.model === requestedModel);
27410
+ if (index < 0 && requestedDisplayName) index = profiles.findIndex((entry) => entry.displayName === requestedDisplayName);
27411
+ const profile = index >= 0 ? {
27412
+ ...profiles[index],
27413
+ ...requestedName ? { name: requestedName } : {},
27414
+ ...requestedDisplayName ? { displayName: requestedDisplayName } : {},
27415
+ ...requestedModel ? { model: requestedModel } : {},
27416
+ ...options2.baseURL ? { baseURL: options2.baseURL } : {},
27417
+ ...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
27418
+ ...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
27419
+ } : newModelProfile({
27420
+ name: requestedName,
27421
+ displayName: requestedDisplayName,
27422
+ model: requestedModel ?? requestedName,
27423
+ baseURL: options2.baseURL,
27424
+ apiKey: options2.apiKey,
27425
+ apiKeyEnv: options2.apiKeyEnv
27426
+ });
27427
+ if (!profile.name || typeof profile.name !== "string") throw new Error("Model profile name is required.");
27428
+ if (!profile.model || typeof profile.model !== "string") throw new Error("Model ID is required.");
27429
+ if (index >= 0) profiles.splice(index, 1);
27430
+ profiles.unshift(profile);
27431
+ providerConfig.modelProfiles = profiles;
27432
+ providers[options2.provider] = providerConfig;
27433
+ config.providers = providers;
27434
+ const content = `${JSON.stringify(config, null, 2)}
27435
+ `;
27436
+ await writeJsonAtomic(filePath, content);
27437
+ return { path: filePath, created: true, content };
27438
+ }
27439
+ async function updateProviderSettings(options2) {
27440
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27441
+ const config = await readConfigObject(filePath);
27442
+ const providers = objectValue(config.providers);
27443
+ const provider = providers[options2.provider];
27444
+ if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
27445
+ const providerConfig = { ...provider };
27446
+ if (options2.baseURL !== void 0) setOrDelete(providerConfig, "baseURL", options2.baseURL);
27447
+ if (options2.apiKey !== void 0) setOrDelete(providerConfig, "apiKey", options2.apiKey);
27448
+ if (options2.apiKeyEnv !== void 0) setOrDelete(providerConfig, "apiKeyEnv", options2.apiKeyEnv);
27449
+ if (options2.authHeader !== void 0) {
27450
+ const header = options2.authHeader.trim();
27451
+ if (header) providerConfig.auth = { type: "api-key", header };
27452
+ else delete providerConfig.auth;
27453
+ }
27454
+ providers[options2.provider] = providerConfig;
27455
+ config.providers = providers;
27456
+ const content = `${JSON.stringify(config, null, 2)}
27457
+ `;
27458
+ await writeJsonAtomic(filePath, content);
27459
+ return { path: filePath, created: true, content };
27460
+ }
27461
+ async function readConfigObject(filePath) {
27462
+ if (!await exists(filePath)) await createUserConfig({ path: filePath });
27463
+ const raw = JSON.parse(await readFile7(filePath, "utf8"));
27464
+ raw.version ??= 2;
27465
+ raw.providers = objectValue(raw.providers);
27466
+ return raw;
27467
+ }
27468
+ function providerPreset(options2) {
27469
+ const preset = options2.preset ?? options2.name;
27470
+ const auth = apiKeyAuthFields(options2);
27471
+ const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
27472
+ if (preset === "openai") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options2, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options2.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
27473
+ if (preset === "anthropic") return { type: "anthropic", baseURL: options2.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options2, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options2.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
27474
+ if (preset === "gemini") {
27475
+ const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
27476
+ return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options2, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
27477
+ }
27478
+ if (preset === "groq") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options2, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options2.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
27479
+ if (preset === "azure") {
27480
+ return {
27481
+ type: "openai-compatible",
27482
+ auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
27483
+ ...auth,
27484
+ modelProfiles: [
27485
+ {
27486
+ name: "azure-example",
27487
+ displayName: "Azure example",
27488
+ model: "azure-deployment-name",
27489
+ baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
27490
+ ...options2.apiKey ? {} : { apiKey: "" },
27491
+ apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
27492
+ }
27493
+ ]
27494
+ };
27495
+ }
27496
+ if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:1234/v1", apiKey: options2.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
27497
+ if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:11434/v1", apiKey: options2.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
27498
+ if (preset === "ollama-cloud") return { type: "ollama", baseURL: options2.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options2, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options2.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
27499
+ if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8080/v1", apiKey: options2.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
27500
+ if (preset === "vllm") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8000/v1", apiKey: options2.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
27501
+ if (preset === "codex") return { type: "codex", baseURL: options2.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
27502
+ 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" } };
27503
+ if ((options2.type ?? preset) === "openai-compatible") {
27504
+ const baseURL = options2.baseURL;
27505
+ if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
27506
+ return {
27507
+ type: "openai-compatible",
27508
+ baseURL,
27509
+ ...auth,
27510
+ ...openAIAuth,
27511
+ catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
27512
+ };
27513
+ }
27514
+ throw new Error(`Unknown provider preset: ${preset}`);
27515
+ }
27516
+ function apiKeyAuthFields(options2, defaultApiKeyEnv) {
27517
+ const apiKeyEnv = options2.apiKeyEnv ?? defaultApiKeyEnv;
27518
+ return {
27519
+ ...options2.apiKey !== void 0 || apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
27520
+ ...apiKeyEnv ? { apiKeyEnv } : {}
27521
+ };
27522
+ }
27523
+ async function writeJsonAtomic(filePath, content) {
27524
+ await mkdir6(path14.dirname(filePath), { recursive: true, mode: 448 });
27525
+ const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
27526
+ await writeFile5(temp, content, { mode: 384 });
27527
+ await rename4(temp, filePath);
27528
+ await chmod3(filePath, 384).catch(() => void 0);
27529
+ }
27530
+ function detectDefaultProvider() {
27531
+ if (process.env.OPENAI_API_KEY) return "openai";
27532
+ if (process.env.ANTHROPIC_API_KEY) return "anthropic";
27533
+ if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
27534
+ if (process.env.GROQ_API_KEY) return "groq";
27535
+ return "openai";
27536
+ }
27537
+ function objectValue(value) {
27538
+ return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
27539
+ }
27540
+ function normalizeOptionalName(value) {
27541
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
27542
+ }
27543
+ function newModelProfile(options2) {
27544
+ const model = options2.model?.trim();
27545
+ if (!model) throw new Error("Model ID is required.");
27546
+ const name = options2.name?.trim() || modelProfileNameFromModel(model);
27547
+ return {
27548
+ name,
27549
+ displayName: options2.displayName?.trim() || name,
27550
+ model,
27551
+ ...options2.baseURL ? { baseURL: options2.baseURL } : {},
27552
+ ...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
27553
+ ...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
27554
+ };
27555
+ }
27556
+ function modelProfileNameFromModel(model) {
27557
+ return model.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "model";
27558
+ }
27559
+ function setOrDelete(target, key, value) {
27560
+ const trimmed = value.trim();
27561
+ if (trimmed) target[key] = trimmed;
27562
+ else delete target[key];
27563
+ }
27564
+ async function exists(filePath) {
27565
+ try {
27566
+ await stat3(filePath);
27567
+ return true;
27568
+ } catch {
27569
+ return false;
27570
+ }
27571
+ }
27572
+ function expandHome2(value) {
27573
+ return resolveExpandedPath(value);
27574
+ }
27575
+
27576
+ // src/transcript.ts
27577
+ import { mkdir as mkdir7, appendFile, writeFile as writeFile6 } from "node:fs/promises";
27578
+ import os9 from "node:os";
27579
+ import path15 from "node:path";
26973
27580
  var TranscriptWriter = class {
26974
27581
  filePath;
26975
27582
  #enabled;
@@ -26977,9 +27584,9 @@ var TranscriptWriter = class {
26977
27584
  #queue = Promise.resolve();
26978
27585
  constructor(options2) {
26979
27586
  this.#enabled = options2.enabled !== false;
26980
- const dir = path14.join(options2.storageDir ?? defaultDemianStorageDir(), "transcripts", options2.sessionId);
26981
- this.filePath = path14.join(dir, "session.jsonl");
26982
- this.#ready = this.#enabled ? mkdir6(dir, { recursive: true }).then(() => writeFile5(this.filePath, "", { flag: "a" })) : Promise.resolve();
27587
+ const dir = path15.join(options2.storageDir ?? defaultDemianStorageDir(), "transcripts", options2.sessionId);
27588
+ this.filePath = path15.join(dir, "session.jsonl");
27589
+ this.#ready = this.#enabled ? mkdir7(dir, { recursive: true }).then(() => writeFile6(this.filePath, "", { flag: "a" })) : Promise.resolve();
26983
27590
  }
26984
27591
  write(event) {
26985
27592
  if (!this.#enabled) return Promise.resolve();
@@ -26995,14 +27602,14 @@ var TranscriptWriter = class {
26995
27602
  }
26996
27603
  };
26997
27604
  function defaultDemianStorageDir() {
26998
- return path14.join(os8.homedir(), ".demian");
27605
+ return path15.join(os9.homedir(), ".demian");
26999
27606
  }
27000
27607
 
27001
27608
  // src/external-runtime/snapshot-diff.ts
27002
27609
  import { createHash as createHash2 } from "node:crypto";
27003
27610
  import { createReadStream } from "node:fs";
27004
- import { readdir, readFile as readFile7, stat as stat3 } from "node:fs/promises";
27005
- import path15 from "node:path";
27611
+ import { readdir, readFile as readFile8, stat as stat4 } from "node:fs/promises";
27612
+ import path16 from "node:path";
27006
27613
 
27007
27614
  // src/workspace/diff.ts
27008
27615
  var CONTEXT_LINES = 3;
@@ -27212,7 +27819,7 @@ async function diffWorkspaceSnapshot(before) {
27212
27819
  }
27213
27820
  async function walk(root, relativeDir, entries, options2) {
27214
27821
  if (entries.size >= options2.maxFiles) return;
27215
- const absoluteDir = path15.join(root, relativeDir);
27822
+ const absoluteDir = path16.join(root, relativeDir);
27216
27823
  let items;
27217
27824
  try {
27218
27825
  items = await readdir(absoluteDir, { withFileTypes: true });
@@ -27223,8 +27830,8 @@ async function walk(root, relativeDir, entries, options2) {
27223
27830
  if (entries.size >= options2.maxFiles) return;
27224
27831
  if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
27225
27832
  if (SKIP_DIRS.has(item.name)) continue;
27226
- const relativePath = normalizeRelativePath(path15.join(relativeDir, item.name));
27227
- const absolutePath = path15.join(root, relativePath);
27833
+ const relativePath = normalizeRelativePath(path16.join(relativeDir, item.name));
27834
+ const absolutePath = path16.join(root, relativePath);
27228
27835
  if (item.isDirectory()) {
27229
27836
  await walk(root, relativePath, entries, options2);
27230
27837
  continue;
@@ -27235,7 +27842,7 @@ async function walk(root, relativeDir, entries, options2) {
27235
27842
  }
27236
27843
  }
27237
27844
  async function snapshotFile(filePath, relativePath, maxTextBytes) {
27238
- const info = await stat3(filePath);
27845
+ const info = await stat4(filePath);
27239
27846
  const sha2562 = await sha256File(filePath);
27240
27847
  const content = info.size <= maxTextBytes ? await readTextContent(filePath) : void 0;
27241
27848
  return { path: relativePath, size: info.size, sha256: sha2562, content };
@@ -27246,7 +27853,7 @@ function snapshotDiffText(entry) {
27246
27853
  `;
27247
27854
  }
27248
27855
  async function readTextContent(filePath) {
27249
- const buffer = await readFile7(filePath);
27856
+ const buffer = await readFile8(filePath);
27250
27857
  if (buffer.includes(0)) return void 0;
27251
27858
  return buffer.toString("utf8");
27252
27859
  }
@@ -27260,7 +27867,7 @@ function sha256File(filePath) {
27260
27867
  });
27261
27868
  }
27262
27869
  function normalizeRelativePath(value) {
27263
- return value.split(path15.sep).join("/");
27870
+ return value.split(path16.sep).join("/");
27264
27871
  }
27265
27872
 
27266
27873
  // src/external-runtime/session-runner.ts
@@ -27695,7 +28302,7 @@ function safeJson(value) {
27695
28302
  }
27696
28303
 
27697
28304
  // src/hooks/dispatcher.ts
27698
- import path17 from "node:path";
28305
+ import path18 from "node:path";
27699
28306
 
27700
28307
  // src/hooks/command.ts
27701
28308
  import { spawn as spawn4 } from "node:child_process";
@@ -27788,7 +28395,7 @@ var blockDangerousBashHook = {
27788
28395
  };
27789
28396
 
27790
28397
  // src/hooks/builtin/protect-env-files.ts
27791
- import path16 from "node:path";
28398
+ import path17 from "node:path";
27792
28399
  var protectEnvFilesHook = {
27793
28400
  name: "protect-env-files",
27794
28401
  event: "PreToolUse",
@@ -27796,7 +28403,7 @@ var protectEnvFilesHook = {
27796
28403
  run(ctx) {
27797
28404
  if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
27798
28405
  const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
27799
- const filePath = typeof input2.path === "string" ? path16.resolve(ctx.cwd, input2.path) : "";
28406
+ const filePath = typeof input2.path === "string" ? path17.resolve(ctx.cwd, input2.path) : "";
27800
28407
  if (filePath && isEnvFile(filePath)) {
27801
28408
  return {
27802
28409
  decision: "block",
@@ -27833,7 +28440,7 @@ var maskSecretsHook = {
27833
28440
  };
27834
28441
 
27835
28442
  // src/hooks/builtin/inject-env-info.ts
27836
- import os9 from "node:os";
28443
+ import os10 from "node:os";
27837
28444
  var injectEnvInfoHook = {
27838
28445
  name: "inject-env-info",
27839
28446
  event: "SessionStart",
@@ -27842,7 +28449,7 @@ var injectEnvInfoHook = {
27842
28449
  decision: "allow",
27843
28450
  patch: {
27844
28451
  systemNote: [
27845
- `Environment: ${os9.type()} ${os9.release()} (${os9.platform()}/${os9.arch()})`,
28452
+ `Environment: ${os10.type()} ${os10.release()} (${os10.platform()}/${os10.arch()})`,
27846
28453
  `cwd: ${ctx.cwd}`,
27847
28454
  `provider: ${ctx.provider ?? "unknown"}`,
27848
28455
  `model: ${ctx.model ?? "unknown"}`,
@@ -27911,14 +28518,14 @@ function matchesHook(match, ctx) {
27911
28518
  const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
27912
28519
  const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
27913
28520
  if (!filePath) return false;
27914
- if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path17.resolve(ctx.cwd, filePath)))) return false;
28521
+ if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path18.resolve(ctx.cwd, filePath)))) return false;
27915
28522
  }
27916
28523
  return true;
27917
28524
  }
27918
28525
 
27919
28526
  // src/multimodal.ts
27920
- import { readFile as readFile8, stat as stat4 } from "node:fs/promises";
27921
- import path18 from "node:path";
28527
+ import { readFile as readFile9, stat as stat5 } from "node:fs/promises";
28528
+ import path19 from "node:path";
27922
28529
  var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
27923
28530
  async function buildUserContent(prompt, images = [], options2) {
27924
28531
  if (images.length === 0) return prompt;
@@ -27937,16 +28544,16 @@ async function buildUserContent(prompt, images = [], options2) {
27937
28544
  async function imageToUrl(input2, options2) {
27938
28545
  if (/^https?:\/\//i.test(input2) || /^data:image\//i.test(input2)) return input2;
27939
28546
  const filePath = resolveInsideCwd(options2.cwd, input2);
27940
- const info = await stat4(filePath);
28547
+ const info = await stat5(filePath);
27941
28548
  if (!info.isFile()) throw new Error(`Image input is not a file: ${input2}`);
27942
28549
  const maxImageBytes = options2.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
27943
28550
  if (info.size > maxImageBytes) throw new Error(`Image is larger than ${maxImageBytes} bytes: ${input2}`);
27944
28551
  const mime = mimeFromPath(filePath);
27945
- const bytes = await readFile8(filePath);
28552
+ const bytes = await readFile9(filePath);
27946
28553
  return `data:${mime};base64,${bytes.toString("base64")}`;
27947
28554
  }
27948
28555
  function mimeFromPath(filePath) {
27949
- switch (path18.extname(filePath).toLowerCase()) {
28556
+ switch (path19.extname(filePath).toLowerCase()) {
27950
28557
  case ".jpg":
27951
28558
  case ".jpeg":
27952
28559
  return "image/jpeg";
@@ -27957,15 +28564,15 @@ function mimeFromPath(filePath) {
27957
28564
  case ".webp":
27958
28565
  return "image/webp";
27959
28566
  default:
27960
- throw new Error(`Unsupported image extension: ${path18.extname(filePath) || "(none)"}`);
28567
+ throw new Error(`Unsupported image extension: ${path19.extname(filePath) || "(none)"}`);
27961
28568
  }
27962
28569
  }
27963
28570
 
27964
28571
  // src/permissions/persistent-grants.ts
27965
- import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
28572
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
27966
28573
  import fs8 from "node:fs";
27967
- import os10 from "node:os";
27968
- import path19 from "node:path";
28574
+ import os11 from "node:os";
28575
+ import path20 from "node:path";
27969
28576
  var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
27970
28577
  var PersistentGrantStore = class {
27971
28578
  filePath;
@@ -27996,22 +28603,22 @@ var PersistentGrantStore = class {
27996
28603
  }
27997
28604
  async #read() {
27998
28605
  if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
27999
- const data = JSON.parse(await readFile9(this.filePath, "utf8"));
28606
+ const data = JSON.parse(await readFile10(this.filePath, "utf8"));
28000
28607
  return {
28001
28608
  version: 1,
28002
28609
  grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
28003
28610
  };
28004
28611
  }
28005
28612
  async #write(file) {
28006
- await mkdir7(path19.dirname(this.filePath), { recursive: true });
28007
- await writeFile6(this.filePath, `${JSON.stringify(file, null, 2)}
28613
+ await mkdir8(path20.dirname(this.filePath), { recursive: true });
28614
+ await writeFile7(this.filePath, `${JSON.stringify(file, null, 2)}
28008
28615
  `, { mode: 384 });
28009
28616
  }
28010
28617
  };
28011
28618
  function resolveGrantPath(cwd, config) {
28012
- if (config.path) return path19.resolve(cwd, config.path);
28013
- if ((config.scope ?? "project") === "user") return path19.join(os10.homedir(), ".demian", "grants.json");
28014
- return path19.join(cwd, ".demian", "grants.json");
28619
+ if (config.path) return path20.resolve(cwd, config.path);
28620
+ if ((config.scope ?? "project") === "user") return path20.join(os11.homedir(), ".demian", "grants.json");
28621
+ return path20.join(cwd, ".demian", "grants.json");
28015
28622
  }
28016
28623
  function isGrantRecord(value) {
28017
28624
  if (!value || typeof value !== "object") return false;
@@ -28444,25 +29051,25 @@ function fail(content, metadata) {
28444
29051
  }
28445
29052
 
28446
29053
  // src/tools/output.ts
28447
- import { mkdir as mkdir8, writeFile as writeFile7 } from "node:fs/promises";
28448
- import path20 from "node:path";
29054
+ import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
29055
+ import path21 from "node:path";
28449
29056
  var DEFAULT_CAP_BYTES = 32 * 1024;
28450
29057
  var HALF_PREVIEW_BYTES = 16 * 1024;
28451
29058
  async function capToolOutput(result, options2) {
28452
29059
  const capBytes = options2.capBytes ?? DEFAULT_CAP_BYTES;
28453
29060
  const bytes = Buffer.byteLength(result.content, "utf8");
28454
29061
  if (bytes <= capBytes) return result;
28455
- const dir = path20.join(options2.cwd, ".demian", "tmp");
28456
- await mkdir8(dir, { recursive: true });
28457
- const outputPath = path20.join(dir, `output-${safeCallId(options2.callId)}.txt`);
28458
- await writeFile7(outputPath, result.content, "utf8");
29062
+ const dir = path21.join(options2.cwd, ".demian", "tmp");
29063
+ await mkdir9(dir, { recursive: true });
29064
+ const outputPath = path21.join(dir, `output-${safeCallId(options2.callId)}.txt`);
29065
+ await writeFile8(outputPath, result.content, "utf8");
28459
29066
  return {
28460
29067
  ...result,
28461
29068
  content: [
28462
29069
  sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
28463
29070
  `
28464
29071
 
28465
- [Full output saved to ${path20.relative(options2.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
29072
+ [Full output saved to ${path21.relative(options2.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
28466
29073
 
28467
29074
  `,
28468
29075
  sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
@@ -28487,8 +29094,8 @@ function sliceUtf8(text, startByte, endByte) {
28487
29094
  }
28488
29095
 
28489
29096
  // src/tools/read-file.ts
28490
- import { open as open2, stat as stat5 } from "node:fs/promises";
28491
- import path21 from "node:path";
29097
+ import { open as open2, stat as stat6 } from "node:fs/promises";
29098
+ import path22 from "node:path";
28492
29099
 
28493
29100
  // src/tools/validation.ts
28494
29101
  function assertObject(input2, toolName) {
@@ -28558,7 +29165,7 @@ var readFileTool = {
28558
29165
  const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
28559
29166
  const offset = positiveInteger(optionalNumberField(object2, "offset"), 1, "offset");
28560
29167
  const limit = positiveInteger(optionalNumberField(object2, "limit"), DEFAULT_LIMIT, "limit");
28561
- const info = await stat5(filePath);
29168
+ const info = await stat6(filePath);
28562
29169
  if (info.isDirectory()) throw new Error(`read_file expected a file but got a directory: ${relativeToCwd(ctx.cwd, filePath)}`);
28563
29170
  if (info.size > MAX_BYTES) throw new Error(`File is larger than ${MAX_BYTES} bytes: ${relativeToCwd(ctx.cwd, filePath)}`);
28564
29171
  const sample = await readBytes(filePath, Math.min(SAMPLE_BYTES, info.size));
@@ -28572,7 +29179,7 @@ var readFileTool = {
28572
29179
  const truncated = start + sliced.length < lines.length;
28573
29180
  return ok(content + (truncated ? `
28574
29181
  ... (${lines.length - start - sliced.length} more lines)` : ""), {
28575
- path: path21.relative(ctx.cwd, filePath),
29182
+ path: path22.relative(ctx.cwd, filePath),
28576
29183
  lines: sliced.length,
28577
29184
  totalLines: lines.length,
28578
29185
  truncated
@@ -28610,8 +29217,8 @@ function looksBinary(buffer) {
28610
29217
  }
28611
29218
 
28612
29219
  // src/tools/write-file.ts
28613
- import { mkdir as mkdir9, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
28614
- import path22 from "node:path";
29220
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
29221
+ import path23 from "node:path";
28615
29222
  var writeFileTool = {
28616
29223
  name: "write_file",
28617
29224
  description: "Create or replace a text file inside the workspace.",
@@ -28629,8 +29236,8 @@ var writeFileTool = {
28629
29236
  const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
28630
29237
  const content = stringField(object2, "content", { allowEmpty: true });
28631
29238
  const before = await readOptionalTextFile(filePath);
28632
- await mkdir9(path22.dirname(filePath), { recursive: true });
28633
- await writeFile8(filePath, content, "utf8");
29239
+ await mkdir10(path23.dirname(filePath), { recursive: true });
29240
+ await writeFile9(filePath, content, "utf8");
28634
29241
  const relative = relativeToCwd(ctx.cwd, filePath);
28635
29242
  const diff = createTextDiff(before, content, relative);
28636
29243
  return ok(`Wrote ${relativeToCwd(ctx.cwd, filePath)} (${Buffer.byteLength(content, "utf8")} bytes).`, {
@@ -28643,7 +29250,7 @@ var writeFileTool = {
28643
29250
  };
28644
29251
  async function readOptionalTextFile(filePath) {
28645
29252
  try {
28646
- return await readFile10(filePath, "utf8");
29253
+ return await readFile11(filePath, "utf8");
28647
29254
  } catch (error) {
28648
29255
  if (isNotFound(error)) return void 0;
28649
29256
  throw error;
@@ -28654,7 +29261,7 @@ function isNotFound(error) {
28654
29261
  }
28655
29262
 
28656
29263
  // src/tools/edit-file.ts
28657
- import { readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
29264
+ import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
28658
29265
  var editFileTool = {
28659
29266
  name: "edit_file",
28660
29267
  description: "Replace an exact string in a text file inside the workspace.",
@@ -28676,12 +29283,12 @@ var editFileTool = {
28676
29283
  const newString = stringField(object2, "newString", { allowEmpty: true });
28677
29284
  const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
28678
29285
  if (oldString === newString) throw new Error("oldString and newString must be different");
28679
- const before = await readFile11(filePath, "utf8");
29286
+ const before = await readFile12(filePath, "utf8");
28680
29287
  const matches = countMatches(before, oldString);
28681
29288
  if (matches === 0) throw new Error("oldString was not found");
28682
29289
  if (matches > 1 && !replaceAll) throw new Error(`oldString matched ${matches} times; set replaceAll=true to replace all matches`);
28683
29290
  const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
28684
- await writeFile9(filePath, after, "utf8");
29291
+ await writeFile10(filePath, after, "utf8");
28685
29292
  const relative = relativeToCwd(ctx.cwd, filePath);
28686
29293
  const diff = createTextDiff(before, after, relative);
28687
29294
  return ok(`Edited ${relative} (${replaceAll ? matches : 1} replacement${matches === 1 ? "" : "s"}).`, {
@@ -28705,7 +29312,7 @@ function countMatches(text, needle) {
28705
29312
 
28706
29313
  // src/tools/bash.ts
28707
29314
  import { spawn as spawn5 } from "node:child_process";
28708
- import path24 from "node:path";
29315
+ import path25 from "node:path";
28709
29316
 
28710
29317
  // src/sandbox/env-only.ts
28711
29318
  function buildEnvOnlyLaunch(command, config) {
@@ -28765,7 +29372,7 @@ function bwrapPath() {
28765
29372
 
28766
29373
  // src/sandbox/macos.ts
28767
29374
  import { existsSync as existsSync3 } from "node:fs";
28768
- import path23 from "node:path";
29375
+ import path24 from "node:path";
28769
29376
  function canUseMacOSSandbox() {
28770
29377
  return process.platform === "darwin" && existsSync3("/usr/bin/sandbox-exec");
28771
29378
  }
@@ -28791,7 +29398,7 @@ function macosProfile(cwd, config) {
28791
29398
  }
28792
29399
  if (mode === "workspace-write") {
28793
29400
  lines.push("(deny file-write*)");
28794
- lines.push(`(allow file-write* (subpath "${escapeProfilePath(path23.resolve(cwd))}"))`);
29401
+ lines.push(`(allow file-write* (subpath "${escapeProfilePath(path24.resolve(cwd))}"))`);
28795
29402
  lines.push('(allow file-write* (subpath "/tmp"))');
28796
29403
  lines.push('(allow file-write* (subpath "/private/tmp"))');
28797
29404
  lines.push('(allow file-write* (subpath "/private/var/folders"))');
@@ -28853,7 +29460,7 @@ ${result.stderr}` : ""
28853
29460
  ].filter(Boolean).join("\n");
28854
29461
  return ok(content, {
28855
29462
  command,
28856
- workdir: path24.relative(ctx.cwd, workdir) || ".",
29463
+ workdir: path25.relative(ctx.cwd, workdir) || ".",
28857
29464
  exitCode: result.exitCode,
28858
29465
  signal: result.signal,
28859
29466
  timedOut: result.timedOut,
@@ -28905,8 +29512,8 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
28905
29512
 
28906
29513
  // src/tools/grep.ts
28907
29514
  import { spawn as spawn6 } from "node:child_process";
28908
- import { readdir as readdir2, readFile as readFile12, stat as stat6 } from "node:fs/promises";
28909
- import path25 from "node:path";
29515
+ import { readdir as readdir2, readFile as readFile13, stat as stat7 } from "node:fs/promises";
29516
+ import path26 from "node:path";
28910
29517
  var MAX_MATCHES = 200;
28911
29518
  var grepTool = {
28912
29519
  name: "grep",
@@ -28981,7 +29588,7 @@ function runRg(cwd, base, pattern, glob, signal) {
28981
29588
  }
28982
29589
  function rewriteRgPath(cwd, line) {
28983
29590
  const [file, rest] = splitFirst(line, ":");
28984
- if (!path25.isAbsolute(file)) return line;
29591
+ if (!path26.isAbsolute(file)) return line;
28985
29592
  return `${relativeToCwd(cwd, file)}:${rest}`;
28986
29593
  }
28987
29594
  function splitFirst(value, delimiter) {
@@ -28996,13 +29603,13 @@ async function fallbackSearch(cwd, base, pattern, glob) {
28996
29603
  if (out.length > MAX_MATCHES) return;
28997
29604
  const relative = relativeToCwd(cwd, item);
28998
29605
  if (isIgnoredPath(relative)) return;
28999
- const info = await stat6(item);
29606
+ const info = await stat7(item);
29000
29607
  if (info.isDirectory()) {
29001
- for (const entry of await readdir2(item)) await walk2(path25.join(item, entry));
29608
+ for (const entry of await readdir2(item)) await walk2(path26.join(item, entry));
29002
29609
  return;
29003
29610
  }
29004
29611
  if (glob && !matchGlob(glob, relative)) return;
29005
- const buffer = await readFile12(item);
29612
+ const buffer = await readFile13(item);
29006
29613
  if (buffer.includes(0)) return;
29007
29614
  const lines = buffer.toString("utf8").split(/\r?\n/);
29008
29615
  for (let i = 0; i < lines.length; i++) {
@@ -29016,8 +29623,8 @@ async function fallbackSearch(cwd, base, pattern, glob) {
29016
29623
  }
29017
29624
 
29018
29625
  // src/tools/glob.ts
29019
- import { readdir as readdir3, stat as stat7 } from "node:fs/promises";
29020
- import path26 from "node:path";
29626
+ import { readdir as readdir3, stat as stat8 } from "node:fs/promises";
29627
+ import path27 from "node:path";
29021
29628
  var MAX_PATHS = 1e3;
29022
29629
  var globTool = {
29023
29630
  name: "glob",
@@ -29049,10 +29656,10 @@ async function collectPaths(cwd, root, pattern) {
29049
29656
  async function walk2(dir) {
29050
29657
  const entries = await readdir3(dir, { withFileTypes: true });
29051
29658
  for (const entry of entries) {
29052
- const absolute = path26.join(dir, entry.name);
29659
+ const absolute = path27.join(dir, entry.name);
29053
29660
  const relative = relativeToCwd(cwd, absolute);
29054
29661
  if (isIgnoredPath(relative)) continue;
29055
- const info = await stat7(absolute);
29662
+ const info = await stat8(absolute);
29056
29663
  if (matchGlob(pattern, relative)) out.push({ relative, mtimeMs: info.mtimeMs });
29057
29664
  if (entry.isDirectory()) await walk2(absolute);
29058
29665
  }
@@ -30051,14 +30658,14 @@ async function runExecutionSession(options2) {
30051
30658
  }
30052
30659
 
30053
30660
  // src/goals/lock.ts
30054
- import { mkdir as mkdir11, open as open3, rm as rm2 } from "node:fs/promises";
30055
- import path28 from "node:path";
30661
+ import { mkdir as mkdir12, open as open3, rm as rm2 } from "node:fs/promises";
30662
+ import path29 from "node:path";
30056
30663
 
30057
30664
  // src/goals/storage.ts
30058
30665
  import { createHash as createHash3 } from "node:crypto";
30059
- import { mkdir as mkdir10, readFile as readFile13, rename as rename4, writeFile as writeFile10 } from "node:fs/promises";
30666
+ import { mkdir as mkdir11, readFile as readFile14, rename as rename5, writeFile as writeFile11 } from "node:fs/promises";
30060
30667
  import fs9 from "node:fs";
30061
- import path27 from "node:path";
30668
+ import path28 from "node:path";
30062
30669
  var GoalStore = class {
30063
30670
  cwd;
30064
30671
  dir;
@@ -30069,12 +30676,12 @@ var GoalStore = class {
30069
30676
  this.cwd = cwd;
30070
30677
  this.scope = normalizeGoalStoreScope(options2.scope);
30071
30678
  this.dir = goalStoreDir(cwd, this.scope);
30072
- this.activePath = path27.join(this.dir, "active.json");
30073
- this.archiveDir = path27.join(this.dir, "archive");
30679
+ this.activePath = path28.join(this.dir, "active.json");
30680
+ this.archiveDir = path28.join(this.dir, "archive");
30074
30681
  }
30075
30682
  async loadActive() {
30076
30683
  if (!fs9.existsSync(this.activePath)) return void 0;
30077
- const text = await readFile13(this.activePath, "utf8");
30684
+ const text = await readFile14(this.activePath, "utf8");
30078
30685
  try {
30079
30686
  return JSON.parse(text);
30080
30687
  } catch {
@@ -30083,36 +30690,36 @@ var GoalStore = class {
30083
30690
  }
30084
30691
  }
30085
30692
  async saveActive(state) {
30086
- await mkdir10(this.dir, { recursive: true });
30087
- const tmp = path27.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
30088
- await writeFile10(tmp, `${JSON.stringify(state, null, 2)}
30693
+ await mkdir11(this.dir, { recursive: true });
30694
+ const tmp = path28.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
30695
+ await writeFile11(tmp, `${JSON.stringify(state, null, 2)}
30089
30696
  `, "utf8");
30090
- await rename4(tmp, this.activePath);
30697
+ await rename5(tmp, this.activePath);
30091
30698
  }
30092
30699
  async archiveActive(status = "cleared") {
30093
30700
  const state = await this.loadActive();
30094
30701
  if (!state) return void 0;
30095
- await mkdir10(this.archiveDir, { recursive: true });
30702
+ await mkdir11(this.archiveDir, { recursive: true });
30096
30703
  const archived = {
30097
30704
  ...state,
30098
30705
  status,
30099
30706
  updatedAt: Date.now()
30100
30707
  };
30101
- await writeFile10(path27.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
30708
+ await writeFile11(path28.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
30102
30709
  `, "utf8");
30103
30710
  await fs9.promises.rm(this.activePath, { force: true });
30104
30711
  return archived;
30105
30712
  }
30106
30713
  async backupCorrupted(text) {
30107
- await mkdir10(this.dir, { recursive: true });
30108
- const backup = path27.join(this.dir, `active.corrupt.${Date.now()}.json`);
30109
- await writeFile10(backup, text, "utf8");
30714
+ await mkdir11(this.dir, { recursive: true });
30715
+ const backup = path28.join(this.dir, `active.corrupt.${Date.now()}.json`);
30716
+ await writeFile11(backup, text, "utf8");
30110
30717
  await fs9.promises.rm(this.activePath, { force: true });
30111
30718
  }
30112
30719
  };
30113
30720
  function goalStoreDir(cwd, scope) {
30114
30721
  const safeScope = normalizeGoalStoreScope(scope);
30115
- return safeScope ? path27.join(cwd, ".demian", "goals", "sessions", safeScope) : path27.join(cwd, ".demian", "goals");
30722
+ return safeScope ? path28.join(cwd, ".demian", "goals", "sessions", safeScope) : path28.join(cwd, ".demian", "goals");
30116
30723
  }
30117
30724
  function normalizeGoalStoreScope(scope) {
30118
30725
  const trimmed = scope?.trim();
@@ -30126,10 +30733,10 @@ function normalizeGoalStoreScope(scope) {
30126
30733
  var GoalLock = class {
30127
30734
  path;
30128
30735
  constructor(cwd, scope) {
30129
- this.path = path28.join(goalStoreDir(cwd, scope), "active.lock");
30736
+ this.path = path29.join(goalStoreDir(cwd, scope), "active.lock");
30130
30737
  }
30131
30738
  async acquire() {
30132
- await mkdir11(path28.dirname(this.path), { recursive: true });
30739
+ await mkdir12(path29.dirname(this.path), { recursive: true });
30133
30740
  const handle = await open3(this.path, "wx").catch((error) => {
30134
30741
  const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
30135
30742
  if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
@@ -30717,8 +31324,8 @@ function sha256(value) {
30717
31324
 
30718
31325
  // src/root-session.ts
30719
31326
  import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
30720
- import os11 from "node:os";
30721
- import path29 from "node:path";
31327
+ import os12 from "node:os";
31328
+ import path30 from "node:path";
30722
31329
 
30723
31330
  // src/tools/cowork.ts
30724
31331
  function createCoworkTool(options2) {
@@ -31541,8 +32148,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
31541
32148
  }
31542
32149
  }
31543
32150
  async createCoworkIsolatedWorkspace(groupId, memberId) {
31544
- const root = await mkdtemp(path29.join(os11.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
31545
- const cwd = path29.join(root, "workspace");
32151
+ const root = await mkdtemp(path30.join(os12.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
32152
+ const cwd = path30.join(root, "workspace");
31546
32153
  await cp(this.#options.cwd, cwd, {
31547
32154
  recursive: true,
31548
32155
  filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
@@ -31897,7 +32504,7 @@ function scopeStaticPrefix(pattern) {
31897
32504
  return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
31898
32505
  }
31899
32506
  function shouldCopyIntoCoworkWorkspace(root, source) {
31900
- const relative = path29.relative(root, source).split(path29.sep).join("/");
32507
+ const relative = path30.relative(root, source).split(path30.sep).join("/");
31901
32508
  if (!relative) return true;
31902
32509
  const parts = relative.split("/");
31903
32510
  return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
@@ -32109,13 +32716,13 @@ function finitePositiveInteger(value) {
32109
32716
  }
32110
32717
 
32111
32718
  // src/ui/preferences.ts
32112
- import { mkdir as mkdir12, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
32719
+ import { mkdir as mkdir13, readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
32113
32720
  import fs10 from "node:fs";
32114
- import path30 from "node:path";
32721
+ import path31 from "node:path";
32115
32722
  var UiPreferenceStore = class {
32116
32723
  filePath;
32117
32724
  constructor(cwd, filePath) {
32118
- this.filePath = filePath ? path30.resolve(cwd, filePath) : path30.join(cwd, ".demian", "preferences.json");
32725
+ this.filePath = filePath ? path31.resolve(cwd, filePath) : path31.join(cwd, ".demian", "preferences.json");
32119
32726
  }
32120
32727
  async load() {
32121
32728
  if (!fs10.existsSync(this.filePath)) return void 0;
@@ -32133,13 +32740,13 @@ var UiPreferenceStore = class {
32133
32740
  model: selection.model,
32134
32741
  updatedAt: Date.now()
32135
32742
  };
32136
- await mkdir12(path30.dirname(this.filePath), { recursive: true });
32137
- await writeFile11(this.filePath, `${JSON.stringify(file, null, 2)}
32743
+ await mkdir13(path31.dirname(this.filePath), { recursive: true });
32744
+ await writeFile12(this.filePath, `${JSON.stringify(file, null, 2)}
32138
32745
  `, { mode: 384 });
32139
32746
  }
32140
32747
  async #read() {
32141
32748
  try {
32142
- return JSON.parse(await readFile14(this.filePath, "utf8"));
32749
+ return JSON.parse(await readFile15(this.filePath, "utf8"));
32143
32750
  } catch {
32144
32751
  return void 0;
32145
32752
  }
@@ -32155,9 +32762,9 @@ function preferenceKey(selection) {
32155
32762
 
32156
32763
  // src/models/catalog.ts
32157
32764
  import crypto6 from "node:crypto";
32158
- import { mkdir as mkdir13, readFile as readFile15, rename as rename5, writeFile as writeFile12 } from "node:fs/promises";
32159
- import os12 from "node:os";
32160
- import path31 from "node:path";
32765
+ import { mkdir as mkdir14, readFile as readFile16, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
32766
+ import os13 from "node:os";
32767
+ import path32 from "node:path";
32161
32768
  var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
32162
32769
  var PING_TIMEOUT_MS = 1500;
32163
32770
  var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
@@ -32469,12 +33076,12 @@ async function fetchJson2(url, options2) {
32469
33076
  return text ? JSON.parse(text) : {};
32470
33077
  }
32471
33078
  function cachePath(cacheKey) {
32472
- return path31.join(os12.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
33079
+ return path32.join(os13.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
32473
33080
  }
32474
33081
  async function readCatalogCache(cacheKey, ttlMs, allow) {
32475
33082
  if (!allow) return void 0;
32476
33083
  try {
32477
- const raw = JSON.parse(await readFile15(cachePath(cacheKey), "utf8"));
33084
+ const raw = JSON.parse(await readFile16(cachePath(cacheKey), "utf8"));
32478
33085
  if (!raw.result) return void 0;
32479
33086
  if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
32480
33087
  return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
@@ -32484,10 +33091,10 @@ async function readCatalogCache(cacheKey, ttlMs, allow) {
32484
33091
  }
32485
33092
  async function writeCatalogCache(cacheKey, result) {
32486
33093
  const filePath = cachePath(cacheKey);
32487
- await mkdir13(path31.dirname(filePath), { recursive: true, mode: 448 });
33094
+ await mkdir14(path32.dirname(filePath), { recursive: true, mode: 448 });
32488
33095
  const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
32489
- await writeFile12(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
32490
- await rename5(temp, filePath);
33096
+ await writeFile13(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
33097
+ await rename6(temp, filePath);
32491
33098
  }
32492
33099
  function safeCacheName(providerName) {
32493
33100
  return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
@@ -32538,7 +33145,7 @@ async function codexClientVersion(override) {
32538
33145
  }
32539
33146
  async function readPackageVersion() {
32540
33147
  try {
32541
- const raw = JSON.parse(await readFile15(new URL("../package.json", import.meta.url), "utf8"));
33148
+ const raw = JSON.parse(await readFile16(new URL("../package.json", import.meta.url), "utf8"));
32542
33149
  return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
32543
33150
  } catch {
32544
33151
  return DEFAULT_CODEX_CLIENT_VERSION;
@@ -32602,6 +33209,9 @@ function providerOptions(config, catalogs = {}) {
32602
33209
  permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
32603
33210
  ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
32604
33211
  available,
33212
+ baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
33213
+ apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
33214
+ auth: sanitizeProviderAuth(provider.auth),
32605
33215
  catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
32606
33216
  };
32607
33217
  }).sort((left, right) => {
@@ -32673,13 +33283,20 @@ function providerCatalogAvailable(catalog) {
32673
33283
  function unavailableLabel(value) {
32674
33284
  return `(n/a) ${value}`;
32675
33285
  }
33286
+ function sanitizeProviderAuth(auth) {
33287
+ if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
33288
+ const record = auth;
33289
+ const type = typeof record.type === "string" ? record.type : void 0;
33290
+ const header = typeof record.header === "string" ? record.header : void 0;
33291
+ return type || header ? { type, header } : void 0;
33292
+ }
32676
33293
  function errorMessage3(error) {
32677
33294
  return error instanceof Error ? error.message : String(error);
32678
33295
  }
32679
33296
 
32680
33297
  // src/ui/conversations.ts
32681
- import { mkdir as mkdir14, readFile as readFile16, readdir as readdir4, rm as rm4, writeFile as writeFile13 } from "node:fs/promises";
32682
- import path32 from "node:path";
33298
+ import { mkdir as mkdir15, readFile as readFile17, readdir as readdir4, rm as rm4, writeFile as writeFile14 } from "node:fs/promises";
33299
+ import path33 from "node:path";
32683
33300
  var CONVERSATIONS_DIR = "conversations";
32684
33301
  var CONVERSATION_INDEX_FILE = "conversations.json";
32685
33302
  var CONVERSATION_FILE = "conversation.json";
@@ -32692,13 +33309,13 @@ function createConversationRecord(input2) {
32692
33309
  title: input2.title?.trim() || "New session",
32693
33310
  createdAt: timestamp,
32694
33311
  updatedAt: timestamp,
32695
- cwd: path32.resolve(input2.cwd),
33312
+ cwd: path33.resolve(input2.cwd),
32696
33313
  modelHistory: cleanModelHistory(input2.history ?? []),
32697
33314
  snapshot: input2.snapshot
32698
33315
  };
32699
33316
  }
32700
33317
  async function loadConversationIndex(storageDir = defaultDemianStorageDir()) {
32701
- const index = await readJson(path32.join(storageDir, CONVERSATION_INDEX_FILE));
33318
+ const index = await readJson(path33.join(storageDir, CONVERSATION_INDEX_FILE));
32702
33319
  const conversations = Array.isArray(index?.conversations) ? index.conversations.map((item) => {
32703
33320
  if (!item || typeof item !== "object") return void 0;
32704
33321
  const object2 = item;
@@ -32732,7 +33349,7 @@ async function loadConversationRecords(storageDir = defaultDemianStorageDir()) {
32732
33349
  async function saveConversationRecords(storageDir, records, selectedSessionId) {
32733
33350
  const root = storageDir ?? defaultDemianStorageDir();
32734
33351
  const kept = [...records].sort((a, b2) => b2.updatedAt - a.updatedAt).slice(0, MAX_STORED_CONVERSATIONS).map((record) => ({ ...record, id: sanitizeConversationId(record.id), modelHistory: cleanModelHistory(record.modelHistory) }));
32735
- await mkdir14(path32.join(root, CONVERSATIONS_DIR), { recursive: true });
33352
+ await mkdir15(path33.join(root, CONVERSATIONS_DIR), { recursive: true });
32736
33353
  for (const record of kept) {
32737
33354
  await writeJson(conversationRecordPath(root, record.id), {
32738
33355
  version: 1,
@@ -32745,7 +33362,7 @@ async function saveConversationRecords(storageDir, records, selectedSessionId) {
32745
33362
  snapshot: record.snapshot
32746
33363
  });
32747
33364
  }
32748
- await writeJson(path32.join(root, CONVERSATION_INDEX_FILE), {
33365
+ await writeJson(path33.join(root, CONVERSATION_INDEX_FILE), {
32749
33366
  version: 1,
32750
33367
  selectedSessionId,
32751
33368
  conversations: kept.map((record) => ({
@@ -32770,7 +33387,7 @@ function findConversation(records, target) {
32770
33387
  return records.find((record) => record.id === value || record.id.startsWith(value)) ?? records.find((record) => record.title.toLowerCase() === normalized) ?? records.find((record) => record.title.toLowerCase().includes(normalized));
32771
33388
  }
32772
33389
  function sortConversationRecords(records, cwd) {
32773
- const resolvedCwd = path32.resolve(cwd);
33390
+ const resolvedCwd = path33.resolve(cwd);
32774
33391
  return [...records].sort((a, b2) => {
32775
33392
  const aCurrent = isConversationForCwd(a, resolvedCwd) ? 0 : 1;
32776
33393
  const bCurrent = isConversationForCwd(b2, resolvedCwd) ? 0 : 1;
@@ -32785,7 +33402,7 @@ function conversationRuntimeStatus(record, cwd) {
32785
33402
  return isConversationForCwd(record, cwd) ? "ready" : "stored";
32786
33403
  }
32787
33404
  function isConversationForCwd(record, cwd) {
32788
- return path32.resolve(record.cwd) === path32.resolve(cwd);
33405
+ return path33.resolve(record.cwd) === path33.resolve(cwd);
32789
33406
  }
32790
33407
  function conversationSummaryLine(record, index, activeId) {
32791
33408
  const active = record.id === activeId ? "*" : " ";
@@ -32805,14 +33422,14 @@ function cleanModelHistory(history) {
32805
33422
  return history.filter((message) => !isInternalModelHistoryMessage(message)).slice(-MAX_MODEL_HISTORY);
32806
33423
  }
32807
33424
  function conversationDir(storageDir, id) {
32808
- return path32.join(storageDir, CONVERSATIONS_DIR, sanitizeConversationId(id));
33425
+ return path33.join(storageDir, CONVERSATIONS_DIR, sanitizeConversationId(id));
32809
33426
  }
32810
33427
  function conversationRecordPath(storageDir, id) {
32811
- return path32.join(conversationDir(storageDir, id), CONVERSATION_FILE);
33428
+ return path33.join(conversationDir(storageDir, id), CONVERSATION_FILE);
32812
33429
  }
32813
33430
  async function listConversationRecordIds(storageDir) {
32814
33431
  try {
32815
- const entries = await readdir4(path32.join(storageDir, CONVERSATIONS_DIR), { withFileTypes: true });
33432
+ const entries = await readdir4(path33.join(storageDir, CONVERSATIONS_DIR), { withFileTypes: true });
32816
33433
  return entries.filter((entry) => entry.isDirectory()).map((entry) => sanitizeConversationId(entry.name)).filter(Boolean);
32817
33434
  } catch {
32818
33435
  return [];
@@ -32820,14 +33437,14 @@ async function listConversationRecordIds(storageDir) {
32820
33437
  }
32821
33438
  async function readJson(filePath) {
32822
33439
  try {
32823
- return JSON.parse(await readFile16(filePath, "utf8"));
33440
+ return JSON.parse(await readFile17(filePath, "utf8"));
32824
33441
  } catch {
32825
33442
  return void 0;
32826
33443
  }
32827
33444
  }
32828
33445
  async function writeJson(filePath, value) {
32829
- await mkdir14(path32.dirname(filePath), { recursive: true });
32830
- await writeFile13(filePath, `${JSON.stringify(value, null, 2)}
33446
+ await mkdir15(path33.dirname(filePath), { recursive: true });
33447
+ await writeFile14(filePath, `${JSON.stringify(value, null, 2)}
32831
33448
  `, "utf8");
32832
33449
  }
32833
33450
  function normalizeConversationRecord(value, fallbackId) {
@@ -32882,11 +33499,12 @@ function formatRelativeAge(ms2) {
32882
33499
 
32883
33500
  // src/ui/tui/controller.ts
32884
33501
  async function runTuiSession(flags, store2) {
32885
- const cwd = path33.resolve(flags.cwd ?? process.cwd());
33502
+ const cwd = path34.resolve(flags.cwd ?? process.cwd());
32886
33503
  const eventBus = new EventBus();
32887
33504
  eventBus.subscribe((event) => store2.handleEvent(event));
32888
- const loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
32889
- const config = flags.context ? mergeConfig(loadedConfig2, {
33505
+ const configInit = await ensureTuiConfigExists(flags);
33506
+ let loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
33507
+ let config = flags.context ? mergeConfig(loadedConfig2, {
32890
33508
  context: {
32891
33509
  main: flags.context
32892
33510
  }
@@ -32906,6 +33524,10 @@ async function runTuiSession(flags, store2) {
32906
33524
  store2.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
32907
33525
  agent: agentName,
32908
33526
  cwd,
33527
+ configPath: configMutationPath(flags),
33528
+ configCreated: configInit.created,
33529
+ configDefaultProvider: config.defaultProvider,
33530
+ configMessage: configInit.created ? `Created config at ${configInit.path}` : void 0,
32909
33531
  agentOptions,
32910
33532
  contextEfficiency: {
32911
33533
  maxContextTokens: config.context.main.maxContextTokens,
@@ -32914,6 +33536,7 @@ async function runTuiSession(flags, store2) {
32914
33536
  }
32915
33537
  });
32916
33538
  let nextPrompt = flags.prompt;
33539
+ let openConfigOnFirstPrompt = configInit.created && !flags.prompt.trim();
32917
33540
  const conversationsEnabled = flags.conversationManagement === true;
32918
33541
  const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
32919
33542
  let currentConversation = conversationState?.current;
@@ -32954,13 +33577,28 @@ async function runTuiSession(flags, store2) {
32954
33577
  await applyStartupSessionSelection(selected);
32955
33578
  }
32956
33579
  for (; ; ) {
32957
- const prompt = await store2.requestPrompt(nextPrompt);
33580
+ const promptPromise = store2.requestPrompt(nextPrompt);
33581
+ if (openConfigOnFirstPrompt) {
33582
+ openConfigOnFirstPrompt = false;
33583
+ store2.openConfigManager(`Created config at ${configInit.path}`);
33584
+ }
33585
+ const prompt = await promptPromise;
32958
33586
  nextPrompt = void 0;
32959
33587
  if (store2.exitRequested()) {
32960
33588
  await saveCurrentSelection();
32961
33589
  return 0;
32962
33590
  }
32963
33591
  if (!prompt) return 0;
33592
+ const configAction = parseTuiConfigAction(prompt);
33593
+ if (configAction) {
33594
+ await handleConfigAction(configAction);
33595
+ continue;
33596
+ }
33597
+ const sessionAction = parseTuiSessionAction(prompt);
33598
+ if (sessionAction) {
33599
+ await handleSessionSelectionShortcut();
33600
+ continue;
33601
+ }
32964
33602
  const sessionCommand = parseSessionCommand(prompt);
32965
33603
  if (sessionCommand) {
32966
33604
  await handleSessionCommand(sessionCommand);
@@ -33237,6 +33875,25 @@ ${sessionListMessage()}`);
33237
33875
  await persistCurrentConversation();
33238
33876
  }
33239
33877
  }
33878
+ async function handleSessionSelectionShortcut() {
33879
+ if (!conversationsEnabled) {
33880
+ store2.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
33881
+ store2.prepareForPrompt("waiting for next message");
33882
+ return;
33883
+ }
33884
+ if (!currentConversation) {
33885
+ currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
33886
+ conversationRecords.set(currentConversation.id, currentConversation);
33887
+ }
33888
+ await persistCurrentConversation();
33889
+ const selection = await store2.requestSessionSelection(startupSessionOptions(), { cancel: "prompt" });
33890
+ if (selection.kind === "cancel") {
33891
+ store2.prepareForPrompt("waiting for next message");
33892
+ return;
33893
+ }
33894
+ if (selection.kind === "exit" || store2.exitRequested()) return;
33895
+ await applyStartupSessionSelection(selection);
33896
+ }
33240
33897
  function startupSessionOptions() {
33241
33898
  const records = sortedConversations();
33242
33899
  const sessionOptions = records.map((record) => ({
@@ -33246,11 +33903,15 @@ ${sessionListMessage()}`);
33246
33903
  cwd: record.cwd,
33247
33904
  status: conversationRuntimeStatus(record, cwd),
33248
33905
  updatedAt: record.updatedAt,
33249
- currentWorkspace: path33.resolve(record.cwd) === cwd
33906
+ currentWorkspace: path34.resolve(record.cwd) === cwd
33250
33907
  }));
33251
33908
  return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
33252
33909
  }
33253
33910
  async function applyStartupSessionSelection(selection) {
33911
+ if (selection.kind === "cancel") {
33912
+ store2.prepareForPrompt("waiting for next message");
33913
+ return;
33914
+ }
33254
33915
  if (selection.kind === "new") {
33255
33916
  const next = createConversationRecord({ id: createRootSessionId(), cwd });
33256
33917
  conversationRecords.set(next.id, next);
@@ -33307,6 +33968,87 @@ ${sessionListMessage()}`);
33307
33968
  await preferenceStore.save(selection);
33308
33969
  savedSelectionKey = key;
33309
33970
  }
33971
+ async function handleConfigAction(action) {
33972
+ const path37 = configMutationPath(flags);
33973
+ try {
33974
+ let message = "Config updated.";
33975
+ let nextSelection = {};
33976
+ if (action.type === "setDefaultProvider") {
33977
+ await updateConfigDefaults({ path: path37, defaultProvider: action.provider });
33978
+ message = `Default provider saved: ${action.provider}`;
33979
+ nextSelection = { provider: action.provider };
33980
+ } else if (action.type === "setDefaultModel") {
33981
+ await setDefaultModelProfile({
33982
+ path: path37,
33983
+ provider: action.provider,
33984
+ profileName: action.profileName,
33985
+ name: action.name,
33986
+ displayName: action.displayName,
33987
+ model: action.model
33988
+ });
33989
+ message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
33990
+ nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
33991
+ } else if (action.type === "addProvider") {
33992
+ await addProvider({ path: path37, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
33993
+ message = `Provider added: ${action.name}`;
33994
+ nextSelection = { provider: action.name };
33995
+ } else if (action.type === "updateProvider") {
33996
+ await updateProviderSettings({
33997
+ path: path37,
33998
+ provider: action.provider,
33999
+ ...action.field === "baseURL" ? { baseURL: action.value } : {},
34000
+ ...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
34001
+ ...action.field === "authHeader" ? { authHeader: action.value } : {}
34002
+ });
34003
+ message = `Provider ${action.field} saved: ${action.provider}`;
34004
+ nextSelection = { provider: action.provider };
34005
+ }
34006
+ await reloadConfigSettings(message, nextSelection);
34007
+ } catch (error) {
34008
+ store2.prepareForPrompt("config action failed");
34009
+ store2.showSystemMessage("Config", errorMessage(error));
34010
+ store2.openConfigManager(errorMessage(error));
34011
+ }
34012
+ }
34013
+ async function reloadConfigSettings(message, selectionFlags = {}) {
34014
+ loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
34015
+ config = flags.context ? mergeConfig(loadedConfig2, {
34016
+ context: {
34017
+ main: flags.context
34018
+ }
34019
+ }) : loadedConfig2;
34020
+ const currentSelection = store2.currentSelection();
34021
+ const selection = createInteractiveModelSelection(
34022
+ config,
34023
+ selectionFlags.provider || selectionFlags.model ? selectionFlags : { provider: currentSelection.providerName, model: currentSelection.model }
34024
+ );
34025
+ store2.configureSettings(selection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
34026
+ agent: store2.currentAgentName(),
34027
+ cwd,
34028
+ configPath: configMutationPath(flags),
34029
+ configDefaultProvider: config.defaultProvider,
34030
+ configMessage: message,
34031
+ agentOptions,
34032
+ contextEfficiency: {
34033
+ maxContextTokens: config.context.main.maxContextTokens,
34034
+ compactAtRatio: config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5,
34035
+ compactAtTokens: config.context.main.maxContextTokens ? Math.floor(config.context.main.maxContextTokens * (config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5)) : void 0
34036
+ }
34037
+ });
34038
+ store2.openConfigManager(message);
34039
+ }
34040
+ }
34041
+ async function ensureTuiConfigExists(flags) {
34042
+ const target = configMutationPath(flags);
34043
+ if (!flags.configPath) {
34044
+ const jsondPath = target.replace(/\.json$/i, ".jsond");
34045
+ if (existsSync4(target) || existsSync4(jsondPath)) return { path: target, created: false };
34046
+ }
34047
+ const result = await createUserConfig({ path: target });
34048
+ return { path: result.path, created: result.created };
34049
+ }
34050
+ function configMutationPath(flags) {
34051
+ return flags.configPath ? path34.resolve(flags.configPath) : defaultUserConfigPath();
33310
34052
  }
33311
34053
  function isGoalStateClearingAction(action) {
33312
34054
  return action === "status" || action === "pause" || action === "resume";
@@ -33344,289 +34086,6 @@ function sessionUsage() {
33344
34086
  import { EventEmitter } from "node:events";
33345
34087
  import fs11 from "node:fs";
33346
34088
  import path35 from "node:path";
33347
-
33348
- // src/config-scaffold.ts
33349
- import { chmod as chmod3, mkdir as mkdir15, readFile as readFile17, rename as rename6, stat as stat8, writeFile as writeFile14 } from "node:fs/promises";
33350
- import os13 from "node:os";
33351
- import path34 from "node:path";
33352
- function defaultUserConfigPath() {
33353
- return path34.join(os13.homedir(), ".demian", "config.json");
33354
- }
33355
- function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
33356
- return {
33357
- version: 2,
33358
- defaultProvider,
33359
- providers: {
33360
- openai: {
33361
- type: "openai-compatible",
33362
- baseURL: "https://api.openai.com/v1",
33363
- apiKey: "",
33364
- apiKeyEnv: "OPENAI_API_KEY",
33365
- catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
33366
- },
33367
- anthropic: {
33368
- type: "anthropic",
33369
- baseURL: "https://api.anthropic.com/v1",
33370
- apiKey: "",
33371
- apiKeyEnv: "ANTHROPIC_API_KEY",
33372
- catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
33373
- },
33374
- gemini: {
33375
- type: "openai-compatible",
33376
- baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
33377
- apiKey: "",
33378
- apiKeyEnv: "GEMINI_API_KEY",
33379
- apiKeyEnvAliases: ["GOOGLE_API_KEY"],
33380
- catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
33381
- },
33382
- groq: {
33383
- type: "openai-compatible",
33384
- baseURL: "https://api.groq.com/openai/v1",
33385
- apiKey: "",
33386
- apiKeyEnv: "GROQ_API_KEY",
33387
- catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
33388
- },
33389
- azure: {
33390
- type: "openai-compatible",
33391
- auth: { type: "api-key", header: "api-key" },
33392
- modelProfiles: [
33393
- {
33394
- name: "azure-example",
33395
- displayName: "Azure example",
33396
- model: "azure-deployment-name",
33397
- baseURL: "https://example.openai.azure.com/openai/v1",
33398
- apiKey: "",
33399
- apiKeyEnv: "AZURE_OPENAI_API_KEY"
33400
- }
33401
- ]
33402
- },
33403
- lmstudio: {
33404
- type: "openai-compatible",
33405
- baseURL: "http://localhost:1234/v1",
33406
- apiKey: "lm-studio",
33407
- modelProfiles: [],
33408
- catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
33409
- },
33410
- "ollama-local": {
33411
- type: "openai-compatible",
33412
- baseURL: "http://localhost:11434/v1",
33413
- apiKey: "ollama",
33414
- modelProfiles: [],
33415
- quirks: { omitTemperature: true },
33416
- catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
33417
- },
33418
- "ollama-cloud": {
33419
- type: "ollama",
33420
- baseURL: "https://ollama.com/api",
33421
- apiKey: "",
33422
- apiKeyEnv: "OLLAMA_API_KEY",
33423
- modelProfiles: [],
33424
- catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
33425
- },
33426
- llamacpp: {
33427
- type: "openai-compatible",
33428
- baseURL: "http://localhost:8080/v1",
33429
- apiKey: "llama.cpp",
33430
- modelProfiles: [],
33431
- catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
33432
- },
33433
- vllm: {
33434
- type: "openai-compatible",
33435
- baseURL: "http://localhost:8000/v1",
33436
- apiKey: "vllm",
33437
- modelProfiles: [],
33438
- catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
33439
- },
33440
- codex: {
33441
- type: "codex",
33442
- baseURL: "https://chatgpt.com/backend-api/codex",
33443
- authStore: "auto",
33444
- allowApiKeyFallback: false,
33445
- promptCacheKey: "root-session",
33446
- catalog: { type: "codex-oauth-models" }
33447
- },
33448
- claudecode: {
33449
- type: "claudecode",
33450
- runtime: "agent-sdk",
33451
- cliPath: "~/.local/bin/claude",
33452
- cwdMode: "session",
33453
- historyPolicy: "passthrough-resume",
33454
- onInvalidResume: "fresh",
33455
- attachmentFallback: "block",
33456
- allowSubagents: false,
33457
- sanitizeApiKeyEnv: true,
33458
- authPreflight: true,
33459
- useBareMode: false,
33460
- usageLedgerScope: "process",
33461
- sessionLock: true,
33462
- abortPolicy: "record-only",
33463
- catalog: { type: "claudecode-supported-models" }
33464
- }
33465
- }
33466
- };
33467
- }
33468
- async function createUserConfig(options2 = {}) {
33469
- const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
33470
- const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
33471
- `;
33472
- if (options2.print) return { path: filePath, created: false, content };
33473
- const existed = await exists(filePath);
33474
- if (existed && !options2.force) return { path: filePath, created: false, content: await readFile17(filePath, "utf8"), existed: true };
33475
- await writeJsonAtomic(filePath, content);
33476
- return { path: filePath, created: true, content, existed };
33477
- }
33478
- async function addProvider(options2) {
33479
- const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
33480
- const config = await readConfigObject(filePath);
33481
- const providers = objectValue(config.providers);
33482
- const name = options2.name;
33483
- if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
33484
- providers[name] = providerPreset(options2);
33485
- config.providers = providers;
33486
- const content = `${JSON.stringify(config, null, 2)}
33487
- `;
33488
- await writeJsonAtomic(filePath, content);
33489
- return { path: filePath, created: true, content };
33490
- }
33491
- async function addModelProfile(options2) {
33492
- const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
33493
- const config = await readConfigObject(filePath);
33494
- const providers = objectValue(config.providers);
33495
- const provider = objectValue(providers[options2.provider]);
33496
- const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
33497
- const displayName = options2.displayName ?? options2.name;
33498
- const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
33499
- const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
33500
- if (existingByName >= 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
33501
- if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options2.force) {
33502
- throw new Error(`Profile displayName "${displayName}" already exists on provider ${options2.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
33503
- }
33504
- const next = {
33505
- name: options2.name,
33506
- displayName,
33507
- model: options2.model,
33508
- ...options2.baseURL ? { baseURL: options2.baseURL } : {},
33509
- ...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
33510
- ...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
33511
- };
33512
- if (existingByName >= 0) profiles[existingByName] = next;
33513
- else profiles.push(next);
33514
- provider.modelProfiles = profiles;
33515
- providers[options2.provider] = provider;
33516
- config.providers = providers;
33517
- const content = `${JSON.stringify(config, null, 2)}
33518
- `;
33519
- await writeJsonAtomic(filePath, content);
33520
- return { path: filePath, created: true, content };
33521
- }
33522
- async function updateConfigDefaults(options2) {
33523
- const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
33524
- const config = await readConfigObject(filePath);
33525
- const defaultProvider = normalizeOptionalName(options2.defaultProvider);
33526
- const defaultAgent = normalizeOptionalName(options2.defaultAgent);
33527
- if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
33528
- if (defaultProvider) config.defaultProvider = defaultProvider;
33529
- if (defaultAgent) config.defaultAgent = defaultAgent;
33530
- const content = `${JSON.stringify(config, null, 2)}
33531
- `;
33532
- await writeJsonAtomic(filePath, content);
33533
- return { path: filePath, created: true, content };
33534
- }
33535
- async function readConfigObject(filePath) {
33536
- if (!await exists(filePath)) await createUserConfig({ path: filePath });
33537
- const raw = JSON.parse(await readFile17(filePath, "utf8"));
33538
- raw.version ??= 2;
33539
- raw.providers = objectValue(raw.providers);
33540
- return raw;
33541
- }
33542
- function providerPreset(options2) {
33543
- const preset = options2.preset ?? options2.name;
33544
- const auth = apiKeyAuthFields(options2);
33545
- const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
33546
- if (preset === "openai") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options2, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options2.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
33547
- if (preset === "anthropic") return { type: "anthropic", baseURL: options2.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options2, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options2.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
33548
- if (preset === "gemini") {
33549
- const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
33550
- return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options2, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
33551
- }
33552
- if (preset === "groq") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options2, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options2.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
33553
- if (preset === "azure") {
33554
- return {
33555
- type: "openai-compatible",
33556
- auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
33557
- ...auth,
33558
- modelProfiles: [
33559
- {
33560
- name: "azure-example",
33561
- displayName: "Azure example",
33562
- model: "azure-deployment-name",
33563
- baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
33564
- ...options2.apiKey ? {} : { apiKey: "" },
33565
- apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
33566
- }
33567
- ]
33568
- };
33569
- }
33570
- if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:1234/v1", apiKey: options2.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
33571
- if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:11434/v1", apiKey: options2.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
33572
- if (preset === "ollama-cloud") return { type: "ollama", baseURL: options2.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options2, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options2.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
33573
- if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8080/v1", apiKey: options2.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
33574
- if (preset === "vllm") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8000/v1", apiKey: options2.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
33575
- if (preset === "codex") return { type: "codex", baseURL: options2.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
33576
- 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" } };
33577
- if ((options2.type ?? preset) === "openai-compatible") {
33578
- const baseURL = options2.baseURL;
33579
- if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
33580
- return {
33581
- type: "openai-compatible",
33582
- baseURL,
33583
- ...auth,
33584
- ...openAIAuth,
33585
- catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
33586
- };
33587
- }
33588
- throw new Error(`Unknown provider preset: ${preset}`);
33589
- }
33590
- function apiKeyAuthFields(options2, defaultApiKeyEnv) {
33591
- const apiKeyEnv = options2.apiKeyEnv ?? defaultApiKeyEnv;
33592
- return {
33593
- ...options2.apiKey !== void 0 || apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
33594
- ...apiKeyEnv ? { apiKeyEnv } : {}
33595
- };
33596
- }
33597
- async function writeJsonAtomic(filePath, content) {
33598
- await mkdir15(path34.dirname(filePath), { recursive: true, mode: 448 });
33599
- const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
33600
- await writeFile14(temp, content, { mode: 384 });
33601
- await rename6(temp, filePath);
33602
- await chmod3(filePath, 384).catch(() => void 0);
33603
- }
33604
- function detectDefaultProvider() {
33605
- if (process.env.OPENAI_API_KEY) return "openai";
33606
- if (process.env.ANTHROPIC_API_KEY) return "anthropic";
33607
- if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
33608
- if (process.env.GROQ_API_KEY) return "groq";
33609
- return "openai";
33610
- }
33611
- function objectValue(value) {
33612
- return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
33613
- }
33614
- function normalizeOptionalName(value) {
33615
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
33616
- }
33617
- async function exists(filePath) {
33618
- try {
33619
- await stat8(filePath);
33620
- return true;
33621
- } catch {
33622
- return false;
33623
- }
33624
- }
33625
- function expandHome2(value) {
33626
- return resolveExpandedPath(value);
33627
- }
33628
-
33629
- // src/config-watcher.ts
33630
34089
  var ConfigWatcher = class extends EventEmitter {
33631
34090
  #filePath;
33632
34091
  #debounceMs;
@@ -33715,6 +34174,20 @@ if (process.argv.includes("--config-template")) {
33715
34174
  `);
33716
34175
  process.exit(0);
33717
34176
  }
34177
+ if (process.argv.includes("--config-read")) {
34178
+ await readConfigMeta(jsonArg("--config-read")).then(
34179
+ (result) => {
34180
+ process.stdout.write(`${JSON.stringify({ ok: true, ...result })}
34181
+ `);
34182
+ process.exit(0);
34183
+ },
34184
+ (error) => {
34185
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
34186
+ `);
34187
+ process.exit(1);
34188
+ }
34189
+ );
34190
+ }
33718
34191
  if (process.argv.includes("--config-init")) {
33719
34192
  const force = process.argv.includes("--force");
33720
34193
  await createUserConfig({ force }).then(
@@ -33772,6 +34245,34 @@ if (process.argv.includes("--config-set-defaults")) {
33772
34245
  }
33773
34246
  );
33774
34247
  }
34248
+ if (process.argv.includes("--config-set-default-model")) {
34249
+ await setDefaultModelProfile(jsonArg("--config-set-default-model")).then(
34250
+ (result) => {
34251
+ process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
34252
+ `);
34253
+ process.exit(0);
34254
+ },
34255
+ (error) => {
34256
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
34257
+ `);
34258
+ process.exit(1);
34259
+ }
34260
+ );
34261
+ }
34262
+ if (process.argv.includes("--config-update-provider")) {
34263
+ await updateProviderSettings(jsonArg("--config-update-provider")).then(
34264
+ (result) => {
34265
+ process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
34266
+ `);
34267
+ process.exit(0);
34268
+ },
34269
+ (error) => {
34270
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
34271
+ `);
34272
+ process.exit(1);
34273
+ }
34274
+ );
34275
+ }
33775
34276
  if (process.argv.includes("--provider-auth-check")) {
33776
34277
  await checkProviderAuth(jsonArg("--provider-auth-check")).then(
33777
34278
  (result) => {
@@ -34346,30 +34847,43 @@ function setApiKey(providerName, apiKey, opts = {}) {
34346
34847
  }
34347
34848
  async function sendConfigMeta() {
34348
34849
  if (!loadedConfig) return;
34850
+ send({ type: "configMeta", config: await buildConfigMeta(loadedConfig) });
34851
+ }
34852
+ async function readConfigMeta(options2) {
34853
+ const cwd = typeof options2.cwd === "string" && options2.cwd.trim() ? options2.cwd : process.cwd();
34854
+ const configPath = typeof options2.configPath === "string" && options2.configPath.trim() ? options2.configPath : defaultUserConfigPath();
34855
+ let created = false;
34856
+ if (options2.create && !existsSync5(configPath)) {
34857
+ const result = await createUserConfig({ path: configPath });
34858
+ created = result.created;
34859
+ }
34860
+ const config = await loadConfig({ cwd, configPath: options2.configPath });
34861
+ return { path: configPath, created, config: await buildConfigMeta(config) };
34862
+ }
34863
+ async function buildConfigMeta(config) {
34349
34864
  const registry = new AgentRegistry();
34350
- for (const agentDefinition of Object.values(loadedConfig.agents ?? {})) registry.register(agentDefinition, { scope: "user", trusted: true });
34351
- const providerEntries = sortProviderNames(Object.keys(loadedConfig.providers)).map((name) => [name, loadedConfig.providers[name]]).filter(([, provider]) => provider && !provider.hidden);
34865
+ for (const agentDefinition of Object.values(config.agents ?? {})) registry.register(agentDefinition, { scope: "user", trusted: true });
34866
+ const providerEntries = sortProviderNames(Object.keys(config.providers)).map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden);
34352
34867
  const providerOrder = new Map(providerEntries.map(([name], index) => [name, index]));
34353
34868
  const providers = (await Promise.all(providerEntries.map(([name, provider]) => providerMeta(name, provider)))).sort((left, right) => {
34354
34869
  if (left.available !== right.available) return left.available ? -1 : 1;
34355
34870
  return (providerOrder.get(left.name) ?? 0) - (providerOrder.get(right.name) ?? 0);
34356
34871
  });
34357
- send({
34358
- type: "configMeta",
34359
- config: {
34360
- defaultProvider: loadedConfig.defaultProvider,
34361
- defaultAgent: loadedConfig.defaultAgent,
34362
- context: {
34363
- main: loadedConfig.context?.main
34364
- },
34365
- providerOrder: providers.map((provider) => provider.name),
34366
- providers,
34367
- agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
34368
- }
34369
- });
34872
+ return {
34873
+ defaultProvider: config.defaultProvider,
34874
+ defaultAgent: config.defaultAgent,
34875
+ context: {
34876
+ main: config.context?.main
34877
+ },
34878
+ providerOrder: providers.map((provider) => provider.name),
34879
+ providers,
34880
+ agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
34881
+ };
34370
34882
  }
34371
34883
  async function providerMeta(name, provider) {
34372
34884
  const apiKeyEnv = typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0;
34885
+ const configuredProfiles = configuredModelProfiles(provider);
34886
+ const auth = sanitizeProviderAuth2(provider.auth);
34373
34887
  const catalog = await listProviderModelCatalog(name, provider).catch((error) => ({ status: "unavailable", providerName: name, models: [], source: "fallback", message: errorMessage4(error) }));
34374
34888
  const available = providerCatalogAvailable2(catalog);
34375
34889
  const modelProfiles = catalog.models.length ? catalog.models.map((model) => ({
@@ -34402,7 +34916,16 @@ async function providerMeta(name, provider) {
34402
34916
  modelLabel: available || !defaultModel ? defaultModel : unavailableLabel2(defaultModel),
34403
34917
  models,
34404
34918
  modelProfiles: labeledProfiles,
34919
+ configuredModelProfiles: configuredProfiles,
34920
+ configured: {
34921
+ baseURL: provider.baseURL,
34922
+ apiKeyEnv,
34923
+ apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
34924
+ auth,
34925
+ hasInlineApiKey: typeof provider.apiKey === "string" && provider.apiKey.length > 0
34926
+ },
34405
34927
  baseURL: provider.baseURL,
34928
+ auth,
34406
34929
  apiKeyEnv,
34407
34930
  apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
34408
34931
  catalog: { status: catalog.status, source: catalog.source, message: catalog.message },
@@ -34411,6 +34934,26 @@ async function providerMeta(name, provider) {
34411
34934
  requiresApiKey: provider.type === "openai-compatible" || provider.type === "anthropic" || provider.type === "ollama"
34412
34935
  };
34413
34936
  }
34937
+ function configuredModelProfiles(provider) {
34938
+ if (!Array.isArray(provider.modelProfiles)) return [];
34939
+ return provider.modelProfiles.filter((profile) => profile && typeof profile === "object" && typeof profile.model === "string" && profile.model.trim()).map((profile, index) => {
34940
+ const name = typeof profile.name === "string" && profile.name.trim() ? profile.name.trim() : typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : profile.model.trim();
34941
+ const displayName = typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : name;
34942
+ return {
34943
+ name,
34944
+ displayName,
34945
+ model: profile.model.trim(),
34946
+ description: typeof profile.description === "string" ? profile.description : void 0,
34947
+ baseURL: typeof profile.baseURL === "string" ? profile.baseURL : void 0,
34948
+ apiKeyEnv: typeof profile.apiKeyEnv === "string" ? profile.apiKeyEnv : void 0,
34949
+ apiKeyEnvAliases: asStringArray(profile.apiKeyEnvAliases),
34950
+ auth: sanitizeProviderAuth2(profile.auth),
34951
+ hasInlineApiKey: typeof profile.apiKey === "string" && profile.apiKey.length > 0,
34952
+ source: "config",
34953
+ isDefault: index === 0
34954
+ };
34955
+ });
34956
+ }
34414
34957
  function providerCatalogAvailable2(catalog) {
34415
34958
  return catalog?.status === "ready" && Array.isArray(catalog.models) && catalog.models.length > 0;
34416
34959
  }
@@ -34420,6 +34963,12 @@ function unavailableLabel2(value) {
34420
34963
  function asStringArray(value) {
34421
34964
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
34422
34965
  }
34966
+ function sanitizeProviderAuth2(auth) {
34967
+ if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
34968
+ const type = typeof auth.type === "string" ? auth.type : void 0;
34969
+ const header = typeof auth.header === "string" ? auth.header : void 0;
34970
+ return type || header ? { type, header } : void 0;
34971
+ }
34423
34972
  function contextOptions(value) {
34424
34973
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
34425
34974
  const context = {};