cheso-cli 0.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,164 @@
1
+ Metadata-Version: 2.3
2
+ Name: cheso-cli
3
+ Version: 0.2.0
4
+ Summary: Use Cheso from your terminal or coding agent: research reports, PPT, web pages, posters, videos, images, document translation and format conversion
5
+ Keywords: cheso,cli,ai-agent,agent-skills,claude-code,codex,ppt,research-report
6
+ Author: cheso
7
+ Author-email: cheso <chesoai2026@gmail.com>
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Office/Business
20
+ Classifier: Topic :: Utilities
21
+ Requires-Dist: httpx>=0.28.1
22
+ Requires-Python: >=3.10
23
+ Project-URL: Homepage, https://cheso.ai
24
+ Project-URL: Agent Skill, https://github.com/cheso-ai-platform/skills
25
+ Description-Content-Type: text/markdown
26
+
27
+ # cheso-cli
28
+
29
+ Use [Cheso](https://cheso.ai) from a terminal or a coding agent (Claude Code, Codex, Cursor ...): research reports
30
+ with citations, PPT decks, web pages, posters, whiteboards, videos, images, document analysis, translation and
31
+ format conversion. Tasks run on Cheso with your own account and credits; result files are downloaded to your
32
+ working directory.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ uv tool install cheso-cli # or: pipx install cheso-cli
38
+ cheso login # opens the Cheso sign-in page in your browser; click Allow
39
+ cheso skill install # teaches Claude Code and Codex how to use Cheso
40
+ ```
41
+
42
+ Requires Python 3.10+. For the China site (zhishi.qq.com) log in with `cheso login --site cn`; later commands use
43
+ the site you logged in to last unless you pass `--site` / `--base-url` or set `CHESO_BASE_URL`.
44
+
45
+ Then ask your agent, for example: "Use Cheso to turn ./notes.pdf into a 10-slide PPT". Or run it yourself:
46
+
47
+ ```bash
48
+ cheso task create --file ./notes.pdf --prompt "Make a 10-slide investor deck in English" --wait
49
+ ```
50
+
51
+ ## Login
52
+
53
+ `cheso login` uses OAuth 2.1 (authorization code + PKCE) with a loopback redirect: the CLI listens once on
54
+ `http://127.0.0.1:<random port>/callback`, opens the Cheso authorization page, and stores the tokens after you
55
+ approve. `--no-browser` only prints the URL; `--port N` fixes the port (for SSH port forwarding:
56
+ `ssh -L N:127.0.0.1:N host`, then open the URL locally). The browser must reach the loopback port of the machine
57
+ running `cheso`.
58
+
59
+ Without a browser or a reachable loopback port (SSH sessions, containers, cloud agents), use
60
+ `cheso login --device` (OAuth device authorization grant, RFC 8628): it prints a Cheso page and a short code; open
61
+ the page on any device, sign in to Cheso, enter the code and click Allow. The CLI polls until you approve (every
62
+ 5 s, up to `--timeout`, the code itself is valid for 10 minutes). Coding agents split it into two commands that
63
+ return quickly: `cheso login --device --no-wait --json` prints the page and code; `cheso login --wait --json` keeps
64
+ waiting and can be run again (exit 6 while still waiting). Only enter a code that you started yourself: whoever
65
+ approves it lets that CLI act as them.
66
+
67
+ - Tokens are stored in `$XDG_CONFIG_HOME/cheso/credentials.json` (default `~/.config/cheso/`), one entry per site,
68
+ readable only by you (file 0600, directory 0700), replaced atomically.
69
+ - Access tokens are refreshed automatically shortly before they expire, and once more if the server rejects one.
70
+ Refresh tokens rotate and the server revokes the login if an old one is reused, so refreshes are serialized with a
71
+ lock file (`credentials.json.lock`): parallel `cheso` commands share one refresh.
72
+ - `cheso logout` revokes the authorization on the server and deletes the local tokens. You can also revoke the CLI
73
+ on the Cheso website under Authorized apps.
74
+ - Token precedence: `--token` > `CHESO_API_TOKEN` > stored login. For CI and scripts, set `CHESO_API_TOKEN` to a
75
+ personal Cheso API token; nothing is written to disk.
76
+
77
+ ## Commands
78
+
79
+ Add `--json` to any command for one JSON document on stdout (progress and warnings go to stderr). Global options
80
+ (`--site`, `--base-url`, `--token`, `--json`) may come before or after the command.
81
+
82
+ | Command | What it does |
83
+ |---|---|
84
+ | `cheso login [--no-browser] [--port N] [--timeout S]` | Sign in with the browser |
85
+ | `cheso login --device [--no-wait] [--timeout S]` | Sign in with a code entered on any device (no browser or loopback needed here) |
86
+ | `cheso login --wait [--timeout S]` | Keep waiting for a sign-in started with `--device --no-wait` |
87
+ | `cheso logout` | Revoke the login and delete stored tokens |
88
+ | `cheso whoami` | Show the account and where the token comes from |
89
+ | `cheso credits` | Show available credits |
90
+ | `cheso task create --prompt TEXT [--file PATH ...] [--file-id ID ...] [--project ID] [--agent-type cheso\|image_edit] [--wait] [--timeout S] [--output-dir DIR]` | Start a task; checks credits first; `--file` uploads local files; `--wait` waits and downloads the results |
91
+ | `cheso task wait ID [--timeout S] [--output-dir DIR] [--no-download]` | Wait for a task (`plan_...`), conversion (`cvt_...`) or translation (`tlj_...`), polling every 3 s and following hand-offs to follow-up tasks; downloads the results |
92
+ | `cheso task get ID` | Show status, answer, files or the pending question (no download) |
93
+ | `cheso task reply ID [--answer "QUESTION=ANSWER" ...] [--skip]` | Answer the question a task is asking (also `N=ANSWER` by number) |
94
+ | `cheso task cancel ID` | Stop a running task |
95
+ | `cheso file upload PATH [--project ID]` | Upload a file; prints its `file_id` |
96
+ | `cheso file list --project ID` | List a project's files |
97
+ | `cheso file download FILE_ID [--version N] [--output-dir DIR]` | Download a file (the latest version by default) |
98
+ | `cheso convert FILE_ID --to TYPE [--wait] [--timeout S] [--output-dir DIR]` | Convert a file (e.g. md to pdf/docx, vppt to pptx/pdf, vmotion to mp4); unsupported targets list the valid ones |
99
+ | `cheso translate FILE_ID --to LANG [--from LANG] [--wait] [--timeout S] [--output-dir DIR]` | Translate a document (zh, zh-Hant, en, ko, ja, es, fr, ar, ru, pt, de, th) |
100
+ | `cheso skill install [--target claude\|codex\|all] [--project] [--force]` | Install the Agent Skill |
101
+ | `cheso skill show` | Print the bundled SKILL.md |
102
+
103
+ Downloads never overwrite: an existing `report.pdf` makes the new file `report (1).pdf`. The default `--timeout`
104
+ is 1800 s, counted from the start of the command and covering the result downloads too: no request starts after it
105
+ and each one gets at most the time left (uploads and starting the task are not cut short). When it passes the task
106
+ keeps running on Cheso and `cheso task wait ID` picks it up again.
107
+
108
+ The low-level API commands of the first version (`profile`, `project ...`, `session create`, `plan create|get`)
109
+ are still available and print raw JSON.
110
+
111
+ ## Exit codes
112
+
113
+ | Code | Meaning |
114
+ |---|---|
115
+ | 0 | Success |
116
+ | 1 | Error |
117
+ | 2 | Usage error (bad flags, unsupported conversion target) |
118
+ | 3 | The task is waiting for your answer: `cheso task reply` |
119
+ | 4 | Login required: `cheso login` or `cheso login --device` (or the token in `CHESO_API_TOKEN` / `--token` was rejected) |
120
+ | 5 | Not enough credits (the JSON error carries `topup_url`) |
121
+ | 6 | Still running when `--timeout` passed: run `cheso task wait ID` again (for `cheso login --device`: `cheso login --wait`) |
122
+ | 7 | Rate limited or project busy; retry later (`retry_after`, `running_task_id` in the JSON error) |
123
+ | 8 | The task succeeded, but some result files were not downloaded (`error.file_ids`); `next` downloads just those. A failed task keeps 1 or 5; its files that did not download carry `error` |
124
+
125
+ ## Agent Skill
126
+
127
+ `cheso skill install` copies `SKILL.md` (shipped inside the package) to:
128
+
129
+ | Target | User level (default) | `--project` |
130
+ |---|---|---|
131
+ | Claude Code | `~/.claude/skills/cheso/` | `./.claude/skills/cheso/` |
132
+ | Codex | `~/.agents/skills/cheso/` | `./.agents/skills/cheso/` |
133
+
134
+ An unchanged older copy is upgraded in place; a copy you edited is left alone unless you pass `--force`.
135
+
136
+ ## Privacy
137
+
138
+ Files you upload and the tasks you start are stored in your Cheso account, in your projects, and use your credits.
139
+ The CLI sends your token only to the Cheso site you chose; uploads and downloads to presigned storage URLs carry no
140
+ token. Tokens are never printed.
141
+
142
+ ## Environment
143
+
144
+ | Variable | Use |
145
+ |---|---|
146
+ | `CHESO_API_TOKEN` | Personal API token (CI, scripts); overrides the stored login |
147
+ | `CHESO_BASE_URL` | Override the site URL (e.g. `https://testcheso.ai` for the test environment) |
148
+ | `CHESO_DEV_SET` | **Test environment only.** Adds `X-Dev-Set: <value>` to Cheso requests to route them to a developer container; has no effect in production |
149
+ | `XDG_CONFIG_HOME` | Where `cheso/credentials.json` lives |
150
+
151
+ Site precedence: `--base-url` > `--site` > `CHESO_BASE_URL` > the site of the last `cheso login` > `https://cheso.ai`.
152
+ Suggested commands (`next`, hints in messages) repeat the `--site` / `--base-url` you passed.
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ cd clients/cli
158
+ uv sync
159
+ uv run --frozen pytest -q
160
+ uvx ruff check . && uvx ruff format --check . # uses the repository ruff.toml
161
+ uv build # wheel and sdist in dist/
162
+ ```
163
+
164
+ System tests (case library, pools, runs, reports) live in [`../test-suite`](../test-suite) (`cheso-test`).
@@ -0,0 +1,138 @@
1
+ # cheso-cli
2
+
3
+ Use [Cheso](https://cheso.ai) from a terminal or a coding agent (Claude Code, Codex, Cursor ...): research reports
4
+ with citations, PPT decks, web pages, posters, whiteboards, videos, images, document analysis, translation and
5
+ format conversion. Tasks run on Cheso with your own account and credits; result files are downloaded to your
6
+ working directory.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ uv tool install cheso-cli # or: pipx install cheso-cli
12
+ cheso login # opens the Cheso sign-in page in your browser; click Allow
13
+ cheso skill install # teaches Claude Code and Codex how to use Cheso
14
+ ```
15
+
16
+ Requires Python 3.10+. For the China site (zhishi.qq.com) log in with `cheso login --site cn`; later commands use
17
+ the site you logged in to last unless you pass `--site` / `--base-url` or set `CHESO_BASE_URL`.
18
+
19
+ Then ask your agent, for example: "Use Cheso to turn ./notes.pdf into a 10-slide PPT". Or run it yourself:
20
+
21
+ ```bash
22
+ cheso task create --file ./notes.pdf --prompt "Make a 10-slide investor deck in English" --wait
23
+ ```
24
+
25
+ ## Login
26
+
27
+ `cheso login` uses OAuth 2.1 (authorization code + PKCE) with a loopback redirect: the CLI listens once on
28
+ `http://127.0.0.1:<random port>/callback`, opens the Cheso authorization page, and stores the tokens after you
29
+ approve. `--no-browser` only prints the URL; `--port N` fixes the port (for SSH port forwarding:
30
+ `ssh -L N:127.0.0.1:N host`, then open the URL locally). The browser must reach the loopback port of the machine
31
+ running `cheso`.
32
+
33
+ Without a browser or a reachable loopback port (SSH sessions, containers, cloud agents), use
34
+ `cheso login --device` (OAuth device authorization grant, RFC 8628): it prints a Cheso page and a short code; open
35
+ the page on any device, sign in to Cheso, enter the code and click Allow. The CLI polls until you approve (every
36
+ 5 s, up to `--timeout`, the code itself is valid for 10 minutes). Coding agents split it into two commands that
37
+ return quickly: `cheso login --device --no-wait --json` prints the page and code; `cheso login --wait --json` keeps
38
+ waiting and can be run again (exit 6 while still waiting). Only enter a code that you started yourself: whoever
39
+ approves it lets that CLI act as them.
40
+
41
+ - Tokens are stored in `$XDG_CONFIG_HOME/cheso/credentials.json` (default `~/.config/cheso/`), one entry per site,
42
+ readable only by you (file 0600, directory 0700), replaced atomically.
43
+ - Access tokens are refreshed automatically shortly before they expire, and once more if the server rejects one.
44
+ Refresh tokens rotate and the server revokes the login if an old one is reused, so refreshes are serialized with a
45
+ lock file (`credentials.json.lock`): parallel `cheso` commands share one refresh.
46
+ - `cheso logout` revokes the authorization on the server and deletes the local tokens. You can also revoke the CLI
47
+ on the Cheso website under Authorized apps.
48
+ - Token precedence: `--token` > `CHESO_API_TOKEN` > stored login. For CI and scripts, set `CHESO_API_TOKEN` to a
49
+ personal Cheso API token; nothing is written to disk.
50
+
51
+ ## Commands
52
+
53
+ Add `--json` to any command for one JSON document on stdout (progress and warnings go to stderr). Global options
54
+ (`--site`, `--base-url`, `--token`, `--json`) may come before or after the command.
55
+
56
+ | Command | What it does |
57
+ |---|---|
58
+ | `cheso login [--no-browser] [--port N] [--timeout S]` | Sign in with the browser |
59
+ | `cheso login --device [--no-wait] [--timeout S]` | Sign in with a code entered on any device (no browser or loopback needed here) |
60
+ | `cheso login --wait [--timeout S]` | Keep waiting for a sign-in started with `--device --no-wait` |
61
+ | `cheso logout` | Revoke the login and delete stored tokens |
62
+ | `cheso whoami` | Show the account and where the token comes from |
63
+ | `cheso credits` | Show available credits |
64
+ | `cheso task create --prompt TEXT [--file PATH ...] [--file-id ID ...] [--project ID] [--agent-type cheso\|image_edit] [--wait] [--timeout S] [--output-dir DIR]` | Start a task; checks credits first; `--file` uploads local files; `--wait` waits and downloads the results |
65
+ | `cheso task wait ID [--timeout S] [--output-dir DIR] [--no-download]` | Wait for a task (`plan_...`), conversion (`cvt_...`) or translation (`tlj_...`), polling every 3 s and following hand-offs to follow-up tasks; downloads the results |
66
+ | `cheso task get ID` | Show status, answer, files or the pending question (no download) |
67
+ | `cheso task reply ID [--answer "QUESTION=ANSWER" ...] [--skip]` | Answer the question a task is asking (also `N=ANSWER` by number) |
68
+ | `cheso task cancel ID` | Stop a running task |
69
+ | `cheso file upload PATH [--project ID]` | Upload a file; prints its `file_id` |
70
+ | `cheso file list --project ID` | List a project's files |
71
+ | `cheso file download FILE_ID [--version N] [--output-dir DIR]` | Download a file (the latest version by default) |
72
+ | `cheso convert FILE_ID --to TYPE [--wait] [--timeout S] [--output-dir DIR]` | Convert a file (e.g. md to pdf/docx, vppt to pptx/pdf, vmotion to mp4); unsupported targets list the valid ones |
73
+ | `cheso translate FILE_ID --to LANG [--from LANG] [--wait] [--timeout S] [--output-dir DIR]` | Translate a document (zh, zh-Hant, en, ko, ja, es, fr, ar, ru, pt, de, th) |
74
+ | `cheso skill install [--target claude\|codex\|all] [--project] [--force]` | Install the Agent Skill |
75
+ | `cheso skill show` | Print the bundled SKILL.md |
76
+
77
+ Downloads never overwrite: an existing `report.pdf` makes the new file `report (1).pdf`. The default `--timeout`
78
+ is 1800 s, counted from the start of the command and covering the result downloads too: no request starts after it
79
+ and each one gets at most the time left (uploads and starting the task are not cut short). When it passes the task
80
+ keeps running on Cheso and `cheso task wait ID` picks it up again.
81
+
82
+ The low-level API commands of the first version (`profile`, `project ...`, `session create`, `plan create|get`)
83
+ are still available and print raw JSON.
84
+
85
+ ## Exit codes
86
+
87
+ | Code | Meaning |
88
+ |---|---|
89
+ | 0 | Success |
90
+ | 1 | Error |
91
+ | 2 | Usage error (bad flags, unsupported conversion target) |
92
+ | 3 | The task is waiting for your answer: `cheso task reply` |
93
+ | 4 | Login required: `cheso login` or `cheso login --device` (or the token in `CHESO_API_TOKEN` / `--token` was rejected) |
94
+ | 5 | Not enough credits (the JSON error carries `topup_url`) |
95
+ | 6 | Still running when `--timeout` passed: run `cheso task wait ID` again (for `cheso login --device`: `cheso login --wait`) |
96
+ | 7 | Rate limited or project busy; retry later (`retry_after`, `running_task_id` in the JSON error) |
97
+ | 8 | The task succeeded, but some result files were not downloaded (`error.file_ids`); `next` downloads just those. A failed task keeps 1 or 5; its files that did not download carry `error` |
98
+
99
+ ## Agent Skill
100
+
101
+ `cheso skill install` copies `SKILL.md` (shipped inside the package) to:
102
+
103
+ | Target | User level (default) | `--project` |
104
+ |---|---|---|
105
+ | Claude Code | `~/.claude/skills/cheso/` | `./.claude/skills/cheso/` |
106
+ | Codex | `~/.agents/skills/cheso/` | `./.agents/skills/cheso/` |
107
+
108
+ An unchanged older copy is upgraded in place; a copy you edited is left alone unless you pass `--force`.
109
+
110
+ ## Privacy
111
+
112
+ Files you upload and the tasks you start are stored in your Cheso account, in your projects, and use your credits.
113
+ The CLI sends your token only to the Cheso site you chose; uploads and downloads to presigned storage URLs carry no
114
+ token. Tokens are never printed.
115
+
116
+ ## Environment
117
+
118
+ | Variable | Use |
119
+ |---|---|
120
+ | `CHESO_API_TOKEN` | Personal API token (CI, scripts); overrides the stored login |
121
+ | `CHESO_BASE_URL` | Override the site URL (e.g. `https://testcheso.ai` for the test environment) |
122
+ | `CHESO_DEV_SET` | **Test environment only.** Adds `X-Dev-Set: <value>` to Cheso requests to route them to a developer container; has no effect in production |
123
+ | `XDG_CONFIG_HOME` | Where `cheso/credentials.json` lives |
124
+
125
+ Site precedence: `--base-url` > `--site` > `CHESO_BASE_URL` > the site of the last `cheso login` > `https://cheso.ai`.
126
+ Suggested commands (`next`, hints in messages) repeat the `--site` / `--base-url` you passed.
127
+
128
+ ## Development
129
+
130
+ ```bash
131
+ cd clients/cli
132
+ uv sync
133
+ uv run --frozen pytest -q
134
+ uvx ruff check . && uvx ruff format --check . # uses the repository ruff.toml
135
+ uv build # wheel and sdist in dist/
136
+ ```
137
+
138
+ System tests (case library, pools, runs, reports) live in [`../test-suite`](../test-suite) (`cheso-test`).
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "cheso-cli"
3
+ version = "0.2.0"
4
+ description = "Use Cheso from your terminal or coding agent: research reports, PPT, web pages, posters, videos, images, document translation and format conversion"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ keywords = [
8
+ "cheso",
9
+ "cli",
10
+ "ai-agent",
11
+ "agent-skills",
12
+ "claude-code",
13
+ "codex",
14
+ "ppt",
15
+ "research-report",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "Intended Audience :: End Users/Desktop",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Topic :: Office/Business",
30
+ "Topic :: Utilities",
31
+ ]
32
+ dependencies = ["httpx>=0.28.1"]
33
+
34
+ [[project.authors]]
35
+ name = "cheso"
36
+ email = "chesoai2026@gmail.com"
37
+
38
+ [project.urls]
39
+ Homepage = "https://cheso.ai"
40
+ "Agent Skill" = "https://github.com/cheso-ai-platform/skills"
41
+
42
+ [project.scripts]
43
+ cheso = "cheso_cli.cli:main"
44
+ cheso-cli = "cheso_cli.cli:main"
45
+
46
+ [build-system]
47
+ requires = ["uv_build>=0.12.0,<0.13.0"]
48
+ build-backend = "uv_build"
49
+
50
+ [dependency-groups]
51
+ dev = ["pytest>=9.1.1"]
52
+
53
+ [tool.pytest.ini_options]
54
+ testpaths = ["tests"]
55
+ pythonpath = ["src"]
@@ -0,0 +1,49 @@
1
+ [project]
2
+ name = "cheso-cli"
3
+ version = "0.2.0"
4
+ description = "Use Cheso from your terminal or coding agent: research reports, PPT, web pages, posters, videos, images, document translation and format conversion"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "cheso", email = "chesoai2026@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.10"
10
+ keywords = ["cheso", "cli", "ai-agent", "agent-skills", "claude-code", "codex", "ppt", "research-report"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Environment :: Console",
14
+ "Intended Audience :: Developers",
15
+ "Intended Audience :: End Users/Desktop",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Office/Business",
24
+ "Topic :: Utilities",
25
+ ]
26
+ dependencies = [
27
+ "httpx>=0.28.1",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://cheso.ai"
32
+ "Agent Skill" = "https://github.com/cheso-ai-platform/skills"
33
+
34
+ [project.scripts]
35
+ cheso = "cheso_cli.cli:main"
36
+ cheso-cli = "cheso_cli.cli:main"
37
+
38
+ [build-system]
39
+ requires = ["uv_build>=0.12.0,<0.13.0"]
40
+ build-backend = "uv_build"
41
+
42
+ [dependency-groups]
43
+ dev = [
44
+ "pytest>=9.1.1",
45
+ ]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ pythonpath = ["src"]
@@ -0,0 +1,12 @@
1
+ """Cheso 命令行工具。"""
2
+
3
+ from cheso_cli.config import VERSION as __version__
4
+
5
+ __all__ = ["__version__", "main"]
6
+
7
+
8
+ def main(argv: list[str] | None = None) -> None:
9
+ """命令行脚本入口(``cheso`` / ``cheso-cli``)。"""
10
+ from cheso_cli.cli import main as _main
11
+
12
+ _main(argv)
@@ -0,0 +1,5 @@
1
+ """模块启动入口:``python -m cheso_cli``。"""
2
+
3
+ from cheso_cli.cli import main
4
+
5
+ main()
@@ -0,0 +1,128 @@
1
+ """决定命令使用哪个令牌,并在需要时刷新已保存的 OAuth 访问令牌。
2
+
3
+ 优先级:``--token`` > ``CHESO_API_TOKEN`` > ``cheso login`` 保存的凭据。
4
+
5
+ 刷新令牌每次使用后都会轮换;服务器再次收到已使用的刷新令牌时,会撤销整个授权。
6
+ Agent 经常同时运行多个 ``cheso`` 命令,因此刷新操作始终持有凭据文件锁,并在拿到锁后重新读取文件。
7
+ 如果其他进程已经完成刷新,就使用其新访问令牌,避免用已经失效的刷新令牌再次刷新。
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import time
14
+ from collections.abc import Callable, Mapping
15
+
16
+ import httpx
17
+
18
+ from cheso_cli.client import StaticToken, TokenSource
19
+ from cheso_cli.config import DEFAULT_SITE, ENV_TOKEN, SITES, Settings
20
+ from cheso_cli.credentials import CredentialStore, SiteCredentials
21
+ from cheso_cli.errors import LoginRequired
22
+ from cheso_cli.oauth import ERROR_INVALID_CLIENT, ERROR_INVALID_GRANT, OAuthClient, OAuthProtocolError
23
+
24
+ # 访问令牌将在以下秒数内过期时提前刷新。
25
+ REFRESH_SKEW_SECONDS = 60.0
26
+
27
+ TOKEN_SOURCE_FLAG = "--token"
28
+ TOKEN_SOURCE_ENV = ENV_TOKEN
29
+ TOKEN_SOURCE_LOGIN = "login"
30
+
31
+
32
+ def login_hint(settings: Settings) -> str:
33
+ """当前站点的登录命令,例如 ``cheso login --site cn``。"""
34
+ for name, url in SITES.items():
35
+ if settings.base_url == url:
36
+ return "cheso login" if name == DEFAULT_SITE else f"cheso login --site {name}"
37
+ return f"cheso login --base-url {settings.base_url}"
38
+
39
+
40
+ class OAuthSession(TokenSource):
41
+ """从凭据文件读取访问令牌,需要时在文件锁内刷新。"""
42
+
43
+ def __init__(
44
+ self,
45
+ settings: Settings,
46
+ store: CredentialStore,
47
+ *,
48
+ transport: httpx.BaseTransport | None = None,
49
+ now: Callable[[], float] = time.time,
50
+ ) -> None:
51
+ self.settings = settings
52
+ self.store = store
53
+ self._transport = transport
54
+ self._now = now
55
+ self.refresh_count = 0
56
+
57
+ def _fresh(self, creds: SiteCredentials) -> bool:
58
+ return bool(creds.access_token) and creds.expires_at - self._now() > REFRESH_SKEW_SECONDS
59
+
60
+ def token(self) -> str:
61
+ creds = self.store.get(self.settings.base_url)
62
+ if not creds.logged_in:
63
+ raise self._login_required("Not logged in")
64
+ if self._fresh(creds):
65
+ return creds.access_token
66
+ return self._refresh(rejected=None)
67
+
68
+ def refresh_after_unauthorized(self, rejected: str) -> str | None:
69
+ return self._refresh(rejected=rejected)
70
+
71
+ def _refresh(self, *, rejected: str | None) -> str:
72
+ """在锁内刷新。``rejected`` 是服务器刚以 401 拒绝的访问令牌,没有时为空。"""
73
+ base = self.settings.base_url
74
+ with self.store.locked():
75
+ creds = self.store.get(base)
76
+ if not creds.logged_in:
77
+ raise self._login_required("Not logged in")
78
+ # 等待锁期间,其他进程可能已经刷新了令牌。
79
+ if self._fresh(creds) and creds.access_token != rejected:
80
+ return creds.access_token
81
+ if not creds.refresh_token or not creds.client_id:
82
+ creds.clear_tokens()
83
+ self.store.put(base, creds)
84
+ raise self._login_required("The login has expired")
85
+ with OAuthClient(self.settings, transport=self._transport, now=self._now) as oauth:
86
+ endpoint = creds.token_endpoint or oauth.metadata().token_endpoint
87
+ try:
88
+ tokens = oauth.refresh(endpoint, client_id=creds.client_id, refresh_token=creds.refresh_token)
89
+ except OAuthProtocolError as e:
90
+ if e.error not in (ERROR_INVALID_GRANT, ERROR_INVALID_CLIENT):
91
+ raise
92
+ creds.clear_tokens()
93
+ if e.error == ERROR_INVALID_CLIENT:
94
+ creds.client_id = ""
95
+ self.store.put(base, creds)
96
+ raise self._login_required("The login has expired or was revoked") from e
97
+ self.refresh_count += 1
98
+ creds.access_token = tokens.access_token
99
+ creds.refresh_token = tokens.refresh_token or creds.refresh_token
100
+ creds.expires_at = tokens.expires_at
101
+ creds.token_endpoint = endpoint
102
+ self.store.put(base, creds)
103
+ return creds.access_token
104
+
105
+ def _login_required(self, what: str) -> LoginRequired:
106
+ return LoginRequired(f"{what} for {self.settings.base_url}. Run `{login_hint(self.settings)}`.")
107
+
108
+
109
+ def resolve_token_source(
110
+ settings: Settings,
111
+ store: CredentialStore,
112
+ *,
113
+ token_flag: str | None,
114
+ env: Mapping[str, str] | None = None,
115
+ transport: httpx.BaseTransport | None = None,
116
+ now: Callable[[], float] = time.time,
117
+ ) -> tuple[TokenSource, str]:
118
+ """返回令牌来源及其名称(``--token``、``CHESO_API_TOKEN`` 或 ``login``)。"""
119
+ env = os.environ if env is None else env
120
+ if token_flag:
121
+ return StaticToken(token_flag), TOKEN_SOURCE_FLAG
122
+ env_token = env.get(ENV_TOKEN, "").strip()
123
+ if env_token:
124
+ return StaticToken(env_token), TOKEN_SOURCE_ENV
125
+ creds = store.get(settings.base_url)
126
+ if not creds.logged_in:
127
+ raise LoginRequired(f"Not logged in to {settings.base_url}. Run `{login_hint(settings)}` (or set {ENV_TOKEN}).")
128
+ return OAuthSession(settings, store, transport=transport, now=now), TOKEN_SOURCE_LOGIN