server-watchdog 0.1.2

A server monitoring and remote control tool via messenger.
Documentation
# 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) |