api-key-manager 2.2.0__tar.gz → 3.0.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 (106) hide show
  1. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/PKG-INFO +31 -8
  2. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/README.md +29 -6
  3. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/PKG-INFO +31 -8
  4. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/SOURCES.txt +3 -0
  5. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/__init__.py +2 -2
  6. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/api_models.py +15 -10
  7. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/checker.py +13 -8
  8. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/cli.py +2 -2
  9. api_key_manager-3.0.1/key_manager/config.py +103 -0
  10. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/detector.py +72 -64
  11. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/logger.py +2 -2
  12. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/parser.py +9 -9
  13. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/__init__.py +2 -1
  14. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/base.py +20 -1
  15. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/google.py +99 -99
  16. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/ssrf.py +29 -2
  17. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/storage.py +15 -2
  18. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/tester.py +16 -13
  19. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/validator.py +16 -12
  20. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/web.py +125 -8
  21. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/webhook.py +6 -5
  22. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/pyproject.toml +2 -2
  23. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_api_endpoints.py +193 -8
  24. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_detector.py +71 -0
  25. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_e2e.py +4 -1
  26. api_key_manager-3.0.1/tests/test_parser_supplement.py +216 -0
  27. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_provider_detection.py +73 -14
  28. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_security.py +3 -3
  29. api_key_manager-3.0.1/tests/test_ssrf_supplement.py +81 -0
  30. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_tester.py +46 -20
  31. api_key_manager-3.0.1/tests/test_validator_supplement.py +196 -0
  32. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_webhook.py +1 -1
  33. api_key_manager-2.2.0/key_manager/config.py +0 -61
  34. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/dependency_links.txt +0 -0
  35. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/entry_points.txt +0 -0
  36. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/requires.txt +0 -0
  37. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/top_level.txt +0 -0
  38. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/__main__.py +0 -0
  39. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/core.py +0 -0
  40. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/errors.py +0 -0
  41. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/i18n.py +0 -0
  42. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/model_capabilities.py +0 -0
  43. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/ai302.py +0 -0
  44. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/anthropic.py +0 -0
  45. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/baichuan.py +0 -0
  46. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/cerebras.py +0 -0
  47. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/cohere.py +0 -0
  48. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/cstcloud.py +0 -0
  49. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/dashscope.py +0 -0
  50. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/dashscope_coding.py +0 -0
  51. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/deepseek.py +0 -0
  52. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/dmxapi.py +0 -0
  53. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/doubao.py +0 -0
  54. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/fireworks.py +0 -0
  55. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/grok.py +0 -0
  56. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/groq.py +0 -0
  57. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/huggingface.py +0 -0
  58. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/hyperbolic.py +0 -0
  59. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/infini.py +0 -0
  60. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/infini_coding.py +0 -0
  61. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/kimi.py +0 -0
  62. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/kimi_coding.py +0 -0
  63. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/longcat.py +0 -0
  64. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/mimo.py +0 -0
  65. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/mimo_plan.py +0 -0
  66. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/minimax.py +0 -0
  67. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/minimax_plan.py +0 -0
  68. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/mistral.py +0 -0
  69. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/models_registry.py +0 -0
  70. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/modelscope.py +0 -0
  71. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/nvidia.py +0 -0
  72. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/ocoolai.py +0 -0
  73. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/openai.py +0 -0
  74. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/opencode.py +0 -0
  75. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/opencode_zen.py +0 -0
  76. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/openrouter.py +0 -0
  77. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/perplexity.py +0 -0
  78. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/poe.py +0 -0
  79. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/ppio.py +0 -0
  80. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/replicate.py +0 -0
  81. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/siliconflow.py +0 -0
  82. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/stepfun.py +0 -0
  83. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/tencent_hunyuan.py +0 -0
  84. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/together.py +0 -0
  85. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/yi.py +0 -0
  86. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/zai.py +0 -0
  87. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/zhipu.py +0 -0
  88. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/zhipu_coding.py +0 -0
  89. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/proxy.py +0 -0
  90. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/py.typed +0 -0
  91. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/url_override.py +0 -0
  92. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/setup.cfg +0 -0
  93. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_base_check.py +0 -0
  94. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_bug_fixes.py +0 -0
  95. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_checker.py +0 -0
  96. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_core.py +0 -0
  97. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_errors.py +0 -0
  98. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_i18n.py +0 -0
  99. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_logger.py +0 -0
  100. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_openapi.py +0 -0
  101. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_parser.py +0 -0
  102. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_providers.py +0 -0
  103. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_proxy.py +0 -0
  104. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_storage.py +0 -0
  105. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_validator.py +0 -0
  106. {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_web_fixes.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: api-key-manager
3
- Version: 2.2.0
4
- Summary: Batch manage API keys for 44+ AI providers with CLI and Web interfaces
3
+ Version: 3.0.1
4
+ Summary: Batch manage API keys for 45+ AI providers with CLI and Web interfaces
5
5
  Author: Townrain
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/Townrain/API-Key-Manager
@@ -85,7 +85,8 @@ Requires-Dist: ruff>=0.4.0; extra == "dev"
85
85
 
86
86
  | 服务商 | 前缀 | 显示名 |
87
87
  |--------|------|--------|
88
- | DashScope | `sk-sp-` | 阿里百炼 |
88
+ | 阿里百炼 | `sk-ws-` / `sk-` |
89
+ | 阿里百炼编程 | `sk-sp-` |
89
90
  | ModelScope | `ms-` | 魔搭 |
90
91
  | Zhipu GLM | `sk-` | 智谱 |
91
92
  | Kimi | `sk-` | 月之暗面 |
@@ -341,7 +342,7 @@ if best_score >= 200:
341
342
  ### 检测优先级
342
343
 
343
344
  | 优先级 | 方法 | 说明 |
344
- ||------||------|
345
+ |--------|------|------|
345
346
  | 1 | 模式匹配 | 唯一前缀,如 `sk-proj-` → OpenAI |
346
347
  | 2 | 格式匹配 | 特殊格式,如 `{id}.{secret}` → 智谱 |
347
348
  | 3 | 全并发探测 | 第一个返回200的服务商胜出 |
@@ -369,9 +370,6 @@ if best_score >= 200:
369
370
  - 基于关键词的模式匹配(authentication、expired、rate limit 等)
370
371
  - 长错误信息截断(超过100字符时截断并添加省略号)
371
372
 
372
- ## 项目结构
373
-
374
-
375
373
  ## 模型检测
376
374
 
377
375
  ### 模型列表来源
@@ -718,6 +716,32 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
718
716
 
719
717
  ## 更新日志
720
718
 
719
+
720
+ ### v3.0.0 (2026-06-15)
721
+
722
+ - **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配,修复 o1 误匹配 o1-mini 等问题
723
+ - **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃、/api/keys 和 /api/keys/export 丢失 key 字段
724
+ - **测试补充**: 新增 8 个端点的测试(test/token, test/concurrency, models/capabilities, models/check, progress/stream 等)
725
+ - **前端缓存优化**: 模型类型切换使用本地缓存过滤,不再重复请求 API
726
+ - **Cherry Studio 同步工作流修复**: 修复 GitHub Actions 路径错误和验证脚本
727
+ - **Autofill 样式修复**: 修复浏览器 autofill 导致输入框底色变白
728
+
729
+ ### v2.2.2 (2026-06-14)
730
+
731
+ - **模型检测二级窗口**: 新增模型检测弹窗,支持搜索、类型筛选、全选、批量检测
732
+ - **模型类型图标**: 显示模型能力图标(视觉/推理/联网/工具/嵌入/重排/免费)
733
+ - **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据(3184 条记录)
734
+ - **检测逻辑优化**: DeepSeek 欠费密钥正确识别,不再误判为其他服务商
735
+ - **指定模型检测**: 自动检测服务商时也支持指定模型检测
736
+
737
+ ### v2.2.1 (2026-06-14)
738
+
739
+ - **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
740
+ - **签名匹配修复**: 状态码不再硬编码 401,使用实际状态码
741
+ - **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
742
+ - **模型选择器**: 新增模型下拉框,支持选择具体模型检测
743
+ - **Cherry Studio 同步**: 修复 ownedBy 映射(alibaba→dashscope 等)
744
+ - **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
721
745
  ### v2.2.0 (2026-06-14)
722
746
 
723
747
  - **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
@@ -726,7 +750,6 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
726
750
  - **超时控制**: 所有网络请求 5 秒超时
727
751
  - **模型同步**: 从 Cherry Studio 同步模型数据,支持 ownedBy 映射
728
752
  - **新增服务商**: OpenCode Go、OpenCode Zen
729
- - **新增服务商**: OpenCode Go、OpenCode Zen
730
753
 
731
754
  ### v2.1.2 (2026-06-11)
732
755
 
@@ -52,7 +52,8 @@
52
52
 
53
53
  | 服务商 | 前缀 | 显示名 |
54
54
  |--------|------|--------|
55
- | DashScope | `sk-sp-` | 阿里百炼 |
55
+ | 阿里百炼 | `sk-ws-` / `sk-` |
56
+ | 阿里百炼编程 | `sk-sp-` |
56
57
  | ModelScope | `ms-` | 魔搭 |
57
58
  | Zhipu GLM | `sk-` | 智谱 |
58
59
  | Kimi | `sk-` | 月之暗面 |
@@ -308,7 +309,7 @@ if best_score >= 200:
308
309
  ### 检测优先级
309
310
 
310
311
  | 优先级 | 方法 | 说明 |
311
- ||------||------|
312
+ |--------|------|------|
312
313
  | 1 | 模式匹配 | 唯一前缀,如 `sk-proj-` → OpenAI |
313
314
  | 2 | 格式匹配 | 特殊格式,如 `{id}.{secret}` → 智谱 |
314
315
  | 3 | 全并发探测 | 第一个返回200的服务商胜出 |
@@ -336,9 +337,6 @@ if best_score >= 200:
336
337
  - 基于关键词的模式匹配(authentication、expired、rate limit 等)
337
338
  - 长错误信息截断(超过100字符时截断并添加省略号)
338
339
 
339
- ## 项目结构
340
-
341
-
342
340
  ## 模型检测
343
341
 
344
342
  ### 模型列表来源
@@ -685,6 +683,32 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
685
683
 
686
684
  ## 更新日志
687
685
 
686
+
687
+ ### v3.0.0 (2026-06-15)
688
+
689
+ - **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配,修复 o1 误匹配 o1-mini 等问题
690
+ - **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃、/api/keys 和 /api/keys/export 丢失 key 字段
691
+ - **测试补充**: 新增 8 个端点的测试(test/token, test/concurrency, models/capabilities, models/check, progress/stream 等)
692
+ - **前端缓存优化**: 模型类型切换使用本地缓存过滤,不再重复请求 API
693
+ - **Cherry Studio 同步工作流修复**: 修复 GitHub Actions 路径错误和验证脚本
694
+ - **Autofill 样式修复**: 修复浏览器 autofill 导致输入框底色变白
695
+
696
+ ### v2.2.2 (2026-06-14)
697
+
698
+ - **模型检测二级窗口**: 新增模型检测弹窗,支持搜索、类型筛选、全选、批量检测
699
+ - **模型类型图标**: 显示模型能力图标(视觉/推理/联网/工具/嵌入/重排/免费)
700
+ - **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据(3184 条记录)
701
+ - **检测逻辑优化**: DeepSeek 欠费密钥正确识别,不再误判为其他服务商
702
+ - **指定模型检测**: 自动检测服务商时也支持指定模型检测
703
+
704
+ ### v2.2.1 (2026-06-14)
705
+
706
+ - **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
707
+ - **签名匹配修复**: 状态码不再硬编码 401,使用实际状态码
708
+ - **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
709
+ - **模型选择器**: 新增模型下拉框,支持选择具体模型检测
710
+ - **Cherry Studio 同步**: 修复 ownedBy 映射(alibaba→dashscope 等)
711
+ - **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
688
712
  ### v2.2.0 (2026-06-14)
689
713
 
690
714
  - **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
@@ -693,7 +717,6 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
693
717
  - **超时控制**: 所有网络请求 5 秒超时
694
718
  - **模型同步**: 从 Cherry Studio 同步模型数据,支持 ownedBy 映射
695
719
  - **新增服务商**: OpenCode Go、OpenCode Zen
696
- - **新增服务商**: OpenCode Go、OpenCode Zen
697
720
 
698
721
  ### v2.1.2 (2026-06-11)
699
722
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: api-key-manager
3
- Version: 2.2.0
4
- Summary: Batch manage API keys for 44+ AI providers with CLI and Web interfaces
3
+ Version: 3.0.1
4
+ Summary: Batch manage API keys for 45+ AI providers with CLI and Web interfaces
5
5
  Author: Townrain
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/Townrain/API-Key-Manager
@@ -85,7 +85,8 @@ Requires-Dist: ruff>=0.4.0; extra == "dev"
85
85
 
86
86
  | 服务商 | 前缀 | 显示名 |
87
87
  |--------|------|--------|
88
- | DashScope | `sk-sp-` | 阿里百炼 |
88
+ | 阿里百炼 | `sk-ws-` / `sk-` |
89
+ | 阿里百炼编程 | `sk-sp-` |
89
90
  | ModelScope | `ms-` | 魔搭 |
90
91
  | Zhipu GLM | `sk-` | 智谱 |
91
92
  | Kimi | `sk-` | 月之暗面 |
@@ -341,7 +342,7 @@ if best_score >= 200:
341
342
  ### 检测优先级
342
343
 
343
344
  | 优先级 | 方法 | 说明 |
344
- ||------||------|
345
+ |--------|------|------|
345
346
  | 1 | 模式匹配 | 唯一前缀,如 `sk-proj-` → OpenAI |
346
347
  | 2 | 格式匹配 | 特殊格式,如 `{id}.{secret}` → 智谱 |
347
348
  | 3 | 全并发探测 | 第一个返回200的服务商胜出 |
@@ -369,9 +370,6 @@ if best_score >= 200:
369
370
  - 基于关键词的模式匹配(authentication、expired、rate limit 等)
370
371
  - 长错误信息截断(超过100字符时截断并添加省略号)
371
372
 
372
- ## 项目结构
373
-
374
-
375
373
  ## 模型检测
376
374
 
377
375
  ### 模型列表来源
@@ -718,6 +716,32 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
718
716
 
719
717
  ## 更新日志
720
718
 
719
+
720
+ ### v3.0.0 (2026-06-15)
721
+
722
+ - **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配,修复 o1 误匹配 o1-mini 等问题
723
+ - **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃、/api/keys 和 /api/keys/export 丢失 key 字段
724
+ - **测试补充**: 新增 8 个端点的测试(test/token, test/concurrency, models/capabilities, models/check, progress/stream 等)
725
+ - **前端缓存优化**: 模型类型切换使用本地缓存过滤,不再重复请求 API
726
+ - **Cherry Studio 同步工作流修复**: 修复 GitHub Actions 路径错误和验证脚本
727
+ - **Autofill 样式修复**: 修复浏览器 autofill 导致输入框底色变白
728
+
729
+ ### v2.2.2 (2026-06-14)
730
+
731
+ - **模型检测二级窗口**: 新增模型检测弹窗,支持搜索、类型筛选、全选、批量检测
732
+ - **模型类型图标**: 显示模型能力图标(视觉/推理/联网/工具/嵌入/重排/免费)
733
+ - **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据(3184 条记录)
734
+ - **检测逻辑优化**: DeepSeek 欠费密钥正确识别,不再误判为其他服务商
735
+ - **指定模型检测**: 自动检测服务商时也支持指定模型检测
736
+
737
+ ### v2.2.1 (2026-06-14)
738
+
739
+ - **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
740
+ - **签名匹配修复**: 状态码不再硬编码 401,使用实际状态码
741
+ - **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
742
+ - **模型选择器**: 新增模型下拉框,支持选择具体模型检测
743
+ - **Cherry Studio 同步**: 修复 ownedBy 映射(alibaba→dashscope 等)
744
+ - **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
721
745
  ### v2.2.0 (2026-06-14)
722
746
 
723
747
  - **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
@@ -726,7 +750,6 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
726
750
  - **超时控制**: 所有网络请求 5 秒超时
727
751
  - **模型同步**: 从 Cherry Studio 同步模型数据,支持 ownedBy 映射
728
752
  - **新增服务商**: OpenCode Go、OpenCode Zen
729
- - **新增服务商**: OpenCode Go、OpenCode Zen
730
753
 
731
754
  ### v2.1.2 (2026-06-11)
732
755
 
@@ -89,12 +89,15 @@ tests/test_i18n.py
89
89
  tests/test_logger.py
90
90
  tests/test_openapi.py
91
91
  tests/test_parser.py
92
+ tests/test_parser_supplement.py
92
93
  tests/test_provider_detection.py
93
94
  tests/test_providers.py
94
95
  tests/test_proxy.py
95
96
  tests/test_security.py
97
+ tests/test_ssrf_supplement.py
96
98
  tests/test_storage.py
97
99
  tests/test_tester.py
98
100
  tests/test_validator.py
101
+ tests/test_validator_supplement.py
99
102
  tests/test_web_fixes.py
100
103
  tests/test_webhook.py
@@ -1,4 +1,4 @@
1
- """Key Manager - Batch API key management for 44+ AI providers."""
1
+ """Key Manager - Batch API key management for 45+ AI providers."""
2
2
  from key_manager.core import KeyManager
3
3
  from key_manager.providers import PROVIDERS, get_display_name
4
4
  from key_manager.errors import KeyManagerError, ErrorCode
@@ -13,5 +13,5 @@ __all__ = [
13
13
  "KeyStore",
14
14
  ]
15
15
 
16
- __version__ = "2.2.0"
16
+ __version__ = "3.0.0"
17
17
 
@@ -71,6 +71,7 @@ class KeyListResponse(BaseModel):
71
71
 
72
72
 
73
73
  class KeyExportItem(BaseModel):
74
+ key_masked: str = Field(..., description="Masked key for display")
74
75
  provider: str = Field(..., description="Provider identifier")
75
76
  max_tokens: int | None = Field(None, description="Maximum token limit")
76
77
  max_concurrency: int | None = Field(None, description="Maximum concurrency limit")
@@ -111,11 +112,12 @@ class ImportResponse(BaseModel):
111
112
  class CheckSingleRequest(BaseModel):
112
113
  key: str = Field(..., min_length=1, description="API key to check")
113
114
  provider: str = Field("", description="Provider name (auto-detected if empty)")
115
+ model: str | None = Field(None, description="Specific model to test (tests all if empty)")
114
116
  custom_base_url: str | None = Field(None, description="Override provider base URL")
115
117
 
116
118
  model_config = {
117
119
  "json_schema_extra": {
118
- "examples": [{"key": "sk-abc123", "provider": "openai"}]
120
+ "examples": [{"key": "sk-abc123", "provider": "openai", "model": "gpt-4o"}]
119
121
  }
120
122
  }
121
123
 
@@ -296,23 +298,26 @@ class StatsResponse(BaseModel):
296
298
  total: int = Field(0, description="Total key count")
297
299
 
298
300
 
299
- class StatsChartProviderEntry(BaseModel):
300
- total: int = Field(0, description="Total keys for this provider")
301
- valid: int = Field(0, description="Valid keys")
302
- invalid: int = Field(0, description="Invalid keys")
303
- error: int = Field(0, description="Errored keys")
304
-
305
-
306
301
  class StatsChartStatuses(BaseModel):
307
302
  valid: int = Field(0, description="Total valid keys")
308
303
  invalid: int = Field(0, description="Total invalid keys")
309
304
  error: int = Field(0, description="Total errored keys")
310
305
  unknown: int = Field(0, description="Total unknown-status keys")
311
306
 
307
+ class StatsChartProviderEntry(BaseModel):
308
+ provider: str = Field(..., description="Provider identifier")
309
+ display_name: str = Field("", description="Human-readable provider name")
310
+ total: int = Field(0, description="Total keys for this provider")
311
+ valid: int = Field(0, description="Valid keys")
312
+ invalid: int = Field(0, description="Invalid keys")
313
+ error: int = Field(0, description="Errored keys")
314
+ statuses: StatsChartStatuses = Field(
315
+ default_factory=StatsChartStatuses, description="Status breakdown"
316
+ )
312
317
 
313
318
  class StatsChartResponse(BaseModel):
314
- providers: dict[str, StatsChartProviderEntry] = Field(
315
- default_factory=dict, description="Per-provider breakdown for charts"
319
+ providers: list[StatsChartProviderEntry] = Field(
320
+ default_factory=list, description="Per-provider breakdown for charts"
316
321
  )
317
322
  statuses: StatsChartStatuses = Field(
318
323
  default_factory=StatsChartStatuses, description="Global status counts"
@@ -1,14 +1,11 @@
1
1
  import asyncio
2
- import json
3
2
  from datetime import datetime
4
- from pathlib import Path
5
3
 
6
4
  import httpx
7
5
 
8
6
  from .providers import PROVIDERS
9
7
  from .validator import validate_keys
10
8
 
11
-
12
9
  async def run_check(keys_file: str = "./data/keys.json",
13
10
  results_file: str = "./data/check_results.json",
14
11
  logs_dir: str = "./data/logs",
@@ -16,14 +13,16 @@ async def run_check(keys_file: str = "./data/keys.json",
16
13
  timeout: int = 30,
17
14
  proxy: str = None,
18
15
  retry_failed: bool = True,
19
- retry_count: int = 2) -> dict:
16
+ retry_count: int = 2,
17
+ config: dict = None) -> dict:
20
18
  results = await validate_keys(
21
19
  keys_file=keys_file,
22
20
  results_file=results_file,
23
21
  logs_dir=logs_dir,
24
22
  concurrency=concurrency,
25
23
  timeout=timeout,
26
- proxy=proxy
24
+ proxy=proxy,
25
+ config=config
27
26
  )
28
27
 
29
28
  # Retry failed keys
@@ -40,12 +39,18 @@ async def run_check(keys_file: str = "./data/keys.json",
40
39
  concurrency=concurrency,
41
40
  timeout=timeout,
42
41
  proxy=proxy,
43
- status_filter="error"
42
+ status_filter="error",
43
+ config=config
44
44
  )
45
45
 
46
- # Merge results
46
+ # Recompute summary from details to avoid double-counting
47
+ # The retry only re-checks error keys, so we need to update the summary
48
+ # based on what changed
49
+ results["summary"]["error"]["count"] = retry_results["summary"]["error"]["count"]
50
+ results["summary"]["error"]["keys"] = retry_results["summary"]["error"]["keys"]
47
51
  results["summary"]["valid"]["count"] += retry_results["summary"]["valid"]["count"]
52
+ results["summary"]["valid"]["keys"].extend(retry_results["summary"]["valid"]["keys"])
48
53
  results["summary"]["invalid"]["count"] += retry_results["summary"]["invalid"]["count"]
49
- results["summary"]["error"]["count"] = retry_results["summary"]["error"]["count"]
54
+ results["summary"]["invalid"]["keys"].extend(retry_results["summary"]["invalid"]["keys"])
50
55
 
51
56
  return results
@@ -2,7 +2,7 @@ import argparse
2
2
  import asyncio
3
3
  import json
4
4
  import sys
5
- from datetime import datetime, timedelta
5
+ from datetime import datetime, timedelta, timezone
6
6
  from pathlib import Path
7
7
 
8
8
  from rich.console import Console
@@ -153,7 +153,7 @@ def cmd_report(args, config):
153
153
  return
154
154
 
155
155
  days = args.days or 7
156
- cutoff = datetime.utcnow() - timedelta(days=days)
156
+ cutoff = datetime.now(timezone.utc) - timedelta(days=days)
157
157
 
158
158
  stats = {}
159
159
  for key, info in data["keys"].items():
@@ -0,0 +1,103 @@
1
+ import copy
2
+ import logging
3
+ import yaml
4
+ from pathlib import Path
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ DEFAULT_CONFIG = {
9
+ "scan": {
10
+ "directories": ["./data/input"],
11
+ "recursive": True,
12
+ },
13
+ "check": {
14
+ "interval_hours": 6,
15
+ "timeout_seconds": 30,
16
+ "concurrency": 100,
17
+ "retry_failed": True,
18
+ "retry_count": 2,
19
+ },
20
+ "test": {
21
+ "token_test": True,
22
+ "token_auto_detect": True,
23
+ "token_steps": [1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576],
24
+ "token_max_manual": None,
25
+ "concurrency_test": True,
26
+ "concurrency_steps": [1, 5, 10, 20, 50, 100],
27
+ "concurrency_timeout_seconds": 120,
28
+ },
29
+ "storage": {
30
+ "keys_file": "./data/keys.json",
31
+ "check_results_file": "./data/check_results.json",
32
+ "test_results_file": "./data/test_results.json",
33
+ "logs_dir": "./data/logs",
34
+ },
35
+ "auth": {
36
+ "api_key": "",
37
+ },
38
+ "rate_limit": {
39
+ "requests_per_minute": 60,
40
+ },
41
+ }
42
+
43
+
44
+ def _deep_merge(base: dict, override: dict) -> dict:
45
+ result = base.copy()
46
+ for key, value in override.items():
47
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
48
+ result[key] = _deep_merge(result[key], value)
49
+ else:
50
+ result[key] = value
51
+ return result
52
+
53
+
54
+ def _validate_config(config: dict) -> dict:
55
+ """Validate config values and warn about unknown keys."""
56
+ valid_keys = set(DEFAULT_CONFIG.keys())
57
+ unknown_keys = set(config.keys()) - valid_keys
58
+ if unknown_keys:
59
+ logger.warning(f"Unknown config keys (ignored): {unknown_keys}")
60
+
61
+ # Validate numeric values
62
+ check_config = config.get("check", {})
63
+ if check_config.get("concurrency", 100) < 1:
64
+ logger.warning("check.concurrency must be >= 1, using default")
65
+ check_config["concurrency"] = 100
66
+ if check_config.get("timeout_seconds", 30) < 1:
67
+ logger.warning("check.timeout_seconds must be >= 1, using default")
68
+ check_config["timeout_seconds"] = 30
69
+
70
+ rate_limit = config.get("rate_limit", {})
71
+ if rate_limit.get("requests_per_minute", 60) < 0:
72
+ logger.warning("rate_limit.requests_per_minute must be >= 0, using default")
73
+ rate_limit["requests_per_minute"] = 60
74
+
75
+ return config
76
+
77
+
78
+ def load_config(path: str = "config.yaml") -> dict:
79
+ config_path = Path(path)
80
+ if config_path.exists():
81
+ try:
82
+ with open(config_path, "r", encoding="utf-8") as f:
83
+ user_config = yaml.safe_load(f) or {}
84
+ if not isinstance(user_config, dict):
85
+ logger.warning(f"Config file {path} is not a dict, using defaults")
86
+ return copy.deepcopy(DEFAULT_CONFIG)
87
+ config = _deep_merge(DEFAULT_CONFIG, user_config)
88
+ return _validate_config(config)
89
+ except yaml.YAMLError as e:
90
+ logger.error(f"Failed to parse config file {path}: {e}")
91
+ return copy.deepcopy(DEFAULT_CONFIG)
92
+ except Exception as e:
93
+ logger.error(f"Failed to load config file {path}: {e}")
94
+ return copy.deepcopy(DEFAULT_CONFIG)
95
+ return copy.deepcopy(DEFAULT_CONFIG)
96
+
97
+
98
+ def save_config(config: dict, path: str = "config.yaml"):
99
+ try:
100
+ with open(path, "w", encoding="utf-8") as f:
101
+ yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
102
+ except Exception as e:
103
+ logger.error(f"Failed to save config file {path}: {e}")