agy-powerline 1.3.0 → 1.5.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/CHANGELOG.md +55 -1
- package/README.fr.md +87 -32
- package/README.ja.md +87 -32
- package/README.md +89 -34
- package/README.zh-CN.md +87 -32
- package/llms.txt +127 -0
- package/package.json +8 -3
- package/src/cli/configure.js +70 -36
- package/src/cli/doctor.js +17 -1
- package/src/index.js +2 -1
- package/src/lib/catalog.js +21 -0
- package/src/lib/colors.js +17 -14
- package/src/lib/config.js +72 -20
- package/src/lib/fit.js +3 -0
- package/src/lib/formatters.js +107 -4
- package/src/lib/git.js +14 -5
- package/src/lib/layouts/lines.js +87 -22
- package/src/lib/layouts/pills.js +120 -33
- package/src/lib/session.js +49 -1
- package/src/lib/shared.js +2 -1
- package/src/lib/widget-options.js +108 -14
- package/src/lib/widget-style.js +112 -10
package/src/cli/configure.js
CHANGED
|
@@ -24,7 +24,7 @@ const {
|
|
|
24
24
|
} = require("../lib/shared");
|
|
25
25
|
const { resolveInstallDir } = require("../lib/paths");
|
|
26
26
|
const { WIDGET_TAGS } = require("../lib/widget-style");
|
|
27
|
-
const { editFields: getEditFields } = require("../lib/widget-options");
|
|
27
|
+
const { editFields: getEditFields, stepNumberField } = require("../lib/widget-options");
|
|
28
28
|
const { loadEditableConfig } = require("./config-guard");
|
|
29
29
|
const {
|
|
30
30
|
parseColor,
|
|
@@ -295,7 +295,15 @@ const TAB_OF_SCREEN = {
|
|
|
295
295
|
glyphs: "powerline",
|
|
296
296
|
};
|
|
297
297
|
|
|
298
|
-
const PRESET_ICONS = {
|
|
298
|
+
const PRESET_ICONS = {
|
|
299
|
+
default: "🎯",
|
|
300
|
+
compact: "⚡",
|
|
301
|
+
agent: "🧠",
|
|
302
|
+
git: "🔀",
|
|
303
|
+
tokens: "📊",
|
|
304
|
+
quotas: "🔋",
|
|
305
|
+
full: "🚀",
|
|
306
|
+
};
|
|
299
307
|
|
|
300
308
|
// Set by renderFrame so tests can draw a screen at a fixed size.
|
|
301
309
|
let columnsOverride = null;
|
|
@@ -409,6 +417,7 @@ function previewParts(width) {
|
|
|
409
417
|
// The toggle keys work on the widget list, so only that screen names them.
|
|
410
418
|
const key = (letter) => (screen === "main" ? letter : "");
|
|
411
419
|
const chips = [
|
|
420
|
+
ui.chip("gaps", preview.rowGap, key("v")),
|
|
412
421
|
ui.chip("emojis", preview.emojis, key("e")),
|
|
413
422
|
ui.chip("minimal", preview.minimalist, key("m")),
|
|
414
423
|
ui.chip("pace", preview.pacing, key("r")),
|
|
@@ -416,10 +425,7 @@ function previewParts(width) {
|
|
|
416
425
|
ui.chip("ascii", preview.ascii, key("i")),
|
|
417
426
|
];
|
|
418
427
|
if (preview.layout === "pills") {
|
|
419
|
-
chips.unshift(
|
|
420
|
-
ui.chip("joins", preview.powerline.enabled, key("j")),
|
|
421
|
-
ui.chip("gaps", preview.rowGap, key("v"))
|
|
422
|
-
);
|
|
428
|
+
chips.unshift(ui.chip("joins", preview.powerline.enabled, key("j")));
|
|
423
429
|
}
|
|
424
430
|
|
|
425
431
|
return { title, bar: String(rendered).split("\n"), chips: chips.join(" ") };
|
|
@@ -849,8 +855,7 @@ function setOption(key, value) {
|
|
|
849
855
|
function stepOption(field, direction) {
|
|
850
856
|
const current = editedWidget()[field.key];
|
|
851
857
|
if (field.number !== undefined) {
|
|
852
|
-
|
|
853
|
-
setOption(field.key, Math.max(0, Math.min(100, from + direction * 5)));
|
|
858
|
+
setOption(field.key, stepNumberField(field, current, direction));
|
|
854
859
|
return;
|
|
855
860
|
}
|
|
856
861
|
const index = field.values.findIndex((value) => value === current);
|
|
@@ -870,11 +875,12 @@ function onEdit(key) {
|
|
|
870
875
|
setOption(field.key, text || undefined);
|
|
871
876
|
textInput = null;
|
|
872
877
|
} else if (key.name === "backspace" || key.name === "delete") {
|
|
873
|
-
textInput = textInput.slice(0, -1);
|
|
874
|
-
} else if (key.sequence && key.
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
+
textInput = Array.from(textInput).slice(0, -1).join("");
|
|
879
|
+
} else if (key.sequence && !key.ctrl && !key.meta && !/[\x00-\x1f\x7f]/.test(key.sequence)) {
|
|
880
|
+
// An emoji arrives as one key with two UTF-16 units, so count code points.
|
|
881
|
+
const max = field?.maxLength ?? TEXT_MAX;
|
|
882
|
+
if (Array.from(textInput + key.sequence).length <= max) {
|
|
883
|
+
textInput += key.sequence;
|
|
878
884
|
}
|
|
879
885
|
}
|
|
880
886
|
return;
|
|
@@ -1029,21 +1035,38 @@ const toHex = (rgb) => `#${rgb.map((part) => part.toString(16).padStart(2, "0"))
|
|
|
1029
1035
|
|
|
1030
1036
|
// One color as a pill in that color with the theme's pill text, the way the
|
|
1031
1037
|
// bar draws it, then its hex value.
|
|
1032
|
-
function
|
|
1033
|
-
return
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
[
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
[
|
|
1044
|
-
|
|
1038
|
+
function colorCell(label, rgb, ink) {
|
|
1039
|
+
return `${ui.pill(` ${label.padEnd(9)}`, rgb, ink)} ${dim(toHex(rgb))}`;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Widget colors on the left, states on the right; a blank line between rows
|
|
1043
|
+
// keeps the pills from touching.
|
|
1044
|
+
const PALETTE_ROWS = [
|
|
1045
|
+
[
|
|
1046
|
+
["model", "Model"],
|
|
1047
|
+
["fiveHour", "OK"],
|
|
1048
|
+
],
|
|
1049
|
+
[
|
|
1050
|
+
["context", "Context"],
|
|
1051
|
+
["caution", "Caution"],
|
|
1052
|
+
],
|
|
1053
|
+
[
|
|
1054
|
+
["git", "Git"],
|
|
1055
|
+
["warn", "Danger"],
|
|
1056
|
+
],
|
|
1057
|
+
[
|
|
1058
|
+
["weekly", "7d quota"],
|
|
1059
|
+
["meta", "Details"],
|
|
1060
|
+
],
|
|
1045
1061
|
];
|
|
1046
1062
|
|
|
1063
|
+
function paletteLines(colors) {
|
|
1064
|
+
return PALETTE_ROWS.flatMap((row, index) => [
|
|
1065
|
+
...(index ? [""] : []),
|
|
1066
|
+
{ raw: row.map(([slot, label]) => colorCell(label, colors[slot], colors.text)).join(" ") },
|
|
1067
|
+
]);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1047
1070
|
function themeDetails(id) {
|
|
1048
1071
|
if (id === "custom") {
|
|
1049
1072
|
return {
|
|
@@ -1067,9 +1090,7 @@ function themeDetails(id) {
|
|
|
1067
1090
|
...(background ? [{ raw: `${dim("For")} ${ui.tag(background)}` }] : []),
|
|
1068
1091
|
"",
|
|
1069
1092
|
heading("Palette"),
|
|
1070
|
-
...
|
|
1071
|
-
colorLine(label, theme.colors[slot], theme.colors.text)
|
|
1072
|
-
),
|
|
1093
|
+
...paletteLines(theme.colors),
|
|
1073
1094
|
"",
|
|
1074
1095
|
heading("Sample"),
|
|
1075
1096
|
...themeSample(theme.colors),
|
|
@@ -1204,7 +1225,7 @@ const POWERLINE_HELP = {
|
|
|
1204
1225
|
enabled:
|
|
1205
1226
|
"On: adjacent widgets share one pill, with a separator between them. Off: every widget gets its own pill.",
|
|
1206
1227
|
rowGap:
|
|
1207
|
-
"Puts a blank line between
|
|
1228
|
+
"Puts a blank line between rows, in every layout, so stacked pills don't touch and text rows get more air. The bar takes more lines (three rows become five).",
|
|
1208
1229
|
style: "Sets start caps, end caps and separators in one go. Enter cycles the styles.",
|
|
1209
1230
|
separators: "Glyphs between widgets of a pill. With several, they cycle per join, left to right.",
|
|
1210
1231
|
startCaps: "Glyphs that open each pill. With several, they cycle per pill, left to right.",
|
|
@@ -1233,7 +1254,7 @@ function powerlineDetails(row) {
|
|
|
1233
1254
|
function drawPowerline() {
|
|
1234
1255
|
const items = POWERLINE_ROWS.map((row) => ({ text: row.label, value: powerlineRowValue(row) }));
|
|
1235
1256
|
const notes =
|
|
1236
|
-
config.layout
|
|
1257
|
+
config.layout !== "pills"
|
|
1237
1258
|
? ["Powerline shapes apply to Pills. Switch layout with l."]
|
|
1238
1259
|
: [
|
|
1239
1260
|
"A style sets caps and separators together; edit a list to fine-tune it.",
|
|
@@ -1318,10 +1339,24 @@ function drawGlyphs() {
|
|
|
1318
1339
|
});
|
|
1319
1340
|
}
|
|
1320
1341
|
|
|
1342
|
+
// Three short paragraphs per layout, each under 90 columns so a wide panel
|
|
1343
|
+
// shows them unwrapped.
|
|
1321
1344
|
const LAYOUT_HELP = {
|
|
1322
|
-
pills:
|
|
1323
|
-
|
|
1324
|
-
"
|
|
1345
|
+
pills: [
|
|
1346
|
+
"Widgets sit in colored pills, joined by powerline caps and separators.",
|
|
1347
|
+
"Meters draw as small dots on the pill's color, with a larger dot as the pace marker.",
|
|
1348
|
+
"The Powerline tab sets the caps, separators, joins and the space between rows.",
|
|
1349
|
+
],
|
|
1350
|
+
lines: [
|
|
1351
|
+
"Colored text on the terminal background, with thin │ dividers between widgets.",
|
|
1352
|
+
"Meters draw as dots in the ok, caution or danger color, over a dim track.",
|
|
1353
|
+
"Powerline shapes don't apply; the theme, widgets and space between rows do.",
|
|
1354
|
+
],
|
|
1355
|
+
classic: [
|
|
1356
|
+
"Colored text on the terminal background, with | dividers between widgets.",
|
|
1357
|
+
"Meters draw as blocks in one color: █ filled, ░ empty and │ as the pace marker.",
|
|
1358
|
+
"Powerline shapes don't apply; the theme, widgets and space between rows do.",
|
|
1359
|
+
],
|
|
1325
1360
|
};
|
|
1326
1361
|
|
|
1327
1362
|
function drawLayout() {
|
|
@@ -1335,10 +1370,9 @@ function drawLayout() {
|
|
|
1335
1370
|
return screenFrame({
|
|
1336
1371
|
title: "Layout",
|
|
1337
1372
|
items,
|
|
1338
|
-
notes: ["Both layouts use your widgets and theme; Powerline shapes apply to Pills."],
|
|
1339
1373
|
details: hovered && {
|
|
1340
1374
|
title: hovered.name,
|
|
1341
|
-
lines: [
|
|
1375
|
+
lines: LAYOUT_HELP[hovered.id].flatMap((line, i) => (i ? ["", line] : [line])),
|
|
1342
1376
|
},
|
|
1343
1377
|
keys: [
|
|
1344
1378
|
[ui.glyph.upDown, "preview"],
|
package/src/cli/doctor.js
CHANGED
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
readConfigFile,
|
|
23
23
|
setConfigDir,
|
|
24
24
|
} = require("../lib/shared");
|
|
25
|
+
const { currentUserAndHost } = require("../lib/session");
|
|
25
26
|
const { runStatuslineCommand } = require("../lib/run");
|
|
26
27
|
const { detectColorDepth } = require("../lib/colors");
|
|
27
28
|
const { readLog, getLogPath } = require("../lib/log");
|
|
@@ -55,6 +56,20 @@ function tildify(text) {
|
|
|
55
56
|
return out;
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
// The bar preview can hold the host widget's user@host, and doctor output
|
|
60
|
+
// gets pasted into issues: the names become user and host.
|
|
61
|
+
function maskHost(text, { username, hostname } = currentUserAndHost()) {
|
|
62
|
+
let out = String(text);
|
|
63
|
+
if (!hostname) {
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
if (username) {
|
|
67
|
+
out = out.split(`${username}@${hostname}`).join("user@host");
|
|
68
|
+
}
|
|
69
|
+
const escaped = hostname.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
70
|
+
return out.replace(new RegExp(`(^|[^\\w.-])${escaped}(?![\\w.-])`, "g"), "$1host");
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
function stripAnsi(text) {
|
|
59
74
|
return String(text).replace(/\x1b\[[0-9;]*m/g, "");
|
|
60
75
|
}
|
|
@@ -216,7 +231,7 @@ async function doctorCommand(options = {}) {
|
|
|
216
231
|
line("spawn", `${GREEN}ok${RESET} (${ms}ms)`);
|
|
217
232
|
// The flex separator pads to the terminal width; shrink those gaps so
|
|
218
233
|
// the widgets fit in the preview on wide terminals.
|
|
219
|
-
line("output", preview.replace(/ {3,}/g, " ").slice(0, 120));
|
|
234
|
+
line("output", maskHost(preview.replace(/ {3,}/g, " ")).slice(0, 120));
|
|
220
235
|
} else {
|
|
221
236
|
failed += 1;
|
|
222
237
|
line("spawn", `${RED}failed${RESET} exit ${result.code} (${ms}ms)`);
|
|
@@ -244,4 +259,5 @@ async function doctorCommand(options = {}) {
|
|
|
244
259
|
|
|
245
260
|
module.exports = {
|
|
246
261
|
doctorCommand,
|
|
262
|
+
maskHost,
|
|
247
263
|
};
|
package/src/index.js
CHANGED
|
@@ -102,7 +102,8 @@ const COMMANDS = [
|
|
|
102
102
|
{
|
|
103
103
|
name: "preset",
|
|
104
104
|
arg: "[name]",
|
|
105
|
-
description:
|
|
105
|
+
description:
|
|
106
|
+
"View or apply a widget preset (default, compact, agent, git, tokens, quotas, full)",
|
|
106
107
|
options: {
|
|
107
108
|
list: { type: "boolean", short: "l", help: "list all available presets" },
|
|
108
109
|
},
|
package/src/lib/catalog.js
CHANGED
|
@@ -122,6 +122,20 @@ const WIDGET_CATALOG = [
|
|
|
122
122
|
description: "name of the project folder",
|
|
123
123
|
summary: "project folder name",
|
|
124
124
|
},
|
|
125
|
+
{
|
|
126
|
+
id: "os",
|
|
127
|
+
label: "Operating System",
|
|
128
|
+
unique: true,
|
|
129
|
+
description: "system agy runs on, including WSL",
|
|
130
|
+
summary: "system running agy",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "host",
|
|
134
|
+
label: "Host",
|
|
135
|
+
unique: true,
|
|
136
|
+
description: "current user and short host name",
|
|
137
|
+
summary: "current user and host",
|
|
138
|
+
},
|
|
125
139
|
{
|
|
126
140
|
id: "git",
|
|
127
141
|
label: "VCS Branch",
|
|
@@ -257,6 +271,13 @@ const WIDGET_CATALOG = [
|
|
|
257
271
|
description: "session tokens, as a total or split into input and output",
|
|
258
272
|
summary: "session tokens, total or split",
|
|
259
273
|
},
|
|
274
|
+
{
|
|
275
|
+
id: "clock",
|
|
276
|
+
label: "Clock",
|
|
277
|
+
unique: true,
|
|
278
|
+
description: "current local time, in the format set under clock",
|
|
279
|
+
summary: "current local time",
|
|
280
|
+
},
|
|
260
281
|
{
|
|
261
282
|
id: "tokensToday",
|
|
262
283
|
label: "Tokens Today",
|
package/src/lib/colors.js
CHANGED
|
@@ -91,21 +91,24 @@ function rgb(r, g, b) {
|
|
|
91
91
|
return [r, g, b];
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (r > 248) {
|
|
101
|
-
return 231;
|
|
102
|
-
}
|
|
94
|
+
// The nearest xterm color: the 6x6x6 cube's levels (0, 95, 135, ... 255) are
|
|
95
|
+
// not evenly spaced, and a near-gray reads better from the gray ramp, so both
|
|
96
|
+
// candidates are measured and the closer one wins (as tmux does).
|
|
97
|
+
const CUBE_LEVELS = [0, 95, 135, 175, 215, 255];
|
|
103
98
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
99
|
+
function rgbToAnsi256([r, g, b]) {
|
|
100
|
+
const toCube = (v) => (v < 48 ? 0 : v < 115 ? 1 : Math.floor((v - 35) / 40));
|
|
101
|
+
const [cr, cg, cb] = [toCube(r), toCube(g), toCube(b)];
|
|
102
|
+
const cube = [CUBE_LEVELS[cr], CUBE_LEVELS[cg], CUBE_LEVELS[cb]];
|
|
103
|
+
|
|
104
|
+
const average = (r + g + b) / 3;
|
|
105
|
+
const grayIndex = average > 238 ? 23 : Math.max(0, Math.floor((average - 3) / 10));
|
|
106
|
+
const level = 8 + 10 * grayIndex;
|
|
107
|
+
const distance = ([x, y, z]) => (x - r) ** 2 + (y - g) ** 2 + (z - b) ** 2;
|
|
108
|
+
|
|
109
|
+
return distance([level, level, level]) < distance(cube)
|
|
110
|
+
? 232 + grayIndex
|
|
111
|
+
: 16 + 36 * cr + 6 * cg + cb;
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
function colorSeq(kind, [r, g, b]) {
|
package/src/lib/config.js
CHANGED
|
@@ -39,6 +39,8 @@ const VALID_WIDGET_IDS = new Set([
|
|
|
39
39
|
"contextTokens",
|
|
40
40
|
"cwd",
|
|
41
41
|
"project",
|
|
42
|
+
"os",
|
|
43
|
+
"host",
|
|
42
44
|
"git",
|
|
43
45
|
"gitChanges",
|
|
44
46
|
"gitFiles",
|
|
@@ -58,6 +60,7 @@ const VALID_WIDGET_IDS = new Set([
|
|
|
58
60
|
"cacheCreate",
|
|
59
61
|
"cacheHit",
|
|
60
62
|
"totalTokens",
|
|
63
|
+
"clock",
|
|
61
64
|
"tokensToday",
|
|
62
65
|
"tokensWeek",
|
|
63
66
|
"exec",
|
|
@@ -91,9 +94,14 @@ const LAYOUTS = {
|
|
|
91
94
|
name: "Lines",
|
|
92
95
|
description: "Plain colored text with dividers and dot meters",
|
|
93
96
|
},
|
|
97
|
+
classic: {
|
|
98
|
+
id: "classic",
|
|
99
|
+
name: "Classic",
|
|
100
|
+
description: "Plain colored text with | dividers and █░│ block meters",
|
|
101
|
+
},
|
|
94
102
|
};
|
|
95
103
|
|
|
96
|
-
const LAYOUT_ORDER = ["pills", "lines"];
|
|
104
|
+
const LAYOUT_ORDER = ["pills", "lines", "classic"];
|
|
97
105
|
|
|
98
106
|
function resolveLayoutId(layoutId) {
|
|
99
107
|
return LAYOUTS[layoutId] ? layoutId : "pills";
|
|
@@ -255,11 +263,12 @@ function saveConfig(config) {
|
|
|
255
263
|
}
|
|
256
264
|
|
|
257
265
|
// Presets set the widget list only; the layout (Pills or Lines) stays the one
|
|
258
|
-
// the user picked. Each keeps all its widgets at 80
|
|
266
|
+
// the user picked. Each answers one question and keeps all its widgets at 80
|
|
267
|
+
// columns (full at 100); widgets with nothing to show hide on their own.
|
|
259
268
|
const PRESETS = {
|
|
260
269
|
default: {
|
|
261
270
|
id: "default",
|
|
262
|
-
name: "
|
|
271
|
+
name: "Default",
|
|
263
272
|
description: "Model, context and git; 5h and 7d quotas with resets on a second row",
|
|
264
273
|
widgets: [
|
|
265
274
|
{ id: "model" },
|
|
@@ -277,41 +286,84 @@ const PRESETS = {
|
|
|
277
286
|
},
|
|
278
287
|
compact: {
|
|
279
288
|
id: "compact",
|
|
280
|
-
name: "
|
|
281
|
-
description: "
|
|
289
|
+
name: "Compact",
|
|
290
|
+
description: "One row: short model, tightest quota, context % and git",
|
|
282
291
|
widgets: [{ id: "model", short: true }, { id: "quota" }, { id: "contextPct" }, { id: "git" }],
|
|
283
292
|
},
|
|
284
|
-
|
|
285
|
-
id: "
|
|
286
|
-
name: "
|
|
287
|
-
description: "
|
|
293
|
+
agent: {
|
|
294
|
+
id: "agent",
|
|
295
|
+
name: "Agent activity",
|
|
296
|
+
description: "Agent state, subagents, title and mode; context, artifacts, quota",
|
|
297
|
+
widgets: [
|
|
298
|
+
{ id: "agentState" },
|
|
299
|
+
{ id: "tasks" },
|
|
300
|
+
{ id: "title" },
|
|
301
|
+
{ id: "separator" },
|
|
302
|
+
{ id: "mode" },
|
|
303
|
+
{ id: "sandbox" },
|
|
304
|
+
{ id: "lineBreak" },
|
|
305
|
+
{ id: "model", short: true },
|
|
306
|
+
{ id: "contextPct" },
|
|
307
|
+
{ id: "artifacts" },
|
|
308
|
+
{ id: "separator" },
|
|
309
|
+
{ id: "quota" },
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
git: {
|
|
313
|
+
id: "git",
|
|
314
|
+
name: "Git focus",
|
|
315
|
+
description: "Project, branch, worktree, stash; changed files and lines, commit",
|
|
288
316
|
widgets: [
|
|
289
317
|
{ id: "project" },
|
|
290
318
|
{ id: "git" },
|
|
291
|
-
{ id: "
|
|
319
|
+
{ id: "gitWorktree" },
|
|
320
|
+
{ id: "gitStash" },
|
|
292
321
|
{ id: "separator" },
|
|
293
322
|
{ id: "model", short: true },
|
|
294
323
|
{ id: "lineBreak" },
|
|
295
|
-
{ id: "
|
|
324
|
+
{ id: "gitFiles" },
|
|
325
|
+
{ id: "gitChanges" },
|
|
326
|
+
{ id: "gitSha" },
|
|
327
|
+
{ id: "separator" },
|
|
328
|
+
{ id: "contextPct" },
|
|
329
|
+
{ id: "quota" },
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
tokens: {
|
|
333
|
+
id: "tokens",
|
|
334
|
+
name: "Token usage",
|
|
335
|
+
description: "Session tokens in and out, cache hit; tokens today and over 7 days",
|
|
336
|
+
widgets: [
|
|
337
|
+
{ id: "model", short: true },
|
|
338
|
+
{ id: "contextTokens" },
|
|
339
|
+
{ id: "separator" },
|
|
340
|
+
{ id: "totalTokens", split: true },
|
|
341
|
+
{ id: "cacheHit" },
|
|
342
|
+
{ id: "lineBreak" },
|
|
343
|
+
{ id: "tokensToday" },
|
|
344
|
+
{ id: "tokensWeek" },
|
|
296
345
|
{ id: "separator" },
|
|
297
346
|
{ id: "quota" },
|
|
298
|
-
{ id: "mode" },
|
|
299
347
|
],
|
|
300
348
|
},
|
|
301
|
-
|
|
302
|
-
id: "
|
|
303
|
-
name: "
|
|
304
|
-
description: "
|
|
349
|
+
quotas: {
|
|
350
|
+
id: "quotas",
|
|
351
|
+
name: "Quota watch",
|
|
352
|
+
description: "Plan and model; 5h and 7d quotas side by side; third-party and Argon",
|
|
305
353
|
widgets: [
|
|
354
|
+
{ id: "plan" },
|
|
355
|
+
{ id: "separator" },
|
|
356
|
+
{ id: "model", short: true },
|
|
357
|
+
{ id: "lineBreak" },
|
|
306
358
|
{ id: "fiveHourBar" },
|
|
307
359
|
{ id: "fiveHourReset" },
|
|
308
|
-
{ id: "
|
|
360
|
+
{ id: "separator" },
|
|
309
361
|
{ id: "weeklyBar" },
|
|
310
362
|
{ id: "weeklyReset" },
|
|
311
363
|
{ id: "lineBreak" },
|
|
312
364
|
{ id: "thirdPartyFiveHour" },
|
|
313
365
|
{ id: "thirdPartyWeekly" },
|
|
314
|
-
{ id: "
|
|
366
|
+
{ id: "separator" },
|
|
315
367
|
{ id: "argonWeekly" },
|
|
316
368
|
{ id: "argonReset" },
|
|
317
369
|
],
|
|
@@ -319,7 +371,7 @@ const PRESETS = {
|
|
|
319
371
|
full: {
|
|
320
372
|
id: "full",
|
|
321
373
|
name: "Everything",
|
|
322
|
-
description: "Session, project and git; context, tokens and cache; both quotas
|
|
374
|
+
description: "Session, project and git; context, tokens and cache; both quotas",
|
|
323
375
|
widgets: [
|
|
324
376
|
{ id: "agentState" },
|
|
325
377
|
{ id: "tasks" },
|
|
@@ -344,7 +396,7 @@ const PRESETS = {
|
|
|
344
396
|
},
|
|
345
397
|
};
|
|
346
398
|
|
|
347
|
-
const PRESET_ORDER = ["default", "compact", "
|
|
399
|
+
const PRESET_ORDER = ["default", "compact", "agent", "git", "tokens", "quotas", "full"];
|
|
348
400
|
|
|
349
401
|
function applyPreset(baseConfig, presetName) {
|
|
350
402
|
const preset = PRESETS[presetName] || PRESETS.default;
|
package/src/lib/fit.js
CHANGED
|
@@ -6,9 +6,12 @@ const { charWidth, visibleWidth } = require("./formatters");
|
|
|
6
6
|
// first entry first. Ids not listed (custom ones) go before all of them.
|
|
7
7
|
const DROP_ORDER = [
|
|
8
8
|
"customText",
|
|
9
|
+
"os",
|
|
10
|
+
"host",
|
|
9
11
|
"gitStash",
|
|
10
12
|
"gitWorktree",
|
|
11
13
|
"title",
|
|
14
|
+
"clock",
|
|
12
15
|
"version",
|
|
13
16
|
"cacheCreate",
|
|
14
17
|
"cacheRead",
|
package/src/lib/formatters.js
CHANGED
|
@@ -374,7 +374,23 @@ function homePath(value) {
|
|
|
374
374
|
return "";
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
-
const
|
|
377
|
+
const input = String(value);
|
|
378
|
+
if (input === "~" || /^~[\\/]/.test(input)) {
|
|
379
|
+
return input;
|
|
380
|
+
}
|
|
381
|
+
const windowsAbsolute = /^[a-z]:[\\/]|^\\\\/i.test(input);
|
|
382
|
+
const posixAbsolute = input.startsWith("/");
|
|
383
|
+
|
|
384
|
+
// Payload paths can come from another OS. Preserve absolute paths in that
|
|
385
|
+
// format instead of resolving them relative to this machine's cwd.
|
|
386
|
+
if (
|
|
387
|
+
(windowsAbsolute && process.platform !== "win32") ||
|
|
388
|
+
(posixAbsolute && process.platform === "win32")
|
|
389
|
+
) {
|
|
390
|
+
return input;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const resolved = path.resolve(input);
|
|
378
394
|
const home = path.resolve(os.homedir());
|
|
379
395
|
const prefix =
|
|
380
396
|
process.platform === "win32"
|
|
@@ -388,6 +404,60 @@ function homePath(value) {
|
|
|
388
404
|
return resolved;
|
|
389
405
|
}
|
|
390
406
|
|
|
407
|
+
function isValidDepth(value) {
|
|
408
|
+
return Number.isInteger(value) && value >= 1 && value <= 10;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Keep the path's root and final `depth` folders, replacing any middle
|
|
412
|
+
// folders with one ellipsis. Accept either OS's path separators on every OS.
|
|
413
|
+
function shortenPath(value, depth, ascii = false) {
|
|
414
|
+
const text = String(value ?? "");
|
|
415
|
+
if (!text || !isValidDepth(depth)) {
|
|
416
|
+
return text;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
let root = "";
|
|
420
|
+
let rest = text;
|
|
421
|
+
let separator = text.match(/[\\/]/)?.[0] || "/";
|
|
422
|
+
// A network share keeps \\server\share (or //server/share) as its root.
|
|
423
|
+
const unc = text.match(/^([\\/]{2}[^\\/]+[\\/][^\\/]+)([\\/])?(.*)$/);
|
|
424
|
+
const drive = text.match(/^([a-z]:)([\\/])(.*)$/i);
|
|
425
|
+
|
|
426
|
+
if (unc) {
|
|
427
|
+
root = unc[1];
|
|
428
|
+
separator = unc[2] || separator;
|
|
429
|
+
rest = unc[3];
|
|
430
|
+
} else if (drive) {
|
|
431
|
+
root = drive[1];
|
|
432
|
+
separator = drive[2];
|
|
433
|
+
rest = drive[3];
|
|
434
|
+
} else if (text === "~" || /^~[\\/]/.test(text)) {
|
|
435
|
+
root = "~";
|
|
436
|
+
if (text.length > 1) {
|
|
437
|
+
separator = text[1];
|
|
438
|
+
rest = text.slice(2);
|
|
439
|
+
} else {
|
|
440
|
+
rest = "";
|
|
441
|
+
}
|
|
442
|
+
} else if (text[0] === "/" || text[0] === "\\") {
|
|
443
|
+
root = text[0];
|
|
444
|
+
separator = text[0];
|
|
445
|
+
rest = text.slice(1);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const folders = rest.split(/[\\/]+/).filter(Boolean);
|
|
449
|
+
if (folders.length <= depth) {
|
|
450
|
+
return text;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const suffix = folders.slice(-depth).join(separator);
|
|
454
|
+
const ellipsis = ascii ? "..." : "…";
|
|
455
|
+
if (root === "/" || root === "\\") {
|
|
456
|
+
return `${root}${ellipsis}${separator}${suffix}`;
|
|
457
|
+
}
|
|
458
|
+
return `${root ? `${root}${separator}` : ""}${ellipsis}${separator}${suffix}`;
|
|
459
|
+
}
|
|
460
|
+
|
|
391
461
|
function label(emojis, emoji, named, value) {
|
|
392
462
|
if (emojis) {
|
|
393
463
|
return named ? ` ${emoji} ${named}: ${value} ` : ` ${emoji} ${value} `;
|
|
@@ -560,15 +630,44 @@ function clipText(text, max, ascii = false) {
|
|
|
560
630
|
const ellipsis = ascii ? "..." : "…";
|
|
561
631
|
let out = "";
|
|
562
632
|
let width = 0;
|
|
563
|
-
|
|
564
|
-
|
|
633
|
+
let index = 0;
|
|
634
|
+
const escape = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\]8;;[^\x1b\x07]*(?:\x07|\x1b\\))/y;
|
|
635
|
+
while (index < value.length) {
|
|
636
|
+
if (value[index] === "\x1b") {
|
|
637
|
+
escape.lastIndex = index;
|
|
638
|
+
const match = escape.exec(value);
|
|
639
|
+
if (match) {
|
|
640
|
+
out += match[0];
|
|
641
|
+
index += match[0].length;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const code = value.codePointAt(index);
|
|
647
|
+
const char = String.fromCodePoint(code);
|
|
648
|
+
const w = charWidth(code);
|
|
565
649
|
if (width + w > max - ellipsis.length) {
|
|
566
650
|
break;
|
|
567
651
|
}
|
|
568
652
|
out += char;
|
|
569
653
|
width += w;
|
|
654
|
+
index += char.length;
|
|
570
655
|
}
|
|
571
|
-
|
|
656
|
+
const clipped = out.trimEnd() + ellipsis;
|
|
657
|
+
return /\x1b\[[0-?]*[ -/]*m/.test(clipped) ? `${clipped}\x1b[0m` : clipped;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function isValidMaxWidth(value) {
|
|
661
|
+
return Number.isInteger(value) && value >= 4 && value <= 200;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function limitWidth(value, widget, ascii = false, fallback) {
|
|
665
|
+
const width = isValidMaxWidth(widget?.maxWidth)
|
|
666
|
+
? widget.maxWidth
|
|
667
|
+
: isValidMaxWidth(fallback)
|
|
668
|
+
? fallback
|
|
669
|
+
: null;
|
|
670
|
+
return width === null ? value : clipText(value, width, ascii);
|
|
572
671
|
}
|
|
573
672
|
|
|
574
673
|
// Removes escape sequences and control characters from text that did not
|
|
@@ -656,6 +755,10 @@ module.exports = {
|
|
|
656
755
|
formatTasks,
|
|
657
756
|
formatMode,
|
|
658
757
|
clipText,
|
|
758
|
+
limitWidth,
|
|
759
|
+
isValidMaxWidth,
|
|
760
|
+
shortenPath,
|
|
761
|
+
isValidDepth,
|
|
659
762
|
quotaColor,
|
|
660
763
|
contextColor,
|
|
661
764
|
resolveQuotaThresholds,
|