exa-ai 0.3.1 → 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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +58 -17
  3. data/exe/exa-ai +111 -6
  4. data/exe/exa-ai-enrichment-cancel +107 -0
  5. data/exe/exa-ai-enrichment-create +235 -0
  6. data/exe/exa-ai-enrichment-delete +121 -0
  7. data/exe/exa-ai-enrichment-get +103 -0
  8. data/exe/exa-ai-enrichment-list +98 -0
  9. data/exe/exa-ai-enrichment-update +170 -0
  10. data/exe/exa-ai-find-similar +240 -0
  11. data/exe/exa-ai-webset-cancel +96 -0
  12. data/exe/exa-ai-webset-create +192 -0
  13. data/exe/exa-ai-webset-delete +110 -0
  14. data/exe/exa-ai-webset-get +92 -0
  15. data/exe/exa-ai-webset-item-delete +111 -0
  16. data/exe/exa-ai-webset-item-get +104 -0
  17. data/exe/exa-ai-webset-item-list +93 -0
  18. data/exe/exa-ai-webset-list +90 -0
  19. data/exe/exa-ai-webset-search-cancel +103 -0
  20. data/exe/exa-ai-webset-search-create +233 -0
  21. data/exe/exa-ai-webset-search-get +104 -0
  22. data/exe/exa-ai-webset-update +139 -0
  23. data/lib/exa/cli/formatters/enrichment_formatter.rb +69 -0
  24. data/lib/exa/cli/formatters/webset_formatter.rb +68 -0
  25. data/lib/exa/cli/formatters/webset_item_formatter.rb +69 -0
  26. data/lib/exa/client.rb +171 -0
  27. data/lib/exa/resources/webset.rb +74 -0
  28. data/lib/exa/resources/webset_collection.rb +33 -0
  29. data/lib/exa/resources/webset_enrichment.rb +71 -0
  30. data/lib/exa/resources/webset_enrichment_collection.rb +28 -0
  31. data/lib/exa/resources/webset_search.rb +112 -0
  32. data/lib/exa/services/parameter_converter.rb +1 -0
  33. data/lib/exa/services/websets/cancel.rb +36 -0
  34. data/lib/exa/services/websets/cancel_enrichment.rb +35 -0
  35. data/lib/exa/services/websets/cancel_search.rb +44 -0
  36. data/lib/exa/services/websets/create.rb +45 -0
  37. data/lib/exa/services/websets/create_enrichment.rb +35 -0
  38. data/lib/exa/services/websets/create_search.rb +48 -0
  39. data/lib/exa/services/websets/create_search_validator.rb +128 -0
  40. data/lib/exa/services/websets/create_validator.rb +189 -0
  41. data/lib/exa/services/websets/delete.rb +36 -0
  42. data/lib/exa/services/websets/delete_enrichment.rb +35 -0
  43. data/lib/exa/services/websets/delete_item.rb +20 -0
  44. data/lib/exa/services/websets/get_item.rb +20 -0
  45. data/lib/exa/services/websets/get_search.rb +43 -0
  46. data/lib/exa/services/websets/list.rb +25 -0
  47. data/lib/exa/services/websets/list_items.rb +20 -0
  48. data/lib/exa/services/websets/retrieve.rb +47 -0
  49. data/lib/exa/services/websets/retrieve_enrichment.rb +35 -0
  50. data/lib/exa/services/websets/update.rb +37 -0
  51. data/lib/exa/services/websets/update_enrichment.rb +36 -0
  52. data/lib/exa/services/websets_parameter_converter.rb +45 -0
  53. data/lib/exa/version.rb +1 -1
  54. data/lib/exa.rb +25 -0
  55. metadata +64 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a7200f1be7297291b6fa6ecddfb0e87d49a9ef570a6bb73c00b7993a459daea
