@qingflow-tech/qingflow-cli 1.1.43-beta.1 → 1.1.43-beta.3

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 (66) hide show
  1. package/README.md +12 -5
  2. package/entry_point.py +1 -1
  3. package/npm/bin/qingflow.mjs +1 -2
  4. package/npm/lib/runtime.mjs +3 -7
  5. package/package.json +1 -2
  6. package/pyproject.toml +1 -3
  7. package/skills/qingflow-app-form/SKILL.md +63 -0
  8. package/skills/qingflow-app-form/agents/openai.yaml +4 -0
  9. package/skills/qingflow-cli/SKILL.md +8 -6
  10. package/skills/qingflow-cli/manifest.yaml +1 -1
  11. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +4 -4
  12. package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +1 -1
  13. package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +1 -1
  14. package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +4 -4
  15. package/skills/qingflow-cli/reference/builder/10-build-single-app.md +5 -5
  16. package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +14 -14
  17. package/skills/qingflow-cli/reference/builder/30-schema-fields.md +98 -101
  18. package/skills/qingflow-cli/reference/builder/40-layout.md +39 -31
  19. package/skills/qingflow-cli/reference/builder/README.md +9 -9
  20. package/skills/qingflow-cli/reference/builder/code-integrations/README.md +4 -2
  21. package/skills/qingflow-cli/reference/builder/code-integrations/code-block.md +2 -0
  22. package/skills/qingflow-cli/reference/builder/code-integrations/q-linker.md +2 -0
  23. package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +45 -38
  24. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_EXPLORATION_REPORT.md +1 -1
  25. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md +1 -1
  26. package/skills/qingflow-cli/reference/examples/README.md +3 -3
  27. package/skills/qingflow-common/SKILL.md +47 -0
  28. package/skills/qingflow-common/agents/openai.yaml +4 -0
  29. package/src/qingflow_mcp/__main__.py +2 -3
  30. package/src/qingflow_mcp/app_form_compat.py +6 -1
  31. package/src/qingflow_mcp/builder_facade/app_form.py +547 -18
  32. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +1321 -178
  33. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +268 -7
  34. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +115 -10
  35. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +304 -7
  36. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +23 -4
  37. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +23 -4
  38. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +23 -4
  39. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +23 -4
  40. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +145 -4
  41. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +69 -10
  42. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +69 -10
  43. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +69 -10
  44. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +360 -7
  45. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +71 -4
  46. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +115 -10
  47. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +78 -10
  48. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +23 -4
  49. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +249 -4
  50. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +71 -4
  51. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +71 -4
  52. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +1041 -20
  53. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +69 -10
  54. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +23 -0
  55. package/src/qingflow_mcp/response_trim.py +41 -1
  56. package/src/qingflow_mcp/server.py +24 -3
  57. package/src/qingflow_mcp/server_app_builder.py +1 -1
  58. package/src/qingflow_mcp/tools/record_tools.py +3 -3
  59. package/src/qingflow_mcp/version.py +2 -4
  60. package/docs/local-agent-install.md +0 -370
  61. package/skills/qingflow-mcp-setup/SKILL.md +0 -115
  62. package/skills/qingflow-mcp-setup/agents/openai.yaml +0 -4
  63. package/skills/qingflow-mcp-setup/references/claude-desktop.md +0 -34
  64. package/skills/qingflow-mcp-setup/references/environments.md +0 -62
  65. package/skills/qingflow-mcp-setup/references/generic-stdio.md +0 -32
  66. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +0 -38
@@ -1,21 +1,26 @@
1
1
  {
2
2
  "$defs": {
3
- "EmptyConfig": {
4
- "additionalProperties": false,
5
- "description": "该字段类型没有特殊配置。",
6
- "type": "object"
7
- },
8
3
  "FieldCommon": {
9
4
  "description": "顶层业务字段的公共属性;字段类型由 TopField 的判别分支定义。",
10
5
  "properties": {
11
- "dataCover": {
12
- "description": "是否作为数据封面;仅顶层附件字段可设为 true。",
13
- "type": "boolean"
14
- },
15
6
  "dataTitle": {
16
7
  "description": "是否作为数据标题;整个 body 最多且必须按规则存在一个。",
17
8
  "type": "boolean"
18
9
  },
10
+ "defaultType": {
11
+ "default": 1,
12
+ "description": "默认值类型;1 表示静态值,2 表示关联/绑定值,3 表示公式值。",
13
+ "enum": [
14
+ 1,
15
+ 2,
16
+ 3
17
+ ],
18
+ "type": "integer"
19
+ },
20
+ "defaultValue": {
21
+ "$ref": "#/$defs/ScalarDefaultValue",
22
+ "description": "普通字段的标量默认值;表格、地址、附件、引用等结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
23
+ },
19
24
  "description": {
20
25
  "description": "字段说明;null 或空字符串表示清空。",
21
26
  "type": [
@@ -48,6 +53,60 @@
48
53
  "name"
49
54
  ],
50
55
  "type": "object"
56
+ },
57
+ "ScalarDefaultValue": {
58
+ "description": "不包含对象或数组的普通字段默认值。",
59
+ "type": [
60
+ "null",
61
+ "boolean",
62
+ "number",
63
+ "string"
64
+ ]
65
+ },
66
+ "TextConfig": {
67
+ "additionalProperties": false,
68
+ "description": "文字类字段的长度限制;省略表示保留当前值。",
69
+ "properties": {
70
+ "canScan": {
71
+ "description": "是否允许扫码输入。",
72
+ "type": [
73
+ "boolean",
74
+ "null"
75
+ ]
76
+ },
77
+ "maxWordCount": {
78
+ "description": "最多字数;null 表示清除限制。",
79
+ "minimum": 0,
80
+ "type": [
81
+ "integer",
82
+ "null"
83
+ ]
84
+ },
85
+ "minWordCount": {
86
+ "description": "最少字数;null 表示清除限制。",
87
+ "minimum": 0,
88
+ "type": [
89
+ "integer",
90
+ "null"
91
+ ]
92
+ },
93
+ "noRepeat": {
94
+ "description": "是否禁止重复值。",
95
+ "type": [
96
+ "boolean",
97
+ "null"
98
+ ]
99
+ },
100
+ "scanType": {
101
+ "description": "扫码类型 ID。",
102
+ "minimum": 0,
103
+ "type": [
104
+ "integer",
105
+ "null"
106
+ ]
107
+ }
108
+ },
109
+ "type": "object"
51
110
  }
52
111
  },
53
112
  "$id": "https://builder.qingflow.com/schemas/app-form-apply/v1alpha1/fields/text.json",
@@ -59,7 +118,7 @@
59
118
  {
60
119
  "properties": {
61
120
  "config": {
62
- "$ref": "#/$defs/EmptyConfig"
121
+ "$ref": "#/$defs/TextConfig"
63
122
  },
64
123
  "type": {
65
124
  "const": "text"
@@ -170,6 +170,20 @@
170
170
  "dataTitle": {
171
171
  "type": "boolean"
172
172
  },
173
+ "defaultType": {
174
+ "default": 1,
175
+ "description": "默认值类型;1 表示静态值,2 表示关联/绑定值,3 表示公式值。",
176
+ "enum": [
177
+ 1,
178
+ 2,
179
+ 3
180
+ ],
181
+ "type": "integer"
182
+ },
183
+ "defaultValue": {
184
+ "$ref": "#/$defs/ScalarDefaultValue",
185
+ "description": "普通字段的标量默认值;结构化默认值必须放在具体字段 config.defaultValues。显式 null 表示清除默认值。"
186
+ },
173
187
  "description": {
174
188
  "type": [
175
189
  "string",
@@ -252,6 +266,15 @@
252
266
  ],
253
267
  "type": "object"
254
268
  },
269
+ "ScalarDefaultValue": {
270
+ "description": "不包含对象或数组的普通字段默认值。",
271
+ "type": [
272
+ "null",
273
+ "boolean",
274
+ "number",
275
+ "string"
276
+ ]
277
+ },
255
278
  "SectionBlock": {
256
279
  "additionalProperties": false,
257
280
  "description": "段落布局容器;不是业务字段,必须独占一个根级块。",
@@ -55,6 +55,8 @@ SUCCESS_POLICY_BY_TOOL: dict[str, TransformFn] = {}
55
55
  def trim_public_response(tool_name: str | None, payload: dict[str, Any]) -> dict[str, Any]:
56
56
  if not isinstance(payload, dict):
57
57
  return payload
58
+ if tool_name == "builder:app_form_apply" and str(payload.get("status") or "").lower() == "partial_success":
59
+ return trim_success_response(tool_name, payload)
58
60
  if _looks_like_failure_payload(payload):
59
61
  status = str(payload.get("status") or "").lower()
60
62
  if _is_executed_nonfatal_payload(payload) or (
@@ -1086,6 +1088,44 @@ def _trim_builder_list_like(payload: JSONObject) -> None:
1086
1088
  _trim_builder_envelope(payload)
1087
1089
 
1088
1090
 
1091
+ def _trim_app_form_apply(payload: JSONObject) -> None:
1092
+ details = payload.get("details")
1093
+ details_diff = deepcopy(details.get("diff")) if isinstance(details, dict) and isinstance(details.get("diff"), dict) else None
1094
+ if isinstance(details, dict):
1095
+ _drop_deep_keys(details, {"transportError"})
1096
+ _trim_builder_envelope(payload)
1097
+ _trim_form_diff(payload)
1098
+ if details_diff is not None:
1099
+ payload.setdefault("details", {})["diff"] = _summarize_form_diff(details_diff)
1100
+
1101
+
1102
+ def _trim_form_diff(payload: JSONObject) -> None:
1103
+ diff = payload.get("diff")
1104
+ if isinstance(diff, dict):
1105
+ payload["diff"] = _summarize_form_diff(diff)
1106
+
1107
+
1108
+ def _summarize_form_diff(diff: dict[str, Any]) -> dict[str, Any]:
1109
+ summary: dict[str, Any] = {}
1110
+ for key in ("added", "updated", "removed"):
1111
+ items = diff.get(key)
1112
+ if not isinstance(items, list):
1113
+ continue
1114
+ names = []
1115
+ for item in items:
1116
+ if isinstance(item, str):
1117
+ names.append(item)
1118
+ elif isinstance(item, dict):
1119
+ name = item.get("name") or item.get("queTitle")
1120
+ if name:
1121
+ names.append(name)
1122
+ summary[key] = names
1123
+ for key in ("layoutChanged", "configChanged"):
1124
+ if key in diff:
1125
+ summary[key] = bool(diff[key])
1126
+ return summary
1127
+
1128
+
1089
1129
  def _register_policy(domains: tuple[str, ...], names: tuple[str, ...], transform: TransformFn) -> None:
1090
1130
  for domain in domains:
1091
1131
  for name in names:
@@ -1192,7 +1232,6 @@ _register_policy(
1192
1232
  "app_resolve",
1193
1233
  "app_form_schema",
1194
1234
  "app_form_get",
1195
- "app_form_apply",
1196
1235
  "app_delete",
1197
1236
  "button_style_catalog_get",
1198
1237
  "app_custom_buttons_apply",
@@ -1218,3 +1257,4 @@ _register_policy(
1218
1257
  ),
1219
1258
  _trim_builder_list_like,
1220
1259
  )
1260
+ _register_policy((BUILDER_DOMAIN,), ("app_form_apply",), _trim_app_form_apply)
@@ -26,12 +26,32 @@ from .tools.workflow_tools import WorkflowTools
26
26
  from .tools.workspace_tools import WorkspaceTools
27
27
 
28
28
 
29
+ def _register_missing_builder_tools(server: FastMCP) -> None:
30
+ """Add current high-level builder tools to the unified MCP without duplicate names."""
31
+ from .server_app_builder import build_builder_server
32
+
33
+ builder_server = build_builder_server()
34
+ registered = server._tool_manager._tools
35
+ for tool in builder_server._tool_manager._tools.values():
36
+ if tool.name in registered:
37
+ continue
38
+ server.add_tool(
39
+ tool.fn,
40
+ name=tool.name,
41
+ title=tool.title,
42
+ description=tool.description,
43
+ annotations=tool.annotations,
44
+ icons=tool.icons,
45
+ meta=tool.meta,
46
+ )
47
+
48
+
29
49
  def build_server() -> FastMCP:
30
50
  today = date.today()
31
51
  current_year = today.year
32
52
  server = FastMCP(
33
53
  "Qingflow MCP",
34
- instructions=f"""Use this server for Qingflow operational workflows. Current date: `{today.isoformat()}`.
54
+ instructions=f"""Use this unified server for Qingflow operational and builder workflows. Current date: `{today.isoformat()}`.
35
55
 
36
56
  ## Authentication
37
57
 
@@ -203,9 +223,9 @@ Normalize relative dates before building DSL.
203
223
 
204
224
  Default to `prod` unless the user explicitly specifies `test`.
205
225
 
206
- ## Constraints
226
+ ## Builder Path
207
227
 
208
- Avoid builder-side app or schema changes here.
228
+ Use the public builder tools for package, AppForm, workflow, view, chart, portal, and application configuration work. Pin the AppForm Schema version, preserve complete form declarations and existing IDs from draft reads, and use `app_form_apply` for reconciliation. Do not fall back to legacy raw builder write routes.
209
229
 
210
230
  ## Feedback Path
211
231
 
@@ -235,6 +255,7 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
235
255
  WorkflowTools(sessions, backend).register(server)
236
256
  ViewTools(sessions, backend).register(server)
237
257
  SolutionTools(sessions, backend).register(server)
258
+ _register_missing_builder_tools(server)
238
259
  return server
239
260
 
240
261
 
@@ -44,7 +44,7 @@ def build_builder_server() -> FastMCP:
44
44
  "For existing object parameter replacement, prefer patch_views, patch_buttons, patch_resources, and patch_charts with set/unset; the tool reads current config and full-saves internally, while upsert_* is for creation or full target configuration and should not be used as an incomplete partial update. "
45
45
  "For builder delete/remove apply results, separate delete execution from readback verification: after DELETE is sent, resources expose delete_executed, readback_status, and safe_to_retry_delete=false. If readback_status is unavailable or still_exists, do not blindly repeat the delete; confirm later with app_get/view_get/chart_get or the relevant apply readback. Views/buttons use single-item readback; associated resources use one app-level resource-pool readback because there is no confirmed single-item GET. "
46
46
  "For builder write/apply results, separate write execution from final readback verification: status=partial_success with write_executed=true and safe_to_retry=false means the write was sent or succeeded, while final readback, publish verification, or metadata confirmation is pending or permission-restricted. Report it as written but unverified; do not repeat the same write blindly. "
47
- "For app_form_apply, pin the version from app_form_schema, validate the JSON declaration with the qingflow-app-form Skill command, preserve the complete body and existing IDs from a draft get, and use dataTitle/dataCover on canonical fields; exactly one top-level dataTitle is required for a non-empty body, while dataCover is optional and must be a top-level attachment field. A root appKey means update and may include packageId; no appKey means create and requires packageId plus spec.appName, spec.icon, and spec.color selected from workspace_icon_catalog_get, with template forbidden. "
47
+ "For app_form_apply, pin the version from app_form_schema, preserve the complete body and existing IDs from a draft get, and use dataTitle/dataCover on canonical fields; App Builder MCP validates the declaration inside app_form_apply, while the CLI route has a separate static validate command. Exactly one top-level dataTitle is required for a non-empty body, while dataCover is optional and must be a top-level attachment field. A root appKey means update and may include packageId; no appKey means create and requires packageId plus spec.appName, spec.icon, and spec.color selected from workspace_icon_catalog_get, with template forbidden. "
48
48
  "For app_views_apply, keep fixed saved filters in filters and configure the frontend query panel separately with query_conditions; query_conditions.rows is a matrix of field names compiled to backend queryCondition queIds. New views default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless associated_resources is explicitly patched. View writes use ViewManagementAuth (beingViewManageStatus), falling back to DataManageAuth when advanced app permissions are not enabled. "
49
49
  "For ordinary business buttons, declare them on the target view with app_views_apply.views[].action_buttons; app_views_apply writes the view first, then creates/binds the button. Use app_custom_buttons_apply only when the user explicitly asks to maintain existing standalone buttons outside view design, such as custom style/icon changes, cross-view reuse, deleting button bodies, bulk binding reorder, or exact qRobot/wings config. Button body writes require EditAppAuth; view_configs placement writes require ViewManagementAuth, matching the backend viewConfig route. For addData buttons, prefer action_buttons.target_app_key + field_mappings/default_values on the view path; do not ask agents to write raw que_relation unless maintaining a legacy config. field_mappings.source_field accepts source schema fields and supported system fields: 数据ID/row_record_id/apply_id/_id means current record id (-17), 编号/record_number means visible record number (0). To fill a target relation with the current record, map {'source_field': '数据ID', 'target_field': '目标引用字段'}; default_values is only for static constants. View button bindings merge by default; standalone view_configs are only for explicit maintenance. Builder view_key arguments are raw keys from app_get.views[].view_key and must not be prefixed with custom:. "
50
50
  "For BI reports, keep report-body development separate from Qingflow in-app display: use app_charts_apply to create, update, remove, or reorder app-source QingBI chart bodies/configs with dataSourceType=qingflow; chart dimension/metric/filter/query fields must come from app_get_fields.chart_fields, not record schema or form-only fields; dataset BI reports are not created or edited by app_charts_apply yet and should be created in QingBI first, then attached with app_associated_resources_apply using report_source=dataset. "
@@ -11895,7 +11895,7 @@ class RecordTools(ToolBase):
11895
11895
  raise RecordInputError(
11896
11896
  message=f"field '{field.que_title}' uses unsupported nested subtable writes",
11897
11897
  error_code="UNSUPPORTED_WRITE_FORMAT",
11898
- fix_hint="Nested subtable writes are not supported in app-user tools.",
11898
+ fix_hint="Nested subtable writes are not supported by the public record tools.",
11899
11899
  details={"location": location, "field": _field_ref_payload(field), "expected_format": _write_format_for_field(field), "received_value": item},
11900
11900
  )
11901
11901
  self._raise_if_verify_unsupported_write_field(field, item, location=location)
@@ -11960,7 +11960,7 @@ class RecordTools(ToolBase):
11960
11960
  raise RecordInputError(
11961
11961
  message=f"field '{field.que_title}' uses unsupported nested subtable writes",
11962
11962
  error_code="UNSUPPORTED_WRITE_FORMAT",
11963
- fix_hint="Nested subtable writes are not supported in app-user tools.",
11963
+ fix_hint="Nested subtable writes are not supported by the public record tools.",
11964
11964
  details={"location": location, "field": _field_ref_payload(field), "expected_format": _write_format_for_field(field), "received_value": raw_value},
11965
11965
  )
11966
11966
  self._raise_if_verify_unsupported_write_field(field, raw_value, location=location)
@@ -21150,7 +21150,7 @@ def _subtable_cell_ref_payload(
21150
21150
 
21151
21151
  def _unsupported_write_reason(que_type: int | None) -> str:
21152
21152
  if que_type == 14:
21153
- return "time range fields require a backend-specific native payload shape that app-user tools do not build yet"
21153
+ return "time range fields require a backend-specific native payload shape that public record tools do not build yet"
21154
21154
  if que_type == 34:
21155
21155
  return "image recognition fields are runtime AI fields and direct writes are not reliably persisted"
21156
21156
  if que_type == 35:
@@ -57,11 +57,9 @@ def _find_package_root() -> Path | None:
57
57
  if version and name in {
58
58
  "qingflow-mcp-workspace",
59
59
  "@qingflow-tech/qingflow-cli",
60
- "@qingflow-tech/qingflow-app-user-mcp",
61
- "@qingflow-tech/qingflow-app-builder-mcp",
60
+ "@qingflow-tech/qingflow-mcp",
62
61
  "@josephyan/qingflow-cli",
63
- "@josephyan/qingflow-app-user-mcp",
64
- "@josephyan/qingflow-app-builder-mcp",
62
+ "@josephyan/qingflow-mcp",
65
63
  }:
66
64
  return parent
67
65
  return None