agy-statusblocks 0.1.1 → 0.1.3

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.md CHANGED
@@ -47,14 +47,30 @@ On narrow/split-pane terminals, the layout degrades gracefully to stacked single
47
47
 
48
48
  ```json
49
49
  {
50
- "segments": ["context", "model", "usage", "state", "git"]
50
+ "segments": ["context", "model", "usage", "state", "git"],
51
+ "border": "rounded"
51
52
  }
52
53
  ```
53
54
 
54
- Or via environment:
55
+ Border styles supported:
56
+ - `rounded` (default) — `╭─╮` `╰─╯` `│` `─`
57
+ - `square` — `┌─┐` `└─┘` `│` `─`
58
+ - `double` — `╔═╗` `╚═╝` `║` `═`
59
+ - `thick` — `┏━┓` `┗━┛` `┃` `━`
60
+ - `ascii` — `+-+` `+-+` `|` `-` (clean standard 7-bit ASCII)
61
+ - `none` — clean padded columns without frame borders
62
+
63
+ Or configure via environment variables:
55
64
 
56
65
  ```sh
57
66
  AGY_STATUSBLOCKS_SEGMENTS=context,model,usage
67
+ AGY_STATUSBLOCKS_BORDER=double
68
+ ```
69
+
70
+ Or pass via CLI flag:
71
+
72
+ ```sh
73
+ npx agy-statusblocks preview --border double
58
74
  ```
59
75
 
60
76
  ## Preview
package/dist/cache.js CHANGED
@@ -3,9 +3,9 @@ import { join } from 'path';
3
3
  import { tmpdir } from 'os';
4
4
  import { createHash } from 'crypto';
5
5
  function cacheDir(create) {
6
- const override = process.env['AGY_STATUSBLOCKS_CACHE_DIR'] || process.env['CLAUDE_STATUSBLOCKS_CACHE_DIR'];
6
+ const override = process.env['AGY_STATUSBLOCKS_CACHE_DIR'];
7
7
  const uid = typeof process.getuid === 'function' ? `-${process.getuid()}` : '';
8
- const dir = override || join(tmpdir(), `claude-statusblocks${uid}`);
8
+ const dir = override || join(tmpdir(), `agy-statusblocks${uid}`);
9
9
  if (create)
10
10
  mkdirSync(dir, { recursive: true });
11
11
  return dir;
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { join, dirname, basename, sep } from 'path';
4
4
  import { homedir } from 'os';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { render } from './layout.js';
7
- import { loadConfig } from './config.js';
7
+ import { loadConfig, normalizeBorderStyle } from './config.js';
8
8
  import { color, c } from './colors.js';
9
9
  import { isValidVersion, isNewerVersion } from './version.js';
10
10
  const PACKAGE_VERSION = (() => {
@@ -134,9 +134,26 @@ function runUninstall() {
134
134
  }
135
135
  console.log(color('✓ agy-statusblocks uninstalled and reverted to built-in status line', c.green));
136
136
  }
137
- function runPreview() {
137
+ function parseBorderFlag(argv) {
138
+ for (let i = 0; i < argv.length; i++) {
139
+ const arg = argv[i];
140
+ if (arg === '--border' || arg === '-b') {
141
+ const next = argv[i + 1];
142
+ if (next)
143
+ return normalizeBorderStyle(next);
144
+ }
145
+ else if (arg && arg.startsWith('--border=')) {
146
+ return normalizeBorderStyle(arg.slice('--border='.length));
147
+ }
148
+ }
149
+ return undefined;
150
+ }
151
+ function runPreview(borderOverride) {
138
152
  process.env['AGY_STATUSBLOCKS_NO_REMOTE'] = '1';
139
153
  const config = loadConfig();
154
+ if (borderOverride) {
155
+ config.border = borderOverride;
156
+ }
140
157
  const now = Date.now() / 1000;
141
158
  const mockData = {
142
159
  product: 'antigravity',
@@ -186,7 +203,8 @@ function runPreview() {
186
203
  level: 'medium',
187
204
  },
188
205
  };
189
- console.log(`\n${color('agy-statusblocks', c.bold, c.gemini)} preview\n`);
206
+ const styleLabel = config.border ?? 'rounded';
207
+ console.log(`\n${color('agy-statusblocks', c.bold, c.gemini)} preview ${color(`[border: ${styleLabel}]`, c.dim)}\n`);
190
208
  for (const width of [120, 80, 50]) {
191
209
  console.log(color('─'.repeat(width), c.gray));
192
210
  console.log(`${width} cols:`);
@@ -199,12 +217,12 @@ function showHelp() {
199
217
  ${color('agy-statusblocks', c.bold, c.gemini)} — block-based status line for Antigravity CLI (agy)
200
218
 
201
219
  ${color('Usage:', c.bold)}
202
- agy-statusblocks init Install into Antigravity CLI settings
203
- agy-statusblocks update Update installed files (--force to downgrade)
204
- agy-statusblocks preview Preview with mock data at various widths
205
- agy-statusblocks uninstall Revert to built-in statusline
206
- agy-statusblocks --version Print the installed package version
207
- agy-statusblocks help Show this help
220
+ agy-statusblocks init Install into Antigravity CLI settings
221
+ agy-statusblocks update Update installed files (--force to downgrade)
222
+ agy-statusblocks preview [--border <style>] Preview with mock data at various widths
223
+ agy-statusblocks uninstall Revert to built-in statusline
224
+ agy-statusblocks --version Print the installed package version
225
+ agy-statusblocks help Show this help
208
226
 
209
227
  ${color('Blocks:', c.bold)}
210
228
  context Context window fill bar, percentage, token counts
@@ -213,13 +231,22 @@ ${color('Blocks:', c.bold)}
213
231
  state Agent state pill and task/subagent/artifact counters
214
232
  git Branch, staged/modified counts, lines added/removed
215
233
 
234
+ ${color('Borders:', c.bold)}
235
+ rounded (default), square, double, thick, ascii, none
236
+
216
237
  ${color('Customize:', c.bold)}
217
- ~/.agy-statusblocks.json: { "segments": ["context", "model", "usage", "state", "git"] }
238
+ ~/.agy-statusblocks.json: { "segments": ["context", "model", "usage", "state", "git"], "border": "rounded" }
218
239
  Env vars: AGY_STATUSBLOCKS_SEGMENTS=context,model,usage
240
+ AGY_STATUSBLOCKS_BORDER=rounded|square|double|thick|ascii|none
241
+ CLI flags: --border, -b <style>
219
242
  `);
220
243
  }
221
- const cmd = process.argv[2];
222
- switch (cmd) {
244
+ const borderFlag = parseBorderFlag(process.argv.slice(2));
245
+ if (borderFlag) {
246
+ process.env['AGY_STATUSBLOCKS_BORDER'] = borderFlag;
247
+ }
248
+ const firstPositional = process.argv.slice(2).find(arg => !arg.startsWith('-'));
249
+ switch (firstPositional) {
223
250
  case 'init':
224
251
  runInit();
225
252
  break;
@@ -227,18 +254,26 @@ switch (cmd) {
227
254
  runUpdate(process.argv.includes('--force'));
228
255
  break;
229
256
  case 'preview':
230
- runPreview();
257
+ runPreview(borderFlag);
231
258
  break;
232
259
  case 'uninstall':
233
260
  runUninstall();
234
261
  break;
235
- case '--version':
236
- case '-v':
237
- console.log(PACKAGE_VERSION);
262
+ case undefined:
263
+ if (process.argv.includes('--version') || process.argv.includes('-v')) {
264
+ console.log(PACKAGE_VERSION);
265
+ }
266
+ else if (process.argv.includes('--help') || process.argv.includes('-h')) {
267
+ showHelp();
268
+ }
269
+ else if (process.stdin.isTTY) {
270
+ showHelp();
271
+ }
272
+ else {
273
+ import('./index.js');
274
+ }
238
275
  break;
239
276
  case 'help':
240
- case '--help':
241
- case '-h':
242
277
  showHelp();
243
278
  break;
244
279
  default:
package/dist/config.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- import type { StatusBlocksConfig } from './types.js';
1
+ import type { StatusBlocksConfig, BorderStyle } from './types.js';
2
+ export declare const VALID_BORDER_STYLES: readonly BorderStyle[];
3
+ export declare function normalizeBorderStyle(val: unknown): BorderStyle | undefined;
2
4
  export declare function loadConfig(): StatusBlocksConfig;
package/dist/config.js CHANGED
@@ -1,8 +1,26 @@
1
1
  import { readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir } from 'os';
4
+ export const VALID_BORDER_STYLES = [
5
+ 'rounded',
6
+ 'square',
7
+ 'double',
8
+ 'thick',
9
+ 'ascii',
10
+ 'none',
11
+ ];
12
+ export function normalizeBorderStyle(val) {
13
+ if (typeof val === 'string') {
14
+ const lower = val.trim().toLowerCase();
15
+ if (VALID_BORDER_STYLES.includes(lower)) {
16
+ return lower;
17
+ }
18
+ }
19
+ return undefined;
20
+ }
4
21
  export function loadConfig() {
5
22
  const envSegments = process.env['AGY_STATUSBLOCKS_SEGMENTS'];
23
+ const envBorder = process.env['AGY_STATUSBLOCKS_BORDER'];
6
24
  const config = {};
7
25
  try {
8
26
  const raw = readFileSync(join(homedir(), '.agy-statusblocks.json'), 'utf8');
@@ -14,10 +32,20 @@ export function loadConfig() {
14
32
  config.remoteUsage = parsed.remoteUsage;
15
33
  if (typeof parsed.stackWithDefault === 'boolean')
16
34
  config.stackWithDefault = parsed.stackWithDefault;
35
+ if (parsed.border) {
36
+ const border = normalizeBorderStyle(parsed.border);
37
+ if (border)
38
+ config.border = border;
39
+ }
17
40
  }
18
41
  }
19
42
  catch { /* no config file — use defaults */ }
20
43
  if (envSegments)
21
44
  config.segments = envSegments.split(',');
45
+ if (envBorder) {
46
+ const border = normalizeBorderStyle(envBorder);
47
+ if (border)
48
+ config.border = border;
49
+ }
22
50
  return config;
23
51
  }
package/dist/index.js CHANGED
@@ -129,6 +129,10 @@ function normalizeInput(raw) {
129
129
  };
130
130
  }
131
131
  let input = '';
132
+ if (process.stdin.isTTY) {
133
+ process.stdout.write('\n');
134
+ process.exit(0);
135
+ }
132
136
  process.stdin.setEncoding('utf8');
133
137
  process.stdin.on('data', (chunk) => { input += chunk; });
134
138
  process.stdin.on('end', () => {
package/dist/layout.d.ts CHANGED
@@ -1,3 +1,19 @@
1
- import type { AgyStatusData, StatusBlocksConfig } from './types.js';
1
+ import type { Block, AgyStatusData, StatusBlocksConfig, BorderStyle } from './types.js';
2
2
  export declare const DEFAULT_ORDER: string[];
3
+ export interface BorderGlyphs {
4
+ tl: string;
5
+ tr: string;
6
+ bl: string;
7
+ br: string;
8
+ h: string;
9
+ v: string;
10
+ }
11
+ export declare const BORDER_STYLES: Record<BorderStyle, BorderGlyphs>;
12
+ type RowGroup = {
13
+ blocks: Block[];
14
+ widths: number[];
15
+ };
16
+ export declare function boxify(lines: string[], innerW: number, style?: BorderStyle, title?: string): string[];
17
+ export declare function renderRow(group: RowGroup, style?: BorderStyle): string[];
3
18
  export declare function render(data: AgyStatusData, width: number, config?: StatusBlocksConfig): string;
19
+ export {};
package/dist/layout.js CHANGED
@@ -7,35 +7,62 @@ import { gitSegment } from './segments/git.js';
7
7
  const ALL_SEGMENTS = [contextSegment, modelSegment, usageSegment, stateSegment, gitSegment];
8
8
  export const DEFAULT_ORDER = ['context', 'model', 'usage', 'state', 'git'];
9
9
  const GAP = 1;
10
- const BOX_CHROME = 4; // ╭─ ... ─╮ has 2 border chars + 2 inner spaces
11
- function boxify(lines, innerW, title) {
10
+ export const BORDER_STYLES = {
11
+ rounded: { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' },
12
+ square: { tl: '┌', tr: '┐', bl: '└', br: '┘', h: '─', v: '│' },
13
+ double: { tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║' },
14
+ thick: { tl: '┏', tr: '┓', bl: '┗', br: '┛', h: '━', v: '┃' },
15
+ ascii: { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' },
16
+ none: { tl: '', tr: '', bl: '', br: '', h: '', v: '' },
17
+ };
18
+ export function boxify(lines, innerW, style = 'rounded', title) {
19
+ if (style === 'none') {
20
+ const boxed = [];
21
+ if (title) {
22
+ boxed.push(' ' + color(title, c.dim) + ' '.repeat(Math.max(0, innerW + 1 - title.length)));
23
+ }
24
+ for (const line of lines) {
25
+ boxed.push(' ' + padRight(line, innerW) + ' ');
26
+ }
27
+ return boxed;
28
+ }
29
+ const g = BORDER_STYLES[style] || BORDER_STYLES.rounded;
12
30
  let top;
13
31
  if (title) {
14
32
  const label = ` ${title} `;
15
33
  const remaining = Math.max(0, innerW + 2 - label.length - 1);
16
- top = color('╭─', c.gray) + color(label, c.dim) + color('─'.repeat(remaining) + '╮', c.gray);
34
+ top = color(g.tl + g.h, c.gray) + color(label, c.dim) + color(g.h.repeat(remaining) + g.tr, c.gray);
17
35
  }
18
36
  else {
19
- top = color('╭' + '─'.repeat(innerW + 2) + '╮', c.gray);
37
+ top = color(g.tl + g.h.repeat(innerW + 2) + g.tr, c.gray);
20
38
  }
21
- const bot = color('╰' + '─'.repeat(innerW + 2) + '╯', c.gray);
39
+ const bot = color(g.bl + g.h.repeat(innerW + 2) + g.br, c.gray);
22
40
  const boxed = [top];
23
41
  for (const line of lines) {
24
- boxed.push(color('│', c.gray) + ' ' + padRight(line, innerW) + ' ' + color('│', c.gray));
42
+ boxed.push(color(g.v, c.gray) + ' ' + padRight(line, innerW) + ' ' + color(g.v, c.gray));
25
43
  }
26
44
  boxed.push(bot);
27
45
  return boxed;
28
46
  }
29
- function renderRow(group) {
47
+ export function renderRow(group, style = 'rounded') {
30
48
  const { blocks, widths } = group;
31
- const boxes = blocks.map((b, i) => boxify(b.lines, widths[i], b.id));
32
- const boxWidths = widths.map(w => w + BOX_CHROME);
49
+ const chrome = style === 'none' ? 2 : 4;
50
+ const boxes = blocks.map((b, i) => boxify(b.lines, widths[i], style, b.id));
51
+ const boxWidths = widths.map(w => w + chrome);
52
+ const g = BORDER_STYLES[style] || BORDER_STYLES.rounded;
33
53
  const maxHeight = Math.max(...boxes.map(b => b.length));
34
54
  for (let i = 0; i < boxes.length; i++) {
35
55
  const box = boxes[i];
36
56
  while (box.length < maxHeight) {
37
- const blankRow = color('│', c.gray) + ' '.repeat(widths[i] + 2) + color('│', c.gray);
38
- box.splice(box.length - 1, 0, blankRow);
57
+ const blankRow = style === 'none'
58
+ ? ' '.repeat(widths[i] + 2)
59
+ : color(g.v, c.gray) + ' '.repeat(widths[i] + 2) + color(g.v, c.gray);
60
+ if (style === 'none') {
61
+ box.push(blankRow);
62
+ }
63
+ else {
64
+ box.splice(box.length - 1, 0, blankRow);
65
+ }
39
66
  }
40
67
  }
41
68
  const rows = [];
@@ -63,12 +90,12 @@ function decodeAssignment(combo, blockCount, targetRows, blockRow, rowUsed) {
63
90
  }
64
91
  return true;
65
92
  }
66
- function computeRowWidths(blockRow, blockCount, targetRows, blockWidths, rowWidths) {
93
+ function computeRowWidths(blockRow, blockCount, targetRows, blockWidths, rowWidths, chrome) {
67
94
  rowWidths.fill(0, 0, targetRows);
68
95
  const rowCounts = new Array(targetRows).fill(0);
69
96
  for (let b = 0; b < blockCount; b++) {
70
97
  const r = blockRow[b];
71
- rowWidths[r] = (rowWidths[r] ?? 0) + (blockWidths[b] ?? 0) + BOX_CHROME;
98
+ rowWidths[r] = (rowWidths[r] ?? 0) + (blockWidths[b] ?? 0) + chrome;
72
99
  rowCounts[r] = (rowCounts[r] ?? 0) + 1;
73
100
  }
74
101
  for (let r = 0; r < targetRows; r++) {
@@ -77,10 +104,34 @@ function computeRowWidths(blockRow, blockCount, targetRows, blockWidths, rowWidt
77
104
  }
78
105
  }
79
106
  }
80
- function solveLayout(blocks, maxWidth) {
107
+ function solveGreedy(blocks, maxWidth, chrome) {
108
+ const rows = [];
109
+ for (const b of blocks) {
110
+ const boxW = b.width + chrome;
111
+ let placed = false;
112
+ for (const r of rows) {
113
+ const addedW = r.blocks.length > 0 ? boxW + GAP : boxW;
114
+ if (r.currentW + addedW <= maxWidth) {
115
+ r.blocks.push(b);
116
+ r.widths.push(b.width);
117
+ r.currentW += addedW;
118
+ placed = true;
119
+ break;
120
+ }
121
+ }
122
+ if (!placed) {
123
+ rows.push({ blocks: [b], widths: [b.width], currentW: boxW });
124
+ }
125
+ }
126
+ rows.sort((a, b) => a.currentW - b.currentW);
127
+ return rows.map(r => ({ blocks: r.blocks, widths: r.widths }));
128
+ }
129
+ function solveLayout(blocks, maxWidth, chrome) {
81
130
  if (blocks.length === 0)
82
131
  return [];
83
132
  const blockCount = blocks.length;
133
+ if (blockCount > 6)
134
+ return solveGreedy(blocks, maxWidth, chrome);
84
135
  const blockWidths = blocks.map(b => b.width);
85
136
  for (let targetRows = 1; targetRows <= blockCount; targetRows++) {
86
137
  const totalCombos = Math.pow(targetRows, blockCount);
@@ -92,7 +143,7 @@ function solveLayout(blocks, maxWidth) {
92
143
  for (let combo = 0; combo < totalCombos; combo++) {
93
144
  if (!decodeAssignment(combo, blockCount, targetRows, blockRow, rowUsed))
94
145
  continue;
95
- computeRowWidths(blockRow, blockCount, targetRows, blockWidths, rowWidths);
146
+ computeRowWidths(blockRow, blockCount, targetRows, blockWidths, rowWidths, chrome);
96
147
  let fits = true;
97
148
  let maxRowW = 0;
98
149
  for (let r = 0; r < targetRows; r++) {
@@ -121,7 +172,7 @@ function solveLayout(blocks, maxWidth) {
121
172
  rWidths.push(blocks[b].width);
122
173
  }
123
174
  }
124
- const totalW = rWidths.reduce((a, w) => a + w + BOX_CHROME, 0) + (rBlocks.length - 1) * GAP;
175
+ const totalW = rWidths.reduce((a, w) => a + w + chrome, 0) + (rBlocks.length - 1) * GAP;
125
176
  rows.push({ blocks: rBlocks, widths: rWidths, totalW });
126
177
  }
127
178
  rows.sort((a, b) => a.totalW - b.totalW);
@@ -132,15 +183,17 @@ function solveLayout(blocks, maxWidth) {
132
183
  }
133
184
  export function render(data, width, config = {}) {
134
185
  const order = config.segments ?? DEFAULT_ORDER;
186
+ const borderStyle = config.border ?? 'rounded';
187
+ const chrome = borderStyle === 'none' ? 2 : 4;
135
188
  const segmentsMap = new Map(ALL_SEGMENTS.map(s => [s.id, s]));
136
189
  const activeSegments = order
137
190
  .map(id => segmentsMap.get(id))
138
191
  .filter((s) => !!s && s.enabled(data));
139
192
  const blocks = activeSegments.map(s => s.render(data, width));
140
- const groups = solveLayout(blocks, width);
193
+ const groups = solveLayout(blocks, width, chrome);
141
194
  const lines = [];
142
195
  for (const group of groups) {
143
- lines.push(...renderRow(group));
196
+ lines.push(...renderRow(group, borderStyle));
144
197
  }
145
198
  return lines.join('\n');
146
199
  }
@@ -1,10 +1,11 @@
1
1
  import { color, c, visibleLength, pctColor, renderBar } from '../colors.js';
2
2
  function formatTokens(n) {
3
- if (n >= 1000000)
4
- return `${(n / 1000000).toFixed(1)}M`;
5
- if (n >= 1000)
6
- return `${Math.round(n / 1000)}K`;
7
- return String(n);
3
+ const safe = Math.max(0, Math.round(n || 0));
4
+ if (safe >= 1000000)
5
+ return `${(safe / 1000000).toFixed(1)}M`;
6
+ if (safe >= 1000)
7
+ return `${Math.round(safe / 1000)}K`;
8
+ return String(safe);
8
9
  }
9
10
  export const contextSegment = {
10
11
  id: 'context',
@@ -31,12 +31,13 @@ function getGitInfo(cwd) {
31
31
  }
32
32
  let info = null;
33
33
  try {
34
- let branch = execSync('git branch --show-current', { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
34
+ const execOpts = { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'], timeout: 1500 };
35
+ let branch = execSync('git branch --show-current', execOpts).trim();
35
36
  if (!branch) {
36
- branch = '@' + execSync('git rev-parse --short HEAD', { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
37
+ branch = '@' + execSync('git rev-parse --short HEAD', execOpts).trim();
37
38
  }
38
- const stagedOut = execSync('git diff --cached --numstat', { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
39
- const modifiedOut = execSync('git diff --numstat', { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
39
+ const stagedOut = execSync('git diff --cached --numstat', execOpts).trim();
40
+ const modifiedOut = execSync('git diff --numstat', execOpts).trim();
40
41
  const staged = stagedOut ? stagedOut.split('\n').length : 0;
41
42
  const modified = modifiedOut ? modifiedOut.split('\n').length : 0;
42
43
  let added = 0, removed = 0;
@@ -16,7 +16,7 @@ function formatResetTime(epochSec) {
16
16
  return `${d}d${h > 0 ? `${h}h` : ''}${m > 0 ? `${m}m` : ''}`;
17
17
  if (h > 0)
18
18
  return `${h}h${m > 0 ? `${m}m` : ''}`;
19
- return `${m}m`;
19
+ return m > 0 ? `${m}m` : '<1m';
20
20
  }
21
21
  function clampPercent(v) {
22
22
  return Math.min(999, Math.max(0, Math.round(v ?? 0)));
@@ -178,12 +178,34 @@ export const usageSegment = {
178
178
  return { id: 'usage', priority: 25, width: 0, lines: [''] };
179
179
  }
180
180
  const BAR_WIDTH = 8;
181
- const rows = limits.slice(0, 2).map(l => {
182
- const bar = renderBar(l.percent, BAR_WIDTH);
183
- const pctStr = color(`${l.percent}%`, pctColor(l.percent), c.bold);
184
- const reset = l.resetsAt ? color(`↻ ${formatResetTime(l.resetsAt)}`, c.dim) : '';
185
- const lbl = color(l.label, c.dim);
186
- const parts = [bar, pctStr, reset, lbl].filter(Boolean);
181
+ const active = limits.slice(0, 2);
182
+ const formatted = active.map(l => {
183
+ const pctStr = `${l.percent}%`;
184
+ const resetTime = l.resetsAt ? formatResetTime(l.resetsAt) : '';
185
+ const resetRaw = resetTime ? `↻ ${resetTime}` : '';
186
+ return {
187
+ limit: l,
188
+ pctStr,
189
+ resetRaw,
190
+ label: l.label,
191
+ };
192
+ });
193
+ const maxPctLen = Math.max(...formatted.map(r => r.pctStr.length));
194
+ const hasAnyReset = formatted.some(r => r.resetRaw.length > 0);
195
+ const maxResetLen = hasAnyReset ? Math.max(...formatted.map(r => r.resetRaw.length)) : 0;
196
+ const maxLabelLen = Math.max(...formatted.map(r => r.label.length));
197
+ const rows = formatted.map(r => {
198
+ const bar = renderBar(r.limit.percent, BAR_WIDTH);
199
+ const paddedPct = ' '.repeat(maxPctLen - r.pctStr.length) + r.pctStr;
200
+ const pctColorStr = color(paddedPct, pctColor(r.limit.percent), c.bold);
201
+ let resetColorStr = '';
202
+ if (hasAnyReset && r.resetRaw) {
203
+ const paddedReset = r.resetRaw + ' '.repeat(maxResetLen - r.resetRaw.length);
204
+ resetColorStr = color(paddedReset, c.dim);
205
+ }
206
+ const paddedLabel = ' '.repeat(maxLabelLen - r.label.length) + r.label;
207
+ const lbl = color(paddedLabel, c.dim);
208
+ const parts = [bar, pctColorStr, resetColorStr, lbl].filter(Boolean);
187
209
  return parts.join(color(' · ', c.dim));
188
210
  });
189
211
  const width = Math.max(...rows.map(visibleLength));
package/dist/types.d.ts CHANGED
@@ -76,8 +76,10 @@ export interface Segment {
76
76
  render(data: AgyStatusData, allocWidth: number): Block;
77
77
  enabled(data: AgyStatusData): boolean;
78
78
  }
79
+ export type BorderStyle = 'rounded' | 'square' | 'double' | 'thick' | 'ascii' | 'none';
79
80
  export interface StatusBlocksConfig {
80
81
  segments?: string[];
81
82
  stackWithDefault?: boolean;
82
83
  remoteUsage?: boolean;
84
+ border?: BorderStyle;
83
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agy-statusblocks",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Opinionated, adaptive block-based status line for Antigravity CLI (agy) with bin-packing layout, rate limit tracking, and live agent telemetry",
5
5
  "type": "module",
6
6
  "bin": {