moondream-client 0.0.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 +7 -0
- data/.env.example +1 -0
- data/.rubocop.yml +42 -0
- data/.ruby-version +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +68 -0
- data/LICENSE +21 -0
- data/README.md +132 -0
- data/Rakefile +16 -0
- data/lib/moondream-client/caption.rb +82 -0
- data/lib/moondream-client/client.rb +180 -0
- data/lib/moondream-client/detect.rb +71 -0
- data/lib/moondream-client/image.rb +49 -0
- data/lib/moondream-client/point.rb +65 -0
- data/lib/moondream-client/query.rb +51 -0
- data/lib/moondream-client/version.rb +5 -0
- data/lib/moondream-client.rb +73 -0
- data/moondream-client.gemspec +33 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 307f9a7735718231a95a7a68764c0958bd7e1f1183ceefd24bf1b89b82050e64
|
|
4
|
+
data.tar.gz: 21646d85a3be9d0eed1682e0038fb005dc3949de31538804e6a14dcbecf7010e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d2703bc5d2f4db1db9fa20686c8b43ac0acc16b70f137ecea334f4d9a289d0703ac54a27231d2f71e6cebdb03420c53e345c26c4d9bc0f2d19dcc6b7d6851f9c
|
|
7
|
+
data.tar.gz: 03becd543a92f77ad0b75b1e848df1c09dff3c66d3bc966e988cce05e767d44a91b9fec90914bd35e0545b88878da957e1e95a5797f5e09585e0d793362a7704
|
data/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MOONDREAM_ACCESS_TOKEN=
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.3.9
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Style/Documentation:
|
|
7
|
+
Enabled: false
|
|
8
|
+
|
|
9
|
+
Bundler/OrderedGems:
|
|
10
|
+
Enabled: false
|
|
11
|
+
|
|
12
|
+
Style/StringLiterals:
|
|
13
|
+
EnforcedStyle: double_quotes
|
|
14
|
+
|
|
15
|
+
Style/FrozenStringLiteralComment:
|
|
16
|
+
SafeAutoCorrect: true
|
|
17
|
+
EnforcedStyle: always_true
|
|
18
|
+
|
|
19
|
+
Metrics/MethodLength:
|
|
20
|
+
Enabled: false
|
|
21
|
+
|
|
22
|
+
Style/AccessorGrouping:
|
|
23
|
+
Enabled: false
|
|
24
|
+
|
|
25
|
+
Metrics/AbcSize:
|
|
26
|
+
Enabled: false
|
|
27
|
+
|
|
28
|
+
Metrics/CyclomaticComplexity:
|
|
29
|
+
Enabled: false
|
|
30
|
+
|
|
31
|
+
Metrics/ParameterLists:
|
|
32
|
+
Enabled: false
|
|
33
|
+
|
|
34
|
+
Metrics/ClassLength:
|
|
35
|
+
Enabled: false
|
|
36
|
+
|
|
37
|
+
Style/HashSyntax:
|
|
38
|
+
Enabled: false
|
|
39
|
+
|
|
40
|
+
Naming/FileName:
|
|
41
|
+
Exclude:
|
|
42
|
+
- "lib/moondream-client.rb"
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.3.0
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
moondream-client (0.0.1)
|
|
5
|
+
base64
|
|
6
|
+
faraday (>= 1)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
ast (2.4.3)
|
|
12
|
+
base64 (0.2.0)
|
|
13
|
+
dotenv (3.1.8)
|
|
14
|
+
faraday (2.14.0)
|
|
15
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
16
|
+
json
|
|
17
|
+
logger
|
|
18
|
+
faraday-net_http (3.4.1)
|
|
19
|
+
net-http (>= 0.5.0)
|
|
20
|
+
json (2.15.1)
|
|
21
|
+
language_server-protocol (3.17.0.5)
|
|
22
|
+
lint_roller (1.1.0)
|
|
23
|
+
logger (1.7.0)
|
|
24
|
+
minitest (5.25.5)
|
|
25
|
+
net-http (0.6.0)
|
|
26
|
+
uri
|
|
27
|
+
parallel (1.27.0)
|
|
28
|
+
parser (3.3.9.0)
|
|
29
|
+
ast (~> 2.4.1)
|
|
30
|
+
racc
|
|
31
|
+
prism (1.5.1)
|
|
32
|
+
racc (1.8.1)
|
|
33
|
+
rainbow (3.1.1)
|
|
34
|
+
rake (13.3.0)
|
|
35
|
+
regexp_parser (2.11.3)
|
|
36
|
+
rubocop (1.81.1)
|
|
37
|
+
json (~> 2.3)
|
|
38
|
+
language_server-protocol (~> 3.17.0.2)
|
|
39
|
+
lint_roller (~> 1.1.0)
|
|
40
|
+
parallel (~> 1.10)
|
|
41
|
+
parser (>= 3.3.0.2)
|
|
42
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
43
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
44
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
45
|
+
ruby-progressbar (~> 1.7)
|
|
46
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
47
|
+
rubocop-ast (1.47.1)
|
|
48
|
+
parser (>= 3.3.7.2)
|
|
49
|
+
prism (~> 1.4)
|
|
50
|
+
ruby-progressbar (1.13.0)
|
|
51
|
+
unicode-display_width (3.2.0)
|
|
52
|
+
unicode-emoji (~> 4.1)
|
|
53
|
+
unicode-emoji (4.1.0)
|
|
54
|
+
uri (1.0.4)
|
|
55
|
+
|
|
56
|
+
PLATFORMS
|
|
57
|
+
arm64-darwin-24
|
|
58
|
+
ruby
|
|
59
|
+
|
|
60
|
+
DEPENDENCIES
|
|
61
|
+
dotenv
|
|
62
|
+
minitest (~> 5.0)
|
|
63
|
+
moondream-client!
|
|
64
|
+
rake (~> 13.0)
|
|
65
|
+
rubocop (~> 1.21)
|
|
66
|
+
|
|
67
|
+
BUNDLED WITH
|
|
68
|
+
2.5.7
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 851 Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# MoondreamClient
|
|
2
|
+
|
|
3
|
+
Ruby client for the Moondream API, providing typed classes for Caption, Query, Detect, and Point, plus streaming captions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the gem and add to the application"s Gemfile by executing:
|
|
8
|
+
|
|
9
|
+
$ bundle add moondream-client
|
|
10
|
+
|
|
11
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
12
|
+
|
|
13
|
+
$ gem install moondream-client
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Configuration
|
|
18
|
+
|
|
19
|
+
Configure the client once at boot (e.g., in Rails an initializer) using `MoondreamClient.configure`.
|
|
20
|
+
|
|
21
|
+
Get an access token from the Moondream Cloud: https://moondream.ai/c/cloud/api-keys.
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
MoondreamClient.configure do |config|
|
|
25
|
+
config.access_token = ENV["MOONDREAM_ACCESS_TOKEN"]
|
|
26
|
+
config.uri_base = "https://api.moondream.ai/v1" # Optional (default: https://api.moondream.ai/v1)
|
|
27
|
+
config.request_timeout = 120 # Optional (default: 120)
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Caption
|
|
32
|
+
|
|
33
|
+
Create captions via the `/caption` endpoint. If you pass an `http(s)` image URL, the client will download it and convert it to a base64 data URL automatically.
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
caption = MoondreamClient::Caption.create!(
|
|
37
|
+
image_url: "data:image/jpeg;base64,..." # or https URL,
|
|
38
|
+
length: "short" # or "normal",
|
|
39
|
+
stream: false
|
|
40
|
+
)
|
|
41
|
+
caption.caption # => String caption text
|
|
42
|
+
caption.request_id # => String request id
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### Streaming captions
|
|
46
|
+
|
|
47
|
+
You can stream caption chunks and get a final `Caption` object at the end:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
final = MoondreamClient::Caption.stream!(
|
|
51
|
+
image_url: "data:image/jpeg;base64,...", # or https URL
|
|
52
|
+
length: "short"
|
|
53
|
+
) do |chunk|
|
|
54
|
+
print chunk # chunk is a String
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
final.caption # => String final caption
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Query
|
|
61
|
+
|
|
62
|
+
Ask questions about an image via `/query`. `http(s)` URLs are automatically converted to base64 data URLs.
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
query = MoondreamClient::Query.create!(
|
|
66
|
+
image_url: "data:image/jpeg;base64,...",
|
|
67
|
+
question: "What color is the car?"
|
|
68
|
+
)
|
|
69
|
+
query.answer # => String answer text
|
|
70
|
+
query.request_id # => String request id
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Detect
|
|
74
|
+
|
|
75
|
+
Detect objects and return bounding boxes via `/detect`. `http(s)` URLs are automatically converted to base64 data URLs.
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
detect = MoondreamClient::Detect.create!(
|
|
79
|
+
image_url: "data:image/jpeg;base64,...",
|
|
80
|
+
object: "person"
|
|
81
|
+
)
|
|
82
|
+
detect.objects # => [#<BoundingBox x_min y_min x_max y_max>]
|
|
83
|
+
detect.request_id # => String request id
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Point
|
|
87
|
+
|
|
88
|
+
Locate center points for objects via `/point`. `http(s)` URLs are automatically converted to base64 data URLs.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
point = MoondreamClient::Point.create!(
|
|
92
|
+
image_url: "data:image/jpeg;base64,...",
|
|
93
|
+
object: "face"
|
|
94
|
+
)
|
|
95
|
+
point.points # => [#<Coordinate x y>]
|
|
96
|
+
point.request_id # => String request id
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Error handling
|
|
100
|
+
|
|
101
|
+
HTTP and API errors raise typed exceptions:
|
|
102
|
+
|
|
103
|
+
- `MoondreamClient::UnauthorizedError` (401)
|
|
104
|
+
- `MoondreamClient::ForbiddenError` (403)
|
|
105
|
+
- `MoondreamClient::NotFoundError` (404)
|
|
106
|
+
- `MoondreamClient::ServerError` (other non-success)
|
|
107
|
+
|
|
108
|
+
Rescue them as needed:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
begin
|
|
112
|
+
MoondreamClient::Caption.create!(
|
|
113
|
+
image_url: "data:image/jpeg;base64,...",
|
|
114
|
+
length: "short"
|
|
115
|
+
)
|
|
116
|
+
rescue MoondreamClient::UnauthorizedError
|
|
117
|
+
# handle invalid/missing token
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
124
|
+
|
|
125
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to rubygems.org.
|
|
126
|
+
|
|
127
|
+
For local development, copy the example environment file and set your API token so `bin/console` can load it automatically:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
cp .env.example .env
|
|
131
|
+
echo 'MOONDREAM_ACCESS_TOKEN=your_api_token_here' >> .env
|
|
132
|
+
```
|
data/Rakefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.libs << "lib"
|
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "rubocop/rake_task"
|
|
13
|
+
|
|
14
|
+
RuboCop::RakeTask.new
|
|
15
|
+
|
|
16
|
+
task default: %i[test rubocop]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MoondreamClient
|
|
4
|
+
class Caption
|
|
5
|
+
# @return [String] The server-generated request identifier.
|
|
6
|
+
attr_reader :request_id
|
|
7
|
+
|
|
8
|
+
# @return [String] The generated caption text.
|
|
9
|
+
attr_reader :caption
|
|
10
|
+
|
|
11
|
+
# Initialize a new Caption result object.
|
|
12
|
+
#
|
|
13
|
+
# @param attributes [Hash] Raw attributes from the /caption endpoint response.
|
|
14
|
+
# @param client [MoondreamClient::Client]
|
|
15
|
+
def initialize(attributes, client: MoondreamClient.client)
|
|
16
|
+
@client = client
|
|
17
|
+
reset_attributes(attributes)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
# Create a caption for an image.
|
|
22
|
+
# Corresponds to POST /caption
|
|
23
|
+
#
|
|
24
|
+
# @param image_url [String] A URL or data URL for the image.
|
|
25
|
+
# @param length [String] "short" or "normal" (default).
|
|
26
|
+
# @param stream [Boolean] Whether to stream the response (default: false).
|
|
27
|
+
# @param client [MoondreamClient::Client]
|
|
28
|
+
#
|
|
29
|
+
# @return [MoondreamClient::Caption]
|
|
30
|
+
def create!(image_url:, length: "normal", stream: false, client: MoondreamClient.client)
|
|
31
|
+
image_data_url = MoondreamClient::Image.to_data_url(image_url)
|
|
32
|
+
payload = {
|
|
33
|
+
image_url: image_data_url,
|
|
34
|
+
length: length,
|
|
35
|
+
stream: stream
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
attributes = client.post("/caption", payload)
|
|
39
|
+
new(attributes, client: client)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Stream caption chunks and return the final Caption instance.
|
|
43
|
+
#
|
|
44
|
+
# @param image_url [String]
|
|
45
|
+
# @param length [String]
|
|
46
|
+
# @param client [MoondreamClient::Client]
|
|
47
|
+
# @yield [chunk] yields each text chunk String as it arrives
|
|
48
|
+
# @return [MoondreamClient::Caption]
|
|
49
|
+
def stream!(image_url:, length: "normal", client: MoondreamClient.client, &block)
|
|
50
|
+
image_data_url = MoondreamClient::Image.to_data_url(image_url)
|
|
51
|
+
payload = {
|
|
52
|
+
image_url: image_data_url,
|
|
53
|
+
length: length,
|
|
54
|
+
stream: true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
caption = nil
|
|
58
|
+
|
|
59
|
+
client.post_stream("/caption", payload) do |data|
|
|
60
|
+
if (chunk = data["chunk"]) && !chunk.to_s.empty?
|
|
61
|
+
block&.call(chunk)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
caption = data["caption"] if data["caption"]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Build the final Caption from aggregated stream content.
|
|
68
|
+
new({ "caption" => caption }, client: client)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# Normalize attributes from the /caption response.
|
|
75
|
+
# @param attributes [Hash]
|
|
76
|
+
# @return [void]
|
|
77
|
+
def reset_attributes(attributes)
|
|
78
|
+
@request_id = attributes["request_id"]
|
|
79
|
+
@caption = attributes["caption"]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MoondreamClient
|
|
4
|
+
class Client
|
|
5
|
+
# The configuration for the client.
|
|
6
|
+
#
|
|
7
|
+
# @return [MoondreamClient::Configuration]
|
|
8
|
+
attr_accessor :configuration
|
|
9
|
+
|
|
10
|
+
# Initialize the client.
|
|
11
|
+
#
|
|
12
|
+
# @param configuration [MoondreamClient::Configuration] The configuration for the client.
|
|
13
|
+
#
|
|
14
|
+
# @return [MoondreamClient::Client]
|
|
15
|
+
def initialize(configuration = MoondreamClient.configuration)
|
|
16
|
+
@configuration = configuration
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Make a POST request to the API.
|
|
20
|
+
#
|
|
21
|
+
# @param path [String] The path to the API endpoint.
|
|
22
|
+
# @param payload [Hash] The payload to send to the API.
|
|
23
|
+
# @param headers [Hash] The headers to send to the API.
|
|
24
|
+
#
|
|
25
|
+
# @return [Hash] The response from the API.
|
|
26
|
+
def post(path, payload, headers: {})
|
|
27
|
+
response = connection.post(build_url(path)) do |request|
|
|
28
|
+
request.headers["X-Moondream-Auth"] = @configuration.access_token if @configuration.access_token
|
|
29
|
+
request.headers["Content-Type"] = "application/json"
|
|
30
|
+
request.headers["Accept"] = "application/json"
|
|
31
|
+
request.headers.merge!(headers)
|
|
32
|
+
request.body = payload.compact.to_json
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
handle_error(response) unless response.success?
|
|
36
|
+
|
|
37
|
+
JSON.parse(response.body)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Make a GET request to the API.
|
|
41
|
+
#
|
|
42
|
+
# @param path [String] The path to the API endpoint.
|
|
43
|
+
#
|
|
44
|
+
# @return [Hash] The response from the API.
|
|
45
|
+
def get(path)
|
|
46
|
+
response = connection.get(build_url(path)) do |request|
|
|
47
|
+
request.headers["X-Moondream-Auth"] = @configuration.access_token if @configuration.access_token
|
|
48
|
+
request.headers["Content-Type"] = "application/json"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
handle_error(response) unless response.success?
|
|
52
|
+
|
|
53
|
+
JSON.parse(response.body)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Make a streaming POST request to the API using text/event-stream.
|
|
57
|
+
# Parses SSE lines and yields decoded event data Hashes to the provided block.
|
|
58
|
+
#
|
|
59
|
+
# @param path [String]
|
|
60
|
+
# @param payload [Hash]
|
|
61
|
+
# @param headers [Hash]
|
|
62
|
+
# @yield [data] yields parsed JSON from each SSE event's data field
|
|
63
|
+
# @return [void]
|
|
64
|
+
def post_stream(path, payload = {}, headers: {}, &block)
|
|
65
|
+
decoder = SSEDecoder.new
|
|
66
|
+
buffer = ""
|
|
67
|
+
|
|
68
|
+
connection.post(build_url(path)) do |request|
|
|
69
|
+
request.headers["X-Moondream-Auth"] = @configuration.access_token if @configuration.access_token
|
|
70
|
+
request.headers["Accept"] = "text/event-stream"
|
|
71
|
+
request.headers["Cache-Control"] = "no-store"
|
|
72
|
+
request.headers["Content-Type"] = "application/json"
|
|
73
|
+
request.headers.merge!(headers)
|
|
74
|
+
request.body = payload.compact.to_json
|
|
75
|
+
request.options.on_data = lambda { |chunk, _total_bytes, _env|
|
|
76
|
+
# Normalize and split into lines, preserving last partial line in buffer
|
|
77
|
+
buffer = (buffer + chunk.to_s).gsub(/\r\n?/, "\n")
|
|
78
|
+
lines = buffer.split("\n", -1)
|
|
79
|
+
buffer = lines.pop || ""
|
|
80
|
+
lines.each do |line|
|
|
81
|
+
event = decoder.decode(line)
|
|
82
|
+
block&.call(event["data"]) if event && event["data"]
|
|
83
|
+
end
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Handle errors from the API.
|
|
89
|
+
#
|
|
90
|
+
# @param response [Faraday::Response] The response from the API.
|
|
91
|
+
#
|
|
92
|
+
# @return [void]
|
|
93
|
+
def handle_error(response)
|
|
94
|
+
case response.status
|
|
95
|
+
when 401
|
|
96
|
+
raise UnauthorizedError, response.body
|
|
97
|
+
when 403
|
|
98
|
+
raise ForbiddenError, response.body
|
|
99
|
+
when 404
|
|
100
|
+
raise NotFoundError, response.body
|
|
101
|
+
else
|
|
102
|
+
raise ServerError, response.body
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# Minimal SSE decoder for parsing standard server-sent event lines.
|
|
109
|
+
class SSEDecoder
|
|
110
|
+
def initialize
|
|
111
|
+
@event = ""
|
|
112
|
+
@data = ""
|
|
113
|
+
@id = nil
|
|
114
|
+
@retry = nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @param line [String]
|
|
118
|
+
# @return [Hash, nil]
|
|
119
|
+
def decode(line)
|
|
120
|
+
return flush_event if line.empty?
|
|
121
|
+
return if line.start_with?(":")
|
|
122
|
+
|
|
123
|
+
field, _, value = line.partition(":")
|
|
124
|
+
value = value.lstrip
|
|
125
|
+
|
|
126
|
+
case field
|
|
127
|
+
when "event"
|
|
128
|
+
@event = value
|
|
129
|
+
when "data"
|
|
130
|
+
@data += "#{value}\n"
|
|
131
|
+
when "id"
|
|
132
|
+
@id = value
|
|
133
|
+
when "retry"
|
|
134
|
+
@retry = value.to_i
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
nil
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def flush_event
|
|
143
|
+
return if @data.empty?
|
|
144
|
+
|
|
145
|
+
data = @data.chomp
|
|
146
|
+
parsed = JSON.parse(data)
|
|
147
|
+
|
|
148
|
+
event = { "data" => parsed }
|
|
149
|
+
event["event"] = @event unless @event.empty?
|
|
150
|
+
event["id"] = @id if @id
|
|
151
|
+
event["retry"] = @retry if @retry
|
|
152
|
+
|
|
153
|
+
@event = ""
|
|
154
|
+
@data = ""
|
|
155
|
+
@id = nil
|
|
156
|
+
@retry = nil
|
|
157
|
+
|
|
158
|
+
event
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Build the URL for the API.
|
|
163
|
+
#
|
|
164
|
+
# @param path [String] The path to the API endpoint.
|
|
165
|
+
def build_url(path)
|
|
166
|
+
"#{@configuration.uri_base}#{path}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Create a connection to the API.
|
|
170
|
+
#
|
|
171
|
+
# @return [Faraday::Connection]
|
|
172
|
+
def connection
|
|
173
|
+
Faraday.new do |faraday|
|
|
174
|
+
faraday.request :url_encoded
|
|
175
|
+
faraday.options.timeout = @configuration.request_timeout
|
|
176
|
+
faraday.options.open_timeout = @configuration.request_timeout
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MoondreamClient
|
|
4
|
+
class Detect
|
|
5
|
+
class BoundingBox
|
|
6
|
+
# @return [Float] Normalized minimum x coordinate (0.0..1.0)
|
|
7
|
+
attr_reader :x_min
|
|
8
|
+
# @return [Float] Normalized minimum y coordinate (0.0..1.0)
|
|
9
|
+
attr_reader :y_min
|
|
10
|
+
# @return [Float] Normalized maximum x coordinate (0.0..1.0)
|
|
11
|
+
attr_reader :x_max
|
|
12
|
+
# @return [Float] Normalized maximum y coordinate (0.0..1.0)
|
|
13
|
+
attr_reader :y_max
|
|
14
|
+
|
|
15
|
+
def initialize(x_min:, y_min:, x_max:, y_max:)
|
|
16
|
+
@x_min = x_min
|
|
17
|
+
@y_min = y_min
|
|
18
|
+
@x_max = x_max
|
|
19
|
+
@y_max = y_max
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [String] The server-generated request identifier.
|
|
24
|
+
attr_reader :request_id
|
|
25
|
+
|
|
26
|
+
# @return [Array<BoundingBox>] The list of detected object bounding boxes.
|
|
27
|
+
attr_reader :objects
|
|
28
|
+
|
|
29
|
+
# Initialize a new Detect result object.
|
|
30
|
+
#
|
|
31
|
+
# @param attributes [Hash] Raw attributes from the /detect endpoint response.
|
|
32
|
+
# @param client [MoondreamClient::Client]
|
|
33
|
+
def initialize(attributes, client: MoondreamClient.client)
|
|
34
|
+
@client = client
|
|
35
|
+
reset_attributes(attributes)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# Detect objects described by `object` within an image.
|
|
40
|
+
# Corresponds to POST /detect
|
|
41
|
+
#
|
|
42
|
+
# @param image_url [String]
|
|
43
|
+
# @param object [String] Object description, e.g. "person".
|
|
44
|
+
# @param client [MoondreamClient::Client]
|
|
45
|
+
#
|
|
46
|
+
# @return [MoondreamClient::Detect]
|
|
47
|
+
def create!(image_url:, object:, client: MoondreamClient.client)
|
|
48
|
+
image_data_url = MoondreamClient::Image.to_data_url(image_url)
|
|
49
|
+
payload = {
|
|
50
|
+
image_url: image_data_url,
|
|
51
|
+
object: object
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
attributes = client.post("/detect", payload)
|
|
55
|
+
new(attributes, client: client)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# Normalize attributes from the /detect response.
|
|
62
|
+
# @param attributes [Hash]
|
|
63
|
+
# @return [void]
|
|
64
|
+
def reset_attributes(attributes)
|
|
65
|
+
@request_id = attributes["request_id"]
|
|
66
|
+
@objects = Array(attributes["objects"]).map do |o|
|
|
67
|
+
BoundingBox.new(x_min: o["x_min"], y_min: o["y_min"], x_max: o["x_max"], y_max: o["y_max"])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module MoondreamClient
|
|
6
|
+
module Image
|
|
7
|
+
class << self
|
|
8
|
+
# Convert an input reference into a data URL.
|
|
9
|
+
# If it is already a data URL, return as-is.
|
|
10
|
+
# If it is an HTTP(S) URL, download and convert to base64 data URL.
|
|
11
|
+
# Otherwise, return as-is.
|
|
12
|
+
#
|
|
13
|
+
# @param reference [String]
|
|
14
|
+
# @return [String]
|
|
15
|
+
def to_data_url(reference)
|
|
16
|
+
return reference if data_url?(reference)
|
|
17
|
+
|
|
18
|
+
return http_to_data_url(reference) if http_url?(reference)
|
|
19
|
+
|
|
20
|
+
reference
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def data_url?(value)
|
|
26
|
+
value.is_a?(String) && value.start_with?("data:")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def http_url?(value)
|
|
30
|
+
value.is_a?(String) && (value.start_with?("http://") || value.start_with?("https://"))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def http_to_data_url(url)
|
|
34
|
+
connection = Faraday.new do |faraday|
|
|
35
|
+
faraday.request :url_encoded
|
|
36
|
+
faraday.options.timeout = MoondreamClient.configuration.request_timeout
|
|
37
|
+
faraday.options.open_timeout = MoondreamClient.configuration.request_timeout
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
response = connection.get(url)
|
|
41
|
+
raise MoondreamClient::ServerError, "Failed to download image: #{response.status}" unless response.success?
|
|
42
|
+
|
|
43
|
+
content_type = response.headers["content-type"] || "image/jpeg"
|
|
44
|
+
base64 = Base64.strict_encode64(response.body)
|
|
45
|
+
"data:#{content_type};base64,#{base64}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MoondreamClient
|
|
4
|
+
class Point
|
|
5
|
+
class Coordinate
|
|
6
|
+
# @return [Float] Normalized x coordinate (0.0..1.0)
|
|
7
|
+
attr_reader :x
|
|
8
|
+
# @return [Float] Normalized y coordinate (0.0..1.0)
|
|
9
|
+
attr_reader :y
|
|
10
|
+
|
|
11
|
+
def initialize(x:, y:) # rubocop:disable Naming/MethodParameterName
|
|
12
|
+
@x = x
|
|
13
|
+
@y = y
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [String] The server-generated request identifier.
|
|
18
|
+
attr_reader :request_id
|
|
19
|
+
|
|
20
|
+
# @return [Array<Coordinate>] The list of point coordinates.
|
|
21
|
+
attr_reader :points
|
|
22
|
+
|
|
23
|
+
# Initialize a new Point result object.
|
|
24
|
+
#
|
|
25
|
+
# @param attributes [Hash] Raw attributes from the /point endpoint response.
|
|
26
|
+
# @param client [MoondreamClient::Client]
|
|
27
|
+
def initialize(attributes, client: MoondreamClient.client)
|
|
28
|
+
@client = client
|
|
29
|
+
reset_attributes(attributes)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
# Locate the center points for objects described by `object` in an image.
|
|
34
|
+
# Corresponds to POST /point
|
|
35
|
+
#
|
|
36
|
+
# @param image_url [String]
|
|
37
|
+
# @param object [String] Object description, e.g. "face".
|
|
38
|
+
# @param client [MoondreamClient::Client]
|
|
39
|
+
#
|
|
40
|
+
# @return [MoondreamClient::Point]
|
|
41
|
+
def create!(image_url:, object:, client: MoondreamClient.client)
|
|
42
|
+
image_data_url = MoondreamClient::Image.to_data_url(image_url)
|
|
43
|
+
payload = {
|
|
44
|
+
image_url: image_data_url,
|
|
45
|
+
object: object
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
attributes = client.post("/point", payload)
|
|
49
|
+
new(attributes, client: client)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# Normalize attributes from the /point response.
|
|
56
|
+
# @param attributes [Hash]
|
|
57
|
+
# @return [void]
|
|
58
|
+
def reset_attributes(attributes)
|
|
59
|
+
@request_id = attributes["request_id"]
|
|
60
|
+
@points = Array(attributes["points"]).map do |p|
|
|
61
|
+
Coordinate.new(x: p["x"], y: p["y"])
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MoondreamClient
|
|
4
|
+
class Query
|
|
5
|
+
# @return [String] The server-generated request identifier.
|
|
6
|
+
attr_reader :request_id
|
|
7
|
+
|
|
8
|
+
# @return [String] The model's answer text.
|
|
9
|
+
attr_reader :answer
|
|
10
|
+
|
|
11
|
+
# Initialize a new Query result object.
|
|
12
|
+
#
|
|
13
|
+
# @param attributes [Hash] Raw attributes from the /query endpoint response.
|
|
14
|
+
# @param client [MoondreamClient::Client]
|
|
15
|
+
def initialize(attributes, client: MoondreamClient.client)
|
|
16
|
+
@client = client
|
|
17
|
+
reset_attributes(attributes)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
# Ask a question about an image.
|
|
22
|
+
# Corresponds to POST /query
|
|
23
|
+
#
|
|
24
|
+
# @param image_url [String]
|
|
25
|
+
# @param question [String]
|
|
26
|
+
# @param client [MoondreamClient::Client]
|
|
27
|
+
#
|
|
28
|
+
# @return [MoondreamClient::Query]
|
|
29
|
+
def create!(image_url:, question:, client: MoondreamClient.client)
|
|
30
|
+
image_data_url = MoondreamClient::Image.to_data_url(image_url)
|
|
31
|
+
payload = {
|
|
32
|
+
image_url: image_data_url,
|
|
33
|
+
question: question
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
attributes = client.post("/query", payload)
|
|
37
|
+
new(attributes, client: client)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# Normalize attributes from the /query response.
|
|
44
|
+
# @param attributes [Hash]
|
|
45
|
+
# @return [void]
|
|
46
|
+
def reset_attributes(attributes)
|
|
47
|
+
@request_id = attributes["request_id"]
|
|
48
|
+
@answer = attributes["answer"]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
require_relative "moondream-client/version"
|
|
7
|
+
require_relative "moondream-client/client"
|
|
8
|
+
require_relative "moondream-client/image"
|
|
9
|
+
require_relative "moondream-client/caption"
|
|
10
|
+
require_relative "moondream-client/detect"
|
|
11
|
+
require_relative "moondream-client/point"
|
|
12
|
+
require_relative "moondream-client/query"
|
|
13
|
+
|
|
14
|
+
module MoondreamClient
|
|
15
|
+
class Error < StandardError; end
|
|
16
|
+
class UnauthorizedError < Error; end
|
|
17
|
+
class NotFoundError < Error; end
|
|
18
|
+
class ServerError < Error; end
|
|
19
|
+
class ConfigurationError < Error; end
|
|
20
|
+
class ForbiddenError < Error; end
|
|
21
|
+
|
|
22
|
+
class Configuration
|
|
23
|
+
DEFAULT_URI_BASE = "https://api.moondream.ai/v1"
|
|
24
|
+
DEFAULT_REQUEST_TIMEOUT = 120
|
|
25
|
+
|
|
26
|
+
# The access token for the API.
|
|
27
|
+
#
|
|
28
|
+
# @return [String]
|
|
29
|
+
attr_accessor :access_token
|
|
30
|
+
|
|
31
|
+
# The base URI for the API.
|
|
32
|
+
#
|
|
33
|
+
# @return [String]
|
|
34
|
+
attr_accessor :uri_base
|
|
35
|
+
|
|
36
|
+
# The request timeout in seconds.
|
|
37
|
+
#
|
|
38
|
+
# @return [Integer]
|
|
39
|
+
attr_accessor :request_timeout
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
@access_token = ENV.fetch("MOONDREAM_ACCESS_TOKEN", nil)
|
|
43
|
+
@uri_base = DEFAULT_URI_BASE
|
|
44
|
+
@request_timeout = DEFAULT_REQUEST_TIMEOUT
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
# The configuration for the client.
|
|
50
|
+
#
|
|
51
|
+
# @return [MoondreamClient::Configuration]
|
|
52
|
+
def configuration
|
|
53
|
+
@configuration ||= Configuration.new
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Allows replacing the configuration object.
|
|
57
|
+
attr_writer :configuration
|
|
58
|
+
|
|
59
|
+
# Configure the client.
|
|
60
|
+
#
|
|
61
|
+
# @yield [MoondreamClient::Configuration] The configuration for the client.
|
|
62
|
+
def configure
|
|
63
|
+
yield(configuration)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The client for the API.
|
|
67
|
+
#
|
|
68
|
+
# @return [MoondreamClient::Client]
|
|
69
|
+
def client
|
|
70
|
+
@client ||= Client.new(configuration)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/moondream-client/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "moondream-client"
|
|
7
|
+
spec.version = MoondreamClient::VERSION
|
|
8
|
+
spec.authors = ["Dylan Player"]
|
|
9
|
+
spec.email = ["dylan@851.sh"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Ruby client for MoonDream API."
|
|
12
|
+
spec.homepage = "https://github.com/851-labs/moondream-client"
|
|
13
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
14
|
+
|
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
18
|
+
spec.license = "MIT"
|
|
19
|
+
|
|
20
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
21
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
22
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
spec.bindir = "exe"
|
|
26
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
27
|
+
spec.require_paths = ["lib"]
|
|
28
|
+
|
|
29
|
+
spec.add_dependency("faraday", ">= 1")
|
|
30
|
+
# Base64 becomes a non-default gem in Ruby >= 3.4
|
|
31
|
+
spec.add_dependency("base64", ">= 0")
|
|
32
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
33
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: moondream-client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dylan Player
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-10-07 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: base64
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
description:
|
|
42
|
+
email:
|
|
43
|
+
- dylan@851.sh
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- ".env.example"
|
|
49
|
+
- ".rubocop.yml"
|
|
50
|
+
- ".ruby-version"
|
|
51
|
+
- Gemfile
|
|
52
|
+
- Gemfile.lock
|
|
53
|
+
- LICENSE
|
|
54
|
+
- README.md
|
|
55
|
+
- Rakefile
|
|
56
|
+
- lib/moondream-client.rb
|
|
57
|
+
- lib/moondream-client/caption.rb
|
|
58
|
+
- lib/moondream-client/client.rb
|
|
59
|
+
- lib/moondream-client/detect.rb
|
|
60
|
+
- lib/moondream-client/image.rb
|
|
61
|
+
- lib/moondream-client/point.rb
|
|
62
|
+
- lib/moondream-client/query.rb
|
|
63
|
+
- lib/moondream-client/version.rb
|
|
64
|
+
- moondream-client.gemspec
|
|
65
|
+
homepage: https://github.com/851-labs/moondream-client
|
|
66
|
+
licenses:
|
|
67
|
+
- MIT
|
|
68
|
+
metadata:
|
|
69
|
+
allowed_push_host: https://rubygems.org
|
|
70
|
+
homepage_uri: https://github.com/851-labs/moondream-client
|
|
71
|
+
source_code_uri: https://github.com/851-labs/moondream-client
|
|
72
|
+
rubygems_mfa_required: 'true'
|
|
73
|
+
post_install_message:
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 3.3.0
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 3.5.3
|
|
89
|
+
signing_key:
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: Ruby client for MoonDream API.
|
|
92
|
+
test_files: []
|