@josephyan/qingflow-cli 1.1.15 → 1.1.17

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.
Files changed (22) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/pyproject.toml +1 -1
  4. package/skills/qingflow-cli/SKILL.md +2 -2
  5. package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +10 -2
  6. package/skills/qingflow-cli/reference/builder/30-schema-fields.md +22 -1
  7. package/skills/qingflow-cli/reference/builder/50-views.md +83 -12
  8. package/skills/qingflow-cli/reference/builder/70-portal.md +16 -4
  9. package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +57 -4
  10. package/skills/qingflow-cli/reference/builder/90-workflow.md +1 -0
  11. package/skills/qingflow-cli/reference/builder/99-publish-verify.md +3 -0
  12. package/skills/qingflow-cli/reference/builder/workflow/README.md +1 -1
  13. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md +1 -1
  14. package/skills/qingflow-cli/reference/examples/portal/portal_sections_all_types.example.json +2 -2
  15. package/skills/qingflow-cli/reference/examples/portal/portal_sections_five_types.example.json +2 -2
  16. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +1 -1
  17. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +4 -0
  18. package/skills/qingflow-cli/reference/record/insert/README.md +3 -0
  19. package/skills/qingflow-cli/reference/task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +2 -0
  20. package/src/qingflow_mcp/builder_facade/models.py +167 -0
  21. package/src/qingflow_mcp/builder_facade/service.py +556 -16
  22. package/src/qingflow_mcp/tools/ai_builder_tools.py +157 -6
@@ -1105,6 +1105,91 @@ class ViewAssociatedResourcesPatch(StrictModel):
1105
1105
  )
1106
1106
 
1107
1107
 
1108
+ class ViewActionButtonPatch(StrictModel):
1109
+ button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
1110
+ client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
1111
+ text: str = Field(validation_alias=AliasChoices("text", "button_text", "buttonText", "name"))
1112
+ action: PublicButtonTriggerAction = Field(validation_alias=AliasChoices("action", "trigger_action", "triggerAction"))
1113
+ url: str | None = Field(default=None, validation_alias=AliasChoices("url", "link_url", "linkUrl", "trigger_link_url", "triggerLinkUrl"))
1114
+ target_app_key: str | None = Field(default=None, validation_alias=AliasChoices("target_app_key", "targetAppKey", "related_app_key", "relatedAppKey"))
1115
+ target_app_name: str | None = Field(default=None, validation_alias=AliasChoices("target_app_name", "targetAppName", "related_app_name", "relatedAppName"))
1116
+ field_mappings: list[dict[str, Any]] = Field(default_factory=list, validation_alias=AliasChoices("field_mappings", "fieldMappings", "mappings"))
1117
+ default_values: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("default_values", "defaultValues", "defaults"))
1118
+ placement: PublicButtonPlacement = Field(default=PublicButtonPlacement.detail, validation_alias=AliasChoices("placement", "position"))
1119
+ primary: bool = Field(default=False, validation_alias=AliasChoices("primary", "being_main", "beingMain"))
1120
+ visible_when: list[list[ViewFilterRulePatch]] = Field(
1121
+ default_factory=list,
1122
+ validation_alias=AliasChoices("visible_when", "visibleWhen", "button_limit", "buttonLimit"),
1123
+ )
1124
+ button_formula: str | None = Field(default=None, validation_alias=AliasChoices("button_formula", "buttonFormula"))
1125
+ button_formula_type: int = Field(default=1, validation_alias=AliasChoices("button_formula_type", "buttonFormulaType"))
1126
+ print_tpls: list[Any] = Field(default_factory=list, validation_alias=AliasChoices("print_tpls", "printTpls"))
1127
+ style_preset: str | None = Field(default=None, validation_alias=AliasChoices("style_preset", "stylePreset"))
1128
+ background_color: str | None = Field(default=None, validation_alias=AliasChoices("background_color", "backgroundColor"))
1129
+ text_color: str | None = Field(default=None, validation_alias=AliasChoices("text_color", "textColor"))
1130
+ button_icon: str | None = Field(default=None, validation_alias=AliasChoices("button_icon", "buttonIcon"))
1131
+ external_qrobot_config: dict[str, Any] | None = Field(
1132
+ default=None,
1133
+ validation_alias=AliasChoices(
1134
+ "external_qrobot_config",
1135
+ "externalQrobotConfig",
1136
+ "externalQRobotConfig",
1137
+ "custom_button_external_qrobot_relation_vo",
1138
+ "customButtonExternalQRobotRelationVO",
1139
+ ),
1140
+ )
1141
+ trigger_wings_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("trigger_wings_config", "triggerWingsConfig"))
1142
+
1143
+ @model_validator(mode="before")
1144
+ @classmethod
1145
+ def normalize_aliases(cls, value: Any) -> Any:
1146
+ if not isinstance(value, dict):
1147
+ return value
1148
+ payload = dict(value)
1149
+ raw_action = payload.get("action", payload.get("trigger_action", payload.get("triggerAction")))
1150
+ if isinstance(raw_action, str):
1151
+ normalized = raw_action.strip()
1152
+ aliases = {
1153
+ "add_data": PublicButtonTriggerAction.add_data.value,
1154
+ "add-data": PublicButtonTriggerAction.add_data.value,
1155
+ "adddata": PublicButtonTriggerAction.add_data.value,
1156
+ "addData": PublicButtonTriggerAction.add_data.value,
1157
+ "link": PublicButtonTriggerAction.link.value,
1158
+ "url": PublicButtonTriggerAction.link.value,
1159
+ "qrobot": PublicButtonTriggerAction.qrobot.value,
1160
+ "qRobot": PublicButtonTriggerAction.qrobot.value,
1161
+ "wings": PublicButtonTriggerAction.wings.value,
1162
+ }
1163
+ payload["action"] = aliases.get(normalized, aliases.get(normalized.lower(), normalized))
1164
+ raw_placement = payload.get("placement", payload.get("position", payload.get("config_type", payload.get("configType"))))
1165
+ if isinstance(raw_placement, str):
1166
+ normalized = raw_placement.strip().lower()
1167
+ if normalized in {"top", "header"}:
1168
+ payload["placement"] = PublicButtonPlacement.header.value
1169
+ elif normalized in {"detail", "data_detail"}:
1170
+ payload["placement"] = PublicButtonPlacement.detail.value
1171
+ elif normalized in {"list", "row", "row_action", "inside"}:
1172
+ payload["placement"] = PublicButtonPlacement.list.value
1173
+ raw_limits = payload.get("visible_when", payload.get("visibleWhen", payload.get("button_limit", payload.get("buttonLimit"))))
1174
+ if isinstance(raw_limits, list) and raw_limits and all(isinstance(item, dict) for item in raw_limits):
1175
+ payload["visible_when"] = [raw_limits]
1176
+ return payload
1177
+
1178
+ @model_validator(mode="after")
1179
+ def validate_shape(self) -> "ViewActionButtonPatch":
1180
+ if not str(self.text or "").strip():
1181
+ raise ValueError("action_buttons[].text is required")
1182
+ if self.action == PublicButtonTriggerAction.link and not str(self.url or "").strip():
1183
+ raise ValueError("link action_buttons require url")
1184
+ if self.action == PublicButtonTriggerAction.add_data and not str(self.target_app_key or "").strip():
1185
+ raise ValueError("add_data action_buttons require target_app_key")
1186
+ if self.action == PublicButtonTriggerAction.qrobot and self.external_qrobot_config is None:
1187
+ raise ValueError("qRobot action_buttons require external_qrobot_config")
1188
+ if self.action == PublicButtonTriggerAction.wings and self.trigger_wings_config is None:
1189
+ raise ValueError("wings action_buttons require trigger_wings_config")
1190
+ return self
1191
+
1192
+
1108
1193
  class ViewUpsertPatch(StrictModel):
1109
1194
  name: str
1110
1195
  view_key: str | None = Field(default=None, validation_alias=AliasChoices("view_key", "viewKey"))
@@ -1116,6 +1201,8 @@ class ViewUpsertPatch(StrictModel):
1116
1201
  end_field: str | None = Field(default=None, validation_alias=AliasChoices("end_field", "endField"))
1117
1202
  title_field: str | None = Field(default=None, validation_alias=AliasChoices("title_field", "titleField"))
1118
1203
  buttons: list["ViewButtonBindingPatch"] | None = None
1204
+ action_buttons: list[ViewActionButtonPatch] | None = Field(default=None, validation_alias=AliasChoices("action_buttons", "actionButtons"))
1205
+ action_buttons_mode: str = Field(default="merge", validation_alias=AliasChoices("action_buttons_mode", "actionButtonsMode", "button_mode", "buttonMode"))
1119
1206
  visibility: VisibilityPatch | None = None
1120
1207
  query_conditions: ViewQueryConditionsPatch | None = Field(default=None, validation_alias=AliasChoices("query_conditions", "queryConditions", "query_condition", "queryCondition"))
1121
1208
  associated_resources: ViewAssociatedResourcesPatch | None = Field(
@@ -1165,6 +1252,12 @@ class ViewUpsertPatch(StrictModel):
1165
1252
  payload["associated_resources"] = payload.pop("associatedReports")
1166
1253
  if "asosChartConfig" in payload and "associated_resources" not in payload:
1167
1254
  payload["associated_resources"] = payload.pop("asosChartConfig")
1255
+ if "actionButtons" in payload and "action_buttons" not in payload:
1256
+ payload["action_buttons"] = payload.pop("actionButtons")
1257
+ if "actionButtonsMode" in payload and "action_buttons_mode" not in payload:
1258
+ payload["action_buttons_mode"] = payload.pop("actionButtonsMode")
1259
+ if "buttonMode" in payload and "action_buttons_mode" not in payload:
1260
+ payload["action_buttons_mode"] = payload.pop("buttonMode")
1168
1261
  raw_type = payload.get("type")
1169
1262
  if isinstance(raw_type, str):
1170
1263
  normalized = raw_type.strip().lower()
@@ -1186,6 +1279,10 @@ class ViewUpsertPatch(StrictModel):
1186
1279
  raise ValueError("board view requires group_by")
1187
1280
  if self.type == PublicViewType.gantt and not (self.start_field and self.end_field):
1188
1281
  raise ValueError("gantt view requires start_field and end_field")
1282
+ normalized_mode = str(self.action_buttons_mode or "merge").strip().lower()
1283
+ if normalized_mode not in {"merge", "replace"}:
1284
+ raise ValueError("action_buttons_mode must be merge or replace")
1285
+ self.action_buttons_mode = normalized_mode
1189
1286
  return self
1190
1287
 
1191
1288
 
@@ -1211,6 +1308,76 @@ class ViewPartialPatch(StrictModel):
1211
1308
  return self
1212
1309
 
1213
1310
 
1311
+ def public_button_trigger_action_value(value: Any) -> str:
1312
+ raw = value.value if isinstance(value, PublicButtonTriggerAction) else str(value or "").strip()
1313
+ normalized = raw.lower()
1314
+ if raw == PublicButtonTriggerAction.add_data.value or normalized in {"add_data", "add-data", "adddata"}:
1315
+ return "add_data"
1316
+ if normalized in {PublicButtonTriggerAction.link.value, "url"}:
1317
+ return "link"
1318
+ if raw == PublicButtonTriggerAction.qrobot.value or normalized == "qrobot":
1319
+ return "qRobot"
1320
+ if normalized == PublicButtonTriggerAction.wings.value:
1321
+ return "wings"
1322
+ return raw
1323
+
1324
+
1325
+ def _drop_empty_public_payload_values(payload: dict[str, Any]) -> dict[str, Any]:
1326
+ compact: dict[str, Any] = {}
1327
+ for key, item in payload.items():
1328
+ if item is None or item == "" or item == [] or item == {}:
1329
+ continue
1330
+ if key == "primary" and item is False:
1331
+ continue
1332
+ compact[key] = item
1333
+ return compact
1334
+
1335
+
1336
+ def public_view_action_button_payload(button: Any, *, compact: bool = False) -> dict[str, Any]:
1337
+ if isinstance(button, ViewActionButtonPatch):
1338
+ payload = button.model_dump(mode="json")
1339
+ elif isinstance(button, dict):
1340
+ payload = dict(button)
1341
+ else:
1342
+ return {}
1343
+ raw_action = payload.get("action", payload.get("trigger_action", payload.get("triggerAction")))
1344
+ if raw_action is not None:
1345
+ payload["action"] = public_button_trigger_action_value(raw_action)
1346
+ payload.pop("trigger_action", None)
1347
+ payload.pop("triggerAction", None)
1348
+ if compact:
1349
+ return _drop_empty_public_payload_values(payload)
1350
+ return payload
1351
+
1352
+
1353
+ def public_view_upsert_payload(view: ViewUpsertPatch) -> dict[str, Any]:
1354
+ payload = view.model_dump(mode="json")
1355
+ if view.action_buttons is not None:
1356
+ payload["action_buttons"] = [
1357
+ public_view_action_button_payload(button, compact=True)
1358
+ for button in view.action_buttons
1359
+ ]
1360
+ return payload
1361
+
1362
+
1363
+ def public_view_partial_payload(patch: ViewPartialPatch) -> dict[str, Any]:
1364
+ payload = patch.model_dump(mode="json")
1365
+ patch_set = payload.get("set")
1366
+ if isinstance(patch_set, dict):
1367
+ raw_buttons = patch_set.get("action_buttons")
1368
+ if raw_buttons is None:
1369
+ raw_buttons = patch_set.get("actionButtons")
1370
+ if isinstance(raw_buttons, list):
1371
+ patch_set = dict(patch_set)
1372
+ patch_set.pop("actionButtons", None)
1373
+ patch_set["action_buttons"] = [
1374
+ public_view_action_button_payload(button, compact=True)
1375
+ for button in raw_buttons
1376
+ ]
1377
+ payload["set"] = patch_set
1378
+ return payload
1379
+
1380
+
1214
1381
  class CustomButtonJudgeValuePatch(StrictModel):
1215
1382
  id: int | str | None = None
1216
1383
  value: Any | None = None