Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion exe/exa-ai-import-create
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def parse_args(argv)
--entity-type TYPE Entity type (options: #{VALID_ENTITY_TYPES.join(', ')})

Options:
--entity-description TXT Description for custom entity type (required with --entity-type custom)
--csv-identifier N CSV column identifier (0-indexed)
--metadata JSON Custom metadata (supports @file.json)
--quiet Suppress normal output (only show errors)
Expand Down Expand Up @@ -102,6 +103,9 @@ def parse_args(argv)
when "--entity-type"
args[:entity_type] = argv[i + 1]
i += 2
when "--entity-description"
args[:entity_description] = argv[i + 1]
i += 2
when "--csv-identifier"
args[:csv_identifier] = argv[i + 1].to_i
i += 2
Expand Down Expand Up @@ -161,6 +165,17 @@ begin
exit 1
end

# Validate entity-description for custom entity type
if args[:entity_type] == "custom"
unless args[:entity_description]
$stderr.puts "Error: --entity-description is required when --entity-type is 'custom'"
$stderr.puts "Run 'exa-ai import-create --help' for usage information"
exit 1
end
elsif args[:entity_description]
$stderr.puts "Warning: --entity-description is only used with --entity-type custom (ignoring)"
end

# Validate file exists
unless File.exist?(args[:file_path])
$stderr.puts "Error: File not found: #{args[:file_path]}"
Expand All @@ -177,12 +192,15 @@ begin
client = Exa::CLI::Base.build_client(api_key)

# Prepare import parameters
entity = { type: args[:entity_type] }
entity[:description] = args[:entity_description] if args[:entity_description]

import_params = {
file_path: args[:file_path],
count: args[:count],
title: args[:title],
format: args[:format],
entity: { type: args[:entity_type] }
entity: entity
}
import_params[:metadata] = args[:metadata] if args[:metadata]

Expand Down
2 changes: 1 addition & 1 deletion exe/exa-ai-webset-search-create
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ begin
search = client.create_webset_search(webset_id: args[:webset_id], **search_params)

# Format and output result
output = Exa::CLI::Formatters::SearchFormatter.format(search, output_format)
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, output_format)
puts output
$stdout.flush

Expand Down
2 changes: 1 addition & 1 deletion exe/exa-ai-webset-search-get
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ begin
search = client.get_webset_search(webset_id: webset_id, id: search_id)

# Format and output
output = Exa::CLI::Formatters::SearchFormatter.format(search, output_format)
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, output_format)
puts output
$stdout.flush

Expand Down
1 change: 1 addition & 0 deletions lib/exa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
require_relative "exa/cli/polling"
require_relative "exa/cli/error_handler"
require_relative "exa/cli/formatters/search_formatter"
require_relative "exa/cli/formatters/webset_search_formatter"
require_relative "exa/cli/formatters/context_formatter"
require_relative "exa/cli/formatters/contents_formatter"
require_relative "exa/cli/formatters/research_formatter"
Expand Down
57 changes: 57 additions & 0 deletions lib/exa/cli/formatters/webset_search_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module Exa
module CLI
module Formatters
class WebsetSearchFormatter
def self.format(search, format)
case format
when "json"
JSON.pretty_generate(search.to_h)
when "pretty"
format_pretty(search)
when "text"
format_text(search)
when "toon"
Exa::CLI::Base.encode_as_toon(search.to_h)
else
JSON.pretty_generate(search.to_h)
end
end

private

def self.format_pretty(search)
output = []
output << "Search ID: #{search.id}"
output << "Status: #{search.status}"
output << "Query: #{search.query}"
output << "Entity Type: #{search.entity&.[]('type') || 'N/A'}" if search.entity
output << "Count: #{search.count}" if search.count
output << "Behavior: #{search.behavior}"
output << "Recall: #{search.recall}" if search.recall
output << "Created: #{search.created_at}"
output << "Updated: #{search.updated_at}"
output << "Progress: #{search.progress}" if search.progress
output << ""

if search.canceled?
output << "Canceled: #{search.canceled_at}"
output << "Cancel Reason: #{search.canceled_reason}" if search.canceled_reason
end

output.join("\n")
end

def self.format_text(search)
[
"ID: #{search.id}",
"Status: #{search.status}",
"Query: #{search.query}",
"Behavior: #{search.behavior}"
].join("\n")
end
end
end
end
end
189 changes: 189 additions & 0 deletions test/cli/formatters/webset_search_formatter_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# frozen_string_literal: true

require "test_helper"

