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.
- checksums.yaml +4 -4
- data/README.md +58 -17
- data/exe/exa-ai +111 -6
- data/exe/exa-ai-enrichment-cancel +107 -0
- data/exe/exa-ai-enrichment-create +235 -0
- data/exe/exa-ai-enrichment-delete +121 -0
- data/exe/exa-ai-enrichment-get +103 -0
- data/exe/exa-ai-enrichment-list +98 -0
- data/exe/exa-ai-enrichment-update +170 -0
- data/exe/exa-ai-find-similar +240 -0
- data/exe/exa-ai-webset-cancel +96 -0
- data/exe/exa-ai-webset-create +192 -0
- data/exe/exa-ai-webset-delete +110 -0
- data/exe/exa-ai-webset-get +92 -0
- data/exe/exa-ai-webset-item-delete +111 -0
- data/exe/exa-ai-webset-item-get +104 -0
- data/exe/exa-ai-webset-item-list +93 -0
- data/exe/exa-ai-webset-list +90 -0
- data/exe/exa-ai-webset-search-cancel +103 -0
- data/exe/exa-ai-webset-search-create +233 -0
- data/exe/exa-ai-webset-search-get +104 -0
- data/exe/exa-ai-webset-update +139 -0
- data/lib/exa/cli/formatters/enrichment_formatter.rb +69 -0
- data/lib/exa/cli/formatters/webset_formatter.rb +68 -0
- data/lib/exa/cli/formatters/webset_item_formatter.rb +69 -0
- data/lib/exa/client.rb +171 -0
- data/lib/exa/resources/webset.rb +74 -0
- data/lib/exa/resources/webset_collection.rb +33 -0
- data/lib/exa/resources/webset_enrichment.rb +71 -0
- data/lib/exa/resources/webset_enrichment_collection.rb +28 -0
- data/lib/exa/resources/webset_search.rb +112 -0
- data/lib/exa/services/parameter_converter.rb +1 -0
- data/lib/exa/services/websets/cancel.rb +36 -0
- data/lib/exa/services/websets/cancel_enrichment.rb +35 -0
- data/lib/exa/services/websets/cancel_search.rb +44 -0
- data/lib/exa/services/websets/create.rb +45 -0
- data/lib/exa/services/websets/create_enrichment.rb +35 -0
- data/lib/exa/services/websets/create_search.rb +48 -0
- data/lib/exa/services/websets/create_search_validator.rb +128 -0
- data/lib/exa/services/websets/create_validator.rb +189 -0
- data/lib/exa/services/websets/delete.rb +36 -0
- data/lib/exa/services/websets/delete_enrichment.rb +35 -0
- data/lib/exa/services/websets/delete_item.rb +20 -0
- data/lib/exa/services/websets/get_item.rb +20 -0
- data/lib/exa/services/websets/get_search.rb +43 -0
- data/lib/exa/services/websets/list.rb +25 -0
- data/lib/exa/services/websets/list_items.rb +20 -0
- data/lib/exa/services/websets/retrieve.rb +47 -0
- data/lib/exa/services/websets/retrieve_enrichment.rb +35 -0
- data/lib/exa/services/websets/update.rb +37 -0
- data/lib/exa/services/websets/update_enrichment.rb +36 -0
- data/lib/exa/services/websets_parameter_converter.rb +45 -0
- data/lib/exa/version.rb +1 -1
- data/lib/exa.rb +25 -0
- metadata +64 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4cc4ccfc59e93013765b9289621cdb0447c4cc5bbc24e1585376ebae4b7b6ff0
|
|
4
|
+
data.tar.gz: 45cbb8655d636906d1230651896aeab9375720e655ca4d18c74f12a425ea5bd7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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("
|
|
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
|
|
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://
|
|
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
|
|
88
|
-
exa-ai search "
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
exa-ai get
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|