claude-code-hud 0.3.10 → 0.3.12
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/package.json +1 -1
- package/scripts/lib/git-info.mjs +18 -15
- package/tui/hud.tsx +21 -4
package/package.json
CHANGED
package/scripts/lib/git-info.mjs
CHANGED
|
@@ -1,39 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Git status via child_process
|
|
2
|
+
* Git status via child_process exec (async — non-blocking).
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
|
|
9
|
+
async function run(cmd, cwd) {
|
|
7
10
|
try {
|
|
8
|
-
|
|
11
|
+
const { stdout } = await execAsync(cmd, { cwd, timeout: 3000 });
|
|
12
|
+
return stdout.trim();
|
|
9
13
|
} catch {
|
|
10
14
|
return '';
|
|
11
15
|
}
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
export function readGitInfo(cwd = process.cwd()) {
|
|
15
|
-
const branch = run('git rev-parse --abbrev-ref HEAD', cwd) || 'unknown';
|
|
18
|
+
export async function readGitInfo(cwd = process.cwd()) {
|
|
19
|
+
const branch = await run('git rev-parse --abbrev-ref HEAD', cwd) || 'unknown';
|
|
16
20
|
if (branch === 'unknown' || branch === 'HEAD') {
|
|
17
21
|
return { isRepo: false, branch: 'unknown', ahead: 0, behind: 0, modified: [], added: [], deleted: [], recentCommits: [], totalChanges: 0 };
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
const [aheadBehind, statusOut, logOut, numstatOut] = await Promise.all([
|
|
25
|
+
run('git rev-list --left-right --count @{upstream}...HEAD 2>/dev/null || echo "0\t0"', cwd),
|
|
26
|
+
run('git status --porcelain', cwd),
|
|
27
|
+
run('git log --oneline -5 --format="%h|%s|%cr"', cwd),
|
|
28
|
+
run('git diff --numstat HEAD 2>/dev/null', cwd),
|
|
29
|
+
]);
|
|
30
|
+
|
|
22
31
|
const [behind = 0, ahead = 0] = aheadBehind.split('\t').map(Number);
|
|
23
32
|
|
|
24
|
-
// status
|
|
25
|
-
const statusOut = run('git status --porcelain', cwd);
|
|
26
33
|
const modified = [], added = [], deleted = [];
|
|
27
34
|
for (const line of statusOut.split('\n').filter(Boolean)) {
|
|
28
35
|
const st = line.slice(0, 2).trim();
|
|
29
36
|
const file = line.slice(2).trimStart();
|
|
30
37
|
if (st === 'M' || st === 'MM' || st === 'AM') modified.push(file);
|
|
31
|
-
else if (st === 'A' || st === '??'
|
|
38
|
+
else if (st === 'A' || st === '??') added.push(file);
|
|
32
39
|
else if (st === 'D') deleted.push(file);
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
// recent commits
|
|
36
|
-
const logOut = run('git log --oneline -5 --format="%h|%s|%cr"', cwd);
|
|
37
42
|
const recentCommits = logOut.split('\n').filter(Boolean).map(l => {
|
|
38
43
|
const [hash, ...rest] = l.split('|');
|
|
39
44
|
const time = rest.pop();
|
|
@@ -41,8 +46,6 @@ export function readGitInfo(cwd = process.cwd()) {
|
|
|
41
46
|
return { hash, msg, time };
|
|
42
47
|
});
|
|
43
48
|
|
|
44
|
-
// diff stats: actual +/- line counts per file
|
|
45
|
-
const numstatOut = run('git diff --numstat HEAD 2>/dev/null', cwd);
|
|
46
49
|
const diffStats = {};
|
|
47
50
|
for (const line of numstatOut.split('\n').filter(Boolean)) {
|
|
48
51
|
const [addStr, delStr, ...fileParts] = line.split('\t');
|
package/tui/hud.tsx
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* HUD Live — Ink TUI
|
|
4
4
|
* Run: npm run hud (from hud-plugin root)
|
|
5
5
|
*/
|
|
6
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
6
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
7
7
|
import { render, Box, Text, useStdout, useInput } from 'ink';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { dirname, join, basename } from 'path';
|
|
@@ -601,6 +601,13 @@ function ProjectTab({ info, treeCursor, treeExpanded, selectedFile, fileLines, f
|
|
|
601
601
|
|
|
602
602
|
// ── Tab 3: GIT ─────────────────────────────────────────────────────────────
|
|
603
603
|
function GitTab({ git, C, termWidth, branchMode, branchList, branchCursor }: any) {
|
|
604
|
+
if (!git.isRepo) return (
|
|
605
|
+
<Box flexDirection="column" borderStyle="single" borderColor={C.border} paddingX={1}>
|
|
606
|
+
<Text color={C.dimmer}>⚠ git repository not found in this directory</Text>
|
|
607
|
+
<Text color={C.dimmer}> cd into a git repo to see branch, diff, and commit info</Text>
|
|
608
|
+
</Box>
|
|
609
|
+
);
|
|
610
|
+
|
|
604
611
|
const gitFiles = [
|
|
605
612
|
...(git.modified ?? []).map((f: string) => ({ status: 'MOD', path: f })),
|
|
606
613
|
...(git.added ?? []).map((f: string) => ({ status: 'ADD', path: f })),
|
|
@@ -746,7 +753,7 @@ function App() {
|
|
|
746
753
|
|
|
747
754
|
const [usage, setUsage] = useState<any>(readTokenUsage(cwd));
|
|
748
755
|
const [history, setHistory] = useState<any>(readTokenHistory(cwd));
|
|
749
|
-
const [git, setGit] = useState<any>(
|
|
756
|
+
const [git, setGit] = useState<any>({ isRepo: false, branch: 'loading…', modified: [], added: [], deleted: [], recentCommits: [], totalChanges: 0 });
|
|
750
757
|
const [project, setProject] = useState<ProjectInfo | null>(null);
|
|
751
758
|
const [rateLimits, setRateLimits] = useState<any>(getUsageSync());
|
|
752
759
|
|
|
@@ -770,6 +777,9 @@ function App() {
|
|
|
770
777
|
const [spinFrame, setSpinFrame] = useState(0);
|
|
771
778
|
const SPIN = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
772
779
|
|
|
780
|
+
// q key debounce ref (require 2 presses within 600ms to quit)
|
|
781
|
+
const lastQRef = useRef(0);
|
|
782
|
+
|
|
773
783
|
// Branch switcher state
|
|
774
784
|
const [branchMode, setBranchMode] = useState(false);
|
|
775
785
|
const [branchList, setBranchList] = useState<string[]>([]);
|
|
@@ -783,8 +793,8 @@ function App() {
|
|
|
783
793
|
const refresh = useCallback(() => {
|
|
784
794
|
setUsage(readTokenUsage(cwd));
|
|
785
795
|
setHistory(readTokenHistory(cwd));
|
|
786
|
-
setGit(readGitInfo(cwd));
|
|
787
796
|
setUpdatedAt(Date.now());
|
|
797
|
+
readGitInfo(cwd).then(setGit).catch(() => {});
|
|
788
798
|
getUsage().then(setRateLimits).catch(() => {});
|
|
789
799
|
readSessionTimeline(cwd).then(entries => {
|
|
790
800
|
setTimeline(entries);
|
|
@@ -802,6 +812,8 @@ function App() {
|
|
|
802
812
|
.catch(() => { setLoading(false); });
|
|
803
813
|
// Full deep scan in background → update silently
|
|
804
814
|
scanProject(cwd, 8).then(p => { setProject(p); }).catch(() => {});
|
|
815
|
+
// Initial git load (async)
|
|
816
|
+
readGitInfo(cwd).then(setGit).catch(() => {});
|
|
805
817
|
// Initial API usage fetch
|
|
806
818
|
getUsage().then(setRateLimits).catch(() => {});
|
|
807
819
|
// Initial timeline load
|
|
@@ -897,7 +909,12 @@ function App() {
|
|
|
897
909
|
return;
|
|
898
910
|
}
|
|
899
911
|
|
|
900
|
-
if (input === 'q' || input === 'ㅂ')
|
|
912
|
+
if (input === 'q' || input === 'ㅂ') {
|
|
913
|
+
const now = Date.now();
|
|
914
|
+
if (now - lastQRef.current < 600) { process.exit(0); }
|
|
915
|
+
lastQRef.current = now;
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
901
918
|
|
|
902
919
|
// Escape: close file viewer first, then quit
|
|
903
920
|
if (key.escape) {
|