@josephyan/qingflow-cli 1.1.2 → 1.1.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.
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @josephyan/qingflow-cli@1.1.2
6
+ npm install @josephyan/qingflow-cli@1.1.3
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-cli@1.1.2 qingflow
12
+ npx -y -p @josephyan/qingflow-cli@1.1.3 qingflow
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-cli",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "1.1.2"
7
+ version = "1.1.3"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -428,6 +428,7 @@ qingflow builder button apply \
428
428
  默认使用 `qingflow builder associated-resource apply` 管理应用级关联资源池与视图展示配置。
429
429
 
430
430
  - 权限分层:`upsert_resources` / `patch_resources` / 删除 / 排序应用级关联资源池走 **EditAppAuth**;`view_configs` 修改某个视图里的关联资源展示配置,还需要视图配置侧的 **ViewManagementAuth**(未开启高级应用权限时回落到 **DataManageAuth**)。
431
+ - 多应用批量配置时可用 `--apps-file`,文件为 JSON 数组,每项写 `{ "app_key": "...", "upsert_resources": [...], "patch_resources": [...], "remove_associated_item_ids": [...], "reorder_associated_item_ids": [...], "view_configs": [...] }`;不要和单应用的 `--app-key` / `--*-file` 混用。
431
432
 
432
433
  ```bash
433
434
  qingflow builder associated-resource apply \
@@ -436,6 +437,11 @@ qingflow builder associated-resource apply \
436
437
  --view-configs-file tmp/associated_view_configs.json
437
438
  ```
438
439
 
440
+ ```bash
441
+ qingflow builder associated-resource apply \
442
+ --apps-file tmp/associated_resource_apps.json
443
+ ```
444
+
439
445
  - Shell 退出码为 0、重定向成功或 `echo OK` 只表示命令执行完,不表示业务成功。必须读取输出 JSON,检查顶层 `status`、`error_code`、`warnings`、`blocking_issues`;`status: failed`、`partial_success`、`ASSOCIATED_RESOURCES_APPLY_BLOCKED` 都不能当作完成。
440
446
  - 关联资源要生效必须同时处理两层:应用级资源池(`upsert_resources` / `patch_resources`)和视图详情展示绑定(`view_configs`)。只传 `--upsert-resources-file` 往往只是在资源池准备资源,不会让按钮或视图详情里看到关联资源。
441
447
  - `builder views apply` 新建视图会默认打开详情页关联查看(展示全部应用级关联资源);若只需要默认展示全部资源,创建视图时不用额外写 `view_configs`。需要指定部分资源、关闭展示或配置匹配筛选时,仍必须使用本工具的 `view_configs` / `match_mappings`。
@@ -5486,6 +5486,9 @@ class AiBuilderFacade:
5486
5486
  result = []
5487
5487
  for c in components:
5488
5488
  item = {k: v for k, v in c.items() if k != "order"}
5489
+ position = item.get("position")
5490
+ if isinstance(position, dict):
5491
+ item["position"] = _portal_component_position_for_request(position)
5489
5492
  result.append(item)
5490
5493
  return result
5491
5494
 
@@ -14180,6 +14183,27 @@ def _portal_position_payload(position: Any, *, inferred_mobile_y: int = 0) -> di
14180
14183
  }
14181
14184
 
14182
14185
 
