Skip to content

Split into a CLIManagerKit library + unify the skill/command/dotfile models - #2

Open
molayab wants to merge 2 commits into
mainfrom
feature/library-target-and-unified-model
Open

Split into a CLIManagerKit library + unify the skill/command/dotfile models#2
molayab wants to merge 2 commits into
mainfrom
feature/library-target-and-unified-model

Conversation

@molayab

@molayab molayab commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Why

This repo has so far only ever been consumed as a compiled CLI binary. An embedding macOS app
(Grove) wants to manage skills/commands/dotfiles from a native UI, which means linking this
package's model layer directly instead of shelling out and parsing text. That wasn't possible
before this PR β€” everything lived in one executableTarget.

Two changes, together:

  1. Split the package into a CLIManagerKit library (the model/file/git layer) and the
    cli-manager executable (ArgumentParser commands only), so CLIManagerKit can be added as a
    normal SPM dependency by anything that wants programmatic access.
  2. Unify the model layer β€” SkillModel, CommandModel, DotfileModel, and
    UserCommandModel become one ManagedItem (+ ManagedItemKind) and one AgentDescriptor,
    instead of three near-duplicate families. A consumer now has one type to work with, not three.

The CLI itself doesn't change. Every subcommand, flag, and message is the same β€” this is an
internal restructuring, not a UX change. swift build, swift test (35 tests), and
swiftlint lint all pass clean.

Architecture

flowchart TD
    subgraph exe["cli-manager (executable target)"]
        CLI["ArgumentParser commands<br/>skill / command / dotfile / sync / repo / push / pull / clean"]
        Term["Terminal.swift<br/>prompts, colored output"]
    end

    subgraph lib["CLIManagerKit (library target, new)"]
        MI["ManagedItem<br/>ManagedItemKind: skill / command / dotfile"]
        AD["AgentDescriptor<br/>allAgentDescriptors"]
        FS["FileManager+Helpers<br/>Frontmatter, GitRunner"]
    end

    CLI --> MI
    CLI --> AD
    CLI --> Term
    MI --> FS
    AD --> FS

    Grove["Grove (future)"] -.->|links CLIManagerKit directly, no shell-out| MI
    Grove -.-> AD
Loading
classDiagram
    class ManagedItemKind {
        <<enumeration>>
        skill
        command
        dotfile
    }
    class ManagedItem {
        +ManagedItemKind kind
        +String id
        +String name
        +String description
        +String body
        +Bool isPrivate
        +URL location
        +String dotfileLink
        +String dotfileFileName
        +load(kind)
        +resolve(filter, from)
    }
    class AgentDescriptor {
        +String id
        +String name
        +URL skillsPath
        +URL commandsPath
        +CommandFormat commandFormat
        +path(kind)
        +detected(kind)
        +resolve(ids, kind)
    }
    ManagedItem --> ManagedItemKind
    AgentDescriptor --> ManagedItemKind
Loading

ManagedItem replaces the three old models β€” a skill and a dotfile are both a directory
(SKILL.md/DOTFILE.md inside), a command is a single file; dotfileLink/dotfileFileName are
the only kind-specific fields, populated only for .dotfile. AgentDescriptor replaces the old
Agent array (skills) and CommandModel.allCommandAgents array (commands) with one canonical
7-agent registry β€” which also fixes a real bug: "opencode" was listed twice, at two
different skill paths (.config/opencode/skills and a stray .agents/skills), a copy-paste
leftover from when the list grew. It's one entry now, matching the README's own agent table.

How to use

The CLI is unchanged:

cli-manager skill activate
cli-manager command activate --agent gemini-cli
cli-manager dotfile link

New: any Swift package can now depend on the model layer directly β€”

.package(url: "https://github.com/molayab/swift-cli-manager", branch: "main")
// ...
.product(name: "CLIManagerKit", package: "swift-cli-manager")
import CLIManagerKit

let (skills, diagnostics) = ManagedItem.load(.skill)
let agentsWithClaude = AgentDescriptor.detected(for: .skill)

ManagedItem.load never prints β€” it returns diagnostics: [String] for anything it skipped
(e.g. a dotfile missing its link: field) so an embedding app can surface them however it wants,
instead of inheriting the CLI's own colored console output.

Testing

  • swift build β€” clean
  • swift test β€” 35/35 passing (ManagedItemTests and AgentDescriptorTests are new; the old
    per-model test files were folded into them)
  • swiftlint lint β€” clean except two pre-existing warnings in unchanged, moved-as-is code
  • Manually exercised skill new/list, command new/list, dotfile status, skill activate,
    command activate (including the Gemini TOML path), and clean --dry-run against a scratch
    repo/home to confirm output is byte-for-byte the same as before the refactor

…models

Restructures the package so the model layer can be linked as a library by an
embedding app (an upcoming Grove integration), and collapses SkillModel,
CommandModel, DotfileModel, and UserCommandModel into one generic
ManagedItem/AgentDescriptor pair instead of three near-duplicate families.

- New CLIManagerKit library target/product: ManagedItem (+ ManagedItemKind),
  AgentDescriptor, and the pure file/frontmatter/git helpers. cli-manager is
  now a thin ArgumentParser executable depending on it.
- Fixes a latent bug found while consolidating the agent registry: "opencode"
  was listed twice with two different skill paths.
- CLI surface, flags, and output are unchanged β€” this is an internal
  restructuring, not a behavior change.
- Tests updated to match (35 passing); swift build/test/swiftlint all clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant