Skip to content

refactor(cli): split command execution out of run.rs #2304

Closed as not planned
Closed as not planned

Description

@elezar

Problem Statement

crates/openshell-cli/src/run.rs has become the central implementation file for most CLI command execution, rendering helpers, parsing helpers, and inline tests. That makes unrelated CLI changes collide in the same file and makes review harder because command behavior, shared utilities, and tests are interleaved.

Current measurements from the feat/gateway-status-driver-info branch:

  • run.rs is 10,103 lines out of 21,091 total lines in crates/openshell-cli/src, about 48% of the CLI source.
  • The next largest CLI files are main.rs at 4,942 lines and ssh.rs at 2,356 lines.
  • run.rs contains about 199 function definitions, including 60 pub async fn command-style entry points and 13 pub fn entry points.
  • The inline test module starts around line 8,058 and contains about 2,046 lines with 95 test functions.
  • In the last six months, run.rs was touched by 88 commits, with roughly 12,827 insertions and 2,724 deletions.
  • main.rs was touched by 71 commits in the same period, and 59 commits touched both main.rs and run.rs, which suggests frequent parser/dispatch/execution coupling.

This matters because CLI work is a high-churn area. A single large execution file increases merge conflicts, makes small behavior changes look broad, and makes command-specific tests harder to find.

Proposed Design

Split CLI command execution incrementally by command group while preserving behavior.

Suggested target structure:

crates/openshell-cli/src/
  main.rs              # clap parser, global setup, dispatch
  run.rs               # temporary compatibility layer or shared entrypoint during migration
  commands/
    mod.rs
    gateway.rs
    sandbox.rs
    provider.rs
    service.rs
    policy.rs
    settings.rs

Start with the gateway command group because it is actively changing and has a natural boundary around gateway registration, selection, status, info, auth, and inference. Move gateway-specific tests with the gateway module. Keep shared utilities, output formatting, TLS/auth setup, and gRPC client construction in existing shared modules or extract them only when multiple command groups need them.

The refactor should be mechanical and behavior-preserving:

  • Move command execution functions by command group.
  • Move command-specific renderers and JSON helpers with their command group.
  • Move tests close to the command implementation they cover.
  • Avoid introducing a command trait or dynamic registry unless a later design requires plugin-style CLI commands.
  • Keep public CLI behavior unchanged except for any already-planned command changes in separate commits.

Definition of done:

  • Gateway command execution is moved out of run.rs.
  • Gateway command tests move with the implementation.
  • Shared helpers remain accessible without circular dependencies.
  • No user-facing CLI behavior changes are introduced by the refactor.
  • Focused CLI tests and cargo fmt --check pass.

Alternatives Considered

Keep run.rs as-is:

  • Lowest immediate risk.
  • Does not address the high churn and conflict surface.

Split into one file per individual command:

  • Gives maximum isolation.
  • Likely too granular at first; command groups are a better intermediate shape for OpenShell's current CLI size.

Introduce a command trait/registry:

  • Useful for plugin-like dynamic commands.
  • More abstraction than needed for a static clap CLI and would make a mechanical refactor harder to review.

Agent Investigation

  • Reviewed crates/openshell-cli/src/run.rs, main.rs, and surrounding CLI modules.
  • Counted CLI source lines and found run.rs is about 48% of crates/openshell-cli/src.
  • Counted function/test density in run.rs: about 199 function definitions and 95 test functions.
  • Reviewed six-month git churn for run.rs and main.rs; found 88 commits touching run.rs, 71 touching main.rs, and 59 touching both.
  • Searched open issues for run.rs CLI refactor split commands; no duplicates were found.

Checklist

  • I've reviewed existing issues and the architecture docs
  • This is a design proposal, not a "please build this" request

