apishare-client 1.2.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,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: apishare-client
3
+ Version: 1.2.0
4
+ Summary: APIShare Mining Client - Contribute GPU/CPU compute power, run AI models, earn credits via tunnel
5
+ Author-email: APIShare Team <apishare@proton.me>
6
+ License: MIT
7
+ Project-URL: Homepage, https://apishare.samai.cc
8
+ Project-URL: Repository, https://github.com/ctz168/apishare
9
+ Project-URL: Documentation, https://apishare.samai.cc/mining
10
+ Project-URL: Bug Tracker, https://github.com/ctz168/apishare/issues
11
+ Project-URL: PyPI, https://pypi.org/project/apishare-client/
12
+ Keywords: apishare,mining,ai,gpu,compute,tunnel,websockets,decentralized,blockchain
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: System :: Distributed Computing
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: websockets>=12.0
26
+ Requires-Dist: psutil>=5.9.0
27
+ Requires-Dist: requests>=2.31.0
@@ -0,0 +1,17 @@
1
+ """APIShare Mining Client - 挖矿客户端
2
+
3
+ 矿工通过此客户端连接 APIShare 平台,贡献GPU/CPU算力赚取AIC积分。
4
+
5
+ 用法:
6
+ apishare login --email you@email.com
7
+ apishare register --name my-node --type gpu --model qwen2.5-0.5b
8
+ apishare serve --model qwen2.5-0.5b --port 8000
9
+ apishare tunnel --node NODE_ID --port 8000
10
+ apishare report --node NODE_ID
11
+ apishare status
12
+ apishare balance
13
+ apishare models
14
+ """
15
+
16
+ __version__ = "1.2.0"
17
+ __author__ = "APIShare Team"
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ APIShare Mining Client - 挖矿客户端 CLI
4
+ ======================================
5
+
6
+ 矿工通过此客户端连接 APIShare 平台,贡献GPU/CPU算力赚取AIC积分。
7
+
8
+ 用法:
9
+ apishare login --email you@email.com # 登录平台
10
+ apishare register --name my-node --type gpu # 注册计算节点
11
+ apishare serve --model qwen2.5-0.5b --port 8000 # 启动本地推理服务
12
+ apishare tunnel --node NODE_ID --port 8000 # 建立WebSocket隧道
13
+ apishare report --node NODE_ID # 上报算力数据
14
+ apishare status # 查看挖矿状态
15
+ apishare balance # 查看积分余额
16
+ apishare models # 查看可用模型
17
+ apishare logout # 登出
18
+
19
+ 完整挖矿流程:
20
+ 1. 登录: apishare login --email you@email.com
21
+ 2. 注册节点: apishare register --name my-gpu --type gpu --model qwen2.5-0.5b
22
+ 3. 启动模型: apishare serve --model qwen2.5-0.5b --port 8000
23
+ 4. 建隧道: apishare tunnel --node NODE_ID --model qwen2.5-0.5b --port 8000
24
+ 5. 查状态: apishare status
25
+
26
+ Docker一键模式:
27
+ docker run -d --gpus all ctz168/apishare-client:latest \\
28
+ tunnel --node NODE_ID --model qwen2.5-0.5b --port 8000 --token YOUR_TOKEN
29
+ """
30
+
31
+ import argparse
32
+ import sys
33
+ import json
34
+ import urllib.request
35
+ import urllib.error
36
+
37
+ from apishare_client import __version__
38
+ from apishare_client.config import (
39
+ get_token, get_platform_url, get_email, get_node_id,
40
+ logout as config_logout, get_supported_models, DEFAULT_PLATFORM_URL,
41
+ )
42
+ from apishare_client.commands.login import login as cmd_login
43
+ from apishare_client.commands.register import register_node as cmd_register
44
+ from apishare_client.commands.serve import start_serve as cmd_serve
45
+ from apishare_client.commands.tunnel import start_tunnel as cmd_tunnel
46
+ from apishare_client.commands.report import report_mining as cmd_report
47
+ from apishare_client.commands.status import show_status as cmd_status
48
+
49
+
50
+ def cmd_balance(args):
51
+ """查看积分余额"""
52
+ token = get_token()
53
+ if not token:
54
+ print("❌ 请先登录: apishare login --email you@email.com")
55
+ return
56
+
57
+ base_url = get_platform_url()
58
+ try:
59
+ url = f"{base_url}/api/auth/me"
60
+ req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
61
+ with urllib.request.urlopen(req, timeout=10) as resp:
62
+ data = json.loads(resp.read().decode("utf-8"))
63
+ print(f"💰 APIShare 积分余额: {data.get('balance', 0):.2f}")
64
+ print(f" 邮箱: {data.get('email', '-')}")
65
+ print(f" 角色: {data.get('role', '-')}")
66
+ except urllib.error.HTTPError as e:
67
+ if e.code == 401:
68
+ print("❌ Token已过期,请重新登录")
69
+ else:
70
+ print(f"❌ 查询失败: HTTP {e.code}")
71
+ except Exception as e:
72
+ print(f"❌ 查询错误: {e}")
73
+
74
+
75
+ def cmd_models(args):
76
+ """查看可用模型"""
77
+ models = get_supported_models()
78
+ print()
79
+ print("=" * 55)
80
+ print(" APIShare 支持的模型")
81
+ print("=" * 55)
82
+ print()
83
+ print(" 🖥️ GPU 模型:")
84
+ for m in models["gpu"]:
85
+ print(f" - {m}")
86
+ print()
87
+ print(" 💻 CPU 模型:")
88
+ for m in models["cpu"]:
89
+ print(f" - {m}")
90
+ print()
91
+ print(" 💡 也可以使用 --model 指定任何模型名称")
92
+ print()
93
+
94
+
95
+ def cmd_logout(args):
96
+ """登出"""
97
+ config_logout()
98
+ print("✅ 已登出")
99
+
100
+
101
+ def main():
102
+ """CLI入口"""
103
+ parser = argparse.ArgumentParser(
104
+ prog="apishare",
105
+ description="APIShare 挖矿客户端 - 贡献GPU/CPU算力赚取AIC积分",
106
+ formatter_class=argparse.RawDescriptionHelpFormatter,
107
+ epilog="""
108
+ 完整挖矿流程:
109
+ 1. 登录平台: apishare login --email you@email.com
110
+ 2. 注册计算节点: apishare register --name my-gpu --type gpu --model qwen2.5-0.5b
111
+ 3. 启动本地模型: apishare serve --model qwen2.5-0.5b --port 8000
112
+ 4. 建立隧道连接: apishare tunnel --node NODE_ID --model qwen2.5-0.5b --port 8000
113
+ 5. 查看挖矿状态: apishare status
114
+
115
+ 更多帮助:
116
+ https://apishare.samai.cc/mining
117
+ """,
118
+ )
119
+
120
+ parser.add_argument("--version", action="version", version=f"apishare {__version__}")
121
+ parser.add_argument("--platform", type=str, help="平台URL (默认: https://apishare.samai.cc)")
122
+ parser.add_argument("--ws-url", type=str, help="WebSocket隧道URL")
123
+
124
+ subparsers = parser.add_subparsers(dest="command", help="可用命令")
125
+
126
+ # login
127
+ p_login = subparsers.add_parser("login", help="登录 APIShare 平台")
128
+ p_login.add_argument("--email", required=True, help="登录邮箱")
129
+ p_login.add_argument("--password", type=str, help="密码 (不填则交互输入)")
130
+
131
+ # register
132
+ p_reg = subparsers.add_parser("register", help="注册计算节点")
133
+ p_reg.add_argument("--name", required=True, help="节点名称")
134
+ p_reg.add_argument("--type", default="gpu", choices=["gpu", "cpu"], help="计算类型")
135
+ p_reg.add_argument("--model", default="qwen2.5-0.5b", help="服务模型名称")
136
+ p_reg.add_argument("--gpu-info", default="", help="GPU信息")
137
+ p_reg.add_argument("--memory", type=int, default=0, help="内存(GB)")
138
+
139
+ # serve
140
+ p_serve = subparsers.add_parser("serve", help="启动本地推理服务")
141
+ p_serve.add_argument("--model", required=True, help="模型名称")
142
+ p_serve.add_argument("--port", type=int, default=8000, help="模型服务端口")
143
+ p_serve.add_argument("--backend", default="auto", choices=["auto", "vllm", "ollama", "openai-compatible"], help="推理后端")
144
+ p_serve.add_argument("--proxy-port", type=int, default=8001, help="代理服务端口")
145
+ p_serve.add_argument("--host", default="0.0.0.0", help="监听地址")
146
+
147
+ # tunnel
148
+ p_tunnel = subparsers.add_parser("tunnel", help="建立WebSocket隧道")
149
+ p_tunnel.add_argument("--node", required=True, help="节点ID")
150
+ p_tunnel.add_argument("--model", default="qwen2.5-0.5b", help="模型名称")
151
+ p_tunnel.add_argument("--port", type=int, default=8000, help="本地模型端口")
152
+ p_tunnel.add_argument("--token", type=str, help="JWT token (覆盖配置文件)")
153
+
154
+ # report
155
+ p_report = subparsers.add_parser("report", help="上报算力数据")
156
+ p_report.add_argument("--node", required=True, help="节点ID")
157
+ p_report.add_argument("--model", default="qwen2.5-0.5b", help="模型名称")
158
+ p_report.add_argument("--tokens-in", type=int, default=0, help="输入token数")
159
+ p_report.add_argument("--tokens-out", type=int, default=0, help="输出token数")
160
+ p_report.add_argument("--gpu-usage", type=float, default=0, help="GPU使用率")
161
+ p_report.add_argument("--auto", action="store_true", help="自动采集系统信息")
162
+
163
+ # status
164
+ p_status = subparsers.add_parser("status", help="查看挖矿状态")
165
+
166
+ # balance
167
+ p_balance = subparsers.add_parser("balance", help="查看积分余额")
168
+
169
+ # models
170
+ p_models = subparsers.add_parser("models", help="查看可用模型")
171
+
172
+ # logout
173
+ p_logout = subparsers.add_parser("logout", help="登出")
174
+
175
+ args = parser.parse_args()
176
+
177
+ if not args.command:
178
+ parser.print_help()
179
+ return
180
+
181
+ # Override platform URL if specified
182
+ if args.platform:
183
+ from apishare_client.config import save_config, load_config
184
+ config = load_config()
185
+ config["platform_url"] = args.platform
186
+ save_config(config)
187
+
188
+ # Dispatch commands
189
+ if args.command == "login":
190
+ cmd_login(email=args.email, password=args.password)
191
+ elif args.command == "register":
192
+ cmd_register(
193
+ name=args.name,
194
+ compute_type=args.type,
195
+ model=args.model,
196
+ gpu_info=args.gpu_info,
197
+ memory_gb=args.memory,
198
+ )
199
+ elif args.command == "serve":
200
+ cmd_serve(
201
+ model=args.model,
202
+ port=args.port,
203
+ backend=args.backend,
204
+ host=args.host,
205
+ proxy_port=args.proxy_port,
206
+ )
207
+ elif args.command == "tunnel":
208
+ cmd_tunnel(
209
+ node_id=args.node,
210
+ model=args.model,
211
+ port=args.port,
212
+ token=args.token,
213
+ )
214
+ elif args.command == "report":
215
+ cmd_report(
216
+ node_id=args.node,
217
+ model=args.model,
218
+ tokens_in=args.tokens_in,
219
+ tokens_out=args.tokens_out,
220
+ gpu_usage=args.gpu_usage,
221
+ auto=args.auto,
222
+ )
223
+ elif args.command == "status":
224
+ cmd_status()
225
+ elif args.command == "balance":
226
+ cmd_balance(args)
227
+ elif args.command == "models":
228
+ cmd_models(args)
229
+ elif args.command == "logout":
230
+ cmd_logout(args)
231
+
232
+
233
+ if __name__ == "__main__":
234
+ main()
@@ -0,0 +1 @@
1
+ """APIShare Mining Client - 命令模块"""
@@ -0,0 +1,77 @@
1
+ """APIShare Mining Client - 登录命令
2
+
3
+ apishare login --email you@email.com
4
+ apishare login --email you@email.com --platform https://apishare.samai.cc
5
+ """
6
+
7
+ import getpass
8
+ import sys
9
+ import urllib.request
10
+ import urllib.error
11
+ import json
12
+
13
+ from apishare_client.config import set_login, get_platform_url, DEFAULT_PLATFORM_URL
14
+
15
+
16
+ def login(email: str, platform_url: str = None, password: str = None) -> bool:
17
+ """登录 APIShare 平台,获取 JWT token"""
18
+ base_url = platform_url or get_platform_url()
19
+
20
+ if not password:
21
+ password = getpass.getpass("请输入密码: ")
22
+
23
+ if not email or not password:
24
+ print("❌ 邮箱和密码不能为空")
25
+ return False
26
+
27
+ print(f"正在登录 {base_url} ...")
28
+
29
+ # 调用平台登录API
30
+ url = f"{base_url}/api/auth/login"
31
+ payload = json.dumps({"email": email, "password": password}).encode("utf-8")
32
+ req = urllib.request.Request(
33
+ url,
34
+ data=payload,
35
+ headers={"Content-Type": "application/json"},
36
+ method="POST",
37
+ )
38
+
39
+ try:
40
+ with urllib.request.urlopen(req, timeout=15) as resp:
41
+ data = json.loads(resp.read().decode("utf-8"))
42
+
43
+ token = data.get("token") or data.get("access_token")
44
+ user = data.get("user", {})
45
+
46
+ if token:
47
+ ws_url = base_url.replace("https://", "wss://").replace("http://", "ws://") + "/api/tunnel/connect"
48
+ set_login(token, email, base_url, ws_url)
49
+ balance = user.get("balance", 0)
50
+ role = user.get("role", "user")
51
+ print(f"✅ 登录成功!")
52
+ print(f" 邮箱: {email}")
53
+ print(f" 角色: {role}")
54
+ print(f" 积分余额: {balance:.2f} AIC")
55
+ print(f" 平台: {base_url}")
56
+ return True
57
+ else:
58
+ print(f"❌ 登录失败: 服务器未返回有效token")
59
+ print(f" 响应: {json.dumps(data, ensure_ascii=False)[:200]}")
60
+ return False
61
+
62
+ except urllib.error.HTTPError as e:
63
+ body = e.read().decode("utf-8", errors="replace")[:200]
64
+ try:
65
+ err_data = json.loads(body)
66
+ msg = err_data.get("detail", body)
67
+ except json.JSONDecodeError:
68
+ msg = body
69
+ print(f"❌ 登录失败: {msg}")
70
+ return False
71
+ except urllib.error.URLError as e:
72
+ print(f"❌ 网络错误: {e.reason}")
73
+ print(f" 请检查网络连接或平台地址是否正确")
74
+ return False
75
+ except Exception as e:
76
+ print(f"❌ 登录异常: {e}")
77
+ return False
@@ -0,0 +1,93 @@
1
+ """APIShare Mining Client - 注册计算节点
2
+
3
+ apishare register --name my-gpu-node --type gpu --model qwen2.5-0.5b
4
+ """
5
+
6
+ import json
7
+ import sys
8
+ import urllib.request
9
+ import urllib.error
10
+
11
+ from apishare_client.config import get_token, get_platform_url, get_email, set_node_id
12
+
13
+
14
+ def register_node(
15
+ name: str,
16
+ compute_type: str = "gpu",
17
+ model: str = "qwen2.5-0.5b",
18
+ gpu_info: str = "",
19
+ cpu_info: str = "",
20
+ memory_gb: int = 0,
21
+ ) -> bool:
22
+ """在平台上注册一个计算节点"""
23
+
24
+ token = get_token()
25
+ if not token:
26
+ print("❌ 未登录,请先运行: apishare login --email you@email.com")
27
+ return False
28
+
29
+ email = get_email() or "unknown"
30
+ base_url = get_platform_url()
31
+
32
+ print(f"正在注册计算节点...")
33
+ print(f" 名称: {name}")
34
+ print(f" 类型: {compute_type.upper()}")
35
+ print(f" 模型: {model}")
36
+
37
+ # 调用平台注册API
38
+ url = f"{base_url}/api/compute/register"
39
+ payload = json.dumps({
40
+ "nodeName": name,
41
+ "computeType": compute_type,
42
+ "servingModel": model,
43
+ "gpuInfo": gpu_info or f"GPU for {model}",
44
+ "cpuInfo": cpu_info or ("CPU for " + model if compute_type == "cpu" else ""),
45
+ "memoryGB": memory_gb or 8,
46
+ }).encode("utf-8")
47
+
48
+ req = urllib.request.Request(
49
+ url,
50
+ data=payload,
51
+ headers={
52
+ "Content-Type": "application/json",
53
+ "Authorization": f"Bearer {token}",
54
+ },
55
+ method="POST",
56
+ )
57
+
58
+ try:
59
+ with urllib.request.urlopen(req, timeout=15) as resp:
60
+ data = json.loads(resp.read().decode("utf-8"))
61
+
62
+ node_id = data.get("id") or data.get("nodeId")
63
+ if node_id:
64
+ set_node_id(node_id)
65
+ print(f"\n✅ 节点注册成功!")
66
+ print(f" 节点ID: {node_id}")
67
+ print(f" 节点名称: {name}")
68
+ print(f"\n下一步:")
69
+ print(f" 1. 启动本地模型服务:")
70
+ print(f" apishare serve --model {model} --port 8000")
71
+ print(f" 2. 建立隧道连接(让平台能调用你的模型):")
72
+ print(f" apishare tunnel --node {node_id} --model {model} --port 8000")
73
+ return True
74
+ else:
75
+ print(f"❌ 注册失败: 服务器未返回节点ID")
76
+ print(f" 响应: {json.dumps(data, ensure_ascii=False)[:200]}")
77
+ return False
78
+
79
+ except urllib.error.HTTPError as e:
80
+ body = e.read().decode("utf-8", errors="replace")[:200]
81
+ try:
82
+ err_data = json.loads(body)
83
+ msg = err_data.get("detail", body)
84
+ except json.JSONDecodeError:
85
+ msg = body
86
+ print(f"❌ 注册失败: {msg}")
87
+ return False
88
+ except urllib.error.URLError as e:
89
+ print(f"❌ 网络错误: {e.reason}")
90
+ return False
91
+ except Exception as e:
92
+ print(f"❌ 注册异常: {e}")
93
+ return False
@@ -0,0 +1,119 @@
1
+ """APIShare Mining Client - 算力上报
2
+
3
+ apishare report --node NODE_ID
4
+ apishare report --node NODE_ID --auto (自动采集系统信息)
5
+ """
6
+
7
+ import json
8
+ import time
9
+ import urllib.request
10
+ import urllib.error
11
+ from typing import Optional
12
+
13
+ from apishare_client.config import get_token, get_platform_url
14
+
15
+
16
+ def report_mining(
17
+ node_id: str,
18
+ tokens_in: int = 0,
19
+ tokens_out: int = 0,
20
+ inference_time: float = 0.0,
21
+ gpu_usage: float = 0.0,
22
+ model: str = "qwen2.5-0.5b",
23
+ compute_type: str = "gpu",
24
+ auto: bool = False,
25
+ ) -> bool:
26
+ """上报挖矿算力数据到平台"""
27
+
28
+ token = get_token()
29
+ if not token:
30
+ print("❌ 未登录,请先运行: apishare login")
31
+ return False
32
+
33
+ base_url = get_platform_url()
34
+
35
+ if auto:
36
+ sys_info = _collect_system_info()
37
+ gpu_usage = gpu_usage or sys_info.get("gpu_usage", 0)
38
+ compute_type = compute_type or sys_info.get("compute_type", "gpu")
39
+
40
+ # 构建上报数据
41
+ report_data = {
42
+ "nodeId": node_id,
43
+ "tokensIn": tokens_in,
44
+ "tokensOut": tokens_out,
45
+ "inferenceTime": inference_time,
46
+ "gpuUsage": gpu_usage,
47
+ "modelName": model,
48
+ "computeType": compute_type,
49
+ "timestamp": time.time(),
50
+ }
51
+
52
+ url = f"{base_url}/api/compute/report"
53
+ payload = json.dumps(report_data).encode("utf-8")
54
+ req = urllib.request.Request(
55
+ url,
56
+ data=payload,
57
+ headers={
58
+ "Content-Type": "application/json",
59
+ "Authorization": f"Bearer {token}",
60
+ },
61
+ method="POST",
62
+ )
63
+
64
+ try:
65
+ with urllib.request.urlopen(req, timeout=15) as resp:
66
+ data = json.loads(resp.read().decode("utf-8"))
67
+
68
+ reward = data.get("reward") or data.get("miningReward", 0)
69
+ score = data.get("computeScore") or data.get("score", 0)
70
+
71
+ print(f"✅ 算力上报成功")
72
+ print(f" 节点: {node_id[:16]}...")
73
+ if score:
74
+ print(f" 算力分: {score:.2f}")
75
+ if reward:
76
+ print(f" 获得奖励: {reward}")
77
+ return True
78
+
79
+ except urllib.error.HTTPError as e:
80
+ body = e.read().decode("utf-8", errors="replace")[:200]
81
+ print(f"❌ 上报失败 (HTTP {e.code}): {body}")
82
+ return False
83
+ except urllib.error.URLError as e:
84
+ print(f"❌ 网络错误: {e.reason}")
85
+ return False
86
+ except Exception as e:
87
+ print(f"❌ 上报异常: {e}")
88
+ return False
89
+
90
+
91
+ def _collect_system_info() -> dict:
92
+ """采集系统信息"""
93
+ info = {"gpu_usage": 0, "compute_type": "cpu", "cpu_count": 0, "memory_gb": 0}
94
+
95
+ try:
96
+ import psutil
97
+ info["cpu_count"] = psutil.cpu_count()
98
+ info["memory_gb"] = round(psutil.virtual_memory().total / (1024**3), 1)
99
+ except ImportError:
100
+ pass
101
+
102
+ # Try to detect GPU
103
+ try:
104
+ import subprocess
105
+ result = subprocess.run(
106
+ ["nvidia-smi", "--query-gpu=utilization.gpu,memory.used,memory.total",
107
+ "--format=csv,noheader,nounits"],
108
+ capture_output=True, text=True, timeout=5
109
+ )
110
+ if result.returncode == 0:
111
+ info["compute_type"] = "gpu"
112
+ lines = result.stdout.strip().split("\n")
113
+ if lines:
114
+ parts = lines[0].split(",")
115
+ info["gpu_usage"] = float(parts[0].strip())
116
+ except Exception:
117
+ pass
118
+
119
+ return info