web-presentation-cli 0.1.0__tar.gz → 0.1.1__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.
Files changed (33) hide show
  1. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/PKG-INFO +4 -2
  2. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/README.md +2 -1
  3. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/pyproject.toml +2 -1
  4. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/__init__.py +1 -1
  5. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/cli.py +1 -2
  6. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/client.py +1 -1
  7. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/asset.py +44 -29
  8. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/catalog.py +13 -12
  9. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/component.py +71 -44
  10. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/job.py +11 -5
  11. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/page.py +41 -20
  12. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/project.py +38 -17
  13. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/style.py +24 -13
  14. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/system.py +6 -33
  15. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/theme.py +25 -14
  16. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/workspace.py +5 -4
  17. web_presentation_cli-0.1.1/src/wp/openapi_help.py +154 -0
  18. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp_api_client/client.py +26 -5
  19. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/tests/test_capability_commands.py +1 -1
  20. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/tests/test_cli_commands.py +2 -2
  21. web_presentation_cli-0.1.1/tests/test_openapi_help.py +113 -0
  22. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/.gitignore +0 -0
  23. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/hatch_build.py +0 -0
  24. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/__init__.py +0 -0
  25. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/auth.py +0 -0
  26. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/common.py +0 -0
  27. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/doctor.py +0 -0
  28. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/profile.py +0 -0
  29. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/commands/screenshot.py +0 -0
  30. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/config.py +0 -0
  31. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp/formatter.py +0 -0
  32. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/src/wp_api_client/__init__.py +0 -0
  33. {web_presentation_cli-0.1.0 → web_presentation_cli-0.1.1}/tests/test_screenshot_command.py +0 -0
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: web-presentation-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Web Presentation CLI - 面向 AI 演示文稿创作平台的命令行与 Agent 工具包
5
5
  Project-URL: Homepage, https://github.com/LLMxPM/web-presentation-agent-kit
6
6
  Project-URL: Repository, https://github.com/LLMxPM/web-presentation-agent-kit
7
7
  Project-URL: Issues, https://github.com/LLMxPM/web-presentation-agent-kit/issues
8
+ License: Apache-2.0
8
9
  Requires-Python: >=3.11
9
10
  Requires-Dist: click>=8.1.7
10
11
  Requires-Dist: httpx>=0.27.0
@@ -55,10 +56,11 @@ wp component list --scope suggested --project-id <project_id>
55
56
  wp asset content get <asset_id>
56
57
  wp theme list
57
58
  wp style list
58
- wp --json guide list
59
59
  wp job wait <job_id>
60
60
  ```
61
61
 
62
62
  复杂写入参数使用 `--payload-file`、`--edits-file`、`--content-file`、`--route-file` 和 `--ids-file`。Build、产物下载、Agent 运行、图片能力、Restore 和 MCP 不属于当前 CLI。
63
63
 
64
+ 叶子命令的 `--help` 会从当前 Profile 的 Backend `/openapi.json` 加载请求参数和完整 Schema;服务不可达时仍返回本地语法帮助,不缓存 Schema。
65
+
64
66
  写入命令支持 `--idempotency-key <key>`;网络超时后需要重放同一业务请求时复用原 key,不要把同一个 key 用于不同请求。
@@ -41,10 +41,11 @@ wp component list --scope suggested --project-id <project_id>
41
41
  wp asset content get <asset_id>
42
42
  wp theme list
43
43
  wp style list
44
- wp --json guide list
45
44
  wp job wait <job_id>
46
45
  ```
47
46
 
48
47
  复杂写入参数使用 `--payload-file`、`--edits-file`、`--content-file`、`--route-file` 和 `--ids-file`。Build、产物下载、Agent 运行、图片能力、Restore 和 MCP 不属于当前 CLI。
49
48
 
49
+ 叶子命令的 `--help` 会从当前 Profile 的 Backend `/openapi.json` 加载请求参数和完整 Schema;服务不可达时仍返回本地语法帮助,不缓存 Schema。
50
+
50
51
  写入命令支持 `--idempotency-key <key>`;网络超时后需要重放同一业务请求时复用原 key,不要把同一个 key 用于不同请求。
@@ -4,9 +4,10 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "web-presentation-cli"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Web Presentation CLI - 面向 AI 演示文稿创作平台的命令行与 Agent 工具包"
9
9
  readme = "README.md"
