llmp 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8faca582b2fb9348ca7659a1e44a16c510ffb91d56381b077fd3b6887ec06ec1
4
+ data.tar.gz: 55dd40087e0e4a62f63a500d40a953ffe1c96c5e7dccc25b57afcc909f8c9dc2
5
+ SHA512:
6
+ metadata.gz: cbb418e596938dbc053b6f6e36011ab6b091fe1791a039bb9c56427513bdde4dd3e2995822687835f6bd5c9ec2a7885280cc13cda5dad51ae9befbb2e6d9d389
7
+ data.tar.gz: dc3683e5a59f2dda7d380f2d3046bc70c12f626e0f24a52fc73b546e275d174c94a6f8edd3f0c50fd4f75fa2069099f01d396940676f003c00e02f40015c4731
data/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # llmpipe
2
+
3
+ A Ruby-based CLI for building composable LLM workflows using the Unix pipe philosophy.
4
+
5
+ ## Overview
6
+
7
+ `llmp` treats LLM prompts as Unix commands that can be chained together. Each pipe is defined by a YAML configuration file and communicates via standard input/output.
8
+
9
+ ```bash
10
+ llmp summarize < article.txt | llmp translate > output.md
11
+ ```
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ gem install llmp
17
+ ```
18
+
19
+ With Nix:
20
+
21
+ ```bash
22
+ nix run github:pcboy/llmpipe -- <pipe_name_or_path> [args...] [options]
23
+ ```
24
+
25
+ ## Why llmp? You could just do `agent <<< 'my prompt'` and be done with it?
26
+
27
+ Yes, you could just do `qwen <<< 'my prompt'` and call it a day. That works fine for one-offs.
28
+
29
+ But you'll miss out on:
30
+
31
+ - **Caching** : llmp responses are automatically cached by content hash. Same input = instant result, no API call.
32
+ - **Single source of truth** : Prompts live in YAML files under one folder, can easily be version controlled
33
+ - **Composability** : Chain pipes together like Unix commands, with each step reusable and testable.
34
+
35
+ It's for sure nothing world-changing. It's just a nice way for me to work with quick LLMs operations from the command line.
36
+
37
+ ## Quick Start
38
+
39
+ ### 1. Configure RubyLLM
40
+
41
+ Create `~/.config/llmp/config.yml`:
42
+
43
+ ```yaml
44
+ # API key for your LLM provider
45
+ openai_api_key: your-api-key
46
+
47
+ # Custom endpoint (for Ollama, vLLM, LiteLLM, etc.)
48
+ openai_api_base: http://localhost:11434/v1
49
+ ```
50
+
51
+ Any configuration key supported by RubyLLM can be used here. The complete list of available options is documented at [rubyllm.com/configuration](https://rubyllm.com/configuration/).
52
+
53
+ ### 2. Create a Pipe
54
+
55
+ Create for instance a `translate.yml` in your project directory, or globally at `$XDG_CONFIG_HOME/llmp/pipes/translate.yml`:
56
+
57
+ ```yaml
58
+ provider: openai
59
+ model: qwen3.5
60
+ temperature: 0.6
61
+
62
+ system_prompt: |
63
+ Detect the language of the input.
64
+ If it's English, translate to Japanese, otherwise translate to English.
65
+ Preserve the original formatting and tone.
66
+ ```
67
+
68
+ ### 3. Run the Pipeline
69
+
70
+ ```bash
71
+ ➜ echo "Hello, world" | llmp translate
72
+ こんにちは、世界
73
+ ```
74
+
75
+ ## CLI Usage
76
+
77
+ ```bash
78
+ llmp <pipe_name_or_path> [args...] [options]
79
+ ```
80
+
81
+ ### Arguments
82
+
83
+ Pipe arguments are available in prompts as `$1`, `$2`, `$3`, etc., and `$*` for all arguments joined:
84
+
85
+ ```bash
86
+ # $1 = "ruby", $2 = "performance"
87
+ llmp review ruby performance < code.rb
88
+
89
+ # $* = "security audit"
90
+ llmp analyze "security audit" < code.rb
91
+ ```
92
+
93
+ ```yaml
94
+ # In your YAML:
95
+ system_prompt: |
96
+ Review this code for $1 issues.
97
+ Focus on: $2
98
+ All args: $*
99
+ ```
100
+
101
+ ### Options
102
+
103
+ | Option | Description |
104
+ | --------------- | ------------------------------- |
105
+ | `-f, --force` | Bypass cache and force LLM call |
106
+ | `-h, --help` | Show help message |
107
+ | `-v, --version` | Print version |
108
+
109
+ ### Pipe Resolution
110
+
111
+ When you run `llmp summarize`, the framework resolves the YAML file using this priority:
112
+
113
+ 1. **Direct path** — If argument contains `/` or ends with `.yml`/`.yaml`
114
+ 2. **Current directory** — Looks for `summarize.yml` or `summarize.yaml`
115
+ 3. **Environment variable** — Checks `$LLMP_PIPES_PATH/summarize.yml`
116
+ 4. **Global fallback** — XDG config directory (`$XDG_CONFIG_HOME/llmp/pipes/summarize.yml`)
117
+
118
+ ## Pipe Definition (YAML Schema)
119
+
120
+ ```yaml
121
+ # Required: Provider and model
122
+ provider: openai # or anthropic, google, etc.
123
+ model: qwen3.5
124
+
125
+ # Optional: Model parameters
126
+ temperature: 0.7
127
+
128
+ # Required: System prompt
129
+ system_prompt: |
130
+ Find Job Title in the given JD
131
+ ```
132
+
133
+ ## Configuration
134
+
135
+ ### RubyLLM Configuration
136
+
137
+ The configuration file is stored following the XDG Base Directory specification:
138
+
139
+ - **Location**: `$XDG_CONFIG_HOME/llmp/config.yml` (defaults to `~/.config/llmp/config.yml`)
140
+
141
+ All keys in this file are passed directly to `RubyLLM.configure`:
142
+
143
+ ```yaml
144
+ # Authentication
145
+ openai_api_key: your-api-key
146
+
147
+ # Custom endpoints
148
+ openai_api_base: http://localhost:11434/v1
149
+
150
+ # Optional settings
151
+ openai_organization: your-org-id
152
+ openai_timeout: 30
153
+ openai_extra_headers:
154
+ X-Custom-Header: value
155
+
156
+ # Logging
157
+ log_level: info
158
+ log_errors: true
159
+ ```
160
+
161
+ Any configuration key supported by RubyLLM can be used here. The complete list of available options is documented at [rubyllm.com/configuration](https://rubyllm.com/configuration/).
162
+
163
+ ## Examples
164
+
165
+ ### Basic Pipeline
166
+
167
+ ```bash
168
+ # Do a code review on current branch, and apply the necessary changes
169
+ git diff master | llmp code-review | qwen -y && \
170
+ git commit -a -m `git diff | llmp commit-message`
171
+ ```
172
+
173
+ ### Everything is cached
174
+
175
+ ```bash
176
+ # First run - cache miss, calls LLM
177
+ llmp summarize < input.txt
178
+
179
+ # Second run - cache hit, instant result
180
+ llmp summarize < input.txt
181
+
182
+ # Force refresh - bypass cache
183
+ llmp summarize --force < input.txt
184
+ ```
185
+
186
+ ### Custom Pipe Path
187
+
188
+ ```bash
189
+ # Use pipe from current directory (my_pipe.yml)
190
+ llmp my_pipe
191
+
192
+ # Use pipe from specific path
193
+ llmp ./pipes/custom.yml
194
+
195
+ # Use pipe from global config directory
196
+ llmp translate # resolves to ~/.config/llmp/translate.yml
197
+ ```
198
+
199
+ ## Caching
200
+
201
+ `llmp` automatically caches LLM responses to save time and API costs.
202
+
203
+ - **Cache key**: SHA256 hash of (YAML content + input data)
204
+ - **Cache location**: `.llmp/cache/`
205
+ - **Cache invalidation**: Automatic when YAML or input changes
206
+ - **Bypass cache**: Use `--force` flag
207
+
208
+ Cache storage: `$XDG_CACHE_HOME/llmp/` (defaults to `~/.cache/llmp/`)
209
+ Config files: `$XDG_CONFIG_HOME/llmp/` (defaults to `~/.config/llmp/`)
210
+
211
+ ## Environment Variables
212
+
213
+ | Variable | Description |
214
+ | ----------------- | ------------------------------------ |
215
+ | `LLMP_PIPES_PATH` | Custom directory for pipe YAML files |
216
+
217
+ ## License
218
+
219
+ MIT
data/bin/llmp ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/llmp'
5
+
6
+ begin
7
+ opts = Llmp::Cli.parse(ARGV)
8
+
9
+ unless opts[:pipe_path]
10
+ warn "Error: Pipe '#{opts[:pipe]}' not found"
11
+ exit 1
12
+ end
13
+
14
+ pipe = Llmp::Pipe.new(opts[:pipe_path], args: opts[:args])
15
+ input_data = $stdin.read
16
+
17
+ result = pipe.execute(input_data, force: opts[:force])
18
+ $stdout.write(result)
19
+ rescue StandardError => e
20
+ warn "Error: #{e.message}"
21
+ warn e.backtrace.first(5) if ENV['LLMP_DEBUG']
22
+ exit 1
23
+ end
data/lib/llmp/cache.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'fileutils'
5
+ require 'json'
6
+ require 'runcom'
7
+
8
+ module Llmp
9
+ class Cache
10
+ def initialize
11
+ @cache = Runcom::Cache.new('llmp')
12
+ @cache_dir = @cache.passive.to_s
13
+ FileUtils.mkdir_p(@cache_dir)
14
+ end
15
+
16
+ def key_for(yaml_content, input_data)
17
+ Digest::SHA256.hexdigest(yaml_content + input_data)
18
+ end
19
+
20
+ def cache_path(key)
21
+ File.join(@cache_dir, "#{key}.json")
22
+ end
23
+
24
+ def lock_path(key)
25
+ File.join(@cache_dir, "#{key}.lock")
26
+ end
27
+
28
+ def fetch(yaml_content, input_data, force: false)
29
+ key = key_for(yaml_content, input_data)
30
+ path = cache_path(key)
31
+ lock_file = lock_path(key)
32
+
33
+ File.open(lock_file, File::CREAT | File::RDWR) do |lf|
34
+ lf.flock(File::LOCK_EX)
35
+
36
+ # Check cache only if not forced
37
+ if !force && File.exist?(path)
38
+ cached = JSON.parse(File.read(path), symbolize_names: true)
39
+ return cached[:output]
40
+ end
41
+
42
+ # Cache miss or forced - compute and store atomically
43
+ output = yield
44
+ File.write(path, JSON.pretty_generate({ output: }))
45
+ output
46
+ end
47
+ end
48
+ end
49
+ end
data/lib/llmp/cli.rb ADDED
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optimist'
4
+ require 'fileutils'
5
+ require 'runcom'
6
+
7
+ module Llmp
8
+ class Cli
9
+ def self.parse(argv = ARGV)
10
+ opts = build_options(argv)
11
+
12
+ positional = argv.reject { |arg| arg.start_with?('-') }
13
+ if positional.empty? || opts[:help]
14
+ build_options(['--help'])
15
+ exit 1
16
+ end
17
+
18
+ opts[:pipe] = positional.first
19
+ opts[:pipe_path] = resolve_pipe_path(positional.first)
20
+ opts[:args] = positional.drop(1)
21
+
22
+ opts
23
+ end
24
+
25
+ def self.build_options(argv)
26
+ Optimist.options(argv) do
27
+ version 'Llmp v0.1.0'
28
+ banner <<~BANNER
29
+
30
+ Usage: llmp <pipe_name_or_path> [args...] [options]
31
+
32
+ A Ruby-based CLI framework for composable LLM workflows using Unix pipe philosophy.
33
+
34
+ Pipe arguments are available in prompts as $1, $2, $3, etc. and $* for all arguments.
35
+
36
+ Examples:
37
+ llmp summarize < url.txt
38
+ llmp ./pipes/custom.yml < url.txt
39
+ llmp translate -f < data.txt
40
+ llmp review ruby -- -Wno-unused-variable < code.rb
41
+ BANNER
42
+
43
+ opt :force, 'Bypass cache and force LLM call', type: :boolean, default: nil, short: 'f'
44
+ opt :help, 'Show this message', type: :boolean, default: nil, short: 'h'
45
+ end
46
+ end
47
+
48
+ def self.resolve_pipe_path(pipe_arg)
49
+ return pipe_arg if pipe_arg.include?('/') || pipe_arg.end_with?('.yml', '.yaml')
50
+
51
+ paths_to_try = [
52
+ cwd_path(pipe_arg),
53
+ env_var_path(pipe_arg),
54
+ global_path(pipe_arg)
55
+ ].compact
56
+
57
+ paths_to_try.each do |path|
58
+ return path if File.exist?(path)
59
+ end
60
+
61
+ nil
62
+ end
63
+
64
+ def self.cwd_path(pipe_arg)
65
+ base_name = pipe_arg.end_with?('.yml', '.yaml') ? pipe_arg : "#{pipe_arg}.yml"
66
+ File.expand_path(base_name)
67
+ end
68
+
69
+ def self.env_var_path(pipe_arg)
70
+ env_path = ENV['LLMP_PIPES_PATH']
71
+ return nil if env_path.nil? || env_path.empty?
72
+
73
+ base_name = pipe_arg.end_with?('.yml', '.yaml') ? pipe_arg : "#{pipe_arg}.yml"
74
+ File.expand_path(base_name, env_path)
75
+ end
76
+
77
+ def self.global_path(pipe_arg)
78
+ config = Runcom::Config.new("llmp/pipes/#{pipe_arg}")
79
+ base_name = pipe_arg.end_with?('.yml', '.yaml') ? pipe_arg : "#{pipe_arg}.yml"
80
+ config_path = config.active || config.passive
81
+ File.join(File.dirname(config_path.to_s), base_name)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+ require 'runcom'
6
+
7
+ module Llmp
8
+ class Config
9
+ def self.load(path: nil)
10
+ if path
11
+ config_path = path
12
+ else
13
+ config = Runcom::Config.new('llmp/config.yml')
14
+ config_path = config.active || config.passive
15
+ end
16
+
17
+ return {} unless config_path
18
+ return {} unless File.exist?(config_path)
19
+
20
+ YAML.safe_load(File.read(config_path.to_s)) || {}
21
+ rescue Psych::SyntaxError => e
22
+ warn "Warning: Invalid YAML in #{config_path}: #{e.message}"
23
+ {}
24
+ end
25
+ end
26
+ end
data/lib/llmp/pipe.rb ADDED
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'json'
5
+ require 'ruby_llm'
6
+ require_relative 'cache'
7
+ require_relative 'config'
8
+
9
+ module Llmp
10
+ class Pipe
11
+ REQUIRED_FIELDS = %w[provider model system_prompt].freeze
12
+ attr_reader :config, :yaml_content
13
+
14
+ def initialize(yaml_path, args: [], cache: nil)
15
+ @yaml_path = yaml_path
16
+ @args = args
17
+ @yaml_content = File.read(yaml_path)
18
+ @config = YAML.safe_load(substitute_args(@yaml_content))
19
+
20
+ missing = REQUIRED_FIELDS.reject { |f| @config.key?(f) }
21
+ raise "Missing required fields: #{missing.join(', ')}" unless missing.empty?
22
+
23
+ @cache = cache || Cache.new
24
+ @input_data = nil
25
+ end
26
+
27
+ def execute(input_data, force: false)
28
+ @input_data = input_data
29
+
30
+ configure
31
+
32
+ @cache.fetch(@yaml_content, @input_data, force: force) do
33
+ call_llm
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def substitute_args(content)
40
+ return content if @args.empty?
41
+
42
+ result = content
43
+
44
+ # Replace $* with all arguments joined by space (YAML-escaped)
45
+ result = result.gsub('$*', escape_yaml_value(@args.join(' ')))
46
+
47
+ # Replace $1, $2, $3, etc. with corresponding arguments (YAML-escaped)
48
+ @args.each_with_index do |arg, index|
49
+ result = result.gsub("$#{index + 1}", escape_yaml_value(arg))
50
+ end
51
+
52
+ result
53
+ end
54
+
55
+ def escape_yaml_value(value)
56
+ YAML.dump(value).gsub(/^--- /, '').strip
57
+ end
58
+
59
+ def configure
60
+ user_config = Config.load
61
+
62
+ RubyLLM.configure do |config|
63
+ user_config.each do |key, value|
64
+ config.send("#{key}=", value) if config.respond_to?("#{key}=")
65
+ end
66
+ end
67
+ end
68
+
69
+ def call_llm
70
+ provider = @config['provider']
71
+ model = @config['model']
72
+ temperature = @config['temperature'] || 0.6
73
+ system_prompt = @config['system_prompt']
74
+
75
+ chat = RubyLLM.chat(model: model, provider: provider, assume_model_exists: true)
76
+ chat.with_temperature(temperature)
77
+ chat.with_instructions(system_prompt)
78
+ response = chat.ask(@input_data)
79
+
80
+ response.content
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Llmp
4
+ VERSION = '0.1.0'
5
+ end
data/lib/llmp.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'llmp/version'
4
+ require_relative 'llmp/config'
5
+ require_relative 'llmp/cache'
6
+ require_relative 'llmp/pipe'
7
+ require_relative 'llmp/cli'
8
+
9
+ module Llmp
10
+ end
data/llmp.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/llmp/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'llmp'
7
+ spec.version = Llmp::VERSION
8
+ spec.authors = ['David Hagege']
9
+ spec.email = ['david@joynetiks.com']
10
+
11
+ spec.summary = 'A Ruby-based CLI for composable LLM workflows'
12
+ spec.description = <<~EOL
13
+ Build composable LLM workflows using Unix pipe philosophy.
14
+ Each pipe is a YAML-configured LLM call that communicates via stdin/stdout.
15
+ EOL
16
+ spec.homepage = 'https://github.com/pcboy/llmpipe'
17
+ spec.license = 'MIT'
18
+ spec.required_ruby_version = '>= 3.4.0'
19
+
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = spec.homepage
22
+
23
+ spec.files = Dir.glob('lib/**/*') + Dir.glob('bin/*') + %w[README.md llmp.gemspec]
24
+
25
+ spec.bindir = 'bin'
26
+ spec.executables = ['llmp']
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency 'json', '~> 2.0'
30
+ spec.add_dependency 'optimist', '~> 3.0'
31
+ spec.add_dependency 'ruby_llm', '~> 1.16'
32
+ spec.add_dependency 'runcom', '~> 12.3'
33
+
34
+ spec.add_development_dependency 'rake', '~> 13.0'
35
+ spec.add_development_dependency 'rspec', '~> 3.0'
36
+ spec.add_development_dependency 'ruby_llm-test', '~> 0.2'
37
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: llmp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Hagege
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-01 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: json
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: optimist
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: ruby_llm
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.16'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.16'
54
+ - !ruby/object:Gem::Dependency
55
+ name: runcom
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '12.3'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '12.3'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '13.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '13.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: ruby_llm-test
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.2'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.2'
110
+ description: |
111
+ Build composable LLM workflows using Unix pipe philosophy.
112
+ Each pipe is a YAML-configured LLM call that communicates via stdin/stdout.
113
+ email:
114
+ - david@joynetiks.com
115
+ executables:
116
+ - llmp
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - README.md
121
+ - bin/llmp
122
+ - lib/llmp.rb
123
+ - lib/llmp/cache.rb
124
+ - lib/llmp/cli.rb
125
+ - lib/llmp/config.rb
126
+ - lib/llmp/pipe.rb
127
+ - lib/llmp/version.rb
128
+ - llmp.gemspec
129
+ homepage: https://github.com/pcboy/llmpipe
130
+ licenses:
131
+ - MIT
132
+ metadata:
133
+ homepage_uri: https://github.com/pcboy/llmpipe
134
+ source_code_uri: https://github.com/pcboy/llmpipe
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 3.4.0
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 3.7.2
150
+ specification_version: 4
151
+ summary: A Ruby-based CLI for composable LLM workflows
152
+ test_files: []