@@ -66,6 +66,9 @@ func main() {
6666 case "help" , "--help" , "-h" :
6767 printUsage ()
6868 return
69+ default :
70+ fmt .Fprintf (os .Stderr , "unknown command: %s\n run 'clipx help' for usage\n " , os .Args [1 ])
71+ os .Exit (1 )
6972 }
7073 }
7174
@@ -156,7 +159,7 @@ func cmdPair(addr string) {
156159 }
157160
158161 fmt .Printf ("✓ paired with %s\n " , resolved )
159- fmt . Println ( " restart clipx or run 'clipx install' to apply" )
162+ restartIfRunning ( )
160163}
161164
162165func cmdUnpair (addr string ) {
@@ -193,6 +196,7 @@ func cmdUnpair(addr string) {
193196 }
194197
195198 fmt .Printf ("✓ unpaired from %s\n " , addr )
199+ restartIfRunning ()
196200}
197201
198202func cmdPeers () {
@@ -213,7 +217,7 @@ func cmdPeers() {
213217func cmdStatus () {
214218 out , err := exec .Command ("launchctl" , "list" , launchAgentLabel ).Output ()
215219 if err != nil {
216- fmt .Println ("● clipx is not running as a LaunchAgent" )
220+ fmt .Println ("○ clipx is not running as a LaunchAgent" )
217221 } else {
218222 fmt .Println ("● clipx is running as a LaunchAgent" )
219223 fmt .Println (string (out ))
@@ -237,33 +241,86 @@ func cmdStatus() {
237241 }
238242}
239243
244+ // detectInstallMethod returns "brew", "go", or "binary" based on
245+ // where the current clipx binary lives.
246+ func detectInstallMethod () string {
247+ exePath , err := os .Executable ()
248+ if err != nil {
249+ return "binary"
250+ }
251+ exePath , _ = filepath .EvalSymlinks (exePath )
252+
253+ if strings .Contains (exePath , "Cellar" ) || strings .Contains (exePath , "homebrew" ) {
254+ return "brew"
255+ }
256+
257+ gopath := os .Getenv ("GOPATH" )
258+ if gopath == "" {
259+ home , _ := os .UserHomeDir ()
260+ gopath = filepath .Join (home , "go" )
261+ }
262+ if strings .HasPrefix (exePath , filepath .Join (gopath , "bin" )) {
263+ return "go"
264+ }
265+
266+ return "binary"
267+ }
268+
240269func cmdUpdate () {
241270 current := getVersion ()
242271 fmt .Printf ("current version: %s\n " , current )
243- fmt .Println ("updating via go install..." )
244-
245- cmd := exec .Command ("go" , "install" , "github.com/gomantics/clipx/cmd/clipx@latest" )
246- cmd .Env = append (os .Environ (),
247- "GONOSUMDB=github.com/gomantics/clipx" ,
248- "GONOSUMCHECK=github.com/gomantics/clipx" ,
249- )
250- cmd .Stdout = os .Stdout
251- cmd .Stderr = os .Stderr
252- if err := cmd .Run (); err != nil {
253- fmt .Fprintf (os .Stderr , "error: go install failed: %v\n " , err )
272+
273+ method := detectInstallMethod ()
274+
275+ switch method {
276+ case "brew" :
277+ fmt .Println ("installed via Homebrew, updating..." )
278+ cmd := exec .Command ("brew" , "upgrade" , "clipx" )
279+ cmd .Stdout = os .Stdout
280+ cmd .Stderr = os .Stderr
281+ if err := cmd .Run (); err != nil {
282+ fmt .Fprintf (os .Stderr , "error: brew upgrade failed: %v\n " , err )
283+ os .Exit (1 )
284+ }
285+
286+ case "go" :
287+ fmt .Println ("installed via go install, updating..." )
288+ cmd := exec .Command ("go" , "install" , "github.com/gomantics/clipx/cmd/clipx@latest" )
289+ cmd .Env = append (os .Environ (),
290+ "GONOSUMDB=github.com/gomantics/clipx" ,
291+ "GONOSUMCHECK=github.com/gomantics/clipx" ,
292+ )
293+ cmd .Stdout = os .Stdout
294+ cmd .Stderr = os .Stderr
295+ if err := cmd .Run (); err != nil {
296+ fmt .Fprintf (os .Stderr , "error: go install failed: %v\n " , err )
297+ os .Exit (1 )
298+ }
299+
300+ default :
301+ fmt .Fprintln (os .Stderr , "cannot detect install method" )
302+ fmt .Fprintln (os .Stderr , "update manually from https://github.com/gomantics/clipx/releases" )
254303 os .Exit (1 )
255304 }
256305
257306 fmt .Println ("✓ clipx updated" )
307+ restartIfRunning ()
308+ }
258309
310+ // restartIfRunning restarts the LaunchAgent if it's currently loaded.
311+ func restartIfRunning () {
259312 plistPath := launchAgentPath ()
260- if _ , err := os .Stat (plistPath ); err == nil {
261- fmt .Println ("restarting LaunchAgent..." )
262- exec .Command ("launchctl" , "unload" , plistPath ).Run ()
263- time .Sleep (500 * time .Millisecond )
264- exec .Command ("launchctl" , "load" , plistPath ).Run ()
265- fmt .Println ("✓ LaunchAgent restarted" )
313+ if _ , err := os .Stat (plistPath ); err != nil {
314+ return // not installed as LaunchAgent
315+ }
316+ if err := exec .Command ("launchctl" , "list" , launchAgentLabel ).Run (); err != nil {
317+ return // not currently running
266318 }
319+ fmt .Println ("restarting LaunchAgent..." )
320+ exec .Command ("launchctl" , "unload" , plistPath ).Run ()
321+ time .Sleep (500 * time .Millisecond )
322+ exec .Command ("launchctl" , "load" , plistPath ).Run ()
323+ fmt .Println ("✓ LaunchAgent restarted" )
267324}
268325
269326// --- LaunchAgent ---
@@ -316,9 +373,13 @@ func cmdInstall() {
316373 fmt .Println ("adding firewall exception (may require sudo password)..." )
317374 fwTool := "/usr/libexec/ApplicationFirewall/socketfilterfw"
318375 if _ , err := os .Stat (fwTool ); err == nil {
319- exec .Command ("sudo" , fwTool , "--add" , binPath ).Run ()
320- exec .Command ("sudo" , fwTool , "--unblockapp" , binPath ).Run ()
321- fmt .Println ("✓ firewall exception added" )
376+ err1 := exec .Command ("sudo" , fwTool , "--add" , binPath ).Run ()
377+ err2 := exec .Command ("sudo" , fwTool , "--unblockapp" , binPath ).Run ()
378+ if err1 != nil || err2 != nil {
379+ fmt .Println ("⚠ firewall exception failed — you may need to allow clipx manually" )
380+ } else {
381+ fmt .Println ("✓ firewall exception added" )
382+ }
322383 }
323384
324385 plistPath := launchAgentPath ()
0 commit comments