# Gemini Usage Status Follow-Up Plan ## 이 파일을 읽는 구현 에이전트에게 **구현 완료 후 `CODE_REVIEW-*-G??.md`의 모든 섹션을 채우는 것이 필수 최종 단계입니다. 이 파일을 채우기 전까지 작업은 완료가 아닙니다.** 아래 체크리스트와 중간/최종 검증을 실제로 실행하고, 구현 내용과 명령 출력을 `CODE_REVIEW-cloud-G07.md`에 기록하세요. `CODE_REVIEW-cloud-G07.md`의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시(`*.log` 변경, `complete.log` 작성)는 구현 에이전트가 수행하지 않습니다. ## 배경 1차 구현은 Gemini `/stats model` quota parser와 checker를 추가했지만, code review에서 두 Required 이슈가 확인되었다. Gemini checker는 `Gemini CLI` 배너만 보고 command를 보낼 수 있어 실제 TUI readiness가 불안정하다. 또한 계획된 `./bin/edge.sh` + `./bin/node.sh` smoke가 실패했고, 보조 `go run node` smoke로 대체되어 사용자 요청의 bin 기준 검증을 만족하지 못했다. ## 분석 결과 ### 읽은 파일 - `agent-ops/skills/common/code-review/SKILL.md` - `agent-task/cli_gemini_usage_status/plan_cloud_G07_0.log` - `agent-task/cli_gemini_usage_status/code_review_cloud_G07_0.log` - `apps/node/internal/adapters/cli/status/gemini.go` - `apps/node/internal/adapters/cli/status/gemini_test.go` - `apps/node/internal/adapters/cli/status/parser.go` - `apps/node/internal/adapters/cli/status/parser_test.go` - `apps/node/internal/adapters/cli/status/status.go` - `apps/node/internal/adapters/cli/status/status_test.go` - `apps/edge/cmd/edge/console.go` - `apps/edge/cmd/edge/console_test.go` - `bin/edge.sh` - `bin/node.sh` ### 테스트 커버리지 공백 - Gemini readiness: 기존 fake tests는 `Gemini CLI` 배너와 ready prompt를 거의 동시에 출력한다. 배너 후 prompt 전 입력이 발생하면 실패하는 테스트가 필요하다. - Bin smoke: 기존 fixed smoke가 실패했다. `bin/node.sh`가 임시 node config를 실제 `node serve`에 전달하는지 검증하는 shell smoke가 필요하다. ### 심볼 참조 - renamed/removed symbol 없음. - 참조 확인 명령: `rg --sort path -n "NewGeminiChecker|GeminiChecker|IOP_NODE_CONFIG|IOP_EDGE_CONFIG|gemini status not implemented|NewGeminiChecker\\(\\)" apps/node/internal/adapters/cli/status bin agent-task/cli_gemini_usage_status` ### 범위 결정 근거 - Gemini `/model`의 모델별 quota는 계속 제외한다. 이번 follow-up은 review Required 이슈만 처리한다. - proto와 config schema는 수정하지 않는다. Shell wrapper override와 TUI readiness 안정화로 충분하다. - 기존 `IOP_EDGE_ADDR` 동작은 유지한다. `bin/node.sh`에는 config file override만 추가해 기본 동작을 바꾸지 않는다. ### 빌드 등급 - build lane: `cloud-G07` — TUI/PTY readiness와 bin shell orchestration 검증이 핵심이다. - review lane: `cloud-G07` — fixed smoke 출력이 실제 재실행과 일치하는지 확인해야 한다. ## 의존 관계 및 구현 순서 1. `REVIEW_API-1`에서 Gemini readiness 조건과 회귀 테스트를 먼저 고친다. 2. `REVIEW_API-2`에서 `bin/node.sh` config override와 bin smoke를 복구한다. ### [REVIEW_API-1] Gemini checker readiness를 prompt-ready 조건으로 제한 #### 문제 `apps/node/internal/adapters/cli/status/gemini.go:97-105`는 `Gemini CLI` 배너만 봐도 ready로 판단하고 300ms 뒤 `/stats model`을 전송한다. 실제 Gemini CLI는 이 배너가 authentication/welcome 단계에서 먼저 보이고 입력 composer는 `? for shortcuts` 또는 prompt/footer 이후 열린다. 이 상태에서는 command가 너무 일찍 보내져 TUI가 무시하거나 다른 상태에 소비할 수 있다. Before (`apps/node/internal/adapters/cli/status/gemini.go:97`): ```go // 1. Wait for startup ready prompt. readyRegex := regexp.MustCompile(`for shortcuts|Gemini CLI|Tips for getting started`) if _, err := waitForAny([]waitPattern{{name: "ready", pattern: readyRegex}}, 20*time.Second); err != nil { return nil, fmt.Errorf("gemini startup failed: %w", err) } time.Sleep(300 * time.Millisecond) // 2. Request /stats model sendText("\x15/stats model\r") ``` #### 해결 방법 `Gemini CLI` banner-only match를 제거하고 input composer가 열린 뒤에만 매치되는 문구로 제한한다. 우선 후보는 `for shortcuts`, `Type your message`, `workspace (/directory)`다. fake TUI는 배너를 먼저 출력하고 500ms 동안 early stdin을 감시한 뒤 ready prompt를 출력하게 만들어, 기존 구현에서는 실패하고 수정 후 통과하게 한다. After: ```go readyRegex := regexp.MustCompile(`for shortcuts|Type your message|workspace \(/directory\)`) ``` #### 수정 파일 및 체크리스트 - [ ] `apps/node/internal/adapters/cli/status/gemini.go` - `readyRegex`에서 `Gemini CLI` 제거 - [ ] `apps/node/internal/adapters/cli/status/gemini.go` - 필요 시 ready wait timeout 에러 tail 개선 - [ ] `apps/node/internal/adapters/cli/status/gemini_test.go` - `TestGeminiCheckerWaitsForInputReady` 추가 #### 테스트 작성 - `apps/node/internal/adapters/cli/status/gemini_test.go` - `TestGeminiCheckerWaitsForInputReady`: fake bash script가 `Gemini CLI v0.42.0` 출력 후 `read -t 0.5 early`로 prompt 전 입력이 오면 exit 4를 낸다. 그 다음 `? for shortcuts`를 출력하고 `/stats model`을 읽어 quota를 반환한다. checker가 early input 없이 정상 quota를 파싱하는지 검증한다. #### 중간 검증 ```bash go test ./apps/node/internal/adapters/cli/status/... -run 'TestGeminiChecker(WaitsForInputReady|RequestsStatsModel|NoAPICallsReturnsRawUnparsed)' -count=1 ``` 기대 결과: Gemini checker fake TUI tests가 모두 통과한다. 캐시 출력은 허용하지 않는다. ### [REVIEW_API-2] `bin/node.sh` config override와 bin-only smoke 복구 #### 문제 `bin/node.sh:6`은 항상 `configs/node.yaml`을 사용한다. `bin/node.sh:11`의 `IOP_EDGE_ADDR`은 reachability check에만 쓰이고, `bin/node.sh:36`의 실제 `node serve --config "$CONFIG_FILE"`에는 반영되지 않는다. 그래서 1차 계획의 fixed smoke는 임시 edge `127.0.0.1:19090`에 reachable check만 통과한 뒤 node가 기본 config의 `localhost:9090`으로 연결해 `error: no nodes connected`로 실패했다. Before (`bin/node.sh:5`): ```bash REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" CONFIG_FILE="$REPO_ROOT/configs/node.yaml" ``` #### 해결 방법 `bin/edge.sh`와 같은 패턴으로 `IOP_NODE_CONFIG` override를 추가한다. 최종 smoke는 `/tmp`에 임시 edge config와 node config를 만들고, edge는 `IOP_EDGE_CONFIG`, node는 `IOP_NODE_CONFIG`로 실행한다. 이로써 `./bin/edge.sh`와 `./bin/node.sh`만으로 fake Gemini `/status`를 검증한다. After: ```bash REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" CONFIG_FILE="${IOP_NODE_CONFIG:-$REPO_ROOT/configs/node.yaml}" ``` #### 수정 파일 및 체크리스트 - [ ] `bin/node.sh` - `IOP_NODE_CONFIG` override 추가 - [ ] `agent-task/cli_gemini_usage_status/CODE_REVIEW-cloud-G07.md` - 최종 검증에 fixed bin-only smoke 실제 출력 기록 #### 테스트 작성 별도 unit test는 작성하지 않는다. 변경은 shell wrapper fallback 1줄이며, 최종 bin-only smoke가 동작을 검증한다. #### 중간 검증 ```bash IOP_NODE_CONFIG=/tmp/non-existent-iop-node.yaml ./bin/node.sh ``` 기대 결과: config 파일이 없어서 edge address parsing 단계에서 invalid edge_addr 또는 file read 기반 error로 실패한다. 즉 `bin/node.sh`가 `IOP_NODE_CONFIG` 값을 사용한다. 이 명령은 실패가 기대 결과이므로 실제 stdout/stderr와 exit status를 기록한다. ## 수정 파일 요약 | 파일 | 항목 | |------|------| | `apps/node/internal/adapters/cli/status/gemini.go` | REVIEW_API-1 | | `apps/node/internal/adapters/cli/status/gemini_test.go` | REVIEW_API-1 | | `bin/node.sh` | REVIEW_API-2 | | `agent-task/cli_gemini_usage_status/CODE_REVIEW-cloud-G07.md` | REVIEW_API-2 | ## 최종 검증 ```bash gofmt -l apps/node/internal/adapters/cli/status/gemini.go apps/node/internal/adapters/cli/status/gemini_test.go ``` 기대 결과: 출력이 없어야 한다. ```bash go test ./apps/node/internal/adapters/cli/status/... ./apps/node/internal/adapters/cli/... ./apps/edge/cmd/edge/... -count=1 ``` 기대 결과: status, cli, edge console tests가 모두 통과한다. 캐시 출력은 허용하지 않는다. ```bash if rg --sort path -n 'gemini status not implemented|NewGeminiChecker\(\)|readyRegex := regexp.MustCompile\(`.*Gemini CLI' apps/node/internal/adapters/cli/status; then exit 1; fi ``` 기대 결과: 출력 없이 exit 0이어야 한다. ```bash tmpdir="$(mktemp -d)" mkfifo "$tmpdir/edge.in" cat > "$tmpdir/gemini" <<'SH' #!/usr/bin/env sh printf 'Gemini CLI v0.42.0\n' printf '? for shortcuts\n' IFS= read -r command cmd="${command#?}" if [ "$cmd" != "/stats model" ] && [ "$command" != "/stats model" ]; then printf 'unexpected command: %s\n' "$command" exit 3 fi printf 'Auto (Gemini 3) Stats For Nerds\n' printf '0%% used (Limit resets in 24h)\n' printf 'Usage limit: 200\n' printf 'Usage limits span all sessions and reset daily.\n' IFS= read -r exit_command exit 0 SH chmod +x "$tmpdir/gemini" perl -0pe 's/listen: "0\.0\.0\.0:9090"/listen: "127.0.0.1:19190"/; s/port: 9092/port: 19192/; s/target: "claude"/target: "gemini"/; s#command: "gemini"#command: "'"$tmpdir"'/gemini"#; s/token: "changeme"/token: "smoketoken"/' configs/edge.yaml > "$tmpdir/edge.yaml" cat > "$tmpdir/node.yaml" <<'YAML' transport: edge_addr: "127.0.0.1:19190" token: "smoketoken" logging: level: "info" pretty: true metrics: port: 19191 YAML IOP_EDGE_CONFIG="$tmpdir/edge.yaml" ./bin/edge.sh < "$tmpdir/edge.in" > "$tmpdir/edge.out" 2> "$tmpdir/edge.err" & edge_pid=$! exec 3> "$tmpdir/edge.in" IOP_NODE_CONFIG="$tmpdir/node.yaml" ./bin/node.sh > "$tmpdir/node.out" 2> "$tmpdir/node.err" & node_pid=$! sleep 12 printf '/nodes\n' >&3 sleep 2 printf '/status\n' >&3 sleep 20 printf '/exit\n' >&3 wait "$edge_pid" kill "$node_pid" 2>/dev/null || true sed -n '1,220p' "$tmpdir/edge.out" sed -n '1,120p' "$tmpdir/edge.err" sed -n '1,160p' "$tmpdir/node.out" sed -n '1,160p' "$tmpdir/node.err" rg -n '\[node-local-node-status\] target=gemini session=default|Daily quota: 100% remaining \(resets 24h\)|Used: 0%|Usage limit: 200' "$tmpdir/edge.out" if rg -n 'unexpected command|error: no nodes connected' "$tmpdir/edge.out" "$tmpdir/edge.err" "$tmpdir/node.out" "$tmpdir/node.err"; then exit 1; fi ``` 기대 결과: `./bin/edge.sh`와 `./bin/node.sh`만으로 edge output에 `[node-local-node-status] target=gemini session=default`, `Daily quota: 100% remaining (resets 24h)`, `Used: 0%`, `Usage limit: 200`이 출력된다. `unexpected command`와 `error: no nodes connected`는 없어야 한다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 전체 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.