<!-- task=oto_agent_registration/01_agent_cli_config plan=0 tag=AGENT_CLI -->

# Plan - AGENT_CLI

## 이 파일을 읽는 구현 에이전트에게

구현 완료 전 `CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 검증 명령을 실제로 실행하고 출력 원문을 붙이며, active 파일은 그대로 둔 채 review 준비 상태를 보고한다. `complete.log` 작성, `.log` 아카이브, task 디렉터리 이동은 code-review-skill 전용이다.

## 배경

Edge bootstrap script는 이미 `oto agent run --config "$config_path"`를 실행하도록 작성되어 있다. 현재 CLI에는 `agent` top-level command가 없어 bootstrap 이후 실행 표면이 비어 있다. 이 작업은 네트워크 등록 구현 전에 `oto agent run --config` 명령과 config 파싱 경계를 먼저 만든다.

## 분석 결과

### 읽은 파일

- `agent-ops/roadmap/current.md`
- `agent-ops/roadmap/milestones/edge-bootstrap-contract.md`
- `agent-ops/roadmap/milestones/oto-agent-registration.md`
- `agent-ops/rules/project/domain/cli/rules.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `bin/main.dart`
- `lib/cli/commands/command_base.dart`
- `lib/cli/commands/command_manager.dart`
- `lib/cli/commands/command_validate.dart`
- `lib/cli/commands/command_exe.dart`
- `pubspec.yaml`
- `assets/script/shell/oto_agent_bootstrap.sh`
- `test/oto_agent_bootstrap_script_test.dart`
- `test/oto_validate_cli_test.dart`
- `test/oto_catalog_cli_test.dart`
- `test/oto_iop_connection_smoke_test.dart`

### 테스트 커버리지 공백

- `oto agent run --config` CLI parsing: 기존 테스트 없음. 새 `test/oto_agent_cli_test.dart`가 필요하다.
- `~/.oto/agent/config.yaml` shape 파싱: bootstrap script contract test는 파일 내용 문자열만 확인한다. 새 `test/oto_agent_config_test.dart`가 필요하다.
- 실제 Edge registration: 이 하위 작업에서는 제외한다. `02+01_edge_registration_runtime`에서 다룬다.

### 심볼 참조

- renamed/removed symbol: 없음.
- 새 command 추가이므로 기존 call site 변경은 `bin/main.dart` command registration뿐이다.

### 분할 판단

- split decision policy를 먼저 평가했다.
- shared task group: `oto_agent_registration`
- `01_agent_cli_config`: CLI command와 config model만 소유하며 선행 의존성이 없다.
- `02+01_edge_registration_runtime`: proto-socket Edge registration/runtime을 소유하며 `01_agent_cli_config` 완료 후 진행한다.
- 분할 이유: CLI/config는 deterministic unit test로 검증 가능하지만 Edge registration은 protocol/socket/long-running runtime risk가 있어 별도 리뷰 단위가 맞다.

### 범위 결정 근거

- 이 작업은 `CommandAgent`와 agent config parser까지만 구현한다.
- proto-socket connect, `RegisterRequest`, heartbeat, retry, online state는 `02+01_edge_registration_runtime`에서 구현한다.
- bootstrap shell script는 이미 검증되어 있으므로 이 작업에서 수정하지 않는다.
- scheduler, pipeline, command catalog 내부 동작은 변경하지 않는다.

### 빌드 등급

- build=`local-G06`, review=`local-G06`.
- 근거: 새 CLI/config 표면이지만 네트워크/프로토콜 구현은 제외하고, 단위 테스트와 bin help smoke로 검증 가능하다.

## 구현 체크리스트

- [ ] AGENT_CLI-1 `AgentConfig` 모델과 YAML 파서를 추가한다.
- [ ] AGENT_CLI-2 `agent run --config` CLI command를 추가하고 `bin/main.dart`에 등록한다.
- [ ] AGENT_CLI-3 config/CLI 단위 테스트와 bin help smoke를 추가한다.
- [ ] 모든 중간 검증과 최종 검증을 실행하고 실제 stdout/stderr를 `CODE_REVIEW-local-G06.md`에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [AGENT_CLI-1] AgentConfig 모델과 YAML 파서

#### 문제

- `assets/script/shell/oto_agent_bootstrap.sh:127`부터 `agent`, `edge`, `runtime` YAML을 작성하지만 production code에는 이 config를 읽는 모델이 없다.

#### 해결 방법

- `lib/oto/agent/agent_config.dart`를 추가한다.
- `AgentConfig.fromYamlContent(String)`와 `AgentConfig.fromFile(String)`를 제공한다.
- 필수 값은 `agent.id`, `agent.enrollment_token`, `edge.url`, `runtime.install_dir`, `runtime.workspace_root`, `runtime.log_dir`로 둔다.
- `agent.alias`는 optional string으로 허용하되 null/empty를 그대로 보존한다.

새 파일 핵심 형태:

```dart
class AgentConfig {
  final AgentIdentityConfig agent;
  final EdgeConnectionConfig edge;
  final AgentRuntimeConfig runtime;

  const AgentConfig({
    required this.agent,
    required this.edge,
    required this.runtime,
  });

  factory AgentConfig.fromYamlContent(String content) {
    final root = loadYaml(content);
    // map validation and required field extraction
  }
}
```

#### 수정 파일 및 체크리스트

