devmate 1.0.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.
@@ -0,0 +1,221 @@
1
+ """
2
+ vault — 加密密钥管理器
3
+
4
+ 加密体系:
5
+ 主密码(用户输入)
6
+ ↓ Argon2id KDF(salt + 内存硬参数)
7
+ 256-bit 主密钥
8
+ ↓ AES-256-GCM(随机 nonce,认证加密)
9
+ 加密密钥值 → 写入 ~/.devmate/vault/keys/<name>
10
+
11
+ 安全性:
12
+ - 主密码不存储,只存 KDF 参数和 salt
13
+ - 每次加密使用随机 nonce(12 字节)
14
+ - 认证标签确保密文完整性
15
+ - 锁定后 salt 和 nonce 从内存中清除
16
+ """
17
+
18
+ import json
19
+ import os
20
+
21
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
22
+ from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
23
+
24
+ from devmate.core.config import VAULT_PATH
25
+ from devmate.core.logging import logger
26
+
27
+ # ── 常量 ────────────────────────────────────────────────
28
+
29
+ KEYS_DIR = VAULT_PATH / "keys"
30
+ LOCK_MARKER = VAULT_PATH / ".locked"
31
+ METADATA_FILE = VAULT_PATH / "metadata.json"
32
+ NONCE_BYTES = 12 # AES-GCM 推荐 nonce 长度
33
+ KEY_BYTES = 32 # AES-256 = 32 字节
34
+
35
+
36
+ # ── 异常 ────────────────────────────────────────────────
37
+
38
+ class VaultError(Exception):
39
+ """vault 操作异常"""
40
+ pass
41
+
42
+ class VaultLockedError(VaultError):
43
+ """vault 已锁定"""
44
+ pass
45
+
46
+ class VaultUnlockedError(VaultError):
47
+ """vault 未锁定(不需要再 unlock)"""
48
+ pass
49
+
50
+ class KeyNotFoundError(VaultError):
51
+ """密钥不存在"""
52
+ pass
53
+
54
+
55
+ # ── Vault 核心 ──────────────────────────────────────────
56
+
57
+ class Vault:
58
+ """加密密钥管理器"""
59
+
60
+ def __init__(self):
61
+ self._key: bytes | None = None # 派生出的 256-bit 密钥(None = 锁定)
62
+ self._salt: bytes | None = None # KDF salt
63
+ self._params: dict | None = None # Argon2id 参数
64
+
65
+ # ── 属性 ────────────────────────────────────
66
+
67
+ @property
68
+ def is_locked(self) -> bool:
69
+ """vault 是否锁定"""
70
+ return self._key is None or LOCK_MARKER.exists()
71
+
72
+ @property
73
+ def is_unlocked(self) -> bool:
74
+ return not self.is_locked
75
+
76
+ # ── 初始化与目录管理 ────────────────────────
77
+
78
+ def _ensure_dirs(self):
79
+ """确保 vault 目录存在"""
80
+ KEYS_DIR.mkdir(parents=True, exist_ok=True)
81
+
82
+ # ── 主密码处理 ──────────────────────────────
83
+
84
+ def _derive_key(self, master_password: str) -> bytes:
85
+ """用 Argon2id 从主密码派生 256-bit 密钥"""
86
+ password_bytes = master_password.encode("utf-8")
87
+
88
+ if self._salt is None:
89
+ # 首次使用:生成随机 salt
90
+ self._salt = os.urandom(16)
91
+ self._params = {
92
+ "salt": self._salt.hex(),
93
+ "iterations": 3, # OWASP 推荐 ≥ 2
94
+ "memory_cost": 65536, # 64 MB
95
+ "lanes": 4,
96
+ "key_len": KEY_BYTES,
97
+ }
98
+ self._save_metadata()
99
+
100
+ kdf = Argon2id(
101
+ salt=bytes.fromhex(self._params["salt"]),
102
+ length=self._params["key_len"],
103
+ iterations=self._params["iterations"],
104
+ memory_cost=self._params["memory_cost"],
105
+ lanes=self._params["lanes"],
106
+ )
107
+ return kdf.derive(password_bytes)
108
+
109
+ # ── 元数据持久化 ────────────────────────────
110
+
111
+ def _save_metadata(self):
112
+ """保存 KDF 参数到文件"""
113
+ data = {
114
+ "salt": self._params["salt"],
115
+ "iterations": self._params["iterations"],
116
+ "memory_cost": self._params["memory_cost"],
117
+ "lanes": self._params["lanes"],
118
+ "key_len": self._params["key_len"],
119
+ }
120
+ METADATA_FILE.write_text(json.dumps(data, indent=2))
121
+
122
+ def _load_metadata(self):
123
+ """从文件加载 KDF 参数"""
124
+ if not METADATA_FILE.exists():
125
+ self._salt = None
126
+ self._params = None
127
+ return
128
+ data = json.loads(METADATA_FILE.read_text())
129
+ self._salt = bytes.fromhex(data["salt"])
130
+ self._params = data
131
+
132
+ # ── 核心操作 ────────────────────────────────
133
+
134
+ def unlock(self, master_password: str):
135
+ """解锁 vault"""
136
+ if self.is_unlocked:
137
+ raise VaultUnlockedError("vault 已经解锁")
138
+
139
+ self._ensure_dirs()
140
+ self._load_metadata()
141
+
142
+ # 首次使用:创建新 vault
143
+ if self._params is None:
144
+ self._key = self._derive_key(master_password)
145
+ self._save_metadata()
146
+ else:
147
+ self._key = self._derive_key(master_password)
148
+
149
+ # 移除锁定标记
150
+ if LOCK_MARKER.exists():
151
+ LOCK_MARKER.unlink()
152
+
153
+ logger.info("vault 已解锁")
154
+
155
+ def lock(self):
156
+ """锁定 vault(从内存中清除密钥)"""
157
+ self._key = None
158
+ self._salt = None
159
+ self._params = None
160
+ LOCK_MARKER.touch()
161
+ logger.info("vault 已锁定")
162
+
163
+ def set(self, name: str, value: str, master_password: str):
164
+ """存储一个加密密钥"""
165
+ if self.is_locked:
166
+ self.unlock(master_password)
167
+
168
+ # 生成随机 nonce
169
+ nonce = os.urandom(NONCE_BYTES)
170
+
171
+ # AES-256-GCM 加密
172
+ aesgcm = AESGCM(self._key)
173
+ ciphertext = aesgcm.encrypt(nonce, value.encode("utf-8"), None)
174
+
175
+ # 存储:nonce + 密文(nonce 在前面,解密时需要)
176
+ key_file = KEYS_DIR / name
177
+ key_file.write_bytes(nonce + ciphertext)
178
+ logger.info(f"密钥已存储: {name}")
179
+
180
+ def get(self, name: str, master_password: str) -> str:
181
+ """读取一个解密后的密钥"""
182
+ if self.is_locked:
183
+ self.unlock(master_password)
184
+
185
+ key_file = KEYS_DIR / name
186
+ if not key_file.exists():
187
+ raise KeyNotFoundError(f"密钥不存在: {name}")
188
+
189
+ data = key_file.read_bytes()
190
+ nonce = data[:NONCE_BYTES]
191
+ ciphertext = data[NONCE_BYTES:]
192
+
193
+ aesgcm = AESGCM(self._key)
194
+ plaintext = aesgcm.decrypt(nonce, ciphertext, None)
195
+ return plaintext.decode("utf-8")
196
+
197
+ def list(self) -> list[str]:
198
+ """列出所有密钥名称(不需要解锁)"""
199
+ if not KEYS_DIR.exists():
200
+ return []
201
+ return sorted(f.name for f in KEYS_DIR.iterdir() if f.is_file())
202
+
203
+ def delete(self, name: str, master_password: str):
204
+ """删除一个密钥"""
205
+ if self.is_locked:
206
+ self.unlock(master_password)
207
+
208
+ key_file = KEYS_DIR / name
209
+ if not key_file.exists():
210
+ raise KeyNotFoundError(f"密钥不存在: {name}")
211
+ key_file.unlink()
212
+ logger.info(f"密钥已删除: {name}")
213
+
214
+ def exists(self, name: str) -> bool:
215
+ """检查密钥是否存在"""
216
+ return (KEYS_DIR / name).exists()
217
+
218
+
219
+ # ── 全局实例 ────────────────────────────────────────────
220
+
221
+ vault = Vault()
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: devmate
3
+ Version: 1.0.0
4
+ Summary: 程序员终端瑞士军刀——替代 5 个独立工具,一个 CLI 搞定
5
+ Author-email: DevMate Team <devmate@example.com>
6
+ License: MIT
7
+ Keywords: cli,developer-tools,terminal,productivity
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Topic :: Software Development
14
+ Classifier: Topic :: Utilities
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: click>=8.1
18
+ Requires-Dist: rich>=13.0
19
+ Requires-Dist: pydantic>=2.0
20
+ Requires-Dist: pydantic-settings>=2.0
21
+ Requires-Dist: pyyaml>=6.0
22
+ Requires-Dist: sqlalchemy>=2.0
23
+ Requires-Dist: alembic>=1.13
24
+ Requires-Dist: cryptography>=42.0
25
+ Requires-Dist: httpx>=0.27
26
+ Provides-Extra: web
27
+ Requires-Dist: fastapi>=0.110; extra == "web"
28
+ Requires-Dist: uvicorn[standard]>=0.29; extra == "web"
29
+ Requires-Dist: jinja2>=3.1; extra == "web"
30
+ Requires-Dist: python-multipart>=0.0.9; extra == "web"
31
+ Requires-Dist: python-jose[cryptography]>=3.3; extra == "web"
32
+ Requires-Dist: passlib[bcrypt]>=1.7; extra == "web"
33
+ Provides-Extra: tui
34
+ Requires-Dist: textual>=1.0; extra == "tui"
35
+ Provides-Extra: ai
36
+ Requires-Dist: chromadb>=0.5; extra == "ai"
37
+ Requires-Dist: tiktoken>=0.7; extra == "ai"
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest>=8.0; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
41
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
42
+ Requires-Dist: ruff>=0.5; extra == "dev"
43
+ Requires-Dist: mypy>=1.10; extra == "dev"
44
+ Provides-Extra: all
45
+ Requires-Dist: devmate[web]; extra == "all"
46
+ Requires-Dist: devmate[tui]; extra == "all"
47
+ Requires-Dist: devmate[ai]; extra == "all"
48
+ Requires-Dist: devmate[dev]; extra == "all"
49
+
50
+ # 🏗️ DevMate
51
+
52
+ > **程序员终端瑞士军刀——替代 5 个独立工具,一个 CLI 搞定**
53
+
54
+ DevMate 是一个 CLI + TUI + Web 三模架构的程序员全能工坊。装一个 devmate,少装 Postman + TablePlus + htop + 正则工具 + 文件管理器。
55
+
56
+ ## 快速开始
57
+
58
+ ```bash
59
+ # 安装
60
+ pip install devmate
61
+
62
+ # 首次运行(交互式引导)
63
+ devmate
64
+
65
+ # 试试这些命令
66
+ devmate tk organize ~/Downloads --dry-run # 整理文件(预览模式)
67
+ devmate db connect sqlite:///test.db # 连接数据库
68
+ devmate api get https://httpbin.org/get # 发送 HTTP 请求
69
+ devmate ai add default --provider openai # 配置 AI 模型
70
+ ```
71
+
72
+ ## 已实现模块(Phase 1)
73
+
74
+ | 模块 | CLI 入口 | 功能 | 状态 |
75
+ |------|---------|------|------|
76
+ | **toolkit** | `devmate tk` | 文件整理、目录大小排行、缓存清理 | ✅ |
77
+ | **apidev** | `devmate api` | HTTP 客户端、Mock Server、请求历史 | ✅ |
78
+ | **dbadmin** | `devmate db` | SQL 编辑器、表浏览器、保存的查询 | ✅ |
79
+ | **apihub** | `devmate ai` | AI 模型统一接入、对话、历史记录 | ✅ |
80
+ | vault | `devmate vault` | 加密密钥管理器 | ✅ |
81
+ | audit | `devmate audit` | 审计日志 | ✅ |
82
+
83
+ ## 安装方式
84
+
85
+ ```bash
86
+ # 最小安装(CLI 核心)
87
+ pip install devmate
88
+
89
+ # 开发安装(含测试 + 代码检查工具)
90
+ pip install "devmate[dev]"
91
+
92
+ # 完整安装
93
+ pip install "devmate[all]"
94
+ ```
95
+
96
+ ## 项目状态
97
+
98
+ **v0.1.0-alpha** — Phase 1 完成,4 个 MVP 模块可用。
99
+
100
+ ## 开发
101
+
102
+ ```bash
103
+ git clone https://github.com/CXuanPro/DevMate.git
104
+ cd DevMate
105
+ python -m venv .venv && source .venv/bin/activate
106
+ pip install -e ".[dev]"
107
+
108
+ # 验证
109
+ devmate --help
110
+
111
+ # 运行测试
112
+ pytest
113
+
114
+ # 代码检查
115
+ ruff check src/ tests/
116
+ ```
@@ -0,0 +1,37 @@
1
+ devmate/__init__.py,sha256=0w2oV2j16BKZhV6VaJqeXdOSXmu2z0sClMhX-NAvbkQ,193
2
+ devmate/__main__.py,sha256=BM2m4-Qx1fDJ7671ZixHiPMeTP_TtR35rvT7rTsSPEU,103
3
+ devmate/app/__init__.py,sha256=zueaNl5oUUM6cKVSMS5bHN5lJi-1ovnQmEQCxadF7Cg,57
4
+ devmate/app/cli.py,sha256=YGuxc_M1XsXt-Wl7J_bl7llsOnw-YZWhzXgjMUPvdbs,60273
5
+ devmate/app/tui.py,sha256=jyiVFVPv527_l-HCfzbS0_5-X9lKdYEF-eFPmMs5o6A,8589
6
+ devmate/app/web/__init__.py,sha256=X-IiyGpgNLBgD91KMgQiLB43op1XR3F6T7jMjBvscWg,3755
7
+ devmate/bus/__init__.py,sha256=0PwmZK-CMsADH_t3M4AFUdk8v7C29dHd6s657rRuTVY,65
8
+ devmate/bus/bus.py,sha256=OQOgkKjZwXPaO_Z95qNbptZ96_44e0uklbpoCNB1dDw,5279
9
+ devmate/bus/command.py,sha256=L-AtXmFFO4m0CEw7pcRUV_-QwFNVISv8tMnHQ-SZYtQ,2444
10
+ devmate/core/__init__.py,sha256=JwY0ZX6F4qt6kTW0qjlPEqtdgVhANMMvmHJ3Nzrwl2o,56
11
+ devmate/core/config.py,sha256=NQpxnqXjJZ3YOzyhzxSfazZD_TNOvYiDD5rwDql66o4,3847
12
+ devmate/core/logging.py,sha256=ImxNsPhTwAjqd9FPckYhlkrsLKMNwHL2kuj2aOuHFpk,1811
13
+ devmate/core/security.py,sha256=-5a6l7MRnA5lv18fCjMgFOdkeMTBZiEJhehBZ4aUtjc,1960
14
+ devmate/events/__init__.py,sha256=Mj86hZaW4-_9tFf7zM1Q7GPuxyPmJQoPE3PJ5fFN6rg,47
15
+ devmate/events/bus.py,sha256=ZqUggP2JfcdFK3Ol0KjDB0xR5nP8Yz_hO3KzLwRWC3c,3516
16
+ devmate/events/domain_event.py,sha256=YBA6805DPuFcLEGWwGLrvHWO1X70mmV4sJd9Tq-q0RM,1356
17
+ devmate/module/__init__.py,sha256=xDsOkChb4D1A_nv9kbhJiRXSxnGpbj8pnQ3TesbKGH8,50
18
+ devmate/module/registry.py,sha256=Z-mhSKvCkNCiIE8Uh9PVR-ZvbD_GdtCC_gvEN9ORa4g,2157
19
+ devmate/security/audit.py,sha256=WDgL-PlHx06YxSRCRQgecy7AHY45xHMhQ18q6-nrz4k,3203
20
+ devmate/security/vault.py,sha256=qsJV325H-1fGZHfyvk971sNELQFCAIIXXtqAglb0LoU,7420
21
+ devmate_agent/__init__.py,sha256=e6I93HLmdvk9Cs0cZJagECV9iusBvUfN0wU8x_md_lI,15691
22
+ devmate_apidev/__init__.py,sha256=2rQw0H-OcCn4-WDD10YdWCJkkUyU427BqGpkymzh7AY,9638
23
+ devmate_apihub/__init__.py,sha256=tbZ466vIC1zC4bHIzrAE1AS9jn2R3srFcPvW4a0j43E,10593
24
+ devmate_dbadmin/__init__.py,sha256=3TDtyywPdAD9gjI-lC_5Iz3x5ouGoobYTcku42OvcgI,9304
25
+ devmate_gitflow/__init__.py,sha256=HhB-Pz0TqTtMEt248D-_GvZAHXDNp4wOE0cIXc6IjQ0,8536
26
+ devmate_monitor/__init__.py,sha256=yLPVbnhUCt8xok__H9MhtHWFXvkvYx57-6jjC7_70IM,3518
27
+ devmate_notekeeper/__init__.py,sha256=-nlLZhWYB3U2Bne-5onqjw86Tta5lc-5UWz5OUFAw-E,9286
28
+ devmate_regexlab/__init__.py,sha256=HcjSs5nSNQcjtnzvUoHfn1jmZva_6ZoLO0mIV8hWcEg,7150
29
+ devmate_reporter/__init__.py,sha256=Zz2wXXldIiVex6G5fp6wE69VUKMYPl69TwYd3zu5f3g,5686
30
+ devmate_scaffold/__init__.py,sha256=mQqGjzgvREaOzdQXnfyLO0ETOt1A-dyx6KkzQNv8NVM,6381
31
+ devmate_sshman/__init__.py,sha256=hJW9oKLKgBregY2-Str0hdNY9rVznHGVUuD-U5o6NIM,11258
32
+ devmate_toolkit/__init__.py,sha256=e_IxW6klFfRLR4bIIElThdzyQ-kr-OEjkRwVWardGxA,7826
33
+ devmate-1.0.0.dist-info/METADATA,sha256=B_p93mUDOHnUyc7IHKD8te32n0dWkYW7QzjHqpxrdZw,3582
34
+ devmate-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
35
+ devmate-1.0.0.dist-info/entry_points.txt,sha256=V1_cyikZm292vEc6la7MgTkDISy1E5uhuIIkRwhs9mE,434
36
+ devmate-1.0.0.dist-info/top_level.txt,sha256=w2celaaxGYQXf2XQPLOyw5TBBKSZgCBU6_KuLYdeMzI,201
37
+ devmate-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,18 @@
1
+ [console_scripts]
2
+ devmate = devmate.app.cli:main
3
+
4
+ [devmate.modules]
5
+ agent = devmate_agent
6
+ apidev = devmate_apidev
7
+ apihub = devmate_apihub
8
+ dbadmin = devmate_dbadmin
9
+ gitflow = devmate_gitflow
10
+ monitor = devmate_monitor
11
+ notekeeper = devmate_notekeeper
12
+ publisher = devmate_publisher
13
+ regexlab = devmate_regexlab
14
+ reporter = devmate_reporter
15
+ scaffold = devmate_scaffold
16
+ sshman = devmate_sshman
17
+ toolkit = devmate_toolkit
18
+ vault = devmate_vault
@@ -0,0 +1,13 @@
1
+ devmate
2
+ devmate_agent
3
+ devmate_apidev
4
+ devmate_apihub
5
+ devmate_dbadmin
6
+ devmate_gitflow
7
+ devmate_monitor
8
+ devmate_notekeeper
9
+ devmate_regexlab
10
+ devmate_reporter
11
+ devmate_scaffold
12
+ devmate_sshman
13
+ devmate_toolkit