Implements API layer for Information Spaces: - GraphQL schema types for spaces, documents, variables - GraphQL queries and mutations for space operations - CLI command group with all space management commands - Resolver functions connecting GraphQL to SpaceService - 38 unit tests for API components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
716 lines
22 KiB
Python
716 lines
22 KiB
Python
"""
|
|
CLI commands for Information Spaces.
|
|
|
|
Provides command-line interface for managing spaces, documents,
|
|
rendering, and synchronization.
|
|
"""
|
|
|
|
import click
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
from tabulate import tabulate
|
|
|
|
from .models import InformationSpace, SpaceConfig, SpaceMetadata, SpaceStatus
|
|
from .services.space_service import SpaceService
|
|
from .repositories.sqlite import (
|
|
SqliteSpaceRepository,
|
|
SqliteDocumentRepository,
|
|
SqliteVariableRepository,
|
|
SqliteReferenceRepository,
|
|
)
|
|
|
|
|
|
def get_db_path() -> Path:
|
|
"""Get the database path."""
|
|
db_path = os.environ.get(
|
|
"MARKITECT_SPACES_DB",
|
|
os.path.expanduser("~/.markitect/spaces.db"),
|
|
)
|
|
path = Path(db_path)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
return path
|
|
|
|
|
|
def get_space_service() -> SpaceService:
|
|
"""Create a space service instance."""
|
|
db_path = get_db_path()
|
|
return SpaceService(
|
|
space_repo=SqliteSpaceRepository(db_path),
|
|
document_repo=SqliteDocumentRepository(db_path),
|
|
variable_repo=SqliteVariableRepository(db_path),
|
|
reference_repo=SqliteReferenceRepository(db_path),
|
|
)
|
|
|
|
|
|
@click.group()
|
|
def space():
|
|
"""Manage Information Spaces."""
|
|
pass
|
|
|
|
|
|
# Space lifecycle commands
|
|
@space.command("create")
|
|
@click.argument("name")
|
|
@click.option("--description", "-d", help="Space description")
|
|
@click.option("--theme", help="Default theme for rendering")
|
|
@click.option("--variant", default="hierarchical", help="Default export variant")
|
|
@click.option("--parent", help="Parent space ID for inheritance")
|
|
@click.option("--tags", help="Comma-separated tags")
|
|
@click.option("--author", help="Author identifier")
|
|
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
|
|
def create_space(
|
|
name: str,
|
|
description: Optional[str],
|
|
theme: Optional[str],
|
|
variant: str,
|
|
parent: Optional[str],
|
|
tags: Optional[str],
|
|
author: Optional[str],
|
|
json_output: bool,
|
|
):
|
|
"""Create a new Information Space."""
|
|
try:
|
|
service = get_space_service()
|
|
|
|
config = SpaceConfig(
|
|
default_variant=variant,
|
|
theme=theme,
|
|
)
|
|
|
|
metadata = SpaceMetadata(
|
|
tags=tags.split(",") if tags else [],
|
|
author=author,
|
|
)
|
|
|
|
space = service.create_space(
|
|
name=name,
|
|
description=description,
|
|
config=config,
|
|
metadata=metadata,
|
|
parent_space_id=parent,
|
|
)
|
|
|
|
if json_output:
|
|
click.echo(
|
|
json.dumps(
|
|
{
|
|
"id": space.id,
|
|
"name": space.name,
|
|
"status": space.status.value,
|
|
"created_at": space.created_at.isoformat() if space.created_at else None,
|
|
},
|
|
indent=2,
|
|
)
|
|
)
|
|
else:
|
|
click.echo(f"Created space: {space.name} (ID: {space.id})")
|
|
click.echo(f"Status: {space.status.value}")
|
|
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("list")
|
|
@click.option("--status", "-s", help="Filter by status")
|
|
@click.option("--limit", "-l", default=50, help="Maximum results")
|
|
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
|
|
def list_spaces(status: Optional[str], limit: int, json_output: bool):
|
|
"""List all Information Spaces."""
|
|
try:
|
|
service = get_space_service()
|
|
spaces = service.list_spaces(status=status, limit=limit)
|
|
|
|
if json_output:
|
|
data = [
|
|
{
|
|
"id": s.id,
|
|
"name": s.name,
|
|
"status": s.status.value,
|
|
"description": s.description,
|
|
}
|
|
for s in spaces
|
|
]
|
|
click.echo(json.dumps(data, indent=2))
|
|
else:
|
|
if not spaces:
|
|
click.echo("No spaces found.")
|
|
return
|
|
|
|
table_data = [
|
|
[s.name, s.id[:8] + "...", s.status.value, s.description or ""]
|
|
for s in spaces
|
|
]
|
|
click.echo(
|
|
tabulate(
|
|
table_data,
|
|
headers=["Name", "ID", "Status", "Description"],
|
|
tablefmt="simple",
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("show")
|
|
@click.argument("space_id_or_name")
|
|
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
|
|
def show_space(space_id_or_name: str, json_output: bool):
|
|
"""Show details of a space."""
|
|
try:
|
|
service = get_space_service()
|
|
|
|
# Try by ID first, then by name
|
|
space = service.get_space(space_id_or_name)
|
|
if not space:
|
|
space = service.get_space_by_name(space_id_or_name)
|
|
|
|
if not space:
|
|
click.echo(f"Space not found: {space_id_or_name}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
documents = service.list_documents(space.id)
|
|
variables = service.list_variables(space.id)
|
|
|
|
if json_output:
|
|
data = {
|
|
"id": space.id,
|
|
"name": space.name,
|
|
"description": space.description,
|
|
"status": space.status.value,
|
|
"config": space.config.to_dict() if space.config else {},
|
|
"metadata": space.metadata.to_dict() if hasattr(space.metadata, 'to_dict') else {},
|
|
"document_count": len(documents),
|
|
"variable_count": len(variables),
|
|
"created_at": space.created_at.isoformat() if space.created_at else None,
|
|
"updated_at": space.updated_at.isoformat() if space.updated_at else None,
|
|
}
|
|
click.echo(json.dumps(data, indent=2))
|
|
else:
|
|
click.echo(f"Space: {space.name}")
|
|
click.echo(f" ID: {space.id}")
|
|
click.echo(f" Status: {space.status.value}")
|
|
click.echo(f" Description: {space.description or 'N/A'}")
|
|
click.echo(f" Documents: {len(documents)}")
|
|
click.echo(f" Variables: {len(variables)}")
|
|
if space.config:
|
|
click.echo(f" Theme: {space.config.theme or 'default'}")
|
|
click.echo(f" Variant: {space.config.default_variant}")
|
|
|
|
except SystemExit:
|
|
raise
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("delete")
|
|
@click.argument("space_id")
|
|
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
|
|
def delete_space(space_id: str, force: bool):
|
|
"""Delete a space."""
|
|
try:
|
|
service = get_space_service()
|
|
space = service.get_space(space_id)
|
|
|
|
if not space:
|
|
click.echo(f"Space not found: {space_id}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
if not force:
|
|
click.confirm(f"Delete space '{space.name}'?", abort=True)
|
|
|
|
service.delete_space(space_id)
|
|
click.echo(f"Deleted space: {space.name}")
|
|
|
|
except click.Abort:
|
|
click.echo("Cancelled.")
|
|
except SystemExit:
|
|
raise
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("activate")
|
|
@click.argument("space_id")
|
|
def activate_space(space_id: str):
|
|
"""Activate a draft space."""
|
|
try:
|
|
service = get_space_service()
|
|
space = service.activate_space(space_id)
|
|
click.echo(f"Activated space: {space.name}")
|
|
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("archive")
|
|
@click.argument("space_id")
|
|
def archive_space(space_id: str):
|
|
"""Archive a space."""
|
|
try:
|
|
service = get_space_service()
|
|
space = service.archive_space(space_id)
|
|
click.echo(f"Archived space: {space.name}")
|
|
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
# Document commands
|
|
@space.command("add-doc")
|
|
@click.argument("space_id")
|
|
@click.option("--path", "-p", required=True, help="Path within space")
|
|
@click.option("--document-id", "-d", help="Document ID (generated if not provided)")
|
|
@click.option("--content", "-c", help="Markdown content")
|
|
@click.option("--file", "-f", "file_path", type=click.Path(exists=True), help="File to read content from")
|
|
def add_document(
|
|
space_id: str,
|
|
path: str,
|
|
document_id: Optional[str],
|
|
content: Optional[str],
|
|
file_path: Optional[str],
|
|
):
|
|
"""Add a document to a space."""
|
|
try:
|
|
service = get_space_service()
|
|
|
|
# Read content from file if provided
|
|
if file_path:
|
|
content = Path(file_path).read_text(encoding="utf-8")
|
|
|
|
if not document_id:
|
|
import uuid
|
|
document_id = str(uuid.uuid4())
|
|
|
|
doc = service.add_document(
|
|
space_id=space_id,
|
|
document_id=document_id,
|
|
space_path=path,
|
|
)
|
|
|
|
click.echo(f"Added document: {path}")
|
|
click.echo(f" Document ID: {doc.document_id}")
|
|
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("remove-doc")
|
|
@click.argument("space_id")
|
|
@click.argument("space_path")
|
|
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
|
|
def remove_document(space_id: str, space_path: str, force: bool):
|
|
"""Remove a document from a space."""
|
|
try:
|
|
service = get_space_service()
|
|
|
|
if not force:
|
|
click.confirm(f"Remove document '{space_path}'?", abort=True)
|
|
|
|
service.remove_document(space_id, space_path)
|
|
click.echo(f"Removed document: {space_path}")
|
|
|
|
except click.Abort:
|
|
click.echo("Cancelled.")
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("list-docs")
|
|
@click.argument("space_id")
|
|
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
|
|
def list_documents(space_id: str, json_output: bool):
|
|
"""List documents in a space."""
|
|
try:
|
|
service = get_space_service()
|
|
documents = service.list_documents(space_id)
|
|
|
|
if json_output:
|
|
data = [
|
|
{
|
|
"document_id": d.document_id,
|
|
"space_path": d.space_path,
|
|
"order_index": d.order_index,
|
|
}
|
|
for d in documents
|
|
]
|
|
click.echo(json.dumps(data, indent=2))
|
|
else:
|
|
if not documents:
|
|
click.echo("No documents in space.")
|
|
return
|
|
|
|
table_data = [
|
|
[d.space_path, d.document_id[:8] + "...", d.order_index]
|
|
for d in documents
|
|
]
|
|
click.echo(
|
|
tabulate(
|
|
table_data,
|
|
headers=["Path", "Doc ID", "Order"],
|
|
tablefmt="simple",
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
# Variable commands
|
|
@space.command("set-var")
|
|
@click.argument("space_id")
|
|
@click.argument("name")
|
|
@click.argument("value")
|
|
@click.option("--scope", default="space", help="Variable scope (space, document, request)")
|
|
def set_variable(space_id: str, name: str, value: str, scope: str):
|
|
"""Set a variable in a space."""
|
|
try:
|
|
service = get_space_service()
|
|
|
|
# Try to parse value as JSON
|
|
try:
|
|
parsed_value = json.loads(value)
|
|
except json.JSONDecodeError:
|
|
parsed_value = value
|
|
|
|
var = service.set_variable(space_id, name, parsed_value, scope)
|
|
click.echo(f"Set variable: {name} = {value}")
|
|
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("get-var")
|
|
@click.argument("space_id")
|
|
@click.argument("name")
|
|
def get_variable(space_id: str, name: str):
|
|
"""Get a variable from a space."""
|
|
try:
|
|
service = get_space_service()
|
|
var = service.get_variable(space_id, name)
|
|
|
|
if var:
|
|
click.echo(f"{name} = {json.dumps(var.value)}")
|
|
else:
|
|
click.echo(f"Variable not found: {name}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
except SystemExit:
|
|
raise
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("list-vars")
|
|
@click.argument("space_id")
|
|
@click.option("--scope", help="Filter by scope")
|
|
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
|
|
def list_variables(space_id: str, scope: Optional[str], json_output: bool):
|
|
"""List variables in a space."""
|
|
try:
|
|
service = get_space_service()
|
|
variables = service.list_variables(space_id, scope=scope)
|
|
|
|
if json_output:
|
|
data = [
|
|
{"name": v.name, "value": v.value, "scope": v.scope}
|
|
for v in variables
|
|
]
|
|
click.echo(json.dumps(data, indent=2))
|
|
else:
|
|
if not variables:
|
|
click.echo("No variables defined.")
|
|
return
|
|
|
|
table_data = [
|
|
[v.name, json.dumps(v.value)[:40], v.scope]
|
|
for v in variables
|
|
]
|
|
click.echo(
|
|
tabulate(
|
|
table_data,
|
|
headers=["Name", "Value", "Scope"],
|
|
tablefmt="simple",
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
# Rendering commands
|
|
@space.command("render")
|
|
@click.argument("space_id")
|
|
@click.option("--output", "-o", type=click.Path(), help="Output directory")
|
|
@click.option("--theme", default="default", help="Theme name")
|
|
@click.option("--toc", is_flag=True, help="Include table of contents")
|
|
@click.option("--document", "-d", help="Render specific document path")
|
|
def render_space(
|
|
space_id: str,
|
|
output: Optional[str],
|
|
theme: str,
|
|
toc: bool,
|
|
document: Optional[str],
|
|
):
|
|
"""Render space documents to HTML."""
|
|
try:
|
|
from .rendering import (
|
|
SpaceRenderingService,
|
|
RenderConfig,
|
|
ThemeConfig,
|
|
)
|
|
|
|
service = get_space_service()
|
|
space = service.get_space(space_id)
|
|
|
|
if not space:
|
|
click.echo(f"Space not found: {space_id}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
config = RenderConfig(
|
|
theme=ThemeConfig(name=theme, layers=[theme]),
|
|
include_toc=toc,
|
|
)
|
|
|
|
renderer = SpaceRenderingService()
|
|
|
|
if document:
|
|
# Render single document
|
|
doc = service.get_document(space_id, document)
|
|
if not doc:
|
|
click.echo(f"Document not found: {document}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
result = renderer.render_document(
|
|
content="# Placeholder", # Would get actual content
|
|
document_id=doc.document_id,
|
|
space_id=space_id,
|
|
)
|
|
|
|
if output:
|
|
output_path = Path(output)
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
output_path.write_text(result.content, encoding="utf-8")
|
|
click.echo(f"Rendered to: {output}")
|
|
else:
|
|
click.echo(result.content)
|
|
|
|
else:
|
|
# Render all documents
|
|
documents = service.list_documents(space_id)
|
|
output_dir = Path(output) if output else Path("./rendered")
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
for doc in documents:
|
|
result = renderer.render_document(
|
|
content="# Placeholder",
|
|
document_id=doc.document_id,
|
|
space_id=space_id,
|
|
)
|
|
|
|
# Create output path
|
|
doc_output = output_dir / doc.space_path.lstrip("/")
|
|
doc_output = doc_output.with_suffix(".html")
|
|
doc_output.parent.mkdir(parents=True, exist_ok=True)
|
|
doc_output.write_text(result.content, encoding="utf-8")
|
|
|
|
click.echo(f"Rendered {len(documents)} documents to: {output_dir}")
|
|
|
|
except SystemExit:
|
|
raise
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
# Sync commands
|
|
@space.command("export")
|
|
@click.argument("space_id")
|
|
@click.argument("directory", type=click.Path())
|
|
@click.option("--variant", default="by_path", help="Export variant (flat, hierarchical, by_path)")
|
|
@click.option("--no-manifest", is_flag=True, help="Skip manifest file")
|
|
def export_space(space_id: str, directory: str, variant: str, no_manifest: bool):
|
|
"""Export a space to a directory."""
|
|
try:
|
|
from .sync import SpaceDirectoryExporter, ExportConfig, ExportVariant
|
|
|
|
service = get_space_service()
|
|
space = service.get_space(space_id)
|
|
|
|
if not space:
|
|
click.echo(f"Space not found: {space_id}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
documents = service.list_documents(space_id)
|
|
|
|
def content_provider(doc_id):
|
|
return f"# Document {doc_id}\n\nContent placeholder."
|
|
|
|
config = ExportConfig(
|
|
variant=ExportVariant(variant),
|
|
include_manifest=not no_manifest,
|
|
)
|
|
|
|
exporter = SpaceDirectoryExporter(config)
|
|
result = exporter.export_space(
|
|
space=space,
|
|
documents=documents,
|
|
content_provider=content_provider,
|
|
target_directory=Path(directory),
|
|
)
|
|
|
|
click.echo(f"Exported {result.file_count} files to: {directory}")
|
|
if result.errors:
|
|
for path, error in result.errors.items():
|
|
click.echo(f" Error: {path}: {error}", err=True)
|
|
|
|
except SystemExit:
|
|
raise
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("import")
|
|
@click.argument("directory", type=click.Path(exists=True))
|
|
@click.option("--space-id", "-s", help="Target space ID (creates new if not provided)")
|
|
@click.option("--name", "-n", help="Space name (for new space)")
|
|
@click.option("--conflict", default="skip", help="Conflict strategy (skip, overwrite, rename)")
|
|
def import_directory(directory: str, space_id: Optional[str], name: Optional[str], conflict: str):
|
|
"""Import a directory into a space."""
|
|
try:
|
|
from .sync import DirectorySpaceImporter, ImportConfig
|
|
|
|
service = get_space_service()
|
|
|
|
# Create or get space
|
|
if space_id:
|
|
space = service.get_space(space_id)
|
|
if not space:
|
|
click.echo(f"Space not found: {space_id}", err=True)
|
|
raise SystemExit(1)
|
|
else:
|
|
space_name = name or Path(directory).name
|
|
space = service.create_space(name=space_name)
|
|
click.echo(f"Created space: {space.name} (ID: {space.id})")
|
|
|
|
config = ImportConfig(conflict_strategy=conflict)
|
|
importer = DirectorySpaceImporter(config)
|
|
|
|
result = importer.import_directory(Path(directory))
|
|
|
|
click.echo(f"Imported {result.document_count} documents")
|
|
if result.conflicts:
|
|
click.echo(f"Conflicts: {len(result.conflicts)}")
|
|
if result.errors:
|
|
for path, error in result.errors.items():
|
|
click.echo(f" Error: {path}: {error}", err=True)
|
|
|
|
except SystemExit:
|
|
raise
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
@space.command("sync")
|
|
@click.argument("space_id")
|
|
@click.argument("directory", type=click.Path())
|
|
@click.option("--direction", default="bidirectional", help="Sync direction")
|
|
@click.option("--conflict", default="newer_wins", help="Conflict resolution strategy")
|
|
@click.option("--dry-run", is_flag=True, help="Preview changes without applying")
|
|
def sync_space(space_id: str, directory: str, direction: str, conflict: str, dry_run: bool):
|
|
"""Sync a space with a directory."""
|
|
try:
|
|
from .sync import (
|
|
BidirectionalSyncCoordinator,
|
|
SyncConfig,
|
|
SyncDirection,
|
|
ConflictResolution,
|
|
)
|
|
|
|
service = get_space_service()
|
|
space = service.get_space(space_id)
|
|
|
|
if not space:
|
|
click.echo(f"Space not found: {space_id}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
documents = service.list_documents(space_id)
|
|
|
|
def content_provider(doc_id):
|
|
return f"# Document {doc_id}\n\nContent placeholder."
|
|
|
|
config = SyncConfig(
|
|
direction=SyncDirection(direction),
|
|
conflict_resolution=ConflictResolution(conflict),
|
|
dry_run=dry_run,
|
|
)
|
|
|
|
coordinator = BidirectionalSyncCoordinator(config)
|
|
result = coordinator.sync(
|
|
space=space,
|
|
documents=documents,
|
|
content_provider=content_provider,
|
|
directory=Path(directory),
|
|
)
|
|
|
|
if dry_run:
|
|
click.echo("Dry run - no changes applied")
|
|
click.echo(f"Would create: {result.created_count}")
|
|
click.echo(f"Would update: {result.updated_count}")
|
|
click.echo(f"Would delete: {result.deleted_count}")
|
|
else:
|
|
click.echo(f"Sync complete:")
|
|
click.echo(f" Created: {result.created_count}")
|
|
click.echo(f" Updated: {result.updated_count}")
|
|
click.echo(f" Deleted: {result.deleted_count}")
|
|
|
|
if result.conflicts:
|
|
click.echo(f" Conflicts: {len(result.conflicts)}")
|
|
for c in result.conflicts[:5]:
|
|
click.echo(f" - {c.path}: {c.winner}")
|
|
|
|
except SystemExit:
|
|
raise
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|
|
|
|
|
|
# Cache commands
|
|
@space.command("invalidate-cache")
|
|
@click.argument("space_id")
|
|
@click.option("--document", "-d", help="Invalidate specific document")
|
|
def invalidate_cache(space_id: str, document: Optional[str]):
|
|
"""Invalidate render cache for a space."""
|
|
try:
|
|
from .rendering import SpaceRenderingService
|
|
|
|
service = SpaceRenderingService()
|
|
|
|
if document:
|
|
invalidated = service.invalidate_document(document, space_id)
|
|
click.echo(f"Invalidated cache for: {document}")
|
|
else:
|
|
count = service.invalidate_space(space_id)
|
|
click.echo(f"Invalidated {count} cached entries")
|
|
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
raise SystemExit(1)
|