feat(debate): 분산 토론 시스템 — Wiki.js 기반 통신, AG 프로젝트 스캐폴딩, handler 리팩토링
- wiki_client.py: find_page()를 singleByPath 직접 조회로 교체 (O(1)) - debate-agent/: gemini/opus AG 프로젝트 생성 (GEMINI.md + wiki_debate.py + /start) - debate_handler.py: 로컬 파일 I/O → Wiki.js API 전환, _sync_wiki() 삭제 - response 페이지 매 턴 초기화 (누적 방지)
This commit is contained in:
47
debate-agent/debate-gemini/.agent/workflows/start.md
Normal file
47
debate-agent/debate-gemini/.agent/workflows/start.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
description: 토론 세션을 시작합니다. 사회자의 input을 읽고 response를 작성합니다.
|
||||
---
|
||||
|
||||
# 토론 참여 시작
|
||||
|
||||
// turbo-all
|
||||
|
||||
## 절차
|
||||
|
||||
### 1. GEMINI.md 행동 규칙 확인
|
||||
|
||||
`GEMINI.md`를 읽어 토론 참여자 행동 규칙을 확인합니다.
|
||||
|
||||
### 2. 사회자 지시 읽기
|
||||
|
||||
```powershell
|
||||
python tools/wiki_debate.py read debates/{slug}/input-gemini
|
||||
```
|
||||
|
||||
> `{slug}`는 현재 진행 중인 토론의 slug입니다. `input-gemini` 페이지가 비어있으면 아직 사회자가 input을 작성하지 않은 것이니 대기하세요.
|
||||
|
||||
### 3. 합의 상태 확인
|
||||
|
||||
```powershell
|
||||
python tools/wiki_debate.py read debates/{slug}/working-document
|
||||
```
|
||||
|
||||
사회자가 정리한 현재 합의 상태를 확인합니다.
|
||||
|
||||
### 4. 답변 작성 및 업로드
|
||||
|
||||
답변을 작성한 후 response 페이지에 업로드:
|
||||
|
||||
```powershell
|
||||
python tools/wiki_debate.py write debates/{slug}/response-gemini "답변 전문"
|
||||
```
|
||||
|
||||
또는 긴 답변은 파일로:
|
||||
|
||||
```powershell
|
||||
python tools/wiki_debate.py write-file debates/{slug}/response-gemini response_draft.md
|
||||
```
|
||||
|
||||
### 5. Discord 완료 시그널
|
||||
|
||||
Discord에 "response 작성 완료"를 게시하여 사회자에게 알립니다.
|
||||
42
debate-agent/debate-gemini/GEMINI.md
Normal file
42
debate-agent/debate-gemini/GEMINI.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Debate Participant — Gemini
|
||||
|
||||
당신은 AI 토론 참여자 (Gemini)입니다. 사회자의 지시에 따라 주어진 주제에 대해 의견을 제시하고, 상대방의 주장을 검증합니다.
|
||||
|
||||
## ⚠️ Wiki.js 기반 답변 (필수)
|
||||
|
||||
### 도구
|
||||
- `python tools/wiki_debate.py read <path>` — Wiki.js 페이지 읽기
|
||||
- `python tools/wiki_debate.py write <path> <content>` — Wiki.js 페이지 쓰기
|
||||
- `python tools/wiki_debate.py write-file <path> <file>` — 파일 내용을 Wiki.js에 업로드
|
||||
|
||||
### 읽을 페이지 (순서대로)
|
||||
1. **`debates/{slug}/input-gemini`** — 사회자가 작성한 지시 + 상대 의견 전문. **반드시 먼저 읽으세요.**
|
||||
2. **`debates/{slug}/working-document`** — 현재까지의 합의 사항 (참고 + 취합 검증용)
|
||||
3. **`debates/{slug}/round-log`** — 지금까지의 전체 토론 기록 (필요 시)
|
||||
|
||||
### 쓸 페이지
|
||||
- **`debates/{slug}/response-gemini`** — 여기에만 전문 답변을 작성하세요.
|
||||
|
||||
### Discord 완료 시그널
|
||||
- Wiki.js에 response 작성이 끝나면 Discord에 "response 작성 완료"를 게시하세요.
|
||||
|
||||
## 행동 규칙
|
||||
|
||||
1. **input 먼저 읽기** — 사회자의 지시, 상대 의견 전문, 방향이 담겨 있음
|
||||
2. **전문으로 답변** — 요약하지 마세요. 논거를 구체적으로 전개하세요
|
||||
3. **상대 의견 검증** — 오류·누락·논리적 허점을 확인하세요
|
||||
4. **사회자 취합 검증** — `working-document`에 사회자가 양측 의견을 올바르게 반영했는지 확인하고, 누락/오류가 있으면 지적하세요
|
||||
5. **개선안 제시** — 단순 반론이 아니라 대안/보강을 함께 제시하세요
|
||||
6. **근거 명시** — 기술적 근거, 사례, 레퍼런스를 포함하세요
|
||||
7. **합의 가능 시 인정** — 상대 의견이 맞으면 솔직히 인정하고 발전시키세요
|
||||
|
||||
## 금지 사항
|
||||
|
||||
- ❌ 상대방의 `input-opus` 또는 `response-opus` 페이지 접근
|
||||
- ❌ `working-document` 수정 (읽기만 — 사회자만 편집)
|
||||
- ❌ 소스 코드 작성 (문서 수준 논의만)
|
||||
- ❌ 사회자 지시 무시
|
||||
- ❌ 주제에서 벗어난 발언
|
||||
- ❌ 근거 없는 주장
|
||||
- ❌ "이하생략" 또는 답변 축약
|
||||
- ❌ Discord에 긴 답변 직접 게시 (반드시 Wiki.js response 페이지에)
|
||||
172
debate-agent/debate-gemini/tools/wiki_debate.py
Normal file
172
debate-agent/debate-gemini/tools/wiki_debate.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""Wiki.js Debate Tool — AG용 Wiki.js 읽기/쓰기 CLI.
|
||||
|
||||
사용법:
|
||||
python tools/wiki_debate.py read <page-path>
|
||||
python tools/wiki_debate.py write <page-path> <content>
|
||||
python tools/wiki_debate.py write-file <page-path> <file-path>
|
||||
|
||||
예시:
|
||||
python tools/wiki_debate.py read debates/frtb/input-gemini
|
||||
python tools/wiki_debate.py write debates/frtb/response-gemini "SA 방식을 제안합니다..."
|
||||
python tools/wiki_debate.py write-file debates/frtb/response-gemini response_draft.md
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import httpx
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# .env 로드 (여러 후보 경로에서 탐색)
|
||||
_candidates = [
|
||||
Path(__file__).parent.parent / ".env", # tools/../.env (배포 위치)
|
||||
Path.cwd() / ".env", # CWD/.env
|
||||
Path.cwd().parent / ".env", # CWD/../.env
|
||||
]
|
||||
for _env_path in _candidates:
|
||||
if _env_path.exists():
|
||||
for line in _env_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if "=" in line:
|
||||
key, _, value = line.partition("=")
|
||||
value = value.strip()
|
||||
if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
|
||||
value = value[1:-1]
|
||||
os.environ.setdefault(key.strip(), value)
|
||||
break # 첫 번째 .env만 사용
|
||||
|
||||
WIKI_URL = os.getenv("WIKI_URL", "https://wiki.variet.net")
|
||||
WIKI_API_KEY = os.getenv("WIKI_API_KEY", "")
|
||||
GRAPHQL_ENDPOINT = f"{WIKI_URL}/graphql"
|
||||
|
||||
|
||||
async def _query(query: str, variables: dict = None) -> dict:
|
||||
"""GraphQL 쿼리 실행."""
|
||||
headers = {
|
||||
"Authorization": f"Bearer {WIKI_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload = {"query": query}
|
||||
if variables:
|
||||
payload["variables"] = variables
|
||||
|
||||
async with httpx.AsyncClient(timeout=15.0) as client:
|
||||
resp = await client.post(GRAPHQL_ENDPOINT, json=payload, headers=headers)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
if "errors" in data and data["errors"]:
|
||||
raise RuntimeError(f"Wiki.js 오류: {data['errors'][0].get('message', data['errors'])}")
|
||||
return data.get("data", {})
|
||||
|
||||
|
||||
async def read_page(path: str) -> str:
|
||||
"""경로로 페이지 읽기 (singleByPath)."""
|
||||
query = """
|
||||
query ($path: String!) {
|
||||
pages { singleByPath(path: $path, locale: "ko") {
|
||||
id, path, title, content
|
||||
}}
|
||||
}
|
||||
"""
|
||||
data = await _query(query, {"path": path})
|
||||
page = data.get("pages", {}).get("singleByPath")
|
||||
if not page:
|
||||
return f"[오류] 페이지를 찾을 수 없습니다: {path}"
|
||||
return page.get("content", "")
|
||||
|
||||
|
||||
async def write_page(path: str, content: str) -> str:
|
||||
"""경로에 페이지 쓰기 (upsert)."""
|
||||
# 기존 페이지 확인
|
||||
find_query = """
|
||||
query ($path: String!) {
|
||||
pages { singleByPath(path: $path, locale: "ko") {
|
||||
id, tags { tag }
|
||||
}}
|
||||
}
|
||||
"""
|
||||
data = await _query(find_query, {"path": path})
|
||||
existing = data.get("pages", {}).get("singleByPath")
|
||||
|
||||
if existing:
|
||||
# 수정
|
||||
tags = [t["tag"] for t in existing.get("tags", [])]
|
||||
update_query = """
|
||||
mutation ($id: Int!, $content: String!, $tags: [String!]) {
|
||||
pages { update(id: $id, content: $content, tags: $tags) {
|
||||
responseResult { succeeded, message }
|
||||
}}
|
||||
}
|
||||
"""
|
||||
result = await _query(update_query, {
|
||||
"id": existing["id"], "content": content, "tags": tags,
|
||||
})
|
||||
ok = result["pages"]["update"]["responseResult"]["succeeded"]
|
||||
msg = result["pages"]["update"]["responseResult"]["message"]
|
||||
return f"✅ 수정 완료 (id: {existing['id']})" if ok else f"❌ 수정 실패: {msg}"
|
||||
else:
|
||||
# 생성
|
||||
title = path.split("/")[-1].replace("-", " ").title()
|
||||
create_query = """
|
||||
mutation ($content: String!, $path: String!, $title: String!,
|
||||
$description: String!, $tags: [String!]!) {
|
||||
pages { create(
|
||||
content: $content, description: $description,
|
||||
editor: "markdown", isPublished: true, isPrivate: false,
|
||||
locale: "ko", path: $path, tags: $tags, title: $title
|
||||
) {
|
||||
responseResult { succeeded, message }
|
||||
page { id }
|
||||
}}
|
||||
}
|
||||
"""
|
||||
result = await _query(create_query, {
|
||||
"content": content, "path": path, "title": title,
|
||||
"description": title, "tags": ["debate"],
|
||||
})
|
||||
ok = result["pages"]["create"]["responseResult"]["succeeded"]
|
||||
if ok:
|
||||
pid = result["pages"]["create"]["page"]["id"]
|
||||
return f"✅ 생성 완료 (id: {pid})"
|
||||
return f"❌ 생성 실패: {result['pages']['create']['responseResult']['message']}"
|
||||
|
||||
|
||||
async def main():
|
||||
if sys.stdout.encoding != "utf-8":
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
||||
|
||||
args = sys.argv[1:]
|
||||
if len(args) < 2:
|
||||
print(__doc__)
|
||||
return
|
||||
|
||||
cmd, path = args[0], args[1]
|
||||
|
||||
if cmd == "read":
|
||||
content = await read_page(path)
|
||||
print(content)
|
||||
|
||||
elif cmd == "write" and len(args) > 2:
|
||||
content = " ".join(args[2:])
|
||||
result = await write_page(path, content)
|
||||
print(result)
|
||||
|
||||
elif cmd == "write-file" and len(args) > 2:
|
||||
file_path = Path(args[2])
|
||||
if not file_path.exists():
|
||||
print(f"❌ 파일을 찾을 수 없습니다: {file_path}")
|
||||
return
|
||||
content = file_path.read_text(encoding="utf-8")
|
||||
result = await write_page(path, content)
|
||||
print(result)
|
||||
|
||||
else:
|
||||
print(__doc__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
47
debate-agent/debate-opus/.agent/workflows/start.md
Normal file
47
debate-agent/debate-opus/.agent/workflows/start.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
description: 토론 세션을 시작합니다. 사회자의 input을 읽고 response를 작성합니다.
|
||||
---
|
||||
|
||||
# 토론 참여 시작
|
||||
|
||||
// turbo-all
|
||||
|
||||
## 절차
|
||||
|
||||
### 1. GEMINI.md 행동 규칙 확인
|
||||
|
||||
`GEMINI.md`를 읽어 토론 참여자 행동 규칙을 확인합니다.
|
||||
|
||||
### 2. 사회자 지시 읽기
|
||||
|
||||
```powershell
|
||||
python tools/wiki_debate.py read debates/{slug}/input-opus
|
||||
```
|
||||
|
||||
> `{slug}`는 현재 진행 중인 토론의 slug입니다. `input-opus` 페이지가 비어있으면 아직 사회자가 input을 작성하지 않은 것이니 대기하세요.
|
||||
|
||||
### 3. 합의 상태 확인
|
||||
|
||||
```powershell
|
||||
python tools/wiki_debate.py read debates/{slug}/working-document
|
||||
```
|
||||
|
||||
사회자가 정리한 현재 합의 상태를 확인합니다.
|
||||
|
||||
### 4. 답변 작성 및 업로드
|
||||
|
||||
답변을 작성한 후 response 페이지에 업로드:
|
||||
|
||||
```powershell
|
||||
python tools/wiki_debate.py write debates/{slug}/response-opus "답변 전문"
|
||||
```
|
||||
|
||||
또는 긴 답변은 파일로:
|
||||
|
||||
```powershell
|
||||
python tools/wiki_debate.py write-file debates/{slug}/response-opus response_draft.md
|
||||
```
|
||||
|
||||
### 5. Discord 완료 시그널
|
||||
|
||||
Discord에 "response 작성 완료"를 게시하여 사회자에게 알립니다.
|
||||
42
debate-agent/debate-opus/GEMINI.md
Normal file
42
debate-agent/debate-opus/GEMINI.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Debate Participant — Opus
|
||||
|
||||
당신은 AI 토론 참여자 (Opus)입니다. 사회자의 지시에 따라 주어진 주제에 대해 의견을 제시하고, 상대방의 주장을 검증합니다.
|
||||
|
||||
## ⚠️ Wiki.js 기반 답변 (필수)
|
||||
|
||||
### 도구
|
||||
- `python tools/wiki_debate.py read <path>` — Wiki.js 페이지 읽기
|
||||
- `python tools/wiki_debate.py write <path> <content>` — Wiki.js 페이지 쓰기
|
||||
- `python tools/wiki_debate.py write-file <path> <file>` — 파일 내용을 Wiki.js에 업로드
|
||||
|
||||
### 읽을 페이지 (순서대로)
|
||||
1. **`debates/{slug}/input-opus`** — 사회자가 작성한 지시 + 상대 의견 전문. **반드시 먼저 읽으세요.**
|
||||
2. **`debates/{slug}/working-document`** — 현재까지의 합의 사항 (참고 + 취합 검증용)
|
||||
3. **`debates/{slug}/round-log`** — 지금까지의 전체 토론 기록 (필요 시)
|
||||
|
||||
### 쓸 페이지
|
||||
- **`debates/{slug}/response-opus`** — 여기에만 전문 답변을 작성하세요.
|
||||
|
||||
### Discord 완료 시그널
|
||||
- Wiki.js에 response 작성이 끝나면 Discord에 "response 작성 완료"를 게시하세요.
|
||||
|
||||
## 행동 규칙
|
||||
|
||||
1. **input 먼저 읽기** — 사회자의 지시, 상대 의견 전문, 방향이 담겨 있음
|
||||
2. **전문으로 답변** — 요약하지 마세요. 논거를 구체적으로 전개하세요
|
||||
3. **상대 의견 검증** — 오류·누락·논리적 허점을 확인하세요
|
||||
4. **사회자 취합 검증** — `working-document`에 사회자가 양측 의견을 올바르게 반영했는지 확인하고, 누락/오류가 있으면 지적하세요
|
||||
5. **개선안 제시** — 단순 반론이 아니라 대안/보강을 함께 제시하세요
|
||||
6. **근거 명시** — 기술적 근거, 사례, 레퍼런스를 포함하세요
|
||||
7. **합의 가능 시 인정** — 상대 의견이 맞으면 솔직히 인정하고 발전시키세요
|
||||
|
||||
## 금지 사항
|
||||
|
||||
- ❌ 상대방의 `input-gemini` 또는 `response-gemini` 페이지 접근
|
||||
- ❌ `working-document` 수정 (읽기만 — 사회자만 편집)
|
||||
- ❌ 소스 코드 작성 (문서 수준 논의만)
|
||||
- ❌ 사회자 지시 무시
|
||||
- ❌ 주제에서 벗어난 발언
|
||||
- ❌ 근거 없는 주장
|
||||
- ❌ "이하생략" 또는 답변 축약
|
||||
- ❌ Discord에 긴 답변 직접 게시 (반드시 Wiki.js response 페이지에)
|
||||
172
debate-agent/debate-opus/tools/wiki_debate.py
Normal file
172
debate-agent/debate-opus/tools/wiki_debate.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""Wiki.js Debate Tool — AG용 Wiki.js 읽기/쓰기 CLI.
|
||||
|
||||
사용법:
|
||||
python tools/wiki_debate.py read <page-path>
|
||||
python tools/wiki_debate.py write <page-path> <content>
|
||||
python tools/wiki_debate.py write-file <page-path> <file-path>
|
||||
|
||||
예시:
|
||||
python tools/wiki_debate.py read debates/frtb/input-gemini
|
||||
python tools/wiki_debate.py write debates/frtb/response-gemini "SA 방식을 제안합니다..."
|
||||
python tools/wiki_debate.py write-file debates/frtb/response-gemini response_draft.md
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import httpx
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# .env 로드 (여러 후보 경로에서 탐색)
|
||||
_candidates = [
|
||||
Path(__file__).parent.parent / ".env", # tools/../.env (배포 위치)
|
||||
Path.cwd() / ".env", # CWD/.env
|
||||
Path.cwd().parent / ".env", # CWD/../.env
|
||||
]
|
||||
for _env_path in _candidates:
|
||||
if _env_path.exists():
|
||||
for line in _env_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if "=" in line:
|
||||
key, _, value = line.partition("=")
|
||||
value = value.strip()
|
||||
if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
|
||||
value = value[1:-1]
|
||||
os.environ.setdefault(key.strip(), value)
|
||||
break # 첫 번째 .env만 사용
|
||||
|
||||
WIKI_URL = os.getenv("WIKI_URL", "https://wiki.variet.net")
|
||||
WIKI_API_KEY = os.getenv("WIKI_API_KEY", "")
|
||||
GRAPHQL_ENDPOINT = f"{WIKI_URL}/graphql"
|
||||
|
||||
|
||||
async def _query(query: str, variables: dict = None) -> dict:
|
||||
"""GraphQL 쿼리 실행."""
|
||||
headers = {
|
||||
"Authorization": f"Bearer {WIKI_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload = {"query": query}
|
||||
if variables:
|
||||
payload["variables"] = variables
|
||||
|
||||
async with httpx.AsyncClient(timeout=15.0) as client:
|
||||
resp = await client.post(GRAPHQL_ENDPOINT, json=payload, headers=headers)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
if "errors" in data and data["errors"]:
|
||||
raise RuntimeError(f"Wiki.js 오류: {data['errors'][0].get('message', data['errors'])}")
|
||||
return data.get("data", {})
|
||||
|
||||
|
||||
async def read_page(path: str) -> str:
|
||||
"""경로로 페이지 읽기 (singleByPath)."""
|
||||
query = """
|
||||
query ($path: String!) {
|
||||
pages { singleByPath(path: $path, locale: "ko") {
|
||||
id, path, title, content
|
||||
}}
|
||||
}
|
||||
"""
|
||||
data = await _query(query, {"path": path})
|
||||
page = data.get("pages", {}).get("singleByPath")
|
||||
if not page:
|
||||
return f"[오류] 페이지를 찾을 수 없습니다: {path}"
|
||||
return page.get("content", "")
|
||||
|
||||
|
||||
async def write_page(path: str, content: str) -> str:
|
||||
"""경로에 페이지 쓰기 (upsert)."""
|
||||
# 기존 페이지 확인
|
||||
find_query = """
|
||||
query ($path: String!) {
|
||||
pages { singleByPath(path: $path, locale: "ko") {
|
||||
id, tags { tag }
|
||||
}}
|
||||
}
|
||||
"""
|
||||
data = await _query(find_query, {"path": path})
|
||||
existing = data.get("pages", {}).get("singleByPath")
|
||||
|
||||
if existing:
|
||||
# 수정
|
||||
tags = [t["tag"] for t in existing.get("tags", [])]
|
||||
update_query = """
|
||||
mutation ($id: Int!, $content: String!, $tags: [String!]) {
|
||||
pages { update(id: $id, content: $content, tags: $tags) {
|
||||
responseResult { succeeded, message }
|
||||
}}
|
||||
}
|
||||
"""
|
||||
result = await _query(update_query, {
|
||||
"id": existing["id"], "content": content, "tags": tags,
|
||||
})
|
||||
ok = result["pages"]["update"]["responseResult"]["succeeded"]
|
||||
msg = result["pages"]["update"]["responseResult"]["message"]
|
||||
return f"✅ 수정 완료 (id: {existing['id']})" if ok else f"❌ 수정 실패: {msg}"
|
||||
else:
|
||||
# 생성
|
||||
title = path.split("/")[-1].replace("-", " ").title()
|
||||
create_query = """
|
||||
mutation ($content: String!, $path: String!, $title: String!,
|
||||
$description: String!, $tags: [String!]!) {
|
||||
pages { create(
|
||||
content: $content, description: $description,
|
||||
editor: "markdown", isPublished: true, isPrivate: false,
|
||||
locale: "ko", path: $path, tags: $tags, title: $title
|
||||
) {
|
||||
responseResult { succeeded, message }
|
||||
page { id }
|
||||
}}
|
||||
}
|
||||
"""
|
||||
result = await _query(create_query, {
|
||||
"content": content, "path": path, "title": title,
|
||||
"description": title, "tags": ["debate"],
|
||||
})
|
||||
ok = result["pages"]["create"]["responseResult"]["succeeded"]
|
||||
if ok:
|
||||
pid = result["pages"]["create"]["page"]["id"]
|
||||
return f"✅ 생성 완료 (id: {pid})"
|
||||
return f"❌ 생성 실패: {result['pages']['create']['responseResult']['message']}"
|
||||
|
||||
|
||||
async def main():
|
||||
if sys.stdout.encoding != "utf-8":
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
||||
|
||||
args = sys.argv[1:]
|
||||
if len(args) < 2:
|
||||
print(__doc__)
|
||||
return
|
||||
|
||||
cmd, path = args[0], args[1]
|
||||
|
||||
if cmd == "read":
|
||||
content = await read_page(path)
|
||||
print(content)
|
||||
|
||||
elif cmd == "write" and len(args) > 2:
|
||||
content = " ".join(args[2:])
|
||||
result = await write_page(path, content)
|
||||
print(result)
|
||||
|
||||
elif cmd == "write-file" and len(args) > 2:
|
||||
file_path = Path(args[2])
|
||||
if not file_path.exists():
|
||||
print(f"❌ 파일을 찾을 수 없습니다: {file_path}")
|
||||
return
|
||||
content = file_path.read_text(encoding="utf-8")
|
||||
result = await write_page(path, content)
|
||||
print(result)
|
||||
|
||||
else:
|
||||
print(__doc__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user