Skip to content
Draft
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
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ go 1.25.1

require (
github.com/flashcatcloud/flashduty-sdk v0.9.1
github.com/flashcatcloud/go-flashduty v0.3.0
github.com/mattn/go-runewidth v0.0.23
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.9
github.com/toon-format/toon-go v0.0.0-20251202084852-7ca0e27c4e8c
golang.org/x/term v0.42.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/toon-format/toon-go v0.0.0-20251202084852-7ca0e27c4e8c // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.43.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJ
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/flashcatcloud/flashduty-sdk v0.9.1 h1:vDTkSjAJJD6Ex5r7S+VCxPi4yxSFNw1bU/SfoRCvk+k=
github.com/flashcatcloud/flashduty-sdk v0.9.1/go.mod h1:dG4eJfdZaj4jNBMwEexbfK/3PmcIMhNeJ88L/DcZzUY=
github.com/flashcatcloud/go-flashduty v0.3.0 h1:DlwkrK/MIkkWfqJoKwvq3fh/8A0A3OUEbAMDIRrkLkI=
github.com/flashcatcloud/go-flashduty v0.3.0/go.mod h1:aA0RtZEs0AYOwwdNKdtVeD8YMOdnmVY1zAlVD+9Ovx8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
Expand Down
23 changes: 12 additions & 11 deletions internal/cli/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

flashduty "github.com/flashcatcloud/flashduty-sdk"
gflashduty "github.com/flashcatcloud/go-flashduty"
"github.com/spf13/cobra"

"github.com/flashcatcloud/flashduty-cli/internal/output"
Expand Down Expand Up @@ -173,28 +174,28 @@ func newAlertEventsCmd() *cobra.Command {
Short: "List alert events",
Args: requireArgs("alert_id"),
RunE: func(cmd *cobra.Command, args []string) error {
return runCommand(cmd, args, func(ctx *RunContext) error {
result, err := ctx.Client.ListAlertEvents(cmdContext(ctx.Cmd), &flashduty.ListAlertEventsInput{
return runGFCommand(cmd, args, func(ctx *RunContext) error {
result, _, err := ctx.GFClient.Alerts.ReadEventList(cmdContext(ctx.Cmd), &gflashduty.AlertEventListRequest{
AlertID: ctx.Args[0],
})
if err != nil {
return err
}

if len(result.AlertEvents) == 0 {
if len(result.Items) == 0 {
ctx.WriteResult("No alert events found.")
return nil
}

cols := []output.Column{
{Header: "EVENT_ID", Field: func(v any) string { return v.(flashduty.AlertEvent).EventID }},
{Header: "SEVERITY", Field: func(v any) string { return v.(flashduty.AlertEvent).EventSeverity }},
{Header: "STATUS", Field: func(v any) string { return v.(flashduty.AlertEvent).EventStatus }},
{Header: "TIME", Field: func(v any) string { return output.FormatTime(v.(flashduty.AlertEvent).EventTime) }},
{Header: "TITLE", MaxWidth: 50, Field: func(v any) string { return v.(flashduty.AlertEvent).Title }},
{Header: "EVENT_ID", Field: func(v any) string { return v.(gflashduty.AlertEventItem).EventID }},
{Header: "SEVERITY", Field: func(v any) string { return v.(gflashduty.AlertEventItem).EventSeverity }},
{Header: "STATUS", Field: func(v any) string { return v.(gflashduty.AlertEventItem).EventStatus }},
{Header: "TIME", Field: func(v any) string { return output.FormatTime(v.(gflashduty.AlertEventItem).EventTime) }},
{Header: "TITLE", MaxWidth: 50, Field: func(v any) string { return v.(gflashduty.AlertEventItem).Title }},
}

return ctx.PrintTotal(result.AlertEvents, cols, len(result.AlertEvents))
return ctx.PrintTotal(result.Items, cols, len(result.Items))
})
},
}
Expand Down Expand Up @@ -255,8 +256,8 @@ func newAlertMergeCmd() *cobra.Command {
Short: "Merge alerts into an incident",
Args: requireArgs("alert_id"),
RunE: func(cmd *cobra.Command, args []string) error {
return runCommand(cmd, args, func(ctx *RunContext) error {
if err := ctx.Client.MergeAlertsToIncident(cmdContext(ctx.Cmd), &flashduty.MergeAlertsInput{
return runGFCommand(cmd, args, func(ctx *RunContext) error {
if _, err := ctx.GFClient.Alerts.WriteMerge(cmdContext(ctx.Cmd), &gflashduty.AlertMergeRequest{
AlertIDs: ctx.Args,
IncidentID: incidentID,
Comment: comment,
Expand Down
30 changes: 16 additions & 14 deletions internal/cli/alert_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package cli

import (
"fmt"
"strings"

flashduty "github.com/flashcatcloud/flashduty-sdk"
gflashduty "github.com/flashcatcloud/go-flashduty"
"github.com/spf13/cobra"

"github.com/flashcatcloud/flashduty-cli/internal/output"
Expand All @@ -27,7 +28,7 @@ func newAlertEventListCmd() *cobra.Command {
Use: "list",
Short: "List alert events globally",
RunE: func(cmd *cobra.Command, args []string) error {
return runCommand(cmd, args, func(ctx *RunContext) error {
return runGFCommand(cmd, args, func(ctx *RunContext) error {
startTime, err := timeutil.Parse(since)
if err != nil {
return fmt.Errorf("invalid --since: %w", err)
Expand All @@ -37,15 +38,16 @@ func newAlertEventListCmd() *cobra.Command {
return fmt.Errorf("invalid --until: %w", err)
}

input := &flashduty.ListAlertEventsGlobalInput{
input := &gflashduty.AlertEventGlobalListRequest{
StartTime: startTime,
EndTime: endTime,
Limit: limit,
Page: page,
}
input.Limit = limit
input.Page = page

if severity != "" {
input.Severities = parseStringSlice(severity)
// go-flashduty takes severities as a comma-separated string.
input.Severities = strings.Join(parseStringSlice(severity), ",")
}

if channel != "" {
Expand All @@ -60,21 +62,21 @@ func newAlertEventListCmd() *cobra.Command {
input.IntegrationTypes = parseStringSlice(integrationType)
}

result, err := ctx.Client.ListAlertEventsGlobal(cmdContext(ctx.Cmd), input)
result, _, err := ctx.GFClient.Alerts.EventReadList(cmdContext(ctx.Cmd), input)
if err != nil {
return err
}

cols := []output.Column{
{Header: "EVENT_ID", Field: func(v any) string { return v.(flashduty.AlertEvent).EventID }},
{Header: "ALERT_ID", Field: func(v any) string { return v.(flashduty.AlertEvent).AlertID }},
{Header: "SEVERITY", Field: func(v any) string { return v.(flashduty.AlertEvent).EventSeverity }},
{Header: "STATUS", Field: func(v any) string { return v.(flashduty.AlertEvent).EventStatus }},
{Header: "TIME", Field: func(v any) string { return output.FormatTime(v.(flashduty.AlertEvent).EventTime) }},
{Header: "TITLE", MaxWidth: 50, Field: func(v any) string { return v.(flashduty.AlertEvent).Title }},
{Header: "EVENT_ID", Field: func(v any) string { return v.(gflashduty.AlertEventItem).EventID }},
{Header: "ALERT_ID", Field: func(v any) string { return v.(gflashduty.AlertEventItem).AlertID }},
{Header: "SEVERITY", Field: func(v any) string { return v.(gflashduty.AlertEventItem).EventSeverity }},
{Header: "STATUS", Field: func(v any) string { return v.(gflashduty.AlertEventItem).EventStatus }},
{Header: "TIME", Field: func(v any) string { return output.FormatTime(v.(gflashduty.AlertEventItem).EventTime) }},
{Header: "TITLE", MaxWidth: 50, Field: func(v any) string { return v.(gflashduty.AlertEventItem).Title }},
}

return ctx.PrintList(result.AlertEvents, cols, len(result.AlertEvents), page, result.Total)
return ctx.PrintList(result.Items, cols, len(result.Items), page, int(result.Total))
})
},
}
Expand Down
30 changes: 15 additions & 15 deletions internal/cli/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package cli
import (
"fmt"

flashduty "github.com/flashcatcloud/flashduty-sdk"
gflashduty "github.com/flashcatcloud/go-flashduty"
"github.com/spf13/cobra"

"github.com/flashcatcloud/flashduty-cli/internal/output"
Expand All @@ -28,7 +28,7 @@ func newAuditSearchCmd() *cobra.Command {
Use: "search",
Short: "Search audit logs",
RunE: func(cmd *cobra.Command, args []string) error {
return runCommand(cmd, args, func(ctx *RunContext) error {
return runGFCommand(cmd, args, func(ctx *RunContext) error {
startTime, err := timeutil.Parse(since)
if err != nil {
return fmt.Errorf("invalid --since: %w", err)
Expand All @@ -38,33 +38,33 @@ func newAuditSearchCmd() *cobra.Command {
return fmt.Errorf("invalid --until: %w", err)
}

input := &flashduty.SearchAuditLogsInput{
input := &gflashduty.AuditSearchRequest{
StartTime: startTime,
EndTime: endTime,
Limit: limit,
PersonID: person,
Limit: int64(limit),
PersonID: uint64(person),
}
if operation != "" {
input.Operations = parseStringSlice(operation)
}

var (
result *flashduty.SearchAuditLogsOutput
result *gflashduty.AuditSearchResponse
cursor string
)
for currentPage := 1; currentPage <= page; currentPage++ {
input.SearchAfterCtx = cursor
result, err = ctx.Client.SearchAuditLogs(cmdContext(ctx.Cmd), input)
result, _, err = ctx.GFClient.AuditLogs.Search(cmdContext(ctx.Cmd), input)
if err != nil {
return err
}
if currentPage == page {
break
}
if result.SearchAfterCtx == "" {
result = &flashduty.SearchAuditLogsOutput{
AuditLogs: []flashduty.AuditLogRecord{},
Total: result.Total,
result = &gflashduty.AuditSearchResponse{
Docs: []gflashduty.AuditLog{},
Total: result.Total,
}
break
}
Expand All @@ -73,32 +73,32 @@ func newAuditSearchCmd() *cobra.Command {

cols := []output.Column{
{Header: "TIME", Field: func(v any) string {
return output.FormatTime(v.(flashduty.AuditLogRecord).CreatedAt)
return output.FormatTime(v.(gflashduty.AuditLog).CreatedAt)
}},
{Header: "PERSON", MaxWidth: 20, Field: func(v any) string {
r := v.(flashduty.AuditLogRecord)
r := v.(gflashduty.AuditLog)
if r.MemberName != "" {
return r.MemberName
}
return fmt.Sprintf("%d", r.MemberID)
}},
{Header: "OPERATION", MaxWidth: 30, Field: func(v any) string {
r := v.(flashduty.AuditLogRecord)
r := v.(gflashduty.AuditLog)
if r.OperationName != "" {
return r.OperationName
}
return r.Operation
}},
{Header: "DETAIL", MaxWidth: 50, Field: func(v any) string {
r := v.(flashduty.AuditLogRecord)
r := v.(gflashduty.AuditLog)
if r.Body != "" {
return r.Body
}
return "-"
}},
}

return ctx.PrintList(result.AuditLogs, cols, len(result.AuditLogs), page, int(result.Total))
return ctx.PrintList(result.Docs, cols, len(result.Docs), page, int(result.Total))
})
},
}
Expand Down
44 changes: 38 additions & 6 deletions internal/cli/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,29 @@ import (
"fmt"
"io"

gflashduty "github.com/flashcatcloud/go-flashduty"
"github.com/spf13/cobra"

"github.com/flashcatcloud/flashduty-cli/internal/output"
)

// RunContext provides helpers for command execution. It is created by
// runCommand and passed to the command's handler function.
//
// Two SDK clients are exposed during the go-flashduty migration:
// - Client — the legacy hand-written SDK, still used by commands that depend
// on server-side enrichment or endpoints go-flashduty does not yet cover.
// - GFClient — the typed go-flashduty SDK, used by migrated commands.
//
// A command uses exactly one of them; the boundary is per-command, not mixed.
type RunContext struct {
Client flashdutyClient
Cmd *cobra.Command
Args []string
Writer io.Writer
Printer output.Printer
Format output.Format
Client flashdutyClient
GFClient *gflashduty.Client
Cmd *cobra.Command
Args []string
Writer io.Writer
Printer output.Printer
Format output.Format
}

// Structured reports whether output should be a machine-readable dump (JSON or
Expand All @@ -27,6 +36,10 @@ func (ctx *RunContext) Structured() bool { return ctx.Format.Structured() }

// runCommand creates a client and RunContext, then calls fn.
// It centralises setup that every API-backed command repeats.
//
// It constructs the legacy client only; commands migrated to go-flashduty use
// runGFCommand instead. Both factories read the same resolved config, so the
// two paths authenticate identically.
func runCommand(cmd *cobra.Command, args []string, fn func(ctx *RunContext) error) error {
client, err := newClient()
if err != nil {
Expand All @@ -43,6 +56,25 @@ func runCommand(cmd *cobra.Command, args []string, fn func(ctx *RunContext) erro
return fn(ctx)
}

// runGFCommand is the go-flashduty counterpart of runCommand. It constructs the
// typed go-flashduty client and leaves RunContext.Client nil — migrated command
// handlers must reach for ctx.GFClient.
func runGFCommand(cmd *cobra.Command, args []string, fn func(ctx *RunContext) error) error {
client, err := newGFClient()
if err != nil {
return err
}
ctx := &RunContext{
GFClient: client,
Cmd: cmd,
Args: args,
Writer: cmd.OutOrStdout(),
Printer: newPrinter(cmd.OutOrStdout()),
Format: currentOutputFormat(),
}
return fn(ctx)
}

// PrintList prints items as a table and appends a "Showing N results (page P, total T)." footer.
func (ctx *RunContext) PrintList(items any, cols []output.Column, count, page, total int) error {
if err := ctx.Printer.Print(items, cols); err != nil {
Expand Down
Loading
Loading