From 3485f9e4748752d7706cdce3d2842cdb844a1f52 Mon Sep 17 00:00:00 2001 From: ajspig Date: Tue, 14 Apr 2026 11:04:45 -0400 Subject: [PATCH] fix: refactor lazy imports to use common.py correctly. --- .gitignore | 2 +- .../src/honcho_cli/commands/conclusion.py | 10 +-- .../src/honcho_cli/commands/config_cmd.py | 3 +- honcho-cli/src/honcho_cli/commands/message.py | 6 +- honcho-cli/src/honcho_cli/commands/peer.py | 21 +------ honcho-cli/src/honcho_cli/commands/session.py | 27 +------- honcho-cli/src/honcho_cli/commands/setup.py | 3 +- .../src/honcho_cli/commands/workspace.py | 13 +--- honcho-cli/src/honcho_cli/common.py | 62 +++++++++++++++++-- honcho-cli/src/honcho_cli/main.py | 56 +---------------- 10 files changed, 65 insertions(+), 138 deletions(-) diff --git a/.gitignore b/.gitignore index ea5ccca1..94aedb5f 100644 --- a/.gitignore +++ b/.gitignore @@ -182,7 +182,7 @@ docs/node_modules timing_logs.csv -config.toml +config.json .aider* CRUSH.md diff --git a/honcho-cli/src/honcho_cli/commands/conclusion.py b/honcho-cli/src/honcho_cli/commands/conclusion.py index 40bf5329..423631fc 100644 --- a/honcho-cli/src/honcho_cli/commands/conclusion.py +++ b/honcho-cli/src/honcho_cli/commands/conclusion.py @@ -11,7 +11,7 @@ from honcho_cli.commands.workspace import _handle_error from honcho_cli.output import print_error, print_result, status, use_json from honcho_cli.validation import validate_resource_id -from honcho_cli.common import add_common_options +from honcho_cli.common import add_common_options, get_client, handle_cmd_flags app = typer.Typer(help="Conclusion (observation) operations.") add_common_options(app) @@ -27,8 +27,6 @@ def list_conclusions( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """List conclusions.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) client, config = get_client() @@ -76,8 +74,6 @@ def search( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Semantic search over conclusions.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) client, config = get_client() @@ -125,8 +121,6 @@ def create( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Create a conclusion.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) client, config = get_client() @@ -184,8 +178,6 @@ def delete( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Delete a conclusion.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) validate_resource_id(conclusion_id, "conclusion") diff --git a/honcho-cli/src/honcho_cli/commands/config_cmd.py b/honcho-cli/src/honcho_cli/commands/config_cmd.py index fa4a11f0..9a173686 100644 --- a/honcho-cli/src/honcho_cli/commands/config_cmd.py +++ b/honcho-cli/src/honcho_cli/commands/config_cmd.py @@ -10,6 +10,7 @@ from __future__ import annotations import typer +from honcho_cli.common import handle_cmd_flags from honcho_cli.config import CLIConfig from honcho_cli.output import print_result @@ -24,8 +25,6 @@ def config( """Show current config (api key redacted).""" if ctx.invoked_subcommand is not None: return - from honcho_cli.common import handle_cmd_flags - handle_cmd_flags(json_output=json_output) cfg = CLIConfig.load() print_result(cfg.redacted()) diff --git a/honcho-cli/src/honcho_cli/commands/message.py b/honcho-cli/src/honcho_cli/commands/message.py index 6f51c8c6..d8068a35 100644 --- a/honcho-cli/src/honcho_cli/commands/message.py +++ b/honcho-cli/src/honcho_cli/commands/message.py @@ -11,7 +11,7 @@ from honcho_cli.commands.workspace import _handle_error from honcho_cli.output import print_result, status from honcho_cli.validation import validate_resource_id -from honcho_cli.common import add_common_options +from honcho_cli.common import add_common_options, get_client, handle_cmd_flags app = typer.Typer(help="Message operations.") add_common_options(app) @@ -29,8 +29,6 @@ def list_messages( ) -> None: """List messages in a session.""" from honcho_cli.commands.session import _get_session_id - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) @@ -94,8 +92,6 @@ def get_message( ) -> None: """Get a single message by ID.""" from honcho_cli.commands.session import _get_session_id - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace) validate_resource_id(message_id, "message") diff --git a/honcho-cli/src/honcho_cli/commands/peer.py b/honcho-cli/src/honcho_cli/commands/peer.py index 6c0e260e..86b46982 100644 --- a/honcho-cli/src/honcho_cli/commands/peer.py +++ b/honcho-cli/src/honcho_cli/commands/peer.py @@ -11,14 +11,13 @@ from honcho_cli.commands.workspace import _config_to_dict, _handle_error from honcho_cli.output import print_result, use_json from honcho_cli.validation import validate_resource_id -from honcho_cli.common import add_common_options +from honcho_cli.common import add_common_options, get_client, get_resolved_config, handle_cmd_flags app = typer.Typer(help="Peer debugging operations.") add_common_options(app) def _get_peer_id(peer_id: str | None) -> str: - from honcho_cli.main import get_resolved_config config = get_resolved_config() pid = peer_id or config.peer_id @@ -37,8 +36,6 @@ def list_peers( ) -> None: """List all peers in the workspace.""" from honcho_cli.commands.workspace import _raw_list - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace) client, config = get_client() @@ -67,8 +64,6 @@ def inspect( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Inspect a peer: card, session count, recent conclusions.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) pid = _get_peer_id(peer_id) @@ -112,8 +107,6 @@ def card( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Get raw peer card content.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) pid = _get_peer_id(peer_id) @@ -136,8 +129,6 @@ def chat( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Query the dialectic about a peer.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) pid = _get_peer_id(None) @@ -162,8 +153,6 @@ def search( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Search a peer's messages.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) pid = _get_peer_id(None) @@ -195,8 +184,6 @@ def create_peer( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Create or get a peer.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client from honcho.api_types import PeerConfig handle_cmd_flags(json_output=json_output, workspace=workspace) @@ -237,8 +224,6 @@ def get_metadata( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Get metadata for a peer.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) pid = _get_peer_id(peer_id) @@ -260,8 +245,6 @@ def set_metadata( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Set metadata for a peer.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) pid = _get_peer_id(None) @@ -294,8 +277,6 @@ def representation( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Get the formatted representation for a peer.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer) pid = _get_peer_id(peer_id) diff --git a/honcho-cli/src/honcho_cli/commands/session.py b/honcho-cli/src/honcho_cli/commands/session.py index 7f549837..61ed341d 100644 --- a/honcho-cli/src/honcho_cli/commands/session.py +++ b/honcho-cli/src/honcho_cli/commands/session.py @@ -11,14 +11,13 @@ from honcho_cli.commands.workspace import _config_to_dict, _handle_error from honcho_cli.output import print_result, status, use_json from honcho_cli.validation import validate_resource_id -from honcho_cli.common import add_common_options +from honcho_cli.common import add_common_options, get_client, get_resolved_config, handle_cmd_flags app = typer.Typer(help="Session debugging operations.") add_common_options(app) def _get_session_id(session_id: str | None) -> str: - from honcho_cli.main import get_resolved_config config = get_resolved_config() sid = session_id or config.session_id @@ -38,8 +37,6 @@ def list_sessions( ) -> None: """List sessions in the workspace.""" from honcho_cli.commands.workspace import _raw_list - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace) client, config = get_client() @@ -73,8 +70,6 @@ def inspect( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Inspect a session: peers, message count, summaries, config.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) @@ -112,8 +107,6 @@ def context( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Get session context (what an agent would see).""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) @@ -136,8 +129,6 @@ def summaries( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Get session summaries (short + long).""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) @@ -165,8 +156,6 @@ def delete( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Delete a session and all its data. Destructive — requires --yes or interactive confirm.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) @@ -217,8 +206,6 @@ def session_peers( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """List peers in a session.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) @@ -241,8 +228,6 @@ def add_peers( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Add peers to a session.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace) sid = _get_session_id(session_id) @@ -264,8 +249,6 @@ def remove_peers( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Remove peers from a session.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace) sid = _get_session_id(session_id) @@ -289,8 +272,6 @@ def search( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Search messages in a session.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) @@ -325,8 +306,6 @@ def representation( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Get the representation of a peer within a session.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) @@ -353,8 +332,6 @@ def get_metadata( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Get metadata for a session.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) @@ -377,8 +354,6 @@ def set_metadata( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Set metadata for a session.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace, session=session) sid = _get_session_id(session_id) diff --git a/honcho-cli/src/honcho_cli/commands/setup.py b/honcho-cli/src/honcho_cli/commands/setup.py index 193ab222..2be1897e 100644 --- a/honcho-cli/src/honcho_cli/commands/setup.py +++ b/honcho-cli/src/honcho_cli/commands/setup.py @@ -20,6 +20,7 @@ from honcho_cli.config import ( CLIConfig, ) from honcho_cli.branding import BANNER, BRAND, ICON_FAIL, ICON_OK, ICON_RUN +from honcho_cli.common import get_resolved_config from honcho_cli.output import print_error, print_result _console = Console(stderr=True) @@ -206,8 +207,6 @@ def doctor( if not use_json(): _console.print(f"\n[bold {BRAND}]Honcho Doctor[/bold {BRAND}]\n") - from honcho_cli.main import get_resolved_config - config = get_resolved_config() _add("Config file", CONFIG_FILE.exists(), str(CONFIG_FILE) if CONFIG_FILE.exists() else f"{CONFIG_FILE} not found") diff --git a/honcho-cli/src/honcho_cli/commands/workspace.py b/honcho-cli/src/honcho_cli/commands/workspace.py index f76f3722..2c156848 100644 --- a/honcho-cli/src/honcho_cli/commands/workspace.py +++ b/honcho-cli/src/honcho_cli/commands/workspace.py @@ -9,14 +9,13 @@ import typer from honcho_cli.output import print_error, print_result, status, use_json from honcho_cli.validation import validate_resource_id -from honcho_cli.common import add_common_options +from honcho_cli.common import add_common_options, get_client, get_resolved_config, handle_cmd_flags app = typer.Typer(help="Workspace operations.") add_common_options(app) def _get_workspace_id(workspace_id: str | None) -> str: - from honcho_cli.main import get_resolved_config config = get_resolved_config() wid = workspace_id or config.workspace_id @@ -42,8 +41,6 @@ def list_workspaces( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """List all accessible workspaces.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output) client, config = get_client(require_workspace=False) @@ -63,8 +60,6 @@ def inspect( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Inspect a workspace: peers, sessions, config.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace) @@ -116,8 +111,6 @@ def delete( Requires --yes to skip confirmation, or will prompt interactively. If sessions exist, requires --cascade to delete them first. """ - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output) @@ -175,8 +168,6 @@ def search( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Search messages across workspace.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace) @@ -208,8 +199,6 @@ def queue_status( json_output: bool = typer.Option(False, "--json", help="Force JSON output"), ) -> None: """Get queue processing status.""" - from honcho_cli.common import handle_cmd_flags - from honcho_cli.main import get_client handle_cmd_flags(json_output=json_output, workspace=workspace) diff --git a/honcho-cli/src/honcho_cli/common.py b/honcho-cli/src/honcho_cli/common.py index 189e61f7..c936e3ac 100644 --- a/honcho-cli/src/honcho_cli/common.py +++ b/honcho-cli/src/honcho_cli/common.py @@ -1,4 +1,4 @@ -"""Shared callback and command-level flag helpers. +"""Shared runtime state, client factory, and command-level flag helpers. Flags like --json, -w, -p, -s work in TWO positions: 1. Group-level (before subcommand): honcho workspace --json list @@ -16,18 +16,70 @@ import typer from honcho_cli.output import set_json_mode +# Global overrides from flags (commands read these) +_global_overrides: dict[str, str | None] = { + "workspace": None, + "peer": None, + "session": None, +} + + +def get_resolved_config(): + """Get config with global flag overrides applied. + + Overrides flow through ``validate_resource_id`` so that a malformed + ``-w``/``-p``/``-s`` value fails fast with a structured error rather than + reaching the API and surfacing as an opaque ``UNKNOWN_ERROR``. + """ + from honcho_cli.config import CLIConfig + from honcho_cli.validation import validate_resource_id + + config = CLIConfig.load() + + if _global_overrides["workspace"]: + config.workspace_id = validate_resource_id(_global_overrides["workspace"], "workspace") + if _global_overrides["peer"]: + config.peer_id = validate_resource_id(_global_overrides["peer"], "peer") + if _global_overrides["session"]: + config.session_id = validate_resource_id(_global_overrides["session"], "session") + + return config + + +def get_client(*, require_workspace: bool = True): + """Create a Honcho client from resolved config. + + By default, refuses to build a client when no workspace is scoped — the + SDK's get-or-create semantics would otherwise silently operate on an empty + workspace. Commands that legitimately run without a workspace (e.g. + ``workspace list``) pass ``require_workspace=False``. + """ + from honcho import Honcho + + from honcho_cli.config import get_client_kwargs + from honcho_cli.output import print_error + + config = get_resolved_config() + if require_workspace and not config.workspace_id: + print_error( + "NO_WORKSPACE", + "No workspace scoped. Pass --workspace/-w or set HONCHO_WORKSPACE_ID.", + ) + raise typer.Exit(1) + return Honcho(**get_client_kwargs(config)), config + + def handle_cmd_flags( json_output: bool = False, workspace: str | None = None, peer: str | None = None, session: str | None = None, + **_kwargs, ) -> None: """Apply command-level flags. Idempotent if already set by group callback.""" if json_output: set_json_mode(True) - from honcho_cli.main import _global_overrides - if workspace: _global_overrides["workspace"] = workspace if peer: @@ -50,8 +102,6 @@ def add_common_options(app: typer.Typer) -> None: if json_output: set_json_mode(True) - from honcho_cli.main import _global_overrides - if workspace: _global_overrides["workspace"] = workspace if peer: @@ -60,4 +110,4 @@ def add_common_options(app: typer.Typer) -> None: _global_overrides["session"] = session if ctx.invoked_subcommand is None: - ctx.get_help() + typer.echo(ctx.get_help()) diff --git a/honcho-cli/src/honcho_cli/main.py b/honcho-cli/src/honcho_cli/main.py index 5f8e04cc..460c0a38 100644 --- a/honcho-cli/src/honcho_cli/main.py +++ b/honcho-cli/src/honcho_cli/main.py @@ -11,6 +11,7 @@ import typer from honcho_cli import __version__ from honcho_cli.branding import BANNER, BRAND +from honcho_cli.common import _global_overrides from honcho_cli.output import set_json_mode app = typer.Typer( @@ -75,61 +76,6 @@ def main( raise typer.Exit() -# Global overrides from flags (commands read these) -_global_overrides: dict[str, str | None] = { - "workspace": None, - "peer": None, - "session": None, -} - - -def get_resolved_config(): - """Get config with global flag overrides applied. - - Overrides flow through ``validate_resource_id`` so that a malformed - ``-w``/``-p``/``-s`` value fails fast with a structured error rather than - reaching the API and surfacing as an opaque ``UNKNOWN_ERROR``. - """ - from honcho_cli.config import CLIConfig - from honcho_cli.validation import validate_resource_id - - config = CLIConfig.load() - - if _global_overrides["workspace"]: - config.workspace_id = validate_resource_id(_global_overrides["workspace"], "workspace") - if _global_overrides["peer"]: - config.peer_id = validate_resource_id(_global_overrides["peer"], "peer") - if _global_overrides["session"]: - config.session_id = validate_resource_id(_global_overrides["session"], "session") - - return config - - -def get_client(*, require_workspace: bool = True): - """Create a Honcho client from resolved config. - - By default, refuses to build a client when no workspace is scoped — the - SDK's get-or-create semantics would otherwise silently operate on an empty - workspace. Commands that legitimately run without a workspace (e.g. - ``workspace list``) pass ``require_workspace=False``. - """ - import typer - - from honcho import Honcho - - from honcho_cli.config import get_client_kwargs - from honcho_cli.output import print_error - - config = get_resolved_config() - if require_workspace and not config.workspace_id: - print_error( - "NO_WORKSPACE", - "No workspace scoped. Pass --workspace/-w or set HONCHO_WORKSPACE_ID.", - ) - raise typer.Exit(1) - return Honcho(**get_client_kwargs(config)), config - - # Register top-level commands from honcho_cli.commands.setup import doctor, init