Skip to content

Commit 1d59cf6

Browse files
feat: add log clearing functionality
Add ability to clear logs via TUI (x key) and MCP tool (devir_clear_logs). When on "All" tab, clears all logs. When on a specific service tab, clears only that service's logs. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b780b45 commit 1d59cf6

File tree

8 files changed

+124
-11
lines changed

8 files changed

+124
-11
lines changed

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"Bash(gofmt:*)",
1515
"Bash(golangci-lint:*)",
1616
"Bash(go list:*)",
17-
"Bash(make test:*)"
17+
"Bash(make test:*)",
18+
"Bash(make lint:*)"
1819
]
1920
},
2021
"enableAllProjectMcpServers": true,

internal/daemon/client.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,15 @@ func (c *Client) KillPorts(ports []int) error {
193193
return c.Send(msg)
194194
}
195195

196+
// ClearLogs sends a clear logs request
197+
func (c *Client) ClearLogs(service string) error {
198+
msg, err := NewMessage(MsgClearLogs, ClearLogsRequest{Service: service})
199+
if err != nil {
200+
return err
201+
}
202+
return c.Send(msg)
203+
}
204+
196205
// WaitForResponse waits for a specific response type
197206
func (c *Client) WaitForResponse(msgType string, timeout time.Duration) (Message, error) {
198207
deadline := time.Now().Add(timeout)
@@ -297,3 +306,13 @@ func (c *Client) KillPortsSync(ports []int, timeout time.Duration) (KillPortsRes
297306

298307
return ParsePayload[KillPortsResponse](msg)
299308
}
309+
310+
// ClearLogsSync clears logs synchronously
311+
func (c *Client) ClearLogsSync(service string, timeout time.Duration) error {
312+
if err := c.ClearLogs(service); err != nil {
313+
return err
314+
}
315+
316+
_, err := c.WaitForResponse(MsgLogsCleared, timeout)
317+
return err
318+
}

internal/daemon/daemon.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ func (d *Daemon) handleMessage(c *clientConn, msg Message) {
228228
d.handleStatus(c)
229229
case MsgLogs:
230230
d.handleLogs(c, msg)
231+
case MsgClearLogs:
232+
d.handleClearLogs(c, msg)
231233
case MsgCheckPorts:
232234
d.handleCheckPorts(c)
233235
case MsgKillPorts:
@@ -461,6 +463,21 @@ func (d *Daemon) handleLogs(c *clientConn, msg Message) {
461463
c.send(resp)
462464
}
463465

466+
func (d *Daemon) handleClearLogs(c *clientConn, msg Message) {
467+
req, err := ParsePayload[ClearLogsRequest](msg)
468+
if err != nil {
469+
d.sendError(c, err.Error())
470+
return
471+
}
472+
473+
if d.runner != nil {
474+
d.runner.ClearLogs(req.Service)
475+
}
476+
477+
resp, _ := NewMessage(MsgLogsCleared, struct{}{})
478+
c.send(resp)
479+
}
480+
464481
func (d *Daemon) handleCheckPorts(c *clientConn) {
465482
var ports []PortInfo
466483
hasConflict := false

internal/daemon/protocol.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,21 @@ const (
1313
MsgRestart = "restart"
1414
MsgStatus = "status"
1515
MsgLogs = "logs"
16+
MsgClearLogs = "clear_logs"
1617
MsgCheckPorts = "check_ports"
1718
MsgKillPorts = "kill_ports"
1819

1920
// Daemon → Client
20-
MsgStarted = "started"
21-
MsgStopped = "stopped"
22-
MsgRestarted = "restarted"
23-
MsgStatusResponse = "status_response"
24-
MsgLogsResponse = "logs_response"
25-
MsgPortsResponse = "ports_response"
26-
MsgKillResponse = "kill_response"
27-
MsgLogEntry = "log_entry" // Broadcast to all clients
28-
MsgError = "error"
21+
MsgStarted = "started"
22+
MsgStopped = "stopped"
23+
MsgRestarted = "restarted"
24+
MsgStatusResponse = "status_response"
25+
MsgLogsResponse = "logs_response"
26+
MsgLogsCleared = "logs_cleared"
27+
MsgPortsResponse = "ports_response"
28+
MsgKillResponse = "kill_response"
29+
MsgLogEntry = "log_entry" // Broadcast to all clients
30+
MsgError = "error"
2931
)
3032

3133
// Message is the wire format for daemon communication
@@ -77,6 +79,11 @@ type KillPortsRequest struct {
7779
Ports []int `json:"ports"`
7880
}
7981

82+
// ClearLogsRequest requests clearing logs
83+
type ClearLogsRequest struct {
84+
Service string `json:"service,omitempty"`
85+
}
86+
8087
// --- Response payloads (Daemon → Client) ---
8188

8289
// StartedResponse confirms services started

internal/mcp/mcp.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ func (m *Server) registerTools() {
7878
Name: "devir_restart",
7979
Description: "Restart a specific service",
8080
}, m.handleRestart)
81+
82+
mcp.AddTool(m.server, &mcp.Tool{
83+
Name: "devir_clear_logs",
84+
Description: "Clear logs from services",
85+
}, m.handleClearLogs)
8186
}
8287

8388
// Run starts the MCP server
@@ -173,6 +178,15 @@ type RestartOutput struct {
173178
Service string `json:"service"`
174179
}
175180

181+
type ClearLogsInput struct {
182+
Service string `json:"service,omitempty" jsonschema:"Service name to clear logs for. If empty clears all logs."`
183+
}
184+
185+
type ClearLogsOutput struct {
186+
Status string `json:"status"`
187+
Service string `json:"service,omitempty"`
188+
}
189+
176190
// Handlers
177191

178192
func (m *Server) handleCheckPorts(ctx context.Context, req *mcp.CallToolRequest, input struct{}) (*mcp.CallToolResult, CheckPortsOutput, error) {
@@ -299,3 +313,14 @@ func (m *Server) handleRestart(ctx context.Context, req *mcp.CallToolRequest, in
299313
Service: input.Service,
300314
}, nil
301315
}
316+
317+
func (m *Server) handleClearLogs(ctx context.Context, req *mcp.CallToolRequest, input ClearLogsInput) (*mcp.CallToolResult, ClearLogsOutput, error) {
318+
if err := m.client.ClearLogsSync(input.Service, 5*time.Second); err != nil {
319+
return nil, ClearLogsOutput{}, err
320+
}
321+
322+
return nil, ClearLogsOutput{
323+
Status: "cleared",
324+
Service: input.Service,
325+
}, nil
326+
}

internal/runner/runner.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,21 @@ func (r *Runner) RestartService(name string) {
607607
go r.startService(name)
608608
}
609609

610+
// ClearLogs clears logs for a specific service or all services
611+
func (r *Runner) ClearLogs(service string) {
612+
r.mu.RLock()
613+
defer r.mu.RUnlock()
614+
615+
for name, state := range r.Services {
616+
if service != "" && name != service {
617+
continue
618+
}
619+
state.Mu.Lock()
620+
state.Logs = nil
621+
state.Mu.Unlock()
622+
}
623+
}
624+
610625
var ansiPattern = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
611626

612627
func (r *Runner) processLine(service, text string, isError bool) {

internal/tui/model.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,19 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
182182
// Copy filtered logs to clipboard
183183
cmds = append(cmds, m.copyLogsToClipboard())
184184

185+
case "x":
186+
// Clear logs
187+
service := ""
188+
if m.activeTab >= 0 && m.activeTab < len(m.services) {
189+
service = m.services[m.activeTab]
190+
}
191+
if m.clientMode {
192+
_ = m.client.ClearLogs(service)
193+
} else {
194+
m.Runner.ClearLogs(service)
195+
}
196+
m.clearLocalLogs(service)
197+
185198
case "up", "k":
186199
m.viewport.ScrollUp(1)
187200
m.autoScroll = false
@@ -410,6 +423,22 @@ func equalIgnoreCase(a, b string) bool {
410423
return true
411424
}
412425

426+
// clearLocalLogs clears logs from the local buffer
427+
func (m *Model) clearLocalLogs(service string) {
428+
if service == "" {
429+
m.logs = nil
430+
} else {
431+
filtered := make([]types.LogEntry, 0)
432+
for _, log := range m.logs {
433+
if log.Service != service {
434+
filtered = append(filtered, log)
435+
}
436+
}
437+
m.logs = filtered
438+
}
439+
m.updateViewport()
440+
}
441+
413442
// copyLogsToClipboard copies filtered logs to system clipboard
414443
func (m *Model) copyLogsToClipboard() tea.Cmd {
415444
return func() tea.Msg {

internal/tui/view.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,6 @@ func (m Model) renderHelp() string {
185185
return HelpStyle.Render(m.statusMsg)
186186
}
187187

188-
help := "Tab: switch │ 1-9: select │ a: all │ /: search │ c: copy │ r: restart │ q: quit"
188+
help := "Tab: switch │ 1-9: select │ a: all │ /: search │ c: copy │ x: clear │ r: restart │ q: quit"
189189
return HelpStyle.Render(help)
190190
}

0 commit comments

Comments
 (0)