@josephyan/qingflow-cli 0.2.0-beta.77 → 0.2.0-beta.78

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@0.2.0-beta.77
6
+ npm install @josephyan/qingflow-cli@0.2.0-beta.78
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-cli@0.2.0-beta.77 qingflow
12
+ npx -y -p @josephyan/qingflow-cli@0.2.0-beta.78 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": "0.2.0-beta.77",
3
+ "version": "0.2.0-beta.78",
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 = "0.2.0b76"
7
+ version = "0.2.0b78"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -2,4 +2,4 @@ from __future__ import annotations
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.2.0b48"
5
+ __version__ = "0.2.0b78"
@@ -291,7 +291,7 @@ class FieldSelector(StrictModel):
291
291
 
292
292
  @model_validator(mode="after")
293
293
  def validate_selector(self) -> "FieldSelector":
294
- if not any((self.field_id, self.que_id, self.name)):
294
+ if self.field_id is None and self.que_id is None and self.name is None:
295
295
  raise ValueError("selector must include field_id, que_id, or name")
296
296
  return self
297
297
 
@@ -8503,6 +8503,8 @@ def _hydrate_relation_field_configs(
8503
8503
  for field in resolved_fields:
8504
8504
  if not isinstance(field, dict) or field.get("type") != FieldType.relation.value:
8505
8505
  continue
8506
+ if not bool(field.get("_relation_config_explicit")) and isinstance(field.get("_reference_config_template"), dict):
8507
+ continue
8506
8508
  target_app_key = str(field.get("target_app_key") or "").strip()
8507
8509
  if not target_app_key:
8508
8510
  continue
@@ -8661,13 +8663,13 @@ def _parse_field(question: dict[str, Any], *, field_id_hint: str | None = None)
8661
8663
  field["relation_mode"] = _relation_mode_from_optional_data_num(reference.get("optionalDataNum"))
8662
8664
  refer_questions = reference.get("referQuestions") if isinstance(reference.get("referQuestions"), list) else []
8663
8665
  visible_fields: list[dict[str, Any]] = []
8664
- display_field_que_id = _coerce_positive_int(reference.get("referQueId"))
8666
+ display_field_que_id = _coerce_nonnegative_int(reference.get("referQueId"))
8665
8667
  display_field_name: str | None = None
8666
8668
  for item in refer_questions:
8667
8669
  if not isinstance(item, dict):
8668
8670
  continue
8669
8671
  selector = {
8670
- "que_id": _coerce_positive_int(item.get("queId")),
8672
+ "que_id": _coerce_nonnegative_int(item.get("queId")),
8671
8673
  "name": str(item.get("queTitle") or "").strip() or None,
8672
8674
  }
8673
8675
  visible_fields.append(selector)
@@ -8682,6 +8684,8 @@ def _parse_field(question: dict[str, Any], *, field_id_hint: str | None = None)
8682
8684
  }
8683
8685
  field["visible_fields"] = visible_fields
8684
8686
  field["field_name_show"] = bool(reference.get("fieldNameShow", True))
8687
+ field["_reference_config_template"] = deepcopy(reference)
8688
+ field["_relation_config_explicit"] = False
8685
8689
  if field_type == FieldType.department:
8686
8690
  department_scope = _normalize_department_scope_from_question(question)
8687
8691
  if department_scope is not None:
@@ -9341,7 +9345,7 @@ def _build_selector_map(fields: list[dict[str, Any]]) -> dict[str, int]:
9341
9345
  for index, field in enumerate(fields):
9342
9346
  field_id = str(field.get("field_id") or "")
9343
9347
  field_name = str(field.get("name") or "")
9344
- que_id = _coerce_positive_int(field.get("que_id"))
9348
+ que_id = _coerce_nonnegative_int(field.get("que_id"))
9345
9349
  if field_id:
9346
9350
  mapping[f"field_id:{field_id}"] = index
9347
9351
  if field_name:
@@ -9356,7 +9360,7 @@ def _resolve_selector(selector_map: dict[str, int], selector: FieldSelector) ->
9356
9360
  value = selector_map.get(f"field_id:{selector.field_id}")
9357
9361
  if value is not None:
9358
9362
  return value
9359
- if selector.que_id:
9363
+ if selector.que_id is not None:
9360
9364
  value = selector_map.get(f"que_id:{selector.que_id}")
9361
9365
  if value is not None:
9362
9366
  return value
@@ -9404,6 +9408,7 @@ def _field_patch_to_internal(patch: FieldPatch) -> dict[str, Any]:
9404
9408
  "que_id": None,
9405
9409
  "default_type": 1,
9406
9410
  "default_value": None,
9411
+ "_relation_config_explicit": patch.type == PublicFieldType.relation,
9407
9412
  }
9408
9413
 
9409
9414
 
@@ -9474,7 +9479,7 @@ def _field_selector_payload_equal(left: Any, right: Any) -> bool:
9474
9479
  return False
9475
9480
  return (
9476
9481
  str(left.get("field_id") or "") == str(right.get("field_id") or "")
9477
- and _coerce_positive_int(left.get("que_id")) == _coerce_positive_int(right.get("que_id"))
9482
+ and _coerce_nonnegative_int(left.get("que_id")) == _coerce_nonnegative_int(right.get("que_id"))
9478
9483
  and str(left.get("name") or "") == str(right.get("name") or "")
9479
9484
  )
9480
9485
 
@@ -9629,6 +9634,10 @@ def _code_block_binding_equal(left: Any, right: Any) -> bool:
9629
9634
 
9630
9635
  def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
9631
9636
  payload = mutation.model_dump(mode="json", exclude_none=True)
9637
+ relation_config_explicit = (
9638
+ payload.get("type") == FieldType.relation.value
9639
+ or any(key in payload for key in ("target_app_key", "display_field", "visible_fields", "relation_mode"))
9640
+ )
9632
9641
  if "name" in payload:
9633
9642
  field["name"] = payload["name"]
9634
9643
  if "type" in payload:
@@ -9671,6 +9680,11 @@ def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
9671
9680
  field["custom_button_text"] = payload["custom_button_text"]
9672
9681
  if "subfields" in payload:
9673
9682
  field["subfields"] = [_field_patch_to_internal(item) for item in payload["subfields"]]
9683
+ if relation_config_explicit:
9684
+ field["_relation_config_explicit"] = True
9685
+ elif payload.get("type") and payload.get("type") != FieldType.relation.value:
9686
+ field.pop("_relation_config_explicit", None)
9687
+ field.pop("_reference_config_template", None)
9674
9688
 
9675
9689
 
9676
9690
  def _resolve_field_selector_with_uniqueness(
@@ -9682,8 +9696,8 @@ def _resolve_field_selector_with_uniqueness(
9682
9696
  selector = FieldSelector.model_validate(selector_payload)
9683
9697
  if selector.field_id:
9684
9698
  matched = [field for field in fields if str(field.get("field_id") or "") == str(selector.field_id)]
9685
- elif selector.que_id:
9686
- matched = [field for field in fields if _coerce_positive_int(field.get("que_id")) == _coerce_positive_int(selector.que_id)]
9699
+ elif selector.que_id is not None:
9700
+ matched = [field for field in fields if _coerce_nonnegative_int(field.get("que_id")) == _coerce_nonnegative_int(selector.que_id)]
9687
9701
  elif selector.name:
9688
9702
  name = str(selector.name or "").strip()
9689
9703
  matched = [field for field in fields if str(field.get("name") or "").strip() == name]
@@ -11142,7 +11156,7 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
11142
11156
  },
11143
11157
  temp_id,
11144
11158
  )
11145
- if field.get("que_id"):
11159
+ if _coerce_nonnegative_int(field.get("que_id")) is not None:
11146
11160
  question["queId"] = field["que_id"]
11147
11161
  else:
11148
11162
  question["queTempId"] = temp_id
