web-presentation-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.
- web_presentation_cli-0.1.0/.gitignore +29 -0
- web_presentation_cli-0.1.0/PKG-INFO +64 -0
- web_presentation_cli-0.1.0/README.md +50 -0
- web_presentation_cli-0.1.0/hatch_build.py +21 -0
- web_presentation_cli-0.1.0/pyproject.toml +46 -0
- web_presentation_cli-0.1.0/src/wp/__init__.py +3 -0
- web_presentation_cli-0.1.0/src/wp/cli.py +68 -0
- web_presentation_cli-0.1.0/src/wp/client.py +27 -0
- web_presentation_cli-0.1.0/src/wp/commands/__init__.py +1 -0
- web_presentation_cli-0.1.0/src/wp/commands/asset.py +218 -0
- web_presentation_cli-0.1.0/src/wp/commands/auth.py +98 -0
- web_presentation_cli-0.1.0/src/wp/commands/catalog.py +84 -0
- web_presentation_cli-0.1.0/src/wp/commands/common.py +182 -0
- web_presentation_cli-0.1.0/src/wp/commands/component.py +293 -0
- web_presentation_cli-0.1.0/src/wp/commands/doctor.py +84 -0
- web_presentation_cli-0.1.0/src/wp/commands/job.py +84 -0
- web_presentation_cli-0.1.0/src/wp/commands/page.py +314 -0
- web_presentation_cli-0.1.0/src/wp/commands/profile.py +71 -0
- web_presentation_cli-0.1.0/src/wp/commands/project.py +244 -0
- web_presentation_cli-0.1.0/src/wp/commands/screenshot.py +73 -0
- web_presentation_cli-0.1.0/src/wp/commands/style.py +155 -0
- web_presentation_cli-0.1.0/src/wp/commands/system.py +93 -0
- web_presentation_cli-0.1.0/src/wp/commands/theme.py +152 -0
- web_presentation_cli-0.1.0/src/wp/commands/workspace.py +89 -0
- web_presentation_cli-0.1.0/src/wp/config.py +91 -0
- web_presentation_cli-0.1.0/src/wp/formatter.py +66 -0
- web_presentation_cli-0.1.0/src/wp_api_client/__init__.py +5 -0
- web_presentation_cli-0.1.0/src/wp_api_client/client.py +431 -0
- web_presentation_cli-0.1.0/tests/test_capability_commands.py +121 -0
- web_presentation_cli-0.1.0/tests/test_cli_commands.py +300 -0
- web_presentation_cli-0.1.0/tests/test_screenshot_command.py +146 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
.coverage*
|
|
10
|
+
htmlcov/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
|
|
15
|
+
# Local configuration and secrets
|
|
16
|
+
.env
|
|
17
|
+
.env.*
|
|
18
|
+
!.env.example
|
|
19
|
+
!.env.*.example
|
|
20
|
+
|
|
21
|
+
# Agent and tool caches
|
|
22
|
+
.codex/
|
|
23
|
+
.tmp/
|
|
24
|
+
|
|
25
|
+
# Editors and OS
|
|
26
|
+
.vscode/
|
|
27
|
+
.idea/
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: web-presentation-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Web Presentation CLI - 面向 AI 演示文稿创作平台的命令行与 Agent 工具包
|
|
5
|
+
Project-URL: Homepage, https://github.com/LLMxPM/web-presentation-agent-kit
|
|
6
|
+
Project-URL: Repository, https://github.com/LLMxPM/web-presentation-agent-kit
|
|
7
|
+
Project-URL: Issues, https://github.com/LLMxPM/web-presentation-agent-kit/issues
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: click>=8.1.7
|
|
10
|
+
Requires-Dist: httpx>=0.27.0
|
|
11
|
+
Requires-Dist: pydantic>=2.7.0
|
|
12
|
+
Requires-Dist: rich>=13.7.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Web Presentation CLI (`wp`)
|
|
16
|
+
|
|
17
|
+
面向 `web-presentation` AI 演示文稿创作平台的官方命令行与 Agent 工具包。
|
|
18
|
+
|
|
19
|
+
External API 路径、Scope、错误码、幂等和异步任务语义以主仓 [External Agent API v1 契约](https://github.com/LLMxPM/web-presentation/blob/main/docs/developer/reference/external-agent-api.md) 为准;CLI 命令和参数以本仓 `wp --help` 与 CLI 实施文档为准。
|
|
20
|
+
|
|
21
|
+
## 安装与快速开始
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# 从 PyPI 安装公开发行版
|
|
25
|
+
uv tool install web-presentation-cli
|
|
26
|
+
# 或使用 pipx
|
|
27
|
+
pipx install web-presentation-cli
|
|
28
|
+
|
|
29
|
+
# 在 agent-kit 仓库根目录安装 CLI
|
|
30
|
+
uv pip install -e ./packages/cli
|
|
31
|
+
|
|
32
|
+
# 登录并绑定 PAT 令牌
|
|
33
|
+
wp login --token wp_pat_xxxxxxxx.yyyyyyyyyyyyyyyy
|
|
34
|
+
# 生产环境可指定 Backend 根地址(不要包含 /api/v1)
|
|
35
|
+
wp login --endpoint https://api.example.com --token wp_pat_xxxxxxxx.yyyyyyyyyyyyyyyy
|
|
36
|
+
|
|
37
|
+
# 检查环境与连通性
|
|
38
|
+
wp doctor
|
|
39
|
+
|
|
40
|
+
# 查看授权的工作空间并切换当前工作空间
|
|
41
|
+
wp workspace list
|
|
42
|
+
wp workspace use <workspace_id>
|
|
43
|
+
|
|
44
|
+
# 查看并切换默认 Profile(Profile 保存 Backend 地址、PAT 和默认工作空间)
|
|
45
|
+
wp profile list
|
|
46
|
+
wp profile use production
|
|
47
|
+
|
|
48
|
+
# 常用操作
|
|
49
|
+
wp system health
|
|
50
|
+
wp project list
|
|
51
|
+
wp project configuration get <project_id>
|
|
52
|
+
wp page list --project-id <project_id>
|
|
53
|
+
wp page dependencies <page_id>
|
|
54
|
+
wp component list --scope suggested --project-id <project_id>
|
|
55
|
+
wp asset content get <asset_id>
|
|
56
|
+
wp theme list
|
|
57
|
+
wp style list
|
|
58
|
+
wp --json guide list
|
|
59
|
+
wp job wait <job_id>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
复杂写入参数使用 `--payload-file`、`--edits-file`、`--content-file`、`--route-file` 和 `--ids-file`。Build、产物下载、Agent 运行、图片能力、Restore 和 MCP 不属于当前 CLI。
|
|
63
|
+
|
|
64
|
+
写入命令支持 `--idempotency-key <key>`;网络超时后需要重放同一业务请求时复用原 key,不要把同一个 key 用于不同请求。
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Web Presentation CLI (`wp`)
|
|
2
|
+
|
|
3
|
+
面向 `web-presentation` AI 演示文稿创作平台的官方命令行与 Agent 工具包。
|
|
4
|
+
|
|
5
|
+
External API 路径、Scope、错误码、幂等和异步任务语义以主仓 [External Agent API v1 契约](https://github.com/LLMxPM/web-presentation/blob/main/docs/developer/reference/external-agent-api.md) 为准;CLI 命令和参数以本仓 `wp --help` 与 CLI 实施文档为准。
|
|
6
|
+
|
|
7
|
+
## 安装与快速开始
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 从 PyPI 安装公开发行版
|
|
11
|
+
uv tool install web-presentation-cli
|
|
12
|
+
# 或使用 pipx
|
|
13
|
+
pipx install web-presentation-cli
|
|
14
|
+
|
|
15
|
+
# 在 agent-kit 仓库根目录安装 CLI
|
|
16
|
+
uv pip install -e ./packages/cli
|
|
17
|
+
|
|
18
|
+
# 登录并绑定 PAT 令牌
|
|
19
|
+
wp login --token wp_pat_xxxxxxxx.yyyyyyyyyyyyyyyy
|
|
20
|
+
# 生产环境可指定 Backend 根地址(不要包含 /api/v1)
|
|
21
|
+
wp login --endpoint https://api.example.com --token wp_pat_xxxxxxxx.yyyyyyyyyyyyyyyy
|
|
22
|
+
|
|
23
|
+
# 检查环境与连通性
|
|
24
|
+
wp doctor
|
|
25
|
+
|
|
26
|
+
# 查看授权的工作空间并切换当前工作空间
|
|
27
|
+
wp workspace list
|
|
28
|
+
wp workspace use <workspace_id>
|
|
29
|
+
|
|
30
|
+
# 查看并切换默认 Profile(Profile 保存 Backend 地址、PAT 和默认工作空间)
|
|
31
|
+
wp profile list
|
|
32
|
+
wp profile use production
|
|
33
|
+
|
|
34
|
+
# 常用操作
|
|
35
|
+
wp system health
|
|
36
|
+
wp project list
|
|
37
|
+
wp project configuration get <project_id>
|
|
38
|
+
wp page list --project-id <project_id>
|
|
39
|
+
wp page dependencies <page_id>
|
|
40
|
+
wp component list --scope suggested --project-id <project_id>
|
|
41
|
+
wp asset content get <asset_id>
|
|
42
|
+
wp theme list
|
|
43
|
+
wp style list
|
|
44
|
+
wp --json guide list
|
|
45
|
+
wp job wait <job_id>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
复杂写入参数使用 `--payload-file`、`--edits-file`、`--content-file`、`--route-file` 和 `--ids-file`。Build、产物下载、Agent 运行、图片能力、Restore 和 MCP 不属于当前 CLI。
|
|
49
|
+
|
|
50
|
+
写入命令支持 `--idempotency-key <key>`;网络超时后需要重放同一业务请求时复用原 key,不要把同一个 key 用于不同请求。
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""文件功能:为 CLI 的 editable 安装映射共享 API Client 源码。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CustomBuildHook(BuildHookInterface):
|
|
12
|
+
"""在本地 editable 构建时,把同仓 API Client 映射到 CLI 环境。"""
|
|
13
|
+
|
|
14
|
+
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
|
|
15
|
+
"""为 editable wheel 注入跨目录 API Client,正式发行包沿用标准打包配置。"""
|
|
16
|
+
|
|
17
|
+
if version != "editable":
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
api_client_source = Path(self.root).parent / "api-client" / "src" / "wp_api_client"
|
|
21
|
+
build_data["force_include_editable"] = {str(api_client_source): "wp_api_client"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "web-presentation-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Web Presentation CLI - 面向 AI 演示文稿创作平台的命令行与 Agent 工具包"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"click>=8.1.7",
|
|
13
|
+
"httpx>=0.27.0",
|
|
14
|
+
"rich>=13.7.0",
|
|
15
|
+
"pydantic>=2.7.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://github.com/LLMxPM/web-presentation-agent-kit"
|
|
20
|
+
Repository = "https://github.com/LLMxPM/web-presentation-agent-kit"
|
|
21
|
+
Issues = "https://github.com/LLMxPM/web-presentation-agent-kit/issues"
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
dev = [
|
|
25
|
+
"pytest>=8.0.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
wp = "wp.cli:main"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/wp"]
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.sdist.force-include]
|
|
35
|
+
"../api-client/src/wp_api_client" = "src/wp_api_client"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
38
|
+
"src/wp_api_client" = "wp_api_client"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel.hooks.custom]
|
|
41
|
+
path = "hatch_build.py"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""文件功能:CLI 总入口,注册全局选项与全部资源子命令。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from wp import __version__
|
|
8
|
+
from wp.commands.asset import asset_group
|
|
9
|
+
from wp.commands.auth import login_cmd, logout_cmd, whoami_cmd
|
|
10
|
+
from wp.commands.component import component_group
|
|
11
|
+
from wp.commands.catalog import font_group, runtime_kit_group
|
|
12
|
+
from wp.commands.doctor import doctor_cmd
|
|
13
|
+
from wp.commands.job import job_group
|
|
14
|
+
from wp.commands.page import page_group
|
|
15
|
+
from wp.commands.profile import profile_group
|
|
16
|
+
from wp.commands.project import project_group
|
|
17
|
+
from wp.commands.style import style_group
|
|
18
|
+
from wp.commands.theme import theme_group
|
|
19
|
+
from wp.commands.workspace import workspace_group
|
|
20
|
+
from wp.commands.system import guide_group, standards_group, system_group
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.group()
|
|
24
|
+
@click.version_option(version=__version__, prog_name="wp")
|
|
25
|
+
@click.option(
|
|
26
|
+
"--json",
|
|
27
|
+
"as_json",
|
|
28
|
+
is_flag=True,
|
|
29
|
+
help="让支持表格视图的命令输出 JSON;复杂响应默认也输出 JSON。",
|
|
30
|
+
)
|
|
31
|
+
@click.option("--profile", help="指定使用的 Profile 名称 (默认 default)")
|
|
32
|
+
@click.option("--workspace", "-w", "workspace_id", type=int, help="覆盖当前操作的目标工作空间 ID")
|
|
33
|
+
@click.pass_context
|
|
34
|
+
def main(ctx: click.Context, as_json: bool, profile: str | None, workspace_id: int | None) -> None:
|
|
35
|
+
"""Web Presentation 官方命令行工具 (wp) - 面向 AI 演示文稿创作平台。"""
|
|
36
|
+
|
|
37
|
+
ctx.ensure_object(dict)
|
|
38
|
+
ctx.obj["as_json"] = as_json
|
|
39
|
+
ctx.obj["profile"] = profile
|
|
40
|
+
ctx.obj["workspace_id"] = workspace_id
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# 注册认证与系统命令
|
|
44
|
+
main.add_command(login_cmd)
|
|
45
|
+
main.add_command(logout_cmd)
|
|
46
|
+
main.add_command(whoami_cmd)
|
|
47
|
+
main.add_command(doctor_cmd)
|
|
48
|
+
main.add_command(profile_group)
|
|
49
|
+
main.add_command(system_group)
|
|
50
|
+
main.add_command(standards_group)
|
|
51
|
+
main.add_command(guide_group)
|
|
52
|
+
main.add_command(runtime_kit_group)
|
|
53
|
+
main.add_command(font_group)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# 注册各实体资源命令组
|
|
57
|
+
main.add_command(workspace_group)
|
|
58
|
+
main.add_command(project_group)
|
|
59
|
+
main.add_command(page_group)
|
|
60
|
+
main.add_command(component_group)
|
|
61
|
+
main.add_command(asset_group)
|
|
62
|
+
main.add_command(theme_group)
|
|
63
|
+
main.add_command(style_group)
|
|
64
|
+
main.add_command(job_group)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""文件功能:提供 CLI 对共享 External API v1 客户端的 Profile 适配。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from wp.config import ProfileConfig
|
|
6
|
+
from wp_api_client import ApiClient as SharedApiClient, ApiClientError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ApiClient(SharedApiClient):
|
|
10
|
+
"""把 CLI ProfileConfig 适配为共享 API Client。"""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
profile: ProfileConfig,
|
|
15
|
+
workspace_id: int | None = None,
|
|
16
|
+
idempotency_key: str | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
super().__init__(
|
|
19
|
+
endpoint=profile.endpoint,
|
|
20
|
+
token=profile.token,
|
|
21
|
+
workspace_id=workspace_id or profile.default_workspace_id,
|
|
22
|
+
user_agent="web-presentation-cli/0.1.0",
|
|
23
|
+
idempotency_key=idempotency_key,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = ["ApiClient", "ApiClientError"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI 子命令模块集合。"""
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""文件功能:提供工作空间资源的查询、上传、文本内容、复制、更新与归档命令。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from wp.client import ApiClientError
|
|
10
|
+
from wp.commands.common import (
|
|
11
|
+
confirm_archive,
|
|
12
|
+
get_client,
|
|
13
|
+
handle_api_error,
|
|
14
|
+
idempotency_key_option,
|
|
15
|
+
output_result,
|
|
16
|
+
read_json_file,
|
|
17
|
+
read_text_file,
|
|
18
|
+
require_array,
|
|
19
|
+
require_ids,
|
|
20
|
+
require_object,
|
|
21
|
+
)
|
|
22
|
+
from wp.formatter import print_table
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
ASSET_TYPES = ["image", "icon", "font", "video", "drawio", "mermaid", "chart", "formula"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.group("asset")
|
|
29
|
+
def asset_group() -> None:
|
|
30
|
+
"""工作空间静态资源管理。"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@asset_group.command("list")
|
|
34
|
+
@click.option("--page", default=1, type=int)
|
|
35
|
+
@click.option("--page-size", default=50, type=int)
|
|
36
|
+
@click.option("--type", "asset_type", type=click.Choice(ASSET_TYPES))
|
|
37
|
+
@click.option("--keyword")
|
|
38
|
+
@click.pass_context
|
|
39
|
+
def list_assets_cmd(ctx: click.Context, page: int, page_size: int, asset_type: str | None, keyword: str | None) -> None:
|
|
40
|
+
"""查询工作空间资源。"""
|
|
41
|
+
|
|
42
|
+
params = {"page": page, "page_size": page_size, "asset_type": asset_type, "keyword": keyword}
|
|
43
|
+
try:
|
|
44
|
+
result = get_client(ctx).get("/assets", params={key: value for key, value in params.items() if value is not None})
|
|
45
|
+
if ctx.obj.get("as_json"):
|
|
46
|
+
output_result(ctx, result)
|
|
47
|
+
return
|
|
48
|
+
rows = [[item.get("id"), item.get("name", "-"), item.get("asset_type", "-"), item.get("status", "-")] for item in result.get("items", [])]
|
|
49
|
+
print_table("资源列表", ["ID", "名称", "类型", "状态"], rows)
|
|
50
|
+
except ApiClientError as err:
|
|
51
|
+
handle_api_error("获取资源列表失败", err)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@asset_group.command("get")
|
|
55
|
+
@click.argument("asset_id", type=int)
|
|
56
|
+
@click.pass_context
|
|
57
|
+
def get_asset_cmd(ctx: click.Context, asset_id: int) -> None:
|
|
58
|
+
"""获取资源详情。"""
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
output_result(ctx, get_client(ctx).get(f"/assets/{asset_id}"))
|
|
62
|
+
except ApiClientError as err:
|
|
63
|
+
handle_api_error("获取资源详情失败", err)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@asset_group.command("upload")
|
|
67
|
+
@click.argument("file_path", type=click.Path(exists=True, dir_okay=False))
|
|
68
|
+
@click.option("--type", "asset_type", type=click.Choice(ASSET_TYPES), required=True)
|
|
69
|
+
@click.option("--name")
|
|
70
|
+
@click.option("--description")
|
|
71
|
+
@idempotency_key_option
|
|
72
|
+
@click.pass_context
|
|
73
|
+
def upload_asset_cmd(ctx: click.Context, file_path: str, asset_type: str, name: str | None, description: str | None) -> None:
|
|
74
|
+
"""上传二进制或图片资源。"""
|
|
75
|
+
|
|
76
|
+
path = Path(file_path)
|
|
77
|
+
try:
|
|
78
|
+
with path.open("rb") as file_obj:
|
|
79
|
+
result = get_client(ctx).upload(
|
|
80
|
+
"/assets",
|
|
81
|
+
files={"file": (path.name, file_obj)},
|
|
82
|
+
data={"asset_type": asset_type, "name": name or path.stem, "description": description},
|
|
83
|
+
)
|
|
84
|
+
output_result(ctx, result)
|
|
85
|
+
except ApiClientError as err:
|
|
86
|
+
handle_api_error("上传资源失败", err)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@asset_group.command("create")
|
|
90
|
+
@click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True)
|
|
91
|
+
@click.option("--content-file", type=click.Path(exists=True, dir_okay=False), help="覆盖 JSON 中 content 的文本文件")
|
|
92
|
+
@idempotency_key_option
|
|
93
|
+
@click.pass_context
|
|
94
|
+
def create_asset_cmd(ctx: click.Context, payload_file: str, content_file: str | None) -> None:
|
|
95
|
+
"""创建文本内容资源。"""
|
|
96
|
+
|
|
97
|
+
payload = require_object(read_json_file(payload_file, label="资源创建载荷"), label="资源创建载荷")
|
|
98
|
+
if content_file:
|
|
99
|
+
payload["content"] = read_text_file(content_file, label="资源内容")
|
|
100
|
+
try:
|
|
101
|
+
output_result(ctx, get_client(ctx).post("/assets/content", json_data=payload))
|
|
102
|
+
except ApiClientError as err:
|
|
103
|
+
handle_api_error("创建文本资源失败", err)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@asset_group.group("content")
|
|
107
|
+
def asset_content_group() -> None:
|
|
108
|
+
"""可编辑文本资源内容。"""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@asset_content_group.command("get")
|
|
112
|
+
@click.argument("asset_id", type=int)
|
|
113
|
+
@click.pass_context
|
|
114
|
+
def get_asset_content_cmd(ctx: click.Context, asset_id: int) -> None:
|
|
115
|
+
"""读取资源文本内容。"""
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
output_result(ctx, get_client(ctx).get(f"/assets/{asset_id}/content"))
|
|
119
|
+
except ApiClientError as err:
|
|
120
|
+
handle_api_error("读取资源内容失败", err)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@asset_content_group.command("update")
|
|
124
|
+
@click.argument("asset_id", type=int)
|
|
125
|
+
@click.option("--content-file", type=click.Path(exists=True, dir_okay=False), required=True)
|
|
126
|
+
@click.option("--change-note")
|
|
127
|
+
@idempotency_key_option
|
|
128
|
+
@click.pass_context
|
|
129
|
+
def update_asset_content_cmd(ctx: click.Context, asset_id: int, content_file: str, change_note: str | None) -> None:
|
|
130
|
+
"""写入资源文本内容。"""
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
output_result(ctx, get_client(ctx).put(f"/assets/{asset_id}/content", json_data={"content": read_text_file(content_file, label="资源内容"), "change_note": change_note}))
|
|
134
|
+
except ApiClientError as err:
|
|
135
|
+
handle_api_error("更新资源内容失败", err)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@asset_content_group.command("preview")
|
|
139
|
+
@click.argument("asset_id", type=int)
|
|
140
|
+
@click.option("--content-file", type=click.Path(exists=True, dir_okay=False), required=True)
|
|
141
|
+
@click.pass_context
|
|
142
|
+
def preview_asset_content_cmd(ctx: click.Context, asset_id: int, content_file: str) -> None:
|
|
143
|
+
"""预览资源文本内容差异。"""
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
output_result(ctx, get_client(ctx).post(f"/assets/{asset_id}/content/preview", json_data={"content": read_text_file(content_file, label="资源内容")}, idempotent=False))
|
|
147
|
+
except ApiClientError as err:
|
|
148
|
+
handle_api_error("预览资源内容失败", err)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@asset_group.group("tags")
|
|
152
|
+
def asset_tags_group() -> None:
|
|
153
|
+
"""资源标签。"""
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@asset_tags_group.command("list")
|
|
157
|
+
@click.option("--type", "asset_type", type=click.Choice(ASSET_TYPES))
|
|
158
|
+
@click.pass_context
|
|
159
|
+
def list_asset_tags_cmd(ctx: click.Context, asset_type: str | None) -> None:
|
|
160
|
+
"""列出工作空间资源标签。"""
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
params = {"asset_type": asset_type} if asset_type else None
|
|
164
|
+
output_result(ctx, get_client(ctx).get("/assets/tags", params=params))
|
|
165
|
+
except ApiClientError as err:
|
|
166
|
+
handle_api_error("获取资源标签失败", err)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@asset_group.command("update")
|
|
170
|
+
@click.argument("asset_id", type=int)
|
|
171
|
+
@click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True)
|
|
172
|
+
@idempotency_key_option
|
|
173
|
+
@click.pass_context
|
|
174
|
+
def update_asset_cmd(ctx: click.Context, asset_id: int, payload_file: str) -> None:
|
|
175
|
+
"""更新资源元数据。"""
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
output_result(ctx, get_client(ctx).patch(f"/assets/{asset_id}", json_data=require_object(read_json_file(payload_file, label="资源更新载荷"), label="资源更新载荷")))
|
|
179
|
+
except ApiClientError as err:
|
|
180
|
+
handle_api_error("更新资源失败", err)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@asset_group.command("copy")
|
|
184
|
+
@click.argument("asset_id", type=int)
|
|
185
|
+
@click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True)
|
|
186
|
+
@idempotency_key_option
|
|
187
|
+
@click.pass_context
|
|
188
|
+
def copy_asset_cmd(ctx: click.Context, asset_id: int, payload_file: str) -> None:
|
|
189
|
+
"""复制资源。"""
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
output_result(ctx, get_client(ctx).post(f"/assets/{asset_id}/copy", json_data=require_object(read_json_file(payload_file, label="资源复制载荷"), label="资源复制载荷")))
|
|
193
|
+
except ApiClientError as err:
|
|
194
|
+
handle_api_error("复制资源失败", err)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@asset_group.command("archive")
|
|
198
|
+
@click.argument("asset_id", type=int, required=False)
|
|
199
|
+
@click.option("--ids-file", type=click.Path(exists=True, dir_okay=False))
|
|
200
|
+
@click.option("--yes", is_flag=True)
|
|
201
|
+
@idempotency_key_option
|
|
202
|
+
@click.pass_context
|
|
203
|
+
def archive_asset_cmd(ctx: click.Context, asset_id: int | None, ids_file: str | None, yes: bool) -> None:
|
|
204
|
+
"""归档单个或一批资源。"""
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
client = get_client(ctx)
|
|
208
|
+
if ids_file:
|
|
209
|
+
ids = require_ids(read_json_file(ids_file, label="归档 ID"))
|
|
210
|
+
confirm_archive(ids, yes=yes, label="资源")
|
|
211
|
+
output_result(ctx, client.post("/assets/batch-archive", json_data={"ids": ids}))
|
|
212
|
+
return
|
|
213
|
+
if asset_id is None:
|
|
214
|
+
raise click.UsageError("必须提供 asset_id 或 --ids-file。")
|
|
215
|
+
confirm_archive([asset_id], yes=yes, label="资源")
|
|
216
|
+
output_result(ctx, client.post(f"/assets/{asset_id}/archive"))
|
|
217
|
+
except ApiClientError as err:
|
|
218
|
+
handle_api_error("归档资源失败", err)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""文件功能:处理 CLI 用户登录、PAT 凭证持久化与身份检查。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from wp.client import ApiClient, ApiClientError
|
|
8
|
+
from wp.config import get_profile, load_config, save_config
|
|
9
|
+
from wp.formatter import print_error, print_json, print_success, print_table
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command("login")
|
|
13
|
+
@click.option("--token", "-t", required=True, prompt=True, hide_input=True, help="个人访问令牌 (PAT)")
|
|
14
|
+
@click.option("--endpoint", "-e", help="Backend 服务地址 (默认 http://127.0.0.1:8000)")
|
|
15
|
+
@click.pass_context
|
|
16
|
+
def login_cmd(ctx: click.Context, token: str, endpoint: str | None) -> None:
|
|
17
|
+
"""登录并保存个人访问令牌 (PAT)。"""
|
|
18
|
+
|
|
19
|
+
cfg = load_config()
|
|
20
|
+
profile = get_profile(cfg, ctx.obj.get("profile"))
|
|
21
|
+
if endpoint:
|
|
22
|
+
profile.endpoint = endpoint
|
|
23
|
+
profile.token = token.strip()
|
|
24
|
+
|
|
25
|
+
client = ApiClient(profile)
|
|
26
|
+
try:
|
|
27
|
+
workspaces = client.get("/workspaces")
|
|
28
|
+
if workspaces:
|
|
29
|
+
profile.default_workspace_id = workspaces[0]["id"]
|
|
30
|
+
save_config(cfg)
|
|
31
|
+
print_success(f"登录成功!已连接到 {profile.endpoint},授权工作空间数: {len(workspaces)}")
|
|
32
|
+
except ApiClientError as err:
|
|
33
|
+
print_error(f"PAT 验证失败: {err.message}", code=err.code)
|
|
34
|
+
raise SystemExit(1)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.command("logout")
|
|
38
|
+
@click.pass_context
|
|
39
|
+
def logout_cmd(ctx: click.Context) -> None:
|
|
40
|
+
"""清除当前 Profile 的访问令牌。"""
|
|
41
|
+
|
|
42
|
+
cfg = load_config()
|
|
43
|
+
profile = get_profile(cfg, ctx.obj.get("profile"))
|
|
44
|
+
profile.token = None
|
|
45
|
+
profile.default_workspace_id = None
|
|
46
|
+
save_config(cfg)
|
|
47
|
+
print_success("已安全清除本地保存的访问令牌。")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@click.command("whoami")
|
|
51
|
+
@click.pass_context
|
|
52
|
+
def whoami_cmd(ctx: click.Context) -> None:
|
|
53
|
+
"""检查当前认证状态与权限能力。"""
|
|
54
|
+
|
|
55
|
+
cfg = load_config()
|
|
56
|
+
profile = get_profile(cfg, ctx.obj.get("profile"))
|
|
57
|
+
client = ApiClient(profile, workspace_id=ctx.obj.get("workspace_id"))
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
identity = client.get("/auth/whoami")
|
|
61
|
+
workspaces = identity.get("workspaces", [])
|
|
62
|
+
if ctx.obj.get("as_json"):
|
|
63
|
+
print_json({"endpoint": profile.endpoint, **identity})
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
user = identity.get("user") or {}
|
|
67
|
+
print_success(
|
|
68
|
+
f"当前环境: [bold]{profile.endpoint}[/bold],"
|
|
69
|
+
f"用户: [bold]{user.get('display_name') or user.get('username') or '-'}[/bold]"
|
|
70
|
+
)
|
|
71
|
+
print_table(
|
|
72
|
+
title="当前身份",
|
|
73
|
+
columns=["字段", "值"],
|
|
74
|
+
rows=[
|
|
75
|
+
["用户 ID", user.get("id")],
|
|
76
|
+
["用户名", user.get("username")],
|
|
77
|
+
["角色", user.get("role")],
|
|
78
|
+
["状态", user.get("status")],
|
|
79
|
+
],
|
|
80
|
+
)
|
|
81
|
+
token = identity.get("token") or {}
|
|
82
|
+
print_table(
|
|
83
|
+
title="当前 PAT",
|
|
84
|
+
columns=["字段", "值"],
|
|
85
|
+
rows=[
|
|
86
|
+
["公开 ID", token.get("token_public_id")],
|
|
87
|
+
["Scopes", ", ".join(token.get("scopes") or []) or "-"],
|
|
88
|
+
["过期时间", token.get("expires_at") or "未设置"],
|
|
89
|
+
],
|
|
90
|
+
)
|
|
91
|
+
print_table(
|
|
92
|
+
title="已授权工作空间",
|
|
93
|
+
columns=["ID", "空间名称", "成员角色"],
|
|
94
|
+
rows=[[w["id"], w["name"], w.get("role")] for w in workspaces],
|
|
95
|
+
)
|
|
96
|
+
except ApiClientError as err:
|
|
97
|
+
print_error(f"查询失败: {err.message}", code=err.code)
|
|
98
|
+
raise SystemExit(1)
|