@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 +457 -114
- package/dist/cli.js.map +1 -1
- package/package.json +3 -3
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.
|
|
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.
|
|
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.
|
|
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
|
|
5273
|
-
return `${indent}${color}${icon} ${node.message}${
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 -
|
|
6417
|
+
* Activate input mode - focus input bar and capture keyboard.
|
|
6250
6418
|
* Called by TUIApp when switching to input mode.
|
|
6251
|
-
*
|
|
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.
|
|
6446
|
+
this.body.height = this.bodyHeightWithoutInput;
|
|
6268
6447
|
this.renderNowCallback();
|
|
6269
6448
|
}
|
|
6270
6449
|
/**
|
|
6271
|
-
* Check if input mode is active (
|
|
6450
|
+
* Check if input mode is active (focused, capturing keystrokes).
|
|
6272
6451
|
*/
|
|
6273
6452
|
isInputActive() {
|
|
6274
|
-
return this.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
6772
|
+
bottom: inputBottom,
|
|
6479
6773
|
left: 0,
|
|
6480
|
-
width:
|
|
6481
|
-
// "
|
|
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:
|
|
6492
|
-
left:
|
|
6493
|
-
// Position after prompt label
|
|
6494
|
-
width: "100%-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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${
|
|
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:${
|
|
6981
|
+
content = `${RED}${BOLD}Error:${RESET2}
|
|
6676
6982
|
${error}`;
|
|
6677
6983
|
} else if (!result) {
|
|
6678
|
-
content = `${DIM}No result data available${
|
|
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${
|
|
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${
|
|
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${
|
|
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}${
|
|
7073
|
+
lines.push(`${DIM}${separator}${RESET2}`);
|
|
6768
7074
|
lines.push(
|
|
6769
|
-
`${roleColor}${BOLD}[${roleName}]${
|
|
7075
|
+
`${roleColor}${BOLD}[${roleName}]${RESET2} ${DIM}Message ${i + 1} of ${messages.length}${RESET2}`
|
|
6770
7076
|
);
|
|
6771
|
-
lines.push(`${DIM}${separator}${
|
|
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}]${
|
|
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}]${
|
|
7103
|
+
lines.push(`${DIM}[Audio: ${mediaType}]${RESET2}`);
|
|
6798
7104
|
} else if (isToolUsePart(part)) {
|
|
6799
|
-
lines.push(`${YELLOW}${BOLD}[Tool Use: ${part.name}]${
|
|
6800
|
-
lines.push(`${DIM}ID: ${part.id}${
|
|
6801
|
-
lines.push(`${DIM}Input:${
|
|
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]${
|
|
6806
|
-
lines.push(`${DIM}Tool Use ID: ${part.tool_use_id}${
|
|
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}]${
|
|
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}${
|
|
6851
|
-
lines.push(`${CYAN}${BOLD}Parameters${
|
|
6852
|
-
lines.push(`${DIM}${separator}${
|
|
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}${
|
|
6863
|
-
lines.push(`${GREEN}${BOLD}Result${
|
|
6864
|
-
lines.push(`${DIM}${separator}${
|
|
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"${
|
|
6881
|
-
result = result.replace(/: "([^"]*)"/g, `: ${GREEN}"$1"${
|
|
6882
|
-
result = result.replace(/: (-?\d+\.?\d*)/g, `: ${YELLOW}$1${
|
|
6883
|
-
result = result.replace(/: (true|false|null)/g, `: ${MAGENTA}$1${
|
|
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
|
|
7424
|
-
const
|
|
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.
|
|
7433
|
-
parts.push(`${BG_BLUE}${WHITE2}
|
|
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}${
|
|
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
|
|
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(`${
|
|
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${
|
|
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}${
|
|
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}${
|
|
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(" | ")}${
|
|
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}${
|
|
7826
|
+
parts.push(`${CYAN2}\u23F5 ${gadgetList}${more}${RESET3}`);
|
|
7493
7827
|
}
|
|
7494
7828
|
}
|
|
7495
|
-
this.statusBox.setContent(parts.join(
|
|
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
|
|
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
|
|
7721
|
-
*
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
10312
|
+
var SESSION_LOGS_BASE = join5(homedir3(), ".llmist", "logs");
|
|
9970
10313
|
function findUniqueName(baseName) {
|
|
9971
|
-
const baseDir =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
10365
|
+
writeFileSync5(options.output, audioBuffer);
|
|
10023
10366
|
if (!options.quiet) {
|
|
10024
10367
|
env.stderr.write(`${SUMMARY_PREFIX} Audio saved to ${options.output}
|
|
10025
10368
|
`);
|