firecrawlrb 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: 6750678d7f156f49281a7ee81cebebab83b09a9241c7ab04efe28041d6498498
4
+ data.tar.gz: 4cbf4932ccb8f642d33627ff8fb4933f37c90888c36ed1ad0de57e129bd76731
5
+ SHA512:
6
+ metadata.gz: 806574da79377099e9d58f93e873145681bad840355397918099562179fc57d48add2a41f597251d3bb9ddf574d24aa1aa34af487f14f1ff4af9a6f40b61ad55
7
+ data.tar.gz: 8a4fd4b8f0996211fce4ca0686e7290d75c297dae1ee7b12a1810396e17a3ce8504cbb33c51e3565cd4c15c839092200f79e0e85cc6a060b1beff75e80227aba
data/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # Firecrawlrb
2
+
3
+ A zero-dependency Ruby client for the [Firecrawl API](https://www.firecrawl.dev). Uses only Ruby standard library (`Net::HTTP`, `URI`, `JSON`).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'firecrawlrb', path: '../firecrawlrb'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Configuration
22
+
23
+ You can provide your Firecrawl API key via an environment variable:
24
+
25
+ ```bash
26
+ export FIRECRAWL_API_KEY='fc-your_api_key_here'
27
+ ```
28
+
29
+ Or via a configuration block in a Rails initializer (`config/initializers/firecrawlrb.rb`):
30
+
31
+ ```ruby
32
+ require 'firecrawlrb'
33
+
34
+ Firecrawlrb.configure do |config|
35
+ config.api_key = ENV['FIRECRAWL_API_KEY']
36
+ end
37
+ ```
38
+
39
+ Or pass it directly when initializing the client:
40
+
41
+ ```ruby
42
+ client = Firecrawlrb::Client.new(api_key: 'fc-your_api_key_here')
43
+ ```
44
+
45
+ ### LLM JSON Extraction
46
+
47
+ You can specify a target URL, schema (concise Ruby Hash/Array or raw JSON Schema), and prompt to receive back JSON matching your schema:
48
+
49
+ #### Concise Ruby Schema Example
50
+
51
+ ```ruby
52
+ client = Firecrawlrb::Client.new
53
+
54
+ schema = {
55
+ products: [
56
+ {
57
+ url: :string,
58
+ image_url: :string,
59
+ price: :string,
60
+ name: :string
61
+ }
62
+ ]
63
+ }
64
+
65
+ prompt = "extract the products from the main product list site for https://www.blindster.com"
66
+
67
+ products = client.extract(
68
+ url: "https://www.blindster.com",
69
+ schema: schema,
70
+ prompt: prompt
71
+ )
72
+
73
+ puts products
74
+ # => { "products" => [ { "name" => "...", "price" => "...", "image_url" => "...", "url" => "..." } ] }
75
+ ```
76
+
77
+ #### Raw JSON Schema Example
78
+
79
+ ```ruby
80
+ schema = {
81
+ type: "object",
82
+ required: [],
83
+ properties: {
84
+ products: {
85
+ type: "array",
86
+ items: {
87
+ type: "object",
88
+ required: [],
89
+ properties: {
90
+ url: { type: "string" },
91
+ image_url: { type: "string" },
92
+ price: { type: "string" },
93
+ name: { type: "string" }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ products = client.extract(
101
+ url: "https://www.blindster.com",
102
+ schema: schema,
103
+ prompt: prompt
104
+ )
105
+ ```
106
+
107
+ ## License
108
+
109
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+
7
+ module Firecrawlrb
8
+ class Client
9
+ API_URL = "https://api.firecrawl.dev/v2/scrape".freeze
10
+
11
+ attr_reader :api_key, :api_url
12
+
13
+ def initialize(api_key: nil, api_url: nil)
14
+ @api_key = api_key || ENV['FIRECRAWL_API_KEY'] || Firecrawlrb.configuration.api_key
15
+ @api_url = api_url || Firecrawlrb.configuration.api_url || API_URL
16
+ raise ArgumentError, "API key is required. Set ENV['FIRECRAWL_API_KEY'], use Firecrawlrb.configure, or pass it to Client.new" if @api_key.nil? || @api_key.empty?
17
+ end
18
+
19
+ # Extract JSON matching a schema using Firecrawl v2 API
20
+ #
21
+ # @param url_or_kw [String, nil] Target URL if positional argument
22
+ # @param schema_arg [Hash, Array, nil] Schema if positional argument
23
+ # @param prompt_arg [String, nil] Prompt if positional argument
24
+ # @param url [String, nil] Target URL if keyword argument
25
+ # @param schema [Hash, Array, nil] Schema if keyword argument
26
+ # @param prompt [String, nil] Prompt if keyword argument
27
+ # @param only_main_content [Boolean] Whether to extract main content only (default true)
28
+ # @param max_age [Integer] Cache max age in ms (default 172800000)
29
+ # @param options [Hash] Additional options to merge into payload
30
+ # @return [Hash, Array, nil] The parsed extracted JSON
31
+ def extract(url_or_kw = nil, schema_arg = nil, prompt_arg = nil, url: nil, schema: nil, prompt: nil, only_main_content: true, max_age: 172800000, options: {})
32
+ target_url = url || url_or_kw
33
+ target_schema = schema || schema_arg
34
+ target_prompt = prompt || prompt_arg
35
+
36
+ raise ArgumentError, "url is required" if target_url.nil? || target_url.to_s.empty?
37
+ raise ArgumentError, "schema is required" if target_schema.nil?
38
+
39
+ normalized_schema = Firecrawlrb::Schema.normalize(target_schema)
40
+
41
+ format_hash = {
42
+ type: "json",
43
+ schema: normalized_schema
44
+ }
45
+ format_hash[:prompt] = target_prompt if target_prompt && !target_prompt.to_s.empty?
46
+
47
+ payload = {
48
+ url: target_url.to_s,
49
+ onlyMainContent: only_main_content,
50
+ maxAge: max_age,
51
+ parsers: [],
52
+ formats: [format_hash]
53
+ }.merge(options)
54
+
55
+ response = request(payload)
56
+ parsed = parse_response(response)
57
+
58
+ if parsed.is_a?(Hash) && parsed.key?("data") && parsed["data"].is_a?(Hash) && parsed["data"].key?("json")
59
+ parsed["data"]["json"]
60
+ elsif parsed.is_a?(Hash) && parsed.key?("json")
61
+ parsed["json"]
62
+ else
63
+ parsed
64
+ end
65
+ end
66
+
67
+ alias scrape extract
68
+
69
+ private
70
+
71
+ def request(payload)
72
+ uri = URI.parse(@api_url)
73
+ http = Net::HTTP.new(uri.host, uri.port)
74
+ http.use_ssl = (uri.scheme == "https")
75
+ http.read_timeout = 180
76
+ http.open_timeout = 180
77
+
78
+ req = Net::HTTP::Post.new(uri.request_uri)
79
+ req['Authorization'] = "Bearer #{@api_key}"
80
+ req['Content-Type'] = 'application/json'
81
+ req.body = JSON.generate(payload)
82
+
83
+ http.request(req)
84
+ end
85
+
86
+ def parse_response(response)
87
+ body = response.body
88
+ begin
89
+ parsed = JSON.parse(body)
90
+ rescue JSON::ParserError
91
+ raise Error, "Invalid JSON response from Firecrawl API (#{response.code}): #{body.to_s[0..100]}..."
92
+ end
93
+
94
+ unless response.is_a?(Net::HTTPSuccess)
95
+ error_msg = parsed['error'] || parsed['message'] || response.message
96
+ raise Error, "Firecrawl API Error (#{response.code}): #{error_msg}"
97
+ end
98
+
99
+ parsed
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "firecrawlrb/client"
4
+
5
+ module Firecrawlrb
6
+ class Error < StandardError; end
7
+
8
+ class Configuration
9
+ attr_accessor :api_key, :api_url
10
+
11
+ def initialize
12
+ @api_key = nil
13
+ @api_url = "https://api.firecrawl.dev/v2/scrape"
14
+ end
15
+ end
16
+
17
+ module Schema
18
+ TYPE_MAPPING = {
19
+ string: "string",
20
+ number: "number",
21
+ float: "number",
22
+ decimal: "number",
23
+ integer: "integer",
24
+ int: "integer",
25
+ boolean: "boolean",
26
+ bool: "boolean",
27
+ array: "array",
28
+ object: "object"
29
+ }.freeze
30
+
31
+ def self.normalize(input)
32
+ case input
33
+ when Hash
34
+ # If it already looks like a raw JSON Schema (has :type or "type" key)
35
+ if input.key?(:type) || input.key?("type") || input.key?(:properties) || input.key?("properties")
36
+ input
37
+ else
38
+ properties = {}
39
+ input.each do |key, value|
40
+ properties[key.to_s] = normalize(value)
41
+ end
42
+ {
43
+ type: "object",
44
+ required: [],
45
+ properties: properties
46
+ }
47
+ end
48
+ when Array
49
+ if input.empty?
50
+ { type: "array", items: { type: "string" } }
51
+ else
52
+ { type: "array", items: normalize(input.first) }
53
+ end
54
+ when Symbol, String
55
+ type_str = input.to_s.downcase.to_sym
56
+ json_type = TYPE_MAPPING[type_str] || input.to_s
57
+ { type: json_type }
58
+ else
59
+ { type: "string" }
60
+ end
61
+ end
62
+ end
63
+
64
+ class << self
65
+ def configuration
66
+ @configuration ||= Configuration.new
67
+ end
68
+
69
+ def configuration=(config)
70
+ @configuration = config
71
+ end
72
+
73
+ def configure
74
+ yield(configuration)
75
+ end
76
+
77
+ def reset
78
+ @configuration = Configuration.new
79
+ end
80
+ end
81
+ end
metadata ADDED
@@ -0,0 +1,43 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: firecrawlrb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Firecrawlrb Author
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Simple zero-dependency Ruby gem for Firecrawl API LLM JSON extraction.
13
+ email:
14
+ - author@example.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - README.md
20
+ - lib/firecrawlrb.rb
21
+ - lib/firecrawlrb/client.rb
22
+ homepage: ''
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.5.0
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubygems_version: 3.6.9
41
+ specification_version: 4
42
+ summary: A zero-dependency Ruby client for the Firecrawl API
43
+ test_files: []