summarize-ruby 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 +7 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE +21 -0
- data/README.md +208 -0
- data/lib/summarize/client.rb +160 -0
- data/lib/summarize/configuration.rb +33 -0
- data/lib/summarize/errors.rb +31 -0
- data/lib/summarize/options.rb +72 -0
- data/lib/summarize/result.rb +101 -0
- data/lib/summarize/version.rb +5 -0
- data/lib/summarize.rb +50 -0
- metadata +74 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4dd0f9c27d9fb176432ad18feb629895241b2b10bf284729855e27707e5c928b
|
|
4
|
+
data.tar.gz: e302af7631889e3543f4540b7359be06b6ca99d8ee30c7b87fc69ec5ec3ff2d3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 886c4ac651c3adf6a9df027460ef03c0ee7fb1deb25b6ad46a5b812380158c7edc5da968a2806df820fd96d32419c0ac268480b01fd7f8a324e08f244b361c50
|
|
7
|
+
data.tar.gz: d41851b8bf030bab1c2856950cfac67554b1eecc15419f7fd714c11bfcab8ee91c50eb41c572379169f0ca4f10293636cb120931690b5a0482483e4a3f185307
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-02-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Ruby wrapper for the `summarize` CLI tool
|
|
13
|
+
- `Summarize.call` for summarizing URLs and file paths
|
|
14
|
+
- `Summarize.from_text` for summarizing text content
|
|
15
|
+
- `Summarize.extract` for content extraction without LLM summarization
|
|
16
|
+
- Streaming support via block syntax
|
|
17
|
+
- Global configuration with `Summarize.configure`
|
|
18
|
+
- Support for all `summarize` CLI options including model, length, language, format, video mode, slides, and more
|
|
19
|
+
- `Summarize::Result` object with accessors for summary, extracted content, LLM metadata, and token metrics
|
|
20
|
+
- Custom error hierarchy: `BinaryNotFoundError`, `TimeoutError`, `CommandError`, etc.
|
|
21
|
+
- Environment variable passthrough for API keys
|
|
22
|
+
- Automatic binary detection
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martiano
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# summarize-ruby
|
|
2
|
+
|
|
3
|
+
Ruby wrapper for the [`summarize`](https://github.com/steipete/summarize) CLI tool. Summarize web pages, files, videos, and text using LLMs from OpenAI, Anthropic, Google, xAI, and more.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
First, install the `summarize` CLI:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g @steipete/summarize
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then add the gem to your Gemfile:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "summarize-ruby"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install directly:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gem install summarize-ruby
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require "summarize"
|
|
29
|
+
|
|
30
|
+
# Summarize a URL
|
|
31
|
+
result = Summarize.call("https://example.com/article")
|
|
32
|
+
puts result.summary
|
|
33
|
+
|
|
34
|
+
# Summarize a local file
|
|
35
|
+
result = Summarize.call("/path/to/document.pdf")
|
|
36
|
+
|
|
37
|
+
# Summarize text
|
|
38
|
+
result = Summarize.from_text("Long article text here...")
|
|
39
|
+
|
|
40
|
+
# Extract content without summarization
|
|
41
|
+
result = Summarize.extract("https://example.com", format: :md)
|
|
42
|
+
puts result.content
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Options
|
|
46
|
+
|
|
47
|
+
Pass any option as a keyword argument:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
result = Summarize.call("https://example.com",
|
|
51
|
+
model: "anthropic/claude-sonnet-4-5",
|
|
52
|
+
length: :short,
|
|
53
|
+
language: "es",
|
|
54
|
+
prompt: "Focus on technical details"
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
All supported options:
|
|
59
|
+
|
|
60
|
+
| Ruby option | CLI flag | Example values |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `model` | `--model` | `"openai/gpt-5-mini"`, `"anthropic/claude-sonnet-4-5"`, `"auto"` |
|
|
63
|
+
| `length` | `--length` | `:short`, `:medium`, `:long`, `:xl`, `:xxl`, `5000` |
|
|
64
|
+
| `language` | `--lang` | `"en"`, `"es"`, `"de"`, `"auto"` |
|
|
65
|
+
| `prompt` | `--prompt` | `"Focus on key takeaways"` |
|
|
66
|
+
| `prompt_file` | `--prompt-file` | `"/path/to/prompt.txt"` |
|
|
67
|
+
| `format` | `--format` | `:text`, `:md` |
|
|
68
|
+
| `timeout` | `--timeout` | `"3m"`, `"30s"` |
|
|
69
|
+
| `retries` | `--retries` | `2` |
|
|
70
|
+
| `cli` | `--cli` | `"claude"`, `"gemini"`, `"codex"` |
|
|
71
|
+
| `video_mode` | `--video-mode` | `:auto`, `:transcript`, `:understand` |
|
|
72
|
+
| `markdown_mode` | `--markdown-mode` | `:off`, `:auto`, `:llm`, `:readability` |
|
|
73
|
+
| `max_output_tokens` | `--max-output-tokens` | `2000` |
|
|
74
|
+
| `max_extract_characters` | `--max-extract-characters` | `10000` |
|
|
75
|
+
| `youtube` | `--youtube` | `"auto"`, `"web"`, `"yt-dlp"` |
|
|
76
|
+
| `transcriber` | `--transcriber` | `"auto"`, `"whisper"`, `"parakeet"` |
|
|
77
|
+
| `firecrawl` | `--firecrawl` | `"off"`, `"auto"`, `"always"` |
|
|
78
|
+
| `preprocess` | `--preprocess` | `"off"`, `"auto"`, `"always"` |
|
|
79
|
+
| `theme` | `--theme` | `"aurora"`, `"ember"`, `"moss"`, `"mono"` |
|
|
80
|
+
| `metrics` | `--metrics` | `"off"`, `"on"`, `"detailed"` |
|
|
81
|
+
| `slides_dir` | `--slides-dir` | `"./my-slides"` |
|
|
82
|
+
| `slides_max` | `--slides-max` | `10` |
|
|
83
|
+
| `slides_min_duration` | `--slides-min-duration` | `5` |
|
|
84
|
+
| `slides_scene_threshold` | `--slides-scene-threshold` | `0.5` |
|
|
85
|
+
|
|
86
|
+
Boolean flags (pass `true` to enable):
|
|
87
|
+
|
|
88
|
+
| Ruby option | CLI flag |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `force_summary` | `--force-summary` |
|
|
91
|
+
| `timestamps` | `--timestamps` |
|
|
92
|
+
| `no_cache` | `--no-cache` |
|
|
93
|
+
| `no_media_cache` | `--no-media-cache` |
|
|
94
|
+
| `slides` | `--slides` |
|
|
95
|
+
| `slides_debug` | `--slides-debug` |
|
|
96
|
+
| `slides_ocr` | `--slides-ocr` |
|
|
97
|
+
| `verbose` | `--verbose` |
|
|
98
|
+
| `debug` | `--debug` |
|
|
99
|
+
| `no_color` | `--no-color` |
|
|
100
|
+
| `plain` | `--plain` |
|
|
101
|
+
|
|
102
|
+
### Streaming
|
|
103
|
+
|
|
104
|
+
Pass a block to stream output as it arrives:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
Summarize.call("https://example.com") do |chunk|
|
|
108
|
+
print chunk
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Result object
|
|
113
|
+
|
|
114
|
+
The `Result` object provides structured access to the response:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
result = Summarize.call("https://example.com")
|
|
118
|
+
|
|
119
|
+
# Summary
|
|
120
|
+
result.summary # => "## Key Points\n..."
|
|
121
|
+
result.success? # => true
|
|
122
|
+
|
|
123
|
+
# Extracted content
|
|
124
|
+
result.title # => "Article Title"
|
|
125
|
+
result.description # => "Article description"
|
|
126
|
+
result.content # => "Full extracted content..."
|
|
127
|
+
result.site_name # => "Example"
|
|
128
|
+
result.media_type # => "text/html"
|
|
129
|
+
|
|
130
|
+
# LLM info
|
|
131
|
+
result.model # => "gpt-5-mini"
|
|
132
|
+
result.provider # => "openai"
|
|
133
|
+
|
|
134
|
+
# Token usage
|
|
135
|
+
result.total_tokens # => 1550
|
|
136
|
+
result.prompt_tokens # => 1200
|
|
137
|
+
result.completion_tokens # => 350
|
|
138
|
+
|
|
139
|
+
# Raw JSON
|
|
140
|
+
result.to_h # => { "summary" => "...", "extracted" => { ... }, ... }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Configuration
|
|
144
|
+
|
|
145
|
+
Set global defaults:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
Summarize.configure do |c|
|
|
149
|
+
c.default_model = "anthropic/claude-sonnet-4-5"
|
|
150
|
+
c.default_length = :medium
|
|
151
|
+
c.default_language = "en"
|
|
152
|
+
c.timeout = "3m"
|
|
153
|
+
c.retries = 2
|
|
154
|
+
c.default_cli = "claude"
|
|
155
|
+
|
|
156
|
+
# Pass API keys to the CLI process
|
|
157
|
+
c.env = {
|
|
158
|
+
"ANTHROPIC_API_KEY" => ENV["ANTHROPIC_API_KEY"],
|
|
159
|
+
"OPENAI_API_KEY" => ENV["OPENAI_API_KEY"]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Custom binary path (auto-detected by default)
|
|
163
|
+
c.binary_path = "/usr/local/bin/summarize"
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Per-call options override configuration defaults:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
Summarize.configure { |c| c.default_model = "anthropic/claude-sonnet-4-5" }
|
|
171
|
+
|
|
172
|
+
# This uses gpt-5-mini, not the configured default
|
|
173
|
+
result = Summarize.call("https://example.com", model: "openai/gpt-5-mini")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Error handling
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
begin
|
|
180
|
+
result = Summarize.call("https://example.com")
|
|
181
|
+
rescue Summarize::BinaryNotFoundError
|
|
182
|
+
# summarize CLI not installed
|
|
183
|
+
rescue Summarize::CommandError => e
|
|
184
|
+
e.exit_code # => 1
|
|
185
|
+
e.stderr # => "error message"
|
|
186
|
+
rescue Summarize::SummarizationError => e
|
|
187
|
+
# JSON parsing failed
|
|
188
|
+
rescue Summarize::Error => e
|
|
189
|
+
# catch-all for any summarize error
|
|
190
|
+
end
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Requirements
|
|
194
|
+
|
|
195
|
+
- Ruby >= 3.1
|
|
196
|
+
- [`summarize`](https://github.com/steipete/summarize) CLI (`npm i -g @steipete/summarize`)
|
|
197
|
+
- At least one LLM provider API key (OpenAI, Anthropic, Google, etc.)
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
bundle install
|
|
203
|
+
bundle exec rspec
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
MIT
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "json"
|
|
5
|
+
require "tempfile"
|
|
6
|
+
|
|
7
|
+
module Summarize
|
|
8
|
+
class Client
|
|
9
|
+
attr_reader :config
|
|
10
|
+
|
|
11
|
+
def initialize(config = Summarize.configuration)
|
|
12
|
+
@config = config
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Summarize a URL or file path.
|
|
16
|
+
#
|
|
17
|
+
# client.call("https://example.com", length: :short, model: "openai/gpt-5-mini")
|
|
18
|
+
# client.call("/path/to/file.pdf", language: "es")
|
|
19
|
+
#
|
|
20
|
+
# With a block, streams chunks as they arrive:
|
|
21
|
+
#
|
|
22
|
+
# client.call("https://example.com") { |chunk| print chunk }
|
|
23
|
+
#
|
|
24
|
+
def call(input, **opts, &block)
|
|
25
|
+
if block_given?
|
|
26
|
+
stream(input, **opts, &block)
|
|
27
|
+
else
|
|
28
|
+
run_json(input, **opts)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Summarize text content by writing to a temp file.
|
|
33
|
+
#
|
|
34
|
+
# client.from_text("Long article text...", length: :medium)
|
|
35
|
+
#
|
|
36
|
+
def from_text(text, **opts, &block)
|
|
37
|
+
with_temp_file(text) do |path|
|
|
38
|
+
if block_given?
|
|
39
|
+
stream(path, **opts, &block)
|
|
40
|
+
else
|
|
41
|
+
run_json(path, **opts)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Extract content without LLM summarization.
|
|
47
|
+
#
|
|
48
|
+
# result = client.extract("https://example.com", format: :md)
|
|
49
|
+
# result.content # => extracted markdown
|
|
50
|
+
#
|
|
51
|
+
def extract(input, **opts)
|
|
52
|
+
run_json(input, extract: true, **opts)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def run_json(input, extract: false, **opts)
|
|
58
|
+
args = build_args(input, extract: extract, stream: false, json: true, **opts)
|
|
59
|
+
|
|
60
|
+
stdout, stderr, status = execute(args)
|
|
61
|
+
|
|
62
|
+
handle_error!(status, stderr) unless status.success?
|
|
63
|
+
|
|
64
|
+
parsed = JSON.parse(stdout)
|
|
65
|
+
Result.new(parsed)
|
|
66
|
+
rescue JSON::ParserError => e
|
|
67
|
+
raise SummarizationError, "Failed to parse JSON output: #{e.message}\nOutput: #{stdout&.slice(0, 500)}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def stream(input, **opts, &block)
|
|
71
|
+
args = build_args(input, stream: true, json: false, **opts)
|
|
72
|
+
|
|
73
|
+
full_output = +""
|
|
74
|
+
|
|
75
|
+
Open3.popen3(command_env, *args) do |stdin, stdout, stderr, wait_thread|
|
|
76
|
+
stdin.close
|
|
77
|
+
|
|
78
|
+
stdout.each_line do |line|
|
|
79
|
+
full_output << line
|
|
80
|
+
block.call(line)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
status = wait_thread.value
|
|
84
|
+
handle_error!(status, stderr.read) unless status.success?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
full_output
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def with_temp_file(text)
|
|
91
|
+
file = Tempfile.new(["summarize-input", ".txt"])
|
|
92
|
+
file.write(text)
|
|
93
|
+
file.flush
|
|
94
|
+
file.close
|
|
95
|
+
yield file.path
|
|
96
|
+
ensure
|
|
97
|
+
file&.unlink
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def build_args(input, extract: false, stream: nil, json: false, **opts)
|
|
101
|
+
merged = apply_defaults(opts)
|
|
102
|
+
|
|
103
|
+
args = [config.binary_path]
|
|
104
|
+
args << input
|
|
105
|
+
|
|
106
|
+
args << "--json" if json
|
|
107
|
+
args << "--stream" << "off" if stream == false
|
|
108
|
+
args << "--stream" << "on" if stream == true
|
|
109
|
+
args << "--extract" if extract
|
|
110
|
+
args << "--metrics" << "on" if json
|
|
111
|
+
|
|
112
|
+
args.concat(Options.new(merged).to_args)
|
|
113
|
+
|
|
114
|
+
args
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def apply_defaults(opts)
|
|
118
|
+
defaults = {}
|
|
119
|
+
defaults[:model] = config.default_model if config.default_model && config.default_model != "auto"
|
|
120
|
+
defaults[:cli] = config.default_cli if config.default_cli
|
|
121
|
+
defaults[:length] = config.default_length if config.default_length
|
|
122
|
+
defaults[:language] = config.default_language if config.default_language
|
|
123
|
+
defaults[:timeout] = config.timeout if config.timeout
|
|
124
|
+
defaults[:retries] = config.retries if config.retries
|
|
125
|
+
|
|
126
|
+
defaults.merge(opts)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def command_env
|
|
130
|
+
env = {}
|
|
131
|
+
config.env.each { |k, v| env[k.to_s] = v.to_s }
|
|
132
|
+
env
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def execute(args)
|
|
136
|
+
validate_binary!
|
|
137
|
+
|
|
138
|
+
Open3.capture3(command_env, *args)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def validate_binary!
|
|
142
|
+
path = config.binary_path
|
|
143
|
+
return if path == "summarize" # rely on PATH
|
|
144
|
+
return if File.executable?(path)
|
|
145
|
+
|
|
146
|
+
raise BinaryNotFoundError, path
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def handle_error!(status, stderr)
|
|
150
|
+
case status.exitstatus
|
|
151
|
+
when 130
|
|
152
|
+
raise Error, "Interrupted (SIGINT)"
|
|
153
|
+
when 143
|
|
154
|
+
raise Error, "Terminated (SIGTERM)"
|
|
155
|
+
else
|
|
156
|
+
raise CommandError.new(status.exitstatus, stderr&.strip || "")
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Summarize
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :default_model, :default_length, :default_language,
|
|
6
|
+
:default_cli, :timeout, :retries, :env
|
|
7
|
+
attr_writer :binary_path
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@binary_path = nil
|
|
11
|
+
@default_model = "auto"
|
|
12
|
+
@default_cli = nil
|
|
13
|
+
@default_length = nil
|
|
14
|
+
@default_language = nil
|
|
15
|
+
@timeout = nil
|
|
16
|
+
@retries = nil
|
|
17
|
+
@env = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def binary_path
|
|
21
|
+
@binary_path ||= find_binary
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def find_binary
|
|
27
|
+
path = `which summarize 2>/dev/null`.strip
|
|
28
|
+
return path unless path.empty?
|
|
29
|
+
|
|
30
|
+
["/usr/local/bin/summarize", "/opt/homebrew/bin/summarize"].find { |p| File.executable?(p) } || "summarize"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Summarize
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class BinaryNotFoundError < Error
|
|
7
|
+
def initialize(path)
|
|
8
|
+
super("summarize binary not found at '#{path}'. Install via: npm i -g @steipete/summarize")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class TimeoutError < Error
|
|
13
|
+
def initialize(timeout)
|
|
14
|
+
super("summarize timed out after #{timeout}")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class ExtractionError < Error; end
|
|
19
|
+
|
|
20
|
+
class SummarizationError < Error; end
|
|
21
|
+
|
|
22
|
+
class CommandError < Error
|
|
23
|
+
attr_reader :exit_code, :stderr
|
|
24
|
+
|
|
25
|
+
def initialize(exit_code, stderr)
|
|
26
|
+
@exit_code = exit_code
|
|
27
|
+
@stderr = stderr
|
|
28
|
+
super("summarize exited with code #{exit_code}: #{stderr}")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Summarize
|
|
4
|
+
class Options
|
|
5
|
+
LENGTHS = %i[short medium long xl xxl s m l].freeze
|
|
6
|
+
VIDEO_MODES = %i[auto transcript understand].freeze
|
|
7
|
+
FORMATS = %i[text md].freeze
|
|
8
|
+
MARKDOWN_MODES = %i[off auto llm readability].freeze
|
|
9
|
+
METRICS_MODES = %i[off on detailed].freeze
|
|
10
|
+
|
|
11
|
+
OPTION_MAP = {
|
|
12
|
+
model: "--model",
|
|
13
|
+
length: "--length",
|
|
14
|
+
language: "--lang",
|
|
15
|
+
timeout: "--timeout",
|
|
16
|
+
retries: "--retries",
|
|
17
|
+
prompt: "--prompt",
|
|
18
|
+
prompt_file: "--prompt-file",
|
|
19
|
+
format: "--format",
|
|
20
|
+
video_mode: "--video-mode",
|
|
21
|
+
markdown_mode: "--markdown-mode",
|
|
22
|
+
max_output_tokens: "--max-output-tokens",
|
|
23
|
+
max_extract_characters: "--max-extract-characters",
|
|
24
|
+
youtube: "--youtube",
|
|
25
|
+
transcriber: "--transcriber",
|
|
26
|
+
firecrawl: "--firecrawl",
|
|
27
|
+
preprocess: "--preprocess",
|
|
28
|
+
theme: "--theme",
|
|
29
|
+
metrics: "--metrics",
|
|
30
|
+
cli: "--cli",
|
|
31
|
+
slides_dir: "--slides-dir",
|
|
32
|
+
slides_scene_threshold: "--slides-scene-threshold",
|
|
33
|
+
slides_max: "--slides-max",
|
|
34
|
+
slides_min_duration: "--slides-min-duration"
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
BOOLEAN_FLAGS = {
|
|
38
|
+
force_summary: "--force-summary",
|
|
39
|
+
timestamps: "--timestamps",
|
|
40
|
+
no_cache: "--no-cache",
|
|
41
|
+
no_media_cache: "--no-media-cache",
|
|
42
|
+
verbose: "--verbose",
|
|
43
|
+
debug: "--debug",
|
|
44
|
+
no_color: "--no-color",
|
|
45
|
+
plain: "--plain",
|
|
46
|
+
slides: "--slides",
|
|
47
|
+
slides_debug: "--slides-debug",
|
|
48
|
+
slides_ocr: "--slides-ocr"
|
|
49
|
+
}.freeze
|
|
50
|
+
|
|
51
|
+
def initialize(opts = {})
|
|
52
|
+
@opts = opts
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_args
|
|
56
|
+
args = []
|
|
57
|
+
|
|
58
|
+
OPTION_MAP.each do |key, flag|
|
|
59
|
+
value = @opts[key]
|
|
60
|
+
next if value.nil?
|
|
61
|
+
|
|
62
|
+
args << flag << value.to_s
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
BOOLEAN_FLAGS.each do |key, flag|
|
|
66
|
+
args << flag if @opts[key]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
args
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Summarize
|
|
4
|
+
class Result
|
|
5
|
+
attr_reader :raw
|
|
6
|
+
|
|
7
|
+
def initialize(json)
|
|
8
|
+
@raw = json
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def summary
|
|
12
|
+
raw["summary"]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def title
|
|
16
|
+
dig("extracted", "title")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def description
|
|
20
|
+
dig("extracted", "description")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def site_name
|
|
24
|
+
dig("extracted", "siteName")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def content
|
|
28
|
+
dig("extracted", "content")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def content_length
|
|
32
|
+
dig("extracted", "contentLength")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def media_type
|
|
36
|
+
dig("extracted", "mediaType")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def source
|
|
40
|
+
dig("extracted", "source")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def model
|
|
44
|
+
dig("llm", "model")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def provider
|
|
48
|
+
dig("llm", "provider")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def prompt
|
|
52
|
+
raw["prompt"]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def metrics
|
|
56
|
+
raw["metrics"]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def llm_metrics
|
|
60
|
+
dig("metrics", "llm") || []
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def total_tokens
|
|
64
|
+
llm_metrics.sum { |m| m["totalTokens"] || 0 }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def prompt_tokens
|
|
68
|
+
llm_metrics.sum { |m| m["promptTokens"] || 0 }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def completion_tokens
|
|
72
|
+
llm_metrics.sum { |m| m["completionTokens"] || 0 }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def input_kind
|
|
76
|
+
dig("input", "kind")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def slides
|
|
80
|
+
raw["slides"]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def success?
|
|
84
|
+
!summary.nil?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def extract_only?
|
|
88
|
+
summary.nil? && !content.nil?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_h
|
|
92
|
+
raw
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def dig(*keys)
|
|
98
|
+
keys.reduce(raw) { |hash, key| hash.is_a?(Hash) ? hash[key] : nil }
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
data/lib/summarize.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "summarize/version"
|
|
4
|
+
require_relative "summarize/configuration"
|
|
5
|
+
require_relative "summarize/errors"
|
|
6
|
+
require_relative "summarize/options"
|
|
7
|
+
require_relative "summarize/result"
|
|
8
|
+
require_relative "summarize/client"
|
|
9
|
+
|
|
10
|
+
module Summarize
|
|
11
|
+
class << self
|
|
12
|
+
attr_writer :configuration
|
|
13
|
+
|
|
14
|
+
def configuration
|
|
15
|
+
@configuration ||= Configuration.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configure
|
|
19
|
+
yield(configuration)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reset_configuration!
|
|
23
|
+
@configuration = Configuration.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Convenience method: summarize a URL or file path.
|
|
27
|
+
#
|
|
28
|
+
# Summarize.call("https://example.com", length: :short)
|
|
29
|
+
#
|
|
30
|
+
def call(input, **opts, &block)
|
|
31
|
+
Client.new.call(input, **opts, &block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Convenience method: summarize text content.
|
|
35
|
+
#
|
|
36
|
+
# Summarize.from_text("Long text...", length: :medium)
|
|
37
|
+
#
|
|
38
|
+
def from_text(text, **opts, &block)
|
|
39
|
+
Client.new.from_text(text, **opts, &block)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Convenience method: extract content without summarization.
|
|
43
|
+
#
|
|
44
|
+
# Summarize.extract("https://example.com", format: :md)
|
|
45
|
+
#
|
|
46
|
+
def extract(input, **opts)
|
|
47
|
+
Client.new.extract(input, **opts)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: summarize-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Martiano
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-02-19 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: json
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
description: A Ruby gem that wraps the summarize CLI tool, providing a clean Ruby
|
|
28
|
+
API for summarizing URLs, files, and text using various LLM providers.
|
|
29
|
+
email:
|
|
30
|
+
- hello@martiano.com
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- CHANGELOG.md
|
|
36
|
+
- LICENSE
|
|
37
|
+
- README.md
|
|
38
|
+
- lib/summarize.rb
|
|
39
|
+
- lib/summarize/client.rb
|
|
40
|
+
- lib/summarize/configuration.rb
|
|
41
|
+
- lib/summarize/errors.rb
|
|
42
|
+
- lib/summarize/options.rb
|
|
43
|
+
- lib/summarize/result.rb
|
|
44
|
+
- lib/summarize/version.rb
|
|
45
|
+
homepage: https://github.com/martiano/summarize-ruby
|
|
46
|
+
licenses:
|
|
47
|
+
- MIT
|
|
48
|
+
metadata:
|
|
49
|
+
homepage_uri: https://github.com/martiano/summarize-ruby
|
|
50
|
+
source_code_uri: https://github.com/martiano/summarize-ruby
|
|
51
|
+
changelog_uri: https://github.com/martiano/summarize-ruby/blob/main/CHANGELOG.md
|
|
52
|
+
bug_tracker_uri: https://github.com/martiano/summarize-ruby/issues
|
|
53
|
+
documentation_uri: https://rubydoc.info/gems/summarize-ruby
|
|
54
|
+
rubygems_mfa_required: 'true'
|
|
55
|
+
post_install_message:
|
|
56
|
+
rdoc_options: []
|
|
57
|
+
require_paths:
|
|
58
|
+
- lib
|
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: 3.1.0
|
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
requirements: []
|
|
70
|
+
rubygems_version: 3.3.27
|
|
71
|
+
signing_key:
|
|
72
|
+
specification_version: 4
|
|
73
|
+
summary: Ruby wrapper for the summarize CLI
|
|
74
|
+
test_files: []
|