@josephyan/qingflow-cli 0.2.0-beta.62 → 0.2.0-beta.64

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.
@@ -7,7 +7,7 @@ from .common import load_list_arg, load_object_arg, require_list_arg
7
7
 
8
8
 
9
9
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
10
- parser = subparsers.add_parser("builder", help="稳定 builder 命令")
10
+ parser = subparsers.add_parser("builder", aliases=["build"], help="稳定 builder 命令")
11
11
  builder_subparsers = parser.add_subparsers(dest="builder_command", required=True)
12
12
 
13
13
  package = builder_subparsers.add_parser("package", help="应用包")
@@ -25,6 +25,34 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
25
25
  app_resolve.add_argument("--package-tag-id", type=int)
26
26
  app_resolve.set_defaults(handler=_handle_app_resolve, format_hint="builder_summary")
27
27
 
28
+ button = builder_subparsers.add_parser("button", help="自定义按钮")
29
+ button_subparsers = button.add_subparsers(dest="builder_button_command", required=True)
30
+
31
+ button_list = button_subparsers.add_parser("list", help="列出自定义按钮")
32
+ button_list.add_argument("--app-key", required=True)
33
+ button_list.set_defaults(handler=_handle_button_list, format_hint="builder_summary")
34
+
35
+ button_get = button_subparsers.add_parser("get", help="读取自定义按钮")
36
+ button_get.add_argument("--app-key", required=True)
37
+ button_get.add_argument("--button-id", type=int, required=True)
38
+ button_get.set_defaults(handler=_handle_button_get, format_hint="builder_summary")
39
+
40
+ button_create = button_subparsers.add_parser("create", help="创建自定义按钮")
41
+ button_create.add_argument("--app-key", required=True)
42
+ button_create.add_argument("--payload-file", required=True)
43
+ button_create.set_defaults(handler=_handle_button_create, format_hint="builder_summary")
44
+
45
+ button_update = button_subparsers.add_parser("update", help="更新自定义按钮")
46
+ button_update.add_argument("--app-key", required=True)
47
+ button_update.add_argument("--button-id", type=int, required=True)
48
+ button_update.add_argument("--payload-file", required=True)
49
+ button_update.set_defaults(handler=_handle_button_update, format_hint="builder_summary")
50
+
51
+ button_delete = button_subparsers.add_parser("delete", help="删除自定义按钮")
52
+ button_delete.add_argument("--app-key", required=True)
53
+ button_delete.add_argument("--button-id", type=int, required=True)
54
+ button_delete.set_defaults(handler=_handle_button_delete, format_hint="builder_summary")
55
+
28
56
  for name, help_text, handler in [
29
57
  ("read-summary", "读取应用摘要", _handle_app_read_summary),
30
58
  ("read-fields", "读取字段摘要", _handle_app_read_fields),
@@ -102,6 +130,35 @@ def _handle_app_resolve(args: argparse.Namespace, context: CliContext) -> dict:
102
130
  )
103
131
 
104
132
 
133
+ def _handle_button_list(args: argparse.Namespace, context: CliContext) -> dict:
134
+ return context.builder.app_custom_button_list(profile=args.profile, app_key=args.app_key)
135
+
136
+
137
+ def _handle_button_get(args: argparse.Namespace, context: CliContext) -> dict:
138
+ return context.builder.app_custom_button_get(profile=args.profile, app_key=args.app_key, button_id=args.button_id)
139
+
140
+
141
+ def _handle_button_create(args: argparse.Namespace, context: CliContext) -> dict:
142
+ return context.builder.app_custom_button_create(
143
+ profile=args.profile,
144
+ app_key=args.app_key,
145
+ payload=load_object_arg(args.payload_file, option_name="--payload-file"),
146
+ )
147
+
148
+
149
+ def _handle_button_update(args: argparse.Namespace, context: CliContext) -> dict:
150
+ return context.builder.app_custom_button_update(
151
+ profile=args.profile,
152
+ app_key=args.app_key,
153
+ button_id=args.button_id,
154
+ payload=load_object_arg(args.payload_file, option_name="--payload-file"),
155
+ )
156
+
157
+
158
+ def _handle_button_delete(args: argparse.Namespace, context: CliContext) -> dict:
159
+ return context.builder.app_custom_button_delete(profile=args.profile, app_key=args.app_key, button_id=args.button_id)
160
+
161
+
105
162
  def _handle_app_read_summary(args: argparse.Namespace, context: CliContext) -> dict:
106
163
  return context.builder.app_read_summary(profile=args.profile, app_key=args.app_key)
107
164
 
@@ -216,6 +216,31 @@ def build_builder_server() -> FastMCP:
216
216
  ) -> dict:
217
217
  return ai_builder.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_tag_id=package_tag_id)
218
218
 
219
+ @server.tool()
220
+ def app_custom_button_list(profile: str = DEFAULT_PROFILE, app_key: str = "") -> dict:
221
+ return ai_builder.app_custom_button_list(profile=profile, app_key=app_key)
222
+
223
+ @server.tool()
224
+ def app_custom_button_get(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> dict:
225
+ return ai_builder.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id)
226
+
227
+ @server.tool()
228
+ def app_custom_button_create(profile: str = DEFAULT_PROFILE, app_key: str = "", payload: dict | None = None) -> dict:
229
+ return ai_builder.app_custom_button_create(profile=profile, app_key=app_key, payload=payload or {})
230
+
231
+ @server.tool()
232
+ def app_custom_button_update(
233
+ profile: str = DEFAULT_PROFILE,
234
+ app_key: str = "",
235
+ button_id: int = 0,
236
+ payload: dict | None = None,
237
+ ) -> dict:
238
+ return ai_builder.app_custom_button_update(profile=profile, app_key=app_key, button_id=button_id, payload=payload or {})
239
+
240
+ @server.tool()
241
+ def app_custom_button_delete(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> dict:
242
+ return ai_builder.app_custom_button_delete(profile=profile, app_key=app_key, button_id=button_id)
243
+
219
244
  @server.tool()
220
245
  def app_read_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> dict:
221
246
  return ai_builder.app_read_summary(profile=profile, app_key=app_key)
@@ -11,6 +11,7 @@ from ..errors import QingflowApiError
11
11
  from ..json_types import JSONObject
12
12
  from ..builder_facade.models import (
13
13
  ChartApplyRequest,
14
+ CustomButtonPatch,
14
15
  FIELD_TYPE_ID_ALIASES,
15
16
  FieldPatch,
16
17
  FieldRemovePatch,
@@ -24,6 +25,7 @@ from ..builder_facade.models import (
24
25
  LayoutPreset,
25
26
  LayoutSectionPatch,
26
27
  PortalApplyRequest,
28
+ PublicButtonTriggerAction,
27
29
  PublicFieldType,
28
30
  PublicRelationMode,
29
31
  PublicChartType,
@@ -37,6 +39,7 @@ from ..builder_facade.models import (
37
39
  from ..builder_facade.service import AiBuilderFacade
38
40
  from .app_tools import AppTools
39
41
  from .base import ToolBase
42
+ from .custom_button_tools import CustomButtonTools
40
43
  from .directory_tools import DirectoryTools
41
44
  from .package_tools import PackageTools
42
45
  from .portal_tools import PortalTools
@@ -54,6 +57,7 @@ class AiBuilderTools(ToolBase):
54
57
  super().__init__(sessions, backend)
55
58
  self._facade = AiBuilderFacade(
56
59
  apps=AppTools(sessions, backend),
60
+ buttons=CustomButtonTools(sessions, backend),
57
61
  packages=PackageTools(sessions, backend),
58
62
  views=ViewTools(sessions, backend),
59
63
  workflows=WorkflowTools(sessions, backend),
@@ -156,6 +160,35 @@ class AiBuilderTools(ToolBase):
156
160
  ) -> JSONObject:
157
161
  return self.app_resolve(profile=profile, app_key=app_key, app_name=app_name, package_tag_id=package_tag_id)
158
162
 
163
+ @mcp.tool()
164
+ def app_custom_button_list(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
165
+ return self.app_custom_button_list(profile=profile, app_key=app_key)
166
+
167
+ @mcp.tool()
168
+ def app_custom_button_get(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> JSONObject:
169
+ return self.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id)
170
+
171
+ @mcp.tool()
172
+ def app_custom_button_create(
173
+ profile: str = DEFAULT_PROFILE,
174
+ app_key: str = "",
175
+ payload: JSONObject | None = None,
176
+ ) -> JSONObject:
177
+ return self.app_custom_button_create(profile=profile, app_key=app_key, payload=payload or {})
178
+
179
+ @mcp.tool()
180
+ def app_custom_button_update(
181
+ profile: str = DEFAULT_PROFILE,
182
+ app_key: str = "",
183
+ button_id: int = 0,
184
+ payload: JSONObject | None = None,
185
+ ) -> JSONObject:
186
+ return self.app_custom_button_update(profile=profile, app_key=app_key, button_id=button_id, payload=payload or {})
187
+
188
+ @mcp.tool()
189
+ def app_custom_button_delete(profile: str = DEFAULT_PROFILE, app_key: str = "", button_id: int = 0) -> JSONObject:
190
+ return self.app_custom_button_delete(profile=profile, app_key=app_key, button_id=button_id)
191
+
159
192
  @mcp.tool()
160
193
  def app_read_summary(profile: str = DEFAULT_PROFILE, app_key: str = "") -> JSONObject:
161
194
  return self.app_read_summary(profile=profile, app_key=app_key)
@@ -518,6 +551,98 @@ class AiBuilderTools(ToolBase):
518
551
  suggested_next_call={"tool_name": "app_resolve", "arguments": {"profile": profile, **normalized_args}},
519
552
  )
520
553
 
554
+ def app_custom_button_list(self, *, profile: str, app_key: str) -> JSONObject:
555
+ normalized_args = {"app_key": app_key}
556
+ return _safe_tool_call(
557
+ lambda: self._facade.app_custom_button_list(profile=profile, app_key=app_key),
558
+ error_code="CUSTOM_BUTTON_LIST_FAILED",
559
+ normalized_args=normalized_args,
560
+ suggested_next_call={"tool_name": "app_custom_button_list", "arguments": {"profile": profile, **normalized_args}},
561
+ )
562
+
563
+ def app_custom_button_get(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
564
+ normalized_args = {"app_key": app_key, "button_id": button_id}
565
+ return _safe_tool_call(
566
+ lambda: self._facade.app_custom_button_get(profile=profile, app_key=app_key, button_id=button_id),
567
+ error_code="CUSTOM_BUTTON_GET_FAILED",
568
+ normalized_args=normalized_args,
569
+ suggested_next_call={"tool_name": "app_custom_button_get", "arguments": {"profile": profile, **normalized_args}},
570
+ )
571
+
572
+ def app_custom_button_create(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
573
+ try:
574
+ request = CustomButtonPatch.model_validate(payload)
575
+ except ValidationError as exc:
576
+ return _validation_failure(
577
+ str(exc),
578
+ tool_name="app_custom_button_create",
579
+ exc=exc,
580
+ suggested_next_call={
581
+ "tool_name": "app_custom_button_create",
582
+ "arguments": {
583
+ "profile": profile,
584
+ "app_key": app_key,
585
+ "payload": {
586
+ "button_text": "新增记录",
587
+ "background_color": "#FFFFFF",
588
+ "text_color": "#494F57",
589
+ "button_icon": "ex-add-outlined",
590
+ "trigger_action": "addData",
591
+ "trigger_add_data_config": {"related_app_key": "TARGET_APP_KEY", "que_relation": []},
592
+ },
593
+ },
594
+ },
595
+ )
596
+ normalized_args = {"app_key": app_key, "payload": request.model_dump(mode="json")}
597
+ return _safe_tool_call(
598
+ lambda: self._facade.app_custom_button_create(profile=profile, app_key=app_key, payload=request),
599
+ error_code="CUSTOM_BUTTON_CREATE_FAILED",
600
+ normalized_args=normalized_args,
601
+ suggested_next_call={"tool_name": "app_custom_button_create", "arguments": {"profile": profile, **normalized_args}},
602
+ )
603
+
604
+ def app_custom_button_update(self, *, profile: str, app_key: str, button_id: int, payload: JSONObject) -> JSONObject:
605
+ try:
606
+ request = CustomButtonPatch.model_validate(payload)
607
+ except ValidationError as exc:
608
+ return _validation_failure(
609
+ str(exc),
610
+ tool_name="app_custom_button_update",
611
+ exc=exc,
612
+ suggested_next_call={
613
+ "tool_name": "app_custom_button_update",
614
+ "arguments": {
615
+ "profile": profile,
616
+ "app_key": app_key,
617
+ "button_id": button_id,
618
+ "payload": {
619
+ "button_text": "新增记录",
620
+ "background_color": "#FFFFFF",
621
+ "text_color": "#494F57",
622
+ "button_icon": "ex-add-outlined",
623
+ "trigger_action": "link",
624
+ "trigger_link_url": "https://example.com",
625
+ },
626
+ },
627
+ },
628
+ )
629
+ normalized_args = {"app_key": app_key, "button_id": button_id, "payload": request.model_dump(mode="json")}
630
+ return _safe_tool_call(
631
+ lambda: self._facade.app_custom_button_update(profile=profile, app_key=app_key, button_id=button_id, payload=request),
632
+ error_code="CUSTOM_BUTTON_UPDATE_FAILED",
633
+ normalized_args=normalized_args,
634
+ suggested_next_call={"tool_name": "app_custom_button_update", "arguments": {"profile": profile, **normalized_args}},
635
+ )
636
+
637
+ def app_custom_button_delete(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
638
+ normalized_args = {"app_key": app_key, "button_id": button_id}
639
+ return _safe_tool_call(
640
+ lambda: self._facade.app_custom_button_delete(profile=profile, app_key=app_key, button_id=button_id),
641
+ error_code="CUSTOM_BUTTON_DELETE_FAILED",
642
+ normalized_args=normalized_args,
643
+ suggested_next_call={"tool_name": "app_custom_button_delete", "arguments": {"profile": profile, **normalized_args}},
644
+ )
645
+
521
646
  def app_read_summary(self, *, profile: str, app_key: str) -> JSONObject:
522
647
  normalized_args = {"app_key": app_key}
523
648
  return _safe_tool_call(
@@ -1531,6 +1656,11 @@ def _public_error_message(error_code: str, error: QingflowApiError) -> str:
1531
1656
  "PACKAGE_RESOLVE_FAILED": "package resolution is unavailable in the current route",
1532
1657
  "PACKAGE_ATTACH_FAILED": "package attachment could not be verified in the current route",
1533
1658
  "APP_RESOLVE_FAILED": "app resolution is unavailable in the current route",
1659
+ "CUSTOM_BUTTON_LIST_FAILED": "custom button list is unavailable in the current route",
1660
+ "CUSTOM_BUTTON_GET_FAILED": "custom button detail is unavailable in the current route",
1661
+ "CUSTOM_BUTTON_CREATE_FAILED": "custom button create could not complete because the route or readback is unavailable",
1662
+ "CUSTOM_BUTTON_UPDATE_FAILED": "custom button update could not complete because the route or readback is unavailable",
1663
+ "CUSTOM_BUTTON_DELETE_FAILED": "custom button delete could not complete because the route is unavailable",
1534
1664
  "APP_READ_FAILED": "app base or schema is unavailable in the current route",
1535
1665
  "FIELDS_READ_FAILED": "app fields are unavailable in the current route",
1536
1666
  "LAYOUT_READ_FAILED": "layout resource is unavailable for this app in the current route",
@@ -1588,6 +1718,97 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
1588
1718
  "role_icon": "ex-user-outlined",
1589
1719
  },
1590
1720
  },
1721
+ "app_custom_button_list": {
1722
+ "allowed_keys": ["app_key"],
1723
+ "aliases": {},
1724
+ "allowed_values": {},
1725
+ "minimal_example": {
1726
+ "profile": "default",
1727
+ "app_key": "APP_KEY",
1728
+ },
1729
+ },
1730
+ "app_custom_button_get": {
1731
+ "allowed_keys": ["app_key", "button_id"],
1732
+ "aliases": {"buttonId": "button_id"},
1733
+ "allowed_values": {},
1734
+ "minimal_example": {
1735
+ "profile": "default",
1736
+ "app_key": "APP_KEY",
1737
+ "button_id": 1001,
1738
+ },
1739
+ },
1740
+ "app_custom_button_create": {
1741
+ "allowed_keys": ["app_key", "payload"],
1742
+ "aliases": {
1743
+ "payload.buttonText": "payload.button_text",
1744
+ "payload.backgroundColor": "payload.background_color",
1745
+ "payload.textColor": "payload.text_color",
1746
+ "payload.buttonIcon": "payload.button_icon",
1747
+ "payload.triggerAction": "payload.trigger_action",
1748
+ "payload.triggerLinkUrl": "payload.trigger_link_url",
1749
+ "payload.triggerAddDataConfig": "payload.trigger_add_data_config",
1750
+ "payload.externalQrobotConfig": "payload.external_qrobot_config",
1751
+ "payload.customButtonExternalQRobotRelationVO": "payload.external_qrobot_config",
1752
+ "payload.triggerWingsConfig": "payload.trigger_wings_config",
1753
+ },
1754
+ "allowed_values": {
1755
+ "payload.trigger_action": [member.value for member in PublicButtonTriggerAction],
1756
+ },
1757
+ "minimal_example": {
1758
+ "profile": "default",
1759
+ "app_key": "APP_KEY",
1760
+ "payload": {
1761
+ "button_text": "新增记录",
1762
+ "background_color": "#FFFFFF",
1763
+ "text_color": "#494F57",
1764
+ "button_icon": "ex-add-outlined",
1765
+ "trigger_action": "link",
1766
+ "trigger_link_url": "https://example.com",
1767
+ },
1768
+ },
1769
+ },
1770
+ "app_custom_button_update": {
1771
+ "allowed_keys": ["app_key", "button_id", "payload"],
1772
+ "aliases": {
1773
+ "buttonId": "button_id",
1774
+ "payload.buttonText": "payload.button_text",
1775
+ "payload.backgroundColor": "payload.background_color",
1776
+ "payload.textColor": "payload.text_color",
1777
+ "payload.buttonIcon": "payload.button_icon",
1778
+ "payload.triggerAction": "payload.trigger_action",
1779
+ "payload.triggerLinkUrl": "payload.trigger_link_url",
1780
+ "payload.triggerAddDataConfig": "payload.trigger_add_data_config",
1781
+ "payload.externalQrobotConfig": "payload.external_qrobot_config",
1782
+ "payload.customButtonExternalQRobotRelationVO": "payload.external_qrobot_config",
1783
+ "payload.triggerWingsConfig": "payload.trigger_wings_config",
1784
+ },
1785
+ "allowed_values": {
1786
+ "payload.trigger_action": [member.value for member in PublicButtonTriggerAction],
1787
+ },
1788
+ "minimal_example": {
1789
+ "profile": "default",
1790
+ "app_key": "APP_KEY",
1791
+ "button_id": 1001,
1792
+ "payload": {
1793
+ "button_text": "查看详情",
1794
+ "background_color": "#FFFFFF",
1795
+ "text_color": "#494F57",
1796
+ "button_icon": "ex-link-outlined",
1797
+ "trigger_action": "link",
1798
+ "trigger_link_url": "https://example.com/detail",
1799
+ },
1800
+ },
1801
+ },
1802
+ "app_custom_button_delete": {
1803
+ "allowed_keys": ["app_key", "button_id"],
1804
+ "aliases": {"buttonId": "button_id"},
1805
+ "allowed_values": {},
1806
+ "minimal_example": {
1807
+ "profile": "default",
1808
+ "app_key": "APP_KEY",
1809
+ "button_id": 1001,
1810
+ },
1811
+ },
1591
1812
  "app_schema_plan": {
1592
1813
  "allowed_keys": ["app_key", "package_tag_id", "app_name", "create_if_missing", "add_fields", "update_fields", "remove_fields"],
1593
1814
  "aliases": {
@@ -1825,7 +2046,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
1825
2046
  },
1826
2047
  },
1827
2048
  "app_views_plan": {
1828
- "allowed_keys": ["app_key", "upsert_views", "remove_views", "preset", "upsert_views[].view_key"],
2049
+ "allowed_keys": ["app_key", "upsert_views", "remove_views", "preset", "upsert_views[].view_key", "upsert_views[].buttons"],
1829
2050
  "aliases": {
1830
2051
  "fields": "columns",
1831
2052
  "column_names": "columns",
@@ -1839,11 +2060,21 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
1839
2060
  "startField": "start_field",
1840
2061
  "endField": "end_field",
1841
2062
  "titleField": "title_field",
2063
+ "buttons[].buttonType": "buttons[].button_type",
2064
+ "buttons[].configType": "buttons[].config_type",
2065
+ "buttons[].buttonId": "buttons[].button_id",
2066
+ "buttons[].beingMain": "buttons[].being_main",
2067
+ "buttons[].buttonLimit": "buttons[].button_limit",
2068
+ "buttons[].buttonFormula": "buttons[].button_formula",
2069
+ "buttons[].buttonFormulaType": "buttons[].button_formula_type",
2070
+ "buttons[].printTpls": "buttons[].print_tpls",
1842
2071
  },
1843
2072
  "allowed_values": {
1844
2073
  "preset": [member.value for member in ViewsPreset],
1845
2074
  "view.type": [member.value for member in PublicViewType],
1846
2075
  "view.filter.operator": [member.value for member in ViewFilterOperator],
2076
+ "view.buttons.button_type": ["SYSTEM", "CUSTOM"],
2077
+ "view.buttons.config_type": ["TOP", "DETAIL"],
1847
2078
  },
1848
2079
  "minimal_example": {
1849
2080
  "profile": "default",
@@ -1869,7 +2100,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
1869
2100
  },
1870
2101
  },
1871
2102
  "app_views_apply": {
1872
- "allowed_keys": ["app_key", "publish", "upsert_views", "remove_views", "upsert_views[].view_key"],
2103
+ "allowed_keys": ["app_key", "publish", "upsert_views", "remove_views", "upsert_views[].view_key", "upsert_views[].buttons"],
1873
2104
  "aliases": {
1874
2105
  "fields": "columns",
1875
2106
  "column_names": "columns",
@@ -1883,16 +2114,27 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
1883
2114
  "startField": "start_field",
1884
2115
  "endField": "end_field",
1885
2116
  "titleField": "title_field",
2117
+ "buttons[].buttonType": "buttons[].button_type",
2118
+ "buttons[].configType": "buttons[].config_type",
2119
+ "buttons[].buttonId": "buttons[].button_id",
2120
+ "buttons[].beingMain": "buttons[].being_main",
2121
+ "buttons[].buttonLimit": "buttons[].button_limit",
2122
+ "buttons[].buttonFormula": "buttons[].button_formula",
2123
+ "buttons[].buttonFormulaType": "buttons[].button_formula_type",
2124
+ "buttons[].printTpls": "buttons[].print_tpls",
1886
2125
  },
1887
2126
  "allowed_values": {
1888
2127
  "view.type": [member.value for member in PublicViewType],
1889
2128
  "view.filter.operator": [member.value for member in ViewFilterOperator],
2129
+ "view.buttons.button_type": ["SYSTEM", "CUSTOM"],
2130
+ "view.buttons.config_type": ["TOP", "DETAIL"],
1890
2131
  },
1891
2132
  "execution_notes": [
1892
2133
  "apply may return partial_success when some views land and others fail",
1893
2134
  "when duplicate view names exist, supply view_key to target the exact view",
1894
2135
  "read back app_read_views_summary after any failed or partial view apply",
1895
2136
  "view existence verification and saved-filter verification are separate; treat filters as unverified until verification.view_filters_verified is true",
2137
+ "buttons omitted preserves existing button config; buttons=[] clears all buttons; buttons=[...] replaces the full button config",
1896
2138
  ],
1897
2139
  "minimal_example": {
1898
2140
  "profile": "default",
@@ -0,0 +1,177 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+
5
+ from ..config import DEFAULT_PROFILE
6
+ from ..errors import QingflowApiError, raise_tool_error
7
+ from ..json_types import JSONObject
8
+ from .base import ToolBase
9
+
10
+
11
+ class CustomButtonTools(ToolBase):
12
+ def custom_button_list(
13
+ self,
14
+ *,
15
+ profile: str,
16
+ app_key: str,
17
+ being_draft: bool = True,
18
+ include_raw: bool = False,
19
+ ) -> JSONObject:
20
+ self._require_app_key(app_key)
21
+
22
+ def runner(session_profile, context):
23
+ result = self.backend.request(
24
+ "GET",
25
+ context,
26
+ f"/app/{app_key}/customButton",
27
+ params={"beingDraft": being_draft},
28
+ )
29
+ items = []
30
+ raw_items = result.get("result") if isinstance(result, dict) and isinstance(result.get("result"), list) else []
31
+ for item in raw_items:
32
+ if not isinstance(item, dict):
33
+ continue
34
+ items.append(self._compact_button_base_info(item))
35
+ response = {
36
+ "profile": profile,
37
+ "ws_id": session_profile.selected_ws_id,
38
+ "app_key": app_key,
39
+ "being_draft": being_draft,
40
+ "items": result if include_raw else items,
41
+ "count": len(items),
42
+ "compact": not include_raw,
43
+ }
44
+ if include_raw:
45
+ response["summary"] = items
46
+ return response
47
+
48
+ return self._run(profile, runner)
49
+
50
+ def custom_button_get(
51
+ self,
52
+ *,
53
+ profile: str,
54
+ app_key: str,
55
+ button_id: int,
56
+ being_draft: bool = True,
57
+ include_raw: bool = False,
58
+ ) -> JSONObject:
59
+ self._require_app_key(app_key)
60
+ self._require_button_id(button_id)
61
+
62
+ def runner(session_profile, context):
63
+ params = {"beingDraft": being_draft}
64
+ result = self.backend.request("GET", context, f"/app/{app_key}/customButton/{button_id}", params=params)
65
+ response = {
66
+ "profile": profile,
67
+ "ws_id": session_profile.selected_ws_id,
68
+ "app_key": app_key,
69
+ "button_id": button_id,
70
+ "being_draft": being_draft,
71
+ "result": result if include_raw else self._compact_button_detail(result if isinstance(result, dict) else {}),
72
+ "compact": not include_raw,
73
+ }
74
+ if include_raw:
75
+ response["summary"] = self._compact_button_detail(result if isinstance(result, dict) else {})
76
+ return response
77
+
78
+ return self._run(profile, runner)
79
+
80
+ def custom_button_create(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
81
+ self._require_app_key(app_key)
82
+ body = self._require_dict(payload)
83
+
84
+ def runner(session_profile, context):
85
+ result = self.backend.request("POST", context, f"/app/{app_key}/customButton", json_body=deepcopy(body))
86
+ return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
87
+
88
+ return self._run(profile, runner)
89
+
90
+ def custom_button_update(self, *, profile: str, app_key: str, button_id: int, payload: JSONObject) -> JSONObject:
91
+ self._require_app_key(app_key)
92
+ self._require_button_id(button_id)
93
+ body = self._require_dict(payload)
94
+
95
+ def runner(session_profile, context):
96
+ result = self.backend.request(
97
+ "POST",
98
+ context,
99
+ f"/app/{app_key}/customButton/{button_id}",
100
+ json_body=deepcopy(body),
101
+ )
102
+ return self._attach_human_review_notice(
103
+ {
104
+ "profile": profile,
105
+ "ws_id": session_profile.selected_ws_id,
106
+ "app_key": app_key,
107
+ "button_id": button_id,
108
+ "result": result,
109
+ },
110
+ operation="update",
111
+ target="custom button configuration",
112
+ )
113
+
114
+ return self._run(profile, runner)
115
+
116
+ def custom_button_delete(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
117
+ self._require_app_key(app_key)
118
+ self._require_button_id(button_id)
119
+
120
+ def runner(session_profile, context):
121
+ result = self.backend.request("DELETE", context, f"/app/{app_key}/customButton/{button_id}")
122
+ return self._attach_human_review_notice(
123
+ {
124
+ "profile": profile,
125
+ "ws_id": session_profile.selected_ws_id,
126
+ "app_key": app_key,
127
+ "button_id": button_id,
128
+ "result": result,
129
+ },
130
+ operation="delete",
131
+ target="custom button configuration",
132
+ )
133
+
134
+ return self._run(profile, runner)
135
+
136
+ def _require_app_key(self, app_key: str) -> None:
137
+ if not str(app_key or "").strip():
138
+ raise_tool_error(QingflowApiError.config_error("app_key is required"))
139
+
140
+ def _require_button_id(self, button_id: int) -> None:
141
+ if not isinstance(button_id, int) or isinstance(button_id, bool) or button_id <= 0:
142
+ raise_tool_error(QingflowApiError.config_error("button_id must be a positive integer"))
143
+
144
+ def _compact_button_base_info(self, item: dict[str, object]) -> JSONObject:
145
+ creator = item.get("creatorUserInfo") if isinstance(item.get("creatorUserInfo"), dict) else {}
146
+ return {
147
+ "button_id": item.get("buttonId"),
148
+ "button_text": item.get("buttonText"),
149
+ "button_icon": item.get("buttonIcon"),
150
+ "background_color": item.get("backgroundColor"),
151
+ "text_color": item.get("textColor"),
152
+ "creator_user_info": {
153
+ "uid": creator.get("uid"),
154
+ "name": creator.get("name"),
155
+ "email": creator.get("email"),
156
+ }
157
+ if creator
158
+ else None,
159
+ "used_in_chart_count": item.get("userInChartCount"),
160
+ "being_effective_external_qrobot": item.get("beingEffectiveExternalQRobot"),
161
+ }
162
+
163
+ def _compact_button_detail(self, item: dict[str, object]) -> JSONObject:
164
+ return {
165
+ "button_id": item.get("buttonId"),
166
+ "button_text": item.get("buttonText"),
167
+ "button_icon": item.get("buttonIcon"),
168
+ "background_color": item.get("backgroundColor"),
169
+ "text_color": item.get("textColor"),
170
+ "trigger_action": item.get("triggerAction"),
171
+ "trigger_link_url": item.get("triggerLinkUrl"),
172
+ "trigger_add_data_config": deepcopy(item.get("triggerAddDataConfig")) if isinstance(item.get("triggerAddDataConfig"), dict) else None,
173
+ "external_qrobot_config": deepcopy(item.get("customButtonExternalQRobotRelationVO"))
174
+ if isinstance(item.get("customButtonExternalQRobotRelationVO"), dict)
175
+ else None,
176
+ "trigger_wings_config": deepcopy(item.get("triggerWingsConfig")) if isinstance(item.get("triggerWingsConfig"), dict) else None,
177
+ }