@@ -11152,13 +11166,23 @@ def _field_to_question(field: dict[str, Any], *, temp_id: int) -> dict[str, Any]
11152
11166
  if "default_value" in field:
11153
11167
  question["queDefaultValue"] = field.get("default_value")
11154
11168
  if field.get("type") == FieldType.relation.value:
11155
- reference = question.get("referenceConfig") if isinstance(question.get("referenceConfig"), dict) else {}
11156
- reference["referAppKey"] = field.get("target_app_key")
11157
- reference["_targetEntityId"] = field.get("target_app_key")
11158
- if field.get("target_field_que_id"):
11159
- reference["referQueId"] = field.get("target_field_que_id")
11160
- reference["optionalDataNum"] = _relation_mode_to_optional_data_num(field.get("relation_mode"))
11161
- question["referenceConfig"] = reference
11169
+ preserved_reference = (
11170
+ deepcopy(field.get("_reference_config_template"))
11171
+ if not bool(field.get("_relation_config_explicit")) and isinstance(field.get("_reference_config_template"), dict)
11172
+ else None
11173
+ )
11174
+ if preserved_reference is not None:
11175
+ preserved_reference["referAppKey"] = field.get("target_app_key")
11176
+ preserved_reference["_targetEntityId"] = field.get("target_app_key")
11177
+ question["referenceConfig"] = preserved_reference
11178
+ else:
11179
+ reference = question.get("referenceConfig") if isinstance(question.get("referenceConfig"), dict) else {}
11180
+ reference["referAppKey"] = field.get("target_app_key")
11181
+ reference["_targetEntityId"] = field.get("target_app_key")
11182
+ if field.get("target_field_que_id") is not None:
11183
+ reference["referQueId"] = field.get("target_field_que_id")
11184
+ reference["optionalDataNum"] = _relation_mode_to_optional_data_num(field.get("relation_mode"))
11185
+ question["referenceConfig"] = reference
11162
11186
  if field.get("type") == FieldType.department.value:
11163
11187
  scope_type, scope_payload = _serialize_department_scope_for_question(field.get("department_scope"))
11164
11188
  question["deptSelectScopeType"] = scope_type
@@ -6,6 +6,7 @@ import sys
6
6
  from typing import Any, Callable, TextIO
7
7
 
8
8
  from ..errors import QingflowApiError
9
+ from ..public_surface import cli_public_tool_spec_from_namespace
9
10
  from ..response_trim import resolve_cli_tool_name, trim_error_response, trim_public_response
10
11
  from .context import CliContext, build_cli_context
11
12
  from .formatters import emit_json_result, emit_text_result