class Exa::CLI::Formatters::WebsetSearchFormatterTest < Minitest::Test
def test_json_format_returns_json_string
search = create_webset_search
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, "json")

# Verify it's valid JSON
parsed = JSON.parse(output)
assert_equal "ws_search_123", parsed["id"]
assert_equal "running", parsed["status"]
assert_equal "AI startups", parsed["query"]
end

def test_pretty_format_shows_search_metadata
search = create_webset_search
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, "pretty")

# Verify it includes expected metadata
assert_includes output, "Search ID: ws_search_123"
assert_includes output, "Status: running"
assert_includes output, "Query: AI startups"
assert_includes output, "Behavior: override"
assert_includes output, "Created: 2024-01-01T12:00:00Z"
end

def test_pretty_format_excludes_results_property
search = create_webset_search
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, "pretty")

# Verify .results is not accessed
refute_includes output, "results"
end

def test_text_format_shows_key_fields
search = create_webset_search
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, "text")

# Verify it includes key fields
assert_includes output, "ID: ws_search_123"
assert_includes output, "Status: running"
assert_includes output, "Query: AI startups"
assert_includes output, "Behavior: override"
end

def test_default_format_is_json
search = create_webset_search
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, nil)

# Should default to JSON
parsed = JSON.parse(output)
assert_equal "ws_search_123", parsed["id"]
end

def test_toon_format_returns_toon_string
search = create_webset_search
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, "toon")

assert_instance_of String, output
assert_includes output, "ws_search_123"
assert_includes output, "AI startups"

# TOON should be more compact than JSON
json_output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, "json")
assert output.length < json_output.length
end

def test_pretty_format_with_canceled_search
search = create_canceled_webset_search
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, "pretty")

assert_includes output, "Status: canceled"
assert_includes output, "Canceled: 2024-01-01T13:00:00Z"
assert_includes output, "Cancel Reason: User requested cancellation"
end

def test_pretty_format_with_custom_entity
search = create_webset_search_with_entity
output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, "pretty")

assert_includes output, "Entity Type: custom"
end

def test_pretty_format_handles_nil_progress
search = create_webset_search_without_progress
# Ensure progress is nil
assert_nil search.progress

output = Exa::CLI::Formatters::WebsetSearchFormatter.format(search, "pretty")
# Should not fail and should have valid output
assert_includes output, "Query:"
end

private

def create_webset_search
Exa::Resources::WebsetSearch.new(
id: "ws_search_123",
object: "webset.search",
status: "running",
webset_id: "ws_456",
query: "AI startups",
entity: { "type" => "company" },
criteria: nil,
count: 50,
behavior: "override",
exclude: nil,
scope: nil,
progress: 25,
recall: false,
metadata: nil,
canceled_at: nil,
canceled_reason: nil,
created_at: "2024-01-01T12:00:00Z",
updated_at: "2024-01-01T12:30:00Z"
)
end

def create_canceled_webset_search
Exa::Resources::WebsetSearch.new(
id: "ws_search_789",
object: "webset.search",
status: "canceled",
webset_id: "ws_456",
query: "tech companies",
entity: nil,
criteria: nil,
count: nil,
behavior: "override",
exclude: nil,
scope: nil,
progress: 50,
recall: false,
metadata: nil,
canceled_at: "2024-01-01T13:00:00Z",
canceled_reason: "User requested cancellation",
created_at: "2024-01-01T12:00:00Z",
updated_at: "2024-01-01T13:00:00Z"
)
end

def create_webset_search_with_entity
Exa::Resources::WebsetSearch.new(
id: "ws_search_custom",
object: "webset.search",
status: "completed",
webset_id: "ws_456",
query: "vintage cars",
entity: { "type" => "custom", "description" => "vintage cars" },
criteria: nil,
count: 20,
behavior: "append",
exclude: nil,
scope: nil,
progress: 100,
recall: false,
metadata: nil,
canceled_at: nil,
canceled_reason: nil,
created_at: "2024-01-01T12:00:00Z",
updated_at: "2024-01-01T12:45:00Z"
)
end

def create_webset_search_without_progress
Exa::Resources::WebsetSearch.new(
id: "ws_search_no_progress",
object: "webset.search",
status: "created",
webset_id: "ws_456",
query: "test query",
entity: nil,
criteria: nil,
count: nil,
behavior: "override",
exclude: nil,
scope: nil,
progress: nil,
recall: false,
metadata: nil,
canceled_at: nil,
canceled_reason: nil,
created_at: "2024-01-01T12:00:00Z",
updated_at: "2024-01-01T12:00:00Z"
)
end
end
Loading