@josephyan/qingflow-cli 0.2.0-beta.1017 → 0.2.0-beta.1018

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 CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @josephyan/qingflow-cli@0.2.0-beta.1017
6
+ npm install @josephyan/qingflow-cli@0.2.0-beta.1018
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-cli@0.2.0-beta.1017 qingflow
12
+ npx -y -p @josephyan/qingflow-cli@0.2.0-beta.1018 qingflow
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-cli",
3
- "version": "0.2.0-beta.1017",
3
+ "version": "0.2.0-beta.1018",
4
4
  "description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "0.2.0b1017"
7
+ version = "0.2.0b1018"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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
- icon_color: str | None = Field(default=None, validation_alias=AliasChoices("icon_color", "iconColor"))
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
- icon_color: str | None = Field(default=None, validation_alias=AliasChoices("icon_color", "iconColor"))
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
@@ -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
 
@@ -134,6 +134,7 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
134
134
  PublicToolSpec(BUILDER_DOMAIN, "role_create", ("role_create",), ("builder", "role", "create"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
135
135
  PublicToolSpec(BUILDER_DOMAIN, "app_release_edit_lock_if_mine", ("app_release_edit_lock_if_mine",), ("builder", "app", "release-edit-lock-if-mine"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
136
136
  PublicToolSpec(BUILDER_DOMAIN, "app_resolve", ("app_resolve",), ("builder", "app", "resolve"), has_contract=True, cli_show_effective_context=True),
137
+ PublicToolSpec(BUILDER_DOMAIN, "button_style_catalog_get", ("button_style_catalog_get",), ("builder", "button", "catalog"), has_contract=True, cli_show_effective_context=True),
137
138
  PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_list", ("app_custom_button_list",), ("builder", "button", "list"), has_contract=True, cli_show_effective_context=True),
138
139
  PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_get", ("app_custom_button_get",), ("builder", "button", "get"), has_contract=True, cli_show_effective_context=True),
139
140
  PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_create", ("app_custom_button_create",), ("builder", "button", "create"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
@@ -831,6 +831,7 @@ _register_policy(
831
831
  "role_create",
832
832
  "app_release_edit_lock_if_mine",
833
833
  "app_resolve",
834
+ "button_style_catalog_get",
834
835
  "app_custom_button_list",
835
836
  "app_custom_button_get",
836
837
  "app_custom_button_create",
@@ -290,6 +290,10 @@ def build_builder_server() -> FastMCP:
290
290
  )
291
291
  return ai_builder.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_id=package_id)
292
292
 
293
+ @server.tool()
294
+ def button_style_catalog_get(profile: str = DEFAULT_PROFILE) -> dict:
295
+ return ai_builder.button_style_catalog_get(profile=profile)
296
+
293
297
  @server.tool()
294
298
  def app_custom_button_list(profile: str = DEFAULT_PROFILE, app_key: str = "") -> dict:
295
299
  return ai_builder.app_custom_button_list(profile=profile, app_key=app_key)
@@ -6,6 +6,12 @@ import time
6
6
 
7
7
  from pydantic import ValidationError
8
8
 
9
+ from ..builder_facade.button_style_catalog import (
10
+ BUTTON_BACKGROUND_COLORS,
11
+ BUTTON_ICONS,
12
+ BUTTON_STYLE_PRESETS,
13
+ BUTTON_TEXT_COLORS,
14
+ )
9
15
  from ..public_surface import public_builder_contract_tool_names
10
16
  from ..config import DEFAULT_PROFILE
11
17
  from ..errors import QingflowApiError
@@ -209,6 +215,10 @@ class AiBuilderTools(ToolBase):
209
215
  )
210
216
  return self.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_id=package_id)
211
217
 
218
+ @mcp.tool()
219
+ def button_style_catalog_get(profile: str = DEFAULT_PROFILE) -> JSONObject:
220
+ return self.button_style_catalog_get(profile=profile)
221
+
212
222
  @mcp.tool()
213
223
  def app_custom_button_list(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
214
224
  return self.app_custom_button_list(profile=profile, app_key=app_key)
@@ -846,6 +856,17 @@ class AiBuilderTools(ToolBase):
846
856
  suggested_next_call={"tool_name": "app_resolve", "arguments": {"profile": profile, **normalized_args}},
847
857
  ))
848
858
 
