kimi-code-usage 0.1.0__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.
@@ -0,0 +1,6 @@
1
+ # Kimi API Usage Reporter 配置
2
+ # 复制此文件为 .env 并填入你的实际 API Key
3
+ KIMI_CODING_API_KEY=your_api_key_here
4
+
5
+ # Base URL(Kimi Coding Plan 用户保持默认即可)
6
+ # KIMI_BASE_URL=https://api.kimi.com/coding/v1
@@ -0,0 +1,29 @@
1
+ # Environment / Secrets
2
+ .env
3
+ *.env
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+ *.egg-info/
10
+ dist/
11
+ build/
12
+
13
+ # Node.js
14
+ node_modules/
15
+ package-lock.json
16
+
17
+ # VSCode Extension artifacts
18
+ *.vsix
19
+ vscode-extension/out/
20
+
21
+ # IDE
22
+ .vscode/
23
+ .idea/
24
+ *.swp
25
+ *.swo
26
+
27
+ # OS
28
+ .DS_Store
29
+ Thumbs.db
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: kimi-code-usage
3
+ Version: 0.1.0
4
+ Summary: A curated Kimi Coding Plan usage monitor with aesthetic CLI and MCP server.
5
+ Project-URL: Homepage, https://github.com/Golden0Voyager/kimi-code-usage
6
+ Project-URL: Repository, https://github.com/Golden0Voyager/kimi-code-usage
7
+ Project-URL: Issues, https://github.com/Golden0Voyager/kimi-code-usage/issues
8
+ Author-email: Haining Yu <hainingyu@gmail.com>
9
+ License: MIT
10
+ Keywords: curated,kimi,mcp,monitor,moonshot,usage
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: aiohttp>=3.8.0
13
+ Requires-Dist: fastmcp>=0.1.0
14
+ Requires-Dist: python-dotenv>=1.0.0
15
+ Requires-Dist: rich>=12.0.0
16
+ Description-Content-Type: text/markdown
17
+
18
+ <p align="center">
19
+ <img src="vscode-extension/assets/banner.png" width="100%" alt="Kimi Code Usage Banner">
20
+ </p>
21
+
22
+ # Kimi Code Usage: The Curated Toolchain
23
+
24
+ **Manifesting your AI quota with aesthetic precision across CLI, MCP, and VS Code.**
25
+ **以优雅的姿态,在终端、AI 助手与编辑器中感知你的 AI 额度。**
26
+
27
+ ---
28
+
29
+ ### 🌟 Project Vision | 项目愿景
30
+
31
+ In the era of "Vibecoding," transparency of resources is a prerequisite for flow. **Kimi Code Usage** is a meticulously crafted toolchain — three components, one soul.
32
+
33
+ 在"直觉编程"时代,资源的透明度是进入心流状态的前提。**Kimi Code Usage** 是一套精心打磨的工具链 — 三种形态,一个灵魂。
34
+
35
+ **Common Prerequisite:** A [Kimi Coding Plan](https://api.kimi.com/coding/v1) API Key, set as `KIMI_API_KEY` in your environment or `.env` file.
36
+ **统一前提:** 在环境变量或 `.env` 文件中设置 `KIMI_API_KEY`。
37
+
38
+ ---
39
+
40
+ ### ⚡ CLI Reporter | 终端报告器
41
+
42
+ > A Rich-rendered panel in your terminal. Zero noise, pure signal.
43
+ > 在你的终端中渲染出带有工业美感的配额面板。
44
+
45
+ **Install & Run:**
46
+ ```bash
47
+ pip install kimi-code-usage
48
+ kimi-usage # Aesthetic Rich panel
49
+ kimi-usage --json # Machine-readable JSON
50
+ kimi-usage --plain # Plain text output
51
+ ```
52
+
53
+ Or run instantly without installing:
54
+ ```bash
55
+ uvx kimi-code-usage
56
+ ```
57
+
58
+ ---
59
+
60
+ ### 🔍 MCP Server | AI 智能体接口
61
+
62
+ > Exposes `get_kimi_usage` to any MCP-compatible AI Agent.
63
+ > 让你的 AI 助手能够主动感知你的额度状态。
64
+
65
+ Compatible with **Claude Code, Cursor, Windsurf, Hermes**, and any MCP-enabled agent.
66
+
67
+ **Add to your MCP config** (e.g., `~/.claude/settings.json`):
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "kimi-code-usage": {
72
+ "command": "uvx",
73
+ "args": ["--from", "kimi-code-usage", "kimi-mcp"],
74
+ "env": {
75
+ "KIMI_API_KEY": "YOUR_KEY"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ > **Note:** The MCP server still needs `--from` because `kimi-mcp` is a separate command from the default `kimi-code-usage` entry point.
83
+
84
+ Then simply ask your AI: *"Check my Kimi quota."* / *"帮我查一下 Kimi 用量。"*
85
+
86
+ ---
87
+
88
+ ### 💎 VS Code Extension | 编辑器插件
89
+
90
+ > A sleek status bar indicator with sensory color alerting.
91
+ > 状态栏实时显示剩余百分比,颜色随额度变化而呼吸。
92
+
93
+ **Install:** Search `Kimi Code Usage` in the VS Code Marketplace, or:
94
+ ```bash
95
+ code --install-extension HainingYu.kimi-code-usage
96
+ ```
97
+
98
+ **Configure** (`Settings > kimiUsage`):
99
+
100
+ | Setting | Description | Default |
101
+ | :--- | :--- | :--- |
102
+ | `apiKey` | API key (or reads `KIMI_API_KEY` env) | `""` |
103
+ | `refreshInterval` | Auto-refresh in minutes | `5` |
104
+ | `warnPercent` | Yellow caution threshold | `30%` |
105
+ | `criticalPercent` | Red alert threshold | `10%` |
106
+
107
+ **Usage:** Status bar shows `⬡ W:96% 5H:99%`. Hover for details. `Cmd+Shift+P → Kimi: Refresh`.
108
+
109
+ ---
110
+
111
+ ### 🎨 About the Curator | 关于策展人
112
+
113
+ Crafted with ❤️ by **Haining Yu**, an Art Curator and Vibecoder. This toolchain is part of a curated collection designed to bridge the gap between aesthetic curation and intuitive, AI-powered coding.
114
+
115
+ 由 **Haining Yu** 精心打磨。作为一名艺术策展人与 Vibecoder,我将代码视作展览,力求在审美策展与直觉化 AI 编程之间寻找完美的平衡。
116
+
117
+ ---
118
+
119
+ <p align="center">
120
+ <strong>Enjoy the flow. Stay in the vibe.</strong>
121
+ </p>
@@ -0,0 +1,104 @@
1
+ <p align="center">
2
+ <img src="vscode-extension/assets/banner.png" width="100%" alt="Kimi Code Usage Banner">
3
+ </p>
4
+
5
+ # Kimi Code Usage: The Curated Toolchain
6
+
7
+ **Manifesting your AI quota with aesthetic precision across CLI, MCP, and VS Code.**
8
+ **以优雅的姿态,在终端、AI 助手与编辑器中感知你的 AI 额度。**
9
+
10
+ ---
11
+
12
+ ### 🌟 Project Vision | 项目愿景
13
+
14
+ In the era of "Vibecoding," transparency of resources is a prerequisite for flow. **Kimi Code Usage** is a meticulously crafted toolchain — three components, one soul.
15
+
16
+ 在"直觉编程"时代,资源的透明度是进入心流状态的前提。**Kimi Code Usage** 是一套精心打磨的工具链 — 三种形态,一个灵魂。
17
+
18
+ **Common Prerequisite:** A [Kimi Coding Plan](https://api.kimi.com/coding/v1) API Key, set as `KIMI_API_KEY` in your environment or `.env` file.
19
+ **统一前提:** 在环境变量或 `.env` 文件中设置 `KIMI_API_KEY`。
20
+
21
+ ---
22
+
23
+ ### ⚡ CLI Reporter | 终端报告器
24
+
25
+ > A Rich-rendered panel in your terminal. Zero noise, pure signal.
26
+ > 在你的终端中渲染出带有工业美感的配额面板。
27
+
28
+ **Install & Run:**
29
+ ```bash
30
+ pip install kimi-code-usage
31
+ kimi-usage # Aesthetic Rich panel
32
+ kimi-usage --json # Machine-readable JSON
33
+ kimi-usage --plain # Plain text output
34
+ ```
35
+
36
+ Or run instantly without installing:
37
+ ```bash
38
+ uvx kimi-code-usage
39
+ ```
40
+
41
+ ---
42
+
43
+ ### 🔍 MCP Server | AI 智能体接口
44
+
45
+ > Exposes `get_kimi_usage` to any MCP-compatible AI Agent.
46
+ > 让你的 AI 助手能够主动感知你的额度状态。
47
+
48
+ Compatible with **Claude Code, Cursor, Windsurf, Hermes**, and any MCP-enabled agent.
49
+
50
+ **Add to your MCP config** (e.g., `~/.claude/settings.json`):
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "kimi-code-usage": {
55
+ "command": "uvx",
56
+ "args": ["--from", "kimi-code-usage", "kimi-mcp"],
57
+ "env": {
58
+ "KIMI_API_KEY": "YOUR_KEY"
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ > **Note:** The MCP server still needs `--from` because `kimi-mcp` is a separate command from the default `kimi-code-usage` entry point.
66
+
67
+ Then simply ask your AI: *"Check my Kimi quota."* / *"帮我查一下 Kimi 用量。"*
68
+
69
+ ---
70
+
71
+ ### 💎 VS Code Extension | 编辑器插件
72
+
73
+ > A sleek status bar indicator with sensory color alerting.
74
+ > 状态栏实时显示剩余百分比,颜色随额度变化而呼吸。
75
+
76
+ **Install:** Search `Kimi Code Usage` in the VS Code Marketplace, or:
77
+ ```bash
78
+ code --install-extension HainingYu.kimi-code-usage
79
+ ```
80
+
81
+ **Configure** (`Settings > kimiUsage`):
82
+
83
+ | Setting | Description | Default |
84
+ | :--- | :--- | :--- |
85
+ | `apiKey` | API key (or reads `KIMI_API_KEY` env) | `""` |
86
+ | `refreshInterval` | Auto-refresh in minutes | `5` |
87
+ | `warnPercent` | Yellow caution threshold | `30%` |
88
+ | `criticalPercent` | Red alert threshold | `10%` |
89
+
90
+ **Usage:** Status bar shows `⬡ W:96% 5H:99%`. Hover for details. `Cmd+Shift+P → Kimi: Refresh`.
91
+
92
+ ---
93
+
94
+ ### 🎨 About the Curator | 关于策展人
95
+
96
+ Crafted with ❤️ by **Haining Yu**, an Art Curator and Vibecoder. This toolchain is part of a curated collection designed to bridge the gap between aesthetic curation and intuitive, AI-powered coding.
97
+
98
+ 由 **Haining Yu** 精心打磨。作为一名艺术策展人与 Vibecoder,我将代码视作展览,力求在审美策展与直觉化 AI 编程之间寻找完美的平衡。
99
+
100
+ ---
101
+
102
+ <p align="center">
103
+ <strong>Enjoy the flow. Stay in the vibe.</strong>
104
+ </p>
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "kimi-code-usage"
3
+ version = "0.1.0"
4
+ description = "A curated Kimi Coding Plan usage monitor with aesthetic CLI and MCP server."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [
8
+ { name = "Haining Yu", email = "hainingyu@gmail.com" }
9
+ ]
10
+ license = { text = "MIT" }
11
+ keywords = ["kimi", "moonshot", "mcp", "usage", "monitor", "curated"]
12
+ dependencies = [
13
+ "aiohttp>=3.8.0",
14
+ "rich>=12.0.0",
15
+ "python-dotenv>=1.0.0",
16
+ "fastmcp>=0.1.0",
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/Golden0Voyager/kimi-code-usage"
21
+ Repository = "https://github.com/Golden0Voyager/kimi-code-usage"
22
+ Issues = "https://github.com/Golden0Voyager/kimi-code-usage/issues"
23
+
24
+ [project.scripts]
25
+ kimi-code-usage = "kimi_code_usage.main:run_cli"
26
+ kimi-usage = "kimi_code_usage.main:run_cli"
27
+ kimi-mcp = "kimi_code_usage.mcp:run_mcp"
28
+
29
+ [build-system]
30
+ requires = ["hatchling"]
31
+ build-backend = "hatchling.build"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/kimi_code_usage"]
@@ -0,0 +1,4 @@
1
+ aiohttp
2
+ rich
3
+ python-dotenv
4
+ mcp
File without changes
@@ -0,0 +1,229 @@
1
+ import os
2
+ import asyncio
3
+ import argparse
4
+ import aiohttp
5
+ import json
6
+ import sys
7
+ from datetime import datetime, timedelta
8
+ from dotenv import load_dotenv
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.text import Text
12
+ from typing import Any, Mapping, Sequence, cast, Tuple, List
13
+
14
+ # --- i18n ---
15
+ LANG = os.getenv("LANG", "en")
16
+ IS_ZH = "zh" in LANG.lower()
17
+
18
+ L_EN = {
19
+ "title": "Kimi Code Usage",
20
+ "weekly_limit": "Weekly Usage",
21
+ "limit_fallback": "Limit",
22
+ "remaining": "remaining",
23
+ "countdown": "Countdown",
24
+ "reset": "Reset",
25
+ "no_data": "No usage data found.",
26
+ "error_key": "KIMI_API_KEY not found in environment or .env file.",
27
+ "error_api": "API Error",
28
+ }
29
+
30
+ L_ZH = {
31
+ "title": "Kimi Code 用量监控",
32
+ "weekly_limit": "周用量限额",
33
+ "limit_fallback": "限额",
34
+ "remaining": "剩余",
35
+ "countdown": "重置倒计时",
36
+ "reset": "重置时间",
37
+ "no_data": "未找到用量数据。",
38
+ "error_key": "未在环境或 .env 文件中找到 KIMI_API_KEY。",
39
+ "error_api": "API 错误",
40
+ }
41
+
42
+ L = L_ZH if IS_ZH else L_EN
43
+
44
+ class UsageRow:
45
+ def __init__(self, label: str, used: int, limit: int, reset_at: str = None, countdown: str = None):
46
+ self.label = label
47
+ self.used = used
48
+ self.limit = limit
49
+ self.reset_at = reset_at
50
+ self.countdown = countdown
51
+
52
+ def _to_int(v) -> int | None:
53
+ try: return int(v)
54
+ except (TypeError, ValueError): return None
55
+
56
+ def _get_reset_info(data: Mapping[str, Any]):
57
+ reset_at = data.get("resetTime") or data.get("reset_at") or data.get("reset_time")
58
+ if reset_at:
59
+ try:
60
+ if isinstance(reset_at, (int, float)):
61
+ dt = datetime.fromtimestamp(reset_at)
62
+ else:
63
+ dt = datetime.fromisoformat(reset_at.replace("Z", "+00:00")).astimezone()
64
+
65
+ now = datetime.now(dt.tzinfo) if dt.tzinfo else datetime.now()
66
+ diff = dt - now
67
+ if diff.total_seconds() <= 0: return dt.strftime("%m-%d %H:%M"), "0m"
68
+ days = diff.days
69
+ hours, rem = divmod(diff.seconds, 3600)
70
+ minutes, _ = divmod(rem, 60)
71
+ parts = []
72
+ if days > 0: parts.append(f"{days}d")
73
+ if hours > 0: parts.append(f"{hours}h")
74
+ parts.append(f"{minutes}m")
75
+ return dt.strftime("%m-%d %H:%M"), " ".join(parts)
76
+ except Exception: pass
77
+
78
+ reset_in = _to_int(data.get("reset_in"))
79
+ if reset_in is not None:
80
+ dt = datetime.now() + timedelta(seconds=reset_in)
81
+ hours, rem = divmod(reset_in, 3600)
82
+ minutes, _ = divmod(rem, 60)
83
+ return dt.strftime("%m-%d %H:%M"), f"{hours}h {minutes}m"
84
+ return None
85
+
86
+ def _limit_label(item, detail, window, idx) -> str:
87
+ duration = _to_int(window.get("duration"))
88
+ time_unit = str(window.get("time_unit") or "").upper()
89
+ if duration and time_unit:
90
+ if "HOUR" in time_unit: return f"{duration}h {L['limit_fallback']}"
91
+ if "DAY" in time_unit: return f"{duration}d {L['limit_fallback']}"
92
+ return f"{L['limit_fallback']} #{idx + 1}"
93
+
94
+ def _to_usage_row(data, *, default_label) -> UsageRow | None:
95
+ limit = _to_int(data.get("limit") or data.get("limit_amount"))
96
+ used = _to_int(data.get("used") or data.get("used_amount"))
97
+ if used is None:
98
+ remaining = _to_int(data.get("remaining"))
99
+ if remaining is not None and limit is not None:
100
+ used = limit - remaining
101
+ if used is None and limit is None: return None
102
+ reset_at, countdown = _get_reset_info(data) or (None, None)
103
+ return UsageRow(
104
+ label=str(data.get("name") or data.get("title") or data.get("model_name") or default_label),
105
+ used=used or 0,
106
+ limit=limit or 0,
107
+ reset_at=reset_at,
108
+ countdown=countdown,
109
+ )
110
+
111
+ def _parse_usage_payload(payload):
112
+ summary = None
113
+ limits = []
114
+
115
+ # Check if it's the direct list from /usage or the nested dict from /usages
116
+ data_list = payload.get("data")
117
+ if isinstance(data_list, Sequence):
118
+ # Format: [{"model_name": "all", ...}, {"model_name": "...", ...}]
119
+ for item in data_list:
120
+ label = L["weekly_limit"] if item.get("model_name") == "all" else L["limit_fallback"]
121
+ row = _to_usage_row(item, default_label=label)
122
+ if row:
123
+ if item.get("model_name") == "all": summary = row
124
+ else: limits.append(row)
125
+ else:
126
+ # Original complex structure
127
+ usage = payload.get("usage")
128
+ if isinstance(usage, Mapping):
129
+ summary = _to_usage_row(cast(Mapping, usage), default_label=L["weekly_limit"])
130
+ raw_limits = payload.get("limits")
131
+ if isinstance(raw_limits, Sequence):
132
+ for idx, item in enumerate(raw_limits):
133
+ if not isinstance(item, Mapping): continue
134
+ detail = item.get("detail") if isinstance(item.get("detail"), Mapping) else item
135
+ window = item.get("window") if isinstance(item.get("window"), Mapping) else {}
136
+ row = _to_usage_row(detail, default_label=_limit_label(item, detail, window, idx))
137
+ if row: limits.append(row)
138
+
139
+ return summary, limits
140
+
141
+ def _get_visual_width(s: str) -> int:
142
+ import unicodedata
143
+ width = 0
144
+ for char in s:
145
+ if unicodedata.east_asian_width(char) in ("W", "F", "A"): width += 2
146
+ else: width += 1
147
+ return width
148
+
149
+ def _format_rows(rows: List[UsageRow]) -> Text:
150
+ visual_widths = [_get_visual_width(r.label) for r in rows]
151
+ max_visual_width = max(visual_widths) if visual_widths else 0
152
+ max_visual_width = max(max_visual_width, 6)
153
+ bar_width = 20
154
+ result = Text()
155
+ for i, row in enumerate(rows):
156
+ used_ratio = row.used / row.limit if row.limit > 0 else 0
157
+ remaining_percent = 100 - (used_ratio * 100)
158
+ color = "red" if used_ratio > 0.9 else "yellow" if used_ratio > 0.7 else "green"
159
+ filled = int(used_ratio * bar_width)
160
+ if i > 0: result.append("\n\n")
161
+
162
+ label_v_width = _get_visual_width(row.label)
163
+ padding = " " * (max_visual_width - label_v_width)
164
+ result.append(f"{row.label}{padding} ", style="cyan")
165
+ result.append("█" * filled, style=color)
166
+ result.append("░" * (bar_width - filled))
167
+ result.append(f" {used_ratio * 100:.0f}% {remaining_percent:.0f}% {L['remaining']}", style="bold")
168
+
169
+ meta_parts = []
170
+ if row.countdown: meta_parts.append(f"{L['countdown']}: {row.countdown}")
171
+ if row.reset_at: meta_parts.append(f"{L['reset']}: {row.reset_at}")
172
+ if meta_parts:
173
+ result.append("\n")
174
+ result.append(" ".join(meta_parts), style="dim cyan")
175
+ return result
176
+
177
+ async def get_usage_data(api_key: str, base_url: str) -> Tuple[UsageRow | None, List[UsageRow]]:
178
+ url = base_url.rstrip("/") + "/usages"
179
+ async with aiohttp.ClientSession() as session:
180
+ async with session.get(url, headers={"Authorization": f"Bearer {api_key}"}) as resp:
181
+ if resp.status != 200:
182
+ # Try fallback /usage if /usages fails
183
+ fallback_url = base_url.rstrip("/") + "/usage"
184
+ async with session.get(fallback_url, headers={"Authorization": f"Bearer {api_key}"}) as f_resp:
185
+ if f_resp.status != 200:
186
+ text = await f_resp.text()
187
+ raise Exception(f"{L['error_api']} {f_resp.status}: {text}")
188
+ payload = await f_resp.json()
189
+ else:
190
+ payload = await resp.json()
191
+
192
+ return _parse_usage_payload(payload)
193
+
194
+ async def main():
195
+ load_dotenv()
196
+ parser = argparse.ArgumentParser(description="Kimi Code Usage CLI")
197
+ parser.add_argument("--json", action="store_true")
198
+ parser.add_argument("--plain", action="store_true")
199
+ args = parser.parse_args()
200
+
201
+ api_key = os.getenv("KIMI_API_KEY") or os.getenv("KIMI_CODING_API_KEY")
202
+ base_url = os.getenv("KIMI_BASE_URL", "https://api.kimi.com/coding/v1")
203
+ if not api_key:
204
+ print(f"[Error] {L['error_key']}", file=sys.stderr)
205
+ return
206
+
207
+ try:
208
+ summary, limits = await get_usage_data(api_key, base_url)
209
+ rows = ([summary] if summary else []) + limits
210
+
211
+ if args.json:
212
+ print(json.dumps([{"label": r.label, "used": r.used, "limit": r.limit, "reset_at": r.reset_at} for r in rows], ensure_ascii=False))
213
+ elif args.plain:
214
+ for r in rows:
215
+ print(f"{r.label}: {r.used}/{r.limit} ({r.used/r.limit*100:.0f}% used)")
216
+ else:
217
+ console = Console()
218
+ if not rows:
219
+ console.print(Panel(Text(L["no_data"], style="dim"), title=f"[bold]{L['title']}[/bold]"))
220
+ else:
221
+ console.print(Panel(_format_rows(rows), title=f"[bold]{L['title']}[/bold]", expand=False, padding=(1, 2, 0, 2)))
222
+ except Exception as e:
223
+ print(f"[Error] {e}", file=sys.stderr)
224
+
225
+ def run_cli():
226
+ asyncio.run(main())
227
+
228
+ if __name__ == "__main__":
229
+ run_cli()
@@ -0,0 +1,51 @@
1
+ import os
2
+ from fastmcp import FastMCP
3
+ from .main import get_usage_data, _format_rows, L
4
+
5
+ # Initialize FastMCP for Kimi Code Usage
6
+ mcp = FastMCP("Kimi Code Usage")
7
+
8
+
9
+ @mcp.tool()
10
+ async def get_kimi_usage() -> str:
11
+ """
12
+ Get the current Kimi Coding Plan API usage and quota limits.
13
+ Returns a formatted summary including used/remaining quota and reset time.
14
+ 获取当前 Kimi Coding Plan API 的使用量和配额限制,包括已用量、剩余量和重置时间。
15
+ """
16
+ api_key = os.getenv("KIMI_API_KEY") or os.getenv("KIMI_CODING_API_KEY")
17
+ base_url = os.getenv("KIMI_BASE_URL", "https://api.kimi.com/coding/v1")
18
+
19
+ if not api_key:
20
+ return f"Error: {L['error_key']}"
21
+
22
+ try:
23
+ summary, limits = await get_usage_data(api_key, base_url)
24
+ rows = ([summary] if summary else []) + limits
25
+
26
+ if not rows:
27
+ return L["no_data"]
28
+
29
+ # Return clean plain text — no ANSI codes, friendly for LLMs
30
+ lines = []
31
+ for row in rows:
32
+ used_ratio = row.used / row.limit if row.limit > 0 else 0
33
+ remaining = row.limit - row.used
34
+ remaining_pct = 100 - used_ratio * 100
35
+ line = f"{row.label}: {row.used}/{row.limit} used ({remaining_pct:.0f}% remaining)"
36
+ if row.countdown and row.reset_at:
37
+ line += f" | Reset in {row.countdown} (at {row.reset_at})"
38
+ lines.append(line)
39
+
40
+ return "\n".join(lines)
41
+
42
+ except Exception as e:
43
+ return f"Error fetching Kimi usage: {str(e)}"
44
+
45
+
46
+ def run_mcp():
47
+ mcp.run()
48
+
49
+
50
+ if __name__ == "__main__":
51
+ run_mcp()