mim-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mim_cli/__init__.py +0 -0
- mim_cli/ai.py +98 -0
- mim_cli/cli.py +830 -0
- mim_cli/config.py +28 -0
- mim_cli/embeddings.py +65 -0
- mim_cli/models.py +49 -0
- mim_cli/net.py +114 -0
- mim_cli/output.py +209 -0
- mim_cli/providers/__init__.py +94 -0
- mim_cli/providers/fetch/__init__.py +1 -0
- mim_cli/providers/fetch/giphy.py +88 -0
- mim_cli/providers/fetch/openverse.py +70 -0
- mim_cli/providers/fetch/pexels.py +138 -0
- mim_cli/providers/fetch/pixabay.py +137 -0
- mim_cli/providers/fetch/reddit.py +210 -0
- mim_cli/providers/fetch/unsplash.py +87 -0
- mim_cli/providers/gemini.py +147 -0
- mim_cli/providers/leonardo.py +206 -0
- mim_cli/providers/replicate.py +175 -0
- mim_cli/saver.py +230 -0
- mim_cli/search.py +96 -0
- mim_cli/store.py +331 -0
- mim_cli-0.1.0.dist-info/METADATA +14 -0
- mim_cli-0.1.0.dist-info/RECORD +26 -0
- mim_cli-0.1.0.dist-info/WHEEL +4 -0
- mim_cli-0.1.0.dist-info/entry_points.txt +2 -0
mim_cli/__init__.py
ADDED
|
File without changes
|
mim_cli/ai.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class GeneratedMetadata:
|
|
10
|
+
name: str
|
|
11
|
+
description: str
|
|
12
|
+
tags: list[str]
|
|
13
|
+
emotions: list[str]
|
|
14
|
+
context: list[str]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
PROMPT_TEMPLATE = """{path}
|
|
18
|
+
|
|
19
|
+
위 파일을 분석해서 밈 라이브러리 검색에 최적화된 메타데이터를 JSON만 반환해 (다른 텍스트 없이):
|
|
20
|
+
{{
|
|
21
|
+
"name": "한국어 짧은 이름 (10자 이내)",
|
|
22
|
+
"description": "무슨 장면인지 상세 설명 (한국어, 2-3문장)",
|
|
23
|
+
"tags": ["한국어 검색 태그 5-10개 (예: 충격, 폭발, 웃음, 피카츄)"],
|
|
24
|
+
"emotions": ["한국어 감정 태그 (예: 충격, 기쁨, 슬픔, 분노, 당혹)"],
|
|
25
|
+
"context": ["한국어 맥락 태그 (예: 반응, 전환, 인트로, 액션, 반전)"]
|
|
26
|
+
}}"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MetadataGenerator:
|
|
30
|
+
def generate(self, file_path: Path) -> GeneratedMetadata:
|
|
31
|
+
"""claude --print로 파일을 분석해 메타데이터 생성.
|
|
32
|
+
|
|
33
|
+
file_path는 저장소 내부의 UUID 경로를 기대 (saver.py가 dest를 전달).
|
|
34
|
+
방어적으로 resolve()로 정규화하여 프롬프트 주입 가능한 문자
|
|
35
|
+
(개행/백틱 등)가 포함된 외부 경로가 섞여도 안전하게.
|
|
36
|
+
"""
|
|
37
|
+
safe = Path(file_path).resolve()
|
|
38
|
+
prompt = PROMPT_TEMPLATE.format(path=safe)
|
|
39
|
+
result = subprocess.run(
|
|
40
|
+
["claude", "--model", "claude-haiku-4-5-20251001", "--print", prompt],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
timeout=60,
|
|
44
|
+
)
|
|
45
|
+
if result.returncode != 0:
|
|
46
|
+
raise RuntimeError(f"claude 실행 실패: {result.stderr[:200]}")
|
|
47
|
+
return self._parse(result.stdout)
|
|
48
|
+
|
|
49
|
+
def _parse(self, text: str) -> GeneratedMetadata:
|
|
50
|
+
"""JSON 블록 추출 — 마크다운 코드 블록 포함 처리.
|
|
51
|
+
|
|
52
|
+
중괄호 균형을 추적해 최외곽 객체를 찾음 (정규식 catastrophic
|
|
53
|
+
backtracking 회피).
|
|
54
|
+
"""
|
|
55
|
+
text = re.sub(r"```(?:json)?\s*", "", text).strip()
|
|
56
|
+
block = _find_first_json_object(text)
|
|
57
|
+
if block is None:
|
|
58
|
+
raise ValueError(f"JSON 파싱 실패. Claude 응답:\n{text[:300]}")
|
|
59
|
+
data = json.loads(block)
|
|
60
|
+
return GeneratedMetadata(
|
|
61
|
+
name=data["name"],
|
|
62
|
+
description=data["description"],
|
|
63
|
+
tags=data.get("tags", []),
|
|
64
|
+
emotions=data.get("emotions", []),
|
|
65
|
+
context=data.get("context", []),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _find_first_json_object(text: str) -> str | None:
|
|
70
|
+
"""중괄호 균형 스캔으로 최초의 JSON 오브젝트 블록 반환.
|
|
71
|
+
|
|
72
|
+
문자열 리터럴 내부의 중괄호/이스케이프를 구분해서 추적.
|
|
73
|
+
"""
|
|
74
|
+
start = text.find("{")
|
|
75
|
+
if start < 0:
|
|
76
|
+
return None
|
|
77
|
+
depth = 0
|
|
78
|
+
in_str = False
|
|
79
|
+
escape = False
|
|
80
|
+
for i in range(start, len(text)):
|
|
81
|
+
c = text[i]
|
|
82
|
+
if in_str:
|
|
83
|
+
if escape:
|
|
84
|
+
escape = False
|
|
85
|
+
elif c == "\\":
|
|
86
|
+
escape = True
|
|
87
|
+
elif c == '"':
|
|
88
|
+
in_str = False
|
|
89
|
+
continue
|
|
90
|
+
if c == '"':
|
|
91
|
+
in_str = True
|
|
92
|
+
elif c == "{":
|
|
93
|
+
depth += 1
|
|
94
|
+
elif c == "}":
|
|
95
|
+
depth -= 1
|
|
96
|
+
if depth == 0:
|
|
97
|
+
return text[start : i + 1]
|
|
98
|
+
return None
|