exa-ai 0.3.0 → 0.4.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +94 -591
  3. data/exe/exa-ai +112 -9
  4. data/exe/exa-ai-answer +1 -5
  5. data/exe/exa-ai-context +1 -4
  6. data/exe/exa-ai-enrichment-cancel +107 -0
  7. data/exe/exa-ai-enrichment-create +235 -0
  8. data/exe/exa-ai-enrichment-delete +121 -0
  9. data/exe/exa-ai-enrichment-get +103 -0
  10. data/exe/exa-ai-enrichment-list +98 -0
  11. data/exe/exa-ai-enrichment-update +170 -0
  12. data/exe/exa-ai-find-similar +240 -0
  13. data/exe/exa-ai-get-contents +1 -4
  14. data/exe/exa-ai-research-get +1 -2
  15. data/exe/exa-ai-research-list +1 -2
  16. data/exe/exa-ai-research-start +1 -3
  17. data/exe/exa-ai-search +1 -3
  18. data/exe/exa-ai-webset-cancel +96 -0
  19. data/exe/exa-ai-webset-create +192 -0
  20. data/exe/exa-ai-webset-delete +110 -0
  21. data/exe/exa-ai-webset-get +92 -0
  22. data/exe/exa-ai-webset-item-delete +111 -0
  23. data/exe/exa-ai-webset-item-get +104 -0
  24. data/exe/exa-ai-webset-item-list +93 -0
  25. data/exe/exa-ai-webset-list +90 -0
  26. data/exe/exa-ai-webset-search-cancel +103 -0
  27. data/exe/exa-ai-webset-search-create +233 -0
  28. data/exe/exa-ai-webset-search-get +104 -0
  29. data/exe/exa-ai-webset-update +139 -0
  30. data/lib/exa/cli/base.rb +3 -3
  31. data/lib/exa/cli/formatters/enrichment_formatter.rb +69 -0
  32. data/lib/exa/cli/formatters/webset_formatter.rb +68 -0
  33. data/lib/exa/cli/formatters/webset_item_formatter.rb +69 -0
  34. data/lib/exa/client.rb +172 -0
  35. data/lib/exa/connection.rb +8 -1
  36. data/lib/exa/resources/webset.rb +74 -0
  37. data/lib/exa/resources/webset_collection.rb +33 -0
  38. data/lib/exa/resources/webset_enrichment.rb +71 -0
  39. data/lib/exa/resources/webset_enrichment_collection.rb +28 -0
  40. data/lib/exa/resources/webset_search.rb +112 -0
  41. data/lib/exa/services/parameter_converter.rb +1 -0
  42. data/lib/exa/services/websets/cancel.rb +36 -0
  43. data/lib/exa/services/websets/cancel_enrichment.rb +35 -0
  44. data/lib/exa/services/websets/cancel_search.rb +44 -0
  45. data/lib/exa/services/websets/create.rb +45 -0
  46. data/lib/exa/services/websets/create_enrichment.rb +35 -0
  47. data/lib/exa/services/websets/create_search.rb +48 -0
  48. data/lib/exa/services/websets/create_search_validator.rb +128 -0
  49. data/lib/exa/services/websets/create_validator.rb +189 -0
  50. data/lib/exa/services/websets/delete.rb +36 -0
  51. data/lib/exa/services/websets/delete_enrichment.rb +35 -0
  52. data/lib/exa/services/websets/delete_item.rb +20 -0
  53. data/lib/exa/services/websets/get_item.rb +20 -0
  54. data/lib/exa/services/websets/get_search.rb +43 -0
  55. data/lib/exa/services/websets/list.rb +25 -0
  56. data/lib/exa/services/websets/list_items.rb +20 -0
  57. data/lib/exa/services/websets/retrieve.rb +47 -0
  58. data/lib/exa/services/websets/retrieve_enrichment.rb +35 -0
  59. data/lib/exa/services/websets/update.rb +37 -0
  60. data/lib/exa/services/websets/update_enrichment.rb +36 -0
  61. data/lib/exa/services/websets_parameter_converter.rb +45 -0
  62. data/lib/exa/version.rb +1 -1
  63. data/lib/exa-ai.rb +5 -0
  64. data/lib/exa.rb +26 -0
  65. metadata +65 -3
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "exa-ai"
5
+ require "json"
6
+
7
+ # Parse arguments
8
+ webset_id = nil
9
+ metadata = nil
10
+ api_key = nil
11
+ output_format = "json"
12
+
13
+ # Recursively convert hash keys from strings to symbols
14
+ def deep_symbolize_keys(obj)
15
+ case obj
16
+ when Hash
17
+ obj.each_with_object({}) do |(key, value), result|
18
+ result[key.to_sym] = deep_symbolize_keys(value)
19
+ end
20
+ when Array
21
+ obj.map { |item| deep_symbolize_keys(item) }
22
+ else
23
+ obj
24
+ end
25
+ end
26
+
27
+ # Helper to parse JSON string or load from file
28
+ def parse_json_or_file(value)
29
+ json_data = if value.start_with?("@")
30
+ file_path = value[1..]
31
+ JSON.parse(File.read(file_path))
32
+ else
33
+ JSON.parse(value)
34
+ end
35
+ deep_symbolize_keys(json_data)
36
+ rescue JSON::ParserError => e
37
+ $stderr.puts "Error: Invalid JSON: #{e.message}"
38
+ exit 1
39
+ rescue Errno::ENOENT => e
40
+ $stderr.puts "Error: File not found: #{e.message}"
41
+ exit 1
42
+ end
43
+
44
+ args = ARGV.dup
45
+ while args.any?
46
+ arg = args.shift
47
+ case arg
48
+ when "--metadata"
49
+ metadata = parse_json_or_file(args.shift)
50
+ when "--api-key"
51
+ api_key = args.shift
52
+ when "--output-format"
53
+ output_format = args.shift
54
+ when "--help", "-h"
55
+ puts <<~HELP
56
+ Usage: exa-ai webset-update <webset_id> [OPTIONS]
57
+
58
+ Update a webset's metadata
59
+
60
+ Arguments:
61
+ webset_id ID of the webset to update (required)
62
+
63
+ Options:
64
+ --metadata JSON Custom metadata to update (supports @file.json)
65
+ --api-key KEY Exa API key (or set EXA_API_KEY env var)
66
+ --output-format FMT Output format: json, pretty, or text (default: json)
67
+ --help, -h Show this help message
68
+
69
+ Examples:
70
+ exa-ai webset-update ws_abc123 --metadata '{"project":"Q1-2025"}'
71
+ exa-ai webset-update ws_abc123 --metadata @metadata.json
72
+ exa-ai webset-update ws_abc123 --metadata '{"tags":["important"]}' --output-format pretty
73
+ HELP
74
+ exit 0
75
+ else
76
+ # First positional argument is webset_id
77
+ if webset_id.nil?
78
+ webset_id = arg
79
+ else
80
+ $stderr.puts "Unknown option: #{arg}"
81
+ exit 1
82
+ end
83
+ end
84
+ end
85
+
86
+ # Validate required arguments
87
+ if webset_id.nil?
88
+ $stderr.puts "Error: webset_id argument is required"
89
+ $stderr.puts "Usage: exa-ai webset-update <webset_id> [OPTIONS]"
90
+ $stderr.puts "Try 'exa-ai webset-update --help' for more information"
91
+ exit 1
92
+ end
93
+
94
+ if metadata.nil?
95
+ $stderr.puts "Error: --metadata is required"
96
+ $stderr.puts "Try 'exa-ai webset-update --help' for more information"
97
+ exit 1
98
+ end
99
+
100
+ begin
101
+ # Resolve API key and format
102
+ api_key = Exa::CLI::Base.resolve_api_key(api_key)
103
+ output_format = Exa::CLI::Base.resolve_output_format(output_format)
104
+
105
+ # Build client
106
+ client = Exa::CLI::Base.build_client(api_key)
107
+
108
+ # Update webset
109
+ webset = client.update_webset(webset_id, metadata: metadata)
110
+
111
+ # Format and output
112
+ output = Exa::CLI::Formatters::WebsetFormatter.format(webset, output_format)
113
+ puts output
114
+
115
+ rescue Exa::NotFound => e
116
+ $stderr.puts "Webset not found: #{e.message}"
117
+ exit 1
118
+ rescue Exa::ConfigurationError => e
119
+ $stderr.puts "Configuration error: #{e.message}"
120
+ exit 1
121
+ rescue Exa::Unauthorized => e
122
+ $stderr.puts "Authentication error: #{e.message}"
123
+ $stderr.puts "Check your API key (set EXA_API_KEY or use --api-key)"
124
+ exit 1
125
+ rescue Exa::ClientError => e
126
+ $stderr.puts "Client error: #{e.message}"
127
+ exit 1
128
+ rescue Exa::ServerError => e
129
+ $stderr.puts "Server error: #{e.message}"
130
+ $stderr.puts "The Exa API may be experiencing issues. Please try again later."
131
+ exit 1
132
+ rescue Exa::Error => e
133
+ $stderr.puts "Error: #{e.message}"
134
+ exit 1
135
+ rescue StandardError => e
136
+ $stderr.puts "Unexpected error: #{e.message}"
137
+ $stderr.puts e.backtrace.first(5) if ENV["DEBUG"]
138
+ exit 1
139
+ end
data/lib/exa/cli/base.rb CHANGED
@@ -11,7 +11,7 @@ module Exa
11
11
  env_key = ENV["EXA_API_KEY"]
12
12
  return env_key if env_key && !env_key.empty?
13
13
 
14
- raise ConfigurationError,
14
+ raise Exa::ConfigurationError,
15
15
  "Missing API key. Set EXA_API_KEY environment variable or use --api-key flag"
16
16
  end
17
17
 
@@ -24,13 +24,13 @@ module Exa
24
24
 
25
25
  return format if valid_formats.include?(format)
26
26
 
27
- raise ConfigurationError,
27
+ raise Exa::ConfigurationError,
28
28
  "Invalid output format: #{format}. Valid formats: #{valid_formats.join(', ')}"
29
29
  end
30
30
 
31
31
  # Build a client instance with the given API key
32
32
  def self.build_client(api_key, **options)
33
- Client.new(api_key: api_key, **options)
33
+ Exa::Client.new(api_key: api_key, **options)
34
34
  end
35
35
 
36
36
  # Format output data based on format type
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module CLI
5
+ module Formatters
6
+ class EnrichmentFormatter
7
+ def self.format(enrichment, output_format)
8
+ case output_format
9
+ when "json"
10
+ JSON.generate(enrichment.to_h)
11
+ when "pretty"
12
+ JSON.pretty_generate(enrichment.to_h)
13
+ when "text"
14
+ format_as_text(enrichment)
15
+ else
16
+ raise ArgumentError, "Unknown output format: #{output_format}"
17
+ end
18
+ end
19
+
20
+ def self.format_collection(collection, output_format)
21
+ case output_format
22
+ when "json"
23
+ JSON.generate(collection.to_h)
24
+ when "pretty"
25
+ JSON.pretty_generate(collection.to_h)
26
+ when "text"
27
+ format_collection_as_text(collection)
28
+ else
29
+ raise ArgumentError, "Unknown output format: #{output_format}"
30
+ end
31
+ end
32
+
33
+ def self.format_as_text(enrichment)
34
+ lines = []
35
+ lines << "Enrichment: #{enrichment.id}"
36
+ lines << "Webset: #{enrichment.webset_id}" if enrichment.webset_id
37
+ lines << "Status: #{enrichment.status}"
38
+ lines << "Title: #{enrichment.title}" if enrichment.title
39
+ lines << "Description: #{enrichment.description}" if enrichment.description
40
+ lines << "Format: #{enrichment.format}" if enrichment.format
41
+
42
+ if enrichment.options && !enrichment.options.empty?
43
+ lines << "\nOptions:"
44
+ enrichment.options.each do |option|
45
+ lines << " - #{option['label']}" if option['label']
46
+ end
47
+ end
48
+
49
+ lines << "Created: #{enrichment.created_at}" if enrichment.created_at
50
+ lines << "Updated: #{enrichment.updated_at}" if enrichment.updated_at
51
+
52
+ lines.join("\n")
53
+ end
54
+ private_class_method :format_as_text
55
+
56
+ def self.format_collection_as_text(collection)
57
+ lines = ["Enrichments (#{collection.data.length} items):"]
58
+ collection.data.each do |enr|
59
+ lines << "\n #{enr['id']}"
60
+ lines << " Status: #{enr['status']}"
61
+ lines << " Title: #{enr['title']}" if enr['title']
62
+ end
63
+ lines.join("\n")
64
+ end
65
+ private_class_method :format_collection_as_text
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module CLI
5
+ module Formatters
6
+ class WebsetFormatter
7
+ def self.format(webset, output_format)
8
+ case output_format
9
+ when "json"
10
+ JSON.generate(webset.to_h)
11
+ when "pretty"
12
+ JSON.pretty_generate(webset.to_h)
13
+ when "text"
14
+ format_as_text(webset)
15
+ else
16
+ raise ArgumentError, "Unknown output format: #{output_format}"
17
+ end
18
+ end
19
+
20
+ def self.format_collection(collection, output_format)
21
+ case output_format
22
+ when "json"
23
+ JSON.generate(collection.to_h)
24
+ when "pretty"
25
+ JSON.pretty_generate(collection.to_h)
26
+ when "text"
27
+ format_collection_as_text(collection)
28
+ else
29
+ raise ArgumentError, "Unknown output format: #{output_format}"
30
+ end
31
+ end
32
+
33
+ def self.format_as_text(webset)
34
+ lines = []
35
+ lines << "Webset: #{webset.id}"
36
+ lines << "Status: #{webset.status}"
37
+ lines << "Created: #{webset.created_at}" if webset.created_at
38
+ lines << "Updated: #{webset.updated_at}" if webset.updated_at
39
+
40
+ if webset.searches && !webset.searches.empty?
41
+ lines << "\nSearches:"
42
+ webset.searches.each do |search|
43
+ lines << " - #{search['query']}" if search['query']
44
+ end
45
+ end
46
+
47
+ if webset.enrichments && !webset.enrichments.empty?
48
+ lines << "\nEnrichments: #{webset.enrichments.length}"
49
+ end
50
+
51
+ lines.join("\n")
52
+ end
53
+ private_class_method :format_as_text
54
+
55
+ def self.format_collection_as_text(collection)
56
+ lines = ["Websets (#{collection.data.length} items):"]
57
+ collection.data.each do |ws|
58
+ lines << "\n #{ws['id']}"
59
+ lines << " Status: #{ws['status']}"
60
+ lines << " Created: #{ws['createdAt']}" if ws['createdAt']
61
+ end
62
+ lines.join("\n")
63
+ end
64
+ private_class_method :format_collection_as_text
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module CLI
5
+ module Formatters
6
+ class WebsetItemFormatter
7
+ def self.format(item, output_format)
8
+ case output_format
9
+ when "json"
10
+ JSON.generate(item)
11
+ when "pretty"
12
+ JSON.pretty_generate(item)
13
+ when "text"
14
+ format_as_text(item)
15
+ else
16
+ raise ArgumentError, "Unknown output format: #{output_format}"
17
+ end
18
+ end
19
+
20
+ def self.format_collection(items, output_format)
21
+ case output_format
22
+ when "json"
23
+ JSON.generate(items)
24
+ when "pretty"
25
+ JSON.pretty_generate(items)
26
+ when "text"
27
+ format_collection_as_text(items)
28
+ else
29
+ raise ArgumentError, "Unknown output format: #{output_format}"
30
+ end
31
+ end
32
+
33
+ def self.format_as_text(item)
34
+ lines = []
35
+ lines << "Item: #{item['id']}"
36
+ lines << "URL: #{item['url']}" if item['url']
37
+ lines << "Title: #{item['title']}" if item['title']
38
+ lines << "Status: #{item['status']}" if item['status']
39
+ lines << "Created: #{item['createdAt']}" if item['createdAt']
40
+ lines << "Updated: #{item['updatedAt']}" if item['updatedAt']
41
+
42
+ if item['entity']
43
+ lines << "\nEntity:"
44
+ lines << " Type: #{item['entity']['type']}" if item['entity']['type']
45
+ lines << " Name: #{item['entity']['name']}" if item['entity']['name']
46
+ end
47
+
48
+ lines.join("\n")
49
+ end
50
+ private_class_method :format_as_text
51
+
52
+ def self.format_collection_as_text(items)
53
+ lines = ["Items (#{items.length} total):"]
54
+ items.each_with_index do |item, idx|
55
+ lines << "\n#{idx + 1}. #{item['id']}"
56
+ lines << " URL: #{item['url']}" if item['url']
57
+ lines << " Title: #{item['title']}" if item['title']
58
+ lines << " Status: #{item['status']}" if item['status']
59
+ if item['entity'] && item['entity']['name']
60
+ lines << " Entity: #{item['entity']['name']}"
61
+ end
62
+ end
63
+ lines.join("\n")
64
+ end
65
+ private_class_method :format_collection_as_text
66
+ end
67
+ end
68
+ end
69
+ end
data/lib/exa/client.rb CHANGED
@@ -148,6 +148,177 @@ module Exa
148
148
  search(query, type: "keyword", includeDomains: ["linkedin.com/in"], **params)
149
149
  end
150
150
 
151
+ # List all websets
152
+ #
153
+ # @param params [Hash] Pagination parameters
154
+ # @option params [String] :cursor Cursor for pagination
155
+ # @option params [Integer] :limit Maximum number of websets to return
156
+ # @return [Resources::WebsetCollection] Paginated list of websets
157
+ def list_websets(**params)
158
+ Services::Websets::List.new(connection, **params).call
159
+ end
160
+
161
+ # Get a specific webset by ID
162
+ #
163
+ # @param id [String] Webset ID
164
+ # @param params [Hash] Optional parameters
165
+ # @option params [Array<String>] :expand Resources to expand in response (e.g., ['items'])
166
+ # @return [Resources::Webset] The requested webset
167
+ def get_webset(id, **params)
168
+ Services::Websets::Retrieve.new(connection, id: id, **params).call
169
+ end
170
+
171
+ # Delete a webset
172
+ #
173
+ # @param id [String] Webset ID
174
+ # @return [Resources::Webset] The deleted webset
175
+ def delete_webset(id)
176
+ Services::Websets::Delete.new(connection, id: id).call
177
+ end
178
+
179
+ # Cancel in-progress operations on a webset
180
+ #
181
+ # @param id [String] Webset ID
182
+ # @return [Resources::Webset] The webset with cancelled operations
183
+ def cancel_webset(id)
184
+ Services::Websets::Cancel.new(connection, id: id).call
185
+ end
186
+
187
+ # Update a webset's metadata
188
+ #
189
+ # @param id [String] Webset ID
190
+ # @param params [Hash] Update parameters
191
+ # @option params [Hash] :metadata Metadata to update
192
+ # @return [Resources::Webset] The updated webset
193
+ def update_webset(id, **params)
194
+ Services::Websets::Update.new(connection, id: id, **params).call
195
+ end
196
+
197
+ # Create a new webset
198
+ #
199
+ # @param params [Hash] Creation parameters
200
+ # @option params [Hash] :search Search configuration
201
+ # @return [Resources::Webset] The newly created webset
202
+ def create_webset(**params)
203
+ Services::Websets::Create.new(connection, **params).call
204
+ end
205
+
206
+ # Create a new enrichment for a webset
207
+ #
208
+ # @param webset_id [String] Webset ID
209
+ # @param params [Hash] Enrichment parameters
210
+ # @option params [String] :description Description of data to extract
211
+ # @option params [String] :format Format type (text, url, options, etc.)
212
+ # @option params [Array<Hash>] :options Options for enrichment
213
+ # @option params [Hash] :metadata Custom metadata
214
+ # @return [Resources::WebsetEnrichment] The newly created enrichment
215
+ def create_enrichment(webset_id:, **params)
216
+ Services::Websets::CreateEnrichment.new(connection, webset_id: webset_id, **params).call
217
+ end
218
+
219
+ # Get a specific enrichment by ID
220
+ #
221
+ # @param webset_id [String] Webset ID
222
+ # @param id [String] Enrichment ID
223
+ # @return [Resources::WebsetEnrichment] The requested enrichment
224
+ def get_enrichment(webset_id:, id:)
225
+ Services::Websets::RetrieveEnrichment.new(connection, webset_id: webset_id, id: id).call
226
+ end
227
+
228
+ # Update an enrichment
229
+ #
230
+ # @param webset_id [String] Webset ID
231
+ # @param id [String] Enrichment ID
232
+ # @param params [Hash] Update parameters
233
+ # @option params [String] :description Updated description
234
+ # @option params [String] :format Updated format
235
+ # @option params [Array<Hash>] :options Updated options
236
+ # @option params [Hash] :metadata Updated metadata
237
+ # @return [Resources::WebsetEnrichment] The updated enrichment
238
+ def update_enrichment(webset_id:, id:, **params)
239
+ Services::Websets::UpdateEnrichment.new(connection, webset_id: webset_id, id: id, **params).call
240
+ end
241
+
242
+ # Delete an enrichment
243
+ #
244
+ # @param webset_id [String] Webset ID
245
+ # @param id [String] Enrichment ID
246
+ # @return [Resources::WebsetEnrichment] The deleted enrichment
247
+ def delete_enrichment(webset_id:, id:)
248
+ Services::Websets::DeleteEnrichment.new(connection, webset_id: webset_id, id: id).call
249
+ end
250
+
251
+ # Cancel a running enrichment
252
+ #
253
+ # @param webset_id [String] Webset ID
254
+ # @param id [String] Enrichment ID
255
+ # @return [Resources::WebsetEnrichment] The cancelled enrichment
256
+ def cancel_enrichment(webset_id:, id:)
257
+ Services::Websets::CancelEnrichment.new(connection, webset_id: webset_id, id: id).call
258
+ end
259
+
260
+ # Create a new search within a webset
261
+ #
262
+ # @param webset_id [String] Webset ID
263
+ # @param params [Hash] Search parameters
264
+ # @option params [String] :query The search query (required)
265
+ # @option params [Integer] :count Number of results to find
266
+ # @option params [Hash] :entity Entity type specification
267
+ # @option params [Array<Hash>] :criteria Search criteria
268
+ # @option params [Array<Hash>] :exclude Items to exclude from results
269
+ # @option params [Array<Hash>] :scope Limit search to specific sources
270
+ # @option params [Boolean] :recall Whether to estimate total available results
271
+ # @option params [String] :behavior "override" or "append" (default: "override")
272
+ # @option params [Hash] :metadata Custom metadata
273
+ # @return [Resources::WebsetSearch] The newly created search
274
+ def create_webset_search(webset_id:, **params)
275
+ Services::Websets::CreateSearch.new(connection, webset_id: webset_id, **params).call
276
+ end
277
+
278
+ # Get a webset search by ID
279
+ #
280
+ # @param webset_id [String] Webset ID
281
+ # @param id [String] Search ID
282
+ # @return [Resources::WebsetSearch] The requested search
283
+ def get_webset_search(webset_id:, id:)
284
+ Services::Websets::GetSearch.new(connection, webset_id: webset_id, id: id).call
285
+ end
286
+
287
+ # Cancel a webset search
288
+ #
289
+ # @param webset_id [String] Webset ID
290
+ # @param id [String] Search ID
291
+ # @return [Resources::WebsetSearch] The cancelled search
292
+ def cancel_webset_search(webset_id:, id:)
293
+ Services::Websets::CancelSearch.new(connection, webset_id: webset_id, id: id).call
294
+ end
295
+
296
+ # Get a webset item by ID
297
+ #
298
+ # @param webset_id [String] Webset ID
299
+ # @param id [String] Item ID
300
+ # @return [Hash] The requested item
301
+ def get_item(webset_id:, id:)
302
+ Services::Websets::GetItem.new(connection, webset_id: webset_id, id: id).call
303
+ end
304
+
305
+ # Delete a webset item by ID
306
+ #
307
+ # @param webset_id [String] Webset ID
308
+ # @param id [String] Item ID
309
+ # @return [Boolean] True if deletion was successful
310
+ def delete_item(webset_id:, id:)
311
+ Services::Websets::DeleteItem.new(connection, webset_id: webset_id, id: id).call
312
+ end
313
+
314
+ # List all items in a webset
315
+ #
316
+ # @param webset_id [String] Webset ID
317
+ # @return [Array<Hash>] Array of items
318
+ def list_items(webset_id:)
319
+ Services::Websets::ListItems.new(connection, webset_id: webset_id).call
320
+ end
321
+
151
322
  private
152
323
 
153
324
  def connection
@@ -161,6 +332,7 @@ module Exa
161
332
  options = {}
162
333
  options[:base_url] = @options[:base_url] if @options[:base_url]
163
334
  options[:timeout] = @options[:timeout] if @options[:timeout]
335
+ options[:debug] = true if ENV["EXA_DEBUG"]
164
336
  options
165
337
  end
166
338
 
@@ -1,17 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "faraday"
4
+ require "logger"
4
5
 
5
6
  module Exa
6
7
  class Connection
8
+ DEFAULT_BASE_URL = "https://api.exa.ai"
7
9
  def self.build(api_key:, **options, &block)
8
10
  Faraday.new(url: options[:base_url] || DEFAULT_BASE_URL) do |conn|
9
11
  # Authentication
10
- conn.request :authorization, "Bearer", api_key
12
+ conn.headers["x-api-key"] = api_key
11
13
 
12
14
  # Request/Response JSON encoding
13
15
  conn.request :json
14
16
 
17
+ # Debug logging (when enabled via option)
18
+ if options[:debug]
19
+ conn.response :logger, Logger.new($stdout), headers: true, bodies: true
20
+ end
21
+
15
22
  # Custom error handling (registered before JSON so it runs after in response chain)
16
23
  conn.response :raise_error
17
24
  conn.response :json, content_type: /\bjson$/
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Resources
5
+ # Represents a webset from the Exa API
6
+ #
7
+ # A webset is a collection of web entities (companies, people, etc.)
8
+ # discovered through searches, imports, and enrichments.
9
+ class Webset < Struct.new(
10
+ :id,
11
+ :object,
12
+ :status,
13
+ :external_id,
14
+ :title,
15
+ :searches,
16
+ :imports,
17
+ :enrichments,
18
+ :monitors,
19
+ :excludes,
20
+ :metadata,
21
+ :created_at,
22
+ :updated_at,
23
+ :items,
24
+ keyword_init: true
25
+ )
26
+ def initialize(
27
+ id:,
28
+ object:,
29
+ status:,
30
+ external_id: nil,
31
+ title: nil,
32
+ searches: nil,
33
+ imports: nil,
34
+ enrichments: nil,
35
+ monitors: nil,
36
+ excludes: nil,
37
+ metadata: nil,
38
+ created_at: nil,
39
+ updated_at: nil,
40
+ items: nil
41
+ )
42
+ super
43
+ freeze
44
+ end
45
+
46
+ def idle?
47
+ status == "idle"
48
+ end
49
+
50
+ def processing?
51
+ status == "processing"
52
+ end
53
+
54
+ def to_h
55
+ {
56
+ id: id,
57
+ object: object,
58
+ status: status,
59
+ external_id: external_id,
60
+ title: title,
61
+ searches: searches,
62
+ imports: imports,
63
+ enrichments: enrichments,
64
+ monitors: monitors,
65
+ excludes: excludes,
66
+ metadata: metadata,
67
+ created_at: created_at,
68
+ updated_at: updated_at,
69
+ items: items
70
+ }.compact
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Resources
5
+ # Represents a paginated list of websets from the Exa API
6
+ #
7
+ # This class wraps the JSON response from the GET /websets/v0/websets endpoint
8
+ # and provides pagination support.
9
+ class WebsetCollection < Struct.new(
10
+ :data,
11
+ :has_more,
12
+ :next_cursor,
13
+ keyword_init: true
14
+ )
15
+ def initialize(data:, has_more: false, next_cursor: nil)
16
+ super
17
+ freeze
18
+ end
19
+
20
+ def empty?
21
+ data.empty?
22
+ end
23
+
24
+ def to_h
25
+ {
26
+ data: data,
27
+ has_more: has_more,
28
+ next_cursor: next_cursor
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end