Initial commit
diff --git a/claudetool/shared.go b/claudetool/shared.go
new file mode 100644
index 0000000..83048b9
--- /dev/null
+++ b/claudetool/shared.go
@@ -0,0 +1,72 @@
+// Package claudetool provides tools for Claude AI models.
+//
+// When adding, removing, or modifying tools in this package,
+// remember to update the tool display template in termui/termui.go
+// to ensure proper tool output formatting.
+package claudetool
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "os"
+ "time"
+)
+
+type workingDirCtxKeyType string
+
+const workingDirCtxKey workingDirCtxKeyType = "workingDir"
+
+func WithWorkingDir(ctx context.Context, wd string) context.Context {
+ return context.WithValue(ctx, workingDirCtxKey, wd)
+}
+
+func WorkingDir(ctx context.Context) string {
+ // If cmd.Dir is empty, it uses the current working directory,
+ // so we can use that as a fallback.
+ wd, _ := ctx.Value(workingDirCtxKey).(string)
+ return wd
+}
+
+// sendTelemetry posts debug data to an internal logging server.
+// It is meant for use by people developing sketch and is disabled by default.
+// This is a best-effort operation; errors are logged but not returned.
+func sendTelemetry(ctx context.Context, typ string, data any) {
+ telemetryEndpoint := os.Getenv("SKETCH_TELEMETRY_ENDPOINT")
+ if telemetryEndpoint == "" {
+ return
+ }
+ err := doPostTelemetry(ctx, telemetryEndpoint, typ, data)
+ if err != nil {
+ slog.DebugContext(ctx, "failed to send JSON to server", "type", typ, "error", err)
+ }
+}
+
+func doPostTelemetry(ctx context.Context, telemetryEndpoint, typ string, data any) error {
+ jsonData, err := json.Marshal(data)
+ if err != nil {
+ return fmt.Errorf("failed to marshal %#v as JSON: %w", data, err)
+ }
+ timestamp := time.Now().Unix()
+ url := fmt.Sprintf(telemetryEndpoint+"/%s_%d.json", typ, timestamp)
+ req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
+ if err != nil {
+ return fmt.Errorf("failed to create HTTP request for %s: %w", typ, err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return fmt.Errorf("failed to send %s JSON to server: %w", typ, err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode/100 != 2 {
+ return fmt.Errorf("server returned non-success status for %s: %d", typ, resp.StatusCode)
+ }
+ slog.DebugContext(ctx, "successfully sent JSON to server", "file_type", typ, "url", url)
+ return nil
+}