10
+ license = { text = "Apache-2.0" }
10
11
  requires-python = ">=3.11"
11
12
  dependencies = [
12
13
  "click>=8.1.7",
@@ -1,3 +1,3 @@
1
1
  """Web Presentation 官方命令行工具包。"""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.1"
@@ -17,7 +17,7 @@ from wp.commands.project import project_group
17
17
  from wp.commands.style import style_group
18
18
  from wp.commands.theme import theme_group
19
19
  from wp.commands.workspace import workspace_group
20
- from wp.commands.system import guide_group, standards_group, system_group
20
+ from wp.commands.system import standards_group, system_group
21
21
 
22
22
 
23
23
  @click.group()
@@ -48,7 +48,6 @@ main.add_command(doctor_cmd)
48
48
  main.add_command(profile_group)
49
49
  main.add_command(system_group)
50
50
  main.add_command(standards_group)
51
- main.add_command(guide_group)
52
51
  main.add_command(runtime_kit_group)
53
52
  main.add_command(font_group)
54
53
 
@@ -19,7 +19,7 @@ class ApiClient(SharedApiClient):
19
19
  endpoint=profile.endpoint,
20
20
  token=profile.token,
21
21
  workspace_id=workspace_id or profile.default_workspace_id,
22
- user_agent="web-presentation-cli/0.1.0",
22
+ user_agent="web-presentation-cli/0.1.1",
23
23
  idempotency_key=idempotency_key,
24
24
  )
25
25
 
@@ -15,11 +15,11 @@ from wp.commands.common import (
15
15
  output_result,
16
16
  read_json_file,
17
17
  read_text_file,
18
- require_array,
19
18
  require_ids,
20
19
  require_object,
21
20
  )
22
21
  from wp.formatter import print_table
22
+ from wp.openapi_help import contract, openapi_command
23
23
 
24
24
 
25
25
  ASSET_TYPES = ["image", "icon", "font", "video", "drawio", "mermaid", "chart", "formula"]
@@ -30,11 +30,11 @@ def asset_group() -> None:
30
30
  """工作空间静态资源管理。"""
31
31
 
32
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")
33
+ @openapi_command(asset_group, "list", contract("GET", "/api/v1/assets"))
34
+ @click.option("--page", default=1, type=int, show_default=True, help="结果页码")
35
+ @click.option("--page-size", default=50, type=int, show_default=True, help="每页返回数量")
36
+ @click.option("--type", "asset_type", type=click.Choice(ASSET_TYPES), help="按资源业务类型筛选")
37
+ @click.option("--keyword", help="按资源名称、说明或标签搜索")
38
38
  @click.pass_context
39
39
  def list_assets_cmd(ctx: click.Context, page: int, page_size: int, asset_type: str | None, keyword: str | None) -> None:
40
40
  """查询工作空间资源。"""
@@ -51,7 +51,7 @@ def list_assets_cmd(ctx: click.Context, page: int, page_size: int, asset_type: s
51
51
  handle_api_error("获取资源列表失败", err)
52
52
 
53
53
 
54
- @asset_group.command("get")
54
+ @openapi_command(asset_group, "get", contract("GET", "/api/v1/assets/{asset_id}"))
55
55
  @click.argument("asset_id", type=int)
56
56
  @click.pass_context
57
57
  def get_asset_cmd(ctx: click.Context, asset_id: int) -> None:
@@ -63,11 +63,16 @@ def get_asset_cmd(ctx: click.Context, asset_id: int) -> None:
63
63
  handle_api_error("获取资源详情失败", err)
64
64
 
65
65
 
66
- @asset_group.command("upload")
66
+ @openapi_command(
67
+ asset_group,
68
+ "upload",
69
+ contract("POST", "/api/v1/assets"),
70
+ examples=("wp asset upload ./hero.png --type image --name hero-image --idempotency-key asset-hero-image",),
71
+ )
67
72
  @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")
73
+ @click.option("--type", "asset_type", type=click.Choice(ASSET_TYPES), required=True, help="上传资源类型")
74
+ @click.option("--name", help="平台逻辑名;默认使用文件名主干")
75
+ @click.option("--description", help="资源内容、来源和使用场景说明")
71
76
  @idempotency_key_option
72
77
  @click.pass_context
73
78
  def upload_asset_cmd(ctx: click.Context, file_path: str, asset_type: str, name: str | None, description: str | None) -> None:
@@ -86,8 +91,13 @@ def upload_asset_cmd(ctx: click.Context, file_path: str, asset_type: str, name:
86
91
  handle_api_error("上传资源失败", err)
87
92
 
88
93
 
89
- @asset_group.command("create")
90
- @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True)
94
+ @openapi_command(
95
+ asset_group,
96
+ "create",
97
+ contract("POST", "/api/v1/assets/content"),
98
+ examples=("wp asset create --payload-file ./asset.json --content-file ./diagram.mmd --idempotency-key asset-diagram",),
99
+ )
100
+ @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True, help="文本资源创建 JSON 请求体")
91
101
  @click.option("--content-file", type=click.Path(exists=True, dir_okay=False), help="覆盖 JSON 中 content 的文本文件")
