@qingflow-tech/qingflow-cli 1.1.2 → 1.1.34

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 (103) hide show
  1. package/README.md +2 -2
  2. package/docs/local-agent-install.md +10 -0
  3. package/package.json +1 -1
  4. package/pyproject.toml +1 -1
  5. package/skills/qingflow-cli/SKILL.md +61 -47
  6. package/skills/qingflow-cli/manifest.yaml +1 -1
  7. package/skills/qingflow-cli/reference/00-INDEX.md +35 -0
  8. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +21 -477
  9. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +44 -290
  10. package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +11 -102
  11. package/skills/qingflow-cli/reference/builder/10-build-single-app.md +57 -0
  12. package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +84 -0
  13. package/skills/qingflow-cli/reference/builder/30-schema-fields.md +317 -0
  14. package/skills/qingflow-cli/reference/builder/40-layout.md +70 -0
  15. package/skills/qingflow-cli/reference/builder/50-views.md +540 -0
  16. package/skills/qingflow-cli/reference/builder/60-charts.md +328 -0
  17. package/skills/qingflow-cli/reference/builder/70-portal.md +372 -0
  18. package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +148 -0
  19. package/skills/qingflow-cli/reference/builder/90-workflow.md +35 -0
  20. package/skills/qingflow-cli/reference/builder/99-publish-verify.md +64 -0
  21. package/skills/qingflow-cli/reference/builder/README.md +62 -0
  22. package/skills/qingflow-cli/reference/builder/code-integrations/README.md +130 -0
  23. package/skills/qingflow-cli/reference/builder/code-integrations/code-block.md +66 -0
  24. package/skills/qingflow-cli/reference/builder/code-integrations/q-linker.md +77 -0
  25. package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +508 -0
  26. package/skills/qingflow-cli/reference/builder/reference/match-rules.md +229 -0
  27. package/skills/qingflow-cli/reference/builder/reference/workspace-icons.md +89 -0
  28. package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +48 -0
  29. package/skills/qingflow-cli/reference/builder/workflow/02-update-mode.md +63 -0
  30. package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +59 -0
  31. package/skills/qingflow-cli/reference/builder/workflow/04-stage1-business-modeling.md +131 -0
  32. package/skills/qingflow-cli/reference/builder/workflow/05-stage2-members-roles.md +29 -0
  33. package/skills/qingflow-cli/reference/builder/workflow/06-stage3-build-spec.md +211 -0
  34. package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +36 -0
  35. package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +66 -0
  36. package/skills/qingflow-cli/reference/builder/workflow/09-stage6-summary.md +88 -0
  37. package/skills/qingflow-cli/reference/builder/workflow/10-node-config-reference.md +93 -0
  38. package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +18 -0
  39. package/skills/qingflow-cli/reference/builder/workflow/README.md +88 -0
  40. package/skills/qingflow-cli/reference/builder/workflow/workflow-schema.json +1754 -0
  41. package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +81 -0
  42. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
  43. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
  44. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
  45. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
  46. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
  47. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
  48. package/skills/qingflow-cli/reference/examples/README.md +17 -0
  49. package/skills/qingflow-cli/reference/examples/associated/associated_resources_semantic_full.example.json +50 -0
  50. package/skills/qingflow-cli/reference/examples/buttons/custom_buttons_advanced.example.json +56 -0
  51. package/skills/qingflow-cli/reference/examples/charts/charts_remove.example.json +1 -0
  52. package/skills/qingflow-cli/reference/examples/charts/charts_reorder.example.json +1 -0
  53. package/skills/qingflow-cli/reference/examples/charts/charts_upsert_bar.example.json +8 -0
  54. package/skills/qingflow-cli/reference/examples/charts/charts_upsert_dashboard_starter.example.json +118 -0
  55. package/skills/qingflow-cli/reference/examples/charts/charts_upsert_minimal.example.json +13 -0
  56. package/skills/qingflow-cli/reference/examples/layout/layout_sections_full.example.json +22 -0
  57. package/skills/qingflow-cli/reference/examples/portal/portal_sections_all_types.example.json +145 -0
  58. package/skills/qingflow-cli/reference/examples/portal/portal_sections_five_types.example.json +126 -0
  59. package/skills/qingflow-cli/reference/examples/portal/portal_sections_standard_workbench.example.json +178 -0
  60. package/skills/qingflow-cli/reference/examples/record/record_insert_all_field_types.example.json +35 -0
  61. package/skills/qingflow-cli/reference/examples/schema/_batch_schema_complex.json +18 -0
  62. package/skills/qingflow-cli/reference/examples/schema/_batch_schema_scalar.json +17 -0
  63. package/skills/qingflow-cli/reference/examples/schema/schema_add_fields_minimal.example.json +7 -0
  64. package/skills/qingflow-cli/reference/examples/schema/schema_apply_add_fields_all_types.json +78 -0
  65. package/skills/qingflow-cli/reference/examples/schema/schema_multi_app_form_relation_layout.example.json +166 -0
  66. package/skills/qingflow-cli/reference/examples/schema/schema_single_app_form_standard.example.json +95 -0
  67. package/skills/qingflow-cli/reference/examples/views/views_batch_full.example.json +159 -0
  68. package/skills/qingflow-cli/reference/examples/views/views_upsert_table_minimal.example.json +42 -0
  69. package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +18 -4
  70. package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +3 -3
  71. package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +57 -7
  72. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +22 -0
  73. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md +31 -0
  74. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
  75. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +167 -0
  76. package/skills/qingflow-cli/reference/record/analysis/README.md +130 -0
  77. package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +91 -0
  78. package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +112 -0
  79. package/skills/qingflow-cli/reference/record/analysis/business-context.md +74 -0
  80. package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +69 -0
  81. package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +106 -0
  82. package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +172 -0
  83. package/skills/qingflow-cli/reference/record/analysis/report-format.md +76 -0
  84. package/skills/qingflow-cli/reference/record/insert/README.md +125 -0
  85. package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +1 -1
  86. package/skills/qingflow-cli/reference/task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +153 -0
  87. package/skills/qingflow-cli/reference/task/ops/README.md +134 -0
  88. package/skills/qingflow-cli/reference/task/ops/environments.md +43 -0
  89. package/skills/qingflow-cli/reference/task/ops/workflow-usage.md +29 -0
  90. package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +42 -7
  91. package/skills/qingflow-cli/scripts/validate_system_build_summary.py +124 -0
  92. package/skills/qingflow-cli/scripts/workflow/diff_flow_spec.py +275 -0
  93. package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +627 -0
  94. package/src/qingflow_mcp/builder_facade/models.py +413 -119
  95. package/src/qingflow_mcp/builder_facade/service.py +4238 -666
  96. package/src/qingflow_mcp/cli/commands/builder.py +232 -75
  97. package/src/qingflow_mcp/cli/main.py +34 -0
  98. package/src/qingflow_mcp/public_surface.py +10 -0
  99. package/src/qingflow_mcp/server_app_builder.py +62 -34
  100. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
  101. package/src/qingflow_mcp/solution/executor.py +2 -0
  102. package/src/qingflow_mcp/tools/ai_builder_tools.py +2649 -457
  103. package/src/qingflow_mcp/tools/record_tools.py +23 -11
@@ -117,10 +117,12 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
117
117
  app_get.add_argument(
118
118
  "builder_app_get_section",
119
119
  nargs="?",
120
- choices=["summary", "fields", "layout", "views", "flow", "charts"],
120
+ choices=["summary", "fields", "layout", "views", "flow", "charts", "buttons", "associated-resources"],
121
121
  default="summary",
122
122
  )
123
- app_get.add_argument("--app-key", required=True)
123
+ app_get.add_argument("--app-key", default="")
124
+ app_get.add_argument("--app-keys", default="", help="逗号分隔的多应用 app_key;用于批量读取同一 section")
125
+ app_get.add_argument("--app-keys-file", help="JSON 字符串数组;用于批量读取同一 section")
124
126
  app_get.set_defaults(handler=_handle_app_get, format_hint="builder_summary")
125
127
 
126
128
  app_repair_code_blocks = app_subparsers.add_parser("repair-code-blocks", help="扫描或修复现有代码块配置")
@@ -135,8 +137,15 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
135
137
  button_catalog = button_subparsers.add_parser("catalog", help="读取按钮样式目录")
136
138
  button_catalog.set_defaults(handler=_handle_button_catalog, format_hint="builder_summary")
137
139
 
140
+ button_get = button_subparsers.add_parser("get", help="读取应用自定义按钮配置")
141
+ button_get.add_argument("--app-key", default="")
142
+ button_get.add_argument("--app-keys", default="", help="逗号分隔的多应用 app_key;用于批量读取按钮配置")
143
+ button_get.add_argument("--app-keys-file", help="JSON 字符串数组;用于批量读取按钮配置")
144
+ button_get.set_defaults(handler=_handle_button_get, format_hint="builder_summary")
145
+
138
146
  button_apply = button_subparsers.add_parser("apply", help="声明式创建、更新或删除自定义按钮")
139
- button_apply.add_argument("--app-key", required=True)
147
+ button_apply.add_argument("--app-key", default="")
148
+ button_apply.add_argument("--apps-file", help="多应用按钮配置 JSON 数组;每项包含 app_key 和按钮 payload")
140
149
  button_apply.add_argument("--upsert-buttons-file")
141
150
  button_apply.add_argument("--patch-buttons-file")
142
151
  button_apply.add_argument("--remove-buttons-file")
@@ -145,8 +154,15 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
145
154
 
146
155
  associated_resource = builder_subparsers.add_parser("associated-resource", aliases=["associated-resources"], help="关联视图/报表")
147
156
  associated_resource_subparsers = associated_resource.add_subparsers(dest="builder_associated_resource_command", required=True)
157
+ associated_resource_get = associated_resource_subparsers.add_parser("get", help="读取应用关联资源配置")
158
+ associated_resource_get.add_argument("--app-key", default="")
159
+ associated_resource_get.add_argument("--app-keys", default="", help="逗号分隔的多应用 app_key;用于批量读取关联资源配置")
160
+ associated_resource_get.add_argument("--app-keys-file", help="JSON 字符串数组;用于批量读取关联资源配置")
161
+ associated_resource_get.set_defaults(handler=_handle_associated_resource_get, format_hint="builder_summary")
162
+
148
163
  associated_resource_apply = associated_resource_subparsers.add_parser("apply", help="声明式管理应用关联资源池和视图展示配置")
149
- associated_resource_apply.add_argument("--app-key", required=True)
164
+ associated_resource_apply.add_argument("--app-key", default="")
165
+ associated_resource_apply.add_argument("--apps-file", help="多应用关联资源配置 JSON 数组;每项包含 app_key 和资源 payload")
150
166
  associated_resource_apply.add_argument("--upsert-resources-file")
151
167
  associated_resource_apply.add_argument("--patch-resources-file")
152
168
  associated_resource_apply.add_argument("--remove-associated-item-ids-file")
@@ -179,6 +195,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
179
195
  portal_apply.add_argument("--hide-copyright", action=argparse.BooleanOptionalAction, default=None)
180
196
  portal_apply.add_argument("--dash-global-config-file")
181
197
  portal_apply.add_argument("--config-file")
198
+ portal_apply.add_argument("--patch-sections-file", help="门户组件局部更新 JSON 数组,每项含 chart_ref/view_ref/order + set/unset")
182
199
  portal_apply.set_defaults(handler=_handle_portal_apply, format_hint="builder_summary", force_json_output=True)
183
200
 
184
201
  portal_delete = portal_subparsers.add_parser("delete", help="删除门户")
@@ -195,9 +212,9 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
195
212
  schema_apply_apply.add_argument("--icon")
196
213
  schema_apply_apply.add_argument("--color")
197
214
  schema_apply_apply.add_argument("--visibility-file")
198
- schema_apply_apply.add_argument("--create-if-missing", action="store_true")
199
215
  schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=None)
200
- schema_apply_apply.add_argument("--apps-file", help="多应用 schema JSON 数组;每项可带 client_key/app_name/add_fields,支持 relation target_app_ref")
216
+ schema_apply_apply.add_argument("--apps-file", help="多应用 schema JSON 对象;每项可带 client_key/app_name/form,支持 relation target_app_ref")
217
+ schema_apply_apply.add_argument("--form-file", help="单应用表单结构 JSON 数组;form[].rows[][] 同时声明字段和表单分组布局")
201
218
  schema_apply_apply.add_argument("--add-fields-file", help="字段 JSON 数组;字段可用 as_data_title/as_data_cover 标记数据标题/封面")
202
219
  schema_apply_apply.add_argument("--update-fields-file", help="字段更新 JSON 数组;set 内可用 as_data_title/as_data_cover 标记数据标题/封面")
203
220
  schema_apply_apply.add_argument("--remove-fields-file")
@@ -206,46 +223,53 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
206
223
  layout_apply = builder_subparsers.add_parser("layout", help="布局")
207
224
  layout_apply_subparsers = layout_apply.add_subparsers(dest="builder_layout_command", required=True)
208
225
  layout_apply_apply = layout_apply_subparsers.add_parser("apply", help="执行布局变更")
209
- layout_apply_apply.add_argument("--app-key", required=True)
226
+ layout_apply_apply.add_argument("--app-key", default="")
227
+ layout_apply_apply.add_argument("--apps-file", help="多应用布局 JSON 数组;每项包含 app_key/mode?/publish?/sections")
210
228
  layout_apply_apply.add_argument("--mode", choices=["merge", "replace"], default="merge")
211
229
  layout_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
212
- layout_apply_apply.add_argument("--sections-file", required=True)
230
+ layout_apply_apply.add_argument("--sections-file")
213
231
  layout_apply_apply.set_defaults(handler=_handle_layout_apply, format_hint="builder_summary", force_json_output=True)
214
232
 
215
233
  views_apply = builder_subparsers.add_parser("views", help="视图")
216
234
  views_apply_subparsers = views_apply.add_subparsers(dest="builder_views_command", required=True)
217
235
  views_apply_apply = views_apply_subparsers.add_parser("apply", help="执行视图变更")
218
- views_apply_apply.add_argument("--app-key", required=True)
236
+ views_apply_apply.add_argument("--app-key", default="")
237
+ views_apply_apply.add_argument("--views-file", help='推荐批量视图 JSON 对象:{"views":[...]};每项包含 app_key、operation、视图配置')
238
+ views_apply_apply.add_argument("--apps-file", help="多应用视图 JSON 数组;每项包含 app_key 和 upsert/patch/remove payload")
219
239
  views_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
220
240
  views_apply_apply.add_argument("--upsert-views-file")
221
241
  views_apply_apply.add_argument("--patch-views-file")
222
242
  views_apply_apply.add_argument("--remove-views-file")
223
243
  views_apply_apply.set_defaults(handler=_handle_views_apply, format_hint="builder_summary", force_json_output=True)
224
244
 
225
- flow_parser = builder_subparsers.add_parser("flow", help="流程(WorkflowSpec)")
226
- flow_subparsers = flow_parser.add_subparsers(dest="builder_flow_command", required=True)
227
-
228
- flow_schema = flow_subparsers.add_parser("schema", help="读取 WorkflowSpec JSON Schema")
245
+ flow_apply = builder_subparsers.add_parser("flow", help="流程")
246
+ flow_apply_subparsers = flow_apply.add_subparsers(dest="builder_flow_command", required=True)
247
+ flow_schema = flow_apply_subparsers.add_parser("schema", help="读取 WorkflowSpec JSON Schema")
229
248
  flow_schema.add_argument("--schema-version", default="")
230
249
  flow_schema.set_defaults(handler=_handle_flow_schema, format_hint="builder_summary")
231
250
 
232
- flow_get = flow_subparsers.add_parser("get", help="读取 WorkflowSpec")
251
+ flow_get = flow_apply_subparsers.add_parser("get", help="读取 WorkflowSpec")
233
252
  flow_get.add_argument("--app-key", required=True)
234
253
  flow_get.add_argument("--version-id", default="")
235
254
  flow_get.set_defaults(handler=_handle_flow_get, format_hint="builder_summary")
236
255
 
237
- flow_apply = flow_subparsers.add_parser("apply", help="应用 WorkflowSpec")
238
- flow_apply.add_argument("--app-key", required=True)
239
- flow_apply.add_argument("--spec-file", required=True)
240
- flow_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
241
- flow_apply.add_argument("--idempotency-key", default="")
242
- flow_apply.add_argument("--schema-version", default="")
243
- flow_apply.set_defaults(handler=_handle_flow_apply, format_hint="builder_summary", force_json_output=True)
256
+ flow_apply_apply = flow_apply_subparsers.add_parser("apply", help="执行流程变更")
257
+ flow_apply_apply.add_argument("--app-key", required=True)
258
+ flow_apply_apply.add_argument("--mode", choices=["replace"], default="replace", help=argparse.SUPPRESS)
259
+ flow_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
260
+ flow_apply_apply.add_argument("--nodes-file", help=argparse.SUPPRESS)
261
+ flow_apply_apply.add_argument("--transitions-file", help=argparse.SUPPRESS)
262
+ flow_apply_apply.add_argument("--spec-file")
263
+ flow_apply_apply.add_argument("--patch-nodes-file", help="节点局部更新 JSON 数组,每项含 id + set/unset")
264
+ flow_apply_apply.add_argument("--idempotency-key", default="")
265
+ flow_apply_apply.add_argument("--schema-version", default="")
266
+ flow_apply_apply.set_defaults(handler=_handle_flow_apply, format_hint="builder_summary", force_json_output=True)
244
267
 
245
268
  charts_apply = builder_subparsers.add_parser("charts", help="报表")
246
269
  charts_apply_subparsers = charts_apply.add_subparsers(dest="builder_charts_command", required=True)
247
270
  charts_apply_apply = charts_apply_subparsers.add_parser("apply", help="执行报表变更")
248
- charts_apply_apply.add_argument("--app-key", required=True)
271
+ charts_apply_apply.add_argument("--app-key", default="")
272
+ charts_apply_apply.add_argument("--apps-file", help="多应用报表 JSON 数组;每项包含 app_key 和 upsert/patch/remove/reorder payload")
249
273
  charts_apply_apply.add_argument("--upsert-file")
250
274
  charts_apply_apply.add_argument("--patch-file")
251
275
  charts_apply_apply.add_argument("--remove-chart-ids-file")
@@ -255,7 +279,9 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
255
279
  publish_verify = builder_subparsers.add_parser("publish", help="发布校验")
256
280
  publish_verify_subparsers = publish_verify.add_subparsers(dest="builder_publish_command", required=True)
257
281
  publish_verify_verify = publish_verify_subparsers.add_parser("verify", help="校验应用发布")
258
- publish_verify_verify.add_argument("--app-key", required=True)
282
+ publish_verify_verify.add_argument("--app-key", default="")
283
+ publish_verify_verify.add_argument("--app-keys", default="", help="逗号分隔的多应用 app_key;用于批量发布校验")
284
+ publish_verify_verify.add_argument("--app-keys-file", help="JSON 字符串数组;用于批量发布校验")
259
285
  publish_verify_verify.add_argument("--expected-package-id", type=int)
260
286
  publish_verify_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_summary", force_json_output=True)
261
287
 
@@ -360,6 +386,7 @@ def _handle_package_apply(args: argparse.Namespace, context: CliContext) -> dict
360
386
  config = load_object_arg(args.config_file, option_name="--config-file")
361
387
  if not isinstance(config, dict):
362
388
  raise_config_error("package apply --config-file must contain a JSON object.")
389
+ config.pop("create_if_missing", None)
363
390
  return context.builder.package_apply(profile=args.profile, **config)
364
391
 
365
392
 
@@ -403,6 +430,9 @@ def _handle_button_catalog(args: argparse.Namespace, context: CliContext) -> dic
403
430
 
404
431
 
405
432
  def _handle_button_apply(args: argparse.Namespace, context: CliContext) -> dict:
433
+ apps = load_list_arg(args.apps_file, option_name="--apps-file") if getattr(args, "apps_file", None) else None
434
+ if apps is None and not (args.app_key or "").strip():
435
+ raise_config_error("builder button apply requires --app-key unless --apps-file is provided")
406
436
  return context.builder.app_custom_buttons_apply(
407
437
  profile=args.profile,
408
438
  app_key=args.app_key,
@@ -410,10 +440,14 @@ def _handle_button_apply(args: argparse.Namespace, context: CliContext) -> dict:
410
440
  patch_buttons=load_list_arg(args.patch_buttons_file, option_name="--patch-buttons-file"),
411
441
  remove_buttons=load_list_arg(args.remove_buttons_file, option_name="--remove-buttons-file"),
412
442
  view_configs=load_list_arg(args.view_configs_file, option_name="--view-configs-file"),
443
+ apps=apps,
413
444
  )
414
445
 
415
446
 
416
447
  def _handle_associated_resource_apply(args: argparse.Namespace, context: CliContext) -> dict:
448
+ apps = load_list_arg(args.apps_file, option_name="--apps-file") if getattr(args, "apps_file", None) else None
449
+ if apps is None and not (args.app_key or "").strip():
450
+ raise_config_error("builder associated-resource apply requires --app-key unless --apps-file is provided")
417
451
  return context.builder.app_associated_resources_apply(
418
452
  profile=args.profile,
419
453
  app_key=args.app_key,
@@ -422,11 +456,26 @@ def _handle_associated_resource_apply(args: argparse.Namespace, context: CliCont
422
456
  remove_associated_item_ids=load_list_arg(args.remove_associated_item_ids_file, option_name="--remove-associated-item-ids-file"),
423
457
  reorder_associated_item_ids=load_list_arg(args.reorder_associated_item_ids_file, option_name="--reorder-associated-item-ids-file"),
424
458
  view_configs=load_list_arg(args.view_configs_file, option_name="--view-configs-file"),
459
+ apps=apps,
425
460
  )
426
461
 
427
462
 
428
463
  def _handle_button_get(args: argparse.Namespace, context: CliContext) -> dict:
429
- return context.builder.app_custom_button_get(profile=args.profile, app_key=args.app_key, button_id=args.button_id)
464
+ app_keys = _app_keys_from_args(args)
465
+ if app_keys is not None:
466
+ return context.builder.app_get_buttons(profile=args.profile, app_key="", app_keys=app_keys)
467
+ if not (args.app_key or "").strip():
468
+ raise_config_error("builder button get requires --app-key unless --app-keys or --app-keys-file is provided")
469
+ return context.builder.app_get_buttons(profile=args.profile, app_key=args.app_key)
470
+
471
+
472
+ def _handle_associated_resource_get(args: argparse.Namespace, context: CliContext) -> dict:
473
+ app_keys = _app_keys_from_args(args)
474
+ if app_keys is not None:
475
+ return context.builder.app_get_associated_resources(profile=args.profile, app_key="", app_keys=app_keys)
476
+ if not (args.app_key or "").strip():
477
+ raise_config_error("builder associated-resource get requires --app-key unless --app-keys or --app-keys-file is provided")
478
+ return context.builder.app_get_associated_resources(profile=args.profile, app_key=args.app_key)
430
479
 
431
480
 
432
481
  def _handle_button_create(args: argparse.Namespace, context: CliContext) -> dict:
@@ -450,6 +499,17 @@ def _handle_button_delete(args: argparse.Namespace, context: CliContext) -> dict
450
499
  return context.builder.app_custom_button_delete(profile=args.profile, app_key=args.app_key, button_id=args.button_id)
451
500
 
452
501
 
502
+ def _app_keys_from_args(args: argparse.Namespace) -> list[str] | None:
503
+ keys: list[str] = []
504
+ raw = str(getattr(args, "app_keys", "") or "").strip()
505
+ if raw:
506
+ keys.extend(part.strip() for part in raw.split(",") if part.strip())
507
+ keys_file = getattr(args, "app_keys_file", None)
508
+ if keys_file:
509
+ keys.extend(str(item).strip() for item in require_list_arg(keys_file, option_name="--app-keys-file") if str(item).strip())
510
+ return keys or None
511
+
512
+
453
513
  def _handle_app_get(args: argparse.Namespace, context: CliContext) -> dict:
454
514
  handlers = {
455
515
  "summary": context.builder.app_get,
@@ -458,7 +518,14 @@ def _handle_app_get(args: argparse.Namespace, context: CliContext) -> dict:
458
518
  "views": context.builder.app_get_views,
459
519
  "flow": context.builder.app_get_flow,
460
520
  "charts": context.builder.app_get_charts,
521
+ "buttons": context.builder.app_get_buttons,
522
+ "associated-resources": context.builder.app_get_associated_resources,
461
523
  }
524
+ app_keys = _app_keys_from_args(args)
525
+ if app_keys is not None:
526
+ return handlers[args.builder_app_get_section](profile=args.profile, app_key="", app_keys=app_keys)
527
+ if not (args.app_key or "").strip():
528
+ raise_config_error("builder app get requires --app-key unless --app-keys or --app-keys-file is provided")
462
529
  return handlers[args.builder_app_get_section](profile=args.profile, app_key=args.app_key)
463
530
 
464
531
 
@@ -495,17 +562,11 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
495
562
  if package_id is None and apps_payload.get("package_id") is not None:
496
563
  package_id = _coerce_apps_file_package_id(apps_payload["package_id"])
497
564
  if args.apps_file:
498
- file_create_if_missing = _coerce_apps_file_bool(
499
- apps_payload.get("create_if_missing"),
500
- field_name="create_if_missing",
501
- default=False,
502
- )
503
565
  file_publish = _coerce_apps_file_bool(
504
566
  apps_payload.get("publish"),
505
567
  field_name="publish",
506
568
  default=True,
507
569
  )
508
- effective_create_if_missing = bool(args.create_if_missing or file_create_if_missing)
509
570
  effective_publish = bool(args.publish if args.publish is not None else file_publish)
510
571
  if not apps:
511
572
  raise_config_error(
@@ -513,10 +574,10 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
513
574
  fix_hint="Pass a JSON array, or a JSON object like {\"package_id\":1001,\"apps\":[...]} with at least one app item.",
514
575
  error_code="APPS_FILE_EMPTY",
515
576
  )
516
- 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:
577
+ if args.app_key or args.app_name or args.app_title or args.form_file or args.add_fields_file or args.update_fields_file or args.remove_fields_file:
517
578
  raise_config_error(
518
- "schema apply multi-app mode accepts --package-id/--create-if-missing plus --apps-file only.",
519
- fix_hint="Use `--apps-file` for batch mode, or remove `--apps-file` and use the single-app arguments.",
579
+ "schema apply multi-app mode accepts --package-id plus --apps-file only.",
580
+ fix_hint="Use apps[].form inside --apps-file for batch mode, or remove --apps-file and use --form-file for a single app.",
520
581
  )
521
582
  if package_id is None:
522
583
  raise_config_error(
@@ -527,9 +588,9 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
527
588
  profile=args.profile,
528
589
  package_id=package_id,
529
590
  visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
530
- create_if_missing=effective_create_if_missing,
531
591
  publish=effective_publish,
532
592
  apps=apps,
593
+ form=None,
533
594
  add_fields=[],
534
595
  update_fields=[],
535
596
  remove_fields=[],
@@ -544,16 +605,16 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
544
605
  has_app_title = bool((args.app_title or "").strip())
545
606
  has_package_id = args.package_id is not None
546
607
  if has_app_key:
547
- if args.create_if_missing or has_package_id:
608
+ if has_package_id:
548
609
  raise_config_error(
549
610
  "schema apply edit mode accepts --app-key and optional --app-name only.",
550
- fix_hint="For existing apps, use `--app-key` and optionally `--app-name`. For create mode, use `--package-id --app-name --create-if-missing`.",
611
+ fix_hint="For existing apps, use `--app-key` and optionally `--app-name`. For create mode, use `--package-id --app-name`.",
551
612
  )
552
613
  else:
553
- if not args.create_if_missing or not has_package_id or not (has_app_name or has_app_title):
614
+ if not has_package_id or not (has_app_name or has_app_title):
554
615
  raise_config_error(
555
- "schema apply create mode requires --package-id, --app-name, and --create-if-missing.",
556
- fix_hint="Use `--app-key` for existing apps, or pass `--package-id --app-name --create-if-missing` to create a new app.",
616
+ "schema apply create mode requires --package-id and --app-name.",
617
+ fix_hint="Use `--app-key` for existing apps, or pass `--package-id --app-name` to create a new app.",
557
618
  )
558
619
  return context.builder.app_schema_apply(
559
620
  profile=args.profile,
@@ -564,8 +625,8 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
564
625
  icon=args.icon,
565
626
  color=args.color,
566
627
  visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
567
- create_if_missing=bool(args.create_if_missing),
568
628
  publish=True if args.publish is None else bool(args.publish),
629
+ form=load_list_arg(args.form_file, option_name="--form-file") if args.form_file else [],
569
630
  add_fields=load_list_arg(args.add_fields_file, option_name="--add-fields-file"),
570
631
  update_fields=load_list_arg(args.update_fields_file, option_name="--update-fields-file"),
571
632
  remove_fields=load_list_arg(args.remove_fields_file, option_name="--remove-fields-file"),
@@ -583,7 +644,12 @@ def _load_apps_file_arg(path: str | None) -> dict[str, object]:
583
644
  "app_name": "员工花名册",
584
645
  "icon": "business-personalcard",
585
646
  "color": "emerald",
586
- "add_fields": [],
647
+ "form": [
648
+ {
649
+ "section": "基础信息",
650
+ "rows": [[{"name": "员工名称", "type": "text", "data_title": True}]],
651
+ }
652
+ ],
587
653
  }
588
654
  ],
589
655
  }
@@ -591,7 +657,8 @@ def _load_apps_file_arg(path: str | None) -> dict[str, object]:
591
657
  "expected_shape": expected_shape,
592
658
  "expected_shape_json": (
593
659
  '{"package_id":1001,"apps":[{"client_key":"employee","app_name":"员工花名册",'
594
- '"icon":"business-personalcard","color":"emerald","add_fields":[]}]}'
660
+ '"icon":"business-personalcard","color":"emerald","form":[{"section":"基础信息",'
661
+ '"rows":[[{"name":"员工名称","type":"text","data_title":true}]]}]}]}'
595
662
  ),
596
663
  }
597
664
  payload = load_json_value(path, option_name="--apps-file")
@@ -602,7 +669,7 @@ def _load_apps_file_arg(path: str | None) -> dict[str, object]:
602
669
  if not isinstance(apps, list):
603
670
  raise_config_error(
604
671
  "--apps-file wrapper object requires an apps array.",
605
- fix_hint="Use {\"package_id\":1001,\"apps\":[...]} or pass a raw apps array like [{\"app_name\":\"...\",\"icon\":\"...\",\"color\":\"...\",\"add_fields\":[...]}].",
672
+ fix_hint='Use {"package_id":1001,"apps":[...]} with apps[].form, or pass a raw apps array with form sections.',
606
673
  error_code="APPS_FILE_SHAPE_INVALID",
607
674
  details=expected_shape_details,
608
675
  )
@@ -617,15 +684,13 @@ def _load_apps_file_arg(path: str | None) -> dict[str, object]:
617
684
  }
618
685
  if wrapper.get("package_id") is not None:
619
686
  result["package_id"] = wrapper.get("package_id")
620
- if wrapper.get("create_if_missing") is not None:
621
- result["create_if_missing"] = wrapper.get("create_if_missing")
622
687
  if wrapper.get("publish") is not None:
623
688
  result["publish"] = wrapper.get("publish")
624
689
  return result
625
690
  if any(isinstance(item, dict) and "apps" in item for item in payload):
626
691
  raise_config_error(
627
692
  "--apps-file root array items must be app items, not package wrapper objects.",
628
- fix_hint="Use one object {\"package_id\":1001,\"apps\":[...]} or a raw app array [{\"app_name\":\"...\",\"icon\":\"...\",\"color\":\"...\",\"add_fields\":[...]}]. Do not wrap multiple {package_id, apps} objects in an array.",
693
+ fix_hint='Use one object {"package_id":1001,"apps":[...]} with apps[].form, or a raw app array with form sections. Do not wrap multiple {package_id, apps} objects in an array.',
629
694
  error_code="APPS_FILE_SHAPE_INVALID",
630
695
  details=expected_shape_details,
631
696
  )
@@ -642,8 +707,6 @@ def _load_apps_file_arg(path: str | None) -> dict[str, object]:
642
707
  result: dict[str, object] = {"apps": apps}
643
708
  if payload.get("package_id") is not None:
644
709
  result["package_id"] = payload.get("package_id")
645
- if payload.get("create_if_missing") is not None:
646
- result["create_if_missing"] = payload.get("create_if_missing")
647
710
  if payload.get("publish") is not None:
648
711
  result["publish"] = payload.get("publish")
649
712
  return result
@@ -709,17 +772,74 @@ def _coerce_apps_file_bool(value: object, *, field_name: str, default: bool) ->
709
772
  )
710
773
 
711
774
 
775
+ def _load_views_file_arg(path: str | None) -> list[object] | None:
776
+ if not path:
777
+ return None
778
+ payload = load_json_value(path, option_name="--views-file")
779
+ if isinstance(payload, dict):
780
+ views = payload.get("views")
781
+ if not isinstance(views, list):
782
+ raise_config_error(
783
+ "--views-file JSON object must contain a views array.",
784
+ fix_hint='Use {"views": [{"operation": "upsert", "app_key": "...", "name": "...", "type": "table", "columns": [...]}]}.',
785
+ error_code="VIEWS_FILE_SHAPE_INVALID",
786
+ details={"expected": '{"views": [...]}', "actual_type": type(views).__name__},
787
+ )
788
+ return views
789
+ if isinstance(payload, list):
790
+ return payload
791
+ raise_config_error(
792
+ "--views-file must be a JSON object with views[] or a JSON array.",
793
+ fix_hint='Prefer {"views": [...]} for new automation. A bare array is accepted only for compatibility.',
794
+ error_code="VIEWS_FILE_SHAPE_INVALID",
795
+ details={"actual_type": type(payload).__name__},
796
+ )
797
+
798
+
712
799
  def _handle_layout_apply(args: argparse.Namespace, context: CliContext) -> dict:
800
+ apps = load_list_arg(args.apps_file, option_name="--apps-file") if getattr(args, "apps_file", None) else None
801
+ if apps is None and not (args.app_key or "").strip():
802
+ raise_config_error("builder layout apply requires --app-key unless --apps-file is provided")
803
+ if apps is None and not getattr(args, "sections_file", None):
804
+ raise_config_error(
805
+ "--sections-file is required",
806
+ fix_hint="Pass --sections-file for single-app layout apply, or use --apps-file for batch layout apply.",
807
+ error_code="ARGUMENT_ERROR",
808
+ details={
809
+ "prog": "qingflow builder layout apply",
810
+ "usage": "qingflow builder layout apply --app-key APP_KEY --sections-file SECTIONS.json",
811
+ },
812
+ )
713
813
  return context.builder.app_layout_apply(
714
814
  profile=args.profile,
715
815
  app_key=args.app_key,
716
816
  mode=args.mode,
717
817
  publish=bool(args.publish),
718
- sections=require_list_arg(args.sections_file, option_name="--sections-file"),
818
+ sections=[] if apps is not None else require_list_arg(args.sections_file, option_name="--sections-file"),
819
+ apps=apps,
719
820
  )
720
821
 
721
822
 
722
823
  def _handle_views_apply(args: argparse.Namespace, context: CliContext) -> dict:
824
+ views = _load_views_file_arg(args.views_file) if getattr(args, "views_file", None) else None
825
+ apps = load_list_arg(args.apps_file, option_name="--apps-file") if getattr(args, "apps_file", None) else None
826
+ if views is not None:
827
+ if apps is not None or (args.app_key or "").strip() or getattr(args, "upsert_views_file", None) or getattr(args, "patch_views_file", None) or getattr(args, "remove_views_file", None):
828
+ raise_config_error(
829
+ "builder views apply --views-file cannot be mixed with --app-key, --apps-file, --upsert-views-file, --patch-views-file, or --remove-views-file.",
830
+ fix_hint="Use one shape: preferred --views-file for flat batch jobs, or legacy --app-key/--apps-file for grouped payloads.",
831
+ )
832
+ return context.builder.app_views_apply(
833
+ profile=args.profile,
834
+ app_key="",
835
+ publish=bool(args.publish),
836
+ upsert_views=[],
837
+ patch_views=[],
838
+ remove_views=[],
839
+ views=views,
840
+ )
841
+ if apps is None and not (args.app_key or "").strip():
842
+ raise_config_error("builder views apply requires --views-file, --apps-file, or --app-key")
723
843
  return context.builder.app_views_apply(
724
844
  profile=args.profile,
725
845
  app_key=args.app_key,
@@ -727,43 +847,63 @@ def _handle_views_apply(args: argparse.Namespace, context: CliContext) -> dict:
727
847
  upsert_views=load_list_arg(args.upsert_views_file, option_name="--upsert-views-file"),
728
848
  patch_views=load_list_arg(args.patch_views_file, option_name="--patch-views-file"),
729
849
  remove_views=load_list_arg(args.remove_views_file, option_name="--remove-views-file"),
850
+ apps=apps,
730
851
  )
731
852
 
732
853
 
733
- def _handle_flow_schema(args: argparse.Namespace, context: CliContext) -> dict:
734
- return context.builder.app_flow_get_schema(
735
- profile=args.profile,
736
- schema_version=args.schema_version or None,
854
+ def _handle_flow_apply(args: argparse.Namespace, context: CliContext) -> dict:
855
+ patch_nodes = load_list_arg(getattr(args, "patch_nodes_file", None), option_name="--patch-nodes-file")
856
+ spec = load_object_arg(getattr(args, "spec_file", None), option_name="--spec-file")
857
+ has_legacy_nodes = bool(getattr(args, "nodes_file", None) or getattr(args, "transitions_file", None))
858
+ if has_legacy_nodes:
859
+ raise_config_error(
860
+ "builder flow apply no longer exposes legacy --nodes-file/--transitions-file input.",
861
+ fix_hint="Use --spec-file for a complete WorkflowSpec, or --patch-nodes-file for targeted edits after builder flow get.",
862
+ error_code="FLOW_LEGACY_INPUT_DISABLED",
863
+ details={"allowed_input_modes": ["--spec-file", "--patch-nodes-file"]},
864
+ )
865
+ selected_modes = sum(1 for selected in (bool(patch_nodes), bool(spec), has_legacy_nodes) if selected)
866
+ if selected_modes != 1:
867
+ raise_config_error(
868
+ "builder flow apply requires exactly one input mode: --spec-file or --patch-nodes-file.",
869
+ fix_hint="Use --spec-file for a complete WorkflowSpec, or --patch-nodes-file for targeted edits after builder flow get.",
870
+ )
871
+ if patch_nodes:
872
+ return context.builder.app_flow_apply(
873
+ profile=args.profile,
874
+ app_key=args.app_key,
875
+ publish=bool(args.publish),
876
+ patch_nodes=patch_nodes,
877
+ idempotency_key=args.idempotency_key,
878
+ schema_version=args.schema_version,
879
+ )
880
+ if spec:
881
+ return context.builder.app_flow_apply(
882
+ profile=args.profile,
883
+ app_key=args.app_key,
884
+ publish=bool(args.publish),
885
+ spec=spec,
886
+ idempotency_key=args.idempotency_key,
887
+ schema_version=args.schema_version,
888
+ )
889
+ raise_config_error(
890
+ "builder flow apply requires --spec-file or --patch-nodes-file.",
891
+ fix_hint="Use --spec-file for a complete WorkflowSpec, or --patch-nodes-file for targeted edits after builder flow get.",
737
892
  )
738
893
 
739
894
 
740
- def _handle_flow_get(args: argparse.Namespace, context: CliContext) -> dict:
741
- return context.builder.app_get_flow(
742
- profile=args.profile,
743
- app_key=args.app_key,
744
- version_id=args.version_id or None,
745
- )
895
+ def _handle_flow_schema(args: argparse.Namespace, context: CliContext) -> dict:
896
+ return context.builder.app_flow_get_schema(profile=args.profile, schema_version=args.schema_version)
746
897
 
747
898
 
748
- def _handle_flow_apply(args: argparse.Namespace, context: CliContext) -> dict:
749
- spec_payload = load_object_arg(args.spec_file, option_name="--spec-file")
750
- if not isinstance(spec_payload, dict):
751
- raise_config_error("flow apply --spec-file must contain a JSON object.")
752
- if "spec" in spec_payload and isinstance(spec_payload.get("spec"), dict):
753
- spec = spec_payload["spec"]
754
- else:
755
- spec = spec_payload
756
- return context.builder.app_flow_apply(
757
- profile=args.profile,
758
- app_key=args.app_key,
759
- publish=bool(args.publish),
760
- spec=spec,
761
- idempotency_key=args.idempotency_key or None,
762
- schema_version=args.schema_version or None,
763
- )
899
+ def _handle_flow_get(args: argparse.Namespace, context: CliContext) -> dict:
900
+ return context.builder.app_flow_get(profile=args.profile, app_key=args.app_key, version_id=args.version_id)
764
901
 
765
902
 
766
903
  def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
904
+ apps = load_list_arg(args.apps_file, option_name="--apps-file") if getattr(args, "apps_file", None) else None
905
+ if apps is None and not (args.app_key or "").strip():
906
+ raise_config_error("builder charts apply requires --app-key unless --apps-file is provided")
767
907
  return context.builder.app_charts_apply(
768
908
  profile=args.profile,
769
909
  app_key=args.app_key,
@@ -771,6 +911,7 @@ def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
771
911
  patch_charts=load_list_arg(args.patch_file, option_name="--patch-file"),
772
912
  remove_chart_ids=load_list_arg(args.remove_chart_ids_file, option_name="--remove-chart-ids-file"),
773
913
  reorder_chart_ids=load_list_arg(args.reorder_chart_ids_file, option_name="--reorder-chart-ids-file"),
914
+ apps=apps,
774
915
  )
775
916
 
776
917
 
@@ -793,6 +934,11 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
793
934
  fix_hint="Use `--dash-key` for an existing portal. For create mode, pass `--package-id --dash-name`.",
794
935
  )
795
936
  sections = [] if not args.sections_file else require_list_arg(args.sections_file, option_name="--sections-file")
937
+ patch_sections = (
938
+ load_list_arg(args.patch_sections_file, option_name="--patch-sections-file")
939
+ if args.patch_sections_file
940
+ else None
941
+ )
796
942
  return context.builder.portal_apply(
797
943
  profile=args.profile,
798
944
  dash_key=args.dash_key,
@@ -809,6 +955,7 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
809
955
  dash_global_config=load_object_arg(args.dash_global_config_file, option_name="--dash-global-config-file"),
810
956
  config=load_object_arg(args.config_file, option_name="--config-file"),
811
957
  payload=payload,
958
+ patch_sections=patch_sections,
812
959
  )
813
960
 
814
961
 
@@ -820,6 +967,16 @@ def _handle_portal_delete(args: argparse.Namespace, context: CliContext) -> dict
820
967
 
821
968
 
822
969
  def _handle_publish_verify(args: argparse.Namespace, context: CliContext) -> dict:
970
+ app_keys = _app_keys_from_args(args)
971
+ if app_keys is not None:
972
+ return context.builder.app_publish_verify(
973
+ profile=args.profile,
974
+ app_key="",
975
+ app_keys=app_keys,
976
+ expected_package_id=args.expected_package_id,
977
+ )
978
+ if not (args.app_key or "").strip():
979
+ raise_config_error("builder publish verify requires --app-key unless --app-keys or --app-keys-file is provided")
823
980
  return context.builder.app_publish_verify(
824
981
  profile=args.profile,
825
982
  app_key=args.app_key,
@@ -79,6 +79,23 @@ def run(
79
79
  return 2
80
80
  except SystemExit as exc:
81
81
  return int(exc.code or 0)
82
+ try:
83
+ _validate_conditional_args(args)
84
+ except _CliArgumentError as exc:
85
+ if _should_force_json_output_argv(normalized_argv):
86
+ payload = {
87
+ "category": "config",
88
+ "status": "failed",
89
+ "error_code": "ARGUMENT_ERROR",
90
+ "message": exc.message,
91
+ "details": {"usage": exc.usage.strip(), "prog": exc.prog},
92
+ }
93
+ payload = _maybe_attach_builder_apply_error_envelope_from_args(args, payload)
94
+ emit_json_result(payload, stream=out)
95
+ return 2
96
+ err.write(exc.usage)
97
+ err.write(f"{exc.prog}: error: {exc.message}\n")
98
+ return 2
82
99
  setattr(args, "_stdin", sys.stdin)
83
100
  setattr(args, "_stdout_stream", out)
84
101
  setattr(args, "_stderr_stream", err)
@@ -144,6 +161,21 @@ def _emit_version(*, json_mode: bool, stdout: TextIO) -> int:
144
161
  return 0
145
162
 
146
163
 
164
+ def _validate_conditional_args(args: argparse.Namespace) -> None:
165
+ if (
166
+ getattr(args, "command", "") in {"builder", "build"}
167
+ and getattr(args, "builder_command", "") == "layout"
168
+ and getattr(args, "builder_layout_command", "") == "apply"
169
+ and not getattr(args, "apps_file", None)
170
+ and not getattr(args, "sections_file", None)
171
+ ):
172
+ raise _CliArgumentError(
173
+ prog="qingflow builder layout apply",
174
+ message="--sections-file is required",
175
+ usage="usage: qingflow builder layout apply --app-key APP_KEY --sections-file SECTIONS.json\n",
176
+ )
177
+
178
+
147
179
  def _should_force_json_output_argv_for_version(argv: list[str]) -> bool:
148
180
  return "--json" in argv
149
181
 
@@ -384,6 +416,8 @@ def _emit_error(payload: dict[str, Any], *, json_mode: bool, stdout: TextIO, std
384
416
 
385
417
 
386
418
  def _error_exit_code(payload: dict[str, Any]) -> int:
419
+ if str(payload.get("error_code") or "").upper() == "ARGUMENT_ERROR":
420
+ return 2
387
421
  if _is_auth_or_workspace_payload(payload):
388
422
  return 3
389
423
  return 4