cheso-cli 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.
cheso_cli/transfer.py ADDED
@@ -0,0 +1,294 @@
1
+ """文件传输:本地文件按预签名、PUT、确认的顺序上传;下载预签名 URL 时不覆盖已有文件。
2
+
3
+ 对象存储的预签名 URL 通过独立 HTTP 客户端访问,始终不携带 Cheso 令牌。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import contextlib
9
+ import hashlib
10
+ import mimetypes
11
+ import os
12
+ import re
13
+ import tempfile
14
+ import time
15
+ from collections.abc import Callable, Iterator
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ import httpx
21
+
22
+ from cheso_cli.client import ApiError, ChesoClient, Deadline, TimeLimitReached, iterate_until
23
+ from cheso_cli.errors import CliError, UsageError
24
+
25
+ # 上传的输入文件作为附件处理,与网页聊天中添加的文件相同。
26
+ ATTACHMENT_CATEGORY = "attachment"
27
+ # 传入 --project 时,文件放入项目的附件目录,路径规则同前端 buildProjectAttachmentPath。
28
+ ATTACHMENT_DIR = "attachments/"
29
+ # create_only 遇到重名时,先尝试“name (1).ext”等名称,再使用时间戳后缀。
30
+ MAX_PATH_ATTEMPTS = 5
31
+ CODE_FILE_PATH_CONFLICT = 1208
32
+ MAX_NAME_CHARS = 200
33
+ DEFAULT_MIME = "application/octet-stream"
34
+ CHUNK = 1 << 20
35
+ STORAGE_TIMEOUT = httpx.Timeout(30.0, read=300.0, write=300.0)
36
+
37
+ # 预签名 PUT 的签名包含 Content-Type,因此预签名请求与 PUT 必须使用相同的值。
38
+ # 优先使用显式类型表;平台自带的 mimetypes 数据库经常缺少 Office 和 Markdown 类型。
39
+ MIME_BY_EXTENSION = {
40
+ ".md": "text/markdown",
41
+ ".markdown": "text/markdown",
42
+ ".txt": "text/plain",
43
+ ".csv": "text/csv",
44
+ ".tsv": "text/tab-separated-values",
45
+ ".json": "application/json",
46
+ ".xml": "application/xml",
47
+ ".yaml": "application/yaml",
48
+ ".yml": "application/yaml",
49
+ ".html": "text/html",
50
+ ".htm": "text/html",
51
+ ".pdf": "application/pdf",
52
+ ".doc": "application/msword",
53
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
54
+ ".xls": "application/vnd.ms-excel",
55
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
56
+ ".ppt": "application/vnd.ms-powerpoint",
57
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
58
+ ".rtf": "application/rtf",
59
+ ".epub": "application/epub+zip",
60
+ ".zip": "application/zip",
61
+ ".png": "image/png",
62
+ ".jpg": "image/jpeg",
63
+ ".jpeg": "image/jpeg",
64
+ ".gif": "image/gif",
65
+ ".webp": "image/webp",
66
+ ".svg": "image/svg+xml",
67
+ ".bmp": "image/bmp",
68
+ ".tif": "image/tiff",
69
+ ".tiff": "image/tiff",
70
+ ".mp4": "video/mp4",
71
+ ".mov": "video/quicktime",
72
+ ".webm": "video/webm",
73
+ ".mp3": "audio/mpeg",
74
+ ".wav": "audio/wav",
75
+ ".m4a": "audio/mp4",
76
+ }
77
+
78
+ _UNSAFE_NAME_CHARS = re.compile(r'[\x00-\x1f\x7f<>:"/\\|?*]')
79
+
80
+
81
+ def new_storage_client(transport: httpx.BaseTransport | None = None) -> httpx.Client:
82
+ """访问对象存储预签名 URL 的 HTTP 客户端;不设置默认请求头,避免令牌泄露。"""
83
+ return httpx.Client(timeout=STORAGE_TIMEOUT, transport=transport, follow_redirects=False)
84
+
85
+
86
+ def safe_filename(raw: str, fallback: str = "untitled") -> str:
87
+ """仅保留文件名:移除目录、控制字符、保留字符及末尾点号和空格,最多 200 个字符。"""
88
+ name = re.split(r"[/\\]", raw or "")[-1]
89
+ name = _UNSAFE_NAME_CHARS.sub("", name).strip().rstrip(". ")
90
+ if not name or name in (".", ".."):
91
+ return fallback
92
+ if len(name) > MAX_NAME_CHARS:
93
+ stem, ext = os.path.splitext(name)
94
+ ext = ext if len(ext) <= 20 else ""
95
+ name = stem[: MAX_NAME_CHARS - len(ext)] + ext
96
+ return name
97
+
98
+
99
+ def guess_mime(name: str) -> str:
100
+ ext = os.path.splitext(name)[1].lower()
101
+ if ext in MIME_BY_EXTENSION:
102
+ return MIME_BY_EXTENSION[ext]
103
+ guessed, _ = mimetypes.guess_type(name, strict=False)
104
+ return guessed or DEFAULT_MIME
105
+
106
+
107
+ def md5_hex(path: Path) -> tuple[str, int]:
108
+ digest = hashlib.md5(usedforsecurity=False)
109
+ size = 0
110
+ with path.open("rb") as f:
111
+ while chunk := f.read(CHUNK):
112
+ digest.update(chunk)
113
+ size += len(chunk)
114
+ return digest.hexdigest(), size
115
+
116
+
117
+ @dataclass
118
+ class UploadResult:
119
+ file_id: str
120
+ name: str
121
+ size: int
122
+ mime_type: str
123
+ project_id: str = ""
124
+ file_path: str = ""
125
+ instant: bool = False
126
+
127
+ def to_dict(self) -> dict[str, Any]:
128
+ data: dict[str, Any] = {
129
+ "file_id": self.file_id,
130
+ "name": self.name,
131
+ "size": self.size,
132
+ "mime_type": self.mime_type,
133
+ "instant": self.instant,
134
+ }
135
+ if self.project_id:
136
+ data["project_id"] = self.project_id
137
+ data["file_path"] = self.file_path
138
+ return data
139
+
140
+
141
+ def attachment_path(name: str, attempt: int, now: float) -> str:
142
+ """生成第 n 次上传尝试的项目路径:原名、“name (n).ext”,最后使用毫秒时间戳。"""
143
+ if attempt == 0:
144
+ return ATTACHMENT_DIR + name
145
+ stem, ext = os.path.splitext(name)
146
+ if attempt < MAX_PATH_ATTEMPTS:
147
+ return f"{ATTACHMENT_DIR}{stem} ({attempt}){ext}"
148
+ return f"{ATTACHMENT_DIR}{stem}_{int(now * 1000)}{ext}"
149
+
150
+
151
+ def upload_file(
152
+ client: ChesoClient,
153
+ storage: httpx.Client,
154
+ path: Path,
155
+ *,
156
+ project_id: str | None = None,
157
+ now: Callable[[], float] = time.time,
158
+ ) -> UploadResult:
159
+ """登记上传、向对象存储 PUT 文件内容,再确认上传;秒传时跳过 PUT 和确认。"""
160
+ if not path.is_file():
161
+ raise UsageError(f"not a file: {path}")
162
+ name = safe_filename(path.name)
163
+ mime = guess_mime(name)
164
+ try:
165
+ digest, size = md5_hex(path)
166
+ except OSError as e:
167
+ raise CliError(f"cannot read {path}: {e.strerror or e}") from e
168
+ body: dict[str, Any] = {
169
+ "name": name,
170
+ "size": size,
171
+ "hash": digest,
172
+ "mime_type": mime,
173
+ "category": ATTACHMENT_CATEGORY,
174
+ }
175
+ file_path = ""
176
+ if not project_id:
177
+ presign = client.file_presign(body) or {}
178
+ else:
179
+ body["project_id"] = project_id
180
+ body["create_only"] = True
181
+ presign = None
182
+ for attempt in range(MAX_PATH_ATTEMPTS + 1):
183
+ file_path = attachment_path(name, attempt, now())
184
+ body["file_path"] = file_path
185
+ try:
186
+ presign = client.file_presign(body) or {}
187
+ break
188
+ except ApiError as e:
189
+ if e.code != CODE_FILE_PATH_CONFLICT:
190
+ raise
191
+ if presign is None:
192
+ raise CliError(f"a file named {name!r} already exists in project {project_id}; rename the file")
193
+ file_id = str(presign.get("file_id") or "")
194
+ version = int(presign.get("version") or 0)
195
+ instant = bool(presign.get("instant"))
196
+ if not file_id:
197
+ raise CliError("upload was not accepted (no file_id returned)")
198
+ if not instant:
199
+ upload_url = str(presign.get("upload_url") or "")
200
+ if not upload_url:
201
+ raise CliError("upload was not accepted (no upload URL returned)")
202
+ _put_object(storage, upload_url, path, size, mime)
203
+ client.file_confirm(file_id=file_id, version=version)
204
+ return UploadResult(
205
+ file_id=file_id,
206
+ name=name,
207
+ size=size,
208
+ mime_type=mime,
209
+ project_id=project_id or "",
210
+ file_path=file_path,
211
+ instant=instant,
212
+ )
213
+
214
+
215
+ def _put_object(storage: httpx.Client, url: str, path: Path, size: int, mime: str) -> None:
216
+ # 预签名 PUT 需要 Content-Length,不能使用分块编码;传入真实文件对象可让 httpx 自动设置。
217
+ headers = {"Content-Type": mime}
218
+ try:
219
+ if size == 0:
220
+ resp = storage.put(url, content=b"", headers=headers)
221
+ else:
222
+ with path.open("rb") as f:
223
+ resp = storage.put(url, content=f, headers=headers)
224
+ except httpx.HTTPError as e:
225
+ raise CliError(f"uploading {path.name} to storage failed: {e.__class__.__name__}") from e
226
+ if not 200 <= resp.status_code < 300:
227
+ raise CliError(f"uploading {path.name} to storage failed (http {resp.status_code})")
228
+
229
+
230
+ def _file_mode() -> int:
231
+ """根据当前 umask 计算普通文件权限;临时文件初始权限为 0600。"""
232
+ mask = os.umask(0)
233
+ os.umask(mask)
234
+ return 0o666 & ~mask
235
+
236
+
237
+ def _reserve_unique(directory: Path, name: str) -> Path:
238
+ """以 O_EXCL 创建此前不存在的空文件,名称为 ``name`` 或 ``stem (n).ext``。"""
239
+ stem, ext = os.path.splitext(name)
240
+ n = 0
241
+ while True:
242
+ candidate = directory / (name if n == 0 else f"{stem} ({n}){ext}")
243
+ try:
244
+ fd = os.open(candidate, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644)
245
+ except FileExistsError:
246
+ n += 1
247
+ continue
248
+ os.close(fd)
249
+ return candidate
250
+
251
+
252
+ def _get_body(storage: httpx.Client, url: str, timeout: httpx.Timeout, name: str) -> Iterator[bytes]:
253
+ with storage.stream("GET", url, follow_redirects=True, timeout=timeout) as resp:
254
+ if resp.status_code != 200:
255
+ raise CliError(f"download of {name} failed (http {resp.status_code})")
256
+ yield from resp.iter_bytes()
257
+
258
+
259
+ def download_url_to_dir(
260
+ storage: httpx.Client, url: str, directory: Path, name: str, *, deadline: Deadline | None = None
261
+ ) -> Path:
262
+ """将文件下载到 ``directory``,命名为 ``name``;重名时添加 `` (n)``,不覆盖已有文件。返回保存路径。
263
+
264
+ 响应内容先写入隐藏临时文件,完整下载后才使用最终文件名。
265
+ 传入 ``deadline`` 时,到期即停止下载并抛出 CliError,即使此时仍在等待读取,详见 iterate_until。
266
+ """
267
+ directory.mkdir(parents=True, exist_ok=True)
268
+ final_name = safe_filename(name, fallback="download")
269
+ fd, tmp = tempfile.mkstemp(prefix=".cheso-", suffix=".part", dir=directory)
270
+ try:
271
+ with os.fdopen(fd, "wb") as f:
272
+ try:
273
+ if deadline is None:
274
+ body = _get_body(storage, url, STORAGE_TIMEOUT, final_name)
275
+ else:
276
+ timeout = deadline.cap(STORAGE_TIMEOUT)
277
+ body = iterate_until(deadline, lambda: _get_body(storage, url, timeout, final_name))
278
+ with contextlib.closing(body):
279
+ for chunk in body:
280
+ f.write(chunk)
281
+ except httpx.HTTPError as e:
282
+ # 同时处理 TimeLimitReached,以及被剩余时限提前终止的读取。
283
+ if isinstance(e, TimeLimitReached) or (deadline and deadline.expired()):
284
+ raise CliError(f"download of {final_name} stopped: --timeout reached") from e
285
+ raise CliError(f"download of {final_name} failed: {e.__class__.__name__}") from e
286
+ target = _reserve_unique(directory, final_name)
287
+ os.replace(tmp, target)
288
+ with contextlib.suppress(OSError):
289
+ os.chmod(target, _file_mode())
290
+ return target
291
+ except BaseException:
292
+ with contextlib.suppress(OSError):
293
+ os.unlink(tmp)
294
+ raise
@@ -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,18 @@
1
+ cheso_cli/__init__.py,sha256=jN_CQOjBxiu3sDzVgFToJ4Jl8f77nJqRyP16LDX4tVk,297
2
+ cheso_cli/__main__.py,sha256=Hc4BL-IeJrsGzr05J_uEEWoddTjUXT9HYrCnryvlrpE,94
3
+ cheso_cli/auth.py,sha256=FEwZcnmSnwioDXG0bX0aRk6ewDBT9cectstxGBV2ufc,5517
4
+ cheso_cli/cli.py,sha256=h7JygZv521sjp6y8VHcCd2R1eEv4phHefZkxL9AnXh4,35801
5
+ cheso_cli/client.py,sha256=-lZhaDfvyRcZIuFTKW4yfeEPuc1Di2t-t-0kbyyVzxQ,19095
6
+ cheso_cli/config.py,sha256=ZPm-l9F0df93FGeKmcsQaKkExeanHdi6VWLYwrce5co,3896
7
+ cheso_cli/credentials.py,sha256=4psYBoWV4BomnMmvBXLAQg8sjZEldnhYENz4z8UxTxg,8695
8
+ cheso_cli/errors.py,sha256=NkwLnatOwIruBsJHoMV56HHOPji4ts1ku_KFLE7e_-I,1615
9
+ cheso_cli/legacy_commands.py,sha256=ir4bT9obzNTJJQUwc1EyfwBpy9QUeaL-nuhpBt-9D0c,2715
10
+ cheso_cli/oauth.py,sha256=p4KZEgzX9jNJxbEDCM5S8yGSHdvV1T-HauHAneqC0MY,35134
11
+ cheso_cli/skill/SKILL.md,sha256=KCiHQR9xlTFogDPicKRE-sGc38AIICjvlitLzSWlIAA,6278
12
+ cheso_cli/skill_install.py,sha256=wIAt5jreBCxKvCHj5s90jL4FtJCvnb6p88vw5VgUQXU,4458
13
+ cheso_cli/tasks.py,sha256=dF2qbMhKJRSqmYLULmGn9GLoW0mr7rp3cnUeenD9XU4,37299
14
+ cheso_cli/transfer.py,sha256=361Xu1VOP3jz0j4Dyrn6-1ek1xHGD72vFip5BHnaTAw,11028
15
+ cheso_cli-0.2.0.dist-info/WHEEL,sha256=7hzKWg-J8I3Buqyw5tBii5z_MmAVsDYXXijd_QAtNZ8,81
16
+ cheso_cli-0.2.0.dist-info/entry_points.txt,sha256=EV6lU1QwuVvZlnSWVqIIbP2GdX5cdrnyzqxLwtH7AFs,77
17
+ cheso_cli-0.2.0.dist-info/METADATA,sha256=BdYu7ASYOSskYPAzLdx1gGhn9Fcp-heZoHdDE2E3DSc,9578
18
+ cheso_cli-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.18
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ cheso = cheso_cli.cli:main
3
+ cheso-cli = cheso_cli.cli:main
4
+