@josephyan/qingflow-app-builder-mcp 1.1.25 → 1.1.26

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 (98) hide show
  1. package/README.md +5 -6
  2. package/docs/local-agent-install.md +6 -57
  3. package/entry_point.py +1 -1
  4. package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
  5. package/npm/lib/runtime.mjs +101 -21
  6. package/npm/scripts/postinstall.mjs +10 -1
  7. package/package.json +2 -3
  8. package/pyproject.toml +1 -1
  9. package/skills/qingflow-app-builder/SKILL.md +42 -104
  10. package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
  11. package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
  12. package/skills/qingflow-app-builder/references/create-app.md +11 -30
  13. package/skills/qingflow-app-builder/references/environments.md +1 -1
  14. package/skills/qingflow-app-builder/references/gotchas.md +9 -31
  15. package/skills/qingflow-app-builder/references/match-rules.md +0 -15
  16. package/skills/qingflow-app-builder/references/solution-playbooks.md +0 -10
  17. package/skills/qingflow-app-builder/references/tool-selection.md +23 -38
  18. package/skills/qingflow-app-builder/references/update-flow.md +38 -22
  19. package/skills/qingflow-app-builder/references/update-schema.md +0 -14
  20. package/skills/qingflow-app-builder/references/update-views.md +14 -83
  21. package/skills/qingflow-app-builder-code-integrations/SKILL.md +3 -5
  22. package/skills/qingflow-app-builder-code-integrations/references/code-block.md +1 -1
  23. package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +1 -1
  24. package/skills/qingflow-mcp-setup/SKILL.md +111 -0
  25. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  26. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  27. package/skills/qingflow-mcp-setup/references/environments.md +61 -0
  28. package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
  29. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  30. package/skills/qingflow-workflow-builder/SKILL.md +96 -0
  31. package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
  32. package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
  33. package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
  34. package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
  35. package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
  36. package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
  37. package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
  38. package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
  39. package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
  40. package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
  41. package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
  42. package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
  43. package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
  44. package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
  45. package/src/qingflow_mcp/__init__.py +1 -1
  46. package/src/qingflow_mcp/__main__.py +2 -6
  47. package/src/qingflow_mcp/builder_facade/models.py +108 -485
  48. package/src/qingflow_mcp/builder_facade/service.py +1686 -6674
  49. package/src/qingflow_mcp/cli/commands/builder.py +364 -322
  50. package/src/qingflow_mcp/cli/commands/chart.py +1 -1
  51. package/src/qingflow_mcp/cli/commands/common.py +3 -12
  52. package/src/qingflow_mcp/cli/commands/exports.py +2 -2
  53. package/src/qingflow_mcp/cli/commands/imports.py +3 -3
  54. package/src/qingflow_mcp/cli/commands/portal.py +2 -2
  55. package/src/qingflow_mcp/cli/commands/record.py +27 -101
  56. package/src/qingflow_mcp/cli/commands/task.py +47 -28
  57. package/src/qingflow_mcp/cli/commands/view.py +1 -1
  58. package/src/qingflow_mcp/cli/context.py +3 -0
  59. package/src/qingflow_mcp/cli/formatters.py +16 -784
  60. package/src/qingflow_mcp/cli/main.py +33 -117
  61. package/src/qingflow_mcp/errors.py +2 -43
  62. package/src/qingflow_mcp/public_surface.py +17 -26
  63. package/src/qingflow_mcp/response_trim.py +17 -81
  64. package/src/qingflow_mcp/server.py +12 -14
  65. package/src/qingflow_mcp/server_app_builder.py +36 -79
  66. package/src/qingflow_mcp/server_app_user.py +16 -22
  67. package/src/qingflow_mcp/session_store.py +7 -11
  68. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  69. package/src/qingflow_mcp/solution/executor.py +18 -245
  70. package/src/qingflow_mcp/tools/ai_builder_tools.py +1396 -3103
  71. package/src/qingflow_mcp/tools/app_tools.py +43 -184
  72. package/src/qingflow_mcp/tools/approval_tools.py +35 -197
  73. package/src/qingflow_mcp/tools/auth_tools.py +16 -92
  74. package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
  75. package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
  76. package/src/qingflow_mcp/tools/directory_tools.py +72 -236
  77. package/src/qingflow_mcp/tools/export_tools.py +34 -244
  78. package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
  79. package/src/qingflow_mcp/tools/file_tools.py +3 -9
  80. package/src/qingflow_mcp/tools/import_tools.py +49 -336
  81. package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
  82. package/src/qingflow_mcp/tools/package_tools.py +6 -118
  83. package/src/qingflow_mcp/tools/portal_tools.py +3 -39
  84. package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
  85. package/src/qingflow_mcp/tools/record_tools.py +360 -1145
  86. package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
  87. package/src/qingflow_mcp/tools/role_tools.py +9 -80
  88. package/src/qingflow_mcp/tools/solution_tools.py +45 -59
  89. package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
  90. package/src/qingflow_mcp/tools/task_tools.py +29 -113
  91. package/src/qingflow_mcp/tools/view_tools.py +3 -106
  92. package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
  93. package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
  94. package/npm/bin/qingflow-skills.mjs +0 -5
  95. package/skills/qingflow-app-builder/references/complete-system-development-guide.md +0 -172
  96. package/skills/qingflow-app-builder/references/single-app-development-guide.md +0 -87
  97. package/skills/qingflow-app-builder/scripts/validate_system_build_summary.py +0 -124
  98. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
@@ -11,7 +11,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
11
11
 
12
12
  get_parser = chart_subparsers.add_parser("get", help="读取报表数据")
13
13
  get_parser.add_argument("--chart-id", required=True)
14
- get_parser.set_defaults(handler=_handle_get, format_hint="chart_get")
14
+ get_parser.set_defaults(handler=_handle_get, format_hint="generic")
15
15
 
16
16
 
17
17
  def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
@@ -48,23 +48,14 @@ def read_secret_arg(value: str | None, *, stdin_enabled: bool, label: str) -> st
48
48
  raise QingflowApiError.config_error(f"{label} is required")
49
49
 
50
50
 
51
- def raise_config_error(
52
- message: str,
53
- *,
54
- fix_hint: str | None = None,
55
- error_code: str = "CONFIG_ERROR",
56
- details: dict[str, Any] | None = None,
57
- ) -> None:
58
- error_details: dict[str, Any] = {"fix_hint": fix_hint or message}
59
- if details:
60
- error_details.update(details)
51
+ def raise_config_error(message: str, *, fix_hint: str) -> None:
61
52
  raise RuntimeError(
62
53
  json.dumps(
63
54
  {
64
55
  "category": "config",
65
56
  "message": message,
66
- "error_code": error_code,
67
- "details": error_details,
57
+ "error_code": "CONFIG_ERROR",
58
+ "details": {"fix_hint": fix_hint},
68
59
  },
69
60
  ensure_ascii=False,
70
61
  )
@@ -12,7 +12,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
12
12
 
13
13
  start = export_subparsers.add_parser("start", help="启动导出")
14
14
  start.add_argument("--app-key", required=True)
15
- start.add_argument("--view-id", required=True)
15
+ start.add_argument("--view-id", default="system:all")
16
16
  start.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
17
17
  start.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
18
18
  start.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
@@ -33,7 +33,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
33
33
 
34
34
  direct = export_subparsers.add_parser("direct", help="直接导出并下载")
35
35
  direct.add_argument("--app-key", required=True)
36
- direct.add_argument("--view-id", required=True)
36
+ direct.add_argument("--view-id", default="system:all")
37
37
  direct.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
38
38
  direct.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
39
39
  direct.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
@@ -13,7 +13,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
13
13
  template = import_subparsers.add_parser("template", help="下载导入模板")
14
14
  template.add_argument("--app-key", required=True)
15
15
  template.add_argument("--download-to-path")
16
- template.set_defaults(handler=_handle_template, format_hint="import_template")
16
+ template.set_defaults(handler=_handle_template, format_hint="")
17
17
 
18
18
  verify = import_subparsers.add_parser("verify", help="校验导入文件")
19
19
  verify.add_argument("--app-key", required=True)
@@ -25,14 +25,14 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
25
25
  repair.add_argument("--authorized-file-modification", action="store_true")
26
26
  repair.add_argument("--output-path")
27
27
  repair.add_argument("--repair", dest="selected_repairs", action="append", default=[])
28
- repair.set_defaults(handler=_handle_repair, format_hint="import_repair")
28
+ repair.set_defaults(handler=_handle_repair, format_hint="")
29
29
 
30
30
  start = import_subparsers.add_parser("start", help="启动导入")
31
31
  start.add_argument("--app-key", required=True)
32
32
  start.add_argument("--verification-id", required=True)
33
33
  start.add_argument("--being-enter-auditing", type=parse_bool_text, required=True)
34
34
  start.add_argument("--view-key")
35
- start.set_defaults(handler=_handle_start, format_hint="import_start")
35
+ start.set_defaults(handler=_handle_start, format_hint="")
36
36
 
37
37
  status = import_subparsers.add_parser("status", help="查询导入状态")
38
38
  status.add_argument("--app-key")
@@ -10,11 +10,11 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
10
10
  portal_subparsers = parser.add_subparsers(dest="portal_command", required=True)
11
11
 
12
12
  list_parser = portal_subparsers.add_parser("list", help="列出当前用户可访问的门户")
13
- list_parser.set_defaults(handler=_handle_list, format_hint="portal_list")
13
+ list_parser.set_defaults(handler=_handle_list, format_hint="generic")
14
14
 
15
15
  get_parser = portal_subparsers.add_parser("get", help="读取门户内容清单")
16
16
  get_parser.add_argument("--dash-key", required=True)
17
- get_parser.set_defaults(handler=_handle_get, format_hint="portal_get")
17
+ get_parser.set_defaults(handler=_handle_get, format_hint="generic")
18
18
 
19
19
 
20
20
  def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
@@ -18,43 +18,34 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
18
18
 
19
19
  schema = record_subparsers.add_parser("schema", help="读取记录相关表结构")
20
20
  schema.add_argument("--mode", dest="legacy_mode", help=argparse.SUPPRESS)
21
- schema_subparsers = schema.add_subparsers(
22
- dest="record_schema_command",
23
- metavar="{browse,insert,update,import,code-block}",
24
- )
21
+ schema_subparsers = schema.add_subparsers(dest="record_schema_command")
25
22
  schema.set_defaults(handler=_handle_schema_root, format_hint="")
26
23
 
27
- schema_applicant = schema_subparsers.add_parser("applicant", help=argparse.SUPPRESS)
24
+ schema_applicant = schema_subparsers.add_parser("applicant", help="读取申请节点表结构")
28
25
  schema_applicant.add_argument("--app-key", required=True)
29
- schema_applicant.set_defaults(handler=_handle_schema_applicant, format_hint="record_schema")
30
- schema_subparsers._choices_actions = [ # type: ignore[attr-defined]
31
- action
32
- for action in schema_subparsers._choices_actions # type: ignore[attr-defined]
33
- if getattr(action, "dest", None) != "applicant"
34
- ]
26
+ schema_applicant.set_defaults(handler=_handle_schema_applicant, format_hint="")
35
27
 
36
28
  schema_browse = schema_subparsers.add_parser("browse", help="读取浏览视图表结构")
37
29
  schema_browse.add_argument("--app-key", required=True)
38
30
  schema_browse.add_argument("--view-id", required=True)
39
- schema_browse.set_defaults(handler=_handle_schema_browse, format_hint="record_schema")
31
+ schema_browse.set_defaults(handler=_handle_schema_browse, format_hint="")
40
32
 
41
33
  schema_insert = schema_subparsers.add_parser("insert", help="读取新增记录表结构")
42
34
  schema_insert.add_argument("--app-key", required=True)
43
- schema_insert.set_defaults(handler=_handle_schema_insert, format_hint="record_schema")
35
+ schema_insert.set_defaults(handler=_handle_schema_insert, format_hint="")
44
36
 
45
37
  schema_update = schema_subparsers.add_parser("update", help="读取更新记录表结构")
46
38
  schema_update.add_argument("--app-key", required=True)
47
39
  schema_update.add_argument("--record-id", required=True)
48
- schema_update.add_argument("--view-id")
49
- schema_update.set_defaults(handler=_handle_schema_update, format_hint="record_schema")
40
+ schema_update.set_defaults(handler=_handle_schema_update, format_hint="")
50
41
 
51
42
  schema_import = schema_subparsers.add_parser("import", help="读取导入表结构")
52
43
  schema_import.add_argument("--app-key", required=True)
53
- schema_import.set_defaults(handler=_handle_schema_import, format_hint="record_schema")
44
+ schema_import.set_defaults(handler=_handle_schema_import, format_hint="")
54
45
 
55
46
  schema_code_block = schema_subparsers.add_parser("code-block", help="读取代码块执行表结构")
56
47
  schema_code_block.add_argument("--app-key", required=True)
57
- schema_code_block.set_defaults(handler=_handle_schema_code_block, format_hint="record_schema")
48
+ schema_code_block.set_defaults(handler=_handle_schema_code_block, format_hint="")
58
49
 
59
50
  member_candidates = record_subparsers.add_parser("member-candidates", help="读取成员字段候选项")
60
51
  member_candidates.add_argument("--app-key", required=True)
@@ -65,7 +56,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
65
56
  member_candidates.add_argument("--record-id")
66
57
  member_candidates.add_argument("--workflow-node-id", type=int)
67
58
  member_candidates.add_argument("--fields-file")
68
- member_candidates.set_defaults(handler=_handle_member_candidates, format_hint="record_candidates")
59
+ member_candidates.set_defaults(handler=_handle_member_candidates, format_hint="")
69
60
 
70
61
  department_candidates = record_subparsers.add_parser("department-candidates", help="读取部门字段候选项")
71
62
  department_candidates.add_argument("--app-key", required=True)
@@ -76,41 +67,18 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
76
67
  department_candidates.add_argument("--record-id")
77
68
  department_candidates.add_argument("--workflow-node-id", type=int)
78
69
  department_candidates.add_argument("--fields-file")
79
- department_candidates.set_defaults(handler=_handle_department_candidates, format_hint="record_candidates")
70
+ department_candidates.set_defaults(handler=_handle_department_candidates, format_hint="")
80
71
 
81
72
  list_parser = record_subparsers.add_parser("list", help="列出记录")
82
73
  list_parser.add_argument("--app-key", required=True)
83
- list_parser.add_argument(
84
- "--column",
85
- dest="columns",
86
- action="append",
87
- type=int,
88
- default=[],
89
- metavar="FIELD_ID",
90
- help="只返回这些 field_id;可重复传",
91
- )
92
- list_parser.add_argument(
93
- "--columns-file",
94
- help="JSON/YAML list;元素为 field_id 整数/整数字符串或 {'field_id': ...}",
95
- )
74
+ list_parser.add_argument("--column", dest="columns", action="append", type=int, default=[])
75
+ list_parser.add_argument("--columns-file")
96
76
  list_parser.add_argument("--query")
97
- list_parser.add_argument(
98
- "--query-field",
99
- dest="query_fields",
100
- action="append",
101
- type=int,
102
- default=[],
103
- metavar="FIELD_ID",
104
- help="全文搜索范围 field_id;可重复传",
105
- )
106
- list_parser.add_argument(
107
- "--query-fields-file",
108
- help="JSON/YAML list;元素为 field_id 整数/整数字符串或 {'field_id': ...}",
109
- )
77
+ list_parser.add_argument("--query-field", dest="query_fields", action="append", type=int, default=[])
78
+ list_parser.add_argument("--query-fields-file")
110
79
  list_parser.add_argument("--where-file")
111
80
  list_parser.add_argument("--order-by-file")
112
81
  list_parser.add_argument("--page", type=int, default=1)
113
- list_parser.add_argument("--page-size", type=int, default=10)
114
82
  list_parser.add_argument("--view-id")
115
83
  list_parser.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
116
84
  list_parser.add_argument("--view-key", dest="legacy_view_key", help=argparse.SUPPRESS)
@@ -119,19 +87,8 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
119
87
 
120
88
  access_parser = record_subparsers.add_parser("access", help="访问记录并写入本地 CSV 分片")
121
89
  access_parser.add_argument("--app-key", required=True)
122
- access_parser.add_argument(
123
- "--column",
124
- dest="columns",
125
- action="append",
126
- type=int,
127
- default=[],
128
- metavar="FIELD_ID",
129
- help="导出这些 field_id 到 CSV;可重复传",
130
- )
131
- access_parser.add_argument(
132
- "--columns-file",
133
- help="JSON/YAML list;元素为 field_id 整数/整数字符串或 {'field_id': ...}",
134
- )
90
+ access_parser.add_argument("--column", dest="columns", action="append", type=int, default=[])
91
+ access_parser.add_argument("--columns-file")
135
92
  access_parser.add_argument("--where-file")
136
93
  access_parser.add_argument("--order-by-file")
137
94
  access_parser.add_argument("--view-id", required=True)
@@ -140,26 +97,15 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
140
97
  get = record_subparsers.add_parser("get", help="读取单条记录")
141
98
  get.add_argument("--app-key", required=True)
142
99
  get.add_argument("--record-id", required=True)
143
- get.add_argument(
144
- "--column",
145
- dest="columns",
146
- action="append",
147
- type=int,
148
- default=[],
149
- metavar="FIELD_ID",
150
- help="聚焦这些 field_id;可重复传",
151
- )
152
- get.add_argument(
153
- "--columns-file",
154
- help="JSON/YAML list;元素为 field_id 整数/整数字符串或 {'field_id': ...}",
155
- )
100
+ get.add_argument("--column", dest="columns", action="append", type=int, default=[])
101
+ get.add_argument("--columns-file")
156
102
  get.add_argument("--view-id")
157
103
  get.set_defaults(handler=_handle_get, format_hint="record_get")
158
104
 
159
105
  logs = record_subparsers.add_parser("logs", help="读取单条记录全量日志并写入本地 JSONL")
160
106
  logs.add_argument("--app-key", required=True)
161
107
  logs.add_argument("--record-id", required=True)
162
- logs.add_argument("--view-id", required=True)
108
+ logs.add_argument("--view-id")
163
109
  logs.set_defaults(handler=_handle_logs, format_hint="record_logs")
164
110
 
165
111
  insert = record_subparsers.add_parser("insert", help="新增记录")
@@ -167,25 +113,22 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
167
113
  insert.add_argument("--fields-file", help=argparse.SUPPRESS)
168
114
  insert.add_argument("--items-file")
169
115
  insert.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
170
- insert.set_defaults(handler=_handle_insert, format_hint="record_write")
116
+ insert.set_defaults(handler=_handle_insert, format_hint="")
171
117
 
172
118
  update = record_subparsers.add_parser("update", help="更新记录")
173
119
  update.add_argument("--app-key", required=True)
174
120
  update.add_argument("--record-id")
175
121
  update.add_argument("--fields-file")
176
122
  update.add_argument("--items-file")
177
- update.add_argument("--view-id")
178
123
  update.add_argument("--dry-run", action=argparse.BooleanOptionalAction, default=False)
179
124
  update.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
180
- update.set_defaults(handler=_handle_update, format_hint="record_write")
125
+ update.set_defaults(handler=_handle_update, format_hint="")
181
126
 
182
127
  delete = record_subparsers.add_parser("delete", help="删除记录")
183
128
  delete.add_argument("--app-key", required=True)
184
129
  delete.add_argument("--record-id")
185
130
  delete.add_argument("--record-ids-file")
186
- delete.add_argument("--view-id")
187
- delete.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
188
- delete.set_defaults(handler=_handle_delete, format_hint="record_delete")
131
+ delete.set_defaults(handler=_handle_delete, format_hint="")
189
132
 
190
133
  analyze = record_subparsers.add_parser("analyze", help=argparse.SUPPRESS)
191
134
  record_subparsers._choices_actions = [ # type: ignore[attr-defined]
@@ -210,7 +153,6 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
210
153
  code_block.add_argument("--app-key", required=True)
211
154
  code_block.add_argument("--record-id", required=True)
212
155
  code_block.add_argument("--code-block-field", required=True)
213
- code_block.add_argument("--view-id")
214
156
  code_block.add_argument("--role", type=int, default=1)
215
157
  code_block.add_argument("--workflow-node-id", type=int)
216
158
  code_block.add_argument("--answers-file")
@@ -219,7 +161,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
219
161
  code_block.add_argument("--apply-writeback", action=argparse.BooleanOptionalAction, default=True)
220
162
  code_block.add_argument("--verify-writeback", action=argparse.BooleanOptionalAction, default=True)
221
163
  code_block.add_argument("--force-refresh-form", action="store_true")
222
- code_block.set_defaults(handler=_handle_code_block_run, format_hint="code_block_run")
164
+ code_block.set_defaults(handler=_handle_code_block_run, format_hint="")
223
165
 
224
166
 
225
167
  def _columns(args: argparse.Namespace) -> list[Any]:
@@ -240,20 +182,20 @@ def _handle_schema_root(args: argparse.Namespace, _context: CliContext) -> dict:
240
182
  mode = (args.legacy_mode or "").strip()
241
183
  if mode:
242
184
  replacement = {
243
- "applicant": "record schema insert --app-key APP_KEY",
185
+ "applicant": "record schema applicant --app-key APP_KEY",
244
186
  "browse": "record schema browse --app-key APP_KEY --view-id VIEW_ID",
245
187
  "insert": "record schema insert --app-key APP_KEY",
246
- "update": "record schema update --app-key APP_KEY --record-id RECORD_ID [--view-id VIEW_ID]",
188
+ "update": "record schema update --app-key APP_KEY --record-id RECORD_ID",
247
189
  "import": "record schema import --app-key APP_KEY",
248
190
  "code-block": "record schema code-block --app-key APP_KEY",
249
- }.get(mode, "record schema <browse|insert|update|import|code-block> ...")
191
+ }.get(mode, "record schema <applicant|browse|insert|update|import|code-block> ...")
250
192
  raise_config_error(
251
193
  "record schema --mode is no longer accepted.",
252
194
  fix_hint=f"Use `{replacement}` instead.",
253
195
  )
254
196
  raise_config_error(
255
197
  "record schema requires an explicit subcommand.",
256
- fix_hint="Use one of: `record schema browse`, `record schema insert`, `record schema update`, `record schema import`, or `record schema code-block`.",
198
+ 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`.",
257
199
  )
258
200
 
259
201
 
@@ -282,7 +224,6 @@ def _handle_schema_update(args: argparse.Namespace, context: CliContext) -> dict
282
224
  profile=args.profile,
283
225
  app_key=args.app_key,
284
226
  record_id=args.record_id,
285
- view_id=args.view_id,
286
227
  )
287
228
 
288
229
 
@@ -368,7 +309,6 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
368
309
  where=load_list_arg(args.where_file, option_name="--where-file"),
369
310
  order_by=load_list_arg(args.order_by_file, option_name="--order-by-file"),
370
311
  page=args.page,
371
- page_size=args.page_size,
372
312
  view_id=args.view_id,
373
313
  )
374
314
 
@@ -442,7 +382,6 @@ def _handle_update(args: argparse.Namespace, context: CliContext) -> dict:
442
382
  record_id=None,
443
383
  fields=None,
444
384
  items=require_list_arg(args.items_file, option_name="--items-file"),
445
- view_id=args.view_id,
446
385
  dry_run=bool(args.dry_run),
447
386
  verify_write=bool(args.verify_write),
448
387
  )
@@ -461,29 +400,17 @@ def _handle_update(args: argparse.Namespace, context: CliContext) -> dict:
461
400
  app_key=args.app_key,
462
401
  record_id=args.record_id,
463
402
  fields=require_object_arg(args.fields_file, option_name="--fields-file"),
464
- view_id=args.view_id,
465
403
  verify_write=bool(args.verify_write),
466
404
  )
467
405
 
468
406
 
469
407
  def _handle_delete(args: argparse.Namespace, context: CliContext) -> dict:
470
- if args.legacy_list_type is not None:
471
- raise_config_error(
472
- "record delete no longer accepts list_type.",
473
- fix_hint="Call `app_get` first and pass an accessible system `view_id`, for example `--view-id system:all`.",
474
- )
475
- if not (args.view_id or "").strip():
476
- raise_config_error(
477
- "record delete requires --view-id.",
478
- fix_hint="Use a system view_id from app get accessible_views, for example `--view-id system:all`; custom views are not supported for deletion.",
479
- )
480
408
  record_ids = load_list_arg(args.record_ids_file, option_name="--record-ids-file")
481
409
  return context.record.record_delete_public(
482
410
  profile=args.profile,
483
411
  app_key=args.app_key,
484
412
  record_id=args.record_id,
485
413
  record_ids=record_ids,
486
- view_id=args.view_id,
487
414
  )
488
415
 
489
416
 
@@ -519,7 +446,6 @@ def _handle_code_block_run(args: argparse.Namespace, context: CliContext) -> dic
519
446
  app_key=args.app_key,
520
447
  record_id=args.record_id,
521
448
  code_block_field=args.code_block_field,
522
- view_id=args.view_id,
523
449
  role=args.role,
524
450
  workflow_node_id=args.workflow_node_id,
525
451
  answers=load_list_arg(args.answers_file, option_name="--answers-file"),
@@ -47,14 +47,20 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
47
47
  list_parser.add_argument("--page-size", type=int, default=20)
48
48
  list_parser.set_defaults(handler=_handle_list, format_hint="task_list")
49
49
 
50
- get = task_subparsers.add_parser("get", help="读取待办详情;--task-id 必须来自 task list 的 data.items[].task_id")
51
- get.add_argument("--task-id", help="来自 `qingflow --json task list` 的 data.items[].task_id;不是列表序号")
50
+ get = task_subparsers.add_parser("get", help="读取待办详情;推荐直接传 --task-id")
51
+ get.add_argument("--task-id")
52
+ get.add_argument("--app-key")
53
+ get.add_argument("--record-id")
54
+ get.add_argument("--workflow-node-id", type=int)
52
55
  get.add_argument("--include-candidates", action=argparse.BooleanOptionalAction, default=True)
53
56
  get.add_argument("--include-associated-reports", action=argparse.BooleanOptionalAction, default=True)
54
- get.set_defaults(handler=_handle_get, format_hint="task_get", app_key="", record_id="", workflow_node_id=0)
57
+ get.set_defaults(handler=_handle_get, format_hint="task_get")
55
58
 
56
- action = task_subparsers.add_parser("action", help="执行待办动作;--task-id 必须来自 task list 的 data.items[].task_id")
57
- action.add_argument("--task-id", help="来自 `qingflow --json task list` 的 data.items[].task_id;不是列表序号")
59
+ action = task_subparsers.add_parser("action", help="执行待办动作")
60
+ action.add_argument("--task-id")
61
+ action.add_argument("--app-key")
62
+ action.add_argument("--record-id")
63
+ action.add_argument("--workflow-node-id", type=int)
58
64
  action.add_argument("--action", help="不传时在交互终端中选择当前待办可执行动作")
59
65
  action.add_argument("--payload-file")
60
66
  action.add_argument("--fields-file")
@@ -62,27 +68,24 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
62
68
  handler=_handle_action,
63
69
  format_hint="task_action_execute",
64
70
  hide_effective_context_line=True,
65
- app_key="",
66
- record_id="",
67
- workflow_node_id=0,
68
71
  )
69
72
 
70
- report = task_subparsers.add_parser("report", help="读取待办关联报表详情;--task-id 必须来自 task list 的 data.items[].task_id")
71
- report.add_argument("--task-id", help="来自 `qingflow --json task list` 的 data.items[].task_id;不是列表序号")
73
+ report = task_subparsers.add_parser("report", help="读取待办关联报表详情;推荐直接传 --task-id")
74
+ report.add_argument("--task-id")
75
+ report.add_argument("--app-key")
76
+ report.add_argument("--record-id")
77
+ report.add_argument("--workflow-node-id", type=int)
72
78
  report.add_argument("--report-id", type=int, help="不传时在交互终端中选择关联报表")
73
79
  report.add_argument("--page", type=int, default=1)
74
80
  report.add_argument("--page-size", type=int, default=20)
75
- report.set_defaults(
76
- handler=_handle_report,
77
- format_hint="task_associated_report_detail_get",
78
- app_key="",
79
- record_id="",
80
- workflow_node_id=0,
81
- )
81
+ report.set_defaults(handler=_handle_report, format_hint="task_associated_report_detail_get")
82
82
 
83
- log = task_subparsers.add_parser("log", help="读取流程日志;--task-id 必须来自 task list 的 data.items[].task_id")
84
- log.add_argument("--task-id", help="来自 `qingflow --json task list` 的 data.items[].task_id;不是列表序号")
85
- log.set_defaults(handler=_handle_log, format_hint="task_workflow_log_get", app_key="", record_id="", workflow_node_id=0)
83
+ log = task_subparsers.add_parser("log", help="读取流程日志;推荐直接传 --task-id")
84
+ log.add_argument("--task-id")
85
+ log.add_argument("--app-key")
86
+ log.add_argument("--record-id")
87
+ log.add_argument("--workflow-node-id", type=int)
88
+ log.set_defaults(handler=_handle_log, format_hint="")
86
89
 
87
90
 
88
91
  def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
@@ -162,6 +165,9 @@ def _handle_action(args: argparse.Namespace, context: CliContext) -> dict:
162
165
  return context.task.task_action_execute(
163
166
  profile=args.profile,
164
167
  task_id=args.task_id,
168
+ app_key=args.app_key or "",
169
+ record_id=args.record_id or "",
170
+ workflow_node_id=int(args.workflow_node_id or 0),
165
171
  action=args.action,
166
172
  payload=payload,
167
173
  fields=fields,
@@ -222,6 +228,9 @@ def _run_task_workbench_task_loop(args: argparse.Namespace, context: CliContext)
222
228
  result = context.task.task_action_execute(
223
229
  profile=args.profile,
224
230
  task_id=args.task_id,
231
+ app_key=args.app_key or "",
232
+ record_id=args.record_id or "",
233
+ workflow_node_id=int(args.workflow_node_id or 0),
225
234
  action=args.action,
226
235
  payload=payload_selection,
227
236
  fields={},
@@ -234,20 +243,30 @@ def _run_task_workbench_task_loop(args: argparse.Namespace, context: CliContext)
234
243
  def _resolve_task_locator_or_select(args: argparse.Namespace, context: CliContext, *, tool_name: str) -> dict | None:
235
244
  if (args.task_id or "").strip():
236
245
  return None
246
+ has_app_key = bool((args.app_key or "").strip())
247
+ has_record_id = bool((args.record_id or "").strip())
248
+ has_workflow_node_id = int(args.workflow_node_id or 0) > 0
249
+ if has_app_key and has_record_id and has_workflow_node_id:
250
+ return None
251
+ if has_app_key or has_record_id or has_workflow_node_id:
252
+ raise_config_error(
253
+ f"{tool_name} requires --task-id, or --app-key together with --record-id and --workflow-node-id",
254
+ fix_hint="Either pass `--task-id TASK_ID`, or provide the full locator triple `--app-key --record-id --workflow-node-id`.",
255
+ )
237
256
 
238
257
  selection = _choose_todo_task_interactively(args, context, tool_name=tool_name)
239
258
  if selection.status == "unavailable":
240
259
  raise_config_error(
241
- f"{tool_name} requires --task-id from task list data.items[].task_id",
260
+ f"{tool_name} requires --task-id, or --app-key together with --record-id and --workflow-node-id",
242
261
  fix_hint=(
243
262
  "Retry in an interactive terminal to choose from current todo tasks, "
244
- "or pass `--task-id` from `task list` explicitly."
263
+ "or pass `--task-id TASK_ID` explicitly."
245
264
  ),
246
265
  )
247
266
  if selection.status == "empty":
248
267
  raise_config_error(
249
268
  selection.message or f"{tool_name} could not open a selector because no current todo tasks are available.",
250
- fix_hint="Run `task list` to confirm current todo tasks, then pass the selected `data.items[].task_id`.",
269
+ fix_hint="Run `task list` to confirm current todo tasks, or retry later with `--task-id TASK_ID`.",
251
270
  )
252
271
  if selection.status == "cancelled":
253
272
  return cancelled_result(selection.message or "已取消")
@@ -308,7 +327,7 @@ def _choose_todo_task_interactively(
308
327
  args,
309
328
  title=title,
310
329
  unavailable_message=(
311
- f"{tool_name} requires --task-id from task list data.items[].task_id"
330
+ f"{tool_name} requires --task-id, or --app-key together with --record-id and --workflow-node-id"
312
331
  ),
313
332
  empty_message=f"{tool_name} could not open a selector because no current todo tasks are available.",
314
333
  load_options=load_options,
@@ -328,7 +347,7 @@ def _resolve_report_id_or_select(args: argparse.Namespace, context: CliContext)
328
347
  if selection.status == "empty":
329
348
  raise_config_error(
330
349
  selection.message or "task report could not open a selector because the selected task has no visible associated reports.",
331
- fix_hint="Run `task get --task-id <data.items[].task_id>` to inspect `extras.associated_reports`, or choose another task.",
350
+ fix_hint="Run `task get --task-id TASK_ID` to inspect `extras.associated_reports`, or choose another task.",
332
351
  )
333
352
  if selection.status == "cancelled":
334
353
  return cancelled_result(selection.message or "已取消")
@@ -376,7 +395,7 @@ def _resolve_task_action_or_select(
376
395
  if selection.status == "empty":
377
396
  raise_config_error(
378
397
  selection.message or "task action could not open an action selector because no interactive actions are available.",
379
- fix_hint="Run `task get --task-id <data.items[].task_id>` to inspect available_actions, or pass a supported `--action` explicitly.",
398
+ fix_hint="Run `task get --task-id TASK_ID` to inspect available_actions, or pass a supported `--action` explicitly.",
380
399
  )
381
400
  if selection.status == "cancelled":
382
401
  return cancelled_result(selection.message or "已取消")
@@ -457,7 +476,7 @@ def _resolve_action_payload_or_select(
457
476
  if selection.status == "empty":
458
477
  raise_config_error(
459
478
  selection.message or "task rollback could not open a selector because no rollback candidates are visible.",
460
- fix_hint="Run `task get --task-id <data.items[].task_id>` to inspect rollback candidates, or choose another action.",
479
+ fix_hint="Run `task get --task-id TASK_ID` to inspect rollback candidates, or choose another action.",
461
480
  )
462
481
  if selection.status == "cancelled":
463
482
  return cancelled_result(selection.message or "已取消")
@@ -474,7 +493,7 @@ def _resolve_action_payload_or_select(
474
493
  if selection.status == "empty":
475
494
  raise_config_error(
476
495
  selection.message or "task transfer could not open a selector because no transfer candidates are visible.",
477
- fix_hint="Run `task get --task-id <data.items[].task_id>` to inspect transfer candidates, or choose another action.",
496
+ fix_hint="Run `task get --task-id TASK_ID` to inspect transfer candidates, or choose another action.",
478
497
  )
479
498
  if selection.status == "cancelled":
480
499
  return cancelled_result(selection.message or "已取消")
@@ -11,7 +11,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
11
11
 
12
12
  get_parser = view_subparsers.add_parser("get", help="读取视图资源描述")
13
13
  get_parser.add_argument("--view-id", required=True)
14
- get_parser.set_defaults(handler=_handle_get, format_hint="view_get")
14
+ get_parser.set_defaults(handler=_handle_get, format_hint="generic")
15
15
 
16
16
 
17
17
  def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
@@ -13,6 +13,7 @@ from ..tools.feedback_tools import FeedbackTools
13
13
  from ..tools.file_tools import FileTools
14
14
  from ..tools.import_tools import ImportTools
15
15
  from ..tools.record_tools import RecordTools
16
+ from ..tools.repository_dev_tools import RepositoryDevTools
16
17
  from ..tools.resource_read_tools import ResourceReadTools
17
18
  from ..tools.task_context_tools import TaskContextTools
18
19
  from ..tools.workspace_tools import WorkspaceTools
@@ -34,6 +35,7 @@ class CliContext:
34
35
  files: FileTools
35
36
  builder_feedback: FeedbackTools
36
37
  builder: AiBuilderTools
38
+ repo: RepositoryDevTools
37
39
 
38
40
  def close(self) -> None:
39
41
  self.backend.close()
@@ -57,4 +59,5 @@ def build_cli_context() -> CliContext:
57
59
  files=FileTools(sessions, backend),
58
60
  builder_feedback=FeedbackTools(backend, mcp_side="App Builder MCP"),
59
61
  builder=AiBuilderTools(sessions, backend),
62
+ repo=RepositoryDevTools(sessions, backend),
60
63
  )