@llmist/cli 15.5.0 → 15.6.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/cli.js CHANGED
@@ -98,7 +98,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
98
98
  // package.json
99
99
  var package_default = {
100
100
  name: "@llmist/cli",
101
- version: "15.5.0",
101
+ version: "15.6.0",
102
102
  description: "CLI for llmist - run LLM agents from the command line",
103
103
  type: "module",
104
104
  main: "dist/cli.js",
@@ -154,7 +154,7 @@ var package_default = {
154
154
  node: ">=22.0.0"
155
155
  },
156
156
  dependencies: {
157
- llmist: "^15.5.0",
157
+ llmist: "^15.6.0",
158
158
  "@unblessed/node": "^1.0.0-alpha.23",
159
159
  chalk: "^5.6.2",
160
160
  commander: "^12.1.0",
@@ -168,7 +168,7 @@ var package_default = {
168
168
  zod: "^4.1.12"
169
169
  },
170
170
  devDependencies: {
171
- "@llmist/testing": "^15.5.0",
171
+ "@llmist/testing": "^15.6.0",
172
172
  "@types/diff": "^8.0.0",
173
173
  "@types/js-yaml": "^4.0.9",
174
174
  "@types/marked-terminal": "^6.1.1",
@@ -4094,6 +4094,7 @@ function configToAgentOptions(config) {
4094
4094
  if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
4095
4095
  if (r.enabled === false) result.noRetry = true;
4096
4096
  }
4097
+ if (config["show-hints"] !== void 0) result.showHints = config["show-hints"];
4097
4098
  return result;
4098
4099
  }
4099
4100
 
@@ -4671,6 +4672,10 @@ var BlockRenderer = class _BlockRenderer {
4671
4672
  currentSessionId = 0;
4672
4673
  /** Previous session ID (for deferred cleanup) */
4673
4674
  previousSessionId = null;
4675
+ /** Callback for content state changes (empty to non-empty or vice versa) */
4676
+ onHasContentChangeCallback = null;
4677
+ /** Last reported hasContent state (to avoid duplicate callbacks) */
4678
+ lastHasContentState = false;
4674
4679
  constructor(container, renderCallback, renderNowCallback) {
4675
4680
  this.container = container;
4676
4681
  this.renderCallback = renderCallback;
@@ -4971,6 +4976,16 @@ var BlockRenderer = class _BlockRenderer {
4971
4976
  child.detach();
4972
4977
  }
4973
4978
  this.renderCallback();
4979
+ this.notifyHasContentChange();
4980
+ }
4981
+ /**
4982
+ * Set callback for content state changes.
4983
+ * Called when blocks transition from empty to non-empty or vice versa.
4984
+ * Used by HintsBar to know when "^B browse" hint should be shown.
4985
+ */
4986
+ onHasContentChange(callback) {
4987
+ this.onHasContentChangeCallback = callback;
4988
+ callback(this.blocks.size > 0);
4974
4989
  }
4975
4990
  /**
4976
4991
  * Start a new session. Called at the start of each REPL turn.
@@ -5010,6 +5025,7 @@ var BlockRenderer = class _BlockRenderer {
5010
5025
  }
5011
5026
  this.previousSessionId = null;
5012
5027
  this.renderCallback();
5028
+ this.notifyHasContentChange();
5013
5029
  }
5014
5030
  /**
5015
5031
  * Get the current session ID (for node creation).
@@ -5098,6 +5114,17 @@ var BlockRenderer = class _BlockRenderer {
5098
5114
  // ───────────────────────────────────────────────────────────────────────────
5099
5115
  // Private - Node & Block Management
5100
5116
  // ───────────────────────────────────────────────────────────────────────────
5117
+ /**
5118
+ * Notify callback if content state has changed.
5119
+ * Only fires when transitioning from empty to non-empty or vice versa.
5120
+ */
5121
+ notifyHasContentChange() {
5122
+ const hasContent = this.blocks.size > 0;
5123
+ if (hasContent !== this.lastHasContentState) {
5124
+ this.lastHasContentState = hasContent;
5125
+ this.onHasContentChangeCallback?.(hasContent);
5126
+ }
5127
+ }
5101
5128
  generateId(prefix) {
5102
5129
  return `${prefix}_${++this.nodeIdCounter}`;
5103
5130
  }
@@ -5124,6 +5151,7 @@ var BlockRenderer = class _BlockRenderer {
5124
5151
  }
5125
5152
  this.applyBottomAlignmentAndScroll();
5126
5153
  this.renderCallback();
5154
+ this.notifyHasContentChange();
5127
5155
  }
5128
5156
  /**
5129
5157
  * Render a node and its children recursively.
@@ -5269,8 +5297,8 @@ ${fullContent}
5269
5297
  case "system_message": {
5270
5298
  const icon = this.getSystemMessageIcon(node.category);
5271
5299
  const color = this.getSystemMessageColor(node.category);
5272
- const RESET2 = "\x1B[0m";
5273
- return `${indent}${color}${icon} ${node.message}${RESET2}`;
5300
+ const RESET3 = "\x1B[0m";
5301
+ return `${indent}${color}${icon} ${node.message}${RESET3}`;
5274
5302
  }
5275
5303
  }
5276
5304
  }
@@ -5297,7 +5325,7 @@ ${fullContent}
5297
5325
  getSystemMessageColor(category) {
5298
5326
  const YELLOW2 = "\x1B[33m";
5299
5327
  const BLUE = "\x1B[34m";
5300
- const GRAY = "\x1B[90m";
5328
+ const GRAY2 = "\x1B[90m";
5301
5329
  const RED2 = "\x1B[31m";
5302
5330
  switch (category) {
5303
5331
  case "throttle":
@@ -5305,7 +5333,7 @@ ${fullContent}
5305
5333
  case "retry":
5306
5334
  return BLUE;
5307
5335
  case "info":
5308
- return GRAY;
5336
+ return GRAY2;
5309
5337
  case "warning":
5310
5338
  return YELLOW2;
5311
5339
  case "error":
@@ -5579,6 +5607,7 @@ ${indicator}`;
5579
5607
  }
5580
5608
  this.applyBottomAlignmentAndScroll();
5581
5609
  this.renderNowCallback();
5610
+ this.notifyHasContentChange();
5582
5611
  }
5583
5612
  /**
5584
5613
  * Get the current content filter mode.
@@ -6036,9 +6065,129 @@ var TUIController = class {
6036
6065
  }
6037
6066
  };
6038
6067
 
6068
+ // src/tui/hints-bar.ts
6069
+ var GRAY = "\x1B[90m";
6070
+ var RESET = "\x1B[0m";
6071
+ var HintsBar = class {
6072
+ hintsBox;
6073
+ renderCallback;
6074
+ focusMode = "browse";
6075
+ contentFilterMode = "full";
6076
+ hasContent = false;
6077
+ constructor(hintsBox, renderCallback) {
6078
+ this.hintsBox = hintsBox;
6079
+ this.renderCallback = renderCallback;
6080
+ this.render();
6081
+ }
6082
+ /**
6083
+ * Update focus mode and re-render hints.
6084
+ */
6085
+ setFocusMode(mode) {
6086
+ if (this.focusMode !== mode) {
6087
+ this.focusMode = mode;
6088
+ this.render();
6089
+ }
6090
+ }
6091
+ /**
6092
+ * Update content filter mode and re-render hints.
6093
+ */
6094
+ setContentFilterMode(mode) {
6095
+ if (this.contentFilterMode !== mode) {
6096
+ this.contentFilterMode = mode;
6097
+ this.render();
6098
+ }
6099
+ }
6100
+ /**
6101
+ * Update whether there's content to browse.
6102
+ * Affects whether ^B browse hint is shown in input mode.
6103
+ */
6104
+ setHasContent(has) {
6105
+ if (this.hasContent !== has) {
6106
+ this.hasContent = has;
6107
+ this.render();
6108
+ }
6109
+ }
6110
+ /**
6111
+ * Get current focus mode.
6112
+ */
6113
+ getFocusMode() {
6114
+ return this.focusMode;
6115
+ }
6116
+ /**
6117
+ * Get current content filter mode.
6118
+ */
6119
+ getContentFilterMode() {
6120
+ return this.contentFilterMode;
6121
+ }
6122
+ /**
6123
+ * Render hints based on current state.
6124
+ */
6125
+ render() {
6126
+ const hints = [];
6127
+ if (this.contentFilterMode === "focused") {
6128
+ hints.push("^K exit focused mode");
6129
+ } else if (this.focusMode === "input") {
6130
+ hints.push("^S multiline");
6131
+ if (this.hasContent) {
6132
+ hints.push("^B browse");
6133
+ }
6134
+ hints.push("^K focused");
6135
+ } else {
6136
+ hints.push("j/k nav");
6137
+ hints.push("Enter expand");
6138
+ hints.push("^B input");
6139
+ hints.push("^K focused");
6140
+ }
6141
+ this.hintsBox.setContent(`${GRAY}${hints.join(" ")}${RESET}`);
6142
+ this.renderCallback();
6143
+ }
6144
+ };
6145
+
6146
+ // src/tui/editor.ts
6147
+ import { spawnSync as spawnSync2 } from "child_process";
6148
+ import { readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
6149
+ import { tmpdir } from "os";
6150
+ import { join as join3 } from "path";
6151
+ function openEditorSync(initialContent = "") {
6152
+ const editor = process.env.VISUAL || process.env.EDITOR || "vi";
6153
+ const tmpFile = join3(tmpdir(), `llmist-input-${Date.now()}.txt`);
6154
+ writeFileSync2(tmpFile, initialContent, "utf-8");
6155
+ try {
6156
+ const parts = editor.split(/\s+/);
6157
+ const cmd = parts[0];
6158
+ const args = [...parts.slice(1), tmpFile];
6159
+ const result = spawnSync2(cmd, args, {
6160
+ stdio: "inherit",
6161
+ // Connect to user's terminal
6162
+ shell: false
6163
+ });
6164
+ if (result.error) {
6165
+ try {
6166
+ unlinkSync(tmpFile);
6167
+ } catch {
6168
+ }
6169
+ return null;
6170
+ }
6171
+ if (result.status === 0) {
6172
+ const content = readFileSync4(tmpFile, "utf-8");
6173
+ unlinkSync(tmpFile);
6174
+ const trimmed = content.trim();
6175
+ return trimmed || null;
6176
+ } else {
6177
+ unlinkSync(tmpFile);
6178
+ return null;
6179
+ }
6180
+ } catch {
6181
+ try {
6182
+ unlinkSync(tmpFile);
6183
+ } catch {
6184
+ }
6185
+ return null;
6186
+ }
6187
+ }
6188
+
6039
6189
  // src/tui/input-handler.ts
6040
- var IDLE_PROMPT = "> ";
6041
- var ACTIVE_PROMPT = ">>> ";
6190
+ var PROMPT = "> ";
6042
6191
  var InputHandler = class {
6043
6192
  inputBar;
6044
6193
  promptLabel;
@@ -6050,6 +6199,14 @@ var InputHandler = class {
6050
6199
  pendingInput = null;
6051
6200
  /** Whether we're waiting for REPL prompt (vs AskUser which should auto-focus) */
6052
6201
  isPendingREPLPrompt = false;
6202
+ /** Whether input mode is currently active (focused, capturing keystrokes) */
6203
+ isActive = false;
6204
+ /** Whether a bracketed paste is in progress */
6205
+ isPasting = false;
6206
+ /** Buffer for accumulating bracketed paste content */
6207
+ pasteBuffer = "";
6208
+ /** Flag to indicate content came from editor (skip paste detection on submit) */
6209
+ fromEditor = false;
6053
6210
  /** Callback when Ctrl+C is pressed */
6054
6211
  ctrlCCallback = null;
6055
6212
  /** Callback when Ctrl+B is pressed (toggle focus mode) */
@@ -6066,16 +6223,23 @@ var InputHandler = class {
6066
6223
  midSessionHandler = null;
6067
6224
  /** Callback to check current focus mode (to avoid conflicts with browse mode) */
6068
6225
  getFocusModeCallback = null;
6069
- constructor(inputBar, promptLabel, body, screen, renderCallback, renderNowCallback) {
6226
+ /** Body height when input bar is visible */
6227
+ bodyHeightWithInput;
6228
+ /** Body height when input bar is hidden (browse mode) */
6229
+ bodyHeightWithoutInput;
6230
+ constructor(inputBar, promptLabel, body, screen, renderCallback, renderNowCallback, hasHints = true) {
6070
6231
  this.inputBar = inputBar;
6071
6232
  this.promptLabel = promptLabel;
6072
6233
  this.body = body;
6073
6234
  this.screen = screen;
6074
6235
  this.renderCallback = renderCallback;
6075
6236
  this.renderNowCallback = renderNowCallback ?? renderCallback;
6237
+ this.bodyHeightWithInput = hasHints ? "100%-3" : "100%-2";
6238
+ this.bodyHeightWithoutInput = hasHints ? "100%-2" : "100%-1";
6076
6239
  this.inputBar.on("submit", (value) => {
6077
6240
  this.handleSubmit(value);
6078
6241
  });
6242
+ this.setupBracketedPasteHandler();
6079
6243
  this.inputBar.on("cancel", () => {
6080
6244
  this.handleCancel();
6081
6245
  });
@@ -6109,6 +6273,10 @@ var InputHandler = class {
6109
6273
  this.ctrlPCallback();
6110
6274
  }
6111
6275
  });
6276
+ this.inputBar.key(["C-s"], () => {
6277
+ const currentValue = this.inputBar.getValue();
6278
+ this.openEditorForInput(currentValue);
6279
+ });
6112
6280
  this.screen.key(["enter"], () => {
6113
6281
  if (this.isPendingREPLPrompt) {
6114
6282
  if (this.getFocusModeCallback?.() === "browse") {
@@ -6246,37 +6414,54 @@ var InputHandler = class {
6246
6414
  // Focus Mode API (controlled by TUIApp)
6247
6415
  // ─────────────────────────────────────────────────────────────────────────────
6248
6416
  /**
6249
- * Activate input mode - show input bar and capture keyboard.
6417
+ * Activate input mode - focus input bar and capture keyboard.
6250
6418
  * Called by TUIApp when switching to input mode.
6251
- * Preserves current prompt indicator and input text.
6419
+ * Shows input bar with active prompt (">>>") and starts capturing keystrokes.
6252
6420
  */
6253
6421
  activate() {
6254
6422
  this.isPendingREPLPrompt = false;
6423
+ this.isActive = true;
6255
6424
  this.promptLabel.show();
6256
6425
  this.inputBar.show();
6426
+ this.body.height = this.bodyHeightWithInput;
6427
+ this.setPrompt(PROMPT);
6257
6428
  this.renderNowCallback();
6258
6429
  this.inputBar.readInput();
6259
6430
  }
6431
+ /** Flag to prevent handleCancel from re-entering during deactivation */
6432
+ isDeactivating = false;
6260
6433
  /**
6261
6434
  * Deactivate input mode - hide input bar completely.
6262
6435
  * Called by TUIApp when switching to browse mode.
6436
+ * Input bar is hidden to give more space to content.
6263
6437
  */
6264
6438
  deactivate() {
6439
+ this.isPendingREPLPrompt = false;
6440
+ this.isActive = false;
6441
+ this.isDeactivating = true;
6442
+ this.inputBar.cancel();
6443
+ this.isDeactivating = false;
6265
6444
  this.promptLabel.hide();
6266
6445
  this.inputBar.hide();
6267
- this.isPendingREPLPrompt = false;
6446
+ this.body.height = this.bodyHeightWithoutInput;
6268
6447
  this.renderNowCallback();
6269
6448
  }
6270
6449
  /**
6271
- * Check if input mode is active (input bar visible and focused).
6450
+ * Check if input mode is active (focused, capturing keystrokes).
6272
6451
  */
6273
6452
  isInputActive() {
6274
- return this.inputBar.visible !== false;
6453
+ return this.isActive;
6275
6454
  }
6276
6455
  /**
6277
6456
  * Handle input submission.
6278
6457
  */
6279
6458
  handleSubmit(rawValue) {
6459
+ if (this.isPasting) {
6460
+ return;
6461
+ }
6462
+ if (this.fromEditor) {
6463
+ this.fromEditor = false;
6464
+ }
6280
6465
  const value = rawValue.trim();
6281
6466
  if (!value) {
6282
6467
  this.inputBar.readInput();
@@ -6298,18 +6483,93 @@ var InputHandler = class {
6298
6483
  * Handle input cancellation (ESC key).
6299
6484
  */
6300
6485
  handleCancel() {
6486
+ if (this.isDeactivating) {
6487
+ return;
6488
+ }
6301
6489
  if (this.pendingInput) {
6302
6490
  this.inputBar.readInput();
6303
6491
  } else {
6304
6492
  this.setIdle();
6305
6493
  }
6306
6494
  }
6495
+ /**
6496
+ * Set up bracketed paste mode detection.
6497
+ *
6498
+ * Terminal emulators that support bracketed paste send:
6499
+ * - \x1b[200~ before pasted content
6500
+ * - \x1b[201~ after pasted content
6501
+ *
6502
+ * This allows reliable detection of paste vs typed input.
6503
+ */
6504
+ setupBracketedPasteHandler() {
6505
+ const PASTE_START = "\x1B[200~";
6506
+ const PASTE_END = "\x1B[201~";
6507
+ this.screen.program.input.on("data", (data) => {
6508
+ const str = data.toString();
6509
+ if (str.includes(PASTE_START)) {
6510
+ this.isPasting = true;
6511
+ this.pasteBuffer = "";
6512
+ const startIdx = str.indexOf(PASTE_START) + PASTE_START.length;
6513
+ const afterStart = str.slice(startIdx);
6514
+ if (afterStart.includes(PASTE_END)) {
6515
+ const endIdx = afterStart.indexOf(PASTE_END);
6516
+ this.pasteBuffer = afterStart.slice(0, endIdx);
6517
+ this.isPasting = false;
6518
+ this.handlePaste(this.pasteBuffer);
6519
+ this.pasteBuffer = "";
6520
+ } else {
6521
+ this.pasteBuffer = afterStart;
6522
+ }
6523
+ return;
6524
+ }
6525
+ if (this.isPasting && str.includes(PASTE_END)) {
6526
+ const endIdx = str.indexOf(PASTE_END);
6527
+ this.pasteBuffer += str.slice(0, endIdx);
6528
+ this.isPasting = false;
6529
+ this.handlePaste(this.pasteBuffer);
6530
+ this.pasteBuffer = "";
6531
+ return;
6532
+ }
6533
+ if (this.isPasting) {
6534
+ this.pasteBuffer += str;
6535
+ }
6536
+ });
6537
+ }
6538
+ /**
6539
+ * Handle completed paste content.
6540
+ *
6541
+ * If content contains newlines, opens $EDITOR for multiline editing.
6542
+ * Otherwise, inserts directly into the input bar.
6543
+ */
6544
+ handlePaste(content) {
6545
+ if (!content) return;
6546
+ if (content.includes("\n")) {
6547
+ const currentValue = this.inputBar.getValue();
6548
+ this.openEditorForInput(currentValue + content);
6549
+ } else {
6550
+ const currentValue = this.inputBar.getValue();
6551
+ this.inputBar.setValue(currentValue + content);
6552
+ this.inputBar.readInput();
6553
+ }
6554
+ }
6555
+ /**
6556
+ * Set prompt text and dynamically adjust layout.
6557
+ * Idle prompt "> " uses 2 chars, active prompt ">>> " uses 4 chars.
6558
+ */
6559
+ setPrompt(prompt) {
6560
+ const width = prompt.length;
6561
+ this.promptLabel.width = width;
6562
+ this.promptLabel.setContent(prompt);
6563
+ this.inputBar.left = width;
6564
+ this.inputBar.width = `100%-${width}`;
6565
+ }
6307
6566
  /**
6308
6567
  * Set input to idle state.
6309
6568
  */
6310
6569
  setIdle() {
6311
6570
  this.isPendingREPLPrompt = false;
6312
- this.promptLabel.setContent(IDLE_PROMPT);
6571
+ this.isActive = false;
6572
+ this.setPrompt(PROMPT);
6313
6573
  this.inputBar.setValue("");
6314
6574
  this.renderCallback();
6315
6575
  }
@@ -6327,7 +6587,7 @@ var InputHandler = class {
6327
6587
  */
6328
6588
  setPendingPrompt() {
6329
6589
  this.isPendingREPLPrompt = true;
6330
- this.promptLabel.setContent(IDLE_PROMPT);
6590
+ this.setPrompt(PROMPT);
6331
6591
  this.inputBar.setValue("");
6332
6592
  this.renderCallback();
6333
6593
  }
@@ -6336,11 +6596,42 @@ var InputHandler = class {
6336
6596
  */
6337
6597
  setActive() {
6338
6598
  this.isPendingREPLPrompt = false;
6339
- this.promptLabel.setContent(ACTIVE_PROMPT);
6599
+ this.isActive = true;
6600
+ this.promptLabel.show();
6601
+ this.inputBar.show();
6602
+ this.body.height = this.bodyHeightWithInput;
6603
+ this.setPrompt(PROMPT);
6340
6604
  this.inputBar.setValue("");
6341
6605
  this.renderNowCallback();
6342
6606
  this.inputBar.readInput();
6343
6607
  }
6608
+ /**
6609
+ * Open $EDITOR for multiline input.
6610
+ * Called when user presses Ctrl+S or pastes multiline content.
6611
+ */
6612
+ openEditorForInput(initialContent) {
6613
+ this.screen.program.clear();
6614
+ this.screen.program.disableMouse();
6615
+ this.screen.program.showCursor();
6616
+ this.screen.program.normalBuffer();
6617
+ const result = openEditorSync(initialContent);
6618
+ this.screen.program.alternateBuffer();
6619
+ this.screen.program.hideCursor();
6620
+ this.screen.program.enableMouse();
6621
+ this.screen.alloc();
6622
+ this.screen.render();
6623
+ this.isPasting = false;
6624
+ this.pasteBuffer = "";
6625
+ setImmediate(() => {
6626
+ if (result) {
6627
+ this.fromEditor = true;
6628
+ this.handleSubmit(result);
6629
+ } else {
6630
+ this.inputBar.setValue(initialContent);
6631
+ this.inputBar.readInput();
6632
+ }
6633
+ });
6634
+ }
6344
6635
  };
6345
6636
 
6346
6637
  // src/tui/keymap.ts
@@ -6446,13 +6737,16 @@ var KeyboardManager = class {
6446
6737
 
6447
6738
  // src/tui/layout.ts
6448
6739
  import { Box as Box2, ScrollableBox, Text, Textbox } from "@unblessed/node";
6449
- function createBlockLayout(screen) {
6740
+ function createBlockLayout(screen, showHints = true) {
6741
+ const inputBottom = showHints ? 2 : 1;
6742
+ const statusBottom = showHints ? 1 : 0;
6743
+ const bodyHeight = showHints ? "100%-3" : "100%-2";
6450
6744
  const body = new ScrollableBox({
6451
6745
  parent: screen,
6452
6746
  top: 0,
6453
6747
  left: 0,
6454
6748
  width: "100%",
6455
- height: "100%-2",
6749
+ height: bodyHeight,
6456
6750
  // Scrolling configuration
6457
6751
  scrollable: true,
6458
6752
  alwaysScroll: true,
@@ -6475,10 +6769,10 @@ function createBlockLayout(screen) {
6475
6769
  });
6476
6770
  const promptLabel = new Text({
6477
6771
  parent: screen,
6478
- bottom: 1,
6772
+ bottom: inputBottom,
6479
6773
  left: 0,
6480
- width: 4,
6481
- // ">>> " = 4 chars (max prompt width)
6774
+ width: 2,
6775
+ // "> " = 2 chars
6482
6776
  height: 1,
6483
6777
  content: "> ",
6484
6778
  style: {
@@ -6488,10 +6782,10 @@ function createBlockLayout(screen) {
6488
6782
  });
6489
6783
  const inputBar = new Textbox({
6490
6784
  parent: screen,
6491
- bottom: 1,
6492
- left: 4,
6493
- // Position after prompt label
6494
- width: "100%-4",
6785
+ bottom: inputBottom,
6786
+ left: 2,
6787
+ // Position after prompt label ("> " = 2 chars)
6788
+ width: "100%-2",
6495
6789
  height: 1,
6496
6790
  keys: true,
6497
6791
  mouse: true,
@@ -6502,7 +6796,7 @@ function createBlockLayout(screen) {
6502
6796
  });
6503
6797
  const statusBar = new Box2({
6504
6798
  parent: screen,
6505
- bottom: 0,
6799
+ bottom: statusBottom,
6506
6800
  left: 0,
6507
6801
  width: "100%",
6508
6802
  height: 1,
@@ -6512,7 +6806,19 @@ function createBlockLayout(screen) {
6512
6806
  bg: "black"
6513
6807
  }
6514
6808
  });
6515
- return { body, promptLabel, inputBar, statusBar };
6809
+ const hintsBar = showHints ? new Box2({
6810
+ parent: screen,
6811
+ bottom: 0,
6812
+ left: 0,
6813
+ width: "100%",
6814
+ height: 1,
6815
+ tags: false,
6816
+ style: {
6817
+ fg: "white",
6818
+ bg: "black"
6819
+ }
6820
+ }) : null;
6821
+ return { body, promptLabel, inputBar, statusBar, hintsBar };
6516
6822
  }
6517
6823
 
6518
6824
  // src/tui/approval-dialog.ts
@@ -6633,7 +6939,7 @@ function escapeContent(str) {
6633
6939
 
6634
6940
  // src/tui/raw-viewer.ts
6635
6941
  import { Box as Box4 } from "@unblessed/node";
6636
- var RESET = "\x1B[0m";
6942
+ var RESET2 = "\x1B[0m";
6637
6943
  var BOLD = "\x1B[1m";
6638
6944
  var DIM = "\x1B[2m";
6639
6945
  var RED = "\x1B[31m";
@@ -6665,17 +6971,17 @@ function showRawViewer(options) {
6665
6971
  if (mode === "request") {
6666
6972
  title = ` Raw Parameters - ${gadgetName} `;
6667
6973
  if (!parameters || Object.keys(parameters).length === 0) {
6668
- content = `${DIM}No parameters${RESET}`;
6974
+ content = `${DIM}No parameters${RESET2}`;
6669
6975
  } else {
6670
6976
  content = formatGadgetParameters(parameters);
6671
6977
  }
6672
6978
  } else {
6673
6979
  title = ` Raw Result - ${gadgetName} `;
6674
6980
  if (error) {
6675
- content = `${RED}${BOLD}Error:${RESET}
6981
+ content = `${RED}${BOLD}Error:${RESET2}
6676
6982
  ${error}`;
6677
6983
  } else if (!result) {
6678
- content = `${DIM}No result data available${RESET}`;
6984
+ content = `${DIM}No result data available${RESET2}`;
6679
6985
  } else {
6680
6986
  content = formatGadgetResult(result);
6681
6987
  }
@@ -6684,14 +6990,14 @@ ${error}`;
6684
6990
  if (mode === "request") {
6685
6991
  title = ` Raw Request - #${iteration} ${model} `;
6686
6992
  if (!request || request.length === 0) {
6687
- content = `${DIM}No request data available${RESET}`;
6993
+ content = `${DIM}No request data available${RESET2}`;
6688
6994
  } else {
6689
6995
  content = formatMessages(request);
6690
6996
  }
6691
6997
  } else {
6692
6998
  title = ` Raw Response - #${iteration} ${model} `;
6693
6999
  if (!response) {
6694
- content = `${DIM}No response data available${RESET}`;
7000
+ content = `${DIM}No response data available${RESET2}`;
6695
7001
  } else {
6696
7002
  content = response;
6697
7003
  }
@@ -6732,7 +7038,7 @@ ${error}`;
6732
7038
  left: 0,
6733
7039
  width: "100%",
6734
7040
  height: 1,
6735
- content: `${DIM} [${WHITE}\u2191/\u2193/PgUp/PgDn${DIM}] Scroll [${WHITE}Home/End${DIM}] Jump [${WHITE}Escape/q${DIM}] Close${RESET}`,
7041
+ content: `${DIM} [${WHITE}\u2191/\u2193/PgUp/PgDn${DIM}] Scroll [${WHITE}Home/End${DIM}] Jump [${WHITE}Escape/q${DIM}] Close${RESET2}`,
6736
7042
  tags: false,
6737
7043
  style: { fg: "white", bg: "black" }
6738
7044
  });
@@ -6764,11 +7070,11 @@ function formatMessages(messages) {
6764
7070
  const msg = messages[i];
6765
7071
  const roleColor = getRoleColor(msg.role);
6766
7072
  const roleName = msg.role.toUpperCase();
6767
- lines.push(`${DIM}${separator}${RESET}`);
7073
+ lines.push(`${DIM}${separator}${RESET2}`);
6768
7074
  lines.push(
6769
- `${roleColor}${BOLD}[${roleName}]${RESET} ${DIM}Message ${i + 1} of ${messages.length}${RESET}`
7075
+ `${roleColor}${BOLD}[${roleName}]${RESET2} ${DIM}Message ${i + 1} of ${messages.length}${RESET2}`
6770
7076
  );
6771
- lines.push(`${DIM}${separator}${RESET}`);
7077
+ lines.push(`${DIM}${separator}${RESET2}`);
6772
7078
  lines.push("");
6773
7079
  const contentLines = formatMessageContent(msg.content);
6774
7080
  lines.push(...contentLines);
@@ -6791,19 +7097,19 @@ function formatMessageContent(content) {
6791
7097
  lines.push(...part.text.split("\n"));
6792
7098
  } else if (isImagePart(part)) {
6793
7099
  const mediaType = part.source?.media_type || "unknown";
6794
- lines.push(`${DIM}[Image: ${mediaType}]${RESET}`);
7100
+ lines.push(`${DIM}[Image: ${mediaType}]${RESET2}`);
6795
7101
  } else if (isAudioPart(part)) {
6796
7102
  const mediaType = part.source?.media_type || "unknown";
6797
- lines.push(`${DIM}[Audio: ${mediaType}]${RESET}`);
7103
+ lines.push(`${DIM}[Audio: ${mediaType}]${RESET2}`);
6798
7104
  } else if (isToolUsePart(part)) {
6799
- lines.push(`${YELLOW}${BOLD}[Tool Use: ${part.name}]${RESET}`);
6800
- lines.push(`${DIM}ID: ${part.id}${RESET}`);
6801
- lines.push(`${DIM}Input:${RESET}`);
7105
+ lines.push(`${YELLOW}${BOLD}[Tool Use: ${part.name}]${RESET2}`);
7106
+ lines.push(`${DIM}ID: ${part.id}${RESET2}`);
7107
+ lines.push(`${DIM}Input:${RESET2}`);
6802
7108
  const inputStr = JSON.stringify(part.input, null, 2);
6803
7109
  lines.push(...inputStr.split("\n").map((l) => ` ${l}`));
6804
7110
  } else if (isToolResultPart(part)) {
6805
- lines.push(`${CYAN}${BOLD}[Tool Result]${RESET}`);
6806
- lines.push(`${DIM}Tool Use ID: ${part.tool_use_id}${RESET}`);
7111
+ lines.push(`${CYAN}${BOLD}[Tool Result]${RESET2}`);
7112
+ lines.push(`${DIM}Tool Use ID: ${part.tool_use_id}${RESET2}`);
6807
7113
  if (typeof part.content === "string") {
6808
7114
  lines.push(...part.content.split("\n"));
6809
7115
  } else {
@@ -6811,7 +7117,7 @@ function formatMessageContent(content) {
6811
7117
  }
6812
7118
  } else {
6813
7119
  const partType = part.type || "unknown";
6814
- lines.push(`${DIM}[${partType}]${RESET}`);
7120
+ lines.push(`${DIM}[${partType}]${RESET2}`);
6815
7121
  lines.push(JSON.stringify(part, null, 2));
6816
7122
  }
6817
7123
  }
@@ -6847,9 +7153,9 @@ function isToolResultPart(part) {
6847
7153
  function formatGadgetParameters(params) {
6848
7154
  const lines = [];
6849
7155
  const separator = "\u2500".repeat(78);
6850
- lines.push(`${DIM}${separator}${RESET}`);
6851
- lines.push(`${CYAN}${BOLD}Parameters${RESET}`);
6852
- lines.push(`${DIM}${separator}${RESET}`);
7156
+ lines.push(`${DIM}${separator}${RESET2}`);
7157
+ lines.push(`${CYAN}${BOLD}Parameters${RESET2}`);
7158
+ lines.push(`${DIM}${separator}${RESET2}`);
6853
7159
  lines.push("");
6854
7160
  const json = JSON.stringify(params, null, 2);
6855
7161
  const highlighted = highlightJson(json);
@@ -6859,9 +7165,9 @@ function formatGadgetParameters(params) {
6859
7165
  function formatGadgetResult(result) {
6860
7166
  const lines = [];
6861
7167
  const separator = "\u2500".repeat(78);
6862
- lines.push(`${DIM}${separator}${RESET}`);
6863
- lines.push(`${GREEN}${BOLD}Result${RESET}`);
6864
- lines.push(`${DIM}${separator}${RESET}`);
7168
+ lines.push(`${DIM}${separator}${RESET2}`);
7169
+ lines.push(`${GREEN}${BOLD}Result${RESET2}`);
7170
+ lines.push(`${DIM}${separator}${RESET2}`);
6865
7171
  lines.push("");
6866
7172
  const trimmed = result.trim();
6867
7173
  if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
@@ -6877,10 +7183,10 @@ function formatGadgetResult(result) {
6877
7183
  return lines.join("\n");
6878
7184
  }
6879
7185
  function highlightJson(json) {
6880
- let result = json.replace(/"([^"]+)":/g, `${CYAN}"$1"${RESET}:`);
6881
- result = result.replace(/: "([^"]*)"/g, `: ${GREEN}"$1"${RESET}`);
6882
- result = result.replace(/: (-?\d+\.?\d*)/g, `: ${YELLOW}$1${RESET}`);
6883
- result = result.replace(/: (true|false|null)/g, `: ${MAGENTA}$1${RESET}`);
7186
+ let result = json.replace(/"([^"]+)":/g, `${CYAN}"$1"${RESET2}:`);
7187
+ result = result.replace(/: "([^"]*)"/g, `: ${GREEN}"$1"${RESET2}`);
7188
+ result = result.replace(/: (-?\d+\.?\d*)/g, `: ${YELLOW}$1${RESET2}`);
7189
+ result = result.replace(/: (true|false|null)/g, `: ${MAGENTA}$1${RESET2}`);
6884
7190
  return result;
6885
7191
  }
6886
7192
 
@@ -6959,6 +7265,7 @@ function createScreen(options) {
6959
7265
  // Use alternate screen buffer (restores on exit)
6960
7266
  useBCE: true
6961
7267
  });
7268
+ screen.program.write("\x1B[?2004h");
6962
7269
  let isDestroyed = false;
6963
7270
  let renderPending = false;
6964
7271
  let renderTimeout = null;
@@ -6989,6 +7296,7 @@ function createScreen(options) {
6989
7296
  clearTimeout(renderTimeout);
6990
7297
  renderTimeout = null;
6991
7298
  }
7299
+ screen.program.write("\x1B[?2004l");
6992
7300
  screen.destroy();
6993
7301
  process.stdout.write("\x1B[?25h");
6994
7302
  };
@@ -7030,7 +7338,7 @@ var StatusBar = class {
7030
7338
  streamingOutputTokens = 0;
7031
7339
  /** Whether we're currently streaming */
7032
7340
  isStreaming = false;
7033
- /** Active LLM calls: Map from label ("#1") to model name */
7341
+ /** Active LLM calls: Map from label ("#1") to model name and start time */
7034
7342
  activeLLMCalls = /* @__PURE__ */ new Map();
7035
7343
  /** Active gadgets (by name) */
7036
7344
  activeGadgets = /* @__PURE__ */ new Set();
@@ -7127,7 +7435,7 @@ var StatusBar = class {
7127
7435
  * @param model - Full model name like "gemini:gemini-2.5-flash"
7128
7436
  */
7129
7437
  startLLMCall(label, model) {
7130
- this.activeLLMCalls.set(label, model);
7438
+ this.activeLLMCalls.set(label, { model, startTime: Date.now() });
7131
7439
  this.startSpinner();
7132
7440
  this.render();
7133
7441
  }
@@ -7140,6 +7448,19 @@ var StatusBar = class {
7140
7448
  this.maybeStopSpinner();
7141
7449
  this.render();
7142
7450
  }
7451
+ /**
7452
+ * Get the start time of the earliest running LLM call.
7453
+ * Used to show elapsed time since the first call started (for concurrent subagents).
7454
+ * @returns Start time in ms, or null if no LLM calls are active
7455
+ */
7456
+ getEarliestLLMCallStartTime() {
7457
+ if (this.activeLLMCalls.size === 0) return null;
7458
+ let earliest = Infinity;
7459
+ for (const { startTime } of this.activeLLMCalls.values()) {
7460
+ if (startTime < earliest) earliest = startTime;
7461
+ }
7462
+ return earliest;
7463
+ }
7143
7464
  /**
7144
7465
  * Track a gadget as active.
7145
7466
  * @param name - Gadget name like "ReadFile" or "BrowseWeb"
@@ -7271,6 +7592,15 @@ var StatusBar = class {
7271
7592
  this.startLLMCall(label, event.model);
7272
7593
  break;
7273
7594
  }
7595
+ case "llm_response_end": {
7596
+ const label = this.nodeIdToLabel.get(event.nodeId);
7597
+ if (label) {
7598
+ this.activeLLMCalls.delete(label);
7599
+ this.maybeStopSpinner();
7600
+ this.render();
7601
+ }
7602
+ break;
7603
+ }
7274
7604
  case "llm_call_complete": {
7275
7605
  const label = this.nodeIdToLabel.get(event.nodeId);
7276
7606
  if (label) {
@@ -7414,68 +7744,72 @@ var StatusBar = class {
7414
7744
  * @param immediate - If true, render immediately without debouncing
7415
7745
  */
7416
7746
  render(immediate = false) {
7417
- const elapsed = this.getElapsedSeconds().toFixed(1);
7418
7747
  const YELLOW2 = "\x1B[33m";
7419
7748
  const GREEN2 = "\x1B[32m";
7420
7749
  const BLUE = "\x1B[34m";
7421
7750
  const CYAN2 = "\x1B[36m";
7422
7751
  const MAGENTA2 = "\x1B[35m";
7423
- const GRAY = "\x1B[90m";
7424
- const RESET2 = "\x1B[0m";
7752
+ const GRAY2 = "\x1B[90m";
7753
+ const RESET3 = "\x1B[0m";
7425
7754
  const BG_BLUE = "\x1B[44m";
7426
- const BG_GREEN = "\x1B[42m";
7427
7755
  const WHITE2 = "\x1B[37m";
7428
- const BLACK = "\x1B[30m";
7429
7756
  const displayInputTokens = this.metrics.inputTokens + this.streamingInputTokens;
7430
7757
  const displayOutputTokens = this.metrics.outputTokens + this.streamingOutputTokens;
7431
7758
  const parts = [];
7432
- if (this.contentFilterMode === "focused") {
7433
- parts.push(`${BG_BLUE}${WHITE2} FOCUSED ${RESET2}`);
7434
- } else if (this.focusMode === "browse") {
7435
- parts.push(`${BG_BLUE}${WHITE2} BROWSE ${RESET2}`);
7436
- } else {
7437
- parts.push(`${BG_GREEN}${BLACK} INPUT ${RESET2}`);
7759
+ if (this.focusMode === "browse" && this.contentFilterMode !== "focused") {
7760
+ parts.push(`${BG_BLUE}${WHITE2} BROWSE ${RESET3}`);
7438
7761
  }
7439
7762
  if (this.profiles.length > 0) {
7440
7763
  const profile = this.profiles[this.currentProfileIndex];
7441
7764
  const display = profile.length > 12 ? `${profile.slice(0, 11)}\u2026` : profile;
7442
- parts.push(`${YELLOW2}${display}${RESET2}`);
7765
+ parts.push(`${YELLOW2}${display}${RESET3}`);
7766
+ }
7767
+ if (displayInputTokens > 0) {
7768
+ const inputPrefix = this.isStreaming && this.streamingInputTokens > 0 ? "~" : "";
7769
+ parts.push(`${YELLOW2}\u2191${inputPrefix}${formatTokens(displayInputTokens)}${RESET3}`);
7443
7770
  }
7444
- const inputPrefix = this.isStreaming && this.streamingInputTokens > 0 ? "~" : "";
7445
- parts.push(`${YELLOW2}\u2191 ${inputPrefix}${formatTokens(displayInputTokens)}${RESET2}`);
7446
7771
  if (this.metrics.cachedTokens > 0) {
7447
- parts.push(`${BLUE}\u293F ${formatTokens(this.metrics.cachedTokens)}${RESET2}`);
7772
+ parts.push(`${BLUE}\u293F${formatTokens(this.metrics.cachedTokens)}${RESET3}`);
7773
+ }
7774
+ if (displayOutputTokens > 0) {
7775
+ const outputPrefix = this.isStreaming ? "~" : "";
7776
+ parts.push(`${GREEN2}\u2193${outputPrefix}${formatTokens(displayOutputTokens)}${RESET3}`);
7777
+ }
7778
+ const earliestStart = this.getEarliestLLMCallStartTime();
7779
+ if (earliestStart !== null) {
7780
+ const elapsedSeconds = (Date.now() - earliestStart) / 1e3;
7781
+ const timeStr = elapsedSeconds % 1 === 0 ? `${elapsedSeconds}s` : `${elapsedSeconds.toFixed(1)}s`;
7782
+ parts.push(`${GRAY2}${timeStr}${RESET3}`);
7783
+ }
7784
+ if (this.metrics.cost > 0) {
7785
+ parts.push(`${CYAN2}$${formatCost(this.metrics.cost)}${RESET3}`);
7448
7786
  }
7449
- const outputPrefix = this.isStreaming ? "~" : "";
7450
- parts.push(`${GREEN2}\u2193 ${outputPrefix}${formatTokens(displayOutputTokens)}${RESET2}`);
7451
- parts.push(`${GRAY}${elapsed}s${RESET2}`);
7452
- parts.push(`${CYAN2}$${formatCost(this.metrics.cost)}${RESET2}`);
7453
7787
  if (this.selectionDebugCallback) {
7454
7788
  const debug = this.selectionDebugCallback();
7455
7789
  const debugStr = `sel:${debug.index}/${debug.total}`;
7456
7790
  const typeStr = debug.nodeType ? ` [${debug.nodeType}]` : "";
7457
- parts.push(`${GRAY}${debugStr}${typeStr}${RESET2}`);
7791
+ parts.push(`${GRAY2}${debugStr}${typeStr}${RESET3}`);
7458
7792
  }
7459
7793
  if (this.rateLimitState?.isThrottling) {
7460
7794
  const { triggeredBy } = this.rateLimitState;
7461
7795
  if (triggeredBy?.daily) {
7462
- parts.push(`${YELLOW2}\u23F8 Daily limit, resets midnight UTC${RESET2}`);
7796
+ parts.push(`${YELLOW2}\u23F8 Daily limit, resets midnight UTC${RESET3}`);
7463
7797
  } else {
7464
7798
  const seconds = Math.ceil(this.rateLimitState.delayMs / 1e3);
7465
7799
  const reason = triggeredBy?.rpm ? " (RPM)" : triggeredBy?.tpm ? " (TPM)" : "";
7466
- parts.push(`${YELLOW2}\u23F8 Throttled ${seconds}s${reason}${RESET2}`);
7800
+ parts.push(`${YELLOW2}\u23F8 Throttled ${seconds}s${reason}${RESET3}`);
7467
7801
  }
7468
7802
  }
7469
7803
  if (this.retryState) {
7470
7804
  const { attemptNumber, retriesLeft } = this.retryState;
7471
7805
  const totalAttempts = attemptNumber + retriesLeft;
7472
- parts.push(`${BLUE}\u{1F504} Retry ${attemptNumber}/${totalAttempts}${RESET2}`);
7806
+ parts.push(`${BLUE}\u{1F504} Retry ${attemptNumber}/${totalAttempts}${RESET3}`);
7473
7807
  }
7474
7808
  if (this.activeLLMCalls.size > 0 || this.activeGadgets.size > 0) {
7475
7809
  const spinner = SPINNER_FRAMES2[this.spinnerFrame];
7476
7810
  if (this.activeLLMCalls.size > 0) {
7477
7811
  const byModel = /* @__PURE__ */ new Map();
7478
- for (const [label, model] of this.activeLLMCalls) {
7812
+ for (const [label, { model }] of this.activeLLMCalls) {
7479
7813
  const shortModel = this.shortenModelName(model);
7480
7814
  if (!byModel.has(shortModel)) byModel.set(shortModel, []);
7481
7815
  byModel.get(shortModel)?.push(label);
@@ -7484,15 +7818,15 @@ var StatusBar = class {
7484
7818
  for (const [model, labels] of byModel) {
7485
7819
  llmParts.push(`${model} ${labels.join(", ")}`);
7486
7820
  }
7487
- parts.push(`${spinner} ${MAGENTA2}${llmParts.join(" | ")}${RESET2}`);
7821
+ parts.push(`${spinner} ${MAGENTA2}${llmParts.join(" | ")}${RESET3}`);
7488
7822
  }
7489
7823
  if (this.activeGadgets.size > 0) {
7490
7824
  const gadgetList = [...this.activeGadgets].slice(0, 3).join(", ");
7491
7825
  const more = this.activeGadgets.size > 3 ? ` +${this.activeGadgets.size - 3}` : "";
7492
- parts.push(`${CYAN2}\u23F5 ${gadgetList}${more}${RESET2}`);
7826
+ parts.push(`${CYAN2}\u23F5 ${gadgetList}${more}${RESET3}`);
7493
7827
  }
7494
7828
  }
7495
- this.statusBox.setContent(parts.join(` ${GRAY}|${RESET2} `));
7829
+ this.statusBox.setContent(parts.join(" "));
7496
7830
  if (immediate) {
7497
7831
  this.renderNowCallback();
7498
7832
  } else {
@@ -7538,7 +7872,12 @@ var TUIApp = class _TUIApp {
7538
7872
  title: "llmist"
7539
7873
  });
7540
7874
  const { screen } = screenCtx;
7541
- const layout = createBlockLayout(screen);
7875
+ const showHints = options.showHints ?? true;
7876
+ const layout = createBlockLayout(screen, showHints);
7877
+ let hintsBar = null;
7878
+ if (layout.hintsBar) {
7879
+ hintsBar = new HintsBar(layout.hintsBar, () => screenCtx.requestRender());
7880
+ }
7542
7881
  const statusBar = new StatusBar(
7543
7882
  layout.statusBar,
7544
7883
  options.model,
@@ -7551,19 +7890,27 @@ var TUIApp = class _TUIApp {
7551
7890
  layout.body,
7552
7891
  screen,
7553
7892
  () => screenCtx.requestRender(),
7554
- () => screenCtx.renderNow()
7893
+ () => screenCtx.renderNow(),
7894
+ showHints
7555
7895
  );
7556
7896
  const blockRenderer = new BlockRenderer(
7557
7897
  layout.body,
7558
7898
  () => screenCtx.requestRender(),
7559
7899
  () => screenCtx.renderNow()
7560
7900
  );
7901
+ if (hintsBar) {
7902
+ blockRenderer.onHasContentChange((hasContent) => {
7903
+ hintsBar.setHasContent(hasContent);
7904
+ });
7905
+ }
7561
7906
  const controller = new TUIController({
7562
7907
  onFocusModeChange: (mode) => {
7563
7908
  applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx);
7909
+ hintsBar?.setFocusMode(mode);
7564
7910
  },
7565
7911
  onContentFilterModeChange: (mode) => {
7566
7912
  applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx);
7913
+ hintsBar?.setContentFilterMode(mode);
7567
7914
  }
7568
7915
  });
7569
7916
  const modalManager = new ModalManager();
@@ -7717,13 +8064,11 @@ var TUIApp = class _TUIApp {
7717
8064
  }
7718
8065
  /**
7719
8066
  * Wait for user to enter a new prompt (REPL mode).
7720
- * Stays in current mode (browse) - user can Tab to input or Enter to start typing.
7721
- * After the prompt is submitted, focus mode switches to BROWSE.
8067
+ * Stays in input mode after submission - user can watch output and type next message.
8068
+ * User can Ctrl+B to browse if they want to navigate blocks.
7722
8069
  */
7723
8070
  async waitForPrompt() {
7724
- const result = await this.inputHandler.waitForPrompt();
7725
- this.controller.setFocusMode("browse");
7726
- return result;
8071
+ return this.inputHandler.waitForPrompt();
7727
8072
  }
7728
8073
  /**
7729
8074
  * Enter the pending REPL prompt state without blocking.
@@ -7942,17 +8287,13 @@ var TUIApp = class _TUIApp {
7942
8287
  };
7943
8288
  function applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx) {
7944
8289
  statusBar.setFocusMode(mode);
7945
- if (mode === "input") {
7946
- layout.body.height = "100%-2";
7947
- } else {
7948
- layout.body.height = "100%-1";
7949
- }
7950
- screenCtx.renderNow();
7951
8290
  if (mode === "input") {
7952
8291
  inputHandler.activate();
7953
8292
  } else {
7954
8293
  inputHandler.deactivate();
8294
+ layout.body.focus();
7955
8295
  }
8296
+ screenCtx.renderNow();
7956
8297
  }
7957
8298
  function applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx) {
7958
8299
  blockRenderer.setContentFilterMode(mode);
@@ -8082,7 +8423,8 @@ async function executeAgent(promptArg, options, env, commandName) {
8082
8423
  tui = await TUIApp.create({
8083
8424
  model: options.model,
8084
8425
  stdin: env.stdin,
8085
- stdout: env.stdout
8426
+ stdout: env.stdout,
8427
+ showHints: options.showHints
8086
8428
  });
8087
8429
  try {
8088
8430
  const fullConfig = loadConfig();
@@ -8471,7 +8813,8 @@ function registerAgentCommand(program, env, config, globalSubagents, globalRateL
8471
8813
  globalRateLimits,
8472
8814
  globalRetry,
8473
8815
  profileRateLimits: config?.["rate-limits"],
8474
- profileRetry: config?.retry
8816
+ profileRetry: config?.retry,
8817
+ showHints: config?.["show-hints"]
8475
8818
  };
8476
8819
  return executeAgent(prompt, mergedOptions, env, "agent");
8477
8820
  }, env)
@@ -8696,7 +9039,7 @@ System Prompt (${chars.toLocaleString()} chars, ${lines} lines):
8696
9039
  }
8697
9040
 
8698
9041
  // src/environment.ts
8699
- import { join as join3 } from "path";
9042
+ import { join as join4 } from "path";
8700
9043
  import readline from "readline";
8701
9044
  import chalk4 from "chalk";
8702
9045
  import { createLogger, LLMist } from "llmist";
@@ -8719,7 +9062,7 @@ function createLoggerFactory(config, sessionLogDir) {
8719
9062
  }
8720
9063
  }
8721
9064
  if (sessionLogDir) {
8722
- const logFile = join3(sessionLogDir, "session.log.jsonl");
9065
+ const logFile = join4(sessionLogDir, "session.log.jsonl");
8723
9066
  const originalLogFile = process.env.LLMIST_LOG_FILE;
8724
9067
  process.env.LLMIST_LOG_FILE = logFile;
8725
9068
  const logger = createLogger(options);
@@ -9265,7 +9608,7 @@ function registerGadgetCommand(program, env) {
9265
9608
  }
9266
9609
 
9267
9610
  // src/image-command.ts
9268
- import { writeFileSync as writeFileSync2 } from "fs";
9611
+ import { writeFileSync as writeFileSync3 } from "fs";
9269
9612
  var DEFAULT_IMAGE_MODEL = "dall-e-3";
9270
9613
  async function executeImage(promptArg, options, env) {
9271
9614
  const prompt = await resolvePrompt(promptArg, env);
@@ -9289,7 +9632,7 @@ async function executeImage(promptArg, options, env) {
9289
9632
  const imageData = result.images[0];
9290
9633
  if (imageData.b64Json) {
9291
9634
  const buffer = Buffer.from(imageData.b64Json, "base64");
9292
- writeFileSync2(options.output, buffer);
9635
+ writeFileSync3(options.output, buffer);
9293
9636
  if (!options.quiet) {
9294
9637
  env.stderr.write(`${SUMMARY_PREFIX} Image saved to ${options.output}
9295
9638
  `);
@@ -9328,7 +9671,7 @@ function registerImageCommand(program, env, config) {
9328
9671
  }
9329
9672
 
9330
9673
  // src/init-command.ts
9331
- import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync3 } from "fs";
9674
+ import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync4 } from "fs";
9332
9675
  import { dirname as dirname2 } from "path";
9333
9676
  var STARTER_CONFIG = `# ~/.llmist/cli.toml
9334
9677
  # llmist CLI configuration file
@@ -9399,7 +9742,7 @@ async function executeInit(_options, env) {
9399
9742
  if (!existsSync3(configDir)) {
9400
9743
  mkdirSync(configDir, { recursive: true });
9401
9744
  }
9402
- writeFileSync3(configPath, STARTER_CONFIG, "utf-8");
9745
+ writeFileSync4(configPath, STARTER_CONFIG, "utf-8");
9403
9746
  env.stderr.write(`Created ${configPath}
9404
9747
  `);
9405
9748
  env.stderr.write("\n");
@@ -9831,7 +10174,7 @@ function registerModelsCommand(program, env) {
9831
10174
  import { existsSync as existsSync4 } from "fs";
9832
10175
  import { mkdir as mkdir2 } from "fs/promises";
9833
10176
  import { homedir as homedir3 } from "os";
9834
- import { join as join4 } from "path";
10177
+ import { join as join5 } from "path";
9835
10178
 
9836
10179
  // src/session-names.ts
9837
10180
  var ADJECTIVES = [
@@ -9966,16 +10309,16 @@ function generateSessionName() {
9966
10309
 
9967
10310
  // src/session.ts
9968
10311
  var currentSession;
9969
- var SESSION_LOGS_BASE = join4(homedir3(), ".llmist", "logs");
10312
+ var SESSION_LOGS_BASE = join5(homedir3(), ".llmist", "logs");
9970
10313
  function findUniqueName(baseName) {
9971
- const baseDir = join4(SESSION_LOGS_BASE, baseName);
10314
+ const baseDir = join5(SESSION_LOGS_BASE, baseName);
9972
10315
  if (!existsSync4(baseDir)) {
9973
10316
  return baseName;
9974
10317
  }
9975
10318
  let suffix = 2;
9976
10319
  while (suffix < 1e3) {
9977
10320
  const name = `${baseName}-${suffix}`;
9978
- const dir = join4(SESSION_LOGS_BASE, name);
10321
+ const dir = join5(SESSION_LOGS_BASE, name);
9979
10322
  if (!existsSync4(dir)) {
9980
10323
  return name;
9981
10324
  }
@@ -9989,14 +10332,14 @@ async function initSession() {
9989
10332
  }
9990
10333
  const baseName = generateSessionName();
9991
10334
  const name = findUniqueName(baseName);
9992
- const logDir = join4(SESSION_LOGS_BASE, name);
10335
+ const logDir = join5(SESSION_LOGS_BASE, name);
9993
10336
  await mkdir2(logDir, { recursive: true });
9994
10337
  currentSession = { name, logDir };
9995
10338
  return currentSession;
9996
10339
  }
9997
10340
 
9998
10341
  // src/speech-command.ts
9999
- import { writeFileSync as writeFileSync4 } from "fs";
10342
+ import { writeFileSync as writeFileSync5 } from "fs";
10000
10343
  var DEFAULT_SPEECH_MODEL = "tts-1";
10001
10344
  var DEFAULT_VOICE = "nova";
10002
10345
  async function executeSpeech(textArg, options, env) {
@@ -10019,7 +10362,7 @@ async function executeSpeech(textArg, options, env) {
10019
10362
  });
10020
10363
  const audioBuffer = Buffer.from(result.audio);
10021
10364
  if (options.output) {
10022
- writeFileSync4(options.output, audioBuffer);
10365
+ writeFileSync5(options.output, audioBuffer);
10023
10366
  if (!options.quiet) {
10024
10367
  env.stderr.write(`${SUMMARY_PREFIX} Audio saved to ${options.output}
10025
10368
  `);