Activity

  1. varshaprasad96 commented on Jul 17, 2026

    @varshaprasad96
    Contributor

    📋 triage-agent

    Triage Assessment

    Classification: feature-valid

    Summary

    This is a well-diagnosed, legitimate refactoring proposal. Every quantitative claim in the issue was independently verified against the current codebase — run.rs is exactly 10,103 lines (48% of CLI source), contains 199 function definitions and 95 test functions, and was touched by 87–88 commits in the last six months. The proposed commands/ directory structure directly mirrors the natural function clusters already present in the file.

    Investigation

    The functions in run.rs group cleanly by command domain: gateway (~30 functions), sandbox (~40 functions), provider (~20 functions), service (~10 functions), plus shared helpers at the top of the file (lines 117–505). The 82 run:: call sites in main.rs confirm the tight coupling. No duplicate issues exist — the closest (#1226) targeted the server-side gRPC trait split, not the CLI command layer.

    One consideration: the shared helper functions at the top of run.rs (phase_name, format_elapsed, format_timestamp, progress display helpers, print_sandbox_header) are used across command groups. The migration plan should explicitly address where these land (e.g., commands/common.rs) to prevent import cycles in early PRs.

    Recommendation

    Accept the issue. The incremental approach — starting with the gateway command group, keeping run.rs as a temporary re-export compatibility layer — is sound and minimizes review risk. This is a clean, bounded refactoring task suitable for incremental PRs.

  2. varshaprasad96 commented on Jul 17, 2026

    @varshaprasad96
    Contributor

    @elezar I can take up this issue and work on refactoring PRs.

  3. elezar commented on Jul 17, 2026

    @elezar
    MemberAuthor

    @varshaprasad96 sure. Please ping me for review on the proposal as well as the PRs.

  4. varshaprasad96 commented on Jul 20, 2026

    @varshaprasad96
    Contributor

    Since the change here is only a refactor, RFC seems to not be required according to contribution guideline. I'm using this comment as a meta issue to create self contained sequence of 6 PRs addressing the issue. Please feel free to comment/add your thoughts.

    Proposed PR Sequence

    After reviewing run.rs in detail — function groupings, cross-group dependencies, and call sites from main.rs — here's an incremental split plan. Each PR leaves the codebase compiling and tests passing.

    PR 1: Scaffolding + shared helpers

    Create commands/mod.rs and commands/common.rs. Move into common.rs:

    • Formatting/display helpers: phase_name, format_epoch_ms, format_elapsed, format_timestamp, print_yaml_line, print_sandbox_policy, short_hash, non_empty_or, format_timestamp_ms, format_optional_epoch_ms, truncate_status_field, truncate_display
    • Parsing utilities: parse_env_pairs, parse_key_value_pairs, parse_secret_material_env_pairs, parse_credential_*, parse_cli_setting_value, parse_duration_to_ms
    • Shared types: PolicyGetView, SandboxUploadPlan, ProvisioningStep, ProvisioningDisplay, SandboxCreateConfig, ForwardTcpConnectionError
    • Shared confirm helpers: confirm_global_setting_takeover, confirm_global_setting_delete

    run.rs re-exports everything from commands::common so all existing callers (including main.rs) keep working unchanged. This is the foundation — every later PR depends on it.

    PR 2: Gateway commands

    Move all gateway functions (~25 functions, lines 553–1726) and their tests (~20 tests) into commands/gateway.rs. This is the cleanest group to extract — almost no inbound dependencies from other command groups, only uses shared helpers already in common.rs. run.rs re-exports commands::gateway::*.

    PR 3: Provider commands

    Move provider commands (~20 public functions), the auto-detection helpers (inferred_provider_type, ensure_required_providers, auto_create_provider), and provider-specific tests into commands/provider.rs. The auto-detection helpers live here because they're provider-domain logic — sandbox_create will import from commands::provider when it moves later.

    PR 4: Service + Settings + Inference

    Three small groups bundled into one PR:

    • Service → commands/service.rs (includes service_forward_tcp and its TCP forwarding helpers)
    • Settings → commands/settings.rs
    • Inference → commands/inference.rs

    fetch_ready_sandbox_for_forward moves to common.rs since it bridges service and sandbox.

    PR 5: Policy + Logs + Draft

    • Policy (~10 functions) → commands/policy.rs
    • Logs → commands/logs.rs
    • Draft → commands/draft.rs

    print_sandbox_policy is already in common.rs from PR 1, so the cross-ref between sandbox_get and sandbox_policy_update is clean.

    PR 6: Sandbox + cleanup

    Move sandbox commands (~15 functions) and sandbox_provider sub-commands into commands/sandbox.rs. This is the most tangled group — doing it last means all its dependencies (provider auto-detection, policy display, settings parsing) are already in their final homes. Remove or shrink the run.rs re-export shim. Move git/upload helpers into common.rs or commands/upload.rs.

    Why this order

    • Shared helpers first prevents import cycles in every subsequent PR.
    • Gateway first because it's the stated priority and the cleanest boundary.
    • Sandbox last because it has the most cross-group calls (into provider, policy, settings) — by the time we get there, all targets are stable.
    • The main tricky cross-call is sandbox_create → provider auto-detection (ensure_required_providers, inferred_provider_type). PR 3 exports those publicly from commands/provider.rs; PR 6 imports them when sandbox moves.

    Target structure

    crates/openshell-cli/src/
      main.rs
      run.rs              # shrinks to re-exports during migration, removed or minimal at the end
      commands/
        mod.rs
        common.rs          # shared helpers, types, parsing, formatting
        gateway.rs
        provider.rs
        sandbox.rs
        service.rs
        settings.rs
        inference.rs
        policy.rs
        logs.rs
        draft.rs
    
  5. github-actions commented on Aug 28, 2026

    @github-actions

    This issue has had no activity for 14 days and is now marked stale. It may be closed in 7 days if there is no further activity. Comment or remove the state:stale label to keep it open.

  6. varshaprasad96 commented on Sep 4, 2026

    @varshaprasad96
    Contributor

    🏗️ build-plan

    Implementation Plan

    Issue type: refactor
    Complexity: Medium
    Confidence: High — boundaries and call sites are clear

    Summary

    Extract the next three command groups from run.rs into dedicated service, settings, and inference modules without changing CLI behavior or dispatch. Preserve the existing run::* compatibility surface so main.rs remains unchanged.

    Scope

    • commands/service.rs: Move service forwarding, CRUD/exposure commands, TCP relay/session helpers, renderers, JSON conversion, and service tests.
    • commands/settings.rs: Move the six gateway/sandbox settings commands and JSON renderers.
    • commands/inference.rs: Move the four inference route commands and their display/error helpers.
    • commands/common.rs: Keep shared setting parsing/confirmation helpers here and relocate their remaining tests from run.rs.
    • commands/mod.rs: Register the three modules.
    • run.rs: Re-export moved entry points and remove moved implementations/tests/imports.
    • main.rs: No changes; existing run::* calls continue compiling.

    fetch_ready_sandbox_for_forward is only used by service forwarding, so it remains private in service.rs.

    Implementation Steps

    1. Create a clean branch from current upstream/main.
    2. Extract service commands and forwarding helpers unchanged, preserving health checks, token cleanup, error classification, stream shutdown, and TCP_NODELAY.
    3. Extract settings commands while retaining shared parsing, formatting, and confirmation helpers in common.rs.
    4. Extract inference commands and display/error helpers unchanged.
    5. Register modules, preserve run.rs re-exports, prune imports, and move/add focused tests.
    6. Run focused tests, the CLI suite, pre-commit, and local CI.

    Test Plan

    • Unit: Move six service tests; add TCP parsing/error-classification, settings JSON, and inference-status tests; move three common setting-parser tests.
    • Integration: Run cargo test -p openshell-cli; existing parser/dispatch and RPC integration tests cover compatibility.
    • E2E: N/A — no gateway, protocol, sandbox, policy, or infrastructure behavior changes.
    • Verification: mise run pre-commit, mise run test, and mise run ci.

    Risks & Open Questions

    • Service forwarding spans two noncontiguous regions; all cancellation and token-cleanup behavior must move intact.
    • Compatibility requires every moved public function to remain re-exported from run.rs.
    • No stakeholder decision is required.

    Documentation Impact

    None expected; commands, flags, output, RPCs, and workflows remain unchanged.

    Gateway Configuration Impact

    None.

    LSM Compatibility

    No impact.


    Revision 1 — initial plan for PR 4

  7. github-actions commented on Sep 18, 2026

    @github-actions

    This issue has had no activity for 14 days and is now marked stale. It may be closed in 7 days if there is no further activity. Comment or remove the state:stale label to keep it open.

  8. github-actions commented on Oct 13, 2026

    @github-actions

    Closing this issue after 14 days with no activity since it was marked stale. Reopen it if the work is still relevant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

state:staleInactive item at risk of automatic closure.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions