exa-ai 0.1.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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +562 -0
  4. data/exe/exa-ai +95 -0
  5. data/exe/exa-ai-answer +131 -0
  6. data/exe/exa-ai-context +104 -0
  7. data/exe/exa-ai-get-contents +114 -0
  8. data/exe/exa-ai-research-get +110 -0
  9. data/exe/exa-ai-research-list +95 -0
  10. data/exe/exa-ai-research-start +175 -0
  11. data/exe/exa-ai-search +134 -0
  12. data/lib/exa/cli/base.rb +51 -0
  13. data/lib/exa/cli/error_handler.rb +98 -0
  14. data/lib/exa/cli/formatters/answer_formatter.rb +63 -0
  15. data/lib/exa/cli/formatters/contents_formatter.rb +50 -0
  16. data/lib/exa/cli/formatters/context_formatter.rb +58 -0
  17. data/lib/exa/cli/formatters/research_formatter.rb +120 -0
  18. data/lib/exa/cli/formatters/search_formatter.rb +44 -0
  19. data/lib/exa/cli/polling.rb +46 -0
  20. data/lib/exa/client.rb +132 -0
  21. data/lib/exa/connection.rb +32 -0
  22. data/lib/exa/error.rb +31 -0
  23. data/lib/exa/middleware/raise_error.rb +55 -0
  24. data/lib/exa/resources/answer.rb +20 -0
  25. data/lib/exa/resources/contents_result.rb +29 -0
  26. data/lib/exa/resources/context_result.rb +37 -0
  27. data/lib/exa/resources/find_similar_result.rb +28 -0
  28. data/lib/exa/resources/research_list.rb +18 -0
  29. data/lib/exa/resources/research_task.rb +39 -0
  30. data/lib/exa/resources/search_result.rb +30 -0
  31. data/lib/exa/services/answer.rb +23 -0
  32. data/lib/exa/services/context.rb +27 -0
  33. data/lib/exa/services/find_similar.rb +26 -0
  34. data/lib/exa/services/get_contents.rb +25 -0
  35. data/lib/exa/services/research_get.rb +30 -0
  36. data/lib/exa/services/research_list.rb +37 -0
  37. data/lib/exa/services/research_start.rb +26 -0
  38. data/lib/exa/services/search.rb +26 -0
  39. data/lib/exa/version.rb +5 -0
  40. data/lib/exa.rb +54 -0
  41. metadata +174 -0
