demian-cli 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui.mjs CHANGED
@@ -23573,11 +23573,18 @@ function providerModelProfiles(providerName, providerConfig) {
23573
23573
  return out;
23574
23574
  }
23575
23575
  function providerModelOptions(providerName, providerConfig) {
23576
- return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => profile.displayName ?? profile.name ?? profile.model);
23576
+ return providerModelProfiles(providerName, providerConfig).filter((profile) => !profile.hidden).map((profile) => displayModelForProfile(profile));
23577
23577
  }
23578
23578
  function defaultModelForProvider(providerName, providerConfig) {
23579
23579
  return providerModelProfiles(providerName, providerConfig)[0]?.model ?? "";
23580
23580
  }
23581
+ function displayModelForProfile(profile) {
23582
+ return profile.displayName?.trim() || profile.name?.trim() || profile.model;
23583
+ }
23584
+ function defaultDisplayModelForProvider(providerName, providerConfig) {
23585
+ const profile = providerModelProfiles(providerName, providerConfig)[0];
23586
+ return profile ? displayModelForProfile(profile) : defaultModelForProvider(providerName, providerConfig);
23587
+ }
23581
23588
  function resolveConfiguredApiKey(providerConfig) {
23582
23589
  if (providerConfig.apiKey) return { value: providerConfig.apiKey };
23583
23590
  for (const envName of [providerConfig.apiKeyEnv, ...providerConfig.apiKeyEnvAliases ?? []]) {
@@ -23614,7 +23621,7 @@ function mergeModelProfile(providerConfig, profile) {
23614
23621
  ...providerConfig,
23615
23622
  ...definedOnly({
23616
23623
  baseURL: profile.baseURL,
23617
- apiKey: profile.apiKey,
23624
+ apiKey: nonEmptyString(profile.apiKey),
23618
23625
  apiKeyEnv: profile.apiKeyEnv,
23619
23626
  apiKeyEnvAliases: profile.apiKeyEnvAliases,
23620
23627
  auth: profile.auth,
@@ -23629,6 +23636,9 @@ function mergeModelProfile(providerConfig, profile) {
23629
23636
  function definedOnly(input2) {
23630
23637
  return Object.fromEntries(Object.entries(input2).filter(([, value]) => value !== void 0));
23631
23638
  }
23639
+ function nonEmptyString(value) {
23640
+ return typeof value === "string" && value.length > 0 ? value : void 0;
23641
+ }
23632
23642
  function fallbackModelForProvider(providerName, providerConfig) {
23633
23643
  if (typeof providerConfig.model === "string" && providerConfig.model.trim()) return providerConfig.model.trim();
23634
23644
  if (providerName === "openai") return "gpt-5.5";
@@ -23932,6 +23942,7 @@ var init_config = __esm({
23932
23942
  defaultProvider: "brave",
23933
23943
  providers: {
23934
23944
  brave: {
23945
+ apiKey: "",
23935
23946
  apiKeyEnv: "BRAVE_SEARCH_API_KEY",
23936
23947
  endpoint: "https://api.search.brave.com/res/v1/web/search",
23937
23948
  count: 10,
@@ -23940,12 +23951,14 @@ var init_config = __esm({
23940
23951
  safeSearch: "moderate"
23941
23952
  },
23942
23953
  tavily: {
23954
+ apiKey: "",
23943
23955
  apiKeyEnv: "TAVILY_API_KEY",
23944
23956
  endpoint: "https://api.tavily.com/search",
23945
23957
  maxResults: 5,
23946
23958
  searchDepth: "basic"
23947
23959
  },
23948
23960
  exa: {
23961
+ apiKey: "",
23949
23962
  apiKeyEnv: "EXA_API_KEY",
23950
23963
  endpoint: "https://api.exa.ai/search",
23951
23964
  numResults: 5,
@@ -24000,6 +24013,7 @@ var init_config = __esm({
24000
24013
  openai: {
24001
24014
  type: "openai-compatible",
24002
24015
  baseURL: "https://api.openai.com/v1",
24016
+ apiKey: "",
24003
24017
  apiKeyEnv: "OPENAI_API_KEY",
24004
24018
  catalog: {
24005
24019
  type: "openai-models",
@@ -24009,6 +24023,7 @@ var init_config = __esm({
24009
24023
  anthropic: {
24010
24024
  type: "anthropic",
24011
24025
  baseURL: "https://api.anthropic.com/v1",
24026
+ apiKey: "",
24012
24027
  apiKeyEnv: "ANTHROPIC_API_KEY",
24013
24028
  catalog: {
24014
24029
  type: "anthropic-models",
@@ -24018,6 +24033,7 @@ var init_config = __esm({
24018
24033
  gemini: {
24019
24034
  type: "openai-compatible",
24020
24035
  baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
24036
+ apiKey: "",
24021
24037
  apiKeyEnv: "GEMINI_API_KEY",
24022
24038
  apiKeyEnvAliases: ["GOOGLE_API_KEY"],
24023
24039
  catalog: {
@@ -24028,6 +24044,7 @@ var init_config = __esm({
24028
24044
  groq: {
24029
24045
  type: "openai-compatible",
24030
24046
  baseURL: "https://api.groq.com/openai/v1",
24047
+ apiKey: "",
24031
24048
  apiKeyEnv: "GROQ_API_KEY",
24032
24049
  catalog: {
24033
24050
  type: "groq-models",
@@ -24043,6 +24060,7 @@ var init_config = __esm({
24043
24060
  displayName: "Azure example",
24044
24061
  model: "azure-deployment-name",
24045
24062
  baseURL: "https://example.openai.azure.com/openai/v1",
24063
+ apiKey: "",
24046
24064
  apiKeyEnv: "AZURE_OPENAI_API_KEY"
24047
24065
  }
24048
24066
  ]
@@ -24051,6 +24069,7 @@ var init_config = __esm({
24051
24069
  type: "openai-compatible",
24052
24070
  baseURL: "http://localhost:1234/v1",
24053
24071
  apiKey: "lm-studio",
24072
+ modelProfiles: [],
24054
24073
  catalog: {
24055
24074
  type: "openai-compatible-models",
24056
24075
  endpoint: "http://localhost:1234/v1/models"
@@ -24060,6 +24079,7 @@ var init_config = __esm({
24060
24079
  type: "openai-compatible",
24061
24080
  baseURL: "http://localhost:11434/v1",
24062
24081
  apiKey: "ollama",
24082
+ modelProfiles: [],
24063
24083
  quirks: { omitTemperature: true },
24064
24084
  catalog: {
24065
24085
  type: "openai-compatible-models",
@@ -24069,7 +24089,9 @@ var init_config = __esm({
24069
24089
  "ollama-cloud": {
24070
24090
  type: "ollama",
24071
24091
  baseURL: "https://ollama.com/api",
24092
+ apiKey: "",
24072
24093
  apiKeyEnv: "OLLAMA_API_KEY",
24094
+ modelProfiles: [],
24073
24095
  catalog: {
24074
24096
  type: "ollama-tags",
24075
24097
  endpoint: "https://ollama.com/api/tags"
@@ -24079,6 +24101,7 @@ var init_config = __esm({
24079
24101
  type: "openai-compatible",
24080
24102
  baseURL: "http://localhost:8080/v1",
24081
24103
  apiKey: "llama.cpp",
24104
+ modelProfiles: [],
24082
24105
  catalog: {
24083
24106
  type: "openai-compatible-models",
24084
24107
  endpoint: "http://localhost:8080/v1/models"
@@ -24088,6 +24111,7 @@ var init_config = __esm({
24088
24111
  type: "openai-compatible",
24089
24112
  baseURL: "http://localhost:8000/v1",
24090
24113
  apiKey: "vllm",
24114
+ modelProfiles: [],
24091
24115
  catalog: {
24092
24116
  type: "openai-compatible-models",
24093
24117
  endpoint: "http://localhost:8000/v1/models"
@@ -25703,7 +25727,7 @@ var init_store = __esm({
25703
25727
  }
25704
25728
  markTaskFailed(message) {
25705
25729
  const lines = [message];
25706
- if (this.#lastRetryPrompt()) lines.push("Press ctrl+r or type /retry to run the last task again.");
25730
+ if (this.#lastRetryPrompt()) lines.push("Press Esc then r or type /retry to run the last task again.");
25707
25731
  this.#append({ kind: "warning", title: "Task Failed", lines });
25708
25732
  this.#state.status.reason = "error";
25709
25733
  this.#state.inputMode = this.#exitRequested ? "done" : "prompt";
@@ -26963,9 +26987,10 @@ __export(app_exports, {
26963
26987
  });
26964
26988
  import React, { useEffect, useState } from "react";
26965
26989
  import { Box, Text, useApp, useInput } from "ink";
26966
- function TuiApp({ store }) {
26990
+ function TuiApp({ store, version = "dev" }) {
26967
26991
  const [state, setState] = useState(() => store.snapshot());
26968
26992
  const [now2, setNow] = useState(() => Date.now());
26993
+ const [shortcutMode, setShortcutMode] = useState(false);
26969
26994
  const app = useApp();
26970
26995
  useEffect(() => {
26971
26996
  const update = () => setState(store.snapshot());
@@ -26978,27 +27003,64 @@ function TuiApp({ store }) {
26978
27003
  const timer = setInterval(() => setNow(Date.now()), 450);
26979
27004
  return () => clearInterval(timer);
26980
27005
  }, [state.workPlan?.planId, state.inputMode]);
27006
+ useEffect(() => {
27007
+ setShortcutMode(false);
27008
+ }, [state.inputMode, state.permission !== void 0]);
26981
27009
  useInput((input2, key) => {
26982
27010
  const textInput = textInputForKeypress(input2, key);
26983
- if (key.ctrl && input2 === "c") {
26984
- store.requestExit();
26985
- app.exit();
26986
- return;
26987
- }
26988
- if (key.ctrl && input2 === "t") {
26989
- store.toggleLatestToolExpanded();
26990
- return;
26991
- }
26992
- if (key.ctrl && input2 === "p") {
26993
- store.toggleWorkPlanExpanded();
26994
- return;
26995
- }
26996
- if (key.ctrl && input2 === "r") {
26997
- store.retryLastPrompt();
26998
- return;
26999
- }
27000
- if (key.ctrl && input2 === "e") {
27001
- store.usePendingClaudeCodePlan();
27011
+ if (shortcutMode) {
27012
+ setShortcutMode(false);
27013
+ if (key.escape) return;
27014
+ const shortcut = input2.toLowerCase();
27015
+ if (shortcut === "q") {
27016
+ store.requestExit();
27017
+ app.exit();
27018
+ return;
27019
+ }
27020
+ if (shortcut === "s" && state.inputMode === "running") {
27021
+ store.stopActiveTask();
27022
+ return;
27023
+ }
27024
+ if (shortcut === "p" && state.inputMode === "prompt") {
27025
+ store.openProviderSelector();
27026
+ return;
27027
+ }
27028
+ if (shortcut === "a" && state.inputMode === "prompt") {
27029
+ store.openAgentSelector();
27030
+ return;
27031
+ }
27032
+ if (shortcut === "m" && state.inputMode === "prompt") {
27033
+ store.openModelEditor();
27034
+ return;
27035
+ }
27036
+ if (shortcut === "o" && state.inputMode === "prompt") {
27037
+ store.openPermissionPresetSelector();
27038
+ return;
27039
+ }
27040
+ if (shortcut === "u" && state.inputMode === "prompt") {
27041
+ store.clearPromptInput();
27042
+ return;
27043
+ }
27044
+ if (shortcut === "d" && state.turnDiff) {
27045
+ store.toggleDiffExpanded();
27046
+ return;
27047
+ }
27048
+ if (shortcut === "t" && state.blocks.some((block) => block.kind === "tool")) {
27049
+ store.toggleLatestToolExpanded();
27050
+ return;
27051
+ }
27052
+ if (shortcut === "w" && (state.workPlan || state.inputMode === "running")) {
27053
+ store.toggleWorkPlanExpanded();
27054
+ return;
27055
+ }
27056
+ if (shortcut === "e" && state.pendingClaudeCodePlan) {
27057
+ store.usePendingClaudeCodePlan();
27058
+ return;
27059
+ }
27060
+ if (shortcut === "r" && state.canRetryLastPrompt) {
27061
+ store.retryLastPrompt();
27062
+ return;
27063
+ }
27002
27064
  return;
27003
27065
  }
27004
27066
  if (state.permission) {
@@ -27070,10 +27132,6 @@ function TuiApp({ store }) {
27070
27132
  store.closeSettings();
27071
27133
  return;
27072
27134
  }
27073
- if (key.ctrl && input2 === "u") {
27074
- store.clearModelInput();
27075
- return;
27076
- }
27077
27135
  if (key.return) {
27078
27136
  if (textInput) store.appendModelInput(textInput);
27079
27137
  store.submitModelInput();
@@ -27087,10 +27145,6 @@ function TuiApp({ store }) {
27087
27145
  return;
27088
27146
  }
27089
27147
  if (state.inputMode === "prompt" || state.inputMode === "running") {
27090
- if (key.tab && state.turnDiff) {
27091
- store.toggleDiffExpanded();
27092
- return;
27093
- }
27094
27148
  if (state.inputMode === "prompt" && key.upArrow) {
27095
27149
  store.navigatePromptHistory(-1);
27096
27150
  return;
@@ -27099,24 +27153,8 @@ function TuiApp({ store }) {
27099
27153
  store.navigatePromptHistory(1);
27100
27154
  return;
27101
27155
  }
27102
- if (!key.return && state.inputMode === "prompt" && !state.promptInput && input2 === "p") {
27103
- store.openProviderSelector();
27104
- return;
27105
- }
27106
- if (!key.return && state.inputMode === "prompt" && !state.promptInput && input2 === "a") {
27107
- store.openAgentSelector();
27108
- return;
27109
- }
27110
- if (!key.return && state.inputMode === "prompt" && !state.promptInput && input2 === "m") {
27111
- store.openModelEditor();
27112
- return;
27113
- }
27114
- if (!key.return && state.inputMode === "prompt" && !state.promptInput && input2 === "o") {
27115
- store.openPermissionPresetSelector();
27116
- return;
27117
- }
27118
- if (key.ctrl && input2 === "u") {
27119
- store.clearPromptInput();
27156
+ if (key.escape) {
27157
+ setShortcutMode(true);
27120
27158
  return;
27121
27159
  }
27122
27160
  if (key.return) {
@@ -27132,17 +27170,29 @@ function TuiApp({ store }) {
27132
27170
  return;
27133
27171
  }
27134
27172
  });
27173
+ const shellProps = { flexDirection: "column", paddingX: 1, minHeight: terminalHeight() };
27174
+ if (shouldShowEmptyState(state)) {
27175
+ return React.createElement(
27176
+ Box,
27177
+ shellProps,
27178
+ React.createElement(EmptyState, { state, version, shortcutMode })
27179
+ );
27180
+ }
27135
27181
  return React.createElement(
27136
27182
  Box,
27137
- { flexDirection: "column", paddingX: 1 },
27138
- React.createElement(StatusBar, { state, now: now2 }),
27139
- React.createElement(SettingsBar, { state }),
27140
- React.createElement(shouldShowLoading(state) ? LoadingView : TranscriptView, { state }),
27141
- React.createElement(DiffAccordion, { state }),
27142
- React.createElement(WorkPlanPanel, { state, now: now2 }),
27143
- React.createElement(ActivityBar, { state }),
27144
- React.createElement(GoalIsland, { state }),
27145
- React.createElement(BottomBar, { state })
27183
+ shellProps,
27184
+ React.createElement(StatusBar, { state, now: now2, version }),
27185
+ React.createElement(
27186
+ Box,
27187
+ { flexDirection: "column", flexGrow: 1, paddingX: 1 },
27188
+ React.createElement(shouldShowLoading(state) ? LoadingView : TranscriptView, { state }),
27189
+ React.createElement(DiffAccordion, { state }),
27190
+ React.createElement(WorkPlanPanel, { state, now: now2 }),
27191
+ React.createElement(ActivityBar, { state }),
27192
+ React.createElement(GoalIsland, { state }),
27193
+ React.createElement(Box, { flexGrow: 1 }),
27194
+ React.createElement(InteractionPanel, { state, shortcutMode })
27195
+ )
27146
27196
  );
27147
27197
  }
27148
27198
  function textInputForKeypress(input2, key) {
@@ -27155,15 +27205,107 @@ function textInputForKeypress(input2, key) {
27155
27205
  function shouldShowLoading(state) {
27156
27206
  return state.inputMode === "starting" || state.inputMode === "running" && state.blocks.length === 0 && !state.streamingText;
27157
27207
  }
27208
+ function shouldShowEmptyState(state) {
27209
+ return state.inputMode === "prompt" && !state.permission && state.blocks.length === 0 && !state.streamingText && !state.turnDiff && !state.workPlan && !state.goal;
27210
+ }
27211
+ function EmptyState({ state, version, shortcutMode }) {
27212
+ const prompt = state.promptInput || 'Ask anything... "\uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uAD6C\uC870\uB97C \uC54C\uB824\uC918"';
27213
+ const hint = shortcutMode ? shortcutHelp(state) : "enter send \xB7 esc commands \xB7 / commands";
27214
+ const topGap = Math.max(2, Math.min(8, Math.floor(terminalHeight() * 0.18)));
27215
+ return React.createElement(
27216
+ Box,
27217
+ { flexDirection: "column", flexGrow: 1 },
27218
+ React.createElement(Box, { height: topGap }),
27219
+ React.createElement(DemianWordmark),
27220
+ React.createElement(Box, { height: 2 }),
27221
+ React.createElement(
27222
+ Box,
27223
+ { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", borderStyle: "double", borderColor: shortcutMode ? "cyan" : "blue", paddingX: 2, paddingY: 1 },
27224
+ React.createElement(Text, { color: state.promptInput ? "white" : "gray" }, prompt)
27225
+ ),
27226
+ React.createElement(SessionMetaBox, { state, version }),
27227
+ React.createElement(
27228
+ Box,
27229
+ { alignSelf: "center", width: promptPanelWidth(), justifyContent: "flex-end", marginTop: 1 },
27230
+ React.createElement(Text, { color: shortcutMode ? "cyan" : "gray" }, hint)
27231
+ ),
27232
+ state.promptError ? React.createElement(Box, { alignSelf: "center", width: promptPanelWidth(), marginTop: 1 }, React.createElement(Text, { color: "yellow" }, state.promptError)) : null,
27233
+ React.createElement(Box, { flexGrow: 1 })
27234
+ );
27235
+ }
27236
+ function SessionMetaBox({ state, version, marginTop = 1 }) {
27237
+ const width = promptPanelWidth();
27238
+ const compact = width < 68;
27239
+ const agent = shorten(state.selectedAgent ?? state.status.agent ?? "general", compact ? 14 : 18);
27240
+ const model = shorten(state.selection?.model ?? state.status.model ?? "model", compact ? Math.max(18, width - 14) : 30);
27241
+ const workspace = shorten(state.status.cwd ?? process.cwd(), compact ? Math.max(18, width - 18) : 46);
27242
+ const rows = compact ? [
27243
+ React.createElement(
27244
+ Box,
27245
+ { key: "mode" },
27246
+ React.createElement(MetaItem, { label: "agent", value: agent, color: "green" }),
27247
+ React.createElement(MetaDivider),
27248
+ React.createElement(MetaItem, { label: "perm", value: state.permissionPreset, color: "yellow" })
27249
+ ),
27250
+ React.createElement(Box, { key: "model" }, React.createElement(MetaItem, { label: "model", value: model, color: "cyan" })),
27251
+ React.createElement(Box, { key: "workspace" }, React.createElement(MetaItem, { label: "workspace", value: workspace, color: "white" })),
27252
+ version ? React.createElement(Box, { key: "version" }, React.createElement(MetaItem, { label: "version", value: `v${version}`, color: "gray" })) : null
27253
+ ].filter(Boolean) : [
27254
+ React.createElement(
27255
+ Box,
27256
+ { key: "mode" },
27257
+ React.createElement(MetaItem, { label: "agent", value: agent, color: "green" }),
27258
+ React.createElement(MetaDivider),
27259
+ React.createElement(MetaItem, { label: "model", value: model, color: "cyan" }),
27260
+ React.createElement(MetaDivider),
27261
+ React.createElement(MetaItem, { label: "perm", value: state.permissionPreset, color: "yellow" })
27262
+ ),
27263
+ React.createElement(
27264
+ Box,
27265
+ { key: "workspace" },
27266
+ React.createElement(MetaItem, { label: "workspace", value: workspace, color: "white" }),
27267
+ version ? React.createElement(MetaDivider) : null,
27268
+ version ? React.createElement(MetaItem, { label: "version", value: `v${version}`, color: "gray" }) : null
27269
+ )
27270
+ ];
27271
+ return React.createElement(
27272
+ Box,
27273
+ { alignSelf: "center", width, flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 2, marginTop },
27274
+ ...rows
27275
+ );
27276
+ }
27277
+ function MetaItem({ label, value, color }) {
27278
+ return React.createElement(
27279
+ Text,
27280
+ null,
27281
+ React.createElement(Text, { color: "gray" }, `${label} `),
27282
+ React.createElement(Text, { color }, value)
27283
+ );
27284
+ }
27285
+ function MetaDivider() {
27286
+ return React.createElement(Text, { color: "gray" }, " \xB7 ");
27287
+ }
27288
+ function DemianWordmark() {
27289
+ if (terminalWidth() < 58) {
27290
+ return React.createElement(Box, { alignSelf: "center" }, React.createElement(Text, { color: "gray", bold: true }, "DEMIAN"));
27291
+ }
27292
+ return React.createElement(
27293
+ Box,
27294
+ { alignSelf: "center", flexDirection: "column" },
27295
+ ...DEMIAN_WORDMARK_LINES.map(
27296
+ (line, index) => React.createElement(Text, { key: index, color: index < 2 ? "gray" : "white", bold: true }, line)
27297
+ )
27298
+ );
27299
+ }
27158
27300
  function LoadingView({ state }) {
27159
- const title = state.inputMode === "starting" ? "Loading Demian" : "Preparing session";
27301
+ const title = state.inputMode === "starting" ? "Demian runtime" : "Preparing session";
27160
27302
  const message = state.activity || (state.inputMode === "starting" ? "Loading configuration" : "Starting session");
27161
27303
  return React.createElement(
27162
27304
  Box,
27163
- { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 1, paddingY: 1 },
27305
+ { flexDirection: "column", marginTop: 1, borderStyle: "double", borderColor: "cyan", paddingX: 1, paddingY: 1 },
27164
27306
  React.createElement(Text, { color: "cyan", bold: true }, title),
27165
27307
  React.createElement(Text, { color: "white" }, message),
27166
- React.createElement(Text, { color: "gray" }, "Please wait while demian gets ready.")
27308
+ React.createElement(Text, { color: "gray" }, "Loading providers, tools, workspace state, and permissions.")
27167
27309
  );
27168
27310
  }
27169
27311
  function contextEfficiencySummary(state) {
@@ -27183,61 +27325,53 @@ function contextEfficiencySummary(state) {
27183
27325
  ].filter(Boolean);
27184
27326
  return parts.join(" | ");
27185
27327
  }
27186
- function StatusBar({ state, now: now2 }) {
27328
+ function StatusBar({ state, now: now2, version }) {
27187
27329
  const status = state.status;
27188
- const items = [
27189
- claudeCodeBadge(state),
27190
- status.provider ? `provider ${status.provider}` : void 0,
27191
- status.model ? `model ${status.model}` : void 0,
27192
- status.agent ? `agent ${status.agent}` : void 0,
27193
- status.cwd ? `cwd ${shorten(status.cwd, 48)}` : void 0,
27194
- status.externalSessionId ? `cc session ${shorten(status.externalSessionId, 18)}` : void 0,
27195
- status.reason ? `reason ${status.reason}` : void 0
27196
- ].filter(Boolean);
27330
+ const mode = tuiMode(state);
27331
+ const provider = state.selection?.providerName ?? status.provider;
27332
+ const model = state.selection?.model ?? status.model ?? "-";
27333
+ const meta = [provider, shorten(model, 32), state.permissionPreset, `v${version}`].filter(Boolean).join(" \xB7 ");
27197
27334
  return React.createElement(
27198
27335
  Box,
27199
- { borderStyle: "round", paddingX: 1 },
27200
- React.createElement(DemianTuiMark, { active: state.inputMode === "starting" || state.inputMode === "running", now: now2 }),
27201
- React.createElement(Text, { color: "cyan", bold: true }, " demian "),
27202
- React.createElement(Text, { color: "gray" }, items.join(" \xB7 "))
27336
+ { paddingX: 1, marginBottom: 1, justifyContent: "space-between" },
27337
+ React.createElement(
27338
+ Text,
27339
+ null,
27340
+ React.createElement(DemianTuiMark, { active: state.inputMode === "starting" || state.inputMode === "running", now: now2 }),
27341
+ React.createElement(Text, { color: "cyan", bold: true }, " Demian")
27342
+ ),
27343
+ React.createElement(
27344
+ Text,
27345
+ null,
27346
+ React.createElement(Text, { color: mode.color }, mode.label),
27347
+ React.createElement(Text, { color: "gray" }, ` \xB7 ${meta}`)
27348
+ )
27203
27349
  );
27204
27350
  }
27205
27351
  function DemianTuiMark({ active, now: now2 }) {
27206
27352
  const frame = active ? Math.floor(now2 / 450) % 4 : 0;
27207
- const shell = frame === 1 ? "<" : frame === 3 ? ">" : "^";
27208
- const face = frame === 2 ? "o" : "*";
27353
+ const mark = active ? ["D>", "D*", "D+", "D*"][frame] : "D>";
27209
27354
  return React.createElement(
27210
27355
  Text,
27211
27356
  { color: "yellow", bold: true },
27212
- `[${shell}${face}]`
27357
+ `[${mark}]`
27213
27358
  );
27214
27359
  }
27215
- function claudeCodeBadge(state) {
27216
- const option = selectedProviderOption(state);
27217
- const isClaudeCode = state.status.runtime === "claudecode" || option?.type === "claudecode" || state.status.provider === "claudecode" || state.selection?.providerName === "claudecode";
27218
- if (!isClaudeCode) return void 0;
27219
- return option?.ga === true ? "[CC]" : "[CC Preview]";
27220
- }
27221
- function selectedProviderOption(state) {
27222
- const providerName = state.selection?.providerName ?? state.status.provider;
27223
- return providerName ? state.providerOptions.find((option) => option.name === providerName) : void 0;
27224
- }
27225
- function SettingsBar({ state }) {
27226
- const selection = state.selection;
27227
- if (!selection) return null;
27228
- const help = state.inputMode === "provider" ? "up/down select | enter apply | esc cancel" : state.inputMode === "agent" ? "up/down select | enter apply | esc cancel" : state.inputMode === "permissionPreset" ? "up/down select | enter apply | esc cancel" : state.inputMode === "model" ? "type model | enter apply | esc cancel" : "p provider | a agent | m model | o permissions | up/down history | /cowork task | /compact compact | enter send | ctrl+c exit";
27229
- return React.createElement(
27230
- Box,
27231
- { borderStyle: "single", paddingX: 1 },
27232
- React.createElement(Text, null, `agent ${state.selectedAgent ?? state.status.agent ?? "?"} | provider ${selection.providerName} (${selection.providerSource}) | model ${selection.model} (${selection.modelSource}) | permission ${state.permissionPreset} | ${help}`)
27233
- );
27360
+ function tuiMode(state) {
27361
+ if (state.done) return { label: "done", color: "green" };
27362
+ if (state.permission) return { label: "approval", color: "yellow" };
27363
+ if (state.inputMode === "starting") return { label: "booting", color: "cyan" };
27364
+ if (state.inputMode === "running") return { label: "running", color: "cyan" };
27365
+ if (state.inputMode === "provider" || state.inputMode === "agent" || state.inputMode === "model" || state.inputMode === "permissionPreset") return { label: "settings", color: "magenta" };
27366
+ if (state.inputMode === "prompt") return { label: "ready", color: "green" };
27367
+ return { label: state.inputMode, color: "gray" };
27234
27368
  }
27235
27369
  function TranscriptView({ state }) {
27236
27370
  const blocks = state.blocks.slice(-12);
27237
27371
  const streaming = !state.streamingFinalized && state.streamingText ? { kind: "assistant", title: "Assistant streaming", lines: streamingLines(state.streamingText) } : void 0;
27238
27372
  return React.createElement(
27239
27373
  Box,
27240
- { flexDirection: "column", marginTop: 1 },
27374
+ { flexDirection: "column", marginTop: 1, paddingX: 1 },
27241
27375
  ...blocks.map((block) => React.createElement(BlockView, { key: block.id, block })),
27242
27376
  streaming ? React.createElement(BlockView, { key: "streaming", block: streaming }) : null
27243
27377
  );
@@ -27429,21 +27563,26 @@ function toneColor(tone) {
27429
27563
  return void 0;
27430
27564
  }
27431
27565
  function ActivityBar({ state }) {
27566
+ if (!shouldShowActivity(state)) return null;
27432
27567
  const warning = state.warnings.at(-1);
27433
27568
  const context = contextEfficiencySummary(state);
27569
+ const color = state.done ? "green" : state.inputMode === "running" ? "cyan" : "blue";
27434
27570
  return React.createElement(
27435
27571
  Box,
27436
- { borderStyle: "round", borderColor: state.done ? "green" : "blue", paddingX: 1, flexDirection: "column" },
27572
+ { borderStyle: "round", borderColor: color, paddingX: 1, flexDirection: "column" },
27437
27573
  React.createElement(
27438
27574
  Box,
27439
27575
  null,
27440
- React.createElement(Text, { color: state.done ? "green" : "blue", bold: true }, "Progress "),
27576
+ React.createElement(Text, { color, bold: true }, "Activity "),
27441
27577
  React.createElement(Text, { color: state.done ? "green" : "white" }, state.activity),
27442
27578
  context ? React.createElement(Text, { color: "gray" }, ` | ${context}`) : null
27443
27579
  ),
27444
27580
  warning ? React.createElement(Text, { color: "yellow" }, ` \xB7 ${warning}`) : null
27445
27581
  );
27446
27582
  }
27583
+ function shouldShowActivity(state) {
27584
+ return state.inputMode === "running" || state.done || state.warnings.length > 0 || Boolean(state.contextEfficiency);
27585
+ }
27447
27586
  function GoalIsland({ state }) {
27448
27587
  const goal = state.goal;
27449
27588
  if (!goal) return null;
@@ -27499,7 +27638,7 @@ function DiffAccordion({ state }) {
27499
27638
  null,
27500
27639
  React.createElement(Text, { color: "cyan", bold: true }, `${indicator} Diff `),
27501
27640
  React.createElement(Text, { color: "white" }, summary),
27502
- React.createElement(Text, { color: "gray" }, " | tab toggle")
27641
+ React.createElement(Text, { color: "gray" }, " | esc then d toggle")
27503
27642
  ),
27504
27643
  state.diffExpanded ? React.createElement(
27505
27644
  Box,
@@ -27539,7 +27678,7 @@ function WorkPlanPanel({ state, now: now2 }) {
27539
27678
  null,
27540
27679
  React.createElement(Text, { color: "cyan", bold: true }, `${expanded ? "[-]" : "[+]"} Plan `),
27541
27680
  React.createElement(Text, { color: "white" }, "Thinking"),
27542
- React.createElement(Text, { color: "gray" }, " | ctrl+p toggle")
27681
+ React.createElement(Text, { color: "gray" }, " | esc then w toggle")
27543
27682
  ),
27544
27683
  expanded ? React.createElement(Text, { color: "gray" }, "Demian is inspecting context and preparing a step-by-step plan.") : null
27545
27684
  );
@@ -27563,7 +27702,7 @@ function WorkPlanPanel({ state, now: now2 }) {
27563
27702
  elapsed ? React.createElement(Text, { color: "gray" }, ` (${elapsed})`) : null,
27564
27703
  React.createElement(Text, { color: "gray" }, ` | ${planProgressGraph(progress.resolved, progress.total)} ${progress.resolved}/${progress.total}`),
27565
27704
  blocked ? React.createElement(Text, { color: "red" }, ` | ${blocked} blocked`) : null,
27566
- React.createElement(Text, { color: "gray" }, " | ctrl+p toggle")
27705
+ React.createElement(Text, { color: "gray" }, " | esc then w toggle")
27567
27706
  ),
27568
27707
  expanded ? React.createElement(
27569
27708
  Box,
@@ -27684,15 +27823,15 @@ function formatElapsed2(ms2) {
27684
27823
  if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
27685
27824
  return `${seconds}s`;
27686
27825
  }
27687
- function BottomBar({ state }) {
27826
+ function InteractionPanel({ state, shortcutMode }) {
27688
27827
  if (state.inputMode === "starting") return React.createElement(LoadingBar);
27689
27828
  if (state.permission) return React.createElement(PermissionBar, { state });
27690
27829
  if (state.inputMode === "provider") return React.createElement(ProviderSelector, { state });
27691
27830
  if (state.inputMode === "agent") return React.createElement(AgentSelector, { state });
27692
27831
  if (state.inputMode === "permissionPreset") return React.createElement(PermissionPresetSelector, { state });
27693
27832
  if (state.inputMode === "model") return React.createElement(ModelEditor, { state });
27694
- if (state.inputMode === "running") return React.createElement(CommandBar, { state });
27695
- if (state.inputMode === "prompt") return React.createElement(PromptBar, { state });
27833
+ if (state.inputMode === "running") return React.createElement(CommandBar, { state, shortcutMode });
27834
+ if (state.inputMode === "prompt") return React.createElement(PromptBar, { state, shortcutMode });
27696
27835
  return React.createElement(PermissionBar, { state });
27697
27836
  }
27698
27837
  function LoadingBar() {
@@ -27700,58 +27839,72 @@ function LoadingBar() {
27700
27839
  Box,
27701
27840
  { flexDirection: "column", borderStyle: "double", paddingX: 1 },
27702
27841
  React.createElement(Text, { color: "cyan", bold: true }, "loading"),
27703
- React.createElement(Text, { color: "gray" }, "demian is preparing the session | ctrl+c exit")
27842
+ React.createElement(Text, { color: "gray" }, "demian is preparing the session | type /exit after loading to quit")
27704
27843
  );
27705
27844
  }
27706
- function PromptBar({ state }) {
27845
+ function shortcutHelp(state) {
27846
+ const parts = ["esc cancel"];
27847
+ if (state.inputMode === "prompt") parts.push("p provider", "a agent", "m model", "o permissions", "u clear");
27848
+ if (state.inputMode === "running") parts.push("s stop");
27849
+ if (state.turnDiff) parts.push("d diff");
27850
+ if (state.blocks.some((block) => block.kind === "tool")) parts.push("t tools");
27851
+ if (state.workPlan || state.inputMode === "running") parts.push("w plan");
27852
+ if (state.pendingClaudeCodePlan) parts.push("e use CC plan");
27853
+ if (state.canRetryLastPrompt) parts.push("r retry");
27854
+ parts.push("q quit");
27855
+ return `shortcut: ${parts.join(" | ")}`;
27856
+ }
27857
+ function PromptBar({ state, shortcutMode }) {
27707
27858
  const value = state.promptInput || "Type first message and press Enter";
27708
- const diffHelp = state.turnDiff ? " | tab diff" : "";
27709
- const toolHelp = state.blocks.some((block) => block.kind === "tool") ? " | ctrl+t tools" : "";
27710
- const planHelp = state.workPlan || state.inputMode === "running" ? " | ctrl+p plan" : "";
27711
- const claudeCodePlanHelp = state.pendingClaudeCodePlan ? " | ctrl+e use CC plan" : "";
27712
- const retryHelp = state.canRetryLastPrompt ? " | ctrl+r retry" : "";
27859
+ const hint = shortcutMode ? shortcutHelp(state) : "enter send | esc shortcuts | up/down history | /cowork | /compact | /retry | /exit";
27713
27860
  return React.createElement(
27714
27861
  Box,
27715
- { flexDirection: "column", borderStyle: "single", paddingX: 1 },
27862
+ { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
27863
+ React.createElement(SessionMetaBox, { state, marginTop: 0 }),
27716
27864
  React.createElement(
27717
27865
  Box,
27718
- null,
27719
- React.createElement(Text, { color: "cyan", bold: true }, "> "),
27720
- React.createElement(Text, { color: "gray" }, `[perm:${state.permissionPreset}] `),
27721
- React.createElement(Text, { color: state.promptInput ? "white" : "gray" }, value)
27722
- ),
27723
- state.promptError ? React.createElement(Text, { color: "yellow" }, state.promptError) : React.createElement(Text, { color: "gray" }, `enter send | up/down history | p/a/m/o settings | /cowork | /compact | /retry | /exit | ctrl+u clear${diffHelp}${toolHelp}${planHelp}${claudeCodePlanHelp}${retryHelp}`)
27866
+ { flexDirection: "column", borderStyle: "double", borderColor: shortcutMode ? "cyan" : "blue", paddingX: 2, paddingY: 1, marginTop: 1 },
27867
+ React.createElement(
27868
+ Box,
27869
+ null,
27870
+ React.createElement(Text, { color: "cyan", bold: true }, "> "),
27871
+ React.createElement(Text, { color: state.promptInput ? "white" : "gray" }, value)
27872
+ ),
27873
+ state.promptError ? React.createElement(Text, { color: "yellow" }, state.promptError) : React.createElement(Text, { color: shortcutMode ? "cyan" : "gray" }, hint)
27874
+ )
27724
27875
  );
27725
27876
  }
27726
- function CommandBar({ state }) {
27877
+ function CommandBar({ state, shortcutMode }) {
27727
27878
  const value = state.promptInput || "Type /stop to stop or /exit to quit";
27728
- const diffHelp = state.turnDiff ? " | tab diff" : "";
27729
- const toolHelp = state.blocks.some((block) => block.kind === "tool") ? " | ctrl+t tools" : "";
27730
- const planHelp = state.workPlan || state.inputMode === "running" ? " | ctrl+p plan" : "";
27879
+ const hint = shortcutMode ? shortcutHelp(state) : "running | /stop stop | /exit quit | esc shortcuts";
27731
27880
  return React.createElement(
27732
27881
  Box,
27733
- { flexDirection: "column", borderStyle: "single", paddingX: 1 },
27882
+ { alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
27883
+ React.createElement(SessionMetaBox, { state, marginTop: 0 }),
27734
27884
  React.createElement(
27735
27885
  Box,
27736
- null,
27737
- React.createElement(Text, { color: "yellow", bold: true }, ": "),
27738
- React.createElement(Text, { color: "gray" }, `[perm:${state.permissionPreset}] `),
27739
- React.createElement(Text, { color: state.promptInput ? "white" : "gray" }, value)
27740
- ),
27741
- state.promptError ? React.createElement(Text, { color: "yellow" }, state.promptError) : React.createElement(Text, { color: "gray" }, `running | /stop stop | /exit quit${diffHelp}${toolHelp}${planHelp}`)
27886
+ { flexDirection: "column", borderStyle: "double", borderColor: shortcutMode ? "cyan" : "yellow", paddingX: 2, paddingY: 1, marginTop: 1 },
27887
+ React.createElement(
27888
+ Box,
27889
+ null,
27890
+ React.createElement(Text, { color: "yellow", bold: true }, ": "),
27891
+ React.createElement(Text, { color: state.promptInput ? "white" : "gray" }, value)
27892
+ ),
27893
+ state.promptError ? React.createElement(Text, { color: "yellow" }, state.promptError) : React.createElement(Text, { color: shortcutMode ? "cyan" : "gray" }, hint)
27894
+ )
27742
27895
  );
27743
27896
  }
27744
27897
  function ProviderSelector({ state }) {
27745
27898
  const items = state.providerOptions;
27746
27899
  return React.createElement(
27747
- Box,
27748
- { flexDirection: "column", borderStyle: "double", paddingX: 1 },
27749
- React.createElement(Text, { color: "cyan", bold: true }, "Select provider"),
27900
+ DialogFrame,
27901
+ { title: "Providers", accent: "magenta" },
27902
+ React.createElement(Text, { color: "gray" }, `${"Provider".padEnd(22)} ${"Model".padEnd(30)} Status`),
27750
27903
  ...items.map(
27751
27904
  (item, index) => React.createElement(
27752
27905
  Text,
27753
27906
  { key: item.name, color: index === state.providerCursor ? "cyan" : item.available === false ? "gray" : "white", bold: index === state.providerCursor },
27754
- `${index === state.providerCursor ? ">" : " "} ${item.label ?? item.name} model ${item.modelLabel ?? item.model}`
27907
+ `${index === state.providerCursor ? ">" : " "} ${shorten(item.label ?? item.name, 20).padEnd(20)} ${shorten((item.modelLabel ?? item.model) || "-", 30).padEnd(30)} ${providerStatusLabel(item)}`
27755
27908
  )
27756
27909
  ),
27757
27910
  React.createElement(Text, { color: "gray" }, "up/down select | enter apply | esc cancel")
@@ -27760,9 +27913,8 @@ function ProviderSelector({ state }) {
27760
27913
  function AgentSelector({ state }) {
27761
27914
  const items = state.agentOptions;
27762
27915
  return React.createElement(
27763
- Box,
27764
- { flexDirection: "column", borderStyle: "double", paddingX: 1 },
27765
- React.createElement(Text, { color: "cyan", bold: true }, "Select agent"),
27916
+ DialogFrame,
27917
+ { title: "Agents", accent: "magenta" },
27766
27918
  ...items.map(
27767
27919
  (item, index) => React.createElement(
27768
27920
  Text,
@@ -27775,9 +27927,8 @@ function AgentSelector({ state }) {
27775
27927
  }
27776
27928
  function PermissionPresetSelector({ state }) {
27777
27929
  return React.createElement(
27778
- Box,
27779
- { flexDirection: "column", borderStyle: "double", paddingX: 1 },
27780
- React.createElement(Text, { color: "cyan", bold: true }, "Default permission"),
27930
+ DialogFrame,
27931
+ { title: "Default Permission", accent: "magenta" },
27781
27932
  ...PERMISSION_PRESETS.map(
27782
27933
  (item, index) => React.createElement(
27783
27934
  Text,
@@ -27798,16 +27949,15 @@ function ModelEditor({ state }) {
27798
27949
  const current = state.selection?.model ?? "";
27799
27950
  const value = state.modelInput || `Keep ${current}`;
27800
27951
  return React.createElement(
27801
- Box,
27802
- { flexDirection: "column", borderStyle: "double", paddingX: 1 },
27803
- React.createElement(Text, { color: "cyan", bold: true }, "Model"),
27952
+ DialogFrame,
27953
+ { title: "Model", accent: "magenta" },
27804
27954
  React.createElement(
27805
27955
  Box,
27806
27956
  null,
27807
27957
  React.createElement(Text, { color: "cyan", bold: true }, "> "),
27808
27958
  React.createElement(Text, { color: state.modelInput ? "white" : "gray" }, value)
27809
27959
  ),
27810
- state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter apply | ctrl+u clear | esc cancel")
27960
+ state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter apply | backspace edit | esc cancel")
27811
27961
  );
27812
27962
  }
27813
27963
  function PermissionBar({ state }) {
@@ -27817,18 +27967,48 @@ function PermissionBar({ state }) {
27817
27967
  }
27818
27968
  const detailLines = describeToolInput(pending.request.tool, pending.request.input);
27819
27969
  return React.createElement(
27820
- Box,
27821
- { flexDirection: "column", borderStyle: "double", paddingX: 1 },
27822
- React.createElement(Text, { color: "yellow", bold: true }, "Permission required"),
27970
+ DialogFrame,
27971
+ { title: "Permission Required", accent: "yellow" },
27823
27972
  React.createElement(Text, null, `Allow ${toolLabel(pending.request.tool, pending.request.input)}?`),
27824
27973
  ...detailLines.map((line, index) => React.createElement(Text, { key: index, color: "white" }, line)),
27825
27974
  ...permissionShortcutLines().map((line, index) => React.createElement(Text, { key: `shortcut-${index}`, color: "cyan", bold: true }, line))
27826
27975
  );
27827
27976
  }
27977
+ function DialogFrame({ title, accent, children }) {
27978
+ return React.createElement(
27979
+ Box,
27980
+ { alignSelf: "center", width: dialogPanelWidth(), flexDirection: "column", borderStyle: "round", borderColor: accent, paddingX: 2, paddingY: 1, marginTop: 1 },
27981
+ React.createElement(Text, { color: accent, bold: true }, title),
27982
+ React.createElement(Box, { height: 1 }),
27983
+ children
27984
+ );
27985
+ }
27986
+ function providerStatusLabel(item) {
27987
+ if (item.available !== false) return "ready";
27988
+ if (item.catalog?.status === "missing-auth") return "auth";
27989
+ if (item.catalog?.status === "unavailable") return "offline";
27990
+ return "n/a";
27991
+ }
27828
27992
  function shorten(value, max) {
27829
27993
  if (value.length <= max) return value;
27830
27994
  return `\u2026${value.slice(value.length - max + 1)}`;
27831
27995
  }
27996
+ function terminalHeight() {
27997
+ const rows = process.stdout.rows;
27998
+ return typeof rows === "number" && Number.isFinite(rows) && rows > 0 ? Math.max(18, rows - 1) : 24;
27999
+ }
28000
+ function terminalWidth() {
28001
+ const columns = process.stdout.columns;
28002
+ return typeof columns === "number" && Number.isFinite(columns) && columns > 0 ? Math.max(48, columns) : 100;
28003
+ }
28004
+ function promptPanelWidth() {
28005
+ const columns = terminalWidth();
28006
+ return Math.max(40, Math.min(96, columns - 8, Math.floor(columns * 0.86)));
28007
+ }
28008
+ function dialogPanelWidth() {
28009
+ const columns = terminalWidth();
28010
+ return Math.max(48, Math.min(92, columns - 10, Math.floor(columns * 0.78)));
28011
+ }
27832
28012
  function formatTokens(value) {
27833
28013
  const tokens = typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.round(value)) : 0;
27834
28014
  if (tokens >= 1e6) return `${trimDecimal2(tokens / 1e6)}m`;
@@ -27842,12 +28022,20 @@ function clamp01(value) {
27842
28022
  if (!Number.isFinite(value)) return 0;
27843
28023
  return Math.max(0, Math.min(1, value));
27844
28024
  }
28025
+ var DEMIAN_WORDMARK_LINES;
27845
28026
  var init_app = __esm({
27846
28027
  "src/ui/tui/app.ts"() {
27847
28028
  "use strict";
27848
28029
  init_store();
27849
28030
  init_presets();
27850
28031
  init_tool_summary();
28032
+ DEMIAN_WORDMARK_LINES = [
28033
+ " _ _ ",
28034
+ " __| | ___ _ __ ___ (_) __ _ _ __ ",
28035
+ " / _` |/ _ \\ '_ ` _ \\| |/ _` | '_ \\ ",
28036
+ "| (_| | __/ | | | | | | (_| | | | |",
28037
+ " \\__,_|\\___|_| |_| |_|_|\\__,_|_| |_|"
28038
+ ];
27851
28039
  }
27852
28040
  });
27853
28041
 
@@ -30877,7 +31065,7 @@ async function runExaSearch(config, options) {
30877
31065
  return searchResult("exa", options.query, results, json.value);
30878
31066
  }
30879
31067
  function resolveApiKey(provider, config) {
30880
- const value = config.apiKey ?? (config.apiKeyEnv ? process.env[config.apiKeyEnv] : void 0);
31068
+ const value = config.apiKey || (config.apiKeyEnv ? process.env[config.apiKeyEnv] : void 0);
30881
31069
  if (value) return { ok: true, value };
30882
31070
  return toolFail(`Missing API key for web_search provider "${provider}". Set webSearch.providers.${provider}.apiKey or set ${config.apiKeyEnv ?? "the configured apiKeyEnv"}.`);
30883
31071
  }
@@ -33864,7 +34052,7 @@ function providerOptions(config, catalogs = {}) {
33864
34052
  isDefault: model.isDefault
33865
34053
  })) : providerModelProfiles(name, provider);
33866
34054
  const modelProfiles = profiles.map((profile) => {
33867
- const displayName = profile.displayName ?? profile.name ?? profile.model;
34055
+ const displayName = displayModelForProfile(profile);
33868
34056
  return {
33869
34057
  ...profile,
33870
34058
  label: available ? displayName : unavailableLabel(displayName),
@@ -33872,7 +34060,7 @@ function providerOptions(config, catalogs = {}) {
33872
34060
  };
33873
34061
  });
33874
34062
  const models = modelProfiles.length ? [...new Set(modelProfiles.map((item) => item.model).filter((item) => typeof item === "string" && item.trim()))] : providerModelOptions(name, provider);
33875
- const defaultModel = defaultModelForProvider(name, provider);
34063
+ const defaultModel = defaultDisplayModelForProvider(name, provider);
33876
34064
  return {
33877
34065
  name,
33878
34066
  label: available ? name : unavailableLabel(name),
@@ -33935,13 +34123,18 @@ function createInteractiveModelSelection(config, flags = {}, saved) {
33935
34123
  }
33936
34124
  const usesSavedProvider = !flags.provider && savedProviderExists && providerName === savedProviderName;
33937
34125
  const savedModel = usesSavedProvider && saved?.model ? saved.model : void 0;
33938
- const model = flags.model ?? savedModel ?? defaultModelForProvider(providerName, providerConfig);
34126
+ const profiles = providerModelProfiles(providerName, providerConfig);
34127
+ const savedProfile = savedModel ? profiles.find((profile) => profile.model === savedModel || profile.displayName === savedModel || profile.name === savedModel) : void 0;
34128
+ const defaultModel = displayModelForProfile(profiles[0] ?? { name: "", displayName: "", model: "" }) || defaultDisplayModelForProvider(providerName, providerConfig);
34129
+ const flagProfile = flags.model ? profiles.find((profile) => profile.model === flags.model || profile.displayName === flags.model || profile.name === flags.model) : void 0;
34130
+ const selectedProfile = flags.model ? flagProfile : savedModel ? savedProfile : profiles[0];
34131
+ const model = flags.model ?? (savedProfile ? displayModelForProfile(savedProfile) : savedModel) ?? defaultModel;
33939
34132
  return {
33940
34133
  providerName,
33941
34134
  providerSource: flags.provider ? "flag" : usesSavedProvider ? "saved" : "config",
33942
34135
  model,
33943
34136
  modelSource: flags.model ? "flag" : savedModel ? "saved" : "config",
33944
- modelProfileName: providerModelProfiles(providerName, providerConfig).find((profile) => profile.model === model || profile.displayName === model || profile.name === model)?.name
34137
+ modelProfileName: selectedProfile?.name
33945
34138
  };
33946
34139
  }
33947
34140
  function providerCatalogAvailable(catalog) {
@@ -34244,6 +34437,7 @@ var init_controller = __esm({
34244
34437
 
34245
34438
  // src/tui.ts
34246
34439
  init_parser();
34440
+ import { readFileSync as readFileSync2 } from "node:fs";
34247
34441
 
34248
34442
  // src/doctor/policies.ts
34249
34443
  import { existsSync } from "node:fs";
@@ -34492,18 +34686,21 @@ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
34492
34686
  openai: {
34493
34687
  type: "openai-compatible",
34494
34688
  baseURL: "https://api.openai.com/v1",
34689
+ apiKey: "",
34495
34690
  apiKeyEnv: "OPENAI_API_KEY",
34496
34691
  catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
34497
34692
  },
34498
34693
  anthropic: {
34499
34694
  type: "anthropic",
34500
34695
  baseURL: "https://api.anthropic.com/v1",
34696
+ apiKey: "",
34501
34697
  apiKeyEnv: "ANTHROPIC_API_KEY",
34502
34698
  catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
34503
34699
  },
34504
34700
  gemini: {
34505
34701
  type: "openai-compatible",
34506
34702
  baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
34703
+ apiKey: "",
34507
34704
  apiKeyEnv: "GEMINI_API_KEY",
34508
34705
  apiKeyEnvAliases: ["GOOGLE_API_KEY"],
34509
34706
  catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
@@ -34511,6 +34708,7 @@ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
34511
34708
  groq: {
34512
34709
  type: "openai-compatible",
34513
34710
  baseURL: "https://api.groq.com/openai/v1",
34711
+ apiKey: "",
34514
34712
  apiKeyEnv: "GROQ_API_KEY",
34515
34713
  catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
34516
34714
  },
@@ -34523,6 +34721,7 @@ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
34523
34721
  displayName: "Azure example",
34524
34722
  model: "azure-deployment-name",
34525
34723
  baseURL: "https://example.openai.azure.com/openai/v1",
34724
+ apiKey: "",
34526
34725
  apiKeyEnv: "AZURE_OPENAI_API_KEY"
34527
34726
  }
34528
34727
  ]
@@ -34531,31 +34730,37 @@ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
34531
34730
  type: "openai-compatible",
34532
34731
  baseURL: "http://localhost:1234/v1",
34533
34732
  apiKey: "lm-studio",
34733
+ modelProfiles: [],
34534
34734
  catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
34535
34735
  },
34536
34736
  "ollama-local": {
34537
34737
  type: "openai-compatible",
34538
34738
  baseURL: "http://localhost:11434/v1",
34539
34739
  apiKey: "ollama",
34740
+ modelProfiles: [],
34540
34741
  quirks: { omitTemperature: true },
34541
34742
  catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
34542
34743
  },
34543
34744
  "ollama-cloud": {
34544
34745
  type: "ollama",
34545
34746
  baseURL: "https://ollama.com/api",
34747
+ apiKey: "",
34546
34748
  apiKeyEnv: "OLLAMA_API_KEY",
34749
+ modelProfiles: [],
34547
34750
  catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
34548
34751
  },
34549
34752
  llamacpp: {
34550
34753
  type: "openai-compatible",
34551
34754
  baseURL: "http://localhost:8080/v1",
34552
34755
  apiKey: "llama.cpp",
34756
+ modelProfiles: [],
34553
34757
  catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
34554
34758
  },
34555
34759
  vllm: {
34556
34760
  type: "openai-compatible",
34557
34761
  baseURL: "http://localhost:8000/v1",
34558
34762
  apiKey: "vllm",
34763
+ modelProfiles: [],
34559
34764
  catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
34560
34765
  },
34561
34766
  codex: {
@@ -34627,7 +34832,7 @@ async function addModelProfile(options) {
34627
34832
  displayName,
34628
34833
  model: options.model,
34629
34834
  ...options.baseURL ? { baseURL: options.baseURL } : {},
34630
- ...options.apiKey ? { apiKey: options.apiKey } : {},
34835
+ ...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
34631
34836
  ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
34632
34837
  };
34633
34838
  if (existingByName >= 0) profiles[existingByName] = next;
@@ -34649,18 +34854,15 @@ async function readConfigObject(filePath) {
34649
34854
  }
34650
34855
  function providerPreset(options) {
34651
34856
  const preset = options.preset ?? options.name;
34652
- const auth = {
34653
- ...options.apiKey ? { apiKey: options.apiKey } : {},
34654
- ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
34655
- };
34857
+ const auth = apiKeyAuthFields(options);
34656
34858
  const openAIAuth = options.authHeader ? { auth: { type: "api-key", header: options.authHeader } } : {};
34657
- if (preset === "openai") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.openai.com/v1", apiKeyEnv: "OPENAI_API_KEY", ...auth, ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
34658
- if (preset === "anthropic") return { type: "anthropic", baseURL: options.baseURL ?? "https://api.anthropic.com/v1", apiKeyEnv: "ANTHROPIC_API_KEY", ...auth, catalog: { type: "anthropic-models", endpoint: `${(options.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
34859
+ 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` } };
34860
+ 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` } };
34659
34861
  if (preset === "gemini") {
34660
34862
  const geminiBase = options.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
34661
- return { type: "openai-compatible", baseURL: geminiBase, apiKeyEnv: "GEMINI_API_KEY", apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...auth, ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
34863
+ 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` } };
34662
34864
  }
34663
- if (preset === "groq") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.groq.com/openai/v1", apiKeyEnv: "GROQ_API_KEY", ...auth, ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
34865
+ 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` } };
34664
34866
  if (preset === "azure") {
34665
34867
  return {
34666
34868
  type: "openai-compatible",
@@ -34672,16 +34874,17 @@ function providerPreset(options) {
34672
34874
  displayName: "Azure example",
34673
34875
  model: "azure-deployment-name",
34674
34876
  baseURL: options.baseURL ?? "https://example.openai.azure.com/openai/v1",
34877
+ ...options.apiKey ? {} : { apiKey: "" },
34675
34878
  apiKeyEnv: options.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
34676
34879
  }
34677
34880
  ]
34678
34881
  };
34679
34882
  }
34680
- if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:1234/v1", apiKey: options.apiKey ?? "lm-studio", catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
34681
- if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:11434/v1", apiKey: options.apiKey ?? "ollama", quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
34682
- if (preset === "ollama-cloud") return { type: "ollama", baseURL: options.baseURL ?? "https://ollama.com/api", ...auth, catalog: { type: "ollama-tags", endpoint: `${(options.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
34683
- if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8080/v1", apiKey: options.apiKey ?? "llama.cpp", catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
34684
- if (preset === "vllm") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8000/v1", apiKey: options.apiKey ?? "vllm", catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
34883
+ 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` } };
34884
+ 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` } };
34885
+ 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` } };
34886
+ 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` } };
34887
+ 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` } };
34685
34888
  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" } };
34686
34889
  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" } };
34687
34890
  if ((options.type ?? preset) === "openai-compatible") {
@@ -34690,14 +34893,20 @@ function providerPreset(options) {
34690
34893
  return {
34691
34894
  type: "openai-compatible",
34692
34895
  baseURL,
34693
- ...options.apiKey ? { apiKey: options.apiKey } : {},
34694
- ...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {},
34896
+ ...auth,
34695
34897
  ...openAIAuth,
34696
34898
  catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
34697
34899
  };
34698
34900
  }
34699
34901
  throw new Error(`Unknown provider preset: ${preset}`);
34700
34902
  }
34903
+ function apiKeyAuthFields(options, defaultApiKeyEnv) {
34904
+ const apiKeyEnv = options.apiKeyEnv ?? defaultApiKeyEnv;
34905
+ return {
34906
+ ...options.apiKey !== void 0 || apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
34907
+ ...apiKeyEnv ? { apiKeyEnv } : {}
34908
+ };
34909
+ }
34701
34910
  async function writeJsonAtomic(filePath, content) {
34702
34911
  await mkdir(path3.dirname(filePath), { recursive: true, mode: 448 });
34703
34912
  const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
@@ -35178,7 +35387,7 @@ async function main(argv = process.argv.slice(2)) {
35178
35387
  ]);
35179
35388
  const React2 = ReactModule.default;
35180
35389
  const store = new TuiStore2();
35181
- const instance = render(React2.createElement(TuiApp2, { store }));
35390
+ const instance = render(React2.createElement(TuiApp2, { store, version: packageVersion() }));
35182
35391
  try {
35183
35392
  const code = await runTuiSession2(flags, store);
35184
35393
  setTimeout(() => instance.unmount(), 100);
@@ -35197,6 +35406,14 @@ async function main(argv = process.argv.slice(2)) {
35197
35406
  throw error;
35198
35407
  }
35199
35408
  }
35409
+ function packageVersion() {
35410
+ try {
35411
+ const metadata = JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
35412
+ return typeof metadata.version === "string" && metadata.version.trim() ? metadata.version : "dev";
35413
+ } catch {
35414
+ return "dev";
35415
+ }
35416
+ }
35200
35417
  function parseArgs(argv) {
35201
35418
  const out = { prompt: "", transcript: true, images: [] };
35202
35419
  const rest = [];
@@ -35317,15 +35534,16 @@ Usage:
35317
35534
  Default:
35318
35535
  demian and demian-cli launch the Ink terminal UI.
35319
35536
  The TUI shows provider/model defaults before asking for the first message.
35320
- Press p to select provider, m to edit model, then Enter to send.
35321
- Press a to select the main agent before sending a message.
35322
- Press o to select the default permission preset: deny, ask, auto, or full.
35537
+ Press Esc then p to select provider, Esc then m to edit model, then Enter to send.
35538
+ Press Esc then a to select the main agent before sending a message.
35539
+ Press Esc then o to select the default permission preset: deny, ask, auto, or full.
35323
35540
  Press up/down in the message composer to recall previous prompts.
35324
- Provider/model selections are remembered in .demian/preferences.json.
35541
+ Provider/model selections are remembered in ~/.demian/preferences.json.
35325
35542
  After each answer, the TUI returns to standby for the next message.
35326
35543
  Use /cowork <task> to explicitly ask Demian to coordinate cowork sub agents in multi-agent mode.
35327
35544
  Use /compact to compact current history, /stop to stop active work, and /exit or /quit to close the TUI.
35328
- Tool approvals show the requested command/path in the bottom bar.
35545
+ Press Esc then q to quit, Esc then s to stop a running task, and Esc then u to clear the composer.
35546
+ Tool approvals show the requested command/path in a permission dialog.
35329
35547
  Press y to allow once, a to always allow the grant scope, or n/Enter to deny.
35330
35548
  Prompt arguments are still accepted as a compatibility shortcut.
35331
35549