blob: 83048b94982743c5933950337c385f886f5459e3 [file] [log] [blame]
Earl Lee2e463fb2025-04-17 11:22:22 -07001// Package claudetool provides tools for Claude AI models.
2//
3// When adding, removing, or modifying tools in this package,
4// remember to update the tool display template in termui/termui.go
5// to ensure proper tool output formatting.
6package claudetool
7
8import (
9 "bytes"
10 "context"
11 "encoding/json"
12 "fmt"
13 "log/slog"
14 "net/http"
15 "os"
16 "time"
17)
18
19type workingDirCtxKeyType string
20
21const workingDirCtxKey workingDirCtxKeyType = "workingDir"
22
23func WithWorkingDir(ctx context.Context, wd string) context.Context {
24 return context.WithValue(ctx, workingDirCtxKey, wd)
25}
26
27func WorkingDir(ctx context.Context) string {
28 // If cmd.Dir is empty, it uses the current working directory,
29 // so we can use that as a fallback.
30 wd, _ := ctx.Value(workingDirCtxKey).(string)
31 return wd
32}
33
34// sendTelemetry posts debug data to an internal logging server.
35// It is meant for use by people developing sketch and is disabled by default.
36// This is a best-effort operation; errors are logged but not returned.
37func sendTelemetry(ctx context.Context, typ string, data any) {
38 telemetryEndpoint := os.Getenv("SKETCH_TELEMETRY_ENDPOINT")
39 if telemetryEndpoint == "" {
40 return
41 }
42 err := doPostTelemetry(ctx, telemetryEndpoint, typ, data)
43 if err != nil {
44 slog.DebugContext(ctx, "failed to send JSON to server", "type", typ, "error", err)
45 }
46}
47
48func doPostTelemetry(ctx context.Context, telemetryEndpoint, typ string, data any) error {
49 jsonData, err := json.Marshal(data)
50 if err != nil {
51 return fmt.Errorf("failed to marshal %#v as JSON: %w", data, err)
52 }
53 timestamp := time.Now().Unix()
54 url := fmt.Sprintf(telemetryEndpoint+"/%s_%d.json", typ, timestamp)
55 req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
56 if err != nil {
57 return fmt.Errorf("failed to create HTTP request for %s: %w", typ, err)
58 }
59 req.Header.Set("Content-Type", "application/json")
60
61 resp, err := http.DefaultClient.Do(req)
62 if err != nil {
63 return fmt.Errorf("failed to send %s JSON to server: %w", typ, err)
64 }
65 defer resp.Body.Close()
66
67 if resp.StatusCode/100 != 2 {
68 return fmt.Errorf("server returned non-success status for %s: %d", typ, resp.StatusCode)
69 }
70 slog.DebugContext(ctx, "successfully sent JSON to server", "file_type", typ, "url", url)
71 return nil
72}