@josephyan/qingflow-cli 1.0.11 → 1.1.2

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 (67) hide show
  1. package/README.md +3 -3
  2. package/npm/bin/qingflow.mjs +40 -2
  3. package/npm/lib/runtime.mjs +386 -15
  4. package/npm/scripts/postinstall.mjs +7 -2
  5. package/package.json +1 -1
  6. package/pyproject.toml +1 -1
  7. package/skills/qingflow-cli/SKILL.md +440 -0
  8. package/skills/qingflow-cli/manifest.yaml +10 -0
  9. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
  10. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +485 -0
  11. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md +237 -0
  12. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_MATCH_RULES.md +137 -0
  13. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md +263 -0
  14. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +304 -0
  15. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md +41 -0
  16. package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
  17. package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
  18. package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
  19. package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
  20. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
  21. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +20 -0
  22. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
  23. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +163 -0
  24. package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +107 -0
  25. package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +151 -0
  26. package/skills/qingflow-cli/reference/_batch_schema_complex.json +18 -0
  27. package/skills/qingflow-cli/reference/_batch_schema_scalar.json +17 -0
  28. package/skills/qingflow-cli/reference/charts_remove.example.json +1 -0
  29. package/skills/qingflow-cli/reference/charts_reorder.example.json +1 -0
  30. package/skills/qingflow-cli/reference/charts_upsert_bar.example.json +8 -0
  31. package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +37 -0
  32. package/skills/qingflow-cli/reference/charts_upsert_minimal.example.json +13 -0
  33. package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +131 -0
  34. package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +126 -0
  35. package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +128 -0
  36. package/skills/qingflow-cli/reference/schema_add_fields_minimal.example.json +7 -0
  37. package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +78 -0
  38. package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +7 -0
  39. package/skills/qingflow-cli/scripts/builder-package-from-app-list.py +140 -0
  40. package/skills/qingflow-cli/scripts/find-app-by-keyword.py +132 -0
  41. package/skills/qingflow-cli/scripts/validate_qingflow_output_files.py +87 -0
  42. package/src/qingflow_mcp/__init__.py +1 -1
  43. package/src/qingflow_mcp/builder_facade/models.py +532 -48
  44. package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
  45. package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
  46. package/src/qingflow_mcp/cli/commands/app.py +3 -16
  47. package/src/qingflow_mcp/cli/commands/builder.py +354 -56
  48. package/src/qingflow_mcp/cli/commands/record.py +89 -2
  49. package/src/qingflow_mcp/cli/formatters.py +32 -1
  50. package/src/qingflow_mcp/cli/main.py +245 -3
  51. package/src/qingflow_mcp/public_surface.py +11 -8
  52. package/src/qingflow_mcp/response_trim.py +143 -14
  53. package/src/qingflow_mcp/server.py +15 -12
  54. package/src/qingflow_mcp/server_app_builder.py +108 -30
  55. package/src/qingflow_mcp/server_app_user.py +17 -18
  56. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  57. package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
  58. package/src/qingflow_mcp/solution/executor.py +3 -133
  59. package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
  60. package/src/qingflow_mcp/tools/app_tools.py +53 -8
  61. package/src/qingflow_mcp/tools/package_tools.py +16 -2
  62. package/src/qingflow_mcp/tools/record_tools.py +2095 -176
  63. package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
  64. package/src/qingflow_mcp/tools/solution_tools.py +30 -2
  65. package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
  66. package/src/qingflow_mcp/version.py +110 -0
  67. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
@@ -1,11 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+ from typing import Any
4
5
 
5
6
  from ..context import CliContext
6
7
  from .common import load_list_arg, load_object_arg, raise_config_error, require_list_arg
7
8
 
8
9
 
10
+ def _parse_app_keys_arg(raw: Any) -> list[str] | None:
11
+ """Parse comma-separated --app-keys argument into a list, or return None if empty."""
12
+ if not raw:
13
+ return None
14
+ keys = [k.strip() for k in str(raw).split(",") if k.strip()]
15
+ return keys if keys else None
16
+
17
+
9
18
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
10
19
  parser = subparsers.add_parser("builder", aliases=["build"], help="稳定 builder 命令")
11
20
  builder_subparsers = parser.add_subparsers(dest="builder_command", required=True)
@@ -42,6 +51,11 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
42
51
  contract.add_argument("--tool-name", required=True)
43
52
  contract.set_defaults(handler=_handle_contract, format_hint="builder_summary")
44
53
 
54
+ icon = builder_subparsers.add_parser("icon", help="工作区图标目录")
55
+ icon_subparsers = icon.add_subparsers(dest="builder_icon_command", required=True)
56
+ icon_catalog = icon_subparsers.add_parser("catalog", help="读取应用、应用包、门户可用图标目录")
57
+ icon_catalog.set_defaults(handler=_handle_icon_catalog, format_hint="builder_summary")
58
+
45
59
  member = builder_subparsers.add_parser("member", help="成员目录")
46
60
  member_subparsers = member.add_subparsers(dest="builder_member_command", required=True)
47
61
  member_search = member_subparsers.add_parser("search", help="搜索成员")
@@ -78,13 +92,18 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
78
92
  solution_install.add_argument("--solution-source", default="solutionDetail")
79
93
  solution_install.set_defaults(handler=_handle_solution_install, format_hint="builder_summary")
80
94
 
95
+ package_list = package_subparsers.add_parser("list", help="列出应用包")
96
+ package_list.add_argument("--trial-status", default="all")
97
+ package_list.add_argument("--query", default="")
98
+ package_list.set_defaults(handler=_handle_package_list, format_hint="builder_summary")
99
+
81
100
  package_get = package_subparsers.add_parser("get", help="读取应用包详情")
82
101
  package_get.add_argument("--package-id", type=int, required=True)
83
102
  package_get.set_defaults(handler=_handle_package_get, format_hint="builder_summary")
84
103
 
85
104
  package_apply = package_subparsers.add_parser("apply", help="创建或更新应用包配置")
86
105
  package_apply.add_argument("--config-file", required=True)
87
- package_apply.set_defaults(handler=_handle_package_apply, format_hint="builder_summary")
106
+ package_apply.set_defaults(handler=_handle_package_apply, format_hint="builder_summary", force_json_output=True)
88
107
 
89
108
  app = builder_subparsers.add_parser("app", help="应用")
90
109
  app_subparsers = app.add_subparsers(dest="builder_app_command", required=True)
@@ -99,16 +118,17 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
99
118
  app_release_lock.add_argument("--app-key", required=True)
100
119
  app_release_lock.add_argument("--lock-owner-email", required=True)
101
120
  app_release_lock.add_argument("--lock-owner-name", required=True)
102
- app_release_lock.set_defaults(handler=_handle_app_release_edit_lock_if_mine, format_hint="builder_summary")
121
+ app_release_lock.set_defaults(handler=_handle_app_release_edit_lock_if_mine, format_hint="builder_summary", force_json_output=True)
103
122
 
104
123
  app_get = app_subparsers.add_parser("get", help="读取应用配置(字段请使用: builder app get --app-key APP fields)")
105
124
  app_get.add_argument(
106
125
  "builder_app_get_section",
107
126
  nargs="?",
108
- choices=["summary", "fields", "layout", "views", "flow", "charts"],
127
+ choices=["summary", "fields", "layout", "views", "flow", "charts", "buttons", "associated-resources"],
109
128
  default="summary",
110
129
  )
111
- app_get.add_argument("--app-key", required=True)
130
+ app_get.add_argument("--app-key", default="")
131
+ app_get.add_argument("--app-keys", help="逗号分隔的多应用批量读取,例如: KEY1,KEY2,KEY3")
112
132
  app_get.set_defaults(handler=_handle_app_get, format_hint="builder_summary")
113
133
 
114
134
  app_repair_code_blocks = app_subparsers.add_parser("repair-code-blocks", help="扫描或修复现有代码块配置")
@@ -123,30 +143,36 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
123
143
  button_catalog = button_subparsers.add_parser("catalog", help="读取按钮样式目录")
124
144
  button_catalog.set_defaults(handler=_handle_button_catalog, format_hint="builder_summary")
125
145
 
126
- button_list = button_subparsers.add_parser("list", help="列出自定义按钮")
127
- button_list.add_argument("--app-key", required=True)
128
- button_list.set_defaults(handler=_handle_button_list, format_hint="builder_summary")
129
-
130
- button_get = button_subparsers.add_parser("get", help="读取自定义按钮")
131
- button_get.add_argument("--app-key", required=True)
132
- button_get.add_argument("--button-id", type=int, required=True)
146
+ button_get = button_subparsers.add_parser("get", help="读取应用自定义按钮配置")
147
+ button_get.add_argument("--app-key", default="")
148
+ button_get.add_argument("--app-keys", help="逗号分隔的多应用批量读取,例如: KEY1,KEY2,KEY3")
133
149
  button_get.set_defaults(handler=_handle_button_get, format_hint="builder_summary")
134
150
 
135
- button_create = button_subparsers.add_parser("create", help="创建自定义按钮")
136
- button_create.add_argument("--app-key", required=True)
137
- button_create.add_argument("--payload-file", required=True)
138
- button_create.set_defaults(handler=_handle_button_create, format_hint="builder_summary")
139
-
140
- button_update = button_subparsers.add_parser("update", help="更新自定义按钮")
141
- button_update.add_argument("--app-key", required=True)
142
- button_update.add_argument("--button-id", type=int, required=True)
143
- button_update.add_argument("--payload-file", required=True)
144
- button_update.set_defaults(handler=_handle_button_update, format_hint="builder_summary")
145
-
146
- button_delete = button_subparsers.add_parser("delete", help="删除自定义按钮")
147
- button_delete.add_argument("--app-key", required=True)
148
- button_delete.add_argument("--button-id", type=int, required=True)
149
- button_delete.set_defaults(handler=_handle_button_delete, format_hint="builder_summary")
151
+ button_apply = button_subparsers.add_parser("apply", help="声明式创建、更新或删除自定义按钮")
152
+ button_apply.add_argument("--app-key", default="")
153
+ button_apply.add_argument("--upsert-buttons-file")
154
+ button_apply.add_argument("--patch-buttons-file")
155
+ button_apply.add_argument("--remove-buttons-file")
156
+ button_apply.add_argument("--view-configs-file")
157
+ button_apply.add_argument("--apps-file", help="多应用批量按钮 JSON 数组,每项含 app_key + upsert_buttons/patch_buttons/remove_buttons/view_configs")
158
+ button_apply.set_defaults(handler=_handle_button_apply, format_hint="builder_summary", force_json_output=True)
159
+
160
+ associated_resource = builder_subparsers.add_parser("associated-resource", aliases=["associated-resources"], help="关联视图/报表")
161
+ associated_resource_subparsers = associated_resource.add_subparsers(dest="builder_associated_resource_command", required=True)
162
+
163
+ associated_resource_get = associated_resource_subparsers.add_parser("get", help="读取应用关联资源配置")
164
+ associated_resource_get.add_argument("--app-key", default="")
165
+ associated_resource_get.add_argument("--app-keys", help="逗号分隔的多应用批量读取,例如: KEY1,KEY2,KEY3")
166
+ associated_resource_get.set_defaults(handler=_handle_associated_resource_get, format_hint="builder_summary")
167
+
168
+ associated_resource_apply = associated_resource_subparsers.add_parser("apply", help="声明式管理应用关联资源池和视图展示配置")
169
+ associated_resource_apply.add_argument("--app-key", required=True)
170
+ associated_resource_apply.add_argument("--upsert-resources-file")
171
+ associated_resource_apply.add_argument("--patch-resources-file")
172
+ associated_resource_apply.add_argument("--remove-associated-item-ids-file")
173
+ associated_resource_apply.add_argument("--reorder-associated-item-ids-file")
174
+ associated_resource_apply.add_argument("--view-configs-file")
175
+ associated_resource_apply.set_defaults(handler=_handle_associated_resource_apply, format_hint="builder_summary", force_json_output=True)
150
176
 
151
177
  portal = builder_subparsers.add_parser("portal", help="门户")
152
178
  portal_subparsers = portal.add_subparsers(dest="builder_portal_command", required=True)
@@ -163,7 +189,9 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
163
189
  portal_apply.add_argument("--dash-name", default="")
164
190
  portal_apply.add_argument("--package-id", type=int)
165
191
  portal_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
192
+ portal_apply.add_argument("--payload-file")
166
193
  portal_apply.add_argument("--sections-file")
194
+ portal_apply.add_argument("--layout-preset", default="")
167
195
  portal_apply.add_argument("--visibility-file")
168
196
  portal_apply.add_argument("--auth-file")
169
197
  portal_apply.add_argument("--icon")
@@ -171,7 +199,8 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
171
199
  portal_apply.add_argument("--hide-copyright", action=argparse.BooleanOptionalAction, default=None)
172
200
  portal_apply.add_argument("--dash-global-config-file")
173
201
  portal_apply.add_argument("--config-file")
174
- portal_apply.set_defaults(handler=_handle_portal_apply, format_hint="builder_summary")
202
+ portal_apply.add_argument("--patch-sections-file", help="门户组件局部更新 JSON 数组,每项含 chart_ref/view_ref/order + set/unset")
203
+ portal_apply.set_defaults(handler=_handle_portal_apply, format_hint="builder_summary", force_json_output=True)
175
204
 
176
205
  schema_apply = builder_subparsers.add_parser("schema", help="字段搭建")
177
206
  schema_apply_subparsers = schema_apply.add_subparsers(dest="builder_schema_command", required=True)
@@ -185,54 +214,72 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
185
214
  schema_apply_apply.add_argument("--visibility-file")
186
215
  schema_apply_apply.add_argument("--create-if-missing", action="store_true")
187
216
  schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
188
- schema_apply_apply.add_argument("--add-fields-file")
189
- schema_apply_apply.add_argument("--update-fields-file")
217
+ schema_apply_apply.add_argument("--apps-file", help="多应用 schema JSON 数组;每项可带 client_key/app_name/add_fields,支持 relation target_app_ref")
218
+ schema_apply_apply.add_argument("--add-fields-file", help="字段 JSON 数组;字段可用 as_data_title/as_data_cover 标记数据标题/封面")
219
+ schema_apply_apply.add_argument("--update-fields-file", help="字段更新 JSON 数组;set 内可用 as_data_title/as_data_cover 标记数据标题/封面")
190
220
  schema_apply_apply.add_argument("--remove-fields-file")
191
- schema_apply_apply.set_defaults(handler=_handle_schema_apply, format_hint="builder_summary")
221
+ schema_apply_apply.set_defaults(handler=_handle_schema_apply, format_hint="builder_summary", force_json_output=True)
192
222
 
193
223
  layout_apply = builder_subparsers.add_parser("layout", help="布局")
194
224
  layout_apply_subparsers = layout_apply.add_subparsers(dest="builder_layout_command", required=True)
195
225
  layout_apply_apply = layout_apply_subparsers.add_parser("apply", help="执行布局变更")
196
- layout_apply_apply.add_argument("--app-key", required=True)
226
+ layout_apply_apply.add_argument("--app-key", default="")
197
227
  layout_apply_apply.add_argument("--mode", choices=["merge", "replace"], default="merge")
198
228
  layout_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
199
- layout_apply_apply.add_argument("--sections-file", required=True)
200
- layout_apply_apply.set_defaults(handler=_handle_layout_apply, format_hint="builder_summary")
229
+ layout_apply_apply.add_argument("--sections-file")
230
+ layout_apply_apply.add_argument("--apps-file", help="多应用批量布局 JSON 数组,每项含 app_key + sections")
231
+ layout_apply_apply.set_defaults(handler=_handle_layout_apply, format_hint="builder_summary", force_json_output=True)
201
232
 
202
233
  views_apply = builder_subparsers.add_parser("views", help="视图")
203
234
  views_apply_subparsers = views_apply.add_subparsers(dest="builder_views_command", required=True)
204
235
  views_apply_apply = views_apply_subparsers.add_parser("apply", help="执行视图变更")
205
- views_apply_apply.add_argument("--app-key", required=True)
236
+ views_apply_apply.add_argument("--app-key", default="")
206
237
  views_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
207
238
  views_apply_apply.add_argument("--upsert-views-file")
239
+ views_apply_apply.add_argument("--patch-views-file")
208
240
  views_apply_apply.add_argument("--remove-views-file")
209
- views_apply_apply.set_defaults(handler=_handle_views_apply, format_hint="builder_summary")
210
-
211
- flow_apply = builder_subparsers.add_parser("flow", help="流程")
212
- flow_apply_subparsers = flow_apply.add_subparsers(dest="builder_flow_command", required=True)
213
- flow_apply_apply = flow_apply_subparsers.add_parser("apply", help="执行流程变更")
214
- flow_apply_apply.add_argument("--app-key", required=True)
215
- flow_apply_apply.add_argument("--mode", choices=["replace"], default="replace")
216
- flow_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
217
- flow_apply_apply.add_argument("--nodes-file", required=True)
218
- flow_apply_apply.add_argument("--transitions-file", required=True)
219
- flow_apply_apply.set_defaults(handler=_handle_flow_apply, format_hint="builder_summary")
241
+ views_apply_apply.add_argument("--apps-file", help="多应用批量视图 JSON 数组,每项含 app_key + upsert_views/patch_views/remove_views")
242
+ views_apply_apply.set_defaults(handler=_handle_views_apply, format_hint="builder_summary", force_json_output=True)
243
+
244
+ flow_parser = builder_subparsers.add_parser("flow", help="流程(WorkflowSpec)")
245
+ flow_subparsers = flow_parser.add_subparsers(dest="builder_flow_command", required=True)
246
+
247
+ flow_schema = flow_subparsers.add_parser("schema", help="读取 WorkflowSpec JSON Schema")
248
+ flow_schema.add_argument("--schema-version", default="")
249
+ flow_schema.set_defaults(handler=_handle_flow_schema, format_hint="builder_summary")
250
+
251
+ flow_get = flow_subparsers.add_parser("get", help="读取 WorkflowSpec")
252
+ flow_get.add_argument("--app-key", required=True)
253
+ flow_get.add_argument("--version-id", default="")
254
+ flow_get.set_defaults(handler=_handle_flow_get, format_hint="builder_summary")
255
+
256
+ flow_apply = flow_subparsers.add_parser("apply", help="应用 WorkflowSpec")
257
+ flow_apply.add_argument("--app-key", required=True)
258
+ flow_apply.add_argument("--spec-file")
259
+ flow_apply.add_argument("--patch-nodes-file", help="节点局部更新 JSON 数组,每项含 id + set/unset")
260
+ flow_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
261
+ flow_apply.add_argument("--idempotency-key", default="")
262
+ flow_apply.add_argument("--schema-version", default="")
263
+ flow_apply.set_defaults(handler=_handle_flow_apply, format_hint="builder_summary", force_json_output=True)
220
264
 
221
265
  charts_apply = builder_subparsers.add_parser("charts", help="报表")
222
266
  charts_apply_subparsers = charts_apply.add_subparsers(dest="builder_charts_command", required=True)
223
267
  charts_apply_apply = charts_apply_subparsers.add_parser("apply", help="执行报表变更")
224
- charts_apply_apply.add_argument("--app-key", required=True)
268
+ charts_apply_apply.add_argument("--app-key", default="")
225
269
  charts_apply_apply.add_argument("--upsert-file")
270
+ charts_apply_apply.add_argument("--patch-file")
226
271
  charts_apply_apply.add_argument("--remove-chart-ids-file")
227
272
  charts_apply_apply.add_argument("--reorder-chart-ids-file")
228
- charts_apply_apply.set_defaults(handler=_handle_charts_apply, format_hint="builder_summary")
273
+ charts_apply_apply.add_argument("--apps-file", help="多应用批量报表 JSON 数组,每项含 app_key + upsert_charts/patch_charts/remove_chart_ids/reorder_chart_ids")
274
+ charts_apply_apply.set_defaults(handler=_handle_charts_apply, format_hint="builder_summary", force_json_output=True)
229
275
 
230
276
  publish_verify = builder_subparsers.add_parser("publish", help="发布校验")
231
277
  publish_verify_subparsers = publish_verify.add_subparsers(dest="builder_publish_command", required=True)
232
278
  publish_verify_verify = publish_verify_subparsers.add_parser("verify", help="校验应用发布")
233
- publish_verify_verify.add_argument("--app-key", required=True)
279
+ publish_verify_verify.add_argument("--app-key", default="")
280
+ publish_verify_verify.add_argument("--app-keys", help="逗号分隔的多应用批量校验,例如: KEY1,KEY2,KEY3")
234
281
  publish_verify_verify.add_argument("--expected-package-id", type=int)