- [ ] `lib/oto/agent/agent_config.dart`: config classes와 parser 추가.
- [ ] `lib/oto/agent/agent_config.dart`: 누락/잘못된 타입 오류 메시지를 command layer에서 노출 가능한 `Exception`으로 정리.

#### 테스트 작성

- 작성: `test/oto_agent_config_test.dart`
- 테스트 이름:
  - `AgentConfig parses bootstrap config yaml`
  - `AgentConfig rejects missing required values`
- 검증 목표: bootstrap script가 만드는 YAML shape와 동일한 config를 읽고 필수 값 누락 시 실패한다.

#### 중간 검증

```bash
dart test test/oto_agent_config_test.dart
```

예상 결과: 모든 테스트 통과.

### [AGENT_CLI-2] agent run CLI command

#### 문제

- `bin/main.dart:14`의 command list에는 `CommandTemplate`, `CommandExe`, `CommandScheduler`, `CommandCatalogCli`, `CommandValidateCli`만 등록되어 있다.
- bootstrap script는 `assets/script/shell/oto_agent_bootstrap.sh:144`에서 `oto agent run --config "$config_path"`를 실행하지만 CLI command가 없다.

#### 해결 방법

- `lib/cli/commands/command_agent.dart`를 추가한다.
- `agent run --config <path>`만 먼저 지원한다.
- command는 config를 읽고 `AgentRunner` interface에 넘긴다.
- 이 하위 작업의 default runner는 registration 미구현 메시지를 반환하지 말고, injectable fake runner로 테스트 가능하게 만든 뒤 실제 default는 `02+01_edge_registration_runtime`에서 연결한다. bin help smoke는 `agent -h`와 `agent run -h` 중심으로 검증한다.

Before:

```dart
// bin/main.dart:9-20
import 'package:oto/cli/commands/command_catalog.dart';
import 'package:oto/cli/commands/command_validate.dart';

void main(List<String> arguments) async {
  Application('oto', 'com.toki-labs.oto', () {
    CLI.initialize(appName, arguments, [
      CommandTemplate(),
      CommandExe(),
      CommandScheduler(),
      CommandCatalogCli(),
      CommandValidateCli()
    ]);
  }, (error, stack) {
```

After:

```dart
import 'package:oto/cli/commands/command_agent.dart';
import 'package:oto/cli/commands/command_catalog.dart';
import 'package:oto/cli/commands/command_validate.dart';

void main(List<String> arguments) async {
  Application('oto', 'com.toki-labs.oto', () {
    CLI.initialize(appName, arguments, [
      CommandTemplate(),
      CommandExe(),
      CommandScheduler(),
      CommandCatalogCli(),
      CommandValidateCli(),
      CommandAgent(),
    ]);
  }, (error, stack) {
```

#### 수정 파일 및 체크리스트

- [ ] `lib/cli/commands/command_agent.dart`: `CommandBase` 구현 추가.
- [ ] `lib/cli/commands/command_agent.dart`: `run`, `--config`, `--config=<path>` parsing 지원.
- [ ] `bin/main.dart`: import와 command registration 추가.

#### 테스트 작성

- 작성: `test/oto_agent_cli_test.dart`
- 테스트 이름:
  - `CommandAgent parses run config and calls runner`
  - `CommandAgent rejects unknown subcommand`
  - `actual bin agent help lists run usage`
- 검증 목표: runner injection으로 config 전달을 확인하고, 실제 bin help에서 command registration을 확인한다.

#### 중간 검증

```bash
dart test test/oto_agent_cli_test.dart
```

예상 결과: 모든 테스트 통과.

### [AGENT_CLI-3] CLI/config 검증 묶음

#### 문제

- 새 command가 기존 CLI help와 execute log 규칙을 깨지 않는지 확인해야 한다.
- `CommandManager`는 `parameters[0].contains('-h')`로 help를 판단하므로 `agent run -h`처럼 nested help를 직접 처리해야 한다.

#### 해결 방법

- `CommandAgent.shouldExecuteWithoutParameters()`는 false로 둔다.
- `agent -h`는 기존 `CommandManager` help로 처리한다.
- `agent run -h`는 `CommandAgent.execute` 내부에서 run usage를 출력한다.
- JSON 출력 모드는 이 하위 작업에서 추가하지 않는다.

#### 수정 파일 및 체크리스트

- [ ] `lib/cli/commands/command_agent.dart`: command help와 run usage 분리.
- [ ] `test/oto_agent_cli_test.dart`: no-argument, root help, run help 동작 검증.

#### 테스트 작성

- 작성: `test/oto_agent_cli_test.dart`
- 검증 목표: 실제 CLI 등록과 help 출력이 안정적이다.

#### 중간 검증

```bash
dart run bin/main.dart agent -h
```

예상 결과: `agent` command help가 출력되고 exit code 0.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/oto/agent/agent_config.dart` | AGENT_CLI-1 |
| `lib/cli/commands/command_agent.dart` | AGENT_CLI-2, AGENT_CLI-3 |
| `bin/main.dart` | AGENT_CLI-2 |
| `test/oto_agent_config_test.dart` | AGENT_CLI-1 |
| `test/oto_agent_cli_test.dart` | AGENT_CLI-2, AGENT_CLI-3 |

## 최종 검증

```bash
dart analyze
dart test test/oto_agent_config_test.dart test/oto_agent_cli_test.dart
dart run bin/main.dart agent -h
```

예상 결과: `dart analyze` 경고/오류 없음, 테스트 통과, help command exit code 0.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
