@qingflow-tech/qingflow-cli 1.1.50 → 1.1.53

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 (71) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/pyproject.toml +8 -2
  4. package/skills/qingflow-cli/SKILL.md +115 -3
  5. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +3 -9
  6. package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +3 -2
  7. package/skills/qingflow-cli/reference/builder/30-schema-fields.md +9 -5
  8. package/skills/qingflow-cli/reference/builder/50-views.md +192 -191
  9. package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +2 -0
  10. package/skills/qingflow-cli/reference/builder/README.md +1 -1
  11. package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +6 -6
  12. package/skills/qingflow-cli/reference/data/README.md +1 -1
  13. package/skills/qingflow-cli/reference/examples/README.md +1 -1
  14. package/skills/qingflow-cli/reference/examples/views/views_apply_v1alpha1.example.json +14 -0
  15. package/skills/qingflow-cli/reference/examples/views/views_upsert_table_minimal.example.json +30 -36
  16. package/src/qingflow_mcp/app_form_compat.py +3 -0
  17. package/src/qingflow_mcp/app_form_validator.py +69 -17
  18. package/src/qingflow_mcp/builder_facade/app_form.py +497 -100
  19. package/src/qingflow_mcp/builder_facade/models.py +227 -17
  20. package/src/qingflow_mcp/builder_facade/schemas/app_form/manifest.json +1 -0
  21. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +64 -30
  22. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +4 -8
  23. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +4 -8
  24. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +6 -10
  25. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +6 -10
  26. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +11 -18
  27. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +4 -8
  28. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +4 -8
  29. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +6 -10
  30. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +4 -8
  31. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +4 -8
  32. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +4 -8
  33. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +6 -10
  34. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +4 -8
  35. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +4 -8
  36. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +4 -8
  37. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +6 -10
  38. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +46 -15
  39. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/rich_text.json +146 -0
  40. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +4 -8
  41. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +4 -8
  42. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +61 -27
  43. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +4 -8
  44. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/formula.json +43 -0
  45. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +7 -12
  46. package/src/qingflow_mcp/builder_facade/schemas/views/manifest.json +42 -0
  47. package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/complete.json +1245 -0
  48. package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/button-config.json +53 -0
  49. package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/data-permission.json +60 -0
  50. package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/display.json +73 -0
  51. package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/operation-permission.json +48 -0
  52. package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/query-condition.json +47 -0
  53. package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/summary.json +319 -0
  54. package/src/qingflow_mcp/builder_facade/service.py +1666 -509
  55. package/src/qingflow_mcp/builder_facade/views_extract.py +592 -0
  56. package/src/qingflow_mcp/builder_facade/views_schema.py +400 -0
  57. package/src/qingflow_mcp/cli/commands/builder.py +76 -55
  58. package/src/qingflow_mcp/config.py +1 -0
  59. package/src/qingflow_mcp/public_surface.py +3 -0
  60. package/src/qingflow_mcp/response_trim.py +11 -2
  61. package/src/qingflow_mcp/server_app_builder.py +34 -10
  62. package/src/qingflow_mcp/solution/compiler/view_compiler.py +2 -1
  63. package/src/qingflow_mcp/solution/executor.py +96 -8
  64. package/src/qingflow_mcp/solution/spec_models.py +1 -0
  65. package/src/qingflow_mcp/tools/ai_builder_tools.py +218 -398
  66. package/src/qingflow_mcp/tools/base.py +42 -10
  67. package/src/qingflow_mcp/tools/solution_tools.py +2 -0
  68. package/src/qingflow_mcp/tools/view_tools.py +0 -6
  69. package/src/qingflow_mcp/tools/view_writer.py +44 -0
  70. package/skills/qingflow-cli/reference/examples/views/views_batch_full.example.json +0 -159
  71. package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +0 -42
@@ -8,6 +8,7 @@ from ..backend_client import BackendRequestContext, BackendClient
8
8
  from ..auth_provider import CliSessionFacade
9
9
  from ..cloud_context import current_call_platform, current_cloud_context