4
- data.tar.gz: ac3a183a8ac848007659ab36cc759780e9724c9f2fdc5159b149eaf0633402af
3
+ metadata.gz: 4cc4ccfc59e93013765b9289621cdb0447c4cc5bbc24e1585376ebae4b7b6ff0
4
+ data.tar.gz: 45cbb8655d636906d1230651896aeab9375720e655ca4d18c74f12a425ea5bd7
5
5
  SHA512:
6
- metadata.gz: 1dff4bc2b63bf920a8b732c4c0f465566fab48c066d1cb9ebc1e6f7f779ee86a780225ac96197d835296c1e780ee914cde34e60cd6f83a04af805f82692d9019
7
- data.tar.gz: 015e85d8c37a4731c170e57129f997a3ff4960e1464aaecc5a42c4ac45d43a475b628ee776a1c6f28d32fa74b73c1bdab5d32e9b66385cb673bce3c7ca09532a
6
+ metadata.gz: 4d569c43e3d9071accaeb081c6c8b51bbd0248258aa36331a8874ae17b648686bf22da5d73faee00b0a8d4a6b2b5a62dc47bcec5e4d4d7202d20870dca4ebb4e
7
+ data.tar.gz: 41c7f61f7de1dcc31fec84588cc2ed6287ddec597554cc18d7a9afbc0b044a790792a598ddfaef68e12174178a373081c3274e6240f6c91f908db14101fc4887
data/README.md CHANGED
@@ -65,11 +65,15 @@ end
65
65
  client = Exa::Client.new
66
66
 
67
67
  # Search the web
68
- results = client.search("ruby programming")
68
+ results = client.search("Ruby programming language")
69
69
  results.results.each { |item| puts "#{item['title']}: #{item['url']}" }
70
70
 
71
+ # Find similar content
72
+ similar = client.find_similar("https://arxiv.org/abs/2307.06435")
73
+ similar.results.each { |item| puts item['url'] }
74
+
71
75
  # Get an answer to a question
72
- answer = client.answer("What are the latest trends in AI?")
76
+ answer = client.answer("What is machine learning?")
73
77
  puts answer.answer
74
78
 
75
79
  # Find code examples
@@ -77,38 +81,75 @@ code = client.context("React hooks")
77
81
  puts code.response
78
82
 
79
83
  # Get page contents
80
- contents = client.get_contents(["https://example.com"])
84
+ contents = client.get_contents(["https://ruby-lang.org"])
81
85
  puts contents.results.first["text"]
82
86
  ```
83
87
 
84
88
  ### Command Line
85
89
 
86
90
  ```bash
87
- # Search the web
88
- exa-ai search "ruby programming"
89
-
90
- # Answer a question
91
+ # Core Search Commands
92
+ exa-ai search "Ruby programming language"
93
+ exa-ai find-similar "https://arxiv.org/abs/2307.06435"
91
94
  exa-ai answer "What is machine learning?"
