gitroots 0.6.0 → 0.6.1

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
@@ -4,7 +4,7 @@ gitroots in your terminal, as a session like Claude Code. Run `gitroots` and you
4
4
 
5
5
  ```
6
6
  ╭──────────────────────────────────────────────────────────────╮
7
- │ ██ ██ ██ ██ gitroots v0.6.0 │
7
+ │ ██ ██ ██ ██ gitroots v0.6.1 │
8
8
  │ ██ ██ ██ ██ code governance and agent sessions │
9
9
  │ ██ ██ ██ ██ Shawki Aladdin · Acme ERP (owner) · gitroots.com│
10
10
  │ ██ ██ ██ ██ ~/work/erp · acme/erp @ main │
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitroots",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "gitroots CLI: code governance and agent sessions.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/runtime.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
 
8
- export const VERSION = '0.6.0';
8
+ export const VERSION = '0.6.1';
9
9
 
10
10
  // Default download location for install.sh / gitroots update. Override with GITROOTS_DOWNLOAD_BASE.
11
11
  export const DOWNLOAD_BASE = process.env.GITROOTS_DOWNLOAD_BASE || 'https://gitroots.com/cli';
@@ -1,40 +1,107 @@
1
- // The gitroots mark (4×4 green grid) drawn with block characters, so it shows in every terminal.
1
+ // The gitroots logo as terminal pixel art: the 4×4 mark and the "gitroots" wordmark.
2
+ // One pixel is one column wide and half a row tall (▀ ▄ █ with fg/bg colors), so pixels are about square.
2
3
  import { state as colorState, c, width, fit } from '../ui/ansi.js';
3
4
 
4
- // Cell colors sampled from assets/logo.png, light lime (top left) to deep green (bottom right).
5
- const GRID = [
6
- ['#B8DB4A', '#9CCF3A', '#5BB33A', '#22954E'],
7
- ['#7DC035', '#63B535', '#258C35', '#0A743F'],
8
- ['#1F8932', '#11713F', '#0A6435', '#0A4F33'],
9
- ['#0A5835', '#0B4F27', '#0A3A27', '#0A3328'],
5
+ // Colors sampled from assets/logo.png.
6
+ const CELLS = [
7
+ ['#B3DA44', '#97D039', '#59B238', '#20944D'],
8
+ ['#7AC135', '#64B532', '#238B32', '#07753E'],
9
+ ['#1D872F', '#12743D', '#096736', '#09512E'],
10
+ ['#075833', '#0A5229', '#0A3E28', '#0A3929'],
10
11
  ];
11
- const FALLBACK = [[149, 112, 70, 29], [106, 70, 28, 23], [28, 23, 22, 22], [22, 22, 22, 22]];
12
+ const WORD = '#E3EDFC';
12
13
 
13
- function cell(hex, r, col) {
14
- if (!colorState.color) return '██';
15
- const [R, G, B] = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
16
- const code = colorState.truecolor ? `\x1b[38;2;${R};${G};${B}m` : `\x1b[38;5;${FALLBACK[r][col]}m`;
17
- return `${code}██\x1b[39m`;
14
+ // 8 px tall glyphs: ascenders in rows 0-1, x-height 2-5, descender 6-7.
15
+ const GLYPHS = {
16
+ g: ['....', '....', '.###', '#..#', '#..#', '.###', '...#', '.##.'],
17
+ i: ['#', '.', '#', '#', '#', '#', '.', '.'],
18
+ t: ['.#.', '.#.', '###', '.#.', '.#.', '.##', '...', '...'],
19
+ r: ['...', '...', '#.#', '##.', '#..', '#..', '...', '...'],
20
+ o: ['....', '....', '.##.', '#..#', '#..#', '.##.', '....', '....'],
21
+ s: ['....', '....', '.###', '#...', '..##', '###.', '....', '....'],
22
+ };
23
+
24
+ const CELL = 2;
25
+ const GAP = 1;
26
+ const MARK = 4 * CELL + 3 * GAP; // 11 px
27
+ const SPACE = 4;
28
+
29
+ function canvas() {
30
+ const word = [...'gitroots'].reduce((rows, ch, i) => rows.map((r, y) => r + (i ? '.' : '') + GLYPHS[ch][y]), Array(8).fill(''));
31
+ let H = Math.max(MARK, 8);
32
+ if (H % 2) H += 1;
33
+ const W = MARK + SPACE + word[0].length;
34
+ const px = Array.from({ length: H }, () => Array(W).fill(null));
35
+ for (let r = 0; r < 4; r++) {
36
+ for (let col = 0; col < 4; col++) {
37
+ for (let y = 0; y < CELL; y++) for (let x = 0; x < CELL; x++) px[r * (CELL + GAP) + y][col * (CELL + GAP) + x] = CELLS[r][col];
38
+ }
39
+ }
40
+ const oy = Math.floor((H - 8) / 2);
41
+ word.forEach((row, y) => [...row].forEach((ch, x) => { if (ch === '#') px[oy + y][MARK + SPACE + x] = WORD; }));
42
+ return { px, W, H };
43
+ }
44
+
45
+ const rgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
46
+
47
+ // Nearest xterm-256 color for terminals without truecolor.
48
+ function x256([r, g, b]) {
49
+ const q = (v) => (v < 48 ? 0 : v < 115 ? 1 : Math.floor((v - 35) / 40));
50
+ const lv = [0, 95, 135, 175, 215, 255];
51
+ const [qr, qg, qb] = [q(r), q(g), q(b)];
52
+ const cube = 16 + 36 * qr + 6 * qg + qb;
53
+ const dc = (lv[qr] - r) ** 2 + (lv[qg] - g) ** 2 + (lv[qb] - b) ** 2;
54
+ const gray = Math.max(0, Math.min(23, Math.round(((r + g + b) / 3 - 8) / 10)));
55
+ const gv = 8 + gray * 10;
56
+ const dg = (gv - r) ** 2 + (gv - g) ** 2 + (gv - b) ** 2;
57
+ return dg < dc ? 232 + gray : cube;
18
58
  }
19
59
 
20
- // Four rows, 11 columns: "██ ██ ██ ██"
21
- export function logoRows() {
22
- return GRID.map((row, r) => row.map((hex, col) => cell(hex, r, col)).join(' '));
60
+ const fg = (hex) => (colorState.truecolor ? `\x1b[38;2;${rgb(hex).join(';')}m` : `\x1b[38;5;${x256(rgb(hex))}m`);
61
+ const bg = (hex) => (colorState.truecolor ? `\x1b[48;2;${rgb(hex).join(';')}m` : `\x1b[48;5;${x256(rgb(hex))}m`);
62
+
63
+ let cached = null;
64
+ /** The logo as terminal lines (6 rows × 48 columns). */
65
+ export function logoLines() {
66
+ if (cached && cached.color === colorState.color && cached.tc === colorState.truecolor) return cached.lines;
67
+ const { px, W, H } = canvas();
68
+ const lines = [];
69
+ for (let y = 0; y < H; y += 2) {
70
+ let s = '';
71
+ for (let x = 0; x < W; x++) {
72
+ const top = px[y][x];
73
+ const bot = px[y + 1][x];
74
+ if (!colorState.color) { s += top && bot ? '█' : top ? '▀' : bot ? '▄' : ' '; continue; }
75
+ if (!top && !bot) s += ' ';
76
+ else if (top && bot && top === bot) s += `${fg(top)}█\x1b[39m`;
77
+ else if (top && bot) s += `${fg(top)}${bg(bot)}▀\x1b[39;49m`;
78
+ else if (top) s += `${fg(top)}▀\x1b[39m`;
79
+ else s += `${fg(bot)}▄\x1b[39m`;
80
+ }
81
+ lines.push(s);
82
+ }
83
+ cached = { color: colorState.color, tc: colorState.truecolor, lines };
84
+ return lines;
23
85
  }
24
86
 
25
- // Claude Code-style welcome box: logo on the left, lines of text on the right.
26
- export function welcomeBox(lines, cols) {
27
- const inner = Math.max(40, Math.min(cols - 2, 100));
28
- const logo = logoRows();
29
- const rows = Math.max(logo.length, lines.length);
30
- const out = [];
87
+ /** Welcome box: the logo with the info lines beside it (or under it in narrow terminals). */
88
+ export function welcomeBox(info, cols) {
89
+ const logo = logoLines();
90
+ const logoW = width(logo[0]);
91
+ const infoW = Math.max(...info.map((l) => width(l)));
92
+ const side = cols >= logoW + infoW + 12;
93
+ const inner = Math.max(logoW + 4, Math.min(cols - 2, side ? logoW + infoW + 9 : Math.max(logoW, infoW) + 6));
31
94
  const b = (s) => c.g(s);
32
- out.push(b(`╭${'─'.repeat(inner)}╮`));
33
- for (let i = 0; i < rows; i++) {
34
- const left = logo[i] || ' '.repeat(11);
35
- const right = lines[i] || '';
36
- out.push(`${b('│')} ${left} ${fit(right, inner - 16)}${' '.repeat(Math.max(0, inner - 16 - width(fit(right, inner - 16))))} ${b('│')}`);
95
+ const row = (s) => `${b('│')} ${fit(s, inner - 4)}${' '.repeat(Math.max(0, inner - 4 - width(fit(s, inner - 4))))} ${b('│')}`;
96
+ const out = [b(`╭${'─'.repeat(inner)}╮`), row('')];
97
+ if (side) {
98
+ const off = Math.max(0, Math.floor((logo.length - info.length) / 2));
99
+ for (let i = 0; i < logo.length; i++) out.push(row(`${logo[i]} ${info[i - off] || ''}`));
100
+ } else {
101
+ for (const l of logo) out.push(row(l));
102
+ out.push(row(''));
103
+ for (const l of info) out.push(row(l));
37
104
  }
38
- out.push(b(`╰${'─'.repeat(inner)}╯`));
105
+ out.push(row(''), b(`╰${'─'.repeat(inner)}╯`));
39
106
  return out;
40
107
  }
@@ -43,8 +43,8 @@ function header(ctx) {
43
43
  ? `${c.b(cfg.user?.name || cfg.username)} ${c.m('·')} ${cfg.org ? `${cfg.org.name} ${c.m(`(${cfg.org.role})`)}` : c.w('no workspace · /workspace')} ${c.m(`· ${ctx.api.host}`)}`
44
44
  : `${c.w('Not signed in')} ${c.m('· /login to connect your gitroots account')}`;
45
45
  return welcomeBox([
46
- `${c.b('gitroots')} ${c.m(`v${VERSION}`)}`,
47
- c.m('code governance and agent sessions'),
46
+ `${c.s(`v${VERSION}`)} ${c.m('· code governance and agent sessions')}`,
47
+ '',
48
48
  who,
49
49
  c.m(`${shortPath(process.cwd())}${repo ? ` · ${repo}${branch ? ` @ ${branch}` : ''}` : ''}`),
50
50
  ], cols);