-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
240 lines (207 loc) · 5.57 KB
/
Copy pathmain.go
File metadata and controls
240 lines (207 loc) · 5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime/debug"
"strings"
"github.com/nitintf/claudewatch/internal/api"
"github.com/nitintf/claudewatch/internal/auth"
"github.com/nitintf/claudewatch/internal/config"
"github.com/nitintf/claudewatch/internal/statusline"
"github.com/nitintf/claudewatch/internal/theme"
)
func main() {
if len(os.Args) > 1 {
var err error
switch os.Args[1] {
case "install":
err = install()
case "uninstall":
err = uninstall()
case "update":
err = update()
case "version":
printVersion()
return
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if os.Args[1] == "install" || os.Args[1] == "uninstall" || os.Args[1] == "update" || os.Args[1] == "version" {
return
}
}
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
data, err := io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("reading stdin: %w", err)
}
if len(data) == 0 {
return fmt.Errorf("no input on stdin — pipe Claude Code JSON or run: claudewatch install")
}
status, err := statusline.Parse(data)
if err != nil {
return err
}
// Populate environment info.
if wd, wdErr := os.Getwd(); wdErr == nil {
status.Cwd = filepath.Base(wd)
}
if branch, brErr := gitBranch(); brErr == nil {
status.Branch = branch
}
cfg, err := config.Load()
if err != nil {
return err
}
t, err := theme.Load(cfg.Theme)
if err != nil {
t, _ = theme.LoadBuiltin("dracula")
}
// Read credentials for plan detection and API access.
var plan string
var usage *api.Usage
creds, credErr := auth.Read()
if credErr == nil {
plan = auth.PlanName(creds.SubscriptionType)
if creds.AccessToken != "" {
usage, _ = api.FetchUsage(creds.AccessToken)
}
}
output := statusline.Render(&status, &t, plan, usage, &cfg)
// Leading reset clears stale ANSI state from previous renders.
// Non-breaking spaces prevent the terminal from collapsing whitespace.
output = "\x1b[0m" + strings.ReplaceAll(output, " ", "\u00A0")
// Extra newline adds visual padding below the status line.
fmt.Println(output + "\n")
return nil
}
func install() error {
binaryPath, err := exec.LookPath("claudewatch")
if err != nil {
binaryPath, err = os.Executable()
if err != nil {
return fmt.Errorf("could not find claudewatch binary: %w", err)
}
}
home, err := os.UserHomeDir()
if err != nil {
return err
}
settingsPath := filepath.Join(home, ".claude", "settings.json")
settings := make(map[string]interface{})
data, err := os.ReadFile(settingsPath)
if err == nil {
_ = json.Unmarshal(data, &settings)
}
settings["statusLine"] = map[string]interface{}{
"type": "command",
"command": binaryPath,
}
_ = os.MkdirAll(filepath.Dir(settingsPath), 0o755)
out, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(settingsPath, out, 0o644); err != nil {
return err
}
// Create default config file if it doesn't exist.
cfgPath, err := config.Path()
if err == nil {
if _, statErr := os.Stat(cfgPath); os.IsNotExist(statErr) {
if saveErr := config.Save(config.DefaultConfig()); saveErr == nil {
fmt.Printf(" config: %s\n", cfgPath)
}
}
}
fmt.Printf("Installed claudewatch as Claude Code status line\n")
fmt.Printf(" binary: %s\n", binaryPath)
fmt.Printf(" settings: %s\n", settingsPath)
fmt.Printf("\nRestart Claude Code to see your status line.\n")
return nil
}
func uninstall() error {
home, err := os.UserHomeDir()
if err != nil {
return err
}
// Remove statusLine from Claude Code settings.
settingsPath := filepath.Join(home, ".claude", "settings.json")
data, err := os.ReadFile(settingsPath)
if err == nil {
settings := make(map[string]interface{})
if err := json.Unmarshal(data, &settings); err == nil {
delete(settings, "statusLine")
out, err := json.MarshalIndent(settings, "", " ")
if err == nil {
_ = os.WriteFile(settingsPath, out, 0o644)
}
}
}
// Remove usage cache.
_ = os.Remove(filepath.Join(os.TempDir(), "claudewatch-usage.json"))
// Remove the binary itself.
binPath, _ := os.Executable()
if binPath != "" {
_ = os.Remove(binPath)
}
fmt.Printf("Uninstalled claudewatch\n")
fmt.Printf(" removed statusLine from %s\n", settingsPath)
fmt.Printf(" kept config at ~/.config/claudewatch/config.toml\n")
if binPath != "" {
fmt.Printf(" removed binary %s\n", binPath)
}
fmt.Printf("\nRestart Claude Code to apply.\n")
return nil
}
func update() error {
// Show current version.
currentVersion := version()
fmt.Printf("Current version: %s\n", currentVersion)
// Fetch and install latest.
fmt.Printf("Fetching latest version...\n")
cmd := exec.Command("go", "install", "github.com/nitintf/claudewatch@latest")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("go install failed: %w", err)
}
// Show new version.
out, err := exec.Command("claudewatch", "version").Output()
if err == nil {
fmt.Printf("Updated to: %s", string(out))
}
// Re-register to update binary path in settings.
if err := install(); err != nil {
return fmt.Errorf("re-registering: %w", err)
}
return nil
}
func version() string {
info, ok := debug.ReadBuildInfo()
if !ok || info.Main.Version == "" || info.Main.Version == "(devel)" {
return "dev"
}
return info.Main.Version
}
func printVersion() {
fmt.Println(version())
}
func gitBranch() (string, error) {
out, err := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}