@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/checker/matcher.go +64 -0
- package/commands/config.go +343 -70
- package/commands/config_test.go +389 -0
- package/commands/run.go +23 -19
- package/commands/snapshot.go +15 -3
- package/commands/snapshot_test.go +88 -0
- package/main.go +58 -8
- package/package.json +1 -1
- package/utils/process.go +15 -4
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
|
|
47
|
-
if err := commands.
|
|
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
|
|
63
|
-
if err := commands.
|
|
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
|
|
74
|
-
if err := commands.
|
|
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
|
-
|
|
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'
|
|
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
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
|
}
|