@josephyan/qingflow-cli 1.0.11 → 1.1.2

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 (67) hide show
  1. package/README.md +3 -3
  2. package/npm/bin/qingflow.mjs +40 -2
  3. package/npm/lib/runtime.mjs +386 -15
  4. package/npm/scripts/postinstall.mjs +7 -2
  5. package/package.json +1 -1
  6. package/pyproject.toml +1 -1
  7. package/skills/qingflow-cli/SKILL.md +440 -0
  8. package/skills/qingflow-cli/manifest.yaml +10 -0
  9. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
  10. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +485 -0
  11. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md +237 -0
  12. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_MATCH_RULES.md +137 -0
  13. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md +263 -0
  14. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +304 -0
  15. package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md +41 -0
  16. package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
  17. package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
  18. package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
  19. package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
  20. package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
  21. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +20 -0
  22. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
  23. package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +163 -0
  24. package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +107 -0
  25. package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +151 -0
  26. package/skills/qingflow-cli/reference/_batch_schema_complex.json +18 -0
  27. package/skills/qingflow-cli/reference/_batch_schema_scalar.json +17 -0
  28. package/skills/qingflow-cli/reference/charts_remove.example.json +1 -0
  29. package/skills/qingflow-cli/reference/charts_reorder.example.json +1 -0
  30. package/skills/qingflow-cli/reference/charts_upsert_bar.example.json +8 -0
  31. package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +37 -0
  32. package/skills/qingflow-cli/reference/charts_upsert_minimal.example.json +13 -0
  33. package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +131 -0
  34. package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +126 -0
  35. package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +128 -0
  36. package/skills/qingflow-cli/reference/schema_add_fields_minimal.example.json +7 -0
  37. package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +78 -0
  38. package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +7 -0
  39. package/skills/qingflow-cli/scripts/builder-package-from-app-list.py +140 -0
  40. package/skills/qingflow-cli/scripts/find-app-by-keyword.py +132 -0
  41. package/skills/qingflow-cli/scripts/validate_qingflow_output_files.py +87 -0
  42. package/src/qingflow_mcp/__init__.py +1 -1
  43. package/src/qingflow_mcp/builder_facade/models.py +532 -48
  44. package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
  45. package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
  46. package/src/qingflow_mcp/cli/commands/app.py +3 -16
  47. package/src/qingflow_mcp/cli/commands/builder.py +354 -56
  48. package/src/qingflow_mcp/cli/commands/record.py +89 -2
  49. package/src/qingflow_mcp/cli/formatters.py +32 -1
  50. package/src/qingflow_mcp/cli/main.py +245 -3
  51. package/src/qingflow_mcp/public_surface.py +11 -8
  52. package/src/qingflow_mcp/response_trim.py +143 -14
  53. package/src/qingflow_mcp/server.py +15 -12
  54. package/src/qingflow_mcp/server_app_builder.py +108 -30
  55. package/src/qingflow_mcp/server_app_user.py +17 -18
  56. package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
  57. package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
  58. package/src/qingflow_mcp/solution/executor.py +3 -133
  59. package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
  60. package/src/qingflow_mcp/tools/app_tools.py +53 -8
  61. package/src/qingflow_mcp/tools/package_tools.py +16 -2
  62. package/src/qingflow_mcp/tools/record_tools.py +2095 -176
  63. package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
  64. package/src/qingflow_mcp/tools/solution_tools.py +30 -2
  65. package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
  66. package/src/qingflow_mcp/version.py +110 -0
  67. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
@@ -13,7 +13,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
13
13
  record_subparsers = parser.add_subparsers(
14
14
  dest="record_command",
15
15
  required=True,
16
- metavar="{schema,list,access,get,insert,update,delete,code-block-run}",
16
+ metavar="{schema,list,access,get,logs,insert,update,delete,member-candidates,department-candidates,code-block-run}",
17
17
  )
18
18
 
19
19
  schema = record_subparsers.add_parser("schema", help="读取记录相关表结构")
@@ -47,6 +47,28 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
47
47
  schema_code_block.add_argument("--app-key", required=True)
48
48
  schema_code_block.set_defaults(handler=_handle_schema_code_block, format_hint="")
49
49
 
50
+ member_candidates = record_subparsers.add_parser("member-candidates", help="读取成员字段候选项")
51
+ member_candidates.add_argument("--app-key", required=True)
52
+ member_candidates.add_argument("--field-id", type=int, required=True)
53
+ member_candidates.add_argument("--keyword", default="")
54
+ member_candidates.add_argument("--page-num", type=int, default=1)
55
+ member_candidates.add_argument("--page-size", type=int, default=20)
56
+ member_candidates.add_argument("--record-id")
57
+ member_candidates.add_argument("--workflow-node-id", type=int)
58
+ member_candidates.add_argument("--fields-file")
59
+ member_candidates.set_defaults(handler=_handle_member_candidates, format_hint="")
60
+
61
+ department_candidates = record_subparsers.add_parser("department-candidates", help="读取部门字段候选项")
62
+ department_candidates.add_argument("--app-key", required=True)
63
+ department_candidates.add_argument("--field-id", type=int, required=True)
64
+ department_candidates.add_argument("--keyword", default="")
65
+ department_candidates.add_argument("--page-num", type=int, default=1)
66
+ department_candidates.add_argument("--page-size", type=int, default=20)
67
+ department_candidates.add_argument("--record-id")
68
+ department_candidates.add_argument("--workflow-node-id", type=int)
69
+ department_candidates.add_argument("--fields-file")
70
+ department_candidates.set_defaults(handler=_handle_department_candidates, format_hint="")
71
+
50
72
  list_parser = record_subparsers.add_parser("list", help="列出记录")
51
73
  list_parser.add_argument("--app-key", required=True)
52
74
  list_parser.add_argument("--column", dest="columns", action="append", type=int, default=[])
@@ -80,9 +102,16 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
80
102
  get.add_argument("--view-id")
81
103
  get.set_defaults(handler=_handle_get, format_hint="record_get")
82
104
 
105
+ logs = record_subparsers.add_parser("logs", help="读取单条记录全量日志并写入本地 JSONL")
106
+ logs.add_argument("--app-key", required=True)
107
+ logs.add_argument("--record-id", required=True)
108
+ logs.add_argument("--view-id")
109
+ logs.set_defaults(handler=_handle_logs, format_hint="record_logs")
110
+
83
111
  insert = record_subparsers.add_parser("insert", help="新增记录")
84
112
  insert.add_argument("--app-key", required=True)
85
- insert.add_argument("--fields-file", required=True)
113
+ insert.add_argument("--fields-file", help=argparse.SUPPRESS)
114
+ insert.add_argument("--items-file")
86
115
  insert.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
87
116
  insert.set_defaults(handler=_handle_insert, format_hint="")
88
117
 
@@ -206,6 +235,38 @@ def _handle_schema_code_block(args: argparse.Namespace, context: CliContext) ->
206
235
  return context.code_block.record_code_block_schema_get_public(profile=args.profile, app_key=args.app_key)
207
236
 
208
237
 
238
+ def _candidate_context_fields(args: argparse.Namespace) -> dict[str, Any]:
239
+ return load_object_arg(args.fields_file, option_name="--fields-file") or {}
240
+
241
+
242
+ def _handle_member_candidates(args: argparse.Namespace, context: CliContext) -> dict:
243
+ return context.record.record_member_candidates(
244
+ profile=args.profile,
245
+ app_key=args.app_key,
246
+ field_id=args.field_id,
247
+ record_id=args.record_id,
248
+ workflow_node_id=args.workflow_node_id,
249
+ fields=_candidate_context_fields(args),
250
+ keyword=args.keyword,
251
+ page_num=args.page_num,
252
+ page_size=args.page_size,
253
+ )
254
+
255
+
256
+ def _handle_department_candidates(args: argparse.Namespace, context: CliContext) -> dict:
257
+ return context.record.record_department_candidates(
258
+ profile=args.profile,
259
+ app_key=args.app_key,
260
+ field_id=args.field_id,
261
+ record_id=args.record_id,
262
+ workflow_node_id=args.workflow_node_id,
263
+ fields=_candidate_context_fields(args),
264
+ keyword=args.keyword,
265
+ page_num=args.page_num,
266
+ page_size=args.page_size,
267
+ )
268
+
269
+
209
270
  def _validate_public_view_selector(
210
271
  *,
211
272
  view_id: str | None,
@@ -273,7 +334,33 @@ def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
273
334
  )
274
335
 
275
336
 
