@qingflow-tech/qingflow-cli 1.1.48-beta.5 → 1.1.49

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 (53) hide show
  1. package/README.md +4 -4
  2. package/npm/lib/runtime.mjs +13 -29
  3. package/package.json +1 -1
  4. package/pyproject.toml +1 -1
  5. package/skills/qingflow-cli/SKILL.md +410 -91
  6. package/skills/qingflow-cli/manifest.yaml +1 -1
  7. package/skills/qingflow-cli/reference/00-INDEX.md +4 -4
  8. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +1 -1
  9. package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +137 -3
  10. package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +4 -6
  11. package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +14 -14
  12. package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +19 -20
  13. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +14 -18
  14. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +12 -7
  15. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +1 -2
  16. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +1 -1
  17. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +95 -76
  18. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_EXPLORATION_REPORT.md +4 -6
  19. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md +13 -13
  20. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +19 -20
  21. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +14 -18
  22. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +1 -2
  23. package/skills/qingflow-cli/reference/record/analysis/README.md +113 -21
  24. package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +10 -10
  25. package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +9 -9
  26. package/skills/qingflow-cli/reference/record/analysis/business-context.md +1 -1
  27. package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +10 -10
  28. package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +80 -52
  29. package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +2 -2
  30. package/skills/qingflow-common/SKILL.md +17 -10
  31. package/src/qingflow_mcp/builder_facade/service.py +1 -119
  32. package/src/qingflow_mcp/cli/commands/__init__.py +1 -2
  33. package/src/qingflow_mcp/cli/commands/auth.py +1 -1
  34. package/src/qingflow_mcp/cli/commands/record.py +1 -1
  35. package/src/qingflow_mcp/cli/context.py +1 -4
  36. package/src/qingflow_mcp/cli/formatters.py +4 -159
  37. package/src/qingflow_mcp/cli/main.py +1 -4
  38. package/src/qingflow_mcp/public_surface.py +0 -6
  39. package/src/qingflow_mcp/response_trim.py +3 -3
  40. package/src/qingflow_mcp/server.py +0 -2
  41. package/src/qingflow_mcp/server_app_builder.py +0 -16
  42. package/src/qingflow_mcp/server_app_user.py +3 -10
  43. package/src/qingflow_mcp/tools/ai_builder_tools.py +39 -208
  44. package/src/qingflow_mcp/tools/app_tools.py +13 -52
  45. package/src/qingflow_mcp/tools/login_link_tools.py +6 -34
  46. package/src/qingflow_mcp/tools/record_tools.py +46 -11
  47. package/src/qingflow_mcp/tools/resource_read_tools.py +0 -13
  48. package/skills/qingflow-cli/reference/data/README.md +0 -32
  49. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_LEGACY_RECORD_RETRIEVAL_WORKFLOW.md +0 -50
  50. package/src/qingflow_mcp/cli/commands/data.py +0 -134
  51. package/src/qingflow_mcp/resource_uri.py +0 -241
  52. package/src/qingflow_mcp/tools/csv_export.py +0 -168
  53. package/src/qingflow_mcp/tools/data_tools.py +0 -271
@@ -19,7 +19,6 @@ from ..backend_client import BackendRequestContext
19
19
  from ..errors import QingflowApiError, backend_code_int, backend_code_value_int, is_auth_like_error
20
20
  from ..json_types import JSONObject
21
21
  from ..list_type_labels import RECORD_LIST_TYPE_LABELS, SYSTEM_VIEW_ID_TO_LIST_TYPE
22
- from ..resource_uri import validate_positive_resource_id, validate_resource_identity
23
22
  from ..solution.build_assembly_store import BuildAssemblyStore, default_artifacts, default_manifest
24
23
  from ..solution.compiler.chart_compiler import qingbi_workspace_visible_auth
25
24
  from ..solution.compiler.form_compiler import build_question, default_form_payload, default_member_auth
@@ -1492,74 +1491,6 @@ class AiBuilderFacade:
1492
1491
  details={"solution_key": requested_solution_key},
1493
1492
  suggested_next_call={"tool_name": "solution_install", "arguments": {"profile": profile, **normalized_args}},
1494
1493
  )
1495
- raw_result = created.get("result") if isinstance(created.get("result"), dict) else {}
1496
- raw_tag_ids = raw_result.get("tagIds")
1497
- raw_app_keys = raw_result.get("appKeys")
1498
- tag_ids, tag_ids_complete = _unique_resource_tag_ids(raw_tag_ids)
1499
- app_keys, app_keys_complete = _unique_resource_app_keys(raw_app_keys)
1500
- resource_list_complete = tag_ids_complete and app_keys_complete
1501
- resources: list[JSONObject] = []
1502
- warnings: list[JSONObject] = []
1503
-
1504
- for tag_id in tag_ids:
1505
- name = None
1506
- try:
1507
- detail = self.packages.package_get(profile=profile, tag_id=tag_id, include_raw=False)
1508
- package = detail.get("result") if isinstance(detail.get("result"), dict) else {}
1509
- name = str(package.get("tagName") or package.get("tag_name") or "").strip() or None
1510
- except (QingflowApiError, RuntimeError):
1511
- pass
1512
- if not name:
1513
- warnings.append(_warning("RESOURCE_NAME_UNAVAILABLE", "installed package name could not be read", package_id=tag_id))
1514
- resources.append(
1515
- _compact_dict(
1516
- {
1517
- "resource_type": "package",
1518
- "operation": "installed",
1519
- "status": "success",
1520
- "id": tag_id,
1521
- "key": str(tag_id),
1522
- "name": name,
1523
- "ids": {"package_id": tag_id},
1524
- }
1525
- )
1526
- )
1527
-
1528
- for app_key in app_keys:
1529
- name = None
1530
- parent = None
1531
- try:
1532
- detail = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=False)
1533
- app = detail.get("result") if isinstance(detail.get("result"), dict) else {}
1534
- name = str(app.get("formTitle") or app.get("appName") or "").strip() or None
1535
- matching_tag_ids = [tag_id for tag_id in _unique_positive_ints(app.get("tagIds")) if tag_id in tag_ids]
1536
- if len(matching_tag_ids) == 1:
1537
- parent = {"resource_type": "package", "id": matching_tag_ids[0], "key": str(matching_tag_ids[0])}
1538
- except (QingflowApiError, RuntimeError):
1539
- pass
1540
- if not name:
1541
- warnings.append(_warning("RESOURCE_NAME_UNAVAILABLE", "installed application name could not be read", app_key=app_key))
1542
- resources.append(
1543
- _compact_dict(
1544
- {
1545
- "resource_type": "app",
1546
- "operation": "installed",
1547
- "status": "success",
1548
- "key": app_key,
1549
- "name": name,
1550
- "ids": {"app_key": app_key},
1551
- "parent": parent,
1552
- }
1553
- )
1554
- )
1555
-
1556
- if not resource_list_complete:
1557
- warnings.append(
1558
- _warning(
1559
- "RESOURCE_LIST_INCOMPLETE",
1560
- "solution installation succeeded, but its returned package/application identity list was incomplete",
1561
- )
1562
- )
1563
1494
  return {
1564
1495
  "status": "success",
1565
1496
  "error_code": None,
@@ -1572,15 +1503,9 @@ class AiBuilderFacade:
1572
1503
  "request_id": created.get("request_id"),
1573
1504
  "suggested_next_call": None,
1574
1505
  "noop": False,
1575
- "warnings": warnings,
1576
- "verified": False,
1577
- "verification": {
1578
- "resource_list_complete": resource_list_complete,
1579
- "business_verified": False,
1580
- },
1506
+ "verification": {},
1581
1507
  "write_executed": True,
1582
1508
  "safe_to_retry": False,
1583
- "resources": resources,
1584
1509
  }
1585
1510
 
1586
1511
  def package_list(self, *, profile: str, trial_status: str = "all", query: str = "") -> JSONObject:
@@ -17236,49 +17161,6 @@ def _coerce_positive_int(value: Any) -> int | None:
17236
17161
  return None
17237
17162
 
17238
17163
 
17239
- def _unique_positive_ints(value: Any) -> list[int]:
17240
- if not isinstance(value, list):
17241
- return []
17242
- result: list[int] = []
17243
- for item in value:
17244
- number = _coerce_positive_int(item)
17245
- if number is not None and number not in result:
17246
- result.append(number)
17247
- return result
17248
-
17249
-
17250
- def _unique_resource_tag_ids(value: Any) -> tuple[list[int], bool]:
17251
- if not isinstance(value, list):
17252
- return [], False
17253
- result: list[int] = []
17254
- complete = True
17255
- for item in value:
17256
- try:
17257
- tag_id = validate_positive_resource_id(item, "package_id")
17258
- except (ValueError, UnicodeError):
17259
- complete = False
17260
- continue
17261
- if tag_id not in result:
17262
- result.append(tag_id)
17263
- return result, complete
17264
-
17265
-
17266
- def _unique_resource_app_keys(value: Any) -> tuple[list[str], bool]:
17267
- if not isinstance(value, list):
17268
- return [], False
17269
- result: list[str] = []
17270
- complete = True
17271
- for item in value:
17272
- try:
17273
- app_key = validate_resource_identity(item, "app_key")
17274
- except (ValueError, UnicodeError):
17275
- complete = False
17276
- continue
17277
- if app_key not in result:
17278
- result.append(app_key)
17279
- return result, complete
17280
-
17281
-
17282
17164
  def _coerce_nonnegative_int(value: Any) -> int | None:
