tinet-clink2-cli 0.0.1__tar.gz → 0.0.2__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.
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/MANIFEST.in +1 -1
- {tinet_clink2_cli-0.0.1/tinet_clink2_cli.egg-info → tinet_clink2_cli-0.0.2}/PKG-INFO +31 -20
- {tinet_clink2_cli-0.0.1/cli_anything → tinet_clink2_cli-0.0.2}/README.md +28 -17
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/cli_anything/clink2/__init__.py +1 -1
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/cli_anything/clink2/clink2_cli.py +141 -17
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/cli_anything/clink2/core/config.py +9 -1
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/cli_anything/clink2/core/http.py +5 -1
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/cli_anything/clink2/core/logging.py +5 -1
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/pyproject.toml +4 -4
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2/tinet_clink2_cli.egg-info}/PKG-INFO +31 -20
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/tinet_clink2_cli.egg-info/SOURCES.txt +1 -1
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/cli_anything/clink2/core/__init__.py +0 -0
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/cli_anything/clink2/core/auth.py +0 -0
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/setup.cfg +0 -0
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/tinet_clink2_cli.egg-info/dependency_links.txt +0 -0
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/tinet_clink2_cli.egg-info/entry_points.txt +0 -0
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/tinet_clink2_cli.egg-info/requires.txt +0 -0
- {tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/tinet_clink2_cli.egg-info/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tinet-clink2-cli
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary: Clink2 OpenAPI CLI
|
|
5
|
-
Requires-Python: >=3.
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Clink2 OpenAPI CLI
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
Requires-Dist: click>=8.0
|
|
8
8
|
Requires-Dist: requests>=2.28
|
|
@@ -13,24 +13,26 @@ Requires-Dist: build>=1.0; extra == "dev"
|
|
|
13
13
|
|
|
14
14
|
Clink2 命令行客户端,封装 OpenAPI 接口,与 [智慧服务平台开发者中心](https://develop.clink.cn) 中参数与响应一致,适合脚本化集成与 Agent 调用。
|
|
15
15
|
|
|
16
|
-
**当前版本:** `0.0.
|
|
16
|
+
**当前版本:** `0.0.2`
|
|
17
17
|
**PyPI:** https://pypi.org/project/tinet-clink2-cli/
|
|
18
18
|
|
|
19
19
|
## 功能概览
|
|
20
20
|
|
|
21
21
|
| 子命令 | 说明 |
|
|
22
22
|
|--------|------|
|
|
23
|
-
| **`config`** |
|
|
23
|
+
| **`config`** | 本地凭证管理 |
|
|
24
24
|
| **`ivr`** | 语音导航查询:列表、节点列表 |
|
|
25
|
+
| **`voicemail`** | 留言箱设置:查看留言箱列表 |
|
|
25
26
|
|
|
26
27
|
所有出站 HTTP 请求自动附加:
|
|
27
|
-
- `X-Trace-Id: {UUID}
|
|
28
|
+
- `X-Trace-Id: {UUID}`
|
|
29
|
+
- `X-Clink2-CLI-Version: {CLI 版本}`
|
|
28
30
|
|
|
29
31
|
本地默认写入 JSON Lines 日志:`~/.clink2/logs/clink2.log`(单文件最大 10MB,滚动保留 5 个)。
|
|
30
32
|
|
|
31
33
|
## 环境要求
|
|
32
34
|
|
|
33
|
-
- Python **≥ 3.
|
|
35
|
+
- Python **≥ 3.10**
|
|
34
36
|
- 运行时需访问公有云接入地址,例如 `https://api-bj.clink.cn` / `https://api-sh.clink.cn`
|
|
35
37
|
- 需在管理后台获取 AccessKeyId / AccessKeySecret(系统管理 → 安全设置 → 接口密钥)
|
|
36
38
|
|
|
@@ -40,14 +42,14 @@ Clink2 命令行客户端,封装 OpenAPI 接口,与 [智慧服务平台开
|
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
44
|
pip install tinet-clink2-cli
|
|
43
|
-
tinet-clink2-cli --version # 0.0.
|
|
45
|
+
tinet-clink2-cli --version # 0.0.2
|
|
44
46
|
tinet-clink2-cli --help
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
指定版本或升级:
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
|
-
pip install 'tinet-clink2-cli==0.0.
|
|
52
|
+
pip install 'tinet-clink2-cli==0.0.2'
|
|
51
53
|
pip install --upgrade tinet-clink2-cli
|
|
52
54
|
```
|
|
53
55
|
|
|
@@ -61,14 +63,15 @@ pipx install tinet-clink2-cli
|
|
|
61
63
|
|
|
62
64
|
**配置文件:** `~/.clink2/config.json`
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
配置示例:
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
69
|
tinet-clink2-cli config set \
|
|
68
|
-
--
|
|
69
|
-
--
|
|
70
|
+
--accessKeyId your_access_key_id \
|
|
71
|
+
--accessKeySecret your_access_key_secret \
|
|
70
72
|
--endpoint https://api-bj.clink.cn \
|
|
71
|
-
--
|
|
73
|
+
--expires 60 \
|
|
74
|
+
--log true
|
|
72
75
|
|
|
73
76
|
tinet-clink2-cli config show
|
|
74
77
|
```
|
|
@@ -84,26 +87,34 @@ tinet-clink2-cli config show
|
|
|
84
87
|
tinet-clink2-cli config show
|
|
85
88
|
|
|
86
89
|
# ivr
|
|
87
|
-
tinet-clink2-cli ivr list
|
|
88
90
|
tinet-clink2-cli ivr nodes --ivrName your_ivr_name
|
|
89
91
|
tinet-clink2-cli --json ivr list
|
|
90
|
-
tinet-clink2-cli --json ivr nodes --ivrName your_ivr_name
|
|
91
92
|
|
|
92
|
-
# dry-run
|
|
93
|
+
# dry-run:仅校验入参与配置,不调用服务端(是否写日志取决于 config.log)
|
|
93
94
|
tinet-clink2-cli --dry-run ivr list
|
|
94
|
-
tinet-clink2-cli --dry-run
|
|
95
|
+
tinet-clink2-cli --dry-run voicemail list --offset 0 --limit 10
|
|
96
|
+
|
|
97
|
+
# verbose:向 stderr 打印 url / method / 版本 Header / TraceId / result(不含 body)
|
|
98
|
+
tinet-clink2-cli --verbose --json voicemail list
|
|
95
99
|
```
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
全局选项:
|
|
102
|
+
- `--json`:机器可读 stdout
|
|
103
|
+
- `--dry-run`:本地校验与预览,不发请求
|
|
104
|
+
- `--verbose`:stderr 排障
|
|
98
105
|
|
|
99
|
-
##
|
|
106
|
+
## 安全提示
|
|
100
107
|
|
|
101
|
-
|
|
108
|
+
- 勿将 AccessKeySecret 写入仓库、截图或日志外发渠道。
|
|
109
|
+
- `config set --log false` 为用户显式关闭本地审计;关闭后排障将缺少本地 JSON 日志。
|
|
110
|
+
|
|
111
|
+
## 帮助与 API 文档
|
|
102
112
|
|
|
103
113
|
```bash
|
|
104
114
|
tinet-clink2-cli --help
|
|
105
115
|
tinet-clink2-cli config --help
|
|
106
116
|
tinet-clink2-cli ivr --help
|
|
117
|
+
tinet-clink2-cli voicemail --help
|
|
107
118
|
```
|
|
108
119
|
|
|
109
120
|
OpenAPI 契约与字段说明见 [智慧服务平台开发者中心](https://develop.clink.cn)。
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
Clink2 命令行客户端,封装 OpenAPI 接口,与 [智慧服务平台开发者中心](https://develop.clink.cn) 中参数与响应一致,适合脚本化集成与 Agent 调用。
|
|
2
2
|
|
|
3
|
-
**当前版本:** `0.0.
|
|
3
|
+
**当前版本:** `0.0.2`
|
|
4
4
|
**PyPI:** https://pypi.org/project/tinet-clink2-cli/
|
|
5
5
|
|
|
6
6
|
## 功能概览
|
|
7
7
|
|
|
8
8
|
| 子命令 | 说明 |
|
|
9
9
|
|--------|------|
|
|
10
|
-
| **`config`** |
|
|
10
|
+
| **`config`** | 本地凭证管理 |
|
|
11
11
|
| **`ivr`** | 语音导航查询:列表、节点列表 |
|
|
12
|
+
| **`voicemail`** | 留言箱设置:查看留言箱列表 |
|
|
12
13
|
|
|
13
14
|
所有出站 HTTP 请求自动附加:
|
|
14
|
-
- `X-Trace-Id: {UUID}
|
|
15
|
+
- `X-Trace-Id: {UUID}`
|
|
16
|
+
- `X-Clink2-CLI-Version: {CLI 版本}`
|
|
15
17
|
|
|
16
18
|
本地默认写入 JSON Lines 日志:`~/.clink2/logs/clink2.log`(单文件最大 10MB,滚动保留 5 个)。
|
|
17
19
|
|
|
18
20
|
## 环境要求
|
|
19
21
|
|
|
20
|
-
- Python **≥ 3.
|
|
22
|
+
- Python **≥ 3.10**
|
|
21
23
|
- 运行时需访问公有云接入地址,例如 `https://api-bj.clink.cn` / `https://api-sh.clink.cn`
|
|
22
24
|
- 需在管理后台获取 AccessKeyId / AccessKeySecret(系统管理 → 安全设置 → 接口密钥)
|
|
23
25
|
|
|
@@ -27,14 +29,14 @@ Clink2 命令行客户端,封装 OpenAPI 接口,与 [智慧服务平台开
|
|
|
27
29
|
|
|
28
30
|
```bash
|
|
29
31
|
pip install tinet-clink2-cli
|
|
30
|
-
tinet-clink2-cli --version # 0.0.
|
|
32
|
+
tinet-clink2-cli --version # 0.0.2
|
|
31
33
|
tinet-clink2-cli --help
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
指定版本或升级:
|
|
35
37
|
|
|
36
38
|
```bash
|
|
37
|
-
pip install 'tinet-clink2-cli==0.0.
|
|
39
|
+
pip install 'tinet-clink2-cli==0.0.2'
|
|
38
40
|
pip install --upgrade tinet-clink2-cli
|
|
39
41
|
```
|
|
40
42
|
|
|
@@ -48,14 +50,15 @@ pipx install tinet-clink2-cli
|
|
|
48
50
|
|
|
49
51
|
**配置文件:** `~/.clink2/config.json`
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
配置示例:
|
|
52
54
|
|
|
53
55
|
```bash
|
|
54
56
|
tinet-clink2-cli config set \
|
|
55
|
-
--
|
|
56
|
-
--
|
|
57
|
+
--accessKeyId your_access_key_id \
|
|
58
|
+
--accessKeySecret your_access_key_secret \
|
|
57
59
|
--endpoint https://api-bj.clink.cn \
|
|
58
|
-
--
|
|
60
|
+
--expires 60 \
|
|
61
|
+
--log true
|
|
59
62
|
|
|
60
63
|
tinet-clink2-cli config show
|
|
61
64
|
```
|
|
@@ -71,26 +74,34 @@ tinet-clink2-cli config show
|
|
|
71
74
|
tinet-clink2-cli config show
|
|
72
75
|
|
|
73
76
|
# ivr
|
|
74
|
-
tinet-clink2-cli ivr list
|
|
75
77
|
tinet-clink2-cli ivr nodes --ivrName your_ivr_name
|
|
76
78
|
tinet-clink2-cli --json ivr list
|
|
77
|
-
tinet-clink2-cli --json ivr nodes --ivrName your_ivr_name
|
|
78
79
|
|
|
79
|
-
# dry-run
|
|
80
|
+
# dry-run:仅校验入参与配置,不调用服务端(是否写日志取决于 config.log)
|
|
80
81
|
tinet-clink2-cli --dry-run ivr list
|
|
81
|
-
tinet-clink2-cli --dry-run
|
|
82
|
+
tinet-clink2-cli --dry-run voicemail list --offset 0 --limit 10
|
|
83
|
+
|
|
84
|
+
# verbose:向 stderr 打印 url / method / 版本 Header / TraceId / result(不含 body)
|
|
85
|
+
tinet-clink2-cli --verbose --json voicemail list
|
|
82
86
|
```
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
全局选项:
|
|
89
|
+
- `--json`:机器可读 stdout
|
|
90
|
+
- `--dry-run`:本地校验与预览,不发请求
|
|
91
|
+
- `--verbose`:stderr 排障
|
|
85
92
|
|
|
86
|
-
##
|
|
93
|
+
## 安全提示
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
- 勿将 AccessKeySecret 写入仓库、截图或日志外发渠道。
|
|
96
|
+
- `config set --log false` 为用户显式关闭本地审计;关闭后排障将缺少本地 JSON 日志。
|
|
97
|
+
|
|
98
|
+
## 帮助与 API 文档
|
|
89
99
|
|
|
90
100
|
```bash
|
|
91
101
|
tinet-clink2-cli --help
|
|
92
102
|
tinet-clink2-cli config --help
|
|
93
103
|
tinet-clink2-cli ivr --help
|
|
104
|
+
tinet-clink2-cli voicemail --help
|
|
94
105
|
```
|
|
95
106
|
|
|
96
107
|
OpenAPI 契约与字段说明见 [智慧服务平台开发者中心](https://develop.clink.cn)。
|
|
@@ -12,23 +12,29 @@ import click
|
|
|
12
12
|
from cli_anything.clink2 import __version__
|
|
13
13
|
from cli_anything.clink2.core import config as cfg
|
|
14
14
|
from cli_anything.clink2.core.http import Clink2Client
|
|
15
|
-
from cli_anything.clink2.core.logging import write_log_record
|
|
15
|
+
from cli_anything.clink2.core.logging import redact_endpoint, write_log_record
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@click.group()
|
|
19
19
|
@click.version_option(
|
|
20
20
|
version=__version__,
|
|
21
21
|
prog_name="tinet-clink2-cli",
|
|
22
|
-
message="%(prog)s,version %(version)s,released on 2026-07-
|
|
22
|
+
message="%(prog)s,version %(version)s,released on 2026-07-21",
|
|
23
23
|
)
|
|
24
24
|
@click.option("--json", "as_json", is_flag=True, help="以 JSON 格式输出结果")
|
|
25
25
|
@click.option("--dry-run", is_flag=True, help="仅校验入参与配置,不调用服务端接口")
|
|
26
|
+
@click.option(
|
|
27
|
+
"--verbose",
|
|
28
|
+
is_flag=True,
|
|
29
|
+
help="向 stderr 打印排障信息(url、method、X-Clink2-CLI-Version、X-Trace-Id、result,不含 body)",
|
|
30
|
+
)
|
|
26
31
|
@click.pass_context
|
|
27
|
-
def cli(ctx: click.Context, as_json: bool, dry_run: bool) -> None:
|
|
32
|
+
def cli(ctx: click.Context, as_json: bool, dry_run: bool, verbose: bool) -> None:
|
|
28
33
|
"""Clink2 OpenAPI 命令行工具"""
|
|
29
34
|
ctx.ensure_object(dict)
|
|
30
35
|
ctx.obj["as_json"] = as_json
|
|
31
36
|
ctx.obj["dry_run"] = dry_run
|
|
37
|
+
ctx.obj["verbose"] = verbose
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
@cli.group()
|
|
@@ -38,13 +44,13 @@ def config() -> None:
|
|
|
38
44
|
|
|
39
45
|
@config.command("set")
|
|
40
46
|
@click.option(
|
|
41
|
-
"--
|
|
47
|
+
"--accessKeyId",
|
|
42
48
|
"access_key_id",
|
|
43
49
|
required=True,
|
|
44
50
|
help="访问密钥 ID",
|
|
45
51
|
)
|
|
46
52
|
@click.option(
|
|
47
|
-
"--
|
|
53
|
+
"--accessKeySecret",
|
|
48
54
|
"access_key_secret",
|
|
49
55
|
required=True,
|
|
50
56
|
help="访问密钥 Secret",
|
|
@@ -56,20 +62,34 @@ def config() -> None:
|
|
|
56
62
|
help="API 接入地址(公有云北京/上海等)",
|
|
57
63
|
)
|
|
58
64
|
@click.option(
|
|
59
|
-
"--
|
|
65
|
+
"--expires",
|
|
60
66
|
"expires",
|
|
61
67
|
default=cfg.DEFAULT_EXPIRES,
|
|
62
68
|
show_default=True,
|
|
63
69
|
type=int,
|
|
64
70
|
help="签名有效时间,单位秒(与 OpenAPI 公共参数 Expires 一致,1–86400)",
|
|
65
71
|
)
|
|
66
|
-
|
|
72
|
+
@click.option(
|
|
73
|
+
"--log",
|
|
74
|
+
type=click.Choice(["true", "false"], case_sensitive=False),
|
|
75
|
+
default="true",
|
|
76
|
+
show_default=True,
|
|
77
|
+
help="是否写入本地 JSON 日志文件(true|false)",
|
|
78
|
+
)
|
|
79
|
+
def config_set(
|
|
80
|
+
access_key_id: str,
|
|
81
|
+
access_key_secret: str,
|
|
82
|
+
endpoint: str,
|
|
83
|
+
expires: int,
|
|
84
|
+
log: str,
|
|
85
|
+
) -> None:
|
|
67
86
|
"""将凭据保存到 ~/.clink2/config.json"""
|
|
68
87
|
path = cfg.save_config(
|
|
69
88
|
access_key_id=access_key_id,
|
|
70
89
|
access_key_secret=access_key_secret,
|
|
71
90
|
endpoint=endpoint,
|
|
72
91
|
expires=expires,
|
|
92
|
+
log=(log.lower() == "true"),
|
|
73
93
|
)
|
|
74
94
|
click.echo(f"配置已保存到 {path}")
|
|
75
95
|
|
|
@@ -86,6 +106,7 @@ def config_show() -> None:
|
|
|
86
106
|
"AccessKeySecret": cfg.mask_secret(data["access_key_secret"]),
|
|
87
107
|
"endpoint": data.get("endpoint", cfg.DEFAULT_ENDPOINT),
|
|
88
108
|
"Expires": data.get("expires", cfg.DEFAULT_EXPIRES),
|
|
109
|
+
"log": data.get("log", cfg.DEFAULT_LOG),
|
|
89
110
|
}
|
|
90
111
|
click.echo(json.dumps(display, ensure_ascii=False, indent=2))
|
|
91
112
|
|
|
@@ -108,6 +129,43 @@ def _emit(ctx: click.Context, payload: Any, *, exit_code: int = 0) -> None:
|
|
|
108
129
|
ctx.exit(exit_code)
|
|
109
130
|
|
|
110
131
|
|
|
132
|
+
def _openapi_business_result(body: Any, *, transport_ok: bool) -> str:
|
|
133
|
+
"""从 OpenAPI 响应体解析业务状态码 result。
|
|
134
|
+
|
|
135
|
+
平台约定(如座席详细信息获取 Wiki):``0`` 成功,``-1`` 失败(常伴随
|
|
136
|
+
``errorCode`` / ``description``)。若响应体未带 ``result`` 字段(部分新版
|
|
137
|
+
CC 列表接口仅返回 ``requestId`` + 业务数据),则:传输成功记 ``0``,否则 ``-1``。
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
body: Parsed JSON body (dict/list/other).
|
|
141
|
+
transport_ok: Whether HTTP layer succeeded (2xx).
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Business result as string, e.g. ``"0"`` or ``"-1"``.
|
|
145
|
+
"""
|
|
146
|
+
if isinstance(body, dict) and body.get("result") is not None:
|
|
147
|
+
return str(body["result"])
|
|
148
|
+
return "0" if transport_ok else "-1"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _emit_verbose(url: str, version: str, trace_id: str, method: str, result: str) -> None:
|
|
152
|
+
"""向 stderr 打印排障信息(不含 body)。
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
url: Request URL (Signature should already be redacted by caller or here).
|
|
156
|
+
version: CLI version string for X-Clink2-CLI-Version.
|
|
157
|
+
trace_id: Client trace id.
|
|
158
|
+
method: HTTP method such as GET or POST.
|
|
159
|
+
result: OpenAPI 业务状态码(响应体 result;0 成功,-1 失败)。
|
|
160
|
+
"""
|
|
161
|
+
safe_url = redact_endpoint(url)
|
|
162
|
+
click.echo(f"url={safe_url}", err=True)
|
|
163
|
+
click.echo(f"method={method.upper()}", err=True)
|
|
164
|
+
click.echo(f"X-Clink2-CLI-Version={version}", err=True)
|
|
165
|
+
click.echo(f"X-Trace-Id={trace_id}", err=True)
|
|
166
|
+
click.echo(f"result={result}", err=True)
|
|
167
|
+
|
|
168
|
+
|
|
111
169
|
def _run_api(
|
|
112
170
|
ctx: click.Context,
|
|
113
171
|
command: str,
|
|
@@ -115,15 +173,21 @@ def _run_api(
|
|
|
115
173
|
path: str,
|
|
116
174
|
params: Optional[dict[str, Any]] = None,
|
|
117
175
|
) -> None:
|
|
118
|
-
"""执行 API 调用或 dry-run
|
|
176
|
+
"""执行 API 调用或 dry-run,并按配置写本地日志"""
|
|
119
177
|
started = time.perf_counter()
|
|
120
178
|
conf = _load_or_fail()
|
|
121
179
|
endpoint = conf["endpoint"].rstrip("/")
|
|
122
180
|
full_url = f"{endpoint}{path}"
|
|
123
181
|
trace_id = str(uuid.uuid4())
|
|
124
182
|
dry_run = bool(ctx.obj.get("dry_run"))
|
|
183
|
+
verbose = bool(ctx.obj.get("verbose"))
|
|
184
|
+
log_enabled = bool(conf.get("log", True))
|
|
185
|
+
http_method = method.upper()
|
|
125
186
|
|
|
126
187
|
if dry_run:
|
|
188
|
+
# 无 OpenAPI 响应体:本地校验通过按业务成功 0 记录
|
|
189
|
+
if verbose:
|
|
190
|
+
_emit_verbose(full_url, __version__, trace_id, http_method, "0")
|
|
127
191
|
duration_ms = int((time.perf_counter() - started) * 1000)
|
|
128
192
|
write_log_record(
|
|
129
193
|
command=command,
|
|
@@ -136,10 +200,11 @@ def _run_api(
|
|
|
136
200
|
error_message=None,
|
|
137
201
|
dry_run=True,
|
|
138
202
|
request_sent=False,
|
|
203
|
+
enabled=log_enabled,
|
|
139
204
|
)
|
|
140
205
|
payload = {
|
|
141
206
|
"dry_run": "ok",
|
|
142
|
-
"method":
|
|
207
|
+
"method": http_method,
|
|
143
208
|
"endpoint": full_url,
|
|
144
209
|
"params": params or {},
|
|
145
210
|
"trace_id": trace_id,
|
|
@@ -154,38 +219,59 @@ def _run_api(
|
|
|
154
219
|
expires=int(conf.get("expires", cfg.DEFAULT_EXPIRES)),
|
|
155
220
|
)
|
|
156
221
|
try:
|
|
157
|
-
body, meta = client.request(
|
|
222
|
+
body, meta = client.request(http_method, path, params=params or {}, trace_id=trace_id)
|
|
158
223
|
duration_ms = meta["duration_ms"]
|
|
159
224
|
status = meta["http_status"]
|
|
160
|
-
|
|
225
|
+
http_ok = 200 <= int(status) < 300
|
|
226
|
+
result = _openapi_business_result(body, transport_ok=http_ok)
|
|
227
|
+
business_ok = result == "0"
|
|
228
|
+
if verbose:
|
|
229
|
+
_emit_verbose(
|
|
230
|
+
meta.get("url") or full_url,
|
|
231
|
+
__version__,
|
|
232
|
+
meta["trace_id"],
|
|
233
|
+
http_method,
|
|
234
|
+
result,
|
|
235
|
+
)
|
|
161
236
|
write_log_record(
|
|
162
237
|
command=command,
|
|
163
238
|
duration_ms=duration_ms,
|
|
164
239
|
endpoint=meta.get("url") or full_url,
|
|
165
240
|
trace_id=meta["trace_id"],
|
|
166
241
|
http_status=status,
|
|
167
|
-
result=
|
|
168
|
-
error_type=None if
|
|
169
|
-
error_message=
|
|
242
|
+
result=result,
|
|
243
|
+
error_type=None if (http_ok and business_ok) else ("http_error" if not http_ok else "business_error"),
|
|
244
|
+
error_message=(
|
|
245
|
+
None
|
|
246
|
+
if (http_ok and business_ok)
|
|
247
|
+
else (f"HTTP {status}" if not http_ok else f"OpenAPI result={result}")
|
|
248
|
+
),
|
|
249
|
+
enabled=log_enabled,
|
|
170
250
|
)
|
|
171
|
-
if not
|
|
251
|
+
if not http_ok:
|
|
172
252
|
err = {"error_type": "http_error", "error_message": f"HTTP {status}", "data": body}
|
|
173
253
|
_emit(ctx, err, exit_code=1)
|
|
174
254
|
return
|
|
255
|
+
if not business_ok:
|
|
256
|
+
_emit(ctx, body, exit_code=1)
|
|
257
|
+
return
|
|
175
258
|
_emit(ctx, body)
|
|
176
259
|
except click.exceptions.Exit:
|
|
177
260
|
raise
|
|
178
261
|
except Exception as exc: # noqa: BLE001
|
|
179
262
|
duration_ms = int((time.perf_counter() - started) * 1000)
|
|
263
|
+
if verbose:
|
|
264
|
+
_emit_verbose(full_url, __version__, trace_id, http_method, "-1")
|
|
180
265
|
write_log_record(
|
|
181
266
|
command=command,
|
|
182
267
|
duration_ms=duration_ms,
|
|
183
268
|
endpoint=full_url,
|
|
184
269
|
trace_id=trace_id,
|
|
185
270
|
http_status=None,
|
|
186
|
-
result="1",
|
|
271
|
+
result="-1",
|
|
187
272
|
error_type=type(exc).__name__,
|
|
188
273
|
error_message=str(exc),
|
|
274
|
+
enabled=log_enabled,
|
|
189
275
|
)
|
|
190
276
|
err = {"error_type": type(exc).__name__, "error_message": str(exc)}
|
|
191
277
|
if ctx.obj.get("as_json"):
|
|
@@ -212,7 +298,7 @@ def ivr_list(ctx: click.Context) -> None:
|
|
|
212
298
|
"--ivrName",
|
|
213
299
|
"ivr_name",
|
|
214
300
|
required=True,
|
|
215
|
-
help="
|
|
301
|
+
help="语音导航名称",
|
|
216
302
|
)
|
|
217
303
|
@click.pass_context
|
|
218
304
|
def ivr_nodes(ctx: click.Context, ivr_name: str) -> None:
|
|
@@ -220,5 +306,43 @@ def ivr_nodes(ctx: click.Context, ivr_name: str) -> None:
|
|
|
220
306
|
_run_api(ctx, "ivr nodes", "GET", "/cc/list_ivr_nodes", params={"ivrName": ivr_name})
|
|
221
307
|
|
|
222
308
|
|
|
309
|
+
@cli.group()
|
|
310
|
+
def voicemail() -> None:
|
|
311
|
+
"""留言箱设置相关查询命令"""
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@voicemail.command("list")
|
|
315
|
+
@click.option(
|
|
316
|
+
"--offset",
|
|
317
|
+
default=0,
|
|
318
|
+
show_default=True,
|
|
319
|
+
type=int,
|
|
320
|
+
help="偏移量,范围 0-99990。默认值为 0。注:limit + offset 不允许超过 100000",
|
|
321
|
+
)
|
|
322
|
+
@click.option(
|
|
323
|
+
"--limit",
|
|
324
|
+
default=10,
|
|
325
|
+
show_default=True,
|
|
326
|
+
type=int,
|
|
327
|
+
help="查询条数,范围 10-100。默认值为 10。注:limit + offset 不允许超过 100000",
|
|
328
|
+
)
|
|
329
|
+
@click.pass_context
|
|
330
|
+
def voicemail_list(ctx: click.Context, offset: int, limit: int) -> None:
|
|
331
|
+
"""查看留言箱列表"""
|
|
332
|
+
if offset < 0 or offset > 99990:
|
|
333
|
+
raise click.ClickException("offset 超出范围 0-99990")
|
|
334
|
+
if limit < 10 or limit > 100:
|
|
335
|
+
raise click.ClickException("limit 超出范围 10-100")
|
|
336
|
+
if limit + offset > 100000:
|
|
337
|
+
raise click.ClickException("limit + offset 不允许超过 100000")
|
|
338
|
+
_run_api(
|
|
339
|
+
ctx,
|
|
340
|
+
"voicemail list",
|
|
341
|
+
"GET",
|
|
342
|
+
"/cc/list_voice_mails",
|
|
343
|
+
params={"offset": offset, "limit": limit},
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
223
347
|
if __name__ == "__main__":
|
|
224
348
|
cli()
|
|
@@ -9,6 +9,7 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
DEFAULT_ENDPOINT = "https://api-bj.clink.cn"
|
|
11
11
|
DEFAULT_EXPIRES = 60
|
|
12
|
+
DEFAULT_LOG = True
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def config_dir() -> Path:
|
|
@@ -42,6 +43,7 @@ def save_config(
|
|
|
42
43
|
access_key_secret: str,
|
|
43
44
|
endpoint: str = DEFAULT_ENDPOINT,
|
|
44
45
|
expires: int = DEFAULT_EXPIRES,
|
|
46
|
+
log: bool = DEFAULT_LOG,
|
|
45
47
|
) -> Path:
|
|
46
48
|
"""Persist credentials and endpoint to ~/.clink2/config.json.
|
|
47
49
|
|
|
@@ -50,6 +52,7 @@ def save_config(
|
|
|
50
52
|
access_key_secret: AccessKeySecret.
|
|
51
53
|
endpoint: API base URL.
|
|
52
54
|
expires: Signature expires seconds.
|
|
55
|
+
log: Whether to write local JSON log files.
|
|
53
56
|
|
|
54
57
|
Returns:
|
|
55
58
|
Path to the written config file.
|
|
@@ -62,6 +65,7 @@ def save_config(
|
|
|
62
65
|
"access_key_secret": access_key_secret,
|
|
63
66
|
"endpoint": endpoint or DEFAULT_ENDPOINT,
|
|
64
67
|
"expires": int(expires) if expires is not None else DEFAULT_EXPIRES,
|
|
68
|
+
"log": bool(log),
|
|
65
69
|
}
|
|
66
70
|
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
67
71
|
os.chmod(path, 0o600)
|
|
@@ -76,7 +80,7 @@ def load_config() -> dict[str, Any]:
|
|
|
76
80
|
|
|
77
81
|
Raises:
|
|
78
82
|
FileNotFoundError: If config file is missing.
|
|
79
|
-
ValueError: If required fields are missing.
|
|
83
|
+
ValueError: If required fields are missing or log is not a boolean.
|
|
80
84
|
"""
|
|
81
85
|
path = config_path()
|
|
82
86
|
if not path.exists():
|
|
@@ -87,4 +91,8 @@ def load_config() -> dict[str, Any]:
|
|
|
87
91
|
raise ValueError(f"配置缺少必填字段: {key}")
|
|
88
92
|
data.setdefault("endpoint", DEFAULT_ENDPOINT)
|
|
89
93
|
data.setdefault("expires", DEFAULT_EXPIRES)
|
|
94
|
+
if "log" not in data:
|
|
95
|
+
data["log"] = DEFAULT_LOG
|
|
96
|
+
elif not isinstance(data["log"], bool):
|
|
97
|
+
raise ValueError("配置字段 log 必须为 JSON boolean(true/false)")
|
|
90
98
|
return data
|
|
@@ -9,6 +9,7 @@ from urllib.parse import urlparse
|
|
|
9
9
|
|
|
10
10
|
import requests
|
|
11
11
|
|
|
12
|
+
from cli_anything.clink2 import __version__
|
|
12
13
|
from cli_anything.clink2.core.auth import sign_request
|
|
13
14
|
|
|
14
15
|
|
|
@@ -91,7 +92,10 @@ class Clink2Client:
|
|
|
91
92
|
method=method.upper(),
|
|
92
93
|
url=url,
|
|
93
94
|
params=query,
|
|
94
|
-
headers={
|
|
95
|
+
headers={
|
|
96
|
+
"X-Trace-Id": tid,
|
|
97
|
+
"X-Clink2-CLI-Version": __version__,
|
|
98
|
+
},
|
|
95
99
|
timeout=30,
|
|
96
100
|
verify=True,
|
|
97
101
|
)
|
|
@@ -78,6 +78,7 @@ def write_log_record(
|
|
|
78
78
|
error_message: Optional[str] = None,
|
|
79
79
|
dry_run: bool = False,
|
|
80
80
|
request_sent: Optional[bool] = None,
|
|
81
|
+
enabled: bool = True,
|
|
81
82
|
) -> dict[str, Any]:
|
|
82
83
|
"""Append one JSON log record to the rotating log file.
|
|
83
84
|
|
|
@@ -92,9 +93,10 @@ def write_log_record(
|
|
|
92
93
|
error_message: Optional error message.
|
|
93
94
|
dry_run: Whether this invocation was --dry-run.
|
|
94
95
|
request_sent: Whether an HTTP request was sent. Only written for dry-run.
|
|
96
|
+
enabled: When False, skip writing to the log file (still return the record).
|
|
95
97
|
|
|
96
98
|
Returns:
|
|
97
|
-
The record that was written.
|
|
99
|
+
The record that was written (or would have been written).
|
|
98
100
|
"""
|
|
99
101
|
now = datetime.now().astimezone()
|
|
100
102
|
record: dict[str, Any] = {
|
|
@@ -115,6 +117,8 @@ def write_log_record(
|
|
|
115
117
|
if dry_run:
|
|
116
118
|
record["dry_run"] = True
|
|
117
119
|
record["request_sent"] = False if request_sent is None else request_sent
|
|
120
|
+
if not enabled:
|
|
121
|
+
return record
|
|
118
122
|
logger = _ensure_handler()
|
|
119
123
|
logger.info(json.dumps(record, ensure_ascii=False))
|
|
120
124
|
return record
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tinet-clink2-cli"
|
|
7
|
-
version = "0.0.
|
|
8
|
-
description = "Clink2 OpenAPI CLI
|
|
9
|
-
readme = "
|
|
10
|
-
requires-python = ">=3.
|
|
7
|
+
version = "0.0.2"
|
|
8
|
+
description = "Clink2 OpenAPI CLI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
dependencies = [
|
|
12
12
|
"click>=8.0",
|
|
13
13
|
"requests>=2.28",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tinet-clink2-cli
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary: Clink2 OpenAPI CLI
|
|
5
|
-
Requires-Python: >=3.
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Clink2 OpenAPI CLI
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
Requires-Dist: click>=8.0
|
|
8
8
|
Requires-Dist: requests>=2.28
|
|
@@ -13,24 +13,26 @@ Requires-Dist: build>=1.0; extra == "dev"
|
|
|
13
13
|
|
|
14
14
|
Clink2 命令行客户端,封装 OpenAPI 接口,与 [智慧服务平台开发者中心](https://develop.clink.cn) 中参数与响应一致,适合脚本化集成与 Agent 调用。
|
|
15
15
|
|
|
16
|
-
**当前版本:** `0.0.
|
|
16
|
+
**当前版本:** `0.0.2`
|
|
17
17
|
**PyPI:** https://pypi.org/project/tinet-clink2-cli/
|
|
18
18
|
|
|
19
19
|
## 功能概览
|
|
20
20
|
|
|
21
21
|
| 子命令 | 说明 |
|
|
22
22
|
|--------|------|
|
|
23
|
-
| **`config`** |
|
|
23
|
+
| **`config`** | 本地凭证管理 |
|
|
24
24
|
| **`ivr`** | 语音导航查询:列表、节点列表 |
|
|
25
|
+
| **`voicemail`** | 留言箱设置:查看留言箱列表 |
|
|
25
26
|
|
|
26
27
|
所有出站 HTTP 请求自动附加:
|
|
27
|
-
- `X-Trace-Id: {UUID}
|
|
28
|
+
- `X-Trace-Id: {UUID}`
|
|
29
|
+
- `X-Clink2-CLI-Version: {CLI 版本}`
|
|
28
30
|
|
|
29
31
|
本地默认写入 JSON Lines 日志:`~/.clink2/logs/clink2.log`(单文件最大 10MB,滚动保留 5 个)。
|
|
30
32
|
|
|
31
33
|
## 环境要求
|
|
32
34
|
|
|
33
|
-
- Python **≥ 3.
|
|
35
|
+
- Python **≥ 3.10**
|
|
34
36
|
- 运行时需访问公有云接入地址,例如 `https://api-bj.clink.cn` / `https://api-sh.clink.cn`
|
|
35
37
|
- 需在管理后台获取 AccessKeyId / AccessKeySecret(系统管理 → 安全设置 → 接口密钥)
|
|
36
38
|
|
|
@@ -40,14 +42,14 @@ Clink2 命令行客户端,封装 OpenAPI 接口,与 [智慧服务平台开
|
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
44
|
pip install tinet-clink2-cli
|
|
43
|
-
tinet-clink2-cli --version # 0.0.
|
|
45
|
+
tinet-clink2-cli --version # 0.0.2
|
|
44
46
|
tinet-clink2-cli --help
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
指定版本或升级:
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
|
-
pip install 'tinet-clink2-cli==0.0.
|
|
52
|
+
pip install 'tinet-clink2-cli==0.0.2'
|
|
51
53
|
pip install --upgrade tinet-clink2-cli
|
|
52
54
|
```
|
|
53
55
|
|
|
@@ -61,14 +63,15 @@ pipx install tinet-clink2-cli
|
|
|
61
63
|
|
|
62
64
|
**配置文件:** `~/.clink2/config.json`
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
配置示例:
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
69
|
tinet-clink2-cli config set \
|
|
68
|
-
--
|
|
69
|
-
--
|
|
70
|
+
--accessKeyId your_access_key_id \
|
|
71
|
+
--accessKeySecret your_access_key_secret \
|
|
70
72
|
--endpoint https://api-bj.clink.cn \
|
|
71
|
-
--
|
|
73
|
+
--expires 60 \
|
|
74
|
+
--log true
|
|
72
75
|
|
|
73
76
|
tinet-clink2-cli config show
|
|
74
77
|
```
|
|
@@ -84,26 +87,34 @@ tinet-clink2-cli config show
|
|
|
84
87
|
tinet-clink2-cli config show
|
|
85
88
|
|
|
86
89
|
# ivr
|
|
87
|
-
tinet-clink2-cli ivr list
|
|
88
90
|
tinet-clink2-cli ivr nodes --ivrName your_ivr_name
|
|
89
91
|
tinet-clink2-cli --json ivr list
|
|
90
|
-
tinet-clink2-cli --json ivr nodes --ivrName your_ivr_name
|
|
91
92
|
|
|
92
|
-
# dry-run
|
|
93
|
+
# dry-run:仅校验入参与配置,不调用服务端(是否写日志取决于 config.log)
|
|
93
94
|
tinet-clink2-cli --dry-run ivr list
|
|
94
|
-
tinet-clink2-cli --dry-run
|
|
95
|
+
tinet-clink2-cli --dry-run voicemail list --offset 0 --limit 10
|
|
96
|
+
|
|
97
|
+
# verbose:向 stderr 打印 url / method / 版本 Header / TraceId / result(不含 body)
|
|
98
|
+
tinet-clink2-cli --verbose --json voicemail list
|
|
95
99
|
```
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
全局选项:
|
|
102
|
+
- `--json`:机器可读 stdout
|
|
103
|
+
- `--dry-run`:本地校验与预览,不发请求
|
|
104
|
+
- `--verbose`:stderr 排障
|
|
98
105
|
|
|
99
|
-
##
|
|
106
|
+
## 安全提示
|
|
100
107
|
|
|
101
|
-
|
|
108
|
+
- 勿将 AccessKeySecret 写入仓库、截图或日志外发渠道。
|
|
109
|
+
- `config set --log false` 为用户显式关闭本地审计;关闭后排障将缺少本地 JSON 日志。
|
|
110
|
+
|
|
111
|
+
## 帮助与 API 文档
|
|
102
112
|
|
|
103
113
|
```bash
|
|
104
114
|
tinet-clink2-cli --help
|
|
105
115
|
tinet-clink2-cli config --help
|
|
106
116
|
tinet-clink2-cli ivr --help
|
|
117
|
+
tinet-clink2-cli voicemail --help
|
|
107
118
|
```
|
|
108
119
|
|
|
109
120
|
OpenAPI 契约与字段说明见 [智慧服务平台开发者中心](https://develop.clink.cn)。
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/tinet_clink2_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{tinet_clink2_cli-0.0.1 → tinet_clink2_cli-0.0.2}/tinet_clink2_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|