diff --git a/commands/cc_statusline.go b/commands/cc_statusline.go index bf1dfa8..ec9b5f7 100644 --- a/commands/cc_statusline.go +++ b/commands/cc_statusline.go @@ -66,6 +66,24 @@ func commandCCStatusline(c *cli.Context) error { var result ccStatuslineResult config, err := configService.ReadConfigFile(ctx) if err == nil { + // Send session-project mapping via daemon socket (fire-and-forget, ~1ms) + if data.SessionID != "" { + projectPath := "" + if data.Workspace != nil { + projectPath = data.Workspace.CurrentDir + if projectPath == "" { + projectPath = data.Workspace.ProjectDir + } + } + if projectPath != "" { + socketPath := config.SocketPath + if socketPath == "" { + socketPath = model.DefaultSocketPath + } + daemon.SendSessionProject(socketPath, data.SessionID, projectPath) + } + } + result = getDaemonInfoWithFallback(ctx, config, data.Cwd) } diff --git a/daemon/client.go b/daemon/client.go index 0b9d4cc..77a146f 100644 --- a/daemon/client.go +++ b/daemon/client.go @@ -51,6 +51,25 @@ func SendLocalDataToSocket( return nil } +// SendSessionProject sends a session-to-project mapping to the daemon (fire-and-forget) +func SendSessionProject(socketPath string, sessionID, projectPath string) { + conn, err := net.DialTimeout("unix", socketPath, 10*time.Millisecond) + if err != nil { + return + } + defer conn.Close() + + msg := SocketMessage{ + Type: SocketMessageTypeSessionProject, + Payload: SessionProjectRequest{ + SessionID: sessionID, + ProjectPath: projectPath, + }, + } + + json.NewEncoder(conn).Encode(msg) +} + // RequestCCInfo requests CC info (cost data and git info) from the daemon func RequestCCInfo(socketPath string, timeRange CCInfoTimeRange, workingDir string, timeout time.Duration) (*CCInfoResponse, error) { conn, err := net.DialTimeout("unix", socketPath, timeout) diff --git a/daemon/socket.go b/daemon/socket.go index 17ada67..e7cfde1 100644 --- a/daemon/socket.go +++ b/daemon/socket.go @@ -1,6 +1,7 @@ package daemon import ( + "context" "encoding/json" "fmt" "log/slog" @@ -17,12 +18,18 @@ import ( type SocketMessageType string const ( - SocketMessageTypeSync SocketMessageType = "sync" - SocketMessageTypeHeartbeat SocketMessageType = "heartbeat" - SocketMessageTypeStatus SocketMessageType = "status" - SocketMessageTypeCCInfo SocketMessageType = "cc_info" + SocketMessageTypeSync SocketMessageType = "sync" + SocketMessageTypeHeartbeat SocketMessageType = "heartbeat" + SocketMessageTypeStatus SocketMessageType = "status" + SocketMessageTypeCCInfo SocketMessageType = "cc_info" + SocketMessageTypeSessionProject SocketMessageType = "session_project" ) +type SessionProjectRequest struct { + SessionID string `json:"sessionId"` + ProjectPath string `json:"projectPath"` +} + type CCInfoTimeRange string const ( @@ -179,6 +186,15 @@ func (p *SocketHandler) handleConnection(conn net.Conn) { encoder.Encode(map[string]string{"status": "ok"}) case SocketMessageTypeCCInfo: p.handleCCInfo(conn, msg) + case SocketMessageTypeSessionProject: + if payload, ok := msg.Payload.(map[string]interface{}); ok { + sessionID, _ := payload["sessionId"].(string) + projectPath, _ := payload["projectPath"].(string) + if sessionID != "" && projectPath != "" { + go model.SendSessionProjectUpdate(context.Background(), *p.config, sessionID, projectPath) + slog.Debug("session_project update dispatched", slog.String("sessionId", sessionID)) + } + } default: slog.Error("Unknown message type:", slog.String("messageType", string(msg.Type))) } diff --git a/model/api_session_project.go b/model/api_session_project.go new file mode 100644 index 0000000..ed5e6f8 --- /dev/null +++ b/model/api_session_project.go @@ -0,0 +1,39 @@ +package model + +import ( + "context" + "net/http" + "time" +) + +type sessionProjectRequest struct { + SessionID string `json:"session_id"` + ProjectPath string `json:"project_path"` +} + +type sessionProjectResponse struct{} + +// SendSessionProjectUpdate sends a session-to-project path mapping to the server +func SendSessionProjectUpdate(ctx context.Context, config ShellTimeConfig, sessionID, projectPath string) error { + ctx, span := modelTracer.Start(ctx, "session_project.send") + defer span.End() + + var resp sessionProjectResponse + err := SendHTTPRequestJSON(HTTPRequestOptions[*sessionProjectRequest, sessionProjectResponse]{ + Context: ctx, + Endpoint: Endpoint{ + APIEndpoint: config.APIEndpoint, + Token: config.Token, + }, + Method: http.MethodPost, + Path: "/api/v1/cc/session-project", + Payload: &sessionProjectRequest{ + SessionID: sessionID, + ProjectPath: projectPath, + }, + Response: &resp, + Timeout: 5 * time.Second, + }) + + return err +} diff --git a/model/cc_statusline_types.go b/model/cc_statusline_types.go index 0278d47..53b1927 100644 --- a/model/cc_statusline_types.go +++ b/model/cc_statusline_types.go @@ -8,6 +8,14 @@ type CCStatuslineInput struct { ContextWindow CCStatuslineContextWindow `json:"context_window"` Cwd string `json:"cwd"` Version string `json:"version"` + SessionID string `json:"session_id"` + Workspace *CCStatuslineWorkspace `json:"workspace"` +} + +// CCStatuslineWorkspace represents workspace information from Claude Code +type CCStatuslineWorkspace struct { + CurrentDir string `json:"current_dir"` + ProjectDir string `json:"project_dir"` } // CCStatuslineModel represents model information