@josephyan/qingflow-cli 0.2.0-beta.999 → 1.0.6
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/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +109 -0
- package/src/qingflow_mcp/builder_facade/button_style_catalog.py +282 -0
- package/src/qingflow_mcp/builder_facade/models.py +44 -5
- package/src/qingflow_mcp/builder_facade/service.py +21 -8
- package/src/qingflow_mcp/cli/commands/__init__.py +2 -1
- package/src/qingflow_mcp/cli/commands/app.py +47 -1
- package/src/qingflow_mcp/cli/commands/builder.py +7 -0
- package/src/qingflow_mcp/cli/commands/exports.py +111 -0
- package/src/qingflow_mcp/cli/commands/record.py +20 -0
- package/src/qingflow_mcp/cli/commands/task.py +644 -22
- package/src/qingflow_mcp/cli/commands/workspace.py +49 -50
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +139 -4
- package/src/qingflow_mcp/cli/interaction.py +72 -0
- package/src/qingflow_mcp/cli/main.py +2 -0
- package/src/qingflow_mcp/cli/terminal_ui.py +55 -9
- package/src/qingflow_mcp/errors.py +2 -2
- package/src/qingflow_mcp/export_store.py +14 -0
- package/src/qingflow_mcp/public_surface.py +6 -0
- package/src/qingflow_mcp/response_trim.py +40 -1
- package/src/qingflow_mcp/server.py +22 -0
- package/src/qingflow_mcp/server_app_builder.py +4 -0
- package/src/qingflow_mcp/server_app_user.py +104 -8
- package/src/qingflow_mcp/session_store.py +57 -6
- package/src/qingflow_mcp/tools/ai_builder_tools.py +59 -16
- package/src/qingflow_mcp/tools/auth_tools.py +26 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +0 -2
- package/src/qingflow_mcp/tools/export_tools.py +1565 -0
- package/src/qingflow_mcp/tools/import_tools.py +42 -2
- package/src/qingflow_mcp/tools/record_tools.py +515 -45
- package/src/qingflow_mcp/tools/resource_read_tools.py +40 -1
- package/src/qingflow_mcp/tools/task_context_tools.py +26 -8
|
@@ -3,6 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
|
|
5
5
|
from ..context import CliContext
|
|
6
|
+
from ..interaction import cancelled_result, resolve_interactive_selection
|
|
7
|
+
from ..terminal_ui import SelectionOption
|
|
8
|
+
from .common import raise_config_error
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
@@ -19,7 +22,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
19
22
|
search.set_defaults(handler=_handle_search, format_hint="app_search")
|
|
20
23
|
|
|
21
24
|
get = app_subparsers.add_parser("get", help="读取应用可访问视图与导入能力")
|
|
22
|
-
get.add_argument("--app-key",
|
|
25
|
+
get.add_argument("--app-key", help="不传时在交互终端中选择应用")
|
|
23
26
|
get.set_defaults(handler=_handle_get, format_hint="app_get")
|
|
24
27
|
|
|
25
28
|
|
|
@@ -37,4 +40,47 @@ def _handle_search(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
43
|
+
if not (args.app_key or "").strip():
|
|
44
|
+
selection = _choose_app_interactively(args, context)
|
|
45
|
+
if selection.status == "unavailable":
|
|
46
|
+
raise_config_error(
|
|
47
|
+
"app get requires --app-key, or an interactive terminal to choose an app",
|
|
48
|
+
fix_hint="Run `app list` to inspect visible apps, or retry with `--app-key APP_KEY`.",
|
|
49
|
+
)
|
|
50
|
+
if selection.status == "empty":
|
|
51
|
+
raise_config_error(
|
|
52
|
+
selection.message or "app get could not open a selector because no visible apps were returned.",
|
|
53
|
+
fix_hint="Run `app list` to confirm visible apps, or retry with `--app-key APP_KEY`.",
|
|
54
|
+
)
|
|
55
|
+
if selection.status == "cancelled":
|
|
56
|
+
return cancelled_result(selection.message or "已取消")
|
|
57
|
+
args.app_key = str(selection.value or "")
|
|
40
58
|
return context.app.app_get(profile=args.profile, app_key=args.app_key)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _choose_app_interactively(args: argparse.Namespace, context: CliContext):
|
|
62
|
+
def load_options() -> list[SelectionOption[str]]:
|
|
63
|
+
result = context.app.app_list(profile=args.profile)
|
|
64
|
+
items = result.get("items") if isinstance(result, dict) and isinstance(result.get("items"), list) else []
|
|
65
|
+
options: list[SelectionOption[str]] = []
|
|
66
|
+
for item in items:
|
|
67
|
+
if not isinstance(item, dict):
|
|
68
|
+
continue
|
|
69
|
+
app_key = str(item.get("app_key") or "").strip()
|
|
70
|
+
if not app_key:
|
|
71
|
+
continue
|
|
72
|
+
app_name = str(item.get("app_name") or app_key).strip() or app_key
|
|
73
|
+
package_name = str(item.get("package_name") or "").strip()
|
|
74
|
+
hint = f"app_key={app_key}"
|
|
75
|
+
if package_name:
|
|
76
|
+
hint += f" · package={package_name}"
|
|
77
|
+
options.append(SelectionOption(value=app_key, label=app_name, hint=hint))
|
|
78
|
+
return options
|
|
79
|
+
|
|
80
|
+
return resolve_interactive_selection(
|
|
81
|
+
args,
|
|
82
|
+
title="选择应用",
|
|
83
|
+
unavailable_message="app get requires --app-key, or an interactive terminal to choose an app",
|
|
84
|
+
empty_message="app get could not open a selector because no visible apps were returned.",
|
|
85
|
+
load_options=load_options,
|
|
86
|
+
)
|
|
@@ -120,6 +120,9 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
120
120
|
button = builder_subparsers.add_parser("button", help="自定义按钮")
|
|
121
121
|
button_subparsers = button.add_subparsers(dest="builder_button_command", required=True)
|
|
122
122
|
|
|
123
|
+
button_catalog = button_subparsers.add_parser("catalog", help="读取按钮样式目录")
|
|
124
|
+
button_catalog.set_defaults(handler=_handle_button_catalog, format_hint="builder_summary")
|
|
125
|
+
|
|
123
126
|
button_list = button_subparsers.add_parser("list", help="列出自定义按钮")
|
|
124
127
|
button_list.add_argument("--app-key", required=True)
|
|
125
128
|
button_list.set_defaults(handler=_handle_button_list, format_hint="builder_summary")
|
|
@@ -362,6 +365,10 @@ def _handle_button_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
362
365
|
return context.builder.app_custom_button_list(profile=args.profile, app_key=args.app_key)
|
|
363
366
|
|
|
364
367
|
|
|
368
|
+
def _handle_button_catalog(args: argparse.Namespace, context: CliContext) -> dict:
|
|
369
|
+
return context.builder.button_style_catalog_get(profile=args.profile)
|
|
370
|
+
|
|
371
|
+
|
|
365
372
|
def _handle_button_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
366
373
|
return context.builder.app_custom_button_get(profile=args.profile, app_key=args.app_key, button_id=args.button_id)
|
|
367
374
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ..context import CliContext
|
|
6
|
+
from .common import load_list_arg
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
10
|
+
parser = subparsers.add_parser("export", help="导出")
|
|
11
|
+
export_subparsers = parser.add_subparsers(dest="export_command", required=True)
|
|
12
|
+
|
|
13
|
+
start = export_subparsers.add_parser("start", help="启动导出")
|
|
14
|
+
start.add_argument("--app-key", required=True)
|
|
15
|
+
start.add_argument("--view-id", default="system:all")
|
|
16
|
+
start.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
|
|
17
|
+
start.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
|
|
18
|
+
start.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
|
|
19
|
+
start.add_argument("--order-by-file", help="JSON/YAML list,内容与 record list 的 order_by DSL 一致;内部查询和导出记录顺序保持一致")
|
|
20
|
+
start.add_argument("--record-id", dest="record_ids", action="append", default=[], help="只导出这些 record_id;不传时导出当前视图全部数据")
|
|
21
|
+
start.add_argument("--record-ids-file", help="JSON/YAML list,内容与 --record-id 语义一致")
|
|
22
|
+
start.add_argument("--include-workflow-log", action=argparse.BooleanOptionalAction, default=False, help="是否同时导出流程日志")
|
|
23
|
+
start.set_defaults(handler=_handle_start, format_hint="export_start")
|
|
24
|
+
|
|
25
|
+
status = export_subparsers.add_parser("status", help="查询导出状态")
|
|
26
|
+
status.add_argument("--export-handle", required=True)
|
|
27
|
+
status.set_defaults(handler=_handle_status, format_hint="export_status")
|
|
28
|
+
|
|
29
|
+
get = export_subparsers.add_parser("get", help="获取导出结果")
|
|
30
|
+
get.add_argument("--export-handle", required=True)
|
|
31
|
+
get.add_argument("--download-to-path")
|
|
32
|
+
get.set_defaults(handler=_handle_get, format_hint="export_get")
|
|
33
|
+
|
|
34
|
+
direct = export_subparsers.add_parser("direct", help="直接导出并下载")
|
|
35
|
+
direct.add_argument("--app-key", required=True)
|
|
36
|
+
direct.add_argument("--view-id", default="system:all")
|
|
37
|
+
direct.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
|
|
38
|
+
direct.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
|
|
39
|
+
direct.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
|
|
40
|
+
direct.add_argument("--order-by-file", help="JSON/YAML list,内容与 record list 的 order_by DSL 一致;内部查询和导出记录顺序保持一致")
|
|
41
|
+
direct.add_argument("--record-id", dest="record_ids", action="append", default=[], help="只导出这些 record_id;不传时导出当前视图全部数据")
|
|
42
|
+
direct.add_argument("--record-ids-file", help="JSON/YAML list,内容与 --record-id 语义一致")
|
|
43
|
+
direct.add_argument("--include-workflow-log", action=argparse.BooleanOptionalAction, default=False, help="是否同时导出流程日志")
|
|
44
|
+
direct.add_argument("--download-to-path")
|
|
45
|
+
direct.add_argument("--wait-timeout-seconds", type=float)
|
|
46
|
+
direct.set_defaults(handler=_handle_direct, format_hint="export_direct")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _columns(args: argparse.Namespace) -> list[int | dict]:
|
|
50
|
+
columns: list[int | dict] = list(args.columns or [])
|
|
51
|
+
if args.columns_file:
|
|
52
|
+
columns.extend(load_list_arg(args.columns_file, option_name="--columns-file"))
|
|
53
|
+
return columns
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _record_ids(args: argparse.Namespace) -> list[str | int]:
|
|
57
|
+
record_ids: list[str | int] = list(args.record_ids or [])
|
|
58
|
+
if args.record_ids_file:
|
|
59
|
+
record_ids.extend(load_list_arg(args.record_ids_file, option_name="--record-ids-file"))
|
|
60
|
+
return record_ids
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _where(args: argparse.Namespace) -> list[dict]:
|
|
64
|
+
return load_list_arg(args.where_file, option_name="--where-file") if args.where_file else []
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _order_by(args: argparse.Namespace) -> list[dict]:
|
|
68
|
+
return load_list_arg(args.order_by_file, option_name="--order-by-file") if args.order_by_file else []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _handle_start(args: argparse.Namespace, context: CliContext) -> dict:
|
|
72
|
+
return context.exports.record_export_start(
|
|
73
|
+
profile=args.profile,
|
|
74
|
+
app_key=args.app_key,
|
|
75
|
+
view_id=args.view_id,
|
|
76
|
+
columns=_columns(args),
|
|
77
|
+
where=_where(args),
|
|
78
|
+
order_by=_order_by(args),
|
|
79
|
+
record_ids=_record_ids(args),
|
|
80
|
+
include_workflow_log=args.include_workflow_log,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _handle_status(args: argparse.Namespace, context: CliContext) -> dict:
|
|
85
|
+
return context.exports.record_export_status_get(
|
|
86
|
+
profile=args.profile,
|
|
87
|
+
export_handle=args.export_handle,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
92
|
+
return context.exports.record_export_get(
|
|
93
|
+
profile=args.profile,
|
|
94
|
+
export_handle=args.export_handle,
|
|
95
|
+
download_to_path=args.download_to_path,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _handle_direct(args: argparse.Namespace, context: CliContext) -> dict:
|
|
100
|
+
return context.exports.record_export_direct(
|
|
101
|
+
profile=args.profile,
|
|
102
|
+
app_key=args.app_key,
|
|
103
|
+
view_id=args.view_id,
|
|
104
|
+
columns=_columns(args),
|
|
105
|
+
where=_where(args),
|
|
106
|
+
order_by=_order_by(args),
|
|
107
|
+
record_ids=_record_ids(args),
|
|
108
|
+
include_workflow_log=args.include_workflow_log,
|
|
109
|
+
download_to_path=args.download_to_path,
|
|
110
|
+
wait_timeout_seconds=args.wait_timeout_seconds,
|
|
111
|
+
)
|
|
@@ -57,6 +57,15 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
57
57
|
list_parser.add_argument("--view-name", dest="legacy_view_name", help=argparse.SUPPRESS)
|
|
58
58
|
list_parser.set_defaults(handler=_handle_list, format_hint="record_list")
|
|
59
59
|
|
|
60
|
+
access_parser = record_subparsers.add_parser("access", help="访问记录并写入本地 CSV 分片")
|
|
61
|
+
access_parser.add_argument("--app-key", required=True)
|
|
62
|
+
access_parser.add_argument("--column", dest="columns", action="append", type=int, default=[])
|
|
63
|
+
access_parser.add_argument("--columns-file")
|
|
64
|
+
access_parser.add_argument("--where-file")
|
|
65
|
+
access_parser.add_argument("--order-by-file")
|
|
66
|
+
access_parser.add_argument("--view-id", required=True)
|
|
67
|
+
access_parser.set_defaults(handler=_handle_access, format_hint="record_access")
|
|
68
|
+
|
|
60
69
|
get = record_subparsers.add_parser("get", help="读取单条记录")
|
|
61
70
|
get.add_argument("--app-key", required=True)
|
|
62
71
|
get.add_argument("--record-id", required=True)
|
|
@@ -224,6 +233,17 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
224
233
|
)
|
|
225
234
|
|
|
226
235
|
|
|
236
|
+
def _handle_access(args: argparse.Namespace, context: CliContext) -> dict:
|
|
237
|
+
return context.record.record_access(
|
|
238
|
+
profile=args.profile,
|
|
239
|
+
app_key=args.app_key,
|
|
240
|
+
columns=_columns(args),
|
|
241
|
+
where=load_list_arg(args.where_file, option_name="--where-file"),
|
|
242
|
+
order_by=load_list_arg(args.order_by_file, option_name="--order-by-file"),
|
|
243
|
+
view_id=args.view_id,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
227
247
|
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
228
248
|
return context.record.record_get_public(
|
|
229
249
|
profile=args.profile,
|