demian-cli 1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui.mjs CHANGED
@@ -147,6 +147,382 @@ var init_path_expansion = __esm({
147
147
  }
148
148
  });
149
149
 
150
+ // src/config-scaffold.ts
151
+ import { chmod, mkdir, readFile as readFile2, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
152
+ import os3 from "node:os";
153
+ import path3 from "node:path";
154
+ function defaultUserConfigPath() {
155
+ return path3.join(os3.homedir(), ".demian", "config.json");
156
+ }
157
+ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
158
+ return {
159
+ version: 2,
160
+ defaultProvider,
161
+ providers: {
162
+ openai: {
163
+ type: "openai-compatible",
164
+ baseURL: "https://api.openai.com/v1",
165
+ apiKey: "",
166
+ apiKeyEnv: "OPENAI_API_KEY",
167
+ catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
168
+ },
169
+ anthropic: {
170
+ type: "anthropic",
171
+ baseURL: "https://api.anthropic.com/v1",
172
+ apiKey: "",
173
+ apiKeyEnv: "ANTHROPIC_API_KEY",
174
+ catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
175
+ },
176
+ gemini: {
177
+ type: "openai-compatible",
178
+ baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
179
+ apiKey: "",
180
+ apiKeyEnv: "GEMINI_API_KEY",
181
+ apiKeyEnvAliases: ["GOOGLE_API_KEY"],
182
+ catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
183
+ },
184
+ groq: {
185
+ type: "openai-compatible",
186
+ baseURL: "https://api.groq.com/openai/v1",
187
+ apiKey: "",
188
+ apiKeyEnv: "GROQ_API_KEY",
189
+ catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
190
+ },
191
+ azure: {
192
+ type: "openai-compatible",
193
+ auth: { type: "api-key", header: "api-key" },
194
+ modelProfiles: [
195
+ {
196
+ name: "azure-example",
197
+ displayName: "Azure example",
198
+ model: "azure-deployment-name",
199
+ baseURL: "https://example.openai.azure.com/openai/v1",
200
+ apiKey: "",
201
+ apiKeyEnv: "AZURE_OPENAI_API_KEY"
202
+ }
203
+ ]
204
+ },
205
+ lmstudio: {
206
+ type: "openai-compatible",
207
+ baseURL: "http://localhost:1234/v1",
208
+ apiKey: "lm-studio",
209
+ modelProfiles: [],
210
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
211
+ },
212
+ "ollama-local": {
213
+ type: "openai-compatible",
214
+ baseURL: "http://localhost:11434/v1",
215
+ apiKey: "ollama",
216
+ modelProfiles: [],
217
+ quirks: { omitTemperature: true },
218
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
219
+ },
220
+ "ollama-cloud": {
221
+ type: "ollama",
222
+ baseURL: "https://ollama.com/api",
223
+ apiKey: "",
224
+ apiKeyEnv: "OLLAMA_API_KEY",
225
+ modelProfiles: [],
226
+ catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
227
+ },
228
+ llamacpp: {
229
+ type: "openai-compatible",
230
+ baseURL: "http://localhost:8080/v1",
231
+ apiKey: "llama.cpp",
232
+ modelProfiles: [],
233
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
234
+ },
235
+ vllm: {
236
+ type: "openai-compatible",
237
+ baseURL: "http://localhost:8000/v1",
238
+ apiKey: "vllm",
239
+ modelProfiles: [],
240
+ catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
241
+ },
242
+ codex: {
243
+ type: "codex",
244
+ baseURL: "https://chatgpt.com/backend-api/codex",
245
+ authStore: "auto",
246
+ allowApiKeyFallback: false,
247
+ promptCacheKey: "root-session",
248
+ catalog: { type: "codex-oauth-models" }
249
+ },
250
+ claudecode: {
251
+ type: "claudecode",
252
+ runtime: "agent-sdk",
253
+ cliPath: "~/.local/bin/claude",
254
+ cwdMode: "session",
255
+ historyPolicy: "passthrough-resume",
256
+ onInvalidResume: "fresh",
257
+ attachmentFallback: "block",
258
+ allowSubagents: false,
259
+ sanitizeApiKeyEnv: true,
260
+ authPreflight: true,
261
+ useBareMode: false,
262
+ usageLedgerScope: "process",
263
+ sessionLock: true,
264
+ abortPolicy: "record-only",
265
+ catalog: { type: "claudecode-supported-models" }
266
+ }
267
+ }
268
+ };
269
+ }
270
+ async function createUserConfig(options = {}) {
271
+ const filePath = expandHome(options.path ?? defaultUserConfigPath());
272
+ const content = `${JSON.stringify(defaultUserConfig(options.defaultProvider), null, 2)}
273
+ `;
274
+ if (options.print) return { path: filePath, created: false, content };
275
+ const existed = await exists(filePath);
276
+ if (existed && !options.force) return { path: filePath, created: false, content: await readFile2(filePath, "utf8"), existed: true };
277
+ await writeJsonAtomic(filePath, content);
278
+ return { path: filePath, created: true, content, existed };
279
+ }
280
+ async function addProvider(options) {
281
+ const filePath = expandHome(options.path ?? defaultUserConfigPath());
282
+ const config = await readConfigObject(filePath);
283
+ const providers = objectValue(config.providers);
284
+ const name = options.name;
285
+ if (providers[name] && !options.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
286
+ providers[name] = providerPreset(options);
287
+ config.providers = providers;
288
+ const content = `${JSON.stringify(config, null, 2)}
289
+ `;
290
+ await writeJsonAtomic(filePath, content);
291
+ return { path: filePath, created: true, content };
292
+ }
293
+ async function addModelProfile(options) {
294
+ const filePath = expandHome(options.path ?? defaultUserConfigPath());
295
+ const config = await readConfigObject(filePath);
296
+ const providers = objectValue(config.providers);
297
+ const provider = objectValue(providers[options.provider]);
298
+ const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
299
+ const displayName = options.displayName ?? options.name;
300
+ const previousName = normalizeOptionalName(options.previousName);
301
+ const existingByPreviousName = previousName ? profiles.findIndex((entry) => entry.name === previousName) : -1;
302
+ const existingByName = profiles.findIndex((entry) => entry.name === options.name);
303
+ const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
304
+ const updateIndex = existingByPreviousName >= 0 ? existingByPreviousName : existingByName;
305
+ if (existingByName >= 0 && existingByName !== updateIndex) throw new Error(`Profile name "${options.name}" already exists on provider ${options.provider}. Pick a different profile name.`);
306
+ if (existingByName >= 0 && existingByPreviousName < 0 && !options.force) throw new Error(`Profile name "${options.name}" already exists on provider ${options.provider}. Use --force to overwrite it.`);
307
+ if (existingByDisplay >= 0 && existingByDisplay !== updateIndex && !options.force) {
308
+ throw new Error(`Profile displayName "${displayName}" already exists on provider ${options.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
309
+ }
310
+ const next = {
311
+ name: options.name,
312
+ displayName,
313
+ model: options.model,
314
+ ...options.baseURL ? { baseURL: options.baseURL } : {},
315
+ ...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
316
+ ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
317
+ };
318
+ if (updateIndex >= 0) profiles[updateIndex] = next;
319
+ else profiles.push(next);
320
+ provider.modelProfiles = profiles;
321
+ providers[options.provider] = provider;
322
+ config.providers = providers;
323
+ const content = `${JSON.stringify(config, null, 2)}
324
+ `;
325
+ await writeJsonAtomic(filePath, content);
326
+ return { path: filePath, created: true, content };
327
+ }
328
+ async function updateConfigDefaults(options) {
329
+ const filePath = expandHome(options.path ?? defaultUserConfigPath());
330
+ const config = await readConfigObject(filePath);
331
+ const defaultProvider = normalizeOptionalName(options.defaultProvider);
332
+ const defaultAgent = normalizeOptionalName(options.defaultAgent);
333
+ if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
334
+ if (defaultProvider) config.defaultProvider = defaultProvider;
335
+ if (defaultAgent) config.defaultAgent = defaultAgent;
336
+ const content = `${JSON.stringify(config, null, 2)}
337
+ `;
338
+ await writeJsonAtomic(filePath, content);
339
+ return { path: filePath, created: true, content };
340
+ }
341
+ async function setDefaultModelProfile(options) {
342
+ const filePath = expandHome(options.path ?? defaultUserConfigPath());
343
+ const config = await readConfigObject(filePath);
344
+ const providers = objectValue(config.providers);
345
+ const provider = providers[options.provider];
346
+ if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options.provider} does not exist.`);
347
+ const providerConfig = { ...provider };
348
+ const profiles = Array.isArray(providerConfig.modelProfiles) ? [...providerConfig.modelProfiles] : [];
349
+ const requestedName = normalizeOptionalName(options.profileName ?? options.name);
350
+ const requestedModel = normalizeOptionalName(options.model);
351
+ const requestedDisplayName = normalizeOptionalName(options.displayName) ?? requestedModel ?? requestedName;
352
+ let index = profiles.findIndex((entry) => requestedName && entry.name === requestedName);
353
+ if (index < 0 && requestedModel) index = profiles.findIndex((entry) => entry.model === requestedModel);
354
+ if (index < 0 && requestedDisplayName) index = profiles.findIndex((entry) => entry.displayName === requestedDisplayName);
355
+ const profile = index >= 0 ? {
356
+ ...profiles[index],
357
+ ...requestedName ? { name: requestedName } : {},
358
+ ...requestedDisplayName ? { displayName: requestedDisplayName } : {},
359
+ ...requestedModel ? { model: requestedModel } : {},
360
+ ...options.baseURL ? { baseURL: options.baseURL } : {},
361
+ ...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
362
+ ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
363
+ } : newModelProfile({
364
+ name: requestedName,
365
+ displayName: requestedDisplayName,
366
+ model: requestedModel ?? requestedName,
367
+ baseURL: options.baseURL,
368
+ apiKey: options.apiKey,
369
+ apiKeyEnv: options.apiKeyEnv
370
+ });
371
+ if (!profile.name || typeof profile.name !== "string") throw new Error("Model profile name is required.");
372
+ if (!profile.model || typeof profile.model !== "string") throw new Error("Model ID is required.");
373
+ if (index >= 0) profiles.splice(index, 1);
374
+ profiles.unshift(profile);
375
+ providerConfig.modelProfiles = profiles;
376
+ providers[options.provider] = providerConfig;
377
+ config.providers = providers;
378
+ const content = `${JSON.stringify(config, null, 2)}
379
+ `;
380
+ await writeJsonAtomic(filePath, content);
381
+ return { path: filePath, created: true, content };
382
+ }
383
+ async function updateProviderSettings(options) {
384
+ const filePath = expandHome(options.path ?? defaultUserConfigPath());
385
+ const config = await readConfigObject(filePath);
386
+ const providers = objectValue(config.providers);
387
+ const provider = providers[options.provider];
388
+ if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options.provider} does not exist.`);
389
+ const providerConfig = { ...provider };
390
+ if (options.baseURL !== void 0) setOrDelete(providerConfig, "baseURL", options.baseURL);
391
+ if (options.apiKey !== void 0) setOrDelete(providerConfig, "apiKey", options.apiKey);
392
+ if (options.apiKeyEnv !== void 0) setOrDelete(providerConfig, "apiKeyEnv", options.apiKeyEnv);
393
+ if (options.authHeader !== void 0) {
394
+ const header = options.authHeader.trim();
395
+ if (header) providerConfig.auth = { type: "api-key", header };
396
+ else delete providerConfig.auth;
397
+ }
398
+ providers[options.provider] = providerConfig;
399
+ config.providers = providers;
400
+ const content = `${JSON.stringify(config, null, 2)}
401
+ `;
402
+ await writeJsonAtomic(filePath, content);
403
+ return { path: filePath, created: true, content };
404
+ }
405
+ async function readConfigObject(filePath) {
406
+ if (!await exists(filePath)) await createUserConfig({ path: filePath });
407
+ const raw = JSON.parse(await readFile2(filePath, "utf8"));
408
+ raw.version ??= 2;
409
+ raw.providers = objectValue(raw.providers);
410
+ return raw;
411
+ }
412
+ function providerPreset(options) {
413
+ const preset = options.preset ?? options.name;
414
+ const auth = apiKeyAuthFields(options);
415
+ const openAIAuth = options.authHeader ? { auth: { type: "api-key", header: options.authHeader } } : {};
416
+ if (preset === "openai") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
417
+ if (preset === "anthropic") return { type: "anthropic", baseURL: options.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
418
+ if (preset === "gemini") {
419
+ const geminiBase = options.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
420
+ return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
421
+ }
422
+ if (preset === "groq") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
423
+ if (preset === "azure") {
424
+ return {
425
+ type: "openai-compatible",
426
+ auth: { type: "api-key", header: options.authHeader ?? "api-key" },
427
+ ...auth,
428
+ modelProfiles: [
429
+ {
430
+ name: "azure-example",
431
+ displayName: "Azure example",
432
+ model: "azure-deployment-name",
433
+ baseURL: options.baseURL ?? "https://example.openai.azure.com/openai/v1",
434
+ ...options.apiKey ? {} : { apiKey: "" },
435
+ apiKeyEnv: options.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
436
+ }
437
+ ]
438
+ };
439
+ }
440
+ if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:1234/v1", apiKey: options.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
441
+ if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:11434/v1", apiKey: options.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
442
+ if (preset === "ollama-cloud") return { type: "ollama", baseURL: options.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
443
+ if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8080/v1", apiKey: options.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
444
+ if (preset === "vllm") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8000/v1", apiKey: options.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
445
+ if (preset === "codex") return { type: "codex", baseURL: options.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
446
+ 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" } };
447
+ if ((options.type ?? preset) === "openai-compatible") {
448
+ const baseURL = options.baseURL;
449
+ if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
450
+ return {
451
+ type: "openai-compatible",
452
+ baseURL,
453
+ ...auth,
454
+ ...openAIAuth,
455
+ catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
456
+ };
457
+ }
458
+ throw new Error(`Unknown provider preset: ${preset}`);
459
+ }
460
+ function apiKeyAuthFields(options, defaultApiKeyEnv) {
461
+ const apiKeyEnv = options.apiKeyEnv ?? defaultApiKeyEnv;
462
+ return {
463
+ ...options.apiKey !== void 0 || apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
464
+ ...apiKeyEnv ? { apiKeyEnv } : {}
465
+ };
466
+ }
467
+ async function writeJsonAtomic(filePath, content) {
468
+ await mkdir(path3.dirname(filePath), { recursive: true, mode: 448 });
469
+ const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
470
+ await writeFile2(temp, content, { mode: 384 });
471
+ await rename(temp, filePath);
472
+ await chmod(filePath, 384).catch(() => void 0);
473
+ }
474
+ function detectDefaultProvider() {
475
+ if (process.env.OPENAI_API_KEY) return "openai";
476
+ if (process.env.ANTHROPIC_API_KEY) return "anthropic";
477
+ if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
478
+ if (process.env.GROQ_API_KEY) return "groq";
479
+ return "openai";
480
+ }
481
+ function objectValue(value) {
482
+ return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
483
+ }
484
+ function normalizeOptionalName(value) {
485
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
486
+ }
487
+ function newModelProfile(options) {
488
+ const model = options.model?.trim();
489
+ if (!model) throw new Error("Model ID is required.");
490
+ const name = options.name?.trim() || modelProfileNameFromModel(model);
491
+ return {
492
+ name,
493
+ displayName: options.displayName?.trim() || name,
494
+ model,
495
+ ...options.baseURL ? { baseURL: options.baseURL } : {},
496
+ ...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
497
+ ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
498
+ };
499
+ }
500
+ function modelProfileNameFromModel(model) {
501
+ return model.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "model";
502
+ }
503
+ function setOrDelete(target, key, value) {
504
+ const trimmed = value.trim();
505
+ if (trimmed) target[key] = trimmed;
506
+ else delete target[key];
507
+ }
508
+ async function exists(filePath) {
509
+ try {
510
+ await stat(filePath);
511
+ return true;
512
+ } catch {
513
+ return false;
514
+ }
515
+ }
516
+ function expandHome(value) {
517
+ return resolveExpandedPath(value);
518
+ }
519
+ var init_config_scaffold = __esm({
520
+ "src/config-scaffold.ts"() {
521
+ "use strict";
522
+ init_path_expansion();
523
+ }
524
+ });
525
+
150
526
  // src/providers/retry.ts
151
527
  async function chatWithRetry(operation, options) {
152
528
  const maxRetries = options.maxRetries ?? 5;
@@ -25628,10 +26004,30 @@ var init_tool_summary = __esm({
25628
26004
  // src/ui/tui/store.ts
25629
26005
  var store_exports = {};
25630
26006
  __export(store_exports, {
26007
+ TUI_CONFIG_ACTION_PREFIX: () => TUI_CONFIG_ACTION_PREFIX,
26008
+ TUI_SESSION_ACTION_PREFIX: () => TUI_SESSION_ACTION_PREFIX,
25631
26009
  TuiStore: () => TuiStore,
25632
26010
  buildClaudeCodePlanExecutionPrompt: () => buildClaudeCodePlanExecutionPrompt,
26011
+ parseTuiConfigAction: () => parseTuiConfigAction,
26012
+ parseTuiSessionAction: () => parseTuiSessionAction,
25633
26013
  streamingLines: () => streamingLines
25634
26014
  });
26015
+ function parseTuiConfigAction(prompt) {
26016
+ if (!prompt.startsWith(TUI_CONFIG_ACTION_PREFIX)) return void 0;
26017
+ try {
26018
+ return JSON.parse(prompt.slice(TUI_CONFIG_ACTION_PREFIX.length));
26019
+ } catch {
26020
+ return void 0;
26021
+ }
26022
+ }
26023
+ function parseTuiSessionAction(prompt) {
26024
+ if (!prompt.startsWith(TUI_SESSION_ACTION_PREFIX)) return void 0;
26025
+ try {
26026
+ return JSON.parse(prompt.slice(TUI_SESSION_ACTION_PREFIX.length));
26027
+ } catch {
26028
+ return void 0;
26029
+ }
26030
+ }
25635
26031
  function toTuiWorkPlan(plan) {
25636
26032
  return {
25637
26033
  planId: plan.planId,
@@ -25791,6 +26187,25 @@ function cloneContextEfficiency(context) {
25791
26187
  lastCompaction: context.lastCompaction ? { ...context.lastCompaction } : void 0
25792
26188
  };
25793
26189
  }
26190
+ function configProviderFieldValue(provider, field) {
26191
+ if (field === "baseURL") return provider.baseURL ?? "";
26192
+ if (field === "apiKeyEnv") return provider.apiKeyEnv ?? "";
26193
+ return provider.auth?.header ?? "";
26194
+ }
26195
+ function defaultConfigProviderPresets() {
26196
+ return [
26197
+ { label: "OpenAI", preset: "openai", name: "openai", apiKeyEnv: "OPENAI_API_KEY", detail: "OpenAI API" },
26198
+ { label: "Anthropic", preset: "anthropic", name: "anthropic", apiKeyEnv: "ANTHROPIC_API_KEY", detail: "Anthropic API" },
26199
+ { label: "Gemini", preset: "gemini", name: "gemini", apiKeyEnv: "GEMINI_API_KEY", detail: "Google Gemini OpenAI-compatible API" },
26200
+ { label: "Groq", preset: "groq", name: "groq", apiKeyEnv: "GROQ_API_KEY", detail: "Groq Cloud" },
26201
+ { label: "Codex", preset: "codex", name: "codex", detail: "ChatGPT Codex OAuth" },
26202
+ { label: "Claude Code", preset: "claudecode", name: "claudecode", detail: "Local Claude Code runtime" },
26203
+ { label: "Ollama local", preset: "ollama-local", name: "ollama-local", detail: "http://localhost:11434" },
26204
+ { label: "LM Studio", preset: "lmstudio", name: "lmstudio", detail: "http://localhost:1234" },
26205
+ { label: "llama.cpp", preset: "llamacpp", name: "llamacpp", detail: "http://localhost:8080" },
26206
+ { label: "vLLM", preset: "vllm", name: "vllm", detail: "http://localhost:8000" }
26207
+ ];
26208
+ }
25794
26209
  function cloneToolRunDetails(details) {
25795
26210
  if (!details) return void 0;
25796
26211
  return {
@@ -25892,7 +26307,7 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
25892
26307
  function streamingLines(markdown) {
25893
26308
  return renderMarkdownFallback(markdown, { width: 100 }).lines;
25894
26309
  }
25895
- var TuiStore;
26310
+ var TUI_CONFIG_ACTION_PREFIX, TUI_SESSION_ACTION_PREFIX, TuiStore;
25896
26311
  var init_store = __esm({
25897
26312
  "src/ui/tui/store.ts"() {
25898
26313
  "use strict";
@@ -25900,6 +26315,8 @@ var init_store = __esm({
25900
26315
  init_commands();
25901
26316
  init_render();
25902
26317
  init_tool_summary();
26318
+ TUI_CONFIG_ACTION_PREFIX = "demian-config:";
26319
+ TUI_SESSION_ACTION_PREFIX = "demian-session:";
25903
26320
  TuiStore = class {
25904
26321
  #state = {
25905
26322
  status: {},
@@ -25908,8 +26325,14 @@ var init_store = __esm({
25908
26325
  inputMode: "starting",
25909
26326
  sessionOptions: [],
25910
26327
  sessionCursor: 0,
26328
+ sessionCancelBehavior: "exit",
25911
26329
  providerOptions: [],
25912
26330
  providerCursor: 0,
26331
+ configModelCursor: 0,
26332
+ configAddProviderCursor: 0,
26333
+ configModelInput: "",
26334
+ configProviderInput: "",
26335
+ configProviderPresets: defaultConfigProviderPresets(),
25913
26336
  agentOptions: [],
25914
26337
  agentCursor: 0,
25915
26338
  permissionPreset: "auto",
@@ -25945,6 +26368,7 @@ var init_store = __esm({
25945
26368
  #pendingPermissionTimeout;
25946
26369
  #syntheticPermissionId = 0;
25947
26370
  #activeClaudeCodePlan;
26371
+ #notifyTimer;
25948
26372
  snapshot() {
25949
26373
  return {
25950
26374
  ...this.#state,
@@ -25952,6 +26376,7 @@ var init_store = __esm({
25952
26376
  selection: this.#state.selection ? { ...this.#state.selection } : void 0,
25953
26377
  sessionOptions: this.#state.sessionOptions.map((option) => ({ ...option })),
25954
26378
  providerOptions: this.#state.providerOptions.map((option) => ({ ...option })),
26379
+ configProviderPresets: this.#state.configProviderPresets.map((option) => ({ ...option })),
25955
26380
  agentOptions: this.#state.agentOptions.map((option) => ({ ...option })),
25956
26381
  blocks: this.#state.blocks.map((block) => ({ ...block, lines: [...block.lines], toolDetails: cloneToolRunDetails(block.toolDetails), goalWork: cloneGoalWork(block.goalWork), cowork: cloneCoworkGroup(block.cowork) })),
25957
26382
  warnings: [...this.#state.warnings],
@@ -26173,6 +26598,10 @@ var init_store = __esm({
26173
26598
  agent: context.agent,
26174
26599
  cwd: context.cwd
26175
26600
  };
26601
+ this.#state.configPath = context.configPath;
26602
+ this.#state.configCreated = context.configCreated;
26603
+ this.#state.configDefaultProvider = context.configDefaultProvider;
26604
+ this.#state.configMessage = context.configMessage;
26176
26605
  if (context.contextEfficiency) {
26177
26606
  this.#state.contextEfficiency = {
26178
26607
  ...this.#state.contextEfficiency ?? { selectedTools: [] },
@@ -26204,7 +26633,7 @@ var init_store = __esm({
26204
26633
  currentPermissionPreset() {
26205
26634
  return this.#state.permissionPreset;
26206
26635
  }
26207
- requestSessionSelection(options) {
26636
+ requestSessionSelection(options, behavior = {}) {
26208
26637
  if (this.#exitRequested) return Promise.resolve({ kind: "exit" });
26209
26638
  const normalized = options.length > 0 ? options.map((option) => ({ ...option })) : [{ kind: "new", title: "New session", status: "ready", currentWorkspace: true }];
26210
26639
  return new Promise((resolve) => {
@@ -26212,6 +26641,7 @@ var init_store = __esm({
26212
26641
  this.#state.inputMode = "session";
26213
26642
  this.#state.sessionOptions = normalized;
26214
26643
  this.#state.sessionCursor = 0;
26644
+ this.#state.sessionCancelBehavior = behavior.cancel ?? "exit";
26215
26645
  this.#state.promptInput = "";
26216
26646
  this.#state.promptError = void 0;
26217
26647
  this.#state.streamingText = "";
@@ -26221,6 +26651,14 @@ var init_store = __esm({
26221
26651
  this.#notify();
26222
26652
  });
26223
26653
  }
26654
+ cancelSessionSelection() {
26655
+ if (this.#state.inputMode !== "session") return false;
26656
+ if (this.#state.sessionCancelBehavior === "prompt") {
26657
+ this.#resolveSession({ kind: "cancel" });
26658
+ return false;
26659
+ }
26660
+ return true;
26661
+ }
26224
26662
  moveSessionCursor(delta) {
26225
26663
  if (this.#state.inputMode !== "session" || this.#state.sessionOptions.length === 0) return;
26226
26664
  const count = this.#state.sessionOptions.length;
@@ -26241,6 +26679,10 @@ var init_store = __esm({
26241
26679
  if (this.#state.inputMode !== "session") return;
26242
26680
  this.#resolveSession({ kind: "new" });
26243
26681
  }
26682
+ submitSessionSelectionShortcut() {
26683
+ if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
26684
+ this.#resolvePrompt(`${TUI_SESSION_ACTION_PREFIX}${JSON.stringify({ type: "select" })}`);
26685
+ }
26244
26686
  requestPrompt(initialPrompt) {
26245
26687
  if (this.#exitRequested) return Promise.resolve("");
26246
26688
  const prompt = (initialPrompt ?? "").trim();
@@ -26338,6 +26780,143 @@ var init_store = __esm({
26338
26780
  this.#state.activity = `provider ${option.name} selected`;
26339
26781
  this.#notify();
26340
26782
  }
26783
+ openConfigManager(message) {
26784
+ const canOpenFromConfig = this.#state.inputMode === "config" || this.#state.inputMode === "configModel" || this.#state.inputMode === "configModelInput" || this.#state.inputMode === "configProviderInput" || this.#state.inputMode === "configAddProvider";
26785
+ if (!canOpenFromConfig && (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim())) return;
26786
+ this.#state.inputMode = "config";
26787
+ const current = this.#state.selection?.providerName ?? this.#state.status.provider;
26788
+ this.#state.providerCursor = Math.max(0, this.#state.providerOptions.findIndex((option) => option.name === current));
26789
+ this.#state.configMessage = message ?? this.#state.configMessage;
26790
+ this.#state.settingsError = void 0;
26791
+ this.#state.activity = "configure providers and models";
26792
+ this.#notify();
26793
+ }
26794
+ openConfigProviderInput(field) {
26795
+ if (this.#state.inputMode !== "config") return;
26796
+ const provider = this.#selectedConfigProvider();
26797
+ if (!provider) return;
26798
+ this.#state.inputMode = "configProviderInput";
26799
+ this.#state.configProviderInputField = field;
26800
+ this.#state.configProviderInput = configProviderFieldValue(provider, field);
26801
+ this.#state.settingsError = void 0;
26802
+ this.#state.activity = `edit ${field} for ${provider.name}`;
26803
+ this.#notify();
26804
+ }
26805
+ appendConfigProviderInput(input2) {
26806
+ if (this.#state.inputMode !== "configProviderInput") return;
26807
+ this.#state.configProviderInput += input2;
26808
+ this.#state.settingsError = void 0;
26809
+ this.#notify();
26810
+ }
26811
+ backspaceConfigProviderInput() {
26812
+ if (this.#state.inputMode !== "configProviderInput") return;
26813
+ this.#state.configProviderInput = Array.from(this.#state.configProviderInput).slice(0, -1).join("");
26814
+ this.#state.settingsError = void 0;
26815
+ this.#notify();
26816
+ }
26817
+ submitConfigProviderInput() {
26818
+ if (this.#state.inputMode !== "configProviderInput") return;
26819
+ const provider = this.#selectedConfigProvider();
26820
+ const field = this.#state.configProviderInputField;
26821
+ if (!provider || !field) return;
26822
+ this.#submitConfigAction({ type: "updateProvider", provider: provider.name, field, value: this.#state.configProviderInput });
26823
+ }
26824
+ moveConfigProviderCursor(delta) {
26825
+ if (this.#state.inputMode !== "config" || this.#state.providerOptions.length === 0) return;
26826
+ const count = this.#state.providerOptions.length;
26827
+ this.#state.providerCursor = (this.#state.providerCursor + delta + count) % count;
26828
+ this.#notify();
26829
+ }
26830
+ submitConfigDefaultProvider() {
26831
+ if (this.#state.inputMode !== "config") return;
26832
+ const option = this.#selectedConfigProvider();
26833
+ if (!option) return;
26834
+ this.#submitConfigAction({ type: "setDefaultProvider", provider: option.name });
26835
+ }
26836
+ openConfigModelSelector() {
26837
+ if (this.#state.inputMode !== "config" && this.#state.inputMode !== "configModelInput") return;
26838
+ const option = this.#selectedConfigProvider();
26839
+ if (!option) return;
26840
+ this.#state.inputMode = "configModel";
26841
+ this.#state.configModelCursor = 0;
26842
+ this.#state.settingsError = void 0;
26843
+ this.#state.activity = `choose default model for ${option.name}`;
26844
+ this.#notify();
26845
+ }
26846
+ moveConfigModelCursor(delta) {
26847
+ if (this.#state.inputMode !== "configModel") return;
26848
+ const profiles = this.#selectedConfigProvider()?.modelProfiles ?? [];
26849
+ if (profiles.length === 0) return;
26850
+ this.#state.configModelCursor = (this.#state.configModelCursor + delta + profiles.length) % profiles.length;
26851
+ this.#notify();
26852
+ }
26853
+ submitConfigDefaultModel() {
26854
+ if (this.#state.inputMode !== "configModel") return;
26855
+ const provider = this.#selectedConfigProvider();
26856
+ const profile = provider?.modelProfiles?.[this.#state.configModelCursor];
26857
+ if (!provider || !profile?.model) return;
26858
+ this.#submitConfigAction({
26859
+ type: "setDefaultModel",
26860
+ provider: provider.name,
26861
+ profileName: profile.name,
26862
+ name: profile.name,
26863
+ displayName: profile.displayName,
26864
+ model: profile.model
26865
+ });
26866
+ }
26867
+ openConfigModelInput() {
26868
+ if (this.#state.inputMode !== "configModel" && this.#state.inputMode !== "config") return;
26869
+ if (!this.#selectedConfigProvider()) return;
26870
+ this.#state.inputMode = "configModelInput";
26871
+ this.#state.configModelInput = "";
26872
+ this.#state.settingsError = void 0;
26873
+ this.#state.activity = "add a model profile";
26874
+ this.#notify();
26875
+ }
26876
+ appendConfigModelInput(input2) {
26877
+ if (this.#state.inputMode !== "configModelInput") return;
26878
+ this.#state.configModelInput += input2;
26879
+ this.#state.settingsError = void 0;
26880
+ this.#notify();
26881
+ }
26882
+ backspaceConfigModelInput() {
26883
+ if (this.#state.inputMode !== "configModelInput") return;
26884
+ this.#state.configModelInput = Array.from(this.#state.configModelInput).slice(0, -1).join("");
26885
+ this.#state.settingsError = void 0;
26886
+ this.#notify();
26887
+ }
26888
+ submitConfigModelInput() {
26889
+ if (this.#state.inputMode !== "configModelInput") return;
26890
+ const provider = this.#selectedConfigProvider();
26891
+ const model = this.#state.configModelInput.trim();
26892
+ if (!provider || !model) {
26893
+ this.#state.settingsError = "Model ID is required.";
26894
+ this.#notify();
26895
+ return;
26896
+ }
26897
+ this.#submitConfigAction({ type: "setDefaultModel", provider: provider.name, model, displayName: model });
26898
+ }
26899
+ openConfigAddProviderSelector() {
26900
+ if (this.#state.inputMode !== "config") return;
26901
+ this.#state.inputMode = "configAddProvider";
26902
+ this.#state.configAddProviderCursor = 0;
26903
+ this.#state.settingsError = void 0;
26904
+ this.#state.activity = "add provider preset";
26905
+ this.#notify();
26906
+ }
26907
+ moveConfigAddProviderCursor(delta) {
26908
+ if (this.#state.inputMode !== "configAddProvider") return;
26909
+ const count = this.#state.configProviderPresets.length;
26910
+ if (count === 0) return;
26911
+ this.#state.configAddProviderCursor = (this.#state.configAddProviderCursor + delta + count) % count;
26912
+ this.#notify();
26913
+ }
26914
+ submitConfigAddProvider() {
26915
+ if (this.#state.inputMode !== "configAddProvider") return;
26916
+ const preset = this.#state.configProviderPresets[this.#state.configAddProviderCursor];
26917
+ if (!preset) return;
26918
+ this.#submitConfigAction({ type: "addProvider", preset: preset.preset, name: preset.name, apiKeyEnv: preset.apiKeyEnv });
26919
+ }
26341
26920
  openAgentSelector() {
26342
26921
  if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
26343
26922
  if (this.#state.agentOptions.length === 0) {
@@ -26436,9 +27015,12 @@ var init_store = __esm({
26436
27015
  this.#notify();
26437
27016
  }
26438
27017
  closeSettings() {
26439
- if (this.#state.inputMode !== "provider" && this.#state.inputMode !== "model" && this.#state.inputMode !== "agent" && this.#state.inputMode !== "permissionPreset") return;
27018
+ if (!["provider", "model", "agent", "permissionPreset", "config", "configModel", "configModelInput", "configProviderInput", "configAddProvider"].includes(this.#state.inputMode)) return;
26440
27019
  this.#state.inputMode = "prompt";
26441
27020
  this.#state.modelInput = "";
27021
+ this.#state.configModelInput = "";
27022
+ this.#state.configProviderInput = "";
27023
+ this.#state.configProviderInputField = void 0;
26442
27024
  this.#state.settingsError = void 0;
26443
27025
  this.#state.activity = "waiting for first message";
26444
27026
  this.#notify();
@@ -26567,9 +27149,12 @@ var init_store = __esm({
26567
27149
  this.#state.streamingFinalized = false;
26568
27150
  this.#activeClaudeCodePlan = this.#isClaudeCodePlanRun(event.provider) ? { text: "", providerName: event.provider, model: event.model, createdAt: event.ts } : void 0;
26569
27151
  }
26570
- if (event.type === "model.text.delta" && !isNestedInvocationEvent(event)) {
27152
+ if (event.type === "model.text.delta") {
27153
+ if (isNestedInvocationEvent(event)) return;
26571
27154
  this.#state.streamingText += event.text;
26572
27155
  this.#state.streamingFinalized = false;
27156
+ this.#notify({ deferMs: 50 });
27157
+ return;
26573
27158
  }
26574
27159
  if (event.type === "model.text" && !isNestedInvocationEvent(event)) {
26575
27160
  this.#state.streamingText = event.text;
@@ -27283,14 +27868,28 @@ var init_store = __esm({
27283
27868
  resolve(prompt);
27284
27869
  this.#notify();
27285
27870
  }
27871
+ #selectedConfigProvider() {
27872
+ return this.#state.providerOptions[this.#state.providerCursor];
27873
+ }
27874
+ #submitConfigAction(action) {
27875
+ this.#state.settingsError = void 0;
27876
+ this.#state.configModelInput = "";
27877
+ this.#state.configProviderInput = "";
27878
+ this.#state.configProviderInputField = void 0;
27879
+ this.#resolvePrompt(`${TUI_CONFIG_ACTION_PREFIX}${JSON.stringify(action)}`);
27880
+ }
27286
27881
  #resolveSession(selection) {
27287
27882
  const resolve = this.#sessionResolve;
27288
27883
  if (!resolve) return;
27289
27884
  this.#sessionResolve = void 0;
27885
+ this.#state.sessionCancelBehavior = "exit";
27290
27886
  if (selection.kind === "exit") {
27291
27887
  this.#state.inputMode = "done";
27292
27888
  this.#state.done = true;
27293
27889
  this.#state.activity = "exiting";
27890
+ } else if (selection.kind === "cancel") {
27891
+ this.#state.inputMode = "prompt";
27892
+ this.#state.activity = "waiting for next message";
27294
27893
  } else {
27295
27894
  this.#state.inputMode = "starting";
27296
27895
  this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
@@ -27402,7 +28001,23 @@ var init_store = __esm({
27402
28001
  }
27403
28002
  };
27404
28003
  }
27405
- #notify() {
28004
+ #notify(options = {}) {
28005
+ const deferMs = options.deferMs ?? 0;
28006
+ if (deferMs > 0) {
28007
+ if (this.#notifyTimer) return;
28008
+ this.#notifyTimer = setTimeout(() => {
28009
+ this.#notifyTimer = void 0;
28010
+ this.#emitNotify();
28011
+ }, deferMs);
28012
+ return;
28013
+ }
28014
+ if (this.#notifyTimer) {
28015
+ clearTimeout(this.#notifyTimer);
28016
+ this.#notifyTimer = void 0;
28017
+ }
28018
+ this.#emitNotify();
28019
+ }
28020
+ #emitNotify() {
27406
28021
  for (const listener of this.#listeners) listener();
27407
28022
  }
27408
28023
  };
@@ -27416,7 +28031,6 @@ __export(app_exports, {
27416
28031
  commandPaletteMatches: () => commandPaletteMatches,
27417
28032
  inputFrameRows: () => inputFrameRows,
27418
28033
  isCtrlCKeypress: () => isCtrlCKeypress,
27419
- isCtrlPKeypress: () => isCtrlPKeypress,
27420
28034
  isPromptNewlineKeypress: () => isPromptNewlineKeypress,
27421
28035
  sessionHeaderLine: () => sessionHeaderLine,
27422
28036
  sessionOptionLine: () => sessionOptionLine,
@@ -27428,13 +28042,16 @@ __export(app_exports, {
27428
28042
  });
27429
28043
  import React, { useEffect, useState } from "react";
27430
28044
  import { Box, Text, useApp, useInput } from "ink";
28045
+ function usePromptDraftSnapshot(promptDraft) {
28046
+ const [snapshot, setSnapshot] = useState(() => promptDraft.snapshot());
28047
+ useEffect(() => promptDraft.subscribe(() => setSnapshot(promptDraft.snapshot())), [promptDraft]);
28048
+ return snapshot;
28049
+ }
27431
28050
  function TuiApp({ store, version = "dev" }) {
27432
28051
  const [state, setState] = useState(() => store.snapshot());
27433
- const [promptInput, setPromptInput] = useState(() => store.snapshot().promptInput);
28052
+ const [promptDraft] = useState(() => new PromptDraftController(store.snapshot().promptInput));
27434
28053
  const [now2, setNow] = useState(() => Date.now());
27435
28054
  const [shortcutMode, setShortcutMode] = useState(false);
27436
- const [slashCommandCursor, setSlashCommandCursor] = useState(0);
27437
- const [slashCommandClosedPrefix, setSlashCommandClosedPrefix] = useState();
27438
28055
  const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
27439
28056
  const [commandPaletteQuery, setCommandPaletteQuery] = useState("");
27440
28057
  const [commandPaletteCursor, setCommandPaletteCursor] = useState(0);
@@ -27443,47 +28060,43 @@ function TuiApp({ store, version = "dev" }) {
27443
28060
  const update = () => {
27444
28061
  const next = store.snapshot();
27445
28062
  setState(next);
27446
- setPromptInput(next.promptInput);
28063
+ promptDraft.setValue(next.promptInput);
27447
28064
  };
27448
28065
  const unsubscribe = store.subscribe(update);
27449
28066
  update();
27450
28067
  return unsubscribe;
27451
- }, [store]);
28068
+ }, [store, promptDraft]);
27452
28069
  useEffect(() => enableEnhancedKeyboardInput(), []);
27453
28070
  useEffect(() => {
27454
28071
  if (!state.workPlan && state.inputMode !== "running" && state.inputMode !== "starting") return;
27455
- const timer = setInterval(() => setNow(Date.now()), 450);
28072
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
27456
28073
  return () => clearInterval(timer);
27457
28074
  }, [state.workPlan?.planId, state.inputMode]);
27458
28075
  useEffect(() => {
27459
28076
  setShortcutMode(false);
27460
28077
  }, [state.inputMode, state.permission !== void 0]);
27461
- useEffect(() => {
27462
- setSlashCommandCursor(0);
27463
- if (slashCommandClosedPrefix && !isSlashCommandClosedForPrompt(promptInput, slashCommandClosedPrefix)) setSlashCommandClosedPrefix(void 0);
27464
- }, [promptInput, state.inputMode, slashCommandClosedPrefix]);
27465
28078
  useEffect(() => {
27466
28079
  setCommandPaletteCursor(0);
27467
28080
  }, [commandPaletteQuery, state.inputMode]);
27468
28081
  useEffect(() => {
27469
28082
  if (commandPaletteOpen && !canUseCommandPalette(state)) setCommandPaletteOpen(false);
27470
28083
  }, [commandPaletteOpen, state.inputMode, state.permission]);
27471
- const commandPaletteItems = commandPaletteOpen ? commandPaletteMatches(commandPaletteQuery, state.inputMode, commandPaletteAvailability(state, promptInput)) : [];
28084
+ const commandPaletteItems = commandPaletteOpen ? commandPaletteMatches(commandPaletteQuery, state.inputMode, commandPaletteAvailability(state, promptDraft.value())) : [];
27472
28085
  const commandPaletteItem = commandPaletteItems[Math.min(commandPaletteCursor, Math.max(0, commandPaletteItems.length - 1))];
28086
+ const openCommandPalette = () => {
28087
+ if (!canUseCommandPalette(state)) return;
28088
+ setCommandPaletteOpen((open4) => !open4);
28089
+ setCommandPaletteQuery("");
28090
+ setCommandPaletteCursor(0);
28091
+ setShortcutMode(false);
28092
+ promptDraft.closeSlashCommandsForCurrentPrompt();
28093
+ };
27473
28094
  useInput((input2, key) => {
27474
28095
  if (isCtrlCKeypress(input2, key)) {
27475
28096
  store.requestExit();
27476
28097
  app.exit();
27477
28098
  return;
27478
28099
  }
27479
- if (isCtrlPKeypress(input2, key) && canUseCommandPalette(state)) {
27480
- setCommandPaletteOpen((open4) => !open4);
27481
- setCommandPaletteQuery("");
27482
- setCommandPaletteCursor(0);
27483
- setShortcutMode(false);
27484
- setSlashCommandClosedPrefix(promptInput);
27485
- return;
27486
- }
27487
28100
  const textInput = textInputForKeypress(input2, key);
27488
28101
  if (commandPaletteOpen) {
27489
28102
  if (key.escape) {
@@ -27499,7 +28112,7 @@ function TuiApp({ store, version = "dev" }) {
27499
28112
  return;
27500
28113
  }
27501
28114
  if (key.return) {
27502
- if (commandPaletteItem) executeCommandPaletteItem({ item: commandPaletteItem, store, app, setPromptInput, setState, close: () => closeCommandPalette(setCommandPaletteOpen, setCommandPaletteQuery, setCommandPaletteCursor) });
28115
+ if (commandPaletteItem) executeCommandPaletteItem({ item: commandPaletteItem, store, app, promptDraft, setState, close: () => closeCommandPalette(setCommandPaletteOpen, setCommandPaletteQuery, setCommandPaletteCursor) });
27503
28116
  return;
27504
28117
  }
27505
28118
  if (key.backspace || key.delete) {
@@ -27525,6 +28138,14 @@ function TuiApp({ store, version = "dev" }) {
27525
28138
  store.stopActiveTask();
27526
28139
  return;
27527
28140
  }
28141
+ if (shortcut === "k" && (state.inputMode === "prompt" || state.inputMode === "running")) {
28142
+ openCommandPalette();
28143
+ return;
28144
+ }
28145
+ if (shortcut === "s" && state.inputMode === "prompt") {
28146
+ store.submitSessionSelectionShortcut();
28147
+ return;
28148
+ }
27528
28149
  if (shortcut === "p" && state.inputMode === "prompt") {
27529
28150
  store.openProviderSelector();
27530
28151
  return;
@@ -27541,9 +28162,13 @@ function TuiApp({ store, version = "dev" }) {
27541
28162
  store.openPermissionPresetSelector();
27542
28163
  return;
27543
28164
  }
28165
+ if (shortcut === "c" && state.inputMode === "prompt") {
28166
+ store.openConfigManager();
28167
+ return;
28168
+ }
27544
28169
  if (shortcut === "u" && state.inputMode === "prompt") {
27545
28170
  store.clearPromptInput({ notify: false });
27546
- setPromptInput("");
28171
+ promptDraft.setValue("");
27547
28172
  clearPromptErrorPreview(setState);
27548
28173
  return;
27549
28174
  }
@@ -27578,7 +28203,14 @@ function TuiApp({ store, version = "dev" }) {
27578
28203
  }
27579
28204
  if (state.inputMode === "session") {
27580
28205
  const normalized = input2.toLowerCase();
27581
- if (key.escape || normalized === "q") {
28206
+ if (key.escape) {
28207
+ if (store.cancelSessionSelection()) {
28208
+ store.requestExit();
28209
+ app.exit();
28210
+ }
28211
+ return;
28212
+ }
28213
+ if (normalized === "q") {
27582
28214
  store.requestExit();
27583
28215
  app.exit();
27584
28216
  return;
@@ -27658,6 +28290,126 @@ function TuiApp({ store, version = "dev" }) {
27658
28290
  }
27659
28291
  return;
27660
28292
  }
28293
+ if (state.inputMode === "config") {
28294
+ if (key.escape) {
28295
+ store.closeSettings();
28296
+ return;
28297
+ }
28298
+ if (key.upArrow) {
28299
+ store.moveConfigProviderCursor(-1);
28300
+ return;
28301
+ }
28302
+ if (key.downArrow) {
28303
+ store.moveConfigProviderCursor(1);
28304
+ return;
28305
+ }
28306
+ const normalized = input2.toLowerCase();
28307
+ if (key.return || normalized === "d") {
28308
+ store.submitConfigDefaultProvider();
28309
+ return;
28310
+ }
28311
+ if (normalized === "m") {
28312
+ store.openConfigModelSelector();
28313
+ return;
28314
+ }
28315
+ if (normalized === "n") {
28316
+ store.openConfigModelInput();
28317
+ return;
28318
+ }
28319
+ if (normalized === "a") {
28320
+ store.openConfigAddProviderSelector();
28321
+ return;
28322
+ }
28323
+ if (normalized === "b") {
28324
+ store.openConfigProviderInput("baseURL");
28325
+ return;
28326
+ }
28327
+ if (normalized === "e") {
28328
+ store.openConfigProviderInput("apiKeyEnv");
28329
+ return;
28330
+ }
28331
+ if (normalized === "h") {
28332
+ store.openConfigProviderInput("authHeader");
28333
+ return;
28334
+ }
28335
+ return;
28336
+ }
28337
+ if (state.inputMode === "configModel") {
28338
+ if (key.escape) {
28339
+ store.openConfigManager();
28340
+ return;
28341
+ }
28342
+ if (key.upArrow) {
28343
+ store.moveConfigModelCursor(-1);
28344
+ return;
28345
+ }
28346
+ if (key.downArrow) {
28347
+ store.moveConfigModelCursor(1);
28348
+ return;
28349
+ }
28350
+ if (input2.toLowerCase() === "n") {
28351
+ store.openConfigModelInput();
28352
+ return;
28353
+ }
28354
+ if (key.return) {
28355
+ store.submitConfigDefaultModel();
28356
+ return;
28357
+ }
28358
+ return;
28359
+ }
28360
+ if (state.inputMode === "configModelInput") {
28361
+ if (key.escape) {
28362
+ store.openConfigModelSelector();
28363
+ return;
28364
+ }
28365
+ if (key.return) {
28366
+ if (textInput) store.appendConfigModelInput(textInput);
28367
+ store.submitConfigModelInput();
28368
+ return;
28369
+ }
28370
+ if (key.backspace || key.delete) {
28371
+ store.backspaceConfigModelInput();
28372
+ return;
28373
+ }
28374
+ if (textInput) store.appendConfigModelInput(textInput);
28375
+ return;
28376
+ }
28377
+ if (state.inputMode === "configProviderInput") {
28378
+ if (key.escape) {
28379
+ store.openConfigManager();
28380
+ return;
28381
+ }
28382
+ if (key.return) {
28383
+ if (textInput) store.appendConfigProviderInput(textInput);
28384
+ store.submitConfigProviderInput();
28385
+ return;
28386
+ }
28387
+ if (key.backspace || key.delete) {
28388
+ store.backspaceConfigProviderInput();
28389
+ return;
28390
+ }
28391
+ if (textInput) store.appendConfigProviderInput(textInput);
28392
+ return;
28393
+ }
28394
+ if (state.inputMode === "configAddProvider") {
28395
+ if (key.escape) {
28396
+ store.openConfigManager();
28397
+ return;
28398
+ }
28399
+ if (key.upArrow) {
28400
+ store.moveConfigAddProviderCursor(-1);
28401
+ return;
28402
+ }
28403
+ if (key.downArrow) {
28404
+ store.moveConfigAddProviderCursor(1);
28405
+ return;
28406
+ }
28407
+ if (key.return) {
28408
+ store.submitConfigAddProvider();
28409
+ return;
28410
+ }
28411
+ return;
28412
+ }
27661
28413
  if (state.inputMode === "model") {
27662
28414
  if (key.escape) {
27663
28415
  store.closeSettings();
@@ -27676,22 +28428,24 @@ function TuiApp({ store, version = "dev" }) {
27676
28428
  return;
27677
28429
  }
27678
28430
  if (state.inputMode === "prompt" || state.inputMode === "running") {
27679
- const slashCommands2 = visibleSlashCommandMatches(promptInput, state.inputMode, slashCommandClosedPrefix);
27680
- const slashCommand = slashCommands2[slashCommandCursor] ?? slashCommands2[0];
27681
- if (slashCommands2.length > 0 && key.escape) {
27682
- setSlashCommandClosedPrefix(promptInput);
28431
+ const promptSnapshot = promptDraft.snapshot();
28432
+ const promptInput = promptSnapshot.value;
28433
+ const slashCommands = visibleSlashCommandMatches(promptInput, state.inputMode, promptSnapshot.slashCommandClosedPrefix);
28434
+ const slashCommand = slashCommands[promptSnapshot.slashCommandCursor] ?? slashCommands[0];
28435
+ if (slashCommands.length > 0 && key.escape) {
28436
+ promptDraft.closeSlashCommandsForCurrentPrompt();
27683
28437
  return;
27684
28438
  }
27685
- if (slashCommands2.length > 0 && key.upArrow) {
27686
- setSlashCommandCursor((cursor) => (cursor - 1 + slashCommands2.length) % slashCommands2.length);
28439
+ if (slashCommands.length > 0 && key.upArrow) {
28440
+ promptDraft.moveSlashCommandCursor(-1, slashCommands.length);
27687
28441
  return;
27688
28442
  }
27689
- if (slashCommands2.length > 0 && key.downArrow) {
27690
- setSlashCommandCursor((cursor) => (cursor + 1) % slashCommands2.length);
28443
+ if (slashCommands.length > 0 && key.downArrow) {
28444
+ promptDraft.moveSlashCommandCursor(1, slashCommands.length);
27691
28445
  return;
27692
28446
  }
27693
- if (slashCommands2.length > 0 && key.tab && slashCommand) {
27694
- applySlashCommandDraft(store, setPromptInput, slashCommand, setSlashCommandClosedPrefix);
28447
+ if (slashCommands.length > 0 && key.tab && slashCommand) {
28448
+ applySlashCommandDraft(store, promptDraft, slashCommand);
27695
28449
  clearPromptErrorPreview(setState);
27696
28450
  return;
27697
28451
  }
@@ -27708,37 +28462,35 @@ function TuiApp({ store, version = "dev" }) {
27708
28462
  return;
27709
28463
  }
27710
28464
  if (isPromptNewlineKeypress(input2, key)) {
27711
- appendPromptDraft(store, setPromptInput, `${textInput}
28465
+ appendPromptDraft(store, promptDraft, `${textInput}
27712
28466
  `);
27713
28467
  clearPromptErrorPreview(setState);
27714
28468
  return;
27715
28469
  }
27716
28470
  if (isPromptSubmitKeypress(input2, key)) {
27717
28471
  if (!textInput && slashCommand && shouldSelectSlashCommand(promptInput, slashCommand)) {
27718
- applySlashCommandDraft(store, setPromptInput, slashCommand, setSlashCommandClosedPrefix);
28472
+ applySlashCommandDraft(store, promptDraft, slashCommand);
27719
28473
  clearPromptErrorPreview(setState);
27720
28474
  return;
27721
28475
  }
27722
- if (textInput) appendPromptDraft(store, setPromptInput, textInput);
28476
+ if (textInput) appendPromptDraft(store, promptDraft, textInput);
27723
28477
  store.submitPromptInput();
27724
28478
  return;
27725
28479
  }
27726
28480
  if (key.backspace || key.delete) {
27727
28481
  store.backspacePromptInput({ notify: false });
27728
- setPromptInput((current) => Array.from(current).slice(0, -1).join(""));
28482
+ promptDraft.backspace();
27729
28483
  clearPromptErrorPreview(setState);
27730
28484
  return;
27731
28485
  }
27732
28486
  if (textInput) {
27733
- appendPromptDraft(store, setPromptInput, textInput);
28487
+ appendPromptDraft(store, promptDraft, textInput);
27734
28488
  clearPromptErrorPreview(setState);
27735
28489
  }
27736
28490
  return;
27737
28491
  }
27738
28492
  });
27739
28493
  const shellProps = { flexDirection: "column", paddingX: 1, minHeight: terminalHeight() };
27740
- const slashCommands = commandPaletteOpen ? [] : visibleSlashCommandMatches(promptInput, state.inputMode, slashCommandClosedPrefix);
27741
- const slashCommandPalette = slashCommands.length > 0 ? { items: slashCommands, cursor: Math.min(slashCommandCursor, slashCommands.length - 1) } : void 0;
27742
28494
  const commandPalette = commandPaletteOpen ? { items: commandPaletteItems, query: commandPaletteQuery, cursor: Math.min(commandPaletteCursor, Math.max(0, commandPaletteItems.length - 1)) } : void 0;
27743
28495
  if (shouldShowSessionStart(state)) {
27744
28496
  return React.createElement(
@@ -27751,7 +28503,7 @@ function TuiApp({ store, version = "dev" }) {
27751
28503
  return React.createElement(
27752
28504
  Box,
27753
28505
  shellProps,
27754
- React.createElement(EmptyState, { state, version, shortcutMode, promptInput, slashCommandPalette, commandPalette })
28506
+ React.createElement(EmptyState, { state, version, shortcutMode, promptDraft, commandPalette })
27755
28507
  );
27756
28508
  }
27757
28509
  return React.createElement(
@@ -27764,24 +28516,24 @@ function TuiApp({ store, version = "dev" }) {
27764
28516
  React.createElement(shouldShowLoading(state) ? LoadingView : TranscriptView, { state }),
27765
28517
  React.createElement(Box, { flexGrow: 1 }),
27766
28518
  React.createElement(BottomStatusStack, { state, now: now2 }),
27767
- React.createElement(InteractionPanel, { state, shortcutMode, promptInput, slashCommandPalette, commandPalette })
28519
+ React.createElement(InteractionPanel, { state, shortcutMode, promptDraft, commandPalette })
27768
28520
  )
27769
28521
  );
27770
28522
  }
27771
- function appendPromptDraft(store, setPromptInput, value) {
28523
+ function appendPromptDraft(store, promptDraft, value) {
27772
28524
  if (!value) return;
27773
28525
  store.appendPromptInput(value, { notify: false });
27774
- setPromptInput((current) => `${current}${value}`);
28526
+ promptDraft.append(value);
27775
28527
  }
27776
28528
  function clearPromptErrorPreview(setState) {
27777
28529
  setState((current) => current.promptError ? { ...current, promptError: void 0 } : current);
27778
28530
  }
27779
- function applySlashCommandDraft(store, setPromptInput, item, setSlashCommandClosedPrefix) {
28531
+ function applySlashCommandDraft(store, promptDraft, item) {
27780
28532
  const next = item.argument ? `${item.insert} ` : item.insert;
27781
28533
  store.clearPromptInput({ notify: false });
27782
28534
  store.appendPromptInput(next, { notify: false });
27783
- setPromptInput(next);
27784
- setSlashCommandClosedPrefix(next);
28535
+ promptDraft.setValue(next);
28536
+ promptDraft.closeSlashCommandsForCurrentPrompt();
27785
28537
  }
27786
28538
  function shouldSelectSlashCommand(promptInput, item) {
27787
28539
  const value = promptInput.trim();
@@ -27793,6 +28545,14 @@ function visibleSlashCommandMatches(promptInput, mode, closedPrefix) {
27793
28545
  if (closedPrefix && isSlashCommandClosedForPrompt(promptInput, closedPrefix)) return [];
27794
28546
  return slashCommandMatches(promptInput, mode).slice(0, 7);
27795
28547
  }
28548
+ function slashCommandPaletteForDraft(snapshot, mode) {
28549
+ const slashCommands = visibleSlashCommandMatches(snapshot.value, mode, snapshot.slashCommandClosedPrefix);
28550
+ if (slashCommands.length === 0) return void 0;
28551
+ return {
28552
+ items: slashCommands,
28553
+ cursor: Math.min(snapshot.slashCommandCursor, slashCommands.length - 1)
28554
+ };
28555
+ }
27796
28556
  function isSlashCommandClosedForPrompt(promptInput, closedPrefix) {
27797
28557
  return promptInput === closedPrefix || closedPrefix.endsWith(" ") && promptInput.startsWith(closedPrefix);
27798
28558
  }
@@ -27861,6 +28621,8 @@ function buildCommandPaletteItems(mode, availability) {
27861
28621
  }
27862
28622
  if (promptReadyForSettings) {
27863
28623
  items.push({ id: "permission-preset", title: "Permission preset", description: "Choose the next-turn permission behavior", group: "Suggested", shortcut: "esc o", action: "open-permissions", keywords: ["permission", "policy", "approval"] });
28624
+ items.push({ id: "configure-providers", title: "Configure providers", description: "Persist default providers and model profiles", group: "Suggested", shortcut: "esc c", action: "open-config", keywords: ["config", "settings", "provider", "model"] });
28625
+ items.push({ id: "select-session", title: "Select session", description: "Open the saved session picker", group: "Session", shortcut: "esc s", action: "open-session", keywords: ["session", "sessions", "conversation", "switch"] });
27864
28626
  }
27865
28627
  for (const command of SLASH_COMMANDS) {
27866
28628
  if (!slashCommandActiveInMode(command, mode)) continue;
@@ -27954,13 +28716,13 @@ function executeCommandPaletteItem({
27954
28716
  item,
27955
28717
  store,
27956
28718
  app,
27957
- setPromptInput,
28719
+ promptDraft,
27958
28720
  setState,
27959
28721
  close
27960
28722
  }) {
27961
28723
  close();
27962
28724
  if (item.slashCommand) {
27963
- applyCommandPaletteSlashCommand(store, setPromptInput, item.slashCommand);
28725
+ applyCommandPaletteSlashCommand(store, promptDraft, item.slashCommand);
27964
28726
  clearPromptErrorPreview(setState);
27965
28727
  return;
27966
28728
  }
@@ -27977,9 +28739,15 @@ function executeCommandPaletteItem({
27977
28739
  case "open-permissions":
27978
28740
  store.openPermissionPresetSelector();
27979
28741
  return;
28742
+ case "open-config":
28743
+ store.openConfigManager();
28744
+ return;
28745
+ case "open-session":
28746
+ store.submitSessionSelectionShortcut();
28747
+ return;
27980
28748
  case "clear-prompt":
27981
28749
  store.clearPromptInput({ notify: false });
27982
- setPromptInput("");
28750
+ promptDraft.setValue("");
27983
28751
  clearPromptErrorPreview(setState);
27984
28752
  return;
27985
28753
  case "retry":
@@ -28006,12 +28774,13 @@ function executeCommandPaletteItem({
28006
28774
  return;
28007
28775
  }
28008
28776
  }
28009
- function applyCommandPaletteSlashCommand(store, setPromptInput, item) {
28777
+ function applyCommandPaletteSlashCommand(store, promptDraft, item) {
28010
28778
  const shouldInsert = item.argument && item.command !== "/session new";
28011
28779
  const next = shouldInsert ? `${item.insert} ` : item.insert;
28012
28780
  store.clearPromptInput({ notify: false });
28013
28781
  store.appendPromptInput(next, { notify: false });
28014
- setPromptInput(next);
28782
+ promptDraft.setValue(next);
28783
+ promptDraft.closeSlashCommandsForCurrentPrompt();
28015
28784
  if (!shouldInsert) store.submitPromptInput();
28016
28785
  }
28017
28786
  function textInputForKeypress(input2, key) {
@@ -28038,15 +28807,6 @@ function isCtrlCKeypress(input2, key) {
28038
28807
  const modifier = Number(csi[2] ?? 1);
28039
28808
  return (codePoint === 3 || codePoint === 99) && hasControlModifier(modifier);
28040
28809
  }
28041
- function isCtrlPKeypress(input2, key) {
28042
- if (input2 === "") return true;
28043
- if (key.ctrl && input2.toLowerCase() === "p") return true;
28044
- const csi = CSI_U_INPUT.exec(input2);
28045
- if (!csi) return false;
28046
- const codePoint = Number(csi[1]);
28047
- const modifier = Number(csi[2] ?? 1);
28048
- return (codePoint === 16 || codePoint === 112) && hasControlModifier(modifier);
28049
- }
28050
28810
  function isPromptSubmitKeypress(input2, key) {
28051
28811
  return Boolean(key.return) || input2 === "\n";
28052
28812
  }
@@ -28129,12 +28889,13 @@ function EmptyState({
28129
28889
  state,
28130
28890
  version,
28131
28891
  shortcutMode,
28132
- promptInput,
28133
- slashCommandPalette,
28892
+ promptDraft,
28134
28893
  commandPalette
28135
28894
  }) {
28895
+ const promptSnapshot = usePromptDraftSnapshot(promptDraft);
28896
+ const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
28136
28897
  const placeholder = 'Ask anything... "\uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uAD6C\uC870\uB97C \uC54C\uB824\uC918"';
28137
- const hint = shortcutMode ? shortcutHelp(state) : "enter send \xB7 shift/option+enter newline \xB7 ctrl+p commands \xB7 /session";
28898
+ const hint = shortcutMode ? shortcutHelp(state) : "enter send \xB7 shift/option+enter newline \xB7 esc k commands \xB7 esc s sessions";
28138
28899
  const hasOverlay = Boolean(slashCommandPalette || commandPalette);
28139
28900
  const topGap = hasOverlay ? 0 : Math.max(1, Math.min(6, Math.floor(terminalHeight() * 0.12)));
28140
28901
  return React.createElement(
@@ -28148,7 +28909,7 @@ function EmptyState({
28148
28909
  React.createElement(InputFrame, {
28149
28910
  borderColor: shortcutMode ? "cyan" : "blue",
28150
28911
  cursorColor: "cyan",
28151
- value: promptInput,
28912
+ value: promptSnapshot.value,
28152
28913
  placeholder,
28153
28914
  marginTop: 0
28154
28915
  }),
@@ -28160,39 +28921,6 @@ function EmptyState({
28160
28921
  )
28161
28922
  );
28162
28923
  }
28163
- function SessionMetaBox({ state, version, marginTop = 1 }) {
28164
- const width = promptPanelWidth();
28165
- const compact = width < 68;
28166
- const agent = shorten(state.selectedAgent ?? state.status.agent ?? "general", compact ? 14 : 18);
28167
- const model = shorten(state.selection?.model ?? state.status.model ?? "model", compact ? Math.max(18, width - 14) : 30);
28168
- const workspace = shorten(state.status.cwd ?? process.cwd(), compact ? Math.max(18, width - 18) : 46);
28169
- const rows = compact ? [
28170
- `agent ${agent} \xB7 perm ${state.permissionPreset}`,
28171
- `model ${model}`,
28172
- `workspace ${workspace}`,
28173
- version ? `version v${version}` : void 0
28174
- ].filter(Boolean) : [
28175
- `agent ${agent} \xB7 model ${model} \xB7 perm ${state.permissionPreset}`,
28176
- `workspace ${workspace}${version ? ` \xB7 version v${version}` : ""}`
28177
- ];
28178
- return React.createElement(
28179
- Box,
28180
- { alignSelf: "center", width, flexDirection: "column", borderStyle: "single", borderColor: "#334155", paddingX: 1, marginTop },
28181
- ...rows.map((line, index) => React.createElement(SurfaceLine, { key: index, text: line ?? "", width: width - 4, backgroundColor: SURFACE.status, color: "white" }))
28182
- );
28183
- }
28184
- function DemianWordmark() {
28185
- if (terminalWidth() < 58) {
28186
- return React.createElement(Box, { alignSelf: "center" }, React.createElement(Text, { color: "gray", bold: true }, "DEMIAN"));
28187
- }
28188
- return React.createElement(
28189
- Box,
28190
- { alignSelf: "center", flexDirection: "column" },
28191
- ...DEMIAN_WORDMARK_LINES.map(
28192
- (line, index) => React.createElement(Text, { key: index, color: index < 2 ? "gray" : "white", bold: true }, line)
28193
- )
28194
- );
28195
- }
28196
28924
  function LoadingView({ state }) {
28197
28925
  const title = state.inputMode === "starting" ? "Demian runtime" : "Preparing session";
28198
28926
  const message = state.activity || (state.inputMode === "starting" ? "Loading configuration" : "Starting session");
@@ -28262,34 +28990,26 @@ function tuiMode(state) {
28262
28990
  if (state.inputMode === "session") return { label: "sessions", color: "magenta" };
28263
28991
  if (state.inputMode === "running") return { label: "running", color: "cyan" };
28264
28992
  if (state.inputMode === "provider" || state.inputMode === "agent" || state.inputMode === "model" || state.inputMode === "permissionPreset") return { label: "settings", color: "magenta" };
28993
+ if (state.inputMode === "config" || state.inputMode === "configModel" || state.inputMode === "configModelInput" || state.inputMode === "configProviderInput" || state.inputMode === "configAddProvider") return { label: "config", color: "magenta" };
28265
28994
  if (state.inputMode === "prompt") return { label: "ready", color: "green" };
28266
28995
  return { label: state.inputMode, color: "gray" };
28267
28996
  }
28268
- function TranscriptView({ state }) {
28269
- const blocks = state.blocks.slice(-12);
28270
- const streaming = !state.streamingFinalized && state.streamingText ? { kind: "assistant", title: "Assistant streaming", lines: streamingLines(state.streamingText) } : void 0;
28271
- const width = transcriptPanelWidth();
28272
- const contentWidth = Math.max(24, width - 2);
28273
- return React.createElement(
28274
- Box,
28275
- { alignSelf: "center", width, flexDirection: "column", marginTop: 1 },
28276
- ...blocks.map((block, index) => React.createElement(BlockView, { key: `${block.id}-${index}`, block, width: contentWidth })),
28277
- streaming ? React.createElement(BlockView, { key: "streaming", block: streaming, width: contentWidth }) : null
28278
- );
28279
- }
28280
- function BlockView({ block, width }) {
28281
- if (block.kind === "tool") return React.createElement(ToolBlockView, { block, width });
28282
- if (block.kind === "goal-work") return React.createElement(GoalWorkBlockView, { block, width });
28283
- if (block.kind === "cowork") return React.createElement(CoworkBlockView, { block, width });
28284
- if (block.kind === "user") return React.createElement(UserBlockView, { block, width });
28285
- const color = block.kind === "user" ? "green" : block.kind === "assistant" ? "white" : block.kind === "warning" ? "yellow" : "magenta";
28286
- const lines = wrapBlockLines(block.lines, width, 80);
28287
- return React.createElement(
28288
- Box,
28289
- { flexDirection: "column", marginBottom: 1 },
28290
- React.createElement(Text, { color, bold: true }, fitToWidth(block.title, width)),
28291
- ...lines.map((line, index) => React.createElement(Text, { key: index }, line || " "))
28292
- );
28997
+ function sameBlockViewProps(previous, next) {
28998
+ return previous.width === next.width && blockRenderSignature(previous.block) === blockRenderSignature(next.block);
28999
+ }
29000
+ function blockRenderSignature(block) {
29001
+ return JSON.stringify({
29002
+ id: "id" in block ? block.id : void 0,
29003
+ kind: block.kind,
29004
+ title: block.title,
29005
+ lines: block.lines,
29006
+ meta: block.meta,
29007
+ toolStatus: block.toolStatus,
29008
+ expanded: block.expanded,
29009
+ toolDetails: block.expanded ? block.toolDetails : void 0,
29010
+ goalWork: block.kind === "goal-work" ? block.goalWork : void 0,
29011
+ cowork: block.kind === "cowork" ? block.cowork : void 0
29012
+ });
28293
29013
  }
28294
29014
  function UserBlockView({ block, width }) {
28295
29015
  const lines = normalizedBlockLines(block.lines).slice(0, 80);
@@ -28753,16 +29473,21 @@ function formatSessionAge(updatedAt) {
28753
29473
  if (hours < 48) return `${hours}h ago`;
28754
29474
  return `${Math.floor(hours / 24)}d ago`;
28755
29475
  }
28756
- function InteractionPanel({ state, shortcutMode, promptInput, slashCommandPalette, commandPalette }) {
29476
+ function InteractionPanel({ state, shortcutMode, promptDraft, commandPalette }) {
28757
29477
  if (state.inputMode === "starting") return React.createElement(LoadingBar);
28758
29478
  if (state.inputMode === "session") return React.createElement(SessionSelector, { state });
28759
29479
  if (state.permission) return React.createElement(PermissionBar, { state });
28760
29480
  if (state.inputMode === "provider") return React.createElement(ProviderSelector, { state });
28761
29481
  if (state.inputMode === "agent") return React.createElement(AgentSelector, { state });
28762
29482
  if (state.inputMode === "permissionPreset") return React.createElement(PermissionPresetSelector, { state });
29483
+ if (state.inputMode === "config") return React.createElement(ConfigManager, { state });
29484
+ if (state.inputMode === "configModel") return React.createElement(ConfigModelSelector, { state });
29485
+ if (state.inputMode === "configModelInput") return React.createElement(ConfigModelInput, { state });
29486
+ if (state.inputMode === "configProviderInput") return React.createElement(ConfigProviderInput, { state });
29487
+ if (state.inputMode === "configAddProvider") return React.createElement(ConfigAddProviderSelector, { state });
28763
29488
  if (state.inputMode === "model") return React.createElement(ModelEditor, { state });
28764
- if (state.inputMode === "running") return React.createElement(CommandBar, { state, shortcutMode, promptInput, slashCommandPalette, commandPalette });
28765
- if (state.inputMode === "prompt") return React.createElement(PromptBar, { state, shortcutMode, promptInput, slashCommandPalette, commandPalette });
29489
+ if (state.inputMode === "running") return React.createElement(CommandBar, { state, shortcutMode, promptDraft, commandPalette });
29490
+ if (state.inputMode === "prompt") return React.createElement(PromptBar, { state, shortcutMode, promptDraft, commandPalette });
28766
29491
  return React.createElement(PermissionBar, { state });
28767
29492
  }
28768
29493
  function LoadingBar() {
@@ -28775,8 +29500,8 @@ function LoadingBar() {
28775
29500
  }
28776
29501
  function shortcutHelp(state) {
28777
29502
  const parts = ["esc cancel"];
28778
- if (state.inputMode === "prompt") parts.push("p provider", "a agent", "m model", "o permissions", "u clear");
28779
- if (state.inputMode === "running") parts.push("s stop");
29503
+ if (state.inputMode === "prompt") parts.push("k commands", "s sessions", "p provider", "a agent", "m model", "o permissions", "c config", "u clear");
29504
+ if (state.inputMode === "running") parts.push("k commands", "s stop");
28780
29505
  if (state.turnDiff) parts.push("d diff");
28781
29506
  if (state.blocks.some((block) => block.kind === "tool")) parts.push("t tools");
28782
29507
  if (state.workPlan || state.inputMode === "running") parts.push("w plan");
@@ -28785,8 +29510,10 @@ function shortcutHelp(state) {
28785
29510
  parts.push("q quit");
28786
29511
  return `shortcut: ${parts.join(" | ")}`;
28787
29512
  }
28788
- function PromptBar({ state, shortcutMode, promptInput, slashCommandPalette, commandPalette }) {
28789
- const hint = shortcutMode ? shortcutHelp(state) : "enter send | shift/option+enter newline | ctrl+p commands | esc shortcuts | up/down history | /session | /compact | /retry | /exit";
29513
+ function PromptBar({ state, shortcutMode, promptDraft, commandPalette }) {
29514
+ const promptSnapshot = usePromptDraftSnapshot(promptDraft);
29515
+ const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
29516
+ const hint = shortcutMode ? shortcutHelp(state) : "enter send | shift/option+enter newline | esc k commands | esc s sessions | esc shortcuts | up/down history | /compact | /retry | /exit";
28790
29517
  return React.createElement(
28791
29518
  Box,
28792
29519
  { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
@@ -28796,15 +29523,17 @@ function PromptBar({ state, shortcutMode, promptInput, slashCommandPalette, comm
28796
29523
  React.createElement(InputFrame, {
28797
29524
  borderColor: shortcutMode ? "cyan" : "blue",
28798
29525
  cursorColor: "cyan",
28799
- value: promptInput,
29526
+ value: promptSnapshot.value,
28800
29527
  placeholder: "Type first message and press Enter",
28801
29528
  marginTop: 1
28802
29529
  }),
28803
29530
  React.createElement(CommandGuide, { hint, color: shortcutMode ? "cyan" : "gray", error: state.promptError })
28804
29531
  );
28805
29532
  }
28806
- function CommandBar({ state, shortcutMode, promptInput, slashCommandPalette, commandPalette }) {
28807
- const hint = shortcutMode ? shortcutHelp(state) : "running | shift/option+enter newline | ctrl+p commands | /stop stop | /exit quit | esc shortcuts";
29533
+ function CommandBar({ state, shortcutMode, promptDraft, commandPalette }) {
29534
+ const promptSnapshot = usePromptDraftSnapshot(promptDraft);
29535
+ const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
29536
+ const hint = shortcutMode ? shortcutHelp(state) : "running | shift/option+enter newline | esc k commands | /stop stop | /exit quit | esc shortcuts";
28808
29537
  return React.createElement(
28809
29538
  Box,
28810
29539
  { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
@@ -28814,7 +29543,7 @@ function CommandBar({ state, shortcutMode, promptInput, slashCommandPalette, com
28814
29543
  React.createElement(InputFrame, {
28815
29544
  borderColor: shortcutMode ? "cyan" : "yellow",
28816
29545
  cursorColor: "yellow",
28817
- value: promptInput,
29546
+ value: promptSnapshot.value,
28818
29547
  placeholder: "Type /stop to stop or /exit to quit",
28819
29548
  marginTop: 1
28820
29549
  }),
@@ -28972,18 +29701,6 @@ function InputFrameLine({ row, width, cursorColor }) {
28972
29701
  function CursorCell({ cursorColor }) {
28973
29702
  return React.createElement(Text, { color: "black", backgroundColor: cursorColor, bold: true }, "\x1B[5m \x1B[25m");
28974
29703
  }
28975
- function CommandGuide({ hint, color, error }) {
28976
- return React.createElement(
28977
- Box,
28978
- { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
28979
- error ? React.createElement(Text, { color: "yellow" }, error) : null,
28980
- React.createElement(
28981
- Box,
28982
- { justifyContent: "flex-end" },
28983
- React.createElement(Text, { color }, hint)
28984
- )
28985
- );
28986
- }
28987
29704
  function SessionSelector({ state }) {
28988
29705
  const items = state.sessionOptions;
28989
29706
  const width = sessionPanelWidth();
@@ -29103,6 +29820,95 @@ function ModelEditor({ state }) {
29103
29820
  state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter apply | backspace edit | esc cancel")
29104
29821
  );
29105
29822
  }
29823
+ function ConfigManager({ state }) {
29824
+ const items = state.providerOptions;
29825
+ const selected = items[state.providerCursor];
29826
+ const defaultProvider = state.configDefaultProvider ?? state.status.provider;
29827
+ const contentWidth = Math.max(40, promptPanelWidth() - 8);
29828
+ return React.createElement(
29829
+ DialogFrame,
29830
+ { title: "Config", accent: "magenta" },
29831
+ React.createElement(Text, { color: state.configCreated ? "green" : "gray" }, state.configPath ? `config: ${shorten(state.configPath, contentWidth)}` : "config: ~/.demian/config.json"),
29832
+ state.configMessage ? React.createElement(Text, { color: "cyan" }, state.configMessage) : null,
29833
+ React.createElement(Text, { color: "gray" }, `${"Provider".padEnd(20)} ${"Default model".padEnd(28)} Status`),
29834
+ ...items.map((item, index) => {
29835
+ const isDefault = item.name === defaultProvider;
29836
+ const pointer = index === state.providerCursor ? ">" : " ";
29837
+ const marker = isDefault ? "*" : " ";
29838
+ return React.createElement(
29839
+ Text,
29840
+ { key: item.name, color: index === state.providerCursor ? "cyan" : item.available === false ? "gray" : "white", bold: index === state.providerCursor },
29841
+ `${pointer}${marker} ${shorten(item.label ?? item.name, 18).padEnd(18)} ${shorten((item.modelLabel ?? item.model) || "-", 28).padEnd(28)} ${providerStatusLabel(item)}`
29842
+ );
29843
+ }),
29844
+ selected ? React.createElement(Text, { color: "gray" }, `${selected.type ?? "provider"}${selected.baseURL ? ` | base ${shorten(selected.baseURL, 42)}` : " | base -"}${selected.apiKeyEnv ? ` | env ${selected.apiKeyEnv}` : " | env -"}${selected.auth?.header ? ` | auth ${selected.auth.header}` : ""}`) : null,
29845
+ React.createElement(Text, { color: "gray" }, "enter/d default | m models | b baseURL | e env | h auth | n new model | a add provider | esc close")
29846
+ );
29847
+ }
29848
+ function ConfigModelSelector({ state }) {
29849
+ const provider = state.providerOptions[state.providerCursor];
29850
+ const profiles = provider?.modelProfiles ?? [];
29851
+ return React.createElement(
29852
+ DialogFrame,
29853
+ { title: provider ? `Models: ${provider.name}` : "Models", accent: "magenta" },
29854
+ profiles.length ? React.createElement(Text, { color: "gray" }, `${"Model".padEnd(32)} Source`) : React.createElement(Text, { color: "yellow" }, "No configured or discoverable models. Press n to add one."),
29855
+ ...profiles.slice(0, 12).map(
29856
+ (item, index) => React.createElement(
29857
+ Text,
29858
+ { key: `${item.name}-${item.model}`, color: index === state.configModelCursor ? "cyan" : item.available === false ? "gray" : "white", bold: index === state.configModelCursor },
29859
+ `${index === state.configModelCursor ? ">" : " "} ${shorten(item.label ?? item.displayName ?? item.model, 30).padEnd(30)} ${item.source ?? "config"}`
29860
+ )
29861
+ ),
29862
+ React.createElement(Text, { color: "gray" }, "enter set default model | n add custom model | esc back")
29863
+ );
29864
+ }
29865
+ function ConfigModelInput({ state }) {
29866
+ const provider = state.providerOptions[state.providerCursor];
29867
+ const value = state.configModelInput || "Model ID";
29868
+ return React.createElement(
29869
+ DialogFrame,
29870
+ { title: provider ? `Add model: ${provider.name}` : "Add model", accent: "magenta" },
29871
+ React.createElement(
29872
+ Box,
29873
+ null,
29874
+ React.createElement(Text, { color: "cyan", bold: true }, "> "),
29875
+ React.createElement(Text, { color: state.configModelInput ? "white" : "gray" }, value)
29876
+ ),
29877
+ state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter add and make default | backspace edit | esc back")
29878
+ );
29879
+ }
29880
+ function ConfigProviderInput({ state }) {
29881
+ const provider = state.providerOptions[state.providerCursor];
29882
+ const field = state.configProviderInputField ?? "baseURL";
29883
+ const label = field === "baseURL" ? "Base URL" : field === "apiKeyEnv" ? "API key env" : "Auth header";
29884
+ const placeholder = field === "baseURL" ? "empty removes provider baseURL" : field === "apiKeyEnv" ? "empty removes provider apiKeyEnv" : "empty removes custom auth header";
29885
+ const value = state.configProviderInput || placeholder;
29886
+ return React.createElement(
29887
+ DialogFrame,
29888
+ { title: provider ? `Edit ${label}: ${provider.name}` : `Edit ${label}`, accent: "magenta" },
29889
+ React.createElement(
29890
+ Box,
29891
+ null,
29892
+ React.createElement(Text, { color: "cyan", bold: true }, "> "),
29893
+ React.createElement(Text, { color: state.configProviderInput ? "white" : "gray" }, value)
29894
+ ),
29895
+ state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter save | backspace edit/clear | esc back")
29896
+ );
29897
+ }
29898
+ function ConfigAddProviderSelector({ state }) {
29899
+ return React.createElement(
29900
+ DialogFrame,
29901
+ { title: "Add Provider", accent: "magenta" },
29902
+ ...state.configProviderPresets.map(
29903
+ (item, index) => React.createElement(
29904
+ Text,
29905
+ { key: item.preset, color: index === state.configAddProviderCursor ? "cyan" : "white", bold: index === state.configAddProviderCursor },
29906
+ `${index === state.configAddProviderCursor ? ">" : " "} ${shorten(item.label, 18).padEnd(18)} ${item.detail ?? item.preset}`
29907
+ )
29908
+ ),
29909
+ React.createElement(Text, { color: "gray" }, "enter add preset | esc back")
29910
+ );
29911
+ }
29106
29912
  function PermissionBar({ state }) {
29107
29913
  const pending = state.permission;
29108
29914
  if (!pending) {
@@ -29328,7 +30134,7 @@ function clamp01(value) {
29328
30134
  if (!Number.isFinite(value)) return 0;
29329
30135
  return Math.max(0, Math.min(1, value));
29330
30136
  }
29331
- var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, DEMIAN_WORDMARK_LINES;
30137
+ var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, PromptDraftController, SessionMetaBox, DEMIAN_WORDMARK_LINES, DemianWordmark, TranscriptView, BlockView, CommandGuide;
29332
30138
  var init_app = __esm({
29333
30139
  "src/ui/tui/app.ts"() {
29334
30140
  "use strict";
@@ -29362,6 +30168,86 @@ var init_app = __esm({
29362
30168
  { command: "/exit", insert: "/exit", mode: "both", description: "Exit Demian", aliases: ["quit", "close"] },
29363
30169
  { command: "/quit", insert: "/quit", mode: "both", description: "Exit Demian", aliases: ["exit", "close"] }
29364
30170
  ];
30171
+ PromptDraftController = class {
30172
+ #snapshot;
30173
+ #listeners = /* @__PURE__ */ new Set();
30174
+ constructor(initialValue) {
30175
+ this.#snapshot = {
30176
+ value: initialValue,
30177
+ slashCommandCursor: 0
30178
+ };
30179
+ }
30180
+ snapshot() {
30181
+ return this.#snapshot;
30182
+ }
30183
+ value() {
30184
+ return this.#snapshot.value;
30185
+ }
30186
+ subscribe(listener) {
30187
+ this.#listeners.add(listener);
30188
+ return () => {
30189
+ this.#listeners.delete(listener);
30190
+ };
30191
+ }
30192
+ setValue(value) {
30193
+ const sameValue = value === this.#snapshot.value;
30194
+ const slashCommandClosedPrefix = this.#snapshot.slashCommandClosedPrefix && isSlashCommandClosedForPrompt(value, this.#snapshot.slashCommandClosedPrefix) ? this.#snapshot.slashCommandClosedPrefix : void 0;
30195
+ this.#set({
30196
+ value,
30197
+ slashCommandCursor: sameValue ? this.#snapshot.slashCommandCursor : 0,
30198
+ slashCommandClosedPrefix
30199
+ });
30200
+ }
30201
+ append(value) {
30202
+ if (!value) return;
30203
+ this.setValue(`${this.#snapshot.value}${value}`);
30204
+ }
30205
+ backspace() {
30206
+ this.setValue(Array.from(this.#snapshot.value).slice(0, -1).join(""));
30207
+ }
30208
+ closeSlashCommandsForCurrentPrompt() {
30209
+ this.#set({
30210
+ ...this.#snapshot,
30211
+ slashCommandCursor: 0,
30212
+ slashCommandClosedPrefix: this.#snapshot.value
30213
+ });
30214
+ }
30215
+ moveSlashCommandCursor(delta, count) {
30216
+ if (count <= 0) return;
30217
+ this.#set({
30218
+ ...this.#snapshot,
30219
+ slashCommandCursor: (this.#snapshot.slashCommandCursor + delta + count) % count
30220
+ });
30221
+ }
30222
+ #set(next) {
30223
+ if (next.value === this.#snapshot.value && next.slashCommandCursor === this.#snapshot.slashCommandCursor && next.slashCommandClosedPrefix === this.#snapshot.slashCommandClosedPrefix) {
30224
+ return;
30225
+ }
30226
+ this.#snapshot = next;
30227
+ for (const listener of this.#listeners) listener();
30228
+ }
30229
+ };
30230
+ SessionMetaBox = React.memo(function SessionMetaBox2({ state, version, marginTop = 1 }) {
30231
+ const width = promptPanelWidth();
30232
+ const compact = width < 68;
30233
+ const agent = shorten(state.selectedAgent ?? state.status.agent ?? "general", compact ? 14 : 18);
30234
+ const model = shorten(state.selection?.model ?? state.status.model ?? "model", compact ? Math.max(18, width - 14) : 30);
30235
+ const workspace = shorten(state.status.cwd ?? process.cwd(), compact ? Math.max(18, width - 18) : 46);
30236
+ const rows = compact ? [
30237
+ `agent ${agent} \xB7 perm ${state.permissionPreset}`,
30238
+ `model ${model}`,
30239
+ `workspace ${workspace}`,
30240
+ version ? `version v${version}` : void 0
30241
+ ].filter(Boolean) : [
30242
+ `agent ${agent} \xB7 model ${model} \xB7 perm ${state.permissionPreset}`,
30243
+ `workspace ${workspace}${version ? ` \xB7 version v${version}` : ""}`
30244
+ ];
30245
+ return React.createElement(
30246
+ Box,
30247
+ { alignSelf: "center", width, flexDirection: "column", borderStyle: "single", borderColor: "#334155", paddingX: 1, marginTop },
30248
+ ...rows.map((line, index) => React.createElement(SurfaceLine, { key: index, text: line ?? "", width: width - 4, backgroundColor: SURFACE.status, color: "white" }))
30249
+ );
30250
+ });
29365
30251
  DEMIAN_WORDMARK_LINES = [
29366
30252
  " _ _ ",
29367
30253
  " __| | ___ _ __ ___ (_) __ _ _ __ ",
@@ -29369,6 +30255,56 @@ var init_app = __esm({
29369
30255
  "| (_| | __/ | | | | | | (_| | | | |",
29370
30256
  " \\__,_|\\___|_| |_| |_|_|\\__,_|_| |_|"
29371
30257
  ];
30258
+ DemianWordmark = React.memo(function DemianWordmark2() {
30259
+ if (terminalWidth() < 58) {
30260
+ return React.createElement(Box, { alignSelf: "center" }, React.createElement(Text, { color: "gray", bold: true }, "DEMIAN"));
30261
+ }
30262
+ return React.createElement(
30263
+ Box,
30264
+ { alignSelf: "center", flexDirection: "column" },
30265
+ ...DEMIAN_WORDMARK_LINES.map(
30266
+ (line, index) => React.createElement(Text, { key: index, color: index < 2 ? "gray" : "white", bold: true }, line)
30267
+ )
30268
+ );
30269
+ });
30270
+ TranscriptView = React.memo(function TranscriptView2({ state }) {
30271
+ const blocks = state.blocks.slice(-12);
30272
+ const streaming = !state.streamingFinalized && state.streamingText ? { kind: "assistant", title: "Assistant streaming", lines: streamingLines(state.streamingText) } : void 0;
30273
+ const width = transcriptPanelWidth();
30274
+ const contentWidth = Math.max(24, width - 2);
30275
+ return React.createElement(
30276
+ Box,
30277
+ { alignSelf: "center", width, flexDirection: "column", marginTop: 1 },
30278
+ ...blocks.map((block) => React.createElement(BlockView, { key: block.id, block, width: contentWidth })),
30279
+ streaming ? React.createElement(BlockView, { key: "streaming", block: streaming, width: contentWidth }) : null
30280
+ );
30281
+ });
30282
+ BlockView = React.memo(function BlockView2({ block, width }) {
30283
+ if (block.kind === "tool") return React.createElement(ToolBlockView, { block, width });
30284
+ if (block.kind === "goal-work") return React.createElement(GoalWorkBlockView, { block, width });
30285
+ if (block.kind === "cowork") return React.createElement(CoworkBlockView, { block, width });
30286
+ if (block.kind === "user") return React.createElement(UserBlockView, { block, width });
30287
+ const color = block.kind === "user" ? "green" : block.kind === "assistant" ? "white" : block.kind === "warning" ? "yellow" : "magenta";
30288
+ const lines = wrapBlockLines(block.lines, width, 80);
30289
+ return React.createElement(
30290
+ Box,
30291
+ { flexDirection: "column", marginBottom: 1 },
30292
+ React.createElement(Text, { color, bold: true }, fitToWidth(block.title, width)),
30293
+ ...lines.map((line, index) => React.createElement(Text, { key: index }, line || " "))
30294
+ );
30295
+ }, sameBlockViewProps);
30296
+ CommandGuide = React.memo(function CommandGuide2({ hint, color, error }) {
30297
+ return React.createElement(
30298
+ Box,
30299
+ { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
30300
+ error ? React.createElement(Text, { color: "yellow" }, error) : null,
30301
+ React.createElement(
30302
+ Box,
30303
+ { justifyContent: "flex-end" },
30304
+ React.createElement(Text, { color }, hint)
30305
+ )
30306
+ );
30307
+ });
29372
30308
  }
29373
30309
  });
29374
30310
 
@@ -35406,6 +36342,9 @@ function providerOptions(config, catalogs = {}) {
35406
36342
  permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
35407
36343
  ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
35408
36344
  available,
36345
+ baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
36346
+ apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
36347
+ auth: sanitizeProviderAuth(provider.auth),
35409
36348
  catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
35410
36349
  };
35411
36350
  }).sort((left, right) => {
@@ -35476,6 +36415,13 @@ function providerCatalogAvailable(catalog) {
35476
36415
  function unavailableLabel(value) {
35477
36416
  return `(n/a) ${value}`;
35478
36417
  }
36418
+ function sanitizeProviderAuth(auth) {
36419
+ if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
36420
+ const record = auth;
36421
+ const type = typeof record.type === "string" ? record.type : void 0;
36422
+ const header = typeof record.header === "string" ? record.header : void 0;
36423
+ return type || header ? { type, header } : void 0;
36424
+ }
35479
36425
  function errorMessage4(error) {
35480
36426
  return error instanceof Error ? error.message : String(error);
35481
36427
  }
@@ -35706,12 +36652,14 @@ __export(controller_exports, {
35706
36652
  runTuiSession: () => runTuiSession
35707
36653
  });
35708
36654
  import path36 from "node:path";
36655
+ import { existsSync as existsSync5 } from "node:fs";
35709
36656
  async function runTuiSession(flags, store) {
35710
36657
  const cwd = path36.resolve(flags.cwd ?? process.cwd());
35711
36658
  const eventBus = new EventBus();
35712
36659
  eventBus.subscribe((event) => store.handleEvent(event));
35713
- const loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
35714
- const config = flags.context ? mergeConfig(loadedConfig, {
36660
+ const configInit = await ensureTuiConfigExists(flags);
36661
+ let loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
36662
+ let config = flags.context ? mergeConfig(loadedConfig, {
35715
36663
  context: {
35716
36664
  main: flags.context
35717
36665
  }
@@ -35731,6 +36679,10 @@ async function runTuiSession(flags, store) {
35731
36679
  store.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
35732
36680
  agent: agentName,
35733
36681
  cwd,
36682
+ configPath: configMutationPath(flags),
36683
+ configCreated: configInit.created,
36684
+ configDefaultProvider: config.defaultProvider,
36685
+ configMessage: configInit.created ? `Created config at ${configInit.path}` : void 0,
35734
36686
  agentOptions,
35735
36687
  contextEfficiency: {
35736
36688
  maxContextTokens: config.context.main.maxContextTokens,
@@ -35739,6 +36691,7 @@ async function runTuiSession(flags, store) {
35739
36691
  }
35740
36692
  });
35741
36693
  let nextPrompt = flags.prompt;
36694
+ let openConfigOnFirstPrompt = configInit.created && !flags.prompt.trim();
35742
36695
  const conversationsEnabled = flags.conversationManagement === true;
35743
36696
  const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
35744
36697
  let currentConversation = conversationState?.current;
@@ -35779,13 +36732,28 @@ async function runTuiSession(flags, store) {
35779
36732
  await applyStartupSessionSelection(selected);
35780
36733
  }
35781
36734
  for (; ; ) {
35782
- const prompt = await store.requestPrompt(nextPrompt);
36735
+ const promptPromise = store.requestPrompt(nextPrompt);
36736
+ if (openConfigOnFirstPrompt) {
36737
+ openConfigOnFirstPrompt = false;
36738
+ store.openConfigManager(`Created config at ${configInit.path}`);
36739
+ }
36740
+ const prompt = await promptPromise;
35783
36741
  nextPrompt = void 0;
35784
36742
  if (store.exitRequested()) {
35785
36743
  await saveCurrentSelection();
35786
36744
  return 0;
35787
36745
  }
35788
36746
  if (!prompt) return 0;
36747
+ const configAction = parseTuiConfigAction(prompt);
36748
+ if (configAction) {
36749
+ await handleConfigAction(configAction);
36750
+ continue;
36751
+ }
36752
+ const sessionAction = parseTuiSessionAction(prompt);
36753
+ if (sessionAction) {
36754
+ await handleSessionSelectionShortcut();
36755
+ continue;
36756
+ }
35789
36757
  const sessionCommand = parseSessionCommand(prompt);
35790
36758
  if (sessionCommand) {
35791
36759
  await handleSessionCommand(sessionCommand);
@@ -36062,6 +37030,25 @@ ${sessionListMessage()}`);
36062
37030
  await persistCurrentConversation();
36063
37031
  }
36064
37032
  }
37033
+ async function handleSessionSelectionShortcut() {
37034
+ if (!conversationsEnabled) {
37035
+ store.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
37036
+ store.prepareForPrompt("waiting for next message");
37037
+ return;
37038
+ }
37039
+ if (!currentConversation) {
37040
+ currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
37041
+ conversationRecords.set(currentConversation.id, currentConversation);
37042
+ }
37043
+ await persistCurrentConversation();
37044
+ const selection = await store.requestSessionSelection(startupSessionOptions(), { cancel: "prompt" });
37045
+ if (selection.kind === "cancel") {
37046
+ store.prepareForPrompt("waiting for next message");
37047
+ return;
37048
+ }
37049
+ if (selection.kind === "exit" || store.exitRequested()) return;
37050
+ await applyStartupSessionSelection(selection);
37051
+ }
36065
37052
  function startupSessionOptions() {
36066
37053
  const records = sortedConversations();
36067
37054
  const sessionOptions = records.map((record) => ({
@@ -36076,6 +37063,10 @@ ${sessionListMessage()}`);
36076
37063
  return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
36077
37064
  }
36078
37065
  async function applyStartupSessionSelection(selection) {
37066
+ if (selection.kind === "cancel") {
37067
+ store.prepareForPrompt("waiting for next message");
37068
+ return;
37069
+ }
36079
37070
  if (selection.kind === "new") {
36080
37071
  const next = createConversationRecord({ id: createRootSessionId(), cwd });
36081
37072
  conversationRecords.set(next.id, next);
@@ -36132,6 +37123,87 @@ ${sessionListMessage()}`);
36132
37123
  await preferenceStore.save(selection);
36133
37124
  savedSelectionKey = key;
36134
37125
  }
37126
+ async function handleConfigAction(action) {
37127
+ const path37 = configMutationPath(flags);
37128
+ try {
37129
+ let message = "Config updated.";
37130
+ let nextSelection = {};
37131
+ if (action.type === "setDefaultProvider") {
37132
+ await updateConfigDefaults({ path: path37, defaultProvider: action.provider });
37133
+ message = `Default provider saved: ${action.provider}`;
37134
+ nextSelection = { provider: action.provider };
37135
+ } else if (action.type === "setDefaultModel") {
37136
+ await setDefaultModelProfile({
37137
+ path: path37,
37138
+ provider: action.provider,
37139
+ profileName: action.profileName,
37140
+ name: action.name,
37141
+ displayName: action.displayName,
37142
+ model: action.model
37143
+ });
37144
+ message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
37145
+ nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
37146
+ } else if (action.type === "addProvider") {
37147
+ await addProvider({ path: path37, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
37148
+ message = `Provider added: ${action.name}`;
37149
+ nextSelection = { provider: action.name };
37150
+ } else if (action.type === "updateProvider") {
37151
+ await updateProviderSettings({
37152
+ path: path37,
37153
+ provider: action.provider,
37154
+ ...action.field === "baseURL" ? { baseURL: action.value } : {},
37155
+ ...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
37156
+ ...action.field === "authHeader" ? { authHeader: action.value } : {}
37157
+ });
37158
+ message = `Provider ${action.field} saved: ${action.provider}`;
37159
+ nextSelection = { provider: action.provider };
37160
+ }
37161
+ await reloadConfigSettings(message, nextSelection);
37162
+ } catch (error) {
37163
+ store.prepareForPrompt("config action failed");
37164
+ store.showSystemMessage("Config", errorMessage(error));
37165
+ store.openConfigManager(errorMessage(error));
37166
+ }
37167
+ }
37168
+ async function reloadConfigSettings(message, selectionFlags = {}) {
37169
+ loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
37170
+ config = flags.context ? mergeConfig(loadedConfig, {
37171
+ context: {
37172
+ main: flags.context
37173
+ }
37174
+ }) : loadedConfig;
37175
+ const currentSelection = store.currentSelection();
37176
+ const selection = createInteractiveModelSelection(
37177
+ config,
37178
+ selectionFlags.provider || selectionFlags.model ? selectionFlags : { provider: currentSelection.providerName, model: currentSelection.model }
37179
+ );
37180
+ store.configureSettings(selection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
37181
+ agent: store.currentAgentName(),
37182
+ cwd,
37183
+ configPath: configMutationPath(flags),
37184
+ configDefaultProvider: config.defaultProvider,
37185
+ configMessage: message,
37186
+ agentOptions,
37187
+ contextEfficiency: {
37188
+ maxContextTokens: config.context.main.maxContextTokens,
37189
+ compactAtRatio: config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5,
37190
+ compactAtTokens: config.context.main.maxContextTokens ? Math.floor(config.context.main.maxContextTokens * (config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5)) : void 0
37191
+ }
37192
+ });
37193
+ store.openConfigManager(message);
37194
+ }
37195
+ }
37196
+ async function ensureTuiConfigExists(flags) {
37197
+ const target = configMutationPath(flags);
37198
+ if (!flags.configPath) {
37199
+ const jsondPath = target.replace(/\.json$/i, ".jsond");
37200
+ if (existsSync5(target) || existsSync5(jsondPath)) return { path: target, created: false };
37201
+ }
37202
+ const result = await createUserConfig({ path: target });
37203
+ return { path: result.path, created: result.created };
37204
+ }
37205
+ function configMutationPath(flags) {
37206
+ return flags.configPath ? path36.resolve(flags.configPath) : defaultUserConfigPath();
36135
37207
  }
36136
37208
  function isGoalStateClearingAction(action) {
36137
37209
  return action === "status" || action === "pause" || action === "resume";
@@ -36170,6 +37242,7 @@ var init_controller = __esm({
36170
37242
  init_registry();
36171
37243
  init_types();
36172
37244
  init_config();
37245
+ init_config_scaffold();
36173
37246
  init_execution();
36174
37247
  init_events();
36175
37248
  init_runtime();
@@ -36184,6 +37257,7 @@ var init_controller = __esm({
36184
37257
  init_history();
36185
37258
  init_preferences();
36186
37259
  init_settings();
37260
+ init_store();
36187
37261
  init_conversations();
36188
37262
  }
36189
37263
  });
@@ -36417,283 +37491,16 @@ Flags:
36417
37491
  }
36418
37492
 
36419
37493
  // src/config-command.ts
37494
+ init_config_scaffold();
37495
+ init_config();
37496
+ init_catalog();
37497
+ init_claudecode_auth_preflight();
37498
+ init_claudecode_paths();
36420
37499
  import { spawn as spawn4 } from "node:child_process";
36421
37500
  import { stat as stat4 } from "node:fs/promises";
36422
37501
  import os11 from "node:os";
36423
37502
  import path17 from "node:path";
36424
37503
  import readline3 from "node:readline/promises";
36425
-
36426
- // src/config-scaffold.ts
36427
- init_path_expansion();
36428
- import { chmod, mkdir, readFile as readFile2, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
36429
- import os3 from "node:os";
36430
- import path3 from "node:path";
36431
- function defaultUserConfigPath() {
36432
- return path3.join(os3.homedir(), ".demian", "config.json");
36433
- }
36434
- function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
36435
- return {
36436
- version: 2,
36437
- defaultProvider,
36438
- providers: {
36439
- openai: {
36440
- type: "openai-compatible",
36441
- baseURL: "https://api.openai.com/v1",
36442
- apiKey: "",
36443
- apiKeyEnv: "OPENAI_API_KEY",
36444
- catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
36445
- },
36446
- anthropic: {
36447
- type: "anthropic",
36448
- baseURL: "https://api.anthropic.com/v1",
36449
- apiKey: "",
36450
- apiKeyEnv: "ANTHROPIC_API_KEY",
36451
- catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
36452
- },
36453
- gemini: {
36454
- type: "openai-compatible",
36455
- baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
36456
- apiKey: "",
36457
- apiKeyEnv: "GEMINI_API_KEY",
36458
- apiKeyEnvAliases: ["GOOGLE_API_KEY"],
36459
- catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
36460
- },
36461
- groq: {
36462
- type: "openai-compatible",
36463
- baseURL: "https://api.groq.com/openai/v1",
36464
- apiKey: "",
36465
- apiKeyEnv: "GROQ_API_KEY",
36466
- catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
36467
- },
36468
- azure: {
36469
- type: "openai-compatible",
36470
- auth: { type: "api-key", header: "api-key" },
36471
- modelProfiles: [
36472
- {
36473
- name: "azure-example",
36474
- displayName: "Azure example",
36475
- model: "azure-deployment-name",
36476
- baseURL: "https://example.openai.azure.com/openai/v1",
36477
- apiKey: "",
36478
- apiKeyEnv: "AZURE_OPENAI_API_KEY"
36479
- }
36480
- ]
36481
- },
36482
- lmstudio: {
36483
- type: "openai-compatible",
36484
- baseURL: "http://localhost:1234/v1",
36485
- apiKey: "lm-studio",
36486
- modelProfiles: [],
36487
- catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
36488
- },
36489
- "ollama-local": {
36490
- type: "openai-compatible",
36491
- baseURL: "http://localhost:11434/v1",
36492
- apiKey: "ollama",
36493
- modelProfiles: [],
36494
- quirks: { omitTemperature: true },
36495
- catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
36496
- },
36497
- "ollama-cloud": {
36498
- type: "ollama",
36499
- baseURL: "https://ollama.com/api",
36500
- apiKey: "",
36501
- apiKeyEnv: "OLLAMA_API_KEY",
36502
- modelProfiles: [],
36503
- catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
36504
- },
36505
- llamacpp: {
36506
- type: "openai-compatible",
36507
- baseURL: "http://localhost:8080/v1",
36508
- apiKey: "llama.cpp",
36509
- modelProfiles: [],
36510
- catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
36511
- },
36512
- vllm: {
36513
- type: "openai-compatible",
36514
- baseURL: "http://localhost:8000/v1",
36515
- apiKey: "vllm",
36516
- modelProfiles: [],
36517
- catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
36518
- },
36519
- codex: {
36520
- type: "codex",
36521
- baseURL: "https://chatgpt.com/backend-api/codex",
36522
- authStore: "auto",
36523
- allowApiKeyFallback: false,
36524
- promptCacheKey: "root-session",
36525
- catalog: { type: "codex-oauth-models" }
36526
- },
36527
- claudecode: {
36528
- type: "claudecode",
36529
- runtime: "agent-sdk",
36530
- cliPath: "~/.local/bin/claude",
36531
- cwdMode: "session",
36532
- historyPolicy: "passthrough-resume",
36533
- onInvalidResume: "fresh",
36534
- attachmentFallback: "block",
36535
- allowSubagents: false,
36536
- sanitizeApiKeyEnv: true,
36537
- authPreflight: true,
36538
- useBareMode: false,
36539
- usageLedgerScope: "process",
36540
- sessionLock: true,
36541
- abortPolicy: "record-only",
36542
- catalog: { type: "claudecode-supported-models" }
36543
- }
36544
- }
36545
- };
36546
- }
36547
- async function createUserConfig(options = {}) {
36548
- const filePath = expandHome(options.path ?? defaultUserConfigPath());
36549
- const content = `${JSON.stringify(defaultUserConfig(options.defaultProvider), null, 2)}
36550
- `;
36551
- if (options.print) return { path: filePath, created: false, content };
36552
- const existed = await exists(filePath);
36553
- if (existed && !options.force) return { path: filePath, created: false, content: await readFile2(filePath, "utf8"), existed: true };
36554
- await writeJsonAtomic(filePath, content);
36555
- return { path: filePath, created: true, content, existed };
36556
- }
36557
- async function addProvider(options) {
36558
- const filePath = expandHome(options.path ?? defaultUserConfigPath());
36559
- const config = await readConfigObject(filePath);
36560
- const providers = objectValue(config.providers);
36561
- const name = options.name;
36562
- if (providers[name] && !options.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
36563
- providers[name] = providerPreset(options);
36564
- config.providers = providers;
36565
- const content = `${JSON.stringify(config, null, 2)}
36566
- `;
36567
- await writeJsonAtomic(filePath, content);
36568
- return { path: filePath, created: true, content };
36569
- }
36570
- async function addModelProfile(options) {
36571
- const filePath = expandHome(options.path ?? defaultUserConfigPath());
36572
- const config = await readConfigObject(filePath);
36573
- const providers = objectValue(config.providers);
36574
- const provider = objectValue(providers[options.provider]);
36575
- const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
36576
- const displayName = options.displayName ?? options.name;
36577
- const existingByName = profiles.findIndex((entry) => entry.name === options.name);
36578
- const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
36579
- if (existingByName >= 0 && !options.force) throw new Error(`Profile name "${options.name}" already exists on provider ${options.provider}. Use --force to overwrite it.`);
36580
- if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options.force) {
36581
- throw new Error(`Profile displayName "${displayName}" already exists on provider ${options.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
36582
- }
36583
- const next = {
36584
- name: options.name,
36585
- displayName,
36586
- model: options.model,
36587
- ...options.baseURL ? { baseURL: options.baseURL } : {},
36588
- ...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
36589
- ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
36590
- };
36591
- if (existingByName >= 0) profiles[existingByName] = next;
36592
- else profiles.push(next);
36593
- provider.modelProfiles = profiles;
36594
- providers[options.provider] = provider;
36595
- config.providers = providers;
36596
- const content = `${JSON.stringify(config, null, 2)}
36597
- `;
36598
- await writeJsonAtomic(filePath, content);
36599
- return { path: filePath, created: true, content };
36600
- }
36601
- async function readConfigObject(filePath) {
36602
- if (!await exists(filePath)) await createUserConfig({ path: filePath });
36603
- const raw = JSON.parse(await readFile2(filePath, "utf8"));
36604
- raw.version ??= 2;
36605
- raw.providers = objectValue(raw.providers);
36606
- return raw;
36607
- }
36608
- function providerPreset(options) {
36609
- const preset = options.preset ?? options.name;
36610
- const auth = apiKeyAuthFields(options);
36611
- const openAIAuth = options.authHeader ? { auth: { type: "api-key", header: options.authHeader } } : {};
36612
- if (preset === "openai") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
36613
- if (preset === "anthropic") return { type: "anthropic", baseURL: options.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
36614
- if (preset === "gemini") {
36615
- const geminiBase = options.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
36616
- return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
36617
- }
36618
- if (preset === "groq") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
36619
- if (preset === "azure") {
36620
- return {
36621
- type: "openai-compatible",
36622
- auth: { type: "api-key", header: options.authHeader ?? "api-key" },
36623
- ...auth,
36624
- modelProfiles: [
36625
- {
36626
- name: "azure-example",
36627
- displayName: "Azure example",
36628
- model: "azure-deployment-name",
36629
- baseURL: options.baseURL ?? "https://example.openai.azure.com/openai/v1",
36630
- ...options.apiKey ? {} : { apiKey: "" },
36631
- apiKeyEnv: options.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
36632
- }
36633
- ]
36634
- };
36635
- }
36636
- if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:1234/v1", apiKey: options.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
36637
- if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:11434/v1", apiKey: options.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
36638
- if (preset === "ollama-cloud") return { type: "ollama", baseURL: options.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
36639
- if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8080/v1", apiKey: options.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
36640
- if (preset === "vllm") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8000/v1", apiKey: options.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
36641
- if (preset === "codex") return { type: "codex", baseURL: options.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
36642
- 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" } };
36643
- if ((options.type ?? preset) === "openai-compatible") {
36644
- const baseURL = options.baseURL;
36645
- if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
36646
- return {
36647
- type: "openai-compatible",
36648
- baseURL,
36649
- ...auth,
36650
- ...openAIAuth,
36651
- catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
36652
- };
36653
- }
36654
- throw new Error(`Unknown provider preset: ${preset}`);
36655
- }
36656
- function apiKeyAuthFields(options, defaultApiKeyEnv) {
36657
- const apiKeyEnv = options.apiKeyEnv ?? defaultApiKeyEnv;
36658
- return {
36659
- ...options.apiKey !== void 0 || apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
36660
- ...apiKeyEnv ? { apiKeyEnv } : {}
36661
- };
36662
- }
36663
- async function writeJsonAtomic(filePath, content) {
36664
- await mkdir(path3.dirname(filePath), { recursive: true, mode: 448 });
36665
- const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
36666
- await writeFile2(temp, content, { mode: 384 });
36667
- await rename(temp, filePath);
36668
- await chmod(filePath, 384).catch(() => void 0);
36669
- }
36670
- function detectDefaultProvider() {
36671
- if (process.env.OPENAI_API_KEY) return "openai";
36672
- if (process.env.ANTHROPIC_API_KEY) return "anthropic";
36673
- if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
36674
- if (process.env.GROQ_API_KEY) return "groq";
36675
- return "openai";
36676
- }
36677
- function objectValue(value) {
36678
- return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
36679
- }
36680
- async function exists(filePath) {
36681
- try {
36682
- await stat(filePath);
36683
- return true;
36684
- } catch {
36685
- return false;
36686
- }
36687
- }
36688
- function expandHome(value) {
36689
- return resolveExpandedPath(value);
36690
- }
36691
-
36692
- // src/config-command.ts
36693
- init_config();
36694
- init_catalog();
36695
- init_claudecode_auth_preflight();
36696
- init_claudecode_paths();
36697
37504
  async function maybeRunConfigCommand(argv) {
36698
37505
  if (argv[0] === "config") return runConfigCommand(argv.slice(1));
36699
37506
  if (argv[0] === "auth") return runAuthCommand(argv.slice(1));
@@ -36807,6 +37614,41 @@ async function runConfigCommand(argv) {
36807
37614
  force
36808
37615
  });
36809
37616
  process.stdout.write(`Updated config: ${result.path}
37617
+ `);
37618
+ return 0;
37619
+ }
37620
+ if (command === "set-default-model") {
37621
+ const [provider, ...tail2] = rest;
37622
+ if (!provider) throw new Error("config set-default-model requires a provider name.");
37623
+ const flags = parseFlags(tail2);
37624
+ const result = await setDefaultModelProfile({
37625
+ path: stringFlag(flags, "path"),
37626
+ provider,
37627
+ profileName: stringFlag(flags, "profile"),
37628
+ name: stringFlag(flags, "name"),
37629
+ displayName: stringFlag(flags, "display-name"),
37630
+ model: stringFlag(flags, "model"),
37631
+ baseURL: stringFlag(flags, "base-url"),
37632
+ apiKey: await apiKeyFromFlags(flags),
37633
+ apiKeyEnv: stringFlag(flags, "api-key-env")
37634
+ });
37635
+ process.stdout.write(`Updated config: ${result.path}
37636
+ `);
37637
+ return 0;
37638
+ }
37639
+ if (command === "update-provider") {
37640
+ const [provider, ...tail2] = rest;
37641
+ if (!provider) throw new Error("config update-provider requires a provider name.");
37642
+ const flags = parseFlags(tail2);
37643
+ const result = await updateProviderSettings({
37644
+ path: stringFlag(flags, "path"),
37645
+ provider,
37646
+ baseURL: stringFlagOrEmpty(flags, "base-url"),
37647
+ apiKey: await apiKeyFromFlags(flags),
37648
+ apiKeyEnv: stringFlagOrEmpty(flags, "api-key-env"),
37649
+ authHeader: stringFlagOrEmpty(flags, "auth-header")
37650
+ });
37651
+ process.stdout.write(`Updated config: ${result.path}
36810
37652
  `);
36811
37653
  return 0;
36812
37654
  }
@@ -36904,6 +37746,10 @@ function stringFlag(flags, name) {
36904
37746
  const value = flags.get(name);
36905
37747
  return typeof value === "string" && value.trim() ? value : void 0;
36906
37748
  }
37749
+ function stringFlagOrEmpty(flags, name) {
37750
+ const value = flags.get(name);
37751
+ return typeof value === "string" ? value : void 0;
37752
+ }
36907
37753
  function requiredStringFlag(flags, name) {
36908
37754
  const value = stringFlag(flags, name);
36909
37755
  if (!value) throw new Error(`--${name} is required.`);
@@ -36946,6 +37792,8 @@ Usage:
36946
37792
  demian config setup interactive multi-step provider wizard
36947
37793
  demian config add-provider <preset|openai-compatible> [--name <name>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--auth-header <header>] [--force]
36948
37794
  demian config add-model <provider> --name <name> --model <model> [--display-name <label>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--force]
37795
+ demian config set-default-model <provider> [--profile <name> | --model <model>] [--display-name <label>]
37796
+ demian config update-provider <provider> [--base-url <url>] [--api-key-env <env>] [--auth-header <header>]
36949
37797
  demian config models <provider> [--refresh] [--config <path>]
36950
37798
  demian config list [--config <path>]
36951
37799
  demian config path
@@ -37129,7 +37977,6 @@ async function main(argv = process.argv.slice(2)) {
37129
37977
  process.stderr.write("demian TUI requires an interactive terminal. Use demian-plain for pipes or non-interactive use.\n");
37130
37978
  return 1;
37131
37979
  }
37132
- if (!flags.noWizard) await runFirstRunWizard().catch(() => void 0);
37133
37980
  try {
37134
37981
  const [{ render }, ReactModule, { TuiApp: TuiApp2 }, { TuiStore: TuiStore2 }, { runTuiSession: runTuiSession2 }] = await Promise.all([
37135
37982
  dynamicImport4("ink"),
@@ -37290,6 +38137,8 @@ Default:
37290
38137
  Press Esc then p to select provider, Esc then m to edit model, then Enter to send.
37291
38138
  Press Esc then a to select the main agent before sending a message.
37292
38139
  Press Esc then o to select the default permission preset: deny, ask, auto, or full.
38140
+ Press Esc then c to configure provider defaults and model profiles.
38141
+ Press Esc then k to open the command palette, or Esc then s to select a saved session.
37293
38142
  Press up/down in the message composer to recall previous prompts.
37294
38143
  Provider/model selections are remembered in ~/.demian/preferences.json.
37295
38144
  After each answer, the TUI returns to standby for the next message.
@@ -37297,7 +38146,7 @@ Default:
37297
38146
  Use /compact to compact current history, /stop to stop active work, and /exit or /quit to close the TUI.
37298
38147
  Use /session list, /session new, /session switch <number|id|title>, /session rename <title>,
37299
38148
  and /session delete <number|id|title> to manage saved CLI conversations.
37300
- Press Esc then q to quit, Esc then s to stop a running task, and Esc then u to clear the composer.
38149
+ Press Esc then q to quit, Esc then s while running to stop the task, and Esc then u to clear the composer.
37301
38150
  Tool approvals show the requested command/path in a permission dialog.
37302
38151
  Press y to allow once, a to always allow the grant scope, or n/Enter to deny.
37303
38152
  Prompt arguments are still accepted as a compatibility shortcut.