Skip to content

Split gor into reusable gor-core library + gor CLI (typed operations) #2

Description

@kerryhatcher

Summary

Split gor into a reusable gor-core library crate and a thin gor CLI crate so other Rust applications can depend on gor's GitHub functionality directly — calling typed operations that return structured data — instead of shelling out to the gor binary or reimplementing the GitHub API layer themselves.

Problem

Today gor is a single crate with a lib.rs + main.rs split, but the "library" only exposes Gor::run() (parse args, dispatch). Every command in src/cmd/* entangles three concerns:

  1. clap argument structs,
  2. GitHub API calls, and
  3. printing tables/JSON to stdout.

Operations return () and print directly; there are no typed models (raw serde_json::Value throughout). A consumer crate that wants "list labels" gets printed text, not Vec<Label>. The only reuse paths available are shelling out to the CLI or reimplementing the API layer.

Proposed solution

A Cargo workspace with two crates:

  • gor-core — publishable library: Client, auth, host, config, repository detection, keyring, error types, and one module per GitHub domain holding both its lean typed model and its typed operations. No CLI dependencies.
  • gor — binary: clap definitions, rendering (tables + JSON), and thin handlers that resolve args → call a gor-core op → render.

Locked decisions

Axis Decision
Reuse target Typed operations returning models, not just a raw client
Packaging Cargo workspace: gor-core (lib) + gor (bin)
Scope Everything — all 24 command domains converted
Model fidelity Lean structs: hand-written fields gor uses + #[serde(flatten)] extra capture-rest

Dependency split

gor-core keeps: reqwest, serde, serde_json, serde_yaml_ng, keyring (optional), thiserror, dirs, gix, tracing.

gor-core drops (they move to the gor bin): clap, clap_complete, indicatif, console, anyhow.

Reusable pattern (per domain)

#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Label {
    pub name: String,
    pub color: String,
    pub description: Option<String>,
    #[serde(flatten)]
    pub extra: serde_json::Map<String, serde_json::Value>, // capture-rest
}

pub fn list(client: &Client, spec: &RepoSplit, opts: &ListOptions)
    -> Result<Vec<Label>, GorError>;

Consumer usage:

let client = gor_core::client::Client::new("github.com")?;
let spec = gor_core::repository::parse_repo_spec("owner/repo")?;
let labels = gor_core::label::list(&client, &spec, &Default::default())?;

Rules: ops return Result<T, GorError> (never anyhow, never print); status→error mapping (404 → NotFound, etc.) lives in the op; models are #[non_exhaustive] + capture-rest so API additions don't break consumers.

Stays bin-only (no reusable API surface)

completion, alias, browse, extension, config (wraps gor_core::config), interactive auth prompting (token exchange itself moves to core), and api (thin raw-request passthrough).

Migration plan

  1. Create the workspace; move shared core files into crates/gor-core, move cli.rs / output.rs (→ render.rs) / main.rs / cmd/ into crates/gor. Compile green, no behavior change.
  2. Convert label end to end as the template.
  3. Convert the remaining 23 domains one at a time — each independently reviewable, build never breaking.
  4. Delete dead helpers left in the bin.

Testing

  • Existing CLI integration tests (assert_cmd, insta) are the safety net — CLI behavior/output must not change.
  • Add per-op unit tests against wiremock asserting typed data and correct GorError variant mapping.
  • Doctests on public gor-core items (crate keeps #![deny(missing_docs)]).

Non-goals

  • No new commands or features.
  • No change to CLI flags, output format, or config format.
  • No async conversion — stays reqwest::blocking.
  • No full faithful GitHub models (every documented field) — lean structs + capture-rest instead.

Notes / open questions

  • #![deny(missing_docs)] + unwrap_used = "deny" carry into gor-core — every public model field/op needs docs. Real cost, sequenced per-domain.
  • Library crate name gor-core assumed; open to alternatives.

Full design spec committed on branch refactor/split-core-library at docs/superpowers/specs/2026-07-20-core-library-split-design.md.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions