@cvprun/cli
v0.1.0
Published
CVP command-line interface
Readme
@cvprun/cli
CVP 에이전트 CLI. 로컬 머신을 CVP 플랫폼의 에이전트로 연결하고,
명령별 기능(현재: modbus-monitor)을 웹 UI와 실시간으로 연동합니다.
요구 사항
- Node.js 22 이상 (전역
WebSocket사용)
사용
npx @cvprun/cli <token> <command> [options]<token>은 웹 UI에서 에이전트 생성 시 표시되는 결합 토큰
(cvp_..._<agent-id>)을 그대로 사용합니다.
npx @cvprun/cli cvp_xxxx..._<agent-id> modbus-monitor전역 설치 시에는 cvp 명령으로 실행합니다.
npm install -g @cvprun/cli
cvp <token> modbus-monitor명령
| 명령 | 설명 |
| ------------------ | ---------------------------------------------------- |
| modbus-monitor | Modbus 클라이언트(monitor)로 접속해 외부 장치를 폴링 |
| modbus-simulator | Modbus TCP 서버(simulator)를 기동 |
| schedule | 등록된 cron 스케줄을 로컬에서 실행 |
| rfdetr | RF-DETR 비전 모델 추론/학습 서버로 대기 |
| sam2 | SAM2 세그멘테이션(매직 완드) 서버로 대기 |
| train | 훈련 위저드 학습 러너로 대기 (rfdetr 어댑터) |
| ytdlp | yt-dlp 동영상 다운로드 러너로 대기 |
명령에 따라 에이전트가 담당하는 connector 모드가 달라지고, 웹 UI의 에이전트 Modbus 화면도 실행 중인 명령에 맞는 탭을 기본으로 보여줍니다.
명령별 실행 권한은 웹 UI의 에이전트 상세 → 권한 탭에서 제어합니다. 차단된
명령으로 접속하면 /connect가 403 command_disabled로 거부하고 CLI는 안내
메시지를 출력한 뒤 종료합니다 (이미 실행 중인 명령은 재시작 시점부터 적용).
옵션
| 옵션 | 설명 |
| -------------- | ---------------------------------------------------------------- |
| --url <base> | API 베이스 URL (기본: $CVP_API_URL 또는 https://app.cvp.run) |
| --help | 도움말 출력 |
| --version | 버전 출력 |
콘솔 로그 적재 (모든 명령 공통)
- 콘솔 로그는 로컬
~/.cache/cvp/logs/<agentId>/YYYY-MM-DD.log(JSONL, UTC 날짜, 최근 14일 보관)에 기록되고, 30초 주기로 해당 일자 파일 전체를 콘솔과 같은 ANSI 컬러 라인으로 렌더해PUT /api/agents/:id/logs/:date로 업로드합니다. - 서버는 프로젝트 스토리지의
Agents/<에이전트 UUID>/logs/<YYYY>/<MM>/<DD>.log키에 덮어쓰므로, '프로젝트 > 파일' 페이지와 에이전트 상세의 '로그' 탭에서 그대로 조회됩니다. 업로드 실패 시 다음 주기에 재시도합니다.
리소스 모니터링 (모든 명령 공통)
에이전트 상세 페이지의 '리소스' 탭에 실시간 시스템 지표를 보냅니다. 어떤 명령을 실행 중이든 켜지며, 별도 설정이 없습니다.
- 수집 항목 — CPU(전체·코어별 사용률, load1), 메모리(+swap), 디스크(마운트 용량·읽기/쓰기 처리율), 네트워크(수신/송신 처리율), 온도, NVIDIA GPU(사용률· VRAM·온도·전력).
- 플랫폼 제약 — CPU·메모리·디스크 용량은 모든 OS 에서 동작합니다. 디스크
I/O(
/proc/diskstats)·네트워크(/proc/net/dev)·온도(/sys/class/thermal,/sys/class/hwmon)는 Linux 전용이고, GPU 는nvidia-smi가 PATH 에 있을 때만 수집합니다. 수집할 수 없는 항목은 UI 가 패널째 숨깁니다. - 런타임 의존성 없음 — Node 내장 API 와
/proc·/sys파싱,nvidia-smi서브프로세스만 씁니다.nvidia-smi가 없으면 첫 시도의 ENOENT 이후 다시 실행하지 않습니다. - 샘플링과 송신은 별개 — 2초마다 샘플을 떠서 약 30분치 링버퍼(900개)를
채우는 일은 항상 하지만, WebSocket 송신은 UI 구독이 살아 있을 때만 합니다.
구독은 UI 가
sys.subscribe를 다시 보내 갱신하는 90초짜리 lease 라서, 브라우저가 탭을 닫으며 사라져도 스트림이 자동으로 멎습니다. - 히스토리는 서버에 저장하지 않습니다. 대시보드를 열면 링버퍼를 백필로 한 번 받아 과거 구간을 채우므로, 볼 수 있는 최대 구간이 그 30분입니다.
modbus 계열 명령 동작
modbus-monitor(monitor): 설정된 외부 Modbus TCP 장치를 영역별pollMs주기로 폴링하여 변경분을 UI에 전파합니다 (읽기 전용).modbus-simulator(simulator): 설정된 포트에 실제 Modbus TCP 서버를 띄웁니다. 외부 Modbus 클라이언트의 쓰기와 UI의 셀 편집이 모두 레지스터에 반영되고, 구독 중인 UI에 델타로 전파됩니다. 레지스터는 시드 값으로 초기화됩니다.- connector 설정 변경/활성화 토글은 약 10초 주기로 자동 반영됩니다.
rfdetr 명령 동작
RF-DETR(Apache-2.0)의 추론/학습 서버로 대기합니다. 객체 탐지 · 인스턴스 세그멘테이션 · 키포인트 세 태스크를 모두 지원하며, 웹 UI의 에이전트 → 어플리케이션 → RF-DETR 화면과 실시간으로 연동됩니다.
- 최초 실행 시 uv로 격리된 Python 3.11 환경을
~/.cache/cvp/rfdetr/에 준비하고rfdetr패키지를 설치합니다 (torch 포함 — 수 분이 걸릴 수 있습니다). - 모델 다운로드: 사전학습 가중치는 사용자가 UI 모델 탭에서 명시적으로
받습니다. 추론·학습은 가중치가 없으면 자동으로 받지 않고
weights_missing으로 되돌려 보냅니다 — 수백 MB 다운로드가 요청 안에서 조용히 일어나면 화면이 멈춘 것처럼 보이기 때문입니다. URL·MD5 는 rfdetr 의ModelWeights레지스트리에서 가져오며, 받은 뒤 MD5 대조와 torch 판독까지 확인합니다. - 추론: UI가 지정한 모델(사전학습 variant 또는 파인튜닝 실행)로 추론하고, 태스크에 따라 박스 · 마스크 폴리곤 · 키포인트를 회신합니다. 모델은 프로세스에 상주하며, 다른 모델이 요청되면 워커를 다시 띄웁니다.
- 학습: UI에서 선택한 CVP 이미지 데이터셋을 태스크에 맞는 COCO 포맷으로
내려받아(세그멘테이션은 폴리곤만, 키포인트는 클래스 스켈레톤을
categories[].keypoints/skeleton으로) 시드 결정적으로 train/valid 분할 후 파인튜닝합니다. 에폭 진행률과 로그가 UI로 중계되고, 완료된 가중치는 즉시 추론에 사용할 수 있습니다. - 학습 취소 시 SIGINT → SIGTERM → SIGKILL 순으로 에스컬레이션하며, GPU(CUDA)가 없으면 CPU로 동작합니다.
- 모델·데이터셋·실행 결과는 모두
~/.cache/cvp/rfdetr/아래에 있습니다 (CVP_RFDETR_DIR로 재정의 가능). rfdetr/DINOv2 의 자체 캐시(RF_HOME,HF_HOME)도 이 디렉토리 안으로 묶어 두었으므로 이 하나만 지우면 정리됩니다.
sam2 명령 동작
SAM2(Apache-2.0) 세그멘테이션 모델의 추론 서버로 대기합니다. 라벨링 에디터의 매직 완드(W)가 연결된 sam2 에이전트를 우선 사용해 토큰 차감 없이 자동 세그멘테이션을 수행합니다 (에이전트가 없으면 클라우드 provider 로 폴백 — 세그멘테이션 provider 가이드).
- 최초 실행 시 uv로 격리된 Python 3.11 환경을
~/.cache/cvp/sam2/에 준비하고sam2패키지를 설치합니다 (torch 포함 — 수 분이 걸릴 수 있습니다). - 체크포인트는 Hugging Face Hub
facebook/sam2.1-hiera-small기본이며CVP_SAM2_MODEL환경변수로 재정의할 수 있습니다. - 추론 요청의 샘플 이미지는 에이전트 토큰으로 내려받아 로컬 캐시하고, 같은 이미지의 연속 클릭은 상주 워커의 임베딩 캐시로 빠르게 처리합니다.
- GPU(CUDA)가 없으면 CPU로 동작합니다.
train 명령 동작
프로젝트의 훈련 위저드(/proj/:id/experiments)가 발사한 학습 run 을 실행하는
러너로 대기합니다. 동작 흐름:
- 위저드가 launch 하면 서버가 WS 로
train.start {run_id}를 보냅니다. 러너는 agent 토큰으로/api/agents/:id/runs/:runId/job을 호출해 잡을 claim 하고 스펙(프레임워크·모델·데이터셋·하이퍼파라미터)을 받습니다. - 프레임워크 어댑터가 학습을 수행합니다. MVP 는 rfdetr 어댑터 하나로,
기존
rfdetr명령의 데이터 준비/가중치/트레이너 모듈을 그대로 재사용합니다 (위저드 컨텍스트에서는 베이스 가중치가 없으면 자동으로 내려받습니다). - 상태·진행률·메트릭·로그는 전부 REST 로 서버에 보고합니다(메트릭은 서버가
MLflow 로 프록시).
train.cancel또는 하트비트 응답의stop_requested로 graceful 종료(SIGINT→SIGTERM→SIGKILL)를 트리거합니다. - 동시 학습은 1개이며, 결과 상태는 종료 경로와 무관하게 항상 보고됩니다.
ytdlp 명령 동작
프로젝트 화면에서 요청한 동영상 다운로드 잡을 실행하는 러너로 대기합니다. 동작 흐름:
- 사용자가 URL 을 넣으면 서버가 잡 행을 만들고 WS 로
ytdlp.start {job_id}를 보냅니다. WS 는 깨우기 신호일 뿐이므로, 러너는 접속 직후와 잡 종료 직후에/api/agents/:id/ytdlp/jobs/next를 확인해 오프라인 동안 쌓인 큐도 소화합니다. - 잡을 claim 하면
~/.cache/cvp/ytdlp/에 uv venv 를 만들고yt-dlp를 설치합니다 (torch 가 없어 준비가 빠릅니다). ffmpeg 는 PATH →imageio-ffmpeg정적 바이너리 순으로 찾고, 둘 다 없으면 병합이 필요한 화질/오디오 프리셋을 거절합니다 (CVP_YTDLP_FFMPEG로 경로를 고정할 수 있습니다). - 다운로드 옵션은 허용리스트로 검증합니다.
--exec·--external-downloader·-o/-P·--cookies등 임의 명령 실행이나 출력 경로 탈취가 가능한 인자는 목록에 없어 자동 거부되며, 출력 경로는 항상 잡 전용 작업 디렉토리로 강제됩니다. - 각 항목은 다운로드 → 프로젝트 '파일' 페이지로 업로드 → 로컬 파일 삭제 순으로 처리합니다. 업로드는 멀티파트이고 러너는 파일 이름만 보냅니다 — 최종 스토리지 키는 서버가 잡의 대상 폴더 안에서 정합니다.
- 상태·진행률·항목·로그는 전부 REST 로 보고합니다.
ytdlp.cancel또는 하트비트 응답의stop_requested로 graceful 종료(SIGINT→SIGTERM→SIGKILL)를 트리거합니다. - 동시 다운로드는 1개이며, 결과 상태는 종료 경로와 무관하게 정확히 한 번 보고됩니다.
재생목록에서 일부 항목만 성공하면
partial로 마감합니다.
라이브러리로도 사용할 수 있습니다.
import {AgentClient, parseAgentToken, runModbusCommand} from '@cvprun/cli';개발
이 패키지는 모노레포 루트(app.cvp.run)의 npm workspace로 관리됩니다.
루트에서 npm install을 한 번 실행하면 의존성이 함께 설치/링크됩니다.
빌드는 루트에서 워크스페이스 플래그로 실행합니다.
npm run build -w @cvprun/cli # Vite 번들 + 타입 선언을 dist/ 로 출력
npm run dev -w @cvprun/cli # watch 모드 빌드테스트
CLI 테스트는 루트의 jsdom 스위트와 분리된 node 환경 vitest 설정
(cli/vitest.config.ts)으로 실행합니다.
npm run test:cli:run # 루트에서 실행 (watch: npm run test:cli)- 테스트·
src/testing/하네스는tsconfig.jsonexclude 로 dist 선언 출력에서 제외되며, 타입 검사는tsconfig.test.json(빌드 스크립트에 포함)이 담당합니다. - 모든 테스트는 OS tmp 의
mkdtemp하위에서만 파일을 만들고(makeTempDir), ephemeral 포트(0)만 사용하며,onTestFinished로 리소스를 정리합니다. 2회 연속 실행해도 결과가 같아야 하고 저장소를 오염시키면 안 됩니다.
공용 테스트 하네스 (src/testing/)
| 모듈 | 용도 |
| ---------------- | -------------------------------------------------------------------------- |
| fake-websocket | 전역 WebSocket 주입형 in-memory 소켓 (open/message/close 를 테스트가 구동) |
| fake-fetch | /api/agents/... 등 HTTP 라우트 스텁 + 호출 기록 |
| tempdir | mkdtemp 임시 디렉토리 + 자동 삭제 |
| env | 환경변수 설정/자동 복원 (CVP_RFDETR_DIR, CVP_UV_BIN 등) |
| modbus-client | 시뮬레이터 loopback 검증용 초소형 Modbus TCP 클라이언트 |
| fake-rfdetr | 실제 uv/Python 없이 @@CVP@@ 프로토콜을 재현하는 가짜 uv/python |
| fake-sam2 | sam2 앱용 가짜 uv/python (fake-rfdetr 의 uv 재사용) |
| fake-ytdlp | ytdlp 앱용 가짜 uv/python/ffmpeg (fake-rfdetr 의 uv 재사용) |
에이전트 앱 추가 시 테스트 체크리스트
새 앱(명령)을 추가할 때 다음을 반드시 갖춥니다.
- 프로토콜 동기화 —
cli/src/protocol.ts와src/worker/lib/agent-protocol.ts양쪽에 메시지 타입을 추가한다.protocol.sync.test.ts가 두 파일의MessageType/CloseCode불일치를 잡아준다 (한쪽만 고치면 실패). - capability 등록 — 명령 이름과 같은 capability 를
agent.hello로 광고하고, workeragent-socket.ts의UI_TO_CVPA화이트리스트에 UI → 에이전트 방향 메시지 타입을 추가한다 (누락 시 UI 소켓이 4102 로 끊김). 에이전트 → UI 브로드캐스트는webSocketMessage의 팬아웃 접두사 조건 (modbus./log./rfdetr./sam2....)에 새 앱 접두사를 추가해야 동작하고,handleUiMessage의 오프라인 오류 회신 매핑에도 앱별 error 타입 분기를 추가한다. - 앱 코어 단위 테스트 — 순수 로직(파서/프레임/상태)은 fake 프로세스·
in-memory 하네스로 검증한다. 무거운 외부 도구(Python 패키지 등)는 절대
실제로 설치/실행하지 않는다 (
fake-rfdetr패턴 참조). - 명령 계층 테스트 —
commands/<app>.test.ts에서 fake WS/fetch 로 명령 전체를 구동해 envelope 핸들링·오류 회신·log.subscribe·terminal close 종료(타이머/프로세스 정리)를 검증한다 (commands/modbus.test.ts패턴). 로그 배선은attachLogStream(client, logHub, agentId)공용 헬퍼를 쓴다 (log.subscribe스냅샷·log.history파일 조회·*.log기록·브로드캐스트 포함 —handleEnvelope에서 두 타입을 헬퍼로 위임하면 끝). 리소스 모니터링도 같은 방식이다 —attachSysMonitor(client)를 붙이고 capabilities 에SYS_CAPABILITY를 넣은 뒤,handleEnvelope에서sys.subscribe/sys.unsubscribe/sys.history를 헬퍼로 넘기고 shutdown 에sysMonitor.stop()을 추가한다. 빠뜨리면 그 명령으로 실행한 에이전트만 리소스 탭이 비어 보인다. - worker 측 테스트 — 새 서버 엔드포인트는
src/worker/api/agents.test.ts패턴(Hono 직접 호출 +testing/supabase-stub)으로 토큰 가드·프로젝트 격리를 검증하고, 중계 규칙은agent-socket.test.ts에 추가한다. - DB — 새 RPC 는 pgTAP(
90-tests.sql)과 api 래퍼 동기화 절차를 따른다 (docs/rules/directory-rules/api.md).
게시
files: ["dist"] 설정으로 이 디렉토리의 dist/만 npm에 업로드됩니다
(npm 이 package.json·README.md·LICENSE 는 항상 함께 포함합니다).
prepublishOnly가 게시 직전에 build:publish를 실행합니다. 이 스크립트는
vite build --mode publish로 빌드하므로 일반 build와 달리 소스맵을 만들지
않습니다 — 배포본에 원본 TypeScript 소스가 실려 나가지 않게 하려는 것입니다.
로컬 build/dev는 디버깅용 소스맵을 그대로 남깁니다.
버전의 단일 원천은 package.json의 version입니다. 빌드 시 vite define이
src/version.ts의 __CLI_VERSION__을 문자열로 치환하므로, 릴리스할 때는
package.json의 버전만 올리면 됩니다(런타임에 package.json을 읽지 않습니다).
npm version patch -w @cvprun/cli # 버전 상향 (원천은 package.json 하나)
npm publish -w @cvprun/cli # scoped public 패키지로 게시게시 후에는 npx @cvprun/cli@latest <token> [command]로 실행합니다.
bin 항목이 cvp 하나뿐이므로 npx 는 패키지명과 실행 파일명이 달라도 그대로
cvp를 실행합니다.
라이선스
CVP License — CVP 서비스 이용 목적의 사용만 허용하는 독점 라이선스입니다.
