package cmd import ( "axolotl/models" "axolotl/service" "axolotl/store" "fmt" "os" "strings" "github.com/spf13/cobra" ) func getNodeService() (service.NodeService, error) { if token := os.Getenv("AX_TOKEN"); token != "" { if cfg.Remote.Host != "" { base := fmt.Sprintf("http://%s:%d", cfg.Remote.Host, cfg.Remote.Port) return service.NewRemoteNodeService(base, ""), nil } st, err := store.FindAndOpenSQLiteStore() if err != nil { return nil, err } agentID := service.LookupAgentToken(st, token) if agentID == "" { return nil, fmt.Errorf("invalid AX_TOKEN: agent not found") } return service.NewLocalNodeService(st, agentID), nil } user := cfg.User if user == "" { return nil, fmt.Errorf("no user configured: run 'ax user set ' first") } if cfg.Remote.Host != "" { base := fmt.Sprintf("http://%s:%d", cfg.Remote.Host, cfg.Remote.Port) return service.NewRemoteNodeService(base, user), nil } st, err := store.FindAndOpenSQLiteStore() if err != nil { return nil, err } return service.NewLocalNodeService(st, user), nil } var jsonFlag bool var cfg *store.Config var rootCmd = &cobra.Command{Use: "ax", Short: "The axolotl issue tracker"} func Execute() { var err error cfg, err = store.LoadConfig() if err != nil { fmt.Fprintln(os.Stderr, "failed to load config:", err) os.Exit(1) } RegisterAliasCommands() if err := rootCmd.Execute(); err != nil { os.Exit(1) } } func init() { rootCmd.PersistentFlags().BoolVar(&jsonFlag, "json", false, "") } func RegisterAliasCommands() { rootCmd.AddGroup(&cobra.Group{ID: "aliases", Title: "Aliases:"}) aliases := cfg.Aliases for _, a := range aliases { rootCmd.AddCommand(&cobra.Command{ Use: a.Name, Short: a.Description, GroupID: "aliases", DisableFlagParsing: true, Run: func(ccmd *cobra.Command, args []string) { acmd := a.Command acmd = strings.ReplaceAll(acmd, "$me", cfg.User) parts := strings.Fields(acmd) var expanded []string usedArgs := make([]bool, len(args)) for _, part := range parts { if part == "$@" { expanded = append(expanded, args...) for i := range usedArgs { usedArgs[i] = true } continue } hasCatchAll := strings.Contains(part, "$@") replaced := part if hasCatchAll { replaced = strings.ReplaceAll(replaced, "$@", strings.Join(args, " ")) for i := range usedArgs { usedArgs[i] = true } } for i := len(args) - 1; i >= 0; i-- { placeholder := fmt.Sprintf("$%d", i+1) if strings.Contains(replaced, placeholder) { replaced = strings.ReplaceAll(replaced, placeholder, args[i]) usedArgs[i] = true } } expanded = append(expanded, replaced) } // Forward any unconsumed args (e.g. --json flag). for i, arg := range args { if !usedArgs[i] { expanded = append(expanded, arg) } } rootCmd.SetArgs(expanded) if err := rootCmd.Execute(); err != nil { os.Exit(1) } }, }) } } // parseRelInput parses a rel string into a RelInput. // Formats: // - "prefix::value" → property rel with no target (tag) // - "relname:target" → edge rel with a target node // - "tagname" → simple label rel with no target (alias for --tag) func parseRelInput(s string) (service.RelInput, error) { if strings.Contains(s, "::") { // Property: name::value — no target node. return service.RelInput{Type: models.RelType(s), Target: ""}, nil } if idx := strings.Index(s, ":"); idx >= 0 { // Edge rel: relname:target. return service.RelInput{Type: models.RelType(s[:idx]), Target: s[idx+1:]}, nil } // Simple label tag — no target node. return service.RelInput{Type: models.RelType(s), Target: ""}, nil }