Skip to content
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0032e63
docs: update Readme
Egzavyer Jan 31, 2026
49be66b
chore: rewrite basic debugger
Egzavyer Jan 31, 2026
8c007b0
chore(lefthook.yml): remove unit and integration tests
Egzavyer Feb 3, 2026
3c384d2
chore: suggested changes
Egzavyer Feb 3, 2026
5304e17
chore(justfile): replace Makefile with justfile
Egzavyer Feb 3, 2026
fcce23d
chore: add just
Egzavyer Feb 3, 2026
f2075ae
feat: client interface with state tracking
xsachax Feb 4, 2026
b666c52
wip: refactor logic into debugger package
Egzavyer Feb 4, 2026
934f238
docs: clean up
Egzavyer Feb 4, 2026
b9d3414
fix: prevent client from sending command while not on breakpoint
xsachax Feb 4, 2026
48e6ecf
refactor: rename server-side client to connection
xsachax Feb 4, 2026
b57f125
chore: remove dead commented code
xsachax Feb 4, 2026
598ddd3
feat: restructure debugger to be owned by hub with command forwarding
xsachax Feb 4, 2026
5fe4bf5
feat: getSessions endpoint
xsachax Feb 17, 2026
028ce73
feat: create session id when none provided
Egzavyer Feb 17, 2026
3ce2bbc
feat: send ack with sessionID back to client on connect
xsachax Feb 17, 2026
d121193
feat: wire debugger to hub
Egzavyer Feb 17, 2026
b3eb2e2
feat: adds wiring for setting breakpoints
Egzavyer Feb 17, 2026
f344c20
feat: send state update on initial breakpoint
Egzavyer Feb 17, 2026
e1efef9
fix: change make to just in lefthook.yml
Egzavyer Feb 17, 2026
c90befa
fix: log when parsing failed
Egzavyer Feb 17, 2026
bed02f0
wip: bruh
xsachax Feb 17, 2026
982ac37
chore: improve logging
xsachax Feb 17, 2026
06d2bf8
chore: improve dummy client for debugging
xsachax Feb 17, 2026
479c08b
feat: add proper line reader to cli
xsachax Feb 18, 2026
914a7fd
feat: cli config
xsachax Feb 18, 2026
8744cd2
feat: graceful exit
xsachax Feb 18, 2026
52f8547
fix: don't hang client on server shutdown
xsachax Feb 22, 2026
f3e45e9
fix: insane clutch
Egzavyer Feb 22, 2026
87f5ff7
fix: send state update on end of debug session
xsachax Feb 22, 2026
35b13bd
fix: remove faulty state updates on client
xsachax Feb 22, 2026
4d1b531
fix: sanitize input path
Egzavyer Feb 22, 2026
240f00a
fix: remove redundant config info
xsachax Feb 23, 2026
15538a3
fix: initialbreakpointhit protocol
Egzavyer Feb 23, 2026
5e92aad
chore: remove incomplete code related to attach
xsachax Feb 23, 2026
b92b43b
feat: works
Egzavyer Feb 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"--platform=linux/amd64"
],
"features": {
"ghcr.io/thediveo/devcontainer-features/lazygit": {}
"ghcr.io/thediveo/devcontainer-features/lazygit": {},
"ghcr.io/jsburckhardt/devcontainer-features/just:1": {}
},
"postCreateCommand": "bash .devcontainer/setup.sh",
"customizations": {
Expand Down
35 changes: 0 additions & 35 deletions Makefile

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

BinGo is a standalone visual concurrency debugger for Go that makes goroutines, channels, and synchronization behavior easy to understand. It captures detailed runtime events and turns them into clear, interactive visualizations, whether you’re running it as a terminal UI or inside editors like VS Code or Vim. With features like goroutine lifecycle tracking, channel and mutex inspection, timeline replay, and deadlock or leak detection, BinGo helps developers see how their concurrent programs actually behave and debug tricky issues that traditional tools miss. Its modular design keeps the core engine UI-agnostic, so new frontends and integrations can be built easily by the community.


## Documentation

For detailed documentation, including client meeting minutes, existing solution comparision, project roadmap, installation instructions, usage guides, and API references, please read the [**Docs**](https://github.com/bingosuite/bingo/tree/main/docs).
186 changes: 186 additions & 0 deletions cmd/bingo-client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package main

import (
"bufio"
"flag"
"fmt"
"io"
"log"
"os"
"strconv"
"strings"
"time"

"github.com/bingosuite/bingo/config"
"github.com/bingosuite/bingo/pkg/client"
"github.com/peterh/liner"
"golang.org/x/term"
)

func main() {
cfg, err := config.Load("config/config.yml")
if err != nil {
log.Printf("Failed to load config: %v", err)
}

if cfg == nil {
cfg = config.Default()
}

defaultAddr := cfg.Server.Addr
if strings.HasPrefix(defaultAddr, ":") {
defaultAddr = "localhost" + defaultAddr
}

server := flag.String("server", defaultAddr, "WebSocket server host:port")
session := flag.String("session", "", "Existing session ID (optional)")
flag.Parse()

c := client.NewClient(*server, *session)
if err := c.Connect(); err != nil {
log.Fatalf("Failed to connect: %v", err)
}
if err := c.Run(); err != nil {
log.Fatalf("Failed to start client: %v", err)
}

log.Println("Connected. Commands: start <path>, stop, c=continue, s=step, b=<file> <line>, state, q=quit")

inputReader := bufio.NewReader(os.Stdin)
useRawInput := term.IsTerminal(int(os.Stdin.Fd()))
var lineEditor *liner.State
if useRawInput {
lineEditor = liner.NewLiner()
lineEditor.SetCtrlCAborts(true)
lineEditor.SetTabCompletionStyle(liner.TabPrints)
defer func() {
_ = lineEditor.Close()
}()
}

go func() {
if err := c.Wait(); err != nil {
log.Println("Server disconnected")
if lineEditor != nil {
_ = lineEditor.Close()
}
os.Exit(1)
}
}()

history := make([]string, 0, 64)

for {
prompt := ""
var rawLine string
var readErr error
if useRawInput {
rawLine, readErr = lineEditor.Prompt(prompt)
} else {
fmt.Print(prompt)
line, err := inputReader.ReadString('\n')
if err != nil {
if err != io.EOF {
log.Printf("Stdin error: %v", err)
}
break
}
rawLine = strings.TrimRight(line, "\r\n")
}
if readErr != nil {
if readErr == liner.ErrPromptAborted || readErr == io.EOF {
break
}
log.Printf("Stdin error: %v", readErr)
break
}

raw := strings.TrimSpace(rawLine)
if raw != "" {
if len(history) == 0 || history[len(history)-1] != rawLine {
history = append(history, rawLine)
if lineEditor != nil {
lineEditor.AppendHistory(rawLine)
}
}
}

input := strings.ToLower(raw)
fields := strings.Fields(raw)
var cmdErr error
switch input {
case "c", "continue":
cmdErr = c.Continue()
time.Sleep(100 * time.Millisecond)
case "s", "step", "stepover":
cmdErr = c.StepOver()
time.Sleep(100 * time.Millisecond)
case "state":
fmt.Printf("state=%s session=%s\n", c.State(), c.SessionID())
case "stop":
cmdErr = c.Stop()
time.Sleep(100 * time.Millisecond)
case "q", "quit", "exit":
_ = c.Close()
return
case "":
continue
default:
if len(fields) > 0 && strings.EqualFold(fields[0], "start") {
cmdErr = handleStartCommand(c, fields)
if cmdErr != nil {
fmt.Println(cmdErr.Error())
cmdErr = nil
break
}
// Give async state updates time to arrive before showing next prompt
time.Sleep(100 * time.Millisecond)
break
}
cmdErr = handleBreakpointCommand(c, raw)
if cmdErr != nil {
fmt.Println(cmdErr.Error())
cmdErr = nil
}
}
if cmdErr != nil {
log.Printf("Command error: %v", cmdErr)
}
}

_ = c.Close()
}

func handleBreakpointCommand(c *client.Client, raw string) error {
fields := strings.Fields(raw)
if len(fields) == 0 {
return nil
}
cmd := strings.ToLower(fields[0])
if cmd != "b" && cmd != "break" && cmd != "breakpoint" {
return fmt.Errorf("unknown command")
}
if len(fields) < 2 || len(fields) > 3 {
return fmt.Errorf("usage: b <line> or b <file> <line>")
}
filename := ""
lineStr := ""
if len(fields) == 2 {
lineStr = fields[1]
} else {
filename = fields[1]
lineStr = fields[2]
}
line, err := strconv.Atoi(lineStr)
if err != nil || line <= 0 {
return fmt.Errorf("invalid line number")
}
return c.SetBreakpoint(filename, line)
}

func handleStartCommand(c *client.Client, fields []string) error {
if len(fields) != 2 {
return fmt.Errorf("usage: start <path>")
}
return c.StartDebug(fields[1])
}
Loading
Loading