agy-powerline 1.2.0 → 1.4.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 +72 -0
- package/README.fr.md +243 -0
- package/README.ja.md +144 -49
- package/README.md +160 -52
- package/README.zh-CN.md +144 -49
- package/package.json +5 -2
- package/src/cli/caps.js +2 -0
- package/src/cli/configure.js +70 -172
- package/src/cli/doctor.js +12 -0
- package/src/cli/export.js +82 -0
- package/src/cli/gradient.js +121 -0
- package/src/cli/import.js +273 -0
- package/src/cli/init.js +186 -7
- package/src/cli/layout.js +2 -0
- package/src/cli/preset.js +2 -0
- package/src/cli/repo-url.js +7 -0
- package/src/cli/theme.js +2 -0
- package/src/cli/uninstall.js +6 -2
- package/src/index.js +53 -1
- package/src/lib/catalog.js +54 -2
- package/src/lib/colors.js +144 -14
- package/src/lib/config-check.js +74 -0
- package/src/lib/config.js +89 -21
- package/src/lib/exec.js +140 -21
- package/src/lib/fit.js +7 -0
- package/src/lib/formatters.js +67 -0
- package/src/lib/git.js +385 -4
- package/src/lib/layouts/lines.js +175 -28
- package/src/lib/layouts/pills.js +232 -33
- package/src/lib/paths.js +73 -13
- package/src/lib/prompt.js +30 -0
- package/src/lib/run.js +2 -0
- package/src/lib/session.js +17 -3
- package/src/lib/shared.js +13 -2
- package/src/lib/themes.js +79 -5
- package/src/lib/usage.js +524 -0
- package/src/lib/widget-options.js +194 -0
- package/src/lib/widget-style.js +45 -0
- package/src/runtime/statusline.js +15 -1
package/src/cli/configure.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
1
3
|
const readline = require("readline");
|
|
2
4
|
const { spawn } = require("child_process");
|
|
3
5
|
const {
|
|
@@ -19,11 +21,10 @@ const {
|
|
|
19
21
|
PRESETS,
|
|
20
22
|
PRESET_ORDER,
|
|
21
23
|
applyPreset,
|
|
22
|
-
resolveQuotaThresholds,
|
|
23
|
-
resolveContextThresholds,
|
|
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
28
|
const { loadEditableConfig } = require("./config-guard");
|
|
28
29
|
const {
|
|
29
30
|
parseColor,
|
|
@@ -48,7 +49,7 @@ const {
|
|
|
48
49
|
glyphName,
|
|
49
50
|
} = require("../lib/powerline");
|
|
50
51
|
|
|
51
|
-
const REPO_URL = "
|
|
52
|
+
const { REPO_URL } = require("./repo-url");
|
|
52
53
|
|
|
53
54
|
const CSI = "\x1b[";
|
|
54
55
|
const RESET = `${CSI}0m`;
|
|
@@ -294,7 +295,15 @@ const TAB_OF_SCREEN = {
|
|
|
294
295
|
glyphs: "powerline",
|
|
295
296
|
};
|
|
296
297
|
|
|
297
|
-
const PRESET_ICONS = {
|
|
298
|
+
const PRESET_ICONS = {
|
|
299
|
+
default: "🎯",
|
|
300
|
+
compact: "⚡",
|
|
301
|
+
agent: "🧠",
|
|
302
|
+
git: "🔀",
|
|
303
|
+
tokens: "📊",
|
|
304
|
+
quotas: "🔋",
|
|
305
|
+
full: "🚀",
|
|
306
|
+
};
|
|
298
307
|
|
|
299
308
|
// Set by renderFrame so tests can draw a screen at a fixed size.
|
|
300
309
|
let columnsOverride = null;
|
|
@@ -408,6 +417,7 @@ function previewParts(width) {
|
|
|
408
417
|
// The toggle keys work on the widget list, so only that screen names them.
|
|
409
418
|
const key = (letter) => (screen === "main" ? letter : "");
|
|
410
419
|
const chips = [
|
|
420
|
+
ui.chip("gaps", preview.rowGap, key("v")),
|
|
411
421
|
ui.chip("emojis", preview.emojis, key("e")),
|
|
412
422
|
ui.chip("minimal", preview.minimalist, key("m")),
|
|
413
423
|
ui.chip("pace", preview.pacing, key("r")),
|
|
@@ -415,10 +425,7 @@ function previewParts(width) {
|
|
|
415
425
|
ui.chip("ascii", preview.ascii, key("i")),
|
|
416
426
|
];
|
|
417
427
|
if (preview.layout === "pills") {
|
|
418
|
-
chips.unshift(
|
|
419
|
-
ui.chip("joins", preview.powerline.enabled, key("j")),
|
|
420
|
-
ui.chip("gaps", preview.rowGap, key("v"))
|
|
421
|
-
);
|
|
428
|
+
chips.unshift(ui.chip("joins", preview.powerline.enabled, key("j")));
|
|
422
429
|
}
|
|
423
430
|
|
|
424
431
|
return { title, bar: String(rendered).split("\n"), chips: chips.join(" ") };
|
|
@@ -749,151 +756,14 @@ function drawMain() {
|
|
|
749
756
|
});
|
|
750
757
|
}
|
|
751
758
|
|
|
752
|
-
// Widgets that take each option, as the layouts read them.
|
|
753
|
-
const QUOTA_WIDGETS = new Set([
|
|
754
|
-
"quota",
|
|
755
|
-
"fiveHourBar",
|
|
756
|
-
"fiveHourPct",
|
|
757
|
-
"weeklyBar",
|
|
758
|
-
"weeklyPct",
|
|
759
|
-
"thirdPartyFiveHour",
|
|
760
|
-
"thirdPartyWeekly",
|
|
761
|
-
"argonWeekly",
|
|
762
|
-
]);
|
|
763
|
-
const PACED_WIDGETS = new Set([
|
|
764
|
-
"fiveHourBar",
|
|
765
|
-
"weeklyBar",
|
|
766
|
-
"thirdPartyFiveHour",
|
|
767
|
-
"thirdPartyWeekly",
|
|
768
|
-
"argonWeekly",
|
|
769
|
-
]);
|
|
770
|
-
const CONTEXT_WIDGETS = new Set(["context", "contextPct", "contextTokens"]);
|
|
771
|
-
const RESET_WIDGETS = new Set(["fiveHourReset", "weeklyReset", "argonReset"]);
|
|
772
|
-
const COLOR_SLOTS = ["model", "context", "git", "weekly", "thirdParty", "fiveHour", "meta"];
|
|
773
759
|
// Longest custom badge text; the badge is meant to be a word or two.
|
|
774
760
|
const TEXT_MAX = 40;
|
|
775
761
|
|
|
776
|
-
const onOff = (value) => (value ? "on" : "off");
|
|
777
|
-
|
|
778
|
-
// The options of one widget: cycle fields step through values, number fields
|
|
779
|
-
// step by 5 with the arrows, text fields take typing, and fixed ones only show
|
|
780
|
-
// their value. undefined means "not set": the widget follows the global
|
|
781
|
-
// setting or its default.
|
|
782
762
|
function editFields(widget) {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
: context
|
|
788
|
-
? resolveContextThresholds({}, config)
|
|
789
|
-
: null;
|
|
790
|
-
const fields = [
|
|
791
|
-
{
|
|
792
|
-
key: "color",
|
|
793
|
-
label: "Color",
|
|
794
|
-
values: [undefined, ...COLOR_SLOTS],
|
|
795
|
-
show: (value) => (value === undefined ? "theme default" : colorName(value)),
|
|
796
|
-
help: "Draws this widget in another of the theme's colors. Caution and danger colors still take over when a quota or the context runs low.",
|
|
797
|
-
},
|
|
798
|
-
{
|
|
799
|
-
key: "raw",
|
|
800
|
-
label: "Label",
|
|
801
|
-
values: [undefined, false, true],
|
|
802
|
-
show: (value) =>
|
|
803
|
-
value === undefined
|
|
804
|
-
? `default (${config.minimalist ? "hidden" : "shown"})`
|
|
805
|
-
: value
|
|
806
|
-
? "hidden, value only"
|
|
807
|
-
: "shown",
|
|
808
|
-
help: "Shows or hides the icon and label before the value. Default follows minimalist mode (m).",
|
|
809
|
-
},
|
|
810
|
-
];
|
|
811
|
-
|
|
812
|
-
if (widget.id === "model") {
|
|
813
|
-
fields.push({
|
|
814
|
-
key: "short",
|
|
815
|
-
label: "Short name",
|
|
816
|
-
values: [undefined, true],
|
|
817
|
-
show: (value) => onOff(value),
|
|
818
|
-
help: "3.8 Flash·High instead of Gemini 3.8 Flash (High).",
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
if (widget.id === "totalTokens") {
|
|
822
|
-
fields.push({
|
|
823
|
-
key: "split",
|
|
824
|
-
label: "Split",
|
|
825
|
-
values: [undefined, true],
|
|
826
|
-
show: (value) => onOff(value),
|
|
827
|
-
help: "Input and output tokens apart (123.1K↑ 355.0K↓) instead of one total.",
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
if (RESET_WIDGETS.has(widget.id)) {
|
|
831
|
-
fields.push({
|
|
832
|
-
key: "format",
|
|
833
|
-
label: "Format",
|
|
834
|
-
values: [undefined, "time", "both"],
|
|
835
|
-
show: (value) => value ?? "countdown",
|
|
836
|
-
help: "countdown (2hr 47m), time (6:33pm) or both (6:33pm (2hr 47m)).",
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
if (widget.id === "customText") {
|
|
840
|
-
fields.push({
|
|
841
|
-
key: "text",
|
|
842
|
-
label: "Text",
|
|
843
|
-
text: true,
|
|
844
|
-
show: (value) => value || "(empty: hidden)",
|
|
845
|
-
help: "The badge's text. Enter to type, Enter again to keep it, Esc to cancel.",
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
if (widget.id === "exec") {
|
|
849
|
-
fields.push({
|
|
850
|
-
key: "cmd",
|
|
851
|
-
label: "Command",
|
|
852
|
-
fixed: true,
|
|
853
|
-
show: (value) => value || "(none)",
|
|
854
|
-
help: `The command runs only in agy, from your install folder. Edit it in ${getConfigPath()}.`,
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
if (thresholds) {
|
|
858
|
-
const what = quota ? "left" : "used";
|
|
859
|
-
fields.push(
|
|
860
|
-
{
|
|
861
|
-
key: "warn",
|
|
862
|
-
label: "Caution at",
|
|
863
|
-
number: thresholds.warn,
|
|
864
|
-
show: (value) => (value === undefined ? `default (${thresholds.warn}%)` : `${value}%`),
|
|
865
|
-
help: `Turns to the caution color at this share ${what}.`,
|
|
866
|
-
},
|
|
867
|
-
{
|
|
868
|
-
key: "crit",
|
|
869
|
-
label: "Danger at",
|
|
870
|
-
number: thresholds.crit,
|
|
871
|
-
show: (value) => (value === undefined ? `default (${thresholds.crit}%)` : `${value}%`),
|
|
872
|
-
help: `Turns to the danger color at this share ${what}.`,
|
|
873
|
-
},
|
|
874
|
-
{
|
|
875
|
-
key: "blink",
|
|
876
|
-
label: "Blink",
|
|
877
|
-
values: [undefined, true, "warn", false],
|
|
878
|
-
show: (value) =>
|
|
879
|
-
value === undefined
|
|
880
|
-
? `default (${thresholds.blink ? "on" : "off"})`
|
|
881
|
-
: { true: "at danger", warn: "at caution and danger", false: "off" }[String(value)],
|
|
882
|
-
help: "Makes the widget blink when it reaches the danger (or caution) level. ASCII mode never blinks.",
|
|
883
|
-
}
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
if (PACED_WIDGETS.has(widget.id)) {
|
|
887
|
-
fields.push({
|
|
888
|
-
key: "pace",
|
|
889
|
-
label: "Pace marker",
|
|
890
|
-
values: [undefined, false, true],
|
|
891
|
-
show: (value) =>
|
|
892
|
-
value === undefined ? `default (${onOff(config.pacing !== false)})` : onOff(value),
|
|
893
|
-
help: "Marks where usage would be if it were spread evenly over the window. Default follows r.",
|
|
894
|
-
});
|
|
895
|
-
}
|
|
896
|
-
return fields;
|
|
763
|
+
return getEditFields(widget, config, {
|
|
764
|
+
colorName: (value) => (value === undefined ? "theme default" : colorName(value)),
|
|
765
|
+
cmdHelp: `The command runs only in agy, from your install folder. Edit it in ${getConfigPath()}.`,
|
|
766
|
+
});
|
|
897
767
|
}
|
|
898
768
|
|
|
899
769
|
// A theme slot's color name, in that color.
|
|
@@ -1165,21 +1035,38 @@ const toHex = (rgb) => `#${rgb.map((part) => part.toString(16).padStart(2, "0"))
|
|
|
1165
1035
|
|
|
1166
1036
|
// One color as a pill in that color with the theme's pill text, the way the
|
|
1167
1037
|
// bar draws it, then its hex value.
|
|
1168
|
-
function
|
|
1169
|
-
return
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
[
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
[
|
|
1180
|
-
|
|
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
|
+
],
|
|
1181
1061
|
];
|
|
1182
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
|
+
|
|
1183
1070
|
function themeDetails(id) {
|
|
1184
1071
|
if (id === "custom") {
|
|
1185
1072
|
return {
|
|
@@ -1203,9 +1090,7 @@ function themeDetails(id) {
|
|
|
1203
1090
|
...(background ? [{ raw: `${dim("For")} ${ui.tag(background)}` }] : []),
|
|
1204
1091
|
"",
|
|
1205
1092
|
heading("Palette"),
|
|
1206
|
-
...
|
|
1207
|
-
colorLine(label, theme.colors[slot], theme.colors.text)
|
|
1208
|
-
),
|
|
1093
|
+
...paletteLines(theme.colors),
|
|
1209
1094
|
"",
|
|
1210
1095
|
heading("Sample"),
|
|
1211
1096
|
...themeSample(theme.colors),
|
|
@@ -1340,7 +1225,7 @@ const POWERLINE_HELP = {
|
|
|
1340
1225
|
enabled:
|
|
1341
1226
|
"On: adjacent widgets share one pill, with a separator between them. Off: every widget gets its own pill.",
|
|
1342
1227
|
rowGap:
|
|
1343
|
-
"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).",
|
|
1344
1229
|
style: "Sets start caps, end caps and separators in one go. Enter cycles the styles.",
|
|
1345
1230
|
separators: "Glyphs between widgets of a pill. With several, they cycle per join, left to right.",
|
|
1346
1231
|
startCaps: "Glyphs that open each pill. With several, they cycle per pill, left to right.",
|
|
@@ -1369,7 +1254,7 @@ function powerlineDetails(row) {
|
|
|
1369
1254
|
function drawPowerline() {
|
|
1370
1255
|
const items = POWERLINE_ROWS.map((row) => ({ text: row.label, value: powerlineRowValue(row) }));
|
|
1371
1256
|
const notes =
|
|
1372
|
-
config.layout
|
|
1257
|
+
config.layout !== "pills"
|
|
1373
1258
|
? ["Powerline shapes apply to Pills. Switch layout with l."]
|
|
1374
1259
|
: [
|
|
1375
1260
|
"A style sets caps and separators together; edit a list to fine-tune it.",
|
|
@@ -1454,10 +1339,24 @@ function drawGlyphs() {
|
|
|
1454
1339
|
});
|
|
1455
1340
|
}
|
|
1456
1341
|
|
|
1342
|
+
// Three short paragraphs per layout, each under 90 columns so a wide panel
|
|
1343
|
+
// shows them unwrapped.
|
|
1457
1344
|
const LAYOUT_HELP = {
|
|
1458
|
-
pills:
|
|
1459
|
-
|
|
1460
|
-
"
|
|
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
|
+
],
|
|
1461
1360
|
};
|
|
1462
1361
|
|
|
1463
1362
|
function drawLayout() {
|
|
@@ -1471,10 +1370,9 @@ function drawLayout() {
|
|
|
1471
1370
|
return screenFrame({
|
|
1472
1371
|
title: "Layout",
|
|
1473
1372
|
items,
|
|
1474
|
-
notes: ["Both layouts use your widgets and theme; Powerline shapes apply to Pills."],
|
|
1475
1373
|
details: hovered && {
|
|
1476
1374
|
title: hovered.name,
|
|
1477
|
-
lines: [
|
|
1375
|
+
lines: LAYOUT_HELP[hovered.id].flatMap((line, i) => (i ? ["", line] : [line])),
|
|
1478
1376
|
},
|
|
1479
1377
|
keys: [
|
|
1480
1378
|
[ui.glyph.upDown, "preview"],
|
package/src/cli/doctor.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
1
3
|
const fs = require("fs");
|
|
2
4
|
const os = require("os");
|
|
3
5
|
const path = require("path");
|
|
@@ -23,6 +25,7 @@ const {
|
|
|
23
25
|
const { runStatuslineCommand } = require("../lib/run");
|
|
24
26
|
const { detectColorDepth } = require("../lib/colors");
|
|
25
27
|
const { readLog, getLogPath } = require("../lib/log");
|
|
28
|
+
const { USAGE_PERIODS, usageAvailable } = require("../lib/usage");
|
|
26
29
|
const { version } = require("../../package.json");
|
|
27
30
|
|
|
28
31
|
const DIM = "\x1b[2m";
|
|
@@ -144,6 +147,15 @@ async function doctorCommand(options = {}) {
|
|
|
144
147
|
console.log(" The bar shows the defaults until you fix the file or delete it.");
|
|
145
148
|
}
|
|
146
149
|
|
|
150
|
+
// Not a failure: the widgets just hide.
|
|
151
|
+
if (configState.config.widgets.some((widget) => USAGE_PERIODS[widget.id]) && !usageAvailable()) {
|
|
152
|
+
console.log();
|
|
153
|
+
console.log(
|
|
154
|
+
`${YELLOW} usage widgets need Node 22.13 or newer${RESET} (this is ${process.version}).`
|
|
155
|
+
);
|
|
156
|
+
console.log(" tokensToday and tokensWeek stay hidden until Node is updated.");
|
|
157
|
+
}
|
|
158
|
+
|
|
147
159
|
const breaking = cmdBreakingChars(installDir);
|
|
148
160
|
if (breaking.length) {
|
|
149
161
|
failed += 1;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { readConfigFile, mergeConfig, setConfigDir } = require("../lib/shared");
|
|
6
|
+
const { resolveInstallDir, writeFileAtomic, PRIVATE_FILE_MODE } = require("../lib/paths");
|
|
7
|
+
const { printInvalidConfig } = require("./config-guard");
|
|
8
|
+
const { version } = require("../../package.json");
|
|
9
|
+
|
|
10
|
+
const DIM = "\x1b[2m";
|
|
11
|
+
const GREEN = "\x1b[32m";
|
|
12
|
+
const RED = "\x1b[31m";
|
|
13
|
+
const YELLOW = "\x1b[33m";
|
|
14
|
+
const RESET = "\x1b[0m";
|
|
15
|
+
|
|
16
|
+
// The config as the bar reads it, with the release that wrote it first, so an
|
|
17
|
+
// import can tell a file from a newer release.
|
|
18
|
+
function exportDocument(config) {
|
|
19
|
+
return { agyPowerline: { version }, ...mergeConfig(config) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function execCommandCount(config) {
|
|
23
|
+
return (config.widgets || []).filter(
|
|
24
|
+
(widget) => widget.id === "exec" && String(widget.cmd || "").trim()
|
|
25
|
+
).length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function exportCommand(file, options = {}) {
|
|
29
|
+
const installDir = resolveInstallDir();
|
|
30
|
+
if (!installDir) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setConfigDir(installDir);
|
|
35
|
+
const { config, error } = readConfigFile();
|
|
36
|
+
if (error) {
|
|
37
|
+
// Exporting the defaults would look like a backup of the user's bar.
|
|
38
|
+
printInvalidConfig(error);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const body = JSON.stringify(exportDocument(config), null, 2) + "\n";
|
|
44
|
+
|
|
45
|
+
if (file) {
|
|
46
|
+
const target = path.resolve(String(file));
|
|
47
|
+
let existing = null;
|
|
48
|
+
try {
|
|
49
|
+
existing = fs.statSync(target);
|
|
50
|
+
} catch {
|
|
51
|
+
// Nothing there yet.
|
|
52
|
+
}
|
|
53
|
+
if (existing?.isDirectory()) {
|
|
54
|
+
console.error(`${RED}error${RESET} ${target} is a folder. Name a file to write.`);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (existing && !options.force) {
|
|
59
|
+
console.error(`${RED}error${RESET} ${target} already exists. Pass --force to replace it.`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Owner-only, like the config itself: it can hold your commands.
|
|
64
|
+
writeFileAtomic(target, body, { mode: PRIVATE_FILE_MODE });
|
|
65
|
+
console.error(`${GREEN}✔${RESET} Config exported to ${DIM}${target}${RESET}`);
|
|
66
|
+
} else {
|
|
67
|
+
process.stdout.write(body);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Notes go to stderr, so `export > file` keeps the JSON clean.
|
|
71
|
+
const commands = execCommandCount(config);
|
|
72
|
+
if (commands) {
|
|
73
|
+
console.error(
|
|
74
|
+
`${YELLOW}note${RESET} The export holds ${commands} exec widget command${commands === 1 ? "" : "s"}; check ${commands === 1 ? "it" : "them"} before sharing the file.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
exportCommand,
|
|
81
|
+
exportDocument,
|
|
82
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
saveConfig,
|
|
5
|
+
setConfigDir,
|
|
6
|
+
getConfigPath,
|
|
7
|
+
renderStatusline,
|
|
8
|
+
sampleData,
|
|
9
|
+
resolveColorDepth,
|
|
10
|
+
} = require("../lib/shared");
|
|
11
|
+
const { GRADIENTS, GRADIENT_ORDER, normalizeGradient, gradientStops } = require("../lib/themes");
|
|
12
|
+
const { bg, gradientColors, setActiveDepth, getActiveDepth } = require("../lib/colors");
|
|
13
|
+
const { resolveInstallDir } = require("../lib/paths");
|
|
14
|
+
const { loadEditableConfig } = require("./config-guard");
|
|
15
|
+
|
|
16
|
+
const DIM = "\x1b[2m";
|
|
17
|
+
const BOLD = "\x1b[1m";
|
|
18
|
+
const GREEN = "\x1b[32m";
|
|
19
|
+
const RED = "\x1b[31m";
|
|
20
|
+
const CYAN = "\x1b[36m";
|
|
21
|
+
const RESET = "\x1b[0m";
|
|
22
|
+
|
|
23
|
+
const STRIP_WIDTH = 24;
|
|
24
|
+
|
|
25
|
+
// A row of colored cells showing the gradient; empty without color.
|
|
26
|
+
function gradientStrip(value, config) {
|
|
27
|
+
const stops = gradientStops(value);
|
|
28
|
+
if (!stops) {
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
const previous = getActiveDepth();
|
|
32
|
+
setActiveDepth(resolveColorDepth({ configColor: config.color, env: process.env }));
|
|
33
|
+
try {
|
|
34
|
+
if (getActiveDepth() === "none") {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
const cells = gradientColors(stops, STRIP_WIDTH).map((color) => `${bg(color)} `);
|
|
38
|
+
return `${cells.join("")}${RESET}`;
|
|
39
|
+
} finally {
|
|
40
|
+
setActiveDepth(previous);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function listGradients(config) {
|
|
45
|
+
const current = config.gradient || "off";
|
|
46
|
+
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(`${BOLD} agy-powerline gradients${RESET}`);
|
|
49
|
+
console.log(` current ${CYAN}${current}${RESET} ${gradientStrip(config.gradient, config)}`);
|
|
50
|
+
console.log(` config ${DIM}${getConfigPath()}${RESET}`);
|
|
51
|
+
console.log();
|
|
52
|
+
|
|
53
|
+
const maxIdLen = Math.max(...GRADIENT_ORDER.map((id) => id.length), "off".length);
|
|
54
|
+
|
|
55
|
+
for (const id of [...GRADIENT_ORDER, "off"]) {
|
|
56
|
+
const isCurrent = id === current;
|
|
57
|
+
const marker = isCurrent ? `${GREEN}*${RESET}` : " ";
|
|
58
|
+
const idFormatted = isCurrent
|
|
59
|
+
? `${BOLD}${CYAN}${id.padEnd(maxIdLen)}${RESET}`
|
|
60
|
+
: id.padEnd(maxIdLen);
|
|
61
|
+
const preview = id === "off" ? `${DIM}theme colors only${RESET}` : gradientStrip(id, config);
|
|
62
|
+
console.log(` ${marker} ${idFormatted} ${preview}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log();
|
|
66
|
+
console.log(` Run ${CYAN}npx agy-powerline gradient <name>${RESET} to color each row.`);
|
|
67
|
+
console.log(` Your own colors: ${CYAN}npx agy-powerline gradient "#ff5f6d-#ffc371"${RESET}`);
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function gradientCommand(name, options = {}) {
|
|
72
|
+
const installDir = resolveInstallDir();
|
|
73
|
+
if (!installDir) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setConfigDir(installDir);
|
|
78
|
+
const config = loadEditableConfig();
|
|
79
|
+
if (!config) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!name || options.list) {
|
|
84
|
+
listGradients(config);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const raw = String(name).trim();
|
|
89
|
+
const off = raw.toLowerCase() === "off";
|
|
90
|
+
const value = off ? null : normalizeGradient(raw);
|
|
91
|
+
|
|
92
|
+
if (!off && !value) {
|
|
93
|
+
console.error(`${RED}error${RESET} Unknown gradient "${raw}".`);
|
|
94
|
+
console.error(` Available gradients: ${GRADIENT_ORDER.join(", ")}, off`);
|
|
95
|
+
console.error(" Or 2 to 6 hex colors joined by dashes, e.g. #ff5f6d-#ffc371");
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (off) {
|
|
101
|
+
delete config.gradient;
|
|
102
|
+
} else {
|
|
103
|
+
config.gradient = value;
|
|
104
|
+
}
|
|
105
|
+
saveConfig(config);
|
|
106
|
+
|
|
107
|
+
const title = off ? "off" : GRADIENTS[value]?.name || value;
|
|
108
|
+
|
|
109
|
+
console.log();
|
|
110
|
+
console.log(`${GREEN}✔${RESET} Gradient set to ${BOLD}${title}${RESET}`);
|
|
111
|
+
console.log(` config ${DIM}${getConfigPath()}${RESET}`);
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(" Preview:");
|
|
114
|
+
console.log(renderStatusline(sampleData(), config));
|
|
115
|
+
console.log();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
gradientCommand,
|
|
120
|
+
gradientStrip,
|
|
121
|
+
};
|