mrstack 1.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.
Files changed (42) hide show
  1. mrstack/__init__.py +4 -0
  2. mrstack/_data/config/com.mrstack.claude-telegram.plist +25 -0
  3. mrstack/_data/config/mcp-config.example.json +23 -0
  4. mrstack/_data/config/start-daemon.sh +53 -0
  5. mrstack/_data/config/start.sh +29 -0
  6. mrstack/_data/schedulers/manage-jobs.sh +87 -0
  7. mrstack/_data/schedulers/morning-briefing.sh +29 -0
  8. mrstack/_data/schedulers/register-jobs.py +182 -0
  9. mrstack/_data/schedulers/run-threads-briefing.sh +36 -0
  10. mrstack/_data/schedulers/weekly-review.sh +26 -0
  11. mrstack/_data/templates/DESIGN-GUIDE.md +160 -0
  12. mrstack/_data/templates/alert.md +56 -0
  13. mrstack/_data/templates/evening-summary.md +73 -0
  14. mrstack/_data/templates/jarvis-alert.md +64 -0
  15. mrstack/_data/templates/morning-briefing.md +53 -0
  16. mrstack/_data/templates/weekly-review.md +79 -0
  17. mrstack/_overlay/api/dashboard.py +223 -0
  18. mrstack/_overlay/api/templates/dashboard.html +328 -0
  19. mrstack/_overlay/bot/handlers/callback.py +1432 -0
  20. mrstack/_overlay/bot/handlers/command.py +1541 -0
  21. mrstack/_overlay/bot/utils/keyboards.py +125 -0
  22. mrstack/_overlay/bot/utils/ui_components.py +166 -0
  23. mrstack/_overlay/claude/session.py +341 -0
  24. mrstack/_overlay/jarvis/__init__.py +77 -0
  25. mrstack/_overlay/jarvis/coach.py +122 -0
  26. mrstack/_overlay/jarvis/context_engine.py +463 -0
  27. mrstack/_overlay/jarvis/pattern_learner.py +255 -0
  28. mrstack/_overlay/jarvis/persona.py +84 -0
  29. mrstack/_overlay/jarvis/platform.py +182 -0
  30. mrstack/_overlay/knowledge/__init__.py +6 -0
  31. mrstack/_overlay/knowledge/manager.py +464 -0
  32. mrstack/_overlay/knowledge/memory_index.py +180 -0
  33. mrstack/cli.py +330 -0
  34. mrstack/constants.py +77 -0
  35. mrstack/daemon.py +325 -0
  36. mrstack/patcher.py +169 -0
  37. mrstack/wizard.py +271 -0
  38. mrstack-1.1.0.dist-info/METADATA +640 -0
  39. mrstack-1.1.0.dist-info/RECORD +42 -0
  40. mrstack-1.1.0.dist-info/WHEEL +4 -0
  41. mrstack-1.1.0.dist-info/entry_points.txt +2 -0
  42. mrstack-1.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,56 @@
1
+ # Alert Template
2
+
3
+ ## 형식
4
+
5
+ ```
6
+ ━━━ Mr.Stack 알림 ━━━
7
+ {emoji} {alert_type}
8
+
9
+ ▸ 내용
10
+ {description}
11
+
12
+ ▸ 발생 시각
13
+ {timestamp}
14
+
15
+ ▸ 영향
16
+ {impact}
17
+
18
+ ▸ 권장 조치
19
+ · {action_1}
20
+ · {action_2}
21
+ ━━━━━━━━━━━━━━━━━━━━
22
+ ```
23
+
24
+ ## 텔레그램 HTML 변환
25
+
26
+ ```html
27
+ <b>━━━ Mr.Stack 알림 ━━━</b>
28
+ {emoji} <b>{alert_type}</b>
29
+
30
+ <b>▸ 내용</b>
31
+ {description}
32
+
33
+ <b>▸ 발생 시각</b>
34
+ {timestamp}
35
+
36
+ <b>▸ 영향</b>
37
+ {impact}
38
+
39
+ <b>▸ 권장 조치</b>
40
+ · {action_1}
41
+ · {action_2}
42
+ ━━━━━━━━━━━━━━━━━━━━
43
+ ```
44
+
45
+ ## 알림 등급
46
+ - 🔴 <b>긴급</b>: 즉시 조치 필요 (서비스 장애, 보안 이슈)
47
+ - 🟡 <b>주의</b>: 확인 필요 (성능 저하, 이상 패턴)
48
+ - 🟢 <b>정보</b>: 참고용 (작업 완료, 상태 변경)
49
+ - ⚪ <b>시스템</b>: 내부 상태 변경 (토큰 갱신, 동기화 완료)
50
+
51
+ ## 규칙
52
+ - 간결하게: 최대 500자
53
+ - 결론 먼저, 상세 나중
54
+ - 조치 가능한 내용 반드시 포함
55
+ - 구분선/섹션 prefix는 디자인 가이드 기준 준수
56
+ - 한국어로 작성
@@ -0,0 +1,73 @@
1
+ # Evening Summary Template
2
+
3
+ ## 형식
4
+
5
+ ```
6
+ ━━━ Mr.Stack 데일리 리포트 ━━━
7
+ 📊 {date} ({weekday})
8
+
9
+ ▸ 생산성 스코어: {score_bar} {score}/10
10
+
11
+ ▸ 오늘의 하이라이트
12
+ ✓ {completed_1} ({detail_1})
13
+ ✓ {completed_2}
14
+
15
+ ▸ 미완료
16
+ ◻ {pending_1} → 내일 이어서
17
+
18
+ ▸ 시간 분석
19
+ 코딩: {coding_bar} {coding_hours}h
20
+ 리뷰: {review_bar} {review_hours}h
21
+ 회의: {meeting_bar} {meeting_hours}h
22
+ 기타: {other_bar} {other_hours}h
23
+
24
+ ▸ 코칭 노트
25
+ 잘한 점: {strength}
26
+ 개선점: {improvement}
27
+ 내일 제안: {suggestion}
28
+ ━━━━━━━━━━━━━━━━━━━━
29
+ ```
30
+
31
+ ## 텔레그램 HTML 변환
32
+
33
+ ```html
34
+ <b>━━━ Mr.Stack 데일리 리포트 ━━━</b>
35
+ 📊 {date} ({weekday})
36
+
37
+ <b>▸ 생산성 스코어:</b> {score_bar} {score}/10
38
+
39
+ <b>▸ 오늘의 하이라이트</b>
40
+ ✓ {completed_1} ({detail_1})
41
+ ✓ {completed_2}
42
+
43
+ <b>▸ 미완료</b>
44
+ ◻ {pending_1} → 내일 이어서
45
+
46
+ <b>▸ 시간 분석</b>
47
+ 코딩: {coding_bar} {coding_hours}h
48
+ 리뷰: {review_bar} {review_hours}h
49
+ 회의: {meeting_bar} {meeting_hours}h
50
+ 기타: {other_bar} {other_hours}h
51
+
52
+ <b>▸ 코칭 노트</b>
53
+ 잘한 점: {strength}
54
+ 개선점: {improvement}
55
+ <i>내일 제안: {suggestion}</i>
56
+ ━━━━━━━━━━━━━━━━━━━━
57
+ ```
58
+
59
+ ## 프로그레스 바 생성 규칙
60
+ - 생산성 스코어: 10칸, score 값만큼 █, 나머지 ░
61
+ - 예: 8/10 → ████████░░
62
+ - 시간 분석: 10칸, (hours / total_hours * 10) 반올림
63
+ - 예: 4.2h / 8h → ██████░░░░
64
+
65
+ ## 규칙
66
+ - 하이라이트: 완료된 항목만, 커밋 수 등 수치 포함
67
+ - 미완료: 다음 날 이어서 할 항목 최대 3개
68
+ - 시간 분석: Jarvis ContextEngine 데이터 기반 (osascript 앱 사용 시간)
69
+ - 코칭 노트: PatternLearner의 일일 분석 결과 활용
70
+ - 이 리포트의 "내일 제안"이 다음 날 morning-briefing의 코칭 라인으로 연결됨
71
+ - 전체 길이: 최대 1500자
72
+ - 한국어로 작성
73
+ - 일일 메모리 저장: ~/claude-telegram/memory/daily/{date}.md 에도 기록
@@ -0,0 +1,64 @@
1
+ # Jarvis Alert Template
2
+
3
+ ## 형식
4
+
5
+ ```
6
+ [Mr.Stack] {message}
7
+ ```
8
+
9
+ ## 알림 유형별 예시
10
+
11
+ ### 배터리/하드웨어
12
+ ```
13
+ [Mr.Stack] 배터리 {pct}% — 저장하시고 충전하세요.
14
+ [Mr.Stack] 디스크 {pct}% 사용 — 정리 필요합니다.
15
+ ```
16
+
17
+ ### 집중/휴식
18
+ ```
19
+ [Mr.Stack] {hours}시간째 코딩 중 — 5분 쉬어가시죠.
20
+ [Mr.Stack] 딥워크 {hours}시간 달성. 훌륭합니다.
21
+ [Mr.Stack] 컨텍스트 전환 {count}회 감지 — 한 가지에 집중해보세요.
22
+ ```
23
+
24
+ ### 복귀/상태 변화
25
+ ```
26
+ [Mr.Stack] 돌아오셨네요. {branch} 브랜치, 변경 {count}개 uncommitted.
27
+ [Mr.Stack] {minutes}분째 자리 비움 — 회의 중이시면 무시하세요.
28
+ ```
29
+
30
+ ### 스케줄
31
+ ```
32
+ [Mr.Stack] {minutes}분 후 {event_title} 시작입니다.
33
+ [Mr.Stack] {event_title} 5분 전 — 준비하세요.
34
+ ```
35
+
36
+ ### 시스템
37
+ ```
38
+ [Mr.Stack] 토큰 갱신 완료. 다음 만료: {expiry_date}.
39
+ [Mr.Stack] 메모리 동기화 완료 ({count}개 항목 업데이트).
40
+ [Mr.Stack] GitHub: {repo}에 새 이슈 #{number}.
41
+ ```
42
+
43
+ ## 텔레그램 HTML 변환
44
+
45
+ ```html
46
+ <b>[Mr.Stack]</b> {message}
47
+ ```
48
+
49
+ 필요시 강조:
50
+ ```html
51
+ <b>[Mr.Stack]</b> 배터리 <b>{pct}%</b> — 저장하시고 충전하세요.
52
+ ```
53
+
54
+ ## 규칙
55
+ - prefix는 반드시 [Mr.Stack]으로 시작
56
+ - 한 줄로 끝내는 것이 원칙 (최대 100자)
57
+ - 존댓말 사용하되 간결하게
58
+ - 조치가 필요하면 "—" 뒤에 구체적 행동 제안
59
+ - 정보 전달만이면 사실만 기술
60
+ - 불필요한 이모지 사용 금지 (prefix의 [Mr.Stack]이 브랜딩 역할)
61
+ - Jarvis ContextEngine 트리거에 의해 자동 발송
62
+ - 긴급도에 따라 텔레그램 알림음 설정:
63
+ - 긴급 (배터리 < 10%, 서비스 장애): disable_notification=false
64
+ - 일반: disable_notification=true
@@ -0,0 +1,53 @@
1
+ # Morning Briefing Template
2
+
3
+ ## 형식
4
+
5
+ ```
6
+ ━━━ Mr.Stack 모닝 브리핑 ━━━
7
+ 📅 {date} ({weekday})
8
+
9
+ ▸ 오늘 일정
10
+ {time} {title} ({location})
11
+ {time} {title} ({location})
12
+ — 일정 없음
13
+
14
+ ▸ Pending Tasks
15
+ ◻ {task_1}
16
+ ◻ {task_2}
17
+
18
+ ▸ AI/Tech 뉴스
19
+ · {headline_1} — {summary_1}
20
+ · {headline_2} — {summary_2}
21
+
22
+ 💡 어제 코칭: "{coaching_quote}"
23
+ ━━━━━━━━━━━━━━━━━━━━
24
+ ```
25
+
26
+ ## 텔레그램 HTML 변환
27
+
28
+ ```html
29
+ <b>━━━ Mr.Stack 모닝 브리핑 ━━━</b>
30
+ 📅 {date} ({weekday})
31
+
32
+ <b>▸ 오늘 일정</b>
33
+ {time} {title} ({location})
34
+
35
+ <b>▸ Pending Tasks</b>
36
+ ◻ {task}
37
+
38
+ <b>▸ AI/Tech 뉴스</b>
39
+ · {headline} — {summary}
40
+
41
+ 💡 <i>어제 코칭: "{coaching_quote}"</i>
42
+ ━━━━━━━━━━━━━━━━━━━━
43
+ ```
44
+
45
+ ## 규칙
46
+ - 전체 길이: 최대 1500자
47
+ - 일정이 없으면 "— 일정 없음" 한 줄로
48
+ - 뉴스는 최대 3개, 1줄 요약
49
+ - Pending Tasks 최대 5개
50
+ - 코칭 라인은 어제 evening-summary의 코칭 노트에서 핵심 1줄 발췌
51
+ - 구분선: ━ (U+2501) 사용, 상단 20자 / 하단 20자
52
+ - 섹션 prefix: ▸ (U+25B8)
53
+ - 한국어로 작성
@@ -0,0 +1,79 @@
1
+ # Weekly Review Template
2
+
3
+ ## 형식
4
+
5
+ ```
6
+ ━━━ Mr.Stack 주간 회고 ━━━
7
+ 📊 {start_date} ~ {end_date}
8
+
9
+ ▸ 주간 통계
10
+ 총 대화: {total_conversations}회 | 커밋: {total_commits}개
11
+ 가장 생산적: {best_day} ({best_day_count}회)
12
+ 피크 시간: {peak_hours}
13
+
14
+ ▸ 프로젝트별
15
+ {project_1} {bar_1} {pct_1}%
16
+ {project_2} {bar_2} {pct_2}%
17
+ {project_3} {bar_3} {pct_3}%
18
+
19
+ ▸ 이번 주 잘한 점
20
+ · {highlight_1}
21
+ · {highlight_2}
22
+
23
+ ▸ 주요 결정사항
24
+ · {decision_1}: {reason_1}
25
+
26
+ ▸ 배운 것
27
+ · {learning_1}
28
+
29
+ ▸ 다음 주 추천
30
+ · {recommendation_1}
31
+ · {recommendation_2}
32
+
33
+ 🏷️ 키워드: {keyword_1}, {keyword_2}, {keyword_3}
34
+ ━━━━━━━━━━━━━━━━━━━━
35
+ ```
36
+
37
+ ## 텔레그램 HTML 변환
38
+
39
+ ```html
40
+ <b>━━━ Mr.Stack 주간 회고 ━━━</b>
41
+ 📊 {start_date} ~ {end_date}
42
+
43
+ <b>▸ 주간 통계</b>
44
+ 총 대화: {total_conversations}회 | 커밋: {total_commits}개
45
+ 가장 생산적: {best_day} ({best_day_count}회)
46
+ 피크 시간: {peak_hours}
47
+
48
+ <b>▸ 프로젝트별</b>
49
+ {project_1} {bar_1} {pct_1}%
50
+ {project_2} {bar_2} {pct_2}%
51
+
52
+ <b>▸ 이번 주 잘한 점</b>
53
+ · {highlight}
54
+
55
+ <b>▸ 주요 결정사항</b>
56
+ · {decision}: {reason}
57
+
58
+ <b>▸ 배운 것</b>
59
+ · {learning}
60
+
61
+ <b>▸ 다음 주 추천</b>
62
+ · {recommendation}
63
+
64
+ 🏷️ <i>키워드: {keyword_1}, {keyword_2}, {keyword_3}</i>
65
+ ━━━━━━━━━━━━━━━━━━━━
66
+ ```
67
+
68
+ ## 프로그레스 바 생성 규칙
69
+ - 10칸 기준: █ (채워진) + ░ (빈)
70
+ - 퍼센트를 10으로 나눠서 반올림한 개수만큼 █
71
+ - 예: 40% → ████░░░░░░
72
+
73
+ ## 규칙
74
+ - ~/claude-telegram/memory/daily/ 파일 기반으로 작성
75
+ - 성과는 구체적 결과물 중심 (커밋 수, 완료 태스크 수 포함)
76
+ - 결정사항은 "왜"를 반드시 포함
77
+ - 다음 주 추천은 데이터 기반 (피크 시간, 생산적 패턴 분석)
78
+ - 전체 길이: 최대 2000자
79
+ - 한국어로 작성
@@ -0,0 +1,223 @@
1
+ """Dashboard Mini App API for Telegram Web App.
2
+
3
+ Serves a single-page dashboard and JSON APIs for:
4
+ - Usage statistics
5
+ - Scheduled jobs (list, toggle, run)
6
+ - Memory status
7
+ - Recent activity timeline
8
+ """
9
+
10
+ import json
11
+ from datetime import datetime, timedelta
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ import structlog
16
+ from fastapi import APIRouter, HTTPException, Request
17
+ from fastapi.responses import HTMLResponse
18
+
19
+ logger = structlog.get_logger()
20
+
21
+ dashboard_router = APIRouter(prefix="/dashboard", tags=["dashboard"])
22
+
23
+ # Path to dashboard HTML template
24
+ _TEMPLATE_DIR = Path(__file__).parent / "templates"
25
+
26
+
27
+ @dashboard_router.get("/", response_class=HTMLResponse)
28
+ async def serve_dashboard() -> HTMLResponse:
29
+ """Serve the dashboard HTML."""
30
+ template_path = _TEMPLATE_DIR / "dashboard.html"
31
+ if not template_path.exists():
32
+ raise HTTPException(status_code=404, detail="Dashboard template not found")
33
+ return HTMLResponse(content=template_path.read_text(encoding="utf-8"))
34
+
35
+
36
+ @dashboard_router.get("/api/stats")
37
+ async def get_stats(request: Request) -> Dict[str, Any]:
38
+ """Return usage statistics."""
39
+ db_manager = request.app.state.db_manager
40
+ if not db_manager:
41
+ return {"error": "Database not available"}
42
+
43
+ try:
44
+ async with db_manager.get_connection() as conn:
45
+ # Total interactions today
46
+ today = datetime.now().strftime("%Y-%m-%d")
47
+ cursor = await conn.execute(
48
+ "SELECT COUNT(*) FROM claude_interactions "
49
+ "WHERE created_at >= ?",
50
+ (today,),
51
+ )
52
+ row = await cursor.fetchone()
53
+ today_count = row[0] if row else 0
54
+
55
+ # Total sessions
56
+ cursor = await conn.execute(
57
+ "SELECT COUNT(DISTINCT session_id) FROM claude_interactions"
58
+ )
59
+ row = await cursor.fetchone()
60
+ total_sessions = row[0] if row else 0
61
+
62
+ # Active jobs count
63
+ cursor = await conn.execute(
64
+ "SELECT COUNT(*) FROM scheduled_jobs WHERE is_active = 1"
65
+ )
66
+ row = await cursor.fetchone()
67
+ active_jobs = row[0] if row else 0
68
+
69
+ return {
70
+ "today_interactions": today_count,
71
+ "total_sessions": total_sessions,
72
+ "active_jobs": active_jobs,
73
+ "timestamp": datetime.now().isoformat(),
74
+ }
75
+ except Exception as e:
76
+ logger.error("Failed to get stats", error=str(e))
77
+ return {"error": str(e)}
78
+
79
+
80
+ @dashboard_router.get("/api/jobs")
81
+ async def get_jobs(request: Request) -> List[Dict[str, Any]]:
82
+ """Return scheduled jobs list."""
83
+ db_manager = request.app.state.db_manager
84
+ if not db_manager:
85
+ return []
86
+
87
+ try:
88
+ async with db_manager.get_connection() as conn:
89
+ cursor = await conn.execute(
90
+ "SELECT job_name, cron_expression, is_active, "
91
+ "last_run_at, next_run_at "
92
+ "FROM scheduled_jobs ORDER BY job_name"
93
+ )
94
+ rows = await cursor.fetchall()
95
+
96
+ return [
97
+ {
98
+ "name": row[0],
99
+ "cron": row[1],
100
+ "active": bool(row[2]),
101
+ "last_run": row[3],
102
+ "next_run": row[4],
103
+ }
104
+ for row in rows
105
+ ]
106
+ except Exception as e:
107
+ logger.error("Failed to get jobs", error=str(e))
108
+ return []
109
+
110
+
111
+ @dashboard_router.post("/api/jobs/{name}/toggle")
112
+ async def toggle_job(name: str, request: Request) -> Dict[str, Any]:
113
+ """Toggle a scheduled job on/off."""
114
+ db_manager = request.app.state.db_manager
115
+ if not db_manager:
116
+ raise HTTPException(status_code=500, detail="Database not available")
117
+
118
+ try:
119
+ async with db_manager.get_connection() as conn:
120
+ cursor = await conn.execute(
121
+ "SELECT is_active FROM scheduled_jobs WHERE job_name = ?",
122
+ (name,),
123
+ )
124
+ row = await cursor.fetchone()
125
+ if not row:
126
+ raise HTTPException(status_code=404, detail=f"Job not found: {name}")
127
+
128
+ new_state = not bool(row[0])
129
+ await conn.execute(
130
+ "UPDATE scheduled_jobs SET is_active = ? WHERE job_name = ?",
131
+ (int(new_state), name),
132
+ )
133
+ await conn.commit()
134
+
135
+ return {"name": name, "active": new_state}
136
+ except HTTPException:
137
+ raise
138
+ except Exception as e:
139
+ raise HTTPException(status_code=500, detail=str(e))
140
+
141
+
142
+ @dashboard_router.post("/api/jobs/{name}/run")
143
+ async def run_job(name: str, request: Request) -> Dict[str, Any]:
144
+ """Trigger immediate job execution."""
145
+ event_bus = getattr(request.app.state, "event_bus", None)
146
+ if not event_bus:
147
+ raise HTTPException(status_code=500, detail="Event bus not available")
148
+
149
+ from ..events.types import ScheduledEvent
150
+
151
+ settings = getattr(request.app.state, "settings", None)
152
+ working_dir = Path(".")
153
+ chat_ids: list = []
154
+ if settings:
155
+ working_dir = settings.approved_directory
156
+ chat_ids = settings.notification_chat_ids or []
157
+
158
+ event = ScheduledEvent(
159
+ job_name=name,
160
+ working_directory=working_dir,
161
+ target_chat_ids=chat_ids,
162
+ )
163
+ await event_bus.publish(event)
164
+
165
+ return {"name": name, "status": "triggered"}
166
+
167
+
168
+ @dashboard_router.get("/api/memory")
169
+ async def get_memory(request: Request) -> Dict[str, Any]:
170
+ """Return memory system status."""
171
+ memory_dir = Path.home() / "claude-telegram" / "memory"
172
+ if not memory_dir.exists():
173
+ return {"status": "not_configured", "files": []}
174
+
175
+ files = []
176
+ for p in sorted(memory_dir.rglob("*.md")):
177
+ rel = p.relative_to(memory_dir)
178
+ files.append({
179
+ "path": str(rel),
180
+ "size": p.stat().st_size,
181
+ "modified": datetime.fromtimestamp(p.stat().st_mtime).isoformat(),
182
+ })
183
+
184
+ patterns_file = memory_dir / "patterns" / "interactions.jsonl"
185
+ interaction_count = 0
186
+ if patterns_file.exists():
187
+ interaction_count = sum(1 for _ in patterns_file.open())
188
+
189
+ return {
190
+ "status": "active",
191
+ "file_count": len(files),
192
+ "files": files[:20],
193
+ "interaction_count": interaction_count,
194
+ }
195
+
196
+
197
+ @dashboard_router.get("/api/activity")
198
+ async def get_activity(request: Request) -> List[Dict[str, Any]]:
199
+ """Return recent interaction timeline."""
200
+ db_manager = request.app.state.db_manager
201
+ if not db_manager:
202
+ return []
203
+
204
+ try:
205
+ async with db_manager.get_connection() as conn:
206
+ cursor = await conn.execute(
207
+ "SELECT prompt, session_id, created_at "
208
+ "FROM claude_interactions "
209
+ "ORDER BY created_at DESC LIMIT 20"
210
+ )
211
+ rows = await cursor.fetchall()
212
+
213
+ return [
214
+ {
215
+ "prompt": row[0][:100] if row[0] else "",
216
+ "session_id": row[1][:8] if row[1] else "",
217
+ "timestamp": row[2],
218
+ }
219
+ for row in rows
220
+ ]
221
+ except Exception as e:
222
+ logger.error("Failed to get activity", error=str(e))
223
+ return []