tytcli 0.2.0__py3-none-any.whl
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.
- tytcli/__init__.py +7 -0
- tytcli/cli.py +174 -0
- tytcli/client.py +832 -0
- tytcli/commands/__init__.py +52 -0
- tytcli/commands/auth.py +147 -0
- tytcli/commands/config.py +61 -0
- tytcli/commands/env.py +103 -0
- tytcli/commands/help.py +105 -0
- tytcli/commands/roadshow.py +898 -0
- tytcli/commands/sentiment.py +200 -0
- tytcli/commands/shared.py +210 -0
- tytcli/commands/upgrade.py +95 -0
- tytcli/commands/wechat.py +253 -0
- tytcli/commands/whoami.py +76 -0
- tytcli/config.py +578 -0
- tytcli/errors.py +71 -0
- tytcli/output.py +82 -0
- tytcli/selfupdate.py +639 -0
- tytcli/skill/SKILL.md +122 -0
- tytcli/skill/references/output-contract.md +93 -0
- tytcli/skill/references/roadshow.md +176 -0
- tytcli/skill/references/selfupdate.md +62 -0
- tytcli/skill/references/sentiment.md +68 -0
- tytcli/skill/references/setup.md +105 -0
- tytcli/skill/references/wechat.md +87 -0
- tytcli/skill/references/whoami.md +42 -0
- tytcli/skill.py +118 -0
- tytcli-0.2.0.dist-info/METADATA +437 -0
- tytcli-0.2.0.dist-info/RECORD +32 -0
- tytcli-0.2.0.dist-info/WHEEL +4 -0
- tytcli-0.2.0.dist-info/entry_points.txt +2 -0
- tytcli-0.2.0.dist-info/licenses/LICENSE.txt +16 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<!-- tytcli skill reference. Read this file only when the task needs it; -->
|
|
2
|
+
<!-- SKILL.md is the routing surface and stays small on purpose. -->
|
|
3
|
+
|
|
4
|
+
# tytcli:公众号文章
|
|
5
|
+
|
|
6
|
+
`wechat articles` / `wechat article` 的用法、精简输出与踩坑点。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 公众号文章
|
|
11
|
+
|
|
12
|
+
> ⚠️ **该模块并非在所有环境都已部署。**在未部署的环境上调用会得到 HTTP 404,
|
|
13
|
+
> 错误的 `details.hint` 会说明“模块可能未部署”(`exit 1`)。那**不是**路由拼错,
|
|
14
|
+
> 重试也不会成功——报错的 `error.details` 带 `env` 与 `base_url`,据此确认当前生效的环境。
|
|
15
|
+
|
|
16
|
+
### 列文章
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
tytcli wechat articles # 全部文章,第一页 20 条
|
|
20
|
+
tytcli wechat articles --page-size 5 # 每页 5 条(1–100)
|
|
21
|
+
tytcli wechat articles --page 2 # 第二页
|
|
22
|
+
tytcli wechat articles --fake-id <ID> # 只看某个公众号
|
|
23
|
+
tytcli wechat articles --view SUBSCRIBED # 我的订阅(或 FAVORITE 我的收藏)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**默认输出是精简索引**,每条只给五个字段:
|
|
27
|
+
`title`、`nickname`、`summary`、`articleId`、`publishTime`。
|
|
28
|
+
|
|
29
|
+
这是故意的:20 条原样输出有 66.8 KiB,而其中 74% 是 `cleanBody`(文章正文)——
|
|
30
|
+
正文应该是详情命令的职责,不该在列表里重复 20 份。
|
|
31
|
+
|
|
32
|
+
输出里的 `__tytcli_hint__` 会告诉你**本次服务端实际还返回了哪些字段**,以及怎么取:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tytcli wechat articles --fields title,digest,articleUrl # 自选字段
|
|
36
|
+
tytcli wechat articles --full # 全量原样(约 67 KiB)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`--fields` 与 `--full` 互斥。请求了服务端没返回的字段时,输出里会给出
|
|
40
|
+
`unknown_fields` 和 `available_fields`,不会静默丢掉。
|
|
41
|
+
|
|
42
|
+
### 看详情
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
tytcli wechat article --article-id <64位十六进制>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`articleId` 直接从列表输出里拿。详情**默认就是全量原样**(单篇约 14 KiB),含正文
|
|
49
|
+
`body` 以及 `viewpoints`、`paragraphsJson`、`topicsJson`、`keywordsJson` 等分析字段。
|
|
50
|
+
|
|
51
|
+
### 三个容易踩的坑
|
|
52
|
+
|
|
53
|
+
1. **不要用标题定位文章。**服务端其实也接受 `identity`
|
|
54
|
+
(`{publishTime, account, title}` 三元组),但本命令**故意不提供**这个入口:
|
|
55
|
+
标题是长中文字符串且必须逐字节原样,实测尾部多一个空格就会得到
|
|
56
|
+
`文章不存在或已失效`。始终用 `articleId`。
|
|
57
|
+
2. **`articleId` 和 `articleKey` 是同一个值,但不能互换。**详情接口只认
|
|
58
|
+
`articleId` 这个参数名;拿同一个值传 `articleKey` 会得到
|
|
59
|
+
`文章 identity 参数不完整或超长`。
|
|
60
|
+
3. **`total` 不等于当页条数。**总数看 `data.total`,今日新增看 `data.todayCount`,
|
|
61
|
+
不要从 `records` 长度推断。
|
|
62
|
+
|
|
63
|
+
## 典型管道
|
|
64
|
+
|
|
65
|
+
先看今天有哪些文章(只要标题和时间):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
tytcli wechat articles --fields title,publishTime,nickname --page-size 20
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
拿第一篇文章的完整详情:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
tytcli wechat articles --page-size 1 | jq -r '.data.records[0].articleId' | xargs -I{} tytcli wechat article --article-id {}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
只要正文,不要分析字段:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
tytcli wechat article --article-id <ID> | jq -r '.data.body'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
先读 hint 再决定要不要加 `--fields`(适合不确定有哪些字段时):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
tytcli wechat articles --page-size 1 | jq -r '.__tytcli_hint__'
|
|
87
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!-- tytcli skill reference. Read this file only when the task needs it; -->
|
|
2
|
+
<!-- SKILL.md is the routing surface and stays small on purpose. -->
|
|
3
|
+
|
|
4
|
+
# tytcli:whoami 与身份查询
|
|
5
|
+
|
|
6
|
+
登录身份、权限菜单树,以及常用的 jq 组合。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 常用组合
|
|
11
|
+
|
|
12
|
+
只取身份摘要:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
tytcli whoami | jq '.identity'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
查看当前账号被授权了哪些功能模块:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
tytcli whoami | jq -r '.data.Data.Menus[].MenuName'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
列出全部可访问路由(含二级菜单):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
tytcli whoami | jq -r '.data.Data.Menus[] | .Url, ((.SubMenuList // [])[].Url)'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
菜单名与路由的对应关系:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
tytcli whoami | jq -r '.data.Data.Menus[] | .SubMenuList // [] | .[] | "\(.MenuName)\t\(.Url)"'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
只用退出码判断登录态,不关心内容:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
if tytcli whoami >/dev/null 2>&1; then echo "已登录"; else echo "未登录或凭证失效"; fi
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`Menus` 里的是**前端路由**,可以用来了解这个账号被授权了哪些功能模块,但不能直接当成 API 路径使用。
|
tytcli/skill.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""读取随包发布的 skill 文档。
|
|
2
|
+
|
|
3
|
+
skill 在仓库里位于 ``skills/tytcli/``,开发期 pi 从那里发现它。hatchling 的
|
|
4
|
+
``force-include``(见 ``pyproject.toml``)把同一批文件复制进 wheel 的
|
|
5
|
+
``tytcli/skill/``,于是**已安装**的 tytcli 也能通过 ``tytcli help`` 打印自己的
|
|
6
|
+
文档。本模块是唯一的读取者:``importlib.resources`` 在 wheel、zip 和 editable
|
|
7
|
+
安装下都能工作。
|
|
8
|
+
|
|
9
|
+
坚持单一来源很重要:文档若存在两处就必然漂移,而包内那一份才是用户真正看到的。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import importlib.resources as resources
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
#: 顶层命令名 -> 记录它的参考文档。空键代表根命令,它的文档就是路由面本身。
|
|
18
|
+
COMMAND_DOCS: dict[str, str] = {
|
|
19
|
+
"": "SKILL.md",
|
|
20
|
+
"whoami": "references/whoami.md",
|
|
21
|
+
"auth": "references/setup.md",
|
|
22
|
+
"config": "references/setup.md",
|
|
23
|
+
"upgrade": "references/selfupdate.md",
|
|
24
|
+
"wechat": "references/wechat.md",
|
|
25
|
+
"sentiment": "references/sentiment.md",
|
|
26
|
+
"roadshow": "references/roadshow.md",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#: 不是命令、但自己拥有一份参考文档的主题,所以即使没有 ``output`` 命令,
|
|
30
|
+
#: ``tytcli help output`` 也能用。
|
|
31
|
+
EXTRA_TOPICS: dict[str, str] = {
|
|
32
|
+
"output": "references/output-contract.md",
|
|
33
|
+
"setup": "references/setup.md",
|
|
34
|
+
"selfupdate": "references/selfupdate.md",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
SKILL_FILENAME = "SKILL.md"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def read_skill_doc(relative: str) -> str | None:
|
|
41
|
+
"""返回一份随包文档;缺失时返回 ``None``。
|
|
42
|
+
|
|
43
|
+
``relative`` 是 skill 目录内的路径,例如 ``SKILL.md`` 或
|
|
44
|
+
``references/wechat.md``。文件缺失在这里**不算错误**:由调用方决定是降级
|
|
45
|
+
(某个命令还没有文档)还是大声失败(用户显式请求的主题)。
|
|
46
|
+
|
|
47
|
+
先读打包副本(``tytcli/skill/``,wheel 与 sdist 里都有)。打包副本不存在时
|
|
48
|
+
回退到仓库的 ``skills/tytcli/``——那正是 editable / 开发安装的情形:那里
|
|
49
|
+
force-include 映射没有跑过,若没有这层回退,``tytcli help`` 对用户可用、
|
|
50
|
+
对正在改文档的人却不可用。在 wheel 里打包副本永远优先,所以两者不可能对
|
|
51
|
+
"用户看到什么"产生分歧。
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
traversable = resources.files("tytcli").joinpath("skill", *relative.split("/"))
|
|
55
|
+
return traversable.read_text(encoding="utf-8")
|
|
56
|
+
except (FileNotFoundError, ModuleNotFoundError, OSError):
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
# editable 安装:src/tytcli/<本文件> -> 仓库根是 parents[2]。
|
|
60
|
+
repo_candidate = Path(__file__).resolve().parents[2] / "skills" / "tytcli" / relative
|
|
61
|
+
try:
|
|
62
|
+
return repo_candidate.read_text(encoding="utf-8")
|
|
63
|
+
except OSError:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def strip_frontmatter(text: str) -> str:
|
|
68
|
+
"""去掉 YAML frontmatter——pi 需要它,终端不需要。
|
|
69
|
+
|
|
70
|
+
``SKILL.md`` 以 ``---\\nname: …\\ndescription: …\\n---`` 开头。原样打印会让
|
|
71
|
+
输出以两行在 shell 里毫无意义的路由元数据开场。
|
|
72
|
+
"""
|
|
73
|
+
if not text.startswith("---\n"):
|
|
74
|
+
return text
|
|
75
|
+
end = text.find("\n---\n", 4)
|
|
76
|
+
if end == -1:
|
|
77
|
+
return text
|
|
78
|
+
return text[end + len("\n---\n"):].lstrip("\n")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def doc_for_topic(topic: str) -> tuple[str, str] | None:
|
|
82
|
+
"""把 ``tytcli help <topic>`` 的参数解析成 ``(相对路径, 正文)``。
|
|
83
|
+
|
|
84
|
+
``None`` 表示主题未知;命令会把它变成一个列出全部合法主题的用法错误。
|
|
85
|
+
"""
|
|
86
|
+
relative = COMMAND_DOCS.get(topic) or EXTRA_TOPICS.get(topic)
|
|
87
|
+
if relative is None:
|
|
88
|
+
return None
|
|
89
|
+
text = read_skill_doc(relative)
|
|
90
|
+
if text is None:
|
|
91
|
+
return None
|
|
92
|
+
return relative, text
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def available_topics() -> list[str]:
|
|
96
|
+
"""所有可接受的 ``help`` 主题:先命令,再额外主题。"""
|
|
97
|
+
ordered = [name for name in COMMAND_DOCS if name]
|
|
98
|
+
ordered += [name for name in EXTRA_TOPICS if name not in ordered]
|
|
99
|
+
return ordered
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def pointer_line(topic: str) -> str:
|
|
103
|
+
"""追加到每个命令 ``--help`` 末尾的那一行指针。"""
|
|
104
|
+
if topic:
|
|
105
|
+
return f"完整文档:tytcli help {topic}"
|
|
106
|
+
return "完整文档:tytcli help"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
__all__ = [
|
|
110
|
+
"COMMAND_DOCS",
|
|
111
|
+
"EXTRA_TOPICS",
|
|
112
|
+
"SKILL_FILENAME",
|
|
113
|
+
"available_topics",
|
|
114
|
+
"doc_for_topic",
|
|
115
|
+
"pointer_line",
|
|
116
|
+
"read_skill_doc",
|
|
117
|
+
"strip_frontmatter",
|
|
118
|
+
]
|