@josephyan/qingflow-cli 0.2.0-beta.999 → 1.0.5
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/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +109 -0
- package/src/qingflow_mcp/builder_facade/button_style_catalog.py +282 -0
- package/src/qingflow_mcp/builder_facade/models.py +44 -5
- package/src/qingflow_mcp/builder_facade/service.py +21 -8
- package/src/qingflow_mcp/cli/commands/__init__.py +2 -1
- package/src/qingflow_mcp/cli/commands/app.py +47 -1
- package/src/qingflow_mcp/cli/commands/builder.py +7 -0
- package/src/qingflow_mcp/cli/commands/exports.py +111 -0
- package/src/qingflow_mcp/cli/commands/record.py +20 -0
- package/src/qingflow_mcp/cli/commands/task.py +644 -22
- package/src/qingflow_mcp/cli/commands/workspace.py +49 -50
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +139 -4
- package/src/qingflow_mcp/cli/interaction.py +72 -0
- package/src/qingflow_mcp/cli/main.py +2 -0
- package/src/qingflow_mcp/cli/terminal_ui.py +55 -9
- package/src/qingflow_mcp/errors.py +2 -2
- package/src/qingflow_mcp/export_store.py +14 -0
- package/src/qingflow_mcp/public_surface.py +6 -0
- package/src/qingflow_mcp/response_trim.py +40 -1
- package/src/qingflow_mcp/server.py +22 -0
- package/src/qingflow_mcp/server_app_builder.py +4 -0
- package/src/qingflow_mcp/server_app_user.py +104 -8
- package/src/qingflow_mcp/session_store.py +57 -6
- package/src/qingflow_mcp/tools/ai_builder_tools.py +59 -16
- package/src/qingflow_mcp/tools/auth_tools.py +26 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +0 -2
- package/src/qingflow_mcp/tools/export_tools.py +1565 -0
- package/src/qingflow_mcp/tools/import_tools.py +42 -2
- package/src/qingflow_mcp/tools/record_tools.py +551 -16
- package/src/qingflow_mcp/tools/resource_read_tools.py +40 -1
- package/src/qingflow_mcp/tools/task_context_tools.py +26 -8
|
@@ -96,6 +96,9 @@ class ResourceReadTools(ToolBase):
|
|
|
96
96
|
)
|
|
97
97
|
)
|
|
98
98
|
if system_view is not None:
|
|
99
|
+
export_capability = _view_export_capability_payload(
|
|
100
|
+
supported=_export_supported_for_view_type(system_view["view_type"])
|
|
101
|
+
)
|
|
99
102
|
return self._run(
|
|
100
103
|
profile,
|
|
101
104
|
lambda session_profile, _context: {
|
|
@@ -106,11 +109,17 @@ class ResourceReadTools(ToolBase):
|
|
|
106
109
|
{
|
|
107
110
|
"code": "VIEW_APP_KEY_UNRESOLVED",
|
|
108
111
|
"message": f"view_get could not resolve app_key for system view `{view_id}`; keep using the app_key from the parent app context.",
|
|
109
|
-
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"code": "VIEW_EXPORT_APP_CONTEXT_REQUIRED",
|
|
115
|
+
"message": f"view_get supports exporting `{view_id}`, but the export call still needs the parent app context `app_key`.",
|
|
116
|
+
},
|
|
110
117
|
],
|
|
111
118
|
"verification": {
|
|
112
119
|
"view_exists": True,
|
|
113
120
|
"descriptor_only": True,
|
|
121
|
+
"export_route_supported": export_capability["supported"],
|
|
122
|
+
"export_permission_verified": export_capability["permission_verified"],
|
|
114
123
|
},
|
|
115
124
|
"data": {
|
|
116
125
|
"app_key": None,
|
|
@@ -120,11 +129,13 @@ class ResourceReadTools(ToolBase):
|
|
|
120
129
|
"view_type": system_view["view_type"],
|
|
121
130
|
"visible_columns": [],
|
|
122
131
|
"analysis_supported": system_view["analysis_supported"],
|
|
132
|
+
"export_capability": export_capability,
|
|
123
133
|
},
|
|
124
134
|
},
|
|
125
135
|
)
|
|
126
136
|
|
|
127
137
|
def runner(session_profile, context):
|
|
138
|
+
raw_view_type = None
|
|
128
139
|
warnings: list[JSONObject] = []
|
|
129
140
|
verification = {
|
|
130
141
|
"view_exists": True,
|
|
@@ -150,6 +161,11 @@ class ResourceReadTools(ToolBase):
|
|
|
150
161
|
str(base_info.get("viewgraphType") or "").strip()
|
|
151
162
|
or str(config.get("viewgraphType") or config.get("viewType") or "").strip()
|
|
152
163
|
)
|
|
164
|
+
export_capability = _view_export_capability_payload(
|
|
165
|
+
supported=_export_supported_for_view_type(raw_view_type or None)
|
|
166
|
+
)
|
|
167
|
+
verification["export_route_supported"] = export_capability["supported"]
|
|
168
|
+
verification["export_permission_verified"] = export_capability["permission_verified"]
|
|
153
169
|
resolved_app_key = str(base_info.get("appKey") or config.get("appKey") or "").strip() or None
|
|
154
170
|
if not resolved_app_key:
|
|
155
171
|
resolved_app_key = self._resolve_app_key_from_view_form(context=context, view_key=view_key)
|
|
@@ -165,6 +181,13 @@ class ResourceReadTools(ToolBase):
|
|
|
165
181
|
"message": f"view_get could not resolve app_key for `{view_id}` from view metadata; keep using the app_key from the parent app or portal context.",
|
|
166
182
|
}
|
|
167
183
|
)
|
|
184
|
+
if export_capability["supported"]:
|
|
185
|
+
warnings.append(
|
|
186
|
+
{
|
|
187
|
+
"code": "VIEW_EXPORT_APP_CONTEXT_REQUIRED",
|
|
188
|
+
"message": f"view_get supports exporting `{view_id}`, but the export call still needs an explicit `app_key` from the parent app context.",
|
|
189
|
+
}
|
|
190
|
+
)
|
|
168
191
|
return {
|
|
169
192
|
"profile": profile,
|
|
170
193
|
"ws_id": session_profile.selected_ws_id,
|
|
@@ -186,6 +209,7 @@ class ResourceReadTools(ToolBase):
|
|
|
186
209
|
if str(item.get("queTitle") or item.get("title") or "").strip()
|
|
187
210
|
],
|
|
188
211
|
"analysis_supported": _analysis_supported_for_view_type(raw_view_type or None),
|
|
212
|
+
"export_capability": export_capability,
|
|
189
213
|
},
|
|
190
214
|
}
|
|
191
215
|
|
|
@@ -464,6 +488,21 @@ def _lookup_system_view_descriptor(view_id: str) -> dict[str, Any] | None:
|
|
|
464
488
|
return None
|
|
465
489
|
|
|
466
490
|
|
|
491
|
+
def _view_export_capability_payload(*, supported: bool) -> JSONObject:
|
|
492
|
+
return {
|
|
493
|
+
"supported": supported,
|
|
494
|
+
"tool": "record_export_start",
|
|
495
|
+
"format": "xlsx",
|
|
496
|
+
"async": True,
|
|
497
|
+
"requires_app_key": True,
|
|
498
|
+
"permission_verified": False,
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _export_supported_for_view_type(view_type: str | None) -> bool:
|
|
503
|
+
return _analysis_supported_for_view_type(view_type)
|
|
504
|
+
|
|
505
|
+
|
|
467
506
|
def _normalize_view_type(view_type: Any) -> str | None:
|
|
468
507
|
value = str(view_type or "").strip()
|
|
469
508
|
if not value:
|
|
@@ -1762,6 +1762,7 @@ class TaskContextTools(ToolBase):
|
|
|
1762
1762
|
"apply_time": record.get("apply_time"),
|
|
1763
1763
|
"last_update_time": record.get("last_update_time"),
|
|
1764
1764
|
"core_fields": self._task_record_core_fields(record.get("answers") or []),
|
|
1765
|
+
"all_fields": self._task_record_all_fields(record.get("answers") or []),
|
|
1765
1766
|
},
|
|
1766
1767
|
"available_actions": available_actions,
|
|
1767
1768
|
"editable_fields": [
|
|
@@ -1921,9 +1922,21 @@ class TaskContextTools(ToolBase):
|
|
|
1921
1922
|
return None
|
|
1922
1923
|
|
|
1923
1924
|
def _task_record_core_fields(self, answers: Any, *, limit: int = 12) -> dict[str, Any]:
|
|
1925
|
+
return self._task_record_field_map(answers, limit=limit, truncate_text=160)
|
|
1926
|
+
|
|
1927
|
+
def _task_record_all_fields(self, answers: Any) -> dict[str, Any]:
|
|
1928
|
+
return self._task_record_field_map(answers, limit=None, truncate_text=None)
|
|
1929
|
+
|
|
1930
|
+
def _task_record_field_map(
|
|
1931
|
+
self,
|
|
1932
|
+
answers: Any,
|
|
1933
|
+
*,
|
|
1934
|
+
limit: int | None,
|
|
1935
|
+
truncate_text: int | None,
|
|
1936
|
+
) -> dict[str, Any]:
|
|
1924
1937
|
if not isinstance(answers, list):
|
|
1925
1938
|
return {}
|
|
1926
|
-
|
|
1939
|
+
field_map: dict[str, Any] = {}
|
|
1927
1940
|
for answer in answers:
|
|
1928
1941
|
if not isinstance(answer, dict):
|
|
1929
1942
|
continue
|
|
@@ -1943,19 +1956,24 @@ class TaskContextTools(ToolBase):
|
|
|
1943
1956
|
value = values[0] if len(values) == 1 else values
|
|
1944
1957
|
if value in (None, "", []):
|
|
1945
1958
|
continue
|
|
1946
|
-
|
|
1947
|
-
if len(
|
|
1959
|
+
field_map[str(title)] = self._compact_task_value(value, truncate_text=truncate_text)
|
|
1960
|
+
if limit is not None and len(field_map) >= limit:
|
|
1948
1961
|
break
|
|
1949
|
-
return
|
|
1962
|
+
return field_map
|
|
1950
1963
|
|
|
1951
|
-
def _compact_task_value(self, value: Any) -> Any:
|
|
1964
|
+
def _compact_task_value(self, value: Any, *, truncate_text: int | None = 160) -> Any:
|
|
1952
1965
|
if isinstance(value, list):
|
|
1953
|
-
|
|
1966
|
+
items = [self._compact_task_value(item, truncate_text=truncate_text) for item in value]
|
|
1967
|
+
if truncate_text is not None:
|
|
1968
|
+
return items[:8]
|
|
1969
|
+
return items
|
|
1954
1970
|
text = re.sub(r"<[^>]+>", " ", str(value))
|
|
1955
1971
|
text = re.sub(r"\s+", " ", text).strip()
|
|
1956
|
-
if len(text) <=
|
|
1972
|
+
if truncate_text is None or len(text) <= truncate_text:
|
|
1957
1973
|
return text
|
|
1958
|
-
|
|
1974
|
+
if truncate_text <= 3:
|
|
1975
|
+
return text[:truncate_text]
|
|
1976
|
+
return text[: truncate_text - 3].rstrip() + "..."
|
|
1959
1977
|
|
|
1960
1978
|
def _compact_task_editable_field(self, field: dict[str, Any], update_schema: dict[str, Any]) -> dict[str, Any]:
|
|
1961
1979
|
payload_template = update_schema.get("payload_template") if isinstance(update_schema.get("payload_template"), dict) else {}
|