kimi-code-usage 0.1.4__tar.gz → 0.1.5__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.
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/PKG-INFO +1 -1
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/pyproject.toml +1 -1
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/src/kimi_code_usage/main.py +175 -5
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/src/kimi_code_usage/providers/kimi.py +19 -4
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/test_main.py +66 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/test_provider_kimi.py +40 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/uv.lock +1 -1
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/.claude/settings.local.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/.env.example +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/.github/workflows/ci.yml +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/.gitignore +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/AGENTS.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/README.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/README.zh.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/kimi-code-usage.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/plans/2026-05-09-pace-indicator.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/plans/2026-05-14-pace-theme-thresholds.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/plans/2026-06-10-multi-llm-quota.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/plans/2026-06-10-vscode-extension-ts-strictness.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/specs/2026-05-09-pace-indicator-design.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/specs/2026-05-14-pace-theme-thresholds-design.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/specs/2026-06-10-multi-llm-quota-design.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/specs/2026-06-10-vscode-extension-multi-provider-design.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/specs/2026-06-10-vscode-extension-ts-refactor-design.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/.gitignore +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/class_index.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/coverage_html_cb_dd2e7eb5.js +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/favicon_32_cb_c827f16f.png +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/function_index.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/index.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/keybd_closed_cb_900cfef5.png +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/status.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/style_cb_4667309f.css +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_581a990e2fe7e610___init___py.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_581a990e2fe7e610_config_py.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_581a990e2fe7e610_main_py.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_581a990e2fe7e610_mcp_py.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_aaf3485d2243f1af___init___py.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_aaf3485d2243f1af_anthropic_py.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_aaf3485d2243f1af_kimi_py.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_aaf3485d2243f1af_openai_py.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_aaf3485d2243f1af_openrouter_py.html +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/kimi_usage.code-workspace +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/requirements.txt +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/src/kimi_code_usage/__init__.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/src/kimi_code_usage/config.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/src/kimi_code_usage/mcp.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/src/kimi_code_usage/providers/__init__.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/src/kimi_code_usage/providers/anthropic.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/src/kimi_code_usage/providers/openai.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/src/kimi_code_usage/providers/openrouter.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/__init__.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/conftest.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/test_config.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/test_mcp.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/test_provider_anthropic.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/test_provider_openai.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/test_provider_openrouter.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/tests/test_providers_init.py +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/.claude/settings.local.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/.github/workflows/ci.yml +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/.prettierignore +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/.prettierrc.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/.vscodeignore +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/LICENSE +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/README.md +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/assets/icon.png +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/assets/moon.svg +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/eslint.config.mjs +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.de.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.en.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.es.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.fr.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.it.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.ja.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.ko.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.pt.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.ru.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.zh-cn.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/package.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/scripts/copy-assets.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/__mocks__/vscode.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/api.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/api.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/apiCache.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/apiCache.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/config.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/config.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/extension.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/extension.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/historyPanel.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/historyPanel.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/i18n.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/i18n.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/notifier.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/notifier.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/pace.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/pace.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/predict.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/predict.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/statusBar.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/statusBar.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/storage-unlink.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/storage.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/storage.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/types.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/src/vscode-mock.test.ts +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/tsconfig.eslint.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/tsconfig.json +0 -0
- {kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/vitest.config.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kimi-code-usage
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: A curated Kimi Coding Plan usage monitor with aesthetic CLI and MCP server.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Golden0Voyager/kimi-code-usage
|
|
6
6
|
Project-URL: Repository, https://github.com/Golden0Voyager/kimi-code-usage
|
|
@@ -11,7 +11,7 @@ from rich.live import Live
|
|
|
11
11
|
from rich.panel import Panel
|
|
12
12
|
from rich.text import Text
|
|
13
13
|
|
|
14
|
-
from kimi_code_usage.config import AppConfig, ConfigResolver, save_theme
|
|
14
|
+
from kimi_code_usage.config import DEFAULT_CONFIG_PATH, AppConfig, ConfigResolver, save_theme
|
|
15
15
|
from kimi_code_usage.providers import DailyUsage, ProviderUsage, dispatch_all
|
|
16
16
|
|
|
17
17
|
# --- i18n ---
|
|
@@ -42,6 +42,41 @@ L_ZH = {
|
|
|
42
42
|
|
|
43
43
|
L = L_ZH if IS_ZH else L_EN
|
|
44
44
|
|
|
45
|
+
_PROVIDER_SETUP_HELP = {
|
|
46
|
+
"kimi": {
|
|
47
|
+
"name": "Kimi Code",
|
|
48
|
+
"key_envs": ("KIMI_API_KEY", "KIMI_CODING_API_KEY"),
|
|
49
|
+
"base_env": "KIMI_BASE_URL",
|
|
50
|
+
"default_url": "https://api.kimi.com/coding/v1",
|
|
51
|
+
"note_en": "Use a Kimi Code / Coding Plan key (sk-kimi-...). Do not use https://api.moonshot.cn/v1.",
|
|
52
|
+
"note_zh": "使用 Kimi Code / Coding Plan 密钥(sk-kimi-...),不要使用 https://api.moonshot.cn/v1。",
|
|
53
|
+
},
|
|
54
|
+
"openai": {
|
|
55
|
+
"name": "OpenAI",
|
|
56
|
+
"key_envs": ("OPENAI_API_KEY",),
|
|
57
|
+
"base_env": "OPENAI_BASE_URL",
|
|
58
|
+
"default_url": "https://api.openai.com",
|
|
59
|
+
"note_en": "Use an OpenAI API key with organization usage access.",
|
|
60
|
+
"note_zh": "使用具备用量接口权限的 OpenAI API Key。",
|
|
61
|
+
},
|
|
62
|
+
"anthropic": {
|
|
63
|
+
"name": "Anthropic",
|
|
64
|
+
"key_envs": ("ANTHROPIC_API_KEY",),
|
|
65
|
+
"base_env": "ANTHROPIC_BASE_URL",
|
|
66
|
+
"default_url": "https://api.anthropic.com",
|
|
67
|
+
"note_en": "Use an Anthropic key that can access the usage endpoint.",
|
|
68
|
+
"note_zh": "使用具备用量接口权限的 Anthropic Key。",
|
|
69
|
+
},
|
|
70
|
+
"openrouter": {
|
|
71
|
+
"name": "OpenRouter",
|
|
72
|
+
"key_envs": ("OPENROUTER_API_KEY", "OPENROUTER_ADMIN_KEY"),
|
|
73
|
+
"base_env": "OPENROUTER_BASE_URL",
|
|
74
|
+
"default_url": "https://openrouter.ai/api",
|
|
75
|
+
"note_en": "Set OPENROUTER_MANAGEMENT_KEY or OPENROUTER_ADMIN_KEY for activity details.",
|
|
76
|
+
"note_zh": "如需活动明细,设置 OPENROUTER_MANAGEMENT_KEY 或 OPENROUTER_ADMIN_KEY。",
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
|
|
45
80
|
def _get_visual_width(s: str) -> int:
|
|
46
81
|
import unicodedata
|
|
47
82
|
width = 0
|
|
@@ -52,6 +87,14 @@ def _get_visual_width(s: str) -> int:
|
|
|
52
87
|
width += 1
|
|
53
88
|
return width
|
|
54
89
|
|
|
90
|
+
|
|
91
|
+
def _mask_secret(secret: str | None) -> str:
|
|
92
|
+
if not secret:
|
|
93
|
+
return "missing"
|
|
94
|
+
if len(secret) <= 8:
|
|
95
|
+
return secret[:2] + "..."
|
|
96
|
+
return f"{secret[:7]}...{secret[-4:]}"
|
|
97
|
+
|
|
55
98
|
def _get_localized_label(label: str, lang_zh: bool = IS_ZH) -> str:
|
|
56
99
|
_L = L_ZH if lang_zh else L_EN
|
|
57
100
|
|
|
@@ -155,6 +198,117 @@ def _get_localized_text_value(text_val: str, lang_zh: bool) -> str:
|
|
|
155
198
|
|
|
156
199
|
return text_val
|
|
157
200
|
|
|
201
|
+
|
|
202
|
+
def _render_interactive_help(lang_zh: bool, or_metric: str = "requests", days_window: int = 30) -> Text:
|
|
203
|
+
text = Text()
|
|
204
|
+
title = "交互帮助" if lang_zh else "Interactive Help"
|
|
205
|
+
text.append(f"{title}\n\n", style="bold")
|
|
206
|
+
if lang_zh:
|
|
207
|
+
rows = [
|
|
208
|
+
("q / Ctrl-C", "退出"),
|
|
209
|
+
("r", "刷新用量"),
|
|
210
|
+
("h / ?", "显示或隐藏帮助"),
|
|
211
|
+
("c", "显示或隐藏配置引导"),
|
|
212
|
+
("1-4", "切换服务商面板"),
|
|
213
|
+
("l", "切换中英文"),
|
|
214
|
+
("←/→ 或 [ / ]", "切换主题"),
|
|
215
|
+
("m", f"切换 OpenRouter 指标(当前:{_metric_label(or_metric, True)})"),
|
|
216
|
+
("d", f"切换 OpenRouter 图表窗口(当前:{days_window}d)"),
|
|
217
|
+
("Enter", "保存主题、语言、可见面板和 OpenRouter 显示偏好"),
|
|
218
|
+
]
|
|
219
|
+
footer = "再次按 h/? 或 c 可回到用量视图。"
|
|
220
|
+
else:
|
|
221
|
+
rows = [
|
|
222
|
+
("q / Ctrl-C", "quit"),
|
|
223
|
+
("r", "refresh usage"),
|
|
224
|
+
("h / ?", "show or hide help"),
|
|
225
|
+
("c", "show or hide configuration guide"),
|
|
226
|
+
("1-4", "toggle provider panels"),
|
|
227
|
+
("l", "toggle language"),
|
|
228
|
+
("←/→ or [ / ]", "cycle theme"),
|
|
229
|
+
("m", f"cycle OpenRouter metric (current: {_metric_label(or_metric, False)})"),
|
|
230
|
+
("d", f"cycle OpenRouter chart window (current: {days_window}d)"),
|
|
231
|
+
("Enter", "save theme, language, visible panels, and OpenRouter display preferences"),
|
|
232
|
+
]
|
|
233
|
+
footer = "Press h/? or c again to return to the usage view."
|
|
234
|
+
|
|
235
|
+
for key, desc in rows:
|
|
236
|
+
text.append(f" {key:<16}", style="bold")
|
|
237
|
+
text.append(f"{desc}\n", style="grey62")
|
|
238
|
+
text.append(f"\n{footer}", style="grey62")
|
|
239
|
+
return text
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _render_config_guide(
|
|
243
|
+
config: AppConfig,
|
|
244
|
+
lang_zh: bool = IS_ZH,
|
|
245
|
+
config_path: str | None = None,
|
|
246
|
+
) -> Text:
|
|
247
|
+
text = Text()
|
|
248
|
+
title = "配置引导" if lang_zh else "Configuration Guide"
|
|
249
|
+
path = config_path or str(DEFAULT_CONFIG_PATH)
|
|
250
|
+
text.append(f"{title}\n\n", style="bold")
|
|
251
|
+
if lang_zh:
|
|
252
|
+
text.append(f"配置文件: {path}\n", style="grey62")
|
|
253
|
+
text.append("读取优先级: config.json > 环境变量 / 当前目录 .env > 默认 base URL\n\n", style="grey62")
|
|
254
|
+
else:
|
|
255
|
+
text.append(f"Config file: {path}\n", style="grey62")
|
|
256
|
+
text.append("Resolution order: config.json > environment variables / current .env > default base URL\n\n", style="grey62")
|
|
257
|
+
|
|
258
|
+
provider_order = list(config.provider_order)
|
|
259
|
+
for provider_name in _PROVIDER_SETUP_HELP:
|
|
260
|
+
if provider_name not in provider_order:
|
|
261
|
+
provider_order.append(provider_name)
|
|
262
|
+
|
|
263
|
+
for provider_name in provider_order:
|
|
264
|
+
help_info = _PROVIDER_SETUP_HELP.get(provider_name)
|
|
265
|
+
if not help_info:
|
|
266
|
+
continue
|
|
267
|
+
p_conf = config.providers.get(provider_name)
|
|
268
|
+
api_key = p_conf.api_key if p_conf else None
|
|
269
|
+
base_url = p_conf.base_url if p_conf and p_conf.base_url else help_info["default_url"]
|
|
270
|
+
enabled = p_conf.enabled if p_conf else True
|
|
271
|
+
status = "enabled" if api_key and enabled else ("disabled" if not enabled else "missing")
|
|
272
|
+
if lang_zh:
|
|
273
|
+
status_label = {"enabled": "已启用", "disabled": "已禁用", "missing": "缺少密钥"}[status]
|
|
274
|
+
text.append(f"{help_info['name']}\n", style="bold")
|
|
275
|
+
text.append(f" 状态: {status_label}\n", style="grey62")
|
|
276
|
+
text.append(f" Key: {_mask_secret(api_key)} ({' / '.join(help_info['key_envs'])})\n", style="grey62")
|
|
277
|
+
text.append(f" Base URL: {base_url} ({help_info['base_env']})\n", style="grey62")
|
|
278
|
+
text.append(f" 提示: {help_info['note_zh']}\n", style="grey62")
|
|
279
|
+
else:
|
|
280
|
+
text.append(f"{help_info['name']}\n", style="bold")
|
|
281
|
+
text.append(f" status: {status}\n", style="grey62")
|
|
282
|
+
text.append(f" key: {_mask_secret(api_key)} ({' / '.join(help_info['key_envs'])})\n", style="grey62")
|
|
283
|
+
text.append(f" baseUrl: {base_url} ({help_info['base_env']})\n", style="grey62")
|
|
284
|
+
text.append(f" note: {help_info['note_en']}\n", style="grey62")
|
|
285
|
+
|
|
286
|
+
if provider_name == "openrouter":
|
|
287
|
+
management_key = p_conf.management_key if p_conf else None
|
|
288
|
+
if lang_zh:
|
|
289
|
+
text.append(
|
|
290
|
+
f" 管理 Key: {_mask_secret(management_key)} (OPENROUTER_MANAGEMENT_KEY / OPENROUTER_ADMIN_KEY)\n",
|
|
291
|
+
style="grey62",
|
|
292
|
+
)
|
|
293
|
+
else:
|
|
294
|
+
text.append(
|
|
295
|
+
f" management key: {_mask_secret(management_key)} (OPENROUTER_MANAGEMENT_KEY / OPENROUTER_ADMIN_KEY)\n",
|
|
296
|
+
style="grey62",
|
|
297
|
+
)
|
|
298
|
+
text.append("\n")
|
|
299
|
+
|
|
300
|
+
if lang_zh:
|
|
301
|
+
text.append(
|
|
302
|
+
"建议把长期配置写入 ~/.kimi-usage/config.json;uvx 每次运行都会读取这个文件。\n",
|
|
303
|
+
style="grey62",
|
|
304
|
+
)
|
|
305
|
+
else:
|
|
306
|
+
text.append(
|
|
307
|
+
"For persistent uvx usage, put long-lived settings in ~/.kimi-usage/config.json.\n",
|
|
308
|
+
style="grey62",
|
|
309
|
+
)
|
|
310
|
+
return text
|
|
311
|
+
|
|
158
312
|
# (typing already imported at top)
|
|
159
313
|
|
|
160
314
|
THEME_MAP = {
|
|
@@ -824,7 +978,7 @@ def _format_aggregated_results(
|
|
|
824
978
|
return result
|
|
825
979
|
|
|
826
980
|
|
|
827
|
-
async def _interactive_mode(config: "AppConfig", initial_theme: str) -> None:
|
|
981
|
+
async def _interactive_mode(config: "AppConfig", initial_theme: str, config_path: str | None = None) -> None:
|
|
828
982
|
"""Live Rich TUI with keyboard theme cycling and provider panel toggling.
|
|
829
983
|
|
|
830
984
|
Keys:
|
|
@@ -860,6 +1014,7 @@ async def _interactive_mode(config: "AppConfig", initial_theme: str) -> None:
|
|
|
860
1014
|
|
|
861
1015
|
or_metric: str = _parse_or_metric(config.or_metric)
|
|
862
1016
|
days_window: int = _parse_days_window(config.days_window)
|
|
1017
|
+
current_view = "usage"
|
|
863
1018
|
|
|
864
1019
|
saved_notice: list = [None] # holds the saved theme name briefly, then None
|
|
865
1020
|
|
|
@@ -871,7 +1026,12 @@ async def _interactive_mode(config: "AppConfig", initial_theme: str) -> None:
|
|
|
871
1026
|
def _build_panel() -> Panel:
|
|
872
1027
|
visible_order = [p for p in config.provider_order if p in visible_providers]
|
|
873
1028
|
_L = L_ZH if lang_zh else L_EN
|
|
874
|
-
|
|
1029
|
+
if current_view == "help":
|
|
1030
|
+
body = _render_interactive_help(lang_zh, or_metric=or_metric, days_window=days_window)
|
|
1031
|
+
elif current_view == "config":
|
|
1032
|
+
body = _render_config_guide(config, lang_zh=lang_zh, config_path=config_path)
|
|
1033
|
+
else:
|
|
1034
|
+
body = _format_aggregated_results(results, errors, visible_order, themes[idx], lang_zh, enabled_providers=set(config.enabled_providers), or_metric=or_metric, days_window=days_window)
|
|
875
1035
|
|
|
876
1036
|
top_bar = Text()
|
|
877
1037
|
# Provider toggles
|
|
@@ -894,6 +1054,8 @@ async def _interactive_mode(config: "AppConfig", initial_theme: str) -> None:
|
|
|
894
1054
|
bindings = [
|
|
895
1055
|
("[q]", "退出" if lang_zh else "quit"),
|
|
896
1056
|
("[r]", "刷新" if lang_zh else "refresh"),
|
|
1057
|
+
("[h/?]", "帮助" if lang_zh else "help"),
|
|
1058
|
+
("[c]", "配置" if lang_zh else "config"),
|
|
897
1059
|
("[←/→]", "主题" if lang_zh else "theme"),
|
|
898
1060
|
("[1-4]", "面板" if lang_zh else "panels"),
|
|
899
1061
|
("[l]", "英" if lang_zh else "ZH"),
|
|
@@ -961,6 +1123,14 @@ async def _interactive_mode(config: "AppConfig", initial_theme: str) -> None:
|
|
|
961
1123
|
|
|
962
1124
|
if saved_notice[0]: # clear notice on any new keypress
|
|
963
1125
|
saved_notice[0] = None
|
|
1126
|
+
if ch in ('h', 'H', '?'):
|
|
1127
|
+
current_view = "usage" if current_view == "help" else "help"
|
|
1128
|
+
live.update(_build_panel())
|
|
1129
|
+
continue
|
|
1130
|
+
if ch in ('c', 'C'):
|
|
1131
|
+
current_view = "usage" if current_view == "config" else "config"
|
|
1132
|
+
live.update(_build_panel())
|
|
1133
|
+
continue
|
|
964
1134
|
if ch in ('\r', '\n'): # Enter → save settings to config file
|
|
965
1135
|
save_theme(
|
|
966
1136
|
themes[idx],
|
|
@@ -1011,7 +1181,7 @@ async def main():
|
|
|
1011
1181
|
parser.add_argument("--json", action="store_true")
|
|
1012
1182
|
parser.add_argument("--plain", action="store_true")
|
|
1013
1183
|
parser.add_argument("-i", "--interactive", action="store_true",
|
|
1014
|
-
help="Interactive mode: ←/→ or [/]
|
|
1184
|
+
help="Interactive mode: h help, c config, ←/→ or [/] themes, r refresh, q quit")
|
|
1015
1185
|
parser.add_argument("--provider", help="Comma-separated providers to query (kimi,openai,anthropic,openrouter)")
|
|
1016
1186
|
parser.add_argument("--config", help="Custom configuration file path")
|
|
1017
1187
|
parser.add_argument("--theme", help="Specify color theme: blue-dark, blue-light, sky-dark, salmon-dark, turquoise-dark, pink-light, violet-dark, amber-dark, mint-dark, monochrome, blind-deuteranopia, blind-tritanopia")
|
|
@@ -1034,7 +1204,7 @@ async def main():
|
|
|
1034
1204
|
|
|
1035
1205
|
# Interactive mode: hand off to TUI loop (handles its own dispatch)
|
|
1036
1206
|
if args.interactive:
|
|
1037
|
-
await _interactive_mode(config, theme_name)
|
|
1207
|
+
await _interactive_mode(config, theme_name, config_path=str(resolver.config_path))
|
|
1038
1208
|
return
|
|
1039
1209
|
|
|
1040
1210
|
results, errors = await dispatch_all(config)
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from collections.abc import Mapping, Sequence
|
|
2
3
|
from datetime import datetime, timedelta
|
|
3
4
|
from typing import Any, cast
|
|
4
5
|
|
|
5
|
-
import os
|
|
6
|
-
|
|
7
6
|
import aiohttp
|
|
8
7
|
|
|
9
8
|
from . import ProviderUsage
|
|
@@ -148,6 +147,12 @@ def _kimi_error_hint(status: int, zh: bool | None = None) -> str:
|
|
|
148
147
|
" ▸ 如需获取正确的 Key,请前往 Kimi Code 控制台创建\n"
|
|
149
148
|
" ▸ 环境变量:KIMI_API_KEY 或 KIMI_CODING_API_KEY"
|
|
150
149
|
),
|
|
150
|
+
404: (
|
|
151
|
+
"未找到 Kimi Code API 用量接口(404)。\n"
|
|
152
|
+
" ▸ 请确认 KIMI_BASE_URL/baseUrl 是 https://api.kimi.com/coding/v1\n"
|
|
153
|
+
" ▸ 请勿使用 Kimi 开放平台地址(https://api.moonshot.cn/v1 或 platform.kimi.com)\n"
|
|
154
|
+
" ▸ 请确认 API Key 来自 Kimi Code 平台(Coding Plan),格式为 sk-kimi-xxx"
|
|
155
|
+
),
|
|
151
156
|
403: "Kimi Code API 拒绝访问(403),请检查 API Key 是否有权限访问用量接口。",
|
|
152
157
|
429: "Kimi Code API 请求过于频繁(429),请稍后重试。",
|
|
153
158
|
}
|
|
@@ -160,6 +165,12 @@ def _kimi_error_hint(status: int, zh: bool | None = None) -> str:
|
|
|
160
165
|
" ▸ Get the correct key from Kimi Code Console\n"
|
|
161
166
|
" ▸ Environment variables: KIMI_API_KEY or KIMI_CODING_API_KEY"
|
|
162
167
|
),
|
|
168
|
+
404: (
|
|
169
|
+
"Kimi Code API usage endpoint was not found (404).\n"
|
|
170
|
+
" ▸ Make sure KIMI_BASE_URL/baseUrl is https://api.kimi.com/coding/v1\n"
|
|
171
|
+
" ▸ Do NOT use the Kimi Open Platform base URL (https://api.moonshot.cn/v1 or platform.kimi.com)\n"
|
|
172
|
+
" ▸ Make sure the API Key is from Kimi Code Platform (Coding Plan), format: sk-kimi-xxx"
|
|
173
|
+
),
|
|
163
174
|
403: "Kimi Code API access denied (403). Check if your API Key has permission to access the usage endpoint.",
|
|
164
175
|
429: "Kimi Code API rate limited (429). Please retry later.",
|
|
165
176
|
}
|
|
@@ -170,7 +181,9 @@ async def fetch_kimi_usage(api_key: str, base_url: str, management_key: str | No
|
|
|
170
181
|
headers = _kimi_headers(api_key)
|
|
171
182
|
async with aiohttp.ClientSession() as session:
|
|
172
183
|
async with session.get(url, headers=headers) as resp:
|
|
173
|
-
if resp.status
|
|
184
|
+
if resp.status == 200:
|
|
185
|
+
payload = await resp.json()
|
|
186
|
+
elif resp.status == 404:
|
|
174
187
|
fallback_url = base_url.rstrip("/") + "/usage"
|
|
175
188
|
async with session.get(fallback_url, headers=headers) as f_resp:
|
|
176
189
|
if f_resp.status != 200:
|
|
@@ -179,7 +192,9 @@ async def fetch_kimi_usage(api_key: str, base_url: str, management_key: str | No
|
|
|
179
192
|
raise Exception(f"{hint}\n Original: API Error {f_resp.status}: {text}")
|
|
180
193
|
payload = await f_resp.json()
|
|
181
194
|
else:
|
|
182
|
-
|
|
195
|
+
text = await resp.text()
|
|
196
|
+
hint = _kimi_error_hint(resp.status)
|
|
197
|
+
raise Exception(f"{hint}\n Original: API Error {resp.status}: {text}")
|
|
183
198
|
|
|
184
199
|
summary, limits = _parse_usage_payload(payload)
|
|
185
200
|
rows = ([summary] if summary else []) + limits
|
|
@@ -330,6 +330,16 @@ def _cfg_one_provider(m):
|
|
|
330
330
|
return cfg
|
|
331
331
|
|
|
332
332
|
|
|
333
|
+
def _panel_plain(panel):
|
|
334
|
+
parts = []
|
|
335
|
+
for item in getattr(panel.renderable, "renderables", []):
|
|
336
|
+
if hasattr(item, "plain"):
|
|
337
|
+
parts.append(item.plain)
|
|
338
|
+
else:
|
|
339
|
+
parts.append(str(item))
|
|
340
|
+
return "\n".join(parts)
|
|
341
|
+
|
|
342
|
+
|
|
333
343
|
@pytest.mark.parametrize("keys,theme,live_update_min,dispatch_call_count,cfg_maker", [
|
|
334
344
|
(["q"], "blue-dark", 1, 1, None),
|
|
335
345
|
(["r", "q"], "blue-dark", 2, 2, None),
|
|
@@ -355,6 +365,62 @@ def test_interactive_mode_initializes_from_config(monkeypatch, language):
|
|
|
355
365
|
_run_interactive_mode(monkeypatch, ["q"], cfg=cfg, live_update_min=1, dispatch_call_count=1)
|
|
356
366
|
|
|
357
367
|
|
|
368
|
+
def test_render_config_guide_includes_supported_provider_setup():
|
|
369
|
+
from kimi_code_usage.config import AppConfig, ProviderConfig
|
|
370
|
+
from kimi_code_usage.main import _render_config_guide
|
|
371
|
+
|
|
372
|
+
cfg = AppConfig()
|
|
373
|
+
cfg.providers = {
|
|
374
|
+
"kimi": ProviderConfig(api_key="sk-kimi-1234567890", base_url="https://api.kimi.com/coding/v1"),
|
|
375
|
+
"openai": ProviderConfig(api_key="sk-openai-abcdef1234", base_url="https://api.openai.com"),
|
|
376
|
+
"anthropic": ProviderConfig(api_key=None, base_url="https://api.anthropic.com"),
|
|
377
|
+
"openrouter": ProviderConfig(api_key="sk-or-v1-abcdef1234", base_url="https://openrouter.ai/api", management_key="or-mgmt-1234"),
|
|
378
|
+
}
|
|
379
|
+
cfg.provider_order = ["kimi", "openai", "anthropic", "openrouter"]
|
|
380
|
+
|
|
381
|
+
text = _render_config_guide(cfg, lang_zh=False, config_path="/tmp/kimi-usage/config.json")
|
|
382
|
+
raw = text.plain
|
|
383
|
+
|
|
384
|
+
assert "/tmp/kimi-usage/config.json" in raw
|
|
385
|
+
assert "KIMI_API_KEY" in raw
|
|
386
|
+
assert "OPENAI_API_KEY" in raw
|
|
387
|
+
assert "ANTHROPIC_API_KEY" in raw
|
|
388
|
+
assert "OPENROUTER_API_KEY" in raw
|
|
389
|
+
assert "OPENROUTER_ADMIN_KEY" in raw
|
|
390
|
+
assert "OPENROUTER_MANAGEMENT_KEY" in raw
|
|
391
|
+
assert "https://api.kimi.com/coding/v1" in raw
|
|
392
|
+
assert "https://openrouter.ai/api" in raw
|
|
393
|
+
assert "https://api.moonshot.cn/v1" in raw
|
|
394
|
+
assert "sk-kimi" in raw
|
|
395
|
+
assert "sk-kimi-1234567890" not in raw
|
|
396
|
+
assert "or-mgmt-1234" not in raw
|
|
397
|
+
assert "missing" in raw
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@pytest.mark.asyncio
|
|
401
|
+
async def test_interactive_mode_help_and_config_keys(monkeypatch):
|
|
402
|
+
_mock_terminal(monkeypatch)
|
|
403
|
+
mock_select, mock_read = _make_select_and_read(["h", "c", "q"])
|
|
404
|
+
import kimi_code_usage.main as main_mod
|
|
405
|
+
monkeypatch.setattr(main_mod._select_module, "select", mock_select)
|
|
406
|
+
monkeypatch.setattr(sys.stdin, "read", mock_read)
|
|
407
|
+
|
|
408
|
+
cfg = _make_interactive_config(monkeypatch)
|
|
409
|
+
mock_res = {"kimi": [ProviderUsage(provider="kimi", label="Weekly Usage", used=5, limit=100, remaining=95, percent=5, reset_at=None, unit="%")]}
|
|
410
|
+
|
|
411
|
+
with patch("kimi_code_usage.main.dispatch_all", AsyncMock(return_value=(mock_res, {}))):
|
|
412
|
+
with patch("kimi_code_usage.main.Live") as mock_live_cls:
|
|
413
|
+
mock_live = mock_live_cls.return_value.__enter__.return_value
|
|
414
|
+
await _interactive_mode(cfg, "blue-dark")
|
|
415
|
+
|
|
416
|
+
rendered = "\n".join(_panel_plain(call.args[0]) for call in mock_live.update.call_args_list)
|
|
417
|
+
assert "Interactive Help" in rendered
|
|
418
|
+
assert "Configuration Guide" in rendered
|
|
419
|
+
assert "OPENAI_API_KEY" in rendered
|
|
420
|
+
assert "ANTHROPIC_API_KEY" in rendered
|
|
421
|
+
assert "OPENROUTER_API_KEY" in rendered
|
|
422
|
+
|
|
423
|
+
|
|
358
424
|
@pytest.mark.asyncio
|
|
359
425
|
async def test_interactive_mode_lone_escape(monkeypatch):
|
|
360
426
|
"""A lone ESC with no follow-up → treated as unknown key (no-op), then q."""
|
|
@@ -5,6 +5,7 @@ import pytest
|
|
|
5
5
|
|
|
6
6
|
from kimi_code_usage.providers.kimi import (
|
|
7
7
|
_get_reset_info,
|
|
8
|
+
_kimi_error_hint,
|
|
8
9
|
_limit_label,
|
|
9
10
|
_parse_usage_payload,
|
|
10
11
|
_to_int,
|
|
@@ -25,6 +26,13 @@ from kimi_code_usage.providers.kimi import (
|
|
|
25
26
|
def test_to_int(val, expected):
|
|
26
27
|
assert _to_int(val) == expected
|
|
27
28
|
|
|
29
|
+
def test_kimi_error_hint_404_explains_usage_endpoint_configuration():
|
|
30
|
+
hint = _kimi_error_hint(404, zh=False)
|
|
31
|
+
assert "usage endpoint" in hint
|
|
32
|
+
assert "https://api.kimi.com/coding/v1" in hint
|
|
33
|
+
assert "api.moonshot.cn" in hint
|
|
34
|
+
assert "sk-kimi" in hint
|
|
35
|
+
|
|
28
36
|
# -- _get_reset_info --
|
|
29
37
|
@pytest.mark.parametrize("key", ["resetTime", "reset_at", "reset_time"])
|
|
30
38
|
def test_get_reset_info_iso_string(key):
|
|
@@ -188,6 +196,38 @@ async def test_fetch_kimi_usage_fallback():
|
|
|
188
196
|
assert len(res) == 1
|
|
189
197
|
assert res[0].used == 200.0
|
|
190
198
|
|
|
199
|
+
@pytest.mark.asyncio
|
|
200
|
+
async def test_fetch_kimi_usage_does_not_fallback_on_auth_error():
|
|
201
|
+
calls = []
|
|
202
|
+
|
|
203
|
+
def make_cm(status, text_data):
|
|
204
|
+
cm = AsyncMock()
|
|
205
|
+
cm.__aenter__ = AsyncMock(return_value=cm)
|
|
206
|
+
cm.__aexit__ = AsyncMock(return_value=None)
|
|
207
|
+
cm.status = status
|
|
208
|
+
cm.text = AsyncMock(return_value=text_data)
|
|
209
|
+
return cm
|
|
210
|
+
|
|
211
|
+
def mock_get(url, *a, **kw):
|
|
212
|
+
calls.append(url)
|
|
213
|
+
if len(calls) == 1:
|
|
214
|
+
return make_cm(401, '{"code":"unauthenticated"}')
|
|
215
|
+
return make_cm(404, '{"error":{"message":"The requested resource was not found","type":"resource_not_found_error"}}')
|
|
216
|
+
|
|
217
|
+
mock_session = AsyncMock()
|
|
218
|
+
mock_session.get = mock_get
|
|
219
|
+
mock_session.__aenter__ = AsyncMock(return_value=mock_session)
|
|
220
|
+
mock_session.__aexit__ = AsyncMock(return_value=None)
|
|
221
|
+
|
|
222
|
+
with patch('aiohttp.ClientSession', return_value=mock_session):
|
|
223
|
+
with pytest.raises(Exception) as exc:
|
|
224
|
+
await fetch_kimi_usage("test-key", "https://api.example.com/v1")
|
|
225
|
+
|
|
226
|
+
assert "401" in str(exc.value)
|
|
227
|
+
assert "unauthenticated" in str(exc.value)
|
|
228
|
+
assert "resource_not_found_error" not in str(exc.value)
|
|
229
|
+
assert calls == ["https://api.example.com/v1/usages"]
|
|
230
|
+
|
|
191
231
|
@pytest.mark.asyncio
|
|
192
232
|
async def test_fetch_kimi_usage_fallback_fails():
|
|
193
233
|
def make_cm(status):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/plans/2026-05-09-pace-indicator.md
RENAMED
|
File without changes
|
|
File without changes
|
{kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/docs/superpowers/plans/2026-06-10-multi-llm-quota.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_aaf3485d2243f1af_anthropic_py.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/htmlcov/z_aaf3485d2243f1af_openrouter_py.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/.claude/settings.local.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kimi_code_usage-0.1.4 → kimi_code_usage-0.1.5}/vscode-extension/l10n/bundle.l10n.zh-cn.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|