@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.
Files changed (70) hide show
  1. package/README.md +5 -3
  2. package/docs/local-agent-install.md +21 -5
  3. package/npm/bin/qingflow-app-builder-mcp.mjs +1 -1
  4. package/npm/lib/runtime.mjs +168 -12
  5. package/package.json +1 -1
  6. package/pyproject.toml +4 -1
  7. package/skills/qingflow-app-builder/SKILL.md +155 -22
  8. package/skills/qingflow-app-builder/references/create-app.md +51 -21
  9. package/skills/qingflow-app-builder/references/environments.md +1 -1
  10. package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
  11. package/skills/qingflow-app-builder/references/gotchas.md +28 -1
  12. package/skills/qingflow-app-builder/references/solution-playbooks.md +14 -12
  13. package/skills/qingflow-app-builder/references/tool-selection.md +47 -19
  14. package/skills/qingflow-app-builder/references/update-flow.md +112 -25
  15. package/skills/qingflow-app-builder/references/update-layout.md +11 -24
  16. package/skills/qingflow-app-builder/references/update-schema.md +1 -23
  17. package/skills/qingflow-app-builder/references/update-views.md +87 -21
  18. package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
  19. package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
  20. package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
  21. package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
  22. package/src/qingflow_mcp/__init__.py +1 -1
  23. package/src/qingflow_mcp/backend_client.py +210 -0
  24. package/src/qingflow_mcp/builder_facade/models.py +1252 -3
  25. package/src/qingflow_mcp/builder_facade/service.py +11367 -2389
  26. package/src/qingflow_mcp/cli/__init__.py +1 -0
  27. package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
  28. package/src/qingflow_mcp/cli/commands/app.py +40 -0
  29. package/src/qingflow_mcp/cli/commands/auth.py +78 -0
  30. package/src/qingflow_mcp/cli/commands/builder.py +515 -0
  31. package/src/qingflow_mcp/cli/commands/common.py +62 -0
  32. package/src/qingflow_mcp/cli/commands/imports.py +96 -0
  33. package/src/qingflow_mcp/cli/commands/record.py +304 -0
  34. package/src/qingflow_mcp/cli/commands/task.py +89 -0
  35. package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
  36. package/src/qingflow_mcp/cli/context.py +48 -0
  37. package/src/qingflow_mcp/cli/formatters.py +355 -0
  38. package/src/qingflow_mcp/cli/json_io.py +50 -0
  39. package/src/qingflow_mcp/cli/main.py +149 -0
  40. package/src/qingflow_mcp/config.py +39 -0
  41. package/src/qingflow_mcp/import_store.py +121 -0
  42. package/src/qingflow_mcp/list_type_labels.py +24 -0
  43. package/src/qingflow_mcp/response_trim.py +668 -0
  44. package/src/qingflow_mcp/server.py +160 -18
  45. package/src/qingflow_mcp/server_app_builder.py +275 -68
  46. package/src/qingflow_mcp/server_app_user.py +219 -191
  47. package/src/qingflow_mcp/session_store.py +41 -1
  48. package/src/qingflow_mcp/solution/compiler/form_compiler.py +43 -4
  49. package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
  50. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
  51. package/src/qingflow_mcp/solution/executor.py +107 -11
  52. package/src/qingflow_mcp/solution/spec_models.py +2 -0
  53. package/src/qingflow_mcp/tools/ai_builder_tools.py +2032 -127
  54. package/src/qingflow_mcp/tools/app_tools.py +419 -12
  55. package/src/qingflow_mcp/tools/approval_tools.py +571 -72
  56. package/src/qingflow_mcp/tools/auth_tools.py +398 -2
  57. package/src/qingflow_mcp/tools/code_block_tools.py +756 -0
  58. package/src/qingflow_mcp/tools/custom_button_tools.py +179 -0
  59. package/src/qingflow_mcp/tools/directory_tools.py +203 -31
  60. package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
  61. package/src/qingflow_mcp/tools/file_tools.py +1 -0
  62. package/src/qingflow_mcp/tools/import_tools.py +2150 -0
  63. package/src/qingflow_mcp/tools/package_tools.py +18 -4
  64. package/src/qingflow_mcp/tools/portal_tools.py +31 -0
  65. package/src/qingflow_mcp/tools/qingbi_report_tools.py +109 -7
  66. package/src/qingflow_mcp/tools/record_tools.py +9894 -1104
  67. package/src/qingflow_mcp/tools/solution_tools.py +115 -3
  68. package/src/qingflow_mcp/tools/task_context_tools.py +2040 -0
  69. package/src/qingflow_mcp/tools/task_tools.py +376 -225
  70. 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
+ )