inloop-brain 0.0.8 → 0.0.10

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: b41aa8aad41b500dc36f73a6e8ba189bc59fbb075d247b662701b78f8f4d39de
4
- data.tar.gz: 79193e316f4d9f0c61b77181b892323f8f5f6ac03732cbb6b626d849664fde48
3
+ metadata.gz: af66fbbb5672cf09b9b5eb5d4ae4d018adc4dd7f2a5be343dae2c93a74732bac
4
+ data.tar.gz: 21efedeaa7f002463d145091df3772c0839db24cbfc762d797a8344f657b85ec
5
5
  SHA512:
6
- metadata.gz: 48b3b27a5155159192447412051c89184bc74a664357ebc50e32b56a75db472cdd0cde7deecf90025bca3ca075010e52da199e371b30705782f23019e2e34d53
7
- data.tar.gz: ef570edf09d65eaec5f7712a29d8cddae885147863faafc1651d70a24101a33a8a1f2154a7b5b337f734e86f98bd5a27c198cc93ff9bf812f28bf4d06c9323eb
6
+ metadata.gz: 03b35d39d7f88c6d788cace6ba09cd524bb4fed2bb3ca6ca4080d23a2d33877a4da7ba7b106db57d596e8aa7f0f3499a0eda25529beacf1731c4968a4390f824
7
+ data.tar.gz: b198e63d50a928d4e7095598795437aae57071e7bf2003bf7bfb9ec6544286cd50f137f3d41f48401c2c2d61a6b7811edf7b927ab03d072811d88a579b833fbc
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Inloop Brain
2
2
 
3
- A command-line tool for interacting with the Inloop Brain Access Kit API.
3
+ A command-line tool for interacting with the Inloop Brain OpenAI-compatible API.
4
4
 
5
5
  ## Installation
6
6
 
@@ -15,27 +15,37 @@ gem install inloop-brain
15
15
  ## Usage
16
16
 
17
17
  ```
18
- echo "What can you do?" | inloop-brain --key=YOUR_ACCESS_KEY
18
+ echo "What can you do?" | inloop-brain --key=YOUR_API_KEY
19
19
  ```
20
20
 
21
21
  Or
22
22
 
23
23
  ```
24
- echo "What can you do?" | inloop-brain -k YOUR_ACCESS_KEY > output.txt
24
+ echo "What can you do?" | inloop-brain -k YOUR_API_KEY > output.txt
25
25
  ```
26
26
 
27
27
  ### Environment Variables
28
28
 
29
- You can customize the target host and protocol:
29
+ Use an API key generated from the Brain API share link in app.inloop.studio. The API uses the published `llms.txt` context when available.
30
30
 
