@josephyan/qingflow-app-builder-mcp 1.1.17 → 1.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +56 -11
- package/skills/qingflow-app-builder/references/complete-system-development-guide.md +54 -5
- package/skills/qingflow-app-builder/references/create-app.md +10 -1
- package/skills/qingflow-app-builder/references/gotchas.md +17 -3
- package/skills/qingflow-app-builder/references/single-app-development-guide.md +33 -4
- package/skills/qingflow-app-builder/references/tool-selection.md +20 -4
- package/skills/qingflow-app-builder/references/update-schema.md +11 -0
- package/skills/qingflow-app-builder/references/update-views.md +49 -0
- package/src/qingflow_mcp/builder_facade/models.py +35 -2
- package/src/qingflow_mcp/builder_facade/service.py +79 -17
- package/src/qingflow_mcp/cli/commands/builder.py +6 -3
- package/src/qingflow_mcp/server_app_builder.py +6 -3
- package/src/qingflow_mcp/tools/ai_builder_tools.py +404 -33
|
@@ -2260,6 +2260,22 @@ class ChartApplyRequest(StrictModel):
|
|
|
2260
2260
|
return self
|
|
2261
2261
|
|
|
2262
2262
|
|
|
2263
|
+
class PortalInlineChartPatch(ChartUpsertPatch):
|
|
2264
|
+
app_key: str = Field(validation_alias=AliasChoices("app_key", "appKey"))
|
|
2265
|
+
|
|
2266
|
+
@model_validator(mode="before")
|
|
2267
|
+
@classmethod
|
|
2268
|
+
def normalize_inline_aliases(cls, value: Any) -> Any:
|
|
2269
|
+
if not isinstance(value, dict):
|
|
2270
|
+
return value
|
|
2271
|
+
payload = dict(value)
|
|
2272
|
+
if "chart_name" in payload and "name" not in payload:
|
|
2273
|
+
payload["name"] = payload.pop("chart_name")
|
|
2274
|
+
if "chartName" in payload and "name" not in payload:
|
|
2275
|
+
payload["name"] = payload.pop("chartName")
|
|
2276
|
+
return payload
|
|
2277
|
+
|
|
2278
|
+
|
|
2263
2279
|
class PortalComponentPositionPatch(StrictModel):
|
|
2264
2280
|
pc_x: int = Field(default=0, validation_alias=AliasChoices("pc_x", "pcX", "x"))
|
|
2265
2281
|
pc_y: int = Field(default=0, validation_alias=AliasChoices("pc_y", "pcY", "y"))
|
|
@@ -2344,6 +2360,7 @@ class PortalSectionPatch(StrictModel):
|
|
|
2344
2360
|
dash_style_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_style_config", "dashStyleConfigBO"))
|
|
2345
2361
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
2346
2362
|
chart_ref: PortalChartRefPatch | None = None
|
|
2363
|
+
chart: PortalInlineChartPatch | None = Field(default=None, validation_alias=AliasChoices("chart", "inline_chart", "inlineChart", "create_chart", "createChart"))
|
|
2347
2364
|
view_ref: PortalViewRefPatch | None = None
|
|
2348
2365
|
text: str | None = None
|
|
2349
2366
|
url: str | None = None
|
|
@@ -2362,6 +2379,14 @@ class PortalSectionPatch(StrictModel):
|
|
|
2362
2379
|
payload["role"] = raw_role.strip().lower()
|
|
2363
2380
|
if "chartRef" in payload and "chart_ref" not in payload:
|
|
2364
2381
|
payload["chart_ref"] = payload.pop("chartRef")
|
|
2382
|
+
if "inlineChart" in payload and "chart" not in payload:
|
|
2383
|
+
payload["chart"] = payload.pop("inlineChart")
|
|
2384
|
+
if "inline_chart" in payload and "chart" not in payload:
|
|
2385
|
+
payload["chart"] = payload.pop("inline_chart")
|
|
2386
|
+
if "createChart" in payload and "chart" not in payload:
|
|
2387
|
+
payload["chart"] = payload.pop("createChart")
|
|
2388
|
+
if "create_chart" in payload and "chart" not in payload:
|
|
2389
|
+
payload["chart"] = payload.pop("create_chart")
|
|
2365
2390
|
if "viewRef" in payload and "view_ref" not in payload:
|
|
2366
2391
|
payload["view_ref"] = payload.pop("viewRef")
|
|
2367
2392
|
if "dashStyleConfigBO" in payload and "dash_style_config" not in payload:
|
|
@@ -2373,8 +2398,8 @@ class PortalSectionPatch(StrictModel):
|
|
|
2373
2398
|
supported = {"chart", "view", "grid", "filter", "text", "link"}
|
|
2374
2399
|
if self.source_type not in supported:
|
|
2375
2400
|
raise ValueError(f"unsupported portal source_type '{self.source_type}'")
|
|
2376
|
-
if self.source_type == "chart" and self.chart_ref is None:
|
|
2377
|
-
raise ValueError("chart section requires chart_ref")
|
|
2401
|
+
if self.source_type == "chart" and self.chart_ref is None and self.chart is None:
|
|
2402
|
+
raise ValueError("chart section requires chart_ref or chart")
|
|
2378
2403
|
if self.source_type == "view" and self.view_ref is None:
|
|
2379
2404
|
raise ValueError("view section requires view_ref")
|
|
2380
2405
|
if self.source_type == "text" and self.text is None:
|
|
@@ -2662,6 +2687,14 @@ def _normalize_field_payload(value: Any) -> Any:
|
|
|
2662
2687
|
if not isinstance(value, dict):
|
|
2663
2688
|
return value
|
|
2664
2689
|
payload = dict(value)
|
|
2690
|
+
if "data_title" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
2691
|
+
payload["as_data_title"] = payload.pop("data_title")
|
|
2692
|
+
if "dataTitle" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
2693
|
+
payload["as_data_title"] = payload.pop("dataTitle")
|
|
2694
|
+
if "data_cover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
2695
|
+
payload["as_data_cover"] = payload.pop("data_cover")
|
|
2696
|
+
if "dataCover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
2697
|
+
payload["as_data_cover"] = payload.pop("dataCover")
|
|
2665
2698
|
if "fields" in payload and "subfields" not in payload:
|
|
2666
2699
|
payload["subfields"] = payload.pop("fields")
|
|
2667
2700
|
if "options" in payload:
|
|
@@ -3155,7 +3155,7 @@ class AiBuilderFacade:
|
|
|
3155
3155
|
return expanded, issues, results
|
|
3156
3156
|
|
|
3157
3157
|
def app_custom_buttons_apply(self, *, profile: str, request: CustomButtonsApplyRequest) -> JSONObject:
|
|
3158
|
-
normalized_args = request.model_dump(mode="json")
|
|
3158
|
+
normalized_args = request.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
|
|
3159
3159
|
app_key = request.app_key
|
|
3160
3160
|
permission_outcomes: list[PermissionCheckOutcome] = []
|
|
3161
3161
|
button_write_intent = bool(request.upsert_buttons or request.patch_buttons or request.remove_buttons)
|
|
@@ -3239,7 +3239,10 @@ class AiBuilderFacade:
|
|
|
3239
3239
|
)
|
|
3240
3240
|
)
|
|
3241
3241
|
upsert_buttons.extend(expanded_buttons)
|
|
3242
|
-
normalized_args["upsert_buttons"] = [
|
|
3242
|
+
normalized_args["upsert_buttons"] = [
|
|
3243
|
+
patch.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
|
|
3244
|
+
for patch in upsert_buttons
|
|
3245
|
+
]
|
|
3243
3246
|
normalized_args["patch_results"] = patch_results
|
|
3244
3247
|
|
|
3245
3248
|
compiled_add_data_configs, add_data_issues = self._compile_custom_button_semantic_add_data_configs(
|
|
@@ -10261,8 +10264,14 @@ class AiBuilderFacade:
|
|
|
10261
10264
|
return CustomButtonsApplyRequest.model_validate(
|
|
10262
10265
|
{
|
|
10263
10266
|
"app_key": app_key,
|
|
10264
|
-
"upsert_buttons": [
|
|
10265
|
-
|
|
10267
|
+
"upsert_buttons": [
|
|
10268
|
+
item.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
|
|
10269
|
+
for item in upsert_buttons
|
|
10270
|
+
],
|
|
10271
|
+
"view_configs": [
|
|
10272
|
+
item.model_dump(mode="json", exclude_none=True, exclude_defaults=True)
|
|
10273
|
+
for item in view_configs
|
|
10274
|
+
],
|
|
10266
10275
|
}
|
|
10267
10276
|
), []
|
|
10268
10277
|
except Exception as error:
|
|
@@ -13018,6 +13027,7 @@ class AiBuilderFacade:
|
|
|
13018
13027
|
draft_readback_error: JSONObject | None = None
|
|
13019
13028
|
live_readback_error: JSONObject | None = None
|
|
13020
13029
|
update_payload: dict[str, Any] = {}
|
|
13030
|
+
inline_chart_results: list[dict[str, Any]] = []
|
|
13021
13031
|
try:
|
|
13022
13032
|
layout_diagnostics: dict[str, Any] = _empty_portal_layout_diagnostics()
|
|
13023
13033
|
if creating:
|
|
@@ -13065,10 +13075,11 @@ class AiBuilderFacade:
|
|
|
13065
13075
|
base_payload=base_payload,
|
|
13066
13076
|
)
|
|
13067
13077
|
if sections_requested:
|
|
13068
|
-
component_payload, layout_metadata = self._build_portal_components_from_sections(
|
|
13078
|
+
component_payload, layout_metadata, inline_chart_results = self._build_portal_components_from_sections(
|
|
13069
13079
|
profile=profile,
|
|
13070
13080
|
sections=request.sections,
|
|
13071
13081
|
layout_preset=request.layout_preset,
|
|
13082
|
+
inline_chart_results=inline_chart_results,
|
|
13072
13083
|
)
|
|
13073
13084
|
layout_diagnostics = _portal_layout_diagnostics(request.sections, component_payload, layout_metadata=layout_metadata)
|
|
13074
13085
|
update_payload["components"] = component_payload
|
|
@@ -13096,11 +13107,12 @@ class AiBuilderFacade:
|
|
|
13096
13107
|
draft_result = {}
|
|
13097
13108
|
except (QingflowApiError, RuntimeError, ValueError) as error:
|
|
13098
13109
|
api_error = _coerce_api_error(error) if not isinstance(error, ValueError) else None
|
|
13099
|
-
|
|
13110
|
+
inline_chart_write_executed = any(bool(item.get("write_executed")) for item in inline_chart_results)
|
|
13111
|
+
if write_executed or inline_chart_write_executed:
|
|
13100
13112
|
transport_error = _transport_error_payload(api_error) if api_error is not None else None
|
|
13101
13113
|
warning = _warning(
|
|
13102
13114
|
"PORTAL_WRITE_INCOMPLETE_AFTER_PARTIAL_WRITE",
|
|
13103
|
-
"one or more portal write steps executed before a later write step failed",
|
|
13115
|
+
"one or more portal or inline-chart write steps executed before a later write step failed",
|
|
13104
13116
|
)
|
|
13105
13117
|
if transport_error is not None:
|
|
13106
13118
|
for key in ("backend_code", "http_status", "request_id"):
|
|
@@ -13121,6 +13133,7 @@ class AiBuilderFacade:
|
|
|
13121
13133
|
if api_error is not None
|
|
13122
13134
|
else {"message": str(error)}
|
|
13123
13135
|
),
|
|
13136
|
+
**({"inline_chart_results": deepcopy(inline_chart_results)} if inline_chart_results else {}),
|
|
13124
13137
|
},
|
|
13125
13138
|
"request_id": api_error.request_id if api_error else None,
|
|
13126
13139
|
"backend_code": api_error.backend_code if api_error else None,
|
|
@@ -13136,6 +13149,7 @@ class AiBuilderFacade:
|
|
|
13136
13149
|
"published": False,
|
|
13137
13150
|
"publish_failed": False,
|
|
13138
13151
|
"write_incomplete": True,
|
|
13152
|
+
"inline_charts_verified": False if inline_chart_results else None,
|
|
13139
13153
|
"readback_unavailable": False,
|
|
13140
13154
|
"metadata_unverified": True,
|
|
13141
13155
|
},
|
|
@@ -13147,6 +13161,7 @@ class AiBuilderFacade:
|
|
|
13147
13161
|
"verified": False,
|
|
13148
13162
|
"write_executed": True,
|
|
13149
13163
|
"safe_to_retry": False,
|
|
13164
|
+
"inline_chart_results": deepcopy(inline_chart_results),
|
|
13150
13165
|
})
|
|
13151
13166
|
return _failed(
|
|
13152
13167
|
"PORTAL_APPLY_FAILED",
|
|
@@ -13258,6 +13273,7 @@ class AiBuilderFacade:
|
|
|
13258
13273
|
"draft": draft_meta_mismatches,
|
|
13259
13274
|
"live": live_meta_mismatches,
|
|
13260
13275
|
},
|
|
13276
|
+
**({"inline_chart_results": deepcopy(inline_chart_results)} if inline_chart_results else {}),
|
|
13261
13277
|
**({"publish_error": publish_error} if publish_error is not None else {}),
|
|
13262
13278
|
**({"draft_readback_error": draft_readback_error} if draft_readback_error is not None else {}),
|
|
13263
13279
|
**({"live_readback_error": live_readback_error} if live_readback_error is not None else {}),
|
|
@@ -13295,6 +13311,7 @@ class AiBuilderFacade:
|
|
|
13295
13311
|
"noop": False,
|
|
13296
13312
|
"warnings": warnings,
|
|
13297
13313
|
"layout_diagnostics": layout_diagnostics,
|
|
13314
|
+
"inline_chart_results": deepcopy(inline_chart_results),
|
|
13298
13315
|
"verification": {
|
|
13299
13316
|
"draft_verified": draft_verified,
|
|
13300
13317
|
"draft_metadata_verified": draft_meta_verified,
|
|
@@ -13302,6 +13319,7 @@ class AiBuilderFacade:
|
|
|
13302
13319
|
"live_metadata_verified": live_meta_verified,
|
|
13303
13320
|
"published": published,
|
|
13304
13321
|
"publish_failed": publish_failed,
|
|
13322
|
+
"inline_charts_verified": all(str(item.get("status") or "") in {"created", "updated"} and str(item.get("chart_id") or "").strip() for item in inline_chart_results),
|
|
13305
13323
|
"readback_unavailable": draft_readback_error is not None or live_readback_error is not None,
|
|
13306
13324
|
"metadata_unverified": draft_readback_error is not None or live_readback_error is not None,
|
|
13307
13325
|
},
|
|
@@ -14055,9 +14073,11 @@ class AiBuilderFacade:
|
|
|
14055
14073
|
profile: str,
|
|
14056
14074
|
sections: list[PortalSectionPatch],
|
|
14057
14075
|
layout_preset: str | None = None,
|
|
14058
|
-
|
|
14076
|
+
inline_chart_results: list[dict[str, Any]] | None = None,
|
|
14077
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
|
|
14059
14078
|
resolved_components: list[dict[str, Any]] = []
|
|
14060
14079
|
layout_metadata: list[dict[str, Any]] = []
|
|
14080
|
+
inline_chart_results = inline_chart_results if inline_chart_results is not None else []
|
|
14061
14081
|
pc_x = 0
|
|
14062
14082
|
pc_y = 0
|
|
14063
14083
|
pc_row_height = 0
|
|
@@ -14084,11 +14104,53 @@ class AiBuilderFacade:
|
|
|
14084
14104
|
dash_style = deepcopy(section.dash_style_config) if isinstance(section.dash_style_config, dict) else None
|
|
14085
14105
|
component: dict[str, Any]
|
|
14086
14106
|
if section.source_type == "chart":
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14107
|
+
if section.chart is not None:
|
|
14108
|
+
inline_chart_payload = section.chart.model_dump(mode="json", exclude={"app_key"})
|
|
14109
|
+
chart_request = ChartApplyRequest(
|
|
14110
|
+
app_key=section.chart.app_key,
|
|
14111
|
+
upsert_charts=[ChartUpsertPatch.model_validate(inline_chart_payload)],
|
|
14112
|
+
patch_charts=[],
|
|
14113
|
+
remove_chart_ids=[],
|
|
14114
|
+
reorder_chart_ids=[],
|
|
14115
|
+
)
|
|
14116
|
+
chart_apply = self.chart_apply(profile=profile, request=chart_request)
|
|
14117
|
+
chart_items = chart_apply.get("chart_results") if isinstance(chart_apply.get("chart_results"), list) else []
|
|
14118
|
+
successful_chart = next(
|
|
14119
|
+
(
|
|
14120
|
+
item
|
|
14121
|
+
for item in chart_items
|
|
14122
|
+
if isinstance(item, dict)
|
|
14123
|
+
and str(item.get("status") or "") in {"created", "updated"}
|
|
14124
|
+
and str(item.get("chart_id") or "").strip()
|
|
14125
|
+
),
|
|
14126
|
+
None,
|
|
14127
|
+
)
|
|
14128
|
+
inline_chart_results.append({
|
|
14129
|
+
"title": section.title,
|
|
14130
|
+
"app_key": section.chart.app_key,
|
|
14131
|
+
"chart_name": section.chart.name,
|
|
14132
|
+
"chart_type": section.chart.chart_type.value,
|
|
14133
|
+
"status": str(successful_chart.get("status") or "") if isinstance(successful_chart, dict) else "failed",
|
|
14134
|
+
"chart_id": str(successful_chart.get("chart_id") or "") if isinstance(successful_chart, dict) else "",
|
|
14135
|
+
"chart_apply_status": chart_apply.get("status"),
|
|
14136
|
+
"write_executed": bool(chart_apply.get("write_executed")),
|
|
14137
|
+
**({"error_code": chart_apply.get("error_code")} if chart_apply.get("error_code") else {}),
|
|
14138
|
+
**({"message": chart_apply.get("message")} if chart_apply.get("message") else {}),
|
|
14139
|
+
})
|
|
14140
|
+
if not isinstance(successful_chart, dict):
|
|
14141
|
+
raise ValueError(f"inline chart '{section.chart.name}' could not be created or updated for portal section '{section.title}'")
|
|
14142
|
+
resolved_chart = {
|
|
14143
|
+
"chart_id": str(successful_chart.get("chart_id") or "").strip(),
|
|
14144
|
+
"chart_name": str(successful_chart.get("name") or section.chart.name).strip(),
|
|
14145
|
+
"app_key": section.chart.app_key,
|
|
14146
|
+
"chart_type": section.chart.chart_type.value,
|
|
14147
|
+
}
|
|
14148
|
+
else:
|
|
14149
|
+
resolved_chart = _resolve_chart_reference(
|
|
14150
|
+
charts=self.charts,
|
|
14151
|
+
profile=profile,
|
|
14152
|
+
ref=section.chart_ref,
|
|
14153
|
+
)
|
|
14092
14154
|
chart_type = str(
|
|
14093
14155
|
resolved_chart.get("chart_type")
|
|
14094
14156
|
or _public_chart_type_from_backend(section.config.get("chartType") or section.config.get("chart_type"))
|
|
@@ -14148,7 +14210,7 @@ class AiBuilderFacade:
|
|
|
14148
14210
|
if dash_style is not None:
|
|
14149
14211
|
component["dashStyleConfigBO"] = dash_style
|
|
14150
14212
|
resolved_components.append(component)
|
|
14151
|
-
return resolved_components, layout_metadata
|
|
14213
|
+
return resolved_components, layout_metadata, inline_chart_results
|
|
14152
14214
|
|
|
14153
14215
|
def _resolve_current_user_identity(self, *, profile: str) -> JSONObject:
|
|
14154
14216
|
session_profile = self.apps.sessions.get_profile(profile)
|
|
@@ -17794,12 +17856,12 @@ def _portal_layout_diagnostics(
|
|
|
17794
17856
|
if source_type == "chart" and role in {"metric", "metrics", "indicator", "kpi"} and not is_metric_chart:
|
|
17795
17857
|
warnings.append(_warning(
|
|
17796
17858
|
"PORTAL_METRIC_SECTION_CHART_TYPE_MISMATCH",
|
|
17797
|
-
"metric portal section must reference a target/indicator chart; create the missing metric chart
|
|
17859
|
+
"metric portal section must reference a target/indicator chart; use inline_chart to create the missing metric chart while assembling the portal",
|
|
17798
17860
|
section_index=index,
|
|
17799
17861
|
title=title,
|
|
17800
17862
|
role=role,
|
|
17801
17863
|
chart_type=chart_type or None,
|
|
17802
|
-
fix_hint="Use
|
|
17864
|
+
fix_hint="Use section.inline_chart with chart_type=target or indicator and a metric expression, or change role away from metric.",
|
|
17803
17865
|
))
|
|
17804
17866
|
if section is not None and section.position is not None and not bool(getattr(section.position, "mobile_provided", False)):
|
|
17805
17867
|
warnings.append(_warning(
|
|
@@ -17859,7 +17921,7 @@ def _append_portal_standard_count_warnings(
|
|
|
17859
17921
|
"standard portal metric area should contain 4-6 metric cards",
|
|
17860
17922
|
actual_count=metric_count,
|
|
17861
17923
|
expected_count="4-6",
|
|
17862
|
-
fix_hint="
|
|
17924
|
+
fix_hint="Use inline_chart for missing target/indicator metric cards, or keep 4 metric cards in one row with pc.cols=6, pc.rows=5.",
|
|
17863
17925
|
))
|
|
17864
17926
|
if (bi_count or require_complete_standard) and not 2 <= bi_count <= 3:
|
|
17865
17927
|
warnings.append(_warning(
|
|
@@ -214,7 +214,8 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
214
214
|
schema_apply_apply.add_argument("--visibility-file")
|
|
215
215
|
schema_apply_apply.add_argument("--create-if-missing", action="store_true")
|
|
216
216
|
schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=None)
|
|
217
|
-
schema_apply_apply.add_argument("--apps-file", help="多应用 schema JSON
|
|
217
|
+
schema_apply_apply.add_argument("--apps-file", help="多应用 schema JSON 对象;每项可带 client_key/app_name/form,支持 relation target_app_ref")
|
|
218
|
+
schema_apply_apply.add_argument("--form-file", help="单应用表单结构 JSON 数组;form[].rows[][] 同时声明字段和表单分组布局")
|
|
218
219
|
schema_apply_apply.add_argument("--add-fields-file", help="字段 JSON 数组;字段可用 as_data_title/as_data_cover 标记数据标题/封面")
|
|
219
220
|
schema_apply_apply.add_argument("--update-fields-file", help="字段更新 JSON 数组;set 内可用 as_data_title/as_data_cover 标记数据标题/封面")
|
|
220
221
|
schema_apply_apply.add_argument("--remove-fields-file")
|
|
@@ -578,10 +579,10 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
578
579
|
fix_hint="Pass a JSON array, or a JSON object like {\"package_id\":1001,\"apps\":[...]} with at least one app item.",
|
|
579
580
|
error_code="APPS_FILE_EMPTY",
|
|
580
581
|
)
|
|
581
|
-
if args.app_key or args.app_name or args.app_title or args.add_fields_file or args.update_fields_file or args.remove_fields_file:
|
|
582
|
+
if args.app_key or args.app_name or args.app_title or args.form_file or args.add_fields_file or args.update_fields_file or args.remove_fields_file:
|
|
582
583
|
raise_config_error(
|
|
583
584
|
"schema apply multi-app mode accepts --package-id plus --apps-file only; --create-if-missing is optional.",
|
|
584
|
-
fix_hint="Use
|
|
585
|
+
fix_hint="Use apps[].form inside --apps-file for batch mode, or remove --apps-file and use --form-file for a single app.",
|
|
585
586
|
)
|
|
586
587
|
if package_id is None:
|
|
587
588
|
raise_config_error(
|
|
@@ -595,6 +596,7 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
595
596
|
create_if_missing=effective_create_if_missing,
|
|
596
597
|
publish=effective_publish,
|
|
597
598
|
apps=apps,
|
|
599
|
+
form=None,
|
|
598
600
|
add_fields=[],
|
|
599
601
|
update_fields=[],
|
|
600
602
|
remove_fields=[],
|
|
@@ -631,6 +633,7 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
631
633
|
visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
|
|
632
634
|
create_if_missing=bool(args.create_if_missing),
|
|
633
635
|
publish=True if args.publish is None else bool(args.publish),
|
|
636
|
+
form=load_list_arg(args.form_file, option_name="--form-file") if args.form_file else [],
|
|
634
637
|
add_fields=load_list_arg(args.add_fields_file, option_name="--add-fields-file"),
|
|
635
638
|
update_fields=load_list_arg(args.update_fields_file, option_name="--update-fields-file"),
|
|
636
639
|
remove_fields=load_list_arg(args.remove_fields_file, option_name="--remove-fields-file"),
|
|
@@ -428,16 +428,17 @@ def build_builder_server() -> FastMCP:
|
|
|
428
428
|
visibility: dict | None = None,
|
|
429
429
|
create_if_missing: bool | None = None,
|
|
430
430
|
publish: bool = True,
|
|
431
|
+
form: list[dict] | None = None,
|
|
431
432
|
add_fields: list[dict] | None = None,
|
|
432
433
|
update_fields: list[dict] | None = None,
|
|
433
434
|
remove_fields: list[dict] | None = None,
|
|
434
435
|
apps: list[dict] | None = None,
|
|
435
436
|
) -> dict:
|
|
436
437
|
if apps:
|
|
437
|
-
if app_key or app_name or app_title or add_fields or update_fields or remove_fields:
|
|
438
|
+
if app_key or app_name or app_title or form or add_fields or update_fields or remove_fields:
|
|
438
439
|
return _config_failure(
|
|
439
440
|
"app_schema_apply multi-app mode accepts package_id plus apps only; create_if_missing is optional.",
|
|
440
|
-
fix_hint="Use
|
|
441
|
+
fix_hint="Use apps[].form for batch mode, or use the single-app arguments without apps.",
|
|
441
442
|
)
|
|
442
443
|
if package_id is None:
|
|
443
444
|
return _config_failure(
|
|
@@ -450,6 +451,7 @@ def build_builder_server() -> FastMCP:
|
|
|
450
451
|
visibility=visibility,
|
|
451
452
|
create_if_missing=True if create_if_missing is None else bool(create_if_missing),
|
|
452
453
|
publish=publish,
|
|
454
|
+
form=None,
|
|
453
455
|
add_fields=[],
|
|
454
456
|
update_fields=[],
|
|
455
457
|
remove_fields=[],
|
|
@@ -482,10 +484,11 @@ def build_builder_server() -> FastMCP:
|
|
|
482
484
|
visibility=visibility,
|
|
483
485
|
create_if_missing=effective_create_if_missing,
|
|
484
486
|
publish=publish,
|
|
487
|
+
form=form or [],
|
|
485
488
|
add_fields=add_fields or [],
|
|
486
489
|
update_fields=update_fields or [],
|
|
487
490
|
remove_fields=remove_fields or [],
|
|
488
|
-
apps=
|
|
491
|
+
apps=None,
|
|
489
492
|
)
|
|
490
493
|
|
|
491
494
|
@server.tool()
|