Skip to content

Commit 035436b

Browse files
feat(sessions): show git repo info in session cards (#70)
When a session's working directory is inside a git repo, the session card now shows the branch name, hosting provider, repo name, and working tree status (staged, modified, untracked, ahead/behind). Live cwd detection updates the git info when the user cd's to a different directory. Includes unit and integration tests covering all providers and cross-platform cwd detection. Closes #69. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 22a3115 commit 035436b

15 files changed

Lines changed: 1934 additions & 1122 deletions

docs/api.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,35 @@ List all active sessions.
6363
"clients": 1,
6464
"createdAt": "2025-01-01T00:00:00.000Z",
6565
"color": "#4a9eff",
66-
"lastActivity": 1719849600000
66+
"lastActivity": 1719849600000,
67+
"git": {
68+
"branch": "main",
69+
"repoName": "owner/repo",
70+
"provider": "GitHub",
71+
"status": {
72+
"clean": false,
73+
"modified": 1,
74+
"staged": 0,
75+
"untracked": 2,
76+
"ahead": 3,
77+
"behind": 0,
78+
"summary": "1 modified, 2 untracked, 3↑"
79+
}
80+
}
6781
}
6882
]
6983
```
7084

71-
| Field | Type | Description |
72-
| -------------- | ------ | ------------------------------------------ |
73-
| `color` | string | Hex color assigned to the session |
74-
| `lastActivity` | number | Unix timestamp (ms) of the last PTY output |
85+
| Field | Type | Description |
86+
| -------------- | ----------- | ------------------------------------------------------------------------------------------------------------ |
87+
| `color` | string | Hex color assigned to the session |
88+
| `lastActivity` | number | Unix timestamp (ms) of the last PTY output |
89+
| `cwd` | string | Live working directory of the shell process (updates when the user `cd`s) |
90+
| `git` | object/null | Git repository info for the session's cwd, or `null` if not in a git repo |
91+
| `git.branch` | string | Current branch name (or short SHA in detached HEAD) |
92+
| `git.repoName` | string/null | Remote repository name (e.g. `owner/repo`) |
93+
| `git.provider` | string/null | Hosting provider: `GitHub`, `GitLab`, `Bitbucket`, `Azure DevOps`, or host |
94+
| `git.status` | object | Working tree status with `clean`, `modified`, `staged`, `untracked`, `ahead`, `behind`, and `summary` fields |
7595

7696
#### `POST /api/sessions`
7797

docs/architecture.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ termbeam/
1414
│ ├── sessions.js # PTY session management
1515
│ ├── routes.js # Express HTTP routes
1616
│ ├── websocket.js # WebSocket connection handling
17+
│ ├── git.js # Git repo detection & status
1718
│ ├── tunnel.js # DevTunnel integration
1819
│ ├── preview.js # Port preview reverse proxy
1920
│ ├── service.js # PM2 service management
@@ -60,7 +61,11 @@ Factory function `createAuth(password)` returns an object with middleware, token
6061

6162
### `sessions.js` — Session Manager
6263

63-
`SessionManager` class wraps the PTY lifecycle. Handles spawning, tracking, listing, updating, and cleaning up terminal sessions. Each session has an auto-assigned color, tracks `lastActivity` timestamps, a `createdAt` timestamp, and supports live updates via the `update()` method. Sessions maintain a scrollback buffer (capped at 200 KB) that is sent to newly connecting clients, and track a `clients` Set of active WebSocket connections. Supports an optional `initialCommand` that is written to the PTY shortly after spawn.
64+
`SessionManager` class wraps the PTY lifecycle. Handles spawning, tracking, listing, updating, and cleaning up terminal sessions. Each session has an auto-assigned color, tracks `lastActivity` timestamps, a `createdAt` timestamp, and supports live updates via the `update()` method. Sessions maintain a scrollback buffer (capped at 200 KB) that is sent to newly connecting clients, and track a `clients` Set of active WebSocket connections. Supports an optional `initialCommand` that is written to the PTY shortly after spawn. The `list()` method detects the live working directory of the shell process (via `lsof` on macOS, `/proc` on Linux) and enriches each session with git repository information, using an async cache to avoid blocking the event loop.
65+
66+
### `git.js` — Git Repository Detection
67+
68+
Detects git repository information for a given directory. Provides `getGitInfo(cwd)` which returns branch name, remote provider (GitHub, GitLab, Bitbucket, Azure DevOps), repository name, and working tree status (staged, modified, untracked counts plus ahead/behind tracking). Also exports `parseRemoteUrl()` and `parseStatus()` for URL parsing and status summarization. All git commands use a 3-second timeout to avoid hanging.
6469

6570
### `routes.js` — HTTP Routes
6671

public/css/themes.css

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/* TermBeam shared theme variables — imported by index.html and terminal.html */
2+
3+
:root {
4+
--bg: #1e1e1e;
5+
--surface: #252526;
6+
--border: #3c3c3c;
7+
--border-subtle: #474747;
8+
--text: #d4d4d4;
9+
--text-secondary: #858585;
10+
--text-dim: #6e6e6e;
11+
--text-muted: #555555;
12+
--accent: #0078d4;
13+
--accent-hover: #1a8ae8;
14+
--accent-active: #005a9e;
15+
--danger: #f14c4c;
16+
--danger-hover: #d73a3a;
17+
--success: #89d185;
18+
--overlay-bg: rgba(0, 0, 0, 0.85);
19+
}
20+
21+
[data-theme='light'] {
22+
--bg: #ffffff;
23+
--surface: #f3f3f3;
24+
--border: #e0e0e0;
25+
--border-subtle: #d0d0d0;
26+
--text: #1e1e1e;
27+
--text-secondary: #616161;
28+
--text-dim: #767676;
29+
--text-muted: #a0a0a0;
30+
--accent: #0078d4;
31+
--accent-hover: #106ebe;
32+
--accent-active: #005a9e;
33+
--danger: #e51400;
34+
--danger-hover: #c20000;
35+
--success: #16825d;
36+
--overlay-bg: rgba(0, 0, 0, 0.5);
37+
}
38+
39+
[data-theme='monokai'] {
40+
--bg: #272822;
41+
--surface: #1e1f1c;
42+
--border: #49483e;
43+
--border-subtle: #5c5c4f;
44+
--text: #f8f8f2;
45+
--text-secondary: #a59f85;
46+
--text-dim: #75715e;
47+
--text-muted: #5a5854;
48+
--accent: #a6e22e;
49+
--accent-hover: #b8f53c;
50+
--accent-active: #8acc16;
51+
--danger: #f92672;
52+
--danger-hover: #e0155d;
53+
--success: #a6e22e;
54+
--overlay-bg: rgba(0, 0, 0, 0.75);
55+
}
56+
57+
[data-theme='solarized-dark'] {
58+
--bg: #002b36;
59+
--surface: #073642;
60+
--border: #586e75;
61+
--border-subtle: #657b83;
62+
--text: #839496;
63+
--text-secondary: #657b83;
64+
--text-dim: #586e75;
65+
--text-muted: #4a5a62;
66+
--accent: #268bd2;
67+
--accent-hover: #379ce3;
68+
--accent-active: #1a7abf;
69+
--danger: #dc322f;
70+
--danger-hover: #c8221f;
71+
--success: #859900;
72+
--overlay-bg: rgba(0, 0, 0, 0.75);
73+
}
74+
75+
[data-theme='solarized-light'] {
76+
--bg: #fdf6e3;
77+
--surface: #eee8d5;
78+
--border: #93a1a1;
79+
--border-subtle: #839496;
80+
--text: #657b83;
81+
--text-secondary: #93a1a1;
82+
--text-dim: #a0a0a0;
83+
--text-muted: #b0b0b0;
84+
--accent: #268bd2;
85+
--accent-hover: #379ce3;
86+
--accent-active: #1a7abf;
87+
--danger: #dc322f;
88+
--danger-hover: #c8221f;
89+
--success: #859900;
90+
--overlay-bg: rgba(0, 0, 0, 0.4);
91+
}
92+
93+
[data-theme='nord'] {
94+
--bg: #2e3440;
95+
--surface: #3b4252;
96+
--border: #434c5e;
97+
--border-subtle: #4c566a;
98+
--text: #d8dee9;
99+
--text-secondary: #b0bac9;
100+
--text-dim: #7b88a1;
101+
--text-muted: #5c6a85;
102+
--accent: #88c0d0;
103+
--accent-hover: #9fd4e4;
104+
--accent-active: #6aafbf;
105+
--danger: #bf616a;
106+
--danger-hover: #a84d57;
107+
--success: #a3be8c;
108+
--overlay-bg: rgba(0, 0, 0, 0.75);
109+
}
110+
111+
[data-theme='dracula'] {
112+
--bg: #282a36;
113+
--surface: #343746;
114+
--border: #44475a;
115+
--border-subtle: #525568;
116+
--text: #f8f8f2;
117+
--text-secondary: #c1c4d2;
118+
--text-dim: #8e92a4;
119+
--text-muted: #6272a4;
120+
--accent: #bd93f9;
121+
--accent-hover: #d0b0ff;
122+
--accent-active: #a77de7;
123+
--danger: #ff5555;
124+
--danger-hover: #e03d3d;
125+
--success: #50fa7b;
126+
--overlay-bg: rgba(0, 0, 0, 0.75);
127+
}
128+
129+
[data-theme='github-dark'] {
130+
--bg: #0d1117;
131+
--surface: #161b22;
132+
--border: #30363d;
133+
--border-subtle: #3d444d;
134+
--text: #c9d1d9;
135+
--text-secondary: #8b949e;
136+
--text-dim: #6e7681;
137+
--text-muted: #484f58;
138+
--accent: #58a6ff;
139+
--accent-hover: #79b8ff;
140+
--accent-active: #388bfd;
141+
--danger: #f85149;
142+
--danger-hover: #da3633;
143+
--success: #3fb950;
144+
--overlay-bg: rgba(0, 0, 0, 0.75);
145+
}
146+
147+
[data-theme='one-dark'] {
148+
--bg: #282c34;
149+
--surface: #21252b;
150+
--border: #3e4452;
151+
--border-subtle: #4b5263;
152+
--text: #abb2bf;
153+
--text-secondary: #7f848e;
154+
--text-dim: #5c6370;
155+
--text-muted: #4b5263;
156+
--accent: #61afef;
157+
--accent-hover: #7dc0ff;
158+
--accent-active: #4d9ede;
159+
--danger: #e06c75;
160+
--danger-hover: #c95c67;
161+
--success: #98c379;
162+
--overlay-bg: rgba(0, 0, 0, 0.75);
163+
}
164+
165+
[data-theme='catppuccin'] {
166+
--bg: #1e1e2e;
167+
--surface: #313244;
168+
--border: #45475a;
169+
--border-subtle: #585b70;
170+
--text: #cdd6f4;
171+
--text-secondary: #a6adc8;
172+
--text-dim: #7f849c;
173+
--text-muted: #585b70;
174+
--accent: #89b4fa;
175+
--accent-hover: #b4d0ff;
176+
--accent-active: #5c9de3;
177+
--danger: #f38ba8;
178+
--danger-hover: #eb7c9d;
179+
--success: #a6e3a1;
180+
--overlay-bg: rgba(0, 0, 0, 0.75);
181+
}
182+
183+
[data-theme='gruvbox'] {
184+
--bg: #282828;
185+
--surface: #3c3836;
186+
--border: #504945;
187+
--border-subtle: #665c54;
188+
--text: #ebdbb2;
189+
--text-secondary: #d5c4a1;
190+
--text-dim: #a89984;
191+
--text-muted: #7c6f64;
192+
--accent: #83a598;
193+
--accent-hover: #9dbfb4;
194+
--accent-active: #6a8f8a;
195+
--danger: #fb4934;
196+
--danger-hover: #e33826;
197+
--success: #b8bb26;
198+
--overlay-bg: rgba(0, 0, 0, 0.75);
199+
}
200+
201+
[data-theme='night-owl'] {
202+
--bg: #011627;
203+
--surface: #0d2a45;
204+
--border: #1d3b53;
205+
--border-subtle: #264863;
206+
--text: #d6deeb;
207+
--text-secondary: #8badc1;
208+
--text-dim: #5f7e97;
209+
--text-muted: #3f5f7d;
210+
--accent: #7fdbca;
211+
--accent-hover: #9ff0e0;
212+
--accent-active: #62c5b5;
213+
--danger: #ef5350;
214+
--danger-hover: #d83130;
215+
--success: #addb67;
216+
--overlay-bg: rgba(0, 0, 0, 0.75);
217+
}

0 commit comments

Comments
 (0)