@josephyan/qingflow-app-builder-mcp 0.2.0-beta.7 → 0.2.0-beta.71
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 +5 -3
- package/docs/local-agent-install.md +21 -5
- package/npm/bin/qingflow-app-builder-mcp.mjs +1 -1
- package/npm/lib/runtime.mjs +168 -12
- package/package.json +1 -1
- package/pyproject.toml +4 -1
- package/skills/qingflow-app-builder/SKILL.md +155 -22
- package/skills/qingflow-app-builder/references/create-app.md +51 -21
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
- package/skills/qingflow-app-builder/references/gotchas.md +28 -1
- package/skills/qingflow-app-builder/references/solution-playbooks.md +14 -12
- package/skills/qingflow-app-builder/references/tool-selection.md +47 -19
- package/skills/qingflow-app-builder/references/update-flow.md +112 -25
- package/skills/qingflow-app-builder/references/update-layout.md +11 -24
- package/skills/qingflow-app-builder/references/update-schema.md +1 -23
- package/skills/qingflow-app-builder/references/update-views.md +87 -21
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
- package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +210 -0
- package/src/qingflow_mcp/builder_facade/models.py +1252 -3
- package/src/qingflow_mcp/builder_facade/service.py +11367 -2389
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +78 -0
- package/src/qingflow_mcp/cli/commands/builder.py +515 -0
- package/src/qingflow_mcp/cli/commands/common.py +62 -0
- package/src/qingflow_mcp/cli/commands/imports.py +96 -0
- package/src/qingflow_mcp/cli/commands/record.py +304 -0
- package/src/qingflow_mcp/cli/commands/task.py +89 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
- package/src/qingflow_mcp/cli/context.py +48 -0
- package/src/qingflow_mcp/cli/formatters.py +355 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +149 -0
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/list_type_labels.py +24 -0
- package/src/qingflow_mcp/response_trim.py +668 -0
- package/src/qingflow_mcp/server.py +160 -18
- package/src/qingflow_mcp/server_app_builder.py +275 -68
- package/src/qingflow_mcp/server_app_user.py +219 -191
- package/src/qingflow_mcp/session_store.py +41 -1
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +43 -4
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
- package/src/qingflow_mcp/solution/executor.py +107 -11
- package/src/qingflow_mcp/solution/spec_models.py +2 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2032 -127
- package/src/qingflow_mcp/tools/app_tools.py +419 -12
- package/src/qingflow_mcp/tools/approval_tools.py +571 -72
- package/src/qingflow_mcp/tools/auth_tools.py +398 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +756 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +179 -0
- package/src/qingflow_mcp/tools/directory_tools.py +203 -31
- package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
- package/src/qingflow_mcp/tools/file_tools.py +1 -0
- package/src/qingflow_mcp/tools/import_tools.py +2150 -0
- package/src/qingflow_mcp/tools/package_tools.py +18 -4
- package/src/qingflow_mcp/tools/portal_tools.py +31 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +109 -7
- package/src/qingflow_mcp/tools/record_tools.py +9894 -1104
- package/src/qingflow_mcp/tools/solution_tools.py +115 -3
- package/src/qingflow_mcp/tools/task_context_tools.py +2040 -0
- package/src/qingflow_mcp/tools/task_tools.py +376 -225
- package/src/qingflow_mcp/tools/workspace_tools.py +163 -19
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ...errors import QingflowApiError
|
|
9
|
+
from ..json_io import load_json_list, load_json_object, load_optional_json_list, load_optional_json_object
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_bool_text(value: str) -> bool:
|
|
13
|
+
normalized = value.strip().lower()
|
|
14
|
+
if normalized in {"true", "1", "yes", "y", "on"}:
|
|
15
|
+
return True
|
|
16
|
+
if normalized in {"false", "0", "no", "n", "off"}:
|
|
17
|
+
return False
|
|
18
|
+
raise argparse.ArgumentTypeError("expected one of: true, false, 1, 0, yes, no")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_list_arg(path: str | None, *, option_name: str) -> list[Any]:
|
|
22
|
+
return load_optional_json_list(path, option_name=option_name)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_object_arg(path: str | None, *, option_name: str) -> dict[str, Any] | None:
|
|
26
|
+
return load_optional_json_object(path, option_name=option_name)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def require_list_arg(path: str | None, *, option_name: str) -> list[Any]:
|
|
30
|
+
if not path:
|
|
31
|
+
raise QingflowApiError.config_error(f"{option_name} is required")
|
|
32
|
+
return load_json_list(path, option_name=option_name)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def require_object_arg(path: str | None, *, option_name: str) -> dict[str, Any]:
|
|
36
|
+
if not path:
|
|
37
|
+
raise QingflowApiError.config_error(f"{option_name} is required")
|
|
38
|
+
return load_json_object(path, option_name=option_name)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def read_secret_arg(value: str | None, *, stdin_enabled: bool, label: str) -> str:
|
|
42
|
+
if stdin_enabled:
|
|
43
|
+
secret = sys.stdin.read().strip()
|
|
44
|
+
if secret:
|
|
45
|
+
return secret
|
|
46
|
+
if value:
|
|
47
|
+
return value
|
|
48
|
+
raise QingflowApiError.config_error(f"{label} is required")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def raise_config_error(message: str, *, fix_hint: str) -> None:
|
|
52
|
+
raise RuntimeError(
|
|
53
|
+
json.dumps(
|
|
54
|
+
{
|
|
55
|
+
"category": "config",
|
|
56
|
+
"message": message,
|
|
57
|
+
"error_code": "CONFIG_ERROR",
|
|
58
|
+
"details": {"fix_hint": fix_hint},
|
|
59
|
+
},
|
|
60
|
+
ensure_ascii=False,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ..context import CliContext
|
|
6
|
+
from .common import parse_bool_text, raise_config_error
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
10
|
+
parser = subparsers.add_parser("import", help="导入")
|
|
11
|
+
import_subparsers = parser.add_subparsers(dest="import_command", required=True)
|
|
12
|
+
|
|
13
|
+
template = import_subparsers.add_parser("template", help="下载导入模板")
|
|
14
|
+
template.add_argument("--app-key", required=True)
|
|
15
|
+
template.add_argument("--download-to-path")
|
|
16
|
+
template.set_defaults(handler=_handle_template, format_hint="")
|
|
17
|
+
|
|
18
|
+
verify = import_subparsers.add_parser("verify", help="校验导入文件")
|
|
19
|
+
verify.add_argument("--app-key", required=True)
|
|
20
|
+
verify.add_argument("--file-path", required=True)
|
|
21
|
+
verify.set_defaults(handler=_handle_verify, format_hint="import_verify")
|
|
22
|
+
|
|
23
|
+
repair = import_subparsers.add_parser("repair", help="授权后修复导入文件")
|
|
24
|
+
repair.add_argument("--verification-id", required=True)
|
|
25
|
+
repair.add_argument("--authorized-file-modification", action="store_true")
|
|
26
|
+
repair.add_argument("--output-path")
|
|
27
|
+
repair.add_argument("--repair", dest="selected_repairs", action="append", default=[])
|
|
28
|
+
repair.set_defaults(handler=_handle_repair, format_hint="")
|
|
29
|
+
|
|
30
|
+
start = import_subparsers.add_parser("start", help="启动导入")
|
|
31
|
+
start.add_argument("--app-key", required=True)
|
|
32
|
+
start.add_argument("--verification-id", required=True)
|
|
33
|
+
start.add_argument("--being-enter-auditing", type=parse_bool_text, required=True)
|
|
34
|
+
start.add_argument("--view-key")
|
|
35
|
+
start.set_defaults(handler=_handle_start, format_hint="")
|
|
36
|
+
|
|
37
|
+
status = import_subparsers.add_parser("status", help="查询导入状态")
|
|
38
|
+
status.add_argument("--app-key")
|
|
39
|
+
status.add_argument("--import-id")
|
|
40
|
+
status.add_argument("--process-id-str")
|
|
41
|
+
status.set_defaults(handler=_handle_status, format_hint="import_status")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _handle_template(args: argparse.Namespace, context: CliContext) -> dict:
|
|
45
|
+
return context.imports.record_import_template_get(
|
|
46
|
+
profile=args.profile,
|
|
47
|
+
app_key=args.app_key,
|
|
48
|
+
download_to_path=args.download_to_path,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _handle_verify(args: argparse.Namespace, context: CliContext) -> dict:
|
|
53
|
+
return context.imports.record_import_verify(
|
|
54
|
+
profile=args.profile,
|
|
55
|
+
app_key=args.app_key,
|
|
56
|
+
file_path=args.file_path,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _handle_repair(args: argparse.Namespace, context: CliContext) -> dict:
|
|
61
|
+
return context.imports.record_import_repair_local(
|
|
62
|
+
profile=args.profile,
|
|
63
|
+
verification_id=args.verification_id,
|
|
64
|
+
authorized_file_modification=bool(args.authorized_file_modification),
|
|
65
|
+
output_path=args.output_path,
|
|
66
|
+
selected_repairs=list(args.selected_repairs or []),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _handle_start(args: argparse.Namespace, context: CliContext) -> dict:
|
|
71
|
+
return context.imports.record_import_start(
|
|
72
|
+
profile=args.profile,
|
|
73
|
+
app_key=args.app_key,
|
|
74
|
+
verification_id=args.verification_id,
|
|
75
|
+
being_enter_auditing=bool(args.being_enter_auditing),
|
|
76
|
+
view_key=args.view_key,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _handle_status(args: argparse.Namespace, context: CliContext) -> dict:
|
|
81
|
+
selectors = [
|
|
82
|
+
bool((args.app_key or "").strip()),
|
|
83
|
+
bool((args.import_id or "").strip()),
|
|
84
|
+
bool((args.process_id_str or "").strip()),
|
|
85
|
+
]
|
|
86
|
+
if sum(selectors) != 1:
|
|
87
|
+
raise_config_error(
|
|
88
|
+
"import status accepts exactly one selector: --process-id-str, --import-id, or --app-key.",
|
|
89
|
+
fix_hint="Use `--process-id-str` or `--import-id` for a known import, or use only `--app-key` to read the latest import in that app.",
|
|
90
|
+
)
|
|
91
|
+
return context.imports.record_import_status_get(
|
|
92
|
+
profile=args.profile,
|
|
93
|
+
app_key=args.app_key,
|
|
94
|
+
import_id=args.import_id,
|
|
95
|
+
process_id_str=args.process_id_str,
|
|
96
|
+
)
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ...errors import QingflowApiError
|
|
7
|
+
from ..context import CliContext
|
|
8
|
+
from .common import load_list_arg, load_object_arg, raise_config_error, require_list_arg, require_object_arg
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
12
|
+
parser = subparsers.add_parser("record", help="记录与表结构")
|
|
13
|
+
record_subparsers = parser.add_subparsers(dest="record_command", required=True)
|
|
14
|
+
|
|
15
|
+
schema = record_subparsers.add_parser("schema", help="读取记录相关表结构")
|
|
16
|
+
schema.add_argument("--mode", dest="legacy_mode", help=argparse.SUPPRESS)
|
|
17
|
+
schema_subparsers = schema.add_subparsers(dest="record_schema_command")
|
|
18
|
+
schema.set_defaults(handler=_handle_schema_root, format_hint="")
|
|
19
|
+
|
|
20
|
+
schema_applicant = schema_subparsers.add_parser("applicant", help="读取申请节点表结构")
|
|
21
|
+
schema_applicant.add_argument("--app-key", required=True)
|
|
22
|
+
schema_applicant.set_defaults(handler=_handle_schema_applicant, format_hint="")
|
|
23
|
+
|
|
24
|
+
schema_browse = schema_subparsers.add_parser("browse", help="读取浏览视图表结构")
|
|
25
|
+
schema_browse.add_argument("--app-key", required=True)
|
|
26
|
+
schema_browse.add_argument("--view-id", required=True)
|
|
27
|
+
schema_browse.set_defaults(handler=_handle_schema_browse, format_hint="")
|
|
28
|
+
|
|
29
|
+
schema_insert = schema_subparsers.add_parser("insert", help="读取新增记录表结构")
|
|
30
|
+
schema_insert.add_argument("--app-key", required=True)
|
|
31
|
+
schema_insert.set_defaults(handler=_handle_schema_insert, format_hint="")
|
|
32
|
+
|
|
33
|
+
schema_update = schema_subparsers.add_parser("update", help="读取更新记录表结构")
|
|
34
|
+
schema_update.add_argument("--app-key", required=True)
|
|
35
|
+
schema_update.add_argument("--record-id", required=True, type=int)
|
|
36
|
+
schema_update.set_defaults(handler=_handle_schema_update, format_hint="")
|
|
37
|
+
|
|
38
|
+
schema_import = schema_subparsers.add_parser("import", help="读取导入表结构")
|
|
39
|
+
schema_import.add_argument("--app-key", required=True)
|
|
40
|
+
schema_import.set_defaults(handler=_handle_schema_import, format_hint="")
|
|
41
|
+
|
|
42
|
+
schema_code_block = schema_subparsers.add_parser("code-block", help="读取代码块执行表结构")
|
|
43
|
+
schema_code_block.add_argument("--app-key", required=True)
|
|
44
|
+
schema_code_block.set_defaults(handler=_handle_schema_code_block, format_hint="")
|
|
45
|
+
|
|
46
|
+
list_parser = record_subparsers.add_parser("list", help="列出记录")
|
|
47
|
+
list_parser.add_argument("--app-key", required=True)
|
|
48
|
+
list_parser.add_argument("--column", dest="columns", action="append", type=int, default=[])
|
|
49
|
+
list_parser.add_argument("--columns-file")
|
|
50
|
+
list_parser.add_argument("--where-file")
|
|
51
|
+
list_parser.add_argument("--order-by-file")
|
|
52
|
+
list_parser.add_argument("--limit", type=int, default=20)
|
|
53
|
+
list_parser.add_argument("--page", type=int, default=1)
|
|
54
|
+
list_parser.add_argument("--view-id")
|
|
55
|
+
list_parser.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
|
|
56
|
+
list_parser.add_argument("--view-key", dest="legacy_view_key", help=argparse.SUPPRESS)
|
|
57
|
+
list_parser.add_argument("--view-name", dest="legacy_view_name", help=argparse.SUPPRESS)
|
|
58
|
+
list_parser.set_defaults(handler=_handle_list, format_hint="record_list")
|
|
59
|
+
|
|
60
|
+
get = record_subparsers.add_parser("get", help="读取单条记录")
|
|
61
|
+
get.add_argument("--app-key", required=True)
|
|
62
|
+
get.add_argument("--record-id", required=True, type=int)
|
|
63
|
+
get.add_argument("--column", dest="columns", action="append", type=int, default=[])
|
|
64
|
+
get.add_argument("--columns-file")
|
|
65
|
+
get.add_argument("--view-id")
|
|
66
|
+
get.set_defaults(handler=_handle_get, format_hint="")
|
|
67
|
+
|
|
68
|
+
insert = record_subparsers.add_parser("insert", help="新增记录")
|
|
69
|
+
insert.add_argument("--app-key", required=True)
|
|
70
|
+
insert.add_argument("--fields-file", required=True)
|
|
71
|
+
insert.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
|
|
72
|
+
insert.set_defaults(handler=_handle_insert, format_hint="")
|
|
73
|
+
|
|
74
|
+
update = record_subparsers.add_parser("update", help="更新记录")
|
|
75
|
+
update.add_argument("--app-key", required=True)
|
|
76
|
+
update.add_argument("--record-id", required=True, type=int)
|
|
77
|
+
update.add_argument("--fields-file", required=True)
|
|
78
|
+
update.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
|
|
79
|
+
update.set_defaults(handler=_handle_update, format_hint="")
|
|
80
|
+
|
|
81
|
+
delete = record_subparsers.add_parser("delete", help="删除记录")
|
|
82
|
+
delete.add_argument("--app-key", required=True)
|
|
83
|
+
delete.add_argument("--record-id", type=int)
|
|
84
|
+
delete.add_argument("--record-ids-file")
|
|
85
|
+
delete.set_defaults(handler=_handle_delete, format_hint="")
|
|
86
|
+
|
|
87
|
+
analyze = record_subparsers.add_parser("analyze", help="分析记录数据")
|
|
88
|
+
analyze.add_argument("--app-key", required=True)
|
|
89
|
+
analyze.add_argument("--dimensions-file")
|
|
90
|
+
analyze.add_argument("--metrics-file")
|
|
91
|
+
analyze.add_argument("--filters-file")
|
|
92
|
+
analyze.add_argument("--sort-file")
|
|
93
|
+
analyze.add_argument("--limit", type=int, default=20)
|
|
94
|
+
analyze.add_argument("--strict-full", action=argparse.BooleanOptionalAction, default=False)
|
|
95
|
+
analyze.add_argument("--view-id")
|
|
96
|
+
analyze.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
|
|
97
|
+
analyze.add_argument("--view-key", dest="legacy_view_key", help=argparse.SUPPRESS)
|
|
98
|
+
analyze.add_argument("--view-name", dest="legacy_view_name", help=argparse.SUPPRESS)
|
|
99
|
+
analyze.set_defaults(handler=_handle_analyze, format_hint="")
|
|
100
|
+
|
|
101
|
+
code_block = record_subparsers.add_parser("code-block-run", help="执行代码块字段")
|
|
102
|
+
code_block.add_argument("--app-key", required=True)
|
|
103
|
+
code_block.add_argument("--record-id", required=True, type=int)
|
|
104
|
+
code_block.add_argument("--code-block-field", required=True)
|
|
105
|
+
code_block.add_argument("--role", type=int, default=1)
|
|
106
|
+
code_block.add_argument("--workflow-node-id", type=int)
|
|
107
|
+
code_block.add_argument("--answers-file")
|
|
108
|
+
code_block.add_argument("--fields-file")
|
|
109
|
+
code_block.add_argument("--manual", action=argparse.BooleanOptionalAction, default=True)
|
|
110
|
+
code_block.add_argument("--apply-writeback", action=argparse.BooleanOptionalAction, default=True)
|
|
111
|
+
code_block.add_argument("--verify-writeback", action=argparse.BooleanOptionalAction, default=True)
|
|
112
|
+
code_block.add_argument("--force-refresh-form", action="store_true")
|
|
113
|
+
code_block.set_defaults(handler=_handle_code_block_run, format_hint="")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _columns(args: argparse.Namespace) -> list[Any]:
|
|
117
|
+
columns: list[Any] = list(args.columns or [])
|
|
118
|
+
if args.columns_file:
|
|
119
|
+
columns.extend(require_list_arg(args.columns_file, option_name="--columns-file"))
|
|
120
|
+
return columns
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _handle_schema_root(args: argparse.Namespace, _context: CliContext) -> dict:
|
|
124
|
+
mode = (args.legacy_mode or "").strip()
|
|
125
|
+
if mode:
|
|
126
|
+
replacement = {
|
|
127
|
+
"applicant": "record schema applicant --app-key APP_KEY",
|
|
128
|
+
"browse": "record schema browse --app-key APP_KEY --view-id VIEW_ID",
|
|
129
|
+
"insert": "record schema insert --app-key APP_KEY",
|
|
130
|
+
"update": "record schema update --app-key APP_KEY --record-id RECORD_ID",
|
|
131
|
+
"import": "record schema import --app-key APP_KEY",
|
|
132
|
+
"code-block": "record schema code-block --app-key APP_KEY",
|
|
133
|
+
}.get(mode, "record schema <applicant|browse|insert|update|import|code-block> ...")
|
|
134
|
+
raise_config_error(
|
|
135
|
+
"record schema --mode is no longer accepted.",
|
|
136
|
+
fix_hint=f"Use `{replacement}` instead.",
|
|
137
|
+
)
|
|
138
|
+
raise_config_error(
|
|
139
|
+
"record schema requires an explicit subcommand.",
|
|
140
|
+
fix_hint="Use one of: `record schema applicant`, `record schema browse`, `record schema insert`, `record schema update`, `record schema import`, or `record schema code-block`.",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _handle_schema_applicant(args: argparse.Namespace, context: CliContext) -> dict:
|
|
145
|
+
return context.record.record_schema_get(
|
|
146
|
+
profile=args.profile,
|
|
147
|
+
app_key=args.app_key,
|
|
148
|
+
schema_mode="applicant",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _handle_schema_browse(args: argparse.Namespace, context: CliContext) -> dict:
|
|
153
|
+
return context.record.record_browse_schema_get_public(
|
|
154
|
+
profile=args.profile,
|
|
155
|
+
app_key=args.app_key,
|
|
156
|
+
view_id=args.view_id,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _handle_schema_insert(args: argparse.Namespace, context: CliContext) -> dict:
|
|
161
|
+
return context.record.record_insert_schema_get_public(profile=args.profile, app_key=args.app_key)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _handle_schema_update(args: argparse.Namespace, context: CliContext) -> dict:
|
|
165
|
+
return context.record.record_update_schema_get_public(
|
|
166
|
+
profile=args.profile,
|
|
167
|
+
app_key=args.app_key,
|
|
168
|
+
record_id=args.record_id,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _handle_schema_import(args: argparse.Namespace, context: CliContext) -> dict:
|
|
173
|
+
return context.imports.record_import_schema_get(profile=args.profile, app_key=args.app_key)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _handle_schema_code_block(args: argparse.Namespace, context: CliContext) -> dict:
|
|
177
|
+
return context.code_block.record_code_block_schema_get_public(profile=args.profile, app_key=args.app_key)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _validate_public_view_selector(
|
|
181
|
+
*,
|
|
182
|
+
view_id: str | None,
|
|
183
|
+
legacy_list_type: int | None,
|
|
184
|
+
legacy_view_key: str | None,
|
|
185
|
+
legacy_view_name: str | None,
|
|
186
|
+
tool_name: str,
|
|
187
|
+
) -> None:
|
|
188
|
+
if legacy_list_type is not None:
|
|
189
|
+
raise_config_error(
|
|
190
|
+
f"{tool_name} no longer accepts list_type.",
|
|
191
|
+
fix_hint="Call `app_get` first and use `accessible_views[].view_id`.",
|
|
192
|
+
)
|
|
193
|
+
if legacy_view_key or legacy_view_name:
|
|
194
|
+
raise_config_error(
|
|
195
|
+
f"{tool_name} no longer accepts view_key or view_name.",
|
|
196
|
+
fix_hint="Call `app_get` first and pass the exact `view_id` from `accessible_views`.",
|
|
197
|
+
)
|
|
198
|
+
if not (view_id or "").strip():
|
|
199
|
+
raise_config_error(
|
|
200
|
+
f"{tool_name} requires view_id.",
|
|
201
|
+
fix_hint="Call `app_get` first and choose one `accessible_views[].view_id`, then retry with `--view-id`.",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
206
|
+
_validate_public_view_selector(
|
|
207
|
+
view_id=args.view_id,
|
|
208
|
+
legacy_list_type=args.legacy_list_type,
|
|
209
|
+
legacy_view_key=args.legacy_view_key,
|
|
210
|
+
legacy_view_name=args.legacy_view_name,
|
|
211
|
+
tool_name="record_list",
|
|
212
|
+
)
|
|
213
|
+
return context.record.record_list(
|
|
214
|
+
profile=args.profile,
|
|
215
|
+
app_key=args.app_key,
|
|
216
|
+
columns=_columns(args),
|
|
217
|
+
where=load_list_arg(args.where_file, option_name="--where-file"),
|
|
218
|
+
order_by=load_list_arg(args.order_by_file, option_name="--order-by-file"),
|
|
219
|
+
limit=args.limit,
|
|
220
|
+
page=args.page,
|
|
221
|
+
view_id=args.view_id,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
226
|
+
return context.record.record_get_public(
|
|
227
|
+
profile=args.profile,
|
|
228
|
+
app_key=args.app_key,
|
|
229
|
+
record_id=args.record_id,
|
|
230
|
+
columns=_columns(args),
|
|
231
|
+
view_id=args.view_id,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _handle_insert(args: argparse.Namespace, context: CliContext) -> dict:
|
|
236
|
+
return context.record.record_insert_public(
|
|
237
|
+
profile=args.profile,
|
|
238
|
+
app_key=args.app_key,
|
|
239
|
+
fields=require_object_arg(args.fields_file, option_name="--fields-file"),
|
|
240
|
+
verify_write=bool(args.verify_write),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _handle_update(args: argparse.Namespace, context: CliContext) -> dict:
|
|
245
|
+
return context.record.record_update_public(
|
|
246
|
+
profile=args.profile,
|
|
247
|
+
app_key=args.app_key,
|
|
248
|
+
record_id=args.record_id,
|
|
249
|
+
fields=require_object_arg(args.fields_file, option_name="--fields-file"),
|
|
250
|
+
verify_write=bool(args.verify_write),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _handle_delete(args: argparse.Namespace, context: CliContext) -> dict:
|
|
255
|
+
record_ids = load_list_arg(args.record_ids_file, option_name="--record-ids-file")
|
|
256
|
+
return context.record.record_delete_public(
|
|
257
|
+
profile=args.profile,
|
|
258
|
+
app_key=args.app_key,
|
|
259
|
+
record_id=args.record_id,
|
|
260
|
+
record_ids=record_ids,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _handle_analyze(args: argparse.Namespace, context: CliContext) -> dict:
|
|
265
|
+
_validate_public_view_selector(
|
|
266
|
+
view_id=args.view_id,
|
|
267
|
+
legacy_list_type=args.legacy_list_type,
|
|
268
|
+
legacy_view_key=args.legacy_view_key,
|
|
269
|
+
legacy_view_name=args.legacy_view_name,
|
|
270
|
+
tool_name="record_analyze",
|
|
271
|
+
)
|
|
272
|
+
return context.record.record_analyze(
|
|
273
|
+
profile=args.profile,
|
|
274
|
+
app_key=args.app_key,
|
|
275
|
+
dimensions=load_list_arg(args.dimensions_file, option_name="--dimensions-file"),
|
|
276
|
+
metrics=load_list_arg(args.metrics_file, option_name="--metrics-file"),
|
|
277
|
+
filters=load_list_arg(args.filters_file, option_name="--filters-file"),
|
|
278
|
+
sort=load_list_arg(args.sort_file, option_name="--sort-file"),
|
|
279
|
+
limit=args.limit,
|
|
280
|
+
strict_full=bool(args.strict_full),
|
|
281
|
+
view_id=args.view_id,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _handle_code_block_run(args: argparse.Namespace, context: CliContext) -> dict:
|
|
286
|
+
if args.workflow_node_id is not None and args.role != 3:
|
|
287
|
+
raise_config_error(
|
|
288
|
+
"workflow_node_id is only accepted when role=3.",
|
|
289
|
+
fix_hint="Remove `--workflow-node-id`, or set `--role 3` when running in workflow context.",
|
|
290
|
+
)
|
|
291
|
+
return context.code_block.record_code_block_run(
|
|
292
|
+
profile=args.profile,
|
|
293
|
+
app_key=args.app_key,
|
|
294
|
+
record_id=args.record_id,
|
|
295
|
+
code_block_field=args.code_block_field,
|
|
296
|
+
role=args.role,
|
|
297
|
+
workflow_node_id=args.workflow_node_id,
|
|
298
|
+
answers=load_list_arg(args.answers_file, option_name="--answers-file"),
|
|
299
|
+
fields=load_object_arg(args.fields_file, option_name="--fields-file") or {},
|
|
300
|
+
manual=bool(args.manual),
|
|
301
|
+
apply_writeback=bool(args.apply_writeback),
|
|
302
|
+
verify_writeback=bool(args.verify_writeback),
|
|
303
|
+
force_refresh_form=bool(args.force_refresh_form),
|
|
304
|
+
)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ..context import CliContext
|
|
6
|
+
from .common import load_object_arg
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
10
|
+
parser = subparsers.add_parser("task", help="待办与流程上下文")
|
|
11
|
+
task_subparsers = parser.add_subparsers(dest="task_command", required=True)
|
|
12
|
+
|
|
13
|
+
list_parser = task_subparsers.add_parser("list", help="列出待办")
|
|
14
|
+
list_parser.add_argument("--task-box", default="todo")
|
|
15
|
+
list_parser.add_argument("--flow-status", default="all")
|
|
16
|
+
list_parser.add_argument("--app-key")
|
|
17
|
+
list_parser.add_argument("--workflow-node-id", type=int)
|
|
18
|
+
list_parser.add_argument("--query")
|
|
19
|
+
list_parser.add_argument("--page", type=int, default=1)
|
|
20
|
+
list_parser.add_argument("--page-size", type=int, default=20)
|
|
21
|
+
list_parser.set_defaults(handler=_handle_list, format_hint="task_list")
|
|
22
|
+
|
|
23
|
+
get = task_subparsers.add_parser("get", help="读取待办详情")
|
|
24
|
+
get.add_argument("--app-key", required=True)
|
|
25
|
+
get.add_argument("--record-id", required=True, type=int)
|
|
26
|
+
get.add_argument("--workflow-node-id", required=True, type=int)
|
|
27
|
+
get.add_argument("--include-candidates", action=argparse.BooleanOptionalAction, default=True)
|
|
28
|
+
get.add_argument("--include-associated-reports", action=argparse.BooleanOptionalAction, default=True)
|
|
29
|
+
get.set_defaults(handler=_handle_get, format_hint="task_get")
|
|
30
|
+
|
|
31
|
+
action = task_subparsers.add_parser("action", help="执行待办动作")
|
|
32
|
+
action.add_argument("--app-key", required=True)
|
|
33
|
+
action.add_argument("--record-id", required=True, type=int)
|
|
34
|
+
action.add_argument("--workflow-node-id", required=True, type=int)
|
|
35
|
+
action.add_argument("--action", required=True)
|
|
36
|
+
action.add_argument("--payload-file")
|
|
37
|
+
action.add_argument("--fields-file")
|
|
38
|
+
action.set_defaults(handler=_handle_action, format_hint="")
|
|
39
|
+
|
|
40
|
+
log = task_subparsers.add_parser("log", help="读取流程日志")
|
|
41
|
+
log.add_argument("--app-key", required=True)
|
|
42
|
+
log.add_argument("--record-id", required=True, type=int)
|
|
43
|
+
log.add_argument("--workflow-node-id", required=True, type=int)
|
|
44
|
+
log.set_defaults(handler=_handle_log, format_hint="")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
48
|
+
return context.task.task_list(
|
|
49
|
+
profile=args.profile,
|
|
50
|
+
task_box=args.task_box,
|
|
51
|
+
flow_status=args.flow_status,
|
|
52
|
+
app_key=args.app_key,
|
|
53
|
+
workflow_node_id=args.workflow_node_id,
|
|
54
|
+
query=args.query,
|
|
55
|
+
page=args.page,
|
|
56
|
+
page_size=args.page_size,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
61
|
+
return context.task.task_get(
|
|
62
|
+
profile=args.profile,
|
|
63
|
+
app_key=args.app_key,
|
|
64
|
+
record_id=args.record_id,
|
|
65
|
+
workflow_node_id=args.workflow_node_id,
|
|
66
|
+
include_candidates=bool(args.include_candidates),
|
|
67
|
+
include_associated_reports=bool(args.include_associated_reports),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _handle_action(args: argparse.Namespace, context: CliContext) -> dict:
|
|
72
|
+
return context.task.task_action_execute(
|
|
73
|
+
profile=args.profile,
|
|
74
|
+
app_key=args.app_key,
|
|
75
|
+
record_id=args.record_id,
|
|
76
|
+
workflow_node_id=args.workflow_node_id,
|
|
77
|
+
action=args.action,
|
|
78
|
+
payload=load_object_arg(args.payload_file, option_name="--payload-file") or {},
|
|
79
|
+
fields=load_object_arg(args.fields_file, option_name="--fields-file") or {},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _handle_log(args: argparse.Namespace, context: CliContext) -> dict:
|
|
84
|
+
return context.task.task_workflow_log_get(
|
|
85
|
+
profile=args.profile,
|
|
86
|
+
app_key=args.app_key,
|
|
87
|
+
record_id=args.record_id,
|
|
88
|
+
workflow_node_id=args.workflow_node_id,
|
|
89
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ..context import CliContext
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
9
|
+
parser = subparsers.add_parser("workspace", help="工作区")
|
|
10
|
+
workspace_subparsers = parser.add_subparsers(dest="workspace_command", required=True)
|
|
11
|
+
|
|
12
|
+
list_parser = workspace_subparsers.add_parser("list", help="列出工作区")
|
|
13
|
+
list_parser.add_argument("--page", type=int, default=1)
|
|
14
|
+
list_parser.add_argument("--page-size", type=int, default=20)
|
|
15
|
+
list_parser.add_argument("--include-external", action="store_true")
|
|
16
|
+
list_parser.set_defaults(handler=_handle_list, format_hint="workspace_list")
|
|
17
|
+
|
|
18
|
+
select = workspace_subparsers.add_parser("select", help="切换工作区")
|
|
19
|
+
select.add_argument("--ws-id", type=int, required=True)
|
|
20
|
+
select.set_defaults(handler=_handle_select, format_hint="workspace_select")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
24
|
+
return context.workspace.workspace_list(
|
|
25
|
+
profile=args.profile,
|
|
26
|
+
page_num=args.page,
|
|
27
|
+
page_size=args.page_size,
|
|
28
|
+
include_external=bool(args.include_external),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _handle_select(args: argparse.Namespace, context: CliContext) -> dict:
|
|
33
|
+
return context.workspace.workspace_select(profile=args.profile, ws_id=args.ws_id)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from ..backend_client import BackendClient
|
|
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
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(slots=True)
|
|
18
|
+
class CliContext:
|
|
19
|
+
sessions: SessionStore
|
|
20
|
+
backend: BackendClient
|
|
21
|
+
auth: AuthTools
|
|
22
|
+
workspace: WorkspaceTools
|
|
23
|
+
app: AppTools
|
|
24
|
+
record: RecordTools
|
|
25
|
+
code_block: CodeBlockTools
|
|
26
|
+
imports: ImportTools
|
|
27
|
+
task: TaskContextTools
|
|
28
|
+
builder: AiBuilderTools
|
|
29
|
+
|
|
30
|
+
def close(self) -> None:
|
|
31
|
+
self.backend.close()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_cli_context() -> CliContext:
|
|
35
|
+
sessions = SessionStore()
|
|
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
|
+
)
|