python-hwpx-automation 6.0.3__py3-none-any.whl
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.
- hwpx_automation/__init__.py +61 -0
- hwpx_automation/__init__.pyi +27 -0
- hwpx_automation/__main__.py +8 -0
- hwpx_automation/agent_document.py +392 -0
- hwpx_automation/api.py +136 -0
- hwpx_automation/blind_eval.py +407 -0
- hwpx_automation/capabilities.py +110 -0
- hwpx_automation/compat.py +48 -0
- hwpx_automation/configuration.py +60 -0
- hwpx_automation/core/__init__.py +2 -0
- hwpx_automation/core/content.py +762 -0
- hwpx_automation/core/context.py +111 -0
- hwpx_automation/core/diff.py +53 -0
- hwpx_automation/core/document.py +37 -0
- hwpx_automation/core/formatting.py +513 -0
- hwpx_automation/core/handles.py +24 -0
- hwpx_automation/core/locations.py +205 -0
- hwpx_automation/core/locator.py +162 -0
- hwpx_automation/core/plan.py +680 -0
- hwpx_automation/core/resources.py +42 -0
- hwpx_automation/core/search.py +296 -0
- hwpx_automation/core/transactions.py +434 -0
- hwpx_automation/core/txn.py +48 -0
- hwpx_automation/document_state.py +95 -0
- hwpx_automation/errors.py +174 -0
- hwpx_automation/execution_lock.py +15 -0
- hwpx_automation/fastmcp_adapter.py +672 -0
- hwpx_automation/form_fill.py +1177 -0
- hwpx_automation/form_output_models.py +223 -0
- hwpx_automation/handlers/__init__.py +2 -0
- hwpx_automation/handlers/_shared.py +377 -0
- hwpx_automation/handlers/agent_document.py +257 -0
- hwpx_automation/handlers/authoring.py +750 -0
- hwpx_automation/handlers/content_edit.py +1078 -0
- hwpx_automation/handlers/form_fill.py +607 -0
- hwpx_automation/handlers/layout_style.py +660 -0
- hwpx_automation/handlers/quality_render.py +566 -0
- hwpx_automation/handlers/read_export.py +1295 -0
- hwpx_automation/handlers/specialized.py +624 -0
- hwpx_automation/handlers/tracked_changes.py +589 -0
- hwpx_automation/handlers/workflow.py +105 -0
- hwpx_automation/hwp_converter.py +227 -0
- hwpx_automation/hwp_support.py +94 -0
- hwpx_automation/hwpx_ops.py +1439 -0
- hwpx_automation/identity.json +263 -0
- hwpx_automation/identity.py +18 -0
- hwpx_automation/ingest_adapters.py +85 -0
- hwpx_automation/markdown_plan.py +216 -0
- hwpx_automation/mcp_cli.py +29 -0
- hwpx_automation/metadata/tools_meta.py +40 -0
- hwpx_automation/mixed_form.py +3007 -0
- hwpx_automation/mutation_models.py +401 -0
- hwpx_automation/network_policy.py +232 -0
- hwpx_automation/office/__init__.py +14 -0
- hwpx_automation/office/agent/__init__.py +125 -0
- hwpx_automation/office/agent/_batch_verification.py +383 -0
- hwpx_automation/office/agent/blueprint/__init__.py +58 -0
- hwpx_automation/office/agent/blueprint/bundle.py +282 -0
- hwpx_automation/office/agent/blueprint/catalog.py +136 -0
- hwpx_automation/office/agent/blueprint/dump.py +520 -0
- hwpx_automation/office/agent/blueprint/mapping.py +312 -0
- hwpx_automation/office/agent/blueprint/model.py +722 -0
- hwpx_automation/office/agent/blueprint/native.py +621 -0
- hwpx_automation/office/agent/blueprint/replay.py +622 -0
- hwpx_automation/office/agent/catalog.py +252 -0
- hwpx_automation/office/agent/cli.py +647 -0
- hwpx_automation/office/agent/commands.py +1383 -0
- hwpx_automation/office/agent/document.py +801 -0
- hwpx_automation/office/agent/form_plan.py +1760 -0
- hwpx_automation/office/agent/model.py +808 -0
- hwpx_automation/office/agent/path.py +155 -0
- hwpx_automation/office/agent/query.py +230 -0
- hwpx_automation/office/agent/story.py +207 -0
- hwpx_automation/office/authoring/__init__.py +3542 -0
- hwpx_automation/office/authoring/advanced_generators.py +154 -0
- hwpx_automation/office/authoring/builder/__init__.py +52 -0
- hwpx_automation/office/authoring/builder/core.py +996 -0
- hwpx_automation/office/authoring/builder/report.py +195 -0
- hwpx_automation/office/authoring/design/__init__.py +30 -0
- hwpx_automation/office/authoring/design/_support.py +144 -0
- hwpx_automation/office/authoring/design/composer.py +282 -0
- hwpx_automation/office/authoring/design/harvest.py +305 -0
- hwpx_automation/office/authoring/design/plan.py +69 -0
- hwpx_automation/office/authoring/design/profile.py +88 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +24 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/validator.py +107 -0
- hwpx_automation/office/authoring/presets/__init__.py +22 -0
- hwpx_automation/office/authoring/presets/proposal.py +538 -0
- hwpx_automation/office/authoring/report_parser.py +141 -0
- hwpx_automation/office/authoring/style_profile.py +437 -0
- hwpx_automation/office/authoring/template_analyzer.py +657 -0
- hwpx_automation/office/compliance/__init__.py +38 -0
- hwpx_automation/office/compliance/official_lint.py +478 -0
- hwpx_automation/office/compliance/pii.py +388 -0
- hwpx_automation/office/document_ops/__init__.py +13 -0
- hwpx_automation/office/document_ops/comparison.py +62 -0
- hwpx_automation/office/document_ops/mail_merge.py +73 -0
- hwpx_automation/office/document_ops/redline.py +35 -0
- hwpx_automation/office/evalplan/__init__.py +36 -0
- hwpx_automation/office/evalplan/runtime.py +2762 -0
- hwpx_automation/office/exam/__init__.py +44 -0
- hwpx_automation/office/exam/compose.py +282 -0
- hwpx_automation/office/exam/ir.py +44 -0
- hwpx_automation/office/exam/measure.py +163 -0
- hwpx_automation/office/exam/parser.py +150 -0
- hwpx_automation/office/exam/profile.py +123 -0
- hwpx_automation/office/form_fill/__init__.py +66 -0
- hwpx_automation/office/form_fill/classification.py +108 -0
- hwpx_automation/office/form_fill/fill_residue.py +242 -0
- hwpx_automation/office/form_fill/fit/__init__.py +36 -0
- hwpx_automation/office/form_fill/fit/apply.py +24 -0
- hwpx_automation/office/form_fill/fit/engine.py +24 -0
- hwpx_automation/office/form_fill/fit/measure.py +50 -0
- hwpx_automation/office/form_fill/fit/policy.py +28 -0
- hwpx_automation/office/form_fill/fit/report.py +28 -0
- hwpx_automation/office/form_fill/fit/seal.py +457 -0
- hwpx_automation/office/form_fill/fit/wordbox.py +1343 -0
- hwpx_automation/office/form_fill/guidance.py +704 -0
- hwpx_automation/office/form_fill/quality.py +961 -0
- hwpx_automation/office/form_fill/split_run.py +333 -0
- hwpx_automation/office/form_fill/template_formfit.py +656 -0
- hwpx_automation/office/house_style/__init__.py +196 -0
- hwpx_automation/office/house_style/composition.py +68 -0
- hwpx_automation/office/house_style/data/bank.json +625 -0
- hwpx_automation/office/house_style/data/genres.json +43 -0
- hwpx_automation/office/quality/__init__.py +14 -0
- hwpx_automation/office/quality/page_guard.py +277 -0
- hwpx_automation/office/rendering/__init__.py +145 -0
- hwpx_automation/office/rendering/_hancom_open_rate.ps1 +374 -0
- hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +162 -0
- hwpx_automation/office/rendering/_render_hwpx.ps1 +72 -0
- hwpx_automation/office/rendering/_render_hwpx_mac.applescript +249 -0
- hwpx_automation/office/rendering/block_splits.py +76 -0
- hwpx_automation/office/rendering/detectors.py +151 -0
- hwpx_automation/office/rendering/diff.py +153 -0
- hwpx_automation/office/rendering/fixture_corpus.py +215 -0
- hwpx_automation/office/rendering/oracle.py +909 -0
- hwpx_automation/office/rendering/page_qa.py +245 -0
- hwpx_automation/office/rendering/qa_contracts.py +293 -0
- hwpx_automation/office/rendering/qa_metrics.py +241 -0
- hwpx_automation/office/rendering/worker.py +290 -0
- hwpx_automation/office/utilities/__init__.py +12 -0
- hwpx_automation/office/utilities/table_compute.py +477 -0
- hwpx_automation/ops_services/__init__.py +1 -0
- hwpx_automation/ops_services/_border_fill.py +283 -0
- hwpx_automation/ops_services/composition.py +55 -0
- hwpx_automation/ops_services/content_layout.py +322 -0
- hwpx_automation/ops_services/context.py +213 -0
- hwpx_automation/ops_services/form_fields.py +557 -0
- hwpx_automation/ops_services/media.py +178 -0
- hwpx_automation/ops_services/memo_style.py +477 -0
- hwpx_automation/ops_services/package_validation.py +166 -0
- hwpx_automation/ops_services/planning.py +201 -0
- hwpx_automation/ops_services/preview_export.py +585 -0
- hwpx_automation/ops_services/read_query.py +601 -0
- hwpx_automation/ops_services/save_policy.py +604 -0
- hwpx_automation/ops_services/tables.py +539 -0
- hwpx_automation/ops_services/transactions.py +616 -0
- hwpx_automation/preview_output_models.py +69 -0
- hwpx_automation/public-modules.json +206 -0
- hwpx_automation/py.typed +1 -0
- hwpx_automation/quality.py +351 -0
- hwpx_automation/quality_generation.py +725 -0
- hwpx_automation/runtime.py +321 -0
- hwpx_automation/runtime_services.py +100 -0
- hwpx_automation/server.py +259 -0
- hwpx_automation/storage.py +747 -0
- hwpx_automation/tool_bindings.py +170 -0
- hwpx_automation/tool_contract.py +982 -0
- hwpx_automation/upstream.py +755 -0
- hwpx_automation/utils/__init__.py +2 -0
- hwpx_automation/utils/helpers.py +29 -0
- hwpx_automation/visual_qa.py +667 -0
- hwpx_automation/workflow/__init__.py +55 -0
- hwpx_automation/workflow/adapters.py +482 -0
- hwpx_automation/workflow/dispatcher.py +213 -0
- hwpx_automation/workflow/models.py +243 -0
- hwpx_automation/workflow/policy.py +198 -0
- hwpx_automation/workflow/render_contracts.py +173 -0
- hwpx_automation/workflow/render_metrics.py +196 -0
- hwpx_automation/workflow/render_queue.py +482 -0
- hwpx_automation/workflow/render_security.py +172 -0
- hwpx_automation/workflow/render_transport.py +369 -0
- hwpx_automation/workflow/rendering.py +206 -0
- hwpx_automation/workflow/service.py +758 -0
- hwpx_automation/workflow/state_machine.py +65 -0
- hwpx_automation/workflow/store.py +747 -0
- hwpx_automation/workspace.py +1694 -0
- python_hwpx_automation-6.0.3.dist-info/METADATA +279 -0
- python_hwpx_automation-6.0.3.dist-info/RECORD +217 -0
- python_hwpx_automation-6.0.3.dist-info/WHEEL +5 -0
- python_hwpx_automation-6.0.3.dist-info/entry_points.txt +3 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/LICENSE +178 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/NOTICE +14 -0
- python_hwpx_automation-6.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Unified ``hwpx`` command line for the semantic agent document interface."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import contextlib
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from collections.abc import Mapping, Sequence
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, TextIO, cast
|
|
14
|
+
|
|
15
|
+
from .catalog import agent_catalog, human_help
|
|
16
|
+
from .blueprint import (
|
|
17
|
+
blueprint_catalog,
|
|
18
|
+
blueprint_human_help,
|
|
19
|
+
dump_document_blueprint,
|
|
20
|
+
read_blueprint_bundle,
|
|
21
|
+
repack_blueprint_bundle,
|
|
22
|
+
replay_document_blueprint,
|
|
23
|
+
)
|
|
24
|
+
from .commands import apply_document_commands
|
|
25
|
+
from .document import HwpxAgentDocument
|
|
26
|
+
from .model import (
|
|
27
|
+
AGENT_BATCH_SCHEMA,
|
|
28
|
+
AgentBatchResult,
|
|
29
|
+
AgentContractError,
|
|
30
|
+
AgentError,
|
|
31
|
+
VERIFICATION_REQUIREMENTS,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
EXIT_OK = 0
|
|
35
|
+
EXIT_UNEXPECTED = 1
|
|
36
|
+
EXIT_USAGE = 2
|
|
37
|
+
EXIT_TARGET = 3
|
|
38
|
+
EXIT_CONFLICT = 4
|
|
39
|
+
EXIT_VERIFICATION = 5
|
|
40
|
+
|
|
41
|
+
_DEFAULT_REQUIREMENTS = ("package", "reopen", "openSafety", "semanticDiff", "bytePreservation")
|
|
42
|
+
_TARGET_CODES = frozenset(
|
|
43
|
+
{"not_found", "ambiguous_target", "volatile_target", "incompatible_parent", "unsupported_content"}
|
|
44
|
+
)
|
|
45
|
+
_CONFLICT_CODES = frozenset({"stale_revision", "identity_collision", "idempotency_conflict"})
|
|
46
|
+
_USAGE_CODES = frozenset(
|
|
47
|
+
{"invalid_syntax", "unknown_kind", "unknown_property", "unsupported_operation", "resource_limit"}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CliUsageError(ValueError):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _HelpFormatter(argparse.HelpFormatter):
|
|
56
|
+
"""Keep public CLI help byte-stable across supported Python minors."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, prog: str) -> None:
|
|
59
|
+
super().__init__(prog, width=80)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class _Parser(argparse.ArgumentParser):
|
|
63
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
64
|
+
kwargs.setdefault("formatter_class", _HelpFormatter)
|
|
65
|
+
super().__init__(*args, **kwargs)
|
|
66
|
+
|
|
67
|
+
def error(self, message: str) -> None: # type: ignore[override]
|
|
68
|
+
# Python 3.10 quotes every choice while later argparse versions do not.
|
|
69
|
+
# Normalize only the parenthesized choice list; keep the invalid value's
|
|
70
|
+
# own quotes intact.
|
|
71
|
+
message = re.sub(r"'([^']*)'(?=, |\))", r"\1", message)
|
|
72
|
+
raise CliUsageError(message)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _json_dump(value: Any, stream: TextIO, *, line: bool = False) -> None:
|
|
76
|
+
json.dump(value, stream, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
77
|
+
stream.write("\n" if line else "\n")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _error_payload(error: AgentError) -> dict[str, Any]:
|
|
81
|
+
return {"ok": False, "error": error.to_dict()}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _usage_error(message: str) -> AgentError:
|
|
85
|
+
return AgentError(
|
|
86
|
+
code="invalid_syntax",
|
|
87
|
+
message=message,
|
|
88
|
+
target="cli",
|
|
89
|
+
recoverability="terminal",
|
|
90
|
+
suggestion="Run 'hwpx help' or 'hwpx <command> --help'.",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _unexpected_error(exc: BaseException) -> AgentError:
|
|
95
|
+
return AgentError(
|
|
96
|
+
code="verification_failed",
|
|
97
|
+
message=f"{type(exc).__name__}: {exc}"[:4096],
|
|
98
|
+
target="cli",
|
|
99
|
+
recoverability="terminal",
|
|
100
|
+
suggestion="Inspect the input and retry; no success should be assumed.",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _exit_code(error: AgentError | None) -> int:
|
|
105
|
+
if error is None:
|
|
106
|
+
return EXIT_OK
|
|
107
|
+
if error.code in _USAGE_CODES:
|
|
108
|
+
return EXIT_USAGE
|
|
109
|
+
if error.code in _TARGET_CODES:
|
|
110
|
+
return EXIT_TARGET
|
|
111
|
+
if error.code in _CONFLICT_CODES:
|
|
112
|
+
return EXIT_CONFLICT
|
|
113
|
+
return EXIT_VERIFICATION
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _read_text(source: str, stdin: TextIO) -> str:
|
|
117
|
+
return stdin.read() if source == "-" else Path(source).read_text(encoding="utf-8")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _read_binary(source: str, stdin: TextIO) -> bytes:
|
|
121
|
+
if source != "-":
|
|
122
|
+
return Path(source).read_bytes()
|
|
123
|
+
stream = getattr(stdin, "buffer", stdin)
|
|
124
|
+
data = stream.read()
|
|
125
|
+
if not isinstance(data, bytes):
|
|
126
|
+
raise CliUsageError("binary bundle stdin requires a binary-capable stream")
|
|
127
|
+
return data
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _write_binary(data: bytes, stdout: TextIO) -> None:
|
|
131
|
+
stream = getattr(stdout, "buffer", stdout)
|
|
132
|
+
try:
|
|
133
|
+
written = cast(Any, stream).write(data)
|
|
134
|
+
except TypeError as exc:
|
|
135
|
+
raise CliUsageError("binary bundle stdout requires a binary-capable stream") from exc
|
|
136
|
+
if written is not None and written != len(data):
|
|
137
|
+
raise OSError("short write while emitting blueprint bundle")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _json_value(value: str, *, name: str, stdin: TextIO) -> Any:
|
|
141
|
+
raw = _read_text(value[1:], stdin) if value.startswith("@") else value
|
|
142
|
+
try:
|
|
143
|
+
return json.loads(raw)
|
|
144
|
+
except json.JSONDecodeError as exc:
|
|
145
|
+
raise CliUsageError(f"{name} is not valid JSON: {exc.msg}") from exc
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _quality_value(value: str, stdin: TextIO) -> Any:
|
|
149
|
+
if value in {"transparent", "strict"}:
|
|
150
|
+
return value
|
|
151
|
+
return _json_value(value, name="quality", stdin=stdin)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _position_value(value: str | None, stdin: TextIO) -> dict[str, Any]:
|
|
155
|
+
if value is None:
|
|
156
|
+
return {"mode": "append"}
|
|
157
|
+
parsed = _json_value(value, name="position", stdin=stdin)
|
|
158
|
+
if not isinstance(parsed, Mapping):
|
|
159
|
+
raise CliUsageError("position must be a JSON object")
|
|
160
|
+
return dict(parsed)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _properties_value(value: str, stdin: TextIO) -> dict[str, Any]:
|
|
164
|
+
parsed = _json_value(value, name="properties", stdin=stdin)
|
|
165
|
+
if not isinstance(parsed, Mapping):
|
|
166
|
+
raise CliUsageError("properties must be a JSON object")
|
|
167
|
+
return dict(parsed)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _add_output_options(parser: argparse.ArgumentParser) -> None:
|
|
171
|
+
parser.add_argument(
|
|
172
|
+
"--format",
|
|
173
|
+
choices=("json", "jsonl", "human"),
|
|
174
|
+
default="json",
|
|
175
|
+
dest="output_format",
|
|
176
|
+
help="response format (default: json)",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _add_mutation_options(parser: argparse.ArgumentParser) -> None:
|
|
181
|
+
parser.add_argument("--expected-revision")
|
|
182
|
+
parser.add_argument("--idempotency-key")
|
|
183
|
+
parser.add_argument("--dry-run", action="store_true")
|
|
184
|
+
parser.add_argument("--overwrite", action="store_true")
|
|
185
|
+
parser.add_argument("--quality", default="transparent")
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"--verify",
|
|
188
|
+
action="append",
|
|
189
|
+
choices=VERIFICATION_REQUIREMENTS,
|
|
190
|
+
dest="verification_requirements",
|
|
191
|
+
)
|
|
192
|
+
_add_output_options(parser)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
196
|
+
parser = _Parser(prog="hwpx", description="Semantic HWPX view/query/atomic-edit interface")
|
|
197
|
+
parser.add_argument("--version", action="version", version="python-hwpx agent interface v1")
|
|
198
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
199
|
+
|
|
200
|
+
help_parser = subparsers.add_parser("help", help="catalog-generated interface help")
|
|
201
|
+
help_parser.add_argument("kind", nargs="?")
|
|
202
|
+
help_parser.add_argument("--json", action="store_true", dest="json_help")
|
|
203
|
+
|
|
204
|
+
for name in ("view", "get"):
|
|
205
|
+
read_parser = subparsers.add_parser(name, help=f"{name} a bounded semantic node")
|
|
206
|
+
read_parser.add_argument("filename")
|
|
207
|
+
if name == "get":
|
|
208
|
+
read_parser.add_argument("path")
|
|
209
|
+
read_parser.add_argument("--depth", type=int, default=2 if name == "view" else 0)
|
|
210
|
+
read_parser.add_argument("--child-limit", type=int, default=50)
|
|
211
|
+
read_parser.add_argument("--expected-revision")
|
|
212
|
+
_add_output_options(read_parser)
|
|
213
|
+
|
|
214
|
+
query_parser = subparsers.add_parser("query", help="run a bounded selector v1 query")
|
|
215
|
+
query_parser.add_argument("filename")
|
|
216
|
+
query_parser.add_argument("selector")
|
|
217
|
+
query_parser.add_argument("--limit", type=int, default=20)
|
|
218
|
+
query_parser.add_argument("--depth", type=int, default=0)
|
|
219
|
+
query_parser.add_argument("--child-limit", type=int, default=20)
|
|
220
|
+
query_parser.add_argument("--expected-revision")
|
|
221
|
+
_add_output_options(query_parser)
|
|
222
|
+
|
|
223
|
+
single_specs = {
|
|
224
|
+
"set": ("path",),
|
|
225
|
+
"add": ("parent", "kind"),
|
|
226
|
+
"remove": ("path",),
|
|
227
|
+
"move": ("path", "parent"),
|
|
228
|
+
"copy": ("path", "parent"),
|
|
229
|
+
}
|
|
230
|
+
for name, fields in single_specs.items():
|
|
231
|
+
command_parser = subparsers.add_parser(name, help=f"apply one atomic {name} command")
|
|
232
|
+
command_parser.add_argument("input")
|
|
233
|
+
command_parser.add_argument("output")
|
|
234
|
+
for field in fields:
|
|
235
|
+
command_parser.add_argument(field)
|
|
236
|
+
if name in {"set", "add"}:
|
|
237
|
+
command_parser.add_argument("--properties", required=True)
|
|
238
|
+
if name in {"add", "move", "copy"}:
|
|
239
|
+
command_parser.add_argument("--position")
|
|
240
|
+
_add_mutation_options(command_parser)
|
|
241
|
+
|
|
242
|
+
batch_parser = subparsers.add_parser("batch", help="apply JSON or JSONL command batches")
|
|
243
|
+
batch_parser.add_argument("request", help="JSON/JSONL path, or '-' for stdin")
|
|
244
|
+
batch_parser.add_argument("--jsonl-input", action="store_true")
|
|
245
|
+
batch_parser.add_argument("--input", help="envelope input when request contains command(s)")
|
|
246
|
+
batch_parser.add_argument("--output", help="envelope output when request contains command(s)")
|
|
247
|
+
_add_mutation_options(batch_parser)
|
|
248
|
+
|
|
249
|
+
dump_parser = subparsers.add_parser(
|
|
250
|
+
"dump",
|
|
251
|
+
help="dump, inspect, or safely repack a typed semantic blueprint",
|
|
252
|
+
description=blueprint_human_help(),
|
|
253
|
+
)
|
|
254
|
+
dump_parser.add_argument("source", nargs="?", help="source HWPX for dump")
|
|
255
|
+
dump_parser.add_argument("--path", default="/", help="revision-bound canonical source path")
|
|
256
|
+
dump_parser.add_argument("--mode", choices=("portable", "source-bound"), default="portable")
|
|
257
|
+
dump_parser.add_argument("--output", "-o", help=".hwpxbp path, or '-' for raw bundle stdout")
|
|
258
|
+
dump_parser.add_argument("--expected-revision")
|
|
259
|
+
dump_parser.add_argument("--overwrite", action="store_true")
|
|
260
|
+
dump_parser.add_argument("--no-assets", action="store_false", dest="include_assets", default=True)
|
|
261
|
+
dump_parser.add_argument(
|
|
262
|
+
"--inspection",
|
|
263
|
+
action="store_false",
|
|
264
|
+
dest="require_replayable",
|
|
265
|
+
default=True,
|
|
266
|
+
help="allow an inspection-only bundle with explicit unsupported inventory",
|
|
267
|
+
)
|
|
268
|
+
dump_parser.add_argument("--inspect", metavar="BUNDLE", help="validate and print one bundle manifest; '-' reads binary stdin")
|
|
269
|
+
dump_parser.add_argument("--repack", metavar="BUNDLE", help="validated source bundle for safe typed JSON repack")
|
|
270
|
+
dump_parser.add_argument("--manifest", help="edited manifest JSON path, or '-' for text stdin")
|
|
271
|
+
_add_output_options(dump_parser)
|
|
272
|
+
|
|
273
|
+
replay_parser = subparsers.add_parser(
|
|
274
|
+
"replay",
|
|
275
|
+
help="atomically replay a validated blueprint request",
|
|
276
|
+
description=blueprint_human_help(),
|
|
277
|
+
)
|
|
278
|
+
replay_parser.add_argument("request", help="replay request JSON path, or '-' for stdin")
|
|
279
|
+
_add_output_options(replay_parser)
|
|
280
|
+
return parser
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _node_human(node: Mapping[str, Any], *, indent: int = 0) -> str:
|
|
284
|
+
prefix = " " * indent
|
|
285
|
+
summary = node.get("summary") or {}
|
|
286
|
+
label = summary.get("text") or summary.get("name") or summary.get("caption") or ""
|
|
287
|
+
if isinstance(label, str) and len(label) > 80:
|
|
288
|
+
label = label[:79] + "…"
|
|
289
|
+
lines = [
|
|
290
|
+
f"{prefix}{node.get('kind')} {node.get('path')}"
|
|
291
|
+
+ (f" — {label}" if label else "")
|
|
292
|
+
+ (" [volatile]" if node.get("volatilePath") else "")
|
|
293
|
+
]
|
|
294
|
+
for child in node.get("children") or ():
|
|
295
|
+
lines.append(_node_human(child, indent=indent + 1))
|
|
296
|
+
coverage = node.get("coverage") or {}
|
|
297
|
+
if coverage.get("unsupportedChildren") or coverage.get("truncatedChildren"):
|
|
298
|
+
lines.append(
|
|
299
|
+
f"{prefix} coverage: unsupported={coverage.get('unsupportedChildren', 0)} "
|
|
300
|
+
f"truncated={coverage.get('truncatedChildren', 0)}"
|
|
301
|
+
)
|
|
302
|
+
return "\n".join(lines)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _query_human(payload: Mapping[str, Any]) -> str:
|
|
306
|
+
lines = [
|
|
307
|
+
f"selector: {payload.get('selector')}",
|
|
308
|
+
f"matches: {len(payload.get('nodes') or ())}"
|
|
309
|
+
+ (" (truncated)" if payload.get("truncated") else ""),
|
|
310
|
+
]
|
|
311
|
+
lines.extend(_node_human(node) for node in payload.get("nodes") or ())
|
|
312
|
+
return "\n".join(lines)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _result_human(result: AgentBatchResult) -> str:
|
|
316
|
+
if not result.ok:
|
|
317
|
+
assert result.error is not None
|
|
318
|
+
return f"FAILED [{result.error.code}] {result.error.message}"
|
|
319
|
+
state = "DRY-RUN" if result.dry_run else "COMMITTED"
|
|
320
|
+
return (
|
|
321
|
+
f"{state}: {len(result.command_results)} command(s)\n"
|
|
322
|
+
f"inputRevision: {result.input_revision}\n"
|
|
323
|
+
f"documentRevision: {result.document_revision}\n"
|
|
324
|
+
f"output: {result.output_filename}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _blueprint_human(value: Mapping[str, Any]) -> str:
|
|
329
|
+
if not value.get("ok"):
|
|
330
|
+
error = value.get("error") or {}
|
|
331
|
+
return f"FAILED [{error.get('code', 'verification_failed')}] {error.get('message', '')}"
|
|
332
|
+
if value.get("schemaVersion") == "hwpx.agent-blueprint-replay-result/v1":
|
|
333
|
+
state = "DRY-RUN" if value.get("dryRun") else "COMMITTED"
|
|
334
|
+
return (
|
|
335
|
+
f"{state}: {value.get('rootPath') or '-'}\n"
|
|
336
|
+
f"blueprintHash: {value.get('blueprintHash')}\n"
|
|
337
|
+
f"documentRevision: {value.get('documentRevision')}\n"
|
|
338
|
+
f"output: {value.get('outputFilename')}"
|
|
339
|
+
)
|
|
340
|
+
return (
|
|
341
|
+
f"BLUEPRINT: {value.get('blueprintHash')}\n"
|
|
342
|
+
f"mode: {value.get('mode')}\n"
|
|
343
|
+
f"nodes: {value.get('nodeCount')}\n"
|
|
344
|
+
f"dependencies: {value.get('dependencyCount')}\n"
|
|
345
|
+
f"output: {value.get('outputFilename')}"
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _emit(value: Mapping[str, Any], output_format: str, stdout: TextIO) -> None:
|
|
350
|
+
if output_format in {"json", "jsonl"}:
|
|
351
|
+
_json_dump(value, stdout, line=output_format == "jsonl")
|
|
352
|
+
elif "schemaVersion" in value and "kind" in value:
|
|
353
|
+
stdout.write(_node_human(value) + "\n")
|
|
354
|
+
elif "nodes" in value:
|
|
355
|
+
stdout.write(_query_human(value) + "\n")
|
|
356
|
+
else:
|
|
357
|
+
stdout.write(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _envelope(
|
|
361
|
+
args: argparse.Namespace,
|
|
362
|
+
commands: Sequence[Mapping[str, Any]],
|
|
363
|
+
*,
|
|
364
|
+
input_filename: str | None = None,
|
|
365
|
+
output_filename: str | None = None,
|
|
366
|
+
stdin: TextIO,
|
|
367
|
+
) -> dict[str, Any]:
|
|
368
|
+
requirements = args.verification_requirements or list(_DEFAULT_REQUIREMENTS)
|
|
369
|
+
return {
|
|
370
|
+
"schemaVersion": AGENT_BATCH_SCHEMA,
|
|
371
|
+
"input": {"filename": input_filename or args.input},
|
|
372
|
+
"output": {
|
|
373
|
+
"filename": output_filename or args.output,
|
|
374
|
+
"overwrite": bool(args.overwrite),
|
|
375
|
+
},
|
|
376
|
+
"commands": [dict(command) for command in commands],
|
|
377
|
+
"expectedRevision": args.expected_revision,
|
|
378
|
+
"idempotencyKey": args.idempotency_key,
|
|
379
|
+
"dryRun": bool(args.dry_run),
|
|
380
|
+
"quality": _quality_value(args.quality, stdin),
|
|
381
|
+
"verificationRequirements": list(requirements),
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _single_command(args: argparse.Namespace, stdin: TextIO) -> dict[str, Any]:
|
|
386
|
+
command: dict[str, Any] = {"commandId": "command", "op": args.command}
|
|
387
|
+
if hasattr(args, "path"):
|
|
388
|
+
command["path"] = args.path
|
|
389
|
+
if hasattr(args, "parent"):
|
|
390
|
+
command["parent"] = args.parent
|
|
391
|
+
if hasattr(args, "kind"):
|
|
392
|
+
command["kind"] = args.kind
|
|
393
|
+
if hasattr(args, "properties"):
|
|
394
|
+
command["properties"] = _properties_value(args.properties, stdin)
|
|
395
|
+
if hasattr(args, "position"):
|
|
396
|
+
command["position"] = _position_value(args.position, stdin)
|
|
397
|
+
return command
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _load_batch_requests(args: argparse.Namespace, stdin: TextIO) -> list[Mapping[str, Any]]:
|
|
401
|
+
text = _read_text(args.request, stdin)
|
|
402
|
+
if args.jsonl_input:
|
|
403
|
+
values: list[Any] = []
|
|
404
|
+
for line_number, line in enumerate(text.splitlines(), start=1):
|
|
405
|
+
if not line.strip():
|
|
406
|
+
continue
|
|
407
|
+
try:
|
|
408
|
+
values.append(json.loads(line))
|
|
409
|
+
except json.JSONDecodeError as exc:
|
|
410
|
+
raise CliUsageError(f"JSONL line {line_number} is invalid: {exc.msg}") from exc
|
|
411
|
+
else:
|
|
412
|
+
try:
|
|
413
|
+
parsed = json.loads(text)
|
|
414
|
+
except json.JSONDecodeError as exc:
|
|
415
|
+
raise CliUsageError(f"batch request is invalid JSON: {exc.msg}") from exc
|
|
416
|
+
values = parsed if isinstance(parsed, list) else [parsed]
|
|
417
|
+
if not values:
|
|
418
|
+
raise CliUsageError("batch request is empty")
|
|
419
|
+
if all(isinstance(value, Mapping) and "commandId" in value and "op" in value for value in values):
|
|
420
|
+
if not args.input or not args.output:
|
|
421
|
+
raise CliUsageError("command-only JSON/JSONL requires --input and --output")
|
|
422
|
+
return [
|
|
423
|
+
_envelope(
|
|
424
|
+
args,
|
|
425
|
+
[dict(value) for value in values],
|
|
426
|
+
input_filename=args.input,
|
|
427
|
+
output_filename=args.output,
|
|
428
|
+
stdin=stdin,
|
|
429
|
+
)
|
|
430
|
+
]
|
|
431
|
+
if not all(isinstance(value, Mapping) for value in values):
|
|
432
|
+
raise CliUsageError("each batch request must be a JSON object")
|
|
433
|
+
return [dict(value) for value in values]
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _run_read(args: argparse.Namespace) -> Mapping[str, Any]: # type: ignore[return]
|
|
437
|
+
with HwpxAgentDocument.open(args.filename) as agent:
|
|
438
|
+
path = "/" if args.command == "view" else args.path
|
|
439
|
+
node = agent.get(
|
|
440
|
+
path,
|
|
441
|
+
depth=args.depth,
|
|
442
|
+
child_limit=args.child_limit,
|
|
443
|
+
expected_revision=args.expected_revision,
|
|
444
|
+
)
|
|
445
|
+
return node.to_dict()
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _run_query(args: argparse.Namespace) -> Mapping[str, Any]: # type: ignore[return]
|
|
449
|
+
with HwpxAgentDocument.open(args.filename) as agent:
|
|
450
|
+
result = agent.query(
|
|
451
|
+
args.selector,
|
|
452
|
+
limit=args.limit,
|
|
453
|
+
node_depth=args.depth,
|
|
454
|
+
child_limit=args.child_limit,
|
|
455
|
+
expected_revision=args.expected_revision,
|
|
456
|
+
)
|
|
457
|
+
return result.to_dict()
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _bundle_payload(bundle: Any, *, output_filename: str | None = None) -> dict[str, Any]:
|
|
461
|
+
manifest = dict(bundle.manifest)
|
|
462
|
+
return {
|
|
463
|
+
"ok": True,
|
|
464
|
+
"schemaVersion": manifest["schemaVersion"],
|
|
465
|
+
"blueprintHash": manifest["blueprintHash"],
|
|
466
|
+
"bundleSha256": bundle.bundle_sha256,
|
|
467
|
+
"bundleBytes": bundle.size,
|
|
468
|
+
"outputFilename": output_filename,
|
|
469
|
+
"mode": manifest["mode"],
|
|
470
|
+
"root": dict(manifest["root"]),
|
|
471
|
+
"nodeCount": len(manifest["nodes"]),
|
|
472
|
+
"dependencyCount": len(manifest["styles"]) + len(manifest["numbering"]) + len(manifest["resources"]),
|
|
473
|
+
"assetCount": len(bundle.assets),
|
|
474
|
+
"unsupported": list(manifest["unsupported"]),
|
|
475
|
+
"fidelity": dict(manifest["fidelity"]),
|
|
476
|
+
"manifest": manifest,
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _run_dump(args: argparse.Namespace, *, stdin: TextIO, stdout: TextIO) -> int:
|
|
481
|
+
modes = sum(value is not None for value in (args.source, args.inspect, args.repack))
|
|
482
|
+
if modes != 1:
|
|
483
|
+
raise CliUsageError("dump requires exactly one source, --inspect, or --repack mode")
|
|
484
|
+
if args.inspect is not None:
|
|
485
|
+
if args.output or args.manifest:
|
|
486
|
+
raise CliUsageError("--inspect does not accept --output or --manifest")
|
|
487
|
+
payload = _bundle_payload(read_blueprint_bundle(_read_binary(args.inspect, stdin)))
|
|
488
|
+
elif args.repack is not None:
|
|
489
|
+
if not args.manifest or not args.output or args.output == "-":
|
|
490
|
+
raise CliUsageError("--repack requires --manifest and a file --output")
|
|
491
|
+
manifest = _json_value(f"@{args.manifest}" if args.manifest != "-" else "@-", name="manifest", stdin=stdin)
|
|
492
|
+
if not isinstance(manifest, Mapping):
|
|
493
|
+
raise CliUsageError("manifest must be a JSON object")
|
|
494
|
+
bundle = repack_blueprint_bundle(
|
|
495
|
+
args.repack,
|
|
496
|
+
args.output,
|
|
497
|
+
dict(manifest),
|
|
498
|
+
overwrite=args.overwrite,
|
|
499
|
+
)
|
|
500
|
+
payload = _bundle_payload(bundle, output_filename=str(args.output))
|
|
501
|
+
else:
|
|
502
|
+
if args.manifest:
|
|
503
|
+
raise CliUsageError("--manifest is valid only with --repack")
|
|
504
|
+
output = None if args.output in {None, "-"} else args.output
|
|
505
|
+
result = dump_document_blueprint(
|
|
506
|
+
args.source,
|
|
507
|
+
path=args.path,
|
|
508
|
+
mode=args.mode,
|
|
509
|
+
expected_revision=args.expected_revision,
|
|
510
|
+
output=output,
|
|
511
|
+
overwrite=args.overwrite,
|
|
512
|
+
include_assets=args.include_assets,
|
|
513
|
+
require_replayable=args.require_replayable,
|
|
514
|
+
)
|
|
515
|
+
if args.output == "-":
|
|
516
|
+
_write_binary(result.bundle_bytes, stdout)
|
|
517
|
+
return EXIT_OK
|
|
518
|
+
payload = result.to_dict()
|
|
519
|
+
if args.output_format == "human":
|
|
520
|
+
stdout.write(_blueprint_human(payload) + "\n")
|
|
521
|
+
else:
|
|
522
|
+
_json_dump(payload, stdout, line=args.output_format == "jsonl")
|
|
523
|
+
return EXIT_OK
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _run_replay(args: argparse.Namespace, *, stdin: TextIO, stdout: TextIO) -> int:
|
|
527
|
+
request = _json_value(f"@{args.request}", name="replay request", stdin=stdin)
|
|
528
|
+
if not isinstance(request, Mapping):
|
|
529
|
+
raise CliUsageError("replay request must be a JSON object")
|
|
530
|
+
result = replay_document_blueprint(dict(request), idempotency_store={})
|
|
531
|
+
payload = result.to_dict()
|
|
532
|
+
if args.output_format == "human":
|
|
533
|
+
stdout.write(_blueprint_human(payload) + "\n")
|
|
534
|
+
else:
|
|
535
|
+
_json_dump(payload, stdout, line=args.output_format == "jsonl")
|
|
536
|
+
return _exit_code(result.error)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _run_mutation(
|
|
540
|
+
args: argparse.Namespace,
|
|
541
|
+
*,
|
|
542
|
+
stdin: TextIO,
|
|
543
|
+
stdout: TextIO,
|
|
544
|
+
) -> int:
|
|
545
|
+
store: dict[str, Any] = {}
|
|
546
|
+
if args.command == "batch":
|
|
547
|
+
requests = _load_batch_requests(args, stdin)
|
|
548
|
+
else:
|
|
549
|
+
requests = [_envelope(args, [_single_command(args, stdin)], stdin=stdin)]
|
|
550
|
+
exit_code = EXIT_OK
|
|
551
|
+
results: list[AgentBatchResult] = []
|
|
552
|
+
for request in requests:
|
|
553
|
+
result = apply_document_commands(request, idempotency_store=store)
|
|
554
|
+
results.append(result)
|
|
555
|
+
exit_code = max(exit_code, _exit_code(result.error))
|
|
556
|
+
if args.output_format == "human":
|
|
557
|
+
for result in results:
|
|
558
|
+
stdout.write(_result_human(result) + "\n")
|
|
559
|
+
elif args.output_format == "jsonl":
|
|
560
|
+
for result in results:
|
|
561
|
+
_json_dump(result.to_dict(), stdout, line=True)
|
|
562
|
+
else:
|
|
563
|
+
payload: Any = results[0].to_dict() if len(results) == 1 else [result.to_dict() for result in results]
|
|
564
|
+
_json_dump(payload, stdout)
|
|
565
|
+
return exit_code
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def main(
|
|
569
|
+
argv: Sequence[str] | None = None,
|
|
570
|
+
*,
|
|
571
|
+
stdin: TextIO | None = None,
|
|
572
|
+
stdout: TextIO | None = None,
|
|
573
|
+
stderr: TextIO | None = None,
|
|
574
|
+
) -> int:
|
|
575
|
+
stdin = cast(TextIO, stdin or sys.stdin)
|
|
576
|
+
stdout = cast(TextIO, stdout or sys.stdout)
|
|
577
|
+
stderr = cast(TextIO, stderr or sys.stderr)
|
|
578
|
+
parser = build_parser()
|
|
579
|
+
try:
|
|
580
|
+
with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
|
|
581
|
+
args = parser.parse_args(list(argv) if argv is not None else None)
|
|
582
|
+
if args.command == "help":
|
|
583
|
+
if args.kind == "blueprint":
|
|
584
|
+
if args.json_help:
|
|
585
|
+
_json_dump(blueprint_catalog(), stdout)
|
|
586
|
+
else:
|
|
587
|
+
stdout.write(blueprint_human_help())
|
|
588
|
+
return EXIT_OK
|
|
589
|
+
if args.json_help:
|
|
590
|
+
payload = agent_catalog() if args.kind is None else {
|
|
591
|
+
"schemaVersion": agent_catalog()["schemaVersion"],
|
|
592
|
+
"nodeKinds": {args.kind: agent_catalog()["nodeKinds"][args.kind]},
|
|
593
|
+
}
|
|
594
|
+
_json_dump(payload, stdout)
|
|
595
|
+
else:
|
|
596
|
+
stdout.write(human_help(args.kind))
|
|
597
|
+
return EXIT_OK
|
|
598
|
+
if args.command in {"view", "get"}:
|
|
599
|
+
_emit(_run_read(args), args.output_format, stdout)
|
|
600
|
+
return EXIT_OK
|
|
601
|
+
if args.command == "query":
|
|
602
|
+
_emit(_run_query(args), args.output_format, stdout)
|
|
603
|
+
return EXIT_OK
|
|
604
|
+
if args.command == "dump":
|
|
605
|
+
return _run_dump(args, stdin=stdin, stdout=stdout)
|
|
606
|
+
if args.command == "replay":
|
|
607
|
+
return _run_replay(args, stdin=stdin, stdout=stdout)
|
|
608
|
+
return _run_mutation(args, stdin=stdin, stdout=stdout)
|
|
609
|
+
except CliUsageError as exc:
|
|
610
|
+
error = _usage_error(str(exc))
|
|
611
|
+
_json_dump(_error_payload(error), stderr)
|
|
612
|
+
return EXIT_USAGE
|
|
613
|
+
except AgentContractError as exc:
|
|
614
|
+
error = AgentError(
|
|
615
|
+
code=exc.code,
|
|
616
|
+
message=str(exc),
|
|
617
|
+
target=exc.target,
|
|
618
|
+
recoverability="needs-review" if exc.code in _TARGET_CODES else "terminal",
|
|
619
|
+
)
|
|
620
|
+
_json_dump(_error_payload(error), stderr)
|
|
621
|
+
return _exit_code(error)
|
|
622
|
+
except (KeyError, OSError, ValueError) as exc:
|
|
623
|
+
error = _usage_error(str(exc)) if isinstance(exc, (KeyError, ValueError)) else _unexpected_error(exc)
|
|
624
|
+
_json_dump(_error_payload(error), stderr)
|
|
625
|
+
return _exit_code(error)
|
|
626
|
+
except SystemExit as exc:
|
|
627
|
+
return int(exc.code or 0)
|
|
628
|
+
except BaseException as exc: # pragma: no cover - terminal fail-closed guard
|
|
629
|
+
error = _unexpected_error(exc)
|
|
630
|
+
_json_dump(_error_payload(error), stderr)
|
|
631
|
+
return EXIT_UNEXPECTED
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
if __name__ == "__main__": # pragma: no cover
|
|
635
|
+
raise SystemExit(main())
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
__all__ = [
|
|
639
|
+
"EXIT_CONFLICT",
|
|
640
|
+
"EXIT_OK",
|
|
641
|
+
"EXIT_TARGET",
|
|
642
|
+
"EXIT_UNEXPECTED",
|
|
643
|
+
"EXIT_USAGE",
|
|
644
|
+
"EXIT_VERIFICATION",
|
|
645
|
+
"build_parser",
|
|
646
|
+
"main",
|
|
647
|
+
]
|