@@ -49,6 +50,8 @@ def run(
49
50
  return 2
50
51
  context = context_factory()
51
52
  try:
53
+ if not bool(args.json):
54
+ _emit_cli_effective_context_notice(args, context, stream=err)
52
55
  result = handler(args, context)
53
56
  except RuntimeError as exc:
54
57
  payload = trim_error_response(_parse_error_payload(exc))
@@ -145,5 +148,31 @@ def _result_exit_code(result: dict[str, Any]) -> int:
145
148
  return 0
146
149
 
147
150
 
151
+ def _emit_cli_effective_context_notice(args: argparse.Namespace, context: CliContext, *, stream: TextIO) -> None:
152
+ spec = cli_public_tool_spec_from_namespace(args)
153
+ if spec is None or not spec.cli_show_effective_context:
154
+ return
155
+ sessions = getattr(context, "sessions", None)
156
+ if sessions is None or not hasattr(sessions, "get_profile"):
157
+ return
158
+ profile_name = str(getattr(args, "profile", "default") or "default")
159
+ try:
160
+ session_profile = sessions.get_profile(profile_name)
161
+ except Exception:
162
+ session_profile = None
163
+ workspace_id = getattr(session_profile, "selected_ws_id", None) if session_profile is not None else None
164
+ workspace_name = getattr(session_profile, "selected_ws_name", None) if session_profile is not None else None
165
+ if workspace_id is None:
166
+ workspace_label = "(not selected)"
167
+ elif workspace_name:
168
+ workspace_label = f"{workspace_name} ({workspace_id})"
169
+ else:
170
+ workspace_label = str(workspace_id)
171
+ lines = [f"Context: profile={profile_name} workspace={workspace_label}"]
172
+ if spec.cli_context_write and profile_name == "default":
173
+ lines.append("Warning: using default profile for a workspace-sensitive write command")
174
+ stream.write("\n".join(lines) + "\n")
175
+
176
+
148
177
  if __name__ == "__main__":
149
178
  main()
@@ -17,6 +17,8 @@ class PublicToolSpec:
17
17
  mcp_public: bool = True
18
18
  cli_public: bool = True
19
19
  has_contract: bool = False
20
+ cli_show_effective_context: bool = False
21
+ cli_context_write: bool = False
20
22
 
21
23
  @property
22
24
  def trim_key(self) -> str:
@@ -34,13 +36,13 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
34
36
  PublicToolSpec(USER_DOMAIN, "auth_logout", ("auth_logout",), ("auth", "logout")),
35
37
  PublicToolSpec(USER_DOMAIN, "workspace_list", ("workspace_list",), ("workspace", "list")),
36
38
  PublicToolSpec(USER_DOMAIN, "workspace_select", ("workspace_select",), ("workspace", "select")),
37
- PublicToolSpec(USER_DOMAIN, "app_list", ("app_list",), ("app", "list")),
38
- PublicToolSpec(USER_DOMAIN, "app_search", ("app_search",), ("app", "search")),
39
- PublicToolSpec(USER_DOMAIN, "app_get", ("app_get",), ("app", "get")),
40
- PublicToolSpec(USER_DOMAIN, "portal_list", ("portal_list",), ("portal", "list")),
41
- PublicToolSpec(USER_DOMAIN, "portal_get", ("portal_get",), ("portal", "get")),
42
- PublicToolSpec(USER_DOMAIN, "view_get", ("view_get",), ("view", "get")),
43
- PublicToolSpec(USER_DOMAIN, "chart_get", ("chart_get",), ("chart", "get")),
39
+ PublicToolSpec(USER_DOMAIN, "app_list", ("app_list",), ("app", "list"), cli_show_effective_context=True),
40
+ PublicToolSpec(USER_DOMAIN, "app_search", ("app_search",), ("app", "search"), cli_show_effective_context=True),
41
+ PublicToolSpec(USER_DOMAIN, "app_get", ("app_get",), ("app", "get"), cli_show_effective_context=True),
42
+ PublicToolSpec(USER_DOMAIN, "portal_list", ("portal_list",), ("portal", "list"), cli_show_effective_context=True),
43
+ PublicToolSpec(USER_DOMAIN, "portal_get", ("portal_get",), ("portal", "get"), cli_show_effective_context=True),
44
+ PublicToolSpec(USER_DOMAIN, "view_get", ("view_get",), ("view", "get"), cli_show_effective_context=True),
45
+ PublicToolSpec(USER_DOMAIN, "chart_get", ("chart_get",), ("chart", "get"), cli_show_effective_context=True),
44
46
  PublicToolSpec(USER_DOMAIN, "file_get_upload_info", ("file_get_upload_info",), cli_public=False),
45
47
  PublicToolSpec(USER_DOMAIN, "file_upload_local", ("file_upload_local",), cli_public=False),
46
48
  PublicToolSpec(USER_DOMAIN, "feedback_submit", ("feedback_submit",), cli_public=False),
@@ -78,22 +80,22 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
78
80
  PublicToolSpec(USER_DOMAIN, "record_member_candidates", ("record_member_candidates",), cli_public=False),
79
81
  PublicToolSpec(USER_DOMAIN, "record_department_candidates", ("record_department_candidates",), cli_public=False),
80
82
  PublicToolSpec(USER_DOMAIN, "record_analyze", ("record_analyze",), ("record", "analyze")),
81
- PublicToolSpec(USER_DOMAIN, "record_list", ("record_list",), ("record", "list")),
82
- PublicToolSpec(USER_DOMAIN, "record_get", ("record_get_public",), ("record", "get")),
83
- PublicToolSpec(USER_DOMAIN, "record_insert", ("record_insert_public",), ("record", "insert")),
84
- PublicToolSpec(USER_DOMAIN, "record_update", ("record_update_public",), ("record", "update")),
85
- PublicToolSpec(USER_DOMAIN, "record_delete", ("record_delete_public",), ("record", "delete")),
83
+ PublicToolSpec(USER_DOMAIN, "record_list", ("record_list",), ("record", "list"), cli_show_effective_context=True),
84
+ PublicToolSpec(USER_DOMAIN, "record_get", ("record_get_public",), ("record", "get"), cli_show_effective_context=True),
85
+ PublicToolSpec(USER_DOMAIN, "record_insert", ("record_insert_public",), ("record", "insert"), cli_show_effective_context=True, cli_context_write=True),
86
+ PublicToolSpec(USER_DOMAIN, "record_update", ("record_update_public",), ("record", "update"), cli_show_effective_context=True, cli_context_write=True),
87
+ PublicToolSpec(USER_DOMAIN, "record_delete", ("record_delete_public",), ("record", "delete"), cli_show_effective_context=True, cli_context_write=True),
86
88
  PublicToolSpec(USER_DOMAIN, "record_import_template_get", ("record_import_template_get",), ("import", "template")),
87
89
  PublicToolSpec(USER_DOMAIN, "record_import_verify", ("record_import_verify",), ("import", "verify")),
88
90
  PublicToolSpec(USER_DOMAIN, "record_import_repair_local", ("record_import_repair_local",), ("import", "repair")),
89
91
  PublicToolSpec(USER_DOMAIN, "record_import_start", ("record_import_start",), ("import", "start")),
90
92
  PublicToolSpec(USER_DOMAIN, "record_import_status_get", ("record_import_status_get",), ("import", "status")),
91
- PublicToolSpec(USER_DOMAIN, "record_code_block_run", ("record_code_block_run",), ("record", "code-block-run")),
92
- PublicToolSpec(USER_DOMAIN, "task_list", ("task_list",), ("task", "list")),
93
- PublicToolSpec(USER_DOMAIN, "task_get", ("task_get",), ("task", "get")),
94
- PublicToolSpec(USER_DOMAIN, "task_action_execute", ("task_action_execute",), ("task", "action")),
95
- PublicToolSpec(USER_DOMAIN, "task_associated_report_detail_get", ("task_associated_report_detail_get",), cli_public=False),
96
- PublicToolSpec(USER_DOMAIN, "task_workflow_log_get", ("task_workflow_log_get",), ("task", "log")),
93
+ PublicToolSpec(USER_DOMAIN, "record_code_block_run", ("record_code_block_run",), ("record", "code-block-run"), cli_show_effective_context=True, cli_context_write=True),
94
+ PublicToolSpec(USER_DOMAIN, "task_list", ("task_list",), ("task", "list"), cli_show_effective_context=True),
95
+ PublicToolSpec(USER_DOMAIN, "task_get", ("task_get",), ("task", "get"), cli_show_effective_context=True),
96
+ PublicToolSpec(USER_DOMAIN, "task_action_execute", ("task_action_execute",), ("task", "action"), cli_show_effective_context=True, cli_context_write=True),
97
+ PublicToolSpec(USER_DOMAIN, "task_associated_report_detail_get", ("task_associated_report_detail_get",), cli_public=False, cli_show_effective_context=True),
98
+ PublicToolSpec(USER_DOMAIN, "task_workflow_log_get", ("task_workflow_log_get",), ("task", "log"), cli_show_effective_context=True),
97
99
  PublicToolSpec(USER_DOMAIN, "directory_search", ("directory_search",), cli_public=False),
98
100
  PublicToolSpec(USER_DOMAIN, "directory_list_internal_users", ("directory_list_internal_users",), cli_public=False),
99
101
  PublicToolSpec(USER_DOMAIN, "directory_list_all_internal_users", ("directory_list_all_internal_users",), cli_public=False),
@@ -111,42 +113,42 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
111
113
  PublicToolSpec(BUILDER_DOMAIN, "auth_logout", ("auth_logout",), ("builder", "auth", "logout"), cli_public=False),
112
114
  PublicToolSpec(BUILDER_DOMAIN, "workspace_list", ("workspace_list",), ("builder", "workspace", "list"), cli_public=False),
113
115
  PublicToolSpec(BUILDER_DOMAIN, "workspace_select", ("workspace_select",), ("builder", "workspace", "select"), cli_public=False),
114
- PublicToolSpec(BUILDER_DOMAIN, "file_upload_local", ("file_upload_local",), ("builder", "file", "upload-local"), has_contract=True),
116
+ 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),
115
117
  PublicToolSpec(BUILDER_DOMAIN, "feedback_submit", ("feedback_submit",), ("builder", "feedback", "submit"), has_contract=True),
