exa-ai 0.5.0 → 0.6.1

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -0
  3. data/exe/exa-ai +55 -1
  4. data/exe/exa-ai-enrichment-create +0 -10
  5. data/exe/exa-ai-import-create +253 -0
  6. data/exe/exa-ai-import-delete +92 -0
  7. data/exe/exa-ai-import-get +92 -0
  8. data/exe/exa-ai-import-list +72 -0
  9. data/exe/exa-ai-import-update +164 -0
  10. data/exe/exa-ai-monitor-create +223 -0
  11. data/exe/exa-ai-monitor-delete +101 -0
  12. data/exe/exa-ai-monitor-get +92 -0
  13. data/exe/exa-ai-monitor-list +90 -0
  14. data/exe/exa-ai-monitor-runs-get +103 -0
  15. data/exe/exa-ai-monitor-runs-list +110 -0
  16. data/exe/exa-ai-monitor-update +207 -0
  17. data/exe/exa-ai-webset-create +64 -8
  18. data/exe/exa-ai-webset-search-create +19 -6
  19. data/exe/exa-ai-webset-search-get +1 -1
  20. data/lib/exa/cli/base.rb +8 -2
  21. data/lib/exa/cli/formatters/answer_formatter.rb +2 -0
  22. data/lib/exa/cli/formatters/contents_formatter.rb +2 -0
  23. data/lib/exa/cli/formatters/context_formatter.rb +2 -0
  24. data/lib/exa/cli/formatters/enrichment_formatter.rb +58 -2
  25. data/lib/exa/cli/formatters/import_formatter.rb +153 -0
  26. data/lib/exa/cli/formatters/monitor_formatter.rb +144 -0
  27. data/lib/exa/cli/formatters/monitor_run_formatter.rb +126 -0
  28. data/lib/exa/cli/formatters/research_formatter.rb +4 -0
  29. data/lib/exa/cli/formatters/search_formatter.rb +2 -0
  30. data/lib/exa/cli/formatters/webset_formatter.rb +65 -1
  31. data/lib/exa/cli/formatters/webset_item_formatter.rb +54 -2
  32. data/lib/exa/cli/formatters/webset_search_formatter.rb +57 -0
  33. data/lib/exa/client.rb +137 -0
  34. data/lib/exa/constants/websets.rb +19 -0
  35. data/lib/exa/resources/import.rb +86 -0
  36. data/lib/exa/resources/import_collection.rb +33 -0
  37. data/lib/exa/resources/monitor.rb +48 -0
  38. data/lib/exa/resources/monitor_collection.rb +30 -0
  39. data/lib/exa/resources/monitor_run.rb +52 -0
  40. data/lib/exa/resources/monitor_run_collection.rb +30 -0
  41. data/lib/exa/services/websets/create_validator.rb +20 -3
  42. data/lib/exa/services/websets/import_create.rb +40 -0
  43. data/lib/exa/services/websets/import_delete.rb +37 -0
  44. data/lib/exa/services/websets/import_get.rb +37 -0
  45. data/lib/exa/services/websets/import_list.rb +25 -0
  46. data/lib/exa/services/websets/import_update.rb +38 -0
  47. data/lib/exa/services/websets/import_upload.rb +58 -0
  48. data/lib/exa/services/websets/monitors/create.rb +42 -0
  49. data/lib/exa/services/websets/monitors/delete.rb +32 -0
  50. data/lib/exa/services/websets/monitors/get.rb +33 -0
  51. data/lib/exa/services/websets/monitors/list.rb +27 -0
  52. data/lib/exa/services/websets/monitors/runs/get.rb +37 -0
  53. data/lib/exa/services/websets/monitors/runs/list.rb +30 -0
  54. data/lib/exa/services/websets/monitors/update.rb +33 -0
  55. data/lib/exa/version.rb +1 -1
  56. data/lib/exa.rb +24 -0
  57. metadata +51 -1
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "exa-ai"
5
+
6
+ # Parse arguments
7
+ monitor_id = nil
8
+ run_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 monitor-runs-get <monitor_id> <run_id> [OPTIONS]
23
+
24
+ Retrieve details for a specific monitor run
25
+
26
+ Arguments:
27
+ monitor_id ID of the monitor (required)
28
+ run_id ID of the run to retrieve (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 monitor-runs-get mon_abc123 run_xyz789
37
+ exa-ai monitor-runs-get mon_abc123 run_xyz789 --output-format pretty
38
+ HELP
39
+ exit 0
40
+ else
41
+ # First positional argument is monitor_id, second is run_id
42
+ if monitor_id.nil?
43
+ monitor_id = arg
44
+ elsif run_id.nil?
45
+ run_id = arg
46
+ else
47
+ $stderr.puts "Unknown option: #{arg}"
48
+ exit 1
49
+ end
50
+ end
51
+ end
52
+
53
+ # Validate
54
+ if monitor_id.nil?
55
+ $stderr.puts "Error: monitor_id argument is required"
56
+ $stderr.puts "Usage: exa-ai monitor-runs-get <monitor_id> <run_id> [OPTIONS]"
57
+ $stderr.puts "Try 'exa-ai monitor-runs-get --help' for more information"
58
+ exit 1
59
+ end
60
+
61
+ if run_id.nil?
62
+ $stderr.puts "Error: run_id argument is required"
63
+ $stderr.puts "Usage: exa-ai monitor-runs-get <monitor_id> <run_id> [OPTIONS]"
64
+ $stderr.puts "Try 'exa-ai monitor-runs-get --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
+ # Get monitor run
77
+ monitor_run = client.get_monitor_run(monitor_id: monitor_id, id: run_id)
78
+
79
+ # Format and output
80
+ output = Exa::CLI::Formatters::MonitorRunFormatter.format(monitor_run, output_format)
81
+ puts output
82
+
83
+ rescue Exa::NotFound => e
84
+ $stderr.puts "Monitor run not found: #{e.message}"
85
+ exit 1
86
+ rescue Exa::Unauthorized => e
87
+ $stderr.puts "Authentication error: #{e.message}"
88
+ $stderr.puts "Check your API key (set EXA_API_KEY or use --api-key)"
89
+ exit 1
90
+ rescue Exa::ClientError => e
91
+ $stderr.puts "Client error: #{e.message}"
92
+ exit 1
93
+ rescue Exa::ServerError => e
94
+ $stderr.puts "Server error: #{e.message}"
95
+ exit 1
96
+ rescue Exa::Error => e
97
+ $stderr.puts "Error: #{e.message}"
98
+ exit 1
99
+ rescue StandardError => e
100
+ $stderr.puts "Unexpected error: #{e.message}"
101
+ $stderr.puts e.backtrace.first(5) if ENV["DEBUG"]
102
+ exit 1
103
+ end
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "exa-ai"
5
+
6
+ # Parse arguments
7
+ monitor_id = nil
8
+ limit = nil
9
+ cursor = nil
10
+ api_key = nil
11
+ output_format = "json"
12
+
13
+ args = ARGV.dup
14
+ while args.any?
15
+ arg = args.shift
16
+ case arg
17
+ when "--limit"
18
+ limit = args.shift&.to_i
19
+ when "--cursor"
20
+ cursor = args.shift
21
+ when "--api-key"
22
+ api_key = args.shift
23
+ when "--output-format"
24
+ output_format = args.shift
25
+ when "--help", "-h"
26
+ puts <<~HELP
27
+ Usage: exa-ai monitor-runs-list <monitor_id> [OPTIONS]
28
+
29
+ List execution runs for a monitor
30
+
31
+ Arguments:
32
+ monitor_id ID of the monitor (required)
33
+
34
+ Options:
35
+ --limit N Maximum number of runs to return (default: 25, max: 100)
36
+ --cursor CURSOR Cursor for pagination (use nextCursor from previous response)
37
+ --api-key KEY Exa API key (or set EXA_API_KEY env var)
38
+ --output-format FMT Output format: json, pretty, or text (default: json)
39
+ --help, -h Show this help message
40
+
41
+ Examples:
42
+ exa-ai monitor-runs-list mon_abc123
43
+ exa-ai monitor-runs-list mon_abc123 --limit 10
44
+ exa-ai monitor-runs-list mon_abc123 --limit 5 --cursor "abc123"
45
+ exa-ai monitor-runs-list mon_abc123 --output-format pretty
46
+ HELP
47
+ exit 0
48
+ else
49
+ # First positional argument is monitor_id
50
+ if monitor_id.nil?
51
+ monitor_id = arg
52
+ else
53
+ $stderr.puts "Unknown option: #{arg}"
54
+ exit 1
55
+ end
56
+ end
57
+ end
58
+
59
+ # Validate
60
+ if monitor_id.nil?
61
+ $stderr.puts "Error: monitor_id argument is required"
62
+ $stderr.puts "Usage: exa-ai monitor-runs-list <monitor_id> [OPTIONS]"
63
+ $stderr.puts "Try 'exa-ai monitor-runs-list --help' for more information"
64
+ exit 1
65
+ end
66
+
67
+ begin
68
+ # Resolve API key and format
69
+ api_key = Exa::CLI::Base.resolve_api_key(api_key)
70
+ output_format = Exa::CLI::Base.resolve_output_format(output_format)
71
+
72
+ # Build client
73
+ client = Exa::CLI::Base.build_client(api_key)
74
+
75
+ # List monitor runs with optional pagination
76
+ list_params = {monitor_id: monitor_id}
77
+ list_params[:limit] = limit if limit
78
+ list_params[:cursor] = cursor if cursor
79
+
80
+ collection = client.list_monitor_runs(**list_params)
81
+
82
+ # Format and output
83
+ output = Exa::CLI::Formatters::MonitorRunFormatter.format_collection(collection, output_format)
84
+ puts output
85
+
86
+ rescue Exa::NotFound => e
87
+ $stderr.puts "Monitor not found: #{e.message}"
88
+ exit 1
89
+ rescue Exa::ConfigurationError => e
90
+ $stderr.puts "Configuration error: #{e.message}"
91
+ exit 1
92
+ rescue Exa::Unauthorized => e
93
+ $stderr.puts "Authentication error: #{e.message}"
94
+ $stderr.puts "Check your API key (set EXA_API_KEY or use --api-key)"
95
+ exit 1
96
+ rescue Exa::ClientError => e
97
+ $stderr.puts "Client error: #{e.message}"
98
+ exit 1
99
+ rescue Exa::ServerError => e
100
+ $stderr.puts "Server error: #{e.message}"
101
+ $stderr.puts "The Exa API may be experiencing issues. Please try again later."
102
+ exit 1
103
+ rescue Exa::Error => e
104
+ $stderr.puts "Error: #{e.message}"
105
+ exit 1
106
+ rescue StandardError => e
107
+ $stderr.puts "Unexpected error: #{e.message}"
108
+ $stderr.puts e.backtrace.first(5) if ENV["DEBUG"]
109
+ exit 1
110
+ end
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "exa-ai"
5
+
6
+ VALID_BEHAVIOR_TYPES = %w[search refresh].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 monitor-update <monitor_id> [OPTIONS]
45
+
46
+ Update a monitor's schedule or behavior
47
+
48
+ Required:
49
+ <monitor_id> Monitor ID
50
+
51
+ Cadence options:
52
+ --cron EXPR Cron expression (e.g., "0 0 * * *")
53
+ --timezone TZ Timezone (e.g., "America/New_York")
54
+
55
+ Behavior options:
56
+ --behavior-type TYPE One of: #{VALID_BEHAVIOR_TYPES.join(', ')}
57
+ --query TEXT Search query (for search behavior)
58
+ --count N Number of results (for search behavior)
59
+ --behavior-mode MODE Mode: override or append (for search behavior)
60
+
61
+ Other options:
62
+ --api-key KEY Exa API key (or set EXA_API_KEY env var)
63
+ --output-format FMT Output format: json, pretty, or text (default: json)
64
+ --help, -h Show this help message
65
+
66
+ Examples:
67
+ # Update cron schedule
68
+ exa-ai monitor-update mon_123 --cron "0 */12 * * *"
69
+
70
+ # Update search query
71
+ exa-ai monitor-update mon_123 --query "AI startups in Europe"
72
+
73
+ # Update multiple fields
74
+ exa-ai monitor-update mon_123 --cron "0 0 * * *" --timezone "UTC" \\
75
+ --query "New companies" --count 100
76
+ HELP
77
+ exit 0
78
+ end
79
+
80
+ args = {
81
+ output_format: "json",
82
+ api_key: nil
83
+ }
84
+
85
+ # First, check for positional argument (monitor_id)
86
+ if argv.empty? || argv[0].start_with?("--")
87
+ return args
88
+ end
89
+
90
+ args[:monitor_id] = argv[0]
91
+ i = 1
92
+
93
+ while i < argv.length
94
+ arg = argv[i]
95
+ case arg
96
+ when "--cron"
97
+ args[:cron] = argv[i + 1]
98
+ i += 2
99
+ when "--timezone"
100
+ args[:timezone] = argv[i + 1]
101
+ i += 2
102
+ when "--behavior-type"
103
+ args[:behavior_type] = argv[i + 1]
104
+ i += 2
105
+ when "--query"
106
+ args[:query] = argv[i + 1]
107
+ i += 2
108
+ when "--count"
109
+ args[:count] = argv[i + 1]&.to_i
110
+ i += 2
111
+ when "--behavior-mode"
112
+ args[:behavior_mode] = argv[i + 1]
113
+ i += 2
114
+ when "--api-key"
115
+ args[:api_key] = argv[i + 1]
116
+ i += 2
117
+ when "--output-format"
118
+ args[:output_format] = argv[i + 1]
119
+ i += 2
120
+ else
121
+ $stderr.puts "Unknown option: #{arg}"
122
+ exit 1
123
+ end
124
+ end
125
+
126
+ args
127
+ end
128
+
129
+ # Validate required arguments
130
+ def validate_args(args)
131
+ unless args[:monitor_id]
132
+ $stderr.puts "Error: monitor_id argument is required"
133
+ $stderr.puts "Usage: exa-ai monitor-update <monitor_id> [OPTIONS]"
134
+ $stderr.puts "Try 'exa-ai monitor-update --help' for more information"
135
+ exit 1
136
+ end
137
+
138
+ # Validate behavior type if provided
139
+ if args[:behavior_type] && !VALID_BEHAVIOR_TYPES.include?(args[:behavior_type])
140
+ $stderr.puts "Error: --behavior-type must be one of: #{VALID_BEHAVIOR_TYPES.join(', ')}"
141
+ exit 1
142
+ end
143
+ end
144
+
145
+ # Main execution
146
+ args = parse_args(ARGV)
147
+ validate_args(args)
148
+
149
+ begin
150
+ # Resolve API key and format
151
+ api_key = Exa::CLI::Base.resolve_api_key(args[:api_key])
152
+ output_format = Exa::CLI::Base.resolve_output_format(args[:output_format])
153
+
154
+ # Build client
155
+ client = Exa::CLI::Base.build_client(api_key)
156
+
157
+ # Build update params
158
+ update_params = {}
159
+
160
+ # Build cadence object if any cadence params provided
161
+ if args[:cron] || args[:timezone]
162
+ update_params[:cadence] = {}
163
+ update_params[:cadence][:cron] = args[:cron] if args[:cron]
164
+ update_params[:cadence][:timezone] = args[:timezone] if args[:timezone]
165
+ end
166
+
167
+ # Build behavior object if any behavior params provided
168
+ if args[:behavior_type] || args[:query] || args[:count] || args[:behavior_mode]
169
+ update_params[:behavior] = {}
170
+ update_params[:behavior][:type] = args[:behavior_type] if args[:behavior_type]
171
+ update_params[:behavior][:query] = args[:query] if args[:query]
172
+ update_params[:behavior][:count] = args[:count] if args[:count]
173
+ update_params[:behavior][:mode] = args[:behavior_mode] if args[:behavior_mode]
174
+ end
175
+
176
+ # Update monitor
177
+ monitor = client.update_monitor(args[:monitor_id], **update_params)
178
+
179
+ # Format and output
180
+ output = Exa::CLI::Formatters::MonitorFormatter.format(monitor, output_format)
181
+ puts output
182
+
183
+ rescue Exa::NotFound => e
184
+ $stderr.puts "Monitor not found: #{e.message}"
185
+ exit 1
186
+ rescue Exa::ConfigurationError => e
187
+ $stderr.puts "Configuration error: #{e.message}"
188
+ exit 1
189
+ rescue Exa::Unauthorized => e
190
+ $stderr.puts "Authentication error: #{e.message}"
191
+ $stderr.puts "Check your API key (set EXA_API_KEY or use --api-key)"
192
+ exit 1
193
+ rescue Exa::ClientError => e
194
+ $stderr.puts "Client error: #{e.message}"
195
+ exit 1
196
+ rescue Exa::ServerError => e
197
+ $stderr.puts "Server error: #{e.message}"
198
+ $stderr.puts "The Exa API may be experiencing issues. Please try again later."
199
+ exit 1
200
+ rescue Exa::Error => e
201
+ $stderr.puts "Error: #{e.message}"
202
+ exit 1
203
+ rescue StandardError => e
204
+ $stderr.puts "Unexpected error: #{e.message}"
205
+ $stderr.puts e.backtrace.first(5) if ENV["DEBUG"]
206
+ exit 1
207
+ end
@@ -46,6 +46,9 @@ def parse_args(argv)
46
46
  while i < argv.length
47
47
  arg = argv[i]
48
48
  case arg
49
+ when "--import"
50
+ args[:import] = argv[i + 1]
51
+ i += 2
49
52
  when "--search"
50
53
  args[:search] = parse_json_or_file(argv[i + 1])
51
54
  i += 2
@@ -72,27 +75,56 @@ def parse_args(argv)
72
75
  i += 2
73
76
  when "--help", "-h"
74
77
  puts <<~HELP
75
- Usage: exa-ai webset-create --search JSON [OPTIONS]
78
+ Usage: exa-ai webset-create (--search JSON | --import ID) [OPTIONS]
76
79
 
77
- Create a new webset with search criteria
80
+ Create a new webset from search criteria or an import
78
81
 
79
- Required:
80
- --search JSON Search configuration (supports @file.json)
82
+ Required (choose one):
83
+ --search JSON Search configuration as JSON (supports @file.json)
84
+ Format: {"query":"...","count":10,"scope":[...]}
85
+ The 'scope' field limits search to specific sources
86
+ --import ID Import/webset ID to attach data to this webset
87
+ (loads data but does NOT filter searches)
88
+ Format: import_abc123 or webset_xyz789
81
89
 
82
90
  Options:
83
91
  --enrichments JSON Array of enrichment configs (supports @file.json)
84
- --exclude JSON Array of exclude configs (supports @file.json)
92
+ Format: [{"description":"...","format":"text"}]
93
+ --exclude JSON Sources to exclude from searches (supports @file.json)
94
+ Format: [{"source":"import|webset","id":"..."}]
85
95
  --external-id ID External identifier for the webset
86
96
  --metadata JSON Custom metadata (supports @file.json)
97
+ Format: {"key":"value"}
87
98
  --wait Wait for webset to reach idle status
88
99
  --api-key KEY Exa API key (or set EXA_API_KEY env var)
89
100
  --output-format FMT Output format: json, pretty, or text (default: json)
90
101
  --help, -h Show this help message
91
102
 
103
+ JSON Format Details:
104
+ search.scope Array of source references to limit search
105
+ Format: [{"source":"import|webset","id":"..."}]
106
+ With relationship (hop search):
107
+ [{"source":"webset","id":"ws_123",
108
+ "relationship":{"definition":"investors of","limit":3}}]
109
+
110
+ IMPORTANT: Cannot use the same import ID in both --import and search.scope
111
+ (this will return a 400 error from the API)
112
+
92
113
  Examples:
114
+ # Create webset from search
93
115
  exa-ai webset-create --search '{"query":"AI startups","count":10}'
94
116
  exa-ai webset-create --search @search.json --enrichments @enrichments.json
95
117
  exa-ai webset-create --search @search.json --wait
118
+
119
+ # Create webset with scoped search (filter to specific import)
120
+ exa-ai webset-create --search '{"query":"CEOs","count":10,"scope":[{"source":"import","id":"import_abc"}]}'
121
+
122
+ # Create webset from import
123
+ exa-ai webset-create --import import_abc123
124
+ exa-ai webset-create --import import_def456 --enrichments @enrichments.json
125
+
126
+ # Load import AND run search (search not scoped to import)
127
+ exa-ai webset-create --import import_abc123 --search '{"query":"investors","count":20}'
96
128
  HELP
97
129
  exit 0
98
130
  else
@@ -109,8 +141,14 @@ begin
109
141
  args = parse_args(ARGV)
110
142
 
111
143
  # Validate required parameters
112
- unless args[:search]
113
- $stderr.puts "Error: --search is required"
144
+ if args[:search] && args[:import]
145
+ $stderr.puts "Error: Cannot specify both --search and --import"
146
+ $stderr.puts "Run 'exa-ai webset-create --help' for usage information"
147
+ exit 1
148
+ end
149
+
150
+ unless args[:search] || args[:import]
151
+ $stderr.puts "Error: Either --search or --import is required"
114
152
  $stderr.puts "Run 'exa-ai webset-create --help' for usage information"
115
153
  exit 1
116
154
  end
@@ -125,7 +163,25 @@ begin
125
163
  client = Exa::CLI::Base.build_client(api_key)
126
164
 
127
165
  # Prepare webset parameters
128
- webset_params = { search: args[:search] }
166
+ webset_params = {}
167
+
168
+ if args[:search]
169
+ webset_params[:search] = args[:search]
170
+ elsif args[:import]
171
+ # Detect source type from ID prefix and convert to API format
172
+ import_id = args[:import]
173
+ source = if import_id.start_with?("import_")
174
+ "import"
175
+ elsif import_id.start_with?("webset_")
176
+ "webset"
177
+ else
178
+ $stderr.puts "Error: Import ID must start with 'import_' or 'webset_'"
179
+ exit 1
180
+ end
181
+
182
+ webset_params[:import] = [{ source: source, id: import_id }]
183
+ end
184
+
129
185
  webset_params[:enrichments] = args[:enrichments] if args[:enrichments]
130
186
  webset_params[:exclude] = args[:exclude] if args[:exclude]
131
187
  webset_params[:externalId] = args[:external_id] if args[:external_id]
@@ -104,11 +104,20 @@ def parse_args(argv)
104
104
  --entity TYPE Entity type: person, company, article, research_paper, custom
105
105
  --entity-description TXT Description for custom entity type (required with --entity custom)
106
106
  --criteria JSON Search criteria array (supports @file.json)
107
+ Format: [{"description":"criterion 1"},{"description":"criterion 2"}]
107
108
  --exclude JSON Items to exclude from results (supports @file.json)
109
+ Format: [{"source":"import|webset","id":"..."}]
108
110
  --scope JSON Limit search to specific sources (supports @file.json)
111
+ Format: [{"source":"import|webset","id":"..."}]
112
+ Filters this search to only items from these sources
113
+ With relationship (hop search):
114
+ [{"source":"webset","id":"ws_123",
115
+ "relationship":{"definition":"investors of","limit":3}}]
109
116
  --recall Estimate total available results
110
- --behavior TYPE "override" or "append" (default: override)
117
+ --behavior TYPE "override" (replace items) or "append" (add items)
118
+ Default: override when scope is present, append otherwise
111
119
  --metadata JSON Custom metadata (supports @file.json)
120
+ Format: {"key":"value"}
112
121
  --api-key KEY Exa API key (or set EXA_API_KEY env var)
113
122
  --output-format FMT Output format: json, pretty, or text (default: json)
114
123
  --help, -h Show this help message
@@ -121,11 +130,15 @@ def parse_args(argv)
121
130
  exa-ai webset-search-create ws_123 --query "tech CEOs" --entity person
122
131
  exa-ai webset-search-create ws_123 --query "Silicon Valley firms" --entity company
123
132
 
124
- # Search with custom entity type
125
- exa-ai webset-search-create ws_123 --query "Ford Mustang" \\
126
- --entity custom --entity-description "vintage cars"
133
+ # Scoped search (filter to specific import)
134
+ exa-ai webset-search-create ws_123 --query "CTOs" \\
135
+ --scope '[{"source":"import","id":"import_abc"}]'
127
136
 
128
- # Other options
137
+ # Hop search (find investors of companies in webset)
138
+ exa-ai webset-search-create ws_123 --query "investors" \\
139
+ --scope '[{"source":"webset","id":"ws_companies","relationship":{"definition":"investors of","limit":5}}]'
140
+
141
+ # Search with criteria and behavior
129
142
  exa-ai webset-search-create ws_123 --query "machine learning" --count 50
130
143
  exa-ai webset-search-create ws_123 --query "research" --behavior append --recall
131
144
  HELP
@@ -203,7 +216,7 @@ begin
203
216
  search = client.create_webset_search(webset_id: args[:webset_id], **search_params)
204
217
 
205
218
  # Format and output result
206
- output = Exa::CLI::Formatters::SearchFormatter.format(search, output_format)
219
+ output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, output_format)
207
220
  puts output
