@josephyan/qingflow-cli 1.0.10 → 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 +3 -3
- package/npm/bin/qingflow.mjs +32 -1
- package/npm/lib/runtime.mjs +43 -2
- package/package.json +1 -1
- package/pyproject.toml +2 -1
- package/skills/qingflow-cli/SKILL.md +440 -0
- package/skills/qingflow-cli/manifest.yaml +10 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +485 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md +237 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_MATCH_RULES.md +137 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md +263 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +304 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md +41 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +20 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +163 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +107 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +151 -0
- package/skills/qingflow-cli/reference/_batch_schema_complex.json +18 -0
- package/skills/qingflow-cli/reference/_batch_schema_scalar.json +17 -0
- package/skills/qingflow-cli/reference/charts_remove.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_reorder.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_upsert_bar.example.json +8 -0
- package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +37 -0
- package/skills/qingflow-cli/reference/charts_upsert_minimal.example.json +13 -0
- package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +131 -0
- package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +126 -0
- package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +128 -0
- package/skills/qingflow-cli/reference/schema_add_fields_minimal.example.json +7 -0
- package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +78 -0
- package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +7 -0
- package/skills/qingflow-cli/scripts/builder-package-from-app-list.py +140 -0
- package/skills/qingflow-cli/scripts/find-app-by-keyword.py +132 -0
- package/skills/qingflow-cli/scripts/validate_qingflow_output_files.py +87 -0
- package/src/qingflow_mcp/__init__.py +1 -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 +89 -4
- package/src/qingflow_mcp/cli/formatters.py +53 -15
- package/src/qingflow_mcp/cli/main.py +204 -3
- package/src/qingflow_mcp/public_surface.py +11 -8
- package/src/qingflow_mcp/response_trim.py +185 -46
- package/src/qingflow_mcp/server.py +18 -15
- package/src/qingflow_mcp/server_app_builder.py +108 -30
- package/src/qingflow_mcp/server_app_user.py +20 -21
- 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 +3408 -599
- 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
|
@@ -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=[])
|
|
@@ -56,7 +78,6 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
56
78
|
list_parser.add_argument("--query-fields-file")
|
|
57
79
|
list_parser.add_argument("--where-file")
|
|
58
80
|
list_parser.add_argument("--order-by-file")
|
|
59
|
-
list_parser.add_argument("--limit", type=int, default=20)
|
|
60
81
|
list_parser.add_argument("--page", type=int, default=1)
|
|
61
82
|
list_parser.add_argument("--view-id")
|
|
62
83
|
list_parser.add_argument("--list-type", dest="legacy_list_type", type=int, help=argparse.SUPPRESS)
|
|
@@ -81,9 +102,16 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
81
102
|
get.add_argument("--view-id")
|
|
82
103
|
get.set_defaults(handler=_handle_get, format_hint="record_get")
|
|
83
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
|
+
|
|
84
111
|
insert = record_subparsers.add_parser("insert", help="新增记录")
|
|
85
112
|
insert.add_argument("--app-key", required=True)
|
|
86
|
-
insert.add_argument("--fields-file",
|
|
113
|
+
insert.add_argument("--fields-file", help=argparse.SUPPRESS)
|
|
114
|
+
insert.add_argument("--items-file")
|
|
87
115
|
insert.add_argument("--verify-write", action=argparse.BooleanOptionalAction, default=True)
|
|
88
116
|
insert.set_defaults(handler=_handle_insert, format_hint="")
|
|
89
117
|
|
|
@@ -207,6 +235,38 @@ def _handle_schema_code_block(args: argparse.Namespace, context: CliContext) ->
|
|
|
207
235
|
return context.code_block.record_code_block_schema_get_public(profile=args.profile, app_key=args.app_key)
|
|
208
236
|
|
|
209
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
|
+
|
|
210
270
|
def _validate_public_view_selector(
|
|
211
271
|
*,
|
|
212
272
|
view_id: str | None,
|
|
@@ -248,7 +308,6 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
248
308
|
query_fields=_query_fields(args),
|
|
249
309
|
where=load_list_arg(args.where_file, option_name="--where-file"),
|
|
250
310
|
order_by=load_list_arg(args.order_by_file, option_name="--order-by-file"),
|
|
251
|
-
limit=args.limit,
|
|
252
311
|
page=args.page,
|
|
253
312
|
view_id=args.view_id,
|
|
254
313
|
)
|
|
@@ -275,7 +334,33 @@ def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
275
334
|
)
|
|
276
335
|
|
|
277
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
|
+
|
|
278
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
|
+
)
|
|
279
364
|
return context.record.record_insert_public(
|
|
280
365
|
profile=args.profile,
|
|
281
366
|
app_key=args.app_key,
|
|
@@ -190,20 +190,9 @@ def _format_record_list(result: dict[str, Any]) -> str:
|
|
|
190
190
|
lines.append(f"- query: {lookup.get('query')}")
|
|
191
191
|
lines.append(f"- confidence: {lookup.get('confidence')}")
|
|
192
192
|
lines.append(f"- next_action: {lookup.get('next_action')}")
|
|
193
|
-
lines.append(f"-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if not isinstance(candidate, dict):
|
|
197
|
-
continue
|
|
198
|
-
title = candidate.get("title") or "-"
|
|
199
|
-
record_id = candidate.get("record_id") or "-"
|
|
200
|
-
score = candidate.get("score")
|
|
201
|
-
lines.append(f" - {record_id} | score={score} | {title}")
|
|
202
|
-
matches = candidate.get("matched_fields") if isinstance(candidate.get("matched_fields"), list) else []
|
|
203
|
-
if matches:
|
|
204
|
-
match = matches[0]
|
|
205
|
-
if isinstance(match, dict):
|
|
206
|
-
lines.append(f" match: {match.get('title')}={match.get('value')}")
|
|
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')}")
|
|
207
196
|
lines.append(f"Returned Records: {len(items)}")
|
|
208
197
|
for item in items[:10]:
|
|
209
198
|
if isinstance(item, dict):
|
|
@@ -256,6 +245,15 @@ def _format_record_get(result: dict[str, Any]) -> str:
|
|
|
256
245
|
media_items = media_assets.get("items") if isinstance(media_assets.get("items"), list) else []
|
|
257
246
|
downloaded_media = [item for item in media_items if isinstance(item, dict) and item.get("access_status") == "downloaded"]
|
|
258
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
|
+
]
|
|
259
257
|
associated_resources = result.get("associated_resources") if isinstance(result.get("associated_resources"), list) else []
|
|
260
258
|
unavailable_context = result.get("unavailable_context") if isinstance(result.get("unavailable_context"), list) else []
|
|
261
259
|
lines = [
|
|
@@ -267,17 +265,26 @@ def _format_record_get(result: dict[str, Any]) -> str:
|
|
|
267
265
|
f"Data logs: {data_logs.get('status') or '-'} / loaded={data_logs.get('items_loaded')}",
|
|
268
266
|
f"Workflow logs: {workflow_logs.get('status') or '-'} / loaded={workflow_logs.get('items_loaded')}",
|
|
269
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)}",
|
|
270
269
|
f"Associated resources: {len(associated_resources)}",
|
|
271
270
|
f"Unavailable contexts: {len(unavailable_context)}",
|
|
272
271
|
]
|
|
273
272
|
if media_assets.get("local_dir"):
|
|
274
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')}")
|
|
275
276
|
if failed_media:
|
|
276
277
|
failure_counts: dict[str, int] = {}
|
|
277
278
|
for item in failed_media:
|
|
278
279
|
key = f"{item.get('access_status') or 'unknown'} via {item.get('download_strategy') or 'unknown'}"
|
|
279
280
|
failure_counts[key] = failure_counts.get(key, 0) + 1
|
|
280
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())))
|
|
281
288
|
summary = result.get("summary") if isinstance(result.get("summary"), dict) else {}
|
|
282
289
|
if summary.get("text"):
|
|
283
290
|
lines.append(f"Summary: {summary.get('text')}")
|
|
@@ -285,6 +292,37 @@ def _format_record_get(result: dict[str, Any]) -> str:
|
|
|
285
292
|
return "\n".join(lines) + "\n"
|
|
286
293
|
|
|
287
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
|
+
|
|
288
326
|
def _format_task_list(result: dict[str, Any]) -> str:
|
|
289
327
|
data = result.get("data") if isinstance(result.get("data"), dict) else {}
|
|
290
328
|
items = data.get("items") if isinstance(data.get("items"), list) else []
|
|
@@ -781,11 +819,11 @@ _FORMATTERS = {
|
|
|
781
819
|
"workspace_get": _format_workspace_get,
|
|
782
820
|
"workspace_select": _format_workspace_select,
|
|
783
821
|
"app_list": _format_app_items,
|
|
784
|
-
"app_search": _format_app_items,
|
|
785
822
|
"app_get": _format_app_get,
|
|
786
823
|
"record_list": _format_record_list,
|
|
787
824
|
"record_access": _format_record_access,
|
|
788
825
|
"record_get": _format_record_get,
|
|
826
|
+
"record_logs": _format_record_logs,
|
|
789
827
|
"task_list": _format_task_list,
|
|
790
828
|
"task_workbench": _format_task_workbench,
|
|
791
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",),
|
|
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, "
|
|
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),
|