chore: drop the mcp-go dependency and remove mcpspike throwaway spike
Nothing in the backend speaks MCP anymore -- internal/garmin talks to its embedded Python wrapper over plain JSON-lines instead. The cmd/mcpspike directory was a temporary spike for validating the mcp-go client, which is no longer needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,243 +0,0 @@
|
||||
// Throwaway spike to validate that mark3labs/mcp-go's stdio client can drive
|
||||
// mcp-garmin (spawn, initialize handshake, call tools, parse results).
|
||||
// Not part of the production build — delete once internal/garmin is built.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/mark3labs/mcp-go/client"
|
||||
"github.com/mark3labs/mcp-go/client/transport"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pythonPath := flag.String("python", "", "path to mcp-garmin .venv python executable")
|
||||
serverPath := flag.String("server", "", "path to mcp-garmin server.py")
|
||||
limit := flag.Int("limit", 5, "activity limit for get_activities")
|
||||
startDate := flag.String("start", "", "start date YYYY-MM-DD for get_activities (default: 365 days ago)")
|
||||
endDate := flag.String("end", "", "end date YYYY-MM-DD for get_activities (default: today)")
|
||||
flag.Parse()
|
||||
|
||||
if *pythonPath == "" || *serverPath == "" {
|
||||
log.Fatal("usage: mcpspike -python <path to .venv/bin/python> -server <path to server.py>")
|
||||
}
|
||||
|
||||
email := os.Getenv("GARMIN_EMAIL")
|
||||
password := os.Getenv("GARMIN_PASSWORD")
|
||||
if email == "" || password == "" {
|
||||
log.Fatal("GARMIN_EMAIL and GARMIN_PASSWORD must be set in the environment")
|
||||
}
|
||||
|
||||
env := []string{
|
||||
"GARMIN_EMAIL=" + email,
|
||||
"GARMIN_PASSWORD=" + password,
|
||||
"PYTHONUNBUFFERED=1",
|
||||
}
|
||||
|
||||
c, err := client.NewStdioMCPClient(*pythonPath, env, *serverPath)
|
||||
if err != nil {
|
||||
log.Fatalf("spawn subprocess: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if stdio, ok := c.GetTransport().(*transport.Stdio); ok {
|
||||
go func() {
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
n, err := stdio.Stderr().Read(buf)
|
||||
if n > 0 {
|
||||
fmt.Fprint(os.Stderr, string(buf[:n]))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
log.Println("warning: could not get stdio transport to forward subprocess stderr")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
initReq := mcp.InitializeRequest{}
|
||||
initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
|
||||
initReq.Params.ClientInfo = mcp.Implementation{Name: "mcpspike", Version: "0.0.1"}
|
||||
|
||||
initRes, err := c.Initialize(ctx, initReq)
|
||||
if err != nil {
|
||||
log.Fatalf("initialize handshake failed: %v", err)
|
||||
}
|
||||
fmt.Printf("initialized OK: server=%s version=%s protocol=%s\n",
|
||||
initRes.ServerInfo.Name, initRes.ServerInfo.Version, initRes.ProtocolVersion)
|
||||
|
||||
tools, err := c.ListTools(ctx, mcp.ListToolsRequest{})
|
||||
if err != nil {
|
||||
log.Fatalf("list tools failed: %v", err)
|
||||
}
|
||||
fmt.Printf("server exposes %d tools:\n", len(tools.Tools))
|
||||
for _, t := range tools.Tools {
|
||||
fmt.Printf(" - %s: %s\n", t.Name, t.Description)
|
||||
}
|
||||
|
||||
fmt.Println("\ncalling authenticate()...")
|
||||
authRes, err := callTool(ctx, c, "authenticate", nil)
|
||||
if err != nil {
|
||||
log.Fatalf("authenticate call failed: %v", err)
|
||||
}
|
||||
fmt.Printf("authenticate() -> %s\n", authRes)
|
||||
|
||||
if containsMFAPrompt(authRes) {
|
||||
fmt.Print("MFA required. Enter code: ")
|
||||
var code string
|
||||
fmt.Scanln(&code)
|
||||
mfaRes, err := callTool(ctx, c, "complete_mfa", map[string]any{"code": code})
|
||||
if err != nil {
|
||||
log.Fatalf("complete_mfa call failed: %v", err)
|
||||
}
|
||||
fmt.Printf("complete_mfa() -> %s\n", mfaRes)
|
||||
}
|
||||
|
||||
start := *startDate
|
||||
if start == "" {
|
||||
start = time.Now().AddDate(0, 0, -365).Format("2006-01-02")
|
||||
}
|
||||
end := *endDate
|
||||
if end == "" {
|
||||
end = time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
fmt.Printf("\ncalling get_activities(start_date=%s, end_date=%s, limit=%d)...\n", start, end, *limit)
|
||||
actRes, err := callTool(ctx, c, "get_activities", map[string]any{
|
||||
"start_date": start,
|
||||
"end_date": end,
|
||||
"limit": *limit,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("get_activities call failed: %v", err)
|
||||
}
|
||||
|
||||
var activities []map[string]any
|
||||
if err := json.Unmarshal([]byte(actRes), &activities); err != nil {
|
||||
fmt.Printf("get_activities() returned non-JSON (likely an error string): %s\n", actRes)
|
||||
return
|
||||
}
|
||||
pretty, _ := json.MarshalIndent(activities, "", " ")
|
||||
fmt.Printf("get_activities() parsed OK, %d activities, %d bytes:\n%s\n", len(activities), len(actRes), truncate(string(pretty), 4000))
|
||||
|
||||
if len(activities) == 0 {
|
||||
fmt.Println("\nno activities in range, skipping get_activity_details")
|
||||
return
|
||||
}
|
||||
|
||||
// Prefer a running activity if one is present, for the most relevant lap/split shape.
|
||||
chosen := activities[0]
|
||||
for _, a := range activities {
|
||||
if at, ok := a["activityType"].(map[string]any); ok {
|
||||
if tk, _ := at["typeKey"].(string); tk == "running" || tk == "trail_running" || tk == "treadmill_running" {
|
||||
chosen = a
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
var activityID string
|
||||
switch v := chosen["activityId"].(type) {
|
||||
case float64:
|
||||
activityID = strconv.FormatFloat(v, 'f', -1, 64)
|
||||
default:
|
||||
activityID = fmt.Sprint(v)
|
||||
}
|
||||
fmt.Printf("\ncalling get_activity_details(activity_id=%s) [activityName=%v]...\n", activityID, chosen["activityName"])
|
||||
|
||||
splitsRes, err := callTool(ctx, c, "get_activity_splits", map[string]any{"activity_id": activityID})
|
||||
if err != nil {
|
||||
log.Fatalf("get_activity_splits call failed: %v", err)
|
||||
}
|
||||
|
||||
var splits map[string]any
|
||||
if err := json.Unmarshal([]byte(splitsRes), &splits); err != nil {
|
||||
fmt.Printf("get_activity_splits() returned non-JSON: %s\n", splitsRes)
|
||||
return
|
||||
}
|
||||
keys := make([]string, 0, len(splits))
|
||||
for k := range splits {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
fmt.Printf("get_activity_splits() top-level keys: %v\n", keys)
|
||||
prettySplits, _ := json.MarshalIndent(splits, "", " ")
|
||||
fmt.Printf("get_activity_splits() parsed OK, %d bytes:\n%s\n", len(splitsRes), truncate(string(prettySplits), 8000))
|
||||
|
||||
fmt.Println("\ncalling get_activity_details() again to inspect metricDescriptors + a small metrics sample...")
|
||||
detailsRes, err := callTool(ctx, c, "get_activity_details", map[string]any{"activity_id": activityID})
|
||||
if err != nil {
|
||||
log.Fatalf("get_activity_details call failed: %v", err)
|
||||
}
|
||||
var details map[string]any
|
||||
if err := json.Unmarshal([]byte(detailsRes), &details); err != nil {
|
||||
fmt.Printf("get_activity_details() returned non-JSON: %s\n", detailsRes)
|
||||
return
|
||||
}
|
||||
if descriptors, ok := details["metricDescriptors"]; ok {
|
||||
pretty, _ := json.MarshalIndent(descriptors, "", " ")
|
||||
fmt.Printf("metricDescriptors:\n%s\n", pretty)
|
||||
} else {
|
||||
fmt.Println("no metricDescriptors key found")
|
||||
}
|
||||
if rows, ok := details["activityDetailMetrics"].([]any); ok && len(rows) > 0 {
|
||||
sampleN := 5
|
||||
if len(rows) < sampleN {
|
||||
sampleN = len(rows)
|
||||
}
|
||||
pretty, _ := json.MarshalIndent(rows[:sampleN], "", " ")
|
||||
fmt.Printf("activityDetailMetrics sample (first %d of %d rows):\n%s\n", sampleN, len(rows), pretty)
|
||||
}
|
||||
}
|
||||
|
||||
func callTool(ctx context.Context, c *client.Client, name string, args map[string]any) (string, error) {
|
||||
req := mcp.CallToolRequest{}
|
||||
req.Params.Name = name
|
||||
req.Params.Arguments = args
|
||||
|
||||
res, err := c.CallTool(ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if res.IsError {
|
||||
return "", fmt.Errorf("tool %s returned an error result", name)
|
||||
}
|
||||
var out string
|
||||
for _, content := range res.Content {
|
||||
if tc, ok := content.(mcp.TextContent); ok {
|
||||
out += tc.Text
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func containsMFAPrompt(s string) bool {
|
||||
for _, needle := range []string{"MFA required", "MFA code", "complete_mfa"} {
|
||||
if len(s) >= len(needle) {
|
||||
for i := 0; i+len(needle) <= len(s); i++ {
|
||||
if s[i:i+len(needle)] == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func truncate(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n] + "...(truncated)"
|
||||
}
|
||||
@@ -6,7 +6,6 @@ require (
|
||||
github.com/coreos/go-oidc/v3 v3.20.0
|
||||
github.com/go-chi/chi/v5 v5.3.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/mark3labs/mcp-go v0.56.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
modernc.org/sqlite v1.53.0
|
||||
)
|
||||
@@ -14,16 +13,11 @@ require (
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/google/jsonschema-go v0.4.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
modernc.org/libc v1.73.4 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
|
||||
@@ -1,53 +1,25 @@
|
||||
github.com/coreos/go-oidc/v3 v3.20.0 h1:EtE0WIBHk03N+DqGkY4+UONzzZHk7amKt6IyNd7OsZE=
|
||||
github.com/coreos/go-oidc/v3 v3.20.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
|
||||
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
|
||||
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mark3labs/mcp-go v0.56.0 h1:7aCj2wODCskMi08f923ADG+EfELZBdiKILny415cIS8=
|
||||
github.com/mark3labs/mcp-go v0.56.0/go.mod h1:+8WclSK1ZUweCP3hvktSji8n8ABG/95QaEkeVE/Uwas=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
@@ -57,12 +29,8 @@ golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c=
|
||||
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
|
||||
|
||||
Reference in New Issue
Block a user