17283
17165
  if isinstance(value, bool) or value is None:
17284
17166
  return None
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
 
5
- from . import app, auth, builder, chart, data, exports, imports, portal, record, task, view, workspace
5
+ from . import app, auth, builder, chart, exports, imports, portal, record, task, view, workspace
6
6
 
7
7
 
8
8
  def register_all_commands(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
@@ -13,7 +13,6 @@ def register_all_commands(subparsers: argparse._SubParsersAction[argparse.Argume
13
13
  view.register(subparsers)
14
14
  chart.register(subparsers)
15
15
  record.register(subparsers)
16
- data.register(subparsers)
17
16
  imports.register(subparsers)
18
17
  exports.register(subparsers)
19
18
  task.register(subparsers)
@@ -41,7 +41,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
41
41
  whoami.set_defaults(handler=_handle_whoami, format_hint="auth_whoami")
42
42
 
43
43
  login_link = auth_subparsers.add_parser("login-link", help="生成当前工作区的临时一次性登录链接(不自动打开)")
44
- login_link.add_argument("--redirect-path", default="", help="登录后跳转的站内路径;资源路径必须属于当前工作区,其他业务路径可包含查询参数和锚点")
44
+ login_link.add_argument("--redirect-path", default="", help="登录后跳转的站内路径,可包含查询参数和锚点")
45
45
  login_link.set_defaults(handler=_handle_login_link, format_hint="login_link")
46
46
 
47
47
  logout = auth_subparsers.add_parser("logout", help="退出登录")
@@ -9,7 +9,7 @@ from .common import load_list_arg, load_object_arg, raise_config_error, require_
9
9
 
10
10
 
11
11
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
12
- parser = subparsers.add_parser("record", help="日志、附件、导入、代码块及 record 专项能力")
12
+ parser = subparsers.add_parser("record", help="记录与表结构")
13
13
  record_subparsers = parser.add_subparsers(
14
14
  dest="record_command",
15
15
  required=True,
@@ -10,13 +10,12 @@ from ..session_store import SessionStore
10
10
  from ..tools.ai_builder_tools import AiBuilderTools
11
11
  from ..tools.app_tools import AppTools
12
12
  from ..tools.auth_tools import AuthTools
13
+ from ..tools.login_link_tools import LoginLinkTools
13
14
  from ..tools.code_block_tools import CodeBlockTools
14
- from ..tools.data_tools import DataTools
15
15
  from ..tools.export_tools import ExportTools
16
16
  from ..tools.feedback_tools import FeedbackTools
17
17
  from ..tools.file_tools import FileTools
18
18
  from ..tools.import_tools import ImportTools
19
- from ..tools.login_link_tools import LoginLinkTools
20
19
  from ..tools.record_tools import RecordTools
21
20
  from ..tools.resource_read_tools import ResourceReadTools
22
21
  from ..tools.task_context_tools import TaskContextTools
@@ -34,7 +33,6 @@ class CliContext:
34
33
  app: AppTools
35
34
  resource: ResourceReadTools
36
35
  record: RecordTools
37
- data: DataTools
38
36
  code_block: CodeBlockTools
39
37
  imports: ImportTools
40
38
  exports: ExportTools
@@ -66,7 +64,6 @@ def build_cli_context() -> CliContext:
66
64
  app=AppTools(sessions, backend),
67
65
  resource=ResourceReadTools(sessions, backend),
68
66
  record=RecordTools(sessions, backend),
69
- data=DataTools(sessions, backend),
70
67
  code_block=CodeBlockTools(sessions, backend),
71
68
  imports=ImportTools(sessions, backend),
72
69
  exports=ExportTools(sessions, backend),
@@ -8,10 +8,7 @@ from typing import Any, TextIO
8
8
  def emit_text_result(result: dict[str, Any], *, hint: str, stream: TextIO) -> None:
9
9
  text = _format_cancelled_result(result)
10
10
  if text is None:
11
- if hint in _DATA_FORMAT_HINTS:
12
- # 数据面失败也必须保留 field_errors 与列表型 hints,不能先走通用失败裁剪。
13
- text = _format_data_result(result)
14
- elif _is_failed_result(result) and hint != "task_action_execute":
11
+ if _is_failed_result(result) and hint != "task_action_execute":
15
12
  text = _format_failed_result(result)
16
13
  else:
17
14
  formatter = _FORMATTERS.get(hint, _format_generic)
@@ -93,92 +90,6 @@ def _format_generic(result: dict[str, Any]) -> str:
93
90
  return "\n".join(lines) + "\n"
94
91
 
95
92
 
96
- def _format_data_result(result: dict[str, Any]) -> str:
97
- lines = [f"Status: {result.get('status') or ('success' if result.get('ok') is not False else 'failed')}"]
98
- if result.get("format") == "csv":
99
- files = result.get("files") if isinstance(result.get("files"), list) else []
100
- lines.extend(
101
- [
102
- f"Local dir: {result.get('local_dir') or '-'}",
103
- f"Files: {len(files)}",
104
- ]
105
- )
106
- for item in files:
107
- if isinstance(item, dict):
108
- lines.append(
109
- f"- part {item.get('part')}: {item.get('local_path')} ({item.get('row_count')} rows)"
110
- )
111
- lines.extend(
112
- [
113
- f"Rows: {result.get('row_count')}",
114
- f"Complete: {result.get('complete')}",
115
- f"Truncated: {result.get('truncated')}",
116
- f"Safe for final conclusion: {result.get('safe_for_final_conclusion')}",
117
- f"Stopped reason: {result.get('stopped_reason') or '-'}",
118
- ]
119
- )
120
- access = result.get("access") if isinstance(result.get("access"), dict) else {}
121
- if access:
122
- sources = access.get("scope_sources") if isinstance(access.get("scope_sources"), list) else []
123
- lines.append(
124
- f"Access: level={access.get('level') or '-'} / can_write={access.get('can_write')}"
125
- f" / scope_sources={','.join(str(item) for item in sources) or '-'}"
126
- )
127
-
128
- records = result.get("records") if isinstance(result.get("records"), dict) else {}
129
- if records:
130
- items = records.get("items") if isinstance(records.get("items"), list) else []
131
- lines.append(
132
- "Pagination: "
133
- f"page={records.get('page')} / requested_page_size={records.get('requested_page_size')} / "
134
- f"page_size={records.get('page_size')} / total={records.get('total')} / "
135
- f"has_more={records.get('has_more')} / returned={len(items)}"
136
- )
137
- record = result.get("record") if isinstance(result.get("record"), dict) else {}
138
- if record:
139
- lines.append(f"Record ID: {record.get('record_id') or '-'}")
140
- items = result.get("items") if isinstance(result.get("items"), list) else []
141
- if items:
142
- lines.append(f"Items: {len(items)}")
143
-
144
- for key, label in (
145
- ("error_code", "Error Code"),
146
- ("message", "Message"),
147
- ("write_executed", "Write Executed"),
148
- ("retry_safe", "Retry Safe"),
149
- ):
150
- if key in result:
151
- lines.append(f"{label}: {result.get(key)}")
152
-
153
- field_errors = result.get("field_errors") if isinstance(result.get("field_errors"), list) else []
154
- if field_errors:
155
- lines.append("Field Errors:")
156
- _append_data_field_errors(lines, field_errors)
157
- for index, item in enumerate(items):
158
- if not isinstance(item, dict) or not isinstance(item.get("field_errors"), list):
159
- continue
160
- lines.append(f"Item {index} Field Errors:")
161
- _append_data_field_errors(lines, item["field_errors"])
162
-
163
- hints = result.get("hints") if isinstance(result.get("hints"), list) else []
164
- lines.append(f"Hints: {len(hints)}")
165
- for hint in hints:
166
- if isinstance(hint, dict):
167
- lines.append(f"- {hint.get('code') or '-'}: {hint.get('message') or '-'}")
168
- else:
169
- lines.append(f"- {hint}")
170
- return "\n".join(lines) + "\n"
171
-
172
-
173
- def _append_data_field_errors(lines: list[str], field_errors: list[Any]) -> None:
174
- for error in field_errors:
175
- if isinstance(error, dict):
176
- lines.append(
177
- f"- {error.get('path') or '-'}: {error.get('message') or '-'}"
178
- f" / fix_hint={error.get('fix_hint') or '-'}"
179
- )
180
-
181
-
182
93
  def _format_whoami(result: dict[str, Any]) -> str:
183
94
  lines = [
184
95
  f"User: {result.get('nick_name') or '-'} ({result.get('email') or '-'})",
@@ -284,10 +195,9 @@ def _format_app_items(result: dict[str, Any]) -> str:
284
195
  str(item.get("app_key") or item.get("appKey") or ""),
285
196
  str(item.get("app_name") or item.get("name") or item.get("title") or ""),
286
197
  str(item.get("package_name") or item.get("tag_name") or ""),
287
- str(item.get("uri") or ""),
288
198
  ]
289
199
  )
290
- return _render_titled_table("Apps", ["app_key", "app_name", "package", "uri"], rows)
200
+ return _render_titled_table("Apps", ["app_key", "app_name", "package"], rows)
291
201
 
292
202
 
293
203
  def _format_app_get(result: dict[str, Any]) -> str:
@@ -295,7 +205,6 @@ def _format_app_get(result: dict[str, Any]) -> str:
295
205
  lines = [
296
206
  f"App: {data.get('app_name') or '-'}",
297
207
  f"App Key: {data.get('app_key') or '-'}",
298
- f"URI: {data.get('uri') or '-'}",
299
208
  f"Can Create: {data.get('can_create')}",
300
209
  ]
301
210
  import_capability = data.get("import_capability")
@@ -319,7 +228,7 @@ def _format_app_get(result: dict[str, Any]) -> str:
319
228
  lines.append(f"Accessible Views: {len(views)}")
320
229
  for item in views[:10]:
321
230
  if isinstance(item, dict):
322
- lines.append(f"- {item.get('view_id')}: {item.get('name')} / {item.get('uri') or '-'}")
231
+ lines.append(f"- {item.get('view_id')}: {item.get('name')}")
323
232
  _append_warnings(lines, result.get("warnings"))
324
233
  return "\n".join(lines) + "\n"
325
234
 
@@ -391,7 +300,6 @@ def _format_view_get(result: dict[str, Any]) -> str:
391
300
  f"View Key: {data.get('view_key') or '-'}",
392
301
  f"App Key: {data.get('app_key') or '-'}",
393
302
  f"View Type: {data.get('view_type') or '-'}",
394
- f"URI: {data.get('uri') or '-'}",
395
303
  f"Analysis Supported: {data.get('analysis_supported')}",
396
304
  ]
397
305
  if export_capability:
@@ -1276,7 +1184,6 @@ def _format_builder_summary(result: dict[str, Any]) -> str:
1276
1184
  ("view_key", "View Key"),
1277
1185
  ("chart_id", "Chart ID"),
1278
1186
  ("package_id", "Package ID"),
1279
- ("uri", "URI"),
1280
1187
  ("verified", "Verified"),
1281
1188
  ("write_executed", "Write Executed"),
1282
1189
  ("delete_executed", "Delete Executed"),
@@ -1309,7 +1216,6 @@ def _format_builder_summary(result: dict[str, Any]) -> str:
1309
1216
  "associations",
1310
1217
  "package_ids",
1311
1218
  "app_keys",
1312
- "resources",
1313
1219
  ):
1314
1220
  value = result.get(key)
1315
1221
  if not isinstance(value, list) and isinstance(data, dict):
@@ -1321,15 +1227,6 @@ def _format_builder_summary(result: dict[str, Any]) -> str:
1321
1227
  lines.append("- " + _builder_item_summary(item))
1322
1228
  if len(value) > len(visible_items):
1323
1229
  lines.append(f"... {len(value) - len(visible_items)} more")
1324
- if key == "apps":
1325
- uri_lines = [
1326
- line
1327
- for item in value
1328
- for line in _builder_batch_item_uri_lines(item)
1329
- ]
1330
- if uri_lines:
1331
- lines.append("Resource URIs:")
1332
- lines.extend(f"- {line}" for line in uri_lines)
1333
1230
  components = result.get("components") if isinstance(result.get("components"), list) else []
1334
1231
  if components:
1335
1232
  lines.append("Component Refs:")
@@ -1398,7 +1295,6 @@ def _builder_item_summary(item: Any) -> str:
1398
1295
  "uid",
1399
1296
  "userId",
1400
1297
  "email",
1401
- "uri",
1402
1298
  )
1403
1299
  parts = [
1404
1300
  f"{key}={item.get(key)}"
@@ -1410,50 +1306,6 @@ def _builder_item_summary(item: Any) -> str:
1410
1306
  return " / ".join(parts) if parts else json.dumps(item, ensure_ascii=False)
1411
1307
 
1412
1308
 
1413
- def _builder_batch_item_uri_lines(item: Any) -> list[str]:
1414
- if not isinstance(item, dict):
1415
- return []
1416
- item_status = str(item.get("status") or "").lower()
1417
- if item_status not in {"success", "partial_success"}:
1418
- return []
1419
- data = item.get("data") if isinstance(item.get("data"), dict) else item.get("result")
1420
- if not isinstance(data, dict) or not _builder_uri_is_confirmed(data, fallback_status=item_status):
1421
- return []
1422
- lines: list[str] = []
1423
- if data.get("uri"):
1424
- lines.append(f"URI: {data['uri']}")
1425
- views = data.get("views") if isinstance(data.get("views"), list) else []
1426
- for view in views:
1427
- if (
1428
- isinstance(view, dict)
1429
- and view.get("uri")
1430
- and _builder_uri_is_confirmed(view, fallback_status=item_status)
1431
- ):
1432
- lines.append(f"View URI: {view['uri']}")
1433
- return lines
1434
-
1435
-
1436
- def _builder_uri_is_confirmed(value: dict[str, Any], *, fallback_status: str) -> bool:
1437
- status = str(value.get("status") or fallback_status).lower()
1438
- operation = str(value.get("operation") or "").lower()
1439
- return (
1440
- status in {"success", "partial_success", "unchanged", "verified"}
1441
- and operation
1442
- not in {
1443
- "failed",
1444
- "pending",
1445
- "planned",
1446
- "readback_pending",
1447
- "pending_readback",
1448
- "unknown",
1449
- "remove",
1450
- "removed",
1451
- "delete",
1452
- "deleted",
1453
- }
1454
- )
1455
-
1456
-
1457
1309
  def _format_file_upload_local(result: dict[str, Any]) -> str:
1458
1310
  lines = [
1459
1311
  f"Upload Kind: {result.get('upload_kind') or result.get('requested_upload_kind') or '-'}",
@@ -1716,7 +1568,7 @@ def _task_action_debug_lines(result: dict[str, Any]) -> list[str]:
1716
1568
 
1717
1569
 
1718
1570
  def _format_login_link(result: dict[str, Any]) -> str:
1719
- return f"登录链接:{result['url']}\n过期时间(Unix 毫秒):{result['expires_at']}\n链接只能使用一次,请勿泄露;仅在用户要求打开时才打开。\n"
1571
+ return f"登录链接:{result['url']}\n过期时间(Unix 毫秒):{result['expires_at']}\n链接仅可使用一次,请勿泄露。\n"
1720
1572
 
1721
1573
 
1722
1574
  _FORMATTERS = {
@@ -1758,11 +1610,4 @@ _FORMATTERS = {
1758
1610
  "builder_summary": _format_builder_summary,
1759
1611
  "file_upload_local": _format_file_upload_local,
1760
1612
  "feedback_submit": _format_feedback_submit,
1761
- "data_query": _format_data_result,
1762
- "data_get": _format_data_result,
1763
- "data_insert": _format_data_result,
1764
- "data_update": _format_data_result,
1765
- "data_delete": _format_data_result,
1766
1613
  }
1767
-
1768
- _DATA_FORMAT_HINTS = {"data_query", "data_get", "data_insert", "data_update", "data_delete"}
@@ -2,13 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
  import json
5
- import os
6
5
  import sys
7
6
  from typing import Any, Callable, TextIO
8
7
 
9
8
  from ..errors import QingflowApiError, backend_code_value_int, message_looks_like_invalid_token
10
9
  from ..public_surface import cli_public_tool_spec_from_namespace
11
- from ..resource_uri import cli_resource_base_url
12
10
  from ..response_trim import resolve_cli_tool_name, trim_error_response, trim_public_response
13
11
  from ..tools.ai_builder_tools import _attach_builder_apply_envelope
14
12
  from ..version import get_cli_version, get_cli_version_info
@@ -96,8 +94,7 @@ def run(
96
94
  context = context_factory()
97
95
  if not bool(args.json):
98
96
  _emit_cli_effective_context_notice(args, context, stream=err)
99
- with cli_resource_base_url(os.getenv("QINGFLOW_BASE_URL") or ""):
100
- result = handler(args, context)
97
+ result = handler(args, context)
101
98
  except SystemExit as exc:
102
99
  return int(exc.code or 0)
103
100
  except RuntimeError as exc:
@@ -40,11 +40,6 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
40
40
  PublicToolSpec(USER_DOMAIN, "workspace_select", ("workspace_select",), ("workspace", "select")),
41
41
  PublicToolSpec(USER_DOMAIN, "app_list", ("app_list",), ("app", "list"), cli_show_effective_context=True),
42
42
  PublicToolSpec(USER_DOMAIN, "app_get", ("app_get",), ("app", "get"), cli_show_effective_context=True),
43
- PublicToolSpec(USER_DOMAIN, "data_query", ("data_query",), ("data", "query"), cli_show_effective_context=True),
44
- PublicToolSpec(USER_DOMAIN, "data_get", ("data_get",), ("data", "get"), cli_show_effective_context=True),
45
- PublicToolSpec(USER_DOMAIN, "data_insert", ("data_insert",), ("data", "insert"), cli_show_effective_context=True, cli_context_write=True),
46
- PublicToolSpec(USER_DOMAIN, "data_update", ("data_update",), ("data", "update"), cli_show_effective_context=True, cli_context_write=True),
47
- PublicToolSpec(USER_DOMAIN, "data_delete", ("data_delete",), ("data", "delete"), cli_show_effective_context=True, cli_context_write=True),
48
43
  PublicToolSpec(USER_DOMAIN, "portal_list", ("portal_list",), ("portal", "list"), cli_show_effective_context=True),
49
44
  PublicToolSpec(USER_DOMAIN, "portal_get", ("portal_get",), ("portal", "get"), cli_show_effective_context=True),
50
45
  PublicToolSpec(USER_DOMAIN, "view_get", ("view_get",), ("view", "get"), cli_show_effective_context=True),
@@ -133,7 +128,6 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
133
128
  PublicToolSpec(BUILDER_DOMAIN, "auth_use_credential", ("auth_use_credential",), ("builder", "auth", "use-credential"), cli_public=False),
134
129
  PublicToolSpec(BUILDER_DOMAIN, "auth_whoami", ("auth_whoami",), ("builder", "auth", "whoami"), cli_public=False),
135
130
  PublicToolSpec(BUILDER_DOMAIN, "auth_logout", ("auth_logout",), ("builder", "auth", "logout"), cli_public=False),
136
- PublicToolSpec(BUILDER_DOMAIN, "get_login_link", ("get_login_link",), mcp_public=True, cli_public=False),
137
131
  PublicToolSpec(BUILDER_DOMAIN, "workspace_list", ("workspace_list",), ("builder", "workspace", "list"), cli_public=False),
138
132
  PublicToolSpec(BUILDER_DOMAIN, "workspace_get", ("workspace_get",), ("builder", "workspace", "get"), cli_public=False),
139
133
  PublicToolSpec(BUILDER_DOMAIN, "file_upload_local", ("file_upload_local",), ("builder", "file", "upload-local"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
@@ -329,7 +329,7 @@ def _trim_workspace_get(payload: JSONObject) -> None:
329
329
 
330
330
  def _trim_app_list_like(payload: JSONObject) -> None:
331
331
  payload.pop("apps", None)
332
- _trim_item_list(payload, "items", allowed=("app_key", "app_name", "package_name", "uri"))
332
+ _trim_item_list(payload, "items", allowed=("app_key", "app_name", "package_name"))
333
333
 
334
334
 
335
335
  def _trim_app_get(payload: JSONObject) -> None:
@@ -337,7 +337,7 @@ def _trim_app_get(payload: JSONObject) -> None:
337
337
  _trim_nested_item_list(
338
338
  payload,
339
339
  ("data", "accessible_views"),
340
- allowed=("view_id", "name", "analysis_supported", "list_supported", "kind", "uri"),
340
+ allowed=("view_id", "name", "analysis_supported", "list_supported", "kind"),
341
341
  )
342
342
 
343
343
 
@@ -1151,6 +1151,7 @@ def _register_policy(domains: tuple[str, ...], names: tuple[str, ...], transform
1151
1151
 
1152
1152
 
1153
1153
  _register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("auth_use_credential", "auth_whoami"), _trim_auth_payload)
1154
+ _register_policy((USER_DOMAIN,), ("get_login_link",), _trim_login_link)
1154
1155
  _register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("auth_logout",), _trim_auth_logout)
1155
1156
  _register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("workspace_list",), _trim_workspace_list)
1156
1157
  _register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("workspace_get", "workspace_select"), _trim_workspace_get)
@@ -1225,7 +1226,6 @@ _register_policy(
1225
1226
  _trim_directory,
1226
1227
  )
1227
1228
  _register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("feedback_submit",), _trim_feedback)
1228
- _register_policy((USER_DOMAIN, BUILDER_DOMAIN), ("get_login_link",), _trim_login_link)
1229
1229
  _register_policy(
1230
1230
  (USER_DOMAIN,),
1231
1231
  (
@@ -7,7 +7,6 @@ from mcp.server.fastmcp import FastMCP
7
7
  from .backend_client import BackendClient
8
8
  from .session_store import SessionStore
9
9
  from .tools.app_tools import AppTools
10
- from .tools.data_tools import DataTools
11
10
  from .tools.auth_tools import AuthTools
12
11
  from .tools.code_block_tools import CodeBlockTools
13
12
  from .tools.feedback_tools import FeedbackTools
@@ -250,7 +249,6 @@ If the current MCP capability is unsupported, the workflow is awkward, or the us
250
249
  TaskContextTools(sessions, backend).register(server)
251
250
  RoleTools(sessions, backend).register(server)
252
251
  AppTools(sessions, backend).register(server)
253
- DataTools(sessions, backend).register(server)
254
252
  QingbiReportTools(sessions, backend).register(server)
255
253
  PackageTools(sessions, backend).register(server)
256
254
  NavigationTools(sessions, backend).register(server)
@@ -9,7 +9,6 @@ from .config import DEFAULT_PROFILE
9
9
  from .response_trim import BUILDER_SERVER_METHOD_MAP, trim_error_response, trim_public_response, wrap_trimmed_methods
10
10
  from .session_store import SessionStore
11
11
  from .tools.ai_builder_tools import AiBuilderTools
12
- from .tools.login_link_tools import LOGIN_LINK_DESCRIPTION, LoginLinkTools
13
12
  from .tools.auth_tools import AuthTools
14
13
  from .tools.feedback_tools import FeedbackTools
15
14
  from .tools.file_tools import FileTools
@@ -42,16 +41,6 @@ def build_builder_server() -> FastMCP:
42
41
  "member_search/role_search/role_create when workflow assignees must come from the directory or role catalog, preferring roles over explicit members unless the user explicitly names members, "
43
42
  "then app_form_apply for complete app form targets, explicit app_delete for application deletion, and app_flow_apply/app_views_apply/app_associated_resources_apply/app_charts_apply/portal_apply/portal_delete for non-form patches. app_custom_buttons_apply is maintenance-only for explicit standalone button work outside normal view design. These apply/delete tools perform validation, normalization, writes, and readback verification where applicable. AppForm publishes only after a draft change or when the published form lags behind; views noop requests skip publish, app_custom_buttons_apply and app_associated_resources_apply publish after at least one write succeeds and expose no draft-only parameter, charts are immediate-live without publish and resolve targets by chart_id first then exact unique chart name, portal updates use replace semantics only when sections are supplied and edit-mode base-info-only updates may omit sections, portal delete separates DELETE execution from readback verification, portal pc layout is a 24-column grid and mobile is a 6-column grid so omit position or use layout_preset when unsure, publish=false only guarantees draft/base-info updates for tools that still expose that parameter, and flow should use publish=false whenever you only want draft/precheck behavior. "
44
43
  "Builder apply/write outputs include schema_version, operation, summary, and resources[]; use resources[].id/key/name/ids/parent as the stable UI and agent display entry, and keep legacy fields such as field_diff/views_diff/chart_results only for compatibility or troubleshooting. "
45
- "创建、修改或安装轻流资源后,最终回复主动提供主要结果的可点击页面入口,无需等待用户再次索要。"
46
- "采用“完成结果 + 查看入口”,文案使用“在轻流中查看应用/应用包/视图”,不默认称为“继续编辑”。"
47
- "默认只交付用户请求的主要结果:安装方案默认提供应用包入口;直接创建或修改应用、视图时提供对应入口;用户明确要求多个主要对象时分别提供。"
48
- "使用业务结果中的稳定 `uri`,按 URI 去重,不为全部子资源预签票据;主要对象没有可靠 URI 时说明限制,不猜地址,也不替换为不相关页面。"
49
- "部分成功时仅交付已确认成功且可定位的主要结果,保留失败或未验证说明。"
50
- "纯数据问答、中间读取、失败资源和删除操作不自动签发;用户明确要求不提供链接时遵从。用户明确要求查看或打开页面时也使用已确认的路径。"
51
- "目标浏览器登录态未知时,临近交付用资源 URI 的同源 pathname 调用 get_login_link,为每个实际展示的入口分别签发,使用返回的完整 URL,不额外追问登录态。"
52
- "只有已有用户确认同一浏览器、网页域名、账号和工作区已登录且无失效信号的事实时,才直接交付稳定 URI;调用过票据工具、自动化浏览器已登录或之前签发过票据均不能证明用户浏览器已登录,已消费链接不可复用。"
53
- "用户未要求打开时不提前打开或消费链接;浏览器不可用时提供可点击链接,不假称已经打开。"
54
- "签发失败时说明免登录链接未生成,提供标注“需登录”的稳定 URI 作为备用入口,不重做业务操作。机器结果继续保留稳定 URI,临时登录链接仅用于面向用户交付。 "
55
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. "
56
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. "
57
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. "
@@ -75,7 +64,6 @@ def build_builder_server() -> FastMCP:
75
64
  workspace = wrap_trimmed_methods(WorkspaceTools(sessions, backend), BUILDER_SERVER_METHOD_MAP)
76
65
  files = wrap_trimmed_methods(FileTools(sessions, backend), BUILDER_SERVER_METHOD_MAP)
77
66
  ai_builder = wrap_trimmed_methods(AiBuilderTools(sessions, backend), BUILDER_SERVER_METHOD_MAP)
78
- login_link = LoginLinkTools(sessions, backend)
79
67
  feedback = FeedbackTools(backend, mcp_side="App Builder MCP")
80
68
 
81
69
  @server.tool()
@@ -102,10 +90,6 @@ def build_builder_server() -> FastMCP:
102
90
  def auth_logout(profile: str = DEFAULT_PROFILE, forget_persisted: bool = False) -> dict:
103
91
  return auth.auth_logout(profile=profile, forget_persisted=forget_persisted)
104
92
 
105
- @server.tool(description=LOGIN_LINK_DESCRIPTION)
106
- def get_login_link(profile: str = DEFAULT_PROFILE, redirect_path: str = "") -> dict:
107
- return login_link.get_login_link(profile=profile, redirect_path=redirect_path)
108
-
109
93
  @server.tool()
110
94
  def workspace_list(
111
95
  profile: str = DEFAULT_PROFILE,