14186
+ def _portal_component_position_for_request(position: dict[str, Any]) -> dict[str, Any]:
14187
+ """Normalize portal_get component positions into PortalSectionPatch input shape."""
14188
+ payload = deepcopy(position)
14189
+ if isinstance(payload.get("pc"), dict) or isinstance(payload.get("mobile"), dict):
14190
+ return payload
14191
+ pc_keys = {"x", "y", "cols", "rows"}
14192
+ if any(key in payload for key in pc_keys):
14193
+ pc = {
14194
+ "x": int(payload.get("x") or 0),
14195
+ "y": int(payload.get("y") or 0),
14196
+ "cols": int(payload.get("cols") or payload.get("w") or 12),
14197
+ "rows": int(payload.get("rows") or payload.get("h") or 8),
14198
+ }
14199
+ normalized: dict[str, Any] = {"pc": pc}
14200
+ mobile = payload.get("mobile")
14201
+ if isinstance(mobile, dict):
14202
+ normalized["mobile"] = mobile
14203
+ return normalized
14204
+ return payload
14205
+
14206
+
14183
14207
  def _portal_component_position_public(
14184
14208
  source_type: Any,
14185
14209
  *,
@@ -166,12 +166,13 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
166
166
  associated_resource_get.set_defaults(handler=_handle_associated_resource_get, format_hint="builder_summary")
167
167
 
168
168
  associated_resource_apply = associated_resource_subparsers.add_parser("apply", help="声明式管理应用关联资源池和视图展示配置")
169
- associated_resource_apply.add_argument("--app-key", required=True)
169
+ associated_resource_apply.add_argument("--app-key", default="")
170
170
  associated_resource_apply.add_argument("--upsert-resources-file")
171
171
  associated_resource_apply.add_argument("--patch-resources-file")
172
172
  associated_resource_apply.add_argument("--remove-associated-item-ids-file")
173
173
  associated_resource_apply.add_argument("--reorder-associated-item-ids-file")
174
174
  associated_resource_apply.add_argument("--view-configs-file")
175
+ associated_resource_apply.add_argument("--apps-file", help="多应用批量关联资源 JSON 数组,每项含 app_key + upsert_resources/patch_resources/remove_associated_item_ids/reorder_associated_item_ids/view_configs")
175
176
  associated_resource_apply.set_defaults(handler=_handle_associated_resource_apply, format_hint="builder_summary", force_json_output=True)
176
177
 
177
178
  portal = builder_subparsers.add_parser("portal", help="门户")
@@ -456,6 +457,36 @@ def _handle_button_apply(args: argparse.Namespace, context: CliContext) -> dict:
456
457
 
457
458
 
458
459
  def _handle_associated_resource_apply(args: argparse.Namespace, context: CliContext) -> dict:
460
+ apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
461
+ if apps:
462
+ single_app_args = [
463
+ a for a in [
464
+ "--app-key" if args.app_key else None,
465
+ "--upsert-resources-file" if getattr(args, "upsert_resources_file", None) else None,
466
+ "--patch-resources-file" if getattr(args, "patch_resources_file", None) else None,
467
+ "--remove-associated-item-ids-file" if getattr(args, "remove_associated_item_ids_file", None) else None,
468
+ "--reorder-associated-item-ids-file" if getattr(args, "reorder_associated_item_ids_file", None) else None,
469
+ "--view-configs-file" if getattr(args, "view_configs_file", None) else None,
470
+ ] if a
471
+ ]
472
+ if single_app_args:
473
+ raise_config_error(
474
+ f"associated-resource apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
475
+ fix_hint="Use --apps-file for batch mode (each item contains app_key + per-app params), or remove --apps-file for single-app mode.",
476
+ )
477
+ if apps:
478
+ return context.builder.app_associated_resources_apply(
479
+ profile=args.profile,
480
+ app_key="",
481
+ upsert_resources=[],
482
+ patch_resources=[],
483
+ remove_associated_item_ids=[],
484
+ reorder_associated_item_ids=[],
485
+ view_configs=[],
486
+ apps=apps,
487
+ )
488
+ if not args.app_key:
489
+ raise_config_error("associated-resource apply requires --app-key or --apps-file", fix_hint="Pass --app-key APP_KEY for single-app mode, or --apps-file for batch mode.")
459
490
  return context.builder.app_associated_resources_apply(
460
491
  profile=args.profile,
461
492
  app_key=args.app_key,
@@ -284,6 +284,7 @@ class AiBuilderTools(ToolBase):
284
284
  remove_associated_item_ids: list[int] | None = None,
285
285
  reorder_associated_item_ids: list[int] | None = None,
286
286
  view_configs: list[JSONObject] | None = None,
287
+ apps: list[JSONObject] | None = None,
287
288
  ) -> JSONObject:
288
289
  return self.app_associated_resources_apply(
289
290
  profile=profile,
@@ -293,6 +294,7 @@ class AiBuilderTools(ToolBase):
293
294
  remove_associated_item_ids=remove_associated_item_ids or [],
294
295
  reorder_associated_item_ids=reorder_associated_item_ids or [],
295
296
  view_configs=view_configs or [],
297
+ apps=apps,
296
298
  )
297
299
 
298
300
  @mcp.tool()
@@ -1120,8 +1122,24 @@ class AiBuilderTools(ToolBase):
1120
1122
  reorder_associated_item_ids: list[int],
1121
1123
  view_configs: list[JSONObject],
1122
1124
  patch_resources: list[JSONObject] | None = None,
1125
+ apps: list[JSONObject] | None = None,
1123
1126
  ) -> JSONObject:
