@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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-record-update/SKILL.md +2 -0
- package/src/qingflow_mcp/builder_facade/models.py +43 -2
- package/src/qingflow_mcp/builder_facade/service.py +1476 -172
- package/src/qingflow_mcp/cli/commands/app.py +3 -16
- package/src/qingflow_mcp/cli/commands/builder.py +68 -13
- package/src/qingflow_mcp/cli/commands/record.py +16 -1
- package/src/qingflow_mcp/cli/formatters.py +32 -1
- package/src/qingflow_mcp/cli/main.py +204 -3
- package/src/qingflow_mcp/public_surface.py +3 -1
- package/src/qingflow_mcp/response_trim.py +70 -13
- package/src/qingflow_mcp/server.py +10 -9
- package/src/qingflow_mcp/server_app_builder.py +53 -7
- package/src/qingflow_mcp/server_app_user.py +12 -15
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1642 -75
- package/src/qingflow_mcp/tools/app_tools.py +53 -8
- package/src/qingflow_mcp/tools/package_tools.py +16 -2
- package/src/qingflow_mcp/tools/record_tools.py +1423 -70
- package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
|
@@ -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(
|
|
533
|
-
has_package_id =
|
|
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 =
|
|
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
|
|
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),
|