@llmist/cli 15.4.1 → 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 +461 -114
- package/dist/cli.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.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",
|
|
@@ -1780,6 +1780,8 @@ Uses layered matching strategies (in order):
|
|
|
1780
1780
|
|
|
1781
1781
|
For multiple edits to the same file, call this gadget multiple times.
|
|
1782
1782
|
Each call provides immediate feedback, allowing you to adjust subsequent edits.`,
|
|
1783
|
+
maxConcurrent: 1,
|
|
1784
|
+
// Sequential execution to prevent race conditions
|
|
1783
1785
|
schema: z2.object({
|
|
1784
1786
|
filePath: z2.string().describe("Path to the file to edit (relative or absolute)"),
|
|
1785
1787
|
search: z2.string().describe("The content to search for in the file"),
|
|
@@ -2036,6 +2038,8 @@ import { z as z5 } from "zod";
|
|
|
2036
2038
|
var writeFile = createGadget5({
|
|
2037
2039
|
name: "WriteFile",
|
|
2038
2040
|
description: "Write content to a file. Creates parent directories if needed. Overwrites existing files. The file path must be within the current working directory or its subdirectories.",
|
|
2041
|
+
maxConcurrent: 1,
|
|
2042
|
+
// Sequential execution to prevent race conditions
|
|
2039
2043
|
schema: z5.object({
|
|
2040
2044
|
filePath: z5.string().describe("Path to the file to write (relative or absolute)"),
|
|
2041
2045
|
content: z5.string().describe("Content to write to the file")
|
|
@@ -4090,6 +4094,7 @@ function configToAgentOptions(config) {
|
|
|
4090
4094
|
if (r["max-timeout"] !== void 0) result.retryMaxTimeout = r["max-timeout"];
|
|
4091
4095
|
if (r.enabled === false) result.noRetry = true;
|
|
4092
4096
|
}
|
|
4097
|
+
if (config["show-hints"] !== void 0) result.showHints = config["show-hints"];
|
|
4093
4098
|
return result;
|
|
4094
4099
|
}
|
|
4095
4100
|
|
|
@@ -4667,6 +4672,10 @@ var BlockRenderer = class _BlockRenderer {
|
|
|
4667
4672
|
currentSessionId = 0;
|
|
4668
4673
|
/** Previous session ID (for deferred cleanup) */
|
|
4669
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;
|
|
4670
4679
|
constructor(container, renderCallback, renderNowCallback) {
|
|
4671
4680
|
this.container = container;
|
|
4672
4681
|
this.renderCallback = renderCallback;
|
|
@@ -4967,6 +4976,16 @@ var BlockRenderer = class _BlockRenderer {
|
|
|
4967
4976
|
child.detach();
|
|
4968
4977
|
}
|
|
4969
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);
|
|
4970
4989
|
}
|
|
4971
4990
|
/**
|
|
4972
4991
|
* Start a new session. Called at the start of each REPL turn.
|
|
@@ -5006,6 +5025,7 @@ var BlockRenderer = class _BlockRenderer {
|
|
|
5006
5025
|
}
|
|
5007
5026
|
this.previousSessionId = null;
|
|
5008
5027
|
this.renderCallback();
|
|
5028
|
+
this.notifyHasContentChange();
|
|
5009
5029
|
}
|
|
5010
5030
|
/**
|
|
5011
5031
|
* Get the current session ID (for node creation).
|
|
@@ -5094,6 +5114,17 @@ var BlockRenderer = class _BlockRenderer {
|
|
|
5094
5114
|
// ───────────────────────────────────────────────────────────────────────────
|
|
5095
5115
|
// Private - Node & Block Management
|
|
5096
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
|
+
}
|
|
5097
5128
|
generateId(prefix) {
|
|
5098
5129
|
return `${prefix}_${++this.nodeIdCounter}`;
|
|
5099
5130
|
}
|
|
@@ -5120,6 +5151,7 @@ var BlockRenderer = class _BlockRenderer {
|
|
|
5120
5151
|
}
|
|
5121
5152
|
this.applyBottomAlignmentAndScroll();
|
|
5122
5153
|
this.renderCallback();
|
|
5154
|
+
this.notifyHasContentChange();
|
|
5123
5155
|
}
|
|
5124
5156
|
/**
|
|
5125
5157
|
* Render a node and its children recursively.
|
|
@@ -5265,8 +5297,8 @@ ${fullContent}
|
|
|
5265
5297
|
case "system_message": {
|
|
5266
5298
|
const icon = this.getSystemMessageIcon(node.category);
|
|
5267
5299
|
const color = this.getSystemMessageColor(node.category);
|
|
5268
|
-
const
|
|
5269
|
-
return `${indent}${color}${icon} ${node.message}${
|
|
5300
|
+
const RESET3 = "\x1B[0m";
|
|
5301
|
+
return `${indent}${color}${icon} ${node.message}${RESET3}`;
|
|
5270
5302
|
}
|
|
5271
5303
|
}
|
|
5272
5304
|
}
|
|
@@ -5293,7 +5325,7 @@ ${fullContent}
|
|
|
5293
5325
|
getSystemMessageColor(category) {
|
|
5294
5326
|
const YELLOW2 = "\x1B[33m";
|
|
5295
5327
|
const BLUE = "\x1B[34m";
|
|
5296
|
-
const
|
|
5328
|
+
const GRAY2 = "\x1B[90m";
|
|
5297
5329
|
const RED2 = "\x1B[31m";
|
|
5298
5330
|
switch (category) {
|
|
5299
5331
|
case "throttle":
|
|
@@ -5301,7 +5333,7 @@ ${fullContent}
|
|
|
5301
5333
|
case "retry":
|
|
5302
5334
|
return BLUE;
|
|
5303
5335
|
case "info":
|
|
5304
|
-
return
|
|
5336
|
+
return GRAY2;
|
|
5305
5337
|
case "warning":
|
|
5306
5338
|
return YELLOW2;
|
|
5307
5339
|
case "error":
|
|
@@ -5575,6 +5607,7 @@ ${indicator}`;
|
|
|
5575
5607
|
}
|
|
5576
5608
|
this.applyBottomAlignmentAndScroll();
|
|
5577
5609
|
this.renderNowCallback();
|
|
5610
|
+
this.notifyHasContentChange();
|
|
5578
5611
|
}
|
|
5579
5612
|
/**
|
|
5580
5613
|
* Get the current content filter mode.
|
|
@@ -6032,9 +6065,129 @@ var TUIController = class {
|
|
|
6032
6065
|
}
|
|
6033
6066
|
};
|
|
6034
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
|
+
|
|
6035
6189
|
// src/tui/input-handler.ts
|
|
6036
|
-
var
|
|
6037
|
-
var ACTIVE_PROMPT = ">>> ";
|
|
6190
|
+
var PROMPT = "> ";
|
|
6038
6191
|
var InputHandler = class {
|
|
6039
6192
|
inputBar;
|
|
6040
6193
|
promptLabel;
|
|
@@ -6046,6 +6199,14 @@ var InputHandler = class {
|
|
|
6046
6199
|
pendingInput = null;
|
|
6047
6200
|
/** Whether we're waiting for REPL prompt (vs AskUser which should auto-focus) */
|
|
6048
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;
|
|
6049
6210
|
/** Callback when Ctrl+C is pressed */
|
|
6050
6211
|
ctrlCCallback = null;
|
|
6051
6212
|
/** Callback when Ctrl+B is pressed (toggle focus mode) */
|
|
@@ -6062,16 +6223,23 @@ var InputHandler = class {
|
|
|
6062
6223
|
midSessionHandler = null;
|
|
6063
6224
|
/** Callback to check current focus mode (to avoid conflicts with browse mode) */
|
|
6064
6225
|
getFocusModeCallback = null;
|
|
6065
|
-
|
|
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) {
|
|
6066
6231
|
this.inputBar = inputBar;
|
|
6067
6232
|
this.promptLabel = promptLabel;
|
|
6068
6233
|
this.body = body;
|
|
6069
6234
|
this.screen = screen;
|
|
6070
6235
|
this.renderCallback = renderCallback;
|
|
6071
6236
|
this.renderNowCallback = renderNowCallback ?? renderCallback;
|
|
6237
|
+
this.bodyHeightWithInput = hasHints ? "100%-3" : "100%-2";
|
|
6238
|
+
this.bodyHeightWithoutInput = hasHints ? "100%-2" : "100%-1";
|
|
6072
6239
|
this.inputBar.on("submit", (value) => {
|
|
6073
6240
|
this.handleSubmit(value);
|
|
6074
6241
|
});
|
|
6242
|
+
this.setupBracketedPasteHandler();
|
|
6075
6243
|
this.inputBar.on("cancel", () => {
|
|
6076
6244
|
this.handleCancel();
|
|
6077
6245
|
});
|
|
@@ -6105,6 +6273,10 @@ var InputHandler = class {
|
|
|
6105
6273
|
this.ctrlPCallback();
|
|
6106
6274
|
}
|
|
6107
6275
|
});
|
|
6276
|
+
this.inputBar.key(["C-s"], () => {
|
|
6277
|
+
const currentValue = this.inputBar.getValue();
|
|
6278
|
+
this.openEditorForInput(currentValue);
|
|
6279
|
+
});
|
|
6108
6280
|
this.screen.key(["enter"], () => {
|
|
6109
6281
|
if (this.isPendingREPLPrompt) {
|
|
6110
6282
|
if (this.getFocusModeCallback?.() === "browse") {
|
|
@@ -6242,37 +6414,54 @@ var InputHandler = class {
|
|
|
6242
6414
|
// Focus Mode API (controlled by TUIApp)
|
|
6243
6415
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
6244
6416
|
/**
|
|
6245
|
-
* Activate input mode -
|
|
6417
|
+
* Activate input mode - focus input bar and capture keyboard.
|
|
6246
6418
|
* Called by TUIApp when switching to input mode.
|
|
6247
|
-
*
|
|
6419
|
+
* Shows input bar with active prompt (">>>") and starts capturing keystrokes.
|
|
6248
6420
|
*/
|
|
6249
6421
|
activate() {
|
|
6250
6422
|
this.isPendingREPLPrompt = false;
|
|
6423
|
+
this.isActive = true;
|
|
6251
6424
|
this.promptLabel.show();
|
|
6252
6425
|
this.inputBar.show();
|
|
6426
|
+
this.body.height = this.bodyHeightWithInput;
|
|
6427
|
+
this.setPrompt(PROMPT);
|
|
6253
6428
|
this.renderNowCallback();
|
|
6254
6429
|
this.inputBar.readInput();
|
|
6255
6430
|
}
|
|
6431
|
+
/** Flag to prevent handleCancel from re-entering during deactivation */
|
|
6432
|
+
isDeactivating = false;
|
|
6256
6433
|
/**
|
|
6257
6434
|
* Deactivate input mode - hide input bar completely.
|
|
6258
6435
|
* Called by TUIApp when switching to browse mode.
|
|
6436
|
+
* Input bar is hidden to give more space to content.
|
|
6259
6437
|
*/
|
|
6260
6438
|
deactivate() {
|
|
6439
|
+
this.isPendingREPLPrompt = false;
|
|
6440
|
+
this.isActive = false;
|
|
6441
|
+
this.isDeactivating = true;
|
|
6442
|
+
this.inputBar.cancel();
|
|
6443
|
+
this.isDeactivating = false;
|
|
6261
6444
|
this.promptLabel.hide();
|
|
6262
6445
|
this.inputBar.hide();
|
|
6263
|
-
this.
|
|
6446
|
+
this.body.height = this.bodyHeightWithoutInput;
|
|
6264
6447
|
this.renderNowCallback();
|
|
6265
6448
|
}
|
|
6266
6449
|
/**
|
|
6267
|
-
* Check if input mode is active (
|
|
6450
|
+
* Check if input mode is active (focused, capturing keystrokes).
|
|
6268
6451
|
*/
|
|
6269
6452
|
isInputActive() {
|
|
6270
|
-
return this.
|
|
6453
|
+
return this.isActive;
|
|
6271
6454
|
}
|
|
6272
6455
|
/**
|
|
6273
6456
|
* Handle input submission.
|
|
6274
6457
|
*/
|
|
6275
6458
|
handleSubmit(rawValue) {
|
|
6459
|
+
if (this.isPasting) {
|
|
6460
|
+
return;
|
|
6461
|
+
}
|
|
6462
|
+
if (this.fromEditor) {
|
|
6463
|
+
this.fromEditor = false;
|
|
6464
|
+
}
|
|
6276
6465
|
const value = rawValue.trim();
|
|
6277
6466
|
if (!value) {
|
|
6278
6467
|
this.inputBar.readInput();
|
|
@@ -6294,18 +6483,93 @@ var InputHandler = class {
|
|
|
6294
6483
|
* Handle input cancellation (ESC key).
|
|
6295
6484
|
*/
|
|
6296
6485
|
handleCancel() {
|
|
6486
|
+
if (this.isDeactivating) {
|
|
6487
|
+
return;
|
|
6488
|
+
}
|
|
6297
6489
|
if (this.pendingInput) {
|
|
6298
6490
|
this.inputBar.readInput();
|
|
6299
6491
|
} else {
|
|
6300
6492
|
this.setIdle();
|
|
6301
6493
|
}
|
|
6302
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
|
+
}
|
|
6303
6566
|
/**
|
|
6304
6567
|
* Set input to idle state.
|
|
6305
6568
|
*/
|
|
6306
6569
|
setIdle() {
|
|
6307
6570
|
this.isPendingREPLPrompt = false;
|
|
6308
|
-
this.
|
|
6571
|
+
this.isActive = false;
|
|
6572
|
+
this.setPrompt(PROMPT);
|
|
6309
6573
|
this.inputBar.setValue("");
|
|
6310
6574
|
this.renderCallback();
|
|
6311
6575
|
}
|
|
@@ -6323,7 +6587,7 @@ var InputHandler = class {
|
|
|
6323
6587
|
*/
|
|
6324
6588
|
setPendingPrompt() {
|
|
6325
6589
|
this.isPendingREPLPrompt = true;
|
|
6326
|
-
this.
|
|
6590
|
+
this.setPrompt(PROMPT);
|
|
6327
6591
|
this.inputBar.setValue("");
|
|
6328
6592
|
this.renderCallback();
|
|
6329
6593
|
}
|
|
@@ -6332,11 +6596,42 @@ var InputHandler = class {
|
|
|
6332
6596
|
*/
|
|
6333
6597
|
setActive() {
|
|
6334
6598
|
this.isPendingREPLPrompt = false;
|
|
6335
|
-
this.
|
|
6599
|
+
this.isActive = true;
|
|
6600
|
+
this.promptLabel.show();
|
|
6601
|
+
this.inputBar.show();
|
|
6602
|
+
this.body.height = this.bodyHeightWithInput;
|
|
6603
|
+
this.setPrompt(PROMPT);
|
|
6336
6604
|
this.inputBar.setValue("");
|
|
6337
6605
|
this.renderNowCallback();
|
|
6338
6606
|
this.inputBar.readInput();
|
|
6339
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
|
+
}
|
|
6340
6635
|
};
|
|
6341
6636
|
|
|
6342
6637
|
// src/tui/keymap.ts
|
|
@@ -6442,13 +6737,16 @@ var KeyboardManager = class {
|
|
|
6442
6737
|
|
|
6443
6738
|
// src/tui/layout.ts
|
|
6444
6739
|
import { Box as Box2, ScrollableBox, Text, Textbox } from "@unblessed/node";
|
|
6445
|
-
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";
|
|
6446
6744
|
const body = new ScrollableBox({
|
|
6447
6745
|
parent: screen,
|
|
6448
6746
|
top: 0,
|
|
6449
6747
|
left: 0,
|
|
6450
6748
|
width: "100%",
|
|
6451
|
-
height:
|
|
6749
|
+
height: bodyHeight,
|
|
6452
6750
|
// Scrolling configuration
|
|
6453
6751
|
scrollable: true,
|
|
6454
6752
|
alwaysScroll: true,
|
|
@@ -6471,10 +6769,10 @@ function createBlockLayout(screen) {
|
|
|
6471
6769
|
});
|
|
6472
6770
|
const promptLabel = new Text({
|
|
6473
6771
|
parent: screen,
|
|
6474
|
-
bottom:
|
|
6772
|
+
bottom: inputBottom,
|
|
6475
6773
|
left: 0,
|
|
6476
|
-
width:
|
|
6477
|
-
// "
|
|
6774
|
+
width: 2,
|
|
6775
|
+
// "> " = 2 chars
|
|
6478
6776
|
height: 1,
|
|
6479
6777
|
content: "> ",
|
|
6480
6778
|
style: {
|
|
@@ -6484,10 +6782,10 @@ function createBlockLayout(screen) {
|
|
|
6484
6782
|
});
|
|
6485
6783
|
const inputBar = new Textbox({
|
|
6486
6784
|
parent: screen,
|
|
6487
|
-
bottom:
|
|
6488
|
-
left:
|
|
6489
|
-
// Position after prompt label
|
|
6490
|
-
width: "100%-
|
|
6785
|
+
bottom: inputBottom,
|
|
6786
|
+
left: 2,
|
|
6787
|
+
// Position after prompt label ("> " = 2 chars)
|
|
6788
|
+
width: "100%-2",
|
|
6491
6789
|
height: 1,
|
|
6492
6790
|
keys: true,
|
|
6493
6791
|
mouse: true,
|
|
@@ -6498,7 +6796,7 @@ function createBlockLayout(screen) {
|
|
|
6498
6796
|
});
|
|
6499
6797
|
const statusBar = new Box2({
|
|
6500
6798
|
parent: screen,
|
|
6501
|
-
bottom:
|
|
6799
|
+
bottom: statusBottom,
|
|
6502
6800
|
left: 0,
|
|
6503
6801
|
width: "100%",
|
|
6504
6802
|
height: 1,
|
|
@@ -6508,7 +6806,19 @@ function createBlockLayout(screen) {
|
|
|
6508
6806
|
bg: "black"
|
|
6509
6807
|
}
|
|
6510
6808
|
});
|
|
6511
|
-
|
|
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 };
|
|
6512
6822
|
}
|
|
6513
6823
|
|
|
6514
6824
|
// src/tui/approval-dialog.ts
|
|
@@ -6629,7 +6939,7 @@ function escapeContent(str) {
|
|
|
6629
6939
|
|
|
6630
6940
|
// src/tui/raw-viewer.ts
|
|
6631
6941
|
import { Box as Box4 } from "@unblessed/node";
|
|
6632
|
-
var
|
|
6942
|
+
var RESET2 = "\x1B[0m";
|
|
6633
6943
|
var BOLD = "\x1B[1m";
|
|
6634
6944
|
var DIM = "\x1B[2m";
|
|
6635
6945
|
var RED = "\x1B[31m";
|
|
@@ -6661,17 +6971,17 @@ function showRawViewer(options) {
|
|
|
6661
6971
|
if (mode === "request") {
|
|
6662
6972
|
title = ` Raw Parameters - ${gadgetName} `;
|
|
6663
6973
|
if (!parameters || Object.keys(parameters).length === 0) {
|
|
6664
|
-
content = `${DIM}No parameters${
|
|
6974
|
+
content = `${DIM}No parameters${RESET2}`;
|
|
6665
6975
|
} else {
|
|
6666
6976
|
content = formatGadgetParameters(parameters);
|
|
6667
6977
|
}
|
|
6668
6978
|
} else {
|
|
6669
6979
|
title = ` Raw Result - ${gadgetName} `;
|
|
6670
6980
|
if (error) {
|
|
6671
|
-
content = `${RED}${BOLD}Error:${
|
|
6981
|
+
content = `${RED}${BOLD}Error:${RESET2}
|
|
6672
6982
|
${error}`;
|
|
6673
6983
|
} else if (!result) {
|
|
6674
|
-
content = `${DIM}No result data available${
|
|
6984
|
+
content = `${DIM}No result data available${RESET2}`;
|
|
6675
6985
|
} else {
|
|
6676
6986
|
content = formatGadgetResult(result);
|
|
6677
6987
|
}
|
|
@@ -6680,14 +6990,14 @@ ${error}`;
|
|
|
6680
6990
|
if (mode === "request") {
|
|
6681
6991
|
title = ` Raw Request - #${iteration} ${model} `;
|
|
6682
6992
|
if (!request || request.length === 0) {
|
|
6683
|
-
content = `${DIM}No request data available${
|
|
6993
|
+
content = `${DIM}No request data available${RESET2}`;
|
|
6684
6994
|
} else {
|
|
6685
6995
|
content = formatMessages(request);
|
|
6686
6996
|
}
|
|
6687
6997
|
} else {
|
|
6688
6998
|
title = ` Raw Response - #${iteration} ${model} `;
|
|
6689
6999
|
if (!response) {
|
|
6690
|
-
content = `${DIM}No response data available${
|
|
7000
|
+
content = `${DIM}No response data available${RESET2}`;
|
|
6691
7001
|
} else {
|
|
6692
7002
|
content = response;
|
|
6693
7003
|
}
|
|
@@ -6728,7 +7038,7 @@ ${error}`;
|
|
|
6728
7038
|
left: 0,
|
|
6729
7039
|
width: "100%",
|
|
6730
7040
|
height: 1,
|
|
6731
|
-
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}`,
|
|
6732
7042
|
tags: false,
|
|
6733
7043
|
style: { fg: "white", bg: "black" }
|
|
6734
7044
|
});
|
|
@@ -6760,11 +7070,11 @@ function formatMessages(messages) {
|
|
|
6760
7070
|
const msg = messages[i];
|
|
6761
7071
|
const roleColor = getRoleColor(msg.role);
|
|
6762
7072
|
const roleName = msg.role.toUpperCase();
|
|
6763
|
-
lines.push(`${DIM}${separator}${
|
|
7073
|
+
lines.push(`${DIM}${separator}${RESET2}`);
|
|
6764
7074
|
lines.push(
|
|
6765
|
-
`${roleColor}${BOLD}[${roleName}]${
|
|
7075
|
+
`${roleColor}${BOLD}[${roleName}]${RESET2} ${DIM}Message ${i + 1} of ${messages.length}${RESET2}`
|
|
6766
7076
|
);
|
|
6767
|
-
lines.push(`${DIM}${separator}${
|
|
7077
|
+
lines.push(`${DIM}${separator}${RESET2}`);
|
|
6768
7078
|
lines.push("");
|
|
6769
7079
|
const contentLines = formatMessageContent(msg.content);
|
|
6770
7080
|
lines.push(...contentLines);
|
|
@@ -6787,19 +7097,19 @@ function formatMessageContent(content) {
|
|
|
6787
7097
|
lines.push(...part.text.split("\n"));
|
|
6788
7098
|
} else if (isImagePart(part)) {
|
|
6789
7099
|
const mediaType = part.source?.media_type || "unknown";
|
|
6790
|
-
lines.push(`${DIM}[Image: ${mediaType}]${
|
|
7100
|
+
lines.push(`${DIM}[Image: ${mediaType}]${RESET2}`);
|
|
6791
7101
|
} else if (isAudioPart(part)) {
|
|
6792
7102
|
const mediaType = part.source?.media_type || "unknown";
|
|
6793
|
-
lines.push(`${DIM}[Audio: ${mediaType}]${
|
|
7103
|
+
lines.push(`${DIM}[Audio: ${mediaType}]${RESET2}`);
|
|
6794
7104
|
} else if (isToolUsePart(part)) {
|
|
6795
|
-
lines.push(`${YELLOW}${BOLD}[Tool Use: ${part.name}]${
|
|
6796
|
-
lines.push(`${DIM}ID: ${part.id}${
|
|
6797
|
-
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}`);
|
|
6798
7108
|
const inputStr = JSON.stringify(part.input, null, 2);
|
|
6799
7109
|
lines.push(...inputStr.split("\n").map((l) => ` ${l}`));
|
|
6800
7110
|
} else if (isToolResultPart(part)) {
|
|
6801
|
-
lines.push(`${CYAN}${BOLD}[Tool Result]${
|
|
6802
|
-
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}`);
|
|
6803
7113
|
if (typeof part.content === "string") {
|
|
6804
7114
|
lines.push(...part.content.split("\n"));
|
|
6805
7115
|
} else {
|
|
@@ -6807,7 +7117,7 @@ function formatMessageContent(content) {
|
|
|
6807
7117
|
}
|
|
6808
7118
|
} else {
|
|
6809
7119
|
const partType = part.type || "unknown";
|
|
6810
|
-
lines.push(`${DIM}[${partType}]${
|
|
7120
|
+
lines.push(`${DIM}[${partType}]${RESET2}`);
|
|
6811
7121
|
lines.push(JSON.stringify(part, null, 2));
|
|
6812
7122
|
}
|
|
6813
7123
|
}
|
|
@@ -6843,9 +7153,9 @@ function isToolResultPart(part) {
|
|
|
6843
7153
|
function formatGadgetParameters(params) {
|
|
6844
7154
|
const lines = [];
|
|
6845
7155
|
const separator = "\u2500".repeat(78);
|
|
6846
|
-
lines.push(`${DIM}${separator}${
|
|
6847
|
-
lines.push(`${CYAN}${BOLD}Parameters${
|
|
6848
|
-
lines.push(`${DIM}${separator}${
|
|
7156
|
+
lines.push(`${DIM}${separator}${RESET2}`);
|
|
7157
|
+
lines.push(`${CYAN}${BOLD}Parameters${RESET2}`);
|
|
7158
|
+
lines.push(`${DIM}${separator}${RESET2}`);
|
|
6849
7159
|
lines.push("");
|
|
6850
7160
|
const json = JSON.stringify(params, null, 2);
|
|
6851
7161
|
const highlighted = highlightJson(json);
|
|
@@ -6855,9 +7165,9 @@ function formatGadgetParameters(params) {
|
|
|
6855
7165
|
function formatGadgetResult(result) {
|
|
6856
7166
|
const lines = [];
|
|
6857
7167
|
const separator = "\u2500".repeat(78);
|
|
6858
|
-
lines.push(`${DIM}${separator}${
|
|
6859
|
-
lines.push(`${GREEN}${BOLD}Result${
|
|
6860
|
-
lines.push(`${DIM}${separator}${
|
|
7168
|
+
lines.push(`${DIM}${separator}${RESET2}`);
|
|
7169
|
+
lines.push(`${GREEN}${BOLD}Result${RESET2}`);
|
|
7170
|
+
lines.push(`${DIM}${separator}${RESET2}`);
|
|
6861
7171
|
lines.push("");
|
|
6862
7172
|
const trimmed = result.trim();
|
|
6863
7173
|
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
@@ -6873,10 +7183,10 @@ function formatGadgetResult(result) {
|
|
|
6873
7183
|
return lines.join("\n");
|
|
6874
7184
|
}
|
|
6875
7185
|
function highlightJson(json) {
|
|
6876
|
-
let result = json.replace(/"([^"]+)":/g, `${CYAN}"$1"${
|
|
6877
|
-
result = result.replace(/: "([^"]*)"/g, `: ${GREEN}"$1"${
|
|
6878
|
-
result = result.replace(/: (-?\d+\.?\d*)/g, `: ${YELLOW}$1${
|
|
6879
|
-
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}`);
|
|
6880
7190
|
return result;
|
|
6881
7191
|
}
|
|
6882
7192
|
|
|
@@ -6955,6 +7265,7 @@ function createScreen(options) {
|
|
|
6955
7265
|
// Use alternate screen buffer (restores on exit)
|
|
6956
7266
|
useBCE: true
|
|
6957
7267
|
});
|
|
7268
|
+
screen.program.write("\x1B[?2004h");
|
|
6958
7269
|
let isDestroyed = false;
|
|
6959
7270
|
let renderPending = false;
|
|
6960
7271
|
let renderTimeout = null;
|
|
@@ -6985,6 +7296,7 @@ function createScreen(options) {
|
|
|
6985
7296
|
clearTimeout(renderTimeout);
|
|
6986
7297
|
renderTimeout = null;
|
|
6987
7298
|
}
|
|
7299
|
+
screen.program.write("\x1B[?2004l");
|
|
6988
7300
|
screen.destroy();
|
|
6989
7301
|
process.stdout.write("\x1B[?25h");
|
|
6990
7302
|
};
|
|
@@ -7026,7 +7338,7 @@ var StatusBar = class {
|
|
|
7026
7338
|
streamingOutputTokens = 0;
|
|
7027
7339
|
/** Whether we're currently streaming */
|
|
7028
7340
|
isStreaming = false;
|
|
7029
|
-
/** Active LLM calls: Map from label ("#1") to model name */
|
|
7341
|
+
/** Active LLM calls: Map from label ("#1") to model name and start time */
|
|
7030
7342
|
activeLLMCalls = /* @__PURE__ */ new Map();
|
|
7031
7343
|
/** Active gadgets (by name) */
|
|
7032
7344
|
activeGadgets = /* @__PURE__ */ new Set();
|
|
@@ -7123,7 +7435,7 @@ var StatusBar = class {
|
|
|
7123
7435
|
* @param model - Full model name like "gemini:gemini-2.5-flash"
|
|
7124
7436
|
*/
|
|
7125
7437
|
startLLMCall(label, model) {
|
|
7126
|
-
this.activeLLMCalls.set(label, model);
|
|
7438
|
+
this.activeLLMCalls.set(label, { model, startTime: Date.now() });
|
|
7127
7439
|
this.startSpinner();
|
|
7128
7440
|
this.render();
|
|
7129
7441
|
}
|
|
@@ -7136,6 +7448,19 @@ var StatusBar = class {
|
|
|
7136
7448
|
this.maybeStopSpinner();
|
|
7137
7449
|
this.render();
|
|
7138
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
|
+
}
|
|
7139
7464
|
/**
|
|
7140
7465
|
* Track a gadget as active.
|
|
7141
7466
|
* @param name - Gadget name like "ReadFile" or "BrowseWeb"
|
|
@@ -7267,6 +7592,15 @@ var StatusBar = class {
|
|
|
7267
7592
|
this.startLLMCall(label, event.model);
|
|
7268
7593
|
break;
|
|
7269
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
|
+
}
|
|
7270
7604
|
case "llm_call_complete": {
|
|
7271
7605
|
const label = this.nodeIdToLabel.get(event.nodeId);
|
|
7272
7606
|
if (label) {
|
|
@@ -7410,68 +7744,72 @@ var StatusBar = class {
|
|
|
7410
7744
|
* @param immediate - If true, render immediately without debouncing
|
|
7411
7745
|
*/
|
|
7412
7746
|
render(immediate = false) {
|
|
7413
|
-
const elapsed = this.getElapsedSeconds().toFixed(1);
|
|
7414
7747
|
const YELLOW2 = "\x1B[33m";
|
|
7415
7748
|
const GREEN2 = "\x1B[32m";
|
|
7416
7749
|
const BLUE = "\x1B[34m";
|
|
7417
7750
|
const CYAN2 = "\x1B[36m";
|
|
7418
7751
|
const MAGENTA2 = "\x1B[35m";
|
|
7419
|
-
const
|
|
7420
|
-
const
|
|
7752
|
+
const GRAY2 = "\x1B[90m";
|
|
7753
|
+
const RESET3 = "\x1B[0m";
|
|
7421
7754
|
const BG_BLUE = "\x1B[44m";
|
|
7422
|
-
const BG_GREEN = "\x1B[42m";
|
|
7423
7755
|
const WHITE2 = "\x1B[37m";
|
|
7424
|
-
const BLACK = "\x1B[30m";
|
|
7425
7756
|
const displayInputTokens = this.metrics.inputTokens + this.streamingInputTokens;
|
|
7426
7757
|
const displayOutputTokens = this.metrics.outputTokens + this.streamingOutputTokens;
|
|
7427
7758
|
const parts = [];
|
|
7428
|
-
if (this.
|
|
7429
|
-
parts.push(`${BG_BLUE}${WHITE2}
|
|
7430
|
-
} else if (this.focusMode === "browse") {
|
|
7431
|
-
parts.push(`${BG_BLUE}${WHITE2} BROWSE ${RESET2}`);
|
|
7432
|
-
} else {
|
|
7433
|
-
parts.push(`${BG_GREEN}${BLACK} INPUT ${RESET2}`);
|
|
7759
|
+
if (this.focusMode === "browse" && this.contentFilterMode !== "focused") {
|
|
7760
|
+
parts.push(`${BG_BLUE}${WHITE2} BROWSE ${RESET3}`);
|
|
7434
7761
|
}
|
|
7435
7762
|
if (this.profiles.length > 0) {
|
|
7436
7763
|
const profile = this.profiles[this.currentProfileIndex];
|
|
7437
7764
|
const display = profile.length > 12 ? `${profile.slice(0, 11)}\u2026` : profile;
|
|
7438
|
-
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}`);
|
|
7439
7770
|
}
|
|
7440
|
-
const inputPrefix = this.isStreaming && this.streamingInputTokens > 0 ? "~" : "";
|
|
7441
|
-
parts.push(`${YELLOW2}\u2191 ${inputPrefix}${formatTokens(displayInputTokens)}${RESET2}`);
|
|
7442
7771
|
if (this.metrics.cachedTokens > 0) {
|
|
7443
|
-
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}`);
|
|
7444
7786
|
}
|
|
7445
|
-
const outputPrefix = this.isStreaming ? "~" : "";
|
|
7446
|
-
parts.push(`${GREEN2}\u2193 ${outputPrefix}${formatTokens(displayOutputTokens)}${RESET2}`);
|
|
7447
|
-
parts.push(`${GRAY}${elapsed}s${RESET2}`);
|
|
7448
|
-
parts.push(`${CYAN2}$${formatCost(this.metrics.cost)}${RESET2}`);
|
|
7449
7787
|
if (this.selectionDebugCallback) {
|
|
7450
7788
|
const debug = this.selectionDebugCallback();
|
|
7451
7789
|
const debugStr = `sel:${debug.index}/${debug.total}`;
|
|
7452
7790
|
const typeStr = debug.nodeType ? ` [${debug.nodeType}]` : "";
|
|
7453
|
-
parts.push(`${
|
|
7791
|
+
parts.push(`${GRAY2}${debugStr}${typeStr}${RESET3}`);
|
|
7454
7792
|
}
|
|
7455
7793
|
if (this.rateLimitState?.isThrottling) {
|
|
7456
7794
|
const { triggeredBy } = this.rateLimitState;
|
|
7457
7795
|
if (triggeredBy?.daily) {
|
|
7458
|
-
parts.push(`${YELLOW2}\u23F8 Daily limit, resets midnight UTC${
|
|
7796
|
+
parts.push(`${YELLOW2}\u23F8 Daily limit, resets midnight UTC${RESET3}`);
|
|
7459
7797
|
} else {
|
|
7460
7798
|
const seconds = Math.ceil(this.rateLimitState.delayMs / 1e3);
|
|
7461
7799
|
const reason = triggeredBy?.rpm ? " (RPM)" : triggeredBy?.tpm ? " (TPM)" : "";
|
|
7462
|
-
parts.push(`${YELLOW2}\u23F8 Throttled ${seconds}s${reason}${
|
|
7800
|
+
parts.push(`${YELLOW2}\u23F8 Throttled ${seconds}s${reason}${RESET3}`);
|
|
7463
7801
|
}
|
|
7464
7802
|
}
|
|
7465
7803
|
if (this.retryState) {
|
|
7466
7804
|
const { attemptNumber, retriesLeft } = this.retryState;
|
|
7467
7805
|
const totalAttempts = attemptNumber + retriesLeft;
|
|
7468
|
-
parts.push(`${BLUE}\u{1F504} Retry ${attemptNumber}/${totalAttempts}${
|
|
7806
|
+
parts.push(`${BLUE}\u{1F504} Retry ${attemptNumber}/${totalAttempts}${RESET3}`);
|
|
7469
7807
|
}
|
|
7470
7808
|
if (this.activeLLMCalls.size > 0 || this.activeGadgets.size > 0) {
|
|
7471
7809
|
const spinner = SPINNER_FRAMES2[this.spinnerFrame];
|
|
7472
7810
|
if (this.activeLLMCalls.size > 0) {
|
|
7473
7811
|
const byModel = /* @__PURE__ */ new Map();
|
|
7474
|
-
for (const [label, model] of this.activeLLMCalls) {
|
|
7812
|
+
for (const [label, { model }] of this.activeLLMCalls) {
|
|
7475
7813
|
const shortModel = this.shortenModelName(model);
|
|
7476
7814
|
if (!byModel.has(shortModel)) byModel.set(shortModel, []);
|
|
7477
7815
|
byModel.get(shortModel)?.push(label);
|
|
@@ -7480,15 +7818,15 @@ var StatusBar = class {
|
|
|
7480
7818
|
for (const [model, labels] of byModel) {
|
|
7481
7819
|
llmParts.push(`${model} ${labels.join(", ")}`);
|
|
7482
7820
|
}
|
|
7483
|
-
parts.push(`${spinner} ${MAGENTA2}${llmParts.join(" | ")}${
|
|
7821
|
+
parts.push(`${spinner} ${MAGENTA2}${llmParts.join(" | ")}${RESET3}`);
|
|
7484
7822
|
}
|
|
7485
7823
|
if (this.activeGadgets.size > 0) {
|
|
7486
7824
|
const gadgetList = [...this.activeGadgets].slice(0, 3).join(", ");
|
|
7487
7825
|
const more = this.activeGadgets.size > 3 ? ` +${this.activeGadgets.size - 3}` : "";
|
|
7488
|
-
parts.push(`${CYAN2}\u23F5 ${gadgetList}${more}${
|
|
7826
|
+
parts.push(`${CYAN2}\u23F5 ${gadgetList}${more}${RESET3}`);
|
|
7489
7827
|
}
|
|
7490
7828
|
}
|
|
7491
|
-
this.statusBox.setContent(parts.join(
|
|
7829
|
+
this.statusBox.setContent(parts.join(" "));
|
|
7492
7830
|
if (immediate) {
|
|
7493
7831
|
this.renderNowCallback();
|
|
7494
7832
|
} else {
|
|
@@ -7534,7 +7872,12 @@ var TUIApp = class _TUIApp {
|
|
|
7534
7872
|
title: "llmist"
|
|
7535
7873
|
});
|
|
7536
7874
|
const { screen } = screenCtx;
|
|
7537
|
-
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
|
+
}
|
|
7538
7881
|
const statusBar = new StatusBar(
|
|
7539
7882
|
layout.statusBar,
|
|
7540
7883
|
options.model,
|
|
@@ -7547,19 +7890,27 @@ var TUIApp = class _TUIApp {
|
|
|
7547
7890
|
layout.body,
|
|
7548
7891
|
screen,
|
|
7549
7892
|
() => screenCtx.requestRender(),
|
|
7550
|
-
() => screenCtx.renderNow()
|
|
7893
|
+
() => screenCtx.renderNow(),
|
|
7894
|
+
showHints
|
|
7551
7895
|
);
|
|
7552
7896
|
const blockRenderer = new BlockRenderer(
|
|
7553
7897
|
layout.body,
|
|
7554
7898
|
() => screenCtx.requestRender(),
|
|
7555
7899
|
() => screenCtx.renderNow()
|
|
7556
7900
|
);
|
|
7901
|
+
if (hintsBar) {
|
|
7902
|
+
blockRenderer.onHasContentChange((hasContent) => {
|
|
7903
|
+
hintsBar.setHasContent(hasContent);
|
|
7904
|
+
});
|
|
7905
|
+
}
|
|
7557
7906
|
const controller = new TUIController({
|
|
7558
7907
|
onFocusModeChange: (mode) => {
|
|
7559
7908
|
applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx);
|
|
7909
|
+
hintsBar?.setFocusMode(mode);
|
|
7560
7910
|
},
|
|
7561
7911
|
onContentFilterModeChange: (mode) => {
|
|
7562
7912
|
applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx);
|
|
7913
|
+
hintsBar?.setContentFilterMode(mode);
|
|
7563
7914
|
}
|
|
7564
7915
|
});
|
|
7565
7916
|
const modalManager = new ModalManager();
|
|
@@ -7713,13 +8064,11 @@ var TUIApp = class _TUIApp {
|
|
|
7713
8064
|
}
|
|
7714
8065
|
/**
|
|
7715
8066
|
* Wait for user to enter a new prompt (REPL mode).
|
|
7716
|
-
* Stays in
|
|
7717
|
-
*
|
|
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.
|
|
7718
8069
|
*/
|
|
7719
8070
|
async waitForPrompt() {
|
|
7720
|
-
|
|
7721
|
-
this.controller.setFocusMode("browse");
|
|
7722
|
-
return result;
|
|
8071
|
+
return this.inputHandler.waitForPrompt();
|
|
7723
8072
|
}
|
|
7724
8073
|
/**
|
|
7725
8074
|
* Enter the pending REPL prompt state without blocking.
|
|
@@ -7938,17 +8287,13 @@ var TUIApp = class _TUIApp {
|
|
|
7938
8287
|
};
|
|
7939
8288
|
function applyFocusMode(mode, layout, statusBar, inputHandler, screenCtx) {
|
|
7940
8289
|
statusBar.setFocusMode(mode);
|
|
7941
|
-
if (mode === "input") {
|
|
7942
|
-
layout.body.height = "100%-2";
|
|
7943
|
-
} else {
|
|
7944
|
-
layout.body.height = "100%-1";
|
|
7945
|
-
}
|
|
7946
|
-
screenCtx.renderNow();
|
|
7947
8290
|
if (mode === "input") {
|
|
7948
8291
|
inputHandler.activate();
|
|
7949
8292
|
} else {
|
|
7950
8293
|
inputHandler.deactivate();
|
|
8294
|
+
layout.body.focus();
|
|
7951
8295
|
}
|
|
8296
|
+
screenCtx.renderNow();
|
|
7952
8297
|
}
|
|
7953
8298
|
function applyContentFilterMode(mode, blockRenderer, statusBar, screenCtx) {
|
|
7954
8299
|
blockRenderer.setContentFilterMode(mode);
|
|
@@ -8078,7 +8423,8 @@ async function executeAgent(promptArg, options, env, commandName) {
|
|
|
8078
8423
|
tui = await TUIApp.create({
|
|
8079
8424
|
model: options.model,
|
|
8080
8425
|
stdin: env.stdin,
|
|
8081
|
-
stdout: env.stdout
|
|
8426
|
+
stdout: env.stdout,
|
|
8427
|
+
showHints: options.showHints
|
|
8082
8428
|
});
|
|
8083
8429
|
try {
|
|
8084
8430
|
const fullConfig = loadConfig();
|
|
@@ -8467,7 +8813,8 @@ function registerAgentCommand(program, env, config, globalSubagents, globalRateL
|
|
|
8467
8813
|
globalRateLimits,
|
|
8468
8814
|
globalRetry,
|
|
8469
8815
|
profileRateLimits: config?.["rate-limits"],
|
|
8470
|
-
profileRetry: config?.retry
|
|
8816
|
+
profileRetry: config?.retry,
|
|
8817
|
+
showHints: config?.["show-hints"]
|
|
8471
8818
|
};
|
|
8472
8819
|
return executeAgent(prompt, mergedOptions, env, "agent");
|
|
8473
8820
|
}, env)
|
|
@@ -8692,7 +9039,7 @@ System Prompt (${chars.toLocaleString()} chars, ${lines} lines):
|
|
|
8692
9039
|
}
|
|
8693
9040
|
|
|
8694
9041
|
// src/environment.ts
|
|
8695
|
-
import { join as
|
|
9042
|
+
import { join as join4 } from "path";
|
|
8696
9043
|
import readline from "readline";
|
|
8697
9044
|
import chalk4 from "chalk";
|
|
8698
9045
|
import { createLogger, LLMist } from "llmist";
|
|
@@ -8715,7 +9062,7 @@ function createLoggerFactory(config, sessionLogDir) {
|
|
|
8715
9062
|
}
|
|
8716
9063
|
}
|
|
8717
9064
|
if (sessionLogDir) {
|
|
8718
|
-
const logFile =
|
|
9065
|
+
const logFile = join4(sessionLogDir, "session.log.jsonl");
|
|
8719
9066
|
const originalLogFile = process.env.LLMIST_LOG_FILE;
|
|
8720
9067
|
process.env.LLMIST_LOG_FILE = logFile;
|
|
8721
9068
|
const logger = createLogger(options);
|
|
@@ -9261,7 +9608,7 @@ function registerGadgetCommand(program, env) {
|
|
|
9261
9608
|
}
|
|
9262
9609
|
|
|
9263
9610
|
// src/image-command.ts
|
|
9264
|
-
import { writeFileSync as
|
|
9611
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
9265
9612
|
var DEFAULT_IMAGE_MODEL = "dall-e-3";
|
|
9266
9613
|
async function executeImage(promptArg, options, env) {
|
|
9267
9614
|
const prompt = await resolvePrompt(promptArg, env);
|
|
@@ -9285,7 +9632,7 @@ async function executeImage(promptArg, options, env) {
|
|
|
9285
9632
|
const imageData = result.images[0];
|
|
9286
9633
|
if (imageData.b64Json) {
|
|
9287
9634
|
const buffer = Buffer.from(imageData.b64Json, "base64");
|
|
9288
|
-
|
|
9635
|
+
writeFileSync3(options.output, buffer);
|
|
9289
9636
|
if (!options.quiet) {
|
|
9290
9637
|
env.stderr.write(`${SUMMARY_PREFIX} Image saved to ${options.output}
|
|
9291
9638
|
`);
|
|
@@ -9324,7 +9671,7 @@ function registerImageCommand(program, env, config) {
|
|
|
9324
9671
|
}
|
|
9325
9672
|
|
|
9326
9673
|
// src/init-command.ts
|
|
9327
|
-
import { existsSync as existsSync3, mkdirSync, writeFileSync as
|
|
9674
|
+
import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync4 } from "fs";
|
|
9328
9675
|
import { dirname as dirname2 } from "path";
|
|
9329
9676
|
var STARTER_CONFIG = `# ~/.llmist/cli.toml
|
|
9330
9677
|
# llmist CLI configuration file
|
|
@@ -9395,7 +9742,7 @@ async function executeInit(_options, env) {
|
|
|
9395
9742
|
if (!existsSync3(configDir)) {
|
|
9396
9743
|
mkdirSync(configDir, { recursive: true });
|
|
9397
9744
|
}
|
|
9398
|
-
|
|
9745
|
+
writeFileSync4(configPath, STARTER_CONFIG, "utf-8");
|
|
9399
9746
|
env.stderr.write(`Created ${configPath}
|
|
9400
9747
|
`);
|
|
9401
9748
|
env.stderr.write("\n");
|
|
@@ -9827,7 +10174,7 @@ function registerModelsCommand(program, env) {
|
|
|
9827
10174
|
import { existsSync as existsSync4 } from "fs";
|
|
9828
10175
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
9829
10176
|
import { homedir as homedir3 } from "os";
|
|
9830
|
-
import { join as
|
|
10177
|
+
import { join as join5 } from "path";
|
|
9831
10178
|
|
|
9832
10179
|
// src/session-names.ts
|
|
9833
10180
|
var ADJECTIVES = [
|
|
@@ -9962,16 +10309,16 @@ function generateSessionName() {
|
|
|
9962
10309
|
|
|
9963
10310
|
// src/session.ts
|
|
9964
10311
|
var currentSession;
|
|
9965
|
-
var SESSION_LOGS_BASE =
|
|
10312
|
+
var SESSION_LOGS_BASE = join5(homedir3(), ".llmist", "logs");
|
|
9966
10313
|
function findUniqueName(baseName) {
|
|
9967
|
-
const baseDir =
|
|
10314
|
+
const baseDir = join5(SESSION_LOGS_BASE, baseName);
|
|
9968
10315
|
if (!existsSync4(baseDir)) {
|
|
9969
10316
|
return baseName;
|
|
9970
10317
|
}
|
|
9971
10318
|
let suffix = 2;
|
|
9972
10319
|
while (suffix < 1e3) {
|
|
9973
10320
|
const name = `${baseName}-${suffix}`;
|
|
9974
|
-
const dir =
|
|
10321
|
+
const dir = join5(SESSION_LOGS_BASE, name);
|
|
9975
10322
|
if (!existsSync4(dir)) {
|
|
9976
10323
|
return name;
|
|
9977
10324
|
}
|
|
@@ -9985,14 +10332,14 @@ async function initSession() {
|
|
|
9985
10332
|
}
|
|
9986
10333
|
const baseName = generateSessionName();
|
|
9987
10334
|
const name = findUniqueName(baseName);
|
|
9988
|
-
const logDir =
|
|
10335
|
+
const logDir = join5(SESSION_LOGS_BASE, name);
|
|
9989
10336
|
await mkdir2(logDir, { recursive: true });
|
|
9990
10337
|
currentSession = { name, logDir };
|
|
9991
10338
|
return currentSession;
|
|
9992
10339
|
}
|
|
9993
10340
|
|
|
9994
10341
|
// src/speech-command.ts
|
|
9995
|
-
import { writeFileSync as
|
|
10342
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
9996
10343
|
var DEFAULT_SPEECH_MODEL = "tts-1";
|
|
9997
10344
|
var DEFAULT_VOICE = "nova";
|
|
9998
10345
|
async function executeSpeech(textArg, options, env) {
|
|
@@ -10015,7 +10362,7 @@ async function executeSpeech(textArg, options, env) {
|
|
|
10015
10362
|
});
|
|
10016
10363
|
const audioBuffer = Buffer.from(result.audio);
|
|
10017
10364
|
if (options.output) {
|
|
10018
|
-
|
|
10365
|
+
writeFileSync5(options.output, audioBuffer);
|
|
10019
10366
|
if (!options.quiet) {
|
|
10020
10367
|
env.stderr.write(`${SUMMARY_PREFIX} Audio saved to ${options.output}
|
|
10021
10368
|
`);
|