fix: removing redundant describe method
This commit is contained in:
parent
31a38ff3a0
commit
8b32aa4622
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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__":
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue