demian-cli 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -1099,10 +1139,20 @@ var TuiStore = class {
1099
1139
  }
1100
1140
  if (option.id) this.#resolveSession({ kind: "session", id: option.id });
1101
1141
  }
1142
+ submitDeleteSessionSelection() {
1143
+ if (this.#state.inputMode !== "session") return;
1144
+ const option = this.#state.sessionOptions[this.#state.sessionCursor];
1145
+ if (!option || option.kind === "new" || !option.id) return;
1146
+ this.#resolveSession({ kind: "delete", id: option.id });
1147
+ }
1102
1148
  submitNewSessionSelection() {
1103
1149
  if (this.#state.inputMode !== "session") return;
1104
1150
  this.#resolveSession({ kind: "new" });
1105
1151
  }
1152
+ submitSessionSelectionShortcut() {
1153
+ if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
1154
+ this.#resolvePrompt(`${TUI_SESSION_ACTION_PREFIX}${JSON.stringify({ type: "select" })}`);
1155
+ }
1106
1156
  requestPrompt(initialPrompt) {
1107
1157
  if (this.#exitRequested) return Promise.resolve("");
1108
1158
  const prompt = (initialPrompt ?? "").trim();
@@ -1200,6 +1250,143 @@ var TuiStore = class {
1200
1250
  this.#state.activity = `provider ${option.name} selected`;
1201
1251
  this.#notify();
1202
1252
  }
1253
+ openConfigManager(message) {
1254
+ const canOpenFromConfig = this.#state.inputMode === "config" || this.#state.inputMode === "configModel" || this.#state.inputMode === "configModelInput" || this.#state.inputMode === "configProviderInput" || this.#state.inputMode === "configAddProvider";
1255
+ if (!canOpenFromConfig && (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim())) return;
1256
+ this.#state.inputMode = "config";
1257
+ const current = this.#state.selection?.providerName ?? this.#state.status.provider;
1258
+ this.#state.providerCursor = Math.max(0, this.#state.providerOptions.findIndex((option) => option.name === current));
1259
+ this.#state.configMessage = message ?? this.#state.configMessage;
1260
+ this.#state.settingsError = void 0;
1261
+ this.#state.activity = "configure providers and models";
1262
+ this.#notify();
1263
+ }
1264
+ openConfigProviderInput(field) {
1265
+ if (this.#state.inputMode !== "config") return;
1266
+ const provider = this.#selectedConfigProvider();
1267
+ if (!provider) return;
1268
+ this.#state.inputMode = "configProviderInput";
1269
+ this.#state.configProviderInputField = field;
1270
+ this.#state.configProviderInput = configProviderFieldValue(provider, field);
1271
+ this.#state.settingsError = void 0;
1272
+ this.#state.activity = `edit ${field} for ${provider.name}`;
1273
+ this.#notify();
1274
+ }
1275
+ appendConfigProviderInput(input2) {
1276
+ if (this.#state.inputMode !== "configProviderInput") return;
1277
+ this.#state.configProviderInput += input2;
1278
+ this.#state.settingsError = void 0;
1279
+ this.#notify();
1280
+ }
1281
+ backspaceConfigProviderInput() {
1282
+ if (this.#state.inputMode !== "configProviderInput") return;
1283
+ this.#state.configProviderInput = Array.from(this.#state.configProviderInput).slice(0, -1).join("");
1284
+ this.#state.settingsError = void 0;
1285
+ this.#notify();
1286
+ }
1287
+ submitConfigProviderInput() {
1288
+ if (this.#state.inputMode !== "configProviderInput") return;
1289
+ const provider = this.#selectedConfigProvider();
1290
+ const field = this.#state.configProviderInputField;
1291
+ if (!provider || !field) return;
1292
+ this.#submitConfigAction({ type: "updateProvider", provider: provider.name, field, value: this.#state.configProviderInput });
1293
+ }
1294
+ moveConfigProviderCursor(delta) {
1295
+ if (this.#state.inputMode !== "config" || this.#state.providerOptions.length === 0) return;
1296
+ const count = this.#state.providerOptions.length;
1297
+ this.#state.providerCursor = (this.#state.providerCursor + delta + count) % count;
1298
+ this.#notify();
1299
+ }
1300
+ submitConfigDefaultProvider() {
1301
+ if (this.#state.inputMode !== "config") return;
1302
+ const option = this.#selectedConfigProvider();
1303
+ if (!option) return;
1304
+ this.#submitConfigAction({ type: "setDefaultProvider", provider: option.name });
1305
+ }
1306
+ openConfigModelSelector() {
1307
+ if (this.#state.inputMode !== "config" && this.#state.inputMode !== "configModelInput") return;
1308
+ const option = this.#selectedConfigProvider();
1309
+ if (!option) return;
1310
+ this.#state.inputMode = "configModel";
1311
+ this.#state.configModelCursor = 0;
1312
+ this.#state.settingsError = void 0;
1313
+ this.#state.activity = `choose default model for ${option.name}`;
1314
+ this.#notify();
1315
+ }
1316
+ moveConfigModelCursor(delta) {
1317
+ if (this.#state.inputMode !== "configModel") return;
1318
+ const profiles = this.#selectedConfigProvider()?.modelProfiles ?? [];
1319
+ if (profiles.length === 0) return;
1320
+ this.#state.configModelCursor = (this.#state.configModelCursor + delta + profiles.length) % profiles.length;
1321
+ this.#notify();
1322
+ }
1323
+ submitConfigDefaultModel() {
1324
+ if (this.#state.inputMode !== "configModel") return;
1325
+ const provider = this.#selectedConfigProvider();
1326
+ const profile = provider?.modelProfiles?.[this.#state.configModelCursor];
1327
+ if (!provider || !profile?.model) return;
1328
+ this.#submitConfigAction({
1329
+ type: "setDefaultModel",
1330
+ provider: provider.name,
1331
+ profileName: profile.name,
1332
+ name: profile.name,
1333
+ displayName: profile.displayName,
1334
+ model: profile.model
1335
+ });
1336
+ }
1337
+ openConfigModelInput() {
1338
+ if (this.#state.inputMode !== "configModel" && this.#state.inputMode !== "config") return;
1339
+ if (!this.#selectedConfigProvider()) return;
1340
+ this.#state.inputMode = "configModelInput";
1341
+ this.#state.configModelInput = "";
1342
+ this.#state.settingsError = void 0;
1343
+ this.#state.activity = "add a model profile";
1344
+ this.#notify();
1345
+ }
1346
+ appendConfigModelInput(input2) {
1347
+ if (this.#state.inputMode !== "configModelInput") return;
1348
+ this.#state.configModelInput += input2;
1349
+ this.#state.settingsError = void 0;
1350
+ this.#notify();
1351
+ }
1352
+ backspaceConfigModelInput() {
1353
+ if (this.#state.inputMode !== "configModelInput") return;
1354
+ this.#state.configModelInput = Array.from(this.#state.configModelInput).slice(0, -1).join("");
1355
+ this.#state.settingsError = void 0;
1356
+ this.#notify();
1357
+ }
1358
+ submitConfigModelInput() {
1359
+ if (this.#state.inputMode !== "configModelInput") return;
1360
+ const provider = this.#selectedConfigProvider();
1361
+ const model = this.#state.configModelInput.trim();
1362
+ if (!provider || !model) {
1363
+ this.#state.settingsError = "Model ID is required.";
1364
+ this.#notify();
1365
+ return;
1366
+ }
1367
+ this.#submitConfigAction({ type: "setDefaultModel", provider: provider.name, model, displayName: model });
1368
+ }
1369
+ openConfigAddProviderSelector() {
1370
+ if (this.#state.inputMode !== "config") return;
1371
+ this.#state.inputMode = "configAddProvider";
1372
+ this.#state.configAddProviderCursor = 0;
1373
+ this.#state.settingsError = void 0;
1374
+ this.#state.activity = "add provider preset";
1375
+ this.#notify();
1376
+ }
1377
+ moveConfigAddProviderCursor(delta) {
1378
+ if (this.#state.inputMode !== "configAddProvider") return;
1379
+ const count = this.#state.configProviderPresets.length;
1380
+ if (count === 0) return;
1381
+ this.#state.configAddProviderCursor = (this.#state.configAddProviderCursor + delta + count) % count;
1382
+ this.#notify();
1383
+ }
1384
+ submitConfigAddProvider() {
1385
+ if (this.#state.inputMode !== "configAddProvider") return;
1386
+ const preset = this.#state.configProviderPresets[this.#state.configAddProviderCursor];
1387
+ if (!preset) return;
1388
+ this.#submitConfigAction({ type: "addProvider", preset: preset.preset, name: preset.name, apiKeyEnv: preset.apiKeyEnv });
1389
+ }
1203
1390
  openAgentSelector() {
1204
1391
  if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
1205
1392
  if (this.#state.agentOptions.length === 0) {
@@ -1298,9 +1485,12 @@ var TuiStore = class {
1298
1485
  this.#notify();
1299
1486
  }
1300
1487
  closeSettings() {
1301
- if (this.#state.inputMode !== "provider" && this.#state.inputMode !== "model" && this.#state.inputMode !== "agent" && this.#state.inputMode !== "permissionPreset") return;
1488
+ if (!["provider", "model", "agent", "permissionPreset", "config", "configModel", "configModelInput", "configProviderInput", "configAddProvider"].includes(this.#state.inputMode)) return;
1302
1489
  this.#state.inputMode = "prompt";
1303
1490
  this.#state.modelInput = "";
1491
+ this.#state.configModelInput = "";
1492
+ this.#state.configProviderInput = "";
1493
+ this.#state.configProviderInputField = void 0;
1304
1494
  this.#state.settingsError = void 0;
1305
1495
  this.#state.activity = "waiting for first message";
1306
1496
  this.#notify();
@@ -1429,9 +1619,12 @@ var TuiStore = class {
1429
1619
  this.#state.streamingFinalized = false;
1430
1620
  this.#activeClaudeCodePlan = this.#isClaudeCodePlanRun(event.provider) ? { text: "", providerName: event.provider, model: event.model, createdAt: event.ts } : void 0;
1431
1621
  }
1432
- if (event.type === "model.text.delta" && !isNestedInvocationEvent(event)) {
1622
+ if (event.type === "model.text.delta") {
1623
+ if (isNestedInvocationEvent(event)) return;
1433
1624
  this.#state.streamingText += event.text;
1434
1625
  this.#state.streamingFinalized = false;
1626
+ this.#notify({ deferMs: 50 });
1627
+ return;
1435
1628
  }
1436
1629
  if (event.type === "model.text" && !isNestedInvocationEvent(event)) {
1437
1630
  this.#state.streamingText = event.text;
@@ -2145,14 +2338,28 @@ var TuiStore = class {
2145
2338
  resolve(prompt);
2146
2339
  this.#notify();
2147
2340
  }
2341
+ #selectedConfigProvider() {
2342
+ return this.#state.providerOptions[this.#state.providerCursor];
2343
+ }
2344
+ #submitConfigAction(action) {
2345
+ this.#state.settingsError = void 0;
2346
+ this.#state.configModelInput = "";
2347
+ this.#state.configProviderInput = "";
2348
+ this.#state.configProviderInputField = void 0;
2349
+ this.#resolvePrompt(`${TUI_CONFIG_ACTION_PREFIX}${JSON.stringify(action)}`);
2350
+ }
2148
2351
  #resolveSession(selection) {
2149
2352
  const resolve = this.#sessionResolve;
2150
2353
  if (!resolve) return;
2151
2354
  this.#sessionResolve = void 0;
2355
+ this.#state.sessionCancelBehavior = "exit";
2152
2356
  if (selection.kind === "exit") {
2153
2357
  this.#state.inputMode = "done";
2154
2358
  this.#state.done = true;
2155
2359
  this.#state.activity = "exiting";
2360
+ } else if (selection.kind === "cancel") {
2361
+ this.#state.inputMode = "prompt";
2362
+ this.#state.activity = "waiting for next message";
2156
2363
  } else {
2157
2364
  this.#state.inputMode = "starting";
2158
2365
  this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
@@ -2264,7 +2471,23 @@ var TuiStore = class {
2264
2471
  }
2265
2472
  };
2266
2473
  }
2267
- #notify() {
2474
+ #notify(options2 = {}) {
2475
+ const deferMs = options2.deferMs ?? 0;
2476
+ if (deferMs > 0) {
2477
+ if (this.#notifyTimer) return;
2478
+ this.#notifyTimer = setTimeout(() => {
2479
+ this.#notifyTimer = void 0;
2480
+ this.#emitNotify();
2481
+ }, deferMs);
2482
+ return;
2483
+ }
2484
+ if (this.#notifyTimer) {
2485
+ clearTimeout(this.#notifyTimer);
2486
+ this.#notifyTimer = void 0;
2487
+ }
2488
+ this.#emitNotify();
2489
+ }
2490
+ #emitNotify() {
2268
2491
  for (const listener of this.#listeners) listener();
2269
2492
  }
2270
2493
  };
@@ -2427,6 +2650,25 @@ function cloneContextEfficiency(context) {
2427
2650
  lastCompaction: context.lastCompaction ? { ...context.lastCompaction } : void 0
2428
2651
  };
2429
2652
  }
2653
+ function configProviderFieldValue(provider, field) {
2654
+ if (field === "baseURL") return provider.baseURL ?? "";
2655
+ if (field === "apiKeyEnv") return provider.apiKeyEnv ?? "";
2656
+ return provider.auth?.header ?? "";
2657
+ }
2658
+ function defaultConfigProviderPresets() {
2659
+ return [
2660
+ { label: "OpenAI", preset: "openai", name: "openai", apiKeyEnv: "OPENAI_API_KEY", detail: "OpenAI API" },
2661
+ { label: "Anthropic", preset: "anthropic", name: "anthropic", apiKeyEnv: "ANTHROPIC_API_KEY", detail: "Anthropic API" },
2662
+ { label: "Gemini", preset: "gemini", name: "gemini", apiKeyEnv: "GEMINI_API_KEY", detail: "Google Gemini OpenAI-compatible API" },
2663
+ { label: "Groq", preset: "groq", name: "groq", apiKeyEnv: "GROQ_API_KEY", detail: "Groq Cloud" },
2664
+ { label: "Codex", preset: "codex", name: "codex", detail: "ChatGPT Codex OAuth" },
2665
+ { label: "Claude Code", preset: "claudecode", name: "claudecode", detail: "Local Claude Code runtime" },
2666
+ { label: "Ollama local", preset: "ollama-local", name: "ollama-local", detail: "http://localhost:11434" },
2667
+ { label: "LM Studio", preset: "lmstudio", name: "lmstudio", detail: "http://localhost:1234" },
2668
+ { label: "llama.cpp", preset: "llamacpp", name: "llamacpp", detail: "http://localhost:8080" },
2669
+ { label: "vLLM", preset: "vllm", name: "vllm", detail: "http://localhost:8000" }
2670
+ ];
2671
+ }
2430
2672
  function cloneToolRunDetails(details) {
2431
2673
  if (!details) return void 0;
2432
2674
  return {
@@ -2527,7 +2769,8 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
2527
2769
  }
2528
2770
 
2529
2771
  // src/ui/tui/controller.ts
2530
- import path33 from "node:path";
2772
+ import path34 from "node:path";
2773
+ import { existsSync as existsSync4 } from "node:fs";
2531
2774
 
2532
2775
  // src/agents/types.ts
2533
2776
  function normalizeAgent(input2) {
@@ -26966,10 +27209,380 @@ function claudeCodeRuntimePolicyHash(config) {
26966
27209
  }
26967
27210
  var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision", "allowedTools", "disallowedTools", "tools", "allowSubagents"];
26968
27211
 
26969
- // src/transcript.ts
26970
- import { mkdir as mkdir6, appendFile, writeFile as writeFile5 } from "node:fs/promises";
27212
+ // src/config-scaffold.ts
27213
+ import { chmod as chmod3, mkdir as mkdir6, readFile as readFile7, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
26971
27214
  import os8 from "node:os";
26972
27215
  import path14 from "node:path";
27216
+ function defaultUserConfigPath() {
27217
+ return path14.join(os8.homedir(), ".demian", "config.json");
27218
+ }
27219
+ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
27220
+ return {
27221
+ version: 2,
27222
+ defaultProvider,
27223
+ providers: {
27224
+ openai: {
27225
+ type: "openai-compatible",
27226
+ baseURL: "https://api.openai.com/v1",
27227
+ apiKey: "",
27228
+ apiKeyEnv: "OPENAI_API_KEY",
27229
+ catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
27230
+ },
27231
+ anthropic: {
27232
+ type: "anthropic",
27233
+ baseURL: "https://api.anthropic.com/v1",
27234
+ apiKey: "",
27235
+ apiKeyEnv: "ANTHROPIC_API_KEY",
27236
+ catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
27237
+ },
27238
+ gemini: {
27239
+ type: "openai-compatible",
27240
+ baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
27241
+ apiKey: "",
27242
+ apiKeyEnv: "GEMINI_API_KEY",
27243
+ apiKeyEnvAliases: ["GOOGLE_API_KEY"],
27244
+ catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
27245
+ },
27246
+ groq: {
27247
+ type: "openai-compatible",
27248
+ baseURL: "https://api.groq.com/openai/v1",
27249
+ apiKey: "",
27250
+ apiKeyEnv: "GROQ_API_KEY",
27251
+ catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
27252
+ },
27253
+ azure: {
27254
+ type: "openai-compatible",
27255
+ auth: { type: "api-key", header: "api-key" },
27256
+ modelProfiles: [
27257
+ {
27258
+ name: "azure-example",
27259
+ displayName: "Azure example",
27260
+ model: "azure-deployment-name",
27261
+ baseURL: "https://example.openai.azure.com/openai/v1",
27262
+ apiKey: "",
27263
+ apiKeyEnv: "AZURE_OPENAI_API_KEY"
27264
+ }
27265
+ ]
27266
+ },
27267
+ lmstudio: {
27268
+ type: "openai-compatible",
27269
+ baseURL: "http://localhost:1234/v1",
27270
+ apiKey: "lm-studio",
27271
+ modelProfiles: [],
27272
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
27273
+ },
27274
+ "ollama-local": {
27275
+ type: "openai-compatible",
27276
+ baseURL: "http://localhost:11434/v1",
27277
+ apiKey: "ollama",
27278
+ modelProfiles: [],
27279
+ quirks: { omitTemperature: true },
27280
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
27281
+ },
27282
+ "ollama-cloud": {
27283
+ type: "ollama",
27284
+ baseURL: "https://ollama.com/api",
27285
+ apiKey: "",
27286
+ apiKeyEnv: "OLLAMA_API_KEY",
27287
+ modelProfiles: [],
27288
+ catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
27289
+ },
27290
+ llamacpp: {
27291
+ type: "openai-compatible",
27292
+ baseURL: "http://localhost:8080/v1",
27293
+ apiKey: "llama.cpp",
27294
+ modelProfiles: [],
27295
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
27296
+ },
27297
+ vllm: {
27298
+ type: "openai-compatible",
27299
+ baseURL: "http://localhost:8000/v1",
27300
+ apiKey: "vllm",
27301
+ modelProfiles: [],
27302
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
27303
+ },
27304
+ codex: {
27305
+ type: "codex",
27306
+ baseURL: "https://chatgpt.com/backend-api/codex",
27307
+ authStore: "auto",
27308
+ allowApiKeyFallback: false,
27309
+ promptCacheKey: "root-session",
27310
+ catalog: { type: "codex-oauth-models" }
27311
+ },
27312
+ claudecode: {
27313
+ type: "claudecode",
27314
+ runtime: "agent-sdk",
27315
+ cliPath: "~/.local/bin/claude",
27316
+ cwdMode: "session",
27317
+ historyPolicy: "passthrough-resume",
27318
+ onInvalidResume: "fresh",
27319
+ attachmentFallback: "block",
27320
+ allowSubagents: false,
27321
+ sanitizeApiKeyEnv: true,
27322
+ authPreflight: true,
27323
+ useBareMode: false,
27324
+ usageLedgerScope: "process",
27325
+ sessionLock: true,
27326
+ abortPolicy: "record-only",
27327
+ catalog: { type: "claudecode-supported-models" }
27328
+ }
27329
+ }
27330
+ };
27331
+ }
27332
+ async function createUserConfig(options2 = {}) {
27333
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27334
+ const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
27335
+ `;
27336
+ if (options2.print) return { path: filePath, created: false, content };
27337
+ const existed = await exists(filePath);
27338
+ if (existed && !options2.force) return { path: filePath, created: false, content: await readFile7(filePath, "utf8"), existed: true };
27339
+ await writeJsonAtomic(filePath, content);
27340
+ return { path: filePath, created: true, content, existed };
27341
+ }
27342
+ async function addProvider(options2) {
27343
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27344
+ const config = await readConfigObject(filePath);
27345
+ const providers = objectValue(config.providers);
27346
+ const name = options2.name;
27347
+ if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
27348
+ providers[name] = providerPreset(options2);
27349
+ config.providers = providers;
27350
+ const content = `${JSON.stringify(config, null, 2)}
27351
+ `;
27352
+ await writeJsonAtomic(filePath, content);
27353
+ return { path: filePath, created: true, content };
27354
+ }
27355
+ async function addModelProfile(options2) {
27356
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27357
+ const config = await readConfigObject(filePath);
27358
+ const providers = objectValue(config.providers);
27359
+ const provider = objectValue(providers[options2.provider]);
27360
+ const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
27361
+ const displayName = options2.displayName ?? options2.name;
27362
+ const previousName = normalizeOptionalName(options2.previousName);
27363
+ const existingByPreviousName = previousName ? profiles.findIndex((entry) => entry.name === previousName) : -1;
27364
+ const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
27365
+ const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
27366
+ const updateIndex = existingByPreviousName >= 0 ? existingByPreviousName : existingByName;
27367
+ if (existingByName >= 0 && existingByName !== updateIndex) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Pick a different profile name.`);
27368
+ 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.`);
27369
+ if (existingByDisplay >= 0 && existingByDisplay !== updateIndex && !options2.force) {
27370
+ 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.`);
27371
+ }
27372
+ const next = {
27373
+ name: options2.name,
27374
+ displayName,
27375
+ model: options2.model,
27376
+ ...options2.baseURL ? { baseURL: options2.baseURL } : {},
27377
+ ...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
27378
+ ...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
27379
+ };
27380
+ if (updateIndex >= 0) profiles[updateIndex] = next;
27381
+ else profiles.push(next);
27382
+ provider.modelProfiles = profiles;
27383
+ providers[options2.provider] = provider;
27384
+ config.providers = providers;
27385
+ const content = `${JSON.stringify(config, null, 2)}
27386
+ `;
27387
+ await writeJsonAtomic(filePath, content);
27388
+ return { path: filePath, created: true, content };
27389
+ }
27390
+ async function updateConfigDefaults(options2) {
27391
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27392
+ const config = await readConfigObject(filePath);
27393
+ const defaultProvider = normalizeOptionalName(options2.defaultProvider);
27394
+ const defaultAgent = normalizeOptionalName(options2.defaultAgent);
27395
+ if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
27396
+ if (defaultProvider) config.defaultProvider = defaultProvider;
27397
+ if (defaultAgent) config.defaultAgent = defaultAgent;
27398
+ const content = `${JSON.stringify(config, null, 2)}
27399
+ `;
27400
+ await writeJsonAtomic(filePath, content);
27401
+ return { path: filePath, created: true, content };
27402
+ }
27403
+ async function setDefaultModelProfile(options2) {
27404
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27405
+ const config = await readConfigObject(filePath);
27406
+ const providers = objectValue(config.providers);
27407
+ const provider = providers[options2.provider];
27408
+ if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
27409
+ const providerConfig = { ...provider };
27410
+ const profiles = Array.isArray(providerConfig.modelProfiles) ? [...providerConfig.modelProfiles] : [];
27411
+ const requestedName = normalizeOptionalName(options2.profileName ?? options2.name);
27412
+ const requestedModel = normalizeOptionalName(options2.model);
27413
+ const requestedDisplayName = normalizeOptionalName(options2.displayName) ?? requestedModel ?? requestedName;
27414
+ let index = profiles.findIndex((entry) => requestedName && entry.name === requestedName);
27415
+ if (index < 0 && requestedModel) index = profiles.findIndex((entry) => entry.model === requestedModel);
27416
+ if (index < 0 && requestedDisplayName) index = profiles.findIndex((entry) => entry.displayName === requestedDisplayName);
27417
+ const profile = index >= 0 ? {
27418
+ ...profiles[index],
27419
+ ...requestedName ? { name: requestedName } : {},
27420
+ ...requestedDisplayName ? { displayName: requestedDisplayName } : {},
27421
+ ...requestedModel ? { model: requestedModel } : {},
27422
+ ...options2.baseURL ? { baseURL: options2.baseURL } : {},
27423
+ ...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
27424
+ ...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
27425
+ } : newModelProfile({
27426
+ name: requestedName,
27427
+ displayName: requestedDisplayName,
27428
+ model: requestedModel ?? requestedName,
27429
+ baseURL: options2.baseURL,
27430
+ apiKey: options2.apiKey,
27431
+ apiKeyEnv: options2.apiKeyEnv
27432
+ });
27433
+ if (!profile.name || typeof profile.name !== "string") throw new Error("Model profile name is required.");
27434
+ if (!profile.model || typeof profile.model !== "string") throw new Error("Model ID is required.");
27435
+ if (index >= 0) profiles.splice(index, 1);
27436
+ profiles.unshift(profile);
27437
+ providerConfig.modelProfiles = profiles;
27438
+ providers[options2.provider] = providerConfig;
27439
+ config.providers = providers;
27440
+ const content = `${JSON.stringify(config, null, 2)}
27441
+ `;
27442
+ await writeJsonAtomic(filePath, content);
27443
+ return { path: filePath, created: true, content };
27444
+ }
27445
+ async function updateProviderSettings(options2) {
27446
+ const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
27447
+ const config = await readConfigObject(filePath);
27448
+ const providers = objectValue(config.providers);
27449
+ const provider = providers[options2.provider];
27450
+ if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
27451
+ const providerConfig = { ...provider };
27452
+ if (options2.baseURL !== void 0) setOrDelete(providerConfig, "baseURL", options2.baseURL);
27453
+ if (options2.apiKey !== void 0) setOrDelete(providerConfig, "apiKey", options2.apiKey);
27454
+ if (options2.apiKeyEnv !== void 0) setOrDelete(providerConfig, "apiKeyEnv", options2.apiKeyEnv);
27455
+ if (options2.authHeader !== void 0) {
27456
+ const header = options2.authHeader.trim();
27457
+ if (header) providerConfig.auth = { type: "api-key", header };
27458
+ else delete providerConfig.auth;
27459
+ }
27460
+ providers[options2.provider] = providerConfig;
27461
+ config.providers = providers;
27462
+ const content = `${JSON.stringify(config, null, 2)}
27463
+ `;
27464
+ await writeJsonAtomic(filePath, content);
27465
+ return { path: filePath, created: true, content };
27466
+ }
27467
+ async function readConfigObject(filePath) {
27468
+ if (!await exists(filePath)) await createUserConfig({ path: filePath });
27469
+ const raw = JSON.parse(await readFile7(filePath, "utf8"));
27470
+ raw.version ??= 2;
27471
+ raw.providers = objectValue(raw.providers);
27472
+ return raw;
27473
+ }
27474
+ function providerPreset(options2) {
27475
+ const preset = options2.preset ?? options2.name;
27476
+ const auth = apiKeyAuthFields(options2);
27477
+ const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
27478
+ 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` } };
27479
+ 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` } };
27480
+ if (preset === "gemini") {
27481
+ const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
27482
+ 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` } };
27483
+ }
27484
+ 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` } };
27485
+ if (preset === "azure") {
27486
+ return {
27487
+ type: "openai-compatible",
27488
+ auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
27489
+ ...auth,
27490
+ modelProfiles: [
27491
+ {
27492
+ name: "azure-example",
27493
+ displayName: "Azure example",
27494
+ model: "azure-deployment-name",
27495
+ baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
27496
+ ...options2.apiKey ? {} : { apiKey: "" },
27497
+ apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
27498
+ }
27499
+ ]
27500
+ };
27501
+ }
27502
+ 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` } };
27503
+ 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` } };
27504
+ 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` } };
27505
+ 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` } };
27506
+ 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` } };
27507
+ 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" } };
27508
+ 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" } };
27509
+ if ((options2.type ?? preset) === "openai-compatible") {
27510
+ const baseURL = options2.baseURL;
27511
+ if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
27512
+ return {
27513
+ type: "openai-compatible",
27514
+ baseURL,
27515
+ ...auth,
27516
+ ...openAIAuth,
27517
+ catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
27518
+ };
27519
+ }
27520
+ throw new Error(`Unknown provider preset: ${preset}`);
27521
+ }
27522
+ function apiKeyAuthFields(options2, defaultApiKeyEnv) {
27523
+ const apiKeyEnv = options2.apiKeyEnv ?? defaultApiKeyEnv;
27524
+ return {
27525
+ ...options2.apiKey !== void 0 || apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
27526
+ ...apiKeyEnv ? { apiKeyEnv } : {}
27527
+ };
27528
+ }
27529
+ async function writeJsonAtomic(filePath, content) {
27530
+ await mkdir6(path14.dirname(filePath), { recursive: true, mode: 448 });
27531
+ const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
27532
+ await writeFile5(temp, content, { mode: 384 });
27533
+ await rename4(temp, filePath);
27534
+ await chmod3(filePath, 384).catch(() => void 0);
27535
+ }
27536
+ function detectDefaultProvider() {
27537
+ if (process.env.OPENAI_API_KEY) return "openai";
27538
+ if (process.env.ANTHROPIC_API_KEY) return "anthropic";
27539
+ if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
27540
+ if (process.env.GROQ_API_KEY) return "groq";
27541
+ return "openai";
27542
+ }
27543
+ function objectValue(value) {
27544
+ return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
27545
+ }
27546
+ function normalizeOptionalName(value) {
27547
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
27548
+ }
27549
+ function newModelProfile(options2) {
27550
+ const model = options2.model?.trim();
27551
+ if (!model) throw new Error("Model ID is required.");
27552
+ const name = options2.name?.trim() || modelProfileNameFromModel(model);
27553
+ return {
27554
+ name,
27555
+ displayName: options2.displayName?.trim() || name,
27556
+ model,
27557
+ ...options2.baseURL ? { baseURL: options2.baseURL } : {},
27558
+ ...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
27559
+ ...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
27560
+ };
27561
+ }
27562
+ function modelProfileNameFromModel(model) {
27563
+ return model.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "model";
27564
+ }
27565
+ function setOrDelete(target, key, value) {
27566
+ const trimmed = value.trim();
27567
+ if (trimmed) target[key] = trimmed;
27568
+ else delete target[key];
27569
+ }
27570
+ async function exists(filePath) {
27571
+ try {
27572
+ await stat3(filePath);
27573
+ return true;
27574
+ } catch {
27575
+ return false;
27576
+ }
27577
+ }
27578
+ function expandHome2(value) {
27579
+ return resolveExpandedPath(value);
27580
+ }
27581
+
27582
+ // src/transcript.ts
27583
+ import { mkdir as mkdir7, appendFile, writeFile as writeFile6 } from "node:fs/promises";
27584
+ import os9 from "node:os";
27585
+ import path15 from "node:path";
26973
27586
  var TranscriptWriter = class {
26974
27587
  filePath;
26975
27588
  #enabled;
@@ -26977,9 +27590,9 @@ var TranscriptWriter = class {
26977
27590
  #queue = Promise.resolve();
26978
27591
  constructor(options2) {
26979
27592
  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();
27593
+ const dir = path15.join(options2.storageDir ?? defaultDemianStorageDir(), "transcripts", options2.sessionId);
27594
+ this.filePath = path15.join(dir, "session.jsonl");
27595
+ this.#ready = this.#enabled ? mkdir7(dir, { recursive: true }).then(() => writeFile6(this.filePath, "", { flag: "a" })) : Promise.resolve();
26983
27596
  }
26984
27597
  write(event) {
26985
27598
  if (!this.#enabled) return Promise.resolve();
@@ -26995,14 +27608,14 @@ var TranscriptWriter = class {
26995
27608
  }
26996
27609
  };
26997
27610
  function defaultDemianStorageDir() {
26998
- return path14.join(os8.homedir(), ".demian");
27611
+ return path15.join(os9.homedir(), ".demian");
26999
27612
  }
27000
27613
 
27001
27614
  // src/external-runtime/snapshot-diff.ts
27002
27615
  import { createHash as createHash2 } from "node:crypto";
27003
27616
  import { createReadStream } from "node:fs";
27004
- import { readdir, readFile as readFile7, stat as stat3 } from "node:fs/promises";
27005
- import path15 from "node:path";
27617
+ import { readdir, readFile as readFile8, stat as stat4 } from "node:fs/promises";
27618
+ import path16 from "node:path";
27006
27619
 
27007
27620
  // src/workspace/diff.ts
27008
27621
  var CONTEXT_LINES = 3;
@@ -27212,7 +27825,7 @@ async function diffWorkspaceSnapshot(before) {
27212
27825
  }
27213
27826
  async function walk(root, relativeDir, entries, options2) {
27214
27827
  if (entries.size >= options2.maxFiles) return;
27215
- const absoluteDir = path15.join(root, relativeDir);
27828
+ const absoluteDir = path16.join(root, relativeDir);
27216
27829
  let items;
27217
27830
  try {
27218
27831
  items = await readdir(absoluteDir, { withFileTypes: true });
@@ -27223,8 +27836,8 @@ async function walk(root, relativeDir, entries, options2) {
27223
27836
  if (entries.size >= options2.maxFiles) return;
27224
27837
  if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
27225
27838
  if (SKIP_DIRS.has(item.name)) continue;
27226
- const relativePath = normalizeRelativePath(path15.join(relativeDir, item.name));
27227
- const absolutePath = path15.join(root, relativePath);
27839
+ const relativePath = normalizeRelativePath(path16.join(relativeDir, item.name));
27840
+ const absolutePath = path16.join(root, relativePath);
27228
27841
  if (item.isDirectory()) {
27229
27842
  await walk(root, relativePath, entries, options2);
27230
27843
  continue;
@@ -27235,7 +27848,7 @@ async function walk(root, relativeDir, entries, options2) {
27235
27848
  }
27236
27849
  }
27237
27850
  async function snapshotFile(filePath, relativePath, maxTextBytes) {
27238
- const info = await stat3(filePath);
27851
+ const info = await stat4(filePath);
27239
27852
  const sha2562 = await sha256File(filePath);
27240
27853
  const content = info.size <= maxTextBytes ? await readTextContent(filePath) : void 0;
27241
27854
  return { path: relativePath, size: info.size, sha256: sha2562, content };
@@ -27246,7 +27859,7 @@ function snapshotDiffText(entry) {
27246
27859
  `;
27247
27860
  }
27248
27861
  async function readTextContent(filePath) {
27249
- const buffer = await readFile7(filePath);
27862
+ const buffer = await readFile8(filePath);
27250
27863
  if (buffer.includes(0)) return void 0;
27251
27864
  return buffer.toString("utf8");
27252
27865
  }
@@ -27260,7 +27873,7 @@ function sha256File(filePath) {
27260
27873
  });
27261
27874
  }
27262
27875
  function normalizeRelativePath(value) {
27263
- return value.split(path15.sep).join("/");
27876
+ return value.split(path16.sep).join("/");
27264
27877
  }
27265
27878
 
27266
27879
  // src/external-runtime/session-runner.ts
@@ -27695,7 +28308,7 @@ function safeJson(value) {
27695
28308
  }
27696
28309
 
27697
28310
  // src/hooks/dispatcher.ts
27698
- import path17 from "node:path";
28311
+ import path18 from "node:path";
27699
28312
 
27700
28313
  // src/hooks/command.ts
27701
28314
  import { spawn as spawn4 } from "node:child_process";
@@ -27788,7 +28401,7 @@ var blockDangerousBashHook = {
27788
28401
  };
27789
28402
 
27790
28403
  // src/hooks/builtin/protect-env-files.ts
27791
- import path16 from "node:path";
28404
+ import path17 from "node:path";
27792
28405
  var protectEnvFilesHook = {
27793
28406
  name: "protect-env-files",
27794
28407
  event: "PreToolUse",
@@ -27796,7 +28409,7 @@ var protectEnvFilesHook = {
27796
28409
  run(ctx) {
27797
28410
  if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
27798
28411
  const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
27799
- const filePath = typeof input2.path === "string" ? path16.resolve(ctx.cwd, input2.path) : "";
28412
+ const filePath = typeof input2.path === "string" ? path17.resolve(ctx.cwd, input2.path) : "";
27800
28413
  if (filePath && isEnvFile(filePath)) {
27801
28414
  return {
27802
28415
  decision: "block",
@@ -27833,7 +28446,7 @@ var maskSecretsHook = {
27833
28446
  };
27834
28447
 
27835
28448
  // src/hooks/builtin/inject-env-info.ts
27836
- import os9 from "node:os";
28449
+ import os10 from "node:os";
27837
28450
  var injectEnvInfoHook = {
27838
28451
  name: "inject-env-info",
27839
28452
  event: "SessionStart",
@@ -27842,7 +28455,7 @@ var injectEnvInfoHook = {
27842
28455
  decision: "allow",
27843
28456
  patch: {
27844
28457
  systemNote: [
27845
- `Environment: ${os9.type()} ${os9.release()} (${os9.platform()}/${os9.arch()})`,
28458
+ `Environment: ${os10.type()} ${os10.release()} (${os10.platform()}/${os10.arch()})`,
27846
28459
  `cwd: ${ctx.cwd}`,
27847
28460
  `provider: ${ctx.provider ?? "unknown"}`,
27848
28461
  `model: ${ctx.model ?? "unknown"}`,
@@ -27911,14 +28524,14 @@ function matchesHook(match, ctx) {
27911
28524
  const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
27912
28525
  const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
27913
28526
  if (!filePath) return false;
27914
- if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path17.resolve(ctx.cwd, filePath)))) return false;
28527
+ if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path18.resolve(ctx.cwd, filePath)))) return false;
27915
28528
  }
27916
28529
  return true;
27917
28530
  }
27918
28531
 
27919
28532
  // src/multimodal.ts
27920
- import { readFile as readFile8, stat as stat4 } from "node:fs/promises";
27921
- import path18 from "node:path";
28533
+ import { readFile as readFile9, stat as stat5 } from "node:fs/promises";
28534
+ import path19 from "node:path";
27922
28535
  var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
27923
28536
  async function buildUserContent(prompt, images = [], options2) {
27924
28537
  if (images.length === 0) return prompt;
@@ -27937,16 +28550,16 @@ async function buildUserContent(prompt, images = [], options2) {
27937
28550
  async function imageToUrl(input2, options2) {
27938
28551
  if (/^https?:\/\//i.test(input2) || /^data:image\//i.test(input2)) return input2;
27939
28552
  const filePath = resolveInsideCwd(options2.cwd, input2);
27940
- const info = await stat4(filePath);
28553
+ const info = await stat5(filePath);
27941
28554
  if (!info.isFile()) throw new Error(`Image input is not a file: ${input2}`);
27942
28555
  const maxImageBytes = options2.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
27943
28556
  if (info.size > maxImageBytes) throw new Error(`Image is larger than ${maxImageBytes} bytes: ${input2}`);
27944
28557
  const mime = mimeFromPath(filePath);
27945
- const bytes = await readFile8(filePath);
28558
+ const bytes = await readFile9(filePath);
27946
28559
  return `data:${mime};base64,${bytes.toString("base64")}`;
27947
28560
  }
27948
28561
  function mimeFromPath(filePath) {
27949
- switch (path18.extname(filePath).toLowerCase()) {
28562
+ switch (path19.extname(filePath).toLowerCase()) {
27950
28563
  case ".jpg":
27951
28564
  case ".jpeg":
27952
28565
  return "image/jpeg";
@@ -27957,15 +28570,15 @@ function mimeFromPath(filePath) {
27957
28570
  case ".webp":
27958
28571
  return "image/webp";
27959
28572
  default:
27960
- throw new Error(`Unsupported image extension: ${path18.extname(filePath) || "(none)"}`);
28573
+ throw new Error(`Unsupported image extension: ${path19.extname(filePath) || "(none)"}`);
27961
28574
  }
27962
28575
  }
27963
28576
 
27964
28577
  // src/permissions/persistent-grants.ts
27965
- import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
28578
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
27966
28579
  import fs8 from "node:fs";
27967
- import os10 from "node:os";
27968
- import path19 from "node:path";
28580
+ import os11 from "node:os";
28581
+ import path20 from "node:path";
27969
28582
  var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
27970
28583
  var PersistentGrantStore = class {
27971
28584
  filePath;
@@ -27996,22 +28609,22 @@ var PersistentGrantStore = class {
27996
28609
  }
27997
28610
  async #read() {
27998
28611
  if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
27999
- const data = JSON.parse(await readFile9(this.filePath, "utf8"));
28612
+ const data = JSON.parse(await readFile10(this.filePath, "utf8"));
28000
28613
  return {
28001
28614
  version: 1,
28002
28615
  grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
28003
28616
  };
28004
28617
  }
28005
28618
  async #write(file) {
28006
- await mkdir7(path19.dirname(this.filePath), { recursive: true });
28007
- await writeFile6(this.filePath, `${JSON.stringify(file, null, 2)}
28619
+ await mkdir8(path20.dirname(this.filePath), { recursive: true });
28620
+ await writeFile7(this.filePath, `${JSON.stringify(file, null, 2)}
28008
28621
  `, { mode: 384 });
28009
28622
  }
28010
28623
  };
28011
28624
  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");
28625
+ if (config.path) return path20.resolve(cwd, config.path);
28626
+ if ((config.scope ?? "project") === "user") return path20.join(os11.homedir(), ".demian", "grants.json");
28627
+ return path20.join(cwd, ".demian", "grants.json");
28015
28628
  }
28016
28629
  function isGrantRecord(value) {
28017
28630
  if (!value || typeof value !== "object") return false;
@@ -28444,25 +29057,25 @@ function fail(content, metadata) {
28444
29057
  }
28445
29058
 
28446
29059
  // src/tools/output.ts
28447
- import { mkdir as mkdir8, writeFile as writeFile7 } from "node:fs/promises";
28448
- import path20 from "node:path";
29060
+ import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
29061
+ import path21 from "node:path";
28449
29062
  var DEFAULT_CAP_BYTES = 32 * 1024;
28450
29063
  var HALF_PREVIEW_BYTES = 16 * 1024;
28451
29064
  async function capToolOutput(result, options2) {
28452
29065
  const capBytes = options2.capBytes ?? DEFAULT_CAP_BYTES;
28453
29066
  const bytes = Buffer.byteLength(result.content, "utf8");
28454
29067
  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");
29068
+ const dir = path21.join(options2.cwd, ".demian", "tmp");
29069
+ await mkdir9(dir, { recursive: true });
29070
+ const outputPath = path21.join(dir, `output-${safeCallId(options2.callId)}.txt`);
29071
+ await writeFile8(outputPath, result.content, "utf8");
28459
29072
  return {
28460
29073
  ...result,
28461
29074
  content: [
28462
29075
  sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
28463
29076
  `
28464
29077
 
28465
- [Full output saved to ${path20.relative(options2.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
29078
+ [Full output saved to ${path21.relative(options2.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
28466
29079
 
28467
29080
  `,
28468
29081
  sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
@@ -28487,8 +29100,8 @@ function sliceUtf8(text, startByte, endByte) {
28487
29100
  }
28488
29101
 
28489
29102
  // src/tools/read-file.ts
28490
- import { open as open2, stat as stat5 } from "node:fs/promises";
28491
- import path21 from "node:path";
29103
+ import { open as open2, stat as stat6 } from "node:fs/promises";
29104
+ import path22 from "node:path";
28492
29105
 
28493
29106
  // src/tools/validation.ts
28494
29107
  function assertObject(input2, toolName) {
@@ -28558,7 +29171,7 @@ var readFileTool = {
28558
29171
  const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
28559
29172
  const offset = positiveInteger(optionalNumberField(object2, "offset"), 1, "offset");
28560
29173
  const limit = positiveInteger(optionalNumberField(object2, "limit"), DEFAULT_LIMIT, "limit");
28561
- const info = await stat5(filePath);
29174
+ const info = await stat6(filePath);
28562
29175
  if (info.isDirectory()) throw new Error(`read_file expected a file but got a directory: ${relativeToCwd(ctx.cwd, filePath)}`);
28563
29176
  if (info.size > MAX_BYTES) throw new Error(`File is larger than ${MAX_BYTES} bytes: ${relativeToCwd(ctx.cwd, filePath)}`);
28564
29177
  const sample = await readBytes(filePath, Math.min(SAMPLE_BYTES, info.size));
@@ -28572,7 +29185,7 @@ var readFileTool = {
28572
29185
  const truncated = start + sliced.length < lines.length;
28573
29186
  return ok(content + (truncated ? `
28574
29187
  ... (${lines.length - start - sliced.length} more lines)` : ""), {
28575
- path: path21.relative(ctx.cwd, filePath),
29188
+ path: path22.relative(ctx.cwd, filePath),
28576
29189
  lines: sliced.length,
28577
29190
  totalLines: lines.length,
28578
29191
  truncated
@@ -28610,8 +29223,8 @@ function looksBinary(buffer) {
28610
29223
  }
28611
29224
 
28612
29225
  // 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";
29226
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
29227
+ import path23 from "node:path";
28615
29228
  var writeFileTool = {
28616
29229
  name: "write_file",
28617
29230
  description: "Create or replace a text file inside the workspace.",
@@ -28629,8 +29242,8 @@ var writeFileTool = {
28629
29242
  const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
28630
29243
  const content = stringField(object2, "content", { allowEmpty: true });
28631
29244
  const before = await readOptionalTextFile(filePath);
28632
- await mkdir9(path22.dirname(filePath), { recursive: true });
28633
- await writeFile8(filePath, content, "utf8");
29245
+ await mkdir10(path23.dirname(filePath), { recursive: true });
29246
+ await writeFile9(filePath, content, "utf8");
28634
29247
  const relative = relativeToCwd(ctx.cwd, filePath);
28635
29248
  const diff = createTextDiff(before, content, relative);
28636
29249
  return ok(`Wrote ${relativeToCwd(ctx.cwd, filePath)} (${Buffer.byteLength(content, "utf8")} bytes).`, {
@@ -28643,7 +29256,7 @@ var writeFileTool = {
28643
29256
  };
28644
29257
  async function readOptionalTextFile(filePath) {
28645
29258
  try {
28646
- return await readFile10(filePath, "utf8");
29259
+ return await readFile11(filePath, "utf8");
28647
29260
  } catch (error) {
28648
29261
  if (isNotFound(error)) return void 0;
28649
29262
  throw error;
@@ -28654,7 +29267,7 @@ function isNotFound(error) {
28654
29267
  }
28655
29268
 
28656
29269
  // src/tools/edit-file.ts
28657
- import { readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
29270
+ import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
28658
29271
  var editFileTool = {
28659
29272
  name: "edit_file",
28660
29273
  description: "Replace an exact string in a text file inside the workspace.",
@@ -28676,12 +29289,12 @@ var editFileTool = {
28676
29289
  const newString = stringField(object2, "newString", { allowEmpty: true });
28677
29290
  const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
28678
29291
  if (oldString === newString) throw new Error("oldString and newString must be different");
28679
- const before = await readFile11(filePath, "utf8");
29292
+ const before = await readFile12(filePath, "utf8");
28680
29293
  const matches = countMatches(before, oldString);
28681
29294
  if (matches === 0) throw new Error("oldString was not found");
28682
29295
  if (matches > 1 && !replaceAll) throw new Error(`oldString matched ${matches} times; set replaceAll=true to replace all matches`);
28683
29296
  const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
28684
- await writeFile9(filePath, after, "utf8");
29297
+ await writeFile10(filePath, after, "utf8");
28685
29298
  const relative = relativeToCwd(ctx.cwd, filePath);
28686
29299
  const diff = createTextDiff(before, after, relative);
28687
29300
  return ok(`Edited ${relative} (${replaceAll ? matches : 1} replacement${matches === 1 ? "" : "s"}).`, {
@@ -28705,7 +29318,7 @@ function countMatches(text, needle) {
28705
29318
 
28706
29319
  // src/tools/bash.ts
28707
29320
  import { spawn as spawn5 } from "node:child_process";
28708
- import path24 from "node:path";
29321
+ import path25 from "node:path";
28709
29322
 
28710
29323
  // src/sandbox/env-only.ts
28711
29324
  function buildEnvOnlyLaunch(command, config) {
@@ -28765,7 +29378,7 @@ function bwrapPath() {
28765
29378
 
28766
29379
  // src/sandbox/macos.ts
28767
29380
  import { existsSync as existsSync3 } from "node:fs";
28768
- import path23 from "node:path";
29381
+ import path24 from "node:path";
28769
29382
  function canUseMacOSSandbox() {
28770
29383
  return process.platform === "darwin" && existsSync3("/usr/bin/sandbox-exec");
28771
29384
  }
@@ -28791,7 +29404,7 @@ function macosProfile(cwd, config) {
28791
29404
  }
28792
29405
  if (mode === "workspace-write") {
28793
29406
  lines.push("(deny file-write*)");
28794
- lines.push(`(allow file-write* (subpath "${escapeProfilePath(path23.resolve(cwd))}"))`);
29407
+ lines.push(`(allow file-write* (subpath "${escapeProfilePath(path24.resolve(cwd))}"))`);
28795
29408
  lines.push('(allow file-write* (subpath "/tmp"))');
28796
29409
  lines.push('(allow file-write* (subpath "/private/tmp"))');
28797
29410
  lines.push('(allow file-write* (subpath "/private/var/folders"))');
@@ -28853,7 +29466,7 @@ ${result.stderr}` : ""
28853
29466
  ].filter(Boolean).join("\n");
28854
29467
  return ok(content, {
28855
29468
  command,
28856
- workdir: path24.relative(ctx.cwd, workdir) || ".",
29469
+ workdir: path25.relative(ctx.cwd, workdir) || ".",
28857
29470
  exitCode: result.exitCode,
28858
29471
  signal: result.signal,
28859
29472
  timedOut: result.timedOut,
@@ -28905,8 +29518,8 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
28905
29518
 
28906
29519
  // src/tools/grep.ts
28907
29520
  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";
29521
+ import { readdir as readdir2, readFile as readFile13, stat as stat7 } from "node:fs/promises";
29522
+ import path26 from "node:path";
28910
29523
  var MAX_MATCHES = 200;
28911
29524
  var grepTool = {
28912
29525
  name: "grep",
@@ -28981,7 +29594,7 @@ function runRg(cwd, base, pattern, glob, signal) {
28981
29594
  }
28982
29595
  function rewriteRgPath(cwd, line) {
28983
29596
  const [file, rest] = splitFirst(line, ":");
28984
- if (!path25.isAbsolute(file)) return line;
29597
+ if (!path26.isAbsolute(file)) return line;
28985
29598
  return `${relativeToCwd(cwd, file)}:${rest}`;
28986
29599
  }
28987
29600
  function splitFirst(value, delimiter) {
@@ -28996,13 +29609,13 @@ async function fallbackSearch(cwd, base, pattern, glob) {
28996
29609
  if (out.length > MAX_MATCHES) return;
28997
29610
  const relative = relativeToCwd(cwd, item);
28998
29611
  if (isIgnoredPath(relative)) return;
28999
- const info = await stat6(item);
29612
+ const info = await stat7(item);
29000
29613
  if (info.isDirectory()) {
29001
- for (const entry of await readdir2(item)) await walk2(path25.join(item, entry));
29614
+ for (const entry of await readdir2(item)) await walk2(path26.join(item, entry));
29002
29615
  return;
29003
29616
  }
29004
29617
  if (glob && !matchGlob(glob, relative)) return;
29005
- const buffer = await readFile12(item);
29618
+ const buffer = await readFile13(item);
29006
29619
  if (buffer.includes(0)) return;
29007
29620
  const lines = buffer.toString("utf8").split(/\r?\n/);
29008
29621
  for (let i = 0; i < lines.length; i++) {
@@ -29016,8 +29629,8 @@ async function fallbackSearch(cwd, base, pattern, glob) {
29016
29629
  }
29017
29630
 
29018
29631
  // src/tools/glob.ts
29019
- import { readdir as readdir3, stat as stat7 } from "node:fs/promises";
29020
- import path26 from "node:path";
29632
+ import { readdir as readdir3, stat as stat8 } from "node:fs/promises";
29633
+ import path27 from "node:path";
29021
29634
  var MAX_PATHS = 1e3;
29022
29635
  var globTool = {
29023
29636
  name: "glob",
@@ -29049,10 +29662,10 @@ async function collectPaths(cwd, root, pattern) {
29049
29662
  async function walk2(dir) {
29050
29663
  const entries = await readdir3(dir, { withFileTypes: true });
29051
29664
  for (const entry of entries) {
29052
- const absolute = path26.join(dir, entry.name);
29665
+ const absolute = path27.join(dir, entry.name);
29053
29666
  const relative = relativeToCwd(cwd, absolute);
29054
29667
  if (isIgnoredPath(relative)) continue;
29055
- const info = await stat7(absolute);
29668
+ const info = await stat8(absolute);
29056
29669
  if (matchGlob(pattern, relative)) out.push({ relative, mtimeMs: info.mtimeMs });
29057
29670
  if (entry.isDirectory()) await walk2(absolute);
29058
29671
  }
@@ -30051,14 +30664,14 @@ async function runExecutionSession(options2) {
30051
30664
  }
30052
30665
 
30053
30666
  // 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";
30667
+ import { mkdir as mkdir12, open as open3, rm as rm2 } from "node:fs/promises";
30668
+ import path29 from "node:path";
30056
30669
 
30057
30670
  // src/goals/storage.ts
30058
30671
  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";
30672
+ import { mkdir as mkdir11, readFile as readFile14, rename as rename5, writeFile as writeFile11 } from "node:fs/promises";
30060
30673
  import fs9 from "node:fs";
30061
- import path27 from "node:path";
30674
+ import path28 from "node:path";
30062
30675
  var GoalStore = class {
30063
30676
  cwd;
30064
30677
  dir;
@@ -30069,12 +30682,12 @@ var GoalStore = class {
30069
30682
  this.cwd = cwd;
30070
30683
  this.scope = normalizeGoalStoreScope(options2.scope);
30071
30684
  this.dir = goalStoreDir(cwd, this.scope);
30072
- this.activePath = path27.join(this.dir, "active.json");
30073
- this.archiveDir = path27.join(this.dir, "archive");
30685
+ this.activePath = path28.join(this.dir, "active.json");
30686
+ this.archiveDir = path28.join(this.dir, "archive");
30074
30687
  }
30075
30688
  async loadActive() {
30076
30689
  if (!fs9.existsSync(this.activePath)) return void 0;
30077
- const text = await readFile13(this.activePath, "utf8");
30690
+ const text = await readFile14(this.activePath, "utf8");
30078
30691
  try {
30079
30692
  return JSON.parse(text);
30080
30693
  } catch {
@@ -30083,36 +30696,36 @@ var GoalStore = class {
30083
30696
  }
30084
30697
  }
30085
30698
  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)}
30699
+ await mkdir11(this.dir, { recursive: true });
30700
+ const tmp = path28.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
30701
+ await writeFile11(tmp, `${JSON.stringify(state, null, 2)}
30089
30702
  `, "utf8");
30090
- await rename4(tmp, this.activePath);
30703
+ await rename5(tmp, this.activePath);
30091
30704
  }
30092
30705
  async archiveActive(status = "cleared") {
30093
30706
  const state = await this.loadActive();
30094
30707
  if (!state) return void 0;
30095
- await mkdir10(this.archiveDir, { recursive: true });
30708
+ await mkdir11(this.archiveDir, { recursive: true });
30096
30709
  const archived = {
30097
30710
  ...state,
30098
30711
  status,
30099
30712
  updatedAt: Date.now()
30100
30713
  };
30101
- await writeFile10(path27.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
30714
+ await writeFile11(path28.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
30102
30715
  `, "utf8");
30103
30716
  await fs9.promises.rm(this.activePath, { force: true });
30104
30717
  return archived;
30105
30718
  }
30106
30719
  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");
30720
+ await mkdir11(this.dir, { recursive: true });
30721
+ const backup = path28.join(this.dir, `active.corrupt.${Date.now()}.json`);
30722
+ await writeFile11(backup, text, "utf8");
30110
30723
  await fs9.promises.rm(this.activePath, { force: true });
30111
30724
  }
30112
30725
  };
30113
30726
  function goalStoreDir(cwd, scope) {
30114
30727
  const safeScope = normalizeGoalStoreScope(scope);
30115
- return safeScope ? path27.join(cwd, ".demian", "goals", "sessions", safeScope) : path27.join(cwd, ".demian", "goals");
30728
+ return safeScope ? path28.join(cwd, ".demian", "goals", "sessions", safeScope) : path28.join(cwd, ".demian", "goals");
30116
30729
  }
30117
30730
  function normalizeGoalStoreScope(scope) {
30118
30731
  const trimmed = scope?.trim();
@@ -30126,10 +30739,10 @@ function normalizeGoalStoreScope(scope) {
30126
30739
  var GoalLock = class {
30127
30740
  path;
30128
30741
  constructor(cwd, scope) {
30129
- this.path = path28.join(goalStoreDir(cwd, scope), "active.lock");
30742
+ this.path = path29.join(goalStoreDir(cwd, scope), "active.lock");
30130
30743
  }
30131
30744
  async acquire() {
30132
- await mkdir11(path28.dirname(this.path), { recursive: true });
30745
+ await mkdir12(path29.dirname(this.path), { recursive: true });
30133
30746
  const handle = await open3(this.path, "wx").catch((error) => {
30134
30747
  const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
30135
30748
  if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
@@ -30717,8 +31330,8 @@ function sha256(value) {
30717
31330
 
30718
31331
  // src/root-session.ts
30719
31332
  import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
30720
- import os11 from "node:os";
30721
- import path29 from "node:path";
31333
+ import os12 from "node:os";
31334
+ import path30 from "node:path";
30722
31335
 
30723
31336
  // src/tools/cowork.ts
30724
31337
  function createCoworkTool(options2) {
@@ -31541,8 +32154,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
31541
32154
  }
31542
32155
  }
31543
32156
  async createCoworkIsolatedWorkspace(groupId, memberId) {
31544
- const root = await mkdtemp(path29.join(os11.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
31545
- const cwd = path29.join(root, "workspace");
32157
+ const root = await mkdtemp(path30.join(os12.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
32158
+ const cwd = path30.join(root, "workspace");
31546
32159
  await cp(this.#options.cwd, cwd, {
31547
32160
  recursive: true,
31548
32161
  filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
@@ -31897,7 +32510,7 @@ function scopeStaticPrefix(pattern) {
31897
32510
  return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
31898
32511
  }
31899
32512
  function shouldCopyIntoCoworkWorkspace(root, source) {
31900
- const relative = path29.relative(root, source).split(path29.sep).join("/");
32513
+ const relative = path30.relative(root, source).split(path30.sep).join("/");
31901
32514
  if (!relative) return true;
31902
32515
  const parts = relative.split("/");
31903
32516
  return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
@@ -32109,13 +32722,13 @@ function finitePositiveInteger(value) {
32109
32722
  }
32110
32723
 
32111
32724
  // src/ui/preferences.ts
32112
- import { mkdir as mkdir12, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
32725
+ import { mkdir as mkdir13, readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
32113
32726
  import fs10 from "node:fs";
32114
- import path30 from "node:path";
32727
+ import path31 from "node:path";
32115
32728
  var UiPreferenceStore = class {
32116
32729
  filePath;
32117
32730
  constructor(cwd, filePath) {
32118
- this.filePath = filePath ? path30.resolve(cwd, filePath) : path30.join(cwd, ".demian", "preferences.json");
32731
+ this.filePath = filePath ? path31.resolve(cwd, filePath) : path31.join(cwd, ".demian", "preferences.json");
32119
32732
  }
32120
32733
  async load() {
32121
32734
  if (!fs10.existsSync(this.filePath)) return void 0;
@@ -32133,13 +32746,13 @@ var UiPreferenceStore = class {
32133
32746
  model: selection.model,
32134
32747
  updatedAt: Date.now()
32135
32748
  };
32136
- await mkdir12(path30.dirname(this.filePath), { recursive: true });
32137
- await writeFile11(this.filePath, `${JSON.stringify(file, null, 2)}
32749
+ await mkdir13(path31.dirname(this.filePath), { recursive: true });
32750
+ await writeFile12(this.filePath, `${JSON.stringify(file, null, 2)}
32138
32751
  `, { mode: 384 });
32139
32752
  }
32140
32753
  async #read() {
32141
32754
  try {
32142
- return JSON.parse(await readFile14(this.filePath, "utf8"));
32755
+ return JSON.parse(await readFile15(this.filePath, "utf8"));
32143
32756
  } catch {
32144
32757
  return void 0;
32145
32758
  }
@@ -32155,9 +32768,9 @@ function preferenceKey(selection) {
32155
32768
 
32156
32769
  // src/models/catalog.ts
32157
32770
  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";
32771
+ import { mkdir as mkdir14, readFile as readFile16, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
32772
+ import os13 from "node:os";
32773
+ import path32 from "node:path";
32161
32774
  var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
32162
32775
  var PING_TIMEOUT_MS = 1500;
32163
32776
  var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
@@ -32469,12 +33082,12 @@ async function fetchJson2(url, options2) {
32469
33082
  return text ? JSON.parse(text) : {};
32470
33083
  }
32471
33084
  function cachePath(cacheKey) {
32472
- return path31.join(os12.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
33085
+ return path32.join(os13.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
32473
33086
  }
32474
33087
  async function readCatalogCache(cacheKey, ttlMs, allow) {
32475
33088
  if (!allow) return void 0;
32476
33089
  try {
32477
- const raw = JSON.parse(await readFile15(cachePath(cacheKey), "utf8"));
33090
+ const raw = JSON.parse(await readFile16(cachePath(cacheKey), "utf8"));
32478
33091
  if (!raw.result) return void 0;
32479
33092
  if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
32480
33093
  return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
@@ -32484,10 +33097,10 @@ async function readCatalogCache(cacheKey, ttlMs, allow) {
32484
33097
  }
32485
33098
  async function writeCatalogCache(cacheKey, result) {
32486
33099
  const filePath = cachePath(cacheKey);
32487
- await mkdir13(path31.dirname(filePath), { recursive: true, mode: 448 });
33100
+ await mkdir14(path32.dirname(filePath), { recursive: true, mode: 448 });
32488
33101
  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);
33102
+ await writeFile13(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
33103
+ await rename6(temp, filePath);
32491
33104
  }
32492
33105
  function safeCacheName(providerName) {
32493
33106
  return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
@@ -32538,7 +33151,7 @@ async function codexClientVersion(override) {
32538
33151
  }
32539
33152
  async function readPackageVersion() {
32540
33153
  try {
32541
- const raw = JSON.parse(await readFile15(new URL("../package.json", import.meta.url), "utf8"));
33154
+ const raw = JSON.parse(await readFile16(new URL("../package.json", import.meta.url), "utf8"));
32542
33155
  return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
32543
33156
  } catch {
32544
33157
  return DEFAULT_CODEX_CLIENT_VERSION;
@@ -32602,6 +33215,9 @@ function providerOptions(config, catalogs = {}) {
32602
33215
  permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
32603
33216
  ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
32604
33217
  available,
33218
+ baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
33219
+ apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
33220
+ auth: sanitizeProviderAuth(provider.auth),
32605
33221
  catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
32606
33222
  };
32607
33223
  }).sort((left, right) => {
@@ -32673,13 +33289,20 @@ function providerCatalogAvailable(catalog) {
32673
33289
  function unavailableLabel(value) {
32674
33290
  return `(n/a) ${value}`;
32675
33291
  }
33292
+ function sanitizeProviderAuth(auth) {
33293
+ if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
33294
+ const record = auth;
33295
+ const type = typeof record.type === "string" ? record.type : void 0;
33296
+ const header = typeof record.header === "string" ? record.header : void 0;
33297
+ return type || header ? { type, header } : void 0;
33298
+ }
32676
33299
  function errorMessage3(error) {
32677
33300
  return error instanceof Error ? error.message : String(error);
32678
33301
  }
32679
33302
 
32680
33303
  // 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";
33304
+ import { mkdir as mkdir15, readFile as readFile17, readdir as readdir4, rm as rm4, writeFile as writeFile14 } from "node:fs/promises";
33305
+ import path33 from "node:path";
32683
33306
  var CONVERSATIONS_DIR = "conversations";
32684
33307
  var CONVERSATION_INDEX_FILE = "conversations.json";
32685
33308
  var CONVERSATION_FILE = "conversation.json";
@@ -32692,13 +33315,13 @@ function createConversationRecord(input2) {
32692
33315
  title: input2.title?.trim() || "New session",
32693
33316
  createdAt: timestamp,
32694
33317
  updatedAt: timestamp,
32695
- cwd: path32.resolve(input2.cwd),
33318
+ cwd: path33.resolve(input2.cwd),
32696
33319
  modelHistory: cleanModelHistory(input2.history ?? []),
32697
33320
  snapshot: input2.snapshot
32698
33321
  };
32699
33322
  }
32700
33323
  async function loadConversationIndex(storageDir = defaultDemianStorageDir()) {
32701
- const index = await readJson(path32.join(storageDir, CONVERSATION_INDEX_FILE));
33324
+ const index = await readJson(path33.join(storageDir, CONVERSATION_INDEX_FILE));
32702
33325
  const conversations = Array.isArray(index?.conversations) ? index.conversations.map((item) => {
32703
33326
  if (!item || typeof item !== "object") return void 0;
32704
33327
  const object2 = item;
@@ -32732,7 +33355,7 @@ async function loadConversationRecords(storageDir = defaultDemianStorageDir()) {
32732
33355
  async function saveConversationRecords(storageDir, records, selectedSessionId) {
32733
33356
  const root = storageDir ?? defaultDemianStorageDir();
32734
33357
  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 });
33358
+ await mkdir15(path33.join(root, CONVERSATIONS_DIR), { recursive: true });
32736
33359
  for (const record of kept) {
32737
33360
  await writeJson(conversationRecordPath(root, record.id), {
32738
33361
  version: 1,
@@ -32745,7 +33368,7 @@ async function saveConversationRecords(storageDir, records, selectedSessionId) {
32745
33368
  snapshot: record.snapshot
32746
33369
  });
32747
33370
  }
32748
- await writeJson(path32.join(root, CONVERSATION_INDEX_FILE), {
33371
+ await writeJson(path33.join(root, CONVERSATION_INDEX_FILE), {
32749
33372
  version: 1,
32750
33373
  selectedSessionId,
32751
33374
  conversations: kept.map((record) => ({
@@ -32770,7 +33393,7 @@ function findConversation(records, target) {
32770
33393
  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
33394
  }
32772
33395
  function sortConversationRecords(records, cwd) {
32773
- const resolvedCwd = path32.resolve(cwd);
33396
+ const resolvedCwd = path33.resolve(cwd);
32774
33397
  return [...records].sort((a, b2) => {
32775
33398
  const aCurrent = isConversationForCwd(a, resolvedCwd) ? 0 : 1;
32776
33399
  const bCurrent = isConversationForCwd(b2, resolvedCwd) ? 0 : 1;
@@ -32785,7 +33408,7 @@ function conversationRuntimeStatus(record, cwd) {
32785
33408
  return isConversationForCwd(record, cwd) ? "ready" : "stored";
32786
33409
  }
32787
33410
  function isConversationForCwd(record, cwd) {
32788
- return path32.resolve(record.cwd) === path32.resolve(cwd);
33411
+ return path33.resolve(record.cwd) === path33.resolve(cwd);
32789
33412
  }
32790
33413
  function conversationSummaryLine(record, index, activeId) {
32791
33414
  const active = record.id === activeId ? "*" : " ";
@@ -32805,14 +33428,14 @@ function cleanModelHistory(history) {
32805
33428
  return history.filter((message) => !isInternalModelHistoryMessage(message)).slice(-MAX_MODEL_HISTORY);
32806
33429
  }
32807
33430
  function conversationDir(storageDir, id) {
32808
- return path32.join(storageDir, CONVERSATIONS_DIR, sanitizeConversationId(id));
33431
+ return path33.join(storageDir, CONVERSATIONS_DIR, sanitizeConversationId(id));
32809
33432
  }
32810
33433
  function conversationRecordPath(storageDir, id) {
32811
- return path32.join(conversationDir(storageDir, id), CONVERSATION_FILE);
33434
+ return path33.join(conversationDir(storageDir, id), CONVERSATION_FILE);
32812
33435
  }
32813
33436
  async function listConversationRecordIds(storageDir) {
32814
33437
  try {
32815
- const entries = await readdir4(path32.join(storageDir, CONVERSATIONS_DIR), { withFileTypes: true });
33438
+ const entries = await readdir4(path33.join(storageDir, CONVERSATIONS_DIR), { withFileTypes: true });
32816
33439
  return entries.filter((entry) => entry.isDirectory()).map((entry) => sanitizeConversationId(entry.name)).filter(Boolean);
32817
33440
  } catch {
32818
33441
  return [];
@@ -32820,14 +33443,14 @@ async function listConversationRecordIds(storageDir) {
32820
33443
  }
32821
33444
  async function readJson(filePath) {
32822
33445
  try {
32823
- return JSON.parse(await readFile16(filePath, "utf8"));
33446
+ return JSON.parse(await readFile17(filePath, "utf8"));
32824
33447
  } catch {
32825
33448
  return void 0;
32826
33449
  }
32827
33450
  }
32828
33451
  async function writeJson(filePath, value) {
32829
- await mkdir14(path32.dirname(filePath), { recursive: true });
32830
- await writeFile13(filePath, `${JSON.stringify(value, null, 2)}
33452
+ await mkdir15(path33.dirname(filePath), { recursive: true });
33453
+ await writeFile14(filePath, `${JSON.stringify(value, null, 2)}
32831
33454
  `, "utf8");
32832
33455
  }
32833
33456
  function normalizeConversationRecord(value, fallbackId) {
@@ -32882,11 +33505,12 @@ function formatRelativeAge(ms2) {
32882
33505
 
32883
33506
  // src/ui/tui/controller.ts
32884
33507
  async function runTuiSession(flags, store2) {
32885
- const cwd = path33.resolve(flags.cwd ?? process.cwd());
33508
+ const cwd = path34.resolve(flags.cwd ?? process.cwd());
32886
33509
  const eventBus = new EventBus();
32887
33510
  eventBus.subscribe((event) => store2.handleEvent(event));
32888
- const loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
32889
- const config = flags.context ? mergeConfig(loadedConfig2, {
33511
+ const configInit = await ensureTuiConfigExists(flags);
33512
+ let loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
33513
+ let config = flags.context ? mergeConfig(loadedConfig2, {
32890
33514
  context: {
32891
33515
  main: flags.context
32892
33516
  }
@@ -32906,6 +33530,10 @@ async function runTuiSession(flags, store2) {
32906
33530
  store2.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
32907
33531
  agent: agentName,
32908
33532
  cwd,
33533
+ configPath: configMutationPath(flags),
33534
+ configCreated: configInit.created,
33535
+ configDefaultProvider: config.defaultProvider,
33536
+ configMessage: configInit.created ? `Created config at ${configInit.path}` : void 0,
32909
33537
  agentOptions,
32910
33538
  contextEfficiency: {
32911
33539
  maxContextTokens: config.context.main.maxContextTokens,
@@ -32914,6 +33542,7 @@ async function runTuiSession(flags, store2) {
32914
33542
  }
32915
33543
  });
32916
33544
  let nextPrompt = flags.prompt;
33545
+ let openConfigOnFirstPrompt = configInit.created && !flags.prompt.trim();
32917
33546
  const conversationsEnabled = flags.conversationManagement === true;
32918
33547
  const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
32919
33548
  let currentConversation = conversationState?.current;
@@ -32954,13 +33583,28 @@ async function runTuiSession(flags, store2) {
32954
33583
  await applyStartupSessionSelection(selected);
32955
33584
  }
32956
33585
  for (; ; ) {
32957
- const prompt = await store2.requestPrompt(nextPrompt);
33586
+ const promptPromise = store2.requestPrompt(nextPrompt);
33587
+ if (openConfigOnFirstPrompt) {
33588
+ openConfigOnFirstPrompt = false;
33589
+ store2.openConfigManager(`Created config at ${configInit.path}`);
33590
+ }
33591
+ const prompt = await promptPromise;
32958
33592
  nextPrompt = void 0;
32959
33593
  if (store2.exitRequested()) {
32960
33594
  await saveCurrentSelection();
32961
33595
  return 0;
32962
33596
  }
32963
33597
  if (!prompt) return 0;
33598
+ const configAction = parseTuiConfigAction(prompt);
33599
+ if (configAction) {
33600
+ await handleConfigAction(configAction);
33601
+ continue;
33602
+ }
33603
+ const sessionAction = parseTuiSessionAction(prompt);
33604
+ if (sessionAction) {
33605
+ await handleSessionSelectionShortcut();
33606
+ continue;
33607
+ }
32964
33608
  const sessionCommand = parseSessionCommand(prompt);
32965
33609
  if (sessionCommand) {
32966
33610
  await handleSessionCommand(sessionCommand);
@@ -33195,22 +33839,13 @@ ${sessionListMessage()}`);
33195
33839
  return;
33196
33840
  }
33197
33841
  if (command.action === "delete") {
33198
- const target = findConversation(sortedConversations(), command.target);
33842
+ const target = command.target ? findConversation(sortedConversations(), command.target) : currentConversation;
33199
33843
  if (!target) {
33200
33844
  store2.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".`);
33201
33845
  store2.prepareForPrompt("waiting for next message");
33202
33846
  return;
33203
33847
  }
33204
- conversationRecords.delete(target.id);
33205
- runtimeByConversation.delete(target.id);
33206
- await deleteConversationRecord(flags.conversationStorageDir, target.id);
33207
- if (currentConversation?.id === target.id) {
33208
- const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
33209
- conversationRecords.set(next.id, next);
33210
- await switchConversation(next);
33211
- }
33212
- store2.prepareForPrompt(`deleted session: ${target.title}`);
33213
- await persistCurrentConversation();
33848
+ await deleteConversationById(target.id);
33214
33849
  return;
33215
33850
  }
33216
33851
  if (command.action === "rename") {
@@ -33237,6 +33872,29 @@ ${sessionListMessage()}`);
33237
33872
  await persistCurrentConversation();
33238
33873
  }
33239
33874
  }
33875
+ async function handleSessionSelectionShortcut() {
33876
+ if (!conversationsEnabled) {
33877
+ store2.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
33878
+ store2.prepareForPrompt("waiting for next message");
33879
+ return;
33880
+ }
33881
+ if (!currentConversation) {
33882
+ currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
33883
+ conversationRecords.set(currentConversation.id, currentConversation);
33884
+ }
33885
+ await persistCurrentConversation();
33886
+ const selection = await store2.requestSessionSelection(startupSessionOptions(), { cancel: "prompt" });
33887
+ if (selection.kind === "cancel") {
33888
+ store2.prepareForPrompt("waiting for next message");
33889
+ return;
33890
+ }
33891
+ if (selection.kind === "exit" || store2.exitRequested()) return;
33892
+ if (selection.kind === "delete") {
33893
+ await deleteConversationById(selection.id);
33894
+ return;
33895
+ }
33896
+ await applyStartupSessionSelection(selection);
33897
+ }
33240
33898
  function startupSessionOptions() {
33241
33899
  const records = sortedConversations();
33242
33900
  const sessionOptions = records.map((record) => ({
@@ -33246,11 +33904,19 @@ ${sessionListMessage()}`);
33246
33904
  cwd: record.cwd,
33247
33905
  status: conversationRuntimeStatus(record, cwd),
33248
33906
  updatedAt: record.updatedAt,
33249
- currentWorkspace: path33.resolve(record.cwd) === cwd
33907
+ currentWorkspace: path34.resolve(record.cwd) === cwd
33250
33908
  }));
33251
33909
  return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
33252
33910
  }
33253
33911
  async function applyStartupSessionSelection(selection) {
33912
+ if (selection.kind === "cancel") {
33913
+ store2.prepareForPrompt("waiting for next message");
33914
+ return;
33915
+ }
33916
+ if (selection.kind === "delete") {
33917
+ await deleteConversationById(selection.id);
33918
+ return;
33919
+ }
33254
33920
  if (selection.kind === "new") {
33255
33921
  const next = createConversationRecord({ id: createRootSessionId(), cwd });
33256
33922
  conversationRecords.set(next.id, next);
@@ -33272,6 +33938,24 @@ ${sessionListMessage()}`);
33272
33938
  store2.prepareForPrompt(`session ready: ${target.title}`);
33273
33939
  await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], target.id);
33274
33940
  }
33941
+ async function deleteConversationById(id) {
33942
+ const target = conversationRecords.get(id) ?? findConversation(sortedConversations(), id);
33943
+ if (!target) {
33944
+ store2.showSystemMessage("Sessions", `No session matched "${id}".`);
33945
+ store2.prepareForPrompt("waiting for next message");
33946
+ return;
33947
+ }
33948
+ conversationRecords.delete(target.id);
33949
+ runtimeByConversation.delete(target.id);
33950
+ await deleteConversationRecord(flags.conversationStorageDir, target.id);
33951
+ if (currentConversation?.id === target.id) {
33952
+ const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
33953
+ conversationRecords.set(next.id, next);
33954
+ await switchConversation(next);
33955
+ }
33956
+ store2.prepareForPrompt(`deleted session: ${target.title}`);
33957
+ await persistCurrentConversation();
33958
+ }
33275
33959
  async function switchConversation(record) {
33276
33960
  currentConversation = record;
33277
33961
  rootSessionId = record.id;
@@ -33307,6 +33991,87 @@ ${sessionListMessage()}`);
33307
33991
  await preferenceStore.save(selection);
33308
33992
  savedSelectionKey = key;
33309
33993
  }
33994
+ async function handleConfigAction(action) {
33995
+ const path37 = configMutationPath(flags);
33996
+ try {
33997
+ let message = "Config updated.";
33998
+ let nextSelection = {};
33999
+ if (action.type === "setDefaultProvider") {
34000
+ await updateConfigDefaults({ path: path37, defaultProvider: action.provider });
34001
+ message = `Default provider saved: ${action.provider}`;
34002
+ nextSelection = { provider: action.provider };
34003
+ } else if (action.type === "setDefaultModel") {
34004
+ await setDefaultModelProfile({
34005
+ path: path37,
34006
+ provider: action.provider,
34007
+ profileName: action.profileName,
34008
+ name: action.name,
34009
+ displayName: action.displayName,
34010
+ model: action.model
34011
+ });
34012
+ message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
34013
+ nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
34014
+ } else if (action.type === "addProvider") {
34015
+ await addProvider({ path: path37, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
34016
+ message = `Provider added: ${action.name}`;
34017
+ nextSelection = { provider: action.name };
34018
+ } else if (action.type === "updateProvider") {
34019
+ await updateProviderSettings({
34020
+ path: path37,
34021
+ provider: action.provider,
34022
+ ...action.field === "baseURL" ? { baseURL: action.value } : {},
34023
+ ...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
34024
+ ...action.field === "authHeader" ? { authHeader: action.value } : {}
34025
+ });
34026
+ message = `Provider ${action.field} saved: ${action.provider}`;
34027
+ nextSelection = { provider: action.provider };
34028
+ }
34029
+ await reloadConfigSettings(message, nextSelection);
34030
+ } catch (error) {
34031
+ store2.prepareForPrompt("config action failed");
34032
+ store2.showSystemMessage("Config", errorMessage(error));
34033
+ store2.openConfigManager(errorMessage(error));
34034
+ }
34035
+ }
34036
+ async function reloadConfigSettings(message, selectionFlags = {}) {
34037
+ loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
34038
+ config = flags.context ? mergeConfig(loadedConfig2, {
34039
+ context: {
34040
+ main: flags.context
34041
+ }
34042
+ }) : loadedConfig2;
34043
+ const currentSelection = store2.currentSelection();
34044
+ const selection = createInteractiveModelSelection(
34045
+ config,
34046
+ selectionFlags.provider || selectionFlags.model ? selectionFlags : { provider: currentSelection.providerName, model: currentSelection.model }
34047
+ );
34048
+ store2.configureSettings(selection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
34049
+ agent: store2.currentAgentName(),
34050
+ cwd,
34051
+ configPath: configMutationPath(flags),
34052
+ configDefaultProvider: config.defaultProvider,
34053
+ configMessage: message,
34054
+ agentOptions,
34055
+ contextEfficiency: {
34056
+ maxContextTokens: config.context.main.maxContextTokens,
34057
+ compactAtRatio: config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5,
34058
+ compactAtTokens: config.context.main.maxContextTokens ? Math.floor(config.context.main.maxContextTokens * (config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5)) : void 0
34059
+ }
34060
+ });
34061
+ store2.openConfigManager(message);
34062
+ }
34063
+ }
34064
+ async function ensureTuiConfigExists(flags) {
34065
+ const target = configMutationPath(flags);
34066
+ if (!flags.configPath) {
34067
+ const jsondPath = target.replace(/\.json$/i, ".jsond");
34068
+ if (existsSync4(target) || existsSync4(jsondPath)) return { path: target, created: false };
34069
+ }
34070
+ const result = await createUserConfig({ path: target });
34071
+ return { path: result.path, created: result.created };
34072
+ }
34073
+ function configMutationPath(flags) {
34074
+ return flags.configPath ? path34.resolve(flags.configPath) : defaultUserConfigPath();
33310
34075
  }
33311
34076
  function isGoalStateClearingAction(action) {
33312
34077
  return action === "status" || action === "pause" || action === "resume";
@@ -33333,7 +34098,7 @@ function sessionUsage() {
33333
34098
  " /session new [title]",
33334
34099
  " /session switch <number|id|title>",
33335
34100
  " /session rename <title>",
33336
- " /session delete <number|id|title>",
34101
+ " /session delete [number|id|title]",
33337
34102
  " /session clear",
33338
34103
  "",
33339
34104
  "Aliases: /sessions, /conversation, /conversations, /conv"
@@ -33344,289 +34109,6 @@ function sessionUsage() {
33344
34109
  import { EventEmitter } from "node:events";
33345
34110
  import fs11 from "node:fs";
33346
34111
  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
34112
  var ConfigWatcher = class extends EventEmitter {
33631
34113
  #filePath;
33632
34114
  #debounceMs;
@@ -33715,6 +34197,20 @@ if (process.argv.includes("--config-template")) {
33715
34197
  `);
33716
34198
  process.exit(0);
33717
34199
  }
34200
+ if (process.argv.includes("--config-read")) {
34201
+ await readConfigMeta(jsonArg("--config-read")).then(
34202
+ (result) => {
34203
+ process.stdout.write(`${JSON.stringify({ ok: true, ...result })}
34204
+ `);
34205
+ process.exit(0);
34206
+ },
34207
+ (error) => {
34208
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
34209
+ `);
34210
+ process.exit(1);
34211
+ }
34212
+ );
34213
+ }
33718
34214
  if (process.argv.includes("--config-init")) {
33719
34215
  const force = process.argv.includes("--force");
33720
34216
  await createUserConfig({ force }).then(
@@ -33772,6 +34268,34 @@ if (process.argv.includes("--config-set-defaults")) {
33772
34268
  }
33773
34269
  );
33774
34270
  }
34271
+ if (process.argv.includes("--config-set-default-model")) {
34272
+ await setDefaultModelProfile(jsonArg("--config-set-default-model")).then(
34273
+ (result) => {
34274
+ process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
34275
+ `);
34276
+ process.exit(0);
34277
+ },
34278
+ (error) => {
34279
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
34280
+ `);
34281
+ process.exit(1);
34282
+ }
34283
+ );
34284
+ }
34285
+ if (process.argv.includes("--config-update-provider")) {
34286
+ await updateProviderSettings(jsonArg("--config-update-provider")).then(
34287
+ (result) => {
34288
+ process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
34289
+ `);
34290
+ process.exit(0);
34291
+ },
34292
+ (error) => {
34293
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
34294
+ `);
34295
+ process.exit(1);
34296
+ }
34297
+ );
34298
+ }
33775
34299
  if (process.argv.includes("--provider-auth-check")) {
33776
34300
  await checkProviderAuth(jsonArg("--provider-auth-check")).then(
33777
34301
  (result) => {
@@ -34346,30 +34870,43 @@ function setApiKey(providerName, apiKey, opts = {}) {
34346
34870
  }
34347
34871
  async function sendConfigMeta() {
34348
34872
  if (!loadedConfig) return;
34873
+ send({ type: "configMeta", config: await buildConfigMeta(loadedConfig) });
34874
+ }
34875
+ async function readConfigMeta(options2) {
34876
+ const cwd = typeof options2.cwd === "string" && options2.cwd.trim() ? options2.cwd : process.cwd();
34877
+ const configPath = typeof options2.configPath === "string" && options2.configPath.trim() ? options2.configPath : defaultUserConfigPath();
34878
+ let created = false;
34879
+ if (options2.create && !existsSync5(configPath)) {
34880
+ const result = await createUserConfig({ path: configPath });
34881
+ created = result.created;
34882
+ }
34883
+ const config = await loadConfig({ cwd, configPath: options2.configPath });
34884
+ return { path: configPath, created, config: await buildConfigMeta(config) };
34885
+ }
34886
+ async function buildConfigMeta(config) {
34349
34887
  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);
34888
+ for (const agentDefinition of Object.values(config.agents ?? {})) registry.register(agentDefinition, { scope: "user", trusted: true });
34889
+ const providerEntries = sortProviderNames(Object.keys(config.providers)).map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden);
34352
34890
  const providerOrder = new Map(providerEntries.map(([name], index) => [name, index]));
34353
34891
  const providers = (await Promise.all(providerEntries.map(([name, provider]) => providerMeta(name, provider)))).sort((left, right) => {
34354
34892
  if (left.available !== right.available) return left.available ? -1 : 1;
34355
34893
  return (providerOrder.get(left.name) ?? 0) - (providerOrder.get(right.name) ?? 0);
34356
34894
  });
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
- });
34895
+ return {
34896
+ defaultProvider: config.defaultProvider,
34897
+ defaultAgent: config.defaultAgent,
34898
+ context: {
34899
+ main: config.context?.main
34900
+ },
34901
+ providerOrder: providers.map((provider) => provider.name),
34902
+ providers,
34903
+ agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
34904
+ };
34370
34905
  }
34371
34906
  async function providerMeta(name, provider) {
34372
34907
  const apiKeyEnv = typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0;
34908
+ const configuredProfiles = configuredModelProfiles(provider);
34909
+ const auth = sanitizeProviderAuth2(provider.auth);
34373
34910
  const catalog = await listProviderModelCatalog(name, provider).catch((error) => ({ status: "unavailable", providerName: name, models: [], source: "fallback", message: errorMessage4(error) }));
34374
34911
  const available = providerCatalogAvailable2(catalog);
34375
34912
  const modelProfiles = catalog.models.length ? catalog.models.map((model) => ({
@@ -34402,7 +34939,16 @@ async function providerMeta(name, provider) {
34402
34939
  modelLabel: available || !defaultModel ? defaultModel : unavailableLabel2(defaultModel),
34403
34940
  models,
34404
34941
  modelProfiles: labeledProfiles,
34942
+ configuredModelProfiles: configuredProfiles,
34943
+ configured: {
34944
+ baseURL: provider.baseURL,
34945
+ apiKeyEnv,
34946
+ apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
34947
+ auth,
34948
+ hasInlineApiKey: typeof provider.apiKey === "string" && provider.apiKey.length > 0
34949
+ },
34405
34950
  baseURL: provider.baseURL,
34951
+ auth,
34406
34952
  apiKeyEnv,
34407
34953
  apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
34408
34954
  catalog: { status: catalog.status, source: catalog.source, message: catalog.message },
@@ -34411,6 +34957,26 @@ async function providerMeta(name, provider) {
34411
34957
  requiresApiKey: provider.type === "openai-compatible" || provider.type === "anthropic" || provider.type === "ollama"
34412
34958
  };
34413
34959
  }
34960
+ function configuredModelProfiles(provider) {
34961
+ if (!Array.isArray(provider.modelProfiles)) return [];
34962
+ return provider.modelProfiles.filter((profile) => profile && typeof profile === "object" && typeof profile.model === "string" && profile.model.trim()).map((profile, index) => {
34963
+ 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();
34964
+ const displayName = typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : name;
34965
+ return {
34966
+ name,
34967
+ displayName,
34968
+ model: profile.model.trim(),
34969
+ description: typeof profile.description === "string" ? profile.description : void 0,
34970
+ baseURL: typeof profile.baseURL === "string" ? profile.baseURL : void 0,
34971
+ apiKeyEnv: typeof profile.apiKeyEnv === "string" ? profile.apiKeyEnv : void 0,
34972
+ apiKeyEnvAliases: asStringArray(profile.apiKeyEnvAliases),
34973
+ auth: sanitizeProviderAuth2(profile.auth),
34974
+ hasInlineApiKey: typeof profile.apiKey === "string" && profile.apiKey.length > 0,
34975
+ source: "config",
34976
+ isDefault: index === 0
34977
+ };
34978
+ });
34979
+ }
34414
34980
  function providerCatalogAvailable2(catalog) {
34415
34981
  return catalog?.status === "ready" && Array.isArray(catalog.models) && catalog.models.length > 0;
34416
34982
  }
@@ -34420,6 +34986,12 @@ function unavailableLabel2(value) {
34420
34986
  function asStringArray(value) {
34421
34987
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
34422
34988
  }
34989
+ function sanitizeProviderAuth2(auth) {
34990
+ if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
34991
+ const type = typeof auth.type === "string" ? auth.type : void 0;
34992
+ const header = typeof auth.header === "string" ? auth.header : void 0;
34993
+ return type || header ? { type, header } : void 0;
34994
+ }
34423
34995
  function contextOptions(value) {
34424
34996
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
34425
34997
  const context = {};