@tanagram/cli 0.4.2 → 0.4.4

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/main.go CHANGED
@@ -1,12 +1,15 @@
1
1
  package main
2
2
 
3
3
  import (
4
+ "encoding/json"
4
5
  "fmt"
6
+ "io"
5
7
  "os"
6
8
 
7
9
  "github.com/tanagram/cli/commands"
8
10
  "github.com/tanagram/cli/metrics"
9
11
  "github.com/tanagram/cli/tui"
12
+ "github.com/tanagram/cli/utils"
10
13
  )
11
14
 
12
15
  // Version is set in install.js via `-X` ldflags
@@ -37,14 +40,37 @@ func main() {
37
40
  "subcommand": subcommand,
38
41
  })
39
42
 
43
+ // THIS IS A HUGE HACK
44
+ // Cursor runs its hooks in the ~/.cursor directory as cwd
45
+ // Claude runs its hooks directly as a subprocess, with your terminal directory as cwd
46
+ // We have existing code that depends on cwd being the workspace root
47
+ // So we detect if we're running through Cursor (`GetParentProcess` does some magic here)
48
+ // and set cwd based on the workspace_root that Cursor gives us.
49
+ // TODO: handle 0 or multiple workspace_roots
50
+ if utils.GetParentProcess() == "cursor" {
51
+ input, err := io.ReadAll(os.Stdin)
52
+ if err == nil {
53
+ var payload struct {
54
+ WorkspaceRoots []string `json:"workspace_roots"`
55
+ }
56
+ if err := json.Unmarshal(input, &payload); err != nil {
57
+ fmt.Fprintf(os.Stderr, "Error parsing input: %v\n", err)
58
+ } else if len(payload.WorkspaceRoots) > 0 {
59
+ os.Chdir(payload.WorkspaceRoots[0])
60
+ } else {
61
+ fmt.Fprintf(os.Stderr, "No workspace roots found\n")
62
+ }
63
+ }
64
+ }
65
+
40
66
  var err error
