@josephyan/qingflow-cli 0.2.0-beta.55
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 +30 -0
- package/docs/local-agent-install.md +235 -0
- package/entry_point.py +13 -0
- package/npm/bin/qingflow.mjs +5 -0
- package/npm/lib/runtime.mjs +204 -0
- package/npm/scripts/postinstall.mjs +16 -0
- package/package.json +34 -0
- package/pyproject.toml +67 -0
- package/qingflow +15 -0
- package/src/qingflow_mcp/__init__.py +5 -0
- package/src/qingflow_mcp/__main__.py +5 -0
- package/src/qingflow_mcp/backend_client.py +547 -0
- package/src/qingflow_mcp/builder_facade/__init__.py +3 -0
- package/src/qingflow_mcp/builder_facade/models.py +985 -0
- package/src/qingflow_mcp/builder_facade/service.py +8243 -0
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +78 -0
- package/src/qingflow_mcp/cli/commands/builder.py +184 -0
- package/src/qingflow_mcp/cli/commands/common.py +47 -0
- package/src/qingflow_mcp/cli/commands/imports.py +86 -0
- package/src/qingflow_mcp/cli/commands/record.py +202 -0
- package/src/qingflow_mcp/cli/commands/task.py +87 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
- package/src/qingflow_mcp/cli/context.py +48 -0
- package/src/qingflow_mcp/cli/formatters.py +269 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +147 -0
- package/src/qingflow_mcp/config.py +221 -0
- package/src/qingflow_mcp/errors.py +66 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/json_types.py +18 -0
- package/src/qingflow_mcp/list_type_labels.py +76 -0
- package/src/qingflow_mcp/server.py +211 -0
- package/src/qingflow_mcp/server_app_builder.py +387 -0
- package/src/qingflow_mcp/server_app_user.py +317 -0
- package/src/qingflow_mcp/session_store.py +289 -0
- package/src/qingflow_mcp/solution/__init__.py +6 -0
- package/src/qingflow_mcp/solution/build_assembly_store.py +181 -0
- package/src/qingflow_mcp/solution/compiler/__init__.py +282 -0
- package/src/qingflow_mcp/solution/compiler/chart_compiler.py +96 -0
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +466 -0
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +113 -0
- package/src/qingflow_mcp/solution/compiler/navigation_compiler.py +57 -0
- package/src/qingflow_mcp/solution/compiler/package_compiler.py +19 -0
- package/src/qingflow_mcp/solution/compiler/portal_compiler.py +60 -0
- package/src/qingflow_mcp/solution/compiler/view_compiler.py +51 -0
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
- package/src/qingflow_mcp/solution/design_session.py +222 -0
- package/src/qingflow_mcp/solution/design_store.py +100 -0
- package/src/qingflow_mcp/solution/executor.py +2339 -0
- package/src/qingflow_mcp/solution/normalizer.py +23 -0
- package/src/qingflow_mcp/solution/requirements_builder.py +536 -0
- package/src/qingflow_mcp/solution/run_store.py +244 -0
- package/src/qingflow_mcp/solution/spec_models.py +853 -0
- package/src/qingflow_mcp/tools/__init__.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2063 -0
- package/src/qingflow_mcp/tools/app_tools.py +850 -0
- package/src/qingflow_mcp/tools/approval_tools.py +833 -0
- package/src/qingflow_mcp/tools/auth_tools.py +697 -0
- package/src/qingflow_mcp/tools/base.py +81 -0
- package/src/qingflow_mcp/tools/code_block_tools.py +679 -0
- package/src/qingflow_mcp/tools/directory_tools.py +648 -0
- package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
- package/src/qingflow_mcp/tools/file_tools.py +385 -0
- package/src/qingflow_mcp/tools/import_tools.py +1971 -0
- package/src/qingflow_mcp/tools/navigation_tools.py +177 -0
- package/src/qingflow_mcp/tools/package_tools.py +240 -0
- package/src/qingflow_mcp/tools/portal_tools.py +131 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +269 -0
- package/src/qingflow_mcp/tools/record_tools.py +12739 -0
- package/src/qingflow_mcp/tools/role_tools.py +94 -0
- package/src/qingflow_mcp/tools/solution_tools.py +3887 -0
- package/src/qingflow_mcp/tools/task_context_tools.py +1423 -0
- package/src/qingflow_mcp/tools/task_tools.py +843 -0
- package/src/qingflow_mcp/tools/view_tools.py +280 -0
- package/src/qingflow_mcp/tools/workflow_tools.py +312 -0
- package/src/qingflow_mcp/tools/workspace_tools.py +219 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from mcp.server.fastmcp import FastMCP
|
|
6
|
+
|
|
7
|
+
from ..backend_client import BackendRequestContext
|
|
8
|
+
from ..config import DEFAULT_PROFILE
|
|
9
|
+
from ..errors import QingflowApiError, raise_tool_error
|
|
10
|
+
from .base import ToolBase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WorkspaceTools(ToolBase):
|
|
14
|
+
def register(self, mcp: FastMCP) -> None:
|
|
15
|
+
@mcp.tool()
|
|
16
|
+
def workspace_list(
|
|
17
|
+
profile: str = DEFAULT_PROFILE,
|
|
18
|
+
page_num: int = 1,
|
|
19
|
+
page_size: int = 20,
|
|
20
|
+
include_external: bool = False,
|
|
21
|
+
) -> dict[str, Any]:
|
|
22
|
+
return self.workspace_list(
|
|
23
|
+
profile=profile,
|
|
24
|
+
page_num=page_num,
|
|
25
|
+
page_size=page_size,
|
|
26
|
+
include_external=include_external,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@mcp.tool()
|
|
30
|
+
def workspace_select(profile: str = DEFAULT_PROFILE, ws_id: int = 0) -> dict[str, Any]:
|
|
31
|
+
return self.workspace_select(profile=profile, ws_id=ws_id)
|
|
32
|
+
|
|
33
|
+
@mcp.tool()
|
|
34
|
+
def workspace_set_plugin_status(
|
|
35
|
+
profile: str = DEFAULT_PROFILE,
|
|
36
|
+
plugin_id: int = 0,
|
|
37
|
+
being_installed: bool = True,
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
return self.workspace_set_plugin_status(
|
|
40
|
+
profile=profile,
|
|
41
|
+
plugin_id=plugin_id,
|
|
42
|
+
being_installed=being_installed,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def workspace_list(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
profile: str = DEFAULT_PROFILE,
|
|
49
|
+
page_num: int = 1,
|
|
50
|
+
page_size: int = 20,
|
|
51
|
+
include_external: bool = False,
|
|
52
|
+
) -> dict[str, Any]:
|
|
53
|
+
if page_num <= 0 or page_size <= 0:
|
|
54
|
+
raise_tool_error(QingflowApiError.config_error("page_num and page_size must be positive"))
|
|
55
|
+
|
|
56
|
+
def runner(_, context):
|
|
57
|
+
path = "/user/allWorkspaceList/pageQuery" if include_external else "/user/workspaceList/pageQuery"
|
|
58
|
+
result = self.backend.request(
|
|
59
|
+
"POST",
|
|
60
|
+
context,
|
|
61
|
+
path,
|
|
62
|
+
json_body={"pageNum": page_num, "pageSize": page_size, "authList": [0, 1, 2]},
|
|
63
|
+
)
|
|
64
|
+
normalized = result if isinstance(result, dict) else {"list": result if isinstance(result, list) else []}
|
|
65
|
+
workspace_list = normalized.get("list") if isinstance(normalized.get("list"), list) else []
|
|
66
|
+
selected_ws_id = _.selected_ws_id if _ is not None else None
|
|
67
|
+
if selected_ws_id and not any(isinstance(item, dict) and item.get("wsId") == selected_ws_id for item in workspace_list):
|
|
68
|
+
try:
|
|
69
|
+
selected_workspace = self.backend.request("GET", context, f"/user/workspace/{selected_ws_id}")
|
|
70
|
+
except QingflowApiError:
|
|
71
|
+
selected_workspace = {
|
|
72
|
+
"wsId": selected_ws_id,
|
|
73
|
+
"workspaceName": _.selected_ws_name if _ is not None else None,
|
|
74
|
+
}
|
|
75
|
+
if isinstance(selected_workspace, dict):
|
|
76
|
+
workspace_list.append(selected_workspace)
|
|
77
|
+
normalized["list"] = workspace_list
|
|
78
|
+
total = normalized.get("total")
|
|
79
|
+
if isinstance(total, int):
|
|
80
|
+
normalized["total"] = total + 1
|
|
81
|
+
return {
|
|
82
|
+
"profile": profile,
|
|
83
|
+
"include_external": include_external,
|
|
84
|
+
"page": normalized,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return self._run(profile, runner, require_workspace=False)
|
|
88
|
+
|
|
89
|
+
def workspace_select(self, *, profile: str = DEFAULT_PROFILE, ws_id: int) -> dict[str, Any]:
|
|
90
|
+
if ws_id <= 0:
|
|
91
|
+
raise_tool_error(QingflowApiError.config_error("ws_id must be positive"))
|
|
92
|
+
|
|
93
|
+
def runner(_, context):
|
|
94
|
+
# Create a context with the target ws_id for the API call
|
|
95
|
+
# This is necessary because the API requires wsId header even when getting workspace details
|
|
96
|
+
call_context = BackendRequestContext(
|
|
97
|
+
base_url=context.base_url,
|
|
98
|
+
token=context.token,
|
|
99
|
+
ws_id=ws_id,
|
|
100
|
+
qf_version=context.qf_version,
|
|
101
|
+
qf_version_source=context.qf_version_source,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
result = self.backend.request("GET", call_context, f"/user/workspace/{ws_id}")
|
|
106
|
+
except QingflowApiError as e:
|
|
107
|
+
if e.http_status == 404:
|
|
108
|
+
result = self._workspace_from_list(call_context, ws_id) or {"wsId": ws_id, "workspaceName": f"Workspace {ws_id}"}
|
|
109
|
+
else:
|
|
110
|
+
raise
|
|
111
|
+
|
|
112
|
+
if isinstance(result, dict):
|
|
113
|
+
ws_name = (
|
|
114
|
+
str(result.get("workspaceName") or result.get("wsName") or "").strip()
|
|
115
|
+
or None
|
|
116
|
+
)
|
|
117
|
+
if ws_name is None:
|
|
118
|
+
fallback_workspace = self._workspace_from_list(call_context, ws_id)
|
|
119
|
+
if isinstance(fallback_workspace, dict):
|
|
120
|
+
result = fallback_workspace
|
|
121
|
+
ws_name = (
|
|
122
|
+
str(result.get("workspaceName") or result.get("wsName") or "").strip()
|
|
123
|
+
or None
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
ws_name = None
|
|
127
|
+
if ws_name is None:
|
|
128
|
+
ws_name = f"Workspace {ws_id}"
|
|
129
|
+
session_profile = self.sessions.select_workspace(profile, ws_id=ws_id, ws_name=ws_name)
|
|
130
|
+
workspace_qf_version = self._workspace_system_version(result)
|
|
131
|
+
should_refresh_route = (
|
|
132
|
+
workspace_qf_version is not None
|
|
133
|
+
and (context.qf_version_source or "unset") != "explicit"
|
|
134
|
+
and workspace_qf_version != session_profile.qf_version
|
|
135
|
+
)
|
|
136
|
+
if should_refresh_route:
|
|
137
|
+
session_profile = self.sessions.update_route(
|
|
138
|
+
profile,
|
|
139
|
+
qf_version=workspace_qf_version,
|
|
140
|
+
qf_version_source="workspace_system_version",
|
|
141
|
+
)
|
|
142
|
+
return {
|
|
143
|
+
"profile": profile,
|
|
144
|
+
"selected_ws_id": session_profile.selected_ws_id,
|
|
145
|
+
"selected_ws_name": session_profile.selected_ws_name,
|
|
146
|
+
"workspace": result,
|
|
147
|
+
"qf_version": session_profile.qf_version,
|
|
148
|
+
"qf_version_source": session_profile.qf_version_source,
|
|
149
|
+
"request_route": self.backend.describe_route(
|
|
150
|
+
BackendRequestContext(
|
|
151
|
+
base_url=session_profile.base_url,
|
|
152
|
+
token=context.token,
|
|
153
|
+
ws_id=session_profile.selected_ws_id,
|
|
154
|
+
qf_version=session_profile.qf_version,
|
|
155
|
+
qf_version_source=session_profile.qf_version_source,
|
|
156
|
+
)
|
|
157
|
+
),
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return self._run(profile, runner, require_workspace=False)
|
|
161
|
+
|
|
162
|
+
def _workspace_from_list(self, context: BackendRequestContext, ws_id: int) -> dict[str, Any] | None:
|
|
163
|
+
try:
|
|
164
|
+
payload = self.backend.request(
|
|
165
|
+
"POST",
|
|
166
|
+
context,
|
|
167
|
+
"/user/workspaceList/pageQuery",
|
|
168
|
+
json_body={"pageNum": 1, "pageSize": 100, "authList": [0, 1, 2]},
|
|
169
|
+
)
|
|
170
|
+
except Exception:
|
|
171
|
+
return None
|
|
172
|
+
workspaces = payload.get("list", []) if isinstance(payload, dict) else []
|
|
173
|
+
found = next(
|
|
174
|
+
(
|
|
175
|
+
item
|
|
176
|
+
for item in workspaces
|
|
177
|
+
if isinstance(item, dict) and item.get("wsId") == ws_id
|
|
178
|
+
),
|
|
179
|
+
None,
|
|
180
|
+
)
|
|
181
|
+
return found if isinstance(found, dict) else None
|
|
182
|
+
|
|
183
|
+
def _workspace_system_version(self, workspace: Any) -> str | None:
|
|
184
|
+
if not isinstance(workspace, dict):
|
|
185
|
+
return None
|
|
186
|
+
value = workspace.get("systemVersion")
|
|
187
|
+
if value is None:
|
|
188
|
+
return None
|
|
189
|
+
normalized = str(value).strip()
|
|
190
|
+
return normalized or None
|
|
191
|
+
|
|
192
|
+
def workspace_set_plugin_status(
|
|
193
|
+
self,
|
|
194
|
+
*,
|
|
195
|
+
profile: str = DEFAULT_PROFILE,
|
|
196
|
+
plugin_id: int,
|
|
197
|
+
being_installed: bool = True,
|
|
198
|
+
) -> dict[str, Any]:
|
|
199
|
+
if plugin_id <= 0:
|
|
200
|
+
raise_tool_error(QingflowApiError.config_error("plugin_id must be positive"))
|
|
201
|
+
|
|
202
|
+
def runner(_, context):
|
|
203
|
+
result = self.backend.request(
|
|
204
|
+
"POST",
|
|
205
|
+
context,
|
|
206
|
+
"/ws/plugin",
|
|
207
|
+
json_body={
|
|
208
|
+
"pluginId": plugin_id,
|
|
209
|
+
"beingInstalled": being_installed,
|
|
210
|
+
},
|
|
211
|
+
)
|
|
212
|
+
return {
|
|
213
|
+
"profile": profile,
|
|
214
|
+
"plugin_id": plugin_id,
|
|
215
|
+
"being_installed": being_installed,
|
|
216
|
+
"result": result,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return self._run(profile, runner)
|