agy-powerline 1.3.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/README.zh-CN.md CHANGED
@@ -3,13 +3,19 @@
3
3
  [English](README.md) | **简体中文** | [日本語](README.ja.md) | [Français](README.fr.md)
4
4
 
5
5
  [![CI](https://github.com/wassim-azi/agy-powerline/actions/workflows/ci.yml/badge.svg)](https://github.com/wassim-azi/agy-powerline/actions/workflows/ci.yml)
6
+ [![CodeQL](https://github.com/wassim-azi/agy-powerline/actions/workflows/codeql.yml/badge.svg)](https://github.com/wassim-azi/agy-powerline/actions/workflows/codeql.yml)
6
7
  [![Version](https://img.shields.io/npm/v/agy-powerline.svg)](https://www.npmjs.com/package/agy-powerline)
7
- [![Downloads](https://img.shields.io/npm/dt/agy-powerline.svg)](https://www.npmjs.com/package/agy-powerline)
8
+ [![Downloads](https://img.shields.io/npm/d18m/agy-powerline.svg?cacheSeconds=86400)](https://www.npmjs.com/package/agy-powerline)
9
+ [![Stars](https://img.shields.io/github/stars/wassim-azi/agy-powerline?style=flat&color=gold&cacheSeconds=86400)](https://github.com/wassim-azi/agy-powerline/stargazers)
10
+ [![Web Configurator](https://img.shields.io/badge/web%20configurator-live-00dc82.svg)](https://agy-powerline.com/configure/)
8
11
  [![Node](https://img.shields.io/badge/node-%3E%3D20-informational.svg)](https://nodejs.org/)
9
- [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-informational.svg)](docs/install.md)
12
+ [![Windows](https://img.shields.io/badge/Windows-0078D6.svg?logo=windows&logoColor=white)](docs/install.md)
13
+ [![macOS](https://img.shields.io/badge/macOS-000000.svg?logo=apple&logoColor=white)](docs/install.md)
14
+ [![Linux](https://img.shields.io/badge/Linux-FCC624.svg?logo=linux&logoColor=black)](docs/install.md)
10
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
16
  [![Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](#隐私与安全)
12
17
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/wassim-azi/agy-powerline/badge)](https://scorecard.dev/viewer/?uri=github.com/wassim-azi/agy-powerline)
18
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/15655/badge)](https://www.bestpractices.dev/projects/15655)
13
19
  [![Mentioned in Awesome Gemini CLI](https://img.shields.io/badge/mentioned%20in-awesome-fc60a8.svg?logo=awesomelists&logoColor=white)](https://github.com/Piebald-AI/awesome-gemini-cli)
14
20
 
15
21
  > 本文译自英文版 [README](README.md),可能落后于英文版;如有出入,以英文版为准。详细文档([`docs/`](docs/))仅提供英文版。
@@ -19,7 +25,7 @@
19
25
  一眼看到当前模型、Gemini 5h/7d、Claude/GPT 和 Argon 配额、上下文窗口、git 分支等信息。在交互式编辑器中挑选组件、主题和布局。一条命令即可安装,零依赖。
20
26
 
21
27
  <p align="center">
22
- <img src="assets/screenshots/pills-dark.png" alt="agy-powerline 的 Pills 布局,dark-ocean 主题:代理状态、模型、套餐、项目和 git;上下文;5h 和 7d 配额及重置时间" width="850">
28
+ <img src="assets/demo.gif" alt="agy-powerline 的 configure:输入 argon 添加 Argon 每周配额,然后浏览预设、主题和布局并实时预览状态栏,最后打开 Powerline 标签页" width="850">
23
29
  </p>
24
30
 
25
31
  ## 安装
@@ -34,6 +40,15 @@ agy # 启动 Antigravity
34
40
 
35
41
  `init` 在替换任何内容之前都会先询问,并会先备份 Antigravity 的 `settings.json`。运行 `init` 后需重启一次 `agy`。详情:[`init` 做了什么](docs/install.md)。
36
42
 
43
+ <details>
44
+ <summary>截图:真实 agy 会话中的状态栏(Windows Terminal)</summary>
45
+
46
+ <p align="center">
47
+ <img src="assets/screenshots/windows.png" alt="Windows Terminal 中 agy 输入框下方的 agy-powerline:上方是一段简短对话,下方是三行的 Pills 状态栏" width="850">
48
+ </p>
49
+
50
+ </details>
51
+
37
52
  也可以让 agy 帮你安装:把下面这段话粘贴到 `agy` 会话中。
38
53
 
39
54
  ```text
@@ -54,13 +69,15 @@ Install the agy-powerline statusline: run `npx agy-powerline init --yes`, then t
54
69
 
55
70
  从一个预设开始,再按需调整:
56
71
 
57
- | 预设 | 显示内容 |
58
- | ----------- | ------------------------------------------------------ |
59
- | `default` | 模型、上下文、git;第二行显示 5h 和 7d 配额及重置时间 |
60
- | `compact` | 单行:简短模型名、最紧张的配额、上下文 %、git |
61
- | `workspace` | 项目、git、代理状态、模型;上下文、最紧张的配额和模式 |
62
- | `limits` | 所有配额(5h、7d、第三方、Argon),每行一个 |
63
- | `full` | 全部:会话、项目和 git;上下文、token 和缓存;两组配额 |
72
+ | 预设 | 显示内容 |
73
+ | --------- | ---------------------------------------------------------------------- |
74
+ | `default` | 模型、上下文、git;第二行显示 5h 和 7d 配额及重置时间 |
75
+ | `compact` | 单行:简短模型名、最紧张的配额、上下文 %、git |
76
+ | `agent` | 代理状态、子代理、标题、模式、沙盒;模型、上下文 %、产物、最紧张的配额 |
77
+ | `git` | 项目、分支、worktree、stash;文件数、改动行数、提交 |
78
+ | `tokens` | 上下文 token、会话输入/输出 token、缓存;今天和 7 天的 token |
79
+ | `quotas` | 套餐和模型;5h 和 7d 配额并排显示,第三方和 Argon 配额在第三行 |
80
+ | `full` | 全部:会话、项目和 git;上下文、token 和缓存;两组配额 |
64
81
 
65
82
  ### 主题
66
83
 
@@ -78,16 +95,48 @@ Install the agy-powerline statusline: run `npx agy-powerline init --yes`, then t
78
95
  **Pills**(默认):powerline 风格的胶囊。可选圆角、尖角、斜角或平直形状,也可以在行与行之间留一个空行。
79
96
 
80
97
  <p align="center">
81
- <img src="assets/screenshots/pills-light.png" alt="agy-powerline 的 Pills 布局,浅色终端上的 light-sky 主题,行间留空" width="850">
98
+ <img src="assets/screenshots/pills-dark.png" alt="agy-powerline 的 Pills 布局,dark-aurora 主题:模型、上下文、项目和 git;Gemini 5h 和 7d 配额,带进度标记和重置时间;Claude/GPT 和 Argon 每周配额" width="850">
82
99
  </p>
83
100
 
101
+ <details>
102
+ <summary>浅色终端</summary>
103
+
104
+ <p align="center">
105
+ <img src="assets/screenshots/pills-light.png" alt="agy-powerline 的 Pills 布局,浅色终端上的 light-paper 主题" width="850">
106
+ </p>
107
+
108
+ </details>
109
+
84
110
  **Lines**:以纯彩色文本显示相同的信息。
85
111
 
86
112
  <p align="center">
87
- <img src="assets/screenshots/lines-dark.png" alt="agy-powerline 的 Lines 布局,深色终端上的 dark-ocean 主题" width="850">
88
- <img src="assets/screenshots/lines-light.png" alt="agy-powerline 的 Lines 布局,浅色终端上的 light-sky 主题" width="850">
113
+ <img src="assets/screenshots/lines-dark.png" alt="agy-powerline 的 Lines 布局,深色终端上的 dark-aurora 主题" width="850">
114
+ </p>
115
+
116
+ <details>
117
+ <summary>浅色终端</summary>
118
+
119
+ <p align="center">
120
+ <img src="assets/screenshots/lines-light.png" alt="agy-powerline 的 Lines 布局,浅色终端上的 light-paper 主题" width="850">
89
121
  </p>
90
122
 
123
+ </details>
124
+
125
+ **Classic**:用 `|` 分隔的纯彩色文本,进度条为方块,例如 `███░│░░░░░`。
126
+
127
+ <p align="center">
128
+ <img src="assets/screenshots/classic-dark.png" alt="agy-powerline 的 Classic 布局,深色终端上的 dark-aurora 主题,方块进度条" width="850">
129
+ </p>
130
+
131
+ <details>
132
+ <summary>浅色终端</summary>
133
+
134
+ <p align="center">
135
+ <img src="assets/screenshots/classic-light.png" alt="agy-powerline 的 Classic 布局,浅色终端上的 light-paper 主题" width="850">
136
+ </p>
137
+
138
+ </details>
139
+
91
140
  ## 组件
92
141
 
93
142
  | 组件 | 显示内容 |
@@ -116,21 +165,21 @@ Install the agy-powerline statusline: run `npx agy-powerline init --yes`, then t
116
165
 
117
166
  ## 命令
118
167
 
119
- | 命令 | 作用 |
120
- | ----------------------------------------- | ----------------------------------------------------- |
121
- | `npx agy-powerline init [--yes]` | 安装或更新状态栏 |
122
- | `npx agy-powerline configure` | 打开编辑器(别名 `config`) |
123
- | `npx agy-powerline preset <name>` | 应用预设(`--list` 查看全部) |
124
- | `npx agy-powerline theme <name>` | 切换主题(`--list`、`--gallery`) |
125
- | `npx agy-powerline gradient <name>` | Color each row with a gradient (`--list`, `off`) |
126
- | `npx agy-powerline layout <pills\|lines>` | 切换布局 |
127
- | `npx agy-powerline caps <style>` | 切换胶囊形状:`round`、`angled`、`slanted`、`flat` |
128
- | `npx agy-powerline export [file]` | Save your config to a file, to back it up or share it |
129
- | `npx agy-powerline import <file>` | Load a config, after showing what changes (`--merge`) |
130
- | `npx agy-powerline doctor` | 检查安装并说明问题所在 |
131
- | `npx agy-powerline report` | 生成保护隐私的错误报告 |
132
- | `npx agy-powerline uninstall [--delete]` | 从 agy 中移除(`--delete` 会同时删除其文件) |
133
- | `npx agy-powerline render --demo` | 用示例数据打印状态栏 |
168
+ | 命令 | 作用 |
169
+ | -------------------------------------------------- | ----------------------------------------------------- |
170
+ | `npx agy-powerline init [--yes]` | 安装或更新状态栏 |
171
+ | `npx agy-powerline configure` | 打开编辑器(别名 `config`) |
172
+ | `npx agy-powerline preset <name>` | 应用预设(`--list` 查看全部) |
173
+ | `npx agy-powerline theme <name>` | 切换主题(`--list`、`--gallery`) |
174
+ | `npx agy-powerline gradient <name>` | Color each row with a gradient (`--list`, `off`) |
175
+ | `npx agy-powerline layout <pills\|lines\|classic>` | 切换布局 |
176
+ | `npx agy-powerline caps <style>` | 切换胶囊形状:`round`、`angled`、`slanted`、`flat` |
177
+ | `npx agy-powerline export [file]` | Save your config to a file, to back it up or share it |
178
+ | `npx agy-powerline import <file>` | Load a config, after showing what changes (`--merge`) |
179
+ | `npx agy-powerline doctor` | 检查安装并说明问题所在 |
180
+ | `npx agy-powerline report` | 生成保护隐私的错误报告 |
181
+ | `npx agy-powerline uninstall [--delete]` | 从 agy 中移除(`--delete` 会同时删除其文件) |
182
+ | `npx agy-powerline render --demo` | 用示例数据打印状态栏 |
134
183
 
135
184
  任何命令加上 `--help` 即可查看其选项。
136
185
 
@@ -185,6 +234,10 @@ npx agy-powerline doctor # 检查一切是否为最新
185
234
 
186
235
  如果 agy-powerline 对你有用,点个 ⭐ 能帮助更多 agy 用户发现它。
187
236
 
237
+ ## Star History
238
+
239
+ [![Star History Chart](https://api.star-history.com/svg?repos=wassim-azi/agy-powerline&type=Date)](https://star-history.com/#wassim-azi/agy-powerline&Date)
240
+
188
241
  ## 许可证
189
242
 
190
243
  [MIT](LICENSE) © Wassim AZI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agy-powerline",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Fast, configurable statusline for Google Antigravity CLI (agy): live Gemini, Claude/GPT and Argon quota pacing, git, and an interactive TUI configurator",
5
5
  "bin": {
6
6
  "agy-powerline": "src/index.js"
@@ -35,6 +35,7 @@
35
35
  "test:update": "node tests/update-snapshots.js",
36
36
  "test:coverage": "node --test --experimental-test-coverage tests/shared.js",
37
37
  "gallery": "node scripts/gallery/build.js",
38
+ "screenshots": "node scripts/gallery/screenshots.js",
38
39
  "configurator": "node scripts/configurator/build.js",
39
40
  "lint": "eslint .",
40
41
  "lint:fix": "eslint . --fix",
@@ -71,6 +72,7 @@
71
72
  "devDependencies": {
72
73
  "@eslint/js": "10.0.1",
73
74
  "eslint": "10.12.0",
75
+ "fast-check": "4.10.2",
74
76
  "globals": "17.13.0",
75
77
  "prettier": "3.9.10"
76
78
  }
@@ -295,7 +295,15 @@ const TAB_OF_SCREEN = {
295
295
  glyphs: "powerline",
296
296
  };
297
297
 
298
- const PRESET_ICONS = { default: "🎯", compact: "⚡", workspace: "📂", limits: "🔋", full: "🚀" };
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(" ") };
@@ -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 colorLine(label, rgb, ink) {
1033
- return { raw: `${ui.pill(` ${label.padEnd(9)}`, rgb, ink)} ${dim(toHex(rgb))}` };
1034
- }
1035
-
1036
- const PALETTE_SLOTS = [
1037
- ["model", "Model"],
1038
- ["context", "Context"],
1039
- ["git", "Git"],
1040
- ["weekly", "7d quota"],
1041
- ["meta", "Details"],
1042
- ["fiveHour", "OK"],
1043
- ["caution", "Caution"],
1044
- ["warn", "Danger"],
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
- ...PALETTE_SLOTS.map(([slot, label]) =>
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 Pills rows, so stacked pills don't touch. The bar takes more lines (three rows become five).",
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 === "lines"
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: "Widgets sit in colored pills. The Powerline tab sets their caps, separators and joins.",
1323
- lines:
1324
- "Colored text on the terminal background, with dividers and dot meters. Powerline shapes don't apply.",
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: [`${hovered.description}.`, "", LAYOUT_HELP[hovered.id] ?? ""],
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/index.js CHANGED
@@ -102,7 +102,8 @@ const COMMANDS = [
102
102
  {
103
103
  name: "preset",
104
104
  arg: "[name]",
105
- description: "View or apply a widget preset (default, compact, workspace, limits, full)",
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/colors.js CHANGED
@@ -91,21 +91,24 @@ function rgb(r, g, b) {
91
91
  return [r, g, b];
92
92
  }
93
93
 
94
- function rgbToAnsi256([r, g, b]) {
95
- if (r === g && g === b) {
96
- if (r < 8) {
97
- return 16;
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
- return Math.round(((r - 8) / 247) * 24) + 232;
105
- }
106
-
107
- const to6 = (v) => Math.round((v / 255) * 5);
108
- return 16 + 36 * to6(r) + 6 * to6(g) + to6(b);
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
@@ -91,9 +91,14 @@ const LAYOUTS = {
91
91
  name: "Lines",
92
92
  description: "Plain colored text with dividers and dot meters",
93
93
  },
94
+ classic: {
95
+ id: "classic",
96
+ name: "Classic",
97
+ description: "Plain colored text with | dividers and █░│ block meters",
98
+ },
94
99
  };
95
100
 
96
- const LAYOUT_ORDER = ["pills", "lines"];
101
+ const LAYOUT_ORDER = ["pills", "lines", "classic"];
97
102
 
98
103
  function resolveLayoutId(layoutId) {
99
104
  return LAYOUTS[layoutId] ? layoutId : "pills";
@@ -255,11 +260,12 @@ function saveConfig(config) {
255
260
  }
256
261
 
257
262
  // 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 columns (full at 100).
263
+ // the user picked. Each answers one question and keeps all its widgets at 80
264
+ // columns (full at 100); widgets with nothing to show hide on their own.
259
265
  const PRESETS = {
260
266
  default: {
261
267
  id: "default",
262
- name: "Everyday",
268
+ name: "Default",
263
269
  description: "Model, context and git; 5h and 7d quotas with resets on a second row",
264
270
  widgets: [
265
271
  { id: "model" },
@@ -277,41 +283,84 @@ const PRESETS = {
277
283
  },
278
284
  compact: {
279
285
  id: "compact",
280
- name: "One line",
281
- description: "Short model, tightest quota, context % and git on a single row",
286
+ name: "Compact",
287
+ description: "One row: short model, tightest quota, context % and git",
282
288
  widgets: [{ id: "model", short: true }, { id: "quota" }, { id: "contextPct" }, { id: "git" }],
283
289
  },
284
- workspace: {
285
- id: "workspace",
286
- name: "Several repos",
287
- description: "Project, git, agent state and model; context, tightest quota and mode below",
290
+ agent: {
291
+ id: "agent",
292
+ name: "Agent activity",
293
+ description: "Agent state, subagents, title and mode; context, artifacts, quota",
294
+ widgets: [
295
+ { id: "agentState" },
296
+ { id: "tasks" },
297
+ { id: "title" },
298
+ { id: "separator" },
299
+ { id: "mode" },
300
+ { id: "sandbox" },
301
+ { id: "lineBreak" },
302
+ { id: "model", short: true },
303
+ { id: "contextPct" },
304
+ { id: "artifacts" },
305
+ { id: "separator" },
306
+ { id: "quota" },
307
+ ],
308
+ },
309
+ git: {
310
+ id: "git",
311
+ name: "Git focus",
312
+ description: "Project, branch, worktree, stash; changed files and lines, commit",
288
313
  widgets: [
289
314
  { id: "project" },
290
315
  { id: "git" },
291
- { id: "agentState" },
316
+ { id: "gitWorktree" },
317
+ { id: "gitStash" },
292
318
  { id: "separator" },
293
319
  { id: "model", short: true },
294
320
  { id: "lineBreak" },
295
- { id: "context" },
321
+ { id: "gitFiles" },
322
+ { id: "gitChanges" },
323
+ { id: "gitSha" },
324
+ { id: "separator" },
325
+ { id: "contextPct" },
326
+ { id: "quota" },
327
+ ],
328
+ },
329
+ tokens: {
330
+ id: "tokens",
331
+ name: "Token usage",
332
+ description: "Session tokens in and out, cache hit; tokens today and over 7 days",
333
+ widgets: [
334
+ { id: "model", short: true },
335
+ { id: "contextTokens" },
336
+ { id: "separator" },
337
+ { id: "totalTokens", split: true },
338
+ { id: "cacheHit" },
339
+ { id: "lineBreak" },
340
+ { id: "tokensToday" },
341
+ { id: "tokensWeek" },
296
342
  { id: "separator" },
297
343
  { id: "quota" },
298
- { id: "mode" },
299
344
  ],
300
345
  },
301
- limits: {
302
- id: "limits",
303
- name: "Heavy usage",
304
- description: "5h, 7d, third-party and Argon quotas with resets, one quota per row",
346
+ quotas: {
347
+ id: "quotas",
348
+ name: "Quota watch",
349
+ description: "Plan and model; 5h and 7d quotas side by side; third-party and Argon",
305
350
  widgets: [
351
+ { id: "plan" },
352
+ { id: "separator" },
353
+ { id: "model", short: true },
354
+ { id: "lineBreak" },
306
355
  { id: "fiveHourBar" },
307
356
  { id: "fiveHourReset" },
308
- { id: "lineBreak" },
357
+ { id: "separator" },
309
358
  { id: "weeklyBar" },
310
359
  { id: "weeklyReset" },
311
360
  { id: "lineBreak" },
312
361
  { id: "thirdPartyFiveHour" },
313
362
  { id: "thirdPartyWeekly" },
314
- { id: "lineBreak" },
363
+ { id: "separator" },
315
364
  { id: "argonWeekly" },
316
365
  { id: "argonReset" },
317
366
  ],
@@ -319,7 +368,7 @@ const PRESETS = {
319
368
  full: {
320
369
  id: "full",
321
370
  name: "Everything",
322
- description: "Session, project and git; context, tokens and cache; both quotas, on three rows",
371
+ description: "Session, project and git; context, tokens and cache; both quotas",
323
372
  widgets: [
324
373
  { id: "agentState" },
325
374
  { id: "tasks" },
@@ -344,7 +393,7 @@ const PRESETS = {
344
393
  },
345
394
  };
346
395
 
347
- const PRESET_ORDER = ["default", "compact", "workspace", "limits", "full"];
396
+ const PRESET_ORDER = ["default", "compact", "agent", "git", "tokens", "quotas", "full"];
348
397
 
349
398
  function applyPreset(baseConfig, presetName) {
350
399
  const preset = PRESETS[presetName] || PRESETS.default;
package/src/lib/git.js CHANGED
@@ -13,6 +13,15 @@ const SYNC_CACHE_NAME = ".git-sync-cache.json";
13
13
  const SYNC_CACHE_MAX = 20;
14
14
  const COMMIT = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
15
15
 
16
+ // A git call that runs past its time is skipped until a later refresh. Tests
17
+ // give git more time: on a busy CI machine a plain call can take longer.
18
+ const timeouts = { sync: 150, detail: 300 };
19
+ function setGitTimeouts(next) {
20
+ const previous = { ...timeouts };
21
+ Object.assign(timeouts, next);
22
+ return previous;
23
+ }
24
+
16
25
  // Only an absolute folder that exists can belong to the user's session. A
17
26
  // relative path (a Windows path read on Linux, for one) resolves against
18
27
  // wherever this process runs, and the upward walk would then report that
@@ -392,7 +401,7 @@ function getGitSync(gitDir, branch) {
392
401
  ["rev-list", "--left-right", "--count", `${branch}...${branch}@{upstream}`, "--"],
393
402
  {
394
403
  cwd: path.dirname(gitDir),
395
- timeout: 150,
404
+ timeout: timeouts.sync,
396
405
  stdio: ["ignore", "pipe", "ignore"],
397
406
  encoding: "utf8",
398
407
  // Antigravity may run the statusline without a console; without this,
@@ -414,7 +423,7 @@ function getGitSync(gitDir, branch) {
414
423
  // A timeout or missing git is worth knowing about when ahead/behind
415
424
  // counts never show.
416
425
  if (err.code === "ETIMEDOUT" || err.signal === "SIGTERM") {
417
- logWarning("GIT_TIMEOUT", { timeoutMs: 150 });
426
+ logWarning("GIT_TIMEOUT", { timeoutMs: timeouts.sync });
418
427
  } else if (err.code === "ENOENT") {
419
428
  logWarning("GIT_NOT_FOUND");
420
429
  }
@@ -437,7 +446,6 @@ const DETAIL_NEEDS = {
437
446
  const STATUS_CACHE_NAME = ".git-status-cache.json";
438
447
  const STATUS_CACHE_MAX = 20;
439
448
  const STATUS_TTL_MS = 5000;
440
- const DETAIL_TIMEOUT_MS = 300;
441
449
 
442
450
  // No optional locks: the bar must never hold the index lock and make the
443
451
  // user's own git commands fail. The -c flags and the submodule skip keep git
@@ -559,7 +567,7 @@ function runGitDetail(cwd, args) {
559
567
  const { execFileSync } = require("child_process");
560
568
  return execFileSync("git", args, {
561
569
  cwd,
562
- timeout: DETAIL_TIMEOUT_MS,
570
+ timeout: timeouts.detail,
563
571
  stdio: ["ignore", "pipe", "ignore"],
564
572
  encoding: "utf8",
565
573
  maxBuffer: 16 * 1024 * 1024,
@@ -567,7 +575,7 @@ function runGitDetail(cwd, args) {
567
575
  });
568
576
  } catch (err) {
569
577
  if (err.code === "ETIMEDOUT" || err.signal === "SIGTERM") {
570
- logWarning("GIT_TIMEOUT", { timeoutMs: DETAIL_TIMEOUT_MS });
578
+ logWarning("GIT_TIMEOUT", { timeoutMs: timeouts.detail });
571
579
  } else if (err.code === "ENOENT") {
572
580
  logWarning("GIT_NOT_FOUND");
573
581
  }
@@ -767,4 +775,5 @@ module.exports = {
767
775
  getGitDetails,
768
776
  resolveGitDetails,
769
777
  clearGitDetailsCache,
778
+ setGitTimeouts,
770
779
  };