@josephyan/qingflow-app-builder-mcp 1.0.9 → 1.1.1
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 +4 -2
- package/npm/bin/qingflow-app-builder-mcp.mjs +31 -2
- package/npm/lib/runtime.mjs +43 -2
- package/package.json +1 -1
- package/pyproject.toml +2 -1
- package/skills/qingflow-app-builder/SKILL.md +135 -14
- package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
- package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
- package/skills/qingflow-app-builder/references/create-app.md +43 -3
- package/skills/qingflow-app-builder/references/gotchas.md +47 -3
- package/skills/qingflow-app-builder/references/match-rules.md +114 -0
- package/skills/qingflow-app-builder/references/public-surface-sync.md +75 -0
- package/skills/qingflow-app-builder/references/tool-selection.md +25 -10
- package/skills/qingflow-app-builder/references/update-flow.md +29 -1
- package/skills/qingflow-app-builder/references/update-schema.md +6 -2
- package/skills/qingflow-app-builder/references/update-views.md +135 -6
- package/skills/qingflow-mcp-setup/SKILL.md +111 -0
- package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
- package/skills/qingflow-mcp-setup/references/environments.md +61 -0
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
- package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
- package/skills/qingflow-workflow-builder/SKILL.md +96 -0
- package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
- package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
- package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
- package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
- package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
- package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
- package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
- package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
- package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +55 -1
- package/src/qingflow_mcp/builder_facade/models.py +532 -48
- package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
- package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
- package/src/qingflow_mcp/cli/commands/app.py +3 -16
- package/src/qingflow_mcp/cli/commands/builder.py +354 -56
- package/src/qingflow_mcp/cli/commands/record.py +112 -6
- package/src/qingflow_mcp/cli/formatters.py +102 -2
- package/src/qingflow_mcp/cli/main.py +204 -3
- package/src/qingflow_mcp/public_surface.py +12 -9
- package/src/qingflow_mcp/response_trim.py +287 -22
- package/src/qingflow_mcp/server.py +29 -20
- package/src/qingflow_mcp/server_app_builder.py +108 -30
- package/src/qingflow_mcp/server_app_user.py +28 -23
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
- package/src/qingflow_mcp/solution/executor.py +3 -133
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
- package/src/qingflow_mcp/tools/app_tools.py +53 -8
- package/src/qingflow_mcp/tools/package_tools.py +16 -2
- package/src/qingflow_mcp/tools/record_tools.py +14283 -9162
- package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
- package/src/qingflow_mcp/tools/solution_tools.py +30 -2
- package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -10,7 +10,11 @@ from .common import load_list_arg, load_object_arg, raise_config_error, require_
|
|
|
10
10
|
|
|
11
11
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
12
12
|
parser = subparsers.add_parser("record", help="记录与表结构")
|
|
13
|
-
record_subparsers = parser.add_subparsers(
|
|
13
|
+
record_subparsers = parser.add_subparsers(
|
|
14
|
+
dest="record_command",
|
|
15
|
+
required=True,
|
|
16
|
+
metavar="{schema,list,access,get,logs,insert,update,delete,member-candidates,department-candidates,code-block-run}",
|
|
17
|
+
)
|
|
14
18
|
|
|
15
19
|
schema = record_subparsers.add_parser("schema", help="读取记录相关表结构")
|
|
16
20
|
schema.add_argument("--mode", dest="legacy_mode", help=argparse.SUPPRESS)
|
|
@@ -43,13 +47,37 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
43
47
|
schema_code_block.add_argument("--app-key", required=True)
|
|
44
48
|
schema_code_block.set_defaults(handler=_handle_schema_code_block, format_hint="")
|
|
45
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
|
+
|
|
46
72
|
list_parser = record_subparsers.add_parser("list", help="列出记录")
|
|
47
73
|
list_parser.add_argument("--app-key", required=True)
|
|
48
74
|
list_parser.add_argument("--column", dest="columns", action="append", type=int, default=[])
|
|
49
75
|
list_parser.add_argument("--columns-file")
|
|
76
|
+
list_parser.add_argument("--query")
|
|
77
|
+
list_parser.add_argument("--query-field", dest="query_fields", action="append", type=int, default=[])
|
|
78
|
+
list_parser.add_argument("--query-fields-file")
|
|
50
79
|
list_parser.add_argument("--where-file")
|
|
51
80
|
list_parser.add_argument("--order-by-file")
|
|
52
|
-
list_parser.add_argument("--limit", type=int, default=20)
|
|
53
81
|
list_parser.add_argument("--page", type=int, default=1)
|
|
54
82
|
list_parser.add_argument("--view-id")
|
|
55
83
|
list_parser.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
|
|
@@ -72,11 +100,18 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
72
100
|
get.add_argument("--column", dest="columns", action="append", type=int, default=[])
|
|
73
101
|
get.add_argument("--columns-file")
|
|
74
102
|
get.add_argument("--view-id")
|
|
75
|
-
get.set_defaults(handler=_handle_get, format_hint="")
|
|
103
|
+
get.set_defaults(handler=_handle_get, format_hint="record_get")
|
|
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")
|
|
76
110
|
|
|
77
111
|
insert = record_subparsers.add_parser("insert", help="新增记录")
|
|
78
112
|
insert.add_argument("--app-key", required=True)
|
|
79
|
-
insert.add_argument("--fields-file",
|
|
113
|
+
insert.add_argument("--fields-file", help=argparse.SUPPRESS)
|
|
114
|
+
insert.add_argument("--items-file")
|
|
80
115
|
insert.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
|
|
81
116
|
insert.set_defaults(handler=_handle_insert, format_hint="")
|
|
82
117
|
|
|
@@ -95,7 +130,12 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
95
130
|
delete.add_argument("--record-ids-file")
|
|
96
131
|
delete.set_defaults(handler=_handle_delete, format_hint="")
|
|
97
132
|
|
|
98
|
-
analyze = record_subparsers.add_parser("analyze", help=
|
|
133
|
+
analyze = record_subparsers.add_parser("analyze", help=argparse.SUPPRESS)
|
|
134
|
+
record_subparsers._choices_actions = [ # type: ignore[attr-defined]
|
|
135
|
+
action
|
|
136
|
+
for action in record_subparsers._choices_actions # type: ignore[attr-defined]
|
|
137
|
+
if getattr(action, "dest", None) != "analyze"
|
|
138
|
+
]
|
|
99
139
|
analyze.add_argument("--app-key", required=True)
|
|
100
140
|
analyze.add_argument("--dimensions-file")
|
|
101
141
|
analyze.add_argument("--metrics-file")
|
|
@@ -131,6 +171,13 @@ def _columns(args: argparse.Namespace) -> list[Any]:
|
|
|
131
171
|
return columns
|
|
132
172
|
|
|
133
173
|
|
|
174
|
+
def _query_fields(args: argparse.Namespace) -> list[Any]:
|
|
175
|
+
query_fields: list[Any] = list(getattr(args, "query_fields", None) or [])
|
|
176
|
+
if getattr(args, "query_fields_file", None):
|
|
177
|
+
query_fields.extend(require_list_arg(args.query_fields_file, option_name="--query-fields-file"))
|
|
178
|
+
return query_fields
|
|
179
|
+
|
|
180
|
+
|
|
134
181
|
def _handle_schema_root(args: argparse.Namespace, _context: CliContext) -> dict:
|
|
135
182
|
mode = (args.legacy_mode or "").strip()
|
|
136
183
|
if mode:
|
|
@@ -188,6 +235,38 @@ def _handle_schema_code_block(args: argparse.Namespace, context: CliContext) ->
|
|
|
188
235
|
return context.code_block.record_code_block_schema_get_public(profile=args.profile, app_key=args.app_key)
|
|
189
236
|
|
|
190
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
|
+
|
|
191
270
|
def _validate_public_view_selector(
|
|
192
271
|
*,
|
|
193
272
|
view_id: str | None,
|
|
@@ -225,9 +304,10 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
225
304
|
profile=args.profile,
|
|
226
305
|
app_key=args.app_key,
|
|
227
306
|
columns=_columns(args),
|
|
307
|
+
query=args.query,
|
|
308
|
+
query_fields=_query_fields(args),
|
|
228
309
|
where=load_list_arg(args.where_file, option_name="--where-file"),
|
|
229
310
|
order_by=load_list_arg(args.order_by_file, option_name="--order-by-file"),
|
|
230
|
-
limit=args.limit,
|
|
231
311
|
page=args.page,
|
|
232
312
|
view_id=args.view_id,
|
|
233
313
|
)
|
|
@@ -254,7 +334,33 @@ def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
254
334
|
)
|
|
255
335
|
|
|
256
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
|
+
|
|
257
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
|
+
)
|
|
258
364
|
return context.record.record_insert_public(
|
|
259
365
|
profile=args.profile,
|
|
260
366
|
app_key=args.app_key,
|
|
@@ -183,7 +183,17 @@ def _format_app_get(result: dict[str, Any]) -> str:
|
|
|
183
183
|
def _format_record_list(result: dict[str, Any]) -> str:
|
|
184
184
|
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
185
185
|
items = data.get("items") if isinstance(data.get("items"), list) else []
|
|
186
|
-
lines = [
|
|
186
|
+
lines: list[str] = []
|
|
187
|
+
lookup = result.get("lookup") if isinstance(result.get("lookup"), dict) else {}
|
|
188
|
+
if lookup:
|
|
189
|
+
lines.append("Lookup:")
|
|
190
|
+
lines.append(f"- query: {lookup.get('query')}")
|
|
191
|
+
lines.append(f"- confidence: {lookup.get('confidence')}")
|
|
192
|
+
lines.append(f"- next_action: {lookup.get('next_action')}")
|
|
193
|
+
lines.append(f"- total_count: {lookup.get('total_count')}")
|
|
194
|
+
lines.append(f"- returned_count: {lookup.get('returned_count')}")
|
|
195
|
+
lines.append(f"- truncated: {lookup.get('truncated')}")
|
|
196
|
+
lines.append(f"Returned Records: {len(items)}")
|
|
187
197
|
for item in items[:10]:
|
|
188
198
|
if isinstance(item, dict):
|
|
189
199
|
lines.append(json.dumps(item, ensure_ascii=False))
|
|
@@ -224,6 +234,95 @@ def _format_record_access(result: dict[str, Any]) -> str:
|
|
|
224
234
|
return "\n".join(lines) + "\n"
|
|
225
235
|
|
|
226
236
|
|
|
237
|
+
def _format_record_get(result: dict[str, Any]) -> str:
|
|
238
|
+
record = result.get("record") if isinstance(result.get("record"), dict) else {}
|
|
239
|
+
app = result.get("app") if isinstance(result.get("app"), dict) else {}
|
|
240
|
+
view = result.get("view") if isinstance(result.get("view"), dict) else {}
|
|
241
|
+
fields = result.get("fields") if isinstance(result.get("fields"), list) else []
|
|
242
|
+
data_logs = result.get("data_logs") if isinstance(result.get("data_logs"), dict) else {}
|
|
243
|
+
workflow_logs = result.get("workflow_logs") if isinstance(result.get("workflow_logs"), dict) else {}
|
|
244
|
+
media_assets = result.get("media_assets") if isinstance(result.get("media_assets"), dict) else {}
|
|
245
|
+
media_items = media_assets.get("items") if isinstance(media_assets.get("items"), list) else []
|
|
246
|
+
downloaded_media = [item for item in media_items if isinstance(item, dict) and item.get("access_status") == "downloaded"]
|
|
247
|
+
failed_media = [item for item in media_items if isinstance(item, dict) and item.get("access_status") != "downloaded"]
|
|
248
|
+
file_assets = result.get("file_assets") if isinstance(result.get("file_assets"), dict) else {}
|
|
249
|
+
file_items = file_assets.get("items") if isinstance(file_assets.get("items"), list) else []
|
|
250
|
+
downloaded_files = [item for item in file_items if isinstance(item, dict) and item.get("access_status") == "downloaded"]
|
|
251
|
+
failed_files = [item for item in file_items if isinstance(item, dict) and item.get("access_status") != "downloaded"]
|
|
252
|
+
extracted_files = [
|
|
253
|
+
item
|
|
254
|
+
for item in downloaded_files
|
|
255
|
+
if isinstance(item.get("extraction"), dict) and item["extraction"].get("status") == "ok"
|
|
256
|
+
]
|
|
257
|
+
associated_resources = result.get("associated_resources") if isinstance(result.get("associated_resources"), list) else []
|
|
258
|
+
unavailable_context = result.get("unavailable_context") if isinstance(result.get("unavailable_context"), list) else []
|
|
259
|
+
lines = [
|
|
260
|
+
f"Status: {result.get('status') or '-'}",
|
|
261
|
+
f"App: {app.get('app_name') or app.get('app_key') or '-'} ({app.get('app_key') or '-'})",
|
|
262
|
+
f"View: {view.get('name') or view.get('view_id') or '-'}",
|
|
263
|
+
f"Record: {record.get('title') or '-'} ({record.get('record_id') or '-'})",
|
|
264
|
+
f"Fields: {len(fields)}",
|
|
265
|
+
f"Data logs: {data_logs.get('status') or '-'} / loaded={data_logs.get('items_loaded')}",
|
|
266
|
+
f"Workflow logs: {workflow_logs.get('status') or '-'} / loaded={workflow_logs.get('items_loaded')}",
|
|
267
|
+
f"Media assets: {media_assets.get('status') or '-'} / downloaded={len(downloaded_media)} / failed={len(failed_media)}",
|
|
268
|
+
f"File assets: {file_assets.get('status') or '-'} / downloaded={len(downloaded_files)} / extracted={len(extracted_files)} / failed={len(failed_files)}",
|
|
269
|
+
f"Associated resources: {len(associated_resources)}",
|
|
270
|
+
f"Unavailable contexts: {len(unavailable_context)}",
|
|
271
|
+
]
|
|
272
|
+
if media_assets.get("local_dir"):
|
|
273
|
+
lines.append(f"Media dir: {media_assets.get('local_dir')}")
|
|
274
|
+
if file_assets.get("local_dir"):
|
|
275
|
+
lines.append(f"File dir: {file_assets.get('local_dir')}")
|
|
276
|
+
if failed_media:
|
|
277
|
+
failure_counts: dict[str, int] = {}
|
|
278
|
+
for item in failed_media:
|
|
279
|
+
key = f"{item.get('access_status') or 'unknown'} via {item.get('download_strategy') or 'unknown'}"
|
|
280
|
+
failure_counts[key] = failure_counts.get(key, 0) + 1
|
|
281
|
+
lines.append("Media failures: " + ", ".join(f"{key}={count}" for key, count in sorted(failure_counts.items())))
|
|
282
|
+
if failed_files:
|
|
283
|
+
failure_counts = {}
|
|
284
|
+
for item in failed_files:
|
|
285
|
+
key = f"{item.get('access_status') or 'unknown'} via {item.get('download_strategy') or 'unknown'}"
|
|
286
|
+
failure_counts[key] = failure_counts.get(key, 0) + 1
|
|
287
|
+
lines.append("File failures: " + ", ".join(f"{key}={count}" for key, count in sorted(failure_counts.items())))
|
|
288
|
+
summary = result.get("summary") if isinstance(result.get("summary"), dict) else {}
|
|
289
|
+
if summary.get("text"):
|
|
290
|
+
lines.append(f"Summary: {summary.get('text')}")
|
|
291
|
+
_append_warnings(lines, result.get("warnings"))
|
|
292
|
+
return "\n".join(lines) + "\n"
|
|
293
|
+
|
|
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
|
+
|
|
227
326
|
def _format_task_list(result: dict[str, Any]) -> str:
|
|
228
327
|
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
229
328
|
items = data.get("items") if isinstance(data.get("items"), list) else []
|
|
@@ -720,10 +819,11 @@ _FORMATTERS = {
|
|
|
720
819
|
"workspace_get": _format_workspace_get,
|
|
721
820
|
"workspace_select": _format_workspace_select,
|
|
722
821
|
"app_list": _format_app_items,
|
|
723
|
-
"app_search": _format_app_items,
|
|
724
822
|
"app_get": _format_app_get,
|
|
725
823
|
"record_list": _format_record_list,
|
|
726
824
|
"record_access": _format_record_access,
|
|
825
|
+
"record_get": _format_record_get,
|
|
826
|
+
"record_logs": _format_record_logs,
|
|
727
827
|
"task_list": _format_task_list,
|
|
728
828
|
"task_workbench": _format_task_workbench,
|
|
729
829
|
"task_get": _format_task_get,
|
|
@@ -8,6 +8,7 @@ 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
|
|
11
12
|
from .context import CliContext, build_cli_context
|
|
12
13
|
from .formatters import emit_json_result, emit_text_result
|
|
13
14
|
from .commands import register_all_commands
|
|
@@ -16,8 +17,21 @@ from .commands import register_all_commands
|
|
|
16
17
|
Handler = Callable[[argparse.Namespace, CliContext], dict[str, Any]]
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
class _CliArgumentError(Exception):
|
|
21
|
+
def __init__(self, *, prog: str, message: str, usage: str) -> None:
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
self.prog = prog
|
|
24
|
+
self.message = message
|
|
25
|
+
self.usage = usage
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _QingflowArgumentParser(argparse.ArgumentParser):
|
|
29
|
+
def error(self, message: str) -> None:
|
|
30
|
+
raise _CliArgumentError(prog=self.prog, message=message, usage=self.format_usage())
|
|
31
|
+
|
|
32
|
+
|
|
19
33
|
def build_parser() -> argparse.ArgumentParser:
|
|
20
|
-
parser =
|
|
34
|
+
parser = _QingflowArgumentParser(prog="qingflow", description="Qingflow CLI")
|
|
21
35
|
parser.add_argument("--profile", default="default", help="会话 profile,默认 default")
|
|
22
36
|
parser.add_argument("--json", action="store_true", help="输出 JSON")
|
|
23
37
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
@@ -42,6 +56,21 @@ def run(
|
|
|
42
56
|
normalized_argv = _normalize_global_args(list(argv) if argv is not None else sys.argv[1:])
|
|
43
57
|
try:
|
|
44
58
|
args = parser.parse_args(normalized_argv)
|
|
59
|
+
except _CliArgumentError as exc:
|
|
60
|
+
if _should_force_json_output_argv(normalized_argv):
|
|
61
|
+
payload = {
|
|
62
|
+
"category": "config",
|
|
63
|
+
"status": "failed",
|
|
64
|
+
"error_code": "ARGUMENT_ERROR",
|
|
65
|
+
"message": exc.message,
|
|
66
|
+
"details": {"usage": exc.usage.strip(), "prog": exc.prog},
|
|
67
|
+
}
|
|
68
|
+
payload = _maybe_attach_builder_apply_error_envelope_from_argv(normalized_argv, payload)
|
|
69
|
+
emit_json_result(payload, stream=out)
|
|
70
|
+
return 2
|
|
71
|
+
err.write(exc.usage)
|
|
72
|
+
err.write(f"{exc.prog}: error: {exc.message}\n")
|
|
73
|
+
return 2
|
|
45
74
|
except SystemExit as exc:
|
|
46
75
|
return int(exc.code or 0)
|
|
47
76
|
setattr(args, "_stdin", sys.stdin)
|
|
@@ -51,8 +80,10 @@ def run(
|
|
|
51
80
|
if handler is None:
|
|
52
81
|
parser.print_help(out)
|
|
53
82
|
return 2
|
|
54
|
-
context = context_factory()
|
|
55
83
|
try:
|
|
84
|
+
if _should_force_json_output(args):
|
|
85
|
+
setattr(args, "json", True)
|
|
86
|
+
context = context_factory()
|
|
56
87
|
if not bool(args.json):
|
|
57
88
|
_emit_cli_effective_context_notice(args, context, stream=err)
|
|
58
89
|
result = handler(args, context)
|
|
@@ -60,12 +91,15 @@ def run(
|
|
|
60
91
|
return int(exc.code or 0)
|
|
61
92
|
except RuntimeError as exc:
|
|
62
93
|
payload = trim_error_response(_parse_error_payload(exc))
|
|
94
|
+
payload = _maybe_attach_builder_apply_error_envelope_from_args(args, payload)
|
|
63
95
|
return _emit_error(payload, json_mode=bool(args.json), stdout=out, stderr=err)
|
|
64
96
|
except QingflowApiError as exc:
|
|
65
97
|
payload = trim_error_response(exc.to_dict())
|
|
98
|
+
payload = _maybe_attach_builder_apply_error_envelope_from_args(args, payload)
|
|
66
99
|
return _emit_error(payload, json_mode=bool(args.json), stdout=out, stderr=err)
|
|
67
100
|
finally:
|
|
68
|
-
context
|
|
101
|
+
if "context" in locals():
|
|
102
|
+
context.close()
|
|
69
103
|
|
|
70
104
|
exit_code = _result_exit_code(result)
|
|
71
105
|
trimmed_result = trim_public_response(resolve_cli_tool_name(args), result) if isinstance(result, dict) else result
|
|
@@ -104,6 +138,173 @@ def _normalize_global_args(argv: list[str]) -> list[str]:
|
|
|
104
138
|
return global_args + remaining
|
|
105
139
|
|
|
106
140
|
|
|
141
|
+
def _should_force_json_output(args: argparse.Namespace) -> bool:
|
|
142
|
+
if bool(getattr(args, "force_json_output", False)):
|
|
143
|
+
return True
|
|
144
|
+
if (
|
|
145
|
+
getattr(args, "command", "") == "builder"
|
|
146
|
+
and getattr(args, "builder_app_command", "") == "repair-code-blocks"
|
|
147
|
+
and bool(getattr(args, "apply", False))
|
|
148
|
+
):
|
|
149
|
+
return True
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _should_force_json_output_argv(argv: list[str]) -> bool:
|
|
154
|
+
tokens = _strip_global_args(argv)
|
|
155
|
+
if not tokens or tokens[0] not in {"builder", "build"}:
|
|
156
|
+
return False
|
|
157
|
+
if len(tokens) < 3:
|
|
158
|
+
return False
|
|
159
|
+
section = tokens[1]
|
|
160
|
+
action = tokens[2]
|
|
161
|
+
if section in {"package", "button", "associated-resource", "associated-resources", "portal", "schema", "layout", "views", "flow", "charts"}:
|
|
162
|
+
return action == "apply"
|
|
163
|
+
if section == "publish":
|
|
164
|
+
return action == "verify"
|
|
165
|
+
if section == "app":
|
|
166
|
+
if action == "release-edit-lock-if-mine":
|
|
167
|
+
return True
|
|
168
|
+
if action == "repair-code-blocks":
|
|
169
|
+
return "--apply" in tokens
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _maybe_attach_builder_apply_error_envelope_from_args(args: argparse.Namespace, payload: dict[str, Any]) -> dict[str, Any]:
|
|
174
|
+
operation = _builder_apply_operation_from_args(args)
|
|
175
|
+
if not operation:
|
|
176
|
+
return payload
|
|
177
|
+
enriched = dict(payload)
|
|
178
|
+
enriched.setdefault("status", "failed")
|
|
179
|
+
enriched.setdefault("ok", False)
|
|
180
|
+
enriched.setdefault("write_executed", False)
|
|
181
|
+
enriched.setdefault("safe_to_retry", False)
|
|
182
|
+
_copy_arg_identity(enriched, args)
|
|
183
|
+
return _attach_builder_apply_envelope(operation, enriched)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _maybe_attach_builder_apply_error_envelope_from_argv(argv: list[str], payload: dict[str, Any]) -> dict[str, Any]:
|
|
187
|
+
operation = _builder_apply_operation_from_argv(argv)
|
|
188
|
+
if not operation:
|
|
189
|
+
return payload
|
|
190
|
+
enriched = dict(payload)
|
|
191
|
+
enriched.setdefault("status", "failed")
|
|
192
|
+
enriched.setdefault("ok", False)
|
|
193
|
+
enriched.setdefault("write_executed", False)
|
|
194
|
+
enriched.setdefault("safe_to_retry", False)
|
|
195
|
+
_copy_argv_identity(enriched, argv)
|
|
196
|
+
return _attach_builder_apply_envelope(operation, enriched)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _builder_apply_operation_from_args(args: argparse.Namespace) -> str | None:
|
|
200
|
+
if getattr(args, "command", "") not in {"builder", "build"}:
|
|
201
|
+
return None
|
|
202
|
+
section = str(getattr(args, "builder_command", "") or "")
|
|
203
|
+
if section == "package" and getattr(args, "builder_package_command", "") == "apply":
|
|
204
|
+
return "package_apply"
|
|
205
|
+
if section == "button" and getattr(args, "builder_button_command", "") == "apply":
|
|
206
|
+
return "app_custom_buttons_apply"
|
|
207
|
+
if section in {"associated-resource", "associated-resources"} and getattr(args, "builder_associated_resource_command", "") == "apply":
|
|
208
|
+
return "app_associated_resources_apply"
|
|
209
|
+
if section == "portal" and getattr(args, "builder_portal_command", "") == "apply":
|
|
210
|
+
return "portal_apply"
|
|
211
|
+
if section == "schema" and getattr(args, "builder_schema_command", "") == "apply":
|
|
212
|
+
return "app_schema_apply"
|
|
213
|
+
if section == "layout" and getattr(args, "builder_layout_command", "") == "apply":
|
|
214
|
+
return "app_layout_apply"
|
|
215
|
+
if section == "views" and getattr(args, "builder_views_command", "") == "apply":
|
|
216
|
+
return "app_views_apply"
|
|
217
|
+
if section == "flow" and getattr(args, "builder_flow_command", "") == "apply":
|
|
218
|
+
return "app_flow_apply"
|
|
219
|
+
if section == "charts" and getattr(args, "builder_charts_command", "") == "apply":
|
|
220
|
+
return "app_charts_apply"
|
|
221
|
+
if section == "publish" and getattr(args, "builder_publish_command", "") == "verify":
|
|
222
|
+
return "app_publish_verify"
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _builder_apply_operation_from_argv(argv: list[str]) -> str | None:
|
|
227
|
+
tokens = _strip_global_args(argv)
|
|
228
|
+
if not tokens or tokens[0] not in {"builder", "build"} or len(tokens) < 3:
|
|
229
|
+
return None
|
|
230
|
+
section = tokens[1]
|
|
231
|
+
action = tokens[2]
|
|
232
|
+
if action != "apply" and not (section == "publish" and action == "verify"):
|
|
233
|
+
return None
|
|
234
|
+
mapping = {
|
|
235
|
+
"package": "package_apply",
|
|
236
|
+
"button": "app_custom_buttons_apply",
|
|
237
|
+
"associated-resource": "app_associated_resources_apply",
|
|
238
|
+
"associated-resources": "app_associated_resources_apply",
|
|
239
|
+
"portal": "portal_apply",
|
|
240
|
+
"schema": "app_schema_apply",
|
|
241
|
+
"layout": "app_layout_apply",
|
|
242
|
+
"views": "app_views_apply",
|
|
243
|
+
"flow": "app_flow_apply",
|
|
244
|
+
"charts": "app_charts_apply",
|
|
245
|
+
"publish": "app_publish_verify",
|
|
246
|
+
}
|
|
247
|
+
return mapping.get(section)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _copy_arg_identity(payload: dict[str, Any], args: argparse.Namespace) -> None:
|
|
251
|
+
for attr, key in (
|
|
252
|
+
("app_key", "app_key"),
|
|
253
|
+
("app_name", "app_name"),
|
|
254
|
+
("app_title", "app_title"),
|
|
255
|
+
("package_id", "package_id"),
|
|
256
|
+
("dash_key", "dash_key"),
|
|
257
|
+
("dash_name", "dash_name"),
|
|
258
|
+
):
|
|
259
|
+
value = getattr(args, attr, None)
|
|
260
|
+
if value not in (None, ""):
|
|
261
|
+
payload.setdefault(key, value)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _copy_argv_identity(payload: dict[str, Any], argv: list[str]) -> None:
|
|
265
|
+
tokens = _strip_global_args(argv)
|
|
266
|
+
option_to_key = {
|
|
267
|
+
"--app-key": "app_key",
|
|
268
|
+
"--app-name": "app_name",
|
|
269
|
+
"--app-title": "app_title",
|
|
270
|
+
"--package-id": "package_id",
|
|
271
|
+
"--dash-key": "dash_key",
|
|
272
|
+
"--dash-name": "dash_name",
|
|
273
|
+
}
|
|
274
|
+
index = 0
|
|
275
|
+
while index < len(tokens):
|
|
276
|
+
token = tokens[index]
|
|
277
|
+
if token in option_to_key and index + 1 < len(tokens):
|
|
278
|
+
payload.setdefault(option_to_key[token], tokens[index + 1])
|
|
279
|
+
index += 2
|
|
280
|
+
continue
|
|
281
|
+
for option, key in option_to_key.items():
|
|
282
|
+
prefix = f"{option}="
|
|
283
|
+
if token.startswith(prefix):
|
|
284
|
+
payload.setdefault(key, token[len(prefix) :])
|
|
285
|
+
break
|
|
286
|
+
index += 1
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _strip_global_args(argv: list[str]) -> list[str]:
|
|
290
|
+
stripped: list[str] = []
|
|
291
|
+
index = 0
|
|
292
|
+
while index < len(argv):
|
|
293
|
+
token = argv[index]
|
|
294
|
+
if token == "--json":
|
|
295
|
+
index += 1
|
|
296
|
+
continue
|
|
297
|
+
if token == "--profile":
|
|
298
|
+
index += 2
|
|
299
|
+
continue
|
|
300
|
+
if token.startswith("--profile="):
|
|
301
|
+
index += 1
|
|
302
|
+
continue
|
|
303
|
+
stripped.append(token)
|
|
304
|
+
index += 1
|
|
305
|
+
return stripped
|
|
306
|
+
|
|
307
|
+
|
|
107
308
|
def _parse_error_payload(exc: RuntimeError) -> dict[str, Any]:
|
|
108
309
|
raw = str(exc)
|
|
109
310
|
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",),
|
|
82
|
-
PublicToolSpec(USER_DOMAIN, "record_department_candidates", ("record_department_candidates",),
|
|
83
|
-
PublicToolSpec(USER_DOMAIN, "record_analyze", ("record_analyze",), ("record", "analyze")),
|
|
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")),
|
|
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, "
|
|
140
|
-
PublicToolSpec(BUILDER_DOMAIN, "
|
|
141
|
-
PublicToolSpec(BUILDER_DOMAIN, "
|
|
142
|
-
PublicToolSpec(BUILDER_DOMAIN, "
|
|
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),
|