exa-ai-ruby 1.1.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d63e020a8a8be86a5f10fe3cef3be38eaec07537c1b4d20cfa52ebc6474c483
4
- data.tar.gz: 5bf845c75eced722a8e3dc0e0bbc4ece8d14b6a74046e685c0f8daefea42ff78
3
+ metadata.gz: cbb55f4b95516f779fda2e4f26d34d38778ce94186683001768348b8378e06ac
4
+ data.tar.gz: dc31ed93afbc2a1f7d96db620fe975d70c2aaa7647a11eaa7592ae973e9fe5cb
5
5
  SHA512:
6
- metadata.gz: a6423df0960415ee47438fbca1c72d015a53fb611f0721671a4d0cba2b7b1c8dff17a71a4e448324f2d42157ae05a4ccf385a351f332ccf91cd40b3a749bfce5
7
- data.tar.gz: 353057eb46a716e65d1b984aea014f5d80d006bb613242bf41311c4a22d55a47b1483f090e9df711b58c15e38ca351e6cf85a192e9a92191a59010db42c4a645
6
+ metadata.gz: 8bcb3d9fa20d16ac14435ad04c327ab508afa1461fe0aa0f99528a5eb423fb32246bfdf5db732547c52bab9f89107dc5feae65f28879699948ef9d188570929f
7
+ data.tar.gz: 56019a774cb70ab4a1f73a9586c1e02735d3df867313c6c62d0012b19180f1067d6b0aa8774b5625a71d5b0b0f3ce924e490c86b8d99256444d66bf3da00b4c7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.1] - 2025-10-26
4
+ - Add CLI output formatters: `--format jsonl` emits one JSON object per line, and `--format markdown` prints share-ready bullet lists/tables.
5
+ - Document copy-paste ready CLI and Ruby API examples in the README so users/LLMs can get started instantly.
6
+
3
7
  ## [1.1.0] - 2025-10-26
4
8
  - Add the `exa` CLI entrypoint (installed automatically with the gem) including multi-account credential management and JSON-friendly output helpers.
5
9
  - Introduce a secure YAML config store (`~/.config/exa/config.yml`) and CLI commands for `accounts:list`, `accounts:add`, `accounts:use`, and `accounts:remove`.
data/README.md CHANGED
@@ -125,17 +125,67 @@ Starting with v1.1.0 the gem ships an `exa` executable that mirrors the API surf
125
125
 
126
126
  Every command accepts `--account`, `--api-key`, `--base-url`, `--config`, and `--format`. If omitted they fall back to the config file, environment variables (`EXA_ACCOUNT`, `EXA_API_KEY`, `EXA_BASE_URL`), or defaults.
127
127
 
128
- 3. **Call the API from any shell**
128
+ 3. **Call the API from any shell**
129
129
 
130
- ```
131
- # Run a typed search (pipe `--json` to jq or capture raw data)
132
- $ exa search:run "latest reasoning LLM papers" --num-results 3 --json
130
+ ```
131
+ # Run a typed search (pipe `--json` to jq or capture raw data)
132
+ $ exa search:run "latest reasoning LLM papers" --num-results 3 --json
133
133
 
134
- # Fetch contents for explicit URLs
135
- $ exa search:contents --urls https://exa.ai,https://exa.com --json
136
- ```
134
+ # Fetch contents for explicit URLs
135
+ $ exa search:contents --urls https://exa.ai,https://exa.com --json
136
+
137
+ # Stream results as JSON lines (great for logging/piping)
138
+ $ exa search:run "ai funding" --num-results 2 --format jsonl
139
+
140
+ # Share-ready Markdown lists
141
+ $ exa websets:list --format markdown
142
+ ```
143
+
144
+ Omit `--json` for friendly summaries; include it when scripting so you get the Sorbet structs serialized as plain JSON.
145
+
146
+ Prefer `--format jsonl` for streaming-friendly logs or `--format markdown` when you want ready-to-share bullet lists/tables.
147
+
148
+ ### Copy-paste CLI examples
149
+
150
+ ```bash
151
+ # 1) Configure credentials once (stored at ~/.config/exa/config.yml)
152
+ exa accounts:add prod --api-key $EXA_API_KEY --base-url https://api.exa.ai
153
+
154
+ # 2) Run searches with different outputs
155
+ exa search:run "latest reasoning LLM papers" --num-results 5
156
+ exa search:run "biotech funding" --format jsonl | tee results.jsonl
157
+
158
+ # 3) Inspect resources in Markdown form (perfect for PRs/notes)
159
+ exa websets:list --format markdown
160
+ exa webhooks:list --format markdown
161
+
162
+ # 4) Use a one-off API key without mutating config
163
+ exa search:contents --urls https://exa.ai --api-key $EXA_API_KEY --json
164
+ ```
165
+
166
+ ### Copy-paste API client example
137
167
 
138
- Omit `--json` for friendly summaries; include it when scripting so you get the Sorbet structs serialized as plain JSON.
168
+ ```ruby
169
+ require "exa"
170
+
171
+ client = Exa::Client.new(api_key: ENV.fetch("EXA_API_KEY"))
172
+
173
+ search = client.search.search(
174
+ query: "latest reasoning LLM papers",
175
+ num_results: 5,
176
+ text: true
177
+ )
178
+
179
+ search.results.each do |result|
180
+ puts "#{result.title} - #{result.url}"
181
+ end
182
+
183
+ # Websets + monitors
184
+ websets = client.websets.list(limit: 5)
185
+ websets.data.each_with_index do |webset, idx|
186
+ puts "#{idx + 1}. #{webset.title} (#{webset.id})"
187
+ end
188
+ ```
139
189
 
140
190
  Command families currently available:
141
191
 
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Exa
6
+ module CLI
7
+ module Formatters
8
+ def self.for(name)
9
+ case name&.to_s&.downcase
10
+ when "json"
11
+ JsonFormatter.new
12
+ when "jsonl"
13
+ JsonlFormatter.new
14
+ when "markdown"
15
+ MarkdownFormatter.new
16
+ else
17
+ TableFormatter.new
18
+ end
19
+ end
20
+
21
+ class BaseFormatter
22
+ def render(cli:, payload:, collection:)
23
+ raise NotImplementedError
24
+ end
25
+
26
+ private
27
+
28
+ def serialize(cli, object)
29
+ cli.send(:serializable, object)
30
+ end
31
+ end
32
+
33
+ class JsonFormatter < BaseFormatter
34
+ def render(cli:, payload:, collection:)
35
+ cli.say JSON.pretty_generate(serialize(cli, payload))
36
+ end
37
+ end
38
+
39
+ class JsonlFormatter < BaseFormatter
40
+ def render(cli:, payload:, collection:)
41
+ items = collection && !collection.empty? ? collection : [payload]
42
+ items.each do |item|
43
+ cli.say JSON.generate(serialize(cli, item))
44
+ end
45
+ end
46
+ end
47
+
48
+ class MarkdownFormatter < BaseFormatter
49
+ def render(cli:, payload:, collection:)
50
+ if collection && !collection.empty?
51
+ collection.each do |item|
52
+ cli.say "- #{markdown_line(cli, item)}"
53
+ end
54
+ else
55
+ cli.say "```json"
56
+ cli.say JSON.pretty_generate(serialize(cli, payload))
57
+ cli.say "```"
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def markdown_line(cli, item)
64
+ title = cli.send(:value_from, item, :title) || cli.send(:value_from, item, :name)
65
+ url = cli.send(:value_from, item, :url)
66
+ id = cli.send(:value_from, item, :id)
67
+ primary = title || id || cli.send(:serializable, item)
68
+
69
+ line = primary.is_a?(String) ? primary : primary.to_s
70
+ line = "[#{line}](#{url})" if url
71
+ line += " (#{id})" if id && (title || url)
72
+ line
73
+ end
74
+ end
75
+
76
+ class TableFormatter < BaseFormatter
77
+ def render(cli:, payload:, collection:)
78
+ if collection && !collection.empty?
79
+ collection.each_with_index do |item, index|
80
+ cli.say cli.send(:format_collection_entry, item, index)
81
+ end
82
+ elsif collection
83
+ cli.say "No results."
84
+ else
85
+ cli.say cli.send(:format_single_entry, payload)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
data/lib/exa/cli/root.rb CHANGED
@@ -5,6 +5,7 @@ require "json"
5
5
  require "exa"
6
6
  require_relative "config_store"
7
7
  require_relative "account_resolver"
8
+ require_relative "formatters"
8
9
 
9
10
  module Exa
10
11
  module CLI
@@ -69,7 +70,7 @@ module Exa
69
70
  class_option :api_key, type: :string, desc: "Override the API key for this invocation"
70
71
  class_option :base_url, type: :string, desc: "Override the API base URL"
71
72
  class_option :config, type: :string, desc: "Path to the exa CLI config file"
72
- class_option :format, type: :string, default: "table", desc: "Output format: table, json, or raw"
73
+ class_option :format, type: :string, default: "table", desc: "Output format: table, json, jsonl, or markdown"
73
74
 
74
75
  # Version -----------------------------------------------------------------
75
76
 
@@ -631,24 +632,10 @@ module Exa
631
632
 
632
633
  def render_response(response, json:, collection_accessor: nil)
633
634
  payload = serializable(response)
634
- if json
635
- say JSON.pretty_generate(payload)
636
- return
637
- end
638
-
639
635
  collection = extract_collection(response, payload, collection_accessor)
640
- if collection
641
- if collection.empty?
642
- say "No results."
643
- else
644
- collection.each_with_index do |item, index|
645
- say format_collection_entry(item, index)
646
- end
647
- end
648
- return
649
- end
650
-
651
- say format_single_entry(payload)
636
+ formatter_name = determine_format(json)
637
+ formatter = Exa::CLI::Formatters.for(formatter_name)
638
+ formatter.render(cli: self, payload: payload, collection: collection)
652
639
  end
653
640
 
654
641
  def render_stream(stream, json:)
@@ -800,3 +787,8 @@ module Exa
800
787
  end
801
788
  end
802
789
  end
790
+ def determine_format(json_requested)
791
+ return "json" if json_requested
792
+ value = options[:format]
793
+ value.nil? ? "table" : value.to_s.downcase
794
+ end
data/lib/exa/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Exa
4
- VERSION = "1.1.0"
4
+ VERSION = "1.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exa-ai-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig Rincon de Arellano
@@ -193,6 +193,7 @@ files:
193
193
  - lib/exa/cli.rb
194
194
  - lib/exa/cli/account_resolver.rb
195
195
  - lib/exa/cli/config_store.rb
196
+ - lib/exa/cli/formatters.rb
196
197
  - lib/exa/cli/root.rb
197
198
  - lib/exa/client.rb
198
199
  - lib/exa/errors.rb