337
+ def _handle_logs(args: argparse.Namespace, context: CliContext) -> dict:
338
+ return context.record.record_logs_get(
339
+ profile=args.profile,
340
+ app_key=args.app_key,
341
+ record_id=args.record_id,
342
+ view_id=args.view_id,
343
+ )
344
+
345
+
276
346
  def _handle_insert(args: argparse.Namespace, context: CliContext) -> dict:
347
+ if args.items_file:
348
+ if args.fields_file:
349
+ raise_config_error(
350
+ "record insert batch mode does not accept --fields-file.",
351
+ fix_hint="Use `record insert --app-key APP_KEY --items-file ITEMS.json` for batch inserts.",
352
+ )
353
+ return context.record.record_insert_public(
354
+ profile=args.profile,
355
+ app_key=args.app_key,
356
+ items=require_list_arg(args.items_file, option_name="--items-file"),
357
+ verify_write=bool(args.verify_write),
358
+ )
359
+ if not args.fields_file:
360
+ raise_config_error(
361
+ "record insert requires --items-file.",
362
+ fix_hint="Use `record insert --app-key APP_KEY --items-file ITEMS.json`; a single insert is one item in the JSON array.",
363
+ )
277
364
  return context.record.record_insert_public(
278
365
  profile=args.profile,
279
366
  app_key=args.app_key,
@@ -292,6 +292,37 @@ def _format_record_get(result: dict[str, Any]) -> str:
292
292
  return "\n".join(lines) + "\n"
293
293
 
294
294
 
295
+ def _format_record_logs(result: dict[str, Any]) -> str:
296
+ app = result.get("app") if isinstance(result.get("app"), dict) else {}
297
+ view = result.get("view") if isinstance(result.get("view"), dict) else {}
298
+ record = result.get("record") if isinstance(result.get("record"), dict) else {}
299
+ data_logs = result.get("data_logs") if isinstance(result.get("data_logs"), dict) else {}
300
+ workflow_logs = result.get("workflow_logs") if isinstance(result.get("workflow_logs"), dict) else {}
301
+ integrity = result.get("context_integrity") if isinstance(result.get("context_integrity"), dict) else {}
302
+ lines = [
303
+ f"Status: {result.get('status') or '-'}",
304
+ f"App: {app.get('app_name') or app.get('app_key') or '-'} ({app.get('app_key') or '-'})",
305
+ f"View: {view.get('name') or view.get('view_id') or '-'}",
306
+ f"Record: {record.get('title') or '-'} ({record.get('record_id') or '-'})",
307
+ f"Data logs: {data_logs.get('status') or '-'} / count={data_logs.get('items_count')} / pages={data_logs.get('pages_fetched')} / complete={data_logs.get('complete')}",
308
+ f"Workflow logs: {workflow_logs.get('status') or '-'} / count={workflow_logs.get('items_count')} / pages={workflow_logs.get('pages_fetched')} / complete={workflow_logs.get('complete')}",
309
+ f"Safe for full log conclusion: {integrity.get('safe_for_full_log_conclusion')}",
310
+ ]
311
+ if result.get("local_dir"):
312
+ lines.append(f"Local dir: {result.get('local_dir')}")
313
+ if data_logs.get("local_path"):
314
+ lines.append(f"Data logs file: {data_logs.get('local_path')}")
315
+ if workflow_logs.get("local_path"):
316
+ lines.append(f"Workflow logs file: {workflow_logs.get('local_path')}")
317
+ if result.get("summary_path"):
318
+ lines.append(f"Summary file: {result.get('summary_path')}")
319
+ _append_warnings(lines, result.get("warnings"))
320
+ unavailable = result.get("unavailable_context") if isinstance(result.get("unavailable_context"), list) else []
321
+ if unavailable:
322
+ lines.append(f"Unavailable contexts: {len(unavailable)}")
323
+ return "\n".join(lines) + "\n"
324
+
325
+
295
326
  def _format_task_list(result: dict[str, Any]) -> str:
296
327
  data = result.get("data") if isinstance(result.get("data"), dict) else {}
297
328
  items = data.get("items") if isinstance(data.get("items"), list) else []
@@ -788,11 +819,11 @@ _FORMATTERS = {
788
819
  "workspace_get": _format_workspace_get,
789
820
  "workspace_select": _format_workspace_select,
790
821
  "app_list": _format_app_items,
791
- "app_search": _format_app_items,
792
822
  "app_get": _format_app_get,
793
823
  "record_list": _format_record_list,
794
824
  "record_access": _format_record_access,
795
825
  "record_get": _format_record_get,
826
+ "record_logs": _format_record_logs,
796
827
  "task_list": _format_task_list,
797
828
  "task_workbench": _format_task_workbench,
798
829
  "task_get": _format_task_get,
@@ -8,6 +8,8 @@ from typing import Any, Callable, TextIO
8
8
  from ..errors import QingflowApiError
9
9
  from ..public_surface import cli_public_tool_spec_from_namespace
10
10
  from ..response_trim import resolve_cli_tool_name, trim_error_response, trim_public_response
11
+ from ..tools.ai_builder_tools import _attach_builder_apply_envelope
12
+ from ..version import get_cli_version, get_cli_version_info
11
13
  from .context import CliContext, build_cli_context
12
14
  from .formatters import emit_json_result, emit_text_result
13
15
  from .commands import register_all_commands
@@ -16,11 +18,27 @@ from .commands import register_all_commands
16
18
  Handler = Callable[[argparse.Namespace, CliContext], dict[str, Any]]
17
19
 
18
20
 
21
+ class _CliArgumentError(Exception):
22
+ def __init__(self, *, prog: str, message: str, usage: str) -> None:
23
+ super().__init__(message)
24
+ self.prog = prog
25
+ self.message = message
26
+ self.usage = usage
27
+
28
+
29
+ class _QingflowArgumentParser(argparse.ArgumentParser):
30
+ def error(self, message: str) -> None:
31
+ raise _CliArgumentError(prog=self.prog, message=message, usage=self.format_usage())
32
+
33
+
19
34
  def build_parser() -> argparse.ArgumentParser:
20
- parser = argparse.ArgumentParser(prog="qingflow", description="Qingflow CLI")
35
+ parser = _QingflowArgumentParser(prog="qingflow", description="Qingflow CLI")
21
36
  parser.add_argument("--profile", default="default", help="会话 profile,默认 default")
22
37
  parser.add_argument("--json", action="store_true", help="输出 JSON")
38
+ parser.add_argument("--version", action="store_true", help="输出 Qingflow CLI 版本")
23
39
  subparsers = parser.add_subparsers(dest="command", required=True)
40
+ version_parser = subparsers.add_parser("version", help="输出 Qingflow CLI 版本")
41
+ version_parser.set_defaults(handler=_handle_version, format_hint="version")
24
42
  register_all_commands(subparsers)
25
43
  return parser
26
44
 
@@ -40,19 +58,40 @@ def run(
40
58
  err = stderr or sys.stderr
41
59
  parser = build_parser()
42
60
  normalized_argv = _normalize_global_args(list(argv) if argv is not None else sys.argv[1:])
61
+ if "--version" in normalized_argv:
62
+ return _emit_version(json_mode=_should_force_json_output_argv_for_version(normalized_argv), stdout=out)
43
63
  try:
44
64
  args = parser.parse_args(normalized_argv)
65
+ except _CliArgumentError as exc:
66
+ if _should_force_json_output_argv(normalized_argv):
67
+ payload = {
68
+ "category": "config",
69
+ "status": "failed",
70
+ "error_code": "ARGUMENT_ERROR",
71
+ "message": exc.message,
72
+ "details": {"usage": exc.usage.strip(), "prog": exc.prog},
73
+ }
74
+ payload = _maybe_attach_builder_apply_error_envelope_from_argv(normalized_argv, payload)
75
+ emit_json_result(payload, stream=out)
76
+ return 2
77
+ err.write(exc.usage)
78
+ err.write(f"{exc.prog}: error: {exc.message}\n")
79
+ return 2
45
80
  except SystemExit as exc:
46
81
  return int(exc.code or 0)
47
82
  setattr(args, "_stdin", sys.stdin)
48
83
  setattr(args, "_stdout_stream", out)
49
84
  setattr(args, "_stderr_stream", err)
85
+ if getattr(args, "command", "") == "version":
86
+ return _emit_version(json_mode=bool(args.json), stdout=out)
50
87
  handler = getattr(args, "handler", None)
51
88
  if handler is None:
52
89
  parser.print_help(out)
53
90
  return 2
54
- context = context_factory()
55
91
  try:
92
+ if _should_force_json_output(args):
93
+ setattr(args, "json", True)
94
+ context = context_factory()
56
95
  if not bool(args.json):
57
96
  _emit_cli_effective_context_notice(args, context, stream=err)
58
97
  result = handler(args, context)
@@ -60,12 +99,15 @@ def run(
60
99
  return int(exc.code or 0)
61
100
  except RuntimeError as exc:
62
101
  payload = trim_error_response(_parse_error_payload(exc))
102
+ payload = _maybe_attach_builder_apply_error_envelope_from_args(args, payload)
63
103
  return _emit_error(payload, json_mode=bool(args.json), stdout=out, stderr=err)
64
104
  except QingflowApiError as exc:
65
105
  payload = trim_error_response(exc.to_dict())
106
+ payload = _maybe_attach_builder_apply_error_envelope_from_args(args, payload)
66
107
  return _emit_error(payload, json_mode=bool(args.json), stdout=out, stderr=err)
67
108
  finally:
68
- context.close()
109
+ if "context" in locals():
110
+ context.close()
69
111
 
70
112
  exit_code = _result_exit_code(result)
71
113
  trimmed_result = trim_public_response(resolve_cli_tool_name(args), result) if isinstance(result, dict) else result
@@ -87,6 +129,10 @@ def _normalize_global_args(argv: list[str]) -> list[str]:
87
129
  global_args.append(token)
88
130
  index += 1
89
131
  continue
132
+ if token == "--version":
133
+ global_args.append(token)
134
+ index += 1
135
+ continue
90
136
  if token == "--profile":
91
137
  global_args.append(token)
92
138
  if index + 1 >= len(argv):
@@ -104,6 +150,202 @@ def _normalize_global_args(argv: list[str]) -> list[str]:
104
150
  return global_args + remaining
105
151
 
106
152
 
153
+ def _handle_version(_args: argparse.Namespace, _context: CliContext) -> dict[str, Any]:
154
+ info = get_cli_version_info()
155
+ return {
156
+ "ok": True,
157
+ "status": "success",
158
+ **info,
159
+ }
160
+
161
+
162
+ def _emit_version(*, json_mode: bool, stdout: TextIO) -> int:
163
+ version = get_cli_version()
164
+ if json_mode:
165
+ emit_json_result(
166
+ {
167
+ "ok": True,
168
+ "status": "success",
169
+ **get_cli_version_info(),
170
+ },
171
+ stream=stdout,
172
+ )
173
+ else:
174
+ stdout.write(f"{version}\n")
175
+ return 0
176
+
177
+
178
+ def _should_force_json_output_argv_for_version(argv: list[str]) -> bool:
179
+ return "--json" in argv
180
+
181
+
182
+ def _should_force_json_output(args: argparse.Namespace) -> bool:
183
+ if bool(getattr(args, "force_json_output", False)):
184
+ return True
185
+ if (
186
+ getattr(args, "command", "") == "builder"
187
+ and getattr(args, "builder_app_command", "") == "repair-code-blocks"
188
+ and bool(getattr(args, "apply", False))
189
+ ):
190
+ return True
191
+ return False
192
+
193
+
194
+ def _should_force_json_output_argv(argv: list[str]) -> bool:
195
+ tokens = _strip_global_args(argv)
196
+ if not tokens or tokens[0] not in {"builder", "build"}:
197
+ return False
198
+ if len(tokens) < 3:
199
+ return False
200
+ section = tokens[1]
201
+ action = tokens[2]
202
+ if section in {"package", "button", "associated-resource", "associated-resources", "portal", "schema", "layout", "views", "flow", "charts"}:
203
+ return action == "apply"
204
+ if section == "publish":
205
+ return action == "verify"
206
+ if section == "app":
207
+ if action == "release-edit-lock-if-mine":
208
+ return True
209
+ if action == "repair-code-blocks":
210
+ return "--apply" in tokens
211
+ return False
212
+
213
+
214
+ def _maybe_attach_builder_apply_error_envelope_from_args(args: argparse.Namespace, payload: dict[str, Any]) -> dict[str, Any]:
215
+ operation = _builder_apply_operation_from_args(args)
216
+ if not operation:
217
+ return payload
218
+ enriched = dict(payload)
219
+ enriched.setdefault("status", "failed")
220
+ enriched.setdefault("ok", False)
221
+ enriched.setdefault("write_executed", False)
222
+ enriched.setdefault("safe_to_retry", False)
223
+ _copy_arg_identity(enriched, args)
224
+ return _attach_builder_apply_envelope(operation, enriched)
225
+
226
+
227
+ def _maybe_attach_builder_apply_error_envelope_from_argv(argv: list[str], payload: dict[str, Any]) -> dict[str, Any]:
228
+ operation = _builder_apply_operation_from_argv(argv)
229
+ if not operation:
230
+ return payload
231
+ enriched = dict(payload)
232
+ enriched.setdefault("status", "failed")
233
+ enriched.setdefault("ok", False)
234
+ enriched.setdefault("write_executed", False)
235
+ enriched.setdefault("safe_to_retry", False)
236
+ _copy_argv_identity(enriched, argv)
237
+ return _attach_builder_apply_envelope(operation, enriched)
238
+
239
+
240
+ def _builder_apply_operation_from_args(args: argparse.Namespace) -> str | None:
241
+ if getattr(args, "command", "") not in {"builder", "build"}:
242
+ return None
243
+ section = str(getattr(args, "builder_command", "") or "")
244
+ if section == "package" and getattr(args, "builder_package_command", "") == "apply":
245
+ return "package_apply"
246
+ if section == "button" and getattr(args, "builder_button_command", "") == "apply":
247
+ return "app_custom_buttons_apply"
248
+ if section in {"associated-resource", "associated-resources"} and getattr(args, "builder_associated_resource_command", "") == "apply":
249
+ return "app_associated_resources_apply"
250
+ if section == "portal" and getattr(args, "builder_portal_command", "") == "apply":
251
+ return "portal_apply"
252
+ if section == "schema" and getattr(args, "builder_schema_command", "") == "apply":
253
+ return "app_schema_apply"
254
+ if section == "layout" and getattr(args, "builder_layout_command", "") == "apply":
255
+ return "app_layout_apply"
256
+ if section == "views" and getattr(args, "builder_views_command", "") == "apply":
257
+ return "app_views_apply"
258
+ if section == "flow" and getattr(args, "builder_flow_command", "") == "apply":
259
+ return "app_flow_apply"
260
+ if section == "charts" and getattr(args, "builder_charts_command", "") == "apply":
261
+ return "app_charts_apply"
262
+ if section == "publish" and getattr(args, "builder_publish_command", "") == "verify":
263
+ return "app_publish_verify"
264
+ return None
265
+
266
+
267
+ def _builder_apply_operation_from_argv(argv: list[str]) -> str | None:
268
+ tokens = _strip_global_args(argv)
269
+ if not tokens or tokens[0] not in {"builder", "build"} or len(tokens) < 3:
270
+ return None
271
+ section = tokens[1]
272
+ action = tokens[2]
273
+ if action != "apply" and not (section == "publish" and action == "verify"):
274
+ return None
275
+ mapping = {
276
+ "package": "package_apply",
277
+ "button": "app_custom_buttons_apply",
278
+ "associated-resource": "app_associated_resources_apply",
279
+ "associated-resources": "app_associated_resources_apply",
280
+ "portal": "portal_apply",
281
+ "schema": "app_schema_apply",
282
+ "layout": "app_layout_apply",
283
+ "views": "app_views_apply",
284
+ "flow": "app_flow_apply",
285
+ "charts": "app_charts_apply",
286
+ "publish": "app_publish_verify",
287
+ }
288
+ return mapping.get(section)
289
+
290
+
291
+ def _copy_arg_identity(payload: dict[str, Any], args: argparse.Namespace) -> None:
292
+ for attr, key in (
293
+ ("app_key", "app_key"),
294
+ ("app_name", "app_name"),
295
+ ("app_title", "app_title"),
296
+ ("package_id", "package_id"),
297
+ ("dash_key", "dash_key"),
298
+ ("dash_name", "dash_name"),
299
+ ):
300
+ value = getattr(args, attr, None)
301
+ if value not in (None, ""):
302
+ payload.setdefault(key, value)
303
+
304
+
305
+ def _copy_argv_identity(payload: dict[str, Any], argv: list[str]) -> None:
306
+ tokens = _strip_global_args(argv)
307
+ option_to_key = {
308
+ "--app-key": "app_key",
309
+ "--app-name": "app_name",
310
+ "--app-title": "app_title",
311
+ "--package-id": "package_id",
312
+ "--dash-key": "dash_key",
313
+ "--dash-name": "dash_name",
314
+ }
315
+ index = 0
316
+ while index < len(tokens):
317
+ token = tokens[index]
318
+ if token in option_to_key and index + 1 < len(tokens):
319
+ payload.setdefault(option_to_key[token], tokens[index + 1])
320
+ index += 2
321
+ continue
322
+ for option, key in option_to_key.items():
323
+ prefix = f"{option}="
324
+ if token.startswith(prefix):
325
+ payload.setdefault(key, token[len(prefix) :])
326
+ break
327
+ index += 1
328
+
329
+
330
+ def _strip_global_args(argv: list[str]) -> list[str]:
331
+ stripped: list[str] = []
332
+ index = 0
333
+ while index < len(argv):
334
+ token = argv[index]
335
+ if token == "--json":
336
+ index += 1
337
+ continue
338
+ if token == "--profile":
339
+ index += 2
340
+ continue
341
+ if token.startswith("--profile="):
342
+ index += 1
343
+ continue
344
+ stripped.append(token)
345
+ index += 1
346
+ return stripped
347
+
348
+
107
349
  def _parse_error_payload(exc: RuntimeError) -> dict[str, Any]:
108
350
  raw = str(exc)
109
351
  try:
@@ -38,7 +38,6 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
38
38
  PublicToolSpec(USER_DOMAIN, "workspace_get", ("workspace_get",), ("workspace", "get")),
39
39
  PublicToolSpec(USER_DOMAIN, "workspace_select", ("workspace_select",), ("workspace", "select")),
40
40
  PublicToolSpec(USER_DOMAIN, "app_list", ("app_list",), ("app", "list"), cli_show_effective_context=True),
41
- PublicToolSpec(USER_DOMAIN, "app_search", ("app_search",), ("app", "search"), cli_show_effective_context=True),
42
41
  PublicToolSpec(USER_DOMAIN, "app_get", ("app_get",), ("app", "get"), cli_show_effective_context=True),
43
42
  PublicToolSpec(USER_DOMAIN, "portal_list", ("portal_list",), ("portal", "list"), cli_show_effective_context=True),
44
43
  PublicToolSpec(USER_DOMAIN, "portal_get", ("portal_get",), ("portal", "get"), cli_show_effective_context=True),
@@ -78,12 +77,13 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
78
77
  ("record_code_block_schema_get_public",),
79
78
  ("record", "schema", "code-block"),
80
79
  ),
81
- PublicToolSpec(USER_DOMAIN, "record_member_candidates", ("record_member_candidates",), cli_public=False),
82
- PublicToolSpec(USER_DOMAIN, "record_department_candidates", ("record_department_candidates",), cli_public=False),
80
+ PublicToolSpec(USER_DOMAIN, "record_member_candidates", ("record_member_candidates",), ("record", "member-candidates")),
81
+ PublicToolSpec(USER_DOMAIN, "record_department_candidates", ("record_department_candidates",), ("record", "department-candidates")),
83
82
  PublicToolSpec(USER_DOMAIN, "record_analyze", ("record_analyze",), ("record", "analyze"), mcp_public=False),
84
83
  PublicToolSpec(USER_DOMAIN, "record_list", ("record_list",), ("record", "list"), cli_show_effective_context=True),
85
84
  PublicToolSpec(USER_DOMAIN, "record_access", ("record_access",), ("record", "access"), cli_show_effective_context=True),
86
85
  PublicToolSpec(USER_DOMAIN, "record_get", ("record_get_public",), ("record", "get"), cli_show_effective_context=True),
86
+ PublicToolSpec(USER_DOMAIN, "record_logs_get", ("record_logs_get",), ("record", "logs"), cli_show_effective_context=True),
87
87
  PublicToolSpec(USER_DOMAIN, "record_insert", ("record_insert_public",), ("record", "insert"), cli_show_effective_context=True, cli_context_write=True),
88
88
  PublicToolSpec(USER_DOMAIN, "record_update", ("record_update_public",), ("record", "update"), cli_show_effective_context=True, cli_context_write=True),
89
89
  PublicToolSpec(USER_DOMAIN, "record_delete", ("record_delete_public",), ("record", "delete"), cli_show_effective_context=True, cli_context_write=True),
@@ -127,6 +127,8 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
127
127
  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),
128
128
  PublicToolSpec(BUILDER_DOMAIN, "feedback_submit", ("feedback_submit",), ("builder", "feedback", "submit"), has_contract=True),
129
129
  PublicToolSpec(BUILDER_DOMAIN, "builder_tool_contract", ("builder_tool_contract",), ("builder", "contract"), has_contract=False),
130
+ PublicToolSpec(BUILDER_DOMAIN, "workspace_icon_catalog_get", ("workspace_icon_catalog_get",), ("builder", "icon", "catalog"), has_contract=True, cli_show_effective_context=True),
131
+ PublicToolSpec(BUILDER_DOMAIN, "package_list", ("package_list",), ("builder", "package", "list"), has_contract=True, cli_show_effective_context=True),
130
132
  PublicToolSpec(BUILDER_DOMAIN, "package_get", ("package_get",), ("builder", "package", "get"), has_contract=True, cli_show_effective_context=True),
131
133
  PublicToolSpec(BUILDER_DOMAIN, "package_apply", ("package_apply",), ("builder", "package", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
132
134
  PublicToolSpec(BUILDER_DOMAIN, "solution_install", ("solution_install",), ("builder", "solution", "install"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
@@ -136,11 +138,10 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
136
138
  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),
137
139
  PublicToolSpec(BUILDER_DOMAIN, "app_resolve", ("app_resolve",), ("builder", "app", "resolve"), has_contract=True, cli_show_effective_context=True),
138
140
  PublicToolSpec(BUILDER_DOMAIN, "button_style_catalog_get", ("button_style_catalog_get",), ("builder", "button", "catalog"), has_contract=True, cli_show_effective_context=True),
139
- PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_list", ("app_custom_button_list",), ("builder", "button", "list"), has_contract=True, cli_show_effective_context=True),
140
- PublicToolSpec(BUILDER_DOMAIN, "app_custom_button_get", ("app_custom_button_get",), ("builder", "button", "get"), has_contract=True, cli_show_effective_context=True),
141
- 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),
142
- 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),
143
- 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),
141
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_buttons", ("app_get_buttons",), ("builder", "button", "get"), has_contract=True, cli_show_effective_context=True),
142
+ PublicToolSpec(BUILDER_DOMAIN, "app_custom_buttons_apply", ("app_custom_buttons_apply",), ("builder", "button", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
143
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_associated_resources", ("app_get_associated_resources",), ("builder", "associated-resource", "get"), has_contract=True, cli_show_effective_context=True),
144
+ PublicToolSpec(BUILDER_DOMAIN, "app_associated_resources_apply", ("app_associated_resources_apply",), ("builder", "associated-resource", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
144
145
  PublicToolSpec(BUILDER_DOMAIN, "app_get", ("app_get",), ("builder", "app", "get", "summary"), has_contract=True, cli_show_effective_context=True),
145
146
  PublicToolSpec(BUILDER_DOMAIN, "app_get_fields", ("app_get_fields",), ("builder", "app", "get", "fields"), has_contract=True, cli_show_effective_context=True),
146
147
  PublicToolSpec(BUILDER_DOMAIN, "app_repair_code_blocks", ("app_repair_code_blocks",), ("builder", "app", "repair-code-blocks"), has_contract=True, cli_show_effective_context=True),
@@ -154,6 +155,8 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
154
155
  PublicToolSpec(BUILDER_DOMAIN, "chart_get", ("chart_get",), ("builder", "chart", "get"), has_contract=True, cli_show_effective_context=True),
155
156
  PublicToolSpec(BUILDER_DOMAIN, "app_schema_apply", ("app_schema_apply",), ("builder", "schema", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
156
157
  PublicToolSpec(BUILDER_DOMAIN, "app_layout_apply", ("app_layout_apply",), ("builder", "layout", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
158
+ PublicToolSpec(BUILDER_DOMAIN, "app_flow_get_schema", ("app_flow_get_schema",), ("builder", "flow", "schema"), has_contract=True, cli_show_effective_context=True),
159
+ PublicToolSpec(BUILDER_DOMAIN, "app_flow_get", ("app_flow_get",), ("builder", "flow", "get"), has_contract=True, cli_show_effective_context=True),
157
160
  PublicToolSpec(BUILDER_DOMAIN, "app_flow_apply", ("app_flow_apply",), ("builder", "flow", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
158
161
  PublicToolSpec(BUILDER_DOMAIN, "app_views_apply", ("app_views_apply",), ("builder", "views", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
159
162
  PublicToolSpec(BUILDER_DOMAIN, "app_charts_apply", ("app_charts_apply",), ("builder", "charts", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),