leetkit-manager 0.1.1__tar.gz

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 (45) hide show
  1. leetkit_manager-0.1.1/.github/workflows/release.yml +153 -0
  2. leetkit_manager-0.1.1/.gitignore +13 -0
  3. leetkit_manager-0.1.1/LICENSE +24 -0
  4. leetkit_manager-0.1.1/PKG-INFO +79 -0
  5. leetkit_manager-0.1.1/README.md +52 -0
  6. leetkit_manager-0.1.1/dev/demo_stub.py +92 -0
  7. leetkit_manager-0.1.1/leetkit_manager/__init__.py +1 -0
  8. leetkit_manager-0.1.1/leetkit_manager/cli.py +91 -0
  9. leetkit_manager-0.1.1/leetkit_manager/config_backup.py +42 -0
  10. leetkit_manager-0.1.1/leetkit_manager/interactive_process.py +125 -0
  11. leetkit_manager-0.1.1/leetkit_manager/lens_contract.py +79 -0
  12. leetkit_manager-0.1.1/leetkit_manager/models.py +155 -0
  13. leetkit_manager-0.1.1/leetkit_manager/orchestrator.py +473 -0
  14. leetkit_manager-0.1.1/leetkit_manager/package_service.py +832 -0
  15. leetkit_manager-0.1.1/leetkit_manager/process_runner.py +183 -0
  16. leetkit_manager-0.1.1/leetkit_manager/redaction.py +70 -0
  17. leetkit_manager-0.1.1/leetkit_manager/shortcut.py +97 -0
  18. leetkit_manager-0.1.1/leetkit_manager/single_instance.py +184 -0
  19. leetkit_manager-0.1.1/leetkit_manager/support_bundle.py +176 -0
  20. leetkit_manager-0.1.1/leetkit_manager/ui/__init__.py +6 -0
  21. leetkit_manager-0.1.1/leetkit_manager/ui/api.py +466 -0
  22. leetkit_manager-0.1.1/leetkit_manager/ui/app.js +1737 -0
  23. leetkit_manager-0.1.1/leetkit_manager/ui/app.py +65 -0
  24. leetkit_manager-0.1.1/leetkit_manager/ui/icon.ico +0 -0
  25. leetkit_manager-0.1.1/leetkit_manager/ui/icon.png +0 -0
  26. leetkit_manager-0.1.1/leetkit_manager/ui/index.html +150 -0
  27. leetkit_manager-0.1.1/leetkit_manager/ui/style.css +966 -0
  28. leetkit_manager-0.1.1/packaging/ANTIVIRUS_GUIDE.md +91 -0
  29. leetkit_manager-0.1.1/packaging/build_exe.ps1 +47 -0
  30. leetkit_manager-0.1.1/pyproject.toml +51 -0
  31. leetkit_manager-0.1.1/tests/conftest.py +13 -0
  32. leetkit_manager-0.1.1/tests/fixtures/dartlens_doctor.json +66 -0
  33. leetkit_manager-0.1.1/tests/fixtures/stocklens_doctor.json +56 -0
  34. leetkit_manager-0.1.1/tests/fixtures/telegramlens_doctor.json +51 -0
  35. leetkit_manager-0.1.1/tests/test_api.py +119 -0
  36. leetkit_manager-0.1.1/tests/test_config_backup.py +47 -0
  37. leetkit_manager-0.1.1/tests/test_integration_real_uv.py +49 -0
  38. leetkit_manager-0.1.1/tests/test_interactive_process.py +85 -0
  39. leetkit_manager-0.1.1/tests/test_models.py +77 -0
  40. leetkit_manager-0.1.1/tests/test_orchestrator.py +611 -0
  41. leetkit_manager-0.1.1/tests/test_package_service.py +603 -0
  42. leetkit_manager-0.1.1/tests/test_process_runner.py +54 -0
  43. leetkit_manager-0.1.1/tests/test_redaction.py +45 -0
  44. leetkit_manager-0.1.1/tests/test_shortcut.py +69 -0
  45. leetkit_manager-0.1.1/tests/test_single_instance.py +131 -0
