@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.
Files changed (67) hide show
  1. package/README.md +3 -3
  2. package/npm/bin/qingflow.mjs +40 -2
  3. package/npm/lib/runtime.mjs +386 -15
  4. package/npm/scripts/postinstall.mjs +7 -2
  5. package/package.json +1 -1
  6. package/pyproject.toml +1 -1
  7. package/skills/qingflow-cli/SKILL.md +440 -0
  8. package/skills/qingflow-cli/manifest.yaml +10 -0
  9. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
  10. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +485 -0
  11. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md +237 -0
  12. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_MATCH_RULES.md +137 -0
  13. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md +263 -0
  14. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +304 -0
  15. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md +41 -0
  16. package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
  17. package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
  18. package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
  19. package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
  20. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
  21. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +20 -0
  22. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
  23. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +163 -0
  24. package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +107 -0
  25. package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +151 -0
  26. package/skills/qingflow-cli/reference/_batch_schema_complex.json +18 -0
  27. package/skills/qingflow-cli/reference/_batch_schema_scalar.json +17 -0
  28. package/skills/qingflow-cli/reference/charts_remove.example.json +1 -0
  29. package/skills/qingflow-cli/reference/charts_reorder.example.json +1 -0
  30. package/skills/qingflow-cli/reference/charts_upsert_bar.example.json +8 -0
  31. package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +37 -0
  32. package/skills/qingflow-cli/reference/charts_upsert_minimal.example.json +13 -0
  33. package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +131 -0
  34. package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +126 -0
  35. package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +128 -0
  36. package/skills/qingflow-cli/reference/schema_add_fields_minimal.example.json +7 -0
  37. package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +78 -0
  38. package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +7 -0
  39. package/skills/qingflow-cli/scripts/builder-package-from-app-list.py +140 -0
  40. package/skills/qingflow-cli/scripts/find-app-by-keyword.py +132 -0
  41. package/skills/qingflow-cli/scripts/validate_qingflow_output_files.py +87 -0
  42. package/src/qingflow_mcp/__init__.py +1 -1
  43. package/src/qingflow_mcp/builder_facade/models.py +532 -48
  44. package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
  45. package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
  46. package/src/qingflow_mcp/cli/commands/app.py +3 -16
  47. package/src/qingflow_mcp/cli/commands/builder.py +354 -56
  48. package/src/qingflow_mcp/cli/commands/record.py +89 -2
  49. package/src/qingflow_mcp/cli/formatters.py +32 -1
  50. package/src/qingflow_mcp/cli/main.py +245 -3
  51. package/src/qingflow_mcp/public_surface.py +11 -8
  52. package/src/qingflow_mcp/response_trim.py +143 -14
  53. package/src/qingflow_mcp/server.py +15 -12
  54. package/src/qingflow_mcp/server_app_builder.py +108 -30
  55. package/src/qingflow_mcp/server_app_user.py +17 -18
  56. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  57. package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
  58. package/src/qingflow_mcp/solution/executor.py +3 -133
  59. package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
  60. package/src/qingflow_mcp/tools/app_tools.py +53 -8
  61. package/src/qingflow_mcp/tools/package_tools.py +16 -2
  62. package/src/qingflow_mcp/tools/record_tools.py +2095 -176
  63. package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
  64. package/src/qingflow_mcp/tools/solution_tools.py +30 -2
  65. package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
  66. package/src/qingflow_mcp/version.py +110 -0
  67. 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())
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
 
6
6
  __all__ = ["__version__"]
7
7
 
8
- _FALLBACK_VERSION = "0.2.0b1017"
8
+ _FALLBACK_VERSION = "0.2.0b1099"
9
9
 
10
10
 
11
11
  def _resolve_local_pyproject_version() -> str | None: