Skip to content

Commit b628855

Browse files
committed
fixes
1 parent 8dda8e3 commit b628855

8 files changed

Lines changed: 164 additions & 25 deletions

File tree

cmd/push-validator/cmd_snapshot.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,12 @@ Examples:
5151
noCache, _ := cmd.Flags().GetBool("no-cache")
5252

5353
if flagOutput != "json" {
54-
fmt.Printf(" Source: %s\n", snapshotURL)
55-
fmt.Printf(" Cache: %s/%s\n", cfg.HomeDir, snapshot.CacheDir)
54+
dim, reset := "\033[2m", "\033[0m"
55+
if os.Getenv("NO_COLOR") != "" {
56+
dim, reset = "", ""
57+
}
58+
fmt.Printf(" %s%-12s %s%s\n", dim, "Source:", ui.ShortenPath(snapshotURL), reset)
59+
fmt.Printf(" %s%-12s %s%s\n", dim, "Cache:", ui.ShortenPath(cfg.HomeDir+"/"+snapshot.CacheDir), reset)
5660
}
5761

5862
// Create progress bar callback
@@ -69,7 +73,7 @@ Examples:
6973
case snapshot.PhaseDownload:
7074
if bar == nil && total > 0 {
7175
bar = ui.NewProgressBar(os.Stdout, total)
72-
bar.SetIndent(" ") // 5-space indent to align with Source/Cache
76+
bar.SetIndent(" ") // 2-space indent to align with Source/Cache
7377
}
7478
if bar != nil {
7579
bar.Update(current)
@@ -138,8 +142,12 @@ Examples:
138142
}
139143

140144
if flagOutput != "json" {
141-
fmt.Printf(" Cache: %s/%s\n", cfg.HomeDir, snapshot.CacheDir)
142-
fmt.Printf(" Destination: %s\n", targetDir)
145+
dim, reset := "\033[2m", "\033[0m"
146+
if os.Getenv("NO_COLOR") != "" {
147+
dim, reset = "", ""
148+
}
149+
fmt.Printf(" %s%-12s %s%s\n", dim, "Cache:", ui.ShortenPath(cfg.HomeDir+"/"+snapshot.CacheDir), reset)
150+
fmt.Printf(" %s%-12s %s%s\n", dim, "Destination:", ui.ShortenPath(targetDir), reset)
143151
}
144152

145153
// Create progress callback
@@ -148,7 +156,7 @@ Examples:
148156
return
149157
}
150158
if phase == snapshot.PhaseExtract && message != "" {
151-
fmt.Printf("\r → Extracting: %-60s", truncate(message, 60))
159+
fmt.Printf("\r → Extracting: %-60s", truncate(message, 60))
152160
}
153161
}
154162

cmd/push-validator/main.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
package main
22

3-
func main() { Execute() }
3+
import "github.com/pushchain/push-validator-cli/internal/ui"
4+
5+
func main() {
6+
// Initialize terminal FIRST, before any charmbracelet imports are used.
7+
// This prevents OSC 11 background color queries and focus events from
8+
// polluting the output stream.
9+
ui.InitTerminal()
10+
11+
Execute()
12+
}

cmd/push-validator/root_cobra.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,9 @@ func init() {
308308
})
309309
return err
310310
}
311-
if flagOutput != "json" {
311+
// Only show success message when NOT in scripted mode (--skip-snapshot)
312+
// install.sh calls with --skip-snapshot and handles its own "Node initialized" message
313+
if flagOutput != "json" && !initSkipSnapshot {
312314
p.Success("Initialization complete")
313315
}
314316
return nil

install.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ if [[ -n "$NO_COLOR" ]] || [[ ! -t 1 ]]; then
2525
CYAN=''; GREEN=''; YELLOW=''; RED=''; BOLD=''; DIM=''; NC=''
2626
fi
2727

28+
# Prevent terminal background color queries from polluting output
29+
# This tells charmbracelet/termenv libraries the background color,
30+
# avoiding OSC 11 queries that cause ^[]11;rgb:... responses in iTerm2
31+
export COLORFGBG="${COLORFGBG:-0;15}"
32+
2833
status() { echo -e "${CYAN}$*${NC}"; }
2934
ok() {
3035
if [[ $PHASE_START_TIME -gt 0 ]]; then
@@ -41,12 +46,17 @@ ok() {
4146
echo -e " ${GREEN}$*${NC}"
4247
fi
4348
}
49+
ok_sub() { echo -e " ${GREEN}$*${NC}"; } # 4-space indent for nested sub-items
4450
warn() { echo -e " ${YELLOW}$*${NC}"; }
4551
err() { echo -e " ${RED}$*${NC}"; }
4652
phase() { echo -e "\n${BOLD}${CYAN}$*${NC}"; }
4753
step() { echo -e " ${DIM}${NC} $*"; }
54+
step_sub() { echo -e " ${DIM}${NC} $*"; } # 4-space indent for nested sub-steps
4855
verbose() { [[ "$VERBOSE" = "yes" ]] && echo -e " ${DIM}$*${NC}" || true; }
4956

57+
# Helper: Shorten path for display (replace $HOME with ~)
58+
short_path() { echo "${1/#$HOME/\~}"; }
59+
5060
# Helper: Indent output lines (adds 2-space prefix)
5161
indent_output() {
5262
while IFS= read -r line; do

internal/sync/monitor.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,10 @@ func Run(ctx context.Context, opts Options) error {
150150
if tty {
151151
extra := ""
152152
if lastPeers > 0 {
153-
extra += fmt.Sprintf(" | peers: %d", lastPeers)
153+
extra += fmt.Sprintf(" peers: %d", lastPeers)
154154
}
155155
if lastLatency > 0 {
156-
extra += fmt.Sprintf(" | rtt: %dms", lastLatency)
156+
extra += fmt.Sprintf(" rtt: %dms", lastLatency)
157157
}
158158
fmt.Fprintf(opts.Out, "\r\033[K %s%s", lineWithETA, extra)
159159
} else {
@@ -236,10 +236,10 @@ func Run(ctx context.Context, opts Options) error {
236236
if tty {
237237
extra := ""
238238
if lastPeers > 0 {
239-
extra += fmt.Sprintf(" | peers: %d", lastPeers)
239+
extra += fmt.Sprintf(" peers: %d", lastPeers)
240240
}
241241
if lastLatency > 0 {
242-
extra += fmt.Sprintf(" | rtt: %dms", lastLatency)
242+
extra += fmt.Sprintf(" rtt: %dms", lastLatency)
243243
}
244244
fmt.Fprintf(opts.Out, "\r\033[K %s%s", lineWithETA, extra)
245245
} else {
@@ -307,10 +307,10 @@ func Run(ctx context.Context, opts Options) error {
307307
if tty {
308308
extra := ""
309309
if lastPeers > 0 {
310-
extra += fmt.Sprintf(" | peers: %d", lastPeers)
310+
extra += fmt.Sprintf(" peers: %d", lastPeers)
311311
}
312312
if lastLatency > 0 {
313-
extra += fmt.Sprintf(" | rtt: %dms", lastLatency)
313+
extra += fmt.Sprintf(" rtt: %dms", lastLatency)
314314
}
315315
fmt.Fprintf(opts.Out, "\r\033[K %s%s", lineWithETA, extra)
316316
} else {
@@ -368,10 +368,10 @@ func Run(ctx context.Context, opts Options) error {
368368
if tty {
369369
extra := ""
370370
if lastPeers > 0 {
371-
extra += fmt.Sprintf(" | peers: %d", lastPeers)
371+
extra += fmt.Sprintf(" peers: %d", lastPeers)
372372
}
373373
if lastLatency > 0 {
374-
extra += fmt.Sprintf(" | rtt: %dms", lastLatency)
374+
extra += fmt.Sprintf(" rtt: %dms", lastLatency)
375375
}
376376
fmt.Fprintf(opts.Out, "\r\033[K %s%s", lineWithETA, extra)
377377
} else {
@@ -413,10 +413,10 @@ func Run(ctx context.Context, opts Options) error {
413413
if tty {
414414
extra := ""
415415
if lastPeers > 0 {
416-
extra += fmt.Sprintf(" | peers: %d", lastPeers)
416+
extra += fmt.Sprintf(" peers: %d", lastPeers)
417417
}
418418
if lastLatency > 0 {
419-
extra += fmt.Sprintf(" | rtt: %dms", lastLatency)
419+
extra += fmt.Sprintf(" rtt: %dms", lastLatency)
420420
}
421421
fmt.Fprintf(opts.Out, "\r\033[K %s%s", lineWithETA, extra)
422422
} else {
@@ -455,10 +455,10 @@ func Run(ctx context.Context, opts Options) error {
455455
if tty {
456456
extra := ""
457457
if lastPeers > 0 {
458-
extra += fmt.Sprintf(" | peers: %d", lastPeers)
458+
extra += fmt.Sprintf(" peers: %d", lastPeers)
459459
}
460460
if lastLatency > 0 {
461-
extra += fmt.Sprintf(" | rtt: %dms", lastLatency)
461+
extra += fmt.Sprintf(" rtt: %dms", lastLatency)
462462
}
463463
fmt.Fprintf(opts.Out, "\r\033[K %s%s\n", lineWithETA, extra)
464464
} else {
@@ -685,9 +685,9 @@ func progressRateAndETA(buf []pt, cur, remote int64) (float64, string) {
685685
if rem < 0 {
686686
rem = 0
687687
}
688-
eta = fmt.Sprintf(" | ETA: %s", (time.Duration(rem * float64(time.Second))).Round(time.Second))
688+
eta = fmt.Sprintf(" ETA %s", (time.Duration(rem * float64(time.Second))).Round(time.Second))
689689
} else if remote > 0 {
690-
eta = " | ETA: 0s"
690+
eta = " ETA 0s"
691691
}
692692
return rate, eta
693693
}
@@ -705,7 +705,7 @@ func renderProgress(percent float64, cur, remote int64) string {
705705
filled = width
706706
}
707707
bar := strings.Repeat("█", filled) + strings.Repeat("░", width-filled)
708-
return fmt.Sprintf("→ Syncing [%s] %.2f%% | %d/%d blocks", bar, percent, cur, remote)
708+
return fmt.Sprintf("→ Syncing [%s] %.2f%% %d/%d blocks", bar, percent, cur, remote)
709709
}
710710

711711
func renderProgressWithQuiet(percent float64, cur, remote int64, quiet bool) string {
@@ -725,7 +725,7 @@ func renderProgressWithQuiet(percent float64, cur, remote int64, quiet bool) str
725725
filled = width
726726
}
727727
bar := strings.Repeat("#", filled) + strings.Repeat("-", width-filled)
728-
return fmt.Sprintf("[%s] %.2f%% | %d/%d", bar, percent, cur, remote)
728+
return fmt.Sprintf("[%s] %.2f%% %d/%d", bar, percent, cur, remote)
729729
}
730730
return renderProgress(percent, cur, remote)
731731
}
@@ -740,11 +740,15 @@ func isTTY() bool {
740740

741741
func hideCursor(w io.Writer, tty bool) {
742742
if tty {
743+
// Disable focus reporting (prevents ^[[I/^[[O sequences)
744+
fmt.Fprint(w, "\x1b[?1004l")
745+
// Hide cursor
743746
fmt.Fprint(w, "\x1b[?25l")
744747
}
745748
}
746749
func showCursor(w io.Writer, tty bool) {
747750
if tty {
751+
// Show cursor (don't re-enable focus reporting as it wasn't necessarily on before)
748752
fmt.Fprint(w, "\x1b[?25h")
749753
}
750754
}

internal/ui/format.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ui
22

33
import (
44
"fmt"
5+
"os"
56
"strings"
67
)
78

@@ -71,3 +72,16 @@ func FormatSpeed(bytesPerSec float64) string {
7172
return fmt.Sprintf("%.0fB/s", bytesPerSec)
7273
}
7374
}
75+
76+
// ShortenPath replaces the home directory with ~ for cleaner display.
77+
// Example: /Users/john/.pchain/data -> ~/.pchain/data
78+
func ShortenPath(path string) string {
79+
home, err := os.UserHomeDir()
80+
if err != nil || home == "" {
81+
return path
82+
}
83+
if strings.HasPrefix(path, home) {
84+
return "~" + path[len(home):]
85+
}
86+
return path
87+
}

internal/ui/progress.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import (
99
"golang.org/x/term"
1010
)
1111

12+
// flushStdin discards any pending input from stdin to prevent
13+
// terminal response sequences (like cursor position reports, focus events)
14+
// from corrupting the output. Uses timeout-based flushing to catch
15+
// asynchronous terminal responses.
16+
func flushStdin() {
17+
FlushStdinWithTimeout(30 * time.Millisecond)
18+
}
19+
1220
// Spinner is a tiny terminal spinner helper.
1321
type Spinner struct {
1422
frames []rune
@@ -74,6 +82,17 @@ func NewProgressBar(out io.Writer, total int64) *ProgressBar {
7482
isTTY = term.IsTerminal(int(f.Fd()))
7583
}
7684

85+
// Disable terminal focus reporting to prevent ^[[I/^[[O sequences
86+
// and flush any pending terminal responses that could corrupt output
87+
if isTTY {
88+
// Disable focus reporting (CSI ? 1004 l)
89+
fmt.Fprint(out, "\033[?1004l")
90+
// Small delay to allow any pending terminal responses to arrive
91+
time.Sleep(10 * time.Millisecond)
92+
// Flush any pending terminal responses from stdin
93+
flushStdin()
94+
}
95+
7796
return &ProgressBar{
7897
out: out,
7998
total: total,
@@ -181,7 +200,7 @@ func (p *ProgressBar) renderTTY(pct float64) {
181200

182201
// Format output with ANSI escape to clear rest of line (fixes /s/s bug)
183202
// \033[K clears from cursor to end of line
184-
fmt.Fprintf(p.out, "\r%s[%s] %5.1f%% %s/%s %s ETA %s\033[K",
203+
fmt.Fprintf(p.out, "\r%s[%s] %5.1f%% %s/%s %s ETA %s\033[K",
185204
p.indent,
186205
bar,
187206
pct,
@@ -218,6 +237,8 @@ func (p *ProgressBar) Finish() {
218237
p.renderTTY(100)
219238
}
220239
fmt.Fprintln(p.out)
240+
// Flush any pending terminal responses that accumulated during progress updates
241+
flushStdin()
221242
} else if p.total > 0 && p.lastPct < 100 {
222243
// Ensure we print 100% for non-TTY
223244
fmt.Fprintf(p.out, "%sDownloading... 100%%\n", p.indent)

internal/ui/terminal_init.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package ui
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"syscall"
7+
"time"
8+
9+
"golang.org/x/term"
10+
)
11+
12+
var terminalInitialized bool
13+
14+
// InitTerminal configures the terminal to prevent escape sequence pollution.
15+
// This MUST be called before any charmbracelet library (lipgloss, bubbletea) usage
16+
// to prevent OSC 11 background color queries from polluting the output.
17+
//
18+
// The issue: muesli/termenv (used by lipgloss) queries terminal background color
19+
// via OSC 11, and the terminal response (\033]11;rgb:...\033\\) gets mixed into stdout.
20+
// Setting COLORFGBG tells termenv the background color, skipping the query.
21+
func InitTerminal() {
22+
if terminalInitialized {
23+
return
24+
}
25+
terminalInitialized = true
26+
27+
// 1. Prevent OSC 11 background color query by pre-setting COLORFGBG
28+
// Format: "foreground;background" where values indicate color indices
29+
// Setting "0;15" indicates dark foreground on light background area
30+
// This prevents termenv from sending OSC 11 query to detect background
31+
if os.Getenv("COLORFGBG") == "" {
32+
os.Setenv("COLORFGBG", "0;15")
33+
}
34+
35+
// 2. For TTY output, disable focus reporting and flush stale responses
36+
// iTerm2 and other terminals can send focus in/out events (^[[I/^[[O])
37+
// which pollute the output stream
38+
if term.IsTerminal(int(os.Stdout.Fd())) {
39+
// Disable focus reporting (CSI ? 1004 l)
40+
fmt.Fprint(os.Stdout, "\033[?1004l")
41+
// Small delay to allow any pending responses to arrive
42+
time.Sleep(20 * time.Millisecond)
43+
// Flush any pending terminal responses from stdin
44+
FlushStdinWithTimeout(50 * time.Millisecond)
45+
}
46+
}
47+
48+
// FlushStdinWithTimeout reads and discards stdin for the specified duration.
49+
// This catches asynchronous terminal responses (cursor position reports,
50+
// OSC responses, focus events) that arrive after queries are sent.
51+
func FlushStdinWithTimeout(timeout time.Duration) {
52+
fd := int(os.Stdin.Fd())
53+
54+
// Set non-blocking mode to read without waiting
55+
if err := syscall.SetNonblock(fd, true); err != nil {
56+
return
57+
}
58+
defer syscall.SetNonblock(fd, false)
59+
60+
buf := make([]byte, 256)
61+
deadline := time.Now().Add(timeout)
62+
63+
for time.Now().Before(deadline) {
64+
n, _ := os.Stdin.Read(buf)
65+
if n <= 0 {
66+
// No data available, wait briefly before checking again
67+
time.Sleep(5 * time.Millisecond)
68+
}
69+
// If we read data, continue the loop to catch more
70+
}
71+
}

0 commit comments

Comments
 (0)