@qingflow-tech/qingflow-app-user-mcp 1.0.3 → 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 +2 -1
- package/skills/qingflow-app-user/SKILL.md +2 -1
- package/skills/qingflow-app-user/references/data-gotchas.md +5 -2
- package/skills/qingflow-app-user/references/public-surface-sync.md +5 -3
- package/skills/qingflow-app-user/references/record-patterns.md +9 -0
- package/skills/qingflow-record-analysis/SKILL.md +103 -166
- package/skills/qingflow-record-analysis/agents/openai.yaml +2 -2
- package/skills/qingflow-record-analysis/references/analysis-gotchas.md +56 -110
- package/skills/qingflow-record-analysis/references/analysis-patterns.md +106 -119
- package/skills/qingflow-record-analysis/references/business-context.md +74 -0
- package/skills/qingflow-record-analysis/references/confidence-reporting.md +49 -72
- package/skills/qingflow-record-analysis/references/data-access-playbook.md +106 -0
- package/skills/qingflow-record-analysis/references/pandas-recipes.md +172 -0
- package/skills/qingflow-record-analysis/references/report-format.md +76 -0
- package/skills/qingflow-record-insert/SKILL.md +2 -2
- package/skills/qingflow-record-update/SKILL.md +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +164 -1
- 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 +44 -5
- package/src/qingflow_mcp/cli/commands/task.py +644 -22
- package/src/qingflow_mcp/cli/commands/workspace.py +64 -2
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +240 -5
- package/src/qingflow_mcp/cli/interaction.py +72 -0
- package/src/qingflow_mcp/cli/main.py +5 -0
- package/src/qingflow_mcp/cli/terminal_ui.py +218 -0
- package/src/qingflow_mcp/errors.py +2 -2
- package/src/qingflow_mcp/export_store.py +14 -0
- package/src/qingflow_mcp/public_surface.py +7 -1
- package/src/qingflow_mcp/response_trim.py +188 -10
- package/src/qingflow_mcp/server.py +37 -9
- package/src/qingflow_mcp/server_app_builder.py +4 -0
- package/src/qingflow_mcp/server_app_user.py +115 -10
- 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 +12793 -8612
- package/src/qingflow_mcp/tools/resource_read_tools.py +40 -1
- package/src/qingflow_mcp/tools/task_context_tools.py +26 -8
- package/skills/qingflow-record-analysis/references/dsl-templates.md +0 -93
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
BUTTON_BACKGROUND_COLORS: tuple[str, ...] = (
|
|
7
|
+
"transparent",
|
|
8
|
+
"#FB9337",
|
|
9
|
+
"#FA6F32",
|
|
10
|
+
"#FAB300",
|
|
11
|
+
"#67C200",
|
|
12
|
+
"#00BD77",
|
|
13
|
+
"#00C5FB",
|
|
14
|
+
"#268BFB",
|
|
15
|
+
"#001A72",
|
|
16
|
+
"#9E64FB",
|
|
17
|
+
"#D164FB",
|
|
18
|
+
"#FB4B51",
|
|
19
|
+
"#FFFFFF",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
BUTTON_TEXT_COLORS: tuple[str, ...] = (
|
|
23
|
+
"#FB9337",
|
|
24
|
+
"#FA6F32",
|
|
25
|
+
"#FAB300",
|
|
26
|
+
"#67C200",
|
|
27
|
+
"#00BD77",
|
|
28
|
+
"#00C5FB",
|
|
29
|
+
"#268BFB",
|
|
30
|
+
"#001A72",
|
|
31
|
+
"#9E64FB",
|
|
32
|
+
"#D164FB",
|
|
33
|
+
"#FB4B51",
|
|
34
|
+
"#494F57",
|
|
35
|
+
"#FFFFFF",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
BUTTON_ICONS: tuple[str, ...] = (
|
|
39
|
+
"ex-print",
|
|
40
|
+
"ex-attachment",
|
|
41
|
+
"ex-handoff",
|
|
42
|
+
"ex-delete",
|
|
43
|
+
"ex-copy",
|
|
44
|
+
"ex-share",
|
|
45
|
+
"ex-edit2",
|
|
46
|
+
"ex-admin-outlined",
|
|
47
|
+
"ex-stamp",
|
|
48
|
+
"ex-kanban",
|
|
49
|
+
"ex-update",
|
|
50
|
+
"ex-tickincircle-outlined",
|
|
51
|
+
"ex-crossincircle-outlined",
|
|
52
|
+
"ex-plus-circle",
|
|
53
|
+
"ex-logout",
|
|
54
|
+
"ex-check",
|
|
55
|
+
"ex-rename",
|
|
56
|
+
"ex-locked",
|
|
57
|
+
"ex-shift",
|
|
58
|
+
"ex-new-tabpage",
|
|
59
|
+
"ex-cross",
|
|
60
|
+
"ex-switch",
|
|
61
|
+
"ex-layer",
|
|
62
|
+
"ex-import",
|
|
63
|
+
"ex-pin-outlined",
|
|
64
|
+
"ex-disabled",
|
|
65
|
+
"ex-duplicate",
|
|
66
|
+
"ex-heart-outlined",
|
|
67
|
+
"ex-plus",
|
|
68
|
+
"ex-save",
|
|
69
|
+
"ex-upload",
|
|
70
|
+
"ex-upgrade1",
|
|
71
|
+
"ex-downgrade",
|
|
72
|
+
"ex-unfold",
|
|
73
|
+
"ex-fold",
|
|
74
|
+
"ex-sort",
|
|
75
|
+
"ex-menu-control",
|
|
76
|
+
"ex-download",
|
|
77
|
+
"ex-insert-below",
|
|
78
|
+
"ex-left-outlined-double",
|
|
79
|
+
"ex-right-outlined-double",
|
|
80
|
+
"ex-recovery",
|
|
81
|
+
"ex-layout",
|
|
82
|
+
"ex-search",
|
|
83
|
+
"ex-preview",
|
|
84
|
+
"ex-invisible",
|
|
85
|
+
"ex-carboncopy",
|
|
86
|
+
"ex-basicInfo",
|
|
87
|
+
"ex-fillIn",
|
|
88
|
+
"ex-refresh",
|
|
89
|
+
"ex-display",
|
|
90
|
+
"ex-message",
|
|
91
|
+
"ex-edit",
|
|
92
|
+
"ex-heart-filled",
|
|
93
|
+
"ex-pin-filled",
|
|
94
|
+
"ex-transfer",
|
|
95
|
+
"ex-cross-circle",
|
|
96
|
+
"ex-clock",
|
|
97
|
+
"ex-admin-filled",
|
|
98
|
+
"ex-tickincircle-circle",
|
|
99
|
+
"ex-all-application",
|
|
100
|
+
"ex-help-filled",
|
|
101
|
+
"ex-streamline",
|
|
102
|
+
"ex-table",
|
|
103
|
+
"ex-rowheight-short",
|
|
104
|
+
"ex-rowheight-medium",
|
|
105
|
+
"ex-rowheight-tall",
|
|
106
|
+
"ex-rowheight-tallest",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
BUTTON_STYLE_PRESETS: tuple[dict[str, Any], ...] = (
|
|
110
|
+
{
|
|
111
|
+
"key": "primary_blue",
|
|
112
|
+
"label": "Primary Blue",
|
|
113
|
+
"button_type": "default",
|
|
114
|
+
"background_color": "#268BFB",
|
|
115
|
+
"text_color": "#FFFFFF",
|
|
116
|
+
"recommended_icons": ["ex-plus-circle", "ex-plus"],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"key": "text_blue",
|
|
120
|
+
"label": "Text Blue",
|
|
121
|
+
"button_type": "text",
|
|
122
|
+
"background_color": "transparent",
|
|
123
|
+
"text_color": "#268BFB",
|
|
124
|
+
"recommended_icons": ["ex-share", "ex-new-tabpage"],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"key": "warning_orange",
|
|
128
|
+
"label": "Warning Orange",
|
|
129
|
+
"button_type": "default",
|
|
130
|
+
"background_color": "#FB9337",
|
|
131
|
+
"text_color": "#FFFFFF",
|
|
132
|
+
"recommended_icons": ["ex-message", "ex-clock"],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"key": "danger_red",
|
|
136
|
+
"label": "Danger Red",
|
|
137
|
+
"button_type": "default",
|
|
138
|
+
"background_color": "#FB4B51",
|
|
139
|
+
"text_color": "#FFFFFF",
|
|
140
|
+
"recommended_icons": ["ex-delete", "ex-cross-circle"],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"key": "neutral_outline",
|
|
144
|
+
"label": "Neutral Outline",
|
|
145
|
+
"button_type": "default",
|
|
146
|
+
"background_color": "#FFFFFF",
|
|
147
|
+
"text_color": "#494F57",
|
|
148
|
+
"recommended_icons": ["ex-edit", "ex-search"],
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"key": "secondary_gray",
|
|
152
|
+
"label": "Secondary Gray",
|
|
153
|
+
"button_type": "text",
|
|
154
|
+
"background_color": "transparent",
|
|
155
|
+
"text_color": "#494F57",
|
|
156
|
+
"recommended_icons": ["ex-edit", "ex-search"],
|
|
157
|
+
},
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
_PRESET_BY_KEY: dict[str, dict[str, Any]] = {
|
|
161
|
+
str(item["key"]).strip(): dict(item) for item in BUTTON_STYLE_PRESETS
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_COLOR_FAMILY_BY_VALUE: dict[str, str] = {
|
|
165
|
+
"transparent": "neutral",
|
|
166
|
+
"#FB9337": "orange",
|
|
167
|
+
"#FA6F32": "orange",
|
|
168
|
+
"#FAB300": "yellow",
|
|
169
|
+
"#67C200": "green",
|
|
170
|
+
"#00BD77": "green",
|
|
171
|
+
"#00C5FB": "cyan",
|
|
172
|
+
"#268BFB": "blue",
|
|
173
|
+
"#001A72": "blue",
|
|
174
|
+
"#9E64FB": "purple",
|
|
175
|
+
"#D164FB": "purple",
|
|
176
|
+
"#FB4B51": "red",
|
|
177
|
+
"#FFFFFF": "white",
|
|
178
|
+
"#494F57": "gray",
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _normalize_color_value(value: str | None) -> str | None:
|
|
183
|
+
normalized = str(value or "").strip()
|
|
184
|
+
if not normalized:
|
|
185
|
+
return None
|
|
186
|
+
lowered = normalized.lower().replace(" ", "")
|
|
187
|
+
if lowered == "transparent":
|
|
188
|
+
return "transparent"
|
|
189
|
+
if lowered in {
|
|
190
|
+
"#fff",
|
|
191
|
+
"#ffffff",
|
|
192
|
+
"white",
|
|
193
|
+
"rgb(255,255,255)",
|
|
194
|
+
"rgba(255,255,255,1)",
|
|
195
|
+
}:
|
|
196
|
+
return "#FFFFFF"
|
|
197
|
+
if normalized.startswith("#"):
|
|
198
|
+
return normalized.upper()
|
|
199
|
+
return normalized
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def button_style_catalog_payload() -> dict[str, Any]:
|
|
203
|
+
return {
|
|
204
|
+
"icons": [{"value": icon, "label": icon} for icon in BUTTON_ICONS],
|
|
205
|
+
"background_colors": [
|
|
206
|
+
{
|
|
207
|
+
"value": color,
|
|
208
|
+
"label": color,
|
|
209
|
+
"family": _COLOR_FAMILY_BY_VALUE.get(color, "custom"),
|
|
210
|
+
}
|
|
211
|
+
for color in BUTTON_BACKGROUND_COLORS
|
|
212
|
+
],
|
|
213
|
+
"text_colors": [
|
|
214
|
+
{
|
|
215
|
+
"value": color,
|
|
216
|
+
"label": color,
|
|
217
|
+
"family": _COLOR_FAMILY_BY_VALUE.get(color, "custom"),
|
|
218
|
+
}
|
|
219
|
+
for color in BUTTON_TEXT_COLORS
|
|
220
|
+
],
|
|
221
|
+
"presets": [deepcopy(item) for item in BUTTON_STYLE_PRESETS],
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def resolve_button_style(
|
|
226
|
+
*,
|
|
227
|
+
style_preset: str | None,
|
|
228
|
+
button_icon: str | None,
|
|
229
|
+
background_color: str | None,
|
|
230
|
+
text_color: str | None,
|
|
231
|
+
require_complete_style: bool,
|
|
232
|
+
) -> dict[str, str | None]:
|
|
233
|
+
preset_key = str(style_preset or "").strip() or None
|
|
234
|
+
resolved_icon = str(button_icon or "").strip() or None
|
|
235
|
+
resolved_background = _normalize_color_value(background_color)
|
|
236
|
+
resolved_text = _normalize_color_value(text_color)
|
|
237
|
+
|
|
238
|
+
if preset_key is not None:
|
|
239
|
+
preset = _PRESET_BY_KEY.get(preset_key)
|
|
240
|
+
if preset is None:
|
|
241
|
+
raise ValueError(
|
|
242
|
+
"unsupported style_preset; use button_style_catalog_get to inspect available presets"
|
|
243
|
+
)
|
|
244
|
+
if resolved_background is None:
|
|
245
|
+
resolved_background = str(preset["background_color"])
|
|
246
|
+
if resolved_text is None:
|
|
247
|
+
resolved_text = str(preset["text_color"])
|
|
248
|
+
if resolved_icon is None:
|
|
249
|
+
recommended = preset.get("recommended_icons") or []
|
|
250
|
+
if recommended:
|
|
251
|
+
resolved_icon = str(recommended[0]).strip() or None
|
|
252
|
+
|
|
253
|
+
if require_complete_style:
|
|
254
|
+
missing: list[str] = []
|
|
255
|
+
if resolved_icon is None:
|
|
256
|
+
missing.append("button_icon")
|
|
257
|
+
if resolved_background is None:
|
|
258
|
+
missing.append("background_color")
|
|
259
|
+
if resolved_text is None:
|
|
260
|
+
missing.append("text_color")
|
|
261
|
+
if missing:
|
|
262
|
+
raise ValueError(f"button style requires {', '.join(missing)}")
|
|
263
|
+
|
|
264
|
+
if resolved_icon is not None and resolved_icon not in BUTTON_ICONS:
|
|
265
|
+
raise ValueError(
|
|
266
|
+
"unsupported button_icon; use button_style_catalog_get to inspect available icons"
|
|
267
|
+
)
|
|
268
|
+
if resolved_background is not None and resolved_background not in BUTTON_BACKGROUND_COLORS:
|
|
269
|
+
raise ValueError(
|
|
270
|
+
"unsupported background_color; current frontend only supports template colors from button_style_catalog_get"
|
|
271
|
+
)
|
|
272
|
+
if resolved_text is not None and resolved_text not in BUTTON_TEXT_COLORS:
|
|
273
|
+
raise ValueError(
|
|
274
|
+
"unsupported text_color; current frontend only supports template colors from button_style_catalog_get"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
"style_preset": preset_key,
|
|
279
|
+
"button_icon": resolved_icon,
|
|
280
|
+
"background_color": resolved_background,
|
|
281
|
+
"text_color": resolved_text,
|
|
282
|
+
}
|
|
@@ -5,6 +5,7 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
from pydantic import AliasChoices, Field, model_validator
|
|
7
7
|
|
|
8
|
+
from .button_style_catalog import resolve_button_style
|
|
8
9
|
from ..solution.spec_models import StrictModel
|
|
9
10
|
|
|
10
11
|
|
|
@@ -1180,10 +1181,10 @@ def _is_white_button_color(value: str | None) -> bool:
|
|
|
1180
1181
|
|
|
1181
1182
|
class CustomButtonPatch(StrictModel):
|
|
1182
1183
|
button_text: str = Field(validation_alias=AliasChoices("button_text", "buttonText"))
|
|
1183
|
-
background_color: str = Field(validation_alias=AliasChoices("background_color", "backgroundColor"))
|
|
1184
|
-
text_color: str = Field(validation_alias=AliasChoices("text_color", "textColor"))
|
|
1185
|
-
button_icon: str = Field(validation_alias=AliasChoices("button_icon", "buttonIcon"))
|
|
1186
|
-
|
|
1184
|
+
background_color: str | None = Field(default=None, validation_alias=AliasChoices("background_color", "backgroundColor"))
|
|
1185
|
+
text_color: str | None = Field(default=None, validation_alias=AliasChoices("text_color", "textColor"))
|
|
1186
|
+
button_icon: str | None = Field(default=None, validation_alias=AliasChoices("button_icon", "buttonIcon"))
|
|
1187
|
+
style_preset: str | None = Field(default=None, validation_alias=AliasChoices("style_preset", "stylePreset"))
|
|
1187
1188
|
trigger_action: PublicButtonTriggerAction = Field(validation_alias=AliasChoices("trigger_action", "triggerAction"))
|
|
1188
1189
|
trigger_link_url: str | None = Field(default=None, validation_alias=AliasChoices("trigger_link_url", "triggerLinkUrl"))
|
|
1189
1190
|
trigger_add_data_config: CustomButtonAddDataConfigPatch | None = Field(
|
|
@@ -1204,8 +1205,29 @@ class CustomButtonPatch(StrictModel):
|
|
|
1204
1205
|
validation_alias=AliasChoices("trigger_wings_config", "triggerWingsConfig"),
|
|
1205
1206
|
)
|
|
1206
1207
|
|
|
1208
|
+
@model_validator(mode="before")
|
|
1209
|
+
@classmethod
|
|
1210
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1211
|
+
if not isinstance(value, dict):
|
|
1212
|
+
return value
|
|
1213
|
+
payload = dict(value)
|
|
1214
|
+
if "icon_color" in payload or "iconColor" in payload:
|
|
1215
|
+
raise ValueError("icon_color is not supported; icon color follows text_color")
|
|
1216
|
+
return payload
|
|
1217
|
+
|
|
1207
1218
|
@model_validator(mode="after")
|
|
1208
1219
|
def validate_shape(self) -> "CustomButtonPatch":
|
|
1220
|
+
resolved = resolve_button_style(
|
|
1221
|
+
style_preset=self.style_preset,
|
|
1222
|
+
button_icon=self.button_icon,
|
|
1223
|
+
background_color=self.background_color,
|
|
1224
|
+
text_color=self.text_color,
|
|
1225
|
+
require_complete_style=True,
|
|
1226
|
+
)
|
|
1227
|
+
self.style_preset = resolved["style_preset"]
|
|
1228
|
+
self.button_icon = resolved["button_icon"]
|
|
1229
|
+
self.background_color = resolved["background_color"]
|
|
1230
|
+
self.text_color = resolved["text_color"]
|
|
1209
1231
|
if self.trigger_action == PublicButtonTriggerAction.link and not str(self.trigger_link_url or "").strip():
|
|
1210
1232
|
raise ValueError("link buttons require trigger_link_url")
|
|
1211
1233
|
if self.trigger_action == PublicButtonTriggerAction.add_data and self.trigger_add_data_config is None:
|
|
@@ -1225,7 +1247,7 @@ class ViewButtonBindingPatch(StrictModel):
|
|
|
1225
1247
|
button_id: int = Field(validation_alias=AliasChoices("button_id", "buttonId", "id"))
|
|
1226
1248
|
button_text: str | None = Field(default=None, validation_alias=AliasChoices("button_text", "buttonText"))
|
|
1227
1249
|
button_icon: str | None = Field(default=None, validation_alias=AliasChoices("button_icon", "buttonIcon"))
|
|
1228
|
-
|
|
1250
|
+
style_preset: str | None = Field(default=None, validation_alias=AliasChoices("style_preset", "stylePreset"))
|
|
1229
1251
|
background_color: str | None = Field(default=None, validation_alias=AliasChoices("background_color", "backgroundColor"))
|
|
1230
1252
|
text_color: str | None = Field(default=None, validation_alias=AliasChoices("text_color", "textColor"))
|
|
1231
1253
|
trigger_action: str | None = Field(default=None, validation_alias=AliasChoices("trigger_action", "triggerAction"))
|
|
@@ -1244,6 +1266,8 @@ class ViewButtonBindingPatch(StrictModel):
|
|
|
1244
1266
|
if not isinstance(value, dict):
|
|
1245
1267
|
return value
|
|
1246
1268
|
payload = dict(value)
|
|
1269
|
+
if "icon_color" in payload or "iconColor" in payload:
|
|
1270
|
+
raise ValueError("icon_color is not supported; icon color follows text_color")
|
|
1247
1271
|
raw_button_type = payload.get("button_type", payload.get("buttonType"))
|
|
1248
1272
|
if isinstance(raw_button_type, str):
|
|
1249
1273
|
normalized_type = raw_button_type.strip().lower()
|
|
@@ -1265,6 +1289,21 @@ class ViewButtonBindingPatch(StrictModel):
|
|
|
1265
1289
|
|
|
1266
1290
|
@model_validator(mode="after")
|
|
1267
1291
|
def validate_shape(self) -> "ViewButtonBindingPatch":
|
|
1292
|
+
require_complete_style = self.button_type == PublicViewButtonType.system or any(
|
|
1293
|
+
str(value or "").strip()
|
|
1294
|
+
for value in (self.style_preset, self.button_icon, self.background_color, self.text_color)
|
|
1295
|
+
)
|
|
1296
|
+
resolved = resolve_button_style(
|
|
1297
|
+
style_preset=self.style_preset,
|
|
1298
|
+
button_icon=self.button_icon,
|
|
1299
|
+
background_color=self.background_color,
|
|
1300
|
+
text_color=self.text_color,
|
|
1301
|
+
require_complete_style=require_complete_style,
|
|
1302
|
+
)
|
|
1303
|
+
self.style_preset = resolved["style_preset"]
|
|
1304
|
+
self.button_icon = resolved["button_icon"]
|
|
1305
|
+
self.background_color = resolved["background_color"]
|
|
1306
|
+
self.text_color = resolved["text_color"]
|
|
1268
1307
|
if self.button_type == PublicViewButtonType.system:
|
|
1269
1308
|
missing = [
|
|
1270
1309
|
field_name
|
|
@@ -34,6 +34,7 @@ from ..tools.role_tools import RoleTools
|
|
|
34
34
|
from ..tools.solution_tools import SolutionTools
|
|
35
35
|
from ..tools.view_tools import ViewTools
|
|
36
36
|
from ..tools.workflow_tools import WorkflowTools
|
|
37
|
+
from .button_style_catalog import button_style_catalog_payload
|
|
37
38
|
from .models import (
|
|
38
39
|
AppChartsReadResponse,
|
|
39
40
|
AppFieldsReadResponse,
|
|
@@ -2353,6 +2354,26 @@ class AiBuilderFacade:
|
|
|
2353
2354
|
**match,
|
|
2354
2355
|
}
|
|
2355
2356
|
|
|
2357
|
+
def button_style_catalog_get(self, *, profile: str) -> JSONObject:
|
|
2358
|
+
return {
|
|
2359
|
+
"status": "success",
|
|
2360
|
+
"error_code": None,
|
|
2361
|
+
"recoverable": False,
|
|
2362
|
+
"message": "read button style catalog",
|
|
2363
|
+
"normalized_args": {},
|
|
2364
|
+
"missing_fields": [],
|
|
2365
|
+
"allowed_values": {},
|
|
2366
|
+
"details": {},
|
|
2367
|
+
"request_id": None,
|
|
2368
|
+
"suggested_next_call": None,
|
|
2369
|
+
"noop": False,
|
|
2370
|
+
"warnings": [],
|
|
2371
|
+
"verification": {"button_style_catalog_verified": True},
|
|
2372
|
+
"verified": True,
|
|
2373
|
+
"profile": profile,
|
|
2374
|
+
**button_style_catalog_payload(),
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2356
2377
|
def app_custom_button_list(self, *, profile: str, app_key: str) -> JSONObject:
|
|
2357
2378
|
normalized_args = {"app_key": app_key}
|
|
2358
2379
|
permission_outcomes: list[PermissionCheckOutcome] = []
|
|
@@ -8035,8 +8056,6 @@ def _serialize_custom_button_payload(payload: CustomButtonPatch) -> dict[str, An
|
|
|
8035
8056
|
"buttonIcon": data["button_icon"],
|
|
8036
8057
|
"triggerAction": data["trigger_action"],
|
|
8037
8058
|
}
|
|
8038
|
-
if str(data.get("icon_color") or "").strip():
|
|
8039
|
-
serialized["iconColor"] = data["icon_color"]
|
|
8040
8059
|
if str(data.get("trigger_link_url") or "").strip():
|
|
8041
8060
|
serialized["triggerLinkUrl"] = data["trigger_link_url"]
|
|
8042
8061
|
trigger_add_data_config = data.get("trigger_add_data_config")
|
|
@@ -8126,7 +8145,6 @@ def _normalize_custom_button_summary(item: dict[str, Any]) -> dict[str, Any]:
|
|
|
8126
8145
|
"button_id": _coerce_positive_int(item.get("button_id") or item.get("buttonId") or item.get("id")),
|
|
8127
8146
|
"button_text": str(item.get("button_text") or item.get("buttonText") or "").strip() or None,
|
|
8128
8147
|
"button_icon": str(item.get("button_icon") or item.get("buttonIcon") or "").strip() or None,
|
|
8129
|
-
"icon_color": str(item.get("icon_color") or item.get("iconColor") or "").strip() or None,
|
|
8130
8148
|
"background_color": str(item.get("background_color") or item.get("backgroundColor") or "").strip() or None,
|
|
8131
8149
|
"text_color": str(item.get("text_color") or item.get("textColor") or "").strip() or None,
|
|
8132
8150
|
"used_in_chart_count": _coerce_nonnegative_int(item.get("used_in_chart_count") or item.get("userInChartCount")),
|
|
@@ -14887,7 +14905,6 @@ def _normalize_view_button_entry(entry: dict[str, Any]) -> dict[str, Any]:
|
|
|
14887
14905
|
for public_key, source_key in (
|
|
14888
14906
|
("default_button_text", "defaultButtonText"),
|
|
14889
14907
|
("button_icon", "buttonIcon"),
|
|
14890
|
-
("icon_color", "iconColor"),
|
|
14891
14908
|
("background_color", "backgroundColor"),
|
|
14892
14909
|
("text_color", "textColor"),
|
|
14893
14910
|
("trigger_link_url", "triggerLinkUrl"),
|
|
@@ -14925,7 +14942,6 @@ def _normalize_view_buttons_for_compare(value: Any) -> list[dict[str, Any]]:
|
|
|
14925
14942
|
"button_id": normalized.get("button_id") if is_custom else None,
|
|
14926
14943
|
"button_text": normalized.get("button_text"),
|
|
14927
14944
|
"button_icon": normalized.get("button_icon"),
|
|
14928
|
-
"icon_color": normalized.get("icon_color"),
|
|
14929
14945
|
"background_color": normalized.get("background_color"),
|
|
14930
14946
|
"text_color": normalized.get("text_color"),
|
|
14931
14947
|
"trigger_action": normalized.get("trigger_action"),
|
|
@@ -15000,7 +15016,6 @@ def _normalize_expected_view_buttons_for_compare(
|
|
|
15000
15016
|
for key in (
|
|
15001
15017
|
"button_text",
|
|
15002
15018
|
"button_icon",
|
|
15003
|
-
"icon_color",
|
|
15004
15019
|
"background_color",
|
|
15005
15020
|
"text_color",
|
|
15006
15021
|
"trigger_action",
|
|
@@ -15183,8 +15198,6 @@ def _serialize_view_button_binding(
|
|
|
15183
15198
|
if binding.button_type in {PublicViewButtonType.system, PublicViewButtonType.custom}:
|
|
15184
15199
|
dto["buttonText"] = binding.button_text
|
|
15185
15200
|
dto["buttonIcon"] = binding.button_icon
|
|
15186
|
-
if str(binding.icon_color or "").strip():
|
|
15187
|
-
dto["iconColor"] = binding.icon_color
|
|
15188
15201
|
dto["backgroundColor"] = binding.background_color
|
|
15189
15202
|
dto["textColor"] = binding.text_color
|
|
15190
15203
|
dto["triggerAction"] = binding.trigger_action
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
|
|
5
|
-
from . import app, auth, builder, chart, imports, portal, record, task, view, workspace
|
|
5
|
+
from . import app, auth, builder, chart, exports, imports, portal, record, task, view, workspace
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def register_all_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
@@ -14,5 +14,6 @@ def register_all_commands(subparsers: argparse._SubParsersAction[argparse.Argume
|
|
|
14
14
|
chart.register(subparsers)
|
|
15
15
|
record.register(subparsers)
|
|
16
16
|
imports.register(subparsers)
|
|
17
|
+
exports.register(subparsers)
|
|
17
18
|
task.register(subparsers)
|
|
18
19
|
builder.register(subparsers)
|
|
@@ -3,6 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
|
|
5
5
|
from ..context import CliContext
|
|
6
|
+
from ..interaction import cancelled_result, resolve_interactive_selection
|
|
7
|
+
from ..terminal_ui import SelectionOption
|
|
8
|
+
from .common import raise_config_error
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
@@ -19,7 +22,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
19
22
|
search.set_defaults(handler=_handle_search, format_hint="app_search")
|
|
20
23
|
|
|
21
24
|
get = app_subparsers.add_parser("get", help="读取应用可访问视图与导入能力")
|
|
22
|
-
get.add_argument("--app-key",
|
|
25
|
+
get.add_argument("--app-key", help="不传时在交互终端中选择应用")
|
|
23
26
|
get.set_defaults(handler=_handle_get, format_hint="app_get")
|
|
24
27
|
|
|
25
28
|
|
|
@@ -37,4 +40,47 @@ def _handle_search(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
43
|
+
if not (args.app_key or "").strip():
|
|
44
|
+
selection = _choose_app_interactively(args, context)
|
|
45
|
+
if selection.status == "unavailable":
|
|
46
|
+
raise_config_error(
|
|
47
|
+
"app get requires --app-key, or an interactive terminal to choose an app",
|
|
48
|
+
fix_hint="Run `app list` to inspect visible apps, or retry with `--app-key APP_KEY`.",
|
|
49
|
+
)
|
|
50
|
+
if selection.status == "empty":
|
|
51
|
+
raise_config_error(
|
|
52
|
+
selection.message or "app get could not open a selector because no visible apps were returned.",
|
|
53
|
+
fix_hint="Run `app list` to confirm visible apps, or retry with `--app-key APP_KEY`.",
|
|
54
|
+
)
|
|
55
|
+
if selection.status == "cancelled":
|
|
56
|
+
return cancelled_result(selection.message or "已取消")
|
|
57
|
+
args.app_key = str(selection.value or "")
|
|
40
58
|
return context.app.app_get(profile=args.profile, app_key=args.app_key)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _choose_app_interactively(args: argparse.Namespace, context: CliContext):
|
|
62
|
+
def load_options() -> list[SelectionOption[str]]:
|
|
63
|
+
result = context.app.app_list(profile=args.profile)
|
|
64
|
+
items = result.get("items") if isinstance(result, dict) and isinstance(result.get("items"), list) else []
|
|
65
|
+
options: list[SelectionOption[str]] = []
|
|
66
|
+
for item in items:
|
|
67
|
+
if not isinstance(item, dict):
|
|
68
|
+
continue
|
|
69
|
+
app_key = str(item.get("app_key") or "").strip()
|
|
70
|
+
if not app_key:
|
|
71
|
+
continue
|
|
72
|
+
app_name = str(item.get("app_name") or app_key).strip() or app_key
|
|
73
|
+
package_name = str(item.get("package_name") or "").strip()
|
|
74
|
+
hint = f"app_key={app_key}"
|
|
75
|
+
if package_name:
|
|
76
|
+
hint += f" · package={package_name}"
|
|
77
|
+
options.append(SelectionOption(value=app_key, label=app_name, hint=hint))
|
|
78
|
+
return options
|
|
79
|
+
|
|
80
|
+
return resolve_interactive_selection(
|
|
81
|
+
args,
|
|
82
|
+
title="选择应用",
|
|
83
|
+
unavailable_message="app get requires --app-key, or an interactive terminal to choose an app",
|
|
84
|
+
empty_message="app get could not open a selector because no visible apps were returned.",
|
|
85
|
+
load_options=load_options,
|
|
86
|
+
)
|
|
@@ -120,6 +120,9 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
120
120
|
button = builder_subparsers.add_parser("button", help="自定义按钮")
|
|
121
121
|
button_subparsers = button.add_subparsers(dest="builder_button_command", required=True)
|
|
122
122
|
|
|
123
|
+
button_catalog = button_subparsers.add_parser("catalog", help="读取按钮样式目录")
|
|
124
|
+
button_catalog.set_defaults(handler=_handle_button_catalog, format_hint="builder_summary")
|
|
125
|
+
|
|
123
126
|
button_list = button_subparsers.add_parser("list", help="列出自定义按钮")
|
|
124
127
|
button_list.add_argument("--app-key", required=True)
|
|
125
128
|
button_list.set_defaults(handler=_handle_button_list, format_hint="builder_summary")
|
|
@@ -362,6 +365,10 @@ def _handle_button_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
362
365
|
return context.builder.app_custom_button_list(profile=args.profile, app_key=args.app_key)
|
|
363
366
|
|
|
364
367
|
|
|
368
|
+
def _handle_button_catalog(args: argparse.Namespace, context: CliContext) -> dict:
|
|
369
|
+
return context.builder.button_style_catalog_get(profile=args.profile)
|
|
370
|
+
|
|
371
|
+
|
|
365
372
|
def _handle_button_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
366
373
|
return context.builder.app_custom_button_get(profile=args.profile, app_key=args.app_key, button_id=args.button_id)
|
|
367
374
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ..context import CliContext
|
|
6
|
+
from .common import load_list_arg
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
10
|
+
parser = subparsers.add_parser("export", help="导出")
|
|
11
|
+
export_subparsers = parser.add_subparsers(dest="export_command", required=True)
|
|
12
|
+
|
|
13
|
+
start = export_subparsers.add_parser("start", help="启动导出")
|
|
14
|
+
start.add_argument("--app-key", required=True)
|
|
15
|
+
start.add_argument("--view-id", default="system:all")
|
|
16
|
+
start.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
|
|
17
|
+
start.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
|
|
18
|
+
start.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
|
|
19
|
+
start.add_argument("--order-by-file", help="JSON/YAML list,内容与 record list 的 order_by DSL 一致;内部查询和导出记录顺序保持一致")
|
|
20
|
+
start.add_argument("--record-id", dest="record_ids", action="append", default=[], help="只导出这些 record_id;不传时导出当前视图全部数据")
|
|
21
|
+
start.add_argument("--record-ids-file", help="JSON/YAML list,内容与 --record-id 语义一致")
|
|
22
|
+
start.add_argument("--include-workflow-log", action=argparse.BooleanOptionalAction, default=False, help="是否同时导出流程日志")
|
|
23
|
+
start.set_defaults(handler=_handle_start, format_hint="export_start")
|
|
24
|
+
|
|
25
|
+
status = export_subparsers.add_parser("status", help="查询导出状态")
|
|
26
|
+
status.add_argument("--export-handle", required=True)
|
|
27
|
+
status.set_defaults(handler=_handle_status, format_hint="export_status")
|
|
28
|
+
|
|
29
|
+
get = export_subparsers.add_parser("get", help="获取导出结果")
|
|
30
|
+
get.add_argument("--export-handle", required=True)
|
|
31
|
+
get.add_argument("--download-to-path")
|
|
32
|
+
get.set_defaults(handler=_handle_get, format_hint="export_get")
|
|
33
|
+
|
|
34
|
+
direct = export_subparsers.add_parser("direct", help="直接导出并下载")
|
|
35
|
+
direct.add_argument("--app-key", required=True)
|
|
36
|
+
direct.add_argument("--view-id", default="system:all")
|
|
37
|
+
direct.add_argument("--column", dest="columns", action="append", type=int, default=[], help="只导出这些 field_id;不传时导出当前视图全部字段")
|
|
38
|
+
direct.add_argument("--columns-file", help="JSON/YAML list,内容与 --column 语义一致")
|
|
39
|
+
direct.add_argument("--where-file", help="JSON/YAML list,内容与 record list 的 where DSL 一致;内部先查命中 record_id 再走原生导出")
|
|
40
|
+
direct.add_argument("--order-by-file", help="JSON/YAML list,内容与 record list 的 order_by DSL 一致;内部查询和导出记录顺序保持一致")
|
|
41
|
+
direct.add_argument("--record-id", dest="record_ids", action="append", default=[], help="只导出这些 record_id;不传时导出当前视图全部数据")
|
|
42
|
+
direct.add_argument("--record-ids-file", help="JSON/YAML list,内容与 --record-id 语义一致")
|
|
43
|
+
direct.add_argument("--include-workflow-log", action=argparse.BooleanOptionalAction, default=False, help="是否同时导出流程日志")
|
|
44
|
+
direct.add_argument("--download-to-path")
|
|
45
|
+
direct.add_argument("--wait-timeout-seconds", type=float)
|
|
46
|
+
direct.set_defaults(handler=_handle_direct, format_hint="export_direct")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _columns(args: argparse.Namespace) -> list[int | dict]:
|
|
50
|
+
columns: list[int | dict] = list(args.columns or [])
|
|
51
|
+
if args.columns_file:
|
|
52
|
+
columns.extend(load_list_arg(args.columns_file, option_name="--columns-file"))
|
|
53
|
+
return columns
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _record_ids(args: argparse.Namespace) -> list[str | int]:
|
|
57
|
+
record_ids: list[str | int] = list(args.record_ids or [])
|
|
58
|
+
if args.record_ids_file:
|
|
59
|
+
record_ids.extend(load_list_arg(args.record_ids_file, option_name="--record-ids-file"))
|
|
60
|
+
return record_ids
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _where(args: argparse.Namespace) -> list[dict]:
|
|
64
|
+
return load_list_arg(args.where_file, option_name="--where-file") if args.where_file else []
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _order_by(args: argparse.Namespace) -> list[dict]:
|
|
68
|
+
return load_list_arg(args.order_by_file, option_name="--order-by-file") if args.order_by_file else []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _handle_start(args: argparse.Namespace, context: CliContext) -> dict:
|
|
72
|
+
return context.exports.record_export_start(
|
|
73
|
+
profile=args.profile,
|
|
74
|
+
app_key=args.app_key,
|
|
75
|
+
view_id=args.view_id,
|
|
76
|
+
columns=_columns(args),
|
|
77
|
+
where=_where(args),
|
|
78
|
+
order_by=_order_by(args),
|
|
79
|
+
record_ids=_record_ids(args),
|
|
80
|
+
include_workflow_log=args.include_workflow_log,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _handle_status(args: argparse.Namespace, context: CliContext) -> dict:
|
|
85
|
+
return context.exports.record_export_status_get(
|
|
86
|
+
profile=args.profile,
|
|
87
|
+
export_handle=args.export_handle,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
92
|
+
return context.exports.record_export_get(
|
|
93
|
+
profile=args.profile,
|
|
94
|
+
export_handle=args.export_handle,
|
|
95
|
+
download_to_path=args.download_to_path,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _handle_direct(args: argparse.Namespace, context: CliContext) -> dict:
|
|
100
|
+
return context.exports.record_export_direct(
|
|
101
|
+
profile=args.profile,
|
|
102
|
+
app_key=args.app_key,
|
|
103
|
+
view_id=args.view_id,
|
|
104
|
+
columns=_columns(args),
|
|
105
|
+
where=_where(args),
|
|
106
|
+
order_by=_order_by(args),
|
|
107
|
+
record_ids=_record_ids(args),
|
|
108
|
+
include_workflow_log=args.include_workflow_log,
|
|
109
|
+
download_to_path=args.download_to_path,
|
|
110
|
+
wait_timeout_seconds=args.wait_timeout_seconds,
|
|
111
|
+
)
|