@tanagram/cli 0.4.18 → 0.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/config.go +21 -10
- package/commands/config_test.go +10 -9
- package/commands/list.go +14 -1
- package/commands/login.go +57 -1
- package/commands/login_test.go +132 -0
- package/commands/run.go +194 -156
- package/commands/snapshot.go +11 -1
- package/commands/snapshot_test.go +2 -1
- package/commands/sync.go +65 -29
- package/commands/sync_policies.go +14 -1
- package/dist/npm/darwin-arm64/tanagram +0 -0
- package/dist/npm/darwin-x64/tanagram +0 -0
- package/dist/npm/linux-arm64/tanagram +0 -0
- package/dist/npm/linux-x64/tanagram +0 -0
- package/dist/npm/tanagram_0.4.20_darwin_amd64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.20_darwin_arm64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.20_linux_amd64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.20_linux_arm64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.20_windows_amd64.zip +0 -0
- package/dist/npm/win32-x64/tanagram.exe +0 -0
- package/go.mod +6 -3
- package/go.sum +18 -2
- package/main.go +116 -22
- package/package.json +1 -1
- package/utils/sentry.go +44 -0
- package/dist/npm/tanagram_0.4.18_darwin_amd64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_darwin_arm64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_linux_amd64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_linux_arm64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_windows_amd64.zip +0 -0
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
package commands
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"context"
|
|
4
5
|
"fmt"
|
|
5
6
|
|
|
7
|
+
"github.com/getsentry/sentry-go"
|
|
6
8
|
"github.com/tanagram/cli/api"
|
|
7
9
|
"github.com/tanagram/cli/storage"
|
|
8
10
|
)
|
|
9
11
|
|
|
10
12
|
// SyncPolicies fetches policies from Tanagram API and saves them locally
|
|
11
|
-
func SyncPolicies() error {
|
|
13
|
+
func SyncPolicies(ctx context.Context) error {
|
|
14
|
+
span := sentry.StartSpan(ctx, "command.sync_policies")
|
|
15
|
+
defer span.Finish()
|
|
16
|
+
|
|
12
17
|
// Find git root
|
|
18
|
+
findGitRootSpan := sentry.StartSpan(span.Context(), "storage.find_git_root")
|
|
13
19
|
gitRoot, err := storage.FindGitRoot()
|
|
20
|
+
findGitRootSpan.Finish()
|
|
14
21
|
if err != nil {
|
|
15
22
|
return fmt.Errorf("not in a git repository: %w", err)
|
|
16
23
|
}
|
|
@@ -18,14 +25,18 @@ func SyncPolicies() error {
|
|
|
18
25
|
fmt.Println("Syncing policies from Tanagram...")
|
|
19
26
|
|
|
20
27
|
// Create API client
|
|
28
|
+
createClientSpan := sentry.StartSpan(span.Context(), "api.new_client")
|
|
21
29
|
client, err := api.NewAPIClient()
|
|
30
|
+
createClientSpan.Finish()
|
|
22
31
|
if err != nil {
|
|
23
32
|
return err
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
// Fetch policies from API
|
|
27
36
|
fmt.Println("Fetching policies from API...")
|
|
37
|
+
fetchPoliciesSpan := sentry.StartSpan(span.Context(), "api.get_policies")
|
|
28
38
|
response, err := client.GetPolicies()
|
|
39
|
+
fetchPoliciesSpan.Finish()
|
|
29
40
|
if err != nil {
|
|
30
41
|
return err
|
|
31
42
|
}
|
|
@@ -49,6 +60,7 @@ func SyncPolicies() error {
|
|
|
49
60
|
fmt.Printf("Policies apply to %d repositories\n", len(repoMap))
|
|
50
61
|
|
|
51
62
|
// Save policies for each repository
|
|
63
|
+
savePoliciesSpan := sentry.StartSpan(span.Context(), "storage.save_policies")
|
|
52
64
|
cloudStorage := storage.NewCloudPolicyStorage(gitRoot)
|
|
53
65
|
savedCount := 0
|
|
54
66
|
|
|
@@ -66,6 +78,7 @@ func SyncPolicies() error {
|
|
|
66
78
|
savedCount++
|
|
67
79
|
fmt.Printf(" ✓ Saved %d policies for %s\n", len(policies), repoKey)
|
|
68
80
|
}
|
|
81
|
+
savePoliciesSpan.Finish()
|
|
69
82
|
|
|
70
83
|
// Save metadata
|
|
71
84
|
orgID := ""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/go.mod
CHANGED
|
@@ -6,8 +6,14 @@ require (
|
|
|
6
6
|
github.com/anthropics/anthropic-sdk-go v1.17.0
|
|
7
7
|
github.com/charmbracelet/bubbletea v1.3.10
|
|
8
8
|
github.com/charmbracelet/lipgloss v1.1.0
|
|
9
|
+
github.com/getsentry/sentry-go v0.40.0
|
|
10
|
+
github.com/getsentry/sentry-go/slog v0.40.0
|
|
11
|
+
github.com/golang-jwt/jwt/v5 v5.3.0
|
|
12
|
+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
|
9
13
|
github.com/posthog/posthog-go v1.6.12
|
|
10
14
|
github.com/shirou/gopsutil/v3 v3.24.5
|
|
15
|
+
github.com/zalando/go-keyring v0.2.6
|
|
16
|
+
golang.org/x/term v0.37.0
|
|
11
17
|
)
|
|
12
18
|
|
|
13
19
|
require (
|
|
@@ -30,7 +36,6 @@ require (
|
|
|
30
36
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
|
31
37
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
32
38
|
github.com/muesli/termenv v0.16.0 // indirect
|
|
33
|
-
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
|
34
39
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
|
35
40
|
github.com/rivo/uniseg v0.4.7 // indirect
|
|
36
41
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
|
@@ -42,8 +47,6 @@ require (
|
|
|
42
47
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
|
43
48
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
44
49
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
|
45
|
-
github.com/zalando/go-keyring v0.2.6 // indirect
|
|
46
50
|
golang.org/x/sys v0.38.0 // indirect
|
|
47
|
-
golang.org/x/term v0.37.0 // indirect
|
|
48
51
|
golang.org/x/text v0.27.0 // indirect
|
|
49
52
|
)
|
package/go.sum
CHANGED
|
@@ -22,13 +22,23 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|
|
22
22
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
23
23
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
|
24
24
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
|
25
|
+
github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo=
|
|
26
|
+
github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
|
|
27
|
+
github.com/getsentry/sentry-go/slog v0.40.0 h1:uR2EPL9w6uHw3XB983IAqzqM9mP+fjJpNY9kfob3/Z8=
|
|
28
|
+
github.com/getsentry/sentry-go/slog v0.40.0/go.mod h1:ArRaP+0rsbnJGyvZwYDo/vDQT/YBbOQeOlO+DGW+F9s=
|
|
29
|
+
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
|
30
|
+
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
|
25
31
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
26
32
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
|
27
33
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
|
28
34
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
35
|
+
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
|
36
|
+
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
|
29
37
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
30
38
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
31
39
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
40
|
+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
|
41
|
+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
|
32
42
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
|
33
43
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
|
34
44
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
@@ -47,8 +57,12 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
|
|
|
47
57
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
48
58
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
|
49
59
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
|
60
|
+
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
|
61
|
+
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
|
50
62
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
|
51
63
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
|
64
|
+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
65
|
+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
52
66
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
53
67
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
54
68
|
github.com/posthog/posthog-go v1.6.12 h1:rsOBL/YdMfLJtOVjKJLgdzYmvaL3aIW6IVbAteSe+aI=
|
|
@@ -64,6 +78,8 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
|
|
|
64
78
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
|
65
79
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
|
66
80
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
|
81
|
+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|
82
|
+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
67
83
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
68
84
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
69
85
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
|
@@ -86,6 +102,8 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
|
|
86
102
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
87
103
|
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
|
|
88
104
|
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
|
|
105
|
+
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|
106
|
+
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|
89
107
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
|
90
108
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
|
91
109
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
@@ -95,8 +113,6 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
|
95
113
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
96
114
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
97
115
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
98
|
-
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
|
99
|
-
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
100
116
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
|
101
117
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
102
118
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
package/main.go
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"context"
|
|
4
5
|
"encoding/json"
|
|
5
6
|
"flag"
|
|
6
7
|
"fmt"
|
|
@@ -9,7 +10,10 @@ import (
|
|
|
9
10
|
"os"
|
|
10
11
|
"path/filepath"
|
|
11
12
|
"strings"
|
|
13
|
+
"time"
|
|
12
14
|
|
|
15
|
+
"github.com/getsentry/sentry-go"
|
|
16
|
+
sentryslog "github.com/getsentry/sentry-go/slog"
|
|
13
17
|
"github.com/tanagram/cli/commands"
|
|
14
18
|
"github.com/tanagram/cli/metrics"
|
|
15
19
|
"github.com/tanagram/cli/tui"
|
|
@@ -30,14 +34,33 @@ func main() {
|
|
|
30
34
|
metrics.SetVersion(Version)
|
|
31
35
|
metrics.Init()
|
|
32
36
|
|
|
37
|
+
if err := sentry.Init(sentry.ClientOptions{
|
|
38
|
+
Dsn: "https://a967718dd129e143907fe01b4e80cad2@o4509017064472576.ingest.us.sentry.io/4510649104007168",
|
|
39
|
+
Release: "tanagram-cli@" + Version,
|
|
40
|
+
Environment: getEnvironment(),
|
|
41
|
+
EnableTracing: true,
|
|
42
|
+
// TOOD: Configure sample rate based on environment (1.0 for dev; 0.1 for prod)
|
|
43
|
+
// Depends on TAN-1968
|
|
44
|
+
TracesSampleRate: 0.1,
|
|
45
|
+
EnableLogs: true,
|
|
46
|
+
}); err != nil {
|
|
47
|
+
slog.Warn("Sentry initialization failed", "error", err)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
ctx := sentry.SetHubOnContext(context.Background(), sentry.CurrentHub())
|
|
51
|
+
|
|
33
52
|
exitCode := 0
|
|
34
53
|
defer func() {
|
|
54
|
+
if r := recover(); r != nil {
|
|
55
|
+
sentry.CurrentHub().Recover(r)
|
|
56
|
+
slog.Error("Panic recovered", "panic", r)
|
|
57
|
+
exitCode = 1
|
|
58
|
+
}
|
|
59
|
+
sentry.Flush(2 * time.Second)
|
|
35
60
|
metrics.Track("cli.exit", map[string]interface{}{
|
|
36
61
|
"exit_code": exitCode,
|
|
37
62
|
})
|
|
38
63
|
metrics.Close()
|
|
39
|
-
// os.Exit immediately exits without calling other `defer`s, so we need to group these two statements
|
|
40
|
-
// and call them in the right order.
|
|
41
64
|
os.Exit(exitCode)
|
|
42
65
|
}()
|
|
43
66
|
|
|
@@ -54,6 +77,12 @@ func main() {
|
|
|
54
77
|
"subcommand": subcommand,
|
|
55
78
|
})
|
|
56
79
|
|
|
80
|
+
tx := sentry.StartTransaction(ctx, fmt.Sprintf("cli.%s", subcommand),
|
|
81
|
+
sentry.WithOpName("command"),
|
|
82
|
+
)
|
|
83
|
+
defer tx.Finish()
|
|
84
|
+
ctx = tx.Context()
|
|
85
|
+
|
|
57
86
|
var logOutput io.Writer = os.Stderr
|
|
58
87
|
if utils.GetParentProcess() == "claude" {
|
|
59
88
|
// We use "exit-code 2" behavior for claude: https://code.claude.com/docs/en/hooks#simple:-exit-code
|
|
@@ -78,11 +107,16 @@ func main() {
|
|
|
78
107
|
logOutput = logFile
|
|
79
108
|
}
|
|
80
109
|
isTTY := term.IsTerminal(int(os.Stdout.Fd()))
|
|
81
|
-
logger := newLogger(*flagLogLevel, *flagLogFormat, logOutput, isTTY)
|
|
110
|
+
logger := newLogger(ctx, *flagLogLevel, *flagLogFormat, logOutput, isTTY)
|
|
82
111
|
slog.SetDefault(logger)
|
|
83
112
|
|
|
113
|
+
utils.SetGlobalTags(map[string]string{
|
|
114
|
+
"parent_process": utils.GetParentProcess(),
|
|
115
|
+
"is_tty": fmt.Sprintf("%t", isTTY),
|
|
116
|
+
})
|
|
117
|
+
|
|
84
118
|
slog.Info("Running CLI with args",
|
|
85
|
-
"args", os.Args[1:],
|
|
119
|
+
"args", strings.Join(os.Args[1:], " "),
|
|
86
120
|
)
|
|
87
121
|
|
|
88
122
|
// THIS IS A HUGE HACK
|
|
@@ -94,7 +128,7 @@ func main() {
|
|
|
94
128
|
// TODO: handle 0 or multiple workspace_roots
|
|
95
129
|
if utils.GetParentProcess() == "cursor" {
|
|
96
130
|
input, err := io.ReadAll(os.Stdin)
|
|
97
|
-
if err == nil {
|
|
131
|
+
if err == nil && len(input) > 0 {
|
|
98
132
|
var payload struct {
|
|
99
133
|
WorkspaceRoots []string `json:"workspace_roots"`
|
|
100
134
|
}
|
|
@@ -115,39 +149,39 @@ func main() {
|
|
|
115
149
|
"command": "run",
|
|
116
150
|
})
|
|
117
151
|
// Auto-setup hooks on first run
|
|
118
|
-
if err := commands.EnsureHooksConfigured(); err != nil {
|
|
152
|
+
if err := commands.EnsureHooksConfigured(ctx); err != nil {
|
|
119
153
|
slog.Error("Failed to configure hooks", "error", err)
|
|
120
154
|
exitCode = 1
|
|
121
155
|
return
|
|
122
156
|
}
|
|
123
|
-
err = commands.Run()
|
|
157
|
+
err = commands.Run(ctx)
|
|
124
158
|
case "snapshot":
|
|
125
159
|
metrics.Track("cli.command.execute", map[string]interface{}{
|
|
126
160
|
"command": "snapshot",
|
|
127
161
|
})
|
|
128
|
-
err = commands.Snapshot()
|
|
162
|
+
err = commands.Snapshot(ctx)
|
|
129
163
|
case "sync":
|
|
130
164
|
metrics.Track("cli.command.execute", map[string]interface{}{
|
|
131
165
|
"command": "sync",
|
|
132
166
|
})
|
|
133
167
|
// Auto-setup hooks on first run
|
|
134
|
-
if err := commands.EnsureHooksConfigured(); err != nil {
|
|
168
|
+
if err := commands.EnsureHooksConfigured(ctx); err != nil {
|
|
135
169
|
slog.Error("Failed to configure hooks", "error", err)
|
|
136
170
|
exitCode = 1
|
|
137
171
|
return
|
|
138
172
|
}
|
|
139
|
-
err = commands.Sync()
|
|
173
|
+
err = commands.Sync(ctx)
|
|
140
174
|
case "list":
|
|
141
175
|
metrics.Track("cli.command.execute", map[string]interface{}{
|
|
142
176
|
"command": "list",
|
|
143
177
|
})
|
|
144
178
|
// Auto-setup hooks on first run
|
|
145
|
-
if err := commands.EnsureHooksConfigured(); err != nil {
|
|
179
|
+
if err := commands.EnsureHooksConfigured(ctx); err != nil {
|
|
146
180
|
slog.Error("Failed to configure hooks", "error", err)
|
|
147
181
|
exitCode = 1
|
|
148
182
|
return
|
|
149
183
|
}
|
|
150
|
-
err = commands.List()
|
|
184
|
+
err = commands.List(ctx)
|
|
151
185
|
case "config":
|
|
152
186
|
// Handle config subcommands
|
|
153
187
|
if len(os.Args) < 3 {
|
|
@@ -170,7 +204,7 @@ func main() {
|
|
|
170
204
|
err = pathErr
|
|
171
205
|
break
|
|
172
206
|
}
|
|
173
|
-
err = commands.ConfigClaude(settingsPath)
|
|
207
|
+
err = commands.ConfigClaude(ctx, settingsPath)
|
|
174
208
|
case "cursor":
|
|
175
209
|
metrics.Track("cli.command.execute", map[string]interface{}{
|
|
176
210
|
"command": "config.cursor",
|
|
@@ -180,12 +214,12 @@ func main() {
|
|
|
180
214
|
err = pathErr
|
|
181
215
|
break
|
|
182
216
|
}
|
|
183
|
-
err = commands.ConfigCursor(hooksPath)
|
|
217
|
+
err = commands.ConfigCursor(ctx, hooksPath)
|
|
184
218
|
case "list":
|
|
185
219
|
metrics.Track("cli.command.execute", map[string]interface{}{
|
|
186
220
|
"command": "config.list",
|
|
187
221
|
})
|
|
188
|
-
err = commands.ConfigList()
|
|
222
|
+
err = commands.ConfigList(ctx)
|
|
189
223
|
default:
|
|
190
224
|
fmt.Fprintf(os.Stderr, "Unknown config subcommand: %s\n", subCmd)
|
|
191
225
|
exitCode = 1
|
|
@@ -216,12 +250,12 @@ func main() {
|
|
|
216
250
|
metrics.Track("cli.command.execute", map[string]interface{}{
|
|
217
251
|
"command": "login",
|
|
218
252
|
})
|
|
219
|
-
err = commands.Login()
|
|
253
|
+
err = commands.Login(ctx)
|
|
220
254
|
case "sync-policies":
|
|
221
255
|
metrics.Track("cli.command.execute", map[string]interface{}{
|
|
222
256
|
"command": "sync-policies",
|
|
223
257
|
})
|
|
224
|
-
err = commands.SyncPolicies()
|
|
258
|
+
err = commands.SyncPolicies(ctx)
|
|
225
259
|
case "version", "-v", "--version":
|
|
226
260
|
fmt.Println(Version)
|
|
227
261
|
return
|
|
@@ -236,6 +270,9 @@ func main() {
|
|
|
236
270
|
}
|
|
237
271
|
|
|
238
272
|
if err != nil {
|
|
273
|
+
utils.CaptureError(err, map[string]string{
|
|
274
|
+
"command": subcommand,
|
|
275
|
+
})
|
|
239
276
|
metrics.Track("cli.command.error", map[string]interface{}{
|
|
240
277
|
"command": subcommand,
|
|
241
278
|
"error": err.Error(),
|
|
@@ -246,6 +283,13 @@ func main() {
|
|
|
246
283
|
}
|
|
247
284
|
}
|
|
248
285
|
|
|
286
|
+
func getEnvironment() string {
|
|
287
|
+
if env := os.Getenv("SENTRY_ENVIRONMENT"); env != "" {
|
|
288
|
+
return env
|
|
289
|
+
}
|
|
290
|
+
return "unconfigured"
|
|
291
|
+
}
|
|
292
|
+
|
|
249
293
|
func printHelp() {
|
|
250
294
|
help := `Tanagram - Policy enforcement for git changes
|
|
251
295
|
|
|
@@ -301,7 +345,7 @@ HOOK WORKFLOW:
|
|
|
301
345
|
fmt.Print(help)
|
|
302
346
|
}
|
|
303
347
|
|
|
304
|
-
func newLogger(levelStr, format string, output io.Writer, isTTY bool) *slog.Logger {
|
|
348
|
+
func newLogger(ctx context.Context, levelStr, format string, output io.Writer, isTTY bool) *slog.Logger {
|
|
305
349
|
var lvl slog.Level
|
|
306
350
|
switch strings.ToLower(levelStr) {
|
|
307
351
|
case "debug":
|
|
@@ -320,10 +364,10 @@ func newLogger(levelStr, format string, output io.Writer, isTTY bool) *slog.Logg
|
|
|
320
364
|
Level: lvl,
|
|
321
365
|
}
|
|
322
366
|
|
|
323
|
-
var
|
|
367
|
+
var baseHandler slog.Handler
|
|
324
368
|
switch strings.ToLower(format) {
|
|
325
369
|
case "json":
|
|
326
|
-
|
|
370
|
+
baseHandler = slog.NewJSONHandler(output, opts)
|
|
327
371
|
default:
|
|
328
372
|
if isTTY {
|
|
329
373
|
opts.ReplaceAttr = func(groups []string, a slog.Attr) slog.Attr {
|
|
@@ -333,10 +377,60 @@ func newLogger(levelStr, format string, output io.Writer, isTTY bool) *slog.Logg
|
|
|
333
377
|
return a
|
|
334
378
|
}
|
|
335
379
|
}
|
|
336
|
-
|
|
380
|
+
baseHandler = slog.NewTextHandler(output, opts)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
sentryHandler := sentryslog.Option{
|
|
384
|
+
EventLevel: []slog.Level{slog.LevelError, sentryslog.LevelFatal},
|
|
385
|
+
// LogLevel defaults to `[]slog.Level{slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError, LevelFatal}`, which seems reasonable
|
|
386
|
+
AddSource: true,
|
|
387
|
+
}.NewSentryHandler(ctx)
|
|
388
|
+
|
|
389
|
+
return slog.New(newMultiHandler(baseHandler, sentryHandler))
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
type multiHandler struct {
|
|
393
|
+
handlers []slog.Handler
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
func newMultiHandler(handlers ...slog.Handler) *multiHandler {
|
|
397
|
+
return &multiHandler{handlers: handlers}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
func (h *multiHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
|
401
|
+
for _, handler := range h.handlers {
|
|
402
|
+
if handler.Enabled(ctx, level) {
|
|
403
|
+
return true
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return false
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
func (h *multiHandler) Handle(ctx context.Context, r slog.Record) error {
|
|
410
|
+
for _, handler := range h.handlers {
|
|
411
|
+
if handler.Enabled(ctx, r.Level) {
|
|
412
|
+
if err := handler.Handle(ctx, r); err != nil {
|
|
413
|
+
return err
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return nil
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
func (h *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
421
|
+
handlers := make([]slog.Handler, len(h.handlers))
|
|
422
|
+
for i, handler := range h.handlers {
|
|
423
|
+
handlers[i] = handler.WithAttrs(attrs)
|
|
337
424
|
}
|
|
425
|
+
return &multiHandler{handlers: handlers}
|
|
426
|
+
}
|
|
338
427
|
|
|
339
|
-
|
|
428
|
+
func (h *multiHandler) WithGroup(name string) slog.Handler {
|
|
429
|
+
handlers := make([]slog.Handler, len(h.handlers))
|
|
430
|
+
for i, handler := range h.handlers {
|
|
431
|
+
handlers[i] = handler.WithGroup(name)
|
|
432
|
+
}
|
|
433
|
+
return &multiHandler{handlers: handlers}
|
|
340
434
|
}
|
|
341
435
|
|
|
342
436
|
func createLogFile(logFilePath string) (string, error) {
|
package/package.json
CHANGED
package/utils/sentry.go
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package utils
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"github.com/getsentry/sentry-go"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
// CaptureError captures an error with additional context to Sentry
|
|
8
|
+
func CaptureError(err error, ctx map[string]string) {
|
|
9
|
+
sentry.WithScope(func(scope *sentry.Scope) {
|
|
10
|
+
for k, v := range ctx {
|
|
11
|
+
scope.SetTag(k, v)
|
|
12
|
+
}
|
|
13
|
+
sentry.CaptureException(err)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// AddBreadcrumb adds a breadcrumb to the current Sentry scope
|
|
18
|
+
func AddBreadcrumb(category, message string, level sentry.Level, data map[string]interface{}) {
|
|
19
|
+
sentry.AddBreadcrumb(&sentry.Breadcrumb{
|
|
20
|
+
Category: category,
|
|
21
|
+
Message: message,
|
|
22
|
+
Level: level,
|
|
23
|
+
Data: data,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// SetUserContext sets the user context for Sentry
|
|
28
|
+
func SetUserContext(userID, email string) {
|
|
29
|
+
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
|
30
|
+
scope.SetUser(sentry.User{
|
|
31
|
+
ID: userID,
|
|
32
|
+
Email: email,
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// SetGlobalTags sets global tags for all Sentry events
|
|
38
|
+
func SetGlobalTags(tags map[string]string) {
|
|
39
|
+
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
|
40
|
+
for k, v := range tags {
|
|
41
|
+
scope.SetTag(k, v)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|