gspark-agent 0.4.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.
- gspark_agent-0.4.0/PKG-INFO +76 -0
- gspark_agent-0.4.0/README.md +59 -0
- gspark_agent-0.4.0/pyproject.toml +32 -0
- gspark_agent-0.4.0/setup.cfg +4 -0
- gspark_agent-0.4.0/src/gspark_agent/__init__.py +3 -0
- gspark_agent-0.4.0/src/gspark_agent/cli.py +440 -0
- gspark_agent-0.4.0/src/gspark_agent/cli_util.py +38 -0
- gspark_agent-0.4.0/src/gspark_agent/client.py +305 -0
- gspark_agent-0.4.0/src/gspark_agent/mcp_server.py +387 -0
- gspark_agent-0.4.0/src/gspark_agent.egg-info/PKG-INFO +76 -0
- gspark_agent-0.4.0/src/gspark_agent.egg-info/SOURCES.txt +13 -0
- gspark_agent-0.4.0/src/gspark_agent.egg-info/dependency_links.txt +1 -0
- gspark_agent-0.4.0/src/gspark_agent.egg-info/entry_points.txt +3 -0
- gspark_agent-0.4.0/src/gspark_agent.egg-info/requires.txt +6 -0
- gspark_agent-0.4.0/src/gspark_agent.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gspark-agent
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: G-spark / HUB Agent tools: CLI + MCP for /v1 API
|
|
5
|
+
Author: G-spark
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://api.g-spark.cn
|
|
8
|
+
Project-URL: Documentation, https://api.g-spark.cn/api/agent
|
|
9
|
+
Keywords: gspark,hub,cli,mcp,agent
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Requires-Dist: typer>=0.12
|
|
14
|
+
Requires-Dist: rich>=13
|
|
15
|
+
Provides-Extra: mcp
|
|
16
|
+
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
17
|
+
|
|
18
|
+
# gspark-agent
|
|
19
|
+
|
|
20
|
+
面向 AI Agent 的 G-spark / HUB 接入工具:**CLI + MCP**,共用同一套 `/v1` 客户端。
|
|
21
|
+
|
|
22
|
+
## 安装
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install "gspark-agent[mcp]"
|
|
26
|
+
# 或隔离安装
|
|
27
|
+
pipx install "gspark-agent[mcp]"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
开发(本仓库):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install "./packages/gspark-agent[mcp]"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 配置
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export GSPARK_API_KEY=sk_xxxx
|
|
40
|
+
export GSPARK_BASE_URL=https://api.g-spark.cn # 或其他节点,如 https://api.hqszh.com
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
兼容别名:`HUB_API_KEY`、`HUB_BASE_URL`。
|
|
44
|
+
|
|
45
|
+
## CLI
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
gspark apps list --json
|
|
49
|
+
gspark apps show video_01 --json
|
|
50
|
+
gspark tasks estimate --app video_01 --input prompt="hello" --json
|
|
51
|
+
gspark tasks run --app video_01 --input prompt="hello" --wait --json
|
|
52
|
+
gspark tasks get <task_id> --json
|
|
53
|
+
gspark account balance --json
|
|
54
|
+
gspark account ledger --page 1 --size 20 --json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- `--json`:紧凑单行 JSON(便于 Agent / 管道)
|
|
58
|
+
- 默认:缩进 JSON;`account ledger` 默认为表格
|
|
59
|
+
|
|
60
|
+
`--input` 支持 JSON 数组/对象,例如:`--input tags='["a","b"]'`、`--input meta='{"k":1}'`。
|
|
61
|
+
|
|
62
|
+
## MCP(Cursor)
|
|
63
|
+
|
|
64
|
+
见 `agent_pack/mcp-config.example.json`;控制台「Agent 接入」页可下载完整接入包。
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
gspark-mcp
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Agent Skill
|
|
71
|
+
|
|
72
|
+
`agent_pack/SKILL.md` 为全站通用说明书,配合 CLI 使用;无需为每个应用单独复制文档。
|
|
73
|
+
|
|
74
|
+
## 许可
|
|
75
|
+
|
|
76
|
+
Proprietary — 允许安装使用;勿再分发改牌。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# gspark-agent
|
|
2
|
+
|
|
3
|
+
面向 AI Agent 的 G-spark / HUB 接入工具:**CLI + MCP**,共用同一套 `/v1` 客户端。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install "gspark-agent[mcp]"
|
|
9
|
+
# 或隔离安装
|
|
10
|
+
pipx install "gspark-agent[mcp]"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
开发(本仓库):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install "./packages/gspark-agent[mcp]"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 配置
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
export GSPARK_API_KEY=sk_xxxx
|
|
23
|
+
export GSPARK_BASE_URL=https://api.g-spark.cn # 或其他节点,如 https://api.hqszh.com
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
兼容别名:`HUB_API_KEY`、`HUB_BASE_URL`。
|
|
27
|
+
|
|
28
|
+
## CLI
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
gspark apps list --json
|
|
32
|
+
gspark apps show video_01 --json
|
|
33
|
+
gspark tasks estimate --app video_01 --input prompt="hello" --json
|
|
34
|
+
gspark tasks run --app video_01 --input prompt="hello" --wait --json
|
|
35
|
+
gspark tasks get <task_id> --json
|
|
36
|
+
gspark account balance --json
|
|
37
|
+
gspark account ledger --page 1 --size 20 --json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `--json`:紧凑单行 JSON(便于 Agent / 管道)
|
|
41
|
+
- 默认:缩进 JSON;`account ledger` 默认为表格
|
|
42
|
+
|
|
43
|
+
`--input` 支持 JSON 数组/对象,例如:`--input tags='["a","b"]'`、`--input meta='{"k":1}'`。
|
|
44
|
+
|
|
45
|
+
## MCP(Cursor)
|
|
46
|
+
|
|
47
|
+
见 `agent_pack/mcp-config.example.json`;控制台「Agent 接入」页可下载完整接入包。
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
gspark-mcp
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Agent Skill
|
|
54
|
+
|
|
55
|
+
`agent_pack/SKILL.md` 为全站通用说明书,配合 CLI 使用;无需为每个应用单独复制文档。
|
|
56
|
+
|
|
57
|
+
## 许可
|
|
58
|
+
|
|
59
|
+
Proprietary — 允许安装使用;勿再分发改牌。
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gspark-agent"
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
description = "G-spark / HUB Agent tools: CLI + MCP for /v1 API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Proprietary" }
|
|
12
|
+
authors = [{ name = "G-spark" }]
|
|
13
|
+
keywords = ["gspark", "hub", "cli", "mcp", "agent"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"httpx>=0.27",
|
|
16
|
+
"typer>=0.12",
|
|
17
|
+
"rich>=13",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://api.g-spark.cn"
|
|
22
|
+
Documentation = "https://api.g-spark.cn/api/agent"
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
mcp = ["mcp>=1.2"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
gspark = "gspark_agent.cli:app"
|
|
29
|
+
gspark-mcp = "gspark_agent.mcp_server:cli_main"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"""G-spark CLI — discover apps dynamically, run tasks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from gspark_agent.cli_util import coerce_value, dumps_payload, ensure_idempotency_key
|
|
10
|
+
from gspark_agent.client import GsparkClient, GsparkError
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(no_args_is_help=True, help="G-spark / HUB Agent CLI")
|
|
13
|
+
apps_app = typer.Typer(help="应用目录与 Schema")
|
|
14
|
+
models_app = typer.Typer(help="模型目录(app_kind=model)")
|
|
15
|
+
skills_app = typer.Typer(help="Skill 库(授权后可读正文)")
|
|
16
|
+
tasks_app = typer.Typer(help="任务创建、查询、等待")
|
|
17
|
+
account_app = typer.Typer(help="账户余额与流水")
|
|
18
|
+
app.add_typer(apps_app, name="apps")
|
|
19
|
+
app.add_typer(models_app, name="models")
|
|
20
|
+
app.add_typer(skills_app, name="skills")
|
|
21
|
+
app.add_typer(tasks_app, name="tasks")
|
|
22
|
+
data_app = typer.Typer(help="数据中心(社媒数据 API)")
|
|
23
|
+
app.add_typer(data_app, name="data")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parse_inputs(pairs: list[str] | None) -> dict[str, Any]:
|
|
27
|
+
out: dict[str, Any] = {}
|
|
28
|
+
for raw in pairs or []:
|
|
29
|
+
if "=" not in raw:
|
|
30
|
+
raise typer.BadParameter(f"无效 --input,需要 key=value: {raw}")
|
|
31
|
+
k, v = raw.split("=", 1)
|
|
32
|
+
k = k.strip()
|
|
33
|
+
if not k:
|
|
34
|
+
raise typer.BadParameter(f"无效 --input: {raw}")
|
|
35
|
+
out[k] = coerce_value(v)
|
|
36
|
+
return out
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _coerce_value(v: str) -> Any:
|
|
40
|
+
"""Backward-compatible alias for tests / callers."""
|
|
41
|
+
return coerce_value(v)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _ensure_idempotency_key(key: str) -> str:
|
|
45
|
+
return ensure_idempotency_key(key)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _emit(data: Any, *, as_json: bool) -> None:
|
|
49
|
+
typer.echo(dumps_payload(data, as_json=as_json))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _client() -> GsparkClient:
|
|
53
|
+
try:
|
|
54
|
+
return GsparkClient.from_env()
|
|
55
|
+
except GsparkError as e:
|
|
56
|
+
typer.secho(str(e), fg=typer.colors.RED, err=True)
|
|
57
|
+
raise typer.Exit(2) from e
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@apps_app.command("list")
|
|
61
|
+
def apps_list(
|
|
62
|
+
page: int = typer.Option(1, min=1),
|
|
63
|
+
size: int = typer.Option(50, min=1, max=100),
|
|
64
|
+
app_kind: str = typer.Option("", "--kind", help="workflow | model | 空=全部"),
|
|
65
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
66
|
+
) -> None:
|
|
67
|
+
"""列出已发布应用(后台新上架会自动出现)。"""
|
|
68
|
+
c = _client()
|
|
69
|
+
try:
|
|
70
|
+
kind = (app_kind or "").strip() or None
|
|
71
|
+
_emit(c.list_apps(page=page, size=size, app_kind=kind), as_json=json_out)
|
|
72
|
+
except GsparkError as e:
|
|
73
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
74
|
+
raise typer.Exit(1) from e
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@models_app.command("list")
|
|
78
|
+
def models_list(
|
|
79
|
+
page: int = typer.Option(1, min=1),
|
|
80
|
+
size: int = typer.Option(50, min=1, max=100),
|
|
81
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
82
|
+
) -> None:
|
|
83
|
+
"""列出已发布模型(等价于 apps list --kind model)。"""
|
|
84
|
+
c = _client()
|
|
85
|
+
try:
|
|
86
|
+
_emit(c.list_models(page=page, size=size), as_json=json_out)
|
|
87
|
+
except GsparkError as e:
|
|
88
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
89
|
+
raise typer.Exit(1) from e
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@apps_app.command("show")
|
|
93
|
+
def apps_show(
|
|
94
|
+
app_code: str = typer.Argument(..., help="应用 app_code"),
|
|
95
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
96
|
+
) -> None:
|
|
97
|
+
"""查看应用详情、inputs 字段 Schema 与定价。"""
|
|
98
|
+
c = _client()
|
|
99
|
+
try:
|
|
100
|
+
_emit(c.get_app(app_code), as_json=json_out)
|
|
101
|
+
except GsparkError as e:
|
|
102
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
103
|
+
raise typer.Exit(1) from e
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@skills_app.command("list")
|
|
107
|
+
def skills_list(
|
|
108
|
+
page: int = typer.Option(1, min=1),
|
|
109
|
+
size: int = typer.Option(50, min=1, max=100),
|
|
110
|
+
usable_only: bool = typer.Option(False, "--usable-only", help="仅已授权"),
|
|
111
|
+
q: str = typer.Option("", "--q", help="搜索"),
|
|
112
|
+
access_type: str = typer.Option("", "--access-type", help="free|membership|paid"),
|
|
113
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
114
|
+
) -> None:
|
|
115
|
+
"""列出 Skill 库(元数据;不含未授权正文)。"""
|
|
116
|
+
c = _client()
|
|
117
|
+
try:
|
|
118
|
+
_emit(
|
|
119
|
+
c.list_skills(
|
|
120
|
+
page=page,
|
|
121
|
+
size=size,
|
|
122
|
+
usable_only=usable_only,
|
|
123
|
+
q=q,
|
|
124
|
+
access_type=access_type,
|
|
125
|
+
),
|
|
126
|
+
as_json=json_out,
|
|
127
|
+
)
|
|
128
|
+
except GsparkError as e:
|
|
129
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
130
|
+
raise typer.Exit(1) from e
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@skills_app.command("show")
|
|
134
|
+
def skills_show(
|
|
135
|
+
skill_code: str = typer.Argument(..., help="Skill code"),
|
|
136
|
+
include_content: bool = typer.Option(
|
|
137
|
+
False,
|
|
138
|
+
"--include-content",
|
|
139
|
+
help="无权限时强制要求正文(会 403);有权限时 show 默认已含 sections",
|
|
140
|
+
),
|
|
141
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
142
|
+
) -> None:
|
|
143
|
+
"""查看 Skill;已授权时响应含 sections 正文。"""
|
|
144
|
+
c = _client()
|
|
145
|
+
try:
|
|
146
|
+
_emit(c.get_skill(skill_code, include_content=include_content), as_json=json_out)
|
|
147
|
+
except GsparkError as e:
|
|
148
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
149
|
+
raise typer.Exit(1) from e
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@tasks_app.command("estimate")
|
|
153
|
+
def tasks_estimate(
|
|
154
|
+
app: str = typer.Option(..., "--app", help="app_code"),
|
|
155
|
+
input: list[str] = typer.Option(None, "--input", "-i", help="字段 key=value,可重复"),
|
|
156
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
157
|
+
) -> None:
|
|
158
|
+
"""预估费用(不扣费)。"""
|
|
159
|
+
c = _client()
|
|
160
|
+
inputs = _parse_inputs(input)
|
|
161
|
+
try:
|
|
162
|
+
_emit(c.estimate(app, inputs), as_json=json_out)
|
|
163
|
+
except GsparkError as e:
|
|
164
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
165
|
+
raise typer.Exit(1) from e
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@tasks_app.command("create")
|
|
169
|
+
def tasks_create(
|
|
170
|
+
app: str = typer.Option(..., "--app"),
|
|
171
|
+
input: list[str] = typer.Option(None, "--input", "-i"),
|
|
172
|
+
idempotency_key: str = typer.Option("", "--idempotency-key"),
|
|
173
|
+
client_ref: str = typer.Option("", "--client-ref"),
|
|
174
|
+
webhook_url: str = typer.Option("", "--webhook-url"),
|
|
175
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
176
|
+
) -> None:
|
|
177
|
+
"""创建任务(预扣),不等待完成。"""
|
|
178
|
+
c = _client()
|
|
179
|
+
inputs = _parse_inputs(input)
|
|
180
|
+
key = _ensure_idempotency_key(idempotency_key)
|
|
181
|
+
try:
|
|
182
|
+
_emit(
|
|
183
|
+
c.create_task(
|
|
184
|
+
app,
|
|
185
|
+
inputs,
|
|
186
|
+
idempotency_key=key,
|
|
187
|
+
client_ref=client_ref,
|
|
188
|
+
webhook_url=webhook_url,
|
|
189
|
+
),
|
|
190
|
+
as_json=json_out,
|
|
191
|
+
)
|
|
192
|
+
except GsparkError as e:
|
|
193
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
194
|
+
raise typer.Exit(1) from e
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@tasks_app.command("run")
|
|
198
|
+
def tasks_run(
|
|
199
|
+
app: str = typer.Option(..., "--app"),
|
|
200
|
+
input: list[str] = typer.Option(None, "--input", "-i"),
|
|
201
|
+
wait: bool = typer.Option(True, "--wait/--no-wait"),
|
|
202
|
+
timeout: float = typer.Option(600.0, "--timeout", help="等待终态秒数"),
|
|
203
|
+
poll_interval: float = typer.Option(2.0, "--poll-interval"),
|
|
204
|
+
idempotency_key: str = typer.Option("", "--idempotency-key"),
|
|
205
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
206
|
+
) -> None:
|
|
207
|
+
"""创建任务并可选等待终态(推荐 Agent 使用)。"""
|
|
208
|
+
c = _client()
|
|
209
|
+
inputs = _parse_inputs(input)
|
|
210
|
+
key = _ensure_idempotency_key(idempotency_key)
|
|
211
|
+
try:
|
|
212
|
+
if wait:
|
|
213
|
+
_emit(
|
|
214
|
+
c.run_task(
|
|
215
|
+
app,
|
|
216
|
+
inputs,
|
|
217
|
+
wait=True,
|
|
218
|
+
timeout=timeout,
|
|
219
|
+
poll_interval=poll_interval,
|
|
220
|
+
idempotency_key=key,
|
|
221
|
+
),
|
|
222
|
+
as_json=json_out,
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
_emit(
|
|
226
|
+
c.create_task(app, inputs, idempotency_key=key),
|
|
227
|
+
as_json=json_out,
|
|
228
|
+
)
|
|
229
|
+
except GsparkError as e:
|
|
230
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
231
|
+
raise typer.Exit(1) from e
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@tasks_app.command("get")
|
|
235
|
+
def tasks_get(
|
|
236
|
+
task_id: str = typer.Argument(...),
|
|
237
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
238
|
+
) -> None:
|
|
239
|
+
"""查询任务状态与 outputs。"""
|
|
240
|
+
c = _client()
|
|
241
|
+
try:
|
|
242
|
+
_emit(c.get_task(task_id), as_json=json_out)
|
|
243
|
+
except GsparkError as e:
|
|
244
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
245
|
+
raise typer.Exit(1) from e
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@tasks_app.command("list")
|
|
249
|
+
def tasks_list(
|
|
250
|
+
page: int = typer.Option(1, min=1),
|
|
251
|
+
size: int = typer.Option(20, min=1, max=100),
|
|
252
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
253
|
+
) -> None:
|
|
254
|
+
"""任务列表。"""
|
|
255
|
+
c = _client()
|
|
256
|
+
try:
|
|
257
|
+
_emit(c.list_tasks(page=page, size=size), as_json=json_out)
|
|
258
|
+
except GsparkError as e:
|
|
259
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
260
|
+
raise typer.Exit(1) from e
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@tasks_app.command("cancel")
|
|
264
|
+
def tasks_cancel(
|
|
265
|
+
task_id: str = typer.Argument(...),
|
|
266
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
267
|
+
) -> None:
|
|
268
|
+
"""取消任务。"""
|
|
269
|
+
c = _client()
|
|
270
|
+
try:
|
|
271
|
+
_emit(c.cancel_task(task_id), as_json=json_out)
|
|
272
|
+
except GsparkError as e:
|
|
273
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
274
|
+
raise typer.Exit(1) from e
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@tasks_app.command("wait")
|
|
278
|
+
def tasks_wait(
|
|
279
|
+
task_id: str = typer.Argument(...),
|
|
280
|
+
timeout: float = typer.Option(600.0, "--timeout"),
|
|
281
|
+
poll_interval: float = typer.Option(2.0, "--poll-interval"),
|
|
282
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
283
|
+
) -> None:
|
|
284
|
+
"""轮询直到任务进入终态。"""
|
|
285
|
+
c = _client()
|
|
286
|
+
try:
|
|
287
|
+
_emit(c.wait_task(task_id, timeout=timeout, poll_interval=poll_interval), as_json=json_out)
|
|
288
|
+
except GsparkError as e:
|
|
289
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
290
|
+
raise typer.Exit(1) from e
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@data_app.command("list")
|
|
294
|
+
def data_list(
|
|
295
|
+
page: int = typer.Option(1, min=1),
|
|
296
|
+
size: int = typer.Option(50, min=1, max=100),
|
|
297
|
+
category: str = typer.Option("", "--category"),
|
|
298
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
299
|
+
) -> None:
|
|
300
|
+
c = _client()
|
|
301
|
+
try:
|
|
302
|
+
_emit(c.list_data_apis(page=page, size=size, category=category), as_json=json_out)
|
|
303
|
+
except GsparkError as e:
|
|
304
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
305
|
+
raise typer.Exit(1) from e
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@data_app.command("show")
|
|
309
|
+
def data_show(api_code: str, json_out: bool = typer.Option(False, "--json")) -> None:
|
|
310
|
+
c = _client()
|
|
311
|
+
try:
|
|
312
|
+
_emit(c.get_data_api(api_code), as_json=json_out)
|
|
313
|
+
except GsparkError as e:
|
|
314
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
315
|
+
raise typer.Exit(1) from e
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@data_app.command("estimate")
|
|
319
|
+
def data_estimate(
|
|
320
|
+
api: str = typer.Option(..., "--api"),
|
|
321
|
+
input: list[str] = typer.Option(None, "--input"),
|
|
322
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
323
|
+
) -> None:
|
|
324
|
+
c = _client()
|
|
325
|
+
try:
|
|
326
|
+
_emit(c.estimate_data_api(api, _parse_inputs(input)), as_json=json_out)
|
|
327
|
+
except GsparkError as e:
|
|
328
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
329
|
+
raise typer.Exit(1) from e
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@data_app.command("invoke")
|
|
333
|
+
def data_invoke(
|
|
334
|
+
api: str = typer.Option(..., "--api"),
|
|
335
|
+
input: list[str] = typer.Option(None, "--input"),
|
|
336
|
+
idempotency_key: str = typer.Option("", "--idempotency-key"),
|
|
337
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
338
|
+
) -> None:
|
|
339
|
+
c = _client()
|
|
340
|
+
try:
|
|
341
|
+
_emit(
|
|
342
|
+
c.invoke_data_api(
|
|
343
|
+
api,
|
|
344
|
+
_parse_inputs(input),
|
|
345
|
+
idempotency_key=_ensure_idempotency_key(idempotency_key),
|
|
346
|
+
),
|
|
347
|
+
as_json=json_out,
|
|
348
|
+
)
|
|
349
|
+
except GsparkError as e:
|
|
350
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
351
|
+
raise typer.Exit(1) from e
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@data_app.command("run")
|
|
355
|
+
def data_run(
|
|
356
|
+
api: str = typer.Option(..., "--api"),
|
|
357
|
+
input: list[str] = typer.Option(None, "--input"),
|
|
358
|
+
wait: bool = typer.Option(True, "--wait/--no-wait"),
|
|
359
|
+
idempotency_key: str = typer.Option("", "--idempotency-key"),
|
|
360
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
361
|
+
) -> None:
|
|
362
|
+
c = _client()
|
|
363
|
+
try:
|
|
364
|
+
_emit(
|
|
365
|
+
c.run_data_api(
|
|
366
|
+
api,
|
|
367
|
+
_parse_inputs(input),
|
|
368
|
+
wait=wait,
|
|
369
|
+
idempotency_key=_ensure_idempotency_key(idempotency_key),
|
|
370
|
+
),
|
|
371
|
+
as_json=json_out,
|
|
372
|
+
)
|
|
373
|
+
except GsparkError as e:
|
|
374
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
375
|
+
raise typer.Exit(1) from e
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@account_app.command("balance")
|
|
379
|
+
def account_balance(json_out: bool = typer.Option(False, "--json")) -> None:
|
|
380
|
+
"""查询余额与短欠。"""
|
|
381
|
+
c = _client()
|
|
382
|
+
try:
|
|
383
|
+
_emit(c.balance(), as_json=json_out)
|
|
384
|
+
except GsparkError as e:
|
|
385
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
386
|
+
raise typer.Exit(1) from e
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@account_app.command("ledger")
|
|
390
|
+
def account_ledger(
|
|
391
|
+
page: int = typer.Option(1, min=1),
|
|
392
|
+
size: int = typer.Option(20, min=1, max=100),
|
|
393
|
+
json_out: bool = typer.Option(False, "--json"),
|
|
394
|
+
) -> None:
|
|
395
|
+
"""查询账户流水(充值、扣费、退款等,按时间倒序)。"""
|
|
396
|
+
c = _client()
|
|
397
|
+
try:
|
|
398
|
+
data = c.ledger(page=page, size=size)
|
|
399
|
+
if json_out:
|
|
400
|
+
_emit(data, as_json=True)
|
|
401
|
+
return
|
|
402
|
+
items = data.get("items") if isinstance(data, dict) else None
|
|
403
|
+
if not isinstance(items, list) or not items:
|
|
404
|
+
typer.echo(f"暂无流水(共 {data.get('total', 0) if isinstance(data, dict) else 0} 条)")
|
|
405
|
+
return
|
|
406
|
+
total = data.get("total", 0)
|
|
407
|
+
currency = data.get("currency") or "CNY"
|
|
408
|
+
typer.echo(f"流水 第 {data.get('page', page)} 页 / 共 {total} 条({currency})")
|
|
409
|
+
typer.echo(f"{'时间':<22} {'变动':>10} {'余额后':>10} {'原因':<18} 关联")
|
|
410
|
+
typer.echo("-" * 78)
|
|
411
|
+
for row in items:
|
|
412
|
+
if not isinstance(row, dict):
|
|
413
|
+
continue
|
|
414
|
+
created = str(row.get("created_at") or "")[:19].replace("T", " ")
|
|
415
|
+
delta = row.get("delta")
|
|
416
|
+
if delta is None and row.get("delta_cents") is not None:
|
|
417
|
+
try:
|
|
418
|
+
delta = round(int(row["delta_cents"]) / 100.0, 2)
|
|
419
|
+
except (TypeError, ValueError):
|
|
420
|
+
delta = row.get("delta_cents")
|
|
421
|
+
after = row.get("balance_after")
|
|
422
|
+
if after is None and row.get("balance_after_cents") is not None:
|
|
423
|
+
try:
|
|
424
|
+
after = round(int(row["balance_after_cents"]) / 100.0, 2)
|
|
425
|
+
except (TypeError, ValueError):
|
|
426
|
+
after = row.get("balance_after_cents")
|
|
427
|
+
reason = str(row.get("reason") or "")[:18]
|
|
428
|
+
ref = f"{row.get('ref_type') or ''}:{row.get('ref_id') or ''}".strip(":")
|
|
429
|
+
typer.echo(f"{created:<22} {delta!s:>10} {after!s:>10} {reason:<18} {ref}")
|
|
430
|
+
except GsparkError as e:
|
|
431
|
+
typer.secho(f"{e.code}: {e}" if e.code else str(e), fg=typer.colors.RED, err=True)
|
|
432
|
+
raise typer.Exit(1) from e
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def main() -> None:
|
|
436
|
+
app()
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
if __name__ == "__main__":
|
|
440
|
+
main()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Shared CLI helpers (no Typer dependency — easy to unit-test)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def coerce_value(v: str) -> Any:
|
|
11
|
+
s = v.strip()
|
|
12
|
+
if s.lower() in {"true", "false"}:
|
|
13
|
+
return s.lower() == "true"
|
|
14
|
+
if s.lower() == "null":
|
|
15
|
+
return None
|
|
16
|
+
if (s.startswith("[") and s.endswith("]")) or (s.startswith("{") and s.endswith("}")):
|
|
17
|
+
try:
|
|
18
|
+
return json.loads(s)
|
|
19
|
+
except json.JSONDecodeError:
|
|
20
|
+
return v
|
|
21
|
+
try:
|
|
22
|
+
if "." in s:
|
|
23
|
+
return float(s)
|
|
24
|
+
return int(s)
|
|
25
|
+
except ValueError:
|
|
26
|
+
return v
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def ensure_idempotency_key(key: str) -> str:
|
|
30
|
+
"""If empty, mint a UUID for this call. Retries must pass the same key explicitly."""
|
|
31
|
+
return (key or "").strip() or str(uuid.uuid4())
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def dumps_payload(data: Any, *, as_json: bool) -> str:
|
|
35
|
+
"""--json → compact one-line; default → indented."""
|
|
36
|
+
if as_json:
|
|
37
|
+
return json.dumps(data, ensure_ascii=False, separators=(",", ":"))
|
|
38
|
+
return json.dumps(data, ensure_ascii=False, indent=2)
|