diff --git a/.github/cla-signatures.json b/.github/cla-signatures.json index a62172eb28a153115f31a83dd1be2c88193b20df..136d5857bf881c2fca2a5ada072cde11ac97af3c 100644 --- a/.github/cla-signatures.json +++ b/.github/cla-signatures.json @@ -655,6 +655,14 @@ "created_at": "2025-09-20T12:37:42Z", "repoId": 987670088, "pullRequestNo": 1095 + }, + { + "name": "Kaneki-x", + "id": 6857108, + "comment_id": 3338743039, + "created_at": "2025-09-26T13:30:16Z", + "repoId": 987670088, + "pullRequestNo": 1135 } ] } \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0172187ca829e659ae9e31e2c58929a259411b0d..cf970b5887bc33fd822ab7fc4fe4540df045a6e1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,6 +18,9 @@ updates: patterns: - "*" ignore: + - dependency-name: github.com/charmbracelet/bubbletea/v2 + versions: + - v2.0.0-beta1 - dependency-name: github.com/charmbracelet/lipgloss/v2 versions: - v2.0.0-beta1 diff --git a/go.mod b/go.mod index d9838be112d21eacbd73f5a6305ed76b6bd78660..c8efb56e77cb645563e1486887d5cd42c840d459 100644 --- a/go.mod +++ b/go.mod @@ -153,7 +153,7 @@ require ( golang.org/x/text v0.29.0 golang.org/x/time v0.8.0 // indirect google.golang.org/api v0.211.0 // indirect - google.golang.org/genai v1.25.0 + google.golang.org/genai v1.26.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/grpc v1.71.0 // indirect google.golang.org/protobuf v1.36.8 // indirect diff --git a/go.sum b/go.sum index d89cf5ea237627b9acba71ff7186a08b72d88004..7c0aa16455c59e740f63fbdd69ecb5b763689783 100644 --- a/go.sum +++ b/go.sum @@ -427,8 +427,8 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.211.0 h1:IUpLjq09jxBSV1lACO33CGY3jsRcbctfGzhj+ZSE/Bg= google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0= -google.golang.org/genai v1.25.0 h1:Cpyh2nmEoOS1eM3mT9XKuA/qWTEDoktfP2gsN3EduPE= -google.golang.org/genai v1.25.0/go.mod h1:OClfdf+r5aaD+sCd4aUSkPzJItmg2wD/WON9lQnRPaY= +google.golang.org/genai v1.26.0 h1:r4HGL54kFv/WCRMTAbZg05Ct+vXfhAbTRlXhFyBkEQo= +google.golang.org/genai v1.26.0/go.mod h1:OClfdf+r5aaD+sCd4aUSkPzJItmg2wD/WON9lQnRPaY= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= diff --git a/internal/event/event.go b/internal/event/event.go index 42272c7035638fee7167b5c3510c7975cb9c9394..ca02c6d89d67be1756b166aea152da165b2712c9 100644 --- a/internal/event/event.go +++ b/internal/event/event.go @@ -9,7 +9,6 @@ import ( "runtime" "github.com/charmbracelet/crush/internal/version" - "github.com/denisbrodbeck/machineid" "github.com/posthog/posthog-go" ) @@ -39,6 +38,7 @@ func Init() { slog.Error("Failed to initialize PostHog client", "error", err) } client = c + distinctId = getDistinctId() } // send logs an event to PostHog with the given event name and properties. @@ -47,7 +47,7 @@ func send(event string, props ...any) { return } err := client.Enqueue(posthog.Capture{ - DistinctId: distinctId(), + DistinctId: distinctId, Event: event, Properties: pairsToProps(props...).Merge(baseProps), }) @@ -105,11 +105,3 @@ func pairsToProps(props ...any) posthog.Properties { func isEven(n int) bool { return n%2 == 0 } - -func distinctId() string { - id, err := machineid.ProtectedID("charm") - if err != nil { - return "crush-cli" - } - return id -} diff --git a/internal/event/identifier.go b/internal/event/identifier.go new file mode 100644 index 0000000000000000000000000000000000000000..ee05f8f58f6dd9a8f662e94992983ce26a94d9b9 --- /dev/null +++ b/internal/event/identifier.go @@ -0,0 +1,49 @@ +package event + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "net" + + "github.com/denisbrodbeck/machineid" +) + +var distinctId string + +const ( + hashKey = "charm" + fallbackId = "unknown" +) + +func getDistinctId() string { + if id, err := machineid.ProtectedID(hashKey); err == nil { + return id + } + if macAddr, err := getMacAddr(); err == nil { + return hashString(macAddr) + } + return fallbackId +} + +func getMacAddr() (string, error) { + interfaces, err := net.Interfaces() + if err != nil { + return "", err + } + for _, iface := range interfaces { + if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagLoopback == 0 && len(iface.HardwareAddr) > 0 { + if addrs, err := iface.Addrs(); err == nil && len(addrs) > 0 { + return iface.HardwareAddr.String(), nil + } + } + } + return "", fmt.Errorf("no active interface with mac address found") +} + +func hashString(str string) string { + hash := hmac.New(sha256.New, []byte(str)) + hash.Write([]byte(hashKey)) + return hex.EncodeToString(hash.Sum(nil)) +} diff --git a/internal/llm/agent/mcp-tools.go b/internal/llm/agent/mcp-tools.go index b36c6d85734d37b7792ea4bbf3556ab37c0eb4d1..70388d8f5e31cdfb4d7923842c25cc7b98c00c6c 100644 --- a/internal/llm/agent/mcp-tools.go +++ b/internal/llm/agent/mcp-tools.go @@ -233,9 +233,9 @@ func updateMCPState(name string, state MCPState, err error, client *client.Clien // CloseMCPClients closes all MCP clients. This should be called during application shutdown. func CloseMCPClients() error { var errs []error - for c := range mcpClients.Seq() { + for name, c := range mcpClients.Seq2() { if err := c.Close(); err != nil { - errs = append(errs, err) + errs = append(errs, fmt.Errorf("close mcp: %s: %w", name, err)) } } mcpBroker.Shutdown() diff --git a/internal/lsp/client.go b/internal/lsp/client.go index 07f14e9cbf2fb4170b0ca2c1b9f3a4bb6de25bfd..94f723abc473d651f98bffafa90b68eabdeefd80 100644 --- a/internal/lsp/client.go +++ b/internal/lsp/client.go @@ -350,29 +350,6 @@ func (c *Client) NotifyChange(ctx context.Context, filepath string) error { return c.client.NotifyDidChangeTextDocument(ctx, uri, int(fileInfo.Version), changes) } -// CloseFile closes a file in the LSP server. -// -// NOTE: this is only ever called on LSP shutdown. -func (c *Client) CloseFile(ctx context.Context, filepath string) error { - uri := string(protocol.URIFromPath(filepath)) - - if _, exists := c.openFiles.Get(uri); !exists { - return nil // Already closed - } - - if c.cfg.Options.DebugLSP { - slog.Debug("Closing file", "file", filepath) - } - - if err := c.client.NotifyDidCloseTextDocument(ctx, uri); err != nil { - return err - } - - c.openFiles.Del(uri) - - return nil -} - // IsFileOpen checks if a file is currently open. func (c *Client) IsFileOpen(filepath string) bool { uri := string(protocol.URIFromPath(filepath)) @@ -382,29 +359,16 @@ func (c *Client) IsFileOpen(filepath string) bool { // CloseAllFiles closes all currently open files. func (c *Client) CloseAllFiles(ctx context.Context) { - filesToClose := make([]string, 0, c.openFiles.Len()) - - // First collect all URIs that need to be closed + debugLSP := c.cfg != nil && c.cfg.Options.DebugLSP for uri := range c.openFiles.Seq2() { - // Convert URI back to file path using proper URI handling - filePath, err := protocol.DocumentURI(uri).Path() - if err != nil { - slog.Error("Failed to convert URI to path for file closing", "uri", uri, "error", err) - continue + if debugLSP { + slog.Debug("Closing file", "file", uri) } - filesToClose = append(filesToClose, filePath) - } - - // Then close them all - for _, filePath := range filesToClose { - err := c.CloseFile(ctx, filePath) - if err != nil && c.cfg != nil && c.cfg.Options.DebugLSP { - slog.Warn("Error closing file", "file", filePath, "error", err) + if err := c.client.NotifyDidCloseTextDocument(ctx, uri); err != nil { + slog.Warn("Error closing rile", "uri", uri, "error", err) + continue } - } - - if c.cfg != nil && c.cfg.Options.DebugLSP { - slog.Debug("Closed all files", "files", filesToClose) + c.openFiles.Del(uri) } }