fix: removing redundant describe method

This commit is contained in:
ajspig 2026-04-13 12:42:54 -04:00
parent 31a38ff3a0
commit 8b32aa4622
6 changed files with 17 additions and 204 deletions

View File

@ -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 <key> <value>` | Set a single config value |
| `honcho describe resource <name>` | 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:

View File

@ -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:

View File

@ -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 <resource>` 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)

View File

@ -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__":

View File

@ -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

View File

@ -8,7 +8,6 @@ description: Debug Honcho peer representations and memory
## Rules
- Use `honcho describe <resource>` to understand schema before constructing queries
- Check queue status when derivation seems stalled
- Compare peer card with conclusions to understand memory state