@josephyan/qingflow-cli 0.2.0-beta.55 → 0.2.0-beta.57
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/cli/commands/app.py +16 -16
- package/src/qingflow_mcp/cli/commands/auth.py +16 -19
- package/src/qingflow_mcp/cli/commands/builder.py +162 -124
- package/src/qingflow_mcp/cli/commands/common.py +95 -21
- package/src/qingflow_mcp/cli/commands/imports.py +34 -42
- package/src/qingflow_mcp/cli/commands/record.py +133 -131
- package/src/qingflow_mcp/cli/commands/task.py +44 -43
- package/src/qingflow_mcp/cli/commands/workspace.py +10 -10
- package/src/qingflow_mcp/cli/context.py +32 -35
- package/src/qingflow_mcp/cli/formatters.py +121 -124
- package/src/qingflow_mcp/cli/main.py +17 -52
- package/src/qingflow_mcp/ops/__init__.py +3 -0
- package/src/qingflow_mcp/ops/apps.py +64 -0
- package/src/qingflow_mcp/ops/auth.py +121 -0
- package/src/qingflow_mcp/ops/base.py +290 -0
- package/src/qingflow_mcp/ops/builder.py +323 -0
- package/src/qingflow_mcp/ops/context.py +120 -0
- package/src/qingflow_mcp/ops/directory.py +171 -0
- package/src/qingflow_mcp/ops/feedback.py +49 -0
- package/src/qingflow_mcp/ops/files.py +78 -0
- package/src/qingflow_mcp/ops/imports.py +140 -0
- package/src/qingflow_mcp/ops/records.py +415 -0
- package/src/qingflow_mcp/ops/tasks.py +171 -0
- package/src/qingflow_mcp/ops/workspace.py +76 -0
- package/src/qingflow_mcp/server_app_builder.py +190 -122
- package/src/qingflow_mcp/server_app_user.py +662 -63
|
@@ -3,52 +3,53 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
|
|
5
5
|
from ..context import CliContext
|
|
6
|
-
from .common import
|
|
6
|
+
from .common import add_file_arg, add_stdin_json_flag, load_object_input
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
10
|
-
parser = subparsers.add_parser("
|
|
11
|
-
task_subparsers = parser.add_subparsers(dest="
|
|
10
|
+
parser = subparsers.add_parser("tasks", help="待办与流程上下文")
|
|
11
|
+
task_subparsers = parser.add_subparsers(dest="tasks_command", required=True)
|
|
12
12
|
|
|
13
13
|
list_parser = task_subparsers.add_parser("list", help="列出待办")
|
|
14
14
|
list_parser.add_argument("--task-box", default="todo")
|
|
15
15
|
list_parser.add_argument("--flow-status", default="all")
|
|
16
|
-
list_parser.add_argument("--app
|
|
17
|
-
list_parser.add_argument("--
|
|
16
|
+
list_parser.add_argument("--app")
|
|
17
|
+
list_parser.add_argument("--node", dest="workflow_node_id", type=int)
|
|
18
18
|
list_parser.add_argument("--query")
|
|
19
19
|
list_parser.add_argument("--page", type=int, default=1)
|
|
20
20
|
list_parser.add_argument("--page-size", type=int, default=20)
|
|
21
|
-
list_parser.set_defaults(handler=_handle_list, format_hint="
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
21
|
+
list_parser.set_defaults(handler=_handle_list, format_hint="tasks_list")
|
|
22
|
+
|
|
23
|
+
show = task_subparsers.add_parser("show", help="读取待办详情")
|
|
24
|
+
show.add_argument("--app", required=True)
|
|
25
|
+
show.add_argument("--record", required=True, type=int)
|
|
26
|
+
show.add_argument("--node", required=True, type=int)
|
|
27
|
+
show.add_argument("--include-candidates", action=argparse.BooleanOptionalAction, default=True)
|
|
28
|
+
show.add_argument("--include-associated-reports", action=argparse.BooleanOptionalAction, default=True)
|
|
29
|
+
show.set_defaults(handler=_handle_show, format_hint="task_show")
|
|
30
|
+
|
|
31
|
+
act = task_subparsers.add_parser("act", help="执行待办动作")
|
|
32
|
+
act.add_argument("--app", required=True)
|
|
33
|
+
act.add_argument("--record", required=True, type=int)
|
|
34
|
+
act.add_argument("--node", required=True, type=int)
|
|
35
|
+
act.add_argument("--action", required=True)
|
|
36
|
+
add_file_arg(act)
|
|
37
|
+
add_stdin_json_flag(act)
|
|
38
|
+
act.set_defaults(handler=_handle_act, format_hint="task_action")
|
|
38
39
|
|
|
39
40
|
log = task_subparsers.add_parser("log", help="读取流程日志")
|
|
40
|
-
log.add_argument("--app
|
|
41
|
-
log.add_argument("--record
|
|
42
|
-
log.add_argument("--
|
|
43
|
-
log.set_defaults(handler=_handle_log, format_hint="")
|
|
41
|
+
log.add_argument("--app", required=True)
|
|
42
|
+
log.add_argument("--record", required=True, type=int)
|
|
43
|
+
log.add_argument("--node", required=True, type=int)
|
|
44
|
+
log.set_defaults(handler=_handle_log, format_hint="task_log")
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
47
|
-
return context.
|
|
48
|
+
return context.tasks.list(
|
|
48
49
|
profile=args.profile,
|
|
49
50
|
task_box=args.task_box,
|
|
50
51
|
flow_status=args.flow_status,
|
|
51
|
-
app_key=args.
|
|
52
|
+
app_key=args.app,
|
|
52
53
|
workflow_node_id=args.workflow_node_id,
|
|
53
54
|
query=args.query,
|
|
54
55
|
page=args.page,
|
|
@@ -56,32 +57,32 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
56
57
|
)
|
|
57
58
|
|
|
58
59
|
|
|
59
|
-
def
|
|
60
|
-
return context.
|
|
60
|
+
def _handle_show(args: argparse.Namespace, context: CliContext) -> dict:
|
|
61
|
+
return context.tasks.show(
|
|
61
62
|
profile=args.profile,
|
|
62
|
-
app_key=args.
|
|
63
|
-
record_id=args.
|
|
64
|
-
workflow_node_id=args.
|
|
63
|
+
app_key=args.app,
|
|
64
|
+
record_id=args.record,
|
|
65
|
+
workflow_node_id=args.node,
|
|
65
66
|
include_candidates=bool(args.include_candidates),
|
|
66
67
|
include_associated_reports=bool(args.include_associated_reports),
|
|
67
68
|
)
|
|
68
69
|
|
|
69
70
|
|
|
70
|
-
def
|
|
71
|
-
return context.
|
|
71
|
+
def _handle_act(args: argparse.Namespace, context: CliContext) -> dict:
|
|
72
|
+
return context.tasks.act(
|
|
72
73
|
profile=args.profile,
|
|
73
|
-
app_key=args.
|
|
74
|
-
record_id=args.
|
|
75
|
-
workflow_node_id=args.
|
|
74
|
+
app_key=args.app,
|
|
75
|
+
record_id=args.record,
|
|
76
|
+
workflow_node_id=args.node,
|
|
76
77
|
action=args.action,
|
|
77
|
-
payload=
|
|
78
|
+
payload=load_object_input(args, required=False),
|
|
78
79
|
)
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
def _handle_log(args: argparse.Namespace, context: CliContext) -> dict:
|
|
82
|
-
return context.
|
|
83
|
+
return context.tasks.log(
|
|
83
84
|
profile=args.profile,
|
|
84
|
-
app_key=args.
|
|
85
|
-
record_id=args.
|
|
86
|
-
workflow_node_id=args.
|
|
85
|
+
app_key=args.app,
|
|
86
|
+
record_id=args.record,
|
|
87
|
+
workflow_node_id=args.node,
|
|
87
88
|
)
|
|
@@ -6,22 +6,22 @@ from ..context import CliContext
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
9
|
-
parser = subparsers.add_parser("
|
|
10
|
-
|
|
9
|
+
parser = subparsers.add_parser("ws", help="工作区操作")
|
|
10
|
+
ws_subparsers = parser.add_subparsers(dest="ws_command", required=True)
|
|
11
11
|
|
|
12
|
-
list_parser =
|
|
12
|
+
list_parser = ws_subparsers.add_parser("list", help="列出工作区")
|
|
13
13
|
list_parser.add_argument("--page", type=int, default=1)
|
|
14
14
|
list_parser.add_argument("--page-size", type=int, default=20)
|
|
15
15
|
list_parser.add_argument("--include-external", action="store_true")
|
|
16
|
-
list_parser.set_defaults(handler=_handle_list, format_hint="
|
|
16
|
+
list_parser.set_defaults(handler=_handle_list, format_hint="ws_list")
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
use = ws_subparsers.add_parser("use", help="切换工作区")
|
|
19
|
+
use.add_argument("--ws-id", type=int, required=True)
|
|
20
|
+
use.set_defaults(handler=_handle_use, format_hint="ws_use")
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
24
|
-
return context.workspace.
|
|
24
|
+
return context.workspace.list(
|
|
25
25
|
profile=args.profile,
|
|
26
26
|
page_num=args.page,
|
|
27
27
|
page_size=args.page_size,
|
|
@@ -29,5 +29,5 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def
|
|
33
|
-
return context.workspace.
|
|
32
|
+
def _handle_use(args: argparse.Namespace, context: CliContext) -> dict:
|
|
33
|
+
return context.workspace.use(profile=args.profile, ws_id=args.ws_id)
|
|
@@ -2,47 +2,44 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
|
|
5
|
-
from ..
|
|
6
|
-
from ..session_store import SessionStore
|
|
7
|
-
from ..tools.ai_builder_tools import AiBuilderTools
|
|
8
|
-
from ..tools.app_tools import AppTools
|
|
9
|
-
from ..tools.auth_tools import AuthTools
|
|
10
|
-
from ..tools.code_block_tools import CodeBlockTools
|
|
11
|
-
from ..tools.import_tools import ImportTools
|
|
12
|
-
from ..tools.record_tools import RecordTools
|
|
13
|
-
from ..tools.task_context_tools import TaskContextTools
|
|
14
|
-
from ..tools.workspace_tools import WorkspaceTools
|
|
5
|
+
from ..ops.context import OperationsRuntime, build_operations_runtime
|
|
15
6
|
|
|
16
7
|
|
|
17
8
|
@dataclass(slots=True)
|
|
18
9
|
class CliContext:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
10
|
+
runtime: OperationsRuntime
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def auth(self):
|
|
14
|
+
return self.runtime.auth
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def workspace(self):
|
|
18
|
+
return self.runtime.workspace
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def apps(self):
|
|
22
|
+
return self.runtime.apps
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def records(self):
|
|
26
|
+
return self.runtime.records
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def imports(self):
|
|
30
|
+
return self.runtime.imports
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def tasks(self):
|
|
34
|
+
return self.runtime.tasks
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def build(self):
|
|
38
|
+
return self.runtime.builder
|
|
29
39
|
|
|
30
40
|
def close(self) -> None:
|
|
31
|
-
self.
|
|
41
|
+
self.runtime.close()
|
|
32
42
|
|
|
33
43
|
|
|
34
44
|
def build_cli_context() -> CliContext:
|
|
35
|
-
|
|
36
|
-
backend = BackendClient()
|
|
37
|
-
return CliContext(
|
|
38
|
-
sessions=sessions,
|
|
39
|
-
backend=backend,
|
|
40
|
-
auth=AuthTools(sessions, backend),
|
|
41
|
-
workspace=WorkspaceTools(sessions, backend),
|
|
42
|
-
app=AppTools(sessions, backend),
|
|
43
|
-
record=RecordTools(sessions, backend),
|
|
44
|
-
code_block=CodeBlockTools(sessions, backend),
|
|
45
|
-
imports=ImportTools(sessions, backend),
|
|
46
|
-
task=TaskContextTools(sessions, backend),
|
|
47
|
-
builder=AiBuilderTools(sessions, backend),
|
|
48
|
-
)
|
|
45
|
+
return CliContext(runtime=build_operations_runtime())
|
|
@@ -3,8 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
from typing import Any, TextIO
|
|
5
5
|
|
|
6
|
+
from ..ops.base import public_result
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
def emit_text_result(result: dict[str, Any], *, hint: str, stream: TextIO) -> None:
|
|
10
|
+
result = public_result(result)
|
|
8
11
|
formatter = _FORMATTERS.get(hint, _format_generic)
|
|
9
12
|
text = formatter(result)
|
|
10
13
|
stream.write(text)
|
|
@@ -12,61 +15,65 @@ def emit_text_result(result: dict[str, Any], *, hint: str, stream: TextIO) -> No
|
|
|
12
15
|
stream.write("\n")
|
|
13
16
|
|
|
14
17
|
|
|
18
|
+
def emit_json_result(result: dict[str, Any], *, stream: TextIO) -> None:
|
|
19
|
+
json.dump(public_result(result), stream, ensure_ascii=False, indent=2)
|
|
20
|
+
stream.write("\n")
|
|
21
|
+
|
|
22
|
+
|
|
15
23
|
def _format_generic(result: dict[str, Any]) -> str:
|
|
16
|
-
lines
|
|
17
|
-
title = _first_present(result, "status", "message")
|
|
18
|
-
if title:
|
|
19
|
-
lines.append(str(title))
|
|
24
|
+
lines = [f"{result.get('message') or result.get('code') or '完成'}"]
|
|
20
25
|
data = result.get("data")
|
|
21
26
|
if isinstance(data, dict):
|
|
22
|
-
|
|
23
|
-
if scalar_lines:
|
|
24
|
-
lines.extend(scalar_lines)
|
|
25
|
-
elif result:
|
|
26
|
-
scalar_lines = _dict_scalar_lines(result)
|
|
27
|
-
if scalar_lines:
|
|
28
|
-
lines.extend(scalar_lines)
|
|
29
|
-
if not lines:
|
|
30
|
-
lines.append(json.dumps(result, ensure_ascii=False, indent=2))
|
|
27
|
+
lines.extend(_scalar_lines(data, limit=10))
|
|
31
28
|
_append_warnings(lines, result.get("warnings"))
|
|
32
|
-
_append_verification(lines, result.get("verification"))
|
|
33
29
|
return "\n".join(lines) + "\n"
|
|
34
30
|
|
|
35
31
|
|
|
36
|
-
def
|
|
32
|
+
def _format_me(result: dict[str, Any]) -> str:
|
|
33
|
+
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
34
|
+
user = data.get("user") if isinstance(data.get("user"), dict) else {}
|
|
35
|
+
workspace = data.get("workspace") if isinstance(data.get("workspace"), dict) else {}
|
|
37
36
|
lines = [
|
|
38
|
-
f"Profile: {result.get('profile')}",
|
|
39
|
-
f"User: {
|
|
40
|
-
f"UID: {
|
|
41
|
-
f"Workspace: {
|
|
42
|
-
f"Base URL: {
|
|
37
|
+
f"Profile: {data.get('profile') or result.get('meta', {}).get('profile') or '-'}",
|
|
38
|
+
f"User: {user.get('nick_name') or '-'} ({user.get('email') or '-'})",
|
|
39
|
+
f"UID: {user.get('uid') or '-'}",
|
|
40
|
+
f"Workspace: {workspace.get('name') or '-'} ({workspace.get('ws_id') or '-'})",
|
|
41
|
+
f"Base URL: {data.get('base_url') or '-'}",
|
|
43
42
|
]
|
|
44
43
|
return "\n".join(lines) + "\n"
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
def _format_workspace_list(result: dict[str, Any]) -> str:
|
|
48
|
-
|
|
49
|
-
items =
|
|
50
|
-
rows = [
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
]
|
|
60
|
-
)
|
|
47
|
+
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
48
|
+
items = data.get("items") if isinstance(data.get("items"), list) else []
|
|
49
|
+
rows = [
|
|
50
|
+
[
|
|
51
|
+
str(item.get("ws_id") or ""),
|
|
52
|
+
str(item.get("name") or ""),
|
|
53
|
+
str(item.get("remark") or ""),
|
|
54
|
+
]
|
|
55
|
+
for item in items
|
|
56
|
+
if isinstance(item, dict)
|
|
57
|
+
]
|
|
61
58
|
return _render_titled_table("Workspaces", ["ws_id", "name", "remark"], rows)
|
|
62
59
|
|
|
63
60
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
def _format_workspace_use(result: dict[str, Any]) -> str:
|
|
62
|
+
workspace = result.get("data", {}).get("workspace") if isinstance(result.get("data"), dict) else {}
|
|
63
|
+
lines = [
|
|
64
|
+
f"Workspace: {workspace.get('name') or '-'}",
|
|
65
|
+
f"WS ID: {workspace.get('ws_id') or '-'}",
|
|
66
|
+
f"QF Version: {result.get('data', {}).get('qf_version') or '-'}",
|
|
67
|
+
f"QF Version Source: {result.get('data', {}).get('qf_version_source') or '-'}",
|
|
68
|
+
]
|
|
69
|
+
return "\n".join(lines) + "\n"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _format_apps_list(result: dict[str, Any]) -> str:
|
|
73
|
+
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
74
|
+
items = data.get("items") if isinstance(data.get("items"), list) else []
|
|
68
75
|
rows = []
|
|
69
|
-
for item in items
|
|
76
|
+
for item in items:
|
|
70
77
|
if not isinstance(item, dict):
|
|
71
78
|
continue
|
|
72
79
|
rows.append(
|
|
@@ -79,7 +86,7 @@ def _format_app_items(result: dict[str, Any]) -> str:
|
|
|
79
86
|
return _render_titled_table("Apps", ["app_key", "app_name", "package"], rows)
|
|
80
87
|
|
|
81
88
|
|
|
82
|
-
def
|
|
89
|
+
def _format_app_show(result: dict[str, Any]) -> str:
|
|
83
90
|
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
84
91
|
lines = [
|
|
85
92
|
f"App: {data.get('app_name') or '-'}",
|
|
@@ -90,8 +97,7 @@ def _format_app_get(result: dict[str, Any]) -> str:
|
|
|
90
97
|
if isinstance(import_capability, dict):
|
|
91
98
|
lines.append(
|
|
92
99
|
"Import Capability: "
|
|
93
|
-
f"{import_capability.get('auth_source') or 'unknown'} / "
|
|
94
|
-
f"can_import={import_capability.get('can_import')}"
|
|
100
|
+
f"{import_capability.get('auth_source') or 'unknown'} / can_import={import_capability.get('can_import')}"
|
|
95
101
|
)
|
|
96
102
|
views = data.get("accessible_views") if isinstance(data.get("accessible_views"), list) else []
|
|
97
103
|
lines.append(f"Accessible Views: {len(views)}")
|
|
@@ -102,7 +108,7 @@ def _format_app_get(result: dict[str, Any]) -> str:
|
|
|
102
108
|
return "\n".join(lines) + "\n"
|
|
103
109
|
|
|
104
110
|
|
|
105
|
-
def
|
|
111
|
+
def _format_records_list(result: dict[str, Any]) -> str:
|
|
106
112
|
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
107
113
|
items = data.get("items") if isinstance(data.get("items"), list) else []
|
|
108
114
|
lines = [f"Returned Records: {len(items)}"]
|
|
@@ -112,40 +118,43 @@ def _format_record_list(result: dict[str, Any]) -> str:
|
|
|
112
118
|
if len(items) > 10:
|
|
113
119
|
lines.append(f"... {len(items) - 10} more")
|
|
114
120
|
_append_warnings(lines, result.get("warnings"))
|
|
115
|
-
_append_verification(lines, result.get("verification"))
|
|
116
121
|
return "\n".join(lines) + "\n"
|
|
117
122
|
|
|
118
123
|
|
|
119
|
-
def
|
|
124
|
+
def _format_record_show(result: dict[str, Any]) -> str:
|
|
120
125
|
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
record = data.get("record")
|
|
127
|
+
if isinstance(record, dict):
|
|
128
|
+
lines = [f"Record ID: {data.get('record_id') or '-'}"]
|
|
129
|
+
lines.extend(_scalar_lines(record, limit=12))
|
|
130
|
+
_append_warnings(lines, result.get("warnings"))
|
|
131
|
+
return "\n".join(lines) + "\n"
|
|
132
|
+
return _format_generic(result)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _format_record_write(result: dict[str, Any]) -> str:
|
|
136
|
+
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
137
|
+
lines = [
|
|
138
|
+
f"Code: {result.get('code')}",
|
|
139
|
+
f"Message: {result.get('message')}",
|
|
140
|
+
]
|
|
141
|
+
if data.get("record_id") is not None:
|
|
142
|
+
lines.append(f"Record ID: {data.get('record_id')}")
|
|
143
|
+
if isinstance(data.get("record_ids"), list):
|
|
144
|
+
lines.append(f"Record IDs: {', '.join(str(item) for item in data['record_ids'])}")
|
|
136
145
|
_append_warnings(lines, result.get("warnings"))
|
|
137
146
|
return "\n".join(lines) + "\n"
|
|
138
147
|
|
|
139
148
|
|
|
140
149
|
def _format_import_verify(result: dict[str, Any]) -> str:
|
|
150
|
+
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
141
151
|
lines = [
|
|
142
|
-
f"
|
|
143
|
-
f"
|
|
144
|
-
f"
|
|
145
|
-
f"
|
|
146
|
-
f"Verification ID: {result.get('verification_id') or '-'}",
|
|
152
|
+
f"File: {data.get('file_name') or data.get('file_path') or '-'}",
|
|
153
|
+
f"Can Import: {data.get('can_import')}",
|
|
154
|
+
f"Apply Rows: {data.get('apply_rows')}",
|
|
155
|
+
f"Verification ID: {data.get('verification_id') or '-'}",
|
|
147
156
|
]
|
|
148
|
-
issues =
|
|
157
|
+
issues = data.get("issues") if isinstance(data.get("issues"), list) else []
|
|
149
158
|
if issues:
|
|
150
159
|
lines.append("Issues:")
|
|
151
160
|
for issue in issues:
|
|
@@ -154,48 +163,51 @@ def _format_import_verify(result: dict[str, Any]) -> str:
|
|
|
154
163
|
else:
|
|
155
164
|
lines.append(f"- {issue}")
|
|
156
165
|
_append_warnings(lines, result.get("warnings"))
|
|
157
|
-
_append_verification(lines, result.get("verification"))
|
|
158
166
|
return "\n".join(lines) + "\n"
|
|
159
167
|
|
|
160
168
|
|
|
161
169
|
def _format_import_status(result: dict[str, Any]) -> str:
|
|
170
|
+
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
162
171
|
lines = [
|
|
163
|
-
f"Status: {
|
|
164
|
-
f"Import ID: {
|
|
165
|
-
f"Process ID: {
|
|
166
|
-
f"Success Rows: {
|
|
167
|
-
f"Failed Rows: {
|
|
168
|
-
f"Progress: {
|
|
172
|
+
f"Status: {data.get('status') or '-'}",
|
|
173
|
+
f"Import ID: {data.get('import_id') or '-'}",
|
|
174
|
+
f"Process ID: {data.get('process_id_str') or '-'}",
|
|
175
|
+
f"Success Rows: {data.get('success_rows') or 0}",
|
|
176
|
+
f"Failed Rows: {data.get('failed_rows') or 0}",
|
|
177
|
+
f"Progress: {data.get('progress') or '-'}",
|
|
169
178
|
]
|
|
170
179
|
_append_warnings(lines, result.get("warnings"))
|
|
171
|
-
_append_verification(lines, result.get("verification"))
|
|
172
180
|
return "\n".join(lines) + "\n"
|
|
173
181
|
|
|
174
182
|
|
|
175
|
-
def
|
|
176
|
-
|
|
177
|
-
if "
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
def _format_tasks_list(result: dict[str, Any]) -> str:
|
|
184
|
+
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
185
|
+
items = data.get("items") if isinstance(data.get("items"), list) else []
|
|
186
|
+
rows = []
|
|
187
|
+
for item in items:
|
|
188
|
+
if not isinstance(item, dict):
|
|
189
|
+
continue
|
|
190
|
+
rows.append(
|
|
191
|
+
[
|
|
192
|
+
str(item.get("app_key") or ""),
|
|
193
|
+
str(item.get("record_id") or ""),
|
|
194
|
+
str(item.get("workflow_node_id") or ""),
|
|
195
|
+
str(item.get("title") or ""),
|
|
196
|
+
]
|
|
197
|
+
)
|
|
198
|
+
output = _render_titled_table("Tasks", ["app_key", "record_id", "node_id", "title"], rows)
|
|
199
|
+
lines = output.rstrip("\n").split("\n")
|
|
189
200
|
_append_warnings(lines, result.get("warnings"))
|
|
190
|
-
_append_verification(lines, result.get("verification"))
|
|
191
|
-
if not lines:
|
|
192
|
-
return _format_generic(result)
|
|
193
201
|
return "\n".join(lines) + "\n"
|
|
194
202
|
|
|
195
203
|
|
|
196
|
-
def
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
def _format_builder_result(result: dict[str, Any]) -> str:
|
|
205
|
+
lines = [f"Code: {result.get('code')}", f"Message: {result.get('message')}"]
|
|
206
|
+
data = result.get("data")
|
|
207
|
+
if isinstance(data, dict):
|
|
208
|
+
lines.extend(_scalar_lines(data, limit=10))
|
|
209
|
+
_append_warnings(lines, result.get("warnings"))
|
|
210
|
+
return "\n".join(lines) + "\n"
|
|
199
211
|
|
|
200
212
|
|
|
201
213
|
def _render_titled_table(title: str, headers: list[str], rows: list[list[str]]) -> str:
|
|
@@ -207,19 +219,20 @@ def _render_titled_table(title: str, headers: list[str], rows: list[list[str]])
|
|
|
207
219
|
for row in rows:
|
|
208
220
|
for index, cell in enumerate(row):
|
|
209
221
|
widths[index] = max(widths[index], len(cell))
|
|
210
|
-
|
|
211
|
-
lines.append(header_line)
|
|
222
|
+
lines.append(" ".join(header.ljust(widths[index]) for index, header in enumerate(headers)))
|
|
212
223
|
lines.append(" ".join("-" * width for width in widths))
|
|
213
224
|
for row in rows:
|
|
214
225
|
lines.append(" ".join(cell.ljust(widths[index]) for index, cell in enumerate(row)))
|
|
215
226
|
return "\n".join(lines) + "\n"
|
|
216
227
|
|
|
217
228
|
|
|
218
|
-
def
|
|
229
|
+
def _scalar_lines(payload: dict[str, Any], *, limit: int) -> list[str]:
|
|
219
230
|
lines: list[str] = []
|
|
220
231
|
for key, value in payload.items():
|
|
221
232
|
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
222
233
|
lines.append(f"{key}: {value}")
|
|
234
|
+
if len(lines) >= limit:
|
|
235
|
+
break
|
|
223
236
|
return lines
|
|
224
237
|
|
|
225
238
|
|
|
@@ -231,39 +244,23 @@ def _append_warnings(lines: list[str], warnings: Any) -> None:
|
|
|
231
244
|
if isinstance(warning, dict):
|
|
232
245
|
code = warning.get("code")
|
|
233
246
|
message = warning.get("message")
|
|
234
|
-
|
|
235
|
-
lines.append(f"- {code or 'WARNING'}: {message or ''}".rstrip())
|
|
236
|
-
else:
|
|
237
|
-
lines.append(f"- {json.dumps(warning, ensure_ascii=False)}")
|
|
247
|
+
lines.append(f"- {code or 'WARNING'}: {message or ''}".rstrip())
|
|
238
248
|
else:
|
|
239
249
|
lines.append(f"- {warning}")
|
|
240
250
|
|
|
241
251
|
|
|
242
|
-
def _append_verification(lines: list[str], verification: Any) -> None:
|
|
243
|
-
if not isinstance(verification, dict) or not verification:
|
|
244
|
-
return
|
|
245
|
-
lines.append("Verification:")
|
|
246
|
-
for key, value in verification.items():
|
|
247
|
-
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
248
|
-
lines.append(f"- {key}: {value}")
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def _first_present(payload: dict[str, Any], *keys: str) -> Any:
|
|
252
|
-
for key in keys:
|
|
253
|
-
if key in payload and payload.get(key) is not None:
|
|
254
|
-
return payload.get(key)
|
|
255
|
-
return None
|
|
256
|
-
|
|
257
|
-
|
|
258
252
|
_FORMATTERS = {
|
|
259
|
-
"
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"
|
|
265
|
-
"
|
|
253
|
+
"me": _format_me,
|
|
254
|
+
"session": _format_me,
|
|
255
|
+
"ws_list": _format_workspace_list,
|
|
256
|
+
"ws_use": _format_workspace_use,
|
|
257
|
+
"apps_list": _format_apps_list,
|
|
258
|
+
"app_show": _format_app_show,
|
|
259
|
+
"records_list": _format_records_list,
|
|
260
|
+
"record_show": _format_record_show,
|
|
261
|
+
"record_write": _format_record_write,
|
|
266
262
|
"import_verify": _format_import_verify,
|
|
267
263
|
"import_status": _format_import_status,
|
|
268
|
-
"
|
|
264
|
+
"tasks_list": _format_tasks_list,
|
|
265
|
+
"builder_result": _format_builder_result,
|
|
269
266
|
}
|