diff --git a/honcho-cli/README.md b/honcho-cli/README.md index b68dcf1d..1da6a562 100644 --- a/honcho-cli/README.md +++ b/honcho-cli/README.md @@ -107,13 +107,12 @@ honcho # show banner + command list |---------|-------------| | `honcho key generate` | Generate a scoped JWT (workspace/peer/session) | -### Config & schema +### Config | Command | Description | |---------|-------------| | `honcho config show` | Show current config (API key redacted) | | `honcho config set ` | Set a single config value | -| `honcho describe resource ` | Schema introspection from live server | ## Agent Usage @@ -150,13 +149,6 @@ HONCHO_API_KEY=xxx honcho init --yes --workspace my-ws honcho init --yes ``` -Schema discovery for agents that need to build requests dynamically: - -```bash -honcho describe resource peer --json -honcho describe resource session --json -``` - ## Context Threading Set defaults once, then skip IDs on subsequent commands: diff --git a/honcho-cli/src/honcho_cli/commands/conclusion.py b/honcho-cli/src/honcho_cli/commands/conclusion.py index 7bb24d5e..e62ffe83 100644 --- a/honcho-cli/src/honcho_cli/commands/conclusion.py +++ b/honcho-cli/src/honcho_cli/commands/conclusion.py @@ -51,13 +51,15 @@ def list_conclusions( { "id": c.id, "content": c.content[:200], + "workspace_id": config.workspace_id, "observer_id": c.observer_id, "observed_id": c.observed_id, + "session_id": c.session_id, "created_at": str(c.created_at), } for c in conclusions ] - print_result(items, columns=["id", "content", "observer_id", "observed_id", "created_at"], title="Conclusions") + print_result(items, columns=["id", "content", "workspace_id", "observer_id", "observed_id", "session_id", "created_at"], title="Conclusions") except Exception as e: _handle_error(e, "conclusion", "list") @@ -98,13 +100,15 @@ def search( { "id": c.id, "content": c.content[:200], + "workspace_id": config.workspace_id, "observer_id": c.observer_id, "observed_id": c.observed_id, + "session_id": c.session_id, "created_at": str(c.created_at), } for c in results ] - print_result(items, columns=["id", "content", "created_at"], title=f"Conclusion search: {query}") + print_result(items, columns=["id", "content", "workspace_id", "session_id", "created_at"], title=f"Conclusion search: {query}") except Exception as e: _handle_error(e, "conclusion", "search") @@ -147,12 +151,21 @@ def create( else: scope = p.conclusions - result = scope.create(content, session_id=session_id) + params: dict[str, object] = {"content": content} + if session_id: + params["session_id"] = session_id + results = scope.create([params]) + result = results[0] if results else None + if result is None: + print_error("CREATE_FAILED", "Conclusion create returned no results") + raise typer.Exit(1) print_result({ "id": result.id, "content": result.content, + "workspace_id": config.workspace_id, "observer_id": result.observer_id, "observed_id": result.observed_id, + "session_id": result.session_id, "created_at": str(result.created_at), }) except Exception as e: diff --git a/honcho-cli/src/honcho_cli/commands/describe.py b/honcho-cli/src/honcho_cli/commands/describe.py deleted file mode 100644 index 3869673b..00000000 --- a/honcho-cli/src/honcho_cli/commands/describe.py +++ /dev/null @@ -1,188 +0,0 @@ -"""Schema introspection: describe resource schemas from live OpenAPI spec.""" - -from __future__ import annotations - -import json -import time -from pathlib import Path -from typing import Optional - -import typer - -from honcho_cli.output import print_error, print_result, set_json_mode, set_quiet_mode, status - -app = typer.Typer(help="Schema introspection from live server.") - -# Local cache for OpenAPI spec -_CACHE_DIR = Path.home() / ".honcho" / "cache" -_CACHE_FILE = _CACHE_DIR / "openapi.json" -_CACHE_TTL = 3600 # 1 hour - -# Map CLI resource names to OpenAPI schema names -RESOURCE_SCHEMA_MAP: dict[str, list[str]] = { - "workspace": ["WorkspaceCreate", "WorkspaceResponse", "WorkspaceConfiguration"], - "peer": ["PeerCreate", "PeerResponse", "PeerConfig"], - "session": ["SessionCreate", "SessionResponse", "SessionConfiguration", "SessionPeerConfig"], - "message": ["MessageCreate", "MessageResponse", "MessageConfiguration"], - "conclusion": ["ConclusionCreate", "ConclusionResponse"], - "key": ["JWTParams"], -} - - -def _fetch_openapi(base_url: str, api_key: str | None = None) -> dict: - """Fetch OpenAPI spec from server, with caching.""" - # Check cache - if _CACHE_FILE.exists(): - mtime = _CACHE_FILE.stat().st_mtime - if time.time() - mtime < _CACHE_TTL: - return json.loads(_CACHE_FILE.read_text()) - - import httpx - - headers = {} - if api_key: - headers["Authorization"] = f"Bearer {api_key}" - - try: - resp = httpx.get(f"{base_url.rstrip('/')}/openapi.json", headers=headers, timeout=15) - resp.raise_for_status() - spec = resp.json() - except Exception as e: - # Fall back to cache if available - if _CACHE_FILE.exists(): - status("Using cached OpenAPI spec (server unreachable)") - return json.loads(_CACHE_FILE.read_text()) - raise - - # Cache it - _CACHE_DIR.mkdir(parents=True, exist_ok=True) - _CACHE_FILE.write_text(json.dumps(spec)) - - return spec - - -def _resolve_ref(spec: dict, ref: str) -> dict: - """Resolve a $ref in an OpenAPI spec.""" - parts = ref.lstrip("#/").split("/") - node = spec - for part in parts: - node = node[part] - return node - - -def _extract_schema(spec: dict, schema_name: str) -> dict | None: - """Extract a schema from the OpenAPI spec components.""" - schemas = spec.get("components", {}).get("schemas", {}) - - # Try exact match first - if schema_name in schemas: - return _flatten_schema(spec, schemas[schema_name]) - - # Try case-insensitive match - for name, schema in schemas.items(): - if name.lower() == schema_name.lower(): - return _flatten_schema(spec, schema) - - return None - - -def _flatten_schema(spec: dict, schema: dict) -> dict: - """Flatten a schema, resolving $ref and allOf.""" - if "$ref" in schema: - return _flatten_schema(spec, _resolve_ref(spec, schema["$ref"])) - - if "allOf" in schema: - merged: dict = {"type": "object", "properties": {}} - for sub in schema["allOf"]: - resolved = _flatten_schema(spec, sub) - if "properties" in resolved: - merged["properties"].update(resolved["properties"]) - if "required" in resolved: - merged.setdefault("required", []).extend(resolved["required"]) - return merged - - return schema - - -def _format_schema(schema: dict) -> dict: - """Format schema for display.""" - props = schema.get("properties", {}) - required = set(schema.get("required", [])) - - fields = {} - for name, prop in props.items(): - field_type = prop.get("type", prop.get("$ref", "unknown")) - if "anyOf" in prop: - types = [t.get("type", "?") for t in prop["anyOf"] if t.get("type") != "null"] - field_type = " | ".join(types) if types else "unknown" - if any(t.get("type") == "null" for t in prop["anyOf"]): - field_type += " (optional)" - - info: dict = {"type": field_type} - if name in required: - info["required"] = True - if "default" in prop: - info["default"] = prop["default"] - if "description" in prop: - info["description"] = prop["description"] - - fields[name] = info - - return fields - - -@app.command("resource") -def describe_resource( - resource: str = typer.Argument(help="Resource type: workspace, peer, session, message, conclusion, key"), -) -> None: - """Describe a resource schema from the live server.""" - from honcho_cli.main import get_resolved_config - - resource = resource.lower() - if resource not in RESOURCE_SCHEMA_MAP: - print_error( - "UNKNOWN_RESOURCE", - f"Unknown resource: '{resource}'", - {"valid_resources": list(RESOURCE_SCHEMA_MAP.keys())}, - ) - raise typer.Exit(1) - - config = get_resolved_config() - - try: - spec = _fetch_openapi(config.base_url, config.api_key) - except Exception as e: - print_error("OPENAPI_ERROR", f"Failed to fetch OpenAPI spec: {e}", {"base_url": config.base_url}) - raise typer.Exit(1) - - schema_names = RESOURCE_SCHEMA_MAP[resource] - result: dict = {} - - for schema_name in schema_names: - schema = _extract_schema(spec, schema_name) - if schema: - result[schema_name] = _format_schema(schema) - - if not result: - print_error("SCHEMA_NOT_FOUND", f"No schemas found for '{resource}'", {"resource": resource}) - raise typer.Exit(1) - - print_result(result) - - -# Make `honcho describe ` work as the default command -# by aliasing the resource command as the callback -@app.callback(invoke_without_command=True) -def describe_callback( - ctx: typer.Context, - resource: Optional[str] = typer.Argument(None, help="Resource type to describe"), - json_output: bool = typer.Option(False, "--json", help="Force JSON output"), - quiet: bool = typer.Option(False, "--quiet", "-q", help="Suppress status messages"), -) -> None: - """Describe resource schemas from the live server's OpenAPI spec.""" - if json_output: - set_json_mode(True) - if quiet: - set_quiet_mode(True) - if resource and not ctx.invoked_subcommand: - ctx.invoke(describe_resource, resource=resource) diff --git a/honcho-cli/src/honcho_cli/main.py b/honcho-cli/src/honcho_cli/main.py index 1c95b645..3eadb75a 100644 --- a/honcho-cli/src/honcho_cli/main.py +++ b/honcho-cli/src/honcho_cli/main.py @@ -108,7 +108,6 @@ app.command()(doctor) # Register command groups from honcho_cli.commands.config_cmd import app as config_app from honcho_cli.commands.conclusion import app as conclusion_app -from honcho_cli.commands.describe import app as describe_app from honcho_cli.commands.key import app as key_app from honcho_cli.commands.message import app as message_app from honcho_cli.commands.peer import app as peer_app @@ -122,7 +121,6 @@ app.add_typer(session_app, name="session") app.add_typer(message_app, name="message") app.add_typer(conclusion_app, name="conclusion") app.add_typer(key_app, name="key") -app.add_typer(describe_app, name="describe") if __name__ == "__main__": diff --git a/honcho-cli/src/honcho_cli/skills/CONTEXT.md b/honcho-cli/src/honcho_cli/skills/CONTEXT.md index 2c505b69..d676d36e 100644 --- a/honcho-cli/src/honcho_cli/skills/CONTEXT.md +++ b/honcho-cli/src/honcho_cli/skills/CONTEXT.md @@ -42,4 +42,3 @@ honcho config set peer_id my-peer - `honcho message` — List and get messages - `honcho conclusion` — List, search, create, delete conclusions - `honcho key` — Generate scoped JWT keys -- `honcho describe` — Schema introspection from live server diff --git a/honcho-cli/src/honcho_cli/skills/honcho-debug.md b/honcho-cli/src/honcho_cli/skills/honcho-debug.md index bec1e29f..547d6163 100644 --- a/honcho-cli/src/honcho_cli/skills/honcho-debug.md +++ b/honcho-cli/src/honcho_cli/skills/honcho-debug.md @@ -8,7 +8,6 @@ description: Debug Honcho peer representations and memory ## Rules -- Use `honcho describe ` to understand schema before constructing queries - Check queue status when derivation seems stalled - Compare peer card with conclusions to understand memory state