@quatcode/agent 0.2.4 → 0.2.6
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/input.d.ts +6 -2
- package/dist/input.d.ts.map +1 -1
- package/dist/input.js +87 -31
- package/dist/input.js.map +1 -1
- package/dist/keyboard.d.ts.map +1 -1
- package/dist/keyboard.js +20 -2
- package/dist/keyboard.js.map +1 -1
- package/dist/theme.d.ts +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +2 -0
- package/dist/theme.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +90 -22
- package/dist/tui.js.map +1 -1
- package/package.json +3 -3
package/dist/tui.js
CHANGED
|
@@ -10,7 +10,7 @@ import { Picker } from "./picker.js";
|
|
|
10
10
|
import { Screen } from "./screen.js";
|
|
11
11
|
import { TurnController, fmtCost, fmtDuration, fmtTokens, toolArg, toolResultBlock } from "./turn.js";
|
|
12
12
|
import { renderBanner } from "./logo.js";
|
|
13
|
-
import { renderMarkdown } from "./markdown.js";
|
|
13
|
+
import { renderMarkdown, wrapSegments } from "./markdown.js";
|
|
14
14
|
import { ADD_PROVIDER, allProviders, buildProvider, defaultModelFor, effortsForModel, hasKey, keyEnvFor, probeProviderModels, providerEnvKey, providerHasModel, providerMeta, providerSlug, registerCustomProvider, removeCustomProvider, resolveProviderForModel, } from "./models.js";
|
|
15
15
|
import { activeThemeId, bg, bold, buildColorRemap, clipAnsi, fg, getThemes, glyphs, padEndTo, palette, remapColors, setTheme, stringWidth, stripAnsi, tone, toolGlyph, } from "./theme.js";
|
|
16
16
|
import { listSessionsForDir, loadConfig, makeSessionId, saveConfig, saveSession, deleteSession } from "./store.js";
|
|
@@ -136,6 +136,9 @@ class TuiApp {
|
|
|
136
136
|
modeBadgeWidth = 0;
|
|
137
137
|
modeBadgeHover = false;
|
|
138
138
|
versionNotice = null;
|
|
139
|
+
bottomHintStart = -1;
|
|
140
|
+
bottomHintWidth = 0;
|
|
141
|
+
bottomHintRow = -1;
|
|
139
142
|
constructor(options, input, output) {
|
|
140
143
|
this.opts = options;
|
|
141
144
|
this.dir = path.resolve(options.dir);
|
|
@@ -433,6 +436,11 @@ class TuiApp {
|
|
|
433
436
|
this.selection = null;
|
|
434
437
|
this.scheduleRender();
|
|
435
438
|
}
|
|
439
|
+
if (key.type === "nav" && key.dir === "down" && key.ctrl) {
|
|
440
|
+
this.scrollOffset = 0;
|
|
441
|
+
this.scheduleRender();
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
436
444
|
if (this.mode === "editing")
|
|
437
445
|
this.handleEditKey(key);
|
|
438
446
|
else if (this.mode === "running")
|
|
@@ -735,16 +743,38 @@ class TuiApp {
|
|
|
735
743
|
if (!rendered)
|
|
736
744
|
return false;
|
|
737
745
|
const top = this.lastHeaderRows + this.lastViewport + this.lastInputOffset;
|
|
738
|
-
const
|
|
739
|
-
if (
|
|
746
|
+
const rowIndex = row - top;
|
|
747
|
+
if (rowIndex < 0 || rowIndex >= rendered.lines.length)
|
|
748
|
+
return false;
|
|
749
|
+
const visual = rendered.rows[rowIndex];
|
|
750
|
+
if (!visual)
|
|
740
751
|
return false;
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
const start = rendered.lineStarts[lineIndex] ?? 0;
|
|
744
|
-
this.buffer.setCursorFromDisplay(lineIndex, Math.max(0, textCol), start);
|
|
752
|
+
const textCol = col - this.padX - visual.prefixWidth;
|
|
753
|
+
this.buffer.setCursorFromDisplay(visual.line, Math.max(0, textCol), visual.start);
|
|
745
754
|
this.scheduleRender();
|
|
746
755
|
return true;
|
|
747
756
|
}
|
|
757
|
+
bottomHintHit(row, col) {
|
|
758
|
+
if (this.bottomHintRow < 0 || this.bottomHintStart < 0)
|
|
759
|
+
return false;
|
|
760
|
+
if (row !== this.bottomHintRow)
|
|
761
|
+
return false;
|
|
762
|
+
const start = this.padX + this.bottomHintStart;
|
|
763
|
+
return col >= start && col < start + this.bottomHintWidth;
|
|
764
|
+
}
|
|
765
|
+
bottomRuleLine() {
|
|
766
|
+
const width = this.contentWidth();
|
|
767
|
+
const label = `${glyphs.arrowDown} bottom · ctrl+${glyphs.arrowDown}`;
|
|
768
|
+
this.bottomHintStart = -1;
|
|
769
|
+
this.bottomHintWidth = 0;
|
|
770
|
+
if (this.scrollOffset > 0 && this.mode !== "picker" && width > 36) {
|
|
771
|
+
const dashes = Math.max(8, width - 2 - stringWidth(label) - 2);
|
|
772
|
+
this.bottomHintStart = 2 + dashes + 2;
|
|
773
|
+
this.bottomHintWidth = stringWidth(label);
|
|
774
|
+
return ` ${tone.mist("─".repeat(dashes))} ${tone.silver(label)}`;
|
|
775
|
+
}
|
|
776
|
+
return ` ${tone.mist("─".repeat(Math.max(8, width - 2)))}`;
|
|
777
|
+
}
|
|
748
778
|
handleHover(row, col) {
|
|
749
779
|
if (row === 1) {
|
|
750
780
|
const hover = this.badgeHit(col);
|
|
@@ -791,6 +821,11 @@ class TuiApp {
|
|
|
791
821
|
this.toggleMode();
|
|
792
822
|
return;
|
|
793
823
|
}
|
|
824
|
+
if (this.bottomHintHit(row, col)) {
|
|
825
|
+
this.scrollOffset = 0;
|
|
826
|
+
this.scheduleRender();
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
794
829
|
if (this.mode === "permission") {
|
|
795
830
|
const index = this.permissionOptionAt(row, col);
|
|
796
831
|
if (index !== null) {
|
|
@@ -1036,6 +1071,8 @@ class TuiApp {
|
|
|
1036
1071
|
const viewport = Math.max(3, rows - header.length - footer.length - this.padTop - this.padBottom);
|
|
1037
1072
|
this.lastHeaderRows = header.length + this.padTop;
|
|
1038
1073
|
this.lastViewport = viewport;
|
|
1074
|
+
this.bottomHintRow =
|
|
1075
|
+
this.bottomHintStart >= 0 ? this.lastHeaderRows + viewport + footer.length - 1 : -1;
|
|
1039
1076
|
let content;
|
|
1040
1077
|
if (this.mode === "picker" && this.picker) {
|
|
1041
1078
|
content = this.picker.render(this.contentWidth() - 2, viewport);
|
|
@@ -1099,7 +1136,14 @@ class TuiApp {
|
|
|
1099
1136
|
this.modeBadgeWidth = stringWidth(badge);
|
|
1100
1137
|
const left = `${before} ${badge}`;
|
|
1101
1138
|
const scrolled = this.scrollOffset > 0 ? tone.mist("↑ ") : "";
|
|
1102
|
-
const
|
|
1139
|
+
const versionText = tone.mist(`v${VERSION}`);
|
|
1140
|
+
const dirLabel = shortenHome(this.dir);
|
|
1141
|
+
const reserve = stringWidth(left) + stringWidth(versionText) + stringWidth(scrolled) + 5;
|
|
1142
|
+
const maxDir = Math.max(12, Math.min(48, width - reserve));
|
|
1143
|
+
const dirText = stringWidth(dirLabel) > maxDir
|
|
1144
|
+
? `…${dirLabel.slice(-(maxDir - 1))}`
|
|
1145
|
+
: dirLabel;
|
|
1146
|
+
const right = `${scrolled}${tone.mist(dirText)} ${versionText}`;
|
|
1103
1147
|
const gap = Math.max(1, width - 2 - stringWidth(left) - stringWidth(right));
|
|
1104
1148
|
return [
|
|
1105
1149
|
` ${left}${" ".repeat(gap)}${right}`,
|
|
@@ -1109,6 +1153,7 @@ class TuiApp {
|
|
|
1109
1153
|
footerLines() {
|
|
1110
1154
|
const width = this.contentWidth();
|
|
1111
1155
|
const rule = ` ${tone.mist("─".repeat(Math.max(8, width - 2)))}`;
|
|
1156
|
+
const bottom = this.bottomRuleLine();
|
|
1112
1157
|
if (this.mode === "editing") {
|
|
1113
1158
|
const rendered = this.buffer.render({
|
|
1114
1159
|
width,
|
|
@@ -1117,23 +1162,35 @@ class TuiApp {
|
|
|
1117
1162
|
mask: this.inputMask,
|
|
1118
1163
|
style: styleInputLine,
|
|
1119
1164
|
});
|
|
1120
|
-
this.
|
|
1165
|
+
const maxRows = Math.max(3, Math.floor(this.screen.rows / 2));
|
|
1166
|
+
if (rendered.lines.length > maxRows) {
|
|
1167
|
+
const start = Math.max(0, Math.min(rendered.cursorRow - maxRows + 1, rendered.lines.length - maxRows));
|
|
1168
|
+
this.lastInputRender = {
|
|
1169
|
+
lines: rendered.lines.slice(start, start + maxRows),
|
|
1170
|
+
rows: rendered.rows.slice(start, start + maxRows),
|
|
1171
|
+
cursorRow: rendered.cursorRow - start,
|
|
1172
|
+
cursorCol: rendered.cursorCol,
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
this.lastInputRender = rendered;
|
|
1177
|
+
}
|
|
1121
1178
|
const suggestions = this.suggestions();
|
|
1122
1179
|
this.lastInputOffset = 1 + suggestions.length;
|
|
1123
|
-
return [rule, ...suggestions, ...
|
|
1180
|
+
return [rule, ...suggestions, ...this.lastInputRender.lines, bottom];
|
|
1124
1181
|
}
|
|
1125
1182
|
this.lastInputRender = null;
|
|
1126
1183
|
if (this.status)
|
|
1127
|
-
return [rule, this.statusLine(),
|
|
1184
|
+
return [rule, this.statusLine(), bottom];
|
|
1128
1185
|
if (this.mode === "running") {
|
|
1129
|
-
return [rule, ` ${tone.mist("// esc interrupts")}`,
|
|
1186
|
+
return [rule, ` ${tone.mist("// esc interrupts")}`, bottom];
|
|
1130
1187
|
}
|
|
1131
1188
|
if (this.mode === "permission")
|
|
1132
|
-
return [rule, ` ${this.permissionHint}`,
|
|
1189
|
+
return [rule, ` ${this.permissionHint}`, bottom];
|
|
1133
1190
|
if (this.mode === "picker" && this.picker) {
|
|
1134
|
-
return [rule, this.picker.footer(this.picker.itemCount()),
|
|
1191
|
+
return [rule, this.picker.footer(this.picker.itemCount()), bottom];
|
|
1135
1192
|
}
|
|
1136
|
-
return [rule,
|
|
1193
|
+
return [rule, "", bottom];
|
|
1137
1194
|
}
|
|
1138
1195
|
statusLine() {
|
|
1139
1196
|
const spin = tone.accent(glyphs.spinner[this.spinnerFrame % glyphs.spinner.length] ?? "-");
|
|
@@ -1293,7 +1350,7 @@ class TuiApp {
|
|
|
1293
1350
|
`${summary.fileCount} files`,
|
|
1294
1351
|
summary.packageManager ?? "",
|
|
1295
1352
|
].filter(Boolean);
|
|
1296
|
-
return tone.mist(parts.join(" · "))
|
|
1353
|
+
return `${tone.mist(parts.join(" · "))} ${tone.silver(shortenHome(this.dir))}`;
|
|
1297
1354
|
}
|
|
1298
1355
|
buildSystem(query) {
|
|
1299
1356
|
if (!this.opts.context || !this.summary)
|
|
@@ -2680,6 +2737,7 @@ class TuiApp {
|
|
|
2680
2737
|
["sessions picker", "ctrl+r rename · ctrl+p pin · ctrl+d delete"],
|
|
2681
2738
|
["↑ ↓", "input history — previous / next message"],
|
|
2682
2739
|
["wheel", "scroll the conversation"],
|
|
2740
|
+
["ctrl+↓", "jump to the bottom of the chat (or click the ↓ button)"],
|
|
2683
2741
|
["ctrl+p/ctrl+n", "input history (same as ↑ ↓)"],
|
|
2684
2742
|
["tab / badge", "switch plan/build · click the header badge · complete commands"],
|
|
2685
2743
|
["ctrl+z", "undo the last message (works while thinking)"],
|
|
@@ -2698,13 +2756,23 @@ class TuiApp {
|
|
|
2698
2756
|
this.append(lines);
|
|
2699
2757
|
}
|
|
2700
2758
|
userBlockLines(input, imageCount = 0) {
|
|
2759
|
+
const width = Math.max(20, this.contentWidth() - 2);
|
|
2701
2760
|
const lines = input.split("\n");
|
|
2702
|
-
const
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2761
|
+
const out = [];
|
|
2762
|
+
for (let index = 0; index < lines.length; index++) {
|
|
2763
|
+
const line = lines[index];
|
|
2764
|
+
if (line === "") {
|
|
2765
|
+
out.push("");
|
|
2766
|
+
continue;
|
|
2767
|
+
}
|
|
2768
|
+
const prefix = index === 0 ? ` ${tone.accent(glyphs.prompt)} ` : " ";
|
|
2769
|
+
const kind = index === 0 ? "bold" : "plain";
|
|
2770
|
+
out.push(...wrapSegments([{ text: line, kind }], prefix, " ", width));
|
|
2771
|
+
}
|
|
2772
|
+
if (imageCount > 0) {
|
|
2773
|
+
out.push(` ${tone.mist(`[${imageCount} image${imageCount === 1 ? "" : "s"} attached]`)}`);
|
|
2774
|
+
}
|
|
2775
|
+
return out;
|
|
2708
2776
|
}
|
|
2709
2777
|
async saveTurn(input, result, durationMs) {
|
|
2710
2778
|
const cost = estimateCost(result.usage, this.model, this.providerId);
|