208
221
  $stdout.flush
209
222
 
@@ -78,7 +78,7 @@ begin
78
78
  search = client.get_webset_search(webset_id: webset_id, id: search_id)
79
79
 
80
80
  # Format and output
81
- output = Exa::CLI::Formatters::SearchFormatter.format(search, output_format)
81
+ output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, output_format)
82
82
  puts output
83
83
  $stdout.flush
84
84
 
data/lib/exa/cli/base.rb CHANGED
@@ -16,11 +16,11 @@ module Exa
16
16
  end
17
17
 
18
18
  # Resolve and validate output format
19
- # Valid formats: json, pretty, text
19
+ # Valid formats: json, pretty, text, toon
20
20
  # Defaults to json
21
21
  def self.resolve_output_format(flag_value)
22
22
  format = (flag_value || "json").downcase
23
- valid_formats = %w[json pretty text]
23
+ valid_formats = %w[json pretty text toon]
24
24
 
25
25
  return format if valid_formats.include?(format)
26
26
 
@@ -46,6 +46,12 @@ module Exa
46
46
  data.to_s
47
47
  end
48
48
  end
49
+
50
+ # Encode data as TOON format
51
+ def self.encode_as_toon(data)
52
+ require "toon" unless defined?(Toon)
53
+ Toon.encode(data)
54
+ end
49
55
  end
50
56
  end
51
57
  end
@@ -12,6 +12,8 @@ module Exa
12
12
  format_pretty(result)
13
13
  when "text"
14
14
  format_text(result)
15
+ when "toon"
16
+ Exa::CLI::Base.encode_as_toon(result.to_h)
15
17
  else
16
18
  JSON.pretty_generate(result.to_h)
17
19
  end
@@ -12,6 +12,8 @@ module Exa
12
12
  format_pretty(result)
13
13
  when "text"
14
14
  format_text(result)
15
+ when "toon"
16
+ Exa::CLI::Base.encode_as_toon(result.to_h)
15
17
  else
16
18
  JSON.pretty_generate(result.to_h)
17
19
  end
@@ -14,6 +14,8 @@ module Exa
14
14
  format_pretty(result)
15
15
  when "text"
16
16
  format_text(result)
17
+ when "toon"
18
+ Exa::CLI::Base.encode_as_toon(result.to_h)
17
19
  else
18
20
  JSON.pretty_generate(result.to_h)
19
21
  end