@josephyan/qingflow-cli 0.2.0-beta.72 → 0.2.0-beta.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +6 -3
- package/src/qingflow_mcp/cli/commands/builder.py +57 -0
- package/src/qingflow_mcp/cli/context.py +6 -0
- package/src/qingflow_mcp/response_trim.py +8 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +79 -0
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-cli@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-cli@0.2.0-beta.73
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.73 qingflow
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josephyan/qingflow-cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.73",
|
|
4
4
|
"description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/pyproject.toml
CHANGED
|
@@ -187,7 +187,8 @@ class FlowConditionRulePatch(StrictModel):
|
|
|
187
187
|
return value
|
|
188
188
|
payload = dict(value)
|
|
189
189
|
if "value" in payload and "values" not in payload:
|
|
190
|
-
|
|
190
|
+
raw_value = payload.pop("value")
|
|
191
|
+
payload["values"] = list(raw_value) if isinstance(raw_value, list) else [raw_value]
|
|
191
192
|
raw_operator = payload.get("operator", payload.get("op"))
|
|
192
193
|
if isinstance(raw_operator, str):
|
|
193
194
|
normalized = raw_operator.strip().lower()
|
|
@@ -238,7 +239,8 @@ class ViewFilterRulePatch(StrictModel):
|
|
|
238
239
|
return value
|
|
239
240
|
payload = dict(value)
|
|
240
241
|
if "value" in payload and "values" not in payload:
|
|
241
|
-
|
|
242
|
+
raw_value = payload.pop("value")
|
|
243
|
+
payload["values"] = list(raw_value) if isinstance(raw_value, list) else [raw_value]
|
|
242
244
|
raw_operator = payload.get("operator", payload.get("op"))
|
|
243
245
|
if isinstance(raw_operator, str):
|
|
244
246
|
normalized = raw_operator.strip().lower()
|
|
@@ -1092,7 +1094,8 @@ class ChartFilterRulePatch(StrictModel):
|
|
|
1092
1094
|
return value
|
|
1093
1095
|
payload = dict(value)
|
|
1094
1096
|
if "value" in payload and "values" not in payload:
|
|
1095
|
-
|
|
1097
|
+
raw_value = payload.pop("value")
|
|
1098
|
+
payload["values"] = list(raw_value) if isinstance(raw_value, list) else [raw_value]
|
|
1096
1099
|
raw_operator = payload.get("operator", payload.get("op"))
|
|
1097
1100
|
if isinstance(raw_operator, str):
|
|
1098
1101
|
normalized = raw_operator.strip().lower()
|
|
@@ -10,6 +10,34 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
10
10
|
parser = subparsers.add_parser("builder", aliases=["build"], help="稳定 builder 命令")
|
|
11
11
|
builder_subparsers = parser.add_subparsers(dest="builder_command", required=True)
|
|
12
12
|
|
|
13
|
+
file_parser = builder_subparsers.add_parser("file", help="builder 侧文件上传")
|
|
14
|
+
file_subparsers = file_parser.add_subparsers(dest="builder_file_command", required=True)
|
|
15
|
+
file_upload_local = file_subparsers.add_parser("upload-local", help="上传本地文件并返回附件值")
|
|
16
|
+
file_upload_local.add_argument("--upload-kind", default="attachment")
|
|
17
|
+
file_upload_local.add_argument("--file-path", required=True)
|
|
18
|
+
file_upload_local.add_argument("--upload-mark")
|
|
19
|
+
file_upload_local.add_argument("--content-type")
|
|
20
|
+
file_upload_local.add_argument("--bucket-type")
|
|
21
|
+
file_upload_local.add_argument("--path-id", type=int)
|
|
22
|
+
file_upload_local.add_argument("--file-related-url")
|
|
23
|
+
file_upload_local.set_defaults(handler=_handle_file_upload_local, format_hint="generic")
|
|
24
|
+
|
|
25
|
+
feedback = builder_subparsers.add_parser("feedback", help="builder 侧反馈提交")
|
|
26
|
+
feedback_subparsers = feedback.add_subparsers(dest="builder_feedback_command", required=True)
|
|
27
|
+
feedback_submit = feedback_subparsers.add_parser("submit", help="提交 builder 能力反馈")
|
|
28
|
+
feedback_submit.add_argument("--category", required=True)
|
|
29
|
+
feedback_submit.add_argument("--title", required=True)
|
|
30
|
+
feedback_submit.add_argument("--description", required=True)
|
|
31
|
+
feedback_submit.add_argument("--expected-behavior")
|
|
32
|
+
feedback_submit.add_argument("--actual-behavior")
|
|
33
|
+
feedback_submit.add_argument("--impact-scope")
|
|
34
|
+
feedback_submit.add_argument("--tool-name")
|
|
35
|
+
feedback_submit.add_argument("--app-key")
|
|
36
|
+
feedback_submit.add_argument("--record-id")
|
|
37
|
+
feedback_submit.add_argument("--workflow-node-id")
|
|
38
|
+
feedback_submit.add_argument("--note")
|
|
39
|
+
feedback_submit.set_defaults(handler=_handle_feedback_submit, format_hint="generic")
|
|
40
|
+
|
|
13
41
|
contract = builder_subparsers.add_parser("contract", help="读取 builder tool 合约")
|
|
14
42
|
contract.add_argument("--tool-name", required=True)
|
|
15
43
|
contract.set_defaults(handler=_handle_contract, format_hint="builder_summary")
|
|
@@ -215,6 +243,35 @@ def _handle_package_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
215
243
|
return context.builder.package_list(profile=args.profile, trial_status=args.trial_status)
|
|
216
244
|
|
|
217
245
|
|
|
246
|
+
def _handle_file_upload_local(args: argparse.Namespace, context: CliContext) -> dict:
|
|
247
|
+
return context.files.file_upload_local(
|
|
248
|
+
profile=args.profile,
|
|
249
|
+
upload_kind=args.upload_kind,
|
|
250
|
+
file_path=args.file_path,
|
|
251
|
+
upload_mark=args.upload_mark,
|
|
252
|
+
content_type=args.content_type,
|
|
253
|
+
bucket_type=args.bucket_type,
|
|
254
|
+
path_id=args.path_id,
|
|
255
|
+
file_related_url=args.file_related_url,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _handle_feedback_submit(args: argparse.Namespace, context: CliContext) -> dict:
|
|
260
|
+
return context.builder_feedback.feedback_submit(
|
|
261
|
+
category=args.category,
|
|
262
|
+
title=args.title,
|
|
263
|
+
description=args.description,
|
|
264
|
+
expected_behavior=args.expected_behavior,
|
|
265
|
+
actual_behavior=args.actual_behavior,
|
|
266
|
+
impact_scope=args.impact_scope,
|
|
267
|
+
tool_name=args.tool_name,
|
|
268
|
+
app_key=args.app_key,
|
|
269
|
+
record_id=args.record_id,
|
|
270
|
+
workflow_node_id=args.workflow_node_id,
|
|
271
|
+
note=args.note,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
218
275
|
def _handle_contract(args: argparse.Namespace, context: CliContext) -> dict:
|
|
219
276
|
return context.builder.builder_tool_contract(tool_name=args.tool_name)
|
|
220
277
|
|
|
@@ -8,6 +8,8 @@ from ..tools.ai_builder_tools import AiBuilderTools
|
|
|
8
8
|
from ..tools.app_tools import AppTools
|
|
9
9
|
from ..tools.auth_tools import AuthTools
|
|
10
10
|
from ..tools.code_block_tools import CodeBlockTools
|
|
11
|
+
from ..tools.feedback_tools import FeedbackTools
|
|
12
|
+
from ..tools.file_tools import FileTools
|
|
11
13
|
from ..tools.import_tools import ImportTools
|
|
12
14
|
from ..tools.record_tools import RecordTools
|
|
13
15
|
from ..tools.resource_read_tools import ResourceReadTools
|
|
@@ -27,6 +29,8 @@ class CliContext:
|
|
|
27
29
|
code_block: CodeBlockTools
|
|
28
30
|
imports: ImportTools
|
|
29
31
|
task: TaskContextTools
|
|
32
|
+
files: FileTools
|
|
33
|
+
builder_feedback: FeedbackTools
|
|
30
34
|
builder: AiBuilderTools
|
|
31
35
|
|
|
32
36
|
def close(self) -> None:
|
|
@@ -47,5 +51,7 @@ def build_cli_context() -> CliContext:
|
|
|
47
51
|
code_block=CodeBlockTools(sessions, backend),
|
|
48
52
|
imports=ImportTools(sessions, backend),
|
|
49
53
|
task=TaskContextTools(sessions, backend),
|
|
54
|
+
files=FileTools(sessions, backend),
|
|
55
|
+
builder_feedback=FeedbackTools(backend, mcp_side="App Builder MCP"),
|
|
50
56
|
builder=AiBuilderTools(sessions, backend),
|
|
51
57
|
)
|
|
@@ -170,6 +170,14 @@ def resolve_cli_tool_name(args: Any) -> str | None:
|
|
|
170
170
|
if command not in {"builder", "build"}:
|
|
171
171
|
return None
|
|
172
172
|
builder_command = getattr(args, "builder_command", None)
|
|
173
|
+
if builder_command == "file":
|
|
174
|
+
return {
|
|
175
|
+
"upload-local": _tool_key(BUILDER_DOMAIN, "file_upload_local"),
|
|
176
|
+
}.get(getattr(args, "builder_file_command", None))
|
|
177
|
+
if builder_command == "feedback":
|
|
178
|
+
return {
|
|
179
|
+
"submit": _tool_key(BUILDER_DOMAIN, "feedback_submit"),
|
|
180
|
+
}.get(getattr(args, "builder_feedback_command", None))
|
|
173
181
|
if builder_command == "contract":
|
|
174
182
|
return _tool_key(BUILDER_DOMAIN, "builder_tool_contract")
|
|
175
183
|
if builder_command == "member":
|
|
@@ -1917,6 +1917,50 @@ def _public_error_message(error_code: str, error: QingflowApiError) -> str:
|
|
|
1917
1917
|
|
|
1918
1918
|
|
|
1919
1919
|
_BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
1920
|
+
"file_upload_local": {
|
|
1921
|
+
"allowed_keys": ["upload_kind", "file_path", "upload_mark", "content_type", "bucket_type", "path_id", "file_related_url"],
|
|
1922
|
+
"aliases": {},
|
|
1923
|
+
"allowed_values": {"upload_kind": ["attachment", "login"]},
|
|
1924
|
+
"execution_notes": [
|
|
1925
|
+
"returns upload metadata plus attachment-ready values for later builder or user writes",
|
|
1926
|
+
"upload_kind=attachment may fall back to login upload info when the backend rejects attachment upload info",
|
|
1927
|
+
"upload_mark is recommended for attachment uploads because it affects the remote storage path",
|
|
1928
|
+
],
|
|
1929
|
+
"minimal_example": {
|
|
1930
|
+
"profile": "default",
|
|
1931
|
+
"upload_kind": "attachment",
|
|
1932
|
+
"file_path": "/absolute/path/to/local-file.txt",
|
|
1933
|
+
"upload_mark": "APP_KEY",
|
|
1934
|
+
},
|
|
1935
|
+
},
|
|
1936
|
+
"feedback_submit": {
|
|
1937
|
+
"allowed_keys": [
|
|
1938
|
+
"category",
|
|
1939
|
+
"title",
|
|
1940
|
+
"description",
|
|
1941
|
+
"expected_behavior",
|
|
1942
|
+
"actual_behavior",
|
|
1943
|
+
"impact_scope",
|
|
1944
|
+
"tool_name",
|
|
1945
|
+
"app_key",
|
|
1946
|
+
"record_id",
|
|
1947
|
+
"workflow_node_id",
|
|
1948
|
+
"note",
|
|
1949
|
+
],
|
|
1950
|
+
"aliases": {},
|
|
1951
|
+
"allowed_values": {"category": ["bug", "missing_capability", "awkward_workflow", "ux", "docs"]},
|
|
1952
|
+
"execution_notes": [
|
|
1953
|
+
"submit feedback only after explicit user confirmation",
|
|
1954
|
+
"use this when the current builder capability is unsupported or awkward after reasonable attempts",
|
|
1955
|
+
"requires feedback qsource configuration but does not require Qingflow login or workspace selection",
|
|
1956
|
+
],
|
|
1957
|
+
"minimal_example": {
|
|
1958
|
+
"category": "missing_capability",
|
|
1959
|
+
"title": "builder chart data read gap",
|
|
1960
|
+
"description": "The current builder capability cannot satisfy the requested workflow.",
|
|
1961
|
+
"tool_name": "chart_get",
|
|
1962
|
+
},
|
|
1963
|
+
},
|
|
1920
1964
|
"package_list": {
|
|
1921
1965
|
"allowed_keys": ["trial_status"],
|
|
1922
1966
|
"aliases": {"trialStatus": "trial_status"},
|
|
@@ -2019,6 +2063,22 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2019
2063
|
"package_tag_id": 1001,
|
|
2020
2064
|
},
|
|
2021
2065
|
},
|
|
2066
|
+
"app_release_edit_lock_if_mine": {
|
|
2067
|
+
"allowed_keys": ["app_key", "lock_owner_email", "lock_owner_name"],
|
|
2068
|
+
"aliases": {},
|
|
2069
|
+
"allowed_values": {},
|
|
2070
|
+
"execution_notes": [
|
|
2071
|
+
"use this only after a builder write fails with APP_EDIT_LOCKED and the lock belongs to the current user",
|
|
2072
|
+
"supply the lock owner details from the failed write result for a safe release attempt",
|
|
2073
|
+
"after a successful release, retry the blocked builder write or publish verification call",
|
|
2074
|
+
],
|
|
2075
|
+
"minimal_example": {
|
|
2076
|
+
"profile": "default",
|
|
2077
|
+
"app_key": "APP_KEY",
|
|
2078
|
+
"lock_owner_email": "user@example.com",
|
|
2079
|
+
"lock_owner_name": "当前用户",
|
|
2080
|
+
},
|
|
2081
|
+
},
|
|
2022
2082
|
"app_custom_button_list": {
|
|
2023
2083
|
"allowed_keys": ["app_key"],
|
|
2024
2084
|
"aliases": {},
|
|
@@ -2475,6 +2535,9 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2475
2535
|
"view.buttons.button_type": ["SYSTEM", "CUSTOM"],
|
|
2476
2536
|
"view.buttons.config_type": ["TOP", "DETAIL"],
|
|
2477
2537
|
},
|
|
2538
|
+
"execution_notes": [
|
|
2539
|
+
"for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
|
|
2540
|
+
],
|
|
2478
2541
|
"minimal_example": {
|
|
2479
2542
|
"profile": "default",
|
|
2480
2543
|
"app_key": "APP_KEY",
|
|
@@ -2534,6 +2597,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2534
2597
|
"read back app_get_views after any failed or partial view apply",
|
|
2535
2598
|
"view existence verification and saved-filter verification are separate; treat filters as unverified until verification.view_filters_verified is true",
|
|
2536
2599
|
"buttons omitted preserves existing button config; buttons=[] clears all buttons; buttons=[...] replaces the full button config",
|
|
2600
|
+
"for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
|
|
2537
2601
|
],
|
|
2538
2602
|
"minimal_example": {
|
|
2539
2603
|
"profile": "default",
|
|
@@ -2779,6 +2843,21 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
2779
2843
|
},
|
|
2780
2844
|
},
|
|
2781
2845
|
},
|
|
2846
|
+
"app_publish_verify": {
|
|
2847
|
+
"allowed_keys": ["app_key", "expected_package_tag_id"],
|
|
2848
|
+
"aliases": {},
|
|
2849
|
+
"allowed_values": {},
|
|
2850
|
+
"execution_notes": [
|
|
2851
|
+
"verifies that the current app draft has been published and is readable through the builder surface",
|
|
2852
|
+
"expected_package_tag_id is optional and adds an extra package consistency check",
|
|
2853
|
+
"when verification fails because of an edit lock owned by the current user, retry after app_release_edit_lock_if_mine",
|
|
2854
|
+
],
|
|
2855
|
+
"minimal_example": {
|
|
2856
|
+
"profile": "default",
|
|
2857
|
+
"app_key": "APP_KEY",
|
|
2858
|
+
"expected_package_tag_id": 1001,
|
|
2859
|
+
},
|
|
2860
|
+
},
|
|
2782
2861
|
}
|
|
2783
2862
|
|
|
2784
2863
|
_PRIVATE_BUILDER_TOOL_CONTRACTS = {
|