Class: Rubino::Commands::Loader

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/commands/loader.rb

Overview

Discovers and manages custom slash commands from configured paths.

Constant Summary collapse

COMMAND_GLOB =
"*.md"
BARE_SLASH_COMMAND =

A bare ‘/` (or slash + only whitespace) is a misfire, not a prompt and not a command: it parses to the built-in `commands` listing so it shows the roster instead of burning an LLM turn (bare `/`, which would parse to a nil name) or reporting “unknown command: /” (`/ foo`, which would parse to an empty command name). Keeps the misfire handled in one place so the REPL dispatcher needs no special case.

"commands"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config: nil) ⇒ Loader

Returns a new instance of Loader.



9
10
11
12
13
# File 'lib/rubino/commands/loader.rb', line 9

def initialize(config: nil)
  @config = config || Rubino.configuration
  @commands = {}
  @discovered = false
end

Class Method Details

.default_command_pathsObject

Default search paths, with the home sentinel resolved to a real dir. Used by the loader and the “/commands” empty-state copy so both report the directories actually searched (RUBINO_HOME-aware).



85
86
87
# File 'lib/rubino/commands/loader.rb', line 85

def self.default_command_paths
  Array(Config::Defaults.to_hash.dig("commands", "paths")).map { |p| resolve_path(p) }
end

.resolve_path(dir) ⇒ Object

Resolves a configured commands path to an absolute directory, expanding the <RUBINO_HOME>/commands sentinel against the resolved home (RUBINO_HOME -> else ~/.rubino) instead of a literal ~/.rubino (#38).



92
93
94
95
96
97
98
99
# File 'lib/rubino/commands/loader.rb', line 92

def self.resolve_path(dir)
  if dir.to_s.start_with?(Config::Defaults::HOME_COMMANDS_PATH)
    suffix = dir.to_s.sub(Config::Defaults::HOME_COMMANDS_PATH, "")
    File.join(Config::Loader.default_home_path, "commands#{suffix}")
  else
    File.expand_path(dir)
  end
end

Instance Method Details

#allObject

Returns all discovered commands



32
33
34
35
# File 'lib/rubino/commands/loader.rb', line 32

def all
  discover! unless @discovered
  @commands.values
end

#discover!Object

Discovers all available commands



16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/rubino/commands/loader.rb', line 16

def discover!
  @commands.clear
  command_paths.each do |dir|
    expanded = self.class.resolve_path(dir)
    next unless File.directory?(expanded)

    Dir.glob(File.join(expanded, COMMAND_GLOB)).each do |path|
      cmd = Command.new(path: path)
      @commands[cmd.name] = cmd
    end
  end
  @discovered = true
  @commands
end

#find(name) ⇒ Object

Finds a command by name (without the leading /)



38
39
40
41
# File 'lib/rubino/commands/loader.rb', line 38

def find(name)
  discover! unless @discovered
  @commands[name.to_s.sub(%r{\A/}, "")]
end

#namesObject

Returns command names for autocomplete



72
73
74
# File 'lib/rubino/commands/loader.rb', line 72

def names
  all.map { |c| "/#{c.name}" }
end

#parse(input) ⇒ Object

Parses a slash command input into [command_name, arguments]



57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/rubino/commands/loader.rb', line 57

def parse(input)
  stripped = input.strip
  return nil unless stripped.start_with?("/")

  parts = stripped[1..].split(/\s+/, 2)
  command_name = parts[0]
  arguments = parts[1] || ""
  # `/` alone, or `/` followed by only whitespace, has no command name —
  # show the roster rather than dispatch a real turn / "unknown command".
  return [BARE_SLASH_COMMAND, ""] if command_name.nil? || command_name.empty?

  [command_name, arguments]
end

#slash_command?(input) ⇒ Boolean

Returns true if input starts with a slash command

Returns:

  • (Boolean)


44
45
46
# File 'lib/rubino/commands/loader.rb', line 44

def slash_command?(input)
  input.strip.start_with?("/")
end