|
| 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