exa-ai 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/README.md +105 -0
- data/exe/exa-ai-answer +8 -2
- data/exe/exa-ai-enrichment-create +1 -11
- data/exe/exa-ai-import-create +19 -1
- data/exe/exa-ai-search +64 -201
- data/exe/exa-ai-webset-create +25 -6
- data/exe/exa-ai-webset-item-list +18 -4
- data/exe/exa-ai-webset-search-create +19 -6
- data/exe/exa-ai-webset-search-get +1 -1
- data/lib/exa/cli/formatters/answer_formatter.rb +22 -14
- data/lib/exa/cli/formatters/enrichment_formatter.rb +54 -2
- data/lib/exa/cli/formatters/import_formatter.rb +70 -2
- data/lib/exa/cli/formatters/monitor_formatter.rb +65 -2
- data/lib/exa/cli/formatters/monitor_run_formatter.rb +53 -2
- data/lib/exa/cli/formatters/webset_formatter.rb +61 -1
- data/lib/exa/cli/formatters/webset_item_formatter.rb +67 -9
- data/lib/exa/cli/formatters/webset_search_formatter.rb +57 -0
- data/lib/exa/cli/search_parser.rb +152 -0
- data/lib/exa/client.rb +6 -29
- data/lib/exa/constants/websets.rb +1 -1
- data/lib/exa/resources/webset_item_collection.rb +33 -0
- data/lib/exa/services/websets/create_validator.rb +15 -0
- data/lib/exa/services/websets/list_items.rb +9 -3
- data/lib/exa/version.rb +1 -1
- data/lib/exa.rb +2 -0
- metadata +18 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a5fb5324a2ae6dfb4380d91bce7d8d41a3f3b14e471e43bf254a5e763ef0113
|
|
4
|
+
data.tar.gz: 539a8486ec5e639c4f43fd3e81071ff0f1e5951b3e42c31bc1915d8e00d1df36
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dd87558e4a7428b56d9e513478e9d02f88cc845bf7d14fa9c0822e4042dd73d100e686ce3c151bc755d680cc283017311ba2b5109eedfcffef03bd99d4457700
|
|
7
|
+
data.tar.gz: 49eac46680edc76e6bfa9a2032b9871841095e052cc6e21eac174675f4060149ddc3a690cdd5b6506f39dbaf621f124d7a288f227f99d1637979729e23d63042
|
data/README.md
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
Ruby client for the Exa.ai API. Search and analyze web content using neural search, question answering, code discovery, and research automation.
|
|
4
4
|
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Requirements](#requirements)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Configuration](#configuration)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Error Handling](#error-handling)
|
|
13
|
+
- [Documentation](#documentation)
|
|
14
|
+
- [Development](#development)
|
|
15
|
+
- [Testing](#testing)
|
|
16
|
+
- [Support](#support)
|
|
17
|
+
- [License](#license)
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- **Ruby 3.0.0 or higher**
|
|
22
|
+
|
|
23
|
+
### Installing Ruby on macOS
|
|
24
|
+
|
|
25
|
+
If you're setting up on a fresh macOS laptop, the easiest way to get Ruby 3.x is through Homebrew:
|
|
26
|
+
|
|
27
|
+
**1. Install Homebrew** (if not already installed):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**2. Install Ruby:**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
brew install ruby
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**3. Add Homebrew's Ruby to your PATH** (follow the instructions Homebrew prints, usually adding to `~/.zshrc`):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >> ~/.zshrc
|
|
43
|
+
source ~/.zshrc
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**4. Verify installation:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
ruby -v # Should show Ruby 3.x
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Alternative: Using a version manager**
|
|
53
|
+
|
|
54
|
+
For managing multiple Ruby versions, consider [rbenv](https://github.com/rbenv/rbenv) or [asdf](https://asdf-vm.com/).
|
|
55
|
+
|
|
5
56
|
## Installation
|
|
6
57
|
|
|
7
58
|
Add to your Gemfile:
|
|
@@ -32,6 +83,20 @@ Get your API key from [dashboard.exa.ai](https://dashboard.exa.ai).
|
|
|
32
83
|
export EXA_API_KEY="your-api-key-here"
|
|
33
84
|
```
|
|
34
85
|
|
|
86
|
+
**Using .env file (local development)**
|
|
87
|
+
|
|
88
|
+
Create a `.env` file in your project root:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Copy the example file
|
|
92
|
+
cp .env.example .env
|
|
93
|
+
|
|
94
|
+
# Edit .env and add your API key
|
|
95
|
+
EXA_API_KEY=your-api-key-here
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The gem automatically loads `.env` files in development when the `dotenv` gem is installed.
|
|
99
|
+
|
|
35
100
|
**Ruby Code**
|
|
36
101
|
|
|
37
102
|
```ruby
|
|
@@ -193,6 +258,46 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for:
|
|
|
193
258
|
- Code conventions
|
|
194
259
|
- Building and releasing
|
|
195
260
|
|
|
261
|
+
## Testing
|
|
262
|
+
|
|
263
|
+
### Running Tests
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# Run unit tests (integration tests skip by default)
|
|
267
|
+
bundle exec rake test
|
|
268
|
+
|
|
269
|
+
# Run integration tests (VCR-based, no real API calls)
|
|
270
|
+
RUN_INTEGRATION_TESTS=true bundle exec rake test
|
|
271
|
+
|
|
272
|
+
# Run CLI integration tests (real API calls, requires explicit opt-in)
|
|
273
|
+
RUN_CLI_INTEGRATION_TESTS=true bundle exec rake test
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Integration Tests
|
|
277
|
+
|
|
278
|
+
**Integration tests are skipped by default** to prevent accidental API calls.
|
|
279
|
+
|
|
280
|
+
**VCR-based integration tests (`RUN_INTEGRATION_TESTS`):**
|
|
281
|
+
- Use recorded HTTP interactions (VCR cassettes)
|
|
282
|
+
- No real API calls when replaying cassettes
|
|
283
|
+
- Set `RUN_INTEGRATION_TESTS=true` to run them
|
|
284
|
+
- Safe to run during development
|
|
285
|
+
|
|
286
|
+
**CLI integration tests (`RUN_CLI_INTEGRATION_TESTS`):**
|
|
287
|
+
- Make real API calls through shell commands
|
|
288
|
+
- Consume Exa's concurrent search quota
|
|
289
|
+
- Set `RUN_CLI_INTEGRATION_TESTS=true` AND `EXA_API_KEY` to run them
|
|
290
|
+
- **Warning:** Can exhaust API quota and trigger rate limits lasting 1-2 days
|
|
291
|
+
|
|
292
|
+
**When to run integration tests:**
|
|
293
|
+
- VCR tests: Anytime (safe, no real API calls)
|
|
294
|
+
- CLI tests: Only before releases or when testing CLI-specific functionality
|
|
295
|
+
|
|
296
|
+
**Test Coverage:**
|
|
297
|
+
- **Unit tests** - Fast, no API calls, always run
|
|
298
|
+
- **VCR integration tests** - Replay cassettes, skipped by default
|
|
299
|
+
- **CLI integration tests** - Real API calls via shell, skipped by default
|
|
300
|
+
|
|
196
301
|
## Support
|
|
197
302
|
|
|
198
303
|
- **Documentation**: https://docs.exa.ai
|
data/exe/exa-ai-answer
CHANGED
|
@@ -9,7 +9,8 @@ def parse_args(argv)
|
|
|
9
9
|
output_format: "json",
|
|
10
10
|
api_key: nil,
|
|
11
11
|
text: false,
|
|
12
|
-
stream: false
|
|
12
|
+
stream: false,
|
|
13
|
+
skip_citations: false
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
# Extract query (first non-flag argument)
|
|
@@ -24,6 +25,9 @@ def parse_args(argv)
|
|
|
24
25
|
when "--stream"
|
|
25
26
|
args[:stream] = true
|
|
26
27
|
i += 1
|
|
28
|
+
when "--skip-citations", "--no-citations"
|
|
29
|
+
args[:skip_citations] = true
|
|
30
|
+
i += 1
|
|
27
31
|
when "--output-schema"
|
|
28
32
|
args[:output_schema] = argv[i + 1]
|
|
29
33
|
i += 2
|
|
@@ -48,6 +52,8 @@ def parse_args(argv)
|
|
|
48
52
|
Options:
|
|
49
53
|
--stream Stream answer chunks in real-time
|
|
50
54
|
--text Include full text content from sources
|
|
55
|
+
--skip-citations Remove citations from output (saves tokens)
|
|
56
|
+
--no-citations Alias for --skip-citations
|
|
51
57
|
--output-schema JSON JSON schema for structured output
|
|
52
58
|
--system-prompt TEXT System prompt to guide answer generation
|
|
53
59
|
--api-key KEY Exa API key (or set EXA_API_KEY env var)
|
|
@@ -123,7 +129,7 @@ begin
|
|
|
123
129
|
else
|
|
124
130
|
# Non-streaming mode - collect full response and format
|
|
125
131
|
result = client.answer(args[:query], **answer_params)
|
|
126
|
-
output = Exa::CLI::Formatters::AnswerFormatter.format(result, output_format)
|
|
132
|
+
output = Exa::CLI::Formatters::AnswerFormatter.format(result, output_format, skip_citations: args[:skip_citations])
|
|
127
133
|
puts output
|
|
128
134
|
$stdout.flush
|
|
129
135
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require "exa-ai"
|
|
5
5
|
|
|
6
|
-
VALID_FORMATS =
|
|
6
|
+
VALID_FORMATS = Exa::Constants::Websets::ENRICHMENT_FORMATS
|
|
7
7
|
|
|
8
8
|
# Recursively convert hash keys from strings to symbols
|
|
9
9
|
def deep_symbolize_keys(obj)
|
|
@@ -51,9 +51,7 @@ def parse_args(argv)
|
|
|
51
51
|
--format TYPE One of: #{VALID_FORMATS.join(', ')}
|
|
52
52
|
|
|
53
53
|
Options:
|
|
54
|
-
--title TEXT Display title
|
|
55
54
|
--options JSON Array of {label: "..."} (required if format=options, supports @file.json)
|
|
56
|
-
--instructions TEXT Additional instructions
|
|
57
55
|
--metadata JSON Custom metadata (supports @file.json)
|
|
58
56
|
--wait Wait for enrichment to complete
|
|
59
57
|
--api-key KEY Exa API key (or set EXA_API_KEY env var)
|
|
@@ -91,15 +89,9 @@ def parse_args(argv)
|
|
|
91
89
|
when "--format"
|
|
92
90
|
args[:format] = argv[i + 1]
|
|
93
91
|
i += 2
|
|
94
|
-
when "--title"
|
|
95
|
-
args[:title] = argv[i + 1]
|
|
96
|
-
i += 2
|
|
97
92
|
when "--options"
|
|
98
93
|
args[:options] = parse_json_or_file(argv[i + 1])
|
|
99
94
|
i += 2
|
|
100
|
-
when "--instructions"
|
|
101
|
-
args[:instructions] = argv[i + 1]
|
|
102
|
-
i += 2
|
|
103
95
|
when "--metadata"
|
|
104
96
|
args[:metadata] = parse_json_or_file(argv[i + 1])
|
|
105
97
|
i += 2
|
|
@@ -170,9 +162,7 @@ begin
|
|
|
170
162
|
description: args[:description],
|
|
171
163
|
format: args[:format]
|
|
172
164
|
}
|
|
173
|
-
enrichment_params[:title] = args[:title] if args[:title]
|
|
174
165
|
enrichment_params[:options] = args[:options] if args[:options]
|
|
175
|
-
enrichment_params[:instructions] = args[:instructions] if args[:instructions]
|
|
176
166
|
enrichment_params[:metadata] = args[:metadata] if args[:metadata]
|
|
177
167
|
|
|
178
168
|
# Create enrichment
|
data/exe/exa-ai-import-create
CHANGED
|
@@ -56,6 +56,7 @@ def parse_args(argv)
|
|
|
56
56
|
--entity-type TYPE Entity type (options: #{VALID_ENTITY_TYPES.join(', ')})
|
|
57
57
|
|
|
58
58
|
Options:
|
|
59
|
+
--entity-description TXT Description for custom entity type (required with --entity-type custom)
|
|
59
60
|
--csv-identifier N CSV column identifier (0-indexed)
|
|
60
61
|
--metadata JSON Custom metadata (supports @file.json)
|
|
61
62
|
--quiet Suppress normal output (only show errors)
|
|
@@ -102,6 +103,9 @@ def parse_args(argv)
|
|
|
102
103
|
when "--entity-type"
|
|
103
104
|
args[:entity_type] = argv[i + 1]
|
|
104
105
|
i += 2
|
|
106
|
+
when "--entity-description"
|
|
107
|
+
args[:entity_description] = argv[i + 1]
|
|
108
|
+
i += 2
|
|
105
109
|
when "--csv-identifier"
|
|
106
110
|
args[:csv_identifier] = argv[i + 1].to_i
|
|
107
111
|
i += 2
|
|
@@ -161,6 +165,17 @@ begin
|
|
|
161
165
|
exit 1
|
|
162
166
|
end
|
|
163
167
|
|
|
168
|
+
# Validate entity-description for custom entity type
|
|
169
|
+
if args[:entity_type] == "custom"
|
|
170
|
+
unless args[:entity_description]
|
|
171
|
+
$stderr.puts "Error: --entity-description is required when --entity-type is 'custom'"
|
|
172
|
+
$stderr.puts "Run 'exa-ai import-create --help' for usage information"
|
|
173
|
+
exit 1
|
|
174
|
+
end
|
|
175
|
+
elsif args[:entity_description]
|
|
176
|
+
$stderr.puts "Warning: --entity-description is only used with --entity-type custom (ignoring)"
|
|
177
|
+
end
|
|
178
|
+
|
|
164
179
|
# Validate file exists
|
|
165
180
|
unless File.exist?(args[:file_path])
|
|
166
181
|
$stderr.puts "Error: File not found: #{args[:file_path]}"
|
|
@@ -177,12 +192,15 @@ begin
|
|
|
177
192
|
client = Exa::CLI::Base.build_client(api_key)
|
|
178
193
|
|
|
179
194
|
# Prepare import parameters
|
|
195
|
+
entity = { type: args[:entity_type] }
|
|
196
|
+
entity[:description] = args[:entity_description] if args[:entity_description]
|
|
197
|
+
|
|
180
198
|
import_params = {
|
|
181
199
|
file_path: args[:file_path],
|
|
182
200
|
count: args[:count],
|
|
183
201
|
title: args[:title],
|
|
184
202
|
format: args[:format],
|
|
185
|
-
entity:
|
|
203
|
+
entity: entity
|
|
186
204
|
}
|
|
187
205
|
import_params[:metadata] = args[:metadata] if args[:metadata]
|
|
188
206
|
|
data/exe/exa-ai-search
CHANGED
|
@@ -2,189 +2,61 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "exa-ai"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
i += 2
|
|
61
|
-
when "--start-published-date"
|
|
62
|
-
args[:start_published_date] = argv[i + 1]
|
|
63
|
-
i += 2
|
|
64
|
-
when "--end-published-date"
|
|
65
|
-
args[:end_published_date] = argv[i + 1]
|
|
66
|
-
i += 2
|
|
67
|
-
when "--start-crawl-date"
|
|
68
|
-
args[:start_crawl_date] = argv[i + 1]
|
|
69
|
-
i += 2
|
|
70
|
-
when "--end-crawl-date"
|
|
71
|
-
args[:end_crawl_date] = argv[i + 1]
|
|
72
|
-
i += 2
|
|
73
|
-
when "--include-text"
|
|
74
|
-
args[:include_text] ||= []
|
|
75
|
-
args[:include_text] << argv[i + 1]
|
|
76
|
-
i += 2
|
|
77
|
-
when "--exclude-text"
|
|
78
|
-
args[:exclude_text] ||= []
|
|
79
|
-
args[:exclude_text] << argv[i + 1]
|
|
80
|
-
i += 2
|
|
81
|
-
when "--text"
|
|
82
|
-
args[:text] = true
|
|
83
|
-
i += 1
|
|
84
|
-
when "--text-max-characters"
|
|
85
|
-
args[:text_max_characters] = argv[i + 1].to_i
|
|
86
|
-
i += 2
|
|
87
|
-
when "--include-html-tags"
|
|
88
|
-
args[:include_html_tags] = true
|
|
89
|
-
i += 1
|
|
90
|
-
when "--summary"
|
|
91
|
-
args[:summary] = true
|
|
92
|
-
i += 1
|
|
93
|
-
when "--summary-query"
|
|
94
|
-
args[:summary_query] = argv[i + 1]
|
|
95
|
-
i += 2
|
|
96
|
-
when "--summary-schema"
|
|
97
|
-
schema_arg = argv[i + 1]
|
|
98
|
-
args[:summary_schema] = if schema_arg.start_with?("@")
|
|
99
|
-
JSON.parse(File.read(schema_arg[1..]))
|
|
100
|
-
else
|
|
101
|
-
JSON.parse(schema_arg)
|
|
102
|
-
end
|
|
103
|
-
i += 2
|
|
104
|
-
when "--context"
|
|
105
|
-
args[:context] = true
|
|
106
|
-
i += 1
|
|
107
|
-
when "--context-max-characters"
|
|
108
|
-
args[:context_max_characters] = argv[i + 1].to_i
|
|
109
|
-
i += 2
|
|
110
|
-
when "--subpages"
|
|
111
|
-
args[:subpages] = argv[i + 1].to_i
|
|
112
|
-
i += 2
|
|
113
|
-
when "--subpage-target"
|
|
114
|
-
args[:subpage_target] ||= []
|
|
115
|
-
args[:subpage_target] << argv[i + 1]
|
|
116
|
-
i += 2
|
|
117
|
-
when "--links"
|
|
118
|
-
args[:links] = argv[i + 1].to_i
|
|
119
|
-
i += 2
|
|
120
|
-
when "--image-links"
|
|
121
|
-
args[:image_links] = argv[i + 1].to_i
|
|
122
|
-
i += 2
|
|
123
|
-
when "--help", "-h"
|
|
124
|
-
puts <<~HELP
|
|
125
|
-
Usage: exa-ai search QUERY [OPTIONS]
|
|
126
|
-
|
|
127
|
-
Search the web using Exa AI
|
|
128
|
-
|
|
129
|
-
Arguments:
|
|
130
|
-
QUERY Search query (required)
|
|
131
|
-
|
|
132
|
-
Options:
|
|
133
|
-
--num-results N Number of results to return (default: 10)
|
|
134
|
-
--type TYPE Search type: fast, deep, keyword, or auto (default: fast)
|
|
135
|
-
--category CAT Focus on specific data category
|
|
136
|
-
Options: "company", "research paper", "news", "pdf",
|
|
137
|
-
"github", "tweet", "personal site", "linkedin profile",
|
|
138
|
-
"financial report"
|
|
139
|
-
--include-domains D Comma-separated list of domains to include
|
|
140
|
-
--exclude-domains D Comma-separated list of domains to exclude
|
|
141
|
-
--start-published-date DATE Filter by published date (ISO 8601 format)
|
|
142
|
-
--end-published-date DATE Filter by published date (ISO 8601 format)
|
|
143
|
-
--start-crawl-date DATE Filter by crawl date (ISO 8601 format)
|
|
144
|
-
--end-crawl-date DATE Filter by crawl date (ISO 8601 format)
|
|
145
|
-
--include-text PHRASE Include results with exact phrase (repeatable)
|
|
146
|
-
--exclude-text PHRASE Exclude results with exact phrase (repeatable)
|
|
147
|
-
|
|
148
|
-
Content Extraction:
|
|
149
|
-
--text Include full webpage text
|
|
150
|
-
--text-max-characters N Max characters for webpage text
|
|
151
|
-
--include-html-tags Include HTML tags in text extraction
|
|
152
|
-
--summary Include AI-generated summary
|
|
153
|
-
--summary-query PROMPT Custom prompt for summary generation
|
|
154
|
-
--summary-schema FILE JSON schema for summary structure (@file syntax)
|
|
155
|
-
--context Format results as context for LLM RAG
|
|
156
|
-
--context-max-characters N Max characters for context string
|
|
157
|
-
--subpages N Number of subpages to crawl
|
|
158
|
-
--subpage-target PHRASE Subpage target phrases (repeatable)
|
|
159
|
-
--links N Number of links to extract per result
|
|
160
|
-
--image-links N Number of image links to extract
|
|
161
|
-
|
|
162
|
-
General Options:
|
|
163
|
-
--linkedin TYPE Search LinkedIn: company, person, or all
|
|
164
|
-
--api-key KEY Exa API key (or set EXA_API_KEY env var)
|
|
165
|
-
--output-format FMT Output format: json, pretty, or text (default: json)
|
|
166
|
-
--help, -h Show this help message
|
|
167
|
-
|
|
168
|
-
Examples:
|
|
169
|
-
exa-ai search "ruby programming"
|
|
170
|
-
exa-ai search "machine learning" --num-results 5 --type deep
|
|
171
|
-
exa-ai search "Latest LLM research" --category "research paper"
|
|
172
|
-
exa-ai search "AI startups" --category company
|
|
173
|
-
exa-ai search "Anthropic" --linkedin company
|
|
174
|
-
exa-ai search "Dario Amodei" --linkedin person
|
|
175
|
-
exa-ai search "AI" --linkedin all
|
|
176
|
-
exa-ai search "AI research" --include-domains arxiv.org,scholar.google.com
|
|
177
|
-
exa-ai search "tutorials" --output-format pretty
|
|
178
|
-
HELP
|
|
179
|
-
exit 0
|
|
180
|
-
else
|
|
181
|
-
query_parts << arg
|
|
182
|
-
i += 1
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
args[:query] = query_parts.join(" ")
|
|
187
|
-
args
|
|
5
|
+
require_relative "../lib/exa/cli/search_parser"
|
|
6
|
+
|
|
7
|
+
def print_help
|
|
8
|
+
puts <<~HELP
|
|
9
|
+
Usage: exa-ai search QUERY [OPTIONS]
|
|
10
|
+
|
|
11
|
+
Search the web using Exa AI
|
|
12
|
+
|
|
13
|
+
Arguments:
|
|
14
|
+
QUERY Search query (required)
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--num-results N Number of results to return (default: 10)
|
|
18
|
+
--type TYPE Search type: fast, deep, keyword, or auto (default: fast)
|
|
19
|
+
--category CAT Focus on specific data category
|
|
20
|
+
Options: "company", "research paper", "news", "pdf",
|
|
21
|
+
"github", "tweet", "personal site", "financial report",
|
|
22
|
+
"people"
|
|
23
|
+
--include-domains D Comma-separated list of domains to include
|
|
24
|
+
--exclude-domains D Comma-separated list of domains to exclude
|
|
25
|
+
--start-published-date DATE Filter by published date (ISO 8601 format)
|
|
26
|
+
--end-published-date DATE Filter by published date (ISO 8601 format)
|
|
27
|
+
--start-crawl-date DATE Filter by crawl date (ISO 8601 format)
|
|
28
|
+
--end-crawl-date DATE Filter by crawl date (ISO 8601 format)
|
|
29
|
+
--include-text PHRASE Include results with exact phrase (repeatable)
|
|
30
|
+
--exclude-text PHRASE Exclude results with exact phrase (repeatable)
|
|
31
|
+
|
|
32
|
+
Content Extraction:
|
|
33
|
+
--text Include full webpage text
|
|
34
|
+
--text-max-characters N Max characters for webpage text
|
|
35
|
+
--include-html-tags Include HTML tags in text extraction
|
|
36
|
+
--summary Include AI-generated summary
|
|
37
|
+
--summary-query PROMPT Custom prompt for summary generation
|
|
38
|
+
--summary-schema FILE JSON schema for summary structure (@file syntax)
|
|
39
|
+
--context Format results as context for LLM RAG
|
|
40
|
+
--context-max-characters N Max characters for context string
|
|
41
|
+
--subpages N Number of subpages to crawl
|
|
42
|
+
--subpage-target PHRASE Subpage target phrases (repeatable)
|
|
43
|
+
--links N Number of links to extract per result
|
|
44
|
+
--image-links N Number of image links to extract
|
|
45
|
+
|
|
46
|
+
General Options:
|
|
47
|
+
--api-key KEY Exa API key (or set EXA_API_KEY env var)
|
|
48
|
+
--output-format FMT Output format: json, pretty, or text (default: json)
|
|
49
|
+
--help, -h Show this help message
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
exa-ai search "ruby programming"
|
|
53
|
+
exa-ai search "machine learning" --num-results 5 --type deep
|
|
54
|
+
exa-ai search "Latest LLM research" --category "research paper"
|
|
55
|
+
exa-ai search "AI startups" --category company
|
|
56
|
+
exa-ai search "Dario Amodei" --category people
|
|
57
|
+
exa-ai search "AI research" --include-domains arxiv.org,scholar.google.com
|
|
58
|
+
exa-ai search "tutorials" --output-format pretty
|
|
59
|
+
HELP
|
|
188
60
|
end
|
|
189
61
|
|
|
190
62
|
# Build contents parameter from extracted flags
|
|
@@ -238,15 +110,15 @@ end
|
|
|
238
110
|
|
|
239
111
|
# Main execution
|
|
240
112
|
begin
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
$stderr.puts "Error: Query is required"
|
|
246
|
-
$stderr.puts "Run 'exa-ai search --help' for usage information"
|
|
247
|
-
exit 1
|
|
113
|
+
# Handle help flag
|
|
114
|
+
if ARGV.include?("--help") || ARGV.include?("-h")
|
|
115
|
+
print_help
|
|
116
|
+
exit 0
|
|
248
117
|
end
|
|
249
118
|
|
|
119
|
+
# Parse command-line arguments
|
|
120
|
+
args = Exa::CLI::SearchParser.parse(ARGV)
|
|
121
|
+
|
|
250
122
|
# Resolve API key
|
|
251
123
|
api_key = Exa::CLI::Base.resolve_api_key(args[:api_key])
|
|
252
124
|
|
|
@@ -272,17 +144,8 @@ begin
|
|
|
272
144
|
contents = build_contents(args)
|
|
273
145
|
search_params.merge!(contents) if contents
|
|
274
146
|
|
|
275
|
-
# Execute search
|
|
276
|
-
result =
|
|
277
|
-
when "company"
|
|
278
|
-
client.linkedin_company(args[:query], **search_params)
|
|
279
|
-
when "person"
|
|
280
|
-
client.linkedin_person(args[:query], **search_params)
|
|
281
|
-
when "all"
|
|
282
|
-
client.search(args[:query], includeDomains: ["linkedin.com"], **search_params)
|
|
283
|
-
else
|
|
284
|
-
client.search(args[:query], **search_params)
|
|
285
|
-
end
|
|
147
|
+
# Execute search
|
|
148
|
+
result = client.search(args[:query], **search_params)
|
|
286
149
|
|
|
287
150
|
# Format and output result
|
|
288
151
|
output = Exa::CLI::Formatters::SearchFormatter.format(result, output_format)
|
data/exe/exa-ai-webset-create
CHANGED
|
@@ -80,32 +80,51 @@ def parse_args(argv)
|
|
|
80
80
|
Create a new webset from search criteria or an import
|
|
81
81
|
|
|
82
82
|
Required (choose one):
|
|
83
|
-
--search JSON Search configuration (supports @file.json)
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
86
89
|
|
|
87
90
|
Options:
|
|
88
91
|
--enrichments JSON Array of enrichment configs (supports @file.json)
|
|
89
|
-
|
|
92
|
+
Format: [{"description":"...","format":"text"}]
|
|
93
|
+
--exclude JSON Sources to exclude from searches (supports @file.json)
|
|
94
|
+
Format: [{"source":"import|webset","id":"..."}]
|
|
90
95
|
--external-id ID External identifier for the webset
|
|
91
96
|
--metadata JSON Custom metadata (supports @file.json)
|
|
97
|
+
Format: {"key":"value"}
|
|
92
98
|
--wait Wait for webset to reach idle status
|
|
93
99
|
--api-key KEY Exa API key (or set EXA_API_KEY env var)
|
|
94
100
|
--output-format FMT Output format: json, pretty, or text (default: json)
|
|
95
101
|
--help, -h Show this help message
|
|
96
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
|
+
|
|
97
113
|
Examples:
|
|
98
114
|
# Create webset from search
|
|
99
115
|
exa-ai webset-create --search '{"query":"AI startups","count":10}'
|
|
100
116
|
exa-ai webset-create --search @search.json --enrichments @enrichments.json
|
|
101
117
|
exa-ai webset-create --search @search.json --wait
|
|
102
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
|
+
|
|
103
122
|
# Create webset from import
|
|
104
123
|
exa-ai webset-create --import import_abc123
|
|
105
124
|
exa-ai webset-create --import import_def456 --enrichments @enrichments.json
|
|
106
125
|
|
|
107
|
-
#
|
|
108
|
-
exa-ai webset-create --import
|
|
126
|
+
# Load import AND run search (search not scoped to import)
|
|
127
|
+
exa-ai webset-create --import import_abc123 --search '{"query":"investors","count":20}'
|
|
109
128
|
HELP
|
|
110
129
|
exit 0
|
|
111
130
|
else
|