refactor(agent): MCP 서버 제거 → CLI 도구 직접 실행 전환 + Wiki 도구 추가
This commit is contained in:
@@ -272,7 +272,7 @@ async def on_message(message: discord.Message):
|
|||||||
await message.reply("실행 중인 작업이 없습니다.")
|
await message.reply("실행 중인 작업이 없습니다.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 에이전트 호출 (MCP 도구 자동 사용)
|
# 에이전트 호출 (CLI 도구 자율 실행)
|
||||||
channel_id = message.channel.id
|
channel_id = message.channel.id
|
||||||
if channel_id in _running_tasks and not _running_tasks[channel_id].done():
|
if channel_id in _running_tasks and not _running_tasks[channel_id].done():
|
||||||
await message.reply("⚠️ 이미 작업이 실행 중입니다. `취소` 후 다시 요청하세요.")
|
await message.reply("⚠️ 이미 작업이 실행 중입니다. `취소` 후 다시 요청하세요.")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
모든 역할이 gemini-3-flash-preview 모델을 사용하며,
|
모든 역할이 gemini-3-flash-preview 모델을 사용하며,
|
||||||
역할별 thinkingBudget을 동적으로 조절합니다.
|
역할별 thinkingBudget을 동적으로 조절합니다.
|
||||||
|
MCP 서버 없이 CLI 도구를 직접 실행하는 구조입니다.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -29,16 +30,7 @@ ROLE_THINKING: dict[str, int] = {
|
|||||||
}
|
}
|
||||||
DEFAULT_THINKING = 4096
|
DEFAULT_THINKING = 4096
|
||||||
|
|
||||||
# 역할별 MCP 서버 접근 허용 목록
|
|
||||||
# agent만 외부 도구 사용 가능, 나머지 역할은 텍스트 전용
|
|
||||||
ROLE_MCP_ACCESS: dict[str, list[str]] = {
|
|
||||||
"agent": ["anime", "infra"],
|
|
||||||
"coder": [],
|
|
||||||
"planner": [],
|
|
||||||
"reviewer": [],
|
|
||||||
"summarizer": [],
|
|
||||||
"unified": [],
|
|
||||||
}
|
|
||||||
|
|
||||||
# 동시 호출 제한 (Gemini AI Ultra 120RPM 고려)
|
# 동시 호출 제한 (Gemini AI Ultra 120RPM 고려)
|
||||||
_semaphore = asyncio.Semaphore(4)
|
_semaphore = asyncio.Semaphore(4)
|
||||||
@@ -73,30 +65,9 @@ class GeminiCaller:
|
|||||||
"--approval-mode", "yolo"]
|
"--approval-mode", "yolo"]
|
||||||
return ["gemini", "--model", GEMINI_MODEL, "--approval-mode", "yolo"]
|
return ["gemini", "--model", GEMINI_MODEL, "--approval-mode", "yolo"]
|
||||||
|
|
||||||
# MCP 서버 설정 (홈 레벨에 등록)
|
|
||||||
_MCP_SERVERS = {
|
|
||||||
"anime": {
|
|
||||||
"command": str(Path(sys.executable)),
|
|
||||||
"args": [str(PROJECT_ROOT / "mcp_servers" / "anime_server.py")],
|
|
||||||
"cwd": str(PROJECT_ROOT),
|
|
||||||
"trust": True,
|
|
||||||
},
|
|
||||||
"infra": {
|
|
||||||
"command": str(Path(sys.executable)),
|
|
||||||
"args": [str(PROJECT_ROOT / "mcp_servers" / "infra_server.py")],
|
|
||||||
"cwd": str(PROJECT_ROOT),
|
|
||||||
"trust": True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _set_thinking_budget(self, role: str):
|
def _set_thinking_budget(self, role: str):
|
||||||
"""역할별 thinkingBudget + MCP 서버 설정을 settings.json에 반영.
|
"""역할별 thinkingBudget을 settings.json에 반영."""
|
||||||
|
|
||||||
MCP 서버는 ROLE_MCP_ACCESS에 따라 역할별로 필터링됩니다.
|
|
||||||
agent 역할만 MCP 도구에 접근 가능하고, 나머지 역할은 제거됩니다.
|
|
||||||
"""
|
|
||||||
budget = ROLE_THINKING.get(role, DEFAULT_THINKING)
|
budget = ROLE_THINKING.get(role, DEFAULT_THINKING)
|
||||||
allowed_mcp = ROLE_MCP_ACCESS.get(role, [])
|
|
||||||
try:
|
try:
|
||||||
if _SETTINGS_PATH.exists():
|
if _SETTINGS_PATH.exists():
|
||||||
settings = json.loads(_SETTINGS_PATH.read_text(encoding="utf-8"))
|
settings = json.loads(_SETTINGS_PATH.read_text(encoding="utf-8"))
|
||||||
@@ -110,22 +81,14 @@ class GeminiCaller:
|
|||||||
thinking = default.setdefault("thinkingConfig", {})
|
thinking = default.setdefault("thinkingConfig", {})
|
||||||
thinking["thinkingBudget"] = budget
|
thinking["thinkingBudget"] = budget
|
||||||
|
|
||||||
# MCP 서버 — 역할별 접근 제어
|
# MCP 서버 제거 (CLI 직접 실행으로 전환)
|
||||||
mcp_servers = settings.setdefault("mcpServers", {})
|
settings.pop("mcpServers", None)
|
||||||
for name, cfg in self._MCP_SERVERS.items():
|
|
||||||
if name in allowed_mcp:
|
|
||||||
mcp_servers[name] = cfg
|
|
||||||
else:
|
|
||||||
mcp_servers.pop(name, None)
|
|
||||||
|
|
||||||
_SETTINGS_PATH.write_text(
|
_SETTINGS_PATH.write_text(
|
||||||
json.dumps(settings, indent=2, ensure_ascii=False),
|
json.dumps(settings, indent=2, ensure_ascii=False),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(f"settings.json 업데이트: role={role}, budget={budget}")
|
||||||
f"settings.json 업데이트: role={role}, budget={budget}, "
|
|
||||||
f"mcp={allowed_mcp or 'none'}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"settings.json 업데이트 실패 (role={role}): {e}")
|
logger.warning(f"settings.json 업데이트 실패 (role={role}): {e}")
|
||||||
|
|
||||||
|
|||||||
90
docs/wiki/climate_risk_scenario_testing.md
Normal file
90
docs/wiki/climate_risk_scenario_testing.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# 금융위험측정에서의 기후 시나리오 테스트 (Climate Scenario Analysis)
|
||||||
|
|
||||||
|
기후 시나리오 분석은 기후변화 및 저탄소 경제로의 전환이 금융 시스템과 개별 금융기관의 건전성에 미치는 영향을 선제적으로 파악하기 위한 핵심적인 리스크 관리 도구입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 개요 및 목적
|
||||||
|
|
||||||
|
### 1.1 개요
|
||||||
|
기후 시나리오 분석은 미래의 기후 변화 경로와 그에 따른 사회·경제적 변화를 가정(Scenario)하고, 이러한 상황이 금융 자산의 가치나 금융기관의 건전성에 미치는 영향을 정량적·정성적으로 평가하는 기법입니다. 과거 데이터에 의존하는 전통적인 리스크 모델과 달리, 전례 없는 기후 변화의 불확실성을 다루기 위해 **미래 지향적(Forward-looking)** 관점을 취합니다.
|
||||||
|
|
||||||
|
### 1.2 주요 리스크 구분
|
||||||
|
기후 리스크는 크게 물리적 리스크와 이행 리스크로 구분됩니다.
|
||||||
|
|
||||||
|
* **물리적 리스크 (Physical Risk):** 기후변화로 인한 물리적 현상이 자산 가치 훼손이나 운영 중단을 초래하여 발생하는 위험입니다.
|
||||||
|
* **급성(Acute):** 태풍, 홍수, 가뭄, 산불 등 기상이변의 빈도와 강도 증가로 인한 즉각적인 피해.
|
||||||
|
* **만성(Chronic):** 지구 온난화에 따른 평균 기온 상승, 해수면 상승 등 장기적인 기후 패턴 변화로 인한 자산 가치 하락.
|
||||||
|
* **이행 리스크 (Transition Risk):** 저탄소 경제로 전환하는 과정에서 발생하는 정책, 법률, 기술, 시장의 변화로 인한 위험입니다.
|
||||||
|
* **정책/법률:** 탄소세 도입, 배출권 거래제 강화, 에너지 효율 규제.
|
||||||
|
* **기술 변화:** 신재생 에너지 기술 발전 및 전기차 보급 확대로 인한 고탄소 산업의 **좌초자산(Stranded Assets)** 발생.
|
||||||
|
* **시장/평판:** 소비자 선호도 변화 및 투자자의 ESG 요구 강화로 인한 수익성 악화.
|
||||||
|
|
||||||
|
### 1.3 목적
|
||||||
|
* **리스크 식별:** 표준 모델이 포착하지 못하는 기후 관련 금융 위험을 수치화.
|
||||||
|
* **전략적 대응:** 다양한 미래 경로에 따른 비즈니스 모델의 회복탄력성(Resilience) 점검.
|
||||||
|
* **규제 대응:** TCFD 권고안 등 국제 기준에 따른 공시 요구 및 감독당국의 스트레스 테스트 대응.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 주요 방법론
|
||||||
|
|
||||||
|
### 2.1 NGFS 시나리오 프레임워크
|
||||||
|
NGFS(녹색금융시스템을 위한 중앙은행 및 감독기구 네트워크)는 전 세계적으로 가장 널리 사용되는 표준 시나리오를 제공합니다.
|
||||||
|
|
||||||
|
1. **질서 있는 전환 (Orderly):** 기후 정책이 조기에 도입되어 물리적/이행 리스크가 낮은 수준에서 관리되며 2050 탄소중립 달성.
|
||||||
|
2. **무질서한 전환 (Disorderly):** 정책 도입이 지연되다가 갑작스럽게 시행되어 이행 리스크가 급격히 상승.
|
||||||
|
3. **핫하우스 월드 (Hot House World):** 현재 정책이 유지되어 지구 온도가 3℃ 이상 상승, 물리적 리스크가 극대화됨.
|
||||||
|
|
||||||
|
### 2.2 분석 접근법
|
||||||
|
* **하향식 접근법 (Top-down):** 중앙은행이나 감독당국이 주도하여 금융 시스템 전체의 리스크를 직접 추정. 결과의 비교 가능성이 높으나 개별 기관의 특성 반영이 미흡함.
|
||||||
|
* **상향식 접근법 (Bottom-up):** 개별 금융기관이 자체 데이터와 리스크 모델을 사용하여 상세 분석 후 보고. 정밀한 리스크 분석이 가능하나 기관별 일관성이 낮음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 국내외 현황
|
||||||
|
|
||||||
|
### 3.1 해외 현황
|
||||||
|
* **BCBS (바젤은행감독위원회):** 기후 리스크를 기존 리스크 관리 체계(Pillar 1, 2, 3)에 통합하도록 권고하고 시나리오 분석 표준화를 추진 중.
|
||||||
|
* **ECB (유럽중앙은행):** 'Fit-for-55' 테스트를 통해 기후 리스크 관리 미흡 은행에 대한 자본 가산(Capital Add-ons) 등 실질적 감독 조치 시행.
|
||||||
|
* **영국 (BoE):** CBES(Climate Biennial Exploratory Scenario)를 통해 은행/보험사의 장기 영향을 분석하고 데이터 공백 해소에 주력.
|
||||||
|
|
||||||
|
### 3.2 국내 현황
|
||||||
|
* **금융감독원 & 한국은행:** 기상청과 협력하여 **'공동 기후 스트레스 테스트'** 추진 중.
|
||||||
|
* **특징:** 장기 분석뿐만 아니라 향후 5년 이내 발생 가능한 이상기후 영향을 집중 점검하는 **단기 시나리오** 개발에 집중.
|
||||||
|
* **추진 일정:** 2026년 하반기 15개 주요 금융회사를 대상으로 실제 영향 측정 및 결과 분석 예정.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 프로그램 구현을 위한 핵심 데이터 구조 및 알고리즘
|
||||||
|
|
||||||
|
### 4.1 핵심 데이터 구조 (Data Schema)
|
||||||
|
|
||||||
|
| 데이터 레이어 | 주요 필드 및 구성 요소 |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **자산 데이터 (Asset)** | `Location(GIS)`, `Sector(NACE)`, `Scope 1/2/3 Emission`, `Book Value`, `LTV` |
|
||||||
|
| **시나리오 데이터 (Climate)** | `Scenario_ID(NGFS)`, `Carbon_Price_Path`, `Hazard_Map(Flooding Depth)`, `GDP_Impact` |
|
||||||
|
| **충격 계수 (Shock Factor)** | `Transition_Vulnerability_Factor(TVF)`, `Damage_Function_Coeff`, `PD_Shift_Matrix` |
|
||||||
|
|
||||||
|
### 4.2 알고리즘 로직 및 전이 경로 (Transmission Logic)
|
||||||
|
|
||||||
|
1. **위해도 평가 (Hazard Assessment):**
|
||||||
|
* 물리적: 특정 위경도 좌표의 침수 깊이(m) 또는 폭염 일수 산출.
|
||||||
|
* 이행: 시나리오별 탄소 가격 상승분($/ton) 도출.
|
||||||
|
2. **노출 및 취약성 적용:**
|
||||||
|
* `Physical_Loss = Asset_Value * Damage_Function(Hazard_Intensity)`
|
||||||
|
* `Transition_Cost = GHG_Emission * Carbon_Price_Delta`
|
||||||
|
3. **재무 영향 산출 (Financial Impact):**
|
||||||
|
* 영업이익 및 현금흐름(CF) 조정 → 이자보상배율 변화 분석.
|
||||||
|
* 부동산 담보 가치 하락분 반영 → LTV(담보인정비율) 재산출.
|
||||||
|
4. **리스크 지표 전이:**
|
||||||
|
* **신용 리스크:** 재무 악화에 따른 부도율(PD) 및 부도시 손실률(LGD) 상향 조정.
|
||||||
|
* **시장 리스크:** 전환 취약성 계수를 반영한 주가 및 채권 가치 재평가.
|
||||||
|
|
||||||
|
### 4.3 자산별 리스크 평가 예시
|
||||||
|
* **부동산:** 홍수 시나리오에서 침수 깊이별 손실률 계수(예: 1m 침수 시 가액 20% 하락)를 적용하여 담보 가치 산출.
|
||||||
|
* **에너지/제조업:** 탄소 가격 급등 시나리오에서 고탄소 배출 기업의 주가 충격 계수(예: 최대 -70% 하락)를 적용하여 투자 포토폴리오 평가.
|
||||||
|
|
||||||
|
---
|
||||||
|
*작성일: 2026년 3월 15일*
|
||||||
|
*출처: NGFS, BCBS, 금융감독원, 한국은행 연구 보고서 종합*
|
||||||
@@ -1 +0,0 @@
|
|||||||
# MCP 서버 패키지
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
"""MCP 서버 — 애니메이션 도구.
|
|
||||||
|
|
||||||
Gemini CLI에서 MCP로 연결하여 애니 검색/다운로드/편성표 조회를 수행합니다.
|
|
||||||
stdio 트랜스포트를 사용합니다.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# 프로젝트 루트를 sys.path에 추가 (tools/, config 접근용)
|
|
||||||
PROJECT_ROOT = Path(__file__).parent.parent
|
|
||||||
sys.path.insert(0, str(PROJECT_ROOT))
|
|
||||||
|
|
||||||
import config # noqa: E402 — .env 로드
|
|
||||||
|
|
||||||
from mcp.server.fastmcp import FastMCP # noqa: E402
|
|
||||||
|
|
||||||
mcp = FastMCP("anime")
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
# 애니 검색
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def anime_search(title: str) -> str:
|
|
||||||
"""애니메이션을 검색합니다.
|
|
||||||
|
|
||||||
제목으로 Anissia에서 애니를 검색하고, 자막 제작자 정보와
|
|
||||||
Nyaa 토렌트 검색 결과를 함께 반환합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
title: 검색할 애니 제목 (한글 또는 일본어)
|
|
||||||
"""
|
|
||||||
from tools.anime_pipeline import AnimePipeline
|
|
||||||
|
|
||||||
pipeline = AnimePipeline()
|
|
||||||
result = await pipeline.search(title)
|
|
||||||
|
|
||||||
if not result.success:
|
|
||||||
errors = "; ".join(result.errors) if result.errors else ""
|
|
||||||
return f"검색 실패: {result.message} {errors}"
|
|
||||||
|
|
||||||
anime = result.anime
|
|
||||||
lines = [
|
|
||||||
f"## {anime.subject} ({anime.original_subject})",
|
|
||||||
f"- 장르: {anime.genres}",
|
|
||||||
]
|
|
||||||
|
|
||||||
week_names = ['일', '월', '화', '수', '목', '금', '토', '기타']
|
|
||||||
if anime.week is not None:
|
|
||||||
lines.append(f"- 편성: {week_names[anime.week]}요일 {anime.time}")
|
|
||||||
lines.append(f"- NAS 폴더: {result.nas_folder}")
|
|
||||||
|
|
||||||
if result.captions:
|
|
||||||
lines.append(f"\n### 자막 ({len(result.captions)}명)")
|
|
||||||
for c in result.captions[:5]:
|
|
||||||
url = f" ({c.website})" if c.website else ""
|
|
||||||
lines.append(f" - {c.name} — {c.episode}화{url}")
|
|
||||||
|
|
||||||
if result.torrents:
|
|
||||||
lines.append(f"\n### 토렌트 ({len(result.torrents)}건)")
|
|
||||||
for t in result.torrents[:5]:
|
|
||||||
ep = f"{t.episode}화 " if t.episode else ""
|
|
||||||
lines.append(f" - [{t.group}] {ep}{t.size} (시드: {t.seeders})")
|
|
||||||
|
|
||||||
if result.errors:
|
|
||||||
lines.append(f"\n### 오류")
|
|
||||||
for e in result.errors:
|
|
||||||
lines.append(f" - {e}")
|
|
||||||
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
# 애니 다운로드
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def anime_download(
|
|
||||||
title: str,
|
|
||||||
mode: str = "auto",
|
|
||||||
episode: int | None = None,
|
|
||||||
) -> str:
|
|
||||||
"""애니메이션을 다운로드합니다 (자막+영상).
|
|
||||||
|
|
||||||
Anissia에서 검색 후 자막과 토렌트를 NAS에 다운로드합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
title: 다운로드할 애니 제목
|
|
||||||
mode: "auto" (자막+영상), "sub_only" (자막만), "video_only" (영상만)
|
|
||||||
episode: 특정 에피소드 번호 (None이면 최신)
|
|
||||||
"""
|
|
||||||
from tools.anime_pipeline import AnimePipeline
|
|
||||||
|
|
||||||
pipeline = AnimePipeline()
|
|
||||||
result = await pipeline.download(title, mode=mode, episode=episode)
|
|
||||||
return result.message
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
# 편성표 조회
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def anime_schedule(weekday: int | None = None, sub_only: bool = False) -> str:
|
|
||||||
"""애니 편성표를 조회합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
weekday: 요일 번호 (0=일, 1=월, ..., 6=토). None이면 전체 방영 중 목록.
|
|
||||||
sub_only: True이면 자막 있는 애니만 표시
|
|
||||||
"""
|
|
||||||
from tools.anissia_client import AnissiaClient
|
|
||||||
|
|
||||||
client = AnissiaClient()
|
|
||||||
|
|
||||||
if weekday is not None:
|
|
||||||
schedule = await client.get_schedule(weekday)
|
|
||||||
week_names = ['일', '월', '화', '수', '목', '금', '토']
|
|
||||||
title = f"{week_names[weekday]}요일 편성표"
|
|
||||||
else:
|
|
||||||
schedule = await client.get_all_schedule()
|
|
||||||
schedule = [a for a in schedule if a.status == "ON"]
|
|
||||||
title = "현재 방영 중인 애니"
|
|
||||||
|
|
||||||
if sub_only:
|
|
||||||
schedule = [a for a in schedule if a.caption_count > 0]
|
|
||||||
title += " (자막 있음)"
|
|
||||||
|
|
||||||
lines = [f"## {title} ({len(schedule)}개)"]
|
|
||||||
for a in schedule[:30]:
|
|
||||||
sub = f" 📝{a.caption_count}" if a.caption_count > 0 else ""
|
|
||||||
lines.append(f"- {a.subject} — {a.time}{sub}")
|
|
||||||
|
|
||||||
if len(schedule) > 30:
|
|
||||||
lines.append(f"... 외 {len(schedule) - 30}개")
|
|
||||||
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
# 다운로드 상태
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def anime_download_status() -> str:
|
|
||||||
"""qBittorrent의 현재 다운로드 상태를 확인합니다."""
|
|
||||||
from tools.anime_pipeline import AnimePipeline
|
|
||||||
|
|
||||||
pipeline = AnimePipeline()
|
|
||||||
conn = await pipeline.qbit.test_connection()
|
|
||||||
|
|
||||||
if not conn.get("connected"):
|
|
||||||
return f"qBittorrent 연결 실패: {conn.get('error', '알 수 없는 오류')}"
|
|
||||||
|
|
||||||
torrents = await pipeline.get_status()
|
|
||||||
|
|
||||||
if not torrents:
|
|
||||||
return "다운로드 중인 항목이 없습니다."
|
|
||||||
|
|
||||||
lines = [f"## 다운로드 큐 ({len(torrents)}건)"]
|
|
||||||
for t in torrents[:15]:
|
|
||||||
icon = "✅" if t["progress"] == "100.0%" else "⏳"
|
|
||||||
lines.append(
|
|
||||||
f"- {icon} {t['name'][:50]} — {t['progress']} "
|
|
||||||
f"({t['speed']}, ETA: {t['eta']})"
|
|
||||||
)
|
|
||||||
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
# NAS 애니 목록
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def anime_nas_list(current_quarter: bool = False, keyword: str = "") -> str:
|
|
||||||
"""NAS에 다운로드된 애니 목록을 조회합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
current_quarter: True이면 이번 분기 애니만 표시
|
|
||||||
keyword: 검색 키워드 (빈 문자열이면 전체)
|
|
||||||
"""
|
|
||||||
from tools.nas_scanner import NasScanner
|
|
||||||
|
|
||||||
scanner = NasScanner()
|
|
||||||
if not scanner.is_accessible():
|
|
||||||
return f"NAS 접근 불가: {scanner.base_path}"
|
|
||||||
|
|
||||||
if keyword:
|
|
||||||
folders = scanner.search(keyword)
|
|
||||||
elif current_quarter:
|
|
||||||
folders = scanner.get_current_quarter_anime()
|
|
||||||
else:
|
|
||||||
folders = scanner.list_anime_folders()
|
|
||||||
|
|
||||||
if not folders:
|
|
||||||
return "조건에 맞는 다운로드된 애니가 없습니다."
|
|
||||||
|
|
||||||
total_vids = sum(f.video_count for f in folders)
|
|
||||||
total_subs = sum(f.subtitle_count for f in folders)
|
|
||||||
total_size = sum(f.total_size_gb for f in folders)
|
|
||||||
|
|
||||||
lines = [f"## NAS 애니 목록 ({len(folders)}개)"]
|
|
||||||
for f in folders[:25]:
|
|
||||||
sub = f" 📝{f.subtitle_count}" if f.subtitle_count > 0 else ""
|
|
||||||
lines.append(
|
|
||||||
f"- {f.title} — 🎬{f.video_count}화{sub} ({f.total_size_gb:.1f}GB)"
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(folders) > 25:
|
|
||||||
lines.append(f"... 외 {len(folders) - 25}개")
|
|
||||||
|
|
||||||
lines.append(f"\n총 {total_vids}개 영상 | {total_subs}개 자막 | {total_size:.1f}GB")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
# 실행
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
mcp.run(transport="stdio")
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
"""MCP 서버 — 인프라 도구 (Gitea + Vikunja).
|
|
||||||
|
|
||||||
Gemini CLI에서 MCP로 연결하여 Git 저장소 관리, 태스크 관리를 수행합니다.
|
|
||||||
stdio 트랜스포트를 사용합니다.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# 프로젝트 루트를 sys.path에 추가
|
|
||||||
PROJECT_ROOT = Path(__file__).parent.parent
|
|
||||||
sys.path.insert(0, str(PROJECT_ROOT))
|
|
||||||
|
|
||||||
import config # noqa: E402
|
|
||||||
|
|
||||||
from mcp.server.fastmcp import FastMCP # noqa: E402
|
|
||||||
|
|
||||||
mcp = FastMCP("infra")
|
|
||||||
|
|
||||||
|
|
||||||
# ══════════════════════════════════════════
|
|
||||||
# Gitea 도구
|
|
||||||
# ══════════════════════════════════════════
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def gitea_commits(limit: int = 5, branch: str = "main") -> str:
|
|
||||||
"""Gitea 저장소의 최근 커밋 목록을 조회합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
limit: 조회할 커밋 수 (기본 5)
|
|
||||||
branch: 브랜치 이름 (기본 main)
|
|
||||||
"""
|
|
||||||
from integrations.gitea_client import GiteaClient
|
|
||||||
|
|
||||||
client = GiteaClient()
|
|
||||||
commits = await client.get_commits(limit=limit, branch=branch)
|
|
||||||
|
|
||||||
if not commits:
|
|
||||||
return "커밋이 없습니다."
|
|
||||||
|
|
||||||
lines = [f"## 최근 커밋 ({branch}, {len(commits)}개)"]
|
|
||||||
for c in commits:
|
|
||||||
lines.append(f"- `{c['sha']}` {c['message']} — {c['author']}")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def gitea_prs(state: str = "open") -> str:
|
|
||||||
"""Gitea 저장소의 PR 목록을 조회합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
state: "open" 또는 "closed" (기본 open)
|
|
||||||
"""
|
|
||||||
from integrations.gitea_client import GiteaClient
|
|
||||||
|
|
||||||
client = GiteaClient()
|
|
||||||
prs = await client.list_prs(state=state)
|
|
||||||
|
|
||||||
if not prs:
|
|
||||||
return f"{state} 상태의 PR이 없습니다."
|
|
||||||
|
|
||||||
lines = [f"## PR 목록 ({state}, {len(prs)}개)"]
|
|
||||||
for p in prs:
|
|
||||||
lines.append(f"- #{p['number']} {p['title']} ({p['state']}, {p['user']})")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def gitea_issues(state: str = "open") -> str:
|
|
||||||
"""Gitea 저장소의 이슈 목록을 조회합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
state: "open" 또는 "closed" (기본 open)
|
|
||||||
"""
|
|
||||||
from integrations.gitea_client import GiteaClient
|
|
||||||
|
|
||||||
client = GiteaClient()
|
|
||||||
issues = await client.list_issues(state=state)
|
|
||||||
|
|
||||||
if not issues:
|
|
||||||
return f"{state} 상태의 이슈가 없습니다."
|
|
||||||
|
|
||||||
lines = [f"## 이슈 목록 ({state}, {len(issues)}개)"]
|
|
||||||
for i in issues:
|
|
||||||
lines.append(f"- #{i['number']} {i['title']} ({i['state']})")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def gitea_branches() -> str:
|
|
||||||
"""Gitea 저장소의 브랜치 목록을 조회합니다."""
|
|
||||||
from integrations.gitea_client import GiteaClient
|
|
||||||
|
|
||||||
client = GiteaClient()
|
|
||||||
branches = await client.list_branches()
|
|
||||||
return "## 브랜치 목록\n" + "\n".join(f"- {b}" for b in branches)
|
|
||||||
|
|
||||||
|
|
||||||
# ══════════════════════════════════════════
|
|
||||||
# Vikunja 도구
|
|
||||||
# ══════════════════════════════════════════
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def vikunja_tasks(filter: str = "todo") -> str:
|
|
||||||
"""Vikunja 프로젝트의 태스크 목록을 조회합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
filter: "todo" (미완료), "done" (완료), "all" (전체)
|
|
||||||
"""
|
|
||||||
from integrations.vikunja_client import VikunjaClient
|
|
||||||
|
|
||||||
client = VikunjaClient()
|
|
||||||
tasks = await client.list_tasks(filter_=filter)
|
|
||||||
|
|
||||||
if not tasks:
|
|
||||||
return f"{filter} 상태의 태스크가 없습니다."
|
|
||||||
|
|
||||||
lines = [f"## 태스크 ({filter}, {len(tasks)}개)"]
|
|
||||||
for t in tasks:
|
|
||||||
icon = "✅" if t["done"] else "⬜"
|
|
||||||
labels = f" [{', '.join(t['labels'])}]" if t["labels"] else ""
|
|
||||||
desc = f" — {t['description']}" if t["description"] else ""
|
|
||||||
lines.append(f"- {icon} #{t['id']} {t['title']}{labels}{desc}")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def vikunja_create_task(title: str, description: str = "") -> str:
|
|
||||||
"""Vikunja에 새 태스크를 생성합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
title: 태스크 제목
|
|
||||||
description: 태스크 설명 (선택)
|
|
||||||
"""
|
|
||||||
from integrations.vikunja_client import VikunjaClient
|
|
||||||
|
|
||||||
client = VikunjaClient()
|
|
||||||
result = await client.create_task(title=title, description=description)
|
|
||||||
return f"태스크 #{result['id']} 생성: {result['title']}"
|
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def vikunja_complete_task(task_id: int) -> str:
|
|
||||||
"""Vikunja 태스크를 완료 처리합니다.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
task_id: 완료할 태스크 ID
|
|
||||||
"""
|
|
||||||
from integrations.vikunja_client import VikunjaClient
|
|
||||||
|
|
||||||
client = VikunjaClient()
|
|
||||||
result = await client.mark_done(task_id)
|
|
||||||
return f"태스크 #{task_id} 완료: {result['title']}"
|
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
# 실행
|
|
||||||
# ──────────────────────────────────────────
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
mcp.run(transport="stdio")
|
|
||||||
100
prompts/agent.md
100
prompts/agent.md
@@ -3,56 +3,82 @@
|
|||||||
당신은 **Variet Agent** — 범용 AI 에이전트입니다.
|
당신은 **Variet Agent** — 범용 AI 에이전트입니다.
|
||||||
사용자의 요청을 이해하고, 필요한 도구를 자율적으로 선택하여 작업을 완수합니다.
|
사용자의 요청을 이해하고, 필요한 도구를 자율적으로 선택하여 작업을 완수합니다.
|
||||||
|
|
||||||
## 도구 사용 원칙
|
## Python
|
||||||
|
|
||||||
- 도구 없이 답변할 수 있으면 **바로 답변**하세요.
|
```
|
||||||
- 도구가 필요하면 호출하고, **결과를 확인한 뒤** 답변하세요.
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe
|
||||||
- 여러 도구를 **순서대로** 사용해야 할 때도 있습니다.
|
```
|
||||||
- 도구 호출 결과가 불충분하면 **다른 도구를 시도**하거나 **다른 파라미터**로 재호출하세요.
|
|
||||||
|
반드시 이 절대경로 사용. `python` 단독 사용 금지.
|
||||||
|
|
||||||
|
## 핵심 규칙
|
||||||
|
|
||||||
|
1. **설명하지 말고 바로 실행하세요.** "~하겠습니다" 없이 즉시 도구를 실행하세요.
|
||||||
|
2. **아래 도구 명령만 사용하세요.** 다른 명령어 사용 금지.
|
||||||
|
3. 도구 없이 답변할 수 있으면 **바로 답변**하세요.
|
||||||
|
4. 도구 호출 결과가 불충분하면 **다른 도구를 시도**하거나 **다른 파라미터**로 재호출하세요.
|
||||||
|
|
||||||
## ⛔ 절대 금지
|
## ⛔ 절대 금지
|
||||||
|
|
||||||
- **쉘 명령어로 직접 다운로드하지 마세요** (curl, wget, pip install 등)
|
- **쉘 명령어로 직접 다운로드하지 마세요** (curl, wget, pip install 등)
|
||||||
- **파일을 직접 생성/수정하지 마세요** — MCP 도구만 사용하세요
|
- **파일을 직접 생성/수정하지 마세요** — 아래 CLI 도구만 사용하세요
|
||||||
- 사용자가 요청하지 않은 작업을 임의로 수행하지 마세요
|
- 사용자가 요청하지 않은 작업을 임의로 수행하지 마세요
|
||||||
|
|
||||||
## 사용 가능한 도구 영역
|
## 사용 가능한 도구
|
||||||
|
|
||||||
### 🎬 anime 서버 — 애니메이션 관련은 반드시 이 도구만 사용
|
### 🎬 애니메이션
|
||||||
- `anime_search` — 애니 검색 (제목, 자막, 토렌트)
|
|
||||||
- `anime_download` — 애니 다운로드 (자막+영상). 한 번에 **하나의 작품**만 다운로드.
|
|
||||||
- `anime_schedule` — 편성표 조회
|
|
||||||
- `anime_download_status` — qBittorrent 상태
|
|
||||||
- `anime_nas_list` — NAS 다운로드 목록
|
|
||||||
|
|
||||||
### 🔧 infra 서버 — Git/태스크 관련은 반드시 이 도구만 사용
|
```bash
|
||||||
- `gitea_commits`, `gitea_prs`, `gitea_issues`, `gitea_branches` — Git 관리
|
# 복합 작업 (대부분의 요청에 이것만 쓰면 됨)
|
||||||
- `vikunja_tasks`, `vikunja_create_task`, `vikunja_complete_task` — 태스크 관리
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/anime_pipeline.py search "제목"
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/anime_pipeline.py download "제목"
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/anime_pipeline.py download "제목" --episode 10
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/anime_pipeline.py batch
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/anime_pipeline.py batch --no-sub-filter
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/anime_pipeline.py status
|
||||||
|
|
||||||
## ⚠️ 복수 작품 처리 — 반드시 전부 완료할 것
|
# NAS 폴더
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/nas_scanner.py scan
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/nas_scanner.py search "키워드"
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/nas_scanner.py summary
|
||||||
|
|
||||||
|
# 개별 도구 (세밀한 제어가 필요할 때)
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/anissia_client.py search "제목"
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/anissia_client.py captions <anime_no>
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/nyaa_client.py search "영문제목"
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/qbit_client.py status
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/qbit_client.py add "magnet:..." --path "경로"
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/qbit_client.py delete <hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📖 Wiki.js
|
||||||
|
|
||||||
|
```bash
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/wiki_client.py list [prefix]
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/wiki_client.py get <path>
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/wiki_client.py create <path> <title> [content]
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe tools/wiki_client.py dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
> **위키 등록 필수**: 조사/리서치 결과는 **반드시** `wiki_client.py create`로 Wiki.js에 등록하세요. 로컬 파일에 쓰지 마세요.
|
||||||
|
|
||||||
|
### 🔧 인프라 (Git/태스크)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vikunja 태스크
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe .agent/workflows/helpers/vikunja_helper.py list
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe .agent/workflows/helpers/vikunja_helper.py create "제목" "설명"
|
||||||
|
C:\ProgramData\miniforge3\envs\variet-agent\python.exe .agent/workflows/helpers/vikunja_helper.py done <ID>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚡ 복수 작품 처리 — 반드시 전부 완료할 것
|
||||||
|
|
||||||
사용자가 "이번 분기 애니 다운받아줘" 등 **복수 작업**을 요청하면:
|
사용자가 "이번 분기 애니 다운받아줘" 등 **복수 작업**을 요청하면:
|
||||||
|
|
||||||
1. `anime_nas_list(current_quarter=True)`로 이번 분기 애니 **전체 목록** 확인
|
1. `anime_pipeline.py batch`를 사용하세요 (가장 효율적)
|
||||||
2. 목록의 **모든 작품**에 대해 `anime_download`를 **하나씩 순서대로 호출**
|
2. 또는 `nas_scanner.py scan`으로 목록 확인 → 각 작품을 `anime_pipeline.py download`로 **하나씩 순서대로** 호출
|
||||||
3. **1개만 하고 멈추지 마세요** — 목록 끝까지 전부 처리해야 합니다
|
3. **1개만 하고 멈추지 마세요** — 목록 끝까지 전부 처리
|
||||||
4. 도중에 개별 실패가 있어도 **다음 작품으로 넘어가세요**
|
4. 도중에 실패가 있어도 **다음 작품으로 넘어가세요**
|
||||||
5. 전부 완료한 뒤 결과를 정리하여 보고하세요
|
|
||||||
|
|
||||||
### 예시 흐름
|
|
||||||
|
|
||||||
```
|
|
||||||
→ anime_nas_list(current_quarter=True)
|
|
||||||
"5개 애니 확인: A, B, C, D, E"
|
|
||||||
|
|
||||||
→ anime_download("A") → 결과 기록
|
|
||||||
→ anime_download("B") → 결과 기록
|
|
||||||
→ anime_download("C") → 결과 기록
|
|
||||||
→ anime_download("D") → 결과 기록
|
|
||||||
→ anime_download("E") → 결과 기록
|
|
||||||
|
|
||||||
→ 최종 보고: "5개 중 3개 성공, 2개 보류"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 응답 규칙
|
## 응답 규칙
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
# Operator — 도구 실행 에이전트
|
|
||||||
|
|
||||||
> CLI 도구를 실행하고 결과를 보고합니다. 코드 수정 금지.
|
|
||||||
|
|
||||||
## Python
|
|
||||||
|
|
||||||
```
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
반드시 이 절대경로 사용. `python` 단독 사용 금지.
|
|
||||||
|
|
||||||
## 핵심 규칙
|
|
||||||
|
|
||||||
1. **설명하지 말고 바로 실행하세요.** "~하겠습니다" 없이 즉시 도구를 실행하세요.
|
|
||||||
2. **파일 탐색/코드 분석 금지.** `ls`, `dir`, `find`, `cat` 등으로 프로젝트를 탐색하지 마세요.
|
|
||||||
3. **아래 도구 명령만 사용하세요.** 다른 명령어 사용 금지.
|
|
||||||
|
|
||||||
## 사용 가능한 도구
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 복합 작업 (대부분의 요청에 이것만 쓰면 됨)
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anime_pipeline.py search "제목"
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anime_pipeline.py download "제목"
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anime_pipeline.py download "제목" --episode 10
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anime_pipeline.py batch
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anime_pipeline.py batch --no-sub-filter
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anime_pipeline.py status
|
|
||||||
|
|
||||||
# NAS 폴더
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/nas_scanner.py scan
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/nas_scanner.py search "키워드"
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/nas_scanner.py summary
|
|
||||||
|
|
||||||
# 개별 도구 (세밀한 제어가 필요할 때)
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anissia_client.py search "제목"
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anissia_client.py captions <anime_no>
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/nyaa_client.py search "영문제목"
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/qbit_client.py status
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/qbit_client.py add "magnet:..." --path "경로"
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/qbit_client.py delete <hash>
|
|
||||||
|
|
||||||
# Wiki.js 도구
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/wiki_client.py list [prefix]
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/wiki_client.py get <path>
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/wiki_client.py create <path> <title> [content]
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/wiki_client.py dashboard
|
|
||||||
```
|
|
||||||
|
|
||||||
## 실행 패턴 예시
|
|
||||||
|
|
||||||
### "이번 분기 애니 뭐있어?"
|
|
||||||
```bash
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/nas_scanner.py scan
|
|
||||||
```
|
|
||||||
|
|
||||||
### "프리렌 검색해줘"
|
|
||||||
```bash
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anime_pipeline.py search "프리렌"
|
|
||||||
```
|
|
||||||
|
|
||||||
### "이번 분기 애니 자막 업데이트 된것들 다운받아줘"
|
|
||||||
```bash
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anime_pipeline.py batch
|
|
||||||
```
|
|
||||||
> `batch`는 이번 분기 NAS 폴더 → Anissia 자막 확인 → 자막 있는 것만 다운을 자동 수행합니다.
|
|
||||||
> 자막 필터 없이 전부 다운받으려면 `--no-sub-filter` 옵션 추가.
|
|
||||||
|
|
||||||
### "다운로드 현황 보여줘"
|
|
||||||
```bash
|
|
||||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe tools/anime_pipeline.py status
|
|
||||||
```
|
|
||||||
|
|
||||||
## 보고서
|
|
||||||
|
|
||||||
작업 완료 후 반드시 이 JSON을 출력하세요:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"title": "작업 제목",
|
|
||||||
"summary": "결과 요약 1-3문장",
|
|
||||||
"changes": [],
|
|
||||||
"verified": true,
|
|
||||||
"warnings": [],
|
|
||||||
"next_steps": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user