10
10
  from ..config import (
11
+ DEFAULT_MEMBER_CREDIT_AVAILABILITY_PATH,
11
12
  get_credit_meter_enabled,
12
13
  get_credit_usage_base_url,
13
14
  get_credit_usage_path,
@@ -19,6 +20,16 @@ from ..session_store import BackendSession, SessionProfile, SessionStore
19
20
 
20
21
  T = TypeVar("T")
21
22
  _RUN_DEPTH: ContextVar[int] = ContextVar("qingflow_mcp_tool_run_depth", default=0)
23
+ _MEMBER_CREDIT_DENIAL_MESSAGES = {
24
+ "WORKSPACE_EXHAUSTED": "当前工作区流币余额不足,请充值后再使用 MCP",
25
+ "MEMBER_MONTHLY_EXHAUSTED": "本月个人流币额度已用完,请联系工作区管理员调整额度",
26
+ "MEMBER_TOTAL_EXHAUSTED": "个人流币总额度已用完,请联系工作区管理员调整额度",
27
+ "MEMBER_MONTHLY_AND_TOTAL_EXHAUSTED": (
28
+ "本月和个人总流币额度均已用完,请联系工作区管理员调整额度"
29
+ ),
30
+ "MEMBER_QUOTA_NOT_CONFIGURED": "当前成员尚未配置流币额度,请联系工作区管理员",
31
+ "MEMBER_USAGE_QUERY_FAILED": "成员流币额度校验失败,请稍后重试",
32
+ }
22
33
 
23
34
 
24
35
  class ToolBase:
@@ -332,28 +343,49 @@ class ToolBase:
332
343
  if context.ws_id is None:
333
344
  raise_tool_error(QingflowApiError(category="payment", message="credit meter requires wsId in current session context"))
334
345
 
346
+ credit_context = BackendRequestContext(
347
+ base_url=get_credit_usage_base_url() or context.base_url,
348
+ token=context.token,
349
+ ws_id=context.ws_id,
350
+ qf_request_id=context.qf_request_id,
351
+ qf_version=context.qf_version,
352
+ qf_version_source=context.qf_version_source,
353
+ )
354
+ self._require_member_credit_available(context=credit_context)
335
355
  self._record_credit_usage(
336
356
  tool_name=tool_name,
337
- context=context,
357
+ context=credit_context,
338
358
  )
339
359
 
360
+ def _require_member_credit_available(self, *, context: BackendRequestContext) -> None:
361
+ availability = self.backend.request("GET", context, DEFAULT_MEMBER_CREDIT_AVAILABILITY_PATH)
362
+ if not isinstance(availability, dict) or not isinstance(availability.get("allowed"), bool):
363
+ raise_tool_error(QingflowApiError(
364
+ category="payment",
365
+ message="成员流币额度校验失败,请稍后重试",
366
+ ))
367
+ if availability["allowed"]:
368
+ return
369
+
370
+ deny_reason = self._normalize_text(availability.get("denyReason")) or "UNKNOWN"
371
+ raise_tool_error(QingflowApiError(
372
+ category="payment",
373
+ message=_MEMBER_CREDIT_DENIAL_MESSAGES.get(
374
+ deny_reason,
375
+ "当前成员无法使用流币,请联系工作区管理员",
376
+ ),
377
+ details={"deny_reason": deny_reason},
378
+ ))
379
+
340
380
  def _record_credit_usage(
341
381
  self,
342
382
  *,
343
383
  tool_name: str,
344
384
  context: BackendRequestContext,
345
385
  ) -> None:
346
- usage_context = BackendRequestContext(
347
- base_url=get_credit_usage_base_url() or context.base_url,
348
- token=context.token,
349
- ws_id=context.ws_id,
350
- qf_request_id=context.qf_request_id,
351
- qf_version=context.qf_version,
352
- qf_version_source=context.qf_version_source,
353
- )
354
386
  self.backend.request(
355
387
  "POST",
356
- usage_context,
388
+ context,
357
389
  get_credit_usage_path(),
358
390
  json_body={
359
391
  "skuType": "MCP",
@@ -38,6 +38,7 @@ from .qingbi_report_tools import QingbiReportTools
38
38
  from .record_tools import RecordTools
39
39
  from .role_tools import RoleTools
40
40
  from .view_tools import ViewTools
41
+ from .view_writer import ViewWriter
41
42
  from .workspace_tools import WorkspaceTools
42
43
 
43
44
  STAGED_BUILD_MODES = {"preflight", "plan", "apply", "repair"}
@@ -1427,6 +1428,7 @@ class SolutionTools(ToolBase):
1427
1428
  app_tools=AppTools(self.sessions, self.backend),
1428
1429
  record_tools=RecordTools(self.sessions, self.backend),
1429
1430
  view_tools=ViewTools(self.sessions, self.backend),
1431
+ view_writer=ViewWriter(self.sessions, self.backend),
1430
1432
  chart_tools=QingbiReportTools(self.sessions, self.backend),
1431
1433
  portal_tools=PortalTools(self.sessions, self.backend),
1432
1434
  navigation_tools=NavigationTools(self.sessions, self.backend),
@@ -111,12 +111,6 @@ class ViewTools(ToolBase):
111
111
  body = self._require_dict(payload)
112
112
  return self._request(profile, "POST", f"/app/{app_key}/view/que/width", app_key=app_key, tool_name="设置视图列宽", json_body=body)
113
113
 
114
- def view_create(self, *, profile: str, payload: JSONObject) -> JSONObject:
115
- """执行视图相关逻辑。"""
116
- body = self._require_dict(payload)
117
- app_key = str(body.get("appKey") or "")
118
- return self._request(profile, "POST", "/view/viewConfig", app_key=app_key, tool_name="创建视图", json_body=body)
119
-
120
114
  def view_get_config(self, *, profile: str, viewgraph_key: str) -> JSONObject:
121
115
  """执行视图相关逻辑。"""
122
116
  self._require_viewgraph_key(viewgraph_key)
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from ..errors import QingflowApiError, raise_tool_error
6
+ from ..json_types import JSONObject, JSONValue
7
+ from .base import ToolBase
8
+
9
+
10
+ class ViewWriter(ToolBase):
11
+ """视图创建写手——POST /view/viewConfig 的唯一落点。
12
+
13
+ 仅限 builder facade 与 solution executor 内部使用;
14
+ 不注册 MCP 工具、不进 public_surface、不暴露 CLI。
15
+ 公开层的视图创建唯一入口是 ``builder views apply`` / ``app_views_apply``。
16
+ """
17
+
18
+ def create_view(self, *, profile: str, payload: JSONObject) -> JSONObject:
19
+ """创建视图(内部 writer;公开入口请走 app_views_apply)。"""
20
+ body = self._require_dict(payload)
21
+ app_key = str(body.get("appKey") or "")
22
+ return self._request(profile, "POST", "/view/viewConfig", app_key=app_key, tool_name="创建视图", json_body=body)
23
+
24
+ def _request(
25
+ self,
26
+ profile: str,
27
+ method: str,
28
+ path: str,
29
+ *,
30
+ json_body: JSONValue = None,
31
+ params: JSONObject | None = None,
32
+ tool_name: str,
33
+ **extra: Any,
34
+ ) -> JSONObject:
35
+ def runner(session_profile, context):
36
+ result = self.backend.request(method, context, path, json_body=json_body, params=params)
37
+ response: JSONObject = {"profile": profile, "ws_id": session_profile.selected_ws_id, "result": result}
38
+ response.update(extra)
39
+ return response
40
+
41
+ return self._run(profile, runner, tool_name=tool_name)
42
+
43
+
44
+ __all__ = ["ViewWriter", "QingflowApiError", "raise_tool_error"]
@@ -1,159 +0,0 @@
1
- {
2
- "views": [
3
- {
4
- "operation": "upsert",
5
- "app_key": "WORK_ORDER_APP_KEY",
6
- "name": "生产工单执行视图",
7
- "type": "table",
8
- "columns": ["工单编号", "产品", "工单状态", "负责人", "计划完成日期"],
9
- "filters": [
10
- {"field_name": "工单状态", "operator": "in", "values": ["待排产", "生产中"]},
11
- {"field_name": "产品", "operator": "contains", "value": "标准"},
12
- {"field_name": "计划完成日期", "operator": "lte", "value": "2026-07-31"}
13
- ],
14
- "query_conditions": {
15
- "enabled": true,
16
- "exact": false,
17
- "hide_before_query": false,
18
- "rows": [["工单编号", "工单状态"], ["负责人", "计划完成日期"]]
19
- },
20
- "visibility": {
21
- "mode": "workspace",
22
- "selectors": {},
23
- "external_mode": "not",
24
- "external_selectors": {}
25
- },
26
- "action_buttons": [
27
- {
28
- "client_key": "create_quality_from_list",
29
- "text": "行内创建质检",
30
- "action": "add_data",
31
- "target_app_key": "QUALITY_APP_KEY",
32
- "field_mappings": [
33
- {"source_field": "数据ID", "target_field": "关联工单"}
34
- ],
35
- "default_values": {"处理状态": "待处理"},
36
- "placement": "list",
37
- "primary": true,
38
- "style_preset": "primary_blue",
39
- "visible_when": [
40
- {"field_name": "工单状态", "operator": "eq", "value": "生产中"}
41
- ]
42
- },
43
- {
44
- "client_key": "create_quality_from_detail",
45
- "text": "详情创建质检",
46
- "action": "add_data",
47
- "target_app_key": "QUALITY_APP_KEY",
48
- "field_mappings": [
49
- {"source_field": "数据ID", "target_field": "关联工单"}
50
- ],
51
- "default_values": {"处理状态": "待处理"},
52
- "placement": "detail",
53
- "style_preset": "neutral_outline"
54
- },
55
- {
56
- "client_key": "open_work_order_sop",
57
- "text": "打开SOP",
58
- "action": "link",
59
- "url": "https://example.com/sop/work-order",
60
- "placement": "header",
61
- "style_preset": "text_blue"
62
- }
63
- ]
64
- },
65
- {
66
- "operation": "upsert",
67
- "app_key": "WORK_ORDER_APP_KEY",
68
- "name": "工单状态看板",
69
- "type": "board",
70
- "columns": ["工单编号", "产品", "负责人", "计划完成日期"],
71
- "group_by": "工单状态",
72
- "filters": [
73
- {"field_name": "工单状态", "operator": "not_empty"},
74
- {"field_name": "关闭日期", "operator": "is_empty"}
75
- ],
76
- "query_conditions": {
77
- "enabled": true,
78
- "rows": [["工单编号", "负责人"], ["计划完成日期"]]
79
- },
80
- "action_buttons": [
81
- {
82
- "text": "看板行内创建质检",
83
- "action": "add_data",
84
- "target_app_key": "QUALITY_APP_KEY",
85
- "field_mappings": [
86
- {"source_field": "数据ID", "target_field": "关联工单"}
87
- ],
88
- "placement": "list",
89
- "visible_when": [
90
- {"field_name": "工单状态", "operator": "eq", "value": "已完工"}
91
- ]
92
- },
93
- {
94
- "text": "看板详情创建质检",
95
- "action": "add_data",
96
- "target_app_key": "QUALITY_APP_KEY",
97
- "field_mappings": [
98
- {"source_field": "数据ID", "target_field": "关联工单"}
99
- ],
100
- "placement": "detail"
101
- }
102
- ]
103
- },
104
- {
105
- "operation": "upsert",
106
- "app_key": "WORK_ORDER_APP_KEY",
107
- "name": "工单计划甘特图",
108
- "type": "gantt",
109
- "columns": ["工单编号", "产品", "工单状态", "计划开始日期", "计划完成日期"],
110
- "start_field": "计划开始日期",
111
- "end_field": "计划完成日期",
112
- "title_field": "工单编号",
113
- "filters": [
114
- {"field_name": "计划开始日期", "operator": "gte", "value": "2026-07-01"}
115
- ],
116
- "query_conditions": {
117
- "enabled": true,
118
- "rows": [["工单编号", "工单状态"], ["计划开始日期", "计划完成日期"]]
119
- },
120
- "action_buttons": [
121
- {
122
- "text": "详情查看排产说明",
123
- "action": "link",
124
- "url": "https://example.com/sop/schedule",
125
- "placement": "detail"
126
- }
127
- ]
128
- },
129
- {
130
- "operation": "patch",
131
- "app_key": "WORK_ORDER_APP_KEY",
132
- "view_key": "RAW_VIEW_KEY_TO_PATCH",
133
- "set": {
134
- "columns": ["工单编号", "工单状态", "负责人"],
135
- "filters": [
136
- {"field_name": "工单状态", "operator": "neq", "value": "已关闭"}
137
- ],
138
- "query_conditions": {
139
- "enabled": true,
140
- "rows": [["工单编号", "工单状态"], ["负责人"]]
141
- },
142
- "action_buttons": [
143
- {
144
- "text": "补充详情按钮",
145
- "action": "link",
146
- "url": "https://example.com/detail-help",
147
- "placement": "detail"
148
- }
149
- ],
150
- "action_buttons_mode": "merge"
151
- }
152
- },
153
- {
154
- "operation": "remove",
155
- "app_key": "WORK_ORDER_APP_KEY",
156
- "view_key": "RAW_VIEW_KEY_TO_REMOVE"
157
- }
158
- ]
159
- }
@@ -1,42 +0,0 @@
1
- {
2
- "views": [
3
- {
4
- "operation": "upsert",
5
- "app_key": "APP_KEY",
6
- "name": "示例业务操作视图_CLI模板",
7
- "type": "table",
8
- "columns": ["主记录编号", "主记录名称", "状态", "负责人"],
9
- "filters": [
10
- {"field_name": "状态", "operator": "neq", "value": "已关闭"}
11
- ],
12
- "query_conditions": {
13
- "enabled": true,
14
- "exact": false,
15
- "hide_before_query": false,
16
- "rows": [["主记录编号", "主记录名称"], ["状态", "负责人"]]
17
- },
18
- "action_buttons": [
19
- {
20
- "text": "行内创建子记录",
21
- "action": "add_data",
22
- "target_app_key": "TARGET_APP_KEY",
23
- "field_mappings": [
24
- {"source_field": "数据ID", "target_field": "关联主记录"}
25
- ],
26
- "default_values": {"目标状态字段": "REPLACE_WITH_TARGET_OPTION_LABEL"},
27
- "placement": "list"
28
- },
29
- {
30
- "text": "详情创建子记录",
31
- "action": "add_data",
32
- "target_app_key": "TARGET_APP_KEY",
33
- "field_mappings": [
34
- {"source_field": "数据ID", "target_field": "关联主记录"}
35
- ],
36
- "default_values": {"目标状态字段": "REPLACE_WITH_TARGET_OPTION_LABEL"},
37
- "placement": "detail"
38
- }
39
- ]
40
- }
41
- ]
42
- }