package cmd import ( "context" "encoding/json" "fmt" "os" "gogs.dmsc.dev/arp/arp_cli/client" "gogs.dmsc.dev/arp/arp_cli/config" "github.com/AlecAivazis/survey/v2" "github.com/olekukonko/tablewriter" "github.com/urfave/cli/v3" ) // MessageCommand returns the message command func MessageCommand() *cli.Command { return &cli.Command{ Name: "message", Usage: "Manage messages", Description: `Manage ARP messages. Messages are sent in channels between users/agents. Use this command to create, list, update, and delete messages. You can also watch for real-time message updates.`, Commands: []*cli.Command{ { Name: "list", Aliases: []string{"ls"}, Usage: "List all messages", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: messageList, }, { Name: "get", Usage: "Get a message by ID", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Message ID", Required: true, }, &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: messageGet, }, { Name: "create", Usage: "Create a new message", Action: messageCreate, Flags: []cli.Flag{ &cli.StringFlag{ Name: "conversation", Aliases: []string{"c"}, Usage: "Conversation/Channel ID", }, &cli.StringFlag{ Name: "sender", Aliases: []string{"s"}, Usage: "Sender user ID", }, &cli.StringFlag{ Name: "content", Aliases: []string{"m"}, Usage: "Message content", }, }, }, { Name: "update", Usage: "Update a message", Action: messageUpdate, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Message ID", Required: true, }, &cli.StringFlag{ Name: "content", Aliases: []string{"c"}, Usage: "Message content", }, }, }, { Name: "delete", Usage: "Delete a message", Action: messageDelete, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Message ID", Required: true, }, &cli.BoolFlag{ Name: "yes", Aliases: []string{"y"}, Usage: "Skip confirmation", }, }, }, { Name: "watch", Usage: "Watch for real-time message updates", Action: messageWatch, }, }, } } type Message struct { ID string `json:"id"` ConversationID string `json:"conversationId"` SenderID string `json:"senderId"` Sender *User `json:"sender"` Content string `json:"content"` SentAt string `json:"sentAt"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } func messageList(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) query := "query Messages { messages { id conversationId sender { id email } content sentAt createdAt } }" resp, err := c.Query(query, nil) if err != nil { return err } var result struct { Messages []Message `json:"messages"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.Messages) } if len(result.Messages) == 0 { fmt.Println("No messages found.") return nil } table := tablewriter.NewWriter(os.Stdout) table.Header([]string{"ID", "Conversation", "Sender", "Content", "Sent At"}) for _, m := range result.Messages { sender := "" if m.Sender != nil { sender = m.Sender.Email } content := m.Content if len(content) > 50 { content = content[:47] + "..." } table.Append([]string{m.ID, m.ConversationID, sender, content, m.SentAt}) } table.Render() return nil } func messageGet(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) id := cmd.String("id") query := "query Message($id: ID!) { message(id: $id) { id conversationId sender { id email } content sentAt createdAt updatedAt } }" resp, err := c.Query(query, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { Message *Message `json:"message"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.Message == nil { return fmt.Errorf("message not found") } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.Message) } m := result.Message fmt.Printf("ID: %s\n", m.ID) fmt.Printf("Conversation ID: %s\n", m.ConversationID) if m.Sender != nil { fmt.Printf("Sender: %s (%s)\n", m.Sender.Email, m.SenderID) } else { fmt.Printf("Sender ID: %s\n", m.SenderID) } fmt.Printf("Content: %s\n", m.Content) fmt.Printf("Sent At: %s\n", m.SentAt) fmt.Printf("Created At: %s\n", m.CreatedAt) fmt.Printf("Updated At: %s\n", m.UpdatedAt) return nil } func messageCreate(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } conversationID := cmd.String("conversation") senderID := cmd.String("sender") content := cmd.String("content") if conversationID == "" { prompt := &survey.Input{Message: "Conversation ID:"} if err := survey.AskOne(prompt, &conversationID, survey.WithValidator(survey.Required)); err != nil { return err } } if senderID == "" { prompt := &survey.Input{Message: "Sender user ID:"} if err := survey.AskOne(prompt, &senderID, survey.WithValidator(survey.Required)); err != nil { return err } } if content == "" { prompt := &survey.Multiline{Message: "Message content:"} if err := survey.AskOne(prompt, &content, survey.WithValidator(survey.Required)); err != nil { return err } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation CreateMessage($input: NewMessage!) { createMessage(input: $input) { id conversationId sender { id email } content sentAt createdAt } }` input := map[string]interface{}{ "conversationId": conversationID, "senderId": senderID, "content": content, } resp, err := c.Mutation(mutation, map[string]interface{}{"input": input}) if err != nil { return err } var result struct { CreateMessage *Message `json:"createMessage"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.CreateMessage == nil { return fmt.Errorf("failed to create message") } fmt.Printf("Message created successfully!\n") fmt.Printf("ID: %s\n", result.CreateMessage.ID) return nil } func messageUpdate(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") content := cmd.String("content") if content == "" { prompt := &survey.Multiline{Message: "New message content:"} if err := survey.AskOne(prompt, &content, survey.WithValidator(survey.Required)); err != nil { return err } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) input := map[string]interface{}{ "content": content, } mutation := `mutation UpdateMessage($id: ID!, $input: UpdateMessageInput!) { updateMessage(id: $id, input: $input) { id content updatedAt } }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input}) if err != nil { return err } var result struct { UpdateMessage *Message `json:"updateMessage"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.UpdateMessage == nil { return fmt.Errorf("message not found") } fmt.Printf("Message updated successfully!\n") fmt.Printf("ID: %s\n", result.UpdateMessage.ID) return nil } func messageDelete(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") skipConfirm := cmd.Bool("yes") if !skipConfirm { confirm := false prompt := &survey.Confirm{ Message: fmt.Sprintf("Are you sure you want to delete message %s?", id), Default: false, } if err := survey.AskOne(prompt, &confirm); err != nil { return err } if !confirm { fmt.Println("Deletion cancelled.") return nil } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation DeleteMessage($id: ID!) { deleteMessage(id: $id) }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { DeleteMessage bool `json:"deleteMessage"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.DeleteMessage { fmt.Printf("Message %s deleted successfully.\n", id) } else { fmt.Printf("Failed to delete message %s.\n", id) } return nil } func messageWatch(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } wsClient := client.NewWebSocketClient(cfg.ServerURL, cfg.Token) if err := wsClient.Connect(); err != nil { return fmt.Errorf("failed to connect: %w", err) } defer wsClient.Close() fmt.Println("Watching for new messages...") fmt.Println("Press Ctrl+C to stop.") subscription := "subscription { messageAdded { id conversationId sender { id email } content sentAt } }" if err := wsClient.Subscribe("1", subscription, nil); err != nil { return fmt.Errorf("failed to subscribe: %w", err) } for { select { case msg := <-wsClient.Messages(): fmt.Printf("New message: %s\n", string(msg)) case err := <-wsClient.Errors(): fmt.Fprintf(os.Stderr, "Error: %v\n", err) case <-wsClient.Done(): return nil case <-ctx.Done(): return nil } } }