31
- - `INLOOP_BRAIN_HOST`: Host to connect to (default: localhost:3000)
32
- - `INLOOP_BRAIN_PROTOCOL`: Protocol to use (default: http)
31
+ - `INLOOP_BRAIN_API_KEY`: API key used for Authorization (recommended)
32
+ - `INLOOP_BRAIN_API_URL`: Full API URL (default: https://app.inloop.studio/api/v1/chat/completions)
33
+ - `INLOOP_BRAIN_MODEL`: Model name to send (default: inloop-brain)
34
+ - `INLOOP_BRAIN_HOST`: Host override (default: app.inloop.studio)
35
+ - `INLOOP_BRAIN_PROTOCOL`: Protocol override (default: https)
36
+ - `INLOOP_BRAIN_SSL_STRICT`: Set to `1` to enforce strict TLS verification (disables the default compatibility behavior that ignores missing certificate CRLs)
37
+
38
+ ### Options
39
+
40
+ - `--api-url`: Override API URL
41
+ - `--model`: Model name to send
42
+ - `--system`: System prompt to prepend
33
43
 
34
44
  ## Development
35
45
 
36
46
  ### Dependencies
37
47
 
38
- - bundler (~> 2.0)
48
+ - bundler (>= 2.0, < 5)
39
49
  - rake (~> 13.0)
40
50
  - rspec (~> 3.0)
41
51
 
data/bin/inloop-brain CHANGED
@@ -1,18 +1,31 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # bin/inloop-brain - A command-line tool to interact with the Brain Access Kit API
3
+ # bin/inloop-brain - A command-line tool to interact with the Inloop Brain API (OpenAI-compatible)
4
+
5
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
4
6
 
5
7
  require 'optparse'
6
8
  require 'net/http'
7
9
  require 'uri'
10
+ require 'json'
11
+ require 'inloop_brain/ssl'
8
12
 
9
13
  # Parse command line options
10
14
  options = {}
11
15
  parser = OptionParser.new do |opts|
12
- opts.banner = "This is brain access kit of inloop.studio\n\n Usage: inloop-brain [options]"
13
- opts.on('-k KEY', '--key=KEY', 'Access key for the Brain Access Kit') do |key|
16
+ opts.banner = "This is the Inloop Brain CLI\n\n Usage: inloop-brain [options]"
17
+ opts.on('-k KEY', '--key=KEY', 'API key for the Inloop Brain (OpenAI-compatible)') do |key|
14
18
  options[:key] = key
15
19
  end
20
+ opts.on('-m MODEL', '--model=MODEL', 'Model name (default: inloop-brain)') do |model|
21
+ options[:model] = model
22
+ end
23
+ opts.on('--system=TEXT', 'System prompt to prepend to the conversation') do |text|
24
+ options[:system] = text
25
+ end
26
+ opts.on('--api-url=URL', 'Override API URL (default: https://app.inloop.studio/api/v1/chat/completions)') do |url|
27
+ options[:api_url] = url
28
+ end
16
29
  opts.on('-d', '--debug', 'Enable debug mode') do
17
30
  options[:debug] = true
18
31
  end
@@ -31,8 +44,9 @@ rescue OptionParser::InvalidOption => e
31
44
  end
32
45
 
33
46
  # Validate required parameters
47
+ options[:key] ||= ENV['INLOOP_BRAIN_API_KEY']
34
48
  unless options[:key]
35
- STDERR.puts "Error: Missing required parameter 'key'"
49
+ STDERR.puts "Error: Missing required parameter 'key' (or set INLOOP_BRAIN_API_KEY)"
36
50
  STDERR.puts parser
37
51
  exit 1
38
52
  end
@@ -47,10 +61,13 @@ if input.empty?
47
61
  end
48
62
 
49
63
  # Configure the API request
50
- host = ENV['INLOOP_BRAIN_HOST'] || 'discovery.inloop.studio'
51
- protocol = ENV['INLOOP_BRAIN_PROTOCOL'] || 'https'
52
- endpoint = "/web_tools/brain_access_kit/#{options[:key]}/handle_prompt.txt"
53
- uri = URI.parse("#{protocol}://#{host}#{endpoint}")
64
+ api_url = options[:api_url] || ENV['INLOOP_BRAIN_API_URL']
65
+ if api_url.nil? || api_url.strip.empty?
66
+ host = ENV['INLOOP_BRAIN_HOST'] || 'app.inloop.studio'
67
+ protocol = ENV['INLOOP_BRAIN_PROTOCOL'] || 'https'
68
+ api_url = "#{protocol}://#{host}/api/v1/chat/completions"
69
+ end
70
+ uri = URI.parse(api_url)
54
71
 
55
72
  if options[:debug]
56
73
  puts "Making request to: #{uri}"
@@ -60,18 +77,42 @@ end
60
77
  http = Net::HTTP.new(uri.host, uri.port)
61
78
  http.use_ssl = (uri.scheme == 'https')
62
79
 
80
+ # Some environments enable CRL checking globally for TLS verification but do not
81
+ # provide the CRLs, causing OpenSSL to abort with:
82
+ # "certificate verify failed (unable to get certificate CRL)"
83
+ # We keep certificate verification enabled, but soft-fail only those CRL-missing
84
+ # errors by default. Set INLOOP_BRAIN_SSL_STRICT=1 to disable this compatibility
85
+ # behavior.
86
+ if http.use_ssl?
87
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
88
+ http.verify_hostname = true if http.respond_to?(:verify_hostname=)
89
+
90
+ strict_ssl = ENV.fetch('INLOOP_BRAIN_SSL_STRICT', '').to_s.downcase
91
+ strict_ssl = %w[1 true yes].include?(strict_ssl)
92
+ unless strict_ssl
93
+ http.verify_callback = InloopBrain::SSL.allow_missing_crl_verify_callback(debug: options[:debug])
94
+ end
95
+ end
96
+
63
97
  # Add proper timeout settings
64
98
  http.open_timeout = 10
65
99
  http.read_timeout = 60
66
100
 
67
- request = Net::HTTP::Post.new(uri.path)
101
+ request = Net::HTTP::Post.new(uri)
68
102
  # Set proper headers
69
- request['Content-Type'] = 'application/x-www-form-urlencoded'
70
- request['Accept'] = 'text/plain'
71
- request['User-Agent'] = 'Inloop-Brain-CLI/0.0.1'
103
+ request['Content-Type'] = 'application/json'
104
+ request['Accept'] = 'application/json'
105
+ request['Authorization'] = "Bearer #{options[:key]}"
106
+ request['User-Agent'] = 'Inloop-Brain-CLI/0.0.10'
72
107
 
73
- # Set form data
74
- request.set_form_data('prompt' => input)
108
+ messages = []
109
+ messages << { role: 'system', content: options[:system] } if options[:system].to_s.strip != ''
110
+ messages << { role: 'user', content: input }
111
+ body = {
112
+ model: options[:model] || ENV['INLOOP_BRAIN_MODEL'] || 'inloop-brain',
113
+ messages: messages
114
+ }
115
+ request.body = JSON.dump(body)
75
116
 
76
117
  # Send the request and handle the response
77
118
  begin
@@ -89,20 +130,26 @@ begin
89
130
  response.each_header { |key, value| puts " #{key}: #{value}" }
90
131
  end
91
132
 
133
+ begin
134
+ payload = JSON.parse(response.body.to_s)
135
+ rescue JSON::ParserError
136
+ payload = nil
137
+ end
138
+
92
139
  if response.code.to_i == 200
93
- puts response.body
140
+ content = payload.dig("choices", 0, "message", "content") if payload
141
+ content ||= payload.dig("choices", 0, "text") if payload
142
+ if content
143
+ puts content
144
+ else
145
+ STDERR.puts "Error: Unexpected response format"
146
+ STDERR.puts response.body
147
+ exit 1
148
+ end
94
149
  else
150
+ message = payload.dig("error", "message") if payload
95
151
  STDERR.puts "Error: API returned status code #{response.code}"
96
- STDERR.puts response.body
97
-
98
- # Additional troubleshooting for 422 errors
99
- if response.code.to_i == 422
100
- STDERR.puts "\nThe 422 error typically indicates a validation problem. Possible issues:"
101
- STDERR.puts "1. The endpoint format may have changed"
102
- STDERR.puts "2. The access key format may be incorrect"
103
- STDERR.puts "3. The prompt may be in an invalid format"
104
- STDERR.puts "\nTry running with --debug for more information."
105
- end
152
+ STDERR.puts(message || response.body)
106
153
  exit 1
107
154
  end
108
155
  rescue => e
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module InloopBrain
6
+ module SSL
7
+ # Compatibility verify callback that soft-fails missing CRL errors.
8
+ #
9
+ # Some systems enable CRL checking globally for TLS verification but do not
10
+ # ship or fetch CRLs, causing OpenSSL to fail with:
11
+ # "certificate verify failed (unable to get certificate CRL)"
12
+ #
13
+ # This callback preserves strict verification for all other errors.
14
+ def self.allow_missing_crl_verify_callback(debug: false, io: $stderr)
15
+ proc do |preverify_ok, store_ctx|
16
+ if preverify_ok
17
+ true
18
+ else
19
+ err = store_ctx.error
20
+ if err == OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL ||
21
+ err == OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL_ISSUER
22
+ if debug
23
+ io.puts "Warning: SSL certificate CRL unavailable (#{store_ctx.error_string}); continuing without CRL check"
24
+ end
25
+ store_ctx.error = OpenSSL::X509::V_OK if store_ctx.respond_to?(:error=)
26
+ true
27
+ else
28
+ false
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inloop-brain
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abhishek Parolkar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-02 00:00:00.000000000 Z
11
+ date: 2026-02-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '2.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5'
20
23
  type: :development
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '2.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: rake
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -61,13 +67,10 @@ executables:
61
67
  extensions: []
62
68
  extra_rdoc_files: []
63
69
  files:
64
- - ".github/workflows/push.yml"
65
- - Gemfile
66
70
  - LICENSE.txt
67
71
  - README.md
68
- - Rakefile
69
72
  - bin/inloop-brain
70
- - inloop-brain.gemspec
73
+ - lib/inloop_brain/ssl.rb
71
74
  homepage: https://inloop.studio
72
75
  licenses:
73
76
  - Nonstandard
@@ -1,34 +0,0 @@
1
- name: Publish RubyGem
2
-
3
- on:
4
- workflow_dispatch:
5
- inputs:
6
- release_tag:
7
- description: "Optional git tag to publish"
8
- required: false
9
- default: ""
10
-
11
- jobs:
12
- publish:
13
- runs-on: ubuntu-latest
14
- permissions:
15
- contents: write
16
- id-token: write
17
- steps:
18
- - name: Checkout repository
19
- uses: actions/checkout@v4
20
- with:
21
- fetch-depth: 0
22
-
23
- - name: Checkout tag
24
- if: inputs.release_tag != ''
25
- run: git checkout "${{ inputs.release_tag }}"
26
-
27
- - name: Set up Ruby
28
- uses: ruby/setup-ruby@v1
29
- with:
30
- ruby-version: "3.2"
31
- bundler-cache: true
32
-
33
- - name: Release gem via Trusted Publisher (OIDC)
34
- uses: rubygems/release-gem@v1
data/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gemspec
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
data/inloop-brain.gemspec DELETED
@@ -1,32 +0,0 @@
1
-
2
-
3
- Gem::Specification.new do |spec|
4
- spec.name = "inloop-brain"
5
- spec.version = "0.0.8"
6
- spec.authors = ["Abhishek Parolkar"]
7
- spec.email = ["abhishek@inloop.studio"]
8
-
9
- spec.summary = "Command-line interface for the Inloop Brain Access Kit"
10
- spec.description = "A simple command-line tool that sends prompts to the Inloop Brain Access Kit API and returns responses"
11
- spec.homepage = "https://inloop.studio"
12
- spec.license = "Nonstandard"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
14
-
15
- spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = "https://github.com/inloopstudio-team/inloop-brain"
17
- spec.metadata["changelog_uri"] = "https://github.com/inloopstudio-team/inloop-brain/blob/main/CHANGELOG.md" # Replace with actual changelog URL
18
-
19
- # Specify which files should be added to the gem when it is released.
20
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
- end
24
- spec.bindir = "bin"
25
- spec.executables = ["inloop-brain"]
26
-
27
-
28
- # Dependencies
29
- spec.add_development_dependency "bundler", "~> 2.0"
30
- spec.add_development_dependency "rake", "~> 13.0"
31
- spec.add_development_dependency "rspec", "~> 3.0"
32
- end