fix(cli): fix JSON/YAML envelope messages[].text to show actual content #6739
@@ -1002,6 +1002,14 @@ iteration` and data corruption under concurrent plan execution. All public
|
||||
replaces the O(n) linear `status` lookup in the `as_completed()` loop with an O(1)
|
||||
`status_map` dict pre-computed before the executor block.
|
||||
|
||||
- **JSON/YAML envelope `messages[].text` content** (#6457): `agents session create`,
|
||||
`list`, `show`, `delete`, `export`, and `import` commands now populate the
|
||||
`messages[].text` field with human-readable text (`"Session created"`,
|
||||
`"N sessions listed"`, `"Session details loaded"`, `"Session deleted"`,
|
||||
`"Export completed"`, `"Import completed"`) instead of the generic `"ok"` fallback.
|
||||
The `export` command gains `--output-format` and the `import` command gains `--format`
|
||||
to select the output envelope format independently of the export/import file format.
|
||||
|
||||
- **Robot Framework TDD Listener Guards** (#5436): Added three guard conditions to the
|
||||
`tdd_expected_fail_listener` `end_test()` function to prevent blindly inverting ALL test
|
||||
failures to passes, which was masking infrastructure errors and causing flaky CI behavior.
|
||||
|
||||
@@ -143,6 +143,7 @@ Feature: Devcontainer Session Cleanup and CLI Commands
|
||||
When I invoke CLI resource stop "local/ctr-stop"
|
||||
Then the CLI exit code should be 0
|
||||
And the CLI output should contain "Stopped"
|
||||
And the CLI stop mock should have been called with "01TESTLIFECYCLE0000000370"
|
||||
|
||||
# ── F6-r6: CLI handler-level error paths ────────────────────
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ Feature: Session CLI commands
|
||||
When I run session CLI create with --format json
|
||||
Then the session CLI create should succeed
|
||||
And the session CLI output should be valid JSON
|
||||
And the session CLI JSON envelope message should be "Session created"
|
||||
|
||||
# List command tests
|
||||
Scenario: List sessions when empty
|
||||
@@ -28,6 +29,12 @@ Feature: Session CLI commands
|
||||
When I run session CLI list
|
||||
Then the session CLI output should contain "No sessions found"
|
||||
|
||||
Scenario: List sessions with JSON format when empty
|
||||
Given there are no mocked sessions
|
||||
When I run session CLI list with --format json
|
||||
Then the session CLI output should be valid JSON
|
||||
And the session CLI JSON envelope message should be "0 sessions listed"
|
||||
|
||||
Scenario: List sessions with populated data
|
||||
Given there are mocked existing sessions
|
||||
When I run session CLI list
|
||||
@@ -38,6 +45,7 @@ Feature: Session CLI commands
|
||||
When I run session CLI list with --format json
|
||||
Then the session CLI output should be valid JSON
|
||||
And the session CLI JSON should contain "sessions"
|
||||
And the session CLI JSON envelope message should be "2 sessions listed"
|
||||
|
||||
Scenario: List sessions JSON matches the documented contract
|
||||
Given there are mocked existing sessions
|
||||
@@ -85,6 +93,7 @@ Feature: Session CLI commands
|
||||
Given there is a mocked session with messages
|
||||
When I run session CLI show with --format json
|
||||
Then the session CLI output should be valid JSON
|
||||
And the session CLI JSON envelope message should be "Session details loaded"
|
||||
|
||||
Scenario: Show session JSON includes token usage counts
|
||||
Given there is a mocked session with messages
|
||||
@@ -103,6 +112,19 @@ Feature: Session CLI commands
|
||||
Then the session CLI delete should succeed
|
||||
And the session CLI output should contain "deleted"
|
||||
|
||||
Scenario: Delete session with JSON format emits envelope
|
||||
Given there is a mocked session to delete
|
||||
When I run session CLI delete with --yes and --format json
|
||||
Then the session CLI delete should succeed
|
||||
And the session CLI output should be valid JSON
|
||||
And the session CLI JSON envelope message should be "Session deleted"
|
||||
|
||||
Scenario: Delete session with --format color emits Rich output
|
||||
Given there is a mocked session to delete
|
||||
When I run session CLI delete with --yes and --format color
|
||||
Then the session CLI delete should succeed
|
||||
And the session CLI output should contain "deleted"
|
||||
|
||||
Scenario: Delete non-existent session
|
||||
When I run session CLI delete with a non-existent ID
|
||||
Then the session CLI should exit with error
|
||||
@@ -154,6 +176,13 @@ Feature: Session CLI commands
|
||||
Then the session CLI export should succeed
|
||||
And the session CLI output should contain "Export completed"
|
||||
|
||||
Scenario: Export session with JSON output format emits envelope
|
||||
Given there is a mocked session for export
|
||||
When I run session CLI export with --output-format json
|
||||
Then the session CLI export should succeed
|
||||
And the session CLI output should be valid JSON
|
||||
And the session CLI JSON envelope message should be "Export completed"
|
||||
|
||||
Scenario: Export non-existent session
|
||||
When I run session CLI export with a non-existent session ID
|
||||
Then the session CLI should exit with error
|
||||
@@ -193,6 +222,13 @@ Feature: Session CLI commands
|
||||
Then the session CLI should exit with error
|
||||
And the session CLI output should contain "Import error"
|
||||
|
||||
Scenario: Import session with JSON format emits envelope
|
||||
Given there is a valid session export file
|
||||
When I run session CLI import with the export file and --format json
|
||||
Then the session CLI import should succeed
|
||||
And the session CLI output should be valid JSON
|
||||
And the session CLI JSON envelope message should be "Import completed"
|
||||
|
||||
Scenario: Import from invalid JSON file
|
||||
Given there is an invalid JSON file
|
||||
When I run session CLI import with the invalid JSON file
|
||||
|
||||
@@ -50,7 +50,7 @@ def step_contains_getLogger(context: Any) -> None:
|
||||
if hasattr(context, "create_source")
|
||||
else context.list_source
|
||||
)
|
||||
assert 'logging.getLogger("cleveragents.mcp")' in source, (
|
||||
assert "logging.getLogger(_MCP_LOGGER_NAME)" in source, (
|
||||
"Source should contain logging.getLogger call"
|
||||
)
|
||||
|
||||
|
||||
@@ -421,6 +421,20 @@ def step_delete_yes(context: Context) -> None:
|
||||
)
|
||||
|
||||
|
||||
@when("I run session CLI delete with --yes and --format json")
|
||||
def step_delete_yes_json(context: Context) -> None:
|
||||
context.result = context.runner.invoke(
|
||||
session_app, ["delete", context.session_id, "--yes", "--format", "json"]
|
||||
)
|
||||
|
||||
|
||||
@when("I run session CLI delete with --yes and --format color")
|
||||
def step_delete_yes_color(context: Context) -> None:
|
||||
context.result = context.runner.invoke(
|
||||
session_app, ["delete", context.session_id, "--yes", "--format", "color"]
|
||||
)
|
||||
|
||||
|
||||
@when("I run session CLI delete with a non-existent ID")
|
||||
def step_delete_nonexistent(context: Context) -> None:
|
||||
context.mock_service.get.side_effect = SessionNotFoundError("Session not found")
|
||||
@@ -463,6 +477,13 @@ def step_export_stdout(context: Context) -> None:
|
||||
context.result = context.runner.invoke(session_app, ["export", context.session_id])
|
||||
|
||||
|
||||
@when("I run session CLI export with --output-format json")
|
||||
def step_export_output_format_json(context: Context) -> None:
|
||||
context.result = context.runner.invoke(
|
||||
session_app, ["export", context.session_id, "--output-format", "json"]
|
||||
)
|
||||
|
||||
|
||||
@when("I run session CLI export with --output to a temp file")
|
||||
def step_export_to_file(context: Context) -> None:
|
||||
fd, path = tempfile.mkstemp(suffix=".json")
|
||||
@@ -566,6 +587,13 @@ def step_import_valid(context: Context) -> None:
|
||||
)
|
||||
|
||||
|
||||
@when("I run session CLI import with the export file and --format json")
|
||||
def step_import_valid_json(context: Context) -> None:
|
||||
context.result = context.runner.invoke(
|
||||
session_app, ["import", "--input", context.import_path, "--format", "json"]
|
||||
)
|
||||
|
||||
|
||||
@when("I run session CLI import with a non-existent file")
|
||||
def step_import_nonexistent(context: Context) -> None:
|
||||
context.result = context.runner.invoke(
|
||||
@@ -756,6 +784,25 @@ def _assert_token_usage_counts_are_ints(token_usage: dict[str, Any]) -> None:
|
||||
assert output_tokens != "***REDACTED***", "output_tokens should not be redacted"
|
||||
|
||||
|
||||
@then('the session CLI JSON envelope message should be "{expected}"')
|
||||
def step_json_envelope_message(context: Context, expected: str) -> None:
|
||||
parsed = json.loads(context.result.output)
|
||||
assert isinstance(parsed, dict), (
|
||||
f"Expected envelope dict, got {type(parsed)}: {context.result.output}"
|
||||
)
|
||||
messages = parsed.get("messages")
|
||||
assert isinstance(messages, list), (
|
||||
f"Expected 'messages' list, got {type(messages)}: {messages}"
|
||||
)
|
||||
assert messages, "Envelope messages should not be empty"
|
||||
first = messages[0]
|
||||
assert isinstance(first, dict), f"Message entry should be dict, got {type(first)}"
|
||||
actual = first.get("text")
|
||||
assert actual == expected, (
|
||||
f"Expected message text '{expected}', got '{actual}' in {messages}"
|
||||
)
|
||||
|
||||
|
||||
@then("the session CLI should exit with error")
|
||||
def step_exit_with_error(context: Context) -> None:
|
||||
assert context.result.exit_code != 0, (
|
||||
|
||||
@@ -1461,8 +1461,12 @@ def resource_remove(
|
||||
# B2 fix: separate type sets — stop works on any container, but rebuild
|
||||
# requires devcontainer-instance since it invokes ``devcontainer up``.
|
||||
# Issue #2588 fix: container-instance is now stoppable — stop_container uses
|
||||
# docker stop with the container_id from the lifecycle tracker, which works
|
||||
# for both container-instance and devcontainer-instance resources.
|
||||
# docker stop with the container_id from the lifecycle tracker, covering
|
||||
# both container-instance and devcontainer-instance resources.
|
||||
# Spec regression guard: docs/specification.md requires both resource types
|
||||
# to support ``agents resource stop``; keep rebuild limited to
|
||||
# devcontainer-instance while allowing the broader stop surface mandated by
|
||||
# the CLI contract.
|
||||
_STOPPABLE_TYPES = frozenset({"devcontainer-instance", "container-instance"})
|
||||
_REBUILDABLE_TYPES = frozenset({"devcontainer-instance"})
|
||||
# F5-r6 fix: moved from inside resource_rebuild() to module level to avoid
|
||||
|
||||
@@ -63,6 +63,20 @@ _MCP_LOGGER_NAME = "cleveragents.mcp"
|
||||
# multiple CLI commands execute concurrently (e.g., in parallel test runners).
|
||||
_mcp_logger_lock = threading.Lock()
|
||||
|
||||
|
||||
def _command_label(subcommand: str, *tokens: str) -> str:
|
||||
"""Build a CLI command string for envelope metadata."""
|
||||
parts: list[str] = ["agents", "session", subcommand]
|
||||
parts.extend(token for token in tokens if token)
|
||||
return " ".join(parts)
|
||||
|
||||
|
||||
def _session_list_message(count: int) -> str:
|
||||
"""Return the human-readable message for session list results."""
|
||||
suffix = "session" if count == 1 else "sessions"
|
||||
return f"{count} {suffix} listed"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module-level service and workflow accessors (patchable in tests)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -325,7 +339,7 @@ def create(
|
||||
"""
|
||||
# Suppress MCP daemon logger during JSON/YAML output to prevent health check
|
||||
# messages from interfering with structured output.
|
||||
mcp_logger = logging.getLogger("cleveragents.mcp")
|
||||
mcp_logger = logging.getLogger(_MCP_LOGGER_NAME)
|
||||
orig_level = mcp_logger.level
|
||||
if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
with _mcp_logger_lock:
|
||||
@@ -355,7 +369,18 @@ def create(
|
||||
data = _session_summary_dict(session)
|
||||
|
||||
if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
typer.echo(format_output(dict(data), fmt))
|
||||
extra_tokens: list[str] = []
|
||||
if actor:
|
||||
extra_tokens.extend(["--actor", actor])
|
||||
extra_tokens.extend(["--format", fmt])
|
||||
typer.echo(
|
||||
format_output(
|
||||
dict(data),
|
||||
fmt,
|
||||
command=_command_label("create", *extra_tokens),
|
||||
messages=[{"level": "ok", "text": "Session created"}],
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
details = (
|
||||
@@ -431,7 +456,7 @@ def list_sessions(
|
||||
"""
|
||||
# Suppress MCP daemon logger during JSON/YAML output to prevent health check
|
||||
# messages from interfering with structured output.
|
||||
mcp_logger = logging.getLogger("cleveragents.mcp")
|
||||
mcp_logger = logging.getLogger(_MCP_LOGGER_NAME)
|
||||
orig_level = mcp_logger.level
|
||||
if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
with _mcp_logger_lock:
|
||||
@@ -456,7 +481,19 @@ def list_sessions(
|
||||
# For machine-readable formats, always emit a structured empty list
|
||||
# so that callers parsing JSON/YAML receive valid output.
|
||||
if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
typer.echo(format_output({"sessions": [], "total": 0}, fmt))
|
||||
typer.echo(
|
||||
format_output(
|
||||
{"sessions": [], "total": 0},
|
||||
fmt,
|
||||
command=_command_label("list", "--format", fmt),
|
||||
messages=[
|
||||
{
|
||||
"level": "ok",
|
||||
"text": _session_list_message(0),
|
||||
}
|
||||
],
|
||||
)
|
||||
)
|
||||
return
|
||||
console.print("[yellow]No sessions found.[/yellow]")
|
||||
console.print("Create one with 'agents session create'")
|
||||
@@ -465,7 +502,19 @@ def list_sessions(
|
||||
data = _session_list_dict(sessions)
|
||||
|
||||
if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
typer.echo(format_output(data, fmt))
|
||||
typer.echo(
|
||||
format_output(
|
||||
data,
|
||||
fmt,
|
||||
command=_command_label("list", "--format", fmt),
|
||||
messages=[
|
||||
{
|
||||
"level": "ok",
|
||||
"text": _session_list_message(len(sessions)),
|
||||
}
|
||||
],
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# Rich table
|
||||
@@ -531,7 +580,19 @@ def show(
|
||||
data = session.as_cli_dict()
|
||||
|
||||
if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
typer.echo(format_output(dict(data), fmt))
|
||||
typer.echo(
|
||||
format_output(
|
||||
dict(data),
|
||||
fmt,
|
||||
command=_command_label(
|
||||
"show",
|
||||
session_id,
|
||||
"--format",
|
||||
fmt,
|
||||
),
|
||||
messages=[{"level": "ok", "text": "Session details loaded"}],
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# Session summary panel — field order per spec: ID, Actor, Messages,
|
||||
@@ -656,9 +717,21 @@ def delete(
|
||||
|
||||
service.delete(session_id)
|
||||
|
||||
# Rich output: render Deletion Summary and Cleanup panels
|
||||
if fmt == OutputFormat.RICH:
|
||||
# Deletion Summary panel
|
||||
if fmt not in (OutputFormat.RICH, OutputFormat.COLOR):
|
||||
# Machine-readable formats: emit a structured JSON/YAML envelope per spec
|
||||
# (docs/specification.md §"agents session delete" line ~1959)
|
||||
typer.echo(
|
||||
format_output(
|
||||
{"session_id": session_id},
|
||||
fmt.value,
|
||||
command=_command_label(
|
||||
"delete", session_id, "--yes", "--format", fmt.value
|
||||
),
|
||||
messages=[{"level": "ok", "text": "Session deleted"}],
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Rich/Color output: render Deletion Summary and Cleanup panels
|
||||
summary_table = Table.grid(padding=(0, 1))
|
||||
summary_table.add_column(style="cyan bold", justify="left")
|
||||
summary_table.add_column(style="white", justify="left")
|
||||
@@ -685,9 +758,6 @@ def delete(
|
||||
console.print(Panel(cleanup_table, title="Cleanup", border_style="blue"))
|
||||
console.print()
|
||||
console.print("[green]✓ OK[/green] Session deleted")
|
||||
else:
|
||||
# Non-rich formats: simple message
|
||||
console.print(f"[green]✓ OK[/green] Session {session_id} deleted")
|
||||
|
||||
except SessionNotFoundError as exc:
|
||||
console.print(f"[red]Session not found:[/red] {session_id}")
|
||||
@@ -722,6 +792,14 @@ def export_session(
|
||||
help="Export format: json (default) or md (Markdown transcript)",
|
||||
),
|
||||
] = "json",
|
||||
output_fmt: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
"--output-format",
|
||||
"-f",
|
||||
help=_FORMAT_HELP,
|
||||
),
|
||||
] = "rich",
|
||||
) -> None:
|
||||
"""Export a session as JSON or Markdown.
|
||||
|
||||
@@ -781,17 +859,75 @@ def export_session(
|
||||
# Create parent directories if needed
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(content, encoding="utf-8")
|
||||
else:
|
||||
elif output_fmt in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
# Only echo raw content to stdout when using rich output format;
|
||||
# for machine-readable formats the envelope wraps the metadata instead.
|
||||
typer.echo(content)
|
||||
|
||||
# Render Rich panels for both file and stdout export paths
|
||||
_render_export_panels(
|
||||
session_id=session_id,
|
||||
output=output,
|
||||
content=content,
|
||||
export_data=json_data,
|
||||
fmt=fmt,
|
||||
)
|
||||
if output_fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
# Non-rich output formats: emit a structured JSON/YAML envelope per spec
|
||||
# (docs/specification.md §"agents session export" line ~2085)
|
||||
size_bytes = len(content.encode("utf-8"))
|
||||
if size_bytes < 1024:
|
||||
size_str = f"{size_bytes} B"
|
||||
elif size_bytes < 1024 * 1024:
|
||||
size_str = f"{size_bytes // 1024} KB"
|
||||
else:
|
||||
size_str = f"{size_bytes // (1024 * 1024)} MB"
|
||||
output_display = str(output) if output is not None else "(stdout)"
|
||||
format_display = "JSON" if fmt == "json" else "Markdown"
|
||||
message_count = len(json_data.get("messages", []))
|
||||
plan_refs = len(json_data.get("linked_plan_ids", []))
|
||||
metadata_keys = len(json_data.get("metadata", {}))
|
||||
actor_config = "included" if json_data.get("actor_name") else "none"
|
||||
schema_version = json_data.get("schema_version", "v1")
|
||||
checksum_raw = json_data.get("checksum", "")
|
||||
checksum_display = (
|
||||
f"sha256:{checksum_raw[:4]}...{checksum_raw[-4:]}"
|
||||
if len(checksum_raw) >= 8
|
||||
else checksum_raw or "n/a"
|
||||
)
|
||||
envelope_data = {
|
||||
"session_export": {
|
||||
"session": session_id,
|
||||
"output": output_display,
|
||||
"messages": message_count,
|
||||
"size": size_str,
|
||||
"format": format_display,
|
||||
},
|
||||
"contents": {
|
||||
"messages": message_count,
|
||||
"plan_references": plan_refs,
|
||||
"metadata_keys": metadata_keys,
|
||||
"actor_config": actor_config,
|
||||
"schema_version": schema_version,
|
||||
},
|
||||
"integrity": {
|
||||
"checksum": checksum_display,
|
||||
"encrypted": False,
|
||||
},
|
||||
}
|
||||
extra_tokens: list[str] = []
|
||||
if output is not None:
|
||||
extra_tokens.extend(["--output", str(output)])
|
||||
extra_tokens.extend(["--output-format", output_fmt])
|
||||
typer.echo(
|
||||
format_output(
|
||||
envelope_data,
|
||||
output_fmt,
|
||||
command=_command_label("export", session_id, *extra_tokens),
|
||||
messages=[{"level": "ok", "text": "Export completed"}],
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Render Rich panels for both file and stdout export paths
|
||||
_render_export_panels(
|
||||
session_id=session_id,
|
||||
output=output,
|
||||
content=content,
|
||||
export_data=json_data,
|
||||
fmt=fmt,
|
||||
)
|
||||
|
||||
except SessionNotFoundError as exc:
|
||||
console.print(f"[red]Session not found:[/red] {session_id}")
|
||||
@@ -891,6 +1027,10 @@ def import_session(
|
||||
Path,
|
||||
typer.Option("--input", "-i", help="Input JSON file path"),
|
||||
],
|
||||
output_fmt: Annotated[
|
||||
str,
|
||||
typer.Option("--format", "-f", help=_FORMAT_HELP),
|
||||
] = "rich",
|
||||
) -> None:
|
||||
"""Import a session from a JSON file.
|
||||
|
||||
@@ -917,29 +1057,63 @@ def import_session(
|
||||
actor_name = data.get("actor_name")
|
||||
session = service.import_session(data)
|
||||
|
||||
# Session Import panel
|
||||
session_details = (
|
||||
f"[bold]Input:[/bold] {input_file}\n"
|
||||
f"[bold]Session ID:[/bold] {session.session_id}\n"
|
||||
f"[bold]Messages:[/bold] {session.message_count}\n"
|
||||
f"[bold]Schema:[/bold] {schema_version}"
|
||||
)
|
||||
console.print(Panel(session_details, title="Session Import", expand=False))
|
||||
if output_fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
# Non-rich output formats: emit a structured JSON/YAML envelope per spec
|
||||
# (docs/specification.md §"agents session import" line ~2205)
|
||||
actor_ref_status = "resolved" if actor_name else "none"
|
||||
envelope_data = {
|
||||
"session_import": {
|
||||
"input": str(input_file),
|
||||
"session_id": session.session_id,
|
||||
"messages": session.message_count,
|
||||
"schema": schema_version,
|
||||
},
|
||||
"validation": {
|
||||
"checksum": "verified",
|
||||
"schema": "compatible",
|
||||
"actor_ref": actor_ref_status,
|
||||
},
|
||||
"merge": {
|
||||
"existing": "none",
|
||||
"strategy": "create new",
|
||||
},
|
||||
}
|
||||
typer.echo(
|
||||
format_output(
|
||||
envelope_data,
|
||||
output_fmt,
|
||||
command=_command_label(
|
||||
"import", "--input", str(input_file), "--format", output_fmt
|
||||
),
|
||||
messages=[{"level": "ok", "text": "Import completed"}],
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Session Import panel
|
||||
session_details = (
|
||||
f"[bold]Input:[/bold] {input_file}\n"
|
||||
f"[bold]Session ID:[/bold] {session.session_id}\n"
|
||||
f"[bold]Messages:[/bold] {session.message_count}\n"
|
||||
f"[bold]Schema:[/bold] {schema_version}"
|
||||
)
|
||||
console.print(Panel(session_details, title="Session Import", expand=False))
|
||||
|
||||
# Validation panel
|
||||
actor_ref_status = "resolved" if actor_name else "none"
|
||||
validation_details = (
|
||||
f"[bold]Checksum:[/bold] verified\n"
|
||||
f"[bold]Schema:[/bold] compatible\n"
|
||||
f"[bold]Actor Ref:[/bold] {actor_ref_status}"
|
||||
)
|
||||
console.print(Panel(validation_details, title="Validation", expand=False))
|
||||
# Validation panel
|
||||
actor_ref_status = "resolved" if actor_name else "none"
|
||||
validation_details = (
|
||||
f"[bold]Checksum:[/bold] verified\n"
|
||||
f"[bold]Schema:[/bold] compatible\n"
|
||||
f"[bold]Actor Ref:[/bold] {actor_ref_status}"
|
||||
)
|
||||
console.print(Panel(validation_details, title="Validation", expand=False))
|
||||
|
||||
# Merge panel
|
||||
merge_details = "[bold]Existing:[/bold] none\n[bold]Strategy:[/bold] create new"
|
||||
console.print(Panel(merge_details, title="Merge", expand=False))
|
||||
# Merge panel
|
||||
merge_details = (
|
||||
"[bold]Existing:[/bold] none\n[bold]Strategy:[/bold] create new"
|
||||
)
|
||||
console.print(Panel(merge_details, title="Merge", expand=False))
|
||||
|
||||
console.print("[green]✓ OK[/green] Import completed")
|
||||
console.print("[green]✓ OK[/green] Import completed")
|
||||
|
||||
except SessionImportError as exc:
|
||||
console.print(f"[red]Import error:[/red] {exc}")
|
||||
|
||||
Reference in New Issue
Block a user