@@ -0,0 +1,153 @@
1
+ name: Release
2
+
3
+ # 태그 푸시(v*) 시 자동:
4
+ # 1. Python 빌드 (wheel + sdist) — Ubuntu
5
+ # 2. 독립 실행 Windows exe 빌드(LeetKitManager.exe) — Windows 러너(PyInstaller는
6
+ # 빌드하는 OS의 실행 파일만 만들 수 있어서 이 작업만 별도 러너가 필요하다)
7
+ # 3. PyPI 업로드 + GitHub Release 생성(wheel/tar.gz/exe 전부 첨부)
8
+
9
+ on:
10
+ push:
11
+ tags:
12
+ - 'v*' # v0.1.0, v0.2.0 등
13
+
14
+ jobs:
15
+ build-wheel:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Setup Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: '3.11'
24
+
25
+ - name: Verify version matches tag
26
+ run: |
27
+ TAG_VER="${GITHUB_REF_NAME#v}"
28
+ PKG_VER=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
29
+ if [ "$TAG_VER" != "$PKG_VER" ]; then
30
+ echo "::error::Tag version ($TAG_VER) does not match pyproject.toml version ($PKG_VER)"
31
+ exit 1
32
+ fi
33
+ echo "Version OK: $PKG_VER"
34
+
35
+ - name: Build distribution
36
+ run: |
37
+ python -m pip install --upgrade pip build
38
+ python -m build
39
+
40
+ - name: Verify distribution
41
+ run: |
42
+ python -m pip install --upgrade twine
43
+ twine check dist/*
44
+
45
+ - uses: actions/upload-artifact@v4
46
+ with:
47
+ name: wheel-dist
48
+ path: |
49
+ dist/*.whl
50
+ dist/*.tar.gz
51
+
52
+ build-exe:
53
+ runs-on: windows-latest
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+
57
+ - name: Setup Python
58
+ uses: actions/setup-python@v5
59
+ with:
60
+ python-version: '3.11'
61
+
62
+ - name: Install build dependencies
63
+ run: python -m pip install -e ".[build]"
64
+
65
+ # 구매자에게 "exe 하나만 받으면 끝"을 실제로 제공하는 핵심 산출물 — 지금까지는
66
+ # 이 작업 자체가 워크플로에 없어서 GitHub Release에 exe가 절대 안 올라갔다.
67
+ - name: Build standalone exe
68
+ shell: pwsh
69
+ run: .\packaging\build_exe.ps1
70
+
71
+ # 자체 업데이트는 실행 중인 exe를 통째로 교체한다 — 받은 파일이 여기서 빌드된
72
+ # 그 파일이 맞는지 앱이 대조할 수 있게 체크섬을 같이 올린다(package_service의
73
+ # fetch_expected_sha256/sha256_of_file이 이 파일을 읽는다).
74
+ - name: Write exe checksum
75
+ shell: pwsh
76
+ run: |
77
+ $hash = (Get-FileHash dist_exe\LeetKitManager.exe -Algorithm SHA256).Hash.ToLower()
78
+ "$hash LeetKitManager.exe" | Out-File -FilePath dist_exe\LeetKitManager.exe.sha256 -Encoding ascii -NoNewline
79
+
80
+ - uses: actions/upload-artifact@v4
81
+ with:
82
+ name: windows-exe
83
+ path: |
84
+ dist_exe/LeetKitManager.exe
85
+ dist_exe/LeetKitManager.exe.sha256
86
+
87
+ release:
88
+ needs: [build-wheel, build-exe]
89
+ runs-on: ubuntu-latest
90
+ permissions:
91
+ contents: write # GitHub Release 생성용
92
+ id-token: write # PyPI Trusted Publishing (OIDC) 용
93
+ steps:
94
+ - uses: actions/download-artifact@v4
95
+ with:
96
+ name: wheel-dist
97
+ path: dist
98
+
99
+ - uses: actions/download-artifact@v4
100
+ with:
101
+ name: windows-exe
102
+ path: dist_exe
103
+
104
+ # Trusted Publishing (OIDC) — PyPI에 등록된 publisher가 자동 인증
105
+ # https://docs.pypi.org/trusted-publishers/
106
+ # 설정: PyPI > Account > Publishing > Add pending publisher
107
+ # Project: leetkit-manager / Owner: Johnhyeon / Repo: leetkit-manager
108
+ # Workflow: release.yml / Environment: (비워둠)
109
+ # PyPI 업로드가 실패해도(예: pending publisher 미등록, 이미 올라간 버전) 아래
110
+ # GitHub Release는 반드시 만들어져야 한다 — 구매자가 실제로 받는 건 거기 붙는
111
+ # exe이고, PyPI는 명령줄 사용자용 부가 경로다. 실패는 워크플로에 경고로 남는다.
112
+ - name: Publish to PyPI (Trusted Publishing)
113
+ uses: pypa/gh-action-pypi-publish@release/v1
114
+ continue-on-error: true
115
+
116
+ - name: Create GitHub Release
117
+ uses: softprops/action-gh-release@v2
118
+ with:
119
+ files: |
120
+ dist/*.whl
121
+ dist/*.tar.gz
122
+ dist_exe/LeetKitManager.exe
123
+ dist_exe/LeetKitManager.exe.sha256
124
+ generate_release_notes: true
125
+ body: |
126
+ > StockLens/DartLens/TelegramLens 설치·진단·업데이트를 한 창에서. 별도 라이선스 키 없이 무료로 씁니다.
127
+
128
+ ---
129
+
130
+ ## 📥 설치
131
+
132
+ **Windows · 명령줄이 익숙하지 않다면** — 아래 `LeetKitManager.exe`를 받아 더블클릭하면 바로 실행됩니다. Lens 설치·업데이트에 필요한 `uv`가 없으면 자동으로 설치합니다.
133
+
134
+ **macOS** — 현재 전용 앱이 없어 명령줄로 설치합니다.
135
+ ```
136
+ uv tool install leetkit-manager
137
+ leetkit-manager gui
138
+ ```
139
+
140
+ **Windows에서도 명령줄이 편하다면**
141
+ ```
142
+ uv tool install leetkit-manager
143
+ ```
144
+
145
+ > **처음 실행할 때 경고가 뜰 수 있습니다.** 서명 안 된 프로그램이라 그렇습니다.
146
+ > - SmartScreen: "추가 정보" → "실행"
147
+ > - 백신이 차단하는 경우: 오탐입니다. 예외 등록 방법은 [백신 안내](https://github.com/Johnhyeon/leetkit-manager/blob/main/packaging/ANTIVIRUS_GUIDE.md) 참고
148
+ > - 파일이 맞는지 확인하려면 위 `LeetKitManager.exe.sha256`의 값과 대조하세요
149
+
150
+ ## 📚 링크
151
+ - [stocklens-mcp](https://github.com/Johnhyeon/stocklens-mcp)
152
+ - [dartlens-mcp](https://github.com/Johnhyeon/dartlens-mcp)
153
+ - [telegramlens-mcp](https://github.com/Johnhyeon/telegramlens-mcp)
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ venv/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ dist/
9
+ build/
10
+ dist_exe/
11
+ build_exe/
12
+ *.spec
13
+ .env
@@ -0,0 +1,24 @@
1
+ LeetKit Manager 라이선스 (Proprietary License)
2
+ Copyright (c) 2026 Johnhyeon. All rights reserved.
3
+
4
+ 본 소프트웨어(LeetKit Manager, 이하 "소프트웨어")는 저작권자 Johnhyeon의 독점
5
+ 저작물입니다. 오픈소스가 아니며, 아래 조건으로 누구나 사용할 수 있습니다
6
+ (StockLens/DartLens/TelegramLens와 달리 별도의 라이선스 키가 필요하지 않습니다).
7
+
8
+ 1. 사용 허가
9
+ - 누구나 소프트웨어를 설치·실행할 수 있습니다.
10
+
11
+ 2. 금지 사항
12
+ - 소프트웨어의 전부 또는 일부를 재배포, 재판매, 대여, 양도, 공개 게시하는 행위.
13
+ - 소프트웨어를 기반으로 한 2차적 저작물을 제작·배포하는 행위.
14
+
15
+ 3. 권리 유보
16
+ - 본 라이선스에 명시적으로 부여되지 않은 모든 권리는 저작권자에게 유보됩니다.
17
+
18
+ 4. 보증 부인 및 책임 제한
19
+ - 소프트웨어는 "있는 그대로(AS IS)" 제공되며, 명시적이든 묵시적이든 어떠한
20
+ 보증도 하지 않습니다.
21
+ - 저작권자는 소프트웨어의 사용 또는 사용 불능으로 인한 어떠한 손해에 대해서도
22
+ 책임지지 않습니다.
23
+
24
+ 문의: 저작권자(Johnhyeon)
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: leetkit-manager
3
+ Version: 0.1.1
4
+ Summary: StockLens/DartLens/TelegramLens 통합 설치·진단·업데이트 Manager
5
+ Project-URL: Homepage, https://github.com/Johnhyeon/leetkit-manager
6
+ Project-URL: Repository, https://github.com/Johnhyeon/leetkit-manager
7
+ Project-URL: Issues, https://github.com/Johnhyeon/leetkit-manager/issues
8
+ Author: Johnhyeon
9
+ License: Proprietary
10
+ License-File: LICENSE
11
+ Keywords: claude,dartlens,manager,mcp,stocklens,telegramlens
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: License :: Other/Proprietary License
15
+ Classifier: Natural Language :: Korean
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: httpx>=0.27
21
+ Requires-Dist: psutil>=5.9
22
+ Requires-Dist: pywebview>=5.0
23
+ Requires-Dist: pywin32>=306; sys_platform == 'win32'
24
+ Provides-Extra: build
25
+ Requires-Dist: pyinstaller>=6.0; extra == 'build'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # LeetKit Manager
29
+
30
+ **StockLens · DartLens · TelegramLens 통합 진단·설치·업데이트 도구**
31
+
32
+ 세 Lens를 하나씩 터미널 명령으로 관리하는 대신, 창 하나에서 상태를 보고 고치는
33
+ 무료 데스크톱 앱입니다. Lens 자체(라이선스 키가 필요한 유료 상품)는 별매이며,
34
+ 이 도구는 그 설치·진단·업데이트 과정을 도와주는 companion 앱입니다.
35
+
36
+ ## 할 수 있는 것
37
+
38
+ - **진단** — 세 Lens 모두 설치 여부·버전·MCP 등록 상태·라이선스 상태를 한 화면에서 확인
39
+ - **MCP 등록** — Claude Desktop/Claude Code에 자동 등록
40
+ - **활성화** — 라이선스 키 입력(창을 벗어나지 않고 붙여넣기)
41
+ - **복구** — 각 Lens가 스스로 안전하다고 판단한 문제만 자동으로 고침(라이선스·세션·수집 데이터는 건드리지 않음)
42
+ - **설치/업데이트** — PyPI 최신 버전으로 설치, 실패 시 이전 버전으로 롤백 명령 안내
43
+ - **지원 문의** — 로그를 안전 목록만 모아 zip으로 만들고, 메일 받는사람/제목/내용을 복사해서 어떤 메일 앱에든 붙여넣기
44
+ - **자기 자신 업데이트** — LeetKit Manager 새 버전이 있으면 상단에 업데이트 버튼이 나타남
45
+
46
+ ## 설치
47
+
48
+ ```
49
+ uv tool install leetkit-manager
50
+ leetkit-manager
51
+ ```
52
+
53
+ 인자 없이 실행하면 대시보드 창이 뜹니다. 최초 실행 시 바탕화면에 바로가기가
54
+ 자동으로 만들어져서, 다음부터는 명령어 없이 아이콘 더블클릭으로 켤 수 있습니다.
55
+
56
+ ### 독립 실행 exe (명령어 없이 배포)
57
+
58
+ 컴퓨터·명령줄에 익숙하지 않은 사용자에게는 `uv tool install` 대신 exe 파일
59
+ 하나를 바로 건넬 수도 있습니다. Python/uv 설치 여부와 무관하게 더블클릭으로
60
+ 바로 실행됩니다.
61
+
62
+ ```
63
+ pip install -e ".[build]"
64
+ powershell -File packaging\build_exe.ps1
65
+ ```
66
+
67
+ `dist_exe\LeetKitManager.exe` 하나가 나옵니다. 서명되지 않은 exe라 스마트스크린
68
+ 경고가 뜰 수 있으니, 실제 배포 전에는 코드사이닝 인증서 적용을 검토하세요.
69
+
70
+ ## 요구 사항
71
+
72
+ - Python 3.11+
73
+ - [uv](https://docs.astral.sh/uv/) — Lens 설치·업데이트에 사용
74
+ - Windows (현재 1차 지원 플랫폼)
75
+
76
+ ## 라이선스
77
+
78
+ [LICENSE](LICENSE) 참고 — 별도 키 없이 누구나 설치·실행 가능하지만, 재배포·2차
79
+ 저작물 제작은 금지됩니다.
@@ -0,0 +1,52 @@
1
+ # LeetKit Manager
2
+
3
+ **StockLens · DartLens · TelegramLens 통합 진단·설치·업데이트 도구**
4
+
5
+ 세 Lens를 하나씩 터미널 명령으로 관리하는 대신, 창 하나에서 상태를 보고 고치는
6
+ 무료 데스크톱 앱입니다. Lens 자체(라이선스 키가 필요한 유료 상품)는 별매이며,
7
+ 이 도구는 그 설치·진단·업데이트 과정을 도와주는 companion 앱입니다.
8
+
9
+ ## 할 수 있는 것
10
+
11
+ - **진단** — 세 Lens 모두 설치 여부·버전·MCP 등록 상태·라이선스 상태를 한 화면에서 확인
12
+ - **MCP 등록** — Claude Desktop/Claude Code에 자동 등록
13
+ - **활성화** — 라이선스 키 입력(창을 벗어나지 않고 붙여넣기)
14
+ - **복구** — 각 Lens가 스스로 안전하다고 판단한 문제만 자동으로 고침(라이선스·세션·수집 데이터는 건드리지 않음)
15
+ - **설치/업데이트** — PyPI 최신 버전으로 설치, 실패 시 이전 버전으로 롤백 명령 안내
16
+ - **지원 문의** — 로그를 안전 목록만 모아 zip으로 만들고, 메일 받는사람/제목/내용을 복사해서 어떤 메일 앱에든 붙여넣기
17
+ - **자기 자신 업데이트** — LeetKit Manager 새 버전이 있으면 상단에 업데이트 버튼이 나타남
18
+
19
+ ## 설치
20
+
21
+ ```
22
+ uv tool install leetkit-manager
23
+ leetkit-manager
24
+ ```
25
+
26
+ 인자 없이 실행하면 대시보드 창이 뜹니다. 최초 실행 시 바탕화면에 바로가기가
27
+ 자동으로 만들어져서, 다음부터는 명령어 없이 아이콘 더블클릭으로 켤 수 있습니다.
28
+
29
+ ### 독립 실행 exe (명령어 없이 배포)
30
+
31
+ 컴퓨터·명령줄에 익숙하지 않은 사용자에게는 `uv tool install` 대신 exe 파일
32
+ 하나를 바로 건넬 수도 있습니다. Python/uv 설치 여부와 무관하게 더블클릭으로
33
+ 바로 실행됩니다.
34
+
35
+ ```
36
+ pip install -e ".[build]"
37
+ powershell -File packaging\build_exe.ps1
38
+ ```
39
+
40
+ `dist_exe\LeetKitManager.exe` 하나가 나옵니다. 서명되지 않은 exe라 스마트스크린
41
+ 경고가 뜰 수 있으니, 실제 배포 전에는 코드사이닝 인증서 적용을 검토하세요.
42
+
43
+ ## 요구 사항
44
+
45
+ - Python 3.11+
46
+ - [uv](https://docs.astral.sh/uv/) — Lens 설치·업데이트에 사용
47
+ - Windows (현재 1차 지원 플랫폼)
48
+
49
+ ## 라이선스
50
+
51
+ [LICENSE](LICENSE) 참고 — 별도 키 없이 누구나 설치·실행 가능하지만, 재배포·2차
52
+ 저작물 제작은 금지됩니다.
@@ -0,0 +1,92 @@
1
+ """온보딩 마법사(및 전체 GUI)를 "방금 산 컴퓨터" 상태로 눈으로 확인하기 위한 개발용 실행기.
2
+
3
+ 새 Windows 계정이나 VM을 따로 만드는 대신, HOME만 임시 폴더로 돌려서 격리한다
4
+ (USERPROFILE/HOME/APPDATA 환경변수) — `~/.leetkit-manager`, webview localStorage,
5
+ `~/.claude.json`/`%APPDATA%/Claude/...`/`~/.codex/config.toml` 전부 이 프로세스 안에서만
6
+ 격리된 새 경로를 보고, 실제 계정의 어떤 파일도 안 건드린다.
7
+
8
+ 주의: 이 격리는 파일 기반 경로에만 적용된다. DartLens의 DART API 키처럼 OS 키체인
9
+ (Windows Credential Manager 등, `keyring` 라이브러리)에 저장되는 자격증명은 HOME과
10
+ 무관하게 Windows 로그인 계정 단위로 공유된다 — 이 데모에서 API 키 등록을 실제로
11
+ 끝까지 진행하면 실제 프로덕션 키를 덮어쓸 수 있다는 뜻이다. 라이선스 키는 파일
12
+ 기반이라 격리되지만, API 키 등록 단계는 이 사실을 알고(가짜 키만 넣어 실패 경로만
13
+ 확인하거나, 실제 키로 덮어써도 상관없다고 판단한 경우에만) 진행할 것.
14
+
15
+ Lens 설치는 PyPI가 아니라 이 리포의 형제 디렉터리(D:/project/stocklens/mcp,
16
+ mcp-dart, telegramlens)에서 직접 uv tool install한다 — 지금 세션에서 만든
17
+ Codex 타겟·텔레그램 로그인 스테퍼 같은 기능은 아직 PyPI에 배포 전이라, PyPI 최신
18
+ 버전만 받으면 이 기능들이 전혀 동작하지 않는 걸 그대로 보게 된다(실제로 처음
19
+ 버전에서 "호환되지 않는 Lens 버전"/"setup 응답을 파싱할 수 없습니다 (exit=2)"로
20
+ 드러난 문제 — Manager 버그가 아니라 Lens 쪽 배포 지연이었다). 로컬 소스로 설치하면
21
+ 지금 작업 중인 코드까지 포함해서 실제 배포될 물건과 동일하게 검증할 수 있다.
22
+
23
+ - 라이선스/DART API 키 입력은 실제 activate/setup 바이너리가 검증한다 — 가짜 키를 넣으면
24
+ 실제 실패 메시지가, 진짜 보유한 키를 넣으면 실제 성공이 뜬다.
25
+ - 텔레그램 로그인도 실제 `telegramlens-login --stepper`가 뜬다 — my.telegram.org에서 받은
26
+ 진짜 API_ID/HASH와 실제 전화번호·SMS 코드가 있어야 완주된다(없으면 중간에 취소하고
27
+ 화면만 확인하면 됨).
28
+
29
+ uv가 이 컴퓨터에 없으면 설치 과정에서 공식 설치 스크립트로 자동 부트스트랩된다(실제
30
+ 신규 구매자와 동일한 경로 — package_service.ensure_uv_available 참고).
31
+
32
+ 실행: `python dev/demo_stub.py` (leetkit-manager 리포 루트에서)
33
+ 배포용 leetkit-manager 패키지에는 포함되지 않는다(pyproject.toml의 wheel 대상이
34
+ `leetkit_manager` 패키지 하나뿐이므로 dev/ 는 자동으로 제외됨).
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import os
40
+ import sys
41
+ import tempfile
42
+ from pathlib import Path
43
+ from unittest.mock import patch
44
+
45
+ if sys.stdout.encoding and sys.stdout.encoding.lower() != "utf-8":
46
+ sys.stdout.reconfigure(encoding="utf-8")
47
+
48
+ _DEMO_HOME = tempfile.mkdtemp(prefix="leetkit-demo-home-")
49
+ os.environ["USERPROFILE"] = _DEMO_HOME
50
+ os.environ["HOME"] = _DEMO_HOME
51
+ os.environ["APPDATA"] = os.path.join(_DEMO_HOME, "AppData", "Roaming")
52
+ os.makedirs(os.environ["APPDATA"], exist_ok=True)
53
+ print(f"[demo_stub] 격리된 가짜 HOME: {_DEMO_HOME}")
54
+ print("[demo_stub] uv 설치/라이선스 검증은 전부 실제로 동작합니다 — 네트워크 필요, 설치에 시간이 걸립니다.")
55
+ print("[demo_stub] 주의: DartLens DART API 키는 OS 키체인에 저장되어 HOME 격리를 안 받습니다 (실제 계정과 공유).")
56
+
57
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
58
+
59
+ _REPO_ROOT = Path(__file__).parent.parent
60
+ _LOCAL_LENS_PATHS = {
61
+ "stocklens-mcp": _REPO_ROOT.parent / "mcp",
62
+ "dartlens-mcp": _REPO_ROOT.parent / "mcp-dart",
63
+ "telegramlens-mcp": _REPO_ROOT.parent / "telegramlens",
64
+ }
65
+
66
+
67
+ def _install_from_local_repo(package_name: str, _version: str, *, timeout=None):
68
+ """package_service.install_version 대신 — PyPI가 아니라 형제 리포 소스에서 설치해
69
+ 이 세션에서 아직 배포 안 한 변경사항(Codex 타겟, 텔레그램 로그인 스테퍼 등)까지
70
+ 포함해서 검증한다. _version은 로컬 설치엔 의미가 없어 무시(실제 버전은 각 리포
71
+ pyproject.toml이 결정)."""
72
+ from leetkit_manager import package_service
73
+
74
+ local_path = _LOCAL_LENS_PATHS.get(package_name)
75
+ if local_path is None or not local_path.exists():
76
+ return package_service.install_version(package_name, _version, timeout=timeout or 120.0)
77
+ return package_service.run_cli(
78
+ ["uv", "tool", "install", "--force", str(local_path)],
79
+ timeout=timeout or 120.0,
80
+ )
81
+
82
+
83
+ def main() -> None:
84
+ from leetkit_manager import package_service
85
+ from leetkit_manager.ui import app
86
+
87
+ with patch.object(package_service, "install_version", side_effect=_install_from_local_repo):
88
+ app.run()
89
+
90
+
91
+ if __name__ == "__main__":
92
+ main()
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,91 @@
1
+ """LeetKit Manager CLI 진입점.
2
+
3
+ leetkit-manager 데스크톱 대시보드(pywebview) 실행 — 기본 동작
4
+ leetkit-manager gui 위와 동일(명시적으로)
5
+ leetkit-manager diagnose [--online] 전체 진단 대시보드(텍스트)
6
+ leetkit-manager check-updates 세 Lens 최신 버전 조회
7
+
8
+ 인자 없이 실행(바탕화면 바로가기 더블클릭 포함)하면 gui로 간다 — 설치 후에는 터미널
9
+ 명령을 몰라도 아이콘만 누르면 되게 하기 위함(uv tool install이 만드는 leetkit-manager.exe
10
+ 자체가 이미 실행 파일이라 별도 빌드 없이 바로가기만 놓으면 된다. shortcut.py 참고).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import sys
17
+
18
+ from leetkit_manager import orchestrator
19
+ from leetkit_manager.lens_contract import LENSES
20
+
21
+
22
+ def _cmd_diagnose(args: argparse.Namespace) -> int:
23
+ diagnoses = orchestrator.run_full_diagnosis(online=args.online)
24
+ summary = orchestrator.summarize(diagnoses)
25
+
26
+ print(f"{summary['total']}개 중 {summary['ok']}개 정상", end="")
27
+ if summary["update_available"]:
28
+ print(f" · 업데이트 {summary['update_available']}개", end="")
29
+ if summary["action_needed"]:
30
+ print(f" · 조치 필요 {summary['action_needed']}개", end="")
31
+ print()
32
+ print()
33
+
34
+ for d in diagnoses:
35
+ version = d.report.installed_version if d.report else "?"
36
+ print(f"[{d.readiness}] {d.lens.display_name} (v{version})")
37
+ if d.report:
38
+ for c in d.report.checks:
39
+ if c.status in ("ok", "skip", "info-skip"):
40
+ continue
41
+ label = "진행중" if c.status == "active" else "문제"
42
+ print(f" - [{label}] {c.id}: {c.summary}")
43
+ if c.action:
44
+ print(f" action: {c.action}")
45
+ print()
46
+
47
+ any_action_needed = summary["action_needed"] > 0
48
+ return 1 if any_action_needed else 0
49
+
50
+
51
+ def _cmd_check_updates(args: argparse.Namespace) -> int:
52
+ latest = orchestrator.check_for_updates()
53
+ for lens in LENSES:
54
+ v = latest.get(lens.name)
55
+ print(f"{lens.display_name}: {v if v else '확인 실패'}")
56
+ return 0
57
+
58
+
59
+ def _cmd_gui(args: argparse.Namespace) -> int:
60
+ from leetkit_manager.ui.app import run
61
+
62
+ run()
63
+ return 0
64
+
65
+
66
+ def _build_parser() -> argparse.ArgumentParser:
67
+ p = argparse.ArgumentParser(prog="leetkit-manager")
68
+ sub = p.add_subparsers(dest="command")
69
+
70
+ diagnose = sub.add_parser("diagnose", help="전체 Lens 진단 대시보드(텍스트)")
71
+ diagnose.add_argument("--online", action="store_true", help="실제 외부 연결 검사 포함")
72
+ diagnose.set_defaults(func=_cmd_diagnose)
73
+
74
+ check_updates = sub.add_parser("check-updates", help="세 Lens 최신 버전 조회(PyPI)")
75
+ check_updates.set_defaults(func=_cmd_check_updates)
76
+
77
+ gui = sub.add_parser("gui", help="데스크톱 대시보드(pywebview) 실행")
78
+ gui.set_defaults(func=_cmd_gui)
79
+
80
+ return p
81
+
82
+
83
+ def main() -> None:
84
+ args = _build_parser().parse_args()
85
+ # 서브커맨드 없이 실행(바로가기 더블클릭 등)하면 gui가 기본 동작.
86
+ func = getattr(args, "func", _cmd_gui)
87
+ sys.exit(func(args))
88
+
89
+
90
+ if __name__ == "__main__":
91
+ main()
@@ -0,0 +1,42 @@
1
+ """Claude Desktop/Code 설정 파일 백업·복원.
2
+
3
+ 각 Lens의 `<lens>-setup`도 자기 몫의 백업(`.json.backup`, 고정 이름 — 매번 덮어씀)을
4
+ 따로 만들지만, 그건 그 Lens 한 번의 변경만 되돌릴 수 있다. 이 모듈은 "여러 Lens를
5
+ 순차 설치·업데이트하는 배치 작업을 시작하는 시점"의 통짜 스냅샷을 시각이 포함된
6
+ 이름(`.bak-YYYYMMDD-HHMMSS`)으로 남겨, 배치 중간에 무엇이 잘못돼도 최초 상태로
7
+ 되돌릴 수 있게 하는 바깥쪽 안전망이다.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+
15
+
16
+ def backup_path_for(config_path: Path, *, at: datetime | None = None) -> Path:
17
+ at = at or datetime.now()
18
+ stamp = at.strftime("%Y%m%d-%H%M%S")
19
+ return config_path.with_name(config_path.name + f".bak-{stamp}")
20
+
21
+
22
+ def backup_config(config_path: Path, *, at: datetime | None = None) -> Path | None:
23
+ """config_path가 존재하면 타임스탬프 백업을 만들고 그 경로를 반환한다.
24
+
25
+ 파일이 아직 없으면(해당 클라이언트를 안 쓰는 경우) 백업할 것이 없으므로 None —
26
+ "백업 없이 설정을 덮어쓰지 않는다"는 새 파일 생성까지 막는 규칙이 아니라, 기존
27
+ 내용을 잃을 위험이 있을 때만 적용된다.
28
+ """
29
+ if not config_path.exists():
30
+ return None
31
+ backup = backup_path_for(config_path, at=at)
32
+ backup.write_bytes(config_path.read_bytes())
33
+ return backup
34
+
35
+
36
+ def restore_config(config_path: Path, backup_path: Path) -> bool:
37
+ """백업 내용을 원래 경로에 되돌려 쓴다. 백업이 없으면 아무 것도 하지 않고 False —
38
+ Manager는 장애 복구를 이유로 파일을 임의로 만들거나 지우지 않는다."""
39
+ if not backup_path.exists():
40
+ return False
41
+ config_path.write_bytes(backup_path.read_bytes())
42
+ return True