41
67
  switch subcommand {
42
68
  case "run":
43
69
  metrics.Track("cli.command.execute", map[string]interface{}{
44
70
  "command": "run",
45
71
  })
46
- // Auto-setup Claude Code hooks on first run
47
- if err := commands.EnsureClaudeConfigured(); err != nil {
72
+ // Auto-setup hooks on first run
73
+ if err := commands.EnsureHooksConfigured(); err != nil {
48
74
  fmt.Fprintf(os.Stderr, "Error: %v\n", err)
49
75
  exitCode = 1
50
76
  return
@@ -59,8 +85,8 @@ func main() {
59
85
  metrics.Track("cli.command.execute", map[string]interface{}{
60
86
  "command": "sync",
61
87
  })
62
- // Auto-setup Claude Code hooks on first run
63
- if err := commands.EnsureClaudeConfigured(); err != nil {
88
+ // Auto-setup hooks on first run
89
+ if err := commands.EnsureHooksConfigured(); err != nil {
64
90
  fmt.Fprintf(os.Stderr, "Error: %v\n", err)
65
91
  exitCode = 1
66
92
  return
@@ -70,8 +96,8 @@ func main() {
70
96
  metrics.Track("cli.command.execute", map[string]interface{}{
71
97
  "command": "list",
72
98
  })
73
- // Auto-setup Claude Code hooks on first run
74
- if err := commands.EnsureClaudeConfigured(); err != nil {
99
+ // Auto-setup hooks on first run
100
+ if err := commands.EnsureHooksConfigured(); err != nil {
75
101
  fmt.Fprintf(os.Stderr, "Error: %v\n", err)
76
102
  exitCode = 1
77
103
  return
@@ -83,6 +109,7 @@ func main() {
83
109
  fmt.Fprintf(os.Stderr, "Usage: tanagram config <subcommand>\n")
84
110
  fmt.Fprintf(os.Stderr, "\nAvailable subcommands:\n")
85
111
  fmt.Fprintf(os.Stderr, " claude Setup Claude Code hook\n")
112
+ fmt.Fprintf(os.Stderr, " cursor Setup Cursor hook\n")
86
113
  fmt.Fprintf(os.Stderr, " list Show hook installation status\n")
87
114
  exitCode = 1
88
115
  return
@@ -93,7 +120,22 @@ func main() {
93
120
  metrics.Track("cli.command.execute", map[string]interface{}{
94
121
  "command": "config.claude",
95
122
  })
96
- err = commands.ConfigClaude()
123
+ settingsPath, pathErr := commands.GetHomeConfigPath(".claude", "settings.json")
124
+ if pathErr != nil {
125
+ err = pathErr
126
+ break
127
+ }
128
+ err = commands.ConfigClaude(settingsPath)
129
+ case "cursor":
130
+ metrics.Track("cli.command.execute", map[string]interface{}{
131
+ "command": "config.cursor",
132
+ })
133
+ hooksPath, pathErr := commands.GetHomeConfigPath(".cursor", "hooks.json")
134
+ if pathErr != nil {
135
+ err = pathErr
136
+ break
137
+ }
138
+ err = commands.ConfigCursor(hooksPath)
97
139
  case "list":
98
140
  metrics.Track("cli.command.execute", map[string]interface{}{
99
141
  "command": "config.list",
@@ -184,6 +226,7 @@ COMMANDS:
184
226
  sync Manually sync instruction files to cache
185
227
  list Show all cached policies
186
228
  config claude Setup Claude Code hook automatically
229
+ config cursor Setup Cursor hook automatically
187
230
  config list Show hook installation status
188
231
  version Show the CLI version
189
232
  help Show this help message
@@ -197,6 +240,7 @@ EXAMPLES:
197
240
  tanagram sync # Manually sync local instruction files
198
241
  tanagram list # View all cached policies
199
242
  tanagram config claude # Setup Claude Code hooks in ~/.claude/settings.json
243
+ tanagram config cursor # Setup Cursor hooks in ~/.cursor/hooks.json
200
244
  tanagram config list # Show where hooks are installed
201
245
  tanagram version # Show the version
202
246
 
@@ -208,9 +252,15 @@ INSTRUCTION FILES:
208
252
  Policies are cached and automatically resynced when files change.
209
253
 
210
254
  HOOK WORKFLOW:
211
- When configured with 'tanagram config claude', two hooks are installed:
255
+ When configured with 'tanagram config claude' or 'tanagram config cursor':
256
+
257
+ Claude Code:
212
258
  1. PreToolUse (Edit|Write): Creates snapshot before Claude makes changes
213
259
  2. PostToolUse (Edit|Write): Checks only Claude's changes against policies
260
+
261
+ Cursor:
262
+ 1. beforeSubmitPrompt: Creates snapshot before agent starts working
263
+ 2. stop: Checks only Cursor's changes after agent completes
214
264
 
215
265
  This prevents false positives from user-written code!
216
266
  `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanagram/cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Tanagram - Catch sloppy code before it ships",
5
5
  "main": "index.js",
6
6
  "bin": {
package/utils/process.go CHANGED
@@ -2,6 +2,7 @@ package utils
2
2
 
3
3
  import (
4
4
  "os"
5
+ "strings"
5
6
  "sync"
6
7
 
7
8
  "github.com/shirou/gopsutil/v3/process"
@@ -17,20 +18,30 @@ var (
17
18
  func GetParentProcess() string {
18
19
  parentProcessOnce.Do(func() {
19
20
  parentProcessName = "unknown"
20
-
21
+
21
22
  parentPID := int32(os.Getppid())
22
23
  parent, err := process.NewProcess(parentPID)
23
24
  if err != nil {
24
25
  return
25
26
  }
26
-
27
+
27
28
  name, err := parent.Name()
28
29
  if err != nil {
29
30
  return
30
31
  }
31
-
32
+
33
+ // Cursor runs hooks via `node` or `zsh` (I've seen both) in ~/.cursor
34
+ // Handle this case and make life easier for users of this function.
35
+ if name == "node" || name == "zsh" {
36
+ cwd, err := parent.Cwd()
37
+ if err == nil && strings.Contains(cwd, ".cursor") {
38
+ parentProcessName = "cursor"
39
+ return
40
+ }
41
+ }
42
+
32
43
  parentProcessName = name
33
44
  })
34
-
45
+
35
46
  return parentProcessName
36
47
  }