92
-
93
- # Find code examples
94
- exa-ai context "async/await error handling"
95
-
96
- # Get page contents
97
- exa-ai get-contents "https://example.com"
98
-
99
- # Start a research task
100
- exa-ai research-start --instructions "Analyze recent ML papers" --wait
95
+ exa-ai context "React hooks" --tokens-num 5000
96
+ exa-ai get-contents "https://ruby-lang.org"
97
+
98
+ # Research Commands
99
+ exa-ai research-start --instructions "What species of ant are similar to honeypot ants?"
100
+ exa-ai research-get RESEARCH_ID
101
+ exa-ai research-list
102
+
103
+ # Webset Management
104
+ exa-ai webset-create --search '{"query":"technology companies","count":1}'
105
+ exa-ai webset-list --limit 5
106
+ exa-ai webset-get WEBSET_ID
107
+ exa-ai webset-update WEBSET_ID --metadata '{"updated":"true","version":"2"}'
108
+ exa-ai webset-delete WEBSET_ID --force
109
+ exa-ai webset-cancel WEBSET_ID
110
+
111
+ # Webset Searches
112
+ exa-ai webset-search-create WEBSET_ID --query "Ford Mustang" --entity custom --entity-description "vintage cars"
113
+ exa-ai webset-search-create WEBSET_ID --query "tech CEOs" --entity person --count 20
114
+ exa-ai webset-search-create WEBSET_ID --query "Y Combinator startups" --entity company
115
+ exa-ai webset-search-get WEBSET_ID SEARCH_ID
116
+ exa-ai webset-search-cancel WEBSET_ID SEARCH_ID
117
+
118
+ # Webset Items
119
+ exa-ai webset-item-list WEBSET_ID
120
+ exa-ai webset-item-get WEBSET_ID ITEM_ID
121
+ exa-ai webset-item-delete WEBSET_ID ITEM_ID --force
122
+
123
+ # Enrichments
124
+ exa-ai enrichment-create WEBSET_ID --description "Find company email" --format text
125
+ exa-ai enrichment-create WEBSET_ID --description "Company size category" --format options --options '[{"label":"Small (1-10)"},{"label":"Medium (11-50)"},{"label":"Large (51+)"}]'
126
+ exa-ai enrichment-list WEBSET_ID
127
+ exa-ai enrichment-get WEBSET_ID ENRICHMENT_ID
128
+ exa-ai enrichment-update WEBSET_ID ENRICHMENT_ID --description "Updated description"
129
+ exa-ai enrichment-delete WEBSET_ID ENRICHMENT_ID --force
130
+ exa-ai enrichment-cancel WEBSET_ID ENRICHMENT_ID
101
131
  ```
102
132
 
103
133
  ## Features
104
134
 
105
135
  The gem provides complete access to Exa's API endpoints:
106
136
 
137
+ ### Core Search
107
138
  - **Search** — Neural and keyword search across billions of web pages
139
+ - **Find Similar** — Discover content similar to a given URL
108
140
  - **Answer** — Generate comprehensive answers with source citations
109
141
  - **Context** — Find relevant code and documentation snippets
110
142
  - **Get Contents** — Extract full text content from web pages
111
- - **Research** — Start and manage long-running research tasks with AI
143
+
144
+ ### Research
145
+ - **Research Tasks** — Start and manage long-running research tasks with AI
146
+ - **Task Management** — Get status updates and list all research tasks
147
+
148
+ ### Websets
149
+ - **Webset Management** — Create, update, delete, and list datasets of web pages
150
+ - **Webset Searches** — Run searches within websets and manage search tasks
151
+ - **Webset Items** — List, retrieve, and manage individual items in websets
152
+ - **Enrichments** — Create and manage AI-powered data enrichment tasks on websets
112
153
 
113
154
  ## Error Handling
114
155
 
data/exe/exa-ai CHANGED
@@ -8,12 +8,31 @@ require "exa-ai"
8
8
  module ExaCLI
9
9
  AVAILABLE_COMMANDS = {
10
10
  "search" => "Search the web",
11
+ "find-similar" => "Find content similar to a URL",
11
12
  "answer" => "Generate an answer to a question",
12
13
  "context" => "Get code context from repositories",
13
14
  "get-contents" => "Retrieve page contents",
14
15
  "research-start" => "Start a research task",
15
16
  "research-get" => "Get research task status",
16
- "research-list" => "List research tasks"
17
+ "research-list" => "List research tasks",
18
+ "webset-create" => "Create a new webset",
19
+ "webset-get" => "Get webset details",
20
+ "webset-list" => "List websets",
21
+ "webset-update" => "Update a webset",
22
+ "webset-delete" => "Delete a webset",
23
+ "webset-cancel" => "Cancel a webset",
24
+ "webset-search-create" => "Create a search in a webset",
25
+ "webset-search-get" => "Get webset search details",
26
+ "webset-search-cancel" => "Cancel a webset search",
27
+ "webset-item-list" => "List items in a webset",
28
+ "webset-item-get" => "Get webset item details",
29
+ "webset-item-delete" => "Delete a webset item",
30
+ "enrichment-create" => "Create an enrichment",
31
+ "enrichment-get" => "Get enrichment details",
32
+ "enrichment-list" => "List enrichments",
33
+ "enrichment-update" => "Update an enrichment",
34
+ "enrichment-delete" => "Delete an enrichment",
35
+ "enrichment-cancel" => "Cancel an enrichment"
17
36
  }.freeze
18
37
 
19
38
  def self.run
@@ -29,6 +48,8 @@ module ExaCLI
29
48
  exit 1
30
49
  when "search"
31
50
  exec File.expand_path("../exa-ai-search", __FILE__), *ARGV[1..]
51
+ when "find-similar"
52
+ exec File.expand_path("../exa-ai-find-similar", __FILE__), *ARGV[1..]
32
53
  when "answer"
33
54
  exec File.expand_path("../exa-ai-answer", __FILE__), *ARGV[1..]
34
55
  when "context"
@@ -41,6 +62,42 @@ module ExaCLI
41
62
  exec File.expand_path("../exa-ai-research-get", __FILE__), *ARGV[1..]
42
63
  when "research-list"
43
64
  exec File.expand_path("../exa-ai-research-list", __FILE__), *ARGV[1..]
65
+ when "webset-create"
66
+ exec File.expand_path("../exa-ai-webset-create", __FILE__), *ARGV[1..]
67
+ when "webset-get"
68
+ exec File.expand_path("../exa-ai-webset-get", __FILE__), *ARGV[1..]
69
+ when "webset-list"
70
+ exec File.expand_path("../exa-ai-webset-list", __FILE__), *ARGV[1..]
71
+ when "webset-update"
72
+ exec File.expand_path("../exa-ai-webset-update", __FILE__), *ARGV[1..]
73
+ when "webset-delete"
74
+ exec File.expand_path("../exa-ai-webset-delete", __FILE__), *ARGV[1..]
75
+ when "webset-cancel"
76
+ exec File.expand_path("../exa-ai-webset-cancel", __FILE__), *ARGV[1..]
77
+ when "webset-search-create"
78
+ exec File.expand_path("../exa-ai-webset-search-create", __FILE__), *ARGV[1..]
79
+ when "webset-search-get"
80
+ exec File.expand_path("../exa-ai-webset-search-get", __FILE__), *ARGV[1..]
81
+ when "webset-search-cancel"
82
+ exec File.expand_path("../exa-ai-webset-search-cancel", __FILE__), *ARGV[1..]
83
+ when "webset-item-list"
84
+ exec File.expand_path("../exa-ai-webset-item-list", __FILE__), *ARGV[1..]
85
+ when "webset-item-get"
86
+ exec File.expand_path("../exa-ai-webset-item-get", __FILE__), *ARGV[1..]
87
+ when "webset-item-delete"
88
+ exec File.expand_path("../exa-ai-webset-item-delete", __FILE__), *ARGV[1..]
89
+ when "enrichment-create"
90
+ exec File.expand_path("../exa-ai-enrichment-create", __FILE__), *ARGV[1..]
91
+ when "enrichment-get"
92
+ exec File.expand_path("../exa-ai-enrichment-get", __FILE__), *ARGV[1..]
93
+ when "enrichment-list"
94
+ exec File.expand_path("../exa-ai-enrichment-list", __FILE__), *ARGV[1..]
95
+ when "enrichment-update"
96
+ exec File.expand_path("../exa-ai-enrichment-update", __FILE__), *ARGV[1..]
97
+ when "enrichment-delete"
98
+ exec File.expand_path("../exa-ai-enrichment-delete", __FILE__), *ARGV[1..]
99
+ when "enrichment-cancel"
100
+ exec File.expand_path("../exa-ai-enrichment-cancel", __FILE__), *ARGV[1..]
44
101
  else
45
102
  print_error_for_command(ARGV[0])
46
103
  exit 1
@@ -52,11 +109,51 @@ module ExaCLI
52
109
  puts ""
53
110
  puts "Usage: exa-ai <command> [options]"
54
111
  puts ""
55
- puts "Commands:"
56
- AVAILABLE_COMMANDS.each do |cmd, desc|
57
- puts " #{cmd.ljust(20)} #{desc}"
58
- end
59
- puts ""
112
+
113
+ print_command_section("Core Search", [
114
+ ["search", "Search the web"],
115
+ ["find-similar", "Find content similar to a URL"],
116
+ ["answer", "Generate an answer to a question"],
117
+ ["context", "Get code context from repositories"],
118
+ ["get-contents", "Retrieve page contents"]
119
+ ])
120
+
121
+ print_command_section("Research", [
122
+ ["research-start", "Start a research task"],
123
+ ["research-get", "Get research task status"],
124
+ ["research-list", "List research tasks"]
125
+ ])
126
+
127
+ print_command_section("Websets", [
128
+ ["webset-create", "Create a new webset"],
129
+ ["webset-get", "Get webset details"],
130
+ ["webset-list", "List websets"],
131
+ ["webset-update", "Update a webset"],
132
+ ["webset-delete", "Delete a webset"],
133
+ ["webset-cancel", "Cancel a webset"]
134
+ ])
135
+
136
+ print_command_section("Webset Searches", [
137
+ ["webset-search-create", "Create a search in a webset"],
138
+ ["webset-search-get", "Get webset search details"],
139
+ ["webset-search-cancel", "Cancel a webset search"]
140
+ ])
141
+
142
+ print_command_section("Webset Items", [
143
+ ["webset-item-list", "List items in a webset"],
144
+ ["webset-item-get", "Get webset item details"],
145
+ ["webset-item-delete", "Delete a webset item"]
146
+ ])
147
+
148
+ print_command_section("Webset Enrichments", [
149
+ ["enrichment-create", "Create an enrichment"],
150
+ ["enrichment-get", "Get enrichment details"],
151
+ ["enrichment-list", "List enrichments"],
152
+ ["enrichment-update", "Update an enrichment"],
153
+ ["enrichment-delete", "Delete an enrichment"],
154
+ ["enrichment-cancel", "Cancel an enrichment"]
155
+ ])
156
+
60
157
  puts "Global Options:"
61
158
  puts " --help, -h Show this help message"
62
159
  puts " --version Show version number"
@@ -67,6 +164,14 @@ module ExaCLI
67
164
  puts " exa-ai research-start --instructions 'Find AI papers'"
68
165
  end
69
166
 
167
+ def self.print_command_section(title, commands)
168
+ puts "#{title}:"
169
+ commands.each do |cmd, desc|
170
+ puts " #{cmd.ljust(24)} #{desc}"
171
+ end
172
+ puts ""
173
+ end
174
+
70
175
  def self.print_error_for_command(cmd)
71
176
  return print_help if cmd.nil?
72
177
 
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "exa-ai"
5
+
6
+ # Parse arguments
7
+ webset_id = nil
8
+ enrichment_id = nil
9
+ api_key = nil
10
+ output_format = "json"
11
+
12
+ args = ARGV.dup
13
+ while args.any?
14
+ arg = args.shift
15
+ case arg
16
+ when "--api-key"
17
+ api_key = args.shift
18
+ when "--output-format"
19
+ output_format = args.shift
20
+ when "--help", "-h"
21
+ puts <<~HELP
22
+ Usage: exa-ai enrichment-cancel <webset_id> <enrichment_id> [OPTIONS]
23
+
24
+ Cancel a running enrichment
25
+
26
+ Arguments:
27
+ webset_id ID of the webset (required)
28
+ enrichment_id ID of the enrichment to cancel (required)
29
+
30
+ Options:
31
+ --api-key KEY Exa API key (or set EXA_API_KEY env var)
32
+ --output-format FMT Output format: json, pretty, or text (default: json)
33
+ --help, -h Show this help message
34
+
35
+ Examples:
36
+ exa-ai enrichment-cancel ws_123 enr_456
37
+ exa-ai enrichment-cancel ws_123 enr_456 --output-format pretty
38
+ HELP
39
+ exit 0
40
+ else
41
+ # First positional argument is webset_id, second is enrichment_id
42
+ if webset_id.nil?
43
+ webset_id = arg
44
+ elsif enrichment_id.nil?
45
+ enrichment_id = arg
46
+ else
47
+ $stderr.puts "Unknown option: #{arg}"
48
+ exit 1
49
+ end
50
+ end
51
+ end
52
+
53
+ # Validate
54
+ if webset_id.nil?
55
+ $stderr.puts "Error: webset_id argument is required"
56
+ $stderr.puts "Usage: exa-ai enrichment-cancel <webset_id> <enrichment_id> [OPTIONS]"
57
+ $stderr.puts "Try 'exa-ai enrichment-cancel --help' for more information"
58
+ exit 1
59
+ end
60
+
61
+ if enrichment_id.nil?
62
+ $stderr.puts "Error: enrichment_id argument is required"
63
+ $stderr.puts "Usage: exa-ai enrichment-cancel <webset_id> <enrichment_id> [OPTIONS]"
64
+ $stderr.puts "Try 'exa-ai enrichment-cancel --help' for more information"
65
+ exit 1
66
+ end
67
+
68
+ begin
69
+ # Resolve API key and format
70
+ api_key = Exa::CLI::Base.resolve_api_key(api_key)
71
+ output_format = Exa::CLI::Base.resolve_output_format(output_format)
72
+
73
+ # Build client
74
+ client = Exa::CLI::Base.build_client(api_key)
75
+
76
+ # Cancel enrichment
77
+ enrichment = client.cancel_enrichment(webset_id: webset_id, id: enrichment_id)
78
+
79
+ # Format and output
80
+ output = Exa::CLI::Formatters::EnrichmentFormatter.format(enrichment, output_format)
81
+ puts output
82
+
83
+ rescue Exa::NotFound => e
84
+ $stderr.puts "Enrichment not found: #{e.message}"
85
+ exit 1
86
+ rescue Exa::ConfigurationError => e
87
+ $stderr.puts "Configuration error: #{e.message}"
88
+ exit 1
89
+ rescue Exa::Unauthorized => e
90
+ $stderr.puts "Authentication error: #{e.message}"
91
+ $stderr.puts "Check your API key (set EXA_API_KEY or use --api-key)"
92
+ exit 1
93
+ rescue Exa::ClientError => e
94
+ $stderr.puts "Client error: #{e.message}"
95
+ exit 1
96
+ rescue Exa::ServerError => e
97
+ $stderr.puts "Server error: #{e.message}"
98
+ $stderr.puts "The Exa API may be experiencing issues. Please try again later."
99
+ exit 1
100
+ rescue Exa::Error => e
101
+ $stderr.puts "Error: #{e.message}"
102
+ exit 1
103
+ rescue StandardError => e
104
+ $stderr.puts "Unexpected error: #{e.message}"
105
+ $stderr.puts e.backtrace.first(5) if ENV["DEBUG"]
106
+ exit 1
107
+ end
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "exa-ai"
5
+
6
+ VALID_FORMATS = %w[text url options].freeze
7
+
8
+ # Recursively convert hash keys from strings to symbols
9
+ def deep_symbolize_keys(obj)
10
+ case obj
11
+ when Hash
12
+ obj.each_with_object({}) do |(key, value), result|
13
+ result[key.to_sym] = deep_symbolize_keys(value)
14
+ end
15
+ when Array
16
+ obj.map { |item| deep_symbolize_keys(item) }
17
+ else
18
+ obj
19
+ end
20
+ end
21
+
22
+ # Parse JSON string or load from file (supports @file.json syntax)
23
+ def parse_json_or_file(value)
24
+ json_data = if value.start_with?("@")
25
+ file_path = value[1..]
26
+ JSON.parse(File.read(file_path))
27
+ else
28
+ JSON.parse(value)
29
+ end
30
+ deep_symbolize_keys(json_data)
31
+ rescue JSON::ParserError => e
32
+ $stderr.puts "Error: Invalid JSON: #{e.message}"
33
+ exit 1
34
+ rescue Errno::ENOENT => e
35
+ $stderr.puts "Error: File not found: #{e.message}"
36
+ exit 1
37
+ end
38
+
39
+ # Parse command-line arguments
40
+ def parse_args(argv)
41
+ # Check for help first
42
+ if argv.include?("--help") || argv.include?("-h")
43
+ puts <<~HELP
44
+ Usage: exa-ai enrichment-create <webset_id> --description TEXT --format TYPE [OPTIONS]
45
+
46
+ Create a new enrichment for a webset
47
+
48
+ Required:
49
+ <webset_id> Webset ID
50
+ --description TEXT What to extract
51
+ --format TYPE One of: #{VALID_FORMATS.join(', ')}
52
+
53
+ Options:
54
+ --title TEXT Display title
55
+ --options JSON Array of {label: "..."} (required if format=options, supports @file.json)
56
+ --instructions TEXT Additional instructions
57
+ --metadata JSON Custom metadata (supports @file.json)
58
+ --wait Wait for enrichment to complete
59
+ --api-key KEY Exa API key (or set EXA_API_KEY env var)
60
+ --output-format FMT Output format: json, pretty, or text (default: json)
61
+ --help, -h Show this help message
62
+
63
+ Examples:
64
+ exa-ai enrichment-create ws_123 --description "Company size" --format text
65
+ exa-ai enrichment-create ws_123 --description "Industry" --format options --options options.json
66
+ exa-ai enrichment-create ws_123 --description "Website URL" --format url --wait
67
+ HELP
68
+ exit 0
69
+ end
70
+
71
+ args = {
72
+ output_format: "json",
73
+ api_key: nil,
74
+ wait: false
75
+ }
76
+
77
+ # First, check for positional argument (webset_id)
78
+ if argv.empty? || argv[0].start_with?("--")
79
+ return args
80
+ end
81
+
82
+ args[:webset_id] = argv[0]
83
+ i = 1
84
+
85
+ while i < argv.length
86
+ arg = argv[i]
87
+ case arg
88
+ when "--description"
89
+ args[:description] = argv[i + 1]
90
+ i += 2
91
+ when "--format"
92
+ args[:format] = argv[i + 1]
93
+ i += 2
94
+ when "--title"
95
+ args[:title] = argv[i + 1]
96
+ i += 2
97
+ when "--options"
98
+ args[:options] = parse_json_or_file(argv[i + 1])
99
+ i += 2
100
+ when "--instructions"
101
+ args[:instructions] = argv[i + 1]
102
+ i += 2
103
+ when "--metadata"
104
+ args[:metadata] = parse_json_or_file(argv[i + 1])
105
+ i += 2
106
+ when "--wait"
107
+ args[:wait] = true
108
+ i += 1
109
+ when "--api-key"
110
+ args[:api_key] = argv[i + 1]
111
+ i += 2
112
+ when "--output-format"
113
+ args[:output_format] = argv[i + 1]
114
+ i += 2
115
+ else
116
+ $stderr.puts "Unknown option: #{arg}"
117
+ exit 1
118
+ end
119
+ end
120
+
121
+ args
122
+ end
123
+
124
+ # Main execution
125
+ begin
126
+ args = parse_args(ARGV)
127
+
128
+ # Validate required parameters
129
+ unless args[:webset_id]
130
+ $stderr.puts "Error: <webset_id> is required"
131
+ $stderr.puts "Run 'exa-ai enrichment-create --help' for usage information"
132
+ exit 1
133
+ end
134
+
135
+ unless args[:description]
136
+ $stderr.puts "Error: --description is required"
137
+ $stderr.puts "Run 'exa-ai enrichment-create --help' for usage information"
138
+ exit 1
139
+ end
140
+
141
+ unless args[:format]
142
+ $stderr.puts "Error: --format is required"
143
+ $stderr.puts "Run 'exa-ai enrichment-create --help' for usage information"
144
+ exit 1
145
+ end
146
+
147
+ # Validate format
148
+ unless VALID_FORMATS.include?(args[:format])
149
+ $stderr.puts "Error: format must be one of: #{VALID_FORMATS.join(', ')}"
150
+ exit 1
151
+ end
152
+
153
+ # Validate options requirement for 'options' format
154
+ if args[:format] == "options" && !args[:options]
155
+ $stderr.puts "Error: --options is required when format is 'options'"
156
+ exit 1
157
+ end
158
+
159
+ # Resolve API key
160
+ api_key = Exa::CLI::Base.resolve_api_key(args[:api_key])
161
+
162
+ # Resolve output format
163
+ output_format = Exa::CLI::Base.resolve_output_format(args[:output_format])
164
+
165
+ # Build client
166
+ client = Exa::CLI::Base.build_client(api_key)
167
+
168
+ # Prepare enrichment parameters
169
+ enrichment_params = {
170
+ description: args[:description],
171
+ format: args[:format]
172
+ }
173
+ enrichment_params[:title] = args[:title] if args[:title]
174
+ enrichment_params[:options] = args[:options] if args[:options]
175
+ enrichment_params[:instructions] = args[:instructions] if args[:instructions]
176
+ enrichment_params[:metadata] = args[:metadata] if args[:metadata]
177
+
178
+ # Create enrichment
179
+ enrichment = client.create_enrichment(webset_id: args[:webset_id], **enrichment_params)
180
+
181
+ # If --wait flag is set, poll until enrichment is complete
182
+ if args[:wait]
183
+ $stderr.print "Creating enrichment... "
184
+
185
+ begin
186
+ final_enrichment = Exa::CLI::Polling.poll(max_duration: 300, initial_delay: 2, max_delay: 10) do
187
+ current = client.get_enrichment(webset_id: args[:webset_id], id: enrichment.id)
188
+
189
+ case current.status
190
+ when "pending"
191
+ $stderr.print "⏳"
192
+ when "running"
193
+ $stderr.print "⚙️"
194
+ end
195
+
196
+ done = current.completed?
197
+ { done: done, result: current, status: current.status }
198
+ end
199
+
200
+ $stderr.puts " COMPLETED"
201
+ enrichment = final_enrichment
202
+
203
+ rescue Exa::CLI::Polling::TimeoutError
204
+ $stderr.puts "\nWarning: Enrichment did not complete within timeout"
205
+ $stderr.puts "Enrichment ID: #{enrichment.id}"
206
+ $stderr.puts "Check status with: exa-ai enrichment-get #{args[:webset_id]} #{enrichment.id}"
207
+ end
208
+ end
209
+
210
+ # Format and output result
211
+ output = Exa::CLI::Formatters::EnrichmentFormatter.format(enrichment, output_format)
212
+ puts output
213
+
214
+ rescue Exa::ConfigurationError => e
215
+ $stderr.puts "Configuration error: #{e.message}"
216
+ exit 1
217
+ rescue Exa::Unauthorized => e
218
+ $stderr.puts "Authentication error: #{e.message}"
219
+ $stderr.puts "Check your API key (set EXA_API_KEY or use --api-key)"
220
+ exit 1
221
+ rescue Exa::ClientError => e
222
+ $stderr.puts "Client error: #{e.message}"
223
+ exit 1
224
+ rescue Exa::ServerError => e
225
+ $stderr.puts "Server error: #{e.message}"
226
+ $stderr.puts "The Exa API may be experiencing issues. Please try again later."
227
+ exit 1
228
+ rescue Exa::Error => e
229
+ $stderr.puts "Error: #{e.message}"
230
+ exit 1
231
+ rescue StandardError => e
232
+ $stderr.puts "Unexpected error: #{e.message}"
233
+ $stderr.puts e.backtrace.first(5) if ENV["DEBUG"]
234
+ exit 1
235
+ end