22
33from __future__ import annotations
44
5+ import io
56import json
67import sys
78
9+ # Windows cp1252 breaks on unicode chars like arrows/dashes in memory titles
10+ if sys .stdout and hasattr (sys .stdout , 'encoding' ) and sys .stdout .encoding and sys .stdout .encoding .lower () != 'utf-8' :
11+ sys .stdout = io .TextIOWrapper (sys .stdout .buffer , encoding = 'utf-8' , errors = 'replace' )
12+ if sys .stderr and hasattr (sys .stderr , 'encoding' ) and sys .stderr .encoding and sys .stderr .encoding .lower () != 'utf-8' :
13+ sys .stderr = io .TextIOWrapper (sys .stderr .buffer , encoding = 'utf-8' , errors = 'replace' )
14+
815import click
916
17+ from . import __version__
1018from .core import Memory
1119from .models import MEMORY_TYPES , MEMORY_STATUSES
1220
@@ -29,16 +37,20 @@ def main(ctx, db, project):
2937@main .command ()
3038@click .option ("--type" , "mem_type" , type = click .Choice (MEMORY_TYPES ), required = True )
3139@click .option ("--title" , required = True )
40+ @click .option ("--status" , "mem_status" , type = click .Choice (MEMORY_STATUSES ), default = "active" ,
41+ help = "Initial status (default: active)." )
3242@click .option ("--tags" , default = "" , help = "Comma-separated tags." )
3343@click .argument ("content" )
3444@click .pass_context
35- def add (ctx , mem_type , title , tags , content ):
45+ def add (ctx , mem_type , title , mem_status , tags , content ):
3646 """Add a memory."""
3747 mem = _get_mem (ctx )
3848 tag_list = [t .strip () for t in tags .split ("," ) if t .strip ()] if tags else []
39- record = mem .add (type = mem_type , title = title , content = content , tags = tag_list , source = "cli" )
49+ record = mem .add (type = mem_type , title = title , content = content , tags = tag_list ,
50+ source = "cli" , status = mem_status )
51+ status_label = f" ({ record .status } )" if record .status != "active" else ""
4052 click .echo (f"Added: { record .id } " )
41- click .echo (f" [{ record .type } ] { record .title } " )
53+ click .echo (f" [{ record .type } ] { record .title } { status_label } " )
4254 mem .close ()
4355
4456
@@ -330,6 +342,13 @@ def health(ctx, days):
330342 if report .orphaned_supersedes :
331343 click .echo (f" ORPHANED: { len (report .orphaned_supersedes )} " )
332344
345+ if report .health_score >= 100 :
346+ validated = report .by_status .get ("validated" , 0 )
347+ click .echo (f" Fully governed. 0 conflicts, 0 stale, { validated } validated rules." )
348+ click .echo (f" You're running a clean memory system. That's rare." )
349+ click .echo (f"\n If this helped, share your setup:" )
350+ click .echo (f" https://github.com/thezenmonster/agentmem/discussions" )
351+
333352 click .echo (f"\n { '=' * 50 } " )
334353 mem .close ()
335354
@@ -378,6 +397,14 @@ def init(ctx, tool, proj):
378397 db_abs = str (Path (db_path ).resolve ()).replace ("\\ " , "/" )
379398
380399 if tool :
400+ # Check MCP dependency
401+ try :
402+ import mcp # noqa: F401
403+ except ImportError :
404+ click .echo (f" [!] MCP package not installed. Run:" )
405+ click .echo (f" pip install quilmem[mcp]" )
406+ click .echo ()
407+
381408 click .echo (f" [3/4] MCP config for { tool } :\n " )
382409
383410 if tool in ("claude" ,):
@@ -430,13 +457,28 @@ def init(ctx, tool, proj):
430457 click .echo (f"\n { '=' * 50 } " )
431458 click .echo (f" Done. Your memory DB is at: { db_abs } " )
432459 click .echo (f" Project: { project } " )
433- click .echo (f"\n Next steps:" )
434- click .echo (f" agentmem add --type decision --title \" My first rule\" \" Description here\" " )
435- click .echo (f" agentmem search \" my rule\" " )
460+ click .echo (f"\n Try the differentiators:" )
461+ click .echo (f" # Add a rule you're certain about" )
462+ click .echo (f" agentmem add --type decision --status validated \\ " )
463+ click .echo (f" --title \" Never force-push to main\" \" Enforced after incident.\" " )
464+ click .echo (f"" )
465+ click .echo (f" # Add something unproven" )
466+ click .echo (f" agentmem add --type decision --status hypothesis \\ " )
467+ click .echo (f" --title \" Maybe batch DB writes\" \" Needs benchmarking.\" " )
468+ click .echo (f"" )
469+ click .echo (f" # Save your session so the next agent picks up where you left off" )
470+ click .echo (f" agentmem save-session \" Working on auth refactor. Blocked on tokens.\" " )
471+ click .echo (f" agentmem load-session" )
472+ click .echo (f"" )
473+ click .echo (f" # Check what your agent should trust" )
436474 click .echo (f" agentmem health" )
437475 if not tool :
438- click .echo (f" agentmem init --tool claude # generate MCP config" )
476+ click .echo (f"" )
477+ click .echo (f" # Connect to your coding agent" )
478+ click .echo (f" agentmem init --tool claude # or cursor, codex, windsurf" )
439479 click .echo (f"{ '=' * 50 } " )
480+ click .echo (f"\n Something break? { NEW_ISSUE_URL } " )
481+ click .echo (f" Paste debug context: agentmem debug-info" )
440482
441483
442484@main .command ()
@@ -536,16 +578,103 @@ def doctor(ctx):
536578 click .echo (f" All checks passed." )
537579 else :
538580 click .echo (f" Some checks failed. See above for fixes." )
581+ click .echo (f"\n Still stuck? { NEW_ISSUE_URL } " )
582+ click .echo (f" Paste debug context: agentmem debug-info" )
539583 click .echo (f"{ '=' * 50 } " )
540584
541585
586+ ISSUES_URL = "https://github.com/thezenmonster/agentmem/issues"
587+ NEW_ISSUE_URL = f"{ ISSUES_URL } /new/choose"
588+
589+
590+ @main .command ("debug-info" )
591+ @click .option ("--json-output" , "as_json" , is_flag = True , help = "Output as JSON for pasting into issues." )
592+ @click .pass_context
593+ def debug_info (ctx , as_json ):
594+ """Print system info for bug reports. Paste the output into a GitHub issue."""
595+ import platform
596+ from pathlib import Path
597+
598+ db_path = ctx .obj ["db" ]
599+ project = ctx .obj .get ("project" , "" )
600+
601+ info = {
602+ "agentmem_version" : __version__ ,
603+ "python_version" : platform .python_version (),
604+ "platform" : platform .platform (),
605+ "os" : platform .system (),
606+ "db_path" : str (Path (db_path ).resolve ()),
607+ "db_exists" : Path (db_path ).exists (),
608+ "project" : project ,
609+ }
610+
611+ # Check MCP
612+ try :
613+ import mcp
614+ info ["mcp_installed" ] = True
615+ info ["mcp_version" ] = getattr (mcp , "__version__" , "unknown" )
616+ except ImportError :
617+ info ["mcp_installed" ] = False
618+
619+ # DB stats if exists
620+ if info ["db_exists" ]:
621+ try :
622+ mem = Memory (path = db_path , project = project )
623+ stats = mem .stats ()
624+ info ["total_memories" ] = stats ["total" ]
625+ info ["db_size_kb" ] = stats ["db_size_kb" ]
626+
627+ row = mem ._conn .execute ("SELECT MAX(version) FROM schema_version" ).fetchone ()
628+ info ["schema_version" ] = row [0 ] if row else 0
629+
630+ from .governance import health_check as hc
631+ report = hc (mem ._conn , project = project )
632+ info ["health_score" ] = report .health_score
633+ info ["conflicts" ] = len (report .conflicts )
634+ info ["stale" ] = len (report .stale )
635+
636+ by_status = {}
637+ for r in mem ._conn .execute (
638+ "SELECT COALESCE(status, 'active') as s, COUNT(*) as c FROM memories GROUP BY s"
639+ ):
640+ by_status [r ["s" ]] = r ["c" ]
641+ info ["by_status" ] = by_status
642+
643+ mem .close ()
644+ except Exception as e :
645+ info ["db_error" ] = str (e )
646+
647+ if as_json :
648+ click .echo (json .dumps (info , indent = 2 ))
649+ else :
650+ click .echo ("agentmem debug-info" )
651+ click .echo (f"{ '=' * 50 } " )
652+ click .echo (f" agentmem: { info ['agentmem_version' ]} " )
653+ click .echo (f" Python: { info ['python_version' ]} " )
654+ click .echo (f" Platform: { info ['platform' ]} " )
655+ click .echo (f" DB: { info ['db_path' ]} ({ 'exists' if info ['db_exists' ] else 'NOT FOUND' } )" )
656+ click .echo (f" Project: { info .get ('project' , '(none)' )} " )
657+ click .echo (f" MCP: { 'yes' + (' v' + info .get ('mcp_version' , '?' )) if info .get ('mcp_installed' ) else 'not installed' } " )
658+ if info .get ("total_memories" ) is not None :
659+ click .echo (f" Memories: { info ['total_memories' ]} ({ info ['db_size_kb' ]} KB)" )
660+ click .echo (f" Schema: v{ info .get ('schema_version' , '?' )} " )
661+ click .echo (f" Health: { info ['health_score' ]:.0f} /100 | Conflicts: { info ['conflicts' ]} | Stale: { info ['stale' ]} " )
662+ if info .get ("by_status" ):
663+ parts = [f"{ s } : { c } " for s , c in sorted (info ["by_status" ].items ())]
664+ click .echo (f" Status: { ', ' .join (parts )} " )
665+ if info .get ("db_error" ):
666+ click .echo (f" DB Error: { info ['db_error' ]} " )
667+ click .echo (f"{ '=' * 50 } " )
668+ click .echo (f"\n Paste this into a bug report: { NEW_ISSUE_URL } " )
669+
670+
542671@main .command ()
543672@click .pass_context
544673def serve (ctx ):
545674 """Start MCP stdio server."""
546675 try :
547676 from .mcp_server import run_server
548677 except ImportError :
549- click .echo ("MCP support requires: pip install agentmem [mcp]" , err = True )
678+ click .echo ("MCP support requires: pip install quilmem [mcp]" , err = True )
550679 sys .exit (1 )
551680 run_server (db_path = ctx .obj ["db" ], project = ctx .obj .get ("project" , "" ))
0 commit comments