@qingflow-tech/qingflow-app-user-mcp 1.0.9 → 1.0.11

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.
@@ -13,30 +13,17 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
13
13
  app_subparsers = parser.add_subparsers(dest="app_command", required=True)
14
14
 
15
15
  list_parser = app_subparsers.add_parser("list", help="列出可见应用")
16
+ list_parser.add_argument("--query", default="", help="按关键词在可见应用列表中本地过滤")
17
+ list_parser.add_argument("--keyword", default="", help="兼容别名;建议使用 --query")
16
18
  list_parser.set_defaults(handler=_handle_list, format_hint="app_list")
17
19
 
18
- search = app_subparsers.add_parser("search", help="搜索应用")
19
- search.add_argument("--keyword", default="")
20
- search.add_argument("--page", type=int, default=1)
21
- search.add_argument("--page-size", type=int, default=50)
22
- search.set_defaults(handler=_handle_search, format_hint="app_search")
23
-
24
20
  get = app_subparsers.add_parser("get", help="读取应用可访问视图与导入能力")
25
21
  get.add_argument("--app-key", help="不传时在交互终端中选择应用")
26
22
  get.set_defaults(handler=_handle_get, format_hint="app_get")
27
23
 
28
24
 
29
25
  def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
30
- return context.app.app_list(profile=args.profile)
31
-
32
-
33
- def _handle_search(args: argparse.Namespace, context: CliContext) -> dict:
34
- return context.app.app_search(
35
- profile=args.profile,
36
- keyword=args.keyword,
37
- page_num=args.page,
38
- page_size=args.page_size,
39
- )
26
+ return context.app.app_list(profile=args.profile, query=args.query, keyword=args.keyword)
40
27
 
41
28
 
42
29
  def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
@@ -42,6 +42,11 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
42
42
  contract.add_argument("--tool-name", required=True)
43
43
  contract.set_defaults(handler=_handle_contract, format_hint="builder_summary")
44
44
 
45
+ icon = builder_subparsers.add_parser("icon", help="工作区图标目录")
46
+ icon_subparsers = icon.add_subparsers(dest="builder_icon_command", required=True)
47
+ icon_catalog = icon_subparsers.add_parser("catalog", help="读取应用、应用包、门户可用图标目录")
48
+ icon_catalog.set_defaults(handler=_handle_icon_catalog, format_hint="builder_summary")
49
+
45
50
  member = builder_subparsers.add_parser("member", help="成员目录")
46
51
  member_subparsers = member.add_subparsers(dest="builder_member_command", required=True)
47
52
  member_search = member_subparsers.add_parser("search", help="搜索成员")
@@ -78,13 +83,18 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
78
83
  solution_install.add_argument("--solution-source", default="solutionDetail")
79
84
  solution_install.set_defaults(handler=_handle_solution_install, format_hint="builder_summary")
80
85
 
86
+ package_list = package_subparsers.add_parser("list", help="列出应用包")
87
+ package_list.add_argument("--trial-status", default="all")
88
+ package_list.add_argument("--query", default="")
89
+ package_list.set_defaults(handler=_handle_package_list, format_hint="builder_summary")
90
+
81
91
  package_get = package_subparsers.add_parser("get", help="读取应用包详情")
82
92
  package_get.add_argument("--package-id", type=int, required=True)
83
93
  package_get.set_defaults(handler=_handle_package_get, format_hint="builder_summary")
84
94
 
85
95
  package_apply = package_subparsers.add_parser("apply", help="创建或更新应用包配置")
86
96
  package_apply.add_argument("--config-file", required=True)
87
- package_apply.set_defaults(handler=_handle_package_apply, format_hint="builder_summary")
97
+ package_apply.set_defaults(handler=_handle_package_apply, format_hint="builder_summary", force_json_output=True)
88
98
 
89
99
  app = builder_subparsers.add_parser("app", help="应用")
90
100
  app_subparsers = app.add_subparsers(dest="builder_app_command", required=True)
@@ -99,7 +109,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
99
109
  app_release_lock.add_argument("--app-key", required=True)
100
110
  app_release_lock.add_argument("--lock-owner-email", required=True)
101
111
  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")
112
+ app_release_lock.set_defaults(handler=_handle_app_release_edit_lock_if_mine, format_hint="builder_summary", force_json_output=True)
103
113
 
104
114
  app_get = app_subparsers.add_parser("get", help="读取应用配置(字段请使用: builder app get --app-key APP fields)")
105
115
  app_get.add_argument(
@@ -129,7 +139,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
129
139
  button_apply.add_argument("--patch-buttons-file")
130
140
  button_apply.add_argument("--remove-buttons-file")
131
141
  button_apply.add_argument("--view-configs-file")
132
- button_apply.set_defaults(handler=_handle_button_apply, format_hint="builder_summary")
142
+ button_apply.set_defaults(handler=_handle_button_apply, format_hint="builder_summary", force_json_output=True)
133
143
 
134
144
  associated_resource = builder_subparsers.add_parser("associated-resource", aliases=["associated-resources"], help="关联视图/报表")
135
145
  associated_resource_subparsers = associated_resource.add_subparsers(dest="builder_associated_resource_command", required=True)
@@ -140,7 +150,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
140
150
  associated_resource_apply.add_argument("--remove-associated-item-ids-file")
141
151
  associated_resource_apply.add_argument("--reorder-associated-item-ids-file")
142
152
  associated_resource_apply.add_argument("--view-configs-file")
143
- associated_resource_apply.set_defaults(handler=_handle_associated_resource_apply, format_hint="builder_summary")
153
+ associated_resource_apply.set_defaults(handler=_handle_associated_resource_apply, format_hint="builder_summary", force_json_output=True)
144
154
 
145
155
  portal = builder_subparsers.add_parser("portal", help="门户")
146
156
  portal_subparsers = portal.add_subparsers(dest="builder_portal_command", required=True)
@@ -157,7 +167,9 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
157
167
  portal_apply.add_argument("--dash-name", default="")
158
168
  portal_apply.add_argument("--package-id", type=int)
159
169
  portal_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
170
+ portal_apply.add_argument("--payload-file")
160
171
  portal_apply.add_argument("--sections-file")
172
+ portal_apply.add_argument("--layout-preset", default="")
161
173
  portal_apply.add_argument("--visibility-file")
162
174
  portal_apply.add_argument("--auth-file")
163
175
  portal_apply.add_argument("--icon")
@@ -165,7 +177,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
165
177
  portal_apply.add_argument("--hide-copyright", action=argparse.BooleanOptionalAction, default=None)
166
178
  portal_apply.add_argument("--dash-global-config-file")
167
179
  portal_apply.add_argument("--config-file")
168
- portal_apply.set_defaults(handler=_handle_portal_apply, format_hint="builder_summary")
180
+ portal_apply.set_defaults(handler=_handle_portal_apply, format_hint="builder_summary", force_json_output=True)
169
181
 
170
182
  schema_apply = builder_subparsers.add_parser("schema", help="字段搭建")
171
183
  schema_apply_subparsers = schema_apply.add_subparsers(dest="builder_schema_command", required=True)
@@ -179,10 +191,11 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
179
191
  schema_apply_apply.add_argument("--visibility-file")
180
192
  schema_apply_apply.add_argument("--create-if-missing", action="store_true")
181
193
  schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
194
+ schema_apply_apply.add_argument("--apps-file", help="多应用 schema JSON 数组;每项可带 client_key/app_name/add_fields,支持 relation target_app_ref")
182
195
  schema_apply_apply.add_argument("--add-fields-file", help="字段 JSON 数组;字段可用 as_data_title/as_data_cover 标记数据标题/封面")
183
196
  schema_apply_apply.add_argument("--update-fields-file", help="字段更新 JSON 数组;set 内可用 as_data_title/as_data_cover 标记数据标题/封面")
184
197
  schema_apply_apply.add_argument("--remove-fields-file")
185
- schema_apply_apply.set_defaults(handler=_handle_schema_apply, format_hint="builder_summary")
198
+ schema_apply_apply.set_defaults(handler=_handle_schema_apply, format_hint="builder_summary", force_json_output=True)
186
199
 
187
200
  layout_apply = builder_subparsers.add_parser("layout", help="布局")
188
201
  layout_apply_subparsers = layout_apply.add_subparsers(dest="builder_layout_command", required=True)
@@ -191,7 +204,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
191
204
  layout_apply_apply.add_argument("--mode", choices=["merge", "replace"], default="merge")
192
205
  layout_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
193
206
  layout_apply_apply.add_argument("--sections-file", required=True)
194
- layout_apply_apply.set_defaults(handler=_handle_layout_apply, format_hint="builder_summary")
207
+ layout_apply_apply.set_defaults(handler=_handle_layout_apply, format_hint="builder_summary", force_json_output=True)
195
208
 
196
209
  views_apply = builder_subparsers.add_parser("views", help="视图")
197
210
  views_apply_subparsers = views_apply.add_subparsers(dest="builder_views_command", required=True)
@@ -201,7 +214,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
201
214
  views_apply_apply.add_argument("--upsert-views-file")
202
215
  views_apply_apply.add_argument("--patch-views-file")
203
216
  views_apply_apply.add_argument("--remove-views-file")
204
- views_apply_apply.set_defaults(handler=_handle_views_apply, format_hint="builder_summary")
217
+ views_apply_apply.set_defaults(handler=_handle_views_apply, format_hint="builder_summary", force_json_output=True)
205
218
 
206
219
  flow_apply = builder_subparsers.add_parser("flow", help="流程")
207
220
  flow_apply_subparsers = flow_apply.add_subparsers(dest="builder_flow_command", required=True)
@@ -211,7 +224,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
211
224
  flow_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
212
225
  flow_apply_apply.add_argument("--nodes-file", required=True)
213
226
  flow_apply_apply.add_argument("--transitions-file", required=True)
214
- flow_apply_apply.set_defaults(handler=_handle_flow_apply, format_hint="builder_summary")
227
+ flow_apply_apply.set_defaults(handler=_handle_flow_apply, format_hint="builder_summary", force_json_output=True)
215
228
 
216
229
  charts_apply = builder_subparsers.add_parser("charts", help="报表")
217
230
  charts_apply_subparsers = charts_apply.add_subparsers(dest="builder_charts_command", required=True)
@@ -221,14 +234,14 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
221
234
  charts_apply_apply.add_argument("--patch-file")
222
235
  charts_apply_apply.add_argument("--remove-chart-ids-file")
223
236
  charts_apply_apply.add_argument("--reorder-chart-ids-file")
224
- charts_apply_apply.set_defaults(handler=_handle_charts_apply, format_hint="builder_summary")
237
+ charts_apply_apply.set_defaults(handler=_handle_charts_apply, format_hint="builder_summary", force_json_output=True)
225
238
 
226
239
  publish_verify = builder_subparsers.add_parser("publish", help="发布校验")
227
240
  publish_verify_subparsers = publish_verify.add_subparsers(dest="builder_publish_command", required=True)
228
241
  publish_verify_verify = publish_verify_subparsers.add_parser("verify", help="校验应用发布")
229
242
  publish_verify_verify.add_argument("--app-key", required=True)
230
243
  publish_verify_verify.add_argument("--expected-package-id", type=int)
231
- publish_verify_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_summary")
244
+ publish_verify_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_summary", force_json_output=True)
232
245
 
233
246
  view = builder_subparsers.add_parser("view", help="视图详情")
234
247
  view_subparsers = view.add_subparsers(dest="builder_view_command", required=True)
@@ -276,6 +289,10 @@ def _handle_contract(args: argparse.Namespace, context: CliContext) -> dict:
276
289
  return context.builder.builder_tool_contract(tool_name=args.tool_name)
277
290
 
278
291
 
292
+ def _handle_icon_catalog(args: argparse.Namespace, context: CliContext) -> dict:
293
+ return context.builder.workspace_icon_catalog_get(profile=args.profile)
294
+
295
+
279
296
  def _handle_member_search(args: argparse.Namespace, context: CliContext) -> dict:
280
297
  return context.builder.member_search(
281
298
  profile=args.profile,
@@ -319,6 +336,10 @@ def _handle_package_get(args: argparse.Namespace, context: CliContext) -> dict:
319
336
  return context.builder.package_get(profile=args.profile, package_id=args.package_id)
320
337
 
321
338
 
339
+ def _handle_package_list(args: argparse.Namespace, context: CliContext) -> dict:
340
+ return context.builder.package_list(profile=args.profile, trial_status=args.trial_status, query=args.query)
341
+
342
+
322
343
  def _handle_package_apply(args: argparse.Namespace, context: CliContext) -> dict:
323
344
  config = load_object_arg(args.config_file, option_name="--config-file")
324
345
  if not isinstance(config, dict):
@@ -451,6 +472,34 @@ def _handle_chart_get(args: argparse.Namespace, context: CliContext) -> dict:
451
472
 
452
473
 
453
474
  def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
475
+ apps = load_list_arg(args.apps_file, option_name="--apps-file")
476
+ if args.apps_file:
477
+ if not apps:
478
+ raise_config_error(
479
+ "schema apply multi-app mode requires a non-empty --apps-file.",
480
+ fix_hint="Pass a JSON array with at least one app item.",
481
+ )
482
+ 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:
483
+ raise_config_error(
484
+ "schema apply multi-app mode accepts --package-id/--create-if-missing plus --apps-file only.",
485
+ fix_hint="Use `--apps-file` for batch mode, or remove `--apps-file` and use the single-app arguments.",
486
+ )
487
+ if args.package_id is None:
488
+ raise_config_error(
489
+ "schema apply multi-app mode requires --package-id.",
490
+ fix_hint="Pass `--package-id` and app names inside --apps-file.",
491
+ )
492
+ return context.builder.app_schema_apply(
493
+ profile=args.profile,
494
+ package_id=args.package_id,
495
+ visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
496
+ create_if_missing=bool(args.create_if_missing),
497
+ publish=bool(args.publish),
498
+ apps=apps,
499
+ add_fields=[],
500
+ update_fields=[],
501
+ remove_fields=[],
502
+ )
454
503
  has_app_key = bool((args.app_key or "").strip())
455
504
  has_app_name = bool((args.app_name or "").strip())
456
505
  has_app_title = bool((args.app_title or "").strip())
@@ -528,9 +577,13 @@ def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
528
577
 
529
578
 
530
579
  def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
580
+ payload = load_object_arg(args.payload_file, option_name="--payload-file") if getattr(args, "payload_file", None) else None
581
+ payload_obj = payload if isinstance(payload, dict) else {}
582
+ 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()
583
+ 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")
531
584
  has_dash_key = bool((args.dash_key or "").strip())
532
- has_dash_name = bool((args.dash_name or "").strip())
533
- has_package_id = args.package_id is not None
585
+ has_dash_name = bool(effective_dash_name)
586
+ has_package_id = effective_package_id is not None
534
587
  if has_dash_key and has_package_id:
535
588
  raise_config_error(
536
589
  "portal apply accepts exactly one selector mode.",
@@ -549,6 +602,7 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
549
602
  package_id=args.package_id,
550
603
  publish=bool(args.publish),
551
604
  sections=sections,
605
+ layout_preset=args.layout_preset,
552
606
  visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
553
607
  auth=load_object_arg(args.auth_file, option_name="--auth-file"),
554
608
  icon=args.icon,
@@ -556,6 +610,7 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
556
610
  hide_copyright=args.hide_copyright,
557
611
  dash_global_config=load_object_arg(args.dash_global_config_file, option_name="--dash-global-config-file"),
558
612
  config=load_object_arg(args.config_file, option_name="--config-file"),
613
+ payload=payload,
559
614
  )
560
615
 
561
616
 
@@ -13,7 +13,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
13
13
  record_subparsers = parser.add_subparsers(
14
14
  dest="record_command",
15
15
  required=True,
16
- metavar="{schema,list,access,get,insert,update,delete,member-candidates,department-candidates,code-block-run}",
16
+ metavar="{schema,list,access,get,logs,insert,update,delete,member-candidates,department-candidates,code-block-run}",
17
17
  )
18
18
 
19
19
  schema = record_subparsers.add_parser("schema", help="读取记录相关表结构")
@@ -102,6 +102,12 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
102
102
  get.add_argument("--view-id")
103
103
  get.set_defaults(handler=_handle_get, format_hint="record_get")
104
104
 
105
+ logs = record_subparsers.add_parser("logs", help="读取单条记录全量日志并写入本地 JSONL")
106
+ logs.add_argument("--app-key", required=True)
107
+ logs.add_argument("--record-id", required=True)
108
+ logs.add_argument("--view-id")
109
+ logs.set_defaults(handler=_handle_logs, format_hint="record_logs")
110
+
105
111
  insert = record_subparsers.add_parser("insert", help="新增记录")
106
112
  insert.add_argument("--app-key", required=True)
107
113
  insert.add_argument("--fields-file", help=argparse.SUPPRESS)
@@ -328,6 +334,15 @@ def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
328
334
  )
329
335
 
330
336
 
337
+ def _handle_logs(args: argparse.Namespace, context: CliContext) -> dict:
338
+ return context.record.record_logs_get(
339
+ profile=args.profile,
340
+ app_key=args.app_key,
341
+ record_id=args.record_id,
342
+ view_id=args.view_id,
343
+ )
344
+
345
+
331
346
  def _handle_insert(args: argparse.Namespace, context: CliContext) -> dict:
332
347
  if args.items_file:
333
348
  if args.fields_file:
@@ -292,6 +292,37 @@ def _format_record_get(result: dict[str, Any]) -> str:
292
292
  return "\n".join(lines) + "\n"
293
293
 
294
294
 
295
+ def _format_record_logs(result: dict[str, Any]) -> str:
296
+ app = result.get("app") if isinstance(result.get("app"), dict) else {}
297
+ view = result.get("view") if isinstance(result.get("view"), dict) else {}
298
+ record = result.get("record") if isinstance(result.get("record"), dict) else {}
299
+ data_logs = result.get("data_logs") if isinstance(result.get("data_logs"), dict) else {}
300
+ workflow_logs = result.get("workflow_logs") if isinstance(result.get("workflow_logs"), dict) else {}
301
+ integrity = result.get("context_integrity") if isinstance(result.get("context_integrity"), dict) else {}
302
+ lines = [
303
+ f"Status: {result.get('status') or '-'}",
304
+ f"App: {app.get('app_name') or app.get('app_key') or '-'} ({app.get('app_key') or '-'})",
305
+ f"View: {view.get('name') or view.get('view_id') or '-'}",
306
+ f"Record: {record.get('title') or '-'} ({record.get('record_id') or '-'})",
307
+ f"Data logs: {data_logs.get('status') or '-'} / count={data_logs.get('items_count')} / pages={data_logs.get('pages_fetched')} / complete={data_logs.get('complete')}",
308
+ f"Workflow logs: {workflow_logs.get('status') or '-'} / count={workflow_logs.get('items_count')} / pages={workflow_logs.get('pages_fetched')} / complete={workflow_logs.get('complete')}",
309
+ f"Safe for full log conclusion: {integrity.get('safe_for_full_log_conclusion')}",
310
+ ]
311
+ if result.get("local_dir"):
312
+ lines.append(f"Local dir: {result.get('local_dir')}")
313
+ if data_logs.get("local_path"):
314
+ lines.append(f"Data logs file: {data_logs.get('local_path')}")
315
+ if workflow_logs.get("local_path"):
316
+ lines.append(f"Workflow logs file: {workflow_logs.get('local_path')}")
317
+ if result.get("summary_path"):
318
+ lines.append(f"Summary file: {result.get('summary_path')}")
319
+ _append_warnings(lines, result.get("warnings"))
320
+ unavailable = result.get("unavailable_context") if isinstance(result.get("unavailable_context"), list) else []
321
+ if unavailable:
322
+ lines.append(f"Unavailable contexts: {len(unavailable)}")
323
+ return "\n".join(lines) + "\n"
324
+
325
+
295
326
  def _format_task_list(result: dict[str, Any]) -> str:
296
327
  data = result.get("data") if isinstance(result.get("data"), dict) else {}
297
328
  items = data.get("items") if isinstance(data.get("items"), list) else []
@@ -788,11 +819,11 @@ _FORMATTERS = {
788
819
  "workspace_get": _format_workspace_get,
789
820
  "workspace_select": _format_workspace_select,
790
821
  "app_list": _format_app_items,
791
- "app_search": _format_app_items,
792
822
  "app_get": _format_app_get,
793
823
  "record_list": _format_record_list,
794
824
  "record_access": _format_record_access,
795
825
  "record_get": _format_record_get,
826
+ "record_logs": _format_record_logs,
796
827
  "task_list": _format_task_list,
797
828
  "task_workbench": _format_task_workbench,
798
829
  "task_get": _format_task_get,
@@ -8,6 +8,7 @@ from typing import Any, Callable, TextIO
8
8
  from ..errors import QingflowApiError
9
9
  from ..public_surface import cli_public_tool_spec_from_namespace
10
10
  from ..response_trim import resolve_cli_tool_name, trim_error_response, trim_public_response
11
+ from ..tools.ai_builder_tools import _attach_builder_apply_envelope
11
12
  from .context import CliContext, build_cli_context
12
13
  from .formatters import emit_json_result, emit_text_result
13
14
  from .commands import register_all_commands
@@ -16,8 +17,21 @@ from .commands import register_all_commands
16
17
  Handler = Callable[[argparse.Namespace, CliContext], dict[str, Any]]
17
18
 
18
19
 
20
+ class _CliArgumentError(Exception):
21
+ def __init__(self, *, prog: str, message: str, usage: str) -> None:
22
+ super().__init__(message)
23
+ self.prog = prog
24
+ self.message = message
25
+ self.usage = usage
26
+
27
+
28
+ class _QingflowArgumentParser(argparse.ArgumentParser):
29
+ def error(self, message: str) -> None:
30
+ raise _CliArgumentError(prog=self.prog, message=message, usage=self.format_usage())
31
+
32
+
19
33
  def build_parser() -> argparse.ArgumentParser:
20
- parser = argparse.ArgumentParser(prog="qingflow", description="Qingflow CLI")
34
+ parser = _QingflowArgumentParser(prog="qingflow", description="Qingflow CLI")
21
35
  parser.add_argument("--profile", default="default", help="会话 profile,默认 default")
22
36
  parser.add_argument("--json", action="store_true", help="输出 JSON")
23
37
  subparsers = parser.add_subparsers(dest="command", required=True)
@@ -42,6 +56,21 @@ def run(
42
56
  normalized_argv = _normalize_global_args(list(argv) if argv is not None else sys.argv[1:])
43
57
  try:
44
58
  args = parser.parse_args(normalized_argv)
59
+ except _CliArgumentError as exc:
60
+ if _should_force_json_output_argv(normalized_argv):
61
+ payload = {
62
+ "category": "config",
63
+ "status": "failed",
64
+ "error_code": "ARGUMENT_ERROR",
65
+ "message": exc.message,
66
+ "details": {"usage": exc.usage.strip(), "prog": exc.prog},
67
+ }
68
+ payload = _maybe_attach_builder_apply_error_envelope_from_argv(normalized_argv, payload)
69
+ emit_json_result(payload, stream=out)
70
+ return 2
71
+ err.write(exc.usage)
72
+ err.write(f"{exc.prog}: error: {exc.message}\n")
73
+ return 2
45
74
  except SystemExit as exc:
46
75
  return int(exc.code or 0)
47
76
  setattr(args, "_stdin", sys.stdin)
@@ -51,8 +80,10 @@ def run(
51
80
  if handler is None:
52
81
  parser.print_help(out)
53
82
  return 2
54
- context = context_factory()
55
83
  try:
84
+ if _should_force_json_output(args):
85
+ setattr(args, "json", True)
86
+ context = context_factory()
56
87
  if not bool(args.json):
57
88
  _emit_cli_effective_context_notice(args, context, stream=err)
58
89
  result = handler(args, context)
@@ -60,12 +91,15 @@ def run(
60
91
  return int(exc.code or 0)
61
92
  except RuntimeError as exc:
62
93
  payload = trim_error_response(_parse_error_payload(exc))
94
+ payload = _maybe_attach_builder_apply_error_envelope_from_args(args, payload)
63
95
  return _emit_error(payload, json_mode=bool(args.json), stdout=out, stderr=err)
64
96
  except QingflowApiError as exc:
65
97
  payload = trim_error_response(exc.to_dict())
98
+ payload = _maybe_attach_builder_apply_error_envelope_from_args(args, payload)
66
99
  return _emit_error(payload, json_mode=bool(args.json), stdout=out, stderr=err)
67
100
  finally:
68
- context.close()
101
+ if "context" in locals():
102
+ context.close()
69
103
 
70
104
  exit_code = _result_exit_code(result)
71
105
  trimmed_result = trim_public_response(resolve_cli_tool_name(args), result) if isinstance(result, dict) else result
@@ -104,6 +138,173 @@ def _normalize_global_args(argv: list[str]) -> list[str]:
104
138
  return global_args + remaining
105
139
 
106
140
 
141
+ def _should_force_json_output(args: argparse.Namespace) -> bool:
142
+ if bool(getattr(args, "force_json_output", False)):
143
+ return True
144
+ if (
145
+ getattr(args, "command", "") == "builder"
146
+ and getattr(args, "builder_app_command", "") == "repair-code-blocks"
147
+ and bool(getattr(args, "apply", False))
148
+ ):
149
+ return True
150
+ return False
151
+
152
+
153
+ def _should_force_json_output_argv(argv: list[str]) -> bool:
154
+ tokens = _strip_global_args(argv)
155
+ if not tokens or tokens[0] not in {"builder", "build"}:
156
+ return False
157
+ if len(tokens) < 3:
158
+ return False
159
+ section = tokens[1]
160
+ action = tokens[2]
161
+ if section in {"package", "button", "associated-resource", "associated-resources", "portal", "schema", "layout", "views", "flow", "charts"}:
162
+ return action == "apply"
163
+ if section == "publish":
164
+ return action == "verify"
165
+ if section == "app":
166
+ if action == "release-edit-lock-if-mine":
167
+ return True
168
+ if action == "repair-code-blocks":
169
+ return "--apply" in tokens
170
+ return False
171
+
172
+
173
+ def _maybe_attach_builder_apply_error_envelope_from_args(args: argparse.Namespace, payload: dict[str, Any]) -> dict[str, Any]:
174
+ operation = _builder_apply_operation_from_args(args)
175
+ if not operation:
176
+ return payload
177
+ enriched = dict(payload)
178
+ enriched.setdefault("status", "failed")
179
+ enriched.setdefault("ok", False)
180
+ enriched.setdefault("write_executed", False)
181
+ enriched.setdefault("safe_to_retry", False)
182
+ _copy_arg_identity(enriched, args)
183
+ return _attach_builder_apply_envelope(operation, enriched)
184
+
185
+
186
+ def _maybe_attach_builder_apply_error_envelope_from_argv(argv: list[str], payload: dict[str, Any]) -> dict[str, Any]:
187
+ operation = _builder_apply_operation_from_argv(argv)
188
+ if not operation:
189
+ return payload
190
+ enriched = dict(payload)
191
+ enriched.setdefault("status", "failed")
192
+ enriched.setdefault("ok", False)
193
+ enriched.setdefault("write_executed", False)
194
+ enriched.setdefault("safe_to_retry", False)
195
+ _copy_argv_identity(enriched, argv)
196
+ return _attach_builder_apply_envelope(operation, enriched)
197
+
198
+
199
+ def _builder_apply_operation_from_args(args: argparse.Namespace) -> str | None:
200
+ if getattr(args, "command", "") not in {"builder", "build"}:
201
+ return None
202
+ section = str(getattr(args, "builder_command", "") or "")
203
+ if section == "package" and getattr(args, "builder_package_command", "") == "apply":
204
+ return "package_apply"
205
+ if section == "button" and getattr(args, "builder_button_command", "") == "apply":
206
+ return "app_custom_buttons_apply"
207
+ if section in {"associated-resource", "associated-resources"} and getattr(args, "builder_associated_resource_command", "") == "apply":
208
+ return "app_associated_resources_apply"
209
+ if section == "portal" and getattr(args, "builder_portal_command", "") == "apply":
210
+ return "portal_apply"
211
+ if section == "schema" and getattr(args, "builder_schema_command", "") == "apply":
212
+ return "app_schema_apply"
213
+ if section == "layout" and getattr(args, "builder_layout_command", "") == "apply":
214
+ return "app_layout_apply"
215
+ if section == "views" and getattr(args, "builder_views_command", "") == "apply":
216
+ return "app_views_apply"
217
+ if section == "flow" and getattr(args, "builder_flow_command", "") == "apply":
218
+ return "app_flow_apply"
219
+ if section == "charts" and getattr(args, "builder_charts_command", "") == "apply":
220
+ return "app_charts_apply"
221
+ if section == "publish" and getattr(args, "builder_publish_command", "") == "verify":
222
+ return "app_publish_verify"
223
+ return None
224
+
225
+
226
+ def _builder_apply_operation_from_argv(argv: list[str]) -> str | None:
227
+ tokens = _strip_global_args(argv)
228
+ if not tokens or tokens[0] not in {"builder", "build"} or len(tokens) < 3:
229
+ return None
230
+ section = tokens[1]
231
+ action = tokens[2]
232
+ if action != "apply" and not (section == "publish" and action == "verify"):
233
+ return None
234
+ mapping = {
235
+ "package": "package_apply",
236
+ "button": "app_custom_buttons_apply",
237
+ "associated-resource": "app_associated_resources_apply",
238
+ "associated-resources": "app_associated_resources_apply",
239
+ "portal": "portal_apply",
240
+ "schema": "app_schema_apply",
241
+ "layout": "app_layout_apply",
242
+ "views": "app_views_apply",
243
+ "flow": "app_flow_apply",
244
+ "charts": "app_charts_apply",
245
+ "publish": "app_publish_verify",
246
+ }
247
+ return mapping.get(section)
248
+
249
+
250
+ def _copy_arg_identity(payload: dict[str, Any], args: argparse.Namespace) -> None:
251
+ for attr, key in (
252
+ ("app_key", "app_key"),
253
+ ("app_name", "app_name"),
254
+ ("app_title", "app_title"),
255
+ ("package_id", "package_id"),
256
+ ("dash_key", "dash_key"),
257
+ ("dash_name", "dash_name"),
258
+ ):
259
+ value = getattr(args, attr, None)
260
+ if value not in (None, ""):
261
+ payload.setdefault(key, value)
262
+
263
+
264
+ def _copy_argv_identity(payload: dict[str, Any], argv: list[str]) -> None:
265
+ tokens = _strip_global_args(argv)
266
+ option_to_key = {
267
+ "--app-key": "app_key",
268
+ "--app-name": "app_name",
269
+ "--app-title": "app_title",
270
+ "--package-id": "package_id",
271
+ "--dash-key": "dash_key",
272
+ "--dash-name": "dash_name",
273
+ }
274
+ index = 0
275
+ while index < len(tokens):
276
+ token = tokens[index]
277
+ if token in option_to_key and index + 1 < len(tokens):
278
+ payload.setdefault(option_to_key[token], tokens[index + 1])
279
+ index += 2
280
+ continue
281
+ for option, key in option_to_key.items():
282
+ prefix = f"{option}="
283
+ if token.startswith(prefix):
284
+ payload.setdefault(key, token[len(prefix) :])
285
+ break
286
+ index += 1
287
+
288
+
289
+ def _strip_global_args(argv: list[str]) -> list[str]:
290
+ stripped: list[str] = []
291
+ index = 0
292
+ while index < len(argv):
293
+ token = argv[index]
294
+ if token == "--json":
295
+ index += 1
296
+ continue
297
+ if token == "--profile":
298
+ index += 2
299
+ continue
300
+ if token.startswith("--profile="):
301
+ index += 1
302
+ continue
303
+ stripped.append(token)
304
+ index += 1
305
+ return stripped
306
+
307
+
107
308
  def _parse_error_payload(exc: RuntimeError) -> dict[str, Any]:
108
309
  raw = str(exc)
109
310
  try:
@@ -38,7 +38,6 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
38
38
  PublicToolSpec(USER_DOMAIN, "workspace_get", ("workspace_get",), ("workspace", "get")),
39
39
  PublicToolSpec(USER_DOMAIN, "workspace_select", ("workspace_select",), ("workspace", "select")),
40
40
  PublicToolSpec(USER_DOMAIN, "app_list", ("app_list",), ("app", "list"), cli_show_effective_context=True),
41
- PublicToolSpec(USER_DOMAIN, "app_search", ("app_search",), ("app", "search"), cli_show_effective_context=True),
42
41
  PublicToolSpec(USER_DOMAIN, "app_get", ("app_get",), ("app", "get"), cli_show_effective_context=True),
43
42
  PublicToolSpec(USER_DOMAIN, "portal_list", ("portal_list",), ("portal", "list"), cli_show_effective_context=True),
44
43
  PublicToolSpec(USER_DOMAIN, "portal_get", ("portal_get",), ("portal", "get"), cli_show_effective_context=True),
@@ -84,6 +83,7 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
84
83
  PublicToolSpec(USER_DOMAIN, "record_list", ("record_list",), ("record", "list"), cli_show_effective_context=True),
85
84
  PublicToolSpec(USER_DOMAIN, "record_access", ("record_access",), ("record", "access"), cli_show_effective_context=True),
86
85
  PublicToolSpec(USER_DOMAIN, "record_get", ("record_get_public",), ("record", "get"), cli_show_effective_context=True),
86
+ PublicToolSpec(USER_DOMAIN, "record_logs_get", ("record_logs_get",), ("record", "logs"), cli_show_effective_context=True),
87
87
  PublicToolSpec(USER_DOMAIN, "record_insert", ("record_insert_public",), ("record", "insert"), cli_show_effective_context=True, cli_context_write=True),
88
88
  PublicToolSpec(USER_DOMAIN, "record_update", ("record_update_public",), ("record", "update"), cli_show_effective_context=True, cli_context_write=True),
89
89
  PublicToolSpec(USER_DOMAIN, "record_delete", ("record_delete_public",), ("record", "delete"), cli_show_effective_context=True, cli_context_write=True),
@@ -127,6 +127,8 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
127
127
  PublicToolSpec(BUILDER_DOMAIN, "file_upload_local", ("file_upload_local",), ("builder", "file", "upload-local"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
128
128
  PublicToolSpec(BUILDER_DOMAIN, "feedback_submit", ("feedback_submit",), ("builder", "feedback", "submit"), has_contract=True),
129
129
  PublicToolSpec(BUILDER_DOMAIN, "builder_tool_contract", ("builder_tool_contract",), ("builder", "contract"), has_contract=False),
130
+ PublicToolSpec(BUILDER_DOMAIN, "workspace_icon_catalog_get", ("workspace_icon_catalog_get",), ("builder", "icon", "catalog"), has_contract=True, cli_show_effective_context=True),
131
+ PublicToolSpec(BUILDER_DOMAIN, "package_list", ("package_list",), ("builder", "package", "list"), has_contract=True, cli_show_effective_context=True),
130
132
  PublicToolSpec(BUILDER_DOMAIN, "package_get", ("package_get",), ("builder", "package", "get"), has_contract=True, cli_show_effective_context=True),
131
133
  PublicToolSpec(BUILDER_DOMAIN, "package_apply", ("package_apply",), ("builder", "package", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
132
134
  PublicToolSpec(BUILDER_DOMAIN, "solution_install", ("solution_install",), ("builder", "solution", "install"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),