Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ It supports authenticated publishing, subscribing, rate-limited usage tracking,
- [x] Health monitoring and system metrics
- [x] Debug mode with detailed timing and proxy information
- [x] Development mode with `--disable-auth` flag for testing
- [x] Multiple output formats (table, JSON, YAML) for automation and scripting

---

Expand Down Expand Up @@ -73,6 +74,10 @@ Download from [releases](https://github.com/getoptimum/mump2p-cli/releases/lates
# List your active topics
./mump2p list-topics

# Output formats - JSON/YAML for automation and scripting
./mump2p list-topics --output=json
./mump2p whoami --output=yaml

# Debug mode - detailed timing and proxy information
./mump2p --debug publish --topic=test-topic --message='Hello World'
./mump2p --debug subscribe --topic=test-topic
Expand Down
113 changes: 88 additions & 25 deletions cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,29 @@ import (
"time"

"github.com/getoptimum/mump2p-cli/internal/auth"
"github.com/getoptimum/mump2p-cli/internal/formatter"
"github.com/spf13/cobra"
)

// WhoamiResponse represents structured authentication status
type WhoamiResponse struct {
ClientID string `json:"client_id" yaml:"client_id"`
Expires string `json:"expires,omitempty" yaml:"expires,omitempty"`
ValidFor string `json:"valid_for,omitempty" yaml:"valid_for,omitempty"`
IsActive bool `json:"is_active" yaml:"is_active"`
IsExpired bool `json:"is_expired,omitempty" yaml:"is_expired,omitempty"`
AuthMode string `json:"auth_mode" yaml:"auth_mode"`
RateLimits *RateLimitInfo `json:"rate_limits,omitempty" yaml:"rate_limits,omitempty"`
}

// RateLimitInfo represents rate limit information
type RateLimitInfo struct {
PublishPerHour int `json:"publish_per_hour" yaml:"publish_per_hour"`
PublishPerSec int `json:"publish_per_sec" yaml:"publish_per_sec"`
MaxMessageSizeMB float64 `json:"max_message_size_mb" yaml:"max_message_size_mb"`
DailyQuotaMB float64 `json:"daily_quota_mb" yaml:"daily_quota_mb"`
}

// loginCmd represents the login command
var loginCmd = &cobra.Command{
Use: "login",
Expand Down Expand Up @@ -57,18 +77,35 @@ var whoamiCmd = &cobra.Command{
Short: "Show current authentication status",
Long: `Display information about the current authentication token.`,
RunE: func(cmd *cobra.Command, args []string) error {
f := formatter.New(GetOutputFormat())

if IsAuthDisabled() {
// Display authentication status when auth is disabled
clientIDToUse := GetClientID()
if clientIDToUse == "" {
clientIDToUse = "(not set - use --client-id flag)"
}
fmt.Println("Authentication Status:")
fmt.Println("----------------------")
fmt.Printf("Client ID: %s\n", clientIDToUse)
fmt.Println("Auth Mode: Disabled (using --disable-auth)")
fmt.Println("Rate Limits: N/A (no limits enforced)")
fmt.Println("Token: N/A (auth disabled)")

response := WhoamiResponse{
ClientID: clientIDToUse,
AuthMode: "disabled",
IsActive: true,
}

if f.IsTable() {
fmt.Println("Authentication Status:")
fmt.Println("----------------------")
fmt.Printf("Client ID: %s\n", clientIDToUse)
fmt.Println("Auth Mode: Disabled (using --disable-auth)")
fmt.Println("Rate Limits: N/A (no limits enforced)")
fmt.Println("Token: N/A (auth disabled)")
} else {
output, err := f.Format(response)
if err != nil {
return fmt.Errorf("failed to format output: %v", err)
}
fmt.Println(output)
}
return nil
}

Expand All @@ -86,31 +123,57 @@ var whoamiCmd = &cobra.Command{
return fmt.Errorf("error parsing token: %v", err)
}

// display token information
fmt.Println("Authentication Status:")
fmt.Println("----------------------")

if claims.Subject != "" {
fmt.Printf("Client ID: %s\n", claims.Subject)
// Prepare structured response
isExpired := time.Now().After(claims.ExpiresAt)
response := WhoamiResponse{
ClientID: claims.Subject,
Expires: claims.ExpiresAt.Format(time.RFC822),
ValidFor: time.Until(claims.ExpiresAt).Round(time.Minute).String(),
IsActive: claims.IsActive,
IsExpired: isExpired,
AuthMode: "enabled",
RateLimits: &RateLimitInfo{
PublishPerHour: claims.MaxPublishPerHour,
PublishPerSec: claims.MaxPublishPerSec,
MaxMessageSizeMB: float64(claims.MaxMessageSize) / (1 << 20),
DailyQuotaMB: float64(claims.DailyQuota) / (1 << 20),
},
}

fmt.Printf("Expires: %s\n", claims.ExpiresAt.Format(time.RFC822))
if f.IsTable() {
// display token information (table format)
fmt.Println("Authentication Status:")
fmt.Println("----------------------")

if claims.Subject != "" {
fmt.Printf("Client ID: %s\n", claims.Subject)
}

fmt.Printf("Expires: %s\n", claims.ExpiresAt.Format(time.RFC822))

if isExpired {
fmt.Println("Token has expired. Please login again.")
} else {
fmt.Printf("Valid for: %s\n", time.Until(claims.ExpiresAt).Round(time.Minute))
}

if time.Now().After(claims.ExpiresAt) {
fmt.Println("Token has expired. Please login again.")
fmt.Printf("Is Active: %t\n", claims.IsActive)
// display rate limit information
fmt.Println("\nRate Limits:")
fmt.Println("------------")
fmt.Printf("Publish Rate: %d per hour\n", claims.MaxPublishPerHour)
fmt.Printf("Publish Rate: %d per second\n", claims.MaxPublishPerSec)
fmt.Printf("Max Message Size: %.2f MB\n", float64(claims.MaxMessageSize)/(1<<20))
fmt.Printf("Daily Quota: %.2f MB\n", float64(claims.DailyQuota)/(1<<20))
} else {
fmt.Printf("Valid for: %s\n", time.Until(claims.ExpiresAt).Round(time.Minute))
// JSON or YAML format
output, err := f.Format(response)
if err != nil {
return fmt.Errorf("failed to format output: %v", err)
}
fmt.Println(output)
}

fmt.Printf("Is Active: %t\n", claims.IsActive)
// display rate limit information
fmt.Println("\nRate Limits:")
fmt.Println("------------")
fmt.Printf("Publish Rate: %d per hour\n", claims.MaxPublishPerHour)
fmt.Printf("Publish Rate: %d per second\n", claims.MaxPublishPerSec)
fmt.Printf("Max Message Size: %.2f MB\n", float64(claims.MaxMessageSize)/(1<<20))
fmt.Printf("Daily Quota: %.2f MB\n", float64(claims.DailyQuota)/(1<<20))

return nil
},
}
Expand Down
39 changes: 28 additions & 11 deletions cmd/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"

"github.com/getoptimum/mump2p-cli/internal/config"
"github.com/getoptimum/mump2p-cli/internal/formatter"
"github.com/spf13/cobra"
)

Expand All @@ -16,10 +17,12 @@ var (

// HealthResponse represents the response from the health endpoint
type HealthResponse struct {
Status string `json:"status"`
MemoryUsed string `json:"memory_used"`
CPUUsed string `json:"cpu_used"`
DiskUsed string `json:"disk_used"`
Status string `json:"status" yaml:"status"`
MemoryUsed string `json:"memory_used" yaml:"memory_used"`
CPUUsed string `json:"cpu_used" yaml:"cpu_used"`
DiskUsed string `json:"disk_used" yaml:"disk_used"`
Country string `json:"country,omitempty" yaml:"country,omitempty"`
CountryISO string `json:"country_iso,omitempty" yaml:"country_iso,omitempty"`
}

var healthCmd = &cobra.Command{
Expand Down Expand Up @@ -62,13 +65,27 @@ var healthCmd = &cobra.Command{
return nil
}

// Display formatted health information
fmt.Println("Proxy Health Status:")
fmt.Println("-------------------")
fmt.Printf("Status: %s\n", healthResp.Status)
fmt.Printf("Memory Used: %s%%\n", healthResp.MemoryUsed)
fmt.Printf("CPU Used: %s%%\n", healthResp.CPUUsed)
fmt.Printf("Disk Used: %s%%\n", healthResp.DiskUsed)
f := formatter.New(GetOutputFormat())

if f.IsTable() {
// Display formatted health information (default table format)
fmt.Println("Proxy Health Status:")
fmt.Println("-------------------")
fmt.Printf("Status: %s\n", healthResp.Status)
fmt.Printf("Memory Used: %s%%\n", healthResp.MemoryUsed)
fmt.Printf("CPU Used: %s%%\n", healthResp.CPUUsed)
fmt.Printf("Disk Used: %s%%\n", healthResp.DiskUsed)
if healthResp.Country != "" {
fmt.Printf("Country: %s (%s)\n", healthResp.Country, healthResp.CountryISO)
}
} else {
// JSON or YAML format
output, err := f.Format(healthResp)
if err != nil {
return fmt.Errorf("failed to format output: %v", err)
}
fmt.Println(output)
}

return nil
},
Expand Down
73 changes: 48 additions & 25 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/getoptimum/mump2p-cli/internal/auth"
"github.com/getoptimum/mump2p-cli/internal/config"
"github.com/getoptimum/mump2p-cli/internal/formatter"
"github.com/spf13/cobra"
)

Expand All @@ -17,9 +18,9 @@ var (

// ListResponse represents the response from the /api/v1/topics endpoint
type ListResponse struct {
ClientID string `json:"client_id"`
Topics []string `json:"topics"`
Count int `json:"count"`
ClientID string `json:"client_id" yaml:"client_id"`
Topics []string `json:"topics" yaml:"topics"`
Count int `json:"count" yaml:"count"`
}

var listTopicsCmd = &cobra.Command{
Expand Down Expand Up @@ -76,21 +77,32 @@ This command shows your active topics and their count.`,
return fmt.Errorf("failed to parse response JSON: %v", err)
}

// Display results in a formatted table
fmt.Printf("\n📋 Subscribed Topics for Client: %s (Auth Disabled)\n", listResponse.ClientID)
fmt.Printf("═══════════════════════════════════════════════════════════════\n")
f := formatter.New(GetOutputFormat())

if f.IsTable() {
// Display results in a formatted table
fmt.Printf("\n📋 Subscribed Topics for Client: %s (Auth Disabled)\n", listResponse.ClientID)
fmt.Printf("═══════════════════════════════════════════════════════════════\n")

if listResponse.Count == 0 {
fmt.Printf(" No active topics found.\n")
fmt.Printf(" Use './mump2p subscribe --topic=<topic-name>' to subscribe to a topic.\n")
} else {
fmt.Printf(" Total Topics: %d\n\n", listResponse.Count)
for i, topic := range listResponse.Topics {
fmt.Printf(" %d. %s\n", i+1, topic)
}
}

if listResponse.Count == 0 {
fmt.Printf(" No active topics found.\n")
fmt.Printf(" Use './mump2p subscribe --topic=<topic-name>' to subscribe to a topic.\n")
fmt.Printf("═══════════════════════════════════════════════════════════════\n")
} else {
fmt.Printf(" Total Topics: %d\n\n", listResponse.Count)
for i, topic := range listResponse.Topics {
fmt.Printf(" %d. %s\n", i+1, topic)
// JSON or YAML format
output, err := f.Format(listResponse)
if err != nil {
return fmt.Errorf("failed to format output: %v", err)
}
fmt.Println(output)
}

fmt.Printf("═══════════════════════════════════════════════════════════════\n")
return nil
}

Expand Down Expand Up @@ -157,21 +169,32 @@ This command shows your active topics and their count.`,
return fmt.Errorf("failed to parse response JSON: %v", err)
}

// Display results in a formatted table
fmt.Printf("\n📋 Subscribed Topics for Client: %s\n", listResponse.ClientID)
fmt.Printf("═══════════════════════════════════════════════════════════════\n")
f := formatter.New(GetOutputFormat())

if f.IsTable() {
// Display results in a formatted table
fmt.Printf("\n📋 Subscribed Topics for Client: %s\n", listResponse.ClientID)
fmt.Printf("═══════════════════════════════════════════════════════════════\n")

if listResponse.Count == 0 {
fmt.Printf(" No active topics found.\n")
fmt.Printf(" Use './mump2p subscribe --topic=<topic-name>' to subscribe to a topic.\n")
if listResponse.Count == 0 {
fmt.Printf(" No active topics found.\n")
fmt.Printf(" Use './mump2p subscribe --topic=<topic-name>' to subscribe to a topic.\n")
} else {
fmt.Printf(" Total Topics: %d\n\n", listResponse.Count)
for i, topic := range listResponse.Topics {
fmt.Printf(" %d. %s\n", i+1, topic)
}
}

fmt.Printf("═══════════════════════════════════════════════════════════════\n")
} else {
fmt.Printf(" Total Topics: %d\n\n", listResponse.Count)
for i, topic := range listResponse.Topics {
fmt.Printf(" %d. %s\n", i+1, topic)
// JSON or YAML format
output, err := f.Format(listResponse)
if err != nil {
return fmt.Errorf("failed to format output: %v", err)
}
fmt.Println(output)
}

fmt.Printf("═══════════════════════════════════════════════════════════════\n")
return nil
},
}
Expand Down
17 changes: 13 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (
)

var (
authPath string // Global flag for custom authentication file path
debug bool // Global flag for debug mode
disableAuth bool // Global flag to disable authentication checks
clientID string // Global flag for client ID (used when auth is disabled)
authPath string // Global flag for custom authentication file path
debug bool // Global flag for debug mode
disableAuth bool // Global flag to disable authentication checks
clientID string // Global flag for client ID (used when auth is disabled)
outputFormat string // Global flag for output format (table, json, yaml)
)

var rootCmd = &cobra.Command{
Expand Down Expand Up @@ -40,6 +41,9 @@ func init() {
// Add global client ID flag
rootCmd.PersistentFlags().StringVar(&clientID, "client-id", "", "Client ID to use (required when --disable-auth is enabled)")

// Add global output format flag
rootCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table, json, yaml)")

// disable completion option
rootCmd.CompletionOptions.DisableDefaultCmd = true
}
Expand Down Expand Up @@ -72,3 +76,8 @@ func IsAuthDisabled() bool {
func GetClientID() string {
return clientID
}

// GetOutputFormat returns the output format
func GetOutputFormat() string {
return outputFormat
}
Loading
Loading