92
102
  @idempotency_key_option
93
103
  @click.pass_context
@@ -108,7 +118,7 @@ def asset_content_group() -> None:
108
118
  """可编辑文本资源内容。"""
109
119
 
110
120
 
111
- @asset_content_group.command("get")
121
+ @openapi_command(asset_content_group, "get", contract("GET", "/api/v1/assets/{asset_id}/content"))
112
122
  @click.argument("asset_id", type=int)
113
123
  @click.pass_context
114
124
  def get_asset_content_cmd(ctx: click.Context, asset_id: int) -> None:
@@ -120,10 +130,10 @@ def get_asset_content_cmd(ctx: click.Context, asset_id: int) -> None:
120
130
  handle_api_error("读取资源内容失败", err)
121
131
 
122
132
 
123
- @asset_content_group.command("update")
133
+ @openapi_command(asset_content_group, "update", contract("PUT", "/api/v1/assets/{asset_id}/content"))
124
134
  @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")
135
+ @click.option("--content-file", type=click.Path(exists=True, dir_okay=False), required=True, help="要写入的完整 UTF-8 文本内容")
136
+ @click.option("--change-note", help="本次完整内容替换的变更说明")
127
137
  @idempotency_key_option
128
138
  @click.pass_context
129
139
  def update_asset_content_cmd(ctx: click.Context, asset_id: int, content_file: str, change_note: str | None) -> None:
@@ -135,9 +145,9 @@ def update_asset_content_cmd(ctx: click.Context, asset_id: int, content_file: st
135
145
  handle_api_error("更新资源内容失败", err)
136
146
 
137
147
 
138
- @asset_content_group.command("preview")
148
+ @openapi_command(asset_content_group, "preview", contract("POST", "/api/v1/assets/{asset_id}/content/preview"))
139
149
  @click.argument("asset_id", type=int)
140
- @click.option("--content-file", type=click.Path(exists=True, dir_okay=False), required=True)
150
+ @click.option("--content-file", type=click.Path(exists=True, dir_okay=False), required=True, help="用于生成差异且不会写入的候选 UTF-8 文本")
141
151
  @click.pass_context
142
152
  def preview_asset_content_cmd(ctx: click.Context, asset_id: int, content_file: str) -> None:
143
153
  """预览资源文本内容差异。"""
@@ -153,8 +163,8 @@ def asset_tags_group() -> None:
153
163
  """资源标签。"""
154
164
 
155
165
 
156
- @asset_tags_group.command("list")
157
- @click.option("--type", "asset_type", type=click.Choice(ASSET_TYPES))
166
+ @openapi_command(asset_tags_group, "list", contract("GET", "/api/v1/assets/tags"))
167
+ @click.option("--type", "asset_type", type=click.Choice(ASSET_TYPES), help="只返回指定资源类型使用的标签")
158
168
  @click.pass_context
159
169
  def list_asset_tags_cmd(ctx: click.Context, asset_type: str | None) -> None:
160
170
  """列出工作空间资源标签。"""
@@ -166,9 +176,9 @@ def list_asset_tags_cmd(ctx: click.Context, asset_type: str | None) -> None:
166
176
  handle_api_error("获取资源标签失败", err)
167
177
 
168
178
 
169
- @asset_group.command("update")
179
+ @openapi_command(asset_group, "update", contract("PATCH", "/api/v1/assets/{asset_id}"))
170
180
  @click.argument("asset_id", type=int)
171
- @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True)
181
+ @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True, help="资源元数据更新 JSON 请求体")
172
182
  @idempotency_key_option
173
183
  @click.pass_context
174
184
  def update_asset_cmd(ctx: click.Context, asset_id: int, payload_file: str) -> None:
@@ -180,9 +190,9 @@ def update_asset_cmd(ctx: click.Context, asset_id: int, payload_file: str) -> No
180
190
  handle_api_error("更新资源失败", err)
181
191
 
182
192
 
183
- @asset_group.command("copy")
193
+ @openapi_command(asset_group, "copy", contract("POST", "/api/v1/assets/{asset_id}/copy"))
184
194
  @click.argument("asset_id", type=int)
185
- @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True)
195
+ @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True, help="资源副本名称、说明等 JSON 请求体")
186
196
  @idempotency_key_option
187
197
  @click.pass_context
188
198
  def copy_asset_cmd(ctx: click.Context, asset_id: int, payload_file: str) -> None:
@@ -194,14 +204,19 @@ def copy_asset_cmd(ctx: click.Context, asset_id: int, payload_file: str) -> None
194
204
  handle_api_error("复制资源失败", err)
195
205
 
196
206
 
197
- @asset_group.command("archive")
207
+ @openapi_command(
208
+ asset_group,
209
+ "archive",
210
+ contract("POST", "/api/v1/assets/{asset_id}/archive", "提供 ASSET_ID 时"),
211
+ contract("POST", "/api/v1/assets/batch-archive", "提供 --ids-file 时"),
212
+ )
198
213
  @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)
214
+ @click.option("--ids-file", type=click.Path(exists=True, dir_okay=False), help="批量归档的正整数 ID JSON 数组")
215
+ @click.option("--yes", is_flag=True, help="仅在用户已明确授权归档时跳过交互确认")
201
216
  @idempotency_key_option
202
217
  @click.pass_context
203
218
  def archive_asset_cmd(ctx: click.Context, asset_id: int | None, ids_file: str | None, yes: bool) -> None:
204
- """归档单个或一批资源。"""
219
+ """归档单个资源或 JSON 数组指定的一批资源;两种目标输入只能选一种。"""
205
220
 
206
221
  try:
207
222
  client = get_client(ctx)
@@ -6,6 +6,7 @@ import click
6
6
 
7
7
  from wp.client import ApiClientError
8
8
  from wp.commands.common import get_client, handle_api_error, output_result
9
+ from wp.openapi_help import contract, openapi_command
9
10
 
10
11
 
11
12
  @click.group("runtime-kit")
@@ -13,13 +14,13 @@ def runtime_kit_group() -> None:
13
14
  """Runtime Kit 公开能力目录。"""
14
15
 
15
16
 
16
- @runtime_kit_group.command("list")
17
- @click.option("--keyword")
18
- @click.option("--category")
19
- @click.option("--kind")
20
- @click.option("--base-name")
21
- @click.option("--version-no", type=int)
22
- @click.option("--include-all-versions", is_flag=True)
17
+ @openapi_command(runtime_kit_group, "list", contract("GET", "/api/v1/runtime-kit"))
18
+ @click.option("--keyword", help="按名称、说明或导入路径搜索")
19
+ @click.option("--category", help="按服务端返回的能力分类筛选")
20
+ @click.option("--kind", help="按能力类型筛选,例如 component、composable、util 或 type")
21
+ @click.option("--base-name", help="按不含版本号的能力基础名称筛选")
22
+ @click.option("--version-no", type=int, help="筛选指定公开版本号")
23
+ @click.option("--include-all-versions", is_flag=True, help="返回所有公开版本;默认只返回推荐版本")
23
24
  @click.pass_context
24
25
  def runtime_kit_list_cmd(
25
26
  ctx: click.Context,
@@ -50,7 +51,7 @@ def runtime_kit_list_cmd(
50
51
  handle_api_error("获取 Runtime Kit 目录失败", err)
51
52
 
52
53
 
53
- @runtime_kit_group.command("get")
54
+ @openapi_command(runtime_kit_group, "get", contract("GET", "/api/v1/runtime-kit/{item}"))
54
55
  @click.argument("item")
55
56
  @click.pass_context
56
57
  def runtime_kit_get_cmd(ctx: click.Context, item: str) -> None:
@@ -67,10 +68,10 @@ def font_group() -> None:
67
68
  """工作空间注册字体目录。"""
68
69
 
69
70
 
70
- @font_group.command("list")
71
- @click.option("--page", default=1, type=int)
72
- @click.option("--page-size", default=50, type=int)
73
- @click.option("--keyword")
71
+ @openapi_command(font_group, "list", contract("GET", "/api/v1/fonts"))
72
+ @click.option("--page", default=1, type=int, show_default=True, help="结果页码")
73
+ @click.option("--page-size", default=50, type=int, show_default=True, help="每页返回数量")
74
+ @click.option("--keyword", help="按字体名称或逻辑名搜索")
74
75
  @click.pass_context
75
76
  def font_list_cmd(ctx: click.Context, page: int, page_size: int, keyword: str | None) -> None:
76
77
  """查询工作空间注册字体。"""
@@ -21,6 +21,7 @@ from wp.commands.common import (
21
21
  )
22
22
  from wp.config import get_profile, load_config
23
23
  from wp.formatter import print_table
24
+ from wp.openapi_help import contract, openapi_command
24
25
 
25
26
 
26
27
  @click.group("component")
@@ -28,12 +29,12 @@ def component_group() -> None:
28
29
  """工作空间组件管理与 Mutation 任务。"""
29
30
 
30
31
 
31
- @component_group.command("list")
32
- @click.option("--page", default=1, type=int)
33
- @click.option("--page-size", default=50, type=int)
34
- @click.option("--keyword")
35
- @click.option("--scope", type=click.Choice(["all", "suggested"]), default="all")
36
- @click.option("--project-id", type=int)
32
+ @openapi_command(component_group, "list", contract("GET", "/api/v1/components"))
33
+ @click.option("--page", default=1, type=int, show_default=True, help="结果页码")
34
+ @click.option("--page-size", default=50, type=int, show_default=True, help="每页返回数量")
35
+ @click.option("--keyword", help="按名称、导入标识或摘要搜索")
36
+ @click.option("--scope", type=click.Choice(["all", "suggested"]), default="all", show_default=True, help="查询全部组件或指定项目的建议组件")
37
+ @click.option("--project-id", type=int, help="scope=suggested 时必填的项目 ID")
37
38
  @click.pass_context
38
39
  def list_components_cmd(ctx: click.Context, page: int, page_size: int, keyword: str | None, scope: str, project_id: int | None) -> None:
39
40
  """查询工作空间组件或项目建议组件。"""
@@ -55,7 +56,7 @@ def list_components_cmd(ctx: click.Context, page: int, page_size: int, keyword:
55
56
  handle_api_error("获取组件列表失败", err)
56
57
 
57
58
 
58
- @component_group.command("get")
59
+ @openapi_command(component_group, "get", contract("GET", "/api/v1/components/{component_id}"))
59
60
  @click.argument("component_id", type=int)
60
61
  @click.pass_context
61
62
  def get_component_cmd(ctx: click.Context, component_id: int) -> None:
@@ -67,16 +68,21 @@ def get_component_cmd(ctx: click.Context, component_id: int) -> None:
67
68
  handle_api_error("获取组件失败", err)
68
69
 
69
70
 
70
- @component_group.command("create")
71
- @click.option("--name")
72
- @click.option("--import-name")
73
- @click.option("--file", "file_path", type=click.Path(exists=True, dir_okay=False))
74
- @click.option("--type", "component_type", default="content")
75
- @click.option("--description")
76
- @click.option("--preview-schema-file", type=click.Path(exists=True, dir_okay=False))
77
- @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False))
78
- @click.option("--wait/--no-wait", default=True)
79
- @click.option("--timeout", type=float, default=120.0, show_default=True)
71
+ @openapi_command(
72
+ component_group,
73
+ "create",
74
+ contract("POST", "/api/v1/components"),
75
+ examples=('wp component create --name "指标卡" --import-name MetricCard --file ./MetricCard.vue --type content --idempotency-key component-metric-card',),
76
+ )
77
+ @click.option("--name", help="组件显示名称;直接参数模式必填")
78
+ @click.option("--import-name", help="稳定导入标识;直接参数模式必填")
79
+ @click.option("--file", "file_path", type=click.Path(exists=True, dir_okay=False), help="完整 Vue 3 SFC 文件;直接参数模式必填")
80
+ @click.option("--type", "component_type", default="content", show_default=True, help="组件类型:content、page、atomic 或服务端兼容别名")
81
+ @click.option("--description", help="组件用途和复用边界说明")
82
+ @click.option("--preview-schema-file", type=click.Path(exists=True, dir_okay=False), help="覆盖请求体 preview_schema 的 JSON 对象")
83
+ @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), help="完整组件创建 JSON 请求体;提供后忽略其它内容参数")
84
+ @click.option("--wait/--no-wait", default=True, help="等待任务终态;--no-wait 只返回入队结果")
85
+ @click.option("--timeout", type=float, default=120.0, show_default=True, help="等待任务终态的最长秒数")
80
86
  @idempotency_key_option
81
87
  @click.pass_context
82
88
  def create_component_cmd(
@@ -91,7 +97,7 @@ def create_component_cmd(
91
97
  wait: bool,
92
98
  timeout: float,
93
99
  ) -> None:
94
- """提交组件创建 Mutation Job。"""
100
+ """提交组件创建 Mutation Job;直接参数模式必须提供名称、导入标识和完整 SFC。"""
95
101
 
96
102
  profile = get_profile(load_config(), ctx.obj.get("profile"))
97
103
  workspace_id = ctx.obj.get("workspace_id") or profile.default_workspace_id
@@ -122,12 +128,18 @@ def create_component_cmd(
122
128
  handle_api_error("提交组件创建失败", err)
123
129
 
124
130
 
125
- @component_group.command("update")
131
+ @openapi_command(
132
+ component_group,
133
+ "update",
134
+ contract("PATCH", "/api/v1/components/{component_id}", "仅更新 name 或 summary 时"),
135
+ contract("POST", "/api/v1/jobs/mutations/components/metadata", "包含 import_name、component_type 或 preview_schema 时"),
136
+ examples=("wp component update 15 --payload-file ./component-update.json --idempotency-key component-15-metadata",),
137
+ )
126
138
  @click.argument("component_id", type=int)
127
- @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True)
139
+ @click.option("--payload-file", type=click.Path(exists=True, dir_okay=False), required=True, help="组件元数据 JSON 请求体;复杂字段会自动进入异步任务")
128
140
  @click.option("--preview-schema-file", type=click.Path(exists=True, dir_okay=False), help="覆盖 payload 中的 Preview Schema JSON")
129
- @click.option("--wait/--no-wait", default=True)
130
- @click.option("--timeout", type=float, default=120.0, show_default=True)
141
+ @click.option("--wait/--no-wait", default=True, help="复杂字段更新时等待任务终态;轻量更新忽略此选项")
142
+ @click.option("--timeout", type=float, default=120.0, show_default=True, help="等待复杂字段更新任务的最长秒数")
131
143
  @idempotency_key_option
132
144
  @click.pass_context
133
145
  def update_component_cmd(
@@ -160,13 +172,18 @@ def update_component_cmd(
160
172
  handle_api_error("更新组件失败", err)
161
173
 
162
174
 
163
- @component_group.command("edit")
175
+ @openapi_command(
176
+ component_group,
177
+ "edit",
178
+ contract("POST", "/api/v1/components/{component_id}/edits"),
179
+ examples=("wp component edit 15 --base-version-no 2 --base-draft-hash <hash> --edits-file ./edits.json --idempotency-key component-15-edit",),
180
+ )
164
181
  @click.argument("component_id", type=int)
165
- @click.option("--edits-file", type=click.Path(exists=True, dir_okay=False), required=True)
166
- @click.option("--base-version-no", type=int, required=True)
167
- @click.option("--base-draft-hash", required=True)
168
- @click.option("--wait/--no-wait", default=True)
169
- @click.option("--timeout", type=float, default=120.0, show_default=True)
182
+ @click.option("--edits-file", type=click.Path(exists=True, dir_okay=False), required=True, help="结构化编辑 JSON 数组;完整字段以当前 OpenAPI Schema 为准")
183
+ @click.option("--base-version-no", type=int, required=True, help="component get 返回的最新发布版本基线")
184
+ @click.option("--base-draft-hash", required=True, help="component get 返回的最新草稿哈希基线")
185
+ @click.option("--wait/--no-wait", default=True, help="等待任务终态;--no-wait 只返回入队结果")
186
+ @click.option("--timeout", type=float, default=120.0, show_default=True, help="等待任务终态的最长秒数")
170
187
  @idempotency_key_option
171
188
  @click.pass_context
172
189
  def edit_component_cmd(ctx: click.Context, component_id: int, edits_file: str, base_version_no: int, base_draft_hash: str, wait: bool, timeout: float) -> None:
@@ -189,7 +206,7 @@ def component_version_group() -> None:
189
206
  """组件历史发布版本。"""
190
207
 
191
208
 
192
- @component_version_group.command("list")
209
+ @openapi_command(component_version_group, "list", contract("GET", "/api/v1/components/{component_id}/versions"))
193
210
  @click.argument("component_id", type=int)
194
211
  @click.pass_context
195
212
  def list_component_versions_cmd(ctx: click.Context, component_id: int) -> None:
@@ -201,7 +218,7 @@ def list_component_versions_cmd(ctx: click.Context, component_id: int) -> None:
201
218
  handle_api_error("获取组件版本失败", err)
202
219
 
203
220
 
204
- @component_version_group.command("get")
221
+ @openapi_command(component_version_group, "get", contract("GET", "/api/v1/components/{component_id}/versions/{version_no}"))
205
222
  @click.argument("component_id", type=int)
206
223
  @click.argument("version_no", type=int)
207
224
  @click.pass_context
@@ -214,7 +231,7 @@ def get_component_version_cmd(ctx: click.Context, component_id: int, version_no:
214
231
  handle_api_error("获取组件版本内容失败", err)
215
232
 
216
233
 
217
- @component_group.command("dependencies")
234
+ @openapi_command(component_group, "dependencies", contract("GET", "/api/v1/components/{component_id}/dependencies"))
218
235
  @click.argument("component_id", type=int)
219
236
  @click.pass_context
220
237
  def component_dependencies_cmd(ctx: click.Context, component_id: int) -> None:
@@ -226,13 +243,18 @@ def component_dependencies_cmd(ctx: click.Context, component_id: int) -> None:
226
243
  handle_api_error("获取组件依赖失败", err)
227
244
 
228
245
 
229
- @component_group.command("validate")
246
+ @openapi_command(
247
+ component_group,
248
+ "validate",
249
+ contract("POST", "/api/v1/validate/entity"),
250
+ examples=("wp --json component validate 15 --mode content --source-file ./MetricCard.vue --detail",),
251
+ )
230
252
  @click.argument("component_id", type=int)
231
- @click.option("--mode", type=click.Choice(["current", "content", "edits"]), default="current")
232
- @click.option("--source-file", type=click.Path(exists=True, dir_okay=False))
233
- @click.option("--edits-file", type=click.Path(exists=True, dir_okay=False))
234
- @click.option("--preview-schema-file", type=click.Path(exists=True, dir_okay=False))
235
- @click.option("--detail", is_flag=True)
253
+ @click.option("--mode", type=click.Choice(["current", "content", "edits"]), default="current", show_default=True, help="校验当前草稿、完整候选源码或结构化 edits")
254
+ @click.option("--source-file", type=click.Path(exists=True, dir_okay=False), help="content 模式必填的完整候选 SFC")
255
+ @click.option("--edits-file", type=click.Path(exists=True, dir_okay=False), help="edits 模式必填的结构化编辑 JSON 数组")
256
+ @click.option("--preview-schema-file", type=click.Path(exists=True, dir_okay=False), help="可选的候选 preview_schema JSON 对象")
257
+ @click.option("--detail", is_flag=True, help="返回完整编译、渲染和布局诊断")
236
258
  @click.pass_context
237
259
  def validate_component_cmd(ctx: click.Context, component_id: int, mode: str, source_file: str | None, edits_file: str | None, preview_schema_file: str | None, detail: bool) -> None:
238
260
  """校验组件当前或候选源码。"""
@@ -254,10 +276,10 @@ def validate_component_cmd(ctx: click.Context, component_id: int, mode: str, sou
254
276
  handle_api_error("组件校验失败", err)
255
277
 
256
278
 
257
- @component_group.command("publish")
279
+ @openapi_command(component_group, "publish", contract("POST", "/api/v1/components/{component_id}/publish"))
258
280
  @click.argument("component_id", type=int)
259
- @click.option("--release-name")
260
- @click.option("--change-note")
281
+ @click.option("--release-name", help="本次发布版本的可读名称")
282
+ @click.option("--change-note", help="本次发布的变更说明")
261
283
  @idempotency_key_option
262
284
  @click.pass_context
263
285
  def publish_component_cmd(ctx: click.Context, component_id: int, release_name: str | None, change_note: str | None) -> None:
@@ -269,14 +291,19 @@ def publish_component_cmd(ctx: click.Context, component_id: int, release_name: s
269
291
  handle_api_error("发布组件失败", err)
270
292
 
271
293
 
272
- @component_group.command("archive")
294
+ @openapi_command(
295
+ component_group,
296
+ "archive",
297
+ contract("POST", "/api/v1/components/{component_id}/archive", "提供 COMPONENT_ID 时"),
298
+ contract("POST", "/api/v1/components/batch-archive", "提供 --ids-file 时"),
299
+ )
273
300
  @click.argument("component_id", type=int, required=False)
274
- @click.option("--ids-file", type=click.Path(exists=True, dir_okay=False))
275
- @click.option("--yes", is_flag=True)
301
+ @click.option("--ids-file", type=click.Path(exists=True, dir_okay=False), help="批量归档的正整数 ID JSON 数组")
302
+ @click.option("--yes", is_flag=True, help="仅在用户已明确授权归档时跳过交互确认")
276
303
  @idempotency_key_option
277
304
  @click.pass_context
278
305
  def archive_component_cmd(ctx: click.Context, component_id: int | None, ids_file: str | None, yes: bool) -> None:
279
- """归档单个或一批组件。"""
306
+ """归档单个组件或 JSON 数组指定的一批组件;两种目标输入只能选一种。"""
280
307
 
281
308
  try:
282
309
  client = get_client(ctx)
@@ -12,6 +12,7 @@ from wp.commands.common import (
12
12
  output_result,
13
13
  require_success_job,
14
14
  )
15
+ from wp.openapi_help import contract, openapi_command
15
16
 
16
17
 
17
18
  @click.group("job")
@@ -19,7 +20,7 @@ def job_group() -> None:
19
20
  """异步任务管理。"""
20
21
 
21
22
 
22
- @job_group.command("get")
23
+ @openapi_command(job_group, "get", contract("GET", "/api/v1/jobs/mutations/{job_id}"))
23
24
  @click.argument("job_id")
24
25
  @click.pass_context
25
26
  def get_job_cmd(ctx: click.Context, job_id: str) -> None:
@@ -31,9 +32,14 @@ def get_job_cmd(ctx: click.Context, job_id: str) -> None:
31
32
  handle_api_error("查询 Job 失败", err)
32
33
 
33
34
 
34
- @job_group.command("wait")
35
+ @openapi_command(
36
+ job_group,
37
+ "wait",
38
+ contract("GET", "/api/v1/jobs/mutations/{job_id}"),
39
+ examples=("wp --json job wait <job_id> --timeout 120",),
40
+ )
35
41
  @click.argument("job_id")
36
- @click.option("--timeout", default=120.0, type=float, show_default=True)
42
+ @click.option("--timeout", default=120.0, type=float, show_default=True, help="等待终态的最长秒数")
37
43
  @click.pass_context
38
44
  def wait_job_cmd(ctx: click.Context, job_id: str, timeout: float) -> None:
39
45
  """等待 Mutation Job 进入终态。"""
@@ -46,7 +52,7 @@ def wait_job_cmd(ctx: click.Context, job_id: str, timeout: float) -> None:
46
52
  handle_api_error("等待 Job 失败", err)
47
53
 
48
54
 
49
- @job_group.command("cancel")
55
+ @openapi_command(job_group, "cancel", contract("POST", "/api/v1/jobs/mutations/{job_id}/cancel"))
50
56
  @click.argument("job_id")
51
57
  @idempotency_key_option
52
58
  @click.pass_context
@@ -65,7 +71,7 @@ def cancel_job_cmd(ctx: click.Context, job_id: str) -> None:
65
71
  handle_api_error("取消 Job 失败", err)
66
72
 
67
73
 
68
- @job_group.command("retry")
74
+ @openapi_command(job_group, "retry", contract("POST", "/api/v1/jobs/mutations/{job_id}/retry"))
69
75
  @click.argument("job_id")
70
76
  @idempotency_key_option
71
77
  @click.pass_context