235
- publish_verify_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_summary")
282
+ publish_verify_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_summary", force_json_output=True)
236
283
 
237
284
  view = builder_subparsers.add_parser("view", help="视图详情")
238
285
  view_subparsers = view.add_subparsers(dest="builder_view_command", required=True)
@@ -280,6 +327,10 @@ def _handle_contract(args: argparse.Namespace, context: CliContext) -> dict:
280
327
  return context.builder.builder_tool_contract(tool_name=args.tool_name)
281
328
 
282
329
 
330
+ def _handle_icon_catalog(args: argparse.Namespace, context: CliContext) -> dict:
331
+ return context.builder.workspace_icon_catalog_get(profile=args.profile)
332
+
333
+
283
334
  def _handle_member_search(args: argparse.Namespace, context: CliContext) -> dict:
284
335
  return context.builder.member_search(
285
336
  profile=args.profile,
@@ -323,6 +374,10 @@ def _handle_package_get(args: argparse.Namespace, context: CliContext) -> dict:
323
374
  return context.builder.package_get(profile=args.profile, package_id=args.package_id)
324
375
 
325
376
 
377
+ def _handle_package_list(args: argparse.Namespace, context: CliContext) -> dict:
378
+ return context.builder.package_list(profile=args.profile, trial_status=args.trial_status, query=args.query)
379
+
380
+
326
381
  def _handle_package_apply(args: argparse.Namespace, context: CliContext) -> dict:
327
382
  config = load_object_arg(args.config_file, option_name="--config-file")
328
383
  if not isinstance(config, dict):
@@ -369,8 +424,67 @@ def _handle_button_catalog(args: argparse.Namespace, context: CliContext) -> dic
369
424
  return context.builder.button_style_catalog_get(profile=args.profile)
370
425
 
371
426
 
427
+ def _handle_button_apply(args: argparse.Namespace, context: CliContext) -> dict:
428
+ apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
429
+ if apps:
430
+ single_app_args = [a for a in ["--app-key" if args.app_key else None, "--upsert-buttons-file" if getattr(args, "upsert_buttons_file", None) else None, "--patch-buttons-file" if getattr(args, "patch_buttons_file", None) else None, "--remove-buttons-file" if getattr(args, "remove_buttons_file", None) else None, "--view-configs-file" if getattr(args, "view_configs_file", None) else None] if a]
431
+ if single_app_args:
432
+ raise_config_error(
433
+ f"button apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
434
+ fix_hint="Use --apps-file for batch mode (each item contains app_key + per-app params), or remove --apps-file for single-app mode.",
435
+ )
436
+ if apps:
437
+ return context.builder.app_custom_buttons_apply(
438
+ profile=args.profile,
439
+ app_key="",
440
+ upsert_buttons=[],
441
+ patch_buttons=[],
442
+ remove_buttons=[],
443
+ view_configs=[],
444
+ apps=apps,
445
+ )
446
+ if not args.app_key:
447
+ raise_config_error("button apply requires --app-key or --apps-file", fix_hint="Pass --app-key APP_KEY for single-app mode, or --apps-file for batch mode.")
448
+ return context.builder.app_custom_buttons_apply(
449
+ profile=args.profile,
450
+ app_key=args.app_key,
451
+ upsert_buttons=load_list_arg(args.upsert_buttons_file, option_name="--upsert-buttons-file"),
452
+ patch_buttons=load_list_arg(args.patch_buttons_file, option_name="--patch-buttons-file"),
453
+ remove_buttons=load_list_arg(args.remove_buttons_file, option_name="--remove-buttons-file"),
454
+ view_configs=load_list_arg(args.view_configs_file, option_name="--view-configs-file"),
455
+ )
456
+
457
+
458
+ def _handle_associated_resource_apply(args: argparse.Namespace, context: CliContext) -> dict:
459
+ return context.builder.app_associated_resources_apply(
460
+ profile=args.profile,
461
+ app_key=args.app_key,
462
+ upsert_resources=load_list_arg(args.upsert_resources_file, option_name="--upsert-resources-file"),
463
+ patch_resources=load_list_arg(args.patch_resources_file, option_name="--patch-resources-file"),
464
+ remove_associated_item_ids=load_list_arg(args.remove_associated_item_ids_file, option_name="--remove-associated-item-ids-file"),
465
+ reorder_associated_item_ids=load_list_arg(args.reorder_associated_item_ids_file, option_name="--reorder-associated-item-ids-file"),
466
+ view_configs=load_list_arg(args.view_configs_file, option_name="--view-configs-file"),
467
+ )
468
+
469
+
372
470
  def _handle_button_get(args: argparse.Namespace, context: CliContext) -> dict:
373
- return context.builder.app_custom_button_get(profile=args.profile, app_key=args.app_key, button_id=args.button_id)
471
+ app_keys = _parse_app_keys_arg(getattr(args, "app_keys", None))
472
+ if app_keys:
473
+ return context.builder.app_get_buttons(profile=args.profile, app_keys=app_keys)
474
+ app_key = str(getattr(args, "app_key", "") or "").strip()
475
+ if not app_key:
476
+ raise_config_error("button get requires --app-key or --app-keys", fix_hint="Pass --app-key APP_KEY or --app-keys KEY1,KEY2")
477
+ return context.builder.app_get_buttons(profile=args.profile, app_key=app_key)
478
+
479
+
480
+ def _handle_associated_resource_get(args: argparse.Namespace, context: CliContext) -> dict:
481
+ app_keys = _parse_app_keys_arg(getattr(args, "app_keys", None))
482
+ if app_keys:
483
+ return context.builder.app_get_associated_resources(profile=args.profile, app_keys=app_keys)
484
+ app_key = str(getattr(args, "app_key", "") or "").strip()
485
+ if not app_key:
486
+ raise_config_error("associated-resource get requires --app-key or --app-keys", fix_hint="Pass --app-key APP_KEY or --app-keys KEY1,KEY2")
487
+ return context.builder.app_get_associated_resources(profile=args.profile, app_key=app_key)
374
488
 
375
489
 
376
490
  def _handle_button_create(args: argparse.Namespace, context: CliContext) -> dict:
@@ -395,6 +509,30 @@ def _handle_button_delete(args: argparse.Namespace, context: CliContext) -> dict
395
509
 
396
510
 
397
511
  def _handle_app_get(args: argparse.Namespace, context: CliContext) -> dict:
512
+ section = args.builder_app_get_section
513
+ app_keys = _parse_app_keys_arg(getattr(args, "app_keys", None))
514
+ app_key = str(getattr(args, "app_key", "") or "").strip()
515
+ if not app_keys and not app_key:
516
+ raise_config_error("app get requires --app-key or --app-keys", fix_hint="Pass --app-key APP_KEY or --app-keys KEY1,KEY2")
517
+ if section == "buttons":
518
+ if app_keys:
519
+ return context.builder.app_get_buttons(profile=args.profile, app_keys=app_keys)
520
+ return context.builder.app_get_buttons(profile=args.profile, app_key=app_key)
521
+ if section == "associated-resources":
522
+ if app_keys:
523
+ return context.builder.app_get_associated_resources(profile=args.profile, app_keys=app_keys)
524
+ return context.builder.app_get_associated_resources(profile=args.profile, app_key=app_key)
525
+ if app_keys:
526
+ batch_handlers = {
527
+ "fields": context.builder.app_get_fields,
528
+ "layout": context.builder.app_get_layout,
529
+ "views": context.builder.app_get_views,
530
+ "flow": context.builder.app_get_flow,
531
+ "charts": context.builder.app_get_charts,
532
+ }
533
+ if section not in batch_handlers:
534
+ raise_config_error(f"app get --app-keys does not support section '{section}'", fix_hint="Batch reads support: fields, layout, views, flow, charts, buttons, associated-resources")
535
+ return batch_handlers[section](profile=args.profile, app_keys=app_keys)
398
536
  handlers = {
399
537
  "summary": context.builder.app_get,
400
538
  "fields": context.builder.app_get_fields,
@@ -403,7 +541,7 @@ def _handle_app_get(args: argparse.Namespace, context: CliContext) -> dict:
403
541
  "flow": context.builder.app_get_flow,
404
542
  "charts": context.builder.app_get_charts,
405
543
  }
406
- return handlers[args.builder_app_get_section](profile=args.profile, app_key=args.app_key)
544
+ return handlers[section](profile=args.profile, app_key=app_key)
407
545
 
408
546
 
409
547
  def _handle_app_repair_code_blocks(args: argparse.Namespace, context: CliContext) -> dict:
@@ -432,6 +570,34 @@ def _handle_chart_get(args: argparse.Namespace, context: CliContext) -> dict:
432
570
 
433
571
 
434
572
  def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
573
+ apps = load_list_arg(args.apps_file, option_name="--apps-file")
574
+ if args.apps_file:
575
+ if not apps:
576
+ raise_config_error(
577
+ "schema apply multi-app mode requires a non-empty --apps-file.",
578
+ fix_hint="Pass a JSON array with at least one app item.",
579
+ )
580
+ if args.app_key or args.app_name or args.app_title or args.add_fields_file or args.update_fields_file or args.remove_fields_file:
581
+ raise_config_error(
582
+ "schema apply multi-app mode accepts --package-id/--create-if-missing plus --apps-file only.",
583
+ fix_hint="Use `--apps-file` for batch mode, or remove `--apps-file` and use the single-app arguments.",
584
+ )
585
+ if args.package_id is None:
586
+ raise_config_error(
587
+ "schema apply multi-app mode requires --package-id.",
588
+ fix_hint="Pass `--package-id` and app names inside --apps-file.",
589
+ )
590
+ return context.builder.app_schema_apply(
591
+ profile=args.profile,
592
+ package_id=args.package_id,
593
+ visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
594
+ create_if_missing=bool(args.create_if_missing),
595
+ publish=bool(args.publish),
596
+ apps=apps,
597
+ add_fields=[],
598
+ update_fields=[],
599
+ remove_fields=[],
600
+ )
435
601
  has_app_key = bool((args.app_key or "").strip())
436
602
  has_app_name = bool((args.app_name or "").strip())
437
603
  has_app_title = bool((args.app_title or "").strip())
@@ -466,6 +632,25 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
466
632
 
467
633
 
468
634
  def _handle_layout_apply(args: argparse.Namespace, context: CliContext) -> dict:
635
+ apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
636
+ if apps:
637
+ single_app_args = [a for a in ["--app-key" if args.app_key else None, "--sections-file" if getattr(args, "sections_file", None) else None] if a]
638
+ if single_app_args:
639
+ raise_config_error(
640
+ f"layout apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
641
+ fix_hint="Use --apps-file for batch mode (each item contains app_key + sections), or remove --apps-file for single-app mode.",
642
+ )
643
+ if apps:
644
+ return context.builder.app_layout_apply(
645
+ profile=args.profile,
646
+ app_key="",
647
+ mode=args.mode,
648
+ publish=bool(args.publish),
649
+ sections=[],
650
+ apps=apps,
651
+ )
652
+ if not args.app_key:
653
+ raise_config_error("layout apply requires --app-key or --apps-file", fix_hint="Pass --app-key + --sections-file for single-app mode, or --apps-file for batch mode.")
469
654
  return context.builder.app_layout_apply(
470
655
  profile=args.profile,
471
656
  app_key=args.app_key,
@@ -476,40 +661,144 @@ def _handle_layout_apply(args: argparse.Namespace, context: CliContext) -> dict:
476
661
 
477
662
 
478
663
  def _handle_views_apply(args: argparse.Namespace, context: CliContext) -> dict:
664
+ apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
665
+ if apps:
666
+ single_app_args = [a for a in ["--app-key" if args.app_key else None, "--upsert-views-file" if getattr(args, "upsert_views_file", None) else None, "--patch-views-file" if getattr(args, "patch_views_file", None) else None, "--remove-views-file" if getattr(args, "remove_views_file", None) else None] if a]
667
+ if single_app_args:
668
+ raise_config_error(
669
+ f"views apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
670
+ fix_hint="Use --apps-file for batch mode (each item contains app_key + per-app params), or remove --apps-file for single-app mode.",
671
+ )
672
+ if apps:
673
+ return context.builder.app_views_apply(
674
+ profile=args.profile,
675
+ app_key="",
676
+ publish=bool(args.publish),
677
+ upsert_views=[],
678
+ patch_views=[],
679
+ remove_views=[],
680
+ apps=apps,
681
+ )
682
+ if not args.app_key:
683
+ raise_config_error("views apply requires --app-key or --apps-file", fix_hint="Pass --app-key for single-app mode, or --apps-file for batch mode.")
479
684
  return context.builder.app_views_apply(
480
685
  profile=args.profile,
481
686
  app_key=args.app_key,
482
687
  publish=bool(args.publish),
483
688
  upsert_views=load_list_arg(args.upsert_views_file, option_name="--upsert-views-file"),
689
+ patch_views=load_list_arg(args.patch_views_file, option_name="--patch-views-file"),
484
690
  remove_views=load_list_arg(args.remove_views_file, option_name="--remove-views-file"),
485
691
  )
486
692
 
487
693
 
694
+ def _handle_flow_schema(args: argparse.Namespace, context: CliContext) -> dict:
695
+ return context.builder.app_flow_get_schema(
696
+ profile=args.profile,
697
+ schema_version=args.schema_version or None,
698
+ )
699
+
700
+
701
+ def _handle_flow_get(args: argparse.Namespace, context: CliContext) -> dict:
702
+ return context.builder.app_get_flow(
703
+ profile=args.profile,
704
+ app_key=args.app_key,
705
+ version_id=args.version_id or None,
706
+ )
707
+
708
+
488
709
  def _handle_flow_apply(args: argparse.Namespace, context: CliContext) -> dict:
710
+ patch_nodes = load_list_arg(getattr(args, "patch_nodes_file", None), option_name="--patch-nodes-file")
711
+ if patch_nodes and args.spec_file:
712
+ raise_config_error(
713
+ "flow apply --spec-file and --patch-nodes-file are mutually exclusive.",
714
+ fix_hint="Use --spec-file to replace the full flow spec, or --patch-nodes-file to patch specific nodes.",
715
+ )
716
+ if patch_nodes:
717
+ return context.builder.app_flow_apply(
718
+ profile=args.profile,
719
+ app_key=args.app_key,
720
+ publish=bool(args.publish),
721
+ spec=None,
722
+ patch_nodes=patch_nodes,
723
+ idempotency_key=args.idempotency_key or None,
724
+ schema_version=args.schema_version or None,
725
+ )
726
+ spec_payload = load_object_arg(args.spec_file, option_name="--spec-file") if args.spec_file else None
727
+ if not isinstance(spec_payload, dict):
728
+ raise_config_error("flow apply requires either --spec-file or --patch-nodes-file.", fix_hint="Pass --spec-file to replace the full flow spec, or --patch-nodes-file to patch specific nodes.")
729
+ if "spec" in spec_payload and isinstance(spec_payload.get("spec"), dict):
730
+ spec = spec_payload["spec"]
731
+ else:
732
+ spec = spec_payload
489
733
  return context.builder.app_flow_apply(
490
734
  profile=args.profile,
491
735
  app_key=args.app_key,
492
- mode=args.mode,
493
736
  publish=bool(args.publish),
494
- nodes=require_list_arg(args.nodes_file, option_name="--nodes-file"),
495
- transitions=require_list_arg(args.transitions_file, option_name="--transitions-file"),
737
+ spec=spec,
738
+ idempotency_key=args.idempotency_key or None,
739
+ schema_version=args.schema_version or None,
496
740
  )
497
741
 
498
742
 
499
743
  def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
744
+ apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
745
+ if apps:
746
+ single_app_args = [a for a in ["--app-key" if args.app_key else None, "--upsert-file" if getattr(args, "upsert_file", None) else None, "--patch-file" if getattr(args, "patch_file", None) else None, "--remove-chart-ids-file" if getattr(args, "remove_chart_ids_file", None) else None, "--reorder-chart-ids-file" if getattr(args, "reorder_chart_ids_file", None) else None] if a]
747
+ if single_app_args:
748
+ raise_config_error(
749
+ f"charts apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
750
+ fix_hint="Use --apps-file for batch mode (each item contains app_key + per-app params), or remove --apps-file for single-app mode.",
751
+ )
752
+ if apps:
753
+ return context.builder.app_charts_apply(
754
+ profile=args.profile,
755
+ app_key="",
756
+ upsert_charts=[],
757
+ patch_charts=[],
758
+ remove_chart_ids=[],
759
+ reorder_chart_ids=[],
760
+ apps=apps,
761
+ )
762
+ if not args.app_key:
763
+ raise_config_error("charts apply requires --app-key or --apps-file", fix_hint="Pass --app-key for single-app mode, or --apps-file for batch mode.")
500
764
  return context.builder.app_charts_apply(
501
765
  profile=args.profile,
502
766
  app_key=args.app_key,
503
767
  upsert_charts=load_list_arg(args.upsert_file, option_name="--upsert-file"),
768
+ patch_charts=load_list_arg(args.patch_file, option_name="--patch-file"),
504
769
  remove_chart_ids=load_list_arg(args.remove_chart_ids_file, option_name="--remove-chart-ids-file"),
505
770
  reorder_chart_ids=load_list_arg(args.reorder_chart_ids_file, option_name="--reorder-chart-ids-file"),
506
771
  )
507
772
 
508
773
 
509
774
  def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
775
+ patch_sections = load_list_arg(getattr(args, "patch_sections_file", None), option_name="--patch-sections-file")
776
+ if patch_sections:
777
+ if not (args.dash_key or "").strip():
778
+ raise_config_error("portal apply --patch-sections-file requires --dash-key", fix_hint="Pass --dash-key DASH_KEY to identify the portal to patch")
779
+ has_sections_file = bool(getattr(args, "sections_file", None))
780
+ has_payload_file = bool(getattr(args, "payload_file", None))
781
+ if has_sections_file or has_payload_file:
782
+ raise_config_error(
783
+ "portal apply --patch-sections-file cannot be combined with --sections-file or --payload-file.",
784
+ fix_hint="Use --patch-sections-file alone to patch specific sections, or --sections-file to replace all sections.",
785
+ )
786
+ return context.builder.portal_apply(
787
+ profile=args.profile,
788
+ dash_key=args.dash_key,
789
+ dash_name="",
790
+ package_id=None,
791
+ publish=bool(args.publish),
792
+ sections=[],
793
+ patch_sections=patch_sections,
794
+ )
795
+ payload = load_object_arg(args.payload_file, option_name="--payload-file") if getattr(args, "payload_file", None) else None
796
+ payload_obj = payload if isinstance(payload, dict) else {}
797
+ effective_dash_name = (args.dash_name or str(payload_obj.get("dash_name") or payload_obj.get("dashName") or payload_obj.get("name") or "")).strip()
798
+ effective_package_id = args.package_id if args.package_id is not None else payload_obj.get("package_id") or payload_obj.get("packageId") or payload_obj.get("package_tag_id")
510
799
  has_dash_key = bool((args.dash_key or "").strip())
511
- has_dash_name = bool((args.dash_name or "").strip())
512
- has_package_id = args.package_id is not None
800
+ has_dash_name = bool(effective_dash_name)
801
+ has_package_id = effective_package_id is not None
513
802
  if has_dash_key and has_package_id:
514
803
  raise_config_error(
515
804
  "portal apply accepts exactly one selector mode.",
@@ -528,6 +817,7 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
528
817
  package_id=args.package_id,
529
818
  publish=bool(args.publish),
530
819
  sections=sections,
820
+ layout_preset=args.layout_preset,
531
821
  visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
532
822
  auth=load_object_arg(args.auth_file, option_name="--auth-file"),
533
823
  icon=args.icon,
@@ -535,10 +825,18 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
535
825
  hide_copyright=args.hide_copyright,
536
826
  dash_global_config=load_object_arg(args.dash_global_config_file, option_name="--dash-global-config-file"),
537
827
  config=load_object_arg(args.config_file, option_name="--config-file"),
828
+ payload=payload,
538
829
  )
539
830
 
540
831
 
541
832
  def _handle_publish_verify(args: argparse.Namespace, context: CliContext) -> dict:
833
+ app_keys = _parse_app_keys_arg(getattr(args, "app_keys", None))
834
+ if app_keys:
835
+ return context.builder.app_publish_verify(
836
+ profile=args.profile,
837
+ app_keys=app_keys,
838
+ expected_package_id=args.expected_package_id,
839
+ )
542
840
  return context.builder.app_publish_verify(
543
841
  profile=args.profile,
544
842
  app_key=args.app_key,