116
- PublicToolSpec(BUILDER_DOMAIN, "package_list", ("package_list",), ("builder", "package", "list"), has_contract=True),
117
- PublicToolSpec(BUILDER_DOMAIN, "package_resolve", ("package_resolve",), ("builder", "package", "resolve"), has_contract=True),
118
+ PublicToolSpec(BUILDER_DOMAIN, "package_list", ("package_list",), ("builder", "package", "list"), has_contract=True, cli_show_effective_context=True),
119
+ PublicToolSpec(BUILDER_DOMAIN, "package_resolve", ("package_resolve",), ("builder", "package", "resolve"), has_contract=True, cli_show_effective_context=True),
118
120
  PublicToolSpec(BUILDER_DOMAIN, "builder_tool_contract", ("builder_tool_contract",), ("builder", "contract"), has_contract=False),
119
- PublicToolSpec(BUILDER_DOMAIN, "package_create", ("package_create",), ("builder", "package", "create"), has_contract=True),
120
- PublicToolSpec(BUILDER_DOMAIN, "solution_install", ("solution_install",), ("builder", "solution", "install"), has_contract=True),
121
- PublicToolSpec(BUILDER_DOMAIN, "member_search", ("member_search",), ("builder", "member", "search"), has_contract=True),
122
- PublicToolSpec(BUILDER_DOMAIN, "role_search", ("role_search",), ("builder", "role", "search"), has_contract=True),
123
- PublicToolSpec(BUILDER_DOMAIN, "role_create", ("role_create",), ("builder", "role", "create"), has_contract=True),
124
- PublicToolSpec(BUILDER_DOMAIN, "package_attach_app", ("package_attach_app",), ("builder", "package", "attach-app"), has_contract=True),
125
- PublicToolSpec(BUILDER_DOMAIN, "app_release_edit_lock_if_mine", ("app_release_edit_lock_if_mine",), ("builder", "app", "release-edit-lock-if-mine"), has_contract=True),
126
- PublicToolSpec(BUILDER_DOMAIN, "app_resolve", ("app_resolve",), ("builder", "app", "resolve"), has_contract=True),
127
- PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_list", ("app_custom_button_list",), ("builder", "button", "list"), has_contract=True),
128
- PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_get", ("app_custom_button_get",), ("builder", "button", "get"), has_contract=True),
129
- PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_create", ("app_custom_button_create",), ("builder", "button", "create"), has_contract=True),
130
- PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_update", ("app_custom_button_update",), ("builder", "button", "update"), has_contract=True),
131
- PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_delete", ("app_custom_button_delete",), ("builder", "button", "delete"), has_contract=True),
132
- PublicToolSpec(BUILDER_DOMAIN, "app_get", ("app_get",), ("builder", "app", "get", "summary"), has_contract=True),
133
- PublicToolSpec(BUILDER_DOMAIN, "app_get_fields", ("app_get_fields",), ("builder", "app", "get", "fields"), has_contract=True),
134
- PublicToolSpec(BUILDER_DOMAIN, "app_repair_code_blocks", ("app_repair_code_blocks",), ("builder", "app", "repair-code-blocks"), has_contract=True),
135
- PublicToolSpec(BUILDER_DOMAIN, "app_get_layout", ("app_get_layout",), ("builder", "app", "get", "layout"), has_contract=True),
136
- PublicToolSpec(BUILDER_DOMAIN, "app_get_views", ("app_get_views",), ("builder", "app", "get", "views"), has_contract=True),
137
- PublicToolSpec(BUILDER_DOMAIN, "app_get_flow", ("app_get_flow",), ("builder", "app", "get", "flow"), has_contract=True),
138
- PublicToolSpec(BUILDER_DOMAIN, "app_get_charts", ("app_get_charts",), ("builder", "app", "get", "charts"), has_contract=True),
139
- PublicToolSpec(BUILDER_DOMAIN, "portal_list", ("portal_list",), ("builder", "portal", "list"), has_contract=True),
140
- PublicToolSpec(BUILDER_DOMAIN, "portal_get", ("portal_get",), ("builder", "portal", "get"), has_contract=True),
141
- PublicToolSpec(BUILDER_DOMAIN, "view_get", ("view_get",), ("builder", "view", "get"), has_contract=True),
142
- PublicToolSpec(BUILDER_DOMAIN, "chart_get", ("chart_get",), ("builder", "chart", "get"), has_contract=True),
143
- PublicToolSpec(BUILDER_DOMAIN, "app_schema_apply", ("app_schema_apply",), ("builder", "schema", "apply"), has_contract=True),
144
- PublicToolSpec(BUILDER_DOMAIN, "app_layout_apply", ("app_layout_apply",), ("builder", "layout", "apply"), has_contract=True),
145
- PublicToolSpec(BUILDER_DOMAIN, "app_flow_apply", ("app_flow_apply",), ("builder", "flow", "apply"), has_contract=True),
146
- PublicToolSpec(BUILDER_DOMAIN, "app_views_apply", ("app_views_apply",), ("builder", "views", "apply"), has_contract=True),
147
- PublicToolSpec(BUILDER_DOMAIN, "app_charts_apply", ("app_charts_apply",), ("builder", "charts", "apply"), has_contract=True),
148
- PublicToolSpec(BUILDER_DOMAIN, "portal_apply", ("portal_apply",), ("builder", "portal", "apply"), has_contract=True),
149
- PublicToolSpec(BUILDER_DOMAIN, "app_publish_verify", ("app_publish_verify",), ("builder", "publish", "verify"), has_contract=True),
121
+ PublicToolSpec(BUILDER_DOMAIN, "package_create", ("package_create",), ("builder", "package", "create"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
122
+ PublicToolSpec(BUILDER_DOMAIN, "solution_install", ("solution_install",), ("builder", "solution", "install"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
123
+ PublicToolSpec(BUILDER_DOMAIN, "member_search", ("member_search",), ("builder", "member", "search"), has_contract=True, cli_show_effective_context=True),
124
+ PublicToolSpec(BUILDER_DOMAIN, "role_search", ("role_search",), ("builder", "role", "search"), has_contract=True, cli_show_effective_context=True),
125
+ PublicToolSpec(BUILDER_DOMAIN, "role_create", ("role_create",), ("builder", "role", "create"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
126
+ PublicToolSpec(BUILDER_DOMAIN, "package_attach_app", ("package_attach_app",), ("builder", "package", "attach-app"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
127
+ PublicToolSpec(BUILDER_DOMAIN, "app_release_edit_lock_if_mine", ("app_release_edit_lock_if_mine",), ("builder", "app", "release-edit-lock-if-mine"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
128
+ PublicToolSpec(BUILDER_DOMAIN, "app_resolve", ("app_resolve",), ("builder", "app", "resolve"), has_contract=True, cli_show_effective_context=True),
129
+ PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_list", ("app_custom_button_list",), ("builder", "button", "list"), has_contract=True, cli_show_effective_context=True),
130
+ PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_get", ("app_custom_button_get",), ("builder", "button", "get"), has_contract=True, cli_show_effective_context=True),
131
+ PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_create", ("app_custom_button_create",), ("builder", "button", "create"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
132
+ PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_update", ("app_custom_button_update",), ("builder", "button", "update"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
133
+ PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_delete", ("app_custom_button_delete",), ("builder", "button", "delete"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
134
+ PublicToolSpec(BUILDER_DOMAIN, "app_get", ("app_get",), ("builder", "app", "get", "summary"), has_contract=True, cli_show_effective_context=True),
135
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_fields", ("app_get_fields",), ("builder", "app", "get", "fields"), has_contract=True, cli_show_effective_context=True),
136
+ PublicToolSpec(BUILDER_DOMAIN, "app_repair_code_blocks", ("app_repair_code_blocks",), ("builder", "app", "repair-code-blocks"), has_contract=True, cli_show_effective_context=True),
137
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_layout", ("app_get_layout",), ("builder", "app", "get", "layout"), has_contract=True, cli_show_effective_context=True),
138
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_views", ("app_get_views",), ("builder", "app", "get", "views"), has_contract=True, cli_show_effective_context=True),
139
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_flow", ("app_get_flow",), ("builder", "app", "get", "flow"), has_contract=True, cli_show_effective_context=True),
140
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_charts", ("app_get_charts",), ("builder", "app", "get", "charts"), has_contract=True, cli_show_effective_context=True),
141
+ PublicToolSpec(BUILDER_DOMAIN, "portal_list", ("portal_list",), ("builder", "portal", "list"), has_contract=True, cli_show_effective_context=True),
142
+ PublicToolSpec(BUILDER_DOMAIN, "portal_get", ("portal_get",), ("builder", "portal", "get"), has_contract=True, cli_show_effective_context=True),
143
+ PublicToolSpec(BUILDER_DOMAIN, "view_get", ("view_get",), ("builder", "view", "get"), has_contract=True, cli_show_effective_context=True),
144
+ PublicToolSpec(BUILDER_DOMAIN, "chart_get", ("chart_get",), ("builder", "chart", "get"), has_contract=True, cli_show_effective_context=True),
145
+ PublicToolSpec(BUILDER_DOMAIN, "app_schema_apply", ("app_schema_apply",), ("builder", "schema", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
146
+ PublicToolSpec(BUILDER_DOMAIN, "app_layout_apply", ("app_layout_apply",), ("builder", "layout", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
147
+ PublicToolSpec(BUILDER_DOMAIN, "app_flow_apply", ("app_flow_apply",), ("builder", "flow", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
148
+ PublicToolSpec(BUILDER_DOMAIN, "app_views_apply", ("app_views_apply",), ("builder", "views", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
149
+ PublicToolSpec(BUILDER_DOMAIN, "app_charts_apply", ("app_charts_apply",), ("builder", "charts", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
150
+ PublicToolSpec(BUILDER_DOMAIN, "portal_apply", ("portal_apply",), ("builder", "portal", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
151
+ PublicToolSpec(BUILDER_DOMAIN, "app_publish_verify", ("app_publish_verify",), ("builder", "publish", "verify"), has_contract=True, cli_show_effective_context=True),
150
152
  )
151
153
 
152
154
 
@@ -195,6 +197,13 @@ def cli_trim_key_from_namespace(args: Namespace) -> str | None:
195
197
  return spec.trim_key if spec is not None else None
196
198
 
197
199
 
200
+ def cli_public_tool_spec_from_namespace(args: Namespace) -> PublicToolSpec | None:
201
+ route = cli_route_from_namespace(args)
202
+ if route is None:
203
+ return None
204
+ return PUBLIC_TOOL_BY_CLI_ROUTE.get(route)
205
+
206
+
198
207
  def cli_route_from_namespace(args: Namespace) -> tuple[str, ...] | None:
199
208
  command = getattr(args, "command", None)
200
209
  if not isinstance(command, str) or not command: