demian-cli 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;
@@ -22813,8 +23189,8 @@ function permissionLabel(req) {
22813
23189
  if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
22814
23190
  return `${req.tool}: ${inputObject.path}`;
22815
23191
  }
22816
- const path37 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
22817
- if (path37) return `${tool}: ${path37}`;
23192
+ const path38 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
23193
+ if (path38) return `${tool}: ${path38}`;
22818
23194
  return tool;
22819
23195
  }
22820
23196
  var init_prompt = __esm({
@@ -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;
@@ -26237,10 +26675,20 @@ var init_store = __esm({
26237
26675
  }
26238
26676
  if (option.id) this.#resolveSession({ kind: "session", id: option.id });
26239
26677
  }
26678
+ submitDeleteSessionSelection() {
26679
+ if (this.#state.inputMode !== "session") return;
26680
+ const option = this.#state.sessionOptions[this.#state.sessionCursor];
26681
+ if (!option || option.kind === "new" || !option.id) return;
26682
+ this.#resolveSession({ kind: "delete", id: option.id });
26683
+ }
26240
26684
  submitNewSessionSelection() {
26241
26685
  if (this.#state.inputMode !== "session") return;
26242
26686
  this.#resolveSession({ kind: "new" });
26243
26687
  }
26688
+ submitSessionSelectionShortcut() {
26689
+ if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
26690
+ this.#resolvePrompt(`${TUI_SESSION_ACTION_PREFIX}${JSON.stringify({ type: "select" })}`);
26691
+ }
26244
26692
  requestPrompt(initialPrompt) {
26245
26693
  if (this.#exitRequested) return Promise.resolve("");
26246
26694
  const prompt = (initialPrompt ?? "").trim();
@@ -26338,6 +26786,143 @@ var init_store = __esm({
26338
26786
  this.#state.activity = `provider ${option.name} selected`;
26339
26787
  this.#notify();
26340
26788
  }
26789
+ openConfigManager(message) {
26790
+ const canOpenFromConfig = this.#state.inputMode === "config" || this.#state.inputMode === "configModel" || this.#state.inputMode === "configModelInput" || this.#state.inputMode === "configProviderInput" || this.#state.inputMode === "configAddProvider";
26791
+ if (!canOpenFromConfig && (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim())) return;
26792
+ this.#state.inputMode = "config";
26793
+ const current = this.#state.selection?.providerName ?? this.#state.status.provider;
26794
+ this.#state.providerCursor = Math.max(0, this.#state.providerOptions.findIndex((option) => option.name === current));
26795
+ this.#state.configMessage = message ?? this.#state.configMessage;
26796
+ this.#state.settingsError = void 0;
26797
+ this.#state.activity = "configure providers and models";
26798
+ this.#notify();
26799
+ }
26800
+ openConfigProviderInput(field) {
26801
+ if (this.#state.inputMode !== "config") return;
26802
+ const provider = this.#selectedConfigProvider();
26803
+ if (!provider) return;
26804
+ this.#state.inputMode = "configProviderInput";
26805
+ this.#state.configProviderInputField = field;
26806
+ this.#state.configProviderInput = configProviderFieldValue(provider, field);
26807
+ this.#state.settingsError = void 0;
26808
+ this.#state.activity = `edit ${field} for ${provider.name}`;
26809
+ this.#notify();
26810
+ }
26811
+ appendConfigProviderInput(input2) {
26812
+ if (this.#state.inputMode !== "configProviderInput") return;
26813
+ this.#state.configProviderInput += input2;
26814
+ this.#state.settingsError = void 0;
26815
+ this.#notify();
26816
+ }
26817
+ backspaceConfigProviderInput() {
26818
+ if (this.#state.inputMode !== "configProviderInput") return;
26819
+ this.#state.configProviderInput = Array.from(this.#state.configProviderInput).slice(0, -1).join("");
26820
+ this.#state.settingsError = void 0;
26821
+ this.#notify();
26822
+ }
26823
+ submitConfigProviderInput() {
26824
+ if (this.#state.inputMode !== "configProviderInput") return;
26825
+ const provider = this.#selectedConfigProvider();
26826
+ const field = this.#state.configProviderInputField;
26827
+ if (!provider || !field) return;
26828
+ this.#submitConfigAction({ type: "updateProvider", provider: provider.name, field, value: this.#state.configProviderInput });
26829
+ }
26830
+ moveConfigProviderCursor(delta) {
26831
+ if (this.#state.inputMode !== "config" || this.#state.providerOptions.length === 0) return;
26832
+ const count = this.#state.providerOptions.length;
26833
+ this.#state.providerCursor = (this.#state.providerCursor + delta + count) % count;
26834
+ this.#notify();
26835
+ }
26836
+ submitConfigDefaultProvider() {
26837
+ if (this.#state.inputMode !== "config") return;
26838
+ const option = this.#selectedConfigProvider();
26839
+ if (!option) return;
26840
+ this.#submitConfigAction({ type: "setDefaultProvider", provider: option.name });
26841
+ }
26842
+ openConfigModelSelector() {
26843
+ if (this.#state.inputMode !== "config" && this.#state.inputMode !== "configModelInput") return;
26844
+ const option = this.#selectedConfigProvider();
26845
+ if (!option) return;
26846
+ this.#state.inputMode = "configModel";
26847
+ this.#state.configModelCursor = 0;
26848
+ this.#state.settingsError = void 0;
26849
+ this.#state.activity = `choose default model for ${option.name}`;
26850
+ this.#notify();
26851
+ }
26852
+ moveConfigModelCursor(delta) {
26853
+ if (this.#state.inputMode !== "configModel") return;
26854
+ const profiles = this.#selectedConfigProvider()?.modelProfiles ?? [];
26855
+ if (profiles.length === 0) return;
26856
+ this.#state.configModelCursor = (this.#state.configModelCursor + delta + profiles.length) % profiles.length;
26857
+ this.#notify();
26858
+ }
26859
+ submitConfigDefaultModel() {
26860
+ if (this.#state.inputMode !== "configModel") return;
26861
+ const provider = this.#selectedConfigProvider();
26862
+ const profile = provider?.modelProfiles?.[this.#state.configModelCursor];
26863
+ if (!provider || !profile?.model) return;
26864
+ this.#submitConfigAction({
26865
+ type: "setDefaultModel",
26866
+ provider: provider.name,
26867
+ profileName: profile.name,
26868
+ name: profile.name,
26869
+ displayName: profile.displayName,
26870
+ model: profile.model
26871
+ });
26872
+ }
26873
+ openConfigModelInput() {
26874
+ if (this.#state.inputMode !== "configModel" && this.#state.inputMode !== "config") return;
26875
+ if (!this.#selectedConfigProvider()) return;
26876
+ this.#state.inputMode = "configModelInput";
26877
+ this.#state.configModelInput = "";
26878
+ this.#state.settingsError = void 0;
26879
+ this.#state.activity = "add a model profile";
26880
+ this.#notify();
26881
+ }
26882
+ appendConfigModelInput(input2) {
26883
+ if (this.#state.inputMode !== "configModelInput") return;
26884
+ this.#state.configModelInput += input2;
26885
+ this.#state.settingsError = void 0;
26886
+ this.#notify();
26887
+ }
26888
+ backspaceConfigModelInput() {
26889
+ if (this.#state.inputMode !== "configModelInput") return;
26890
+ this.#state.configModelInput = Array.from(this.#state.configModelInput).slice(0, -1).join("");
26891
+ this.#state.settingsError = void 0;
26892
+ this.#notify();
26893
+ }
26894
+ submitConfigModelInput() {
26895
+ if (this.#state.inputMode !== "configModelInput") return;
26896
+ const provider = this.#selectedConfigProvider();
26897
+ const model = this.#state.configModelInput.trim();
26898
+ if (!provider || !model) {
26899
+ this.#state.settingsError = "Model ID is required.";
26900
+ this.#notify();
26901
+ return;
26902
+ }
26903
+ this.#submitConfigAction({ type: "setDefaultModel", provider: provider.name, model, displayName: model });
26904
+ }
26905
+ openConfigAddProviderSelector() {
26906
+ if (this.#state.inputMode !== "config") return;
26907
+ this.#state.inputMode = "configAddProvider";
26908
+ this.#state.configAddProviderCursor = 0;
26909
+ this.#state.settingsError = void 0;
26910
+ this.#state.activity = "add provider preset";
26911
+ this.#notify();
26912
+ }
26913
+ moveConfigAddProviderCursor(delta) {
26914
+ if (this.#state.inputMode !== "configAddProvider") return;
26915
+ const count = this.#state.configProviderPresets.length;
26916
+ if (count === 0) return;
26917
+ this.#state.configAddProviderCursor = (this.#state.configAddProviderCursor + delta + count) % count;
26918
+ this.#notify();
26919
+ }
26920
+ submitConfigAddProvider() {
26921
+ if (this.#state.inputMode !== "configAddProvider") return;
26922
+ const preset = this.#state.configProviderPresets[this.#state.configAddProviderCursor];
26923
+ if (!preset) return;
26924
+ this.#submitConfigAction({ type: "addProvider", preset: preset.preset, name: preset.name, apiKeyEnv: preset.apiKeyEnv });
26925
+ }
26341
26926
  openAgentSelector() {
26342
26927
  if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
26343
26928
  if (this.#state.agentOptions.length === 0) {
@@ -26436,9 +27021,12 @@ var init_store = __esm({
26436
27021
  this.#notify();
26437
27022
  }
26438
27023
  closeSettings() {
26439
- if (this.#state.inputMode !== "provider" && this.#state.inputMode !== "model" && this.#state.inputMode !== "agent" && this.#state.inputMode !== "permissionPreset") return;
27024
+ if (!["provider", "model", "agent", "permissionPreset", "config", "configModel", "configModelInput", "configProviderInput", "configAddProvider"].includes(this.#state.inputMode)) return;
26440
27025
  this.#state.inputMode = "prompt";
26441
27026
  this.#state.modelInput = "";
27027
+ this.#state.configModelInput = "";
27028
+ this.#state.configProviderInput = "";
27029
+ this.#state.configProviderInputField = void 0;
26442
27030
  this.#state.settingsError = void 0;
26443
27031
  this.#state.activity = "waiting for first message";
26444
27032
  this.#notify();
@@ -26567,9 +27155,12 @@ var init_store = __esm({
26567
27155
  this.#state.streamingFinalized = false;
26568
27156
  this.#activeClaudeCodePlan = this.#isClaudeCodePlanRun(event.provider) ? { text: "", providerName: event.provider, model: event.model, createdAt: event.ts } : void 0;
26569
27157
  }
26570
- if (event.type === "model.text.delta" && !isNestedInvocationEvent(event)) {
27158
+ if (event.type === "model.text.delta") {
27159
+ if (isNestedInvocationEvent(event)) return;
26571
27160
  this.#state.streamingText += event.text;
26572
27161
  this.#state.streamingFinalized = false;
27162
+ this.#notify({ deferMs: 50 });
27163
+ return;
26573
27164
  }
26574
27165
  if (event.type === "model.text" && !isNestedInvocationEvent(event)) {
26575
27166
  this.#state.streamingText = event.text;
@@ -27283,14 +27874,28 @@ var init_store = __esm({
27283
27874
  resolve(prompt);
27284
27875
  this.#notify();
27285
27876
  }
27877
+ #selectedConfigProvider() {
27878
+ return this.#state.providerOptions[this.#state.providerCursor];
27879
+ }
27880
+ #submitConfigAction(action) {
27881
+ this.#state.settingsError = void 0;
27882
+ this.#state.configModelInput = "";
27883
+ this.#state.configProviderInput = "";
27884
+ this.#state.configProviderInputField = void 0;
27885
+ this.#resolvePrompt(`${TUI_CONFIG_ACTION_PREFIX}${JSON.stringify(action)}`);
27886
+ }
27286
27887
  #resolveSession(selection) {
27287
27888
  const resolve = this.#sessionResolve;
27288
27889
  if (!resolve) return;
27289
27890
  this.#sessionResolve = void 0;
27891
+ this.#state.sessionCancelBehavior = "exit";
27290
27892
  if (selection.kind === "exit") {
27291
27893
  this.#state.inputMode = "done";
27292
27894
  this.#state.done = true;
27293
27895
  this.#state.activity = "exiting";
27896
+ } else if (selection.kind === "cancel") {
27897
+ this.#state.inputMode = "prompt";
27898
+ this.#state.activity = "waiting for next message";
27294
27899
  } else {
27295
27900
  this.#state.inputMode = "starting";
27296
27901
  this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
@@ -27402,7 +28007,23 @@ var init_store = __esm({
27402
28007
  }
27403
28008
  };
27404
28009
  }
27405
- #notify() {
28010
+ #notify(options = {}) {
28011
+ const deferMs = options.deferMs ?? 0;
28012
+ if (deferMs > 0) {
28013
+ if (this.#notifyTimer) return;
28014
+ this.#notifyTimer = setTimeout(() => {
28015
+ this.#notifyTimer = void 0;
28016
+ this.#emitNotify();
28017
+ }, deferMs);
28018
+ return;
28019
+ }
28020
+ if (this.#notifyTimer) {
28021
+ clearTimeout(this.#notifyTimer);
28022
+ this.#notifyTimer = void 0;
28023
+ }
28024
+ this.#emitNotify();
28025
+ }
28026
+ #emitNotify() {
27406
28027
  for (const listener of this.#listeners) listener();
27407
28028
  }
27408
28029
  };
@@ -27414,9 +28035,9 @@ var app_exports = {};
27414
28035
  __export(app_exports, {
27415
28036
  TuiApp: () => TuiApp,
27416
28037
  commandPaletteMatches: () => commandPaletteMatches,
28038
+ initialSessionCommandLines: () => initialSessionCommandLines,
27417
28039
  inputFrameRows: () => inputFrameRows,
27418
28040
  isCtrlCKeypress: () => isCtrlCKeypress,
27419
- isCtrlPKeypress: () => isCtrlPKeypress,
27420
28041
  isPromptNewlineKeypress: () => isPromptNewlineKeypress,
27421
28042
  sessionHeaderLine: () => sessionHeaderLine,
27422
28043
  sessionOptionLine: () => sessionOptionLine,
@@ -27428,13 +28049,16 @@ __export(app_exports, {
27428
28049
  });
27429
28050
  import React, { useEffect, useState } from "react";
27430
28051
  import { Box, Text, useApp, useInput } from "ink";
28052
+ function usePromptDraftSnapshot(promptDraft) {
28053
+ const [snapshot, setSnapshot] = useState(() => promptDraft.snapshot());
28054
+ useEffect(() => promptDraft.subscribe(() => setSnapshot(promptDraft.snapshot())), [promptDraft]);
28055
+ return snapshot;
28056
+ }
27431
28057
  function TuiApp({ store, version = "dev" }) {
27432
28058
  const [state, setState] = useState(() => store.snapshot());
27433
- const [promptInput, setPromptInput] = useState(() => store.snapshot().promptInput);
28059
+ const [promptDraft] = useState(() => new PromptDraftController(store.snapshot().promptInput));
27434
28060
  const [now2, setNow] = useState(() => Date.now());
27435
28061
  const [shortcutMode, setShortcutMode] = useState(false);
27436
- const [slashCommandCursor, setSlashCommandCursor] = useState(0);
27437
- const [slashCommandClosedPrefix, setSlashCommandClosedPrefix] = useState();
27438
28062
  const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
27439
28063
  const [commandPaletteQuery, setCommandPaletteQuery] = useState("");
27440
28064
  const [commandPaletteCursor, setCommandPaletteCursor] = useState(0);
@@ -27443,47 +28067,43 @@ function TuiApp({ store, version = "dev" }) {
27443
28067
  const update = () => {
27444
28068
  const next = store.snapshot();
27445
28069
  setState(next);
27446
- setPromptInput(next.promptInput);
28070
+ promptDraft.setValue(next.promptInput);
27447
28071
  };
27448
28072
  const unsubscribe = store.subscribe(update);
27449
28073
  update();
27450
28074
  return unsubscribe;
27451
- }, [store]);
28075
+ }, [store, promptDraft]);
27452
28076
  useEffect(() => enableEnhancedKeyboardInput(), []);
27453
28077
  useEffect(() => {
27454
28078
  if (!state.workPlan && state.inputMode !== "running" && state.inputMode !== "starting") return;
27455
- const timer = setInterval(() => setNow(Date.now()), 450);
28079
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
27456
28080
  return () => clearInterval(timer);
27457
28081
  }, [state.workPlan?.planId, state.inputMode]);
27458
28082
  useEffect(() => {
27459
28083
  setShortcutMode(false);
27460
28084
  }, [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
28085
  useEffect(() => {
27466
28086
  setCommandPaletteCursor(0);
27467
28087
  }, [commandPaletteQuery, state.inputMode]);
27468
28088
  useEffect(() => {
27469
28089
  if (commandPaletteOpen && !canUseCommandPalette(state)) setCommandPaletteOpen(false);
27470
28090
  }, [commandPaletteOpen, state.inputMode, state.permission]);
27471
- const commandPaletteItems = commandPaletteOpen ? commandPaletteMatches(commandPaletteQuery, state.inputMode, commandPaletteAvailability(state, promptInput)) : [];
28091
+ const commandPaletteItems = commandPaletteOpen ? commandPaletteMatches(commandPaletteQuery, state.inputMode, commandPaletteAvailability(state, promptDraft.value())) : [];
27472
28092
  const commandPaletteItem = commandPaletteItems[Math.min(commandPaletteCursor, Math.max(0, commandPaletteItems.length - 1))];
28093
+ const openCommandPalette = () => {
28094
+ if (!canUseCommandPalette(state)) return;
28095
+ setCommandPaletteOpen((open4) => !open4);
28096
+ setCommandPaletteQuery("");
28097
+ setCommandPaletteCursor(0);
28098
+ setShortcutMode(false);
28099
+ promptDraft.closeSlashCommandsForCurrentPrompt();
28100
+ };
27473
28101
  useInput((input2, key) => {
27474
28102
  if (isCtrlCKeypress(input2, key)) {
27475
28103
  store.requestExit();
27476
28104
  app.exit();
27477
28105
  return;
27478
28106
  }
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
28107
  const textInput = textInputForKeypress(input2, key);
27488
28108
  if (commandPaletteOpen) {
27489
28109
  if (key.escape) {
@@ -27499,7 +28119,7 @@ function TuiApp({ store, version = "dev" }) {
27499
28119
  return;
27500
28120
  }
27501
28121
  if (key.return) {
27502
- if (commandPaletteItem) executeCommandPaletteItem({ item: commandPaletteItem, store, app, setPromptInput, setState, close: () => closeCommandPalette(setCommandPaletteOpen, setCommandPaletteQuery, setCommandPaletteCursor) });
28122
+ if (commandPaletteItem) executeCommandPaletteItem({ item: commandPaletteItem, store, app, promptDraft, setState, close: () => closeCommandPalette(setCommandPaletteOpen, setCommandPaletteQuery, setCommandPaletteCursor) });
27503
28123
  return;
27504
28124
  }
27505
28125
  if (key.backspace || key.delete) {
@@ -27525,6 +28145,14 @@ function TuiApp({ store, version = "dev" }) {
27525
28145
  store.stopActiveTask();
27526
28146
  return;
27527
28147
  }
28148
+ if (shortcut === "k" && (state.inputMode === "prompt" || state.inputMode === "running")) {
28149
+ openCommandPalette();
28150
+ return;
28151
+ }
28152
+ if (shortcut === "s" && state.inputMode === "prompt") {
28153
+ store.submitSessionSelectionShortcut();
28154
+ return;
28155
+ }
27528
28156
  if (shortcut === "p" && state.inputMode === "prompt") {
27529
28157
  store.openProviderSelector();
27530
28158
  return;
@@ -27541,9 +28169,13 @@ function TuiApp({ store, version = "dev" }) {
27541
28169
  store.openPermissionPresetSelector();
27542
28170
  return;
27543
28171
  }
28172
+ if (shortcut === "c" && state.inputMode === "prompt") {
28173
+ store.openConfigManager();
28174
+ return;
28175
+ }
27544
28176
  if (shortcut === "u" && state.inputMode === "prompt") {
27545
28177
  store.clearPromptInput({ notify: false });
27546
- setPromptInput("");
28178
+ promptDraft.setValue("");
27547
28179
  clearPromptErrorPreview(setState);
27548
28180
  return;
27549
28181
  }
@@ -27578,7 +28210,14 @@ function TuiApp({ store, version = "dev" }) {
27578
28210
  }
27579
28211
  if (state.inputMode === "session") {
27580
28212
  const normalized = input2.toLowerCase();
27581
- if (key.escape || normalized === "q") {
28213
+ if (key.escape) {
28214
+ if (store.cancelSessionSelection()) {
28215
+ store.requestExit();
28216
+ app.exit();
28217
+ }
28218
+ return;
28219
+ }
28220
+ if (normalized === "q") {
27582
28221
  store.requestExit();
27583
28222
  app.exit();
27584
28223
  return;
@@ -27587,6 +28226,10 @@ function TuiApp({ store, version = "dev" }) {
27587
28226
  store.submitNewSessionSelection();
27588
28227
  return;
27589
28228
  }
28229
+ if (normalized === "d") {
28230
+ store.submitDeleteSessionSelection();
28231
+ return;
28232
+ }
27590
28233
  if (key.upArrow) {
27591
28234
  store.moveSessionCursor(-1);
27592
28235
  return;
@@ -27658,6 +28301,126 @@ function TuiApp({ store, version = "dev" }) {
27658
28301
  }
27659
28302
  return;
27660
28303
  }
28304
+ if (state.inputMode === "config") {
28305
+ if (key.escape) {
28306
+ store.closeSettings();
28307
+ return;
28308
+ }
28309
+ if (key.upArrow) {
28310
+ store.moveConfigProviderCursor(-1);
28311
+ return;
28312
+ }
28313
+ if (key.downArrow) {
28314
+ store.moveConfigProviderCursor(1);
28315
+ return;
28316
+ }
28317
+ const normalized = input2.toLowerCase();
28318
+ if (key.return || normalized === "d") {
28319
+ store.submitConfigDefaultProvider();
28320
+ return;
28321
+ }
28322
+ if (normalized === "m") {
28323
+ store.openConfigModelSelector();
28324
+ return;
28325
+ }
28326
+ if (normalized === "n") {
28327
+ store.openConfigModelInput();
28328
+ return;
28329
+ }
28330
+ if (normalized === "a") {
28331
+ store.openConfigAddProviderSelector();
28332
+ return;
28333
+ }
28334
+ if (normalized === "b") {
28335
+ store.openConfigProviderInput("baseURL");
28336
+ return;
28337
+ }
28338
+ if (normalized === "e") {
28339
+ store.openConfigProviderInput("apiKeyEnv");
28340
+ return;
28341
+ }
28342
+ if (normalized === "h") {
28343
+ store.openConfigProviderInput("authHeader");
28344
+ return;
28345
+ }
28346
+ return;
28347
+ }
28348
+ if (state.inputMode === "configModel") {
28349
+ if (key.escape) {
28350
+ store.openConfigManager();
28351
+ return;
28352
+ }
28353
+ if (key.upArrow) {
28354
+ store.moveConfigModelCursor(-1);
28355
+ return;
28356
+ }
28357
+ if (key.downArrow) {
28358
+ store.moveConfigModelCursor(1);
28359
+ return;
28360
+ }
28361
+ if (input2.toLowerCase() === "n") {
28362
+ store.openConfigModelInput();
28363
+ return;
28364
+ }
28365
+ if (key.return) {
28366
+ store.submitConfigDefaultModel();
28367
+ return;
28368
+ }
28369
+ return;
28370
+ }
28371
+ if (state.inputMode === "configModelInput") {
28372
+ if (key.escape) {
28373
+ store.openConfigModelSelector();
28374
+ return;
28375
+ }
28376
+ if (key.return) {
28377
+ if (textInput) store.appendConfigModelInput(textInput);
28378
+ store.submitConfigModelInput();
28379
+ return;
28380
+ }
28381
+ if (key.backspace || key.delete) {
28382
+ store.backspaceConfigModelInput();
28383
+ return;
28384
+ }
28385
+ if (textInput) store.appendConfigModelInput(textInput);
28386
+ return;
28387
+ }
28388
+ if (state.inputMode === "configProviderInput") {
28389
+ if (key.escape) {
28390
+ store.openConfigManager();
28391
+ return;
28392
+ }
28393
+ if (key.return) {
28394
+ if (textInput) store.appendConfigProviderInput(textInput);
28395
+ store.submitConfigProviderInput();
28396
+ return;
28397
+ }
28398
+ if (key.backspace || key.delete) {
28399
+ store.backspaceConfigProviderInput();
28400
+ return;
28401
+ }
28402
+ if (textInput) store.appendConfigProviderInput(textInput);
28403
+ return;
28404
+ }
28405
+ if (state.inputMode === "configAddProvider") {
28406
+ if (key.escape) {
28407
+ store.openConfigManager();
28408
+ return;
28409
+ }
28410
+ if (key.upArrow) {
28411
+ store.moveConfigAddProviderCursor(-1);
28412
+ return;
28413
+ }
28414
+ if (key.downArrow) {
28415
+ store.moveConfigAddProviderCursor(1);
28416
+ return;
28417
+ }
28418
+ if (key.return) {
28419
+ store.submitConfigAddProvider();
28420
+ return;
28421
+ }
28422
+ return;
28423
+ }
27661
28424
  if (state.inputMode === "model") {
27662
28425
  if (key.escape) {
27663
28426
  store.closeSettings();
@@ -27676,22 +28439,24 @@ function TuiApp({ store, version = "dev" }) {
27676
28439
  return;
27677
28440
  }
27678
28441
  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);
28442
+ const promptSnapshot = promptDraft.snapshot();
28443
+ const promptInput = promptSnapshot.value;
28444
+ const slashCommands = visibleSlashCommandMatches(promptInput, state.inputMode, promptSnapshot.slashCommandClosedPrefix);
28445
+ const slashCommand = slashCommands[promptSnapshot.slashCommandCursor] ?? slashCommands[0];
28446
+ if (slashCommands.length > 0 && key.escape) {
28447
+ promptDraft.closeSlashCommandsForCurrentPrompt();
27683
28448
  return;
27684
28449
  }
27685
- if (slashCommands2.length > 0 && key.upArrow) {
27686
- setSlashCommandCursor((cursor) => (cursor - 1 + slashCommands2.length) % slashCommands2.length);
28450
+ if (slashCommands.length > 0 && key.upArrow) {
28451
+ promptDraft.moveSlashCommandCursor(-1, slashCommands.length);
27687
28452
  return;
27688
28453
  }
27689
- if (slashCommands2.length > 0 && key.downArrow) {
27690
- setSlashCommandCursor((cursor) => (cursor + 1) % slashCommands2.length);
28454
+ if (slashCommands.length > 0 && key.downArrow) {
28455
+ promptDraft.moveSlashCommandCursor(1, slashCommands.length);
27691
28456
  return;
27692
28457
  }
27693
- if (slashCommands2.length > 0 && key.tab && slashCommand) {
27694
- applySlashCommandDraft(store, setPromptInput, slashCommand, setSlashCommandClosedPrefix);
28458
+ if (slashCommands.length > 0 && key.tab && slashCommand) {
28459
+ applySlashCommandDraft(store, promptDraft, slashCommand);
27695
28460
  clearPromptErrorPreview(setState);
27696
28461
  return;
27697
28462
  }
@@ -27708,37 +28473,35 @@ function TuiApp({ store, version = "dev" }) {
27708
28473
  return;
27709
28474
  }
27710
28475
  if (isPromptNewlineKeypress(input2, key)) {
27711
- appendPromptDraft(store, setPromptInput, `${textInput}
28476
+ appendPromptDraft(store, promptDraft, `${textInput}
27712
28477
  `);
27713
28478
  clearPromptErrorPreview(setState);
27714
28479
  return;
27715
28480
  }
27716
28481
  if (isPromptSubmitKeypress(input2, key)) {
27717
28482
  if (!textInput && slashCommand && shouldSelectSlashCommand(promptInput, slashCommand)) {
27718
- applySlashCommandDraft(store, setPromptInput, slashCommand, setSlashCommandClosedPrefix);
28483
+ applySlashCommandDraft(store, promptDraft, slashCommand);
27719
28484
  clearPromptErrorPreview(setState);
27720
28485
  return;
27721
28486
  }
27722
- if (textInput) appendPromptDraft(store, setPromptInput, textInput);
28487
+ if (textInput) appendPromptDraft(store, promptDraft, textInput);
27723
28488
  store.submitPromptInput();
27724
28489
  return;
27725
28490
  }
27726
28491
  if (key.backspace || key.delete) {
27727
28492
  store.backspacePromptInput({ notify: false });
27728
- setPromptInput((current) => Array.from(current).slice(0, -1).join(""));
28493
+ promptDraft.backspace();
27729
28494
  clearPromptErrorPreview(setState);
27730
28495
  return;
27731
28496
  }
27732
28497
  if (textInput) {
27733
- appendPromptDraft(store, setPromptInput, textInput);
28498
+ appendPromptDraft(store, promptDraft, textInput);
27734
28499
  clearPromptErrorPreview(setState);
27735
28500
  }
27736
28501
  return;
27737
28502
  }
27738
28503
  });
27739
28504
  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
28505
  const commandPalette = commandPaletteOpen ? { items: commandPaletteItems, query: commandPaletteQuery, cursor: Math.min(commandPaletteCursor, Math.max(0, commandPaletteItems.length - 1)) } : void 0;
27743
28506
  if (shouldShowSessionStart(state)) {
27744
28507
  return React.createElement(
@@ -27751,7 +28514,7 @@ function TuiApp({ store, version = "dev" }) {
27751
28514
  return React.createElement(
27752
28515
  Box,
27753
28516
  shellProps,
27754
- React.createElement(EmptyState, { state, version, shortcutMode, promptInput, slashCommandPalette, commandPalette })
28517
+ React.createElement(EmptyState, { state, version, shortcutMode, promptDraft, commandPalette })
27755
28518
  );
27756
28519
  }
27757
28520
  return React.createElement(
@@ -27764,24 +28527,24 @@ function TuiApp({ store, version = "dev" }) {
27764
28527
  React.createElement(shouldShowLoading(state) ? LoadingView : TranscriptView, { state }),
27765
28528
  React.createElement(Box, { flexGrow: 1 }),
27766
28529
  React.createElement(BottomStatusStack, { state, now: now2 }),
27767
- React.createElement(InteractionPanel, { state, shortcutMode, promptInput, slashCommandPalette, commandPalette })
28530
+ React.createElement(InteractionPanel, { state, shortcutMode, promptDraft, commandPalette })
27768
28531
  )
27769
28532
  );
27770
28533
  }
27771
- function appendPromptDraft(store, setPromptInput, value) {
28534
+ function appendPromptDraft(store, promptDraft, value) {
27772
28535
  if (!value) return;
27773
28536
  store.appendPromptInput(value, { notify: false });
27774
- setPromptInput((current) => `${current}${value}`);
28537
+ promptDraft.append(value);
27775
28538
  }
27776
28539
  function clearPromptErrorPreview(setState) {
27777
28540
  setState((current) => current.promptError ? { ...current, promptError: void 0 } : current);
27778
28541
  }
27779
- function applySlashCommandDraft(store, setPromptInput, item, setSlashCommandClosedPrefix) {
28542
+ function applySlashCommandDraft(store, promptDraft, item) {
27780
28543
  const next = item.argument ? `${item.insert} ` : item.insert;
27781
28544
  store.clearPromptInput({ notify: false });
27782
28545
  store.appendPromptInput(next, { notify: false });
27783
- setPromptInput(next);
27784
- setSlashCommandClosedPrefix(next);
28546
+ promptDraft.setValue(next);
28547
+ promptDraft.closeSlashCommandsForCurrentPrompt();
27785
28548
  }
27786
28549
  function shouldSelectSlashCommand(promptInput, item) {
27787
28550
  const value = promptInput.trim();
@@ -27793,6 +28556,14 @@ function visibleSlashCommandMatches(promptInput, mode, closedPrefix) {
27793
28556
  if (closedPrefix && isSlashCommandClosedForPrompt(promptInput, closedPrefix)) return [];
27794
28557
  return slashCommandMatches(promptInput, mode).slice(0, 7);
27795
28558
  }
28559
+ function slashCommandPaletteForDraft(snapshot, mode) {
28560
+ const slashCommands = visibleSlashCommandMatches(snapshot.value, mode, snapshot.slashCommandClosedPrefix);
28561
+ if (slashCommands.length === 0) return void 0;
28562
+ return {
28563
+ items: slashCommands,
28564
+ cursor: Math.min(snapshot.slashCommandCursor, slashCommands.length - 1)
28565
+ };
28566
+ }
27796
28567
  function isSlashCommandClosedForPrompt(promptInput, closedPrefix) {
27797
28568
  return promptInput === closedPrefix || closedPrefix.endsWith(" ") && promptInput.startsWith(closedPrefix);
27798
28569
  }
@@ -27861,6 +28632,9 @@ function buildCommandPaletteItems(mode, availability) {
27861
28632
  }
27862
28633
  if (promptReadyForSettings) {
27863
28634
  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"] });
28635
+ 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"] });
28636
+ 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"] });
28637
+ items.push({ id: "delete-current-session", title: "Delete current session", description: "Remove the current saved conversation", group: "Session", shortcut: "/session delete", action: "delete-session", keywords: ["delete", "remove", "session", "conversation"] });
27864
28638
  }
27865
28639
  for (const command of SLASH_COMMANDS) {
27866
28640
  if (!slashCommandActiveInMode(command, mode)) continue;
@@ -27954,13 +28728,13 @@ function executeCommandPaletteItem({
27954
28728
  item,
27955
28729
  store,
27956
28730
  app,
27957
- setPromptInput,
28731
+ promptDraft,
27958
28732
  setState,
27959
28733
  close
27960
28734
  }) {
27961
28735
  close();
27962
28736
  if (item.slashCommand) {
27963
- applyCommandPaletteSlashCommand(store, setPromptInput, item.slashCommand);
28737
+ applyCommandPaletteSlashCommand(store, promptDraft, item.slashCommand);
27964
28738
  clearPromptErrorPreview(setState);
27965
28739
  return;
27966
28740
  }
@@ -27977,9 +28751,21 @@ function executeCommandPaletteItem({
27977
28751
  case "open-permissions":
27978
28752
  store.openPermissionPresetSelector();
27979
28753
  return;
28754
+ case "open-config":
28755
+ store.openConfigManager();
28756
+ return;
28757
+ case "open-session":
28758
+ store.submitSessionSelectionShortcut();
28759
+ return;
28760
+ case "delete-session":
28761
+ store.clearPromptInput({ notify: false });
28762
+ store.appendPromptInput("/session delete", { notify: false });
28763
+ promptDraft.setValue("/session delete");
28764
+ store.submitPromptInput();
28765
+ return;
27980
28766
  case "clear-prompt":
27981
28767
  store.clearPromptInput({ notify: false });
27982
- setPromptInput("");
28768
+ promptDraft.setValue("");
27983
28769
  clearPromptErrorPreview(setState);
27984
28770
  return;
27985
28771
  case "retry":
@@ -28006,12 +28792,13 @@ function executeCommandPaletteItem({
28006
28792
  return;
28007
28793
  }
28008
28794
  }
28009
- function applyCommandPaletteSlashCommand(store, setPromptInput, item) {
28795
+ function applyCommandPaletteSlashCommand(store, promptDraft, item) {
28010
28796
  const shouldInsert = item.argument && item.command !== "/session new";
28011
28797
  const next = shouldInsert ? `${item.insert} ` : item.insert;
28012
28798
  store.clearPromptInput({ notify: false });
28013
28799
  store.appendPromptInput(next, { notify: false });
28014
- setPromptInput(next);
28800
+ promptDraft.setValue(next);
28801
+ promptDraft.closeSlashCommandsForCurrentPrompt();
28015
28802
  if (!shouldInsert) store.submitPromptInput();
28016
28803
  }
28017
28804
  function textInputForKeypress(input2, key) {
@@ -28038,15 +28825,6 @@ function isCtrlCKeypress(input2, key) {
28038
28825
  const modifier = Number(csi[2] ?? 1);
28039
28826
  return (codePoint === 3 || codePoint === 99) && hasControlModifier(modifier);
28040
28827
  }
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
28828
  function isPromptSubmitKeypress(input2, key) {
28051
28829
  return Boolean(key.return) || input2 === "\n";
28052
28830
  }
@@ -28119,7 +28897,7 @@ function SessionStart({ state, version }) {
28119
28897
  React.createElement(
28120
28898
  Box,
28121
28899
  { alignSelf: "center", width, justifyContent: "space-between", marginTop: 1 },
28122
- React.createElement(Text, { color: "white" }, "enter open \xB7 n new \xB7 q quit"),
28900
+ React.createElement(Text, { color: "white" }, "enter open \xB7 n new \xB7 d delete \xB7 esc cancel \xB7 q quit"),
28123
28901
  React.createElement(Text, { color: "white" }, `v${version}`)
28124
28902
  ),
28125
28903
  React.createElement(Box, { flexGrow: 1 })
@@ -28129,12 +28907,13 @@ function EmptyState({
28129
28907
  state,
28130
28908
  version,
28131
28909
  shortcutMode,
28132
- promptInput,
28133
- slashCommandPalette,
28910
+ promptDraft,
28134
28911
  commandPalette
28135
28912
  }) {
28913
+ const promptSnapshot = usePromptDraftSnapshot(promptDraft);
28914
+ const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
28136
28915
  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";
28916
+ const hint = shortcutMode ? shortcutHelp(state) : "enter send \xB7 shift/option+enter newline \xB7 esc k commands \xB7 esc s sessions";
28138
28917
  const hasOverlay = Boolean(slashCommandPalette || commandPalette);
28139
28918
  const topGap = hasOverlay ? 0 : Math.max(1, Math.min(6, Math.floor(terminalHeight() * 0.12)));
28140
28919
  return React.createElement(
@@ -28148,50 +28927,46 @@ function EmptyState({
28148
28927
  React.createElement(InputFrame, {
28149
28928
  borderColor: shortcutMode ? "cyan" : "blue",
28150
28929
  cursorColor: "cyan",
28151
- value: promptInput,
28930
+ value: promptSnapshot.value,
28152
28931
  placeholder,
28153
28932
  marginTop: 0
28154
28933
  }),
28155
28934
  React.createElement(CommandGuide, { hint, color: shortcutMode ? "cyan" : "gray", error: state.promptError }),
28156
28935
  hasOverlay ? null : React.createElement(SessionMetaBox, { state, version, marginTop: 2 }),
28936
+ hasOverlay ? null : React.createElement(SessionCommandsBox, { marginTop: 1 }),
28157
28937
  React.createElement(
28158
28938
  Box,
28159
28939
  { flexGrow: 1 }
28160
28940
  )
28161
28941
  );
28162
28942
  }
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}` : ""}`
28943
+ function initialSessionCommandLines(width) {
28944
+ const target = Math.max(24, width);
28945
+ if (target < 48) {
28946
+ return [
28947
+ "Session commands",
28948
+ "esc s sessions",
28949
+ "/session list",
28950
+ "/session new [title]",
28951
+ "/session switch <target>",
28952
+ "/session rename <title>",
28953
+ "/session delete [target]"
28954
+ ].map((line) => fitToWidth(line, target));
28955
+ }
28956
+ const commandWidth = Math.min(28, Math.max(24, Math.floor(target * 0.42)));
28957
+ const descriptionWidth = Math.max(0, target - commandWidth - 1);
28958
+ const rows = [
28959
+ ["esc s", "open saved sessions"],
28960
+ ["/session list", "list saved sessions"],
28961
+ ["/session new [title]", "create a session"],
28962
+ ["/session switch <target>", "switch by number, id, or title"],
28963
+ ["/session rename <title>", "rename current session"],
28964
+ ["/session delete [target]", "delete current or saved session"]
28965
+ ];
28966
+ return [
28967
+ fitToWidth("Session commands", target),
28968
+ ...rows.map(([command, description]) => `${padCell(command, commandWidth)} ${truncateEndToWidth(description, descriptionWidth)}`)
28177
28969
  ];
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
28970
  }
28196
28971
  function LoadingView({ state }) {
28197
28972
  const title = state.inputMode === "starting" ? "Demian runtime" : "Preparing session";
@@ -28262,34 +29037,26 @@ function tuiMode(state) {
28262
29037
  if (state.inputMode === "session") return { label: "sessions", color: "magenta" };
28263
29038
  if (state.inputMode === "running") return { label: "running", color: "cyan" };
28264
29039
  if (state.inputMode === "provider" || state.inputMode === "agent" || state.inputMode === "model" || state.inputMode === "permissionPreset") return { label: "settings", color: "magenta" };
29040
+ if (state.inputMode === "config" || state.inputMode === "configModel" || state.inputMode === "configModelInput" || state.inputMode === "configProviderInput" || state.inputMode === "configAddProvider") return { label: "config", color: "magenta" };
28265
29041
  if (state.inputMode === "prompt") return { label: "ready", color: "green" };
28266
29042
  return { label: state.inputMode, color: "gray" };
28267
29043
  }
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
- );
29044
+ function sameBlockViewProps(previous, next) {
29045
+ return previous.width === next.width && blockRenderSignature(previous.block) === blockRenderSignature(next.block);
29046
+ }
29047
+ function blockRenderSignature(block) {
29048
+ return JSON.stringify({
29049
+ id: "id" in block ? block.id : void 0,
29050
+ kind: block.kind,
29051
+ title: block.title,
29052
+ lines: block.lines,
29053
+ meta: block.meta,
29054
+ toolStatus: block.toolStatus,
29055
+ expanded: block.expanded,
29056
+ toolDetails: block.expanded ? block.toolDetails : void 0,
29057
+ goalWork: block.kind === "goal-work" ? block.goalWork : void 0,
29058
+ cowork: block.kind === "cowork" ? block.cowork : void 0
29059
+ });
28293
29060
  }
28294
29061
  function UserBlockView({ block, width }) {
28295
29062
  const lines = normalizedBlockLines(block.lines).slice(0, 80);
@@ -28753,16 +29520,21 @@ function formatSessionAge(updatedAt) {
28753
29520
  if (hours < 48) return `${hours}h ago`;
28754
29521
  return `${Math.floor(hours / 24)}d ago`;
28755
29522
  }
28756
- function InteractionPanel({ state, shortcutMode, promptInput, slashCommandPalette, commandPalette }) {
29523
+ function InteractionPanel({ state, shortcutMode, promptDraft, commandPalette }) {
28757
29524
  if (state.inputMode === "starting") return React.createElement(LoadingBar);
28758
29525
  if (state.inputMode === "session") return React.createElement(SessionSelector, { state });
28759
29526
  if (state.permission) return React.createElement(PermissionBar, { state });
28760
29527
  if (state.inputMode === "provider") return React.createElement(ProviderSelector, { state });
28761
29528
  if (state.inputMode === "agent") return React.createElement(AgentSelector, { state });
28762
29529
  if (state.inputMode === "permissionPreset") return React.createElement(PermissionPresetSelector, { state });
29530
+ if (state.inputMode === "config") return React.createElement(ConfigManager, { state });
29531
+ if (state.inputMode === "configModel") return React.createElement(ConfigModelSelector, { state });
29532
+ if (state.inputMode === "configModelInput") return React.createElement(ConfigModelInput, { state });
29533
+ if (state.inputMode === "configProviderInput") return React.createElement(ConfigProviderInput, { state });
29534
+ if (state.inputMode === "configAddProvider") return React.createElement(ConfigAddProviderSelector, { state });
28763
29535
  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 });
29536
+ if (state.inputMode === "running") return React.createElement(CommandBar, { state, shortcutMode, promptDraft, commandPalette });
29537
+ if (state.inputMode === "prompt") return React.createElement(PromptBar, { state, shortcutMode, promptDraft, commandPalette });
28766
29538
  return React.createElement(PermissionBar, { state });
28767
29539
  }
28768
29540
  function LoadingBar() {
@@ -28775,8 +29547,8 @@ function LoadingBar() {
28775
29547
  }
28776
29548
  function shortcutHelp(state) {
28777
29549
  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");
29550
+ if (state.inputMode === "prompt") parts.push("k commands", "s sessions", "p provider", "a agent", "m model", "o permissions", "c config", "u clear");
29551
+ if (state.inputMode === "running") parts.push("k commands", "s stop");
28780
29552
  if (state.turnDiff) parts.push("d diff");
28781
29553
  if (state.blocks.some((block) => block.kind === "tool")) parts.push("t tools");
28782
29554
  if (state.workPlan || state.inputMode === "running") parts.push("w plan");
@@ -28785,8 +29557,10 @@ function shortcutHelp(state) {
28785
29557
  parts.push("q quit");
28786
29558
  return `shortcut: ${parts.join(" | ")}`;
28787
29559
  }
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";
29560
+ function PromptBar({ state, shortcutMode, promptDraft, commandPalette }) {
29561
+ const promptSnapshot = usePromptDraftSnapshot(promptDraft);
29562
+ const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
29563
+ 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
29564
  return React.createElement(
28791
29565
  Box,
28792
29566
  { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
@@ -28796,15 +29570,17 @@ function PromptBar({ state, shortcutMode, promptInput, slashCommandPalette, comm
28796
29570
  React.createElement(InputFrame, {
28797
29571
  borderColor: shortcutMode ? "cyan" : "blue",
28798
29572
  cursorColor: "cyan",
28799
- value: promptInput,
29573
+ value: promptSnapshot.value,
28800
29574
  placeholder: "Type first message and press Enter",
28801
29575
  marginTop: 1
28802
29576
  }),
28803
29577
  React.createElement(CommandGuide, { hint, color: shortcutMode ? "cyan" : "gray", error: state.promptError })
28804
29578
  );
28805
29579
  }
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";
29580
+ function CommandBar({ state, shortcutMode, promptDraft, commandPalette }) {
29581
+ const promptSnapshot = usePromptDraftSnapshot(promptDraft);
29582
+ const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
29583
+ const hint = shortcutMode ? shortcutHelp(state) : "running | shift/option+enter newline | esc k commands | /stop stop | /exit quit | esc shortcuts";
28808
29584
  return React.createElement(
28809
29585
  Box,
28810
29586
  { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
@@ -28814,7 +29590,7 @@ function CommandBar({ state, shortcutMode, promptInput, slashCommandPalette, com
28814
29590
  React.createElement(InputFrame, {
28815
29591
  borderColor: shortcutMode ? "cyan" : "yellow",
28816
29592
  cursorColor: "yellow",
28817
- value: promptInput,
29593
+ value: promptSnapshot.value,
28818
29594
  placeholder: "Type /stop to stop or /exit to quit",
28819
29595
  marginTop: 1
28820
29596
  }),
@@ -28972,18 +29748,6 @@ function InputFrameLine({ row, width, cursorColor }) {
28972
29748
  function CursorCell({ cursorColor }) {
28973
29749
  return React.createElement(Text, { color: "black", backgroundColor: cursorColor, bold: true }, "\x1B[5m \x1B[25m");
28974
29750
  }
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
29751
  function SessionSelector({ state }) {
28988
29752
  const items = state.sessionOptions;
28989
29753
  const width = sessionPanelWidth();
@@ -29001,7 +29765,8 @@ function SessionSelector({ state }) {
29001
29765
  sessionOptionLine(item, index, selected, contentWidth)
29002
29766
  );
29003
29767
  }),
29004
- items.length === 0 ? React.createElement(Text, { color: "gray" }, "No saved sessions. Press n to create one.") : null
29768
+ items.length === 0 ? React.createElement(Text, { color: "gray" }, "No saved sessions. Press n to create one.") : null,
29769
+ React.createElement(Text, { color: "gray" }, "enter switch | n new | d delete | esc cancel")
29005
29770
  );
29006
29771
  }
29007
29772
  function sessionSelectorColumns(width) {
@@ -29103,6 +29868,95 @@ function ModelEditor({ state }) {
29103
29868
  state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter apply | backspace edit | esc cancel")
29104
29869
  );
29105
29870
  }
29871
+ function ConfigManager({ state }) {
29872
+ const items = state.providerOptions;
29873
+ const selected = items[state.providerCursor];
29874
+ const defaultProvider = state.configDefaultProvider ?? state.status.provider;
29875
+ const contentWidth = Math.max(40, promptPanelWidth() - 8);
29876
+ return React.createElement(
29877
+ DialogFrame,
29878
+ { title: "Config", accent: "magenta" },
29879
+ React.createElement(Text, { color: state.configCreated ? "green" : "gray" }, state.configPath ? `config: ${shorten(state.configPath, contentWidth)}` : "config: ~/.demian/config.json"),
29880
+ state.configMessage ? React.createElement(Text, { color: "cyan" }, state.configMessage) : null,
29881
+ React.createElement(Text, { color: "gray" }, `${"Provider".padEnd(20)} ${"Default model".padEnd(28)} Status`),
29882
+ ...items.map((item, index) => {
29883
+ const isDefault = item.name === defaultProvider;
29884
+ const pointer = index === state.providerCursor ? ">" : " ";
29885
+ const marker = isDefault ? "*" : " ";
29886
+ return React.createElement(
29887
+ Text,
29888
+ { key: item.name, color: index === state.providerCursor ? "cyan" : item.available === false ? "gray" : "white", bold: index === state.providerCursor },
29889
+ `${pointer}${marker} ${shorten(item.label ?? item.name, 18).padEnd(18)} ${shorten((item.modelLabel ?? item.model) || "-", 28).padEnd(28)} ${providerStatusLabel(item)}`
29890
+ );
29891
+ }),
29892
+ 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,
29893
+ React.createElement(Text, { color: "gray" }, "enter/d default | m models | b baseURL | e env | h auth | n new model | a add provider | esc close")
29894
+ );
29895
+ }
29896
+ function ConfigModelSelector({ state }) {
29897
+ const provider = state.providerOptions[state.providerCursor];
29898
+ const profiles = provider?.modelProfiles ?? [];
29899
+ return React.createElement(
29900
+ DialogFrame,
29901
+ { title: provider ? `Models: ${provider.name}` : "Models", accent: "magenta" },
29902
+ 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."),
29903
+ ...profiles.slice(0, 12).map(
29904
+ (item, index) => React.createElement(
29905
+ Text,
29906
+ { key: `${item.name}-${item.model}`, color: index === state.configModelCursor ? "cyan" : item.available === false ? "gray" : "white", bold: index === state.configModelCursor },
29907
+ `${index === state.configModelCursor ? ">" : " "} ${shorten(item.label ?? item.displayName ?? item.model, 30).padEnd(30)} ${item.source ?? "config"}`
29908
+ )
29909
+ ),
29910
+ React.createElement(Text, { color: "gray" }, "enter set default model | n add custom model | esc back")
29911
+ );
29912
+ }
29913
+ function ConfigModelInput({ state }) {
29914
+ const provider = state.providerOptions[state.providerCursor];
29915
+ const value = state.configModelInput || "Model ID";
29916
+ return React.createElement(
29917
+ DialogFrame,
29918
+ { title: provider ? `Add model: ${provider.name}` : "Add model", accent: "magenta" },
29919
+ React.createElement(
29920
+ Box,
29921
+ null,
29922
+ React.createElement(Text, { color: "cyan", bold: true }, "> "),
29923
+ React.createElement(Text, { color: state.configModelInput ? "white" : "gray" }, value)
29924
+ ),
29925
+ state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter add and make default | backspace edit | esc back")
29926
+ );
29927
+ }
29928
+ function ConfigProviderInput({ state }) {
29929
+ const provider = state.providerOptions[state.providerCursor];
29930
+ const field = state.configProviderInputField ?? "baseURL";
29931
+ const label = field === "baseURL" ? "Base URL" : field === "apiKeyEnv" ? "API key env" : "Auth header";
29932
+ const placeholder = field === "baseURL" ? "empty removes provider baseURL" : field === "apiKeyEnv" ? "empty removes provider apiKeyEnv" : "empty removes custom auth header";
29933
+ const value = state.configProviderInput || placeholder;
29934
+ return React.createElement(
29935
+ DialogFrame,
29936
+ { title: provider ? `Edit ${label}: ${provider.name}` : `Edit ${label}`, accent: "magenta" },
29937
+ React.createElement(
29938
+ Box,
29939
+ null,
29940
+ React.createElement(Text, { color: "cyan", bold: true }, "> "),
29941
+ React.createElement(Text, { color: state.configProviderInput ? "white" : "gray" }, value)
29942
+ ),
29943
+ state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter save | backspace edit/clear | esc back")
29944
+ );
29945
+ }
29946
+ function ConfigAddProviderSelector({ state }) {
29947
+ return React.createElement(
29948
+ DialogFrame,
29949
+ { title: "Add Provider", accent: "magenta" },
29950
+ ...state.configProviderPresets.map(
29951
+ (item, index) => React.createElement(
29952
+ Text,
29953
+ { key: item.preset, color: index === state.configAddProviderCursor ? "cyan" : "white", bold: index === state.configAddProviderCursor },
29954
+ `${index === state.configAddProviderCursor ? ">" : " "} ${shorten(item.label, 18).padEnd(18)} ${item.detail ?? item.preset}`
29955
+ )
29956
+ ),
29957
+ React.createElement(Text, { color: "gray" }, "enter add preset | esc back")
29958
+ );
29959
+ }
29106
29960
  function PermissionBar({ state }) {
29107
29961
  const pending = state.permission;
29108
29962
  if (!pending) {
@@ -29328,7 +30182,7 @@ function clamp01(value) {
29328
30182
  if (!Number.isFinite(value)) return 0;
29329
30183
  return Math.max(0, Math.min(1, value));
29330
30184
  }
29331
- var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, DEMIAN_WORDMARK_LINES;
30185
+ var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, PromptDraftController, SessionMetaBox, SessionCommandsBox, DEMIAN_WORDMARK_LINES, DemianWordmark, TranscriptView, BlockView, CommandGuide;
29332
30186
  var init_app = __esm({
29333
30187
  "src/ui/tui/app.ts"() {
29334
30188
  "use strict";
@@ -29362,6 +30216,105 @@ var init_app = __esm({
29362
30216
  { command: "/exit", insert: "/exit", mode: "both", description: "Exit Demian", aliases: ["quit", "close"] },
29363
30217
  { command: "/quit", insert: "/quit", mode: "both", description: "Exit Demian", aliases: ["exit", "close"] }
29364
30218
  ];
30219
+ PromptDraftController = class {
30220
+ #snapshot;
30221
+ #listeners = /* @__PURE__ */ new Set();
30222
+ constructor(initialValue) {
30223
+ this.#snapshot = {
30224
+ value: initialValue,
30225
+ slashCommandCursor: 0
30226
+ };
30227
+ }
30228
+ snapshot() {
30229
+ return this.#snapshot;
30230
+ }
30231
+ value() {
30232
+ return this.#snapshot.value;
30233
+ }
30234
+ subscribe(listener) {
30235
+ this.#listeners.add(listener);
30236
+ return () => {
30237
+ this.#listeners.delete(listener);
30238
+ };
30239
+ }
30240
+ setValue(value) {
30241
+ const sameValue = value === this.#snapshot.value;
30242
+ const slashCommandClosedPrefix = this.#snapshot.slashCommandClosedPrefix && isSlashCommandClosedForPrompt(value, this.#snapshot.slashCommandClosedPrefix) ? this.#snapshot.slashCommandClosedPrefix : void 0;
30243
+ this.#set({
30244
+ value,
30245
+ slashCommandCursor: sameValue ? this.#snapshot.slashCommandCursor : 0,
30246
+ slashCommandClosedPrefix
30247
+ });
30248
+ }
30249
+ append(value) {
30250
+ if (!value) return;
30251
+ this.setValue(`${this.#snapshot.value}${value}`);
30252
+ }
30253
+ backspace() {
30254
+ this.setValue(Array.from(this.#snapshot.value).slice(0, -1).join(""));
30255
+ }
30256
+ closeSlashCommandsForCurrentPrompt() {
30257
+ this.#set({
30258
+ ...this.#snapshot,
30259
+ slashCommandCursor: 0,
30260
+ slashCommandClosedPrefix: this.#snapshot.value
30261
+ });
30262
+ }
30263
+ moveSlashCommandCursor(delta, count) {
30264
+ if (count <= 0) return;
30265
+ this.#set({
30266
+ ...this.#snapshot,
30267
+ slashCommandCursor: (this.#snapshot.slashCommandCursor + delta + count) % count
30268
+ });
30269
+ }
30270
+ #set(next) {
30271
+ if (next.value === this.#snapshot.value && next.slashCommandCursor === this.#snapshot.slashCommandCursor && next.slashCommandClosedPrefix === this.#snapshot.slashCommandClosedPrefix) {
30272
+ return;
30273
+ }
30274
+ this.#snapshot = next;
30275
+ for (const listener of this.#listeners) listener();
30276
+ }
30277
+ };
30278
+ SessionMetaBox = React.memo(function SessionMetaBox2({ state, version, marginTop = 1 }) {
30279
+ const width = promptPanelWidth();
30280
+ const compact = width < 68;
30281
+ const agent = shorten(state.selectedAgent ?? state.status.agent ?? "general", compact ? 14 : 18);
30282
+ const model = shorten(state.selection?.model ?? state.status.model ?? "model", compact ? Math.max(18, width - 14) : 30);
30283
+ const workspace = shorten(state.status.cwd ?? process.cwd(), compact ? Math.max(18, width - 18) : 46);
30284
+ const rows = compact ? [
30285
+ `agent ${agent} \xB7 perm ${state.permissionPreset}`,
30286
+ `model ${model}`,
30287
+ `workspace ${workspace}`,
30288
+ version ? `version v${version}` : void 0
30289
+ ].filter(Boolean) : [
30290
+ `agent ${agent} \xB7 model ${model} \xB7 perm ${state.permissionPreset}`,
30291
+ `workspace ${workspace}${version ? ` \xB7 version v${version}` : ""}`
30292
+ ];
30293
+ return React.createElement(
30294
+ Box,
30295
+ { alignSelf: "center", width, flexDirection: "column", borderStyle: "single", borderColor: "#334155", paddingX: 1, marginTop },
30296
+ ...rows.map((line, index) => React.createElement(SurfaceLine, { key: index, text: line ?? "", width: width - 4, backgroundColor: SURFACE.status, color: "white" }))
30297
+ );
30298
+ });
30299
+ SessionCommandsBox = React.memo(function SessionCommandsBox2({ marginTop = 1 }) {
30300
+ const width = promptPanelWidth();
30301
+ const contentWidth = Math.max(24, width - 4);
30302
+ const lines = initialSessionCommandLines(contentWidth);
30303
+ return React.createElement(
30304
+ Box,
30305
+ { alignSelf: "center", width, flexDirection: "column", borderStyle: "single", borderColor: "#334155", paddingX: 1, marginTop },
30306
+ ...lines.map(
30307
+ (line, index) => React.createElement(SurfaceLine, {
30308
+ key: index,
30309
+ text: line,
30310
+ width: contentWidth,
30311
+ backgroundColor: SURFACE.status,
30312
+ color: index === 0 ? "magenta" : "gray",
30313
+ bold: index === 0
30314
+ })
30315
+ )
30316
+ );
30317
+ });
29365
30318
  DEMIAN_WORDMARK_LINES = [
29366
30319
  " _ _ ",
29367
30320
  " __| | ___ _ __ ___ (_) __ _ _ __ ",
@@ -29369,6 +30322,56 @@ var init_app = __esm({
29369
30322
  "| (_| | __/ | | | | | | (_| | | | |",
29370
30323
  " \\__,_|\\___|_| |_| |_|_|\\__,_|_| |_|"
29371
30324
  ];
30325
+ DemianWordmark = React.memo(function DemianWordmark2() {
30326
+ if (terminalWidth() < 58) {
30327
+ return React.createElement(Box, { alignSelf: "center" }, React.createElement(Text, { color: "gray", bold: true }, "DEMIAN"));
30328
+ }
30329
+ return React.createElement(
30330
+ Box,
30331
+ { alignSelf: "center", flexDirection: "column" },
30332
+ ...DEMIAN_WORDMARK_LINES.map(
30333
+ (line, index) => React.createElement(Text, { key: index, color: index < 2 ? "gray" : "white", bold: true }, line)
30334
+ )
30335
+ );
30336
+ });
30337
+ TranscriptView = React.memo(function TranscriptView2({ state }) {
30338
+ const blocks = state.blocks.slice(-12);
30339
+ const streaming = !state.streamingFinalized && state.streamingText ? { kind: "assistant", title: "Assistant streaming", lines: streamingLines(state.streamingText) } : void 0;
30340
+ const width = transcriptPanelWidth();
30341
+ const contentWidth = Math.max(24, width - 2);
30342
+ return React.createElement(
30343
+ Box,
30344
+ { alignSelf: "center", width, flexDirection: "column", marginTop: 1 },
30345
+ ...blocks.map((block) => React.createElement(BlockView, { key: block.id, block, width: contentWidth })),
30346
+ streaming ? React.createElement(BlockView, { key: "streaming", block: streaming, width: contentWidth }) : null
30347
+ );
30348
+ });
30349
+ BlockView = React.memo(function BlockView2({ block, width }) {
30350
+ if (block.kind === "tool") return React.createElement(ToolBlockView, { block, width });
30351
+ if (block.kind === "goal-work") return React.createElement(GoalWorkBlockView, { block, width });
30352
+ if (block.kind === "cowork") return React.createElement(CoworkBlockView, { block, width });
30353
+ if (block.kind === "user") return React.createElement(UserBlockView, { block, width });
30354
+ const color = block.kind === "user" ? "green" : block.kind === "assistant" ? "white" : block.kind === "warning" ? "yellow" : "magenta";
30355
+ const lines = wrapBlockLines(block.lines, width, 80);
30356
+ return React.createElement(
30357
+ Box,
30358
+ { flexDirection: "column", marginBottom: 1 },
30359
+ React.createElement(Text, { color, bold: true }, fitToWidth(block.title, width)),
30360
+ ...lines.map((line, index) => React.createElement(Text, { key: index }, line || " "))
30361
+ );
30362
+ }, sameBlockViewProps);
30363
+ CommandGuide = React.memo(function CommandGuide2({ hint, color, error }) {
30364
+ return React.createElement(
30365
+ Box,
30366
+ { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
30367
+ error ? React.createElement(Text, { color: "yellow" }, error) : null,
30368
+ React.createElement(
30369
+ Box,
30370
+ { justifyContent: "flex-end" },
30371
+ React.createElement(Text, { color }, hint)
30372
+ )
30373
+ );
30374
+ });
29372
30375
  }
29373
30376
  });
29374
30377
 
@@ -34096,17 +35099,17 @@ function findDependencyCycle(group) {
34096
35099
  const byId = new Map(group.map((member) => [member.memberId, member]));
34097
35100
  const visiting = /* @__PURE__ */ new Set();
34098
35101
  const visited = /* @__PURE__ */ new Set();
34099
- const path37 = [];
35102
+ const path38 = [];
34100
35103
  const visit = (id) => {
34101
- if (visiting.has(id)) return [...path37.slice(path37.indexOf(id)), id];
35104
+ if (visiting.has(id)) return [...path38.slice(path38.indexOf(id)), id];
34102
35105
  if (visited.has(id)) return void 0;
34103
35106
  visiting.add(id);
34104
- path37.push(id);
35107
+ path38.push(id);
34105
35108
  for (const dep of byId.get(id)?.dependsOn ?? []) {
34106
35109
  const cycle = visit(dep);
34107
35110
  if (cycle) return cycle;
34108
35111
  }
34109
- path37.pop();
35112
+ path38.pop();
34110
35113
  visiting.delete(id);
34111
35114
  visited.add(id);
34112
35115
  return void 0;
@@ -35406,6 +36409,9 @@ function providerOptions(config, catalogs = {}) {
35406
36409
  permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
35407
36410
  ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
35408
36411
  available,
36412
+ baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
36413
+ apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
36414
+ auth: sanitizeProviderAuth(provider.auth),
35409
36415
  catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
35410
36416
  };
35411
36417
  }).sort((left, right) => {
@@ -35476,6 +36482,13 @@ function providerCatalogAvailable(catalog) {
35476
36482
  function unavailableLabel(value) {
35477
36483
  return `(n/a) ${value}`;
35478
36484
  }
36485
+ function sanitizeProviderAuth(auth) {
36486
+ if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
36487
+ const record = auth;
36488
+ const type = typeof record.type === "string" ? record.type : void 0;
36489
+ const header = typeof record.header === "string" ? record.header : void 0;
36490
+ return type || header ? { type, header } : void 0;
36491
+ }
35479
36492
  function errorMessage4(error) {
35480
36493
  return error instanceof Error ? error.message : String(error);
35481
36494
  }
@@ -35706,12 +36719,14 @@ __export(controller_exports, {
35706
36719
  runTuiSession: () => runTuiSession
35707
36720
  });
35708
36721
  import path36 from "node:path";
36722
+ import { existsSync as existsSync5 } from "node:fs";
35709
36723
  async function runTuiSession(flags, store) {
35710
36724
  const cwd = path36.resolve(flags.cwd ?? process.cwd());
35711
36725
  const eventBus = new EventBus();
35712
36726
  eventBus.subscribe((event) => store.handleEvent(event));
35713
- const loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
35714
- const config = flags.context ? mergeConfig(loadedConfig, {
36727
+ const configInit = await ensureTuiConfigExists(flags);
36728
+ let loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
36729
+ let config = flags.context ? mergeConfig(loadedConfig, {
35715
36730
  context: {
35716
36731
  main: flags.context
35717
36732
  }
@@ -35731,6 +36746,10 @@ async function runTuiSession(flags, store) {
35731
36746
  store.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
35732
36747
  agent: agentName,
35733
36748
  cwd,
36749
+ configPath: configMutationPath(flags),
36750
+ configCreated: configInit.created,
36751
+ configDefaultProvider: config.defaultProvider,
36752
+ configMessage: configInit.created ? `Created config at ${configInit.path}` : void 0,
35734
36753
  agentOptions,
35735
36754
  contextEfficiency: {
35736
36755
  maxContextTokens: config.context.main.maxContextTokens,
@@ -35739,6 +36758,7 @@ async function runTuiSession(flags, store) {
35739
36758
  }
35740
36759
  });
35741
36760
  let nextPrompt = flags.prompt;
36761
+ let openConfigOnFirstPrompt = configInit.created && !flags.prompt.trim();
35742
36762
  const conversationsEnabled = flags.conversationManagement === true;
35743
36763
  const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
35744
36764
  let currentConversation = conversationState?.current;
@@ -35779,13 +36799,28 @@ async function runTuiSession(flags, store) {
35779
36799
  await applyStartupSessionSelection(selected);
35780
36800
  }
35781
36801
  for (; ; ) {
35782
- const prompt = await store.requestPrompt(nextPrompt);
36802
+ const promptPromise = store.requestPrompt(nextPrompt);
36803
+ if (openConfigOnFirstPrompt) {
36804
+ openConfigOnFirstPrompt = false;
36805
+ store.openConfigManager(`Created config at ${configInit.path}`);
36806
+ }
36807
+ const prompt = await promptPromise;
35783
36808
  nextPrompt = void 0;
35784
36809
  if (store.exitRequested()) {
35785
36810
  await saveCurrentSelection();
35786
36811
  return 0;
35787
36812
  }
35788
36813
  if (!prompt) return 0;
36814
+ const configAction = parseTuiConfigAction(prompt);
36815
+ if (configAction) {
36816
+ await handleConfigAction(configAction);
36817
+ continue;
36818
+ }
36819
+ const sessionAction = parseTuiSessionAction(prompt);
36820
+ if (sessionAction) {
36821
+ await handleSessionSelectionShortcut();
36822
+ continue;
36823
+ }
35789
36824
  const sessionCommand = parseSessionCommand(prompt);
35790
36825
  if (sessionCommand) {
35791
36826
  await handleSessionCommand(sessionCommand);
@@ -36020,22 +37055,13 @@ ${sessionListMessage()}`);
36020
37055
  return;
36021
37056
  }
36022
37057
  if (command.action === "delete") {
36023
- const target = findConversation(sortedConversations(), command.target);
37058
+ const target = command.target ? findConversation(sortedConversations(), command.target) : currentConversation;
36024
37059
  if (!target) {
36025
37060
  store.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".`);
36026
37061
  store.prepareForPrompt("waiting for next message");
36027
37062
  return;
36028
37063
  }
36029
- conversationRecords.delete(target.id);
36030
- runtimeByConversation.delete(target.id);
36031
- await deleteConversationRecord(flags.conversationStorageDir, target.id);
36032
- if (currentConversation?.id === target.id) {
36033
- const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
36034
- conversationRecords.set(next.id, next);
36035
- await switchConversation(next);
36036
- }
36037
- store.prepareForPrompt(`deleted session: ${target.title}`);
36038
- await persistCurrentConversation();
37064
+ await deleteConversationById(target.id);
36039
37065
  return;
36040
37066
  }
36041
37067
  if (command.action === "rename") {
@@ -36062,6 +37088,29 @@ ${sessionListMessage()}`);
36062
37088
  await persistCurrentConversation();
36063
37089
  }
36064
37090
  }
37091
+ async function handleSessionSelectionShortcut() {
37092
+ if (!conversationsEnabled) {
37093
+ store.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
37094
+ store.prepareForPrompt("waiting for next message");
37095
+ return;
37096
+ }
37097
+ if (!currentConversation) {
37098
+ currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
37099
+ conversationRecords.set(currentConversation.id, currentConversation);
37100
+ }
37101
+ await persistCurrentConversation();
37102
+ const selection = await store.requestSessionSelection(startupSessionOptions(), { cancel: "prompt" });
37103
+ if (selection.kind === "cancel") {
37104
+ store.prepareForPrompt("waiting for next message");
37105
+ return;
37106
+ }
37107
+ if (selection.kind === "exit" || store.exitRequested()) return;
37108
+ if (selection.kind === "delete") {
37109
+ await deleteConversationById(selection.id);
37110
+ return;
37111
+ }
37112
+ await applyStartupSessionSelection(selection);
37113
+ }
36065
37114
  function startupSessionOptions() {
36066
37115
  const records = sortedConversations();
36067
37116
  const sessionOptions = records.map((record) => ({
@@ -36076,6 +37125,14 @@ ${sessionListMessage()}`);
36076
37125
  return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
36077
37126
  }
36078
37127
  async function applyStartupSessionSelection(selection) {
37128
+ if (selection.kind === "cancel") {
37129
+ store.prepareForPrompt("waiting for next message");
37130
+ return;
37131
+ }
37132
+ if (selection.kind === "delete") {
37133
+ await deleteConversationById(selection.id);
37134
+ return;
37135
+ }
36079
37136
  if (selection.kind === "new") {
36080
37137
  const next = createConversationRecord({ id: createRootSessionId(), cwd });
36081
37138
  conversationRecords.set(next.id, next);
@@ -36097,6 +37154,24 @@ ${sessionListMessage()}`);
36097
37154
  store.prepareForPrompt(`session ready: ${target.title}`);
36098
37155
  await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], target.id);
36099
37156
  }
37157
+ async function deleteConversationById(id) {
37158
+ const target = conversationRecords.get(id) ?? findConversation(sortedConversations(), id);
37159
+ if (!target) {
37160
+ store.showSystemMessage("Sessions", `No session matched "${id}".`);
37161
+ store.prepareForPrompt("waiting for next message");
37162
+ return;
37163
+ }
37164
+ conversationRecords.delete(target.id);
37165
+ runtimeByConversation.delete(target.id);
37166
+ await deleteConversationRecord(flags.conversationStorageDir, target.id);
37167
+ if (currentConversation?.id === target.id) {
37168
+ const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
37169
+ conversationRecords.set(next.id, next);
37170
+ await switchConversation(next);
37171
+ }
37172
+ store.prepareForPrompt(`deleted session: ${target.title}`);
37173
+ await persistCurrentConversation();
37174
+ }
36100
37175
  async function switchConversation(record) {
36101
37176
  currentConversation = record;
36102
37177
  rootSessionId = record.id;
@@ -36132,6 +37207,87 @@ ${sessionListMessage()}`);
36132
37207
  await preferenceStore.save(selection);
36133
37208
  savedSelectionKey = key;
36134
37209
  }
37210
+ async function handleConfigAction(action) {
37211
+ const path38 = configMutationPath(flags);
37212
+ try {
37213
+ let message = "Config updated.";
37214
+ let nextSelection = {};
37215
+ if (action.type === "setDefaultProvider") {
37216
+ await updateConfigDefaults({ path: path38, defaultProvider: action.provider });
37217
+ message = `Default provider saved: ${action.provider}`;
37218
+ nextSelection = { provider: action.provider };
37219
+ } else if (action.type === "setDefaultModel") {
37220
+ await setDefaultModelProfile({
37221
+ path: path38,
37222
+ provider: action.provider,
37223
+ profileName: action.profileName,
37224
+ name: action.name,
37225
+ displayName: action.displayName,
37226
+ model: action.model
37227
+ });
37228
+ message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
37229
+ nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
37230
+ } else if (action.type === "addProvider") {
37231
+ await addProvider({ path: path38, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
37232
+ message = `Provider added: ${action.name}`;
37233
+ nextSelection = { provider: action.name };
37234
+ } else if (action.type === "updateProvider") {
37235
+ await updateProviderSettings({
37236
+ path: path38,
37237
+ provider: action.provider,
37238
+ ...action.field === "baseURL" ? { baseURL: action.value } : {},
37239
+ ...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
37240
+ ...action.field === "authHeader" ? { authHeader: action.value } : {}
37241
+ });
37242
+ message = `Provider ${action.field} saved: ${action.provider}`;
37243
+ nextSelection = { provider: action.provider };
37244
+ }
37245
+ await reloadConfigSettings(message, nextSelection);
37246
+ } catch (error) {
37247
+ store.prepareForPrompt("config action failed");
37248
+ store.showSystemMessage("Config", errorMessage(error));
37249
+ store.openConfigManager(errorMessage(error));
37250
+ }
37251
+ }
37252
+ async function reloadConfigSettings(message, selectionFlags = {}) {
37253
+ loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
37254
+ config = flags.context ? mergeConfig(loadedConfig, {
37255
+ context: {
37256
+ main: flags.context
37257
+ }
37258
+ }) : loadedConfig;
37259
+ const currentSelection = store.currentSelection();
37260
+ const selection = createInteractiveModelSelection(
37261
+ config,
37262
+ selectionFlags.provider || selectionFlags.model ? selectionFlags : { provider: currentSelection.providerName, model: currentSelection.model }
37263
+ );
37264
+ store.configureSettings(selection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
37265
+ agent: store.currentAgentName(),
37266
+ cwd,
37267
+ configPath: configMutationPath(flags),
37268
+ configDefaultProvider: config.defaultProvider,
37269
+ configMessage: message,
37270
+ agentOptions,
37271
+ contextEfficiency: {
37272
+ maxContextTokens: config.context.main.maxContextTokens,
37273
+ compactAtRatio: config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5,
37274
+ compactAtTokens: config.context.main.maxContextTokens ? Math.floor(config.context.main.maxContextTokens * (config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5)) : void 0
37275
+ }
37276
+ });
37277
+ store.openConfigManager(message);
37278
+ }
37279
+ }
37280
+ async function ensureTuiConfigExists(flags) {
37281
+ const target = configMutationPath(flags);
37282
+ if (!flags.configPath) {
37283
+ const jsondPath = target.replace(/\.json$/i, ".jsond");
37284
+ if (existsSync5(target) || existsSync5(jsondPath)) return { path: target, created: false };
37285
+ }
37286
+ const result = await createUserConfig({ path: target });
37287
+ return { path: result.path, created: result.created };
37288
+ }
37289
+ function configMutationPath(flags) {
37290
+ return flags.configPath ? path36.resolve(flags.configPath) : defaultUserConfigPath();
36135
37291
  }
36136
37292
  function isGoalStateClearingAction(action) {
36137
37293
  return action === "status" || action === "pause" || action === "resume";
@@ -36158,7 +37314,7 @@ function sessionUsage() {
36158
37314
  " /session new [title]",
36159
37315
  " /session switch <number|id|title>",
36160
37316
  " /session rename <title>",
36161
- " /session delete <number|id|title>",
37317
+ " /session delete [number|id|title]",
36162
37318
  " /session clear",
36163
37319
  "",
36164
37320
  "Aliases: /sessions, /conversation, /conversations, /conv"
@@ -36170,6 +37326,7 @@ var init_controller = __esm({
36170
37326
  init_registry();
36171
37327
  init_types();
36172
37328
  init_config();
37329
+ init_config_scaffold();
36173
37330
  init_execution();
36174
37331
  init_events();
36175
37332
  init_runtime();
@@ -36184,10 +37341,156 @@ var init_controller = __esm({
36184
37341
  init_history();
36185
37342
  init_preferences();
36186
37343
  init_settings();
37344
+ init_store();
36187
37345
  init_conversations();
36188
37346
  }
36189
37347
  });
36190
37348
 
37349
+ // src/ui/tui/engine/ink-engine.ts
37350
+ var ink_engine_exports = {};
37351
+ __export(ink_engine_exports, {
37352
+ runInkTuiEngine: () => runInkTuiEngine
37353
+ });
37354
+ async function runInkTuiEngine(options) {
37355
+ const [{ render }, ReactModule, { TuiApp: TuiApp2 }, { TuiStore: TuiStore2 }, { runTuiSession: runTuiSession2 }] = await Promise.all([
37356
+ dynamicImport4("ink"),
37357
+ dynamicImport4("react"),
37358
+ Promise.resolve().then(() => (init_app(), app_exports)),
37359
+ Promise.resolve().then(() => (init_store(), store_exports)),
37360
+ Promise.resolve().then(() => (init_controller(), controller_exports))
37361
+ ]);
37362
+ const React2 = ReactModule.default;
37363
+ const store = new TuiStore2();
37364
+ const instance = render(React2.createElement(TuiApp2, { store, version: options.version }));
37365
+ try {
37366
+ const code = await runTuiSession2({ ...options.flags, conversationManagement: true }, store);
37367
+ setTimeout(() => instance.unmount(), 100);
37368
+ await instance.waitUntilExit();
37369
+ return code;
37370
+ } catch (error) {
37371
+ instance.unmount();
37372
+ await instance.waitUntilExit().catch(() => void 0);
37373
+ throw error;
37374
+ }
37375
+ }
37376
+ var dynamicImport4;
37377
+ var init_ink_engine = __esm({
37378
+ "src/ui/tui/engine/ink-engine.ts"() {
37379
+ "use strict";
37380
+ dynamicImport4 = new Function("specifier", "return import(specifier)");
37381
+ }
37382
+ });
37383
+
37384
+ // src/ui/tui/engine/bubbletea-engine.ts
37385
+ var bubbletea_engine_exports = {};
37386
+ __export(bubbletea_engine_exports, {
37387
+ BubbleTeaSidecarUnavailableError: () => BubbleTeaSidecarUnavailableError,
37388
+ bubbleTeaSidecarCandidates: () => bubbleTeaSidecarCandidates,
37389
+ bubbleTeaSidecarPlatformKey: () => bubbleTeaSidecarPlatformKey,
37390
+ resolveBubbleTeaSidecarBinary: () => resolveBubbleTeaSidecarBinary,
37391
+ runBubbleTeaTuiEngine: () => runBubbleTeaTuiEngine
37392
+ });
37393
+ import { access } from "node:fs/promises";
37394
+ import { constants } from "node:fs";
37395
+ import { spawn as spawn8 } from "node:child_process";
37396
+ import path37 from "node:path";
37397
+ import { fileURLToPath } from "node:url";
37398
+ function bubbleTeaSidecarPlatformKey(platform = process.platform, arch = process.arch) {
37399
+ if (platform === "darwin" && arch === "arm64") return "darwin-arm64";
37400
+ if (platform === "linux" && arch === "x64") return "linux-x64";
37401
+ return void 0;
37402
+ }
37403
+ function bubbleTeaSidecarCandidates(platformKey = bubbleTeaSidecarPlatformKey(), moduleUrl = import.meta.url) {
37404
+ if (!platformKey) return [];
37405
+ const names = [
37406
+ new URL(`./tui-sidecar/${platformKey}/demian-tui`, moduleUrl),
37407
+ new URL(`../../../../dist/tui-sidecar/${platformKey}/demian-tui`, moduleUrl)
37408
+ ].map((url) => fileURLToPath(url));
37409
+ return [...new Set(names.map((name) => path37.resolve(name)))];
37410
+ }
37411
+ async function resolveBubbleTeaSidecarBinary(options = {}) {
37412
+ const platformKey = options.platformKey ?? bubbleTeaSidecarPlatformKey();
37413
+ if (!platformKey) {
37414
+ throw new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar is not packaged for ${process.platform}-${process.arch}.`);
37415
+ }
37416
+ const candidates = options.candidates ?? bubbleTeaSidecarCandidates(platformKey);
37417
+ for (const candidate of candidates) {
37418
+ try {
37419
+ await access(candidate, constants.X_OK);
37420
+ return candidate;
37421
+ } catch {
37422
+ }
37423
+ }
37424
+ throw new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar binary was not found for ${platformKey}. Run npm run build:tui-sidecar.`);
37425
+ }
37426
+ async function runBubbleTeaTuiEngine(options) {
37427
+ try {
37428
+ const binaryPath = await resolveBubbleTeaSidecarBinary();
37429
+ const probe = await runSidecarProbe(binaryPath);
37430
+ process.stderr.write(`Bubble Tea sidecar ${probe.version ?? "unknown"} is available, but IPC rendering is still gated; falling back to Ink for this run.
37431
+ `);
37432
+ } catch (error) {
37433
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)} Falling back to Ink TUI.
37434
+ `);
37435
+ }
37436
+ return runInkTuiEngine(options);
37437
+ }
37438
+ async function runSidecarProbe(binaryPath, timeoutMs = 1500) {
37439
+ return await new Promise((resolve, reject) => {
37440
+ const child = spawn8(binaryPath, ["--probe"], { stdio: ["ignore", "pipe", "pipe"] });
37441
+ let stdout = "";
37442
+ let stderr = "";
37443
+ let settled = false;
37444
+ const timer = setTimeout(() => {
37445
+ if (settled) return;
37446
+ settled = true;
37447
+ child.kill("SIGTERM");
37448
+ reject(new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar startup timed out after ${timeoutMs}ms.`));
37449
+ }, timeoutMs);
37450
+ child.stdout?.setEncoding("utf8");
37451
+ child.stderr?.setEncoding("utf8");
37452
+ child.stdout?.on("data", (chunk) => {
37453
+ stdout += chunk;
37454
+ });
37455
+ child.stderr?.on("data", (chunk) => {
37456
+ stderr += chunk;
37457
+ });
37458
+ child.on("error", (error) => {
37459
+ if (settled) return;
37460
+ settled = true;
37461
+ clearTimeout(timer);
37462
+ reject(new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar failed to start: ${error.message}`, { cause: error }));
37463
+ });
37464
+ child.on("close", (code) => {
37465
+ if (settled) return;
37466
+ settled = true;
37467
+ clearTimeout(timer);
37468
+ if (code !== 0) {
37469
+ reject(new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar probe exited with ${code ?? "unknown"}${stderr ? `: ${stderr.trim()}` : ""}`));
37470
+ return;
37471
+ }
37472
+ try {
37473
+ resolve(JSON.parse(stdout));
37474
+ } catch (error) {
37475
+ reject(new BubbleTeaSidecarUnavailableError("Bubble Tea sidecar probe returned invalid JSON.", { cause: error }));
37476
+ }
37477
+ });
37478
+ });
37479
+ }
37480
+ var BubbleTeaSidecarUnavailableError;
37481
+ var init_bubbletea_engine = __esm({
37482
+ "src/ui/tui/engine/bubbletea-engine.ts"() {
37483
+ "use strict";
37484
+ init_ink_engine();
37485
+ BubbleTeaSidecarUnavailableError = class extends Error {
37486
+ constructor(message, options) {
37487
+ super(message, options);
37488
+ this.name = "BubbleTeaSidecarUnavailableError";
37489
+ }
37490
+ };
37491
+ }
37492
+ });
37493
+
36191
37494
  // src/tui.ts
36192
37495
  init_parser();
36193
37496
  import { readFileSync as readFileSync2 } from "node:fs";
@@ -36417,283 +37720,16 @@ Flags:
36417
37720
  }
36418
37721
 
36419
37722
  // src/config-command.ts
37723
+ init_config_scaffold();
37724
+ init_config();
37725
+ init_catalog();
37726
+ init_claudecode_auth_preflight();
37727
+ init_claudecode_paths();
36420
37728
  import { spawn as spawn4 } from "node:child_process";
36421
37729
  import { stat as stat4 } from "node:fs/promises";
36422
37730
  import os11 from "node:os";
36423
37731
  import path17 from "node:path";
36424
37732
  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
37733
  async function maybeRunConfigCommand(argv) {
36698
37734
  if (argv[0] === "config") return runConfigCommand(argv.slice(1));
36699
37735
  if (argv[0] === "auth") return runAuthCommand(argv.slice(1));
@@ -36807,6 +37843,41 @@ async function runConfigCommand(argv) {
36807
37843
  force
36808
37844
  });
36809
37845
  process.stdout.write(`Updated config: ${result.path}
37846
+ `);
37847
+ return 0;
37848
+ }
37849
+ if (command === "set-default-model") {
37850
+ const [provider, ...tail2] = rest;
37851
+ if (!provider) throw new Error("config set-default-model requires a provider name.");
37852
+ const flags = parseFlags(tail2);
37853
+ const result = await setDefaultModelProfile({
37854
+ path: stringFlag(flags, "path"),
37855
+ provider,
37856
+ profileName: stringFlag(flags, "profile"),
37857
+ name: stringFlag(flags, "name"),
37858
+ displayName: stringFlag(flags, "display-name"),
37859
+ model: stringFlag(flags, "model"),
37860
+ baseURL: stringFlag(flags, "base-url"),
37861
+ apiKey: await apiKeyFromFlags(flags),
37862
+ apiKeyEnv: stringFlag(flags, "api-key-env")
37863
+ });
37864
+ process.stdout.write(`Updated config: ${result.path}
37865
+ `);
37866
+ return 0;
37867
+ }
37868
+ if (command === "update-provider") {
37869
+ const [provider, ...tail2] = rest;
37870
+ if (!provider) throw new Error("config update-provider requires a provider name.");
37871
+ const flags = parseFlags(tail2);
37872
+ const result = await updateProviderSettings({
37873
+ path: stringFlag(flags, "path"),
37874
+ provider,
37875
+ baseURL: stringFlagOrEmpty(flags, "base-url"),
37876
+ apiKey: await apiKeyFromFlags(flags),
37877
+ apiKeyEnv: stringFlagOrEmpty(flags, "api-key-env"),
37878
+ authHeader: stringFlagOrEmpty(flags, "auth-header")
37879
+ });
37880
+ process.stdout.write(`Updated config: ${result.path}
36810
37881
  `);
36811
37882
  return 0;
36812
37883
  }
@@ -36904,6 +37975,10 @@ function stringFlag(flags, name) {
36904
37975
  const value = flags.get(name);
36905
37976
  return typeof value === "string" && value.trim() ? value : void 0;
36906
37977
  }
37978
+ function stringFlagOrEmpty(flags, name) {
37979
+ const value = flags.get(name);
37980
+ return typeof value === "string" ? value : void 0;
37981
+ }
36907
37982
  function requiredStringFlag(flags, name) {
36908
37983
  const value = stringFlag(flags, name);
36909
37984
  if (!value) throw new Error(`--${name} is required.`);
@@ -36946,6 +38021,8 @@ Usage:
36946
38021
  demian config setup interactive multi-step provider wizard
36947
38022
  demian config add-provider <preset|openai-compatible> [--name <name>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--auth-header <header>] [--force]
36948
38023
  demian config add-model <provider> --name <name> --model <model> [--display-name <label>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--force]
38024
+ demian config set-default-model <provider> [--profile <name> | --model <model>] [--display-name <label>]
38025
+ demian config update-provider <provider> [--base-url <url>] [--api-key-env <env>] [--auth-header <header>]
36949
38026
  demian config models <provider> [--refresh] [--config <path>]
36950
38027
  demian config list [--config <path>]
36951
38028
  demian config path
@@ -37112,8 +38189,23 @@ async function checkCodexAuth() {
37112
38189
  return { ok: false, source: "none", message: "no codex auth file or OPENAI_API_KEY; run `demian auth login codex`" };
37113
38190
  }
37114
38191
 
38192
+ // src/ui/tui/engine/index.ts
38193
+ var TUI_ENGINE_NAMES = /* @__PURE__ */ new Set(["ink", "bubbletea"]);
38194
+ function resolveTuiEngineName(options = {}) {
38195
+ const raw = (options.cliEngine ?? options.env?.DEMIAN_TUI_ENGINE ?? "ink").trim().toLowerCase();
38196
+ if (TUI_ENGINE_NAMES.has(raw)) return raw;
38197
+ throw new Error(`Unsupported TUI engine "${raw}". Use "ink" or "bubbletea".`);
38198
+ }
38199
+ async function runTuiEngine(engine, options) {
38200
+ if (engine === "ink") {
38201
+ const { runInkTuiEngine: runInkTuiEngine2 } = await Promise.resolve().then(() => (init_ink_engine(), ink_engine_exports));
38202
+ return runInkTuiEngine2(options);
38203
+ }
38204
+ const { runBubbleTeaTuiEngine: runBubbleTeaTuiEngine2 } = await Promise.resolve().then(() => (init_bubbletea_engine(), bubbletea_engine_exports));
38205
+ return runBubbleTeaTuiEngine2(options);
38206
+ }
38207
+
37115
38208
  // src/tui.ts
37116
- var dynamicImport4 = new Function("specifier", "return import(specifier)");
37117
38209
  async function main(argv = process.argv.slice(2)) {
37118
38210
  const configCommandResult = await maybeRunConfigCommand(argv);
37119
38211
  if (configCommandResult !== void 0) return configCommandResult;
@@ -37129,28 +38221,9 @@ async function main(argv = process.argv.slice(2)) {
37129
38221
  process.stderr.write("demian TUI requires an interactive terminal. Use demian-plain for pipes or non-interactive use.\n");
37130
38222
  return 1;
37131
38223
  }
37132
- if (!flags.noWizard) await runFirstRunWizard().catch(() => void 0);
37133
38224
  try {
37134
- const [{ render }, ReactModule, { TuiApp: TuiApp2 }, { TuiStore: TuiStore2 }, { runTuiSession: runTuiSession2 }] = await Promise.all([
37135
- dynamicImport4("ink"),
37136
- dynamicImport4("react"),
37137
- Promise.resolve().then(() => (init_app(), app_exports)),
37138
- Promise.resolve().then(() => (init_store(), store_exports)),
37139
- Promise.resolve().then(() => (init_controller(), controller_exports))
37140
- ]);
37141
- const React2 = ReactModule.default;
37142
- const store = new TuiStore2();
37143
- const instance = render(React2.createElement(TuiApp2, { store, version: packageVersion() }));
37144
- try {
37145
- const code = await runTuiSession2({ ...flags, conversationManagement: true }, store);
37146
- setTimeout(() => instance.unmount(), 100);
37147
- await instance.waitUntilExit();
37148
- return code;
37149
- } catch (error) {
37150
- instance.unmount();
37151
- await instance.waitUntilExit().catch(() => void 0);
37152
- throw error;
37153
- }
38225
+ const engine = resolveTuiEngineName({ cliEngine: flags.engine, env: process.env });
38226
+ return await runTuiEngine(engine, { flags, version: packageVersion() });
37154
38227
  } catch (error) {
37155
38228
  if (isMissingDependency(error)) {
37156
38229
  process.stderr.write("TUI dependencies are not installed. Run `npm install` in demian, or use `demian-plain`.\n");
@@ -37212,6 +38285,14 @@ function parseArgs(argv) {
37212
38285
  out.goal = true;
37213
38286
  continue;
37214
38287
  }
38288
+ if (arg === "--engine") {
38289
+ out.engine = takeValue2(argv, ++i, arg);
38290
+ continue;
38291
+ }
38292
+ if (arg.startsWith("--engine=")) {
38293
+ out.engine = arg.slice("--engine=".length);
38294
+ continue;
38295
+ }
37215
38296
  if (arg === "--image") {
37216
38297
  out.images.push(takeValue2(argv, ++i, arg));
37217
38298
  continue;
@@ -37290,14 +38371,16 @@ Default:
37290
38371
  Press Esc then p to select provider, Esc then m to edit model, then Enter to send.
37291
38372
  Press Esc then a to select the main agent before sending a message.
37292
38373
  Press Esc then o to select the default permission preset: deny, ask, auto, or full.
38374
+ Press Esc then c to configure provider defaults and model profiles.
38375
+ Press Esc then k to open the command palette, or Esc then s to select a saved session.
37293
38376
  Press up/down in the message composer to recall previous prompts.
37294
38377
  Provider/model selections are remembered in ~/.demian/preferences.json.
37295
38378
  After each answer, the TUI returns to standby for the next message.
37296
38379
  Use /cowork <task> to explicitly ask Demian to coordinate cowork sub agents in multi-agent mode.
37297
38380
  Use /compact to compact current history, /stop to stop active work, and /exit or /quit to close the TUI.
37298
38381
  Use /session list, /session new, /session switch <number|id|title>, /session rename <title>,
37299
- 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.
38382
+ and /session delete [number|id|title] to manage saved CLI conversations.
38383
+ Press Esc then q to quit, Esc then s while running to stop the task, and Esc then u to clear the composer.
37301
38384
  Tool approvals show the requested command/path in a permission dialog.
37302
38385
  Press y to allow once, a to always allow the grant scope, or n/Enter to deny.
37303
38386
  Prompt arguments are still accepted as a compatibility shortcut.
@@ -37325,6 +38408,8 @@ Flags:
37325
38408
  --no-wizard skip the first-run config wizard
37326
38409
  --goal run the initial prompt as an explicit /goal objective
37327
38410
  /ralph-loop --fresh-context is single-agent only
38411
+ --engine <ink|bubbletea>
38412
+ select the TUI renderer; defaults to ink during migration
37328
38413
  --config <path> load an additional config file
37329
38414
  --help, -h show this help
37330
38415