data/exe/exa-ai-answer ADDED
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Set up load paths
5
+ require "bundler/setup"
6
+ require "exa"
7
+ require "exa/cli/base"
8
+ require "exa/cli/formatters/answer_formatter"
9
+
10
+ # Parse command-line arguments
11
+ def parse_args(argv)
12
+ args = {
13
+ output_format: "json",
14
+ api_key: nil,
15
+ text: false
16
+ }
17
+
18
+ # Extract query (first non-flag argument)
19
+ query_parts = []
20
+ i = 0
21
+ while i < argv.length
22
+ arg = argv[i]
23
+ case arg
24
+ when "--text"
25
+ args[:text] = true
26
+ i += 1
27
+ when "--output-schema"
28
+ args[:output_schema] = argv[i + 1]
29
+ i += 2
30
+ when "--api-key"
31
+ args[:api_key] = argv[i + 1]
32
+ i += 2
33
+ when "--output-format"
34
+ args[:output_format] = argv[i + 1]
35
+ i += 2
36
+ when "--help", "-h"
37
+ puts <<~HELP
38
+ Usage: exa-api answer QUERY [OPTIONS]
39
+
40
+ Generate an answer to a question using Exa AI
41
+
42
+ Arguments:
43
+ QUERY Question or query to answer (required)
44
+
45
+ Options:
46
+ --text Include full text content from sources
47
+ --output-schema JSON JSON schema for structured output
48
+ --api-key KEY Exa API key (or set EXA_API_KEY env var)
49
+ --output-format FMT Output format: json, pretty, or text (default: json)
50
+ --help, -h Show this help message
51
+
52
+ Examples:
53
+ exa-api answer "What is the capital of France?"
54
+ exa-api answer "Latest AI breakthroughs" --text
55
+ exa-api answer "Ruby best practices" --output-format pretty
56
+ exa-api answer "What is the capital of France?" --output-schema '{"type":"object","properties":{"city":{"type":"string"},"state":{"type":"string"}}}'
57
+ HELP
58
+ exit 0
59
+ else
60
+ query_parts << arg
61
+ i += 1
62
+ end
63
+ end
64
+
65
+ args[:query] = query_parts.join(" ")
66
+ args
67
+ end
68
+
69
+ # Main execution
70
+ begin
71
+ args = parse_args(ARGV)
72
+
73
+ # Validate query
74
+ if args[:query].nil? || args[:query].empty?
75
+ $stderr.puts "Error: Query is required"
76
+ $stderr.puts "Run 'exa-api answer --help' for usage information"
77
+ exit 1
78
+ end
79
+
80
+ # Resolve API key
81
+ api_key = Exa::CLI::Base.resolve_api_key(args[:api_key])
82
+
83
+ # Resolve output format
84
+ output_format = Exa::CLI::Base.resolve_output_format(args[:output_format])
85
+
86
+ # Build client
87
+ client = Exa::CLI::Base.build_client(api_key)
88
+
89
+ # Prepare answer parameters
90
+ answer_params = {}
91
+ answer_params[:text] = args[:text] if args[:text]
92
+
93
+ # Parse output_schema as JSON if provided
94
+ if args[:output_schema]
95
+ begin
96
+ answer_params[:output_schema] = JSON.parse(args[:output_schema])
97
+ rescue JSON::ParserError => e
98
+ $stderr.puts "Error: Invalid JSON in --output-schema: #{e.message}"
99
+ exit 1
100
+ end
101
+ end
102
+
103
+ # Execute answer
104
+ result = client.answer(args[:query], **answer_params)
105
+
106
+ # Format and output result
107
+ output = Exa::CLI::Formatters::AnswerFormatter.format(result, output_format)
108
+ puts output
109
+
110
+ rescue Exa::ConfigurationError => e
111
+ $stderr.puts "Configuration error: #{e.message}"
112
+ exit 1
113
+ rescue Exa::Unauthorized => e
114
+ $stderr.puts "Authentication error: #{e.message}"
115
+ $stderr.puts "Check your API key (set EXA_API_KEY or use --api-key)"
116
+ exit 1
117
+ rescue Exa::ClientError => e
118
+ $stderr.puts "Client error: #{e.message}"
119
+ exit 1
120
+ rescue Exa::ServerError => e
121
+ $stderr.puts "Server error: #{e.message}"
122
+ $stderr.puts "The Exa API may be experiencing issues. Please try again later."
123
+ exit 1
124
+ rescue Exa::Error => e
125
+ $stderr.puts "Error: #{e.message}"
126
+ exit 1
127
+ rescue StandardError => e
128
+ $stderr.puts "Unexpected error: #{e.message}"
129
+ $stderr.puts e.backtrace.first(5) if ENV["DEBUG"]
130
+ exit 1
131
+ end
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "exa"
6
+ require "exa/cli/base"
7
+ require "exa/cli/formatters/context_formatter"
8
+
9
+ # Parse command line arguments
10
+ def parse_args(args)
11
+ query = nil
12
+ api_key = nil
13
+ tokens_num = "dynamic"
14
+ output_format = nil
15
+
16
+ i = 0
17
+ while i < args.length
18
+ arg = args[i]
19
+
20
+ case arg
21
+ when "--api-key"
22
+ i += 1
23
+ api_key = args[i]
24
+ when "--tokens-num"
25
+ i += 1
26
+ tokens_num = args[i]
27
+ # Convert to integer if it's not "dynamic"
28
+ tokens_num = tokens_num.to_i unless tokens_num.downcase == "dynamic"
29
+ when "--output-format"
30
+ i += 1
31
+ output_format = args[i]
32
+ when "--help", "-h"
33
+ print_help
34
+ exit 0
35
+ else
36
+ # First non-flag argument is the query
37
+ query = arg if query.nil?
38
+ end
39
+
40
+ i += 1
41
+ end
42
+
43
+ { query: query, api_key: api_key, tokens_num: tokens_num, output_format: output_format }
44
+ end
45
+
46
+ def print_help
47
+ puts "Exa Context - Get code context from repositories"
48
+ puts ""
49
+ puts "Usage: exa-api context <query> [options]"
50
+ puts ""
51
+ puts "Arguments:"
52
+ puts " query Search query for code context (required)"
53
+ puts ""
54
+ puts "Options:"
55
+ puts " --tokens-num NUM Number of tokens for response (or 'dynamic', default: dynamic)"
56
+ puts " --api-key KEY Exa API key (or set EXA_API_KEY env var)"
57
+ puts " --output-format FMT Output format: json, pretty, or text (default: json)"
58
+ puts " --help, -h Show this help message"
59
+ puts ""
60
+ puts "Examples:"
61
+ puts " exa-api context 'React hooks'"
62
+ puts " exa-api context 'authentication with JWT in Ruby' --tokens-num 5000"
63
+ puts " exa-api context 'React hooks' --output-format text"
64
+ end
65
+
66
+ begin
67
+ # Parse arguments
68
+ options = parse_args(ARGV)
69
+
70
+ # Validate query
71
+ unless options[:query]
72
+ puts "Error: Query argument required"
73
+ puts ""
74
+ puts "Run 'exa-api context --help' for usage information."
75
+ exit 1
76
+ end
77
+
78
+ # Resolve API key
79
+ api_key = Exa::CLI::Base.resolve_api_key(options[:api_key])
80
+
81
+ # Resolve output format
82
+ output_format = Exa::CLI::Base.resolve_output_format(options[:output_format])
83
+
84
+ # Build client
85
+ client = Exa::CLI::Base.build_client(api_key)
86
+
87
+ # Build request parameters
88
+ params = {}
89
+ params[:tokens_num] = options[:tokens_num]
90
+
91
+ # Call API
92
+ result = client.context(options[:query], **params)
93
+
94
+ # Format and output
95
+ output = Exa::CLI::Formatters::ContextFormatter.format(result, output_format)
96
+ puts output
97
+ rescue Exa::Error => e
98
+ puts "Error: #{e.message}"
99
+ exit 1
100
+ rescue StandardError => e
101
+ puts "Unexpected error: #{e.message}"
102
+ puts e.backtrace.first(5) if ENV["DEBUG"]
103
+ exit 1
104
+ end
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "exa"
6
+ require "exa/cli/base"
7
+ require "exa/cli/formatters/contents_formatter"
8
+
9
+ # Parse command line arguments
10
+ def parse_args(args)
11
+ ids = []
12
+ api_key = nil
13
+ text = false
14
+ highlights = false
15
+ summary = false
16
+ output_format = nil
17
+
18
+ i = 0
19
+ while i < args.length
20
+ arg = args[i]
21
+
22
+ case arg
23
+ when "--api-key"
24
+ i += 1
25
+ api_key = args[i]
26
+ when "--text"
27
+ text = true
28
+ when "--highlights"
29
+ highlights = true
30
+ when "--summary"
31
+ summary = true
32
+ when "--output-format"
33
+ i += 1
34
+ output_format = args[i]
35
+ when "--help", "-h"
36
+ print_help
37
+ exit 0
38
+ else
39
+ # First non-flag argument is the IDs (comma-separated or single)
40
+ if ids.empty?
41
+ ids_arg = arg
42
+ ids = ids_arg.include?(",") ? ids_arg.split(",").map(&:strip) : [ids_arg]
43
+ end
44
+ end
45
+
46
+ i += 1
47
+ end
48
+
49
+ { ids: ids, api_key: api_key, text: text, highlights: highlights, summary: summary, output_format: output_format }
50
+ end
51
+
52
+ def print_help
53
+ puts "Exa Get-Contents - Retrieve page contents"
54
+ puts ""
55
+ puts "Usage: exa-api get-contents <ids> [options]"
56
+ puts ""
57
+ puts "Arguments:"
58
+ puts " ids Comma-separated list of IDs or URLs (required)"
59
+ puts ""
60
+ puts "Options:"
61
+ puts " --text Include page text in response"
62
+ puts " --highlights Include highlights in response"
63
+ puts " --summary Include summary in response"
64
+ puts " --api-key KEY Exa API key (or set EXA_API_KEY env var)"
65
+ puts " --output-format FMT Output format: json, pretty, or text (default: json)"
66
+ puts " --help, -h Show this help message"
67
+ puts ""
68
+ puts "Examples:"
69
+ puts " exa-api get-contents 'https://example.com'"
70
+ puts " exa-api get-contents 'id1,id2,id3' --text"
71
+ puts " exa-api get-contents 'https://example.com' --highlights --output-format pretty"
72
+ end
73
+
74
+ begin
75
+ # Parse arguments
76
+ options = parse_args(ARGV)
77
+
78
+ # Validate IDs
79
+ if options[:ids].empty?
80
+ puts "Error: IDs argument required"
81
+ puts ""
82
+ puts "Run 'exa-api get-contents --help' for usage information."
83
+ exit 1
84
+ end
85
+
86
+ # Resolve API key
87
+ api_key = Exa::CLI::Base.resolve_api_key(options[:api_key])
88
+
89
+ # Resolve output format
90
+ output_format = Exa::CLI::Base.resolve_output_format(options[:output_format])
91
+
92
+ # Build client
93
+ client = Exa::CLI::Base.build_client(api_key)
94
+
95
+ # Build request parameters
96
+ params = {}
97
+ params[:text] = true if options[:text]
98
+ params[:highlights] = true if options[:highlights]
99
+ params[:summary] = true if options[:summary]
100
+
101
+ # Call API
102
+ result = client.get_contents(options[:ids], **params)
103
+
104
+ # Format and output
105
+ output = Exa::CLI::Formatters::ContentsFormatter.format(result, output_format)
106
+ puts output
107
+ rescue Exa::Error => e
108
+ puts "Error: #{e.message}"
109
+ exit 1
110
+ rescue StandardError => e
111
+ puts "Unexpected error: #{e.message}"
112
+ puts e.backtrace.first(5) if ENV["DEBUG"]
113
+ exit 1
114
+ end
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "exa"
6
+
7
+ # Parse command-line arguments
8
+ api_key = nil
9
+ research_id = nil
10
+ events = false
11
+ stream = false
12
+ output_format = "json"
13
+
14
+ args = ARGV.dup
15
+ while args.any?
16
+ arg = args.shift
17
+ case arg
18
+ when "--api-key"
19
+ api_key = args.shift
20
+ when "--events"
21
+ events = true
22
+ when "--stream"
23
+ stream = true
24
+ when "--output-format"
25
+ output_format = args.shift
26
+ when "--help", "-h"
27
+ puts <<~HELP
28
+ Usage: exa-api research-get <research_id> [options]
29
+
30
+ Get the status and results of a research task.
31
+
32
+ Arguments:
33
+ research_id ID of the research task to retrieve
34
+
35
+ Options:
36
+ --api-key KEY Exa API key (or use EXA_API_KEY env var)
37
+ --events Include task execution events in response
38
+ --stream Stream the response (for real-time updates)
39
+ --output-format FORMAT Output format: json, pretty, or text (default: json)
40
+ --help, -h Show this help message
41
+
42
+ Examples:
43
+ exa-api research-get research_123
44
+ exa-api research-get research_123 --events
45
+ exa-api research-get research_123 --stream
46
+ exa-api research-get research_123 --output-format pretty
47
+ HELP
48
+ exit 0
49
+ else
50
+ # First positional argument is the research_id
51
+ if research_id.nil?
52
+ research_id = arg
53
+ else
54
+ warn "Unknown option: #{arg}"
55
+ exit 1
56
+ end
57
+ end
58
+ end
59
+
60
+ # Validate required arguments
61
+ if research_id.nil?
62
+ warn "Error: Research ID argument required"
63
+ warn "Usage: exa-api research-get <research_id> [options]"
64
+ warn "Try 'exa-api research-get --help' for more information"
65
+ exit 1
66
+ end
67
+
68
+ begin
69
+ # Resolve API key
70
+ api_key = Exa::CLI::Base.resolve_api_key(api_key)
71
+
72
+ # Build client
73
+ client = Exa::CLI::Base.build_client(api_key)
74
+
75
+ # Build parameters
76
+ params = {}
77
+ params[:events] = events if events
78
+ params[:stream] = stream if stream
79
+
80
+ # Call API
81
+ result = client.research_get(research_id, **params)
82
+
83
+ # Format output
84
+ formatted = Exa::CLI::Formatters::ResearchFormatter.format_task(
85
+ result,
86
+ output_format,
87
+ show_events: events
88
+ )
89
+ puts formatted
90
+
91
+ rescue Exa::NotFound => e
92
+ warn "Research task not found: #{e.message}"
93
+ exit 1
94
+ rescue Exa::Unauthorized => e
95
+ warn "Authentication failed: #{e.message}"
96
+ warn "Please provide a valid API key via --api-key or EXA_API_KEY environment variable"
97
+ exit 1
98
+ rescue Exa::ClientError => e
99
+ warn "Client error: #{e.message}"
100
+ exit 1
101
+ rescue Exa::ServerError => e
102
+ warn "Server error: #{e.message}"
103
+ exit 1
104
+ rescue Exa::Error => e
105
+ warn "Error: #{e.message}"
106
+ exit 1
107
+ rescue StandardError => e
108
+ warn "Unexpected error: #{e.message}"
109
+ exit 1
110
+ end
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "exa"
6
+
7
+ # Parse command-line arguments
8
+ api_key = nil
9
+ cursor = nil
10
+ limit = 10
11
+ output_format = "json"
12
+
13
+ args = ARGV.dup
14
+ while args.any?
15
+ arg = args.shift
16
+ case arg
17
+ when "--api-key"
18
+ api_key = args.shift
19
+ when "--cursor"
20
+ cursor = args.shift
21
+ when "--limit"
22
+ limit = args.shift.to_i
23
+ when "--output-format"
24
+ output_format = args.shift
25
+ when "--help", "-h"
26
+ puts <<~HELP
27
+ Usage: exa-api research-list [options]
28
+
29
+ List research tasks with cursor-based pagination.
30
+
31
+ Options:
32
+ --api-key KEY Exa API key (or use EXA_API_KEY env var)
33
+ --cursor CURSOR Pagination cursor for next page
34
+ --limit LIMIT Number of results per page (default: 10)
35
+ --output-format FORMAT Output format: json, pretty, or text (default: json)
36
+ --help, -h Show this help message
37
+
38
+ Examples:
39
+ exa-api research-list
40
+ exa-api research-list --limit 20
41
+ exa-api research-list --cursor next_page_cursor
42
+ exa-api research-list --output-format pretty
43
+ HELP
44
+ exit 0
45
+ else
46
+ warn "Unknown option: #{arg}"
47
+ exit 1
48
+ end
49
+ end
50
+
51
+ begin
52
+ # Resolve API key
53
+ api_key = Exa::CLI::Base.resolve_api_key(api_key)
54
+
55
+ # Build client
56
+ client = Exa::CLI::Base.build_client(api_key)
57
+
58
+ # Build parameters
59
+ params = {limit: limit}
60
+ params[:cursor] = cursor if cursor
61
+
62
+ # Call API
63
+ result = client.research_list(**params)
64
+
65
+ # Format output
66
+ formatted = Exa::CLI::Formatters::ResearchFormatter.format_list(result, output_format)
67
+ puts formatted
68
+
69
+ # Show pagination info if there are more results
70
+ if result.has_more && result.next_cursor
71
+ if output_format == "pretty"
72
+ puts "\n" + "=" * 80
73
+ puts "More results available. Use --cursor #{result.next_cursor} to get next page."
74
+ else
75
+ warn "More results available. Use --cursor #{result.next_cursor} to get next page."
76
+ end
77
+ end
78
+
79
+ rescue Exa::Unauthorized => e
80
+ warn "Authentication failed: #{e.message}"
81
+ warn "Please provide a valid API key via --api-key or EXA_API_KEY environment variable"
82
+ exit 1
83
+ rescue Exa::ClientError => e
84
+ warn "Client error: #{e.message}"
85
+ exit 1
86
+ rescue Exa::ServerError => e
87
+ warn "Server error: #{e.message}"
88
+ exit 1
89
+ rescue Exa::Error => e
90
+ warn "Error: #{e.message}"
91
+ exit 1
92
+ rescue StandardError => e
93
+ warn "Unexpected error: #{e.message}"
94
+ exit 1
95
+ end