1124
1127
  """执行应用关联资源 apply 逻辑。"""
1128
+ if apps:
1129
+ return self._facade._batch_write_apps(
1130
+ profile=profile,
1131
+ apps=apps,
1132
+ single_writer=lambda profile, app_key, **kw: self.app_associated_resources_apply(
1133
+ profile=profile,
1134
+ app_key=app_key,
1135
+ upsert_resources=kw.get("upsert_resources", []),
1136
+ patch_resources=kw.get("patch_resources", []),
1137
+ remove_associated_item_ids=kw.get("remove_associated_item_ids", []),
1138
+ reorder_associated_item_ids=kw.get("reorder_associated_item_ids", []),
1139
+ view_configs=kw.get("view_configs", []),
1140
+ ),
1141
+ tool_name="app_associated_resources_apply",
1142
+ )
1125
1143
  raw_request = {
1126
1144
  "app_key": app_key,
1127
1145
  "upsert_resources": upsert_resources,
@@ -4458,6 +4476,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4458
4476
  "app_associated_resources_apply": {
4459
4477
  "allowed_keys": [
4460
4478
  "app_key",
4479
+ "apps",
4461
4480
  "upsert_resources",
4462
4481
  "patch_resources",
4463
4482
  "remove_associated_item_ids",
@@ -4527,6 +4546,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4527
4546
  "if an associated resource delete returns readback_status=unavailable or still_exists, treat the result as readback pending and do not blindly repeat the delete",
4528
4547
  "this tool publishes after at least one write succeeds; there is no draft-only mode",
4529
4548
  "visible=false hides the associated-resource area without clearing previous selected ids; visible=true with limit_type=all shows the whole app-level pool",
4549
+ "accepts apps[] for multi-app batch; each item is {app_key, upsert_resources?, patch_resources?, remove_associated_item_ids?, reorder_associated_item_ids?, view_configs?}",
4530
4550
  ],
4531
4551
  "minimal_example": {
4532
4552
  "profile": "default",
@@ -4557,6 +4577,22 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
4557
4577
  }
4558
4578
  ],
4559
4579
  },
4580
+ "batch_example": {
4581
+ "profile": "default",
4582
+ "apps": [
4583
+ {
4584
+ "app_key": "APP_1",
4585
+ "upsert_resources": [
4586
+ {"client_key": "orders_view", "graph_type": "view", "target_app_key": "ORDER_APP", "view_key": "ORDER_VIEW"}
4587
+ ],
4588
+ "view_configs": [{"view_key": "MAIN_VIEW", "limit_type": "select", "associated_item_refs": ["orders_view"]}],
4589
+ },
4590
+ {
4591
+ "app_key": "APP_2",
4592
+ "view_configs": [{"view_key": "MAIN_VIEW", "visible": True, "limit_type": "all"}],
4593
+ },
4594
+ ],
4595
+ },
4560
4596
  },
4561
4597
  "app_schema_plan": {
4562
4598
  "allowed_keys": ["app_key", "package_id", "app_name", "icon", "color", "visibility", "create_if_missing", "add_fields", "update_fields", "remove_fields"],
@@ -5552,7 +5588,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
5552
5588
  "call workspace_icon_catalog_get or `qingflow --json builder icon catalog` for supported icon/color candidates",
5553
5589
  "portal_apply uses replace semantics for sections",
5554
5590
  "when editing an existing portal, sections may be omitted to update only base info such as visibility, icon, or package",
5555
- "use patch_sections[] for targeted section updates without replacing all sections; each item needs one selector (chart_ref with chart_id/chart_key/chart_name, view_ref with view_key/view_name, or order as 0-based index) plus set/unset",
5591
+ "use patch_sections[] for section-level patch updates without replacing all sections; each item needs one selector (chart_ref with chart_id/chart_key/chart_name, view_ref with view_key/view_name, or order as 0-based index) plus set/unset",
5556
5592
  "when sections[] is supplied without patch_sections[], it uses replace semantics for all sections",
5557
5593
  "remove a section by omitting it from the sections list (replace mode) or by unset in patch_sections (patch mode)",
5558
5594
  "package_id is required when creating a new portal",