@@ -2,7 +2,6 @@
import json
import sys
import subprocess
import contextlib
import io
import click
@@ -69,17 +68,22 @@ def safe_cli_wrapper():
affected_commands = len ( sys . argv ) > = 2 and sys . argv [ 1 ] in [ " install " , " update " ]
try :
with contextlib . redirect_stderr ( stderr_capture ) , contextlib . redirect_stdout ( stdout_capture ) :
with contextlib . redirect_stderr ( stderr_capture ) , contextlib . redirect_stdout (
stdout_capture
) :
cli ( standalone_mode = False )
except click . UsageError as e :
if affected_commands and " Got unexpected extra argument " in str ( e ) :
# This is the spurious error for install/update commands
# Check if we got some stdout output indicating success
captured_stdout = stdout_capture . getvalue ( )
success_indicators = [ " Installing agents to: " , " Updating all installed agents: " ]
success_indicators = [
" Installing agents to: " ,
" Updating all installed agents: " ,
]
if any ( indicator in captured_stdout for indicator in success_indicators ) :
# The command was actually executing, show the real output
print ( captured_stdout , end = ' ' )
print ( captured_stdout , end = " " )
sys . exit ( 0 )
else :
# This might be a real error
@@ -96,29 +100,45 @@ def safe_cli_wrapper():
if e . code == 0 :
# Successful exit
print ( captured_stdout , end = ' ' )
print ( captured_stdout , end = " " )
else :
# Error exit - show both stdout and stderr unless it's the spurious error
if affected_commands and " Got unexpected extra argument " in captured_stderr :
# Show only stdout for install/update commands with spurious errors
print ( captured_stdout , end = ' ' )
success_indicators = [ " Installing agents to: " , " Updating all installed agents: " ]
if any ( indicator in captured_stdout for indicator in success_indicators ) :
print ( captured_stdout , end = " " )
success_indicators = [
" Installing agents to: " ,
" Updating all installed agents: " ,
]
if any (
indicator in captured_stdout for indicator in success_indicators
) :
sys . exit ( 0 ) # Override error exit if we see success indicators
else :
# Show everything for other commands
print ( captured_stdout , end = ' ' )
print ( captured_stderr , end = ' ' , file = sys . stderr )
print ( captured_stdout , end = " " )
print ( captured_stderr , end = " " , file = sys . stderr )
sys . exit ( e . code )
except Exception as e :
print ( f " Error: { e } " )
sys . exit ( 1 )
# If we get here, show captured output
print ( stdout_capture . getvalue ( ) , end = ' ' )
print ( stdout_capture . getvalue ( ) , end = " " )
stderr_content = stderr_capture . getvalue ( )
if stderr_content and not ( affected_commands and " Got unexpected extra argument " in stderr_content ) :
print ( stderr_content , end = ' ' , file = sys . stderr )
if stderr_content and not (
affected_commands and " Got unexpected extra argument " in stderr_content
) :
print ( stderr_content , end = " " , file = sys . stderr )
_FEEDBACK_CHANNELS = {
" issues " : " https://gitea.coulomb.social/coulomb/kaizen-agentic/issues " ,
" issue_templates " : " https://gitea.coulomb.social/coulomb/kaizen-agentic/issues/new/choose " ,
" feedback_guide " : " https://gitea.coulomb.social/coulomb/kaizen-agentic/src/branch/main/docs/FEEDBACK.md " ,
" contributing " : " https://gitea.coulomb.social/coulomb/kaizen-agentic/src/branch/main/CONTRIBUTING.md " ,
}
@click.group ( )
@click.version_option ( )
@@ -127,6 +147,32 @@ def cli():
pass
@cli.command ( " feedback " )
@click.option ( " --json " , " as_json " , is_flag = True , help = " Emit machine-readable JSON " )
def feedback ( as_json : bool ) :
""" Show how to submit bugs, ideas, and adoption feedback. """
payload = {
" channels " : _FEEDBACK_CHANNELS ,
" templates " : [ " bug_report " , " feature_request " , " feedback " ] ,
" cli_hint " : " Use Gitea issue templates or State Hub messages for cross-repo coordination " ,
}
if as_json :
click . echo ( json . dumps ( payload , indent = 2 , sort_keys = True ) )
return
click . echo ( " Kaizen Agentic — feedback channels " )
click . echo ( " = " * 40 )
click . echo ( f " Issues: { _FEEDBACK_CHANNELS [ ' issues ' ] } " )
click . echo ( f " New issue: { _FEEDBACK_CHANNELS [ ' issue_templates ' ] } " )
click . echo ( f " Feedback guide: { _FEEDBACK_CHANNELS [ ' feedback_guide ' ] } " )
click . echo ( f " Contributing: { _FEEDBACK_CHANNELS [ ' contributing ' ] } " )
click . echo ( )
click . echo ( " Templates: bug report · feature request · general feedback " )
click . echo (
" Tip: include Python version and `kaizen-agentic --version` in bug reports. "
)
@cli.command ( " list " )
@click.option (
" --category " ,
@@ -842,7 +888,9 @@ session_count: 0
registry = _get_registry ( )
protocols_dir = registry . agents_dir / " protocols " / agent_name
if protocols_dir . exists ( ) :
slugs = [ f . stem for f in sorted ( protocols_dir . glob ( " *.md " ) ) if f . name != " README.md " ]
slugs = [
f . stem for f in sorted ( protocols_dir . glob ( " *.md " ) ) if f . name != " README.md "
]
if slugs :
click . echo ( f " Protocols available for ' { agent_name } ' : " )
for slug in slugs :
@@ -852,7 +900,9 @@ session_count: 0
@memory.command ( " brief " )
@click.argument ( " agent_name " )
@click.option ( " --target " , " -t " , default = " . " , help = " Project root (default: current) " )
@click.option ( " --raw " , is_flag = True , help = " Dump raw memory files without synthesis header " )
@click.option (
" --raw " , is_flag = True , help = " Dump raw memory files without synthesis header "
)
def memory_brief ( agent_name : str , target : str , raw : bool ) :
""" Print a coach-synthesised orientation for an agent.
@@ -889,6 +939,7 @@ def memory_brief(agent_name: str, target: str, raw: bool):
return
from datetime import date as _date
today = _date . today ( ) . isoformat ( )
sources = ( [ agent_name ] if own_memory else [ ] ) + list ( other_memories . keys ( ) )
@@ -918,7 +969,9 @@ def memory_brief(agent_name: str, target: str, raw: bool):
click . echo ( " ### Your Memory " )
click . echo ( own_memory )
else :
click . echo ( f " ### Your Memory \n (none — run: kaizen-agentic memory init { agent_name } ) \n " )
click . echo (
f " ### Your Memory \n (none — run: kaizen-agentic memory init { agent_name } ) \n "
)
# Cross-agent context
if other_memories :
@@ -928,17 +981,23 @@ def memory_brief(agent_name: str, target: str, raw: bool):
click . echo ( f " --- { name } --- " )
click . echo ( content )
else :
click . echo ( " ### Context From Other Agents \n No other agent memories found in this project. \n " )
click . echo (
" ### Context From Other Agents \n No other agent memories found in this project. \n "
)
click . echo ( " --- " )
click . echo ( " Tip: Load agents/agent-coach.md in your Claude session and pass this output " )
click . echo (
" Tip: Load agents/agent-coach.md in your Claude session and pass this output "
)
click . echo ( " for a full cross-agent synthesis and orientation brief. " )
@memory.command ( " clear " )
@click.argument ( " agent_name " )
@click.option ( " --target " , " -t " , default = " . " , help = " Project root (default: current) " )
@click.confirmation_option ( prompt = " This will permanently delete the agent memory. Continue? " )
@click.confirmation_option (
prompt = " This will permanently delete the agent memory. Continue? "
)
def memory_clear ( agent_name : str , target : str ) :
""" Wipe agent memory for the current project. """
memory_path = _memory_path ( target , agent_name )
@@ -964,13 +1023,19 @@ def metrics():
@metrics.command ( " record " )
@click.argument ( " agent_name " )
@click.option ( " --target " , " -t " , default = " . " , help = " Project root (default: current) " )
@click.option ( " --success " , " outcome_success " , is_flag = True , help = " Record successful execution " )
@click.option ( " --failure " , " outcome_failure " , is_flag = True , help = " Record failed execution " )
@click.option (
" --success " , " outcome_success " , is_flag = True , help = " Record successful execution "
)
@click.option (
" --failure " , " outcome_failure " , is_flag = True , help = " Record failed execution "
)
@click.option ( " --time " , " execution_time " , type = float , help = " Execution time in seconds " )
@click.option ( " --quality " , type = float , help = " Quality score 0.0– 1.0 " )
@click.option ( " --session-id " , help = " Optional session identifier " )
@click.option ( " --idempotency-key " , help = " Skip append if this key was already recorded " )
@click.option ( " --json " , " json_input " , is_flag = True , help = " Read full record JSON from stdin " )
@click.option (
" --json " , " json_input " , is_flag = True , help = " Read full record JSON from stdin "
)
def metrics_record (
agent_name : str ,
target : str ,
@@ -995,7 +1060,9 @@ def metrics_record(
click . echo ( " Error: use only one of --success or --failure " , err = True )
sys . exit ( 1 )
if not outcome_success and not outcome_failure :
click . echo ( " Error: specify --success or --failure (or use --json) " , err = True )
click . echo (
" Error: specify --success or --failure (or use --json) " , err = True
)
sys . exit ( 1 )
payload = { " success " : outcome_success }
if execution_time is not None :
@@ -1010,13 +1077,17 @@ def metrics_record(
if store . append ( payload , idempotency_key = idempotency_key ) :
click . echo ( f " Recorded metrics for ' { agent_name } ' " )
else :
click . echo ( f " Skipped duplicate record for ' { agent_name } ' (idempotency key exists) " )
click . echo (
f " Skipped duplicate record for ' { agent_name } ' (idempotency key exists) "
)
@metrics.command ( " show " )
@click.argument ( " agent_name " )
@click.option ( " --target " , " -t " , default = " . " , help = " Project root (default: current) " )
@click.option ( " --limit " , " -n " , default = 5 , show_default = True , help = " Recent executions to show " )
@click.option (
" --limit " , " -n " , default = 5 , show_default = True , help = " Recent executions to show "
)
def metrics_show ( agent_name : str , target : str , limit : int ) :
""" Print metrics summary and recent executions for an agent. """
store = MetricsStore ( _project_root ( target ) , agent_name )
@@ -1073,7 +1144,9 @@ def metrics_optimize(agent_name: Optional[str], target: str, min_samples: int):
if not agents :
click . echo ( " No agent metrics found to optimize. " )
click . echo ( " Record executions with: kaizen-agentic metrics record <agent> --success " )
click . echo (
" Record executions with: kaizen-agentic metrics record <agent> --success "
)
return
optimizer_store = OptimizerStore ( project_root )
@@ -1287,6 +1360,7 @@ def _memory_path(target: str, agent_name: str) -> Path:
def _today ( ) - > str :
from datetime import date
return date . today ( ) . isoformat ( )
@@ -1312,14 +1386,20 @@ def _get_registry() -> AgentRegistry:
# Try relative to package
agents_dir = Path ( kaizen_agentic . __file__ ) . parent / " data " / " agents "
except ImportError :
click . echo ( " Error: Could not find agents directory " )
click . echo (
" Make sure you ' re in a kaizen-agentic project or have the package installed "
)
click . echo ( " Error: kaizen-agentic package is not installed. " , err = True )
click . echo ( " Fix: pip install -e . (from repo root) " , err = True )
click . echo ( " Or: run from a project with an agents/ directory " , err = True )
sys . exit ( 1 )
if not agents_dir . exists ( ) :
click . echo ( f " Error: A gents directory not found: { agents_dir } " )
click . echo ( f " Error: a gents directory not found: { agents_dir } " , err = True )
click . echo (
" Fix: cd into a kaizen-agentic checkout or a project with agents/ " ,
err = True ,
)
click . echo (
" Or: kaizen-agentic install <template> to scaffold agents " , err = True
)
sys . exit ( 1 )
return AgentRegistry ( agents_dir )