demian-cli 1.1.2 → 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
@@ -23189,8 +23189,8 @@ function permissionLabel(req) {
23189
23189
  if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
23190
23190
  return `${req.tool}: ${inputObject.path}`;
23191
23191
  }
23192
- const path37 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
23193
- 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}`;
23194
23194
  return tool;
23195
23195
  }
23196
23196
  var init_prompt = __esm({
@@ -26675,6 +26675,12 @@ var init_store = __esm({
26675
26675
  }
26676
26676
  if (option.id) this.#resolveSession({ kind: "session", id: option.id });
26677
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
+ }
26678
26684
  submitNewSessionSelection() {
26679
26685
  if (this.#state.inputMode !== "session") return;
26680
26686
  this.#resolveSession({ kind: "new" });
@@ -28029,6 +28035,7 @@ var app_exports = {};
28029
28035
  __export(app_exports, {
28030
28036
  TuiApp: () => TuiApp,
28031
28037
  commandPaletteMatches: () => commandPaletteMatches,
28038
+ initialSessionCommandLines: () => initialSessionCommandLines,
28032
28039
  inputFrameRows: () => inputFrameRows,
28033
28040
  isCtrlCKeypress: () => isCtrlCKeypress,
28034
28041
  isPromptNewlineKeypress: () => isPromptNewlineKeypress,
@@ -28219,6 +28226,10 @@ function TuiApp({ store, version = "dev" }) {
28219
28226
  store.submitNewSessionSelection();
28220
28227
  return;
28221
28228
  }
28229
+ if (normalized === "d") {
28230
+ store.submitDeleteSessionSelection();
28231
+ return;
28232
+ }
28222
28233
  if (key.upArrow) {
28223
28234
  store.moveSessionCursor(-1);
28224
28235
  return;
@@ -28623,6 +28634,7 @@ function buildCommandPaletteItems(mode, availability) {
28623
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"] });
28624
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"] });
28625
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"] });
28626
28638
  }
28627
28639
  for (const command of SLASH_COMMANDS) {
28628
28640
  if (!slashCommandActiveInMode(command, mode)) continue;
@@ -28745,6 +28757,12 @@ function executeCommandPaletteItem({
28745
28757
  case "open-session":
28746
28758
  store.submitSessionSelectionShortcut();
28747
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;
28748
28766
  case "clear-prompt":
28749
28767
  store.clearPromptInput({ notify: false });
28750
28768
  promptDraft.setValue("");
@@ -28879,7 +28897,7 @@ function SessionStart({ state, version }) {
28879
28897
  React.createElement(
28880
28898
  Box,
28881
28899
  { alignSelf: "center", width, justifyContent: "space-between", marginTop: 1 },
28882
- 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"),
28883
28901
  React.createElement(Text, { color: "white" }, `v${version}`)
28884
28902
  ),
28885
28903
  React.createElement(Box, { flexGrow: 1 })
@@ -28915,12 +28933,41 @@ function EmptyState({
28915
28933
  }),
28916
28934
  React.createElement(CommandGuide, { hint, color: shortcutMode ? "cyan" : "gray", error: state.promptError }),
28917
28935
  hasOverlay ? null : React.createElement(SessionMetaBox, { state, version, marginTop: 2 }),
28936
+ hasOverlay ? null : React.createElement(SessionCommandsBox, { marginTop: 1 }),
28918
28937
  React.createElement(
28919
28938
  Box,
28920
28939
  { flexGrow: 1 }
28921
28940
  )
28922
28941
  );
28923
28942
  }
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)}`)
28969
+ ];
28970
+ }
28924
28971
  function LoadingView({ state }) {
28925
28972
  const title = state.inputMode === "starting" ? "Demian runtime" : "Preparing session";
28926
28973
  const message = state.activity || (state.inputMode === "starting" ? "Loading configuration" : "Starting session");
@@ -29718,7 +29765,8 @@ function SessionSelector({ state }) {
29718
29765
  sessionOptionLine(item, index, selected, contentWidth)
29719
29766
  );
29720
29767
  }),
29721
- 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")
29722
29770
  );
29723
29771
  }
29724
29772
  function sessionSelectorColumns(width) {
@@ -30134,7 +30182,7 @@ function clamp01(value) {
30134
30182
  if (!Number.isFinite(value)) return 0;
30135
30183
  return Math.max(0, Math.min(1, value));
30136
30184
  }
30137
- var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, PromptDraftController, SessionMetaBox, DEMIAN_WORDMARK_LINES, DemianWordmark, TranscriptView, BlockView, CommandGuide;
30185
+ var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, PromptDraftController, SessionMetaBox, SessionCommandsBox, DEMIAN_WORDMARK_LINES, DemianWordmark, TranscriptView, BlockView, CommandGuide;
30138
30186
  var init_app = __esm({
30139
30187
  "src/ui/tui/app.ts"() {
30140
30188
  "use strict";
@@ -30248,6 +30296,25 @@ var init_app = __esm({
30248
30296
  ...rows.map((line, index) => React.createElement(SurfaceLine, { key: index, text: line ?? "", width: width - 4, backgroundColor: SURFACE.status, color: "white" }))
30249
30297
  );
30250
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
+ });
30251
30318
  DEMIAN_WORDMARK_LINES = [
30252
30319
  " _ _ ",
30253
30320
  " __| | ___ _ __ ___ (_) __ _ _ __ ",
@@ -35032,17 +35099,17 @@ function findDependencyCycle(group) {
35032
35099
  const byId = new Map(group.map((member) => [member.memberId, member]));
35033
35100
  const visiting = /* @__PURE__ */ new Set();
35034
35101
  const visited = /* @__PURE__ */ new Set();
35035
- const path37 = [];
35102
+ const path38 = [];
35036
35103
  const visit = (id) => {
35037
- if (visiting.has(id)) return [...path37.slice(path37.indexOf(id)), id];
35104
+ if (visiting.has(id)) return [...path38.slice(path38.indexOf(id)), id];
35038
35105
  if (visited.has(id)) return void 0;
35039
35106
  visiting.add(id);
35040
- path37.push(id);
35107
+ path38.push(id);
35041
35108
  for (const dep of byId.get(id)?.dependsOn ?? []) {
35042
35109
  const cycle = visit(dep);
35043
35110
  if (cycle) return cycle;
35044
35111
  }
35045
- path37.pop();
35112
+ path38.pop();
35046
35113
  visiting.delete(id);
35047
35114
  visited.add(id);
35048
35115
  return void 0;
@@ -36988,22 +37055,13 @@ ${sessionListMessage()}`);
36988
37055
  return;
36989
37056
  }
36990
37057
  if (command.action === "delete") {
36991
- const target = findConversation(sortedConversations(), command.target);
37058
+ const target = command.target ? findConversation(sortedConversations(), command.target) : currentConversation;
36992
37059
  if (!target) {
36993
37060
  store.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".`);
36994
37061
  store.prepareForPrompt("waiting for next message");
36995
37062
  return;
36996
37063
  }
36997
- conversationRecords.delete(target.id);
36998
- runtimeByConversation.delete(target.id);
36999
- await deleteConversationRecord(flags.conversationStorageDir, target.id);
37000
- if (currentConversation?.id === target.id) {
37001
- const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
37002
- conversationRecords.set(next.id, next);
37003
- await switchConversation(next);
37004
- }
37005
- store.prepareForPrompt(`deleted session: ${target.title}`);
37006
- await persistCurrentConversation();
37064
+ await deleteConversationById(target.id);
37007
37065
  return;
37008
37066
  }
37009
37067
  if (command.action === "rename") {
@@ -37047,6 +37105,10 @@ ${sessionListMessage()}`);
37047
37105
  return;
37048
37106
  }
37049
37107
  if (selection.kind === "exit" || store.exitRequested()) return;
37108
+ if (selection.kind === "delete") {
37109
+ await deleteConversationById(selection.id);
37110
+ return;
37111
+ }
37050
37112
  await applyStartupSessionSelection(selection);
37051
37113
  }
37052
37114
  function startupSessionOptions() {
@@ -37067,6 +37129,10 @@ ${sessionListMessage()}`);
37067
37129
  store.prepareForPrompt("waiting for next message");
37068
37130
  return;
37069
37131
  }
37132
+ if (selection.kind === "delete") {
37133
+ await deleteConversationById(selection.id);
37134
+ return;
37135
+ }
37070
37136
  if (selection.kind === "new") {
37071
37137
  const next = createConversationRecord({ id: createRootSessionId(), cwd });
37072
37138
  conversationRecords.set(next.id, next);
@@ -37088,6 +37154,24 @@ ${sessionListMessage()}`);
37088
37154
  store.prepareForPrompt(`session ready: ${target.title}`);
37089
37155
  await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], target.id);
37090
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
+ }
37091
37175
  async function switchConversation(record) {
37092
37176
  currentConversation = record;
37093
37177
  rootSessionId = record.id;
@@ -37124,17 +37208,17 @@ ${sessionListMessage()}`);
37124
37208
  savedSelectionKey = key;
37125
37209
  }
37126
37210
  async function handleConfigAction(action) {
37127
- const path37 = configMutationPath(flags);
37211
+ const path38 = configMutationPath(flags);
37128
37212
  try {
37129
37213
  let message = "Config updated.";
37130
37214
  let nextSelection = {};
37131
37215
  if (action.type === "setDefaultProvider") {
37132
- await updateConfigDefaults({ path: path37, defaultProvider: action.provider });
37216
+ await updateConfigDefaults({ path: path38, defaultProvider: action.provider });
37133
37217
  message = `Default provider saved: ${action.provider}`;
37134
37218
  nextSelection = { provider: action.provider };
37135
37219
  } else if (action.type === "setDefaultModel") {
37136
37220
  await setDefaultModelProfile({
37137
- path: path37,
37221
+ path: path38,
37138
37222
  provider: action.provider,
37139
37223
  profileName: action.profileName,
37140
37224
  name: action.name,
@@ -37144,12 +37228,12 @@ ${sessionListMessage()}`);
37144
37228
  message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
37145
37229
  nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
37146
37230
  } else if (action.type === "addProvider") {
37147
- await addProvider({ path: path37, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
37231
+ await addProvider({ path: path38, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
37148
37232
  message = `Provider added: ${action.name}`;
37149
37233
  nextSelection = { provider: action.name };
37150
37234
  } else if (action.type === "updateProvider") {
37151
37235
  await updateProviderSettings({
37152
- path: path37,
37236
+ path: path38,
37153
37237
  provider: action.provider,
37154
37238
  ...action.field === "baseURL" ? { baseURL: action.value } : {},
37155
37239
  ...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
@@ -37230,7 +37314,7 @@ function sessionUsage() {
37230
37314
  " /session new [title]",
37231
37315
  " /session switch <number|id|title>",
37232
37316
  " /session rename <title>",
37233
- " /session delete <number|id|title>",
37317
+ " /session delete [number|id|title]",
37234
37318
  " /session clear",
37235
37319
  "",
37236
37320
  "Aliases: /sessions, /conversation, /conversations, /conv"
@@ -37262,6 +37346,151 @@ var init_controller = __esm({
37262
37346
  }
37263
37347
  });
37264
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
+
37265
37494
  // src/tui.ts
37266
37495
  init_parser();
37267
37496
  import { readFileSync as readFileSync2 } from "node:fs";
@@ -37960,8 +38189,23 @@ async function checkCodexAuth() {
37960
38189
  return { ok: false, source: "none", message: "no codex auth file or OPENAI_API_KEY; run `demian auth login codex`" };
37961
38190
  }
37962
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
+
37963
38208
  // src/tui.ts
37964
- var dynamicImport4 = new Function("specifier", "return import(specifier)");
37965
38209
  async function main(argv = process.argv.slice(2)) {
37966
38210
  const configCommandResult = await maybeRunConfigCommand(argv);
37967
38211
  if (configCommandResult !== void 0) return configCommandResult;
@@ -37978,26 +38222,8 @@ async function main(argv = process.argv.slice(2)) {
37978
38222
  return 1;
37979
38223
  }
37980
38224
  try {
37981
- const [{ render }, ReactModule, { TuiApp: TuiApp2 }, { TuiStore: TuiStore2 }, { runTuiSession: runTuiSession2 }] = await Promise.all([
37982
- dynamicImport4("ink"),
37983
- dynamicImport4("react"),
37984
- Promise.resolve().then(() => (init_app(), app_exports)),
37985
- Promise.resolve().then(() => (init_store(), store_exports)),
37986
- Promise.resolve().then(() => (init_controller(), controller_exports))
37987
- ]);
37988
- const React2 = ReactModule.default;
37989
- const store = new TuiStore2();
37990
- const instance = render(React2.createElement(TuiApp2, { store, version: packageVersion() }));
37991
- try {
37992
- const code = await runTuiSession2({ ...flags, conversationManagement: true }, store);
37993
- setTimeout(() => instance.unmount(), 100);
37994
- await instance.waitUntilExit();
37995
- return code;
37996
- } catch (error) {
37997
- instance.unmount();
37998
- await instance.waitUntilExit().catch(() => void 0);
37999
- throw error;
38000
- }
38225
+ const engine = resolveTuiEngineName({ cliEngine: flags.engine, env: process.env });
38226
+ return await runTuiEngine(engine, { flags, version: packageVersion() });
38001
38227
  } catch (error) {
38002
38228
  if (isMissingDependency(error)) {
38003
38229
  process.stderr.write("TUI dependencies are not installed. Run `npm install` in demian, or use `demian-plain`.\n");
@@ -38059,6 +38285,14 @@ function parseArgs(argv) {
38059
38285
  out.goal = true;
38060
38286
  continue;
38061
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
+ }
38062
38296
  if (arg === "--image") {
38063
38297
  out.images.push(takeValue2(argv, ++i, arg));
38064
38298
  continue;
@@ -38145,7 +38379,7 @@ Default:
38145
38379
  Use /cowork <task> to explicitly ask Demian to coordinate cowork sub agents in multi-agent mode.
38146
38380
  Use /compact to compact current history, /stop to stop active work, and /exit or /quit to close the TUI.
38147
38381
  Use /session list, /session new, /session switch <number|id|title>, /session rename <title>,
38148
- and /session delete <number|id|title> to manage saved CLI conversations.
38382
+ and /session delete [number|id|title] to manage saved CLI conversations.
38149
38383
  Press Esc then q to quit, Esc then s while running to stop the task, and Esc then u to clear the composer.
38150
38384
  Tool approvals show the requested command/path in a permission dialog.
38151
38385
  Press y to allow once, a to always allow the grant scope, or n/Enter to deny.
@@ -38174,6 +38408,8 @@ Flags:
38174
38408
  --no-wizard skip the first-run config wizard
38175
38409
  --goal run the initial prompt as an explicit /goal objective
38176
38410
  /ralph-loop --fresh-context is single-agent only
38411
+ --engine <ink|bubbletea>
38412
+ select the TUI renderer; defaults to ink during migration
38177
38413
  --config <path> load an additional config file
38178
38414
  --help, -h show this help
38179
38415
 
@@ -1139,6 +1139,12 @@ var TuiStore = class {
1139
1139
  }
1140
1140
  if (option.id) this.#resolveSession({ kind: "session", id: option.id });
1141
1141
  }
1142
+ submitDeleteSessionSelection() {
1143
+ if (this.#state.inputMode !== "session") return;
1144
+ const option = this.#state.sessionOptions[this.#state.sessionCursor];
1145
+ if (!option || option.kind === "new" || !option.id) return;
1146
+ this.#resolveSession({ kind: "delete", id: option.id });
1147
+ }
1142
1148
  submitNewSessionSelection() {
1143
1149
  if (this.#state.inputMode !== "session") return;
1144
1150
  this.#resolveSession({ kind: "new" });
@@ -33833,22 +33839,13 @@ ${sessionListMessage()}`);
33833
33839
  return;
33834
33840
  }
33835
33841
  if (command.action === "delete") {
33836
- const target = findConversation(sortedConversations(), command.target);
33842
+ const target = command.target ? findConversation(sortedConversations(), command.target) : currentConversation;
33837
33843
  if (!target) {
33838
33844
  store2.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".`);
33839
33845
  store2.prepareForPrompt("waiting for next message");
33840
33846
  return;
33841
33847
  }
33842
- conversationRecords.delete(target.id);
33843
- runtimeByConversation.delete(target.id);
33844
- await deleteConversationRecord(flags.conversationStorageDir, target.id);
33845
- if (currentConversation?.id === target.id) {
33846
- const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
33847
- conversationRecords.set(next.id, next);
33848
- await switchConversation(next);
33849
- }
33850
- store2.prepareForPrompt(`deleted session: ${target.title}`);
33851
- await persistCurrentConversation();
33848
+ await deleteConversationById(target.id);
33852
33849
  return;
33853
33850
  }
33854
33851
  if (command.action === "rename") {
@@ -33892,6 +33889,10 @@ ${sessionListMessage()}`);
33892
33889
  return;
33893
33890
  }
33894
33891
  if (selection.kind === "exit" || store2.exitRequested()) return;
33892
+ if (selection.kind === "delete") {
33893
+ await deleteConversationById(selection.id);
33894
+ return;
33895
+ }
33895
33896
  await applyStartupSessionSelection(selection);
33896
33897
  }
33897
33898
  function startupSessionOptions() {
@@ -33912,6 +33913,10 @@ ${sessionListMessage()}`);
33912
33913
  store2.prepareForPrompt("waiting for next message");
33913
33914
  return;
33914
33915
  }
33916
+ if (selection.kind === "delete") {
33917
+ await deleteConversationById(selection.id);
33918
+ return;
33919
+ }
33915
33920
  if (selection.kind === "new") {
33916
33921
  const next = createConversationRecord({ id: createRootSessionId(), cwd });
33917
33922
  conversationRecords.set(next.id, next);
@@ -33933,6 +33938,24 @@ ${sessionListMessage()}`);
33933
33938
  store2.prepareForPrompt(`session ready: ${target.title}`);
33934
33939
  await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], target.id);
33935
33940
  }
33941
+ async function deleteConversationById(id) {
33942
+ const target = conversationRecords.get(id) ?? findConversation(sortedConversations(), id);
33943
+ if (!target) {
33944
+ store2.showSystemMessage("Sessions", `No session matched "${id}".`);
33945
+ store2.prepareForPrompt("waiting for next message");
33946
+ return;
33947
+ }
33948
+ conversationRecords.delete(target.id);
33949
+ runtimeByConversation.delete(target.id);
33950
+ await deleteConversationRecord(flags.conversationStorageDir, target.id);
33951
+ if (currentConversation?.id === target.id) {
33952
+ const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
33953
+ conversationRecords.set(next.id, next);
33954
+ await switchConversation(next);
33955
+ }
33956
+ store2.prepareForPrompt(`deleted session: ${target.title}`);
33957
+ await persistCurrentConversation();
33958
+ }
33936
33959
  async function switchConversation(record) {
33937
33960
  currentConversation = record;
33938
33961
  rootSessionId = record.id;
@@ -34075,7 +34098,7 @@ function sessionUsage() {
34075
34098
  " /session new [title]",
34076
34099
  " /session switch <number|id|title>",
34077
34100
  " /session rename <title>",
34078
- " /session delete <number|id|title>",
34101
+ " /session delete [number|id|title]",
34079
34102
  " /session clear",
34080
34103
  "",
34081
34104
  "Aliases: /sessions, /conversation, /conversations, /conv"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "demian-cli",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Local terminal coding agent with TUI, goals, cowork agents, permissions, and provider switching.",
5
5
  "type": "module",
6
6
  "icon": "media/demian.svg",
@@ -26,7 +26,10 @@
26
26
  ],
27
27
  "scripts": {
28
28
  "build": "esbuild src/index.ts src/tui.ts src/cli.ts src/vscode-worker.ts --bundle --platform=node --format=esm --target=node22 --outdir=dist --out-extension:.js=.mjs --loader:.ts=ts --define:process.env.DEV=\\\"false\\\" --external:@anthropic-ai/claude-agent-sdk-* --external:ink --external:react --external:react-devtools-core --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\"",
29
- "prepack": "npm run build",
29
+ "build:tui-sidecar": "npm run build:tui-sidecar:darwin-arm64 && npm run build:tui-sidecar:linux-x64",
30
+ "build:tui-sidecar:darwin-arm64": "mkdir -p dist/tui-sidecar/darwin-arm64 && cd tui-sidecar && GOOS=darwin GOARCH=arm64 go build -o ../dist/tui-sidecar/darwin-arm64/demian-tui ./cmd/demian-tui",
31
+ "build:tui-sidecar:linux-x64": "mkdir -p dist/tui-sidecar/linux-x64 && cd tui-sidecar && GOOS=linux GOARCH=amd64 go build -o ../dist/tui-sidecar/linux-x64/demian-tui ./cmd/demian-tui",
32
+ "prepack": "npm run build && npm run build:tui-sidecar",
30
33
  "test": "node --experimental-strip-types --test test/*.test.ts",
31
34
  "smoke": "npm run smoke:tui && npm run smoke:plain",
32
35
  "smoke:tui": "bin/demian --help",
@@ -41,9 +44,9 @@
41
44
  "chalk": "^5.4.1",
42
45
  "cli-highlight": "^2.1.11",
43
46
  "highlight.js": "^11.11.1",
44
- "ink": "^5.0.1",
47
+ "ink": "^7.0.3",
45
48
  "marked": "^15.0.7",
46
- "react": "^18.3.1",
49
+ "react": "^19.2.0",
47
50
  "slice-ansi": "^7.1.0",
48
51
  "string-width": "^7.2.0",
49
52
  "wrap-ansi": "^9.0.0"