@tanagram/cli 0.1.21 → 0.1.24
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/README.md +23 -45
- package/commands/run.go +21 -0
- package/go.mod +3 -1
- package/go.sum +6 -0
- package/install.js +8 -1
- package/main.go +18 -0
- package/metrics/metrics.go +116 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -97,10 +97,31 @@ tanagram help
|
|
|
97
97
|
- **`help`** - Show usage information
|
|
98
98
|
|
|
99
99
|
### Claude Code Hook
|
|
100
|
-
You can install the CLI as a Claude Code [hook](https://code.claude.com/docs/en/hooks) to have Claude automatically iterate on Tanagram's output.
|
|
100
|
+
You can install the CLI as a Claude Code [hook](https://code.claude.com/docs/en/hooks) to have Claude automatically iterate on Tanagram's output.
|
|
101
|
+
|
|
102
|
+
Add this to your `~/.claude/settings.json` (user settings) or `.claude/settings.json` (project settings):
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
"hooks": {
|
|
106
|
+
"PostToolUse": [
|
|
107
|
+
{
|
|
108
|
+
"matcher": "Edit|Write",
|
|
109
|
+
"hooks": [
|
|
110
|
+
{
|
|
111
|
+
"type": "command",
|
|
112
|
+
"command": "tanagram"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For example, your full `settings.json` file might look like this:
|
|
101
121
|
|
|
102
122
|
```json
|
|
103
123
|
{
|
|
124
|
+
"alwaysThinkingEnabled": true,
|
|
104
125
|
"hooks": {
|
|
105
126
|
"PostToolUse": [
|
|
106
127
|
{
|
|
@@ -215,47 +236,4 @@ Then run `tanagram` to enforce them locally!
|
|
|
215
236
|
|
|
216
237
|
---
|
|
217
238
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
221
|
-
|
|
222
|
-
### Development Setup
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
# Clone the repository
|
|
226
|
-
git clone https://github.com/tanagram/cli.git
|
|
227
|
-
cd cli
|
|
228
|
-
|
|
229
|
-
# Install dependencies and build
|
|
230
|
-
npm install
|
|
231
|
-
|
|
232
|
-
# Run tests
|
|
233
|
-
npm test
|
|
234
|
-
|
|
235
|
-
# Build manually
|
|
236
|
-
go build -o bin/tanagram .
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Publishing a Release
|
|
240
|
-
|
|
241
|
-
To publish a new version:
|
|
242
|
-
|
|
243
|
-
```bash
|
|
244
|
-
# 1. Update version in package.json
|
|
245
|
-
npm version patch # or minor, or major
|
|
246
|
-
|
|
247
|
-
# 2. Commit and tag
|
|
248
|
-
git add package.json package-lock.json
|
|
249
|
-
git commit -m "Bump version to $(node -p "require('./package.json').version")"
|
|
250
|
-
git tag v$(node -p "require('./package.json').version")
|
|
251
|
-
git push && git push origin --tags
|
|
252
|
-
|
|
253
|
-
# 3. Publish to npm
|
|
254
|
-
npm publish
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
**Note:** The Go compiler is automatically downloaded and used during `npm install` via the `go-bin` package, so no pre-built binaries needed!
|
|
258
|
-
|
|
259
|
-
## License
|
|
260
|
-
|
|
261
|
-
MIT
|
|
239
|
+
Built by [@fluttermatt](https://x.com/fluttermatt) and the Tanagram team. Talk to us [on Twitter](https://x.com/tanagram_) or email: founders AT tanagram.ai
|
package/commands/run.go
CHANGED
|
@@ -11,6 +11,7 @@ import (
|
|
|
11
11
|
"github.com/tanagram/cli/checker"
|
|
12
12
|
"github.com/tanagram/cli/extractor"
|
|
13
13
|
"github.com/tanagram/cli/git"
|
|
14
|
+
"github.com/tanagram/cli/metrics"
|
|
14
15
|
"github.com/tanagram/cli/parser"
|
|
15
16
|
"github.com/tanagram/cli/storage"
|
|
16
17
|
)
|
|
@@ -72,6 +73,7 @@ func Run() error {
|
|
|
72
73
|
if len(filesToSync) > 0 {
|
|
73
74
|
fmt.Printf("\nSyncing policies with LLM (processing %d changed file(s) in parallel)...\n", len(filesToSync))
|
|
74
75
|
|
|
76
|
+
syncStart := time.Now()
|
|
75
77
|
ctx := context.Background()
|
|
76
78
|
|
|
77
79
|
// Result type for collecting sync results
|
|
@@ -164,7 +166,15 @@ func Run() error {
|
|
|
164
166
|
return fmt.Errorf("failed to save cache: %w", err)
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
syncDuration := time.Since(syncStart)
|
|
167
170
|
fmt.Printf("\n✓ Synced %d policies from %d changed file(s)\n", totalPolicies, len(filesToSync))
|
|
171
|
+
|
|
172
|
+
// Track sync metrics
|
|
173
|
+
metrics.Track("cli.sync.complete", map[string]interface{}{
|
|
174
|
+
"files_synced": len(filesToSync),
|
|
175
|
+
"policies_synced": totalPolicies,
|
|
176
|
+
"duration_seconds": syncDuration.Seconds(),
|
|
177
|
+
})
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
// Load all policies from cache
|
|
@@ -196,7 +206,18 @@ func Run() error {
|
|
|
196
206
|
|
|
197
207
|
// Check changes against policies (both regex and LLM-based)
|
|
198
208
|
ctx := context.Background()
|
|
209
|
+
checkStart := time.Now()
|
|
199
210
|
result := checker.CheckChanges(ctx, diffResult.Changes, policies)
|
|
211
|
+
checkDuration := time.Since(checkStart)
|
|
212
|
+
|
|
213
|
+
// Track policy check results (similar to policy.execute.result in github-app)
|
|
214
|
+
metrics.Track("cli.policy.check.result", map[string]interface{}{
|
|
215
|
+
"policies_checked": len(policies),
|
|
216
|
+
"changes_checked": len(diffResult.Changes),
|
|
217
|
+
"violations_found": len(result.Violations),
|
|
218
|
+
"has_violations": len(result.Violations) > 0,
|
|
219
|
+
"duration_seconds": checkDuration.Seconds(),
|
|
220
|
+
})
|
|
200
221
|
|
|
201
222
|
// Handle results based on whether violations were found
|
|
202
223
|
if len(result.Violations) > 0 {
|
package/go.mod
CHANGED
|
@@ -5,7 +5,9 @@ go 1.23.0
|
|
|
5
5
|
require github.com/anthropics/anthropic-sdk-go v1.17.0
|
|
6
6
|
|
|
7
7
|
require (
|
|
8
|
-
github.com/
|
|
8
|
+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
|
9
|
+
github.com/posthog/posthog-go v1.6.12 // indirect
|
|
10
|
+
github.com/stretchr/testify v1.10.0 // indirect
|
|
9
11
|
github.com/tidwall/gjson v1.18.0 // indirect
|
|
10
12
|
github.com/tidwall/match v1.1.1 // indirect
|
|
11
13
|
github.com/tidwall/pretty v1.2.1 // indirect
|
package/go.sum
CHANGED
|
@@ -2,10 +2,16 @@ github.com/anthropics/anthropic-sdk-go v1.17.0 h1:BwK8ApcmaAUkvZTiQE0yi3R9XneEFs
|
|
|
2
2
|
github.com/anthropics/anthropic-sdk-go v1.17.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
|
3
3
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
4
4
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
5
|
+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
|
6
|
+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
|
5
7
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
6
8
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
9
|
+
github.com/posthog/posthog-go v1.6.12 h1:rsOBL/YdMfLJtOVjKJLgdzYmvaL3aIW6IVbAteSe+aI=
|
|
10
|
+
github.com/posthog/posthog-go v1.6.12/go.mod h1:LcC1Nu4AgvV22EndTtrMXTy+7RGVC0MhChSw7Qk5XkY=
|
|
7
11
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
8
12
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
13
|
+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
14
|
+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
9
15
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
|
10
16
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
|
11
17
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
package/install.js
CHANGED
|
@@ -79,8 +79,15 @@ function buildBinary(goCommand) {
|
|
|
79
79
|
|
|
80
80
|
console.log('🔧 Building Tanagram CLI...');
|
|
81
81
|
|
|
82
|
+
// Build ldflags to inject PostHog key at build time
|
|
83
|
+
let ldflags = '';
|
|
84
|
+
if (process.env.POSTHOG_WRITE_KEY) {
|
|
85
|
+
ldflags = `-ldflags="-X 'github.com/tanagram/cli/metrics.posthogWriteKey=${process.env.POSTHOG_WRITE_KEY}'"`;
|
|
86
|
+
console.log(' 📊 Injecting PostHog metrics key...');
|
|
87
|
+
}
|
|
88
|
+
|
|
82
89
|
try {
|
|
83
|
-
execSync(`"${goCommand}" build -o "${binaryPath}" .`, {
|
|
90
|
+
execSync(`"${goCommand}" build ${ldflags} -o "${binaryPath}" .`, {
|
|
84
91
|
cwd: __dirname,
|
|
85
92
|
stdio: 'inherit',
|
|
86
93
|
env: {
|
package/main.go
CHANGED
|
@@ -5,9 +5,14 @@ import (
|
|
|
5
5
|
"os"
|
|
6
6
|
|
|
7
7
|
"github.com/tanagram/cli/commands"
|
|
8
|
+
"github.com/tanagram/cli/metrics"
|
|
8
9
|
)
|
|
9
10
|
|
|
10
11
|
func main() {
|
|
12
|
+
// Initialize metrics (similar to PosthogService initialization)
|
|
13
|
+
metrics.Init()
|
|
14
|
+
defer metrics.Close()
|
|
15
|
+
|
|
11
16
|
// Get subcommand (default to "run" if none provided)
|
|
12
17
|
subcommand := "run"
|
|
13
18
|
if len(os.Args) > 1 {
|
|
@@ -17,10 +22,19 @@ func main() {
|
|
|
17
22
|
var err error
|
|
18
23
|
switch subcommand {
|
|
19
24
|
case "run":
|
|
25
|
+
metrics.Track("command.execute", map[string]interface{}{
|
|
26
|
+
"command": "run",
|
|
27
|
+
})
|
|
20
28
|
err = commands.Run()
|
|
21
29
|
case "sync":
|
|
30
|
+
metrics.Track("command.execute", map[string]interface{}{
|
|
31
|
+
"command": "sync",
|
|
32
|
+
})
|
|
22
33
|
err = commands.Sync()
|
|
23
34
|
case "list":
|
|
35
|
+
metrics.Track("command.execute", map[string]interface{}{
|
|
36
|
+
"command": "list",
|
|
37
|
+
})
|
|
24
38
|
err = commands.List()
|
|
25
39
|
case "help", "-h", "--help":
|
|
26
40
|
printHelp()
|
|
@@ -32,6 +46,10 @@ func main() {
|
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
if err != nil {
|
|
49
|
+
metrics.Track("command.error", map[string]interface{}{
|
|
50
|
+
"command": subcommand,
|
|
51
|
+
"error": err.Error(),
|
|
52
|
+
})
|
|
35
53
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
36
54
|
os.Exit(1)
|
|
37
55
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
package metrics
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"runtime"
|
|
6
|
+
"time"
|
|
7
|
+
|
|
8
|
+
"github.com/posthog/posthog-go"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
var (
|
|
12
|
+
client posthog.Client
|
|
13
|
+
|
|
14
|
+
// Injected at build time via -ldflags
|
|
15
|
+
posthogWriteKey = ""
|
|
16
|
+
posthogHost = "https://us.i.posthog.com"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// Init initializes the PostHog client
|
|
20
|
+
// Similar to PosthogService.py and webui/app/lib/posthog.ts
|
|
21
|
+
func Init() {
|
|
22
|
+
// Use embedded key (set at build time)
|
|
23
|
+
writeKey := posthogWriteKey
|
|
24
|
+
|
|
25
|
+
// Allow override via env var for local development
|
|
26
|
+
if envKey := os.Getenv("POSTHOG_WRITE_KEY"); envKey != "" {
|
|
27
|
+
writeKey = envKey
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if writeKey == "" {
|
|
31
|
+
// Fail silently if not configured - metrics are optional
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
host := posthogHost
|
|
36
|
+
// Allow override via env var for local development
|
|
37
|
+
if envHost := os.Getenv("POSTHOG_HOST"); envHost != "" {
|
|
38
|
+
host = envHost
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var err error
|
|
42
|
+
client, err = posthog.NewWithConfig(
|
|
43
|
+
writeKey,
|
|
44
|
+
posthog.Config{
|
|
45
|
+
Endpoint: host,
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
if err != nil {
|
|
49
|
+
// Log but don't fail - metrics shouldn't break the CLI
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Close flushes and closes the PostHog client
|
|
55
|
+
func Close() {
|
|
56
|
+
if client != nil {
|
|
57
|
+
client.Close()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Track sends an event to PostHog
|
|
62
|
+
// Similar to MetricsService.put_metric_single_count()
|
|
63
|
+
func Track(event string, properties map[string]interface{}) {
|
|
64
|
+
if client == nil {
|
|
65
|
+
return // Silently skip if not initialized
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if properties == nil {
|
|
69
|
+
properties = make(map[string]interface{})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Add context dimensions similar to MetricsService
|
|
73
|
+
properties["$process_person_profile"] = false // Match Python implementation
|
|
74
|
+
properties["_deployment_env"] = getDeploymentEnv()
|
|
75
|
+
properties["os"] = runtime.GOOS
|
|
76
|
+
properties["arch"] = runtime.GOARCH
|
|
77
|
+
properties["cli_version"] = getVersion()
|
|
78
|
+
|
|
79
|
+
err := client.Enqueue(posthog.Capture{
|
|
80
|
+
DistinctId: getDistinctId(),
|
|
81
|
+
Event: event,
|
|
82
|
+
Properties: properties,
|
|
83
|
+
Timestamp: time.Now(),
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if err != nil {
|
|
87
|
+
// Silently fail - don't break CLI execution for metrics
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// getDistinctId returns a stable anonymous identifier
|
|
93
|
+
// Similar to user_id_for_github_dot_com() pattern
|
|
94
|
+
func getDistinctId() string {
|
|
95
|
+
// Use hostname as stable identifier
|
|
96
|
+
hostname, err := os.Hostname()
|
|
97
|
+
if err != nil {
|
|
98
|
+
return "unknown"
|
|
99
|
+
}
|
|
100
|
+
return "cli_" + hostname
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// getDeploymentEnv returns the deployment environment
|
|
104
|
+
// Similar to _env_name_from_github_app_id()
|
|
105
|
+
func getDeploymentEnv() string {
|
|
106
|
+
if env := os.Getenv("TANAGRAM_ENV"); env != "" {
|
|
107
|
+
return env
|
|
108
|
+
}
|
|
109
|
+
return "unknown"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// getVersion returns the CLI version
|
|
113
|
+
func getVersion() string {
|
|
114
|
+
// TODO: embed version at build time with -ldflags
|
|
115
|
+
return "0.1.14"
|
|
116
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanagram/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"description": "Tanagram - Catch sloppy code before it ships",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"postinstall": "node install.js",
|
|
11
|
-
"test": "go test ./..."
|
|
11
|
+
"test": "go test ./...",
|
|
12
|
+
"prepublishOnly": "export $(grep -v '^#' .env.publish | xargs) && node install.js"
|
|
12
13
|
},
|
|
13
14
|
"keywords": [
|
|
14
15
|
"tanagram",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"fixtures/",
|
|
39
40
|
"git/",
|
|
40
41
|
"llm/",
|
|
42
|
+
"metrics/",
|
|
41
43
|
"parser/",
|
|
42
44
|
"storage/",
|
|
43
45
|
"main.go",
|