# Command Documentation System — Design Plan
## 문제 정의
현재 봇 커맨드 UX에는 두 가지 핵심 문제가 있다.
1. **도움말이 커맨드 정의와 분리되어 있다.**
`INVALID_COMMAND_MESSAGE`는 `general.rs`에 하드코딩된 정적 문자열이다. 커맨드를 추가/수정해도 이 문자열을 수동으로 동기화해야 한다. `/alarm`, `/event`, `/register`는 이미 누락되어 있다.
2. **Telegram 네이티브 UX를 전혀 활용하지 않는다.**
Telegram BotFather에 커맨드를 등록하면 `/` 입력 시 자동완성 메뉴가 표시된다. 현재 이 API(`setMyCommands`)를 호출하지 않아 사용자는 커맨드 목록을 알 방법이 없다.
---
## 설계 목표
- 커맨드 문서(이름, 설명, 사용법, 예시)를 **커맨드 정의 옆에 선언**한다.
- 별도의 문자열을 수동으로 관리하지 않는다. 도움말은 정의로부터 **자동 생성**된다.
- Telegram `setMyCommands` API로 커맨드 목록을 **봇 UI에 등록**한다.
- `/help` 및 `/help <command>`로 인챗에서 도움말을 조회할 수 있다.
---
## 제안 설계
### 1. `CommandMeta` — 커맨드 메타데이터 구조체
`src/application/handler/command.rs`에 추가.
```rust
pub struct CommandMeta {
pub command: &'static str, // "/health"
pub description: &'static str, // 한 줄 설명 (Telegram 메뉴에 노출)
pub usage: &'static str, // "/health [server_name]"
pub examples: &'static [&'static str],
}
```
`description`은 Telegram의 `setMyCommands`에 그대로 전달되므로 64자 이내로 유지한다.
---
### 2. 선언적 등록 — `CommandDoc` Derive 매크로
커맨드 열거형 각 variant에 속성으로 메타데이터를 선언한다.
`app/struct-input` 패턴과 동일하게 proc-macro derive로 구현한다.
```rust
// src/application/handler/command.rs
#[derive(Debug, CommandDoc)]
pub enum Command {
#[command(
name = "/logs",
desc = "서버의 최근 로그를 가져옵니다",
usage = "/logs <server_name> <lines>",
examples = ["/logs main 100", "/logs api 50"]
)]
Logs(String, i32),
#[command(
name = "/health",
desc = "서버 헬스체크 결과를 확인합니다",
usage = "/health [server_name]",
examples = ["/health", "/health main"]
)]
HealthCheckAll,
HealthCheck(String), // HealthCheckAll과 같은 #[command] 그룹으로 묶임
#[command(
name = "/alarm",
desc = "이벤트 알람을 구독 또는 해제합니다",
usage = "/alarm <add|remove|list> [event_name]",
examples = ["/alarm list", "/alarm add cpu-high", "/alarm remove cpu-high"]
)]
Alarm(AlarmCommand),
#[command(
name = "/event",
desc = "설정된 이벤트 목록을 표시합니다",
usage = "/event [list]",
examples = ["/event", "/event list"]
)]
EventList,
Help(Option<String>), // 메타데이터 불필요
Nothing,
}
```
`#[derive(CommandDoc)]` 매크로는 다음을 자동 생성한다.
```rust
impl Command {
pub fn all_docs() -> &'static [CommandMeta] { /* 매크로 생성 */ }
pub fn find_doc(name: &str) -> Option<&'static CommandMeta> { /* 매크로 생성 */ }
}
```
---
### 3. 매크로 구현 위치
두 가지 선택지가 있다.
| `app/struct-input`에 추가 | 기존 proc-macro 인프라 재활용 | struct-input의 책임 범위가 넓어짐 |
| `app/command-doc`으로 분리 | 단일 책임, 독립 버전 관리 가능 | 새 crate 추가 |
**권장: `app/command-doc`으로 분리.**
`struct-input`은 CLI 입력 매핑에 집중하고, 커맨드 문서화는 별도 관심사다.
crate 구조:
```
app/
struct-input/ # 기존
command-doc/ # 신규
src/
lib.rs # proc-macro crate entry
```
`Cargo.toml`에 추가:
```toml
command-doc = { version = "0.1.0", path = "./app/command-doc" }
```
---
### 4. `/help` 커맨드
`Command::parse()`에 `/help` 파싱 추가.
```
/help → Help(None) 전체 커맨드 목록
/help logs → Help(Some("logs")) /logs 상세 도움말
/help health → Help(Some("health"))
```
응답 포맷:
```
# 전체 도움말 (/help)
사용 가능한 커맨드:
/logs — 서버의 최근 로그를 가져옵니다
/health — 서버 헬스체크 결과를 확인합니다
/alarm — 이벤트 알람을 구독 또는 해제합니다
/event — 설정된 이벤트 목록을 표시합니다
자세한 사용법: /help <command>
```
```
# 커맨드별 도움말 (/help logs)
/logs — 서버의 최근 로그를 가져옵니다
사용법: /logs <server_name> <lines>
예시:
/logs main 100
/logs api 50
```
---
### 5. Telegram `setMyCommands` 등록
`sw run` 시작 시 Telegram API에 커맨드 목록을 등록한다.
이렇게 하면 사용자가 `/` 입력 시 자동완성 메뉴가 표시된다.
**API 호출:**
```
POST https://api.telegram.org/bot<token>/setMyCommands
{
"commands": [
{ "command": "logs", "description": "서버의 최근 로그를 가져옵니다" },
{ "command": "health", "description": "서버 헬스체크 결과를 확인합니다" },
{ "command": "alarm", "description": "이벤트 알람을 구독 또는 해제합니다" },
{ "command": "event", "description": "설정된 이벤트 목록을 표시합니다" },
{ "command": "help", "description": "커맨드 사용법을 표시합니다" }
]
}
```
`Command::all_docs()`에서 메타데이터를 읽어 DTO를 생성하므로 별도 관리 불필요.
**호출 위치:** `src/infrastructure/client/telegram.rs` — `TelegramClient` 초기화 시
**새 DTO:** `SetMyCommandsDto`, `BotCommand` — `src/infrastructure/client/telegram/dto.rs`에 추가
---
## 구현 단계
### Phase 1 — 데이터 구조 정의
- [ ] `CommandMeta` 구조체 추가 (`src/application/handler/command.rs`)
- [ ] `Command::all_docs()`, `Command::find_doc()` 메서드를 **수동으로** 구현 (매크로 없이)
- [ ] `Command::Nothing` 응답을 `all_docs()` 기반으로 자동 생성되도록 교체
### Phase 2 — `/help` 커맨드
- [ ] `Command::Help(Option<String>)` variant 추가
- [ ] `Command::parse()`에 `/help`, `/help <name>` 파싱 추가
- [ ] `Run` impl에서 `Help` 처리 — 전체/개별 도움말 반환
### Phase 3 — Telegram 네이티브 등록
- [ ] `SetMyCommandsDto`, `BotCommand` DTO 추가 (`dto.rs`)
- [ ] `TelegramClient::set_commands()` 메서드 추가
- [ ] `sw run` 시 `ClientManager::load_clients()` 이후 호출
### Phase 4 — Proc-macro (선택, Phase 1~3 이후)
- [ ] `app/command-doc` proc-macro crate 생성
- [ ] `#[derive(CommandDoc)]` 구현
- [ ] Phase 1의 수동 구현을 매크로로 교체
---
## 영향 범위
| `src/application/handler/command.rs` | `CommandMeta` 추가, `Help` variant 추가, `all_docs()` 추가 |
| `src/application/handler/general.rs` | `INVALID_COMMAND_MESSAGE` 제거, `Help` 처리 추가 |
| `src/infrastructure/client/telegram/dto.rs` | `SetMyCommandsDto`, `BotCommand` 추가 |
| `src/infrastructure/client/telegram.rs` | `set_commands()` 메서드 추가, init 시 호출 |
| `src/infrastructure/client.rs` | `ClientManager::load_clients()` 이후 `set_commands` 호출 |
| `app/command-doc/` | 신규 (Phase 4) |
| `Cargo.toml` | `command-doc` 의존성 추가 (Phase 4) |