yuque-cli 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.
- yuque_cli-0.1.0/.claude/settings.local.json +16 -0
- yuque_cli-0.1.0/.gitignore +5 -0
- yuque_cli-0.1.0/.python-version +1 -0
- yuque_cli-0.1.0/.vscode/extensions.json +6 -0
- yuque_cli-0.1.0/.vscode/settings.json +8 -0
- yuque_cli-0.1.0/AGENTS.md +3 -0
- yuque_cli-0.1.0/CLAUDE.md +1 -0
- yuque_cli-0.1.0/CONTEXT.md +29 -0
- yuque_cli-0.1.0/PKG-INFO +190 -0
- yuque_cli-0.1.0/README.md +180 -0
- yuque_cli-0.1.0/docs/adr/0001-use-internal-web-api-with-cookie-auth.md +14 -0
- yuque_cli-0.1.0/docs/adr/0002-cookie-acquisition-cdp-with-manual-fallback.md +21 -0
- yuque_cli-0.1.0/docs/adr/0003-doc-update-fetch-then-merge.md +16 -0
- yuque_cli-0.1.0/docs/internal-api.md +76 -0
- yuque_cli-0.1.0/main.py +6 -0
- yuque_cli-0.1.0/mise.toml +29 -0
- yuque_cli-0.1.0/pyproject.toml +36 -0
- yuque_cli-0.1.0/src/yuque_cli/__init__.py +3 -0
- yuque_cli-0.1.0/src/yuque_cli/__main__.py +4 -0
- yuque_cli-0.1.0/src/yuque_cli/appdata.py +39 -0
- yuque_cli-0.1.0/src/yuque_cli/auth.py +153 -0
- yuque_cli-0.1.0/src/yuque_cli/cli.py +355 -0
- yuque_cli-0.1.0/src/yuque_cli/client.py +199 -0
- yuque_cli-0.1.0/src/yuque_cli/config.py +31 -0
- yuque_cli-0.1.0/src/yuque_cli/errors.py +17 -0
- yuque_cli-0.1.0/src/yuque_cli/inputs.py +35 -0
- yuque_cli-0.1.0/src/yuque_cli/output.py +63 -0
- yuque_cli-0.1.0/src/yuque_cli/session.py +69 -0
- yuque_cli-0.1.0/src/yuque_cli/urls.py +63 -0
- yuque_cli-0.1.0/test/test_appdata.py +53 -0
- yuque_cli-0.1.0/test/test_auth.py +113 -0
- yuque_cli-0.1.0/test/test_cli.py +297 -0
- yuque_cli-0.1.0/test/test_client.py +179 -0
- yuque_cli-0.1.0/test/test_config.py +42 -0
- yuque_cli-0.1.0/test/test_inputs.py +43 -0
- yuque_cli-0.1.0/test/test_output.py +41 -0
- yuque_cli-0.1.0/test/test_session.py +78 -0
- yuque_cli-0.1.0/test/test_urls.py +84 -0
- yuque_cli-0.1.0/uv.lock +367 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"mcp__chrome-devtools__new_page",
|
|
5
|
+
"Bash(uv run *)",
|
|
6
|
+
"mcp__context7__resolve-library-id",
|
|
7
|
+
"mcp__context7__query-docs",
|
|
8
|
+
"Bash(uv pip *)",
|
|
9
|
+
"Bash(uv add *)",
|
|
10
|
+
"Bash(ps -p 46455 -o command=)",
|
|
11
|
+
"Bash(curl --noproxy '*' -sS -m 3 -i http://127.0.0.1:9222/)",
|
|
12
|
+
"Bash(curl --noproxy '*' -sS -m 3 http://127.0.0.1:9222/json)",
|
|
13
|
+
"Bash(curl --noproxy '*' -sS -m 3 http://127.0.0.1:9222/json/list)"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 语雀 CLI (yuque-cli)
|
|
2
|
+
|
|
3
|
+
针对语雀(含企业版)的通用命令行客户端:以浏览器 cookie 认证、驱动语雀内部 web 接口,提供文档与评论的增删改查。默认连接 `www.yuque.com`,企业实例用环境变量 `YUQUE_HOST` 指定。
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
|
|
7
|
+
**知识库 (Book)**:
|
|
8
|
+
语雀中承载一组文档的容器,对应网页端的「知识库」。内部 API 中称 `book`(本工具采用此英文名);官方开放 API 中称 `repo`。
|
|
9
|
+
_Avoid_: Repo, 仓库, 文档库
|
|
10
|
+
|
|
11
|
+
**文档 (Doc)**:
|
|
12
|
+
知识库内的一篇文档,是本工具 CRUD 的核心对象。
|
|
13
|
+
_Avoid_: 文章, Article, Page
|
|
14
|
+
|
|
15
|
+
**评论 (Comment)**:
|
|
16
|
+
挂在某一篇文档上的评论。
|
|
17
|
+
_Avoid_: 回复, Reply
|
|
18
|
+
|
|
19
|
+
**login**:
|
|
20
|
+
用户或团队的唯一标识,位于 URL 第一段(`<host>/{login}/...`)。可指个人用户,也可指团队,二者在 URL 中形态一致。
|
|
21
|
+
_Avoid_: username, 账号, owner
|
|
22
|
+
|
|
23
|
+
**namespace**:
|
|
24
|
+
定位一个知识库的路径,形如 `{login}/{book}`(语雀官方术语),即知识库的浏览器地址(book URL)。用户可直接用此 URL 指定一个知识库;文档地址在其后再加 `/{slug}`(doc URL)。两种 URL 去掉 host 的路径形态与完整 URL 等价;数字 id 不属于用户语汇,仅内部解析时使用。
|
|
25
|
+
_Avoid_: 把单独的 login 误当作 namespace(URL 第一段只是 login,不是完整 namespace)
|
|
26
|
+
|
|
27
|
+
**slug**:
|
|
28
|
+
文档在其知识库内的可读标识,位于 URL 末段。一篇文档的完整地址即 `{login}/{book}/{slug}`,也就是其浏览器 URL。
|
|
29
|
+
_Avoid_: 短链, 别名
|
yuque_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yuque-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 针对企业语雀实例的命令行客户端:cookie 认证,驱动内部 web 接口,提供文档与评论的 CRUD
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: httpx>=0.27
|
|
7
|
+
Requires-Dist: typer>=0.12
|
|
8
|
+
Requires-Dist: websocket-client>=1.9.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# yuque-cli
|
|
12
|
+
|
|
13
|
+
面向语雀(含企业版)的通用命令行客户端:以浏览器 cookie 认证,驱动语雀内部 web 接口,提供文档(doc)与评论(comment)的增删改查。默认连接 `www.yuque.com`,企业实例用环境变量 `YUQUE_HOST` 指定。
|
|
14
|
+
|
|
15
|
+
> 术语(知识库/文档/评论/login/namespace/slug)见 [CONTEXT.md](./CONTEXT.md);关键设计取舍见 [docs/adr](./docs/adr)。
|
|
16
|
+
|
|
17
|
+
## 特性
|
|
18
|
+
|
|
19
|
+
- **复用浏览器登录态**:默认探测本地 Chrome 远程调试(CDP)自动抓取 cookie,无需手动复制;失败可回退手动粘贴。
|
|
20
|
+
- **干净的 Markdown 输出**:`doc get` 默认输出纯 Markdown,便于管道与落盘;`--json` 输出含 Lake 正文的完整详情。
|
|
21
|
+
- **先取后合并的更新**:`doc update` 只改你指定的字段,其余保持原值。
|
|
22
|
+
- **脚本友好**:全局 `--json` 输出原始 JSON,`-y` 跳过确认,正文支持 `--body`/`--file`/管道 stdin。
|
|
23
|
+
|
|
24
|
+
## 安装
|
|
25
|
+
|
|
26
|
+
需要 Python ≥ 3.10 与 [uv](https://docs.astral.sh/uv/)。
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv sync # 安装依赖
|
|
30
|
+
uv run yuque --help # 在项目内运行
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
或作为工具安装,得到全局 `yuque` 命令:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv tool install .
|
|
37
|
+
yuque --help
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 快速开始
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
yuque auth login # 登录(自动 CDP,失败回退手动粘贴)
|
|
44
|
+
yuque auth status # 查看当前身份
|
|
45
|
+
yuque book list # 列出我的知识库
|
|
46
|
+
yuque doc list <login>/<book> # 列出知识库下的文档
|
|
47
|
+
yuque doc get <login>/<book>/<slug> # 取文档正文(Markdown)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`<login>/<book>/<slug>` 既可写成去掉 host 的路径形态,也可直接粘贴完整浏览器 URL(见 [URL 形态](#url-形态))。
|
|
51
|
+
|
|
52
|
+
## 认证
|
|
53
|
+
|
|
54
|
+
### 自动(CDP)
|
|
55
|
+
|
|
56
|
+
`yuque auth login` 默认尝试从**你当前的 Chrome**(已登录目标语雀站点)直接抓取 cookie。前提是 Chrome 以远程调试端口启动:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 完全退出 Chrome 后,带远程调试重新启动(默认 profile 即可),再登录你的语雀站点
|
|
60
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
随后 `yuque auth login` 会自动连接、抓取目标站点的全部 cookie(含 `_yuque_session` 与 CSRF `yuque_ctoken`)并保存会话。
|
|
64
|
+
|
|
65
|
+
### 手动
|
|
66
|
+
|
|
67
|
+
未检测到远程调试、或自动抓取失败时,会提示粘贴 cookie;也可直接 `yuque auth login --manual` 跳过 CDP。cookie 取自浏览器 DevTools → Application → Cookies,或控制台 `document.cookie`。
|
|
68
|
+
|
|
69
|
+
### 会话与退出
|
|
70
|
+
|
|
71
|
+
- 会话保存在 `~/.config/yuque-cli/session.json`(权限 `0600`)。
|
|
72
|
+
- `yuque auth logout` 清除本地会话。
|
|
73
|
+
|
|
74
|
+
### 代理说明
|
|
75
|
+
|
|
76
|
+
对语雀的 API 请求**默认直连,不读取 `*_proxy` 环境变量**(`trust_env=False`),避免请求被本地代理(如 Clash)拦截,也免去 SOCKS 代理触发 `socksio` 缺失的报错。如果你的网络必须经代理才能访问目标实例,需要移除该参数并引入 `httpx[socks]` 依赖。
|
|
77
|
+
|
|
78
|
+
## 命令参考
|
|
79
|
+
|
|
80
|
+
### 全局选项
|
|
81
|
+
|
|
82
|
+
置于子命令之前,如 `yuque --json book list`。
|
|
83
|
+
|
|
84
|
+
| 选项 | 说明 |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `--json` | 输出原始 JSON(便于脚本解析) |
|
|
87
|
+
| `-y`, `--yes` | 跳过所有确认(删除等危险操作) |
|
|
88
|
+
| `-v`, `--verbose` | 打印内部 HTTP 请求到 stderr |
|
|
89
|
+
| `-V`, `--version` | 显示版本 |
|
|
90
|
+
|
|
91
|
+
### auth — 登录态管理
|
|
92
|
+
|
|
93
|
+
| 命令 | 说明 |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| `auth login [--manual]` | 登录;默认探测 CDP 自动获取,`--manual` 直接手动粘贴 |
|
|
96
|
+
| `auth status` | 显示当前登录身份(`GET /api/mine`) |
|
|
97
|
+
| `auth logout` | 清除本地会话 |
|
|
98
|
+
|
|
99
|
+
### book — 知识库
|
|
100
|
+
|
|
101
|
+
| 命令 | 说明 |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `book list` | 列出我的知识库(`GET /api/mine/books`) |
|
|
104
|
+
|
|
105
|
+
### doc — 文档
|
|
106
|
+
|
|
107
|
+
| 命令 | 说明 |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `doc list <login>/<book>` | 列出知识库下的文档 |
|
|
110
|
+
| `doc get <login>/<book>/<slug>` | 取正文;默认干净 Markdown,`--json` 出详情(含 Lake) |
|
|
111
|
+
| `doc create <login>/<book> -t <标题> [选项]` | 在知识库中创建文档 |
|
|
112
|
+
| `doc update <login>/<book>/<slug> [选项]` | 更新文档(先取后合并,未给的字段保持原值) |
|
|
113
|
+
| `doc delete <login>/<book>/<slug>` | 删除文档(软删,可在回收站恢复) |
|
|
114
|
+
|
|
115
|
+
`doc create` 选项:`-t/--title`(必填)、`--slug`(省略则自动生成)、`--public/--private`、`-b/--body`、`-F/--file`。
|
|
116
|
+
|
|
117
|
+
`doc update` 选项:`-t/--title`、`--public/--private`、`-b/--body`、`-F/--file`;至少给其一,否则报错。
|
|
118
|
+
|
|
119
|
+
可见性 `--public/--private`:不给则创建时继承知识库默认、更新时保持原值。
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# 创建(内联正文)
|
|
123
|
+
yuque doc create your-team/handbook -t "上手指南" --body "# 标题\n正文"
|
|
124
|
+
|
|
125
|
+
# 从文件创建,并设为私有
|
|
126
|
+
yuque doc create your-team/handbook -t "周报" -F report.md --private
|
|
127
|
+
|
|
128
|
+
# 仅改标题(正文与可见性保持不变)
|
|
129
|
+
yuque doc update your-team/handbook/getting-started -t "新标题"
|
|
130
|
+
|
|
131
|
+
# 删除(非交互环境需 -y)
|
|
132
|
+
yuque -y doc delete your-team/handbook/old-draft
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### comment — 评论
|
|
136
|
+
|
|
137
|
+
| 命令 | 说明 |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `comment list <login>/<book>/<slug>` | 列出文档评论 |
|
|
140
|
+
| `comment add <login>/<book>/<slug> [-b/--body \| -F/--file]` | 添加评论(正文不能为空) |
|
|
141
|
+
| `comment delete <comment_id>` | 删除一条评论(id 取自 `comment list`) |
|
|
142
|
+
|
|
143
|
+
## URL 形态
|
|
144
|
+
|
|
145
|
+
凡接受文档/知识库地址的命令,既可用**完整浏览器 URL**,也可用**去掉 host 的路径形态**,二者等价:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
https://<host>/<login>/<book>/<slug> ←→ <login>/<book>/<slug> (文档)
|
|
149
|
+
https://<host>/<login>/<book> ←→ <login>/<book> (知识库)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
数字 id 不属于用户语汇,仅内部解析时使用。
|
|
153
|
+
|
|
154
|
+
## 正文输入
|
|
155
|
+
|
|
156
|
+
`doc create` / `comment add` 的正文按以下优先级取值(`--body` 与 `--file` 互斥):
|
|
157
|
+
|
|
158
|
+
1. `-b/--body`(内联字符串)
|
|
159
|
+
2. `-F/--file`(从文件读;`-F -` 表示读 stdin)
|
|
160
|
+
3. 管道 stdin(直接 `... | yuque doc create ...`)
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
echo "# 来自管道的正文" | yuque doc create your-team/handbook -t "标题"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`doc update` 略有不同:只有显式给 `-b/--body` 或 `-F/--file`(含 `-F -` 读 stdin)才会改正文;**裸管道不会被读取**——不给正文来源即表示保持原正文不变。
|
|
167
|
+
|
|
168
|
+
## 环境变量
|
|
169
|
+
|
|
170
|
+
| 变量 | 作用 | 默认 |
|
|
171
|
+
| --- | --- | --- |
|
|
172
|
+
| `YUQUE_HOST` | 目标语雀 host | `www.yuque.com` |
|
|
173
|
+
| `YUQUE_CONFIG_DIR` | 配置/会话目录 | `$XDG_CONFIG_HOME/yuque-cli` 或 `~/.config/yuque-cli` |
|
|
174
|
+
| `XDG_CONFIG_HOME` | 配置根目录 | `~/.config` |
|
|
175
|
+
| `NO_COLOR` | 设置后禁用彩色输出 | — |
|
|
176
|
+
|
|
177
|
+
## 开发
|
|
178
|
+
|
|
179
|
+
本项目遵循 TDD,并用 [mise](https://mise.jdx.dev/) 管理工具链(锁定 uv)与常用任务。
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
mise run deps # 安装依赖(uv sync)
|
|
183
|
+
mise run test # 运行测试(pytest)
|
|
184
|
+
mise run lint # ruff check && ruff format
|
|
185
|
+
mise run check # ty 类型检查
|
|
186
|
+
mise run cli -- --help # 运行 CLI(-- 之后为 yuque 的参数)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- 源码:`src/yuque_cli/`;测试:`test/`。
|
|
190
|
+
- 设计文档:术语表 [CONTEXT.md](./CONTEXT.md)、决策记录 [docs/adr](./docs/adr)、内部接口速查 [docs/internal-api.md](./docs/internal-api.md)。
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# yuque-cli
|
|
2
|
+
|
|
3
|
+
面向语雀(含企业版)的通用命令行客户端:以浏览器 cookie 认证,驱动语雀内部 web 接口,提供文档(doc)与评论(comment)的增删改查。默认连接 `www.yuque.com`,企业实例用环境变量 `YUQUE_HOST` 指定。
|
|
4
|
+
|
|
5
|
+
> 术语(知识库/文档/评论/login/namespace/slug)见 [CONTEXT.md](./CONTEXT.md);关键设计取舍见 [docs/adr](./docs/adr)。
|
|
6
|
+
|
|
7
|
+
## 特性
|
|
8
|
+
|
|
9
|
+
- **复用浏览器登录态**:默认探测本地 Chrome 远程调试(CDP)自动抓取 cookie,无需手动复制;失败可回退手动粘贴。
|
|
10
|
+
- **干净的 Markdown 输出**:`doc get` 默认输出纯 Markdown,便于管道与落盘;`--json` 输出含 Lake 正文的完整详情。
|
|
11
|
+
- **先取后合并的更新**:`doc update` 只改你指定的字段,其余保持原值。
|
|
12
|
+
- **脚本友好**:全局 `--json` 输出原始 JSON,`-y` 跳过确认,正文支持 `--body`/`--file`/管道 stdin。
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
需要 Python ≥ 3.10 与 [uv](https://docs.astral.sh/uv/)。
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv sync # 安装依赖
|
|
20
|
+
uv run yuque --help # 在项目内运行
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
或作为工具安装,得到全局 `yuque` 命令:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv tool install .
|
|
27
|
+
yuque --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 快速开始
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
yuque auth login # 登录(自动 CDP,失败回退手动粘贴)
|
|
34
|
+
yuque auth status # 查看当前身份
|
|
35
|
+
yuque book list # 列出我的知识库
|
|
36
|
+
yuque doc list <login>/<book> # 列出知识库下的文档
|
|
37
|
+
yuque doc get <login>/<book>/<slug> # 取文档正文(Markdown)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`<login>/<book>/<slug>` 既可写成去掉 host 的路径形态,也可直接粘贴完整浏览器 URL(见 [URL 形态](#url-形态))。
|
|
41
|
+
|
|
42
|
+
## 认证
|
|
43
|
+
|
|
44
|
+
### 自动(CDP)
|
|
45
|
+
|
|
46
|
+
`yuque auth login` 默认尝试从**你当前的 Chrome**(已登录目标语雀站点)直接抓取 cookie。前提是 Chrome 以远程调试端口启动:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 完全退出 Chrome 后,带远程调试重新启动(默认 profile 即可),再登录你的语雀站点
|
|
50
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
随后 `yuque auth login` 会自动连接、抓取目标站点的全部 cookie(含 `_yuque_session` 与 CSRF `yuque_ctoken`)并保存会话。
|
|
54
|
+
|
|
55
|
+
### 手动
|
|
56
|
+
|
|
57
|
+
未检测到远程调试、或自动抓取失败时,会提示粘贴 cookie;也可直接 `yuque auth login --manual` 跳过 CDP。cookie 取自浏览器 DevTools → Application → Cookies,或控制台 `document.cookie`。
|
|
58
|
+
|
|
59
|
+
### 会话与退出
|
|
60
|
+
|
|
61
|
+
- 会话保存在 `~/.config/yuque-cli/session.json`(权限 `0600`)。
|
|
62
|
+
- `yuque auth logout` 清除本地会话。
|
|
63
|
+
|
|
64
|
+
### 代理说明
|
|
65
|
+
|
|
66
|
+
对语雀的 API 请求**默认直连,不读取 `*_proxy` 环境变量**(`trust_env=False`),避免请求被本地代理(如 Clash)拦截,也免去 SOCKS 代理触发 `socksio` 缺失的报错。如果你的网络必须经代理才能访问目标实例,需要移除该参数并引入 `httpx[socks]` 依赖。
|
|
67
|
+
|
|
68
|
+
## 命令参考
|
|
69
|
+
|
|
70
|
+
### 全局选项
|
|
71
|
+
|
|
72
|
+
置于子命令之前,如 `yuque --json book list`。
|
|
73
|
+
|
|
74
|
+
| 选项 | 说明 |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `--json` | 输出原始 JSON(便于脚本解析) |
|
|
77
|
+
| `-y`, `--yes` | 跳过所有确认(删除等危险操作) |
|
|
78
|
+
| `-v`, `--verbose` | 打印内部 HTTP 请求到 stderr |
|
|
79
|
+
| `-V`, `--version` | 显示版本 |
|
|
80
|
+
|
|
81
|
+
### auth — 登录态管理
|
|
82
|
+
|
|
83
|
+
| 命令 | 说明 |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `auth login [--manual]` | 登录;默认探测 CDP 自动获取,`--manual` 直接手动粘贴 |
|
|
86
|
+
| `auth status` | 显示当前登录身份(`GET /api/mine`) |
|
|
87
|
+
| `auth logout` | 清除本地会话 |
|
|
88
|
+
|
|
89
|
+
### book — 知识库
|
|
90
|
+
|
|
91
|
+
| 命令 | 说明 |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `book list` | 列出我的知识库(`GET /api/mine/books`) |
|
|
94
|
+
|
|
95
|
+
### doc — 文档
|
|
96
|
+
|
|
97
|
+
| 命令 | 说明 |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| `doc list <login>/<book>` | 列出知识库下的文档 |
|
|
100
|
+
| `doc get <login>/<book>/<slug>` | 取正文;默认干净 Markdown,`--json` 出详情(含 Lake) |
|
|
101
|
+
| `doc create <login>/<book> -t <标题> [选项]` | 在知识库中创建文档 |
|
|
102
|
+
| `doc update <login>/<book>/<slug> [选项]` | 更新文档(先取后合并,未给的字段保持原值) |
|
|
103
|
+
| `doc delete <login>/<book>/<slug>` | 删除文档(软删,可在回收站恢复) |
|
|
104
|
+
|
|
105
|
+
`doc create` 选项:`-t/--title`(必填)、`--slug`(省略则自动生成)、`--public/--private`、`-b/--body`、`-F/--file`。
|
|
106
|
+
|
|
107
|
+
`doc update` 选项:`-t/--title`、`--public/--private`、`-b/--body`、`-F/--file`;至少给其一,否则报错。
|
|
108
|
+
|
|
109
|
+
可见性 `--public/--private`:不给则创建时继承知识库默认、更新时保持原值。
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 创建(内联正文)
|
|
113
|
+
yuque doc create your-team/handbook -t "上手指南" --body "# 标题\n正文"
|
|
114
|
+
|
|
115
|
+
# 从文件创建,并设为私有
|
|
116
|
+
yuque doc create your-team/handbook -t "周报" -F report.md --private
|
|
117
|
+
|
|
118
|
+
# 仅改标题(正文与可见性保持不变)
|
|
119
|
+
yuque doc update your-team/handbook/getting-started -t "新标题"
|
|
120
|
+
|
|
121
|
+
# 删除(非交互环境需 -y)
|
|
122
|
+
yuque -y doc delete your-team/handbook/old-draft
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### comment — 评论
|
|
126
|
+
|
|
127
|
+
| 命令 | 说明 |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `comment list <login>/<book>/<slug>` | 列出文档评论 |
|
|
130
|
+
| `comment add <login>/<book>/<slug> [-b/--body \| -F/--file]` | 添加评论(正文不能为空) |
|
|
131
|
+
| `comment delete <comment_id>` | 删除一条评论(id 取自 `comment list`) |
|
|
132
|
+
|
|
133
|
+
## URL 形态
|
|
134
|
+
|
|
135
|
+
凡接受文档/知识库地址的命令,既可用**完整浏览器 URL**,也可用**去掉 host 的路径形态**,二者等价:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
https://<host>/<login>/<book>/<slug> ←→ <login>/<book>/<slug> (文档)
|
|
139
|
+
https://<host>/<login>/<book> ←→ <login>/<book> (知识库)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
数字 id 不属于用户语汇,仅内部解析时使用。
|
|
143
|
+
|
|
144
|
+
## 正文输入
|
|
145
|
+
|
|
146
|
+
`doc create` / `comment add` 的正文按以下优先级取值(`--body` 与 `--file` 互斥):
|
|
147
|
+
|
|
148
|
+
1. `-b/--body`(内联字符串)
|
|
149
|
+
2. `-F/--file`(从文件读;`-F -` 表示读 stdin)
|
|
150
|
+
3. 管道 stdin(直接 `... | yuque doc create ...`)
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
echo "# 来自管道的正文" | yuque doc create your-team/handbook -t "标题"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`doc update` 略有不同:只有显式给 `-b/--body` 或 `-F/--file`(含 `-F -` 读 stdin)才会改正文;**裸管道不会被读取**——不给正文来源即表示保持原正文不变。
|
|
157
|
+
|
|
158
|
+
## 环境变量
|
|
159
|
+
|
|
160
|
+
| 变量 | 作用 | 默认 |
|
|
161
|
+
| --- | --- | --- |
|
|
162
|
+
| `YUQUE_HOST` | 目标语雀 host | `www.yuque.com` |
|
|
163
|
+
| `YUQUE_CONFIG_DIR` | 配置/会话目录 | `$XDG_CONFIG_HOME/yuque-cli` 或 `~/.config/yuque-cli` |
|
|
164
|
+
| `XDG_CONFIG_HOME` | 配置根目录 | `~/.config` |
|
|
165
|
+
| `NO_COLOR` | 设置后禁用彩色输出 | — |
|
|
166
|
+
|
|
167
|
+
## 开发
|
|
168
|
+
|
|
169
|
+
本项目遵循 TDD,并用 [mise](https://mise.jdx.dev/) 管理工具链(锁定 uv)与常用任务。
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
mise run deps # 安装依赖(uv sync)
|
|
173
|
+
mise run test # 运行测试(pytest)
|
|
174
|
+
mise run lint # ruff check && ruff format
|
|
175
|
+
mise run check # ty 类型检查
|
|
176
|
+
mise run cli -- --help # 运行 CLI(-- 之后为 yuque 的参数)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- 源码:`src/yuque_cli/`;测试:`test/`。
|
|
180
|
+
- 设计文档:术语表 [CONTEXT.md](./CONTEXT.md)、决策记录 [docs/adr](./docs/adr)、内部接口速查 [docs/internal-api.md](./docs/internal-api.md)。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# 使用语雀内部 web API + cookie 认证,而非官方 Open API v2
|
|
2
|
+
|
|
3
|
+
目标企业语雀实例(企业版)禁用了个人 Token 生成,而官方 Open API v2 以 `X-Auth-Token` 认证,在禁 token 的实例上基本不可用;此外 v2 **完全没有评论接口**(Doc 对象上只有 `commentsCount` 计数)。因此本 CLI 直接驱动语雀网页端 SPA 所用的内部 `/api` 接口,以浏览器会话 cookie(`_yuque_session`)认证,写操作再带 CSRF(cookie 中的 `ctoken` ↔ 请求头 `x-csrf-token`)。
|
|
4
|
+
|
|
5
|
+
## 权衡
|
|
6
|
+
|
|
7
|
+
- **代价**:内部接口未公开、无版本承诺,可能随语雀前端升级而失效。
|
|
8
|
+
- **为何仍选它**:在「禁 token + 需要评论 CRUD」的约束下,这是唯一能同时覆盖文档与评论的路径。真实端点可借助 Chrome remote debugging 观察 SPA 流量扒取并固化。
|
|
9
|
+
|
|
10
|
+
## 后果
|
|
11
|
+
|
|
12
|
+
- 需要一层 CSRF 处理:读操作仅需 cookie,写操作(POST/PUT/DELETE)须额外带 `x-csrf-token`。
|
|
13
|
+
- cookie 失效(401)需可被识别并提示用户重新登录。
|
|
14
|
+
- 企业版 cookie 名可能与公版不同,认证模块不应硬编码假定唯一的 cookie 键名。
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Cookie 获取:DevToolsActivePort 直连浏览器级 WS(抑制 Origin、绕代理)+ 手动粘贴兜底
|
|
2
|
+
|
|
3
|
+
在禁用 token(见 [0001](./0001-use-internal-web-api-with-cookie-auth.md))的前提下,认证依赖浏览器 cookie。`auth login` 默认尝试从**用户当前的 Chrome**(已登录目标站点)抓 cookie,失败再回退手动粘贴。实测在 目标站点 走通的路径如下,三处都踩过坑:
|
|
4
|
+
|
|
5
|
+
1. **发现端点用 `DevToolsActivePort` 文件,而非 `/json` HTTP**。Chrome 136+ 在**默认 profile** 下禁用了 `/json`、`/json/version` 等 HTTP 调试接口(直连返回空 `404`),但 profile 目录下的 `DevToolsActivePort` 文件仍记录「端口 + 浏览器级 WS 路径」(如 `9222` / `/devtools/browser/<uuid>`)。故以该文件为主、`/json/version` 仅作兜底。
|
|
6
|
+
2. **连 WS 必须抑制 `Origin` 头**。Chrome 111+ 默认拒绝带 `Origin` 的 WS 握手(`403`,提示需 `--remote-allow-origins`)。客户端**不发 Origin 头**即可连上,免去用户重启 Chrome 加该 flag。握手实测约 2s,超时要给足(用 5s)。
|
|
7
|
+
3. **本地回环必须绕开代理**。用户常设 `http_proxy/all_proxy`(如 Clash 的 `127.0.0.1:7897`);默认信任环境变量会把对 `127.0.0.1:9222` 的请求也走代理而被拦(404、响应头带 `Proxy-Connection`)。故 HTTP 探测用 `trust_env=False`、WS 用 `http_no_proxy`。
|
|
8
|
+
|
|
9
|
+
连上后调 CDP `Storage.getCookies`,过滤出 目标站点 的全部 cookie(含 httpOnly 的 `_yuque_session` 与 CSRF `yuque_ctoken`),**必须校验确有 `_yuque_session` 才认**。`--manual` 跳过自动直奔手动粘贴。
|
|
10
|
+
|
|
11
|
+
## 被否决的备选
|
|
12
|
+
|
|
13
|
+
- **专属调试 profile + 一次性重登 SSO**:可复现,但要在新 profile 重新登录,违背「复用现有登录态」。
|
|
14
|
+
- **直接解密 Chrome cookie DB(macOS Keychain)**:OS 特定、需关 Chrome、弹 Keychain,且加密随版本收紧有长期失效风险。
|
|
15
|
+
- **要求用户加 `--remote-allow-origins=*` 重启 Chrome**:可行但多一步重启;改用抑制 Origin 头规避。
|
|
16
|
+
|
|
17
|
+
## 后果
|
|
18
|
+
|
|
19
|
+
- CDP 自动化要求用户的 Chrome 以 `--remote-debugging-port=9222` 启动(默认 profile 即可)并已登录目标站点;对 `uvx` 分发的同事不保证人人如此,故**手动粘贴必须始终可用且文档清晰**。
|
|
20
|
+
- 发现/握手/取 cookie 任一环失败都优雅退回手动;`-v` 可打印真实失败原因。
|
|
21
|
+
- 写操作仍需 CSRF:`yuque_ctoken` cookie 值作为请求头 `x-csrf-token`。
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# doc update 采用「先取后合并」而非整体替换
|
|
2
|
+
|
|
3
|
+
语雀内部 `PUT /api/docs/{id}` 接收 `{title, body, format, public}`,外观上是整篇替换;但 discovery spike(见 [internal-api.md](../internal-api.md))只验证了「写得进去」,**未验证省略某字段时该字段会被保留还是被清空**。若实际为整体替换语义,`doc update <doc-url> --title X`(不带正文)就会把正文清空。
|
|
4
|
+
|
|
5
|
+
因此 `doc update` 先 `GET /api/docs/{slug}?book_id=` 取回现有 `title/body/public`,仅用用户本次命令**显式给出**的字段覆盖,再整体 PUT 回去。
|
|
6
|
+
|
|
7
|
+
## 权衡
|
|
8
|
+
|
|
9
|
+
- **代价**:每次更新多一次 GET 往返;且引入「读-改-写」窗口——两端同时改会发生后写覆盖先写。本工具是单人交互式 CLI,此窗口可接受。
|
|
10
|
+
- **为何仍选它**:在 PUT 替换语义未经验证的前提下,这是唯一能安全支持「只改标题 / 只改可见性」而不误清空正文的做法。误清空是不可逆的内容损失(软删只回收文档整体,救不回被覆盖的正文版本),代价远高于一次 GET。
|
|
11
|
+
|
|
12
|
+
## 后果
|
|
13
|
+
|
|
14
|
+
- `--title`、正文(`--body`/`--file`)、`--public`/`--private` 均可单独给;未给的字段保持原值(可见性为三态:不给即保持)。
|
|
15
|
+
- 必须至少给一个可改字段,否则报错(无可更新内容),不发空 PUT。
|
|
16
|
+
- 若日后验证内部 PUT 确为「缺字段即保留」的 patch 语义,可移除这次 GET、简化为直接发增量;属可逆优化。
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 语雀内部 Web API 参考(实测)
|
|
2
|
+
|
|
3
|
+
> 本文档记录 `yuque-cli` 所依赖的语雀**内部** web 接口(非官方 Open API v2,原因见 [ADR 0001](./adr/0001-use-internal-web-api-with-cookie-auth.md))。
|
|
4
|
+
> 全部端点于 **2026-06-01** 针对某企业语雀实例实测通过(discovery + 写链路验证 spike)。
|
|
5
|
+
> 内部接口未公开、无版本承诺,可能随语雀前端升级而变化。
|
|
6
|
+
|
|
7
|
+
## 认证与通用请求头
|
|
8
|
+
|
|
9
|
+
所有请求都带:
|
|
10
|
+
|
|
11
|
+
| 头 | 值 | 说明 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `Cookie` | 完整 cookie 串 | 须含 `_yuque_session`(登录态)与 `yuque_ctoken`(CSRF) |
|
|
14
|
+
| `User-Agent` | 浏览器 UA | 必需,缺失会被拒 |
|
|
15
|
+
| `Referer` | 相关页面 URL | 内部接口会校验来源 |
|
|
16
|
+
| `x-requested-with` | `XMLHttpRequest` | |
|
|
17
|
+
|
|
18
|
+
**写操作(POST/PUT/DELETE)额外带**:
|
|
19
|
+
|
|
20
|
+
| 头 | 值 |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `Content-Type` | `application/json` |
|
|
23
|
+
| `x-csrf-token` | **`yuque_ctoken` cookie 的值**(实测有效) |
|
|
24
|
+
|
|
25
|
+
## URL → id 解析
|
|
26
|
+
|
|
27
|
+
写命令大多需要 `book_id`(部分需要数字 `doc_id`),但**没有干净的 REST 路径**可从 `login/book_slug` 反查(`/api/repos/{ns}`、`/api/books/{id}` 均 404)。
|
|
28
|
+
|
|
29
|
+
通用解法:GET 页面 HTML `https://<host>/{login}/{book}[/{slug}]`,提取其中
|
|
30
|
+
`window.appData = JSON.parse(decodeURIComponent("..."))`,解码后读:
|
|
31
|
+
|
|
32
|
+
- `appData.book.id` → book_id
|
|
33
|
+
- `appData.doc.id` → doc_id(doc 页面才有)
|
|
34
|
+
|
|
35
|
+
## 用户 / Auth
|
|
36
|
+
|
|
37
|
+
| 能力 | 方法 路径 | 响应要点 |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| 当前用户(auth info) | `GET /api/mine` | `data{ login, name, id, email, organization_login, user_id, ... }` |
|
|
40
|
+
|
|
41
|
+
## 知识库 / Repo
|
|
42
|
+
|
|
43
|
+
| 能力 | 方法 路径 | 响应要点 |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| 我的知识库(repo list) | `GET /api/mine/books` | `[{ id, slug, name, items_count, public, user_id, abilities{create_doc,modify_setting,destroy} }]` |
|
|
46
|
+
|
|
47
|
+
> `user_id` 为当前用户 id 即个人库,否则为团队/群组库。`abilities.destroy` 仅指「删整个知识库」,不影响删除自建文档。
|
|
48
|
+
|
|
49
|
+
## 文档 / Doc
|
|
50
|
+
|
|
51
|
+
| 能力 | 方法 路径 | 备注 |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| 列出文档 | `GET /api/books/{book_id}/docs` | 返回 `[{id, slug, title, book_id, ...}]` |
|
|
54
|
+
| 取正文(Markdown) | `GET /{login}/{book}/{slug}/markdown?plain=true&linebreak=false&anchor=false` | 干净 Markdown,**不需 book_id、非 /api** |
|
|
55
|
+
| 取详情(JSON/Lake) | `GET /api/docs/{slug或id}?book_id={book_id}` | **必须带 `book_id`**,否则 404 |
|
|
56
|
+
| 创建 | `POST /api/docs` body `{book_id, title, slug, format:"markdown", body, public}` | 返回 `data{id, slug, body, body_draft, format, status, ...}` |
|
|
57
|
+
| 更新 | `PUT /api/docs/{id}` body `{title, body, format:"markdown", public}` | |
|
|
58
|
+
| 删除 | `DELETE /api/docs/{id}` | 软删 → 回收站(可恢复);自建文档可删 |
|
|
59
|
+
|
|
60
|
+
> `format:"markdown"` 在创建/更新时被直接接受,**无需构造 Lake 格式**。
|
|
61
|
+
|
|
62
|
+
## 评论 / Comment(文档级)
|
|
63
|
+
|
|
64
|
+
| 能力 | 方法 路径 | 备注 |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| 列出 | `GET /api/comments?commentable_id={doc_id}&commentable_type=Doc` | |
|
|
67
|
+
| 创建 | `POST /api/comments` body `{commentable_id, commentable_type:"Doc", body}` | 正文字段是 **`body`**(不是 `content`) |
|
|
68
|
+
| 删除 | `DELETE /api/comments/{comment_id}` | |
|
|
69
|
+
|
|
70
|
+
> 评论对象含 `selection_id` / `selection_type`:文档级评论为空,行内「划词评论」靠它们锚定。本工具仅做文档级(见对话决策)。
|
|
71
|
+
|
|
72
|
+
## 已知坑
|
|
73
|
+
|
|
74
|
+
- `/api/docs/{slug}` **不带 `book_id` 会 404**——demo 旧代码的 `--lake` 路径即栽在这里(从未真正跑通)。
|
|
75
|
+
- 删除是**软删**(进回收站),非物理删除。
|
|
76
|
+
- 数字 id 与 slug 在 `/api/docs/{x}?book_id=` 下均可用;写操作(PUT/DELETE)实测用数字 `id`。
|
yuque_cli-0.1.0/main.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[tools]
|
|
2
|
+
uv = "latest"
|
|
3
|
+
|
|
4
|
+
[tasks.deps]
|
|
5
|
+
run = "uv sync"
|
|
6
|
+
|
|
7
|
+
[tasks.cli]
|
|
8
|
+
depends = ["deps"]
|
|
9
|
+
run = "uv run yuque"
|
|
10
|
+
|
|
11
|
+
[tasks.test]
|
|
12
|
+
depends = ["deps"]
|
|
13
|
+
run = "uv run pytest"
|
|
14
|
+
|
|
15
|
+
[tasks.lint]
|
|
16
|
+
depends = ["deps"]
|
|
17
|
+
run = "ruff check && ruff format"
|
|
18
|
+
|
|
19
|
+
[tasks.check]
|
|
20
|
+
depends = ["deps"]
|
|
21
|
+
run = "ty check"
|
|
22
|
+
|
|
23
|
+
[tasks.build]
|
|
24
|
+
depends = ["deps"]
|
|
25
|
+
run = "uv build"
|
|
26
|
+
|
|
27
|
+
[tasks.publish]
|
|
28
|
+
depends = ["build"]
|
|
29
|
+
run = "uv publish"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "yuque-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "针对企业语雀实例的命令行客户端:cookie 认证,驱动内部 web 接口,提供文档与评论的 CRUD"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"typer>=0.12",
|
|
9
|
+
"httpx>=0.27",
|
|
10
|
+
"websocket-client>=1.9.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
yuque = "yuque_cli.cli:app"
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"ruff>=0.15.15",
|
|
19
|
+
"ty>=0.0.40",
|
|
20
|
+
"pytest>=8",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["hatchling"]
|
|
25
|
+
build-backend = "hatchling.build"
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/yuque_cli"]
|
|
29
|
+
|
|
30
|
+
[tool.ruff]
|
|
31
|
+
line-length = 88
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = ["test"]
|
|
35
|
+
pythonpath = ["src"]
|
|
36
|
+
addopts = "-q"
|