fix: #167 — show-command and show-tool now accept --output-format flag; CLI parity with session-lifecycle family

Closes the inspect-capability parity gap: show-command and show-tool were
the only discovery/inspection CLI commands lacking --output-format support,
making them outliers in the ecosystem that already had unified JSON
contracts across list-sessions, load-session, delete-session, and
flush-transcript (#160/#165/#166).

Concrete additions:

- show-command: --output-format {text,json}
- show-tool: --output-format {text,json}

JSON envelope shape (found case):
  {name, found: true, source_hint, responsibility}

JSON envelope shape (not-found case):
  {name, found: false, error: {kind:'command_not_found'|'tool_not_found',
                               message, retryable: false}}

Exit codes:
  0 = success
  1 = not found

Backward compatibility:
  - Default (no --output-format) is 'text' (unchanged)
  - Text output byte-identical to pre-#167 (three newline-separated lines)

Tests (10 new, test_show_command_tool_output_format.py):
  - TestShowCommandOutputFormat (5): found + not-found in JSON; text mode
    backward compat; text is default
  - TestShowToolOutputFormat (3): found + not-found in JSON; text mode
    backward compat
  - TestShowCommandToolFormatParity (2): both accept same flag choices;
    consistent JSON envelope shape

Full suite: 114 → 124 passing, zero regression.

Closes ROADMAP #167.

Why this matters:
  Before: Claws calling show-command/show-tool had to parse human-readable
  prose output via regex, with no structured error signal.
  After: Same envelope contract as load-session and friends: JSON-first,
  typed errors, machine-parseable.

Related clusters:
  - Session-lifecycle CLI parity family (#160, #165, #166, #167)
  - Machine-readable error contracts (same vein as #162 atomicity + #164
    cancellation state-safety: structured boundaries for orchestration)
This commit is contained in:
YeonGyu-Kim
2026-04-22 18:21:38 +09:00
parent 524edb2b2e
commit 01dca90e95
2 changed files with 255 additions and 4 deletions

View File

@@ -157,8 +157,10 @@ def build_parser() -> argparse.ArgumentParser:
show_command = subparsers.add_parser('show-command', help='show one mirrored command entry by exact name')
show_command.add_argument('name')
show_command.add_argument('--output-format', choices=['text', 'json'], default='text')
show_tool = subparsers.add_parser('show-tool', help='show one mirrored tool entry by exact name')
show_tool.add_argument('name')
show_tool.add_argument('--output-format', choices=['text', 'json'], default='text')
exec_command_parser = subparsers.add_parser('exec-command', help='execute a mirrored command shim by exact name')
exec_command_parser.add_argument('name')
@@ -394,16 +396,62 @@ def main(argv: list[str] | None = None) -> int:
if args.command == 'show-command':
module = get_command(args.name)
if module is None:
print(f'Command not found: {args.name}')
if args.output_format == 'json':
import json
error_envelope = {
'name': args.name,
'found': False,
'error': {
'kind': 'command_not_found',
'message': f'Unknown command: {args.name}',
'retryable': False,
},
}
print(json.dumps(error_envelope))
else:
print(f'Command not found: {args.name}')
return 1
print('\n'.join([module.name, module.source_hint, module.responsibility]))
if args.output_format == 'json':
import json
output = {
'name': module.name,
'found': True,
'source_hint': module.source_hint,
'responsibility': module.responsibility,
}
print(json.dumps(output))
else:
print('\n'.join([module.name, module.source_hint, module.responsibility]))
return 0
if args.command == 'show-tool':
module = get_tool(args.name)
if module is None:
print(f'Tool not found: {args.name}')
if args.output_format == 'json':
import json
error_envelope = {
'name': args.name,
'found': False,
'error': {
'kind': 'tool_not_found',
'message': f'Unknown tool: {args.name}',
'retryable': False,
},
}
print(json.dumps(error_envelope))
else:
print(f'Tool not found: {args.name}')
return 1
print('\n'.join([module.name, module.source_hint, module.responsibility]))
if args.output_format == 'json':
import json
output = {
'name': module.name,
'found': True,
'source_hint': module.source_hint,
'responsibility': module.responsibility,
}
print(json.dumps(output))
else:
print('\n'.join([module.name, module.source_hint, module.responsibility]))
return 0
if args.command == 'exec-command':
result = execute_command(args.name, args.prompt)