859
+ @tool_cn_name("按钮样式目录")
860
+ def button_style_catalog_get(self, *, profile: str) -> JSONObject:
861
+ """执行按钮样式相关逻辑。"""
862
+ normalized_args: dict[str, object] = {}
863
+ return _safe_tool_call(
864
+ lambda: self._facade.button_style_catalog_get(profile=profile),
865
+ error_code="BUTTON_STYLE_CATALOG_GET_FAILED",
866
+ normalized_args=normalized_args,
867
+ suggested_next_call={"tool_name": "button_style_catalog_get", "arguments": {"profile": profile}},
868
+ )
869
+
849
870
  @tool_cn_name("应用按钮列表")
850
871
  def app_custom_button_list(self, *, profile: str, app_key: str) -> JSONObject:
851
872
  """执行应用相关逻辑。"""
@@ -885,9 +906,8 @@ class AiBuilderTools(ToolBase):
885
906
  "app_key": app_key,
886
907
  "payload": {
887
908
  "button_text": "新增记录",
888
- "background_color": "#FFFFFF",
889
- "text_color": "#494F57",
890
- "button_icon": "ex-add-outlined",
909
+ "style_preset": "primary_blue",
910
+ "button_icon": "ex-plus-circle",
891
911
  "trigger_action": "addData",
892
912
  "trigger_add_data_config": {"related_app_key": "TARGET_APP_KEY", "que_relation": []},
893
913
  },
@@ -920,9 +940,8 @@ class AiBuilderTools(ToolBase):
920
940
  "button_id": button_id,
921
941
  "payload": {
922
942
  "button_text": "新增记录",
923
- "background_color": "#FFFFFF",
924
- "text_color": "#494F57",
925
- "button_icon": "ex-add-outlined",
943
+ "style_preset": "neutral_outline",
944
+ "button_icon": "ex-edit",
926
945
  "trigger_action": "link",
927
946
  "trigger_link_url": "https://example.com",
928
947
  },
@@ -2535,6 +2554,24 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2535
2554
  "lock_owner_name": "当前用户",
2536
2555
  },
2537
2556
  },
2557
+ "button_style_catalog_get": {
2558
+ "allowed_keys": [],
2559
+ "aliases": {},
2560
+ "allowed_values": {
2561
+ "preset.key": [item["key"] for item in BUTTON_STYLE_PRESETS],
2562
+ "icon": list(BUTTON_ICONS),
2563
+ "background_color": list(BUTTON_BACKGROUND_COLORS),
2564
+ "text_color": list(BUTTON_TEXT_COLORS),
2565
+ },
2566
+ "execution_notes": [
2567
+ "use this read-only tool before button writes when an agent needs a supported icon or color choice",
2568
+ "current frontend only supports template icons and template colors from this catalog",
2569
+ "text/icon color is unified through text_color; there is no separate icon_color",
2570
+ ],
2571
+ "minimal_example": {
2572
+ "profile": "default",
2573
+ },
2574
+ },
2538
2575
  "app_custom_button_list": {
2539
2576
  "allowed_keys": ["app_key"],
2540
2577
  "aliases": {},
@@ -2558,10 +2595,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2558
2595
  "allowed_keys": ["app_key", "payload"],
2559
2596
  "aliases": {
2560
2597
  "payload.buttonText": "payload.button_text",
2598
+ "payload.stylePreset": "payload.style_preset",
2561
2599
  "payload.backgroundColor": "payload.background_color",
2562
2600
  "payload.textColor": "payload.text_color",
2563
2601
  "payload.buttonIcon": "payload.button_icon",
2564
- "payload.iconColor": "payload.icon_color",
2565
2602
  "payload.triggerAction": "payload.trigger_action",
2566
2603
  "payload.triggerLinkUrl": "payload.trigger_link_url",
2567
2604
  "payload.triggerAddDataConfig": "payload.trigger_add_data_config",
@@ -2571,10 +2608,15 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2571
2608
  },
2572
2609
  "allowed_values": {
2573
2610
  "payload.trigger_action": [member.value for member in PublicButtonTriggerAction],
2611
+ "payload.style_preset": [item["key"] for item in BUTTON_STYLE_PRESETS],
2612
+ "payload.button_icon": list(BUTTON_ICONS),
2613
+ "payload.background_color": list(BUTTON_BACKGROUND_COLORS),
2614
+ "payload.text_color": list(BUTTON_TEXT_COLORS),
2574
2615
  },
2575
2616
  "execution_notes": [
2576
2617
  "custom button writes now auto-publish the current app draft as a fixed closing step",
2577
2618
  "background_color and text_color cannot both be white",
2619
+ "payload accepts either style_preset + optional button_icon, or explicit button_icon/background_color/text_color from button_style_catalog_get",
2578
2620
  "for addData buttons, put field mappings in payload.trigger_add_data_config.que_relation",
2579
2621
  ],
2580
2622
  "minimal_example": {
@@ -2582,10 +2624,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2582
2624
  "app_key": "APP_KEY",
2583
2625
  "payload": {
2584
2626
  "button_text": "新增记录",
2585
- "background_color": "#FFFFFF",
2586
- "text_color": "#494F57",
2587
- "button_icon": "ex-add-outlined",
2588
- "icon_color": "#494F57",
2627
+ "style_preset": "primary_blue",
2628
+ "button_icon": "ex-plus-circle",
2589
2629
  "trigger_action": "link",
2590
2630
  "trigger_link_url": "https://example.com",
2591
2631
  },
@@ -2596,10 +2636,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2596
2636
  "aliases": {
2597
2637
  "buttonId": "button_id",
2598
2638
  "payload.buttonText": "payload.button_text",
2639
+ "payload.stylePreset": "payload.style_preset",
2599
2640
  "payload.backgroundColor": "payload.background_color",
2600
2641
  "payload.textColor": "payload.text_color",
2601
2642
  "payload.buttonIcon": "payload.button_icon",
2602
- "payload.iconColor": "payload.icon_color",
2603
2643
  "payload.triggerAction": "payload.trigger_action",
2604
2644
  "payload.triggerLinkUrl": "payload.trigger_link_url",
2605
2645
  "payload.triggerAddDataConfig": "payload.trigger_add_data_config",
@@ -2609,10 +2649,15 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2609
2649
  },
2610
2650
  "allowed_values": {
2611
2651
  "payload.trigger_action": [member.value for member in PublicButtonTriggerAction],
2652
+ "payload.style_preset": [item["key"] for item in BUTTON_STYLE_PRESETS],
2653
+ "payload.button_icon": list(BUTTON_ICONS),
2654
+ "payload.background_color": list(BUTTON_BACKGROUND_COLORS),
2655
+ "payload.text_color": list(BUTTON_TEXT_COLORS),
2612
2656
  },
2613
2657
  "execution_notes": [
2614
2658
  "custom button writes now auto-publish the current app draft as a fixed closing step",
2615
2659
  "background_color and text_color cannot both be white",
2660
+ "payload accepts either style_preset + optional button_icon, or explicit button_icon/background_color/text_color from button_style_catalog_get",
2616
2661
  "for addData buttons, put field mappings in payload.trigger_add_data_config.que_relation",
2617
2662
  ],
2618
2663
  "minimal_example": {
@@ -2621,10 +2666,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
2621
2666
  "button_id": 1001,
2622
2667
  "payload": {
2623
2668
  "button_text": "查看详情",
2624
- "background_color": "#FFFFFF",
2625
- "text_color": "#494F57",
2626
- "button_icon": "ex-link-outlined",
2627
- "icon_color": "#494F57",
2669
+ "style_preset": "neutral_outline",
2670
+ "button_icon": "ex-edit",
2628
2671
  "trigger_action": "link",
2629
2672
  "trigger_link_url": "https://example.com/detail",
2630
2673
  },
@@ -169,7 +169,6 @@ class CustomButtonTools(ToolBase):
169
169
  "button_id": item.get("buttonId"),
170
170
  "button_text": item.get("buttonText"),
171
171
  "button_icon": item.get("buttonIcon"),
172
- "icon_color": item.get("iconColor"),
173
172
  "background_color": item.get("backgroundColor"),
174
173
  "text_color": item.get("textColor"),
175
174
  "creator_user_info": {
@@ -189,7 +188,6 @@ class CustomButtonTools(ToolBase):
189
188
  "button_id": item.get("buttonId"),
190
189
  "button_text": item.get("buttonText"),
191
190
  "button_icon": item.get("buttonIcon"),
192
- "icon_color": item.get("iconColor"),
193
191
  "background_color": item.get("backgroundColor"),
194
192
  "text_color": item.get("textColor"),
195
193
  "trigger_action": item.get("triggerAction"),