@josephyan/qingflow-cli 1.0.11 → 1.1.2
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 +3 -3
- package/npm/bin/qingflow.mjs +40 -2
- package/npm/lib/runtime.mjs +386 -15
- package/npm/scripts/postinstall.mjs +7 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +440 -0
- package/skills/qingflow-cli/manifest.yaml +10 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +485 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md +237 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_MATCH_RULES.md +137 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md +263 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +304 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md +41 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +20 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +163 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +107 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +151 -0
- package/skills/qingflow-cli/reference/_batch_schema_complex.json +18 -0
- package/skills/qingflow-cli/reference/_batch_schema_scalar.json +17 -0
- package/skills/qingflow-cli/reference/charts_remove.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_reorder.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_upsert_bar.example.json +8 -0
- package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +37 -0
- package/skills/qingflow-cli/reference/charts_upsert_minimal.example.json +13 -0
- package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +131 -0
- package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +126 -0
- package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +128 -0
- package/skills/qingflow-cli/reference/schema_add_fields_minimal.example.json +7 -0
- package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +78 -0
- package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +7 -0
- package/skills/qingflow-cli/scripts/builder-package-from-app-list.py +140 -0
- package/skills/qingflow-cli/scripts/find-app-by-keyword.py +132 -0
- package/skills/qingflow-cli/scripts/validate_qingflow_output_files.py +87 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +532 -48
- package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
- package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
- package/src/qingflow_mcp/cli/commands/app.py +3 -16
- package/src/qingflow_mcp/cli/commands/builder.py +354 -56
- package/src/qingflow_mcp/cli/commands/record.py +89 -2
- package/src/qingflow_mcp/cli/formatters.py +32 -1
- package/src/qingflow_mcp/cli/main.py +245 -3
- package/src/qingflow_mcp/public_surface.py +11 -8
- package/src/qingflow_mcp/response_trim.py +143 -14
- package/src/qingflow_mcp/server.py +15 -12
- package/src/qingflow_mcp/server_app_builder.py +108 -30
- package/src/qingflow_mcp/server_app_user.py +17 -18
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
- package/src/qingflow_mcp/solution/executor.py +3 -133
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
- package/src/qingflow_mcp/tools/app_tools.py +53 -8
- package/src/qingflow_mcp/tools/package_tools.py +16 -2
- package/src/qingflow_mcp/tools/record_tools.py +2095 -176
- package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
- package/src/qingflow_mcp/tools/solution_tools.py +30 -2
- package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
- package/src/qingflow_mcp/version.py +110 -0
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""旧版 CLI 兼容:在本地对已登录会话的「应用列表」做关键字过滤。
|
|
3
|
+
新版 CLI 默认使用 `qingflow --json app list --query <keyword>`;本脚本仅调用
|
|
4
|
+
`qingflow --json app list`。仅用标准库 json/subprocess/os/shutil。
|
|
5
|
+
|
|
6
|
+
qingflow stderr 不重定向,与直接执行 CLI 时一致。
|
|
7
|
+
qingflow 非零退出码时:本脚本将 **stdout 原文**写入当前进程 stdout;退出码与该进程相同。
|
|
8
|
+
|
|
9
|
+
用法:
|
|
10
|
+
python3 scripts/find-app-by-keyword.py '<keyword>'
|
|
11
|
+
./scripts/find-app-by-keyword.py '<keyword>'
|
|
12
|
+
|
|
13
|
+
环境(可选): QINGFLOW_PROFILE → 等价于 qingflow --profile <名称>
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _usage() -> None:
|
|
27
|
+
bn = os.path.basename(sys.argv[0]) if sys.argv else "find-app-by-keyword.py"
|
|
28
|
+
print(f"用法: {bn} <keyword>", file=sys.stderr)
|
|
29
|
+
print(
|
|
30
|
+
" 匹配 app_name、package_name、app_key 子串(不区分大小写)。"
|
|
31
|
+
" 仅 subprocess: qingflow [--profile …] --json app list",
|
|
32
|
+
file=sys.stderr,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _merge_and_dedupe(data: dict[str, Any]) -> list[dict[str, Any]]:
|
|
37
|
+
parts: list[dict[str, Any]] = []
|
|
38
|
+
it = data.get("items")
|
|
39
|
+
if isinstance(it, list):
|
|
40
|
+
for x in it:
|
|
41
|
+
if isinstance(x, dict):
|
|
42
|
+
parts.append(x)
|
|
43
|
+
nested = data.get("data")
|
|
44
|
+
if isinstance(nested, dict):
|
|
45
|
+
it2 = nested.get("items")
|
|
46
|
+
if isinstance(it2, list):
|
|
47
|
+
for x in it2:
|
|
48
|
+
if isinstance(x, dict):
|
|
49
|
+
parts.append(x)
|
|
50
|
+
# 与 jq unique_by(.app_key) / 首次出现获胜 对齐
|
|
51
|
+
seen: set[Any] = set()
|
|
52
|
+
out: list[dict[str, Any]] = []
|
|
53
|
+
for obj in parts:
|
|
54
|
+
k = obj.get("app_key")
|
|
55
|
+
if k in seen:
|
|
56
|
+
continue
|
|
57
|
+
seen.add(k)
|
|
58
|
+
out.append(obj)
|
|
59
|
+
return out
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _filtered(keyword_lc: str, rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
63
|
+
hits: list[dict[str, Any]] = []
|
|
64
|
+
for o in rows:
|
|
65
|
+
for field in ("app_name", "package_name", "app_key"):
|
|
66
|
+
v = o.get(field)
|
|
67
|
+
if isinstance(v, str) and keyword_lc in v.lower():
|
|
68
|
+
hits.append(o)
|
|
69
|
+
break
|
|
70
|
+
return hits
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main() -> None:
|
|
74
|
+
if len(sys.argv) != 2:
|
|
75
|
+
_usage()
|
|
76
|
+
sys.exit(2)
|
|
77
|
+
kw = sys.argv[1].strip()
|
|
78
|
+
if not kw:
|
|
79
|
+
bn = os.path.basename(sys.argv[0]) if sys.argv else "find-app-by-keyword.py"
|
|
80
|
+
print(f"{bn}: keyword 不能为空", file=sys.stderr)
|
|
81
|
+
_usage()
|
|
82
|
+
sys.exit(2)
|
|
83
|
+
|
|
84
|
+
if not shutil.which("qingflow"):
|
|
85
|
+
print("qingflow: command not found", file=sys.stderr)
|
|
86
|
+
sys.exit(127)
|
|
87
|
+
|
|
88
|
+
cmd = ["qingflow"]
|
|
89
|
+
pf = os.environ.get("QINGFLOW_PROFILE") or os.environ.get("QING_FLOW_PROFILE")
|
|
90
|
+
if pf:
|
|
91
|
+
cmd.extend(["--profile", pf])
|
|
92
|
+
cmd.extend(["--json", "app", "list"])
|
|
93
|
+
|
|
94
|
+
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=None)
|
|
95
|
+
buf = proc.stdout
|
|
96
|
+
|
|
97
|
+
if proc.returncode != 0:
|
|
98
|
+
sys.stdout.buffer.write(buf)
|
|
99
|
+
sys.stdout.flush()
|
|
100
|
+
raise SystemExit(proc.returncode)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
text = buf.decode("utf-8")
|
|
104
|
+
except UnicodeDecodeError as e:
|
|
105
|
+
print(f"qingflow stdout UTF-8 解码失败: {e}", file=sys.stderr)
|
|
106
|
+
sys.stdout.buffer.write(buf)
|
|
107
|
+
sys.stdout.flush()
|
|
108
|
+
raise SystemExit(1)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
payload = json.loads(text)
|
|
112
|
+
except json.JSONDecodeError as e:
|
|
113
|
+
print(f"qingflow stdout 不是合法 JSON: {e}", file=sys.stderr)
|
|
114
|
+
sys.stdout.buffer.write(buf)
|
|
115
|
+
sys.stdout.flush()
|
|
116
|
+
raise SystemExit(1)
|
|
117
|
+
|
|
118
|
+
if not isinstance(payload, dict):
|
|
119
|
+
print("qingflow JSON 根不是 object", file=sys.stderr)
|
|
120
|
+
sys.stdout.buffer.write(buf)
|
|
121
|
+
sys.stdout.flush()
|
|
122
|
+
raise SystemExit(1)
|
|
123
|
+
|
|
124
|
+
merged = _merge_and_dedupe(payload)
|
|
125
|
+
hits = _filtered(kw.lower(), merged)
|
|
126
|
+
out = {"keyword": kw, "match_count": len(hits), "items": hits}
|
|
127
|
+
json.dump(out, sys.stdout, ensure_ascii=False, indent=2)
|
|
128
|
+
sys.stdout.write("\n")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
main()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import glob
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main(argv: list[str] | None = None) -> int:
|
|
13
|
+
parser = argparse.ArgumentParser(description="Validate Qingflow CLI output artifact files.")
|
|
14
|
+
parser.add_argument("paths", nargs="*", help="Output files to validate")
|
|
15
|
+
parser.add_argument("--glob", action="append", default=[], dest="glob_patterns", help="Glob pattern for output files")
|
|
16
|
+
parser.add_argument("--allow-invalid-json", action="store_true", help="Only check file existence and non-zero size")
|
|
17
|
+
args = parser.parse_args(argv)
|
|
18
|
+
|
|
19
|
+
paths = _expand_paths(args.paths, args.glob_patterns)
|
|
20
|
+
issues: list[dict[str, Any]] = []
|
|
21
|
+
if not paths:
|
|
22
|
+
issues.append(_issue("NO_FILES", "$", "no output files were provided"))
|
|
23
|
+
for path in paths:
|
|
24
|
+
issues.extend(validate_output_file(path, require_json=not args.allow_invalid_json))
|
|
25
|
+
|
|
26
|
+
payload: dict[str, Any] = {"status": "failed" if issues else "success", "checked_count": len(paths), "issues": issues}
|
|
27
|
+
if issues:
|
|
28
|
+
payload["error_code"] = "QINGFLOW_OUTPUT_ARTIFACTS_INVALID"
|
|
29
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
30
|
+
return 1 if issues else 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def validate_output_file(path: Path, *, require_json: bool = True) -> list[dict[str, Any]]:
|
|
34
|
+
path_text = str(path)
|
|
35
|
+
if not path.exists():
|
|
36
|
+
return [_issue("OUTPUT_FILE_MISSING", path_text, "output file does not exist")]
|
|
37
|
+
if not path.is_file():
|
|
38
|
+
return [_issue("OUTPUT_PATH_NOT_FILE", path_text, "output path is not a file")]
|
|
39
|
+
try:
|
|
40
|
+
size = path.stat().st_size
|
|
41
|
+
except OSError as exc:
|
|
42
|
+
return [_issue("OUTPUT_FILE_STAT_FAILED", path_text, str(exc))]
|
|
43
|
+
if size == 0:
|
|
44
|
+
return [
|
|
45
|
+
_issue(
|
|
46
|
+
"OUTPUT_FILE_EMPTY",
|
|
47
|
+
path_text,
|
|
48
|
+
"output file is 0 bytes; treat write state as unknown and run readback before retry",
|
|
49
|
+
next_action="readback_before_retry",
|
|
50
|
+
)
|
|
51
|
+
]
|
|
52
|
+
if not require_json:
|
|
53
|
+
return []
|
|
54
|
+
try:
|
|
55
|
+
json.loads(path.read_text(encoding="utf-8"))
|
|
56
|
+
except OSError as exc:
|
|
57
|
+
return [_issue("OUTPUT_FILE_READ_FAILED", path_text, str(exc))]
|
|
58
|
+
except json.JSONDecodeError as exc:
|
|
59
|
+
return [_issue("OUTPUT_FILE_INVALID_JSON", path_text, exc.msg)]
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _expand_paths(raw_paths: list[str], patterns: list[str]) -> list[Path]:
|
|
64
|
+
seen: set[str] = set()
|
|
65
|
+
paths: list[Path] = []
|
|
66
|
+
for raw in raw_paths:
|
|
67
|
+
path = Path(raw)
|
|
68
|
+
key = str(path)
|
|
69
|
+
if key not in seen:
|
|
70
|
+
paths.append(path)
|
|
71
|
+
seen.add(key)
|
|
72
|
+
for pattern in patterns:
|
|
73
|
+
for match in sorted(glob.glob(pattern)):
|
|
74
|
+
path = Path(match)
|
|
75
|
+
key = str(path)
|
|
76
|
+
if key not in seen:
|
|
77
|
+
paths.append(path)
|
|
78
|
+
seen.add(key)
|
|
79
|
+
return paths
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _issue(code: str, path: str, message: str, **extra: Any) -> dict[str, Any]:
|
|
83
|
+
return {"code": code, "path": path, "message": message, **extra}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
sys.exit(main())
|