Module: Lyman::CLI::Registry

Defined in:
lib/lyman/cli/registry.rb

Overview

What lyman installs. Everything the generator plants into a client project is declared here — this list, not directory layout, is the boundary between what lyman does (lib/lyman/cli) and what lyman installs (everything named below).

Constant Summary collapse

GEM_ROOT =
File.expand_path("../../..", __dir__)
ARTIFACTS =
{
  "lyman_entry" => {
    source: "templates/lib_lyman.rb",
    dest: "lib/lyman.rb",
    role: :managed,
    description: "Library entry point; requires shifty and every planted module"
  },
  "conversation" => {
    source: "lib/lyman/conversation.rb",
    dest: "lib/lyman/conversation.rb",
    role: :managed,
    description: "The item that flows through pipelines"
  },
  "chat_completion" => {
    source: "lib/lyman/workers/chat_completion.rb",
    dest: "lib/lyman/workers/chat_completion.rb",
    role: :managed,
    description: "Relay worker: OpenAI-compatible chat completions (streaming + blocking)"
  },
  "tool_execution" => {
    source: "lib/lyman/workers/tool_execution.rb",
    dest: "lib/lyman/workers/tool_execution.rb",
    role: :managed,
    description: "Relay worker: executes pending tool calls"
  },
  "harness" => {
    source: "harness/chat.rb",
    dest: "harness/chat.rb",
    role: :owned,
    description: "The wiring script — yours from day one; lyman never updates it"
  },
  "claude_md" => {
    source: "templates/CLAUDE.md",
    dest: "CLAUDE.md",
    role: :owned,
    alternative: "claude_skill",
    description: "Guidance for coding agents working in this project"
  },
  # The same guidance as claude_md, packaged as a Claude Code skill —
  # for projects that already have a CLAUDE.md lyman shouldn't clobber.
  # `optional:` keeps `new` from planting it (a fresh scaffold gets
  # claude_md instead); `alternative:` on claude_md points here when
  # `add` refuses to overwrite an existing CLAUDE.md.
  "claude_skill" => {
    source: "templates/SKILL.md",
    dest: ".claude/skills/lyman/SKILL.md",
    role: :owned,
    optional: true,
    description: "Claude Code skill variant of the CLAUDE.md guidance — for projects with their own CLAUDE.md"
  },
  "gemfile" => {
    source: "templates/Gemfile",
    dest: "Gemfile",
    role: :owned,
    description: "Client dependencies: shifty (and ostruct for ruby >= 4)"
  },
  "gitignore" => {
    source: "templates/gitignore",
    dest: ".gitignore",
    role: :owned,
    description: "A minimal starter .gitignore (.lyman/ stays tracked on purpose)"
  }
}.freeze

Class Method Summary collapse

Class Method Details

.defaultObject

What new plants: everything except opt-in artifacts (those exist for situations a fresh scaffold can't be in, like a pre-existing CLAUDE.md — reach them with lyman add).



109
110
111
# File 'lib/lyman/cli/registry.rb', line 109

def self.default
  ARTIFACTS.reject { |_, spec| spec[:optional] }
end

.fetch(name) ⇒ Object



74
75
76
77
78
79
# File 'lib/lyman/cli/registry.rb', line 74

def self.fetch(name)
  ARTIFACTS.fetch(name) do
    valid = ARTIFACTS.keys.join(", ")
    raise Thor::Error, "Unknown artifact #{name.inspect}. Valid artifacts: #{valid}"
  end
end

.managedObject



102
103
104
# File 'lib/lyman/cli/registry.rb', line 102

def self.managed
  ARTIFACTS.select { |_, spec| spec[:role] == :managed }
end

.resolve(token, manifest: nil) ⇒ Object

Commands accept an artifact name or a project-relative path — you shouldn't have to remember the token while looking at the file. Resolution order: registry name, then the path recorded in this project's manifest (authoritative for where the file actually is), then the registry's dest (for artifacts not yet planted).

Raises:

  • (Thor::Error)


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/lyman/cli/registry.rb', line 86

def self.resolve(token, manifest: nil)
  return token if ARTIFACTS.key?(token)

  path = token.delete_prefix("./")
  if manifest
    name, _entry = manifest.artifacts.find { |_, entry| entry["path"] == path }
    return name if name
  end
  name, _spec = ARTIFACTS.find { |_, spec| spec[:dest] == path }
  return name if name

  valid = ARTIFACTS.keys.join(", ")
  raise Thor::Error, "Unknown artifact #{token.inspect}. " \
    "Give an artifact name (#{valid}) or a planted path (e.g. lib/lyman/conversation.rb)."
end

.source_path(spec, source_root: GEM_ROOT) ⇒ Object



113
114
115
# File 'lib/lyman/cli/registry.rb', line 113

def self.source_path(spec, source_root: GEM_ROOT)
  File.join(source_root, spec[:source])
end