Initial upload: Agentic Codebase Memory Manager v0.1.0
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
2026-03-22 16:01:00 +00:00
parent 0f90468ffb
commit f0bcc54b9f

View File

@@ -0,0 +1,245 @@
"""CLI entry point for memory manager."""
import os
import sys
from typing import Optional
import click
import uvicorn
from memory_manager.core.services import MemoryService, CommitService
from memory_manager.db.repository import MemoryRepository
from memory_manager.api.app import app
db_path = os.getenv("MEMORY_DB_PATH", ".memory/codebase_memory.db")
os.makedirs(os.path.dirname(db_path), exist_ok=True)
repository = MemoryRepository(db_path)
memory_service = MemoryService(repository)
commit_service = CommitService(repository)
@click.group()
@click.version_option(version="0.1.0")
def cli():
"""Agentic Codebase Memory Manager - A shared memory layer for AI coding agents."""
pass
@cli.command()
@click.option("--title", required=True, help="Entry title")
@click.option("--content", required=True, help="Entry content")
@click.option("--category", required=True, type=click.Choice(["decision", "feature", "refactoring", "architecture", "bug", "note"]), help="Entry category")
@click.option("--tags", default="", help="Comma-separated tags")
@click.option("--agent-id", default=lambda: os.getenv("AGENT_ID", "unknown"), help="Agent identifier")
def add(title: str, content: str, category: str, tags: str, agent_id: str):
"""Add a new memory entry."""
import asyncio
async def _add():
tag_list = [t.strip() for t in tags.split(",") if t.strip()]
entry = await memory_service.create_entry(
title=title,
content=content,
category=category,
tags=tag_list,
agent_id=agent_id,
)
click.echo(f"Created entry {entry.id}: {entry.title}")
asyncio.run(_add())
@cli.command()
@click.option("--category", help="Filter by category")
@click.option("--agent-id", help="Filter by agent")
@click.option("--limit", default=100, help="Maximum entries to return")
def list(category: Optional[str], agent_id: Optional[str], limit: int):
"""List memory entries."""
import asyncio
async def _list():
entries = await memory_service.list_entries(
category=category,
agent_id=agent_id,
limit=limit,
)
if not entries:
click.echo("No entries found.")
return
for entry in entries:
click.echo(f"[{entry.id}] {entry.category.value}: {entry.title}")
click.echo(f" {entry.content[:100]}...")
click.echo(f" Tags: {', '.join(entry.tags or [])}")
click.echo()
asyncio.run(_list())
@cli.command()
@click.argument("query")
@click.option("--category", help="Filter by category")
def search(query: str, category: Optional[str]):
"""Search memory entries."""
import asyncio
async def _search():
entries = await memory_service.search_entries(
query=query,
category=category,
)
if not entries:
click.echo(f"No entries found matching '{query}'.")
return
click.echo(f"Found {len(entries)} entries:")
for entry in entries:
click.echo(f"[{entry.id}] {entry.category.value}: {entry.title}")
asyncio.run(_search())
@cli.command()
@click.argument("entry_id", type=int)
def get(entry_id: int):
"""Get a specific entry."""
import asyncio
async def _get():
entry = await memory_service.get_entry(entry_id)
if not entry:
click.echo(f"Entry {entry_id} not found.")
return
click.echo(f"Title: {entry.title}")
click.echo(f"Category: {entry.category.value}")
click.echo(f"Content: {entry.content}")
click.echo(f"Tags: {', '.join(entry.tags or [])}")
click.echo(f"Agent: {entry.agent_id}")
click.echo(f"Created: {entry.created_at}")
click.echo(f"Updated: {entry.updated_at}")
asyncio.run(_get())
@cli.command()
@click.argument("entry_id", type=int)
@click.option("--title", help="New title")
@click.option("--content", help="New content")
@click.option("--category", type=click.Choice(["decision", "feature", "refactoring", "architecture", "bug", "note"]), help="New category")
@click.option("--tags", help="Comma-separated tags")
def update(entry_id: int, title: Optional[str], content: Optional[str], category: Optional[str], tags: Optional[str]):
"""Update a memory entry."""
import asyncio
async def _update():
tag_list = [t.strip() for t in tags.split(",")] if tags else None
updated = await memory_service.update_entry(
entry_id=entry_id,
title=title,
content=content,
category=category,
tags=tag_list,
)
if not updated:
click.echo(f"Entry {entry_id} not found.")
return
click.echo(f"Updated entry {entry_id}: {updated.title}")
asyncio.run(_update())
@cli.command()
@click.argument("entry_id", type=int)
def delete(entry_id: int):
"""Delete a memory entry."""
import asyncio
async def _delete():
deleted = await memory_service.delete_entry(entry_id)
if not deleted:
click.echo(f"Entry {entry_id} not found.")
return
click.echo(f"Deleted entry {entry_id}.")
asyncio.run(_delete())
@cli.command()
@click.option("--message", required=True, help="Commit message")
@click.option("--agent-id", default=lambda: os.getenv("AGENT_ID", "unknown"), help="Agent identifier")
def commit(message: str, agent_id: str):
"""Create a commit snapshot of current memory state."""
import asyncio
async def _commit():
new_commit = await commit_service.create_commit(
message=message,
agent_id=agent_id,
)
click.echo(f"Created commit {new_commit.hash[:8]}: {message}")
asyncio.run(_commit())
@cli.command()
@click.option("--limit", default=50, help="Maximum commits to show")
def log(limit: int):
"""Show commit history."""
import asyncio
async def _log():
commits = await commit_service.get_commits(limit=limit)
if not commits:
click.echo("No commits yet.")
return
for commit in commits:
click.echo(f"{commit.hash[:8]} - {commit.message}")
click.echo(f" Agent: {commit.agent_id} | {commit.created_at}")
click.echo()
asyncio.run(_log())
@cli.command()
@click.argument("hash1")
@click.argument("hash2")
def diff(hash1: str, hash2: str):
"""Show diff between two commits."""
import asyncio
async def _diff():
diff_result = await commit_service.diff_commits(hash1, hash2)
if diff_result["added"]:
click.echo("Added entries:")
for entry in diff_result["added"]:
click.echo(f" + {entry['title']}")
if diff_result["removed"]:
click.echo("Removed entries:")
for entry in diff_result["removed"]:
click.echo(f" - {entry['title']}")
if diff_result["modified"]:
click.echo("Modified entries:")
for mod in diff_result["modified"]:
click.echo(f" ~ {mod['before']['title']}")
if not any([diff_result["added"], diff_result["removed"], diff_result["modified"]]):
click.echo("No differences found.")
asyncio.run(_diff())
@cli.command()
@click.option("--host", default=lambda: os.getenv("MEMORY_API_HOST", "127.0.0.1"), help="Host to bind to")
@click.option("--port", default=lambda: int(os.getenv("MEMORY_API_PORT", "8080")), type=int, help="Port to bind to")
def serve(host: str, port: int):
"""Start the API server."""
uvicorn.run(app, host=host, port=port)
@cli.command()
def tui():
"""Launch the TUI dashboard."""
from memory_manager.tui.app import run_tui
run_tui()
if __name__ == "__main__":
cli()