Skip to content

Commit 14577a7

Browse files
committed
feat: implement Flashduty CLI with full command set
Implements a CLI for the Flashduty platform consuming the flashduty-sdk. Commands: - login, config show/set, version - incident list/get/create/update/ack/close/timeline/alerts/similar - change list, member list, team list, channel list - escalation-rule list, field list - statuspage list/changes/create-incident/create-timeline - template get-preset/validate/variables/functions Features: - Table output with truncation (--no-trunc to disable) - JSON output (--json flag) - Config file at ~/.flashduty/config.yaml with env var override - Interactive prompting for incident create - Time parsing: Go durations, dates, datetimes, unix timestamps - Pagination support (--page flag) - Channel name resolution for escalation rules - Silent SDK logger for clean CLI output - GoReleaser + GitHub Actions CI/CD 26 commands across 11 resource types.
0 parents  commit 14577a7

29 files changed

Lines changed: 3607 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-go@v5
16+
with:
17+
go-version: "1.24"
18+
19+
- name: Build
20+
run: go build ./...
21+
22+
- name: Vet
23+
run: go vet ./...
24+
25+
- name: Test
26+
run: go test -race -cover ./...

.github/workflows/release.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- uses: actions/setup-go@v5
20+
with:
21+
go-version: "1.24"
22+
23+
- uses: goreleaser/goreleaser-action@v6
24+
with:
25+
version: latest
26+
args: release --clean
27+
env:
28+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
bin/
2+
*.exe
3+
*.exe~
4+
*.dll
5+
*.so
6+
*.dylib
7+
*.test
8+
*.out
9+
.DS_Store

.goreleaser.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
version: 2
2+
3+
builds:
4+
- main: ./cmd/flashduty
5+
binary: flashduty
6+
env:
7+
- CGO_ENABLED=0
8+
goos:
9+
- linux
10+
- darwin
11+
- windows
12+
goarch:
13+
- amd64
14+
- arm64
15+
ldflags:
16+
- -s -w -X main.version={{.Version}} -X main.commit={{.ShortCommit}} -X main.date={{.Date}}
17+
18+
archives:
19+
- format: tar.gz
20+
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
21+
format_overrides:
22+
- goos: windows
23+
format: zip
24+
25+
changelog:
26+
sort: asc
27+
filters:
28+
exclude:
29+
- "^docs:"
30+
- "^test:"
31+
- "^chore:"

Makefile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
BINARY=flashduty
2+
VERSION=$(shell git describe --tags --always --dirty)
3+
COMMIT=$(shell git rev-parse --short HEAD)
4+
DATE=$(shell date -u +%Y-%m-%dT%H:%M:%SZ)
5+
LDFLAGS=-ldflags "-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(DATE)"
6+
7+
.PHONY: build check lint test clean
8+
9+
build:
10+
go build $(LDFLAGS) -o bin/$(BINARY) ./cmd/flashduty
11+
12+
check: lint test build
13+
14+
lint:
15+
golangci-lint run ./...
16+
17+
test:
18+
go test -race -cover ./...
19+
20+
clean:
21+
rm -rf bin/

cmd/flashduty/main.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/flashcatcloud/flashduty-cli/internal/cli"
8+
)
9+
10+
var (
11+
version = "dev"
12+
commit = "none"
13+
date = "unknown"
14+
)
15+
16+
func main() {
17+
cli.SetVersionInfo(version, commit, date)
18+
if err := cli.Execute(); err != nil {
19+
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
20+
os.Exit(1)
21+
}
22+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# FlashDuty CLI Design
2+
3+
## Overview
4+
5+
A command-line interface for the FlashDuty platform, providing all functionality currently available through the flashduty-mcp-server but as a traditional CLI tool. Built in Go, consuming a shared `flashduty-go-sdk` module that is also used by the MCP server.
6+
7+
## Decisions
8+
9+
| Aspect | Decision |
10+
|--------|----------|
11+
| Language | Go |
12+
| CLI framework | Cobra + Viper |
13+
| API client | New `flashduty-go-sdk` repo, shared with MCP server |
14+
| Command style | Noun-verb (`flashduty incident list`) |
15+
| Auth | Config file (`~/.flashduty/config.yaml`) + env var + flag |
16+
| Output | Human-readable tables by default, `--json` for machine output |
17+
| Enrichment | On by default, `--raw` to skip |
18+
| Distribution | GoReleaser + GitHub Releases + Homebrew tap |
19+
20+
## Dependency Graph
21+
22+
```
23+
flashduty-go-sdk
24+
^ ^
25+
| |
26+
flashduty-CLI flashduty-mcp-server
27+
```
28+
29+
## SDK Design (`flashduty-go-sdk`)
30+
31+
A new standalone repo extracted from the MCP server's `pkg/flashduty/` package (~3,600 lines).
32+
33+
### Structure
34+
35+
```
36+
flashduty-go-sdk/
37+
├── client.go # HTTP client
38+
├── client_options.go # Functional options: WithBaseURL, WithTimeout, etc.
39+
├── incidents.go # Incident API methods + types
40+
├── incidents_enrich.go # Incident enrichment logic
41+
├── changes.go # Change API methods + types
42+
├── statuspage.go # Status page API methods + types
43+
├── members.go # Member/person API methods + types
44+
├── teams.go # Team API methods + types
45+
├── channels.go # Channel API methods + types
46+
├── channels_enrich.go # Channel enrichment logic
47+
├── escalation_rules.go # Escalation rule API methods + types
48+
├── fields.go # Custom field API methods + types
49+
├── errors.go # Error types and handling
50+
├── go.mod
51+
└── go.sum
52+
```
53+
54+
### What moves to the SDK
55+
56+
- `Client` struct and HTTP plumbing (auth, headers, timeouts, response parsing)
57+
- All API methods (`ListIncidents`, `CreateIncident`, `AckIncident`, etc.)
58+
- All domain types (`Incident`, `Channel`, `Member`, `Team`, etc.)
59+
- Enrichment logic (`EnrichIncidents`, `EnrichChannels`)
60+
- Error types
61+
62+
### What stays in each consumer
63+
64+
- MCP server: tool registration, MCP parameter parsing, TOON formatting, toolset management
65+
- CLI: Cobra commands, flag parsing, table formatting, config file handling
66+
67+
### API Style
68+
69+
```go
70+
client := flashduty.NewClient("fd_xxx",
71+
flashduty.WithBaseURL("https://api.flashcat.cloud"),
72+
flashduty.WithTimeout(30 * time.Second),
73+
)
74+
75+
incidents, err := client.ListIncidents(ctx, &flashduty.ListIncidentsInput{
76+
Status: []string{"triggered", "acknowledged"},
77+
Severity: []string{"critical"},
78+
ChannelID: "ch_abc",
79+
})
80+
81+
enriched, err := client.EnrichIncidents(ctx, incidents)
82+
```
83+
84+
## CLI Design
85+
86+
### Project Structure
87+
88+
```
89+
flashduty-CLI/
90+
├── cmd/
91+
│ └── flashduty/
92+
│ └── main.go # Entry point
93+
├── internal/
94+
│ ├── cli/
95+
│ │ ├── root.go # Root command, global flags (--json, --raw)
96+
│ │ ├── login.go # flashduty login / flashduty config
97+
│ │ ├── incident.go # incident subcommands
98+
│ │ ├── change.go # change subcommands
99+
│ │ ├── statuspage.go # status-page subcommands
100+
│ │ ├── member.go # member subcommands
101+
│ │ ├── team.go # team subcommands
102+
│ │ ├── channel.go # channel subcommands
103+
│ │ ├── escalationrule.go # escalation-rule subcommands
104+
│ │ └── field.go # field subcommands
105+
│ ├── config/
106+
│ │ └── config.go # Config file + env var loading
107+
│ └── output/
108+
│ ├── table.go # Human-readable table formatter
109+
│ └── json.go # JSON output formatter
110+
├── go.mod
111+
├── go.sum
112+
├── Makefile
113+
├── .goreleaser.yml
114+
└── .github/
115+
└── workflows/
116+
├── ci.yml # Lint + test + build
117+
└── release.yml # GoReleaser on tag push
118+
```
119+
120+
### Commands
121+
122+
```
123+
flashduty login # auth setup (interactive)
124+
flashduty config show # print resolved config (key masked)
125+
flashduty config set <key> <value> # override specific config values
126+
127+
flashduty incident list # query incidents
128+
flashduty incident get <id> # get specific incident
129+
flashduty incident create # create incident
130+
flashduty incident update <id> # modify incident
131+
flashduty incident ack <id> # acknowledge
132+
flashduty incident close <id> # close/resolve
133+
flashduty incident timeline <id> # view timeline
134+
flashduty incident alerts <id> # view alerts
135+
flashduty incident similar <id> # find similar
136+
137+
flashduty change list # query changes
138+
139+
flashduty status-page list # list status pages
140+
flashduty status-page changes # list status change events
141+
flashduty status-page create-incident # create status page incident
142+
flashduty status-page create-timeline # add timeline to status change
143+
144+
flashduty member list # query members
145+
flashduty team list # query teams
146+
flashduty channel list # query channels
147+
flashduty escalation-rule list # query escalation rules
148+
flashduty field list # query fields
149+
```
150+
151+
### Authentication
152+
153+
**Config file** at `~/.flashduty/config.yaml`:
154+
155+
```yaml
156+
app_key: fd_xxxxxxxxxxxxxxxx
157+
base_url: https://api.flashcat.cloud
158+
```
159+
160+
**`flashduty login` flow:**
161+
162+
1. Prompt for app key (masked input)
163+
2. Validate by calling `POST /member/list` with limit=1
164+
3. On success, write `~/.flashduty/config.yaml` (0600 permissions) and print authenticated user name
165+
4. On failure, print error and exit non-zero
166+
167+
**Resolution order** (highest priority first):
168+
169+
1. `--app-key` flag (hidden from help output to discourage shell history exposure)
170+
2. `FLASHDUTY_APP_KEY` environment variable
171+
3. `~/.flashduty/config.yaml`
172+
173+
### Output
174+
175+
**Default: human-readable tables**
176+
177+
```
178+
$ flashduty incident list
179+
180+
ID TITLE SEVERITY STATUS CHANNEL CREATED
181+
inc_abc123 DB connection timeout Critical Triggered Production 2026-03-13 10:23
182+
inc_def456 High memory usage Warning Acknowledged Staging 2026-03-13 09:15
183+
inc_ghi789 SSL cert expiring Info Resolved Production 2026-03-12 18:00
184+
185+
3 incidents found.
186+
```
187+
188+
**`--json`: machine-parseable JSON**
189+
190+
```
191+
$ flashduty incident list --json
192+
[{"id":"inc_abc123","title":"DB connection timeout",...},...]
193+
```
194+
195+
- Table formatting via `text/tabwriter` (stdlib)
196+
- `--json` global flag inherited by all subcommands
197+
- `--raw` skips enrichment, works with both table and JSON
198+
199+
### Distribution
200+
201+
- **GoReleaser** (`.goreleaser.yml`): builds for linux/darwin/windows across amd64/arm64
202+
- **GitHub Releases**: triggered on tag push (`v0.1.0`, etc.)
203+
- **Homebrew tap**: via `homebrew-tap` repo (`brew install flashcatcloud/tap/flashduty`)
204+
205+
## Implementation Order
206+
207+
1. Extract `flashduty-go-sdk` from MCP server's `pkg/flashduty/`
208+
2. Refactor MCP server to consume the SDK (verify no regressions)
209+
3. Build CLI skeleton (root command, config, login)
210+
4. Implement commands resource by resource (incidents first -- largest surface area)
211+
5. Add GoReleaser + Homebrew tap
212+
6. Polish (shell completions, `--help` examples)

0 commit comments

Comments
 (0)