llm-shell 0.9.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/llm/function.rb DELETED
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Function
4
- ##
5
- # Returns true when a function is a built-in function
6
- # @return [Boolean]
7
- def builtin?
8
- @builtin
9
- end
10
-
11
- ##
12
- # Mark a function as a built-in function
13
- # @return [void]
14
- def builtin!
15
- @builtin = true
16
- end
17
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell::Command
4
- ##
5
- # The {LLM::Shell::Command::Extension LLM::Shell::Command::Extension}
6
- # module extends the `LLM` constant with methods that can provide shell
7
- # commands for an active llm-shell session.
8
- #
9
- # @example hello command
10
- # LLM.command(:hello) do |cmd|
11
- # cmd.define do |name|
12
- # io.rewind.print("Hello #{name}")
13
- # end
14
- # end
15
- module Extension
16
- ##
17
- # @example
18
- # LLM.command(:hello) do |cmd|
19
- # cmd.define do |name|
20
- # io.rewind.print("Hello #{name}")
21
- # end
22
- # end
23
- # @param [String] name
24
- # The name of the command
25
- # @yieldparam [LLM::Shell::Command] cmd
26
- # Yields an instance of LLM::Shell::Command
27
- # @return [void]
28
- def command(name)
29
- cmd = LLM::Shell::Command.new
30
- cmd.name(name)
31
- yield cmd
32
- commands[cmd.name] = cmd
33
- end
34
-
35
- ##
36
- # @return [Hash<String, LLM::Shell::Command>]
37
- def commands
38
- @commands ||= {}
39
- end
40
- end
41
- LLM.extend(Extension)
42
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell
4
- class Command
5
- require_relative "commands/utils"
6
-
7
- ##
8
- # @api private
9
- class Context < Struct.new(:bot, :io)
10
- def pager(...)
11
- LLM::Shell.pager(...)
12
- end
13
- end
14
-
15
- ##
16
- # Returns the underlying command object
17
- # @return [Class, #call]
18
- attr_reader :object
19
-
20
- ##
21
- # Set or get the command name
22
- # @param [String, nil] name
23
- # The name of the command
24
- # @return [String]
25
- def name(name = nil)
26
- if name
27
- @name = name
28
- else
29
- @name
30
- end
31
- end
32
-
33
- ##
34
- # Set or get the command description
35
- # @param [String, nil] desc
36
- # The description of the command
37
- # @return [String]
38
- def description(desc = nil)
39
- if desc
40
- @description = desc
41
- else
42
- @description
43
- end
44
- end
45
-
46
- ##
47
- # Setup the command context
48
- # @return [void]
49
- def setup(bot, io)
50
- @context = Context.new(bot, io)
51
- end
52
-
53
- ##
54
- # Define the command
55
- # @return [void]
56
- def define(klass = nil, &b)
57
- @object = klass || b
58
- end
59
- alias_method :register, :define
60
-
61
- ##
62
- # Call the command
63
- # @reurn [void]
64
- def call(*argv)
65
- if @context.nil?
66
- raise "context has not been setup"
67
- elsif Class === @object
68
- @object.new(@context).call(*argv)
69
- else
70
- @context.instance_exec(*argv, &@object)
71
- end
72
- end
73
-
74
- ##
75
- # @return [Boolean]
76
- # Returns true if this is a builtin command
77
- def builtin?
78
- @builtin
79
- end
80
-
81
- ##
82
- # Mark this command as builtin command
83
- # @return [void]
84
- def builtin!
85
- @builtin = true
86
- end
87
- end
88
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell::Command
4
- class ClearScreen
5
- require_relative "utils"
6
- include Utils
7
-
8
- ##
9
- # @param [LLM::Shell::Context] context
10
- # The context of the command
11
- # @return [LLM::Shell::Command::ClearScreen]
12
- def initialize(context)
13
- @context = context
14
- end
15
-
16
- ##
17
- # Clears the screen
18
- # @return [void]
19
- def call = clear_screen
20
-
21
- private
22
-
23
- def console = IO.console
24
- def clear_screen = console.clear_screen
25
-
26
- LLM.command "clear-screen" do |cmd|
27
- cmd.description "Clear the screen"
28
- cmd.register(self)
29
- cmd.builtin!
30
- end
31
- end
32
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell::Command
4
- class DirImport
5
- require_relative "utils"
6
- include Utils
7
-
8
- ##
9
- # Completes a path with a wildcard.
10
- # @param path [String]
11
- # The path to complete.
12
- # @return [Array<String>]
13
- # Returns the completed path(s)
14
- def self.complete(path)
15
- Dir["#{path}*"]
16
- end
17
-
18
- ##
19
- # @param [LLM::Shell::Context] context
20
- # The context of the command
21
- # @return [LLM::Shell::Command::DirImport]
22
- def initialize(context)
23
- @context = context
24
- end
25
-
26
- ##
27
- # Recursively imports all files in a directory.
28
- # @return [void]
29
- def call(dir)
30
- Dir.entries(dir).each do |file|
31
- if file == "." || file == ".."
32
- next
33
- elsif File.directory? File.join(dir, file)
34
- call File.join(dir, file)
35
- else
36
- import File.join(dir, file)
37
- end
38
- end
39
- end
40
-
41
- private
42
-
43
- LLM.command "dir-import" do |cmd|
44
- cmd.description "Share a directory with the LLM"
45
- cmd.register(self)
46
- cmd.builtin!
47
- end
48
- end
49
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell::Command
4
- class FileImport
5
- require_relative "utils"
6
- include Utils
7
-
8
- ##
9
- # Completes a path with a wildcard.
10
- # @param path [String]
11
- # The path to complete.
12
- # @return [Array<String>]
13
- # Returns the completed path(s)
14
- def self.complete(path)
15
- Dir["#{path}*"]
16
- end
17
-
18
- ##
19
- # @param [LLM::Shell::Context] context
20
- # The context of the command
21
- # @return [LLM::Shell::Command::FileImport]
22
- def initialize(context)
23
- @context = context
24
- end
25
-
26
- ##
27
- # Imports one or more globbed files.
28
- # @return [void]
29
- def call(*files)
30
- Dir[*files].each { import(_1) }
31
- end
32
-
33
- private
34
-
35
- LLM.command "file-import" do |cmd|
36
- cmd.description "Share a file with the LLM"
37
- cmd.register(self)
38
- cmd.builtin!
39
- end
40
- end
41
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell::Command
4
- class Help
5
- require_relative "utils"
6
- include Utils
7
-
8
- ##
9
- # @param [LLM::Shell::Context] context
10
- # The context of the command
11
- # @return [LLM::Shell::Command::Help]
12
- def initialize(context)
13
- @context = context
14
- end
15
-
16
- ##
17
- # Prints help
18
- # @return [void]
19
- def call
20
- pager do |io|
21
- render_commands(io)
22
- render_functions(io)
23
- end
24
- end
25
-
26
- private
27
-
28
- def render_commands(io)
29
- io.print(Paint["Commands", :bold, :underline], "\n\n")
30
- io.print(Paint["Builtin", :bold], "\n\n")
31
- render_group commands.select(&:builtin?), io, :cyan
32
- io.print(Paint["User", :bold], "\n\n")
33
- render_group commands.reject(&:builtin?), io, :cyan
34
- end
35
-
36
- def render_functions(io)
37
- io.print(Paint["Functions", :bold, :underline], "\n\n")
38
- io.print(Paint["Builtin", :bold], "\n\n")
39
- render_group functions.select(&:builtin?), io, :blue
40
- io.print(Paint["User", :bold], "\n\n")
41
- render_group functions.reject(&:builtin?), io, :blue
42
- end
43
-
44
- def render_group(commands, io, bgcolor)
45
- if commands.empty?
46
- io.print(Paint["None available", :yellow], "\n\n")
47
- else
48
- commands.each.with_index(1) do |command, index|
49
- io.print(name(command, index, bgcolor), "\n")
50
- io.print(desc(command), "\n\n")
51
- end
52
- end
53
- end
54
-
55
- def commands = LLM.commands.values.sort_by(&:name)
56
- def functions = LLM.functions.values.sort_by(&:name)
57
- def name(command, index, bgcolor) = [Paint[" #{index} ", :white, bgcolor, :bold], " ", Paint[command.name, :bold]].join
58
- def desc(command) = command.description || "No description"
59
-
60
- LLM.command "help" do |cmd|
61
- cmd.description "Show the help menu"
62
- cmd.register(self)
63
- cmd.builtin!
64
- end
65
- end
66
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell::Command
4
- class ShowChat
5
- require_relative "utils"
6
- include Utils
7
-
8
- ##
9
- # @param [LLM::Shell::Context] context
10
- # The context of the command
11
- # @return [LLM::Shell::Command::ShowChat]
12
- def initialize(context)
13
- @context = context
14
- end
15
-
16
- ##
17
- # Emits the full chat history to standard output
18
- # @return [void]
19
- def call
20
- clear_screen
21
- emit
22
- end
23
-
24
- private
25
-
26
- def emit
27
- pager do |io|
28
- messages.each.with_index do |message, index|
29
- next if index <= 1
30
- io << render(message) << "\n"
31
- end
32
- end
33
- end
34
-
35
- def console = IO.console
36
- def clear_screen = console.clear_screen
37
- def messages = bot.messages
38
- def render(message) = LLM::Shell::Renderer.new(message).render
39
-
40
- LLM.command "show-chat" do |cmd|
41
- cmd.description "Show the chat"
42
- cmd.register(self)
43
- cmd.builtin!
44
- end
45
- end
46
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell::Command
4
- class ShowVersion
5
- require_relative "utils"
6
- include Utils
7
-
8
- ##
9
- # @param [LLM::Shell::Context] context
10
- # The context of the command
11
- # @return [LLM::Shell::Command::ShowVersion]
12
- def initialize(context)
13
- @context = context
14
- end
15
-
16
- ##
17
- # Shows the current llm-shell version
18
- # @return [void]
19
- def call
20
- pager do |io|
21
- io.write("llm-shell version: #{LLM::Shell::VERSION}\n")
22
- end
23
- end
24
-
25
- private
26
-
27
- LLM.command "show-version" do |cmd|
28
- cmd.description "Show the llm-shell version"
29
- cmd.register(self)
30
- cmd.builtin!
31
- end
32
- end
33
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell::Command
4
- class SystemPrompt
5
- require_relative "utils"
6
- include Utils
7
-
8
- ##
9
- # @param [LLM::Shell::Context] context
10
- # The context of the command
11
- # @return [LLM::Shell::Command::SystemPrompt]
12
- def initialize(context)
13
- @context = context
14
- end
15
-
16
- ##
17
- # Emits the system prompt to standard output
18
- # @return [void]
19
- def call
20
- pager do |io|
21
- io.write render(bot.messages[0])
22
- end
23
- end
24
-
25
- private
26
-
27
- def render(message) = LLM::Shell::Renderer.new(message).render
28
-
29
- LLM.command "system-prompt" do |cmd|
30
- cmd.description "Show the system prompt"
31
- cmd.register(self)
32
- cmd.builtin!
33
- end
34
- end
35
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell::Command
4
- module Utils
5
- private
6
-
7
- def import(file)
8
- return unless File.file?(file)
9
- bot.chat [
10
- "<file path=\"#{file}\">",
11
- File.read(file),
12
- "</file>"
13
- ].join("\n")
14
- end
15
-
16
- def bot = @context.bot
17
- def io = @context.io
18
- def pager(...) = @context.pager(...)
19
- def file_pattern = /\A<file path=(.+?)>/
20
- end
21
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell
4
- class Completion
5
- ##
6
- # Returns a proc suitable for Readline completion.
7
- # @return [Proc]
8
- def self.to_proc
9
- new.to_proc
10
- end
11
-
12
- ##
13
- # @return [LLM::Shell::Completion]
14
- def initialize
15
- @commands = LLM.commands
16
- end
17
-
18
- ##
19
- # Returns a proc suitable for Readline completion.
20
- # @return [Proc]
21
- def to_proc
22
- method(:complete).to_proc
23
- end
24
-
25
- private
26
-
27
- def complete(input)
28
- words = Readline.line_buffer.split(" ")
29
- command = words[0]
30
- if commands[command]
31
- object = commands[command].object
32
- object.respond_to?(:complete) ? object.complete(input) : []
33
- else
34
- commands.keys.grep(/\A#{command}/)
35
- end
36
- end
37
-
38
- attr_reader :commands
39
- end
40
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell
4
- class Config
5
- ##
6
- # @param [String] provider
7
- # @return [LLM::Shell::Config]
8
- def initialize(provider)
9
- @provider = provider
10
- end
11
-
12
- ##
13
- # @return [Hash]
14
- def merge(other)
15
- to_h.merge(other)
16
- end
17
-
18
- ##
19
- # @return [Hash]
20
- def to_h
21
- yaml[@provider] || {}
22
- end
23
-
24
- private
25
-
26
- def yaml
27
- return {} unless File.readable?(path)
28
- @yaml ||= YAML.load_file(path)
29
- end
30
-
31
- def path
32
- File.join LLM::Shell.home, "config.yml"
33
- end
34
- end
35
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell
4
- class Default
5
- def initialize(provider)
6
- @provider = provider
7
- end
8
-
9
- def prompt
10
- File.join(SHAREDIR, "prompts", "default.txt")
11
- end
12
-
13
- def role
14
- case @provider
15
- when "openai", "ollama" then :system
16
- else :user
17
- end
18
- end
19
-
20
- SHAREDIR = File.join(__dir__, "..", "..", "..", "share", "llm-shell")
21
- private_constant :SHAREDIR
22
- end
23
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Shell
4
- class Formatter
5
- FormatError = Class.new(RuntimeError)
6
- include Command::Utils
7
-
8
- def initialize(messages)
9
- @messages = messages.reject(&:tool_call?)
10
- end
11
-
12
- def format!(role)
13
- case role
14
- when :user then format_user(messages)
15
- when :assistant then format_assistant(messages)
16
- else raise FormatError.new("#{role} is not known")
17
- end
18
- end
19
-
20
- private
21
-
22
- def format_user(messages)
23
- messages.filter_map do |message|
24
- next unless message.user?
25
- next unless String === message.content
26
- next unless message.content !~ file_pattern
27
- render(message.tap(&:read!))
28
- end.join("\n")
29
- end
30
-
31
- def format_assistant(messages)
32
- messages.filter_map do |message|
33
- next unless message.assistant?
34
- next unless String === message.content
35
- render(message.tap(&:read!))
36
- end.join("\n")
37
- end
38
-
39
- attr_reader :messages
40
- def render(message) = Renderer.new(message).render
41
- end
42
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LLM::Shell::Functions
4
- class ReadFile
5
- def call(path:)
6
- {ok: true, content: File.read(path)}
7
- rescue => ex
8
- {ok: false, error: {class: ex.class.to_s, message: ex.message}}
9
- end
10
-
11
- private
12
-
13
- LLM.function(:read_file) do |fn|
14
- fn.description "Read the contents of a file"
15
- fn.params do |schema|
16
- schema.object(path: schema.string.required)
17
- end
18
- fn.register(self)
19
- fn.builtin!
20
- end
21
- end
22
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LLM::Shell::Functions
4
- class WriteFile
5
- def call(path:, content:)
6
- {ok: true, content: File.binwrite(path, content)}
7
- rescue => ex
8
- {ok: false, error: {class: ex.class.to_s, message: ex.message}}
9
- end
10
-
11
- private
12
-
13
- LLM.function(:write_file) do |fn|
14
- fn.description "Write the contents of a file"
15
- fn.params do |schema|
16
- schema.object(path: schema.string.required, content: schema.string.required)
17
- end
18
- fn.register(self)
19
- fn.builtin!
20
- end
21
- end
22
- end