relay.app 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/README.md +4 -4
- data/app/concerns/attachment.rb +2 -0
- data/app/concerns/context.rb +5 -12
- data/app/hooks/require_user.rb +1 -1
- data/app/init/database.rb +1 -1
- data/app/init/router.rb +0 -2
- data/app/init.rb +17 -7
- data/app/models/context.rb +13 -4
- data/app/pages/base.rb +0 -1
- data/app/routes/base.rb +0 -1
- data/app/routes/mcp/base.rb +1 -1
- data/app/routes/websocket/connection.rb +3 -3
- data/app/tools/juke_box.rb +1 -1
- data/app/views/fragments/_input.erb +3 -0
- data/app/views/fragments/_providers.erb +1 -2
- data/bin/relay +9 -17
- data/config.ru +1 -1
- data/lib/relay/version.rb +1 -1
- data/lib/relay.rb +26 -13
- data/libexec/relay/bootstrap +2 -1
- data/libexec/relay/configure +51 -15
- data/libexec/relay/console +8 -0
- data/libexec/relay/download-models +1 -1
- data/libexec/relay/migrate +2 -1
- data/libexec/relay/setup +1 -1
- data/libexec/relay/start +3 -2
- data/public/js/relay.js +81 -11
- data/public/js/relay.js.map +1 -1
- metadata +21 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 51b2db665dd9baa7f6003b0cdd360d07ab3e1cba08f451400d7063f4bce234d5
|
|
4
|
+
data.tar.gz: e058de42d7416b06df5e35c605c734d8b7af9f93d7fda9015b81f576cca7d286
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2efe61c19fb25fcbe28dfc94ad68bcae7fe6927950edf0ab617fe473166590491d465913d185ffa94f35d6e4458b2ed0534afddb71a160faba72445bcb36b674
|
|
7
|
+
data.tar.gz: 1185e1e81c2c8180ee17165579400a688fc146af4e40f2cb9d03ed2e149274ff2bbfd7fdd7125c638c995beab6e955b2c06ac63995bfb01d9d093d4bb9158e9d
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### Change
|
|
6
|
+
|
|
7
|
+
* **Move the default Relay home to `~/.config/relay`** <br>
|
|
8
|
+
Change Relay's default writable home directory from `~/.relay` to
|
|
9
|
+
`~/.config/relay`, and update the README examples for user-installed
|
|
10
|
+
tools to match the new location.
|
|
11
|
+
|
|
12
|
+
* **Add `relay console`** <br>
|
|
13
|
+
Add a console command that boots Relay and starts IRB in the loaded
|
|
14
|
+
application process for interactive debugging.
|
|
15
|
+
|
|
16
|
+
* **Add AWS Bedrock provider support** <br>
|
|
17
|
+
Add Bedrock to Relay's provider registry and persisted context
|
|
18
|
+
initialization, and extend `relay configure` to prompt for AWS access
|
|
19
|
+
key credentials.
|
|
20
|
+
|
|
21
|
+
* **Prepopulate provider API key prompts from environment** <br>
|
|
22
|
+
Let `relay configure` reuse existing provider secrets from process
|
|
23
|
+
environment variables such as `OPENAI_API_KEY` and
|
|
24
|
+
`DEEPSEEK_API_KEY`, while still writing Relay's canonical `*_SECRET`
|
|
25
|
+
keys to `~/.relay/env`.
|
|
26
|
+
|
|
27
|
+
### Fix
|
|
28
|
+
|
|
29
|
+
* **Launch Falcon with the current Ruby interpreter** <br>
|
|
30
|
+
Change `relay start` to exec Falcon through `RbConfig.ruby -S` so the
|
|
31
|
+
server process uses the same Ruby and gem environment as Relay instead
|
|
32
|
+
of whichever `falcon` binary appears first on `PATH`.
|
|
33
|
+
|
|
34
|
+
* **Load user-installed tools through Zeitwerk** <br>
|
|
35
|
+
Replace manual loading of `~/.config/relay/tools/*.rb` with a dedicated
|
|
36
|
+
Zeitwerk loader so development reloads unload and recreate user tools
|
|
37
|
+
instead of reopening existing classes.
|
|
38
|
+
|
|
39
|
+
* **Activate the `llm.rb` gem before `require "llm"`** <br>
|
|
40
|
+
Avoid RubyGems loading the unrelated `llm` gem when both gems are
|
|
41
|
+
installed by explicitly activating `llm.rb` before requiring its
|
|
42
|
+
`llm` entrypoint.
|
|
43
|
+
|
|
5
44
|
## v0.5.0
|
|
6
45
|
|
|
7
46
|
Model catalog workflow release.
|
data/README.md
CHANGED
|
@@ -7,9 +7,9 @@ set of dependencies - built on Roda, Sequel, Falcon, [llm.rb](https://github.com
|
|
|
7
7
|
HTMX and web sockets.
|
|
8
8
|
|
|
9
9
|
There is support for connecting to MCP servers too - both HTTP and stdio. You can
|
|
10
|
-
add your own tools to `~/.relay/tools` which is a neat way to extend the
|
|
11
|
-
with your own functionality. The database uses SQLite3 to keep things
|
|
12
|
-
goal is to have something you can setup in under two minutes.
|
|
10
|
+
add your own tools to `~/.config/relay/tools` which is a neat way to extend the
|
|
11
|
+
environment with your own functionality. The database uses SQLite3 to keep things
|
|
12
|
+
simple - the goal is to have something you can setup in under two minutes.
|
|
13
13
|
|
|
14
14
|
## Getting started
|
|
15
15
|
|
|
@@ -62,7 +62,7 @@ Very easy.
|
|
|
62
62
|
|
|
63
63
|
## How do I add my own tool?
|
|
64
64
|
|
|
65
|
-
Before running `relay start` you should add
|
|
65
|
+
Before running `relay start` you should add `~/.config/relay/tools/<yourtool>.rb`.
|
|
66
66
|
The tool will be automatically made available to the LLM. This is how a tool
|
|
67
67
|
might look - it is not very useful because it does not emit command output
|
|
68
68
|
but it serves as a simple example that you can modify and change to meet
|
data/app/concerns/attachment.rb
CHANGED
data/app/concerns/context.rb
CHANGED
|
@@ -55,9 +55,9 @@ module Relay::Concerns
|
|
|
55
55
|
# @return [Array<Relay::Models::MCP>]
|
|
56
56
|
# Saved MCP servers for the current user, newest first.
|
|
57
57
|
def mcps
|
|
58
|
-
@mcps ||= user ? Relay::Models::MCP.summary_dataset(user.mcps_dataset)
|
|
59
|
-
reverse_order(:created_at)
|
|
60
|
-
all : []
|
|
58
|
+
@mcps ||= user ? Relay::Models::MCP.summary_dataset(user.mcps_dataset)
|
|
59
|
+
.reverse_order(:created_at)
|
|
60
|
+
.all : []
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
##
|
|
@@ -94,13 +94,7 @@ module Relay::Concerns
|
|
|
94
94
|
# @return [Hash<String, LLM::Provider>]
|
|
95
95
|
# A map of initialized LLM providers.
|
|
96
96
|
def llms
|
|
97
|
-
|
|
98
|
-
"openai" => LLM.openai(key: ENV["OPENAI_SECRET"]),
|
|
99
|
-
"google" => LLM.google(key: ENV["GOOGLE_SECRET"]),
|
|
100
|
-
"anthropic" => LLM.anthropic(key: ENV["ANTHROPIC_SECRET"]),
|
|
101
|
-
"deepseek" => LLM.deepseek(key: ENV["DEEPSEEK_SECRET"]),
|
|
102
|
-
"xai" => LLM.xai(key: ENV["XAI_SECRET"])
|
|
103
|
-
}.transform_values(&:persist!)
|
|
97
|
+
Relay.providers
|
|
104
98
|
end
|
|
105
99
|
|
|
106
100
|
##
|
|
@@ -114,7 +108,7 @@ module Relay::Concerns
|
|
|
114
108
|
# @return [String]
|
|
115
109
|
# Returns the default chat model for the current provider.
|
|
116
110
|
def default_model
|
|
117
|
-
case (provider = llms
|
|
111
|
+
case (provider = llms[self.provider]).name
|
|
118
112
|
when :deepseek then "deepseek-v4-flash"
|
|
119
113
|
when :openai then "gpt-5.4"
|
|
120
114
|
when :xai then "grok-3"
|
|
@@ -142,6 +136,5 @@ module Relay::Concerns
|
|
|
142
136
|
def user
|
|
143
137
|
@user ||= Relay::Models::User[session["user_id"]] if session["user_id"]
|
|
144
138
|
end
|
|
145
|
-
|
|
146
139
|
end
|
|
147
140
|
end
|
data/app/hooks/require_user.rb
CHANGED
data/app/init/database.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Relay::Database
|
|
|
8
8
|
# @param [String] env
|
|
9
9
|
# @return [Hash]
|
|
10
10
|
def load(env:)
|
|
11
|
-
erb = ERB.new(File.read(File.join(
|
|
11
|
+
erb = ERB.new(File.read(File.join(Relay.home, "db", "config.yml")))
|
|
12
12
|
config = YAML.safe_load(erb.result, aliases: true)
|
|
13
13
|
config.fetch(env)
|
|
14
14
|
end
|
data/app/init/router.rb
CHANGED
data/app/init.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Relay
|
|
4
|
+
require_relative "../lib/relay"
|
|
5
|
+
|
|
4
6
|
require "async"
|
|
5
7
|
require "async/websocket"
|
|
6
8
|
require "async/websocket/adapters/rack"
|
|
@@ -12,7 +14,6 @@ module Relay
|
|
|
12
14
|
require "zeitwerk"
|
|
13
15
|
require "sequel"
|
|
14
16
|
require "yaml"
|
|
15
|
-
require "relay"
|
|
16
17
|
|
|
17
18
|
loader = Zeitwerk::Loader.new
|
|
18
19
|
loader.inflector.inflect(
|
|
@@ -25,7 +26,6 @@ module Relay
|
|
|
25
26
|
File.join(__dir__, "init")
|
|
26
27
|
)
|
|
27
28
|
loader.push_dir(__dir__, namespace: self)
|
|
28
|
-
|
|
29
29
|
loader.enable_reloading if development?
|
|
30
30
|
loader.setup
|
|
31
31
|
|
|
@@ -37,14 +37,24 @@ module Relay
|
|
|
37
37
|
end
|
|
38
38
|
@loader = loader
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
Relay.bootstrap!
|
|
41
|
+
user_tools_dir = File.join(home, "tools")
|
|
42
|
+
user_loader = Zeitwerk::Loader.new
|
|
43
|
+
user_loader.tag = "relay-user-tools"
|
|
44
|
+
user_loader.push_dir(user_tools_dir)
|
|
45
|
+
user_loader.enable_reloading if development?
|
|
46
|
+
user_loader.setup
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Returns the Zeitwerk loader used for user-installed tools
|
|
50
|
+
# @return [Zeitwerk::Loader]
|
|
51
|
+
def self.user_loader
|
|
52
|
+
@user_loader
|
|
53
|
+
end
|
|
54
|
+
@user_loader = user_loader
|
|
44
55
|
|
|
45
56
|
require_relative "init/env"
|
|
46
57
|
require_relative "init/database"
|
|
47
58
|
require_relative "init/router"
|
|
48
|
-
|
|
49
59
|
Relay.reload
|
|
50
60
|
end
|
data/app/models/context.rb
CHANGED
|
@@ -44,13 +44,13 @@ module Relay::Models
|
|
|
44
44
|
|
|
45
45
|
##
|
|
46
46
|
# @note
|
|
47
|
-
# This method excludes tool calls and system messages.
|
|
47
|
+
# This method excludes tool calls, tool returns, and system messages.
|
|
48
48
|
# It is safe to render in the UI.
|
|
49
49
|
# @return [Array<Hash>]
|
|
50
50
|
# Returns persisted user and assistant messages
|
|
51
51
|
def messages
|
|
52
52
|
ctx.messages.filter_map do |message|
|
|
53
|
-
next if message.tool_call? || message.compaction?
|
|
53
|
+
next if message.tool_call? || message.tool_return? || message.compaction?
|
|
54
54
|
next unless message.user? || message.assistant?
|
|
55
55
|
{role: message.role.to_sym, content: message.content.to_s}
|
|
56
56
|
end
|
|
@@ -67,11 +67,20 @@ module Relay::Models
|
|
|
67
67
|
private
|
|
68
68
|
|
|
69
69
|
def set_provider
|
|
70
|
-
|
|
70
|
+
case provider
|
|
71
|
+
when "bedrock"
|
|
72
|
+
LLM.bedrock(
|
|
73
|
+
access_key_id: ENV["AWS_ACCESS_KEY_ID"],
|
|
74
|
+
secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
|
|
75
|
+
persistent: true
|
|
76
|
+
)
|
|
77
|
+
else
|
|
78
|
+
LLM.method(provider).call(key: ENV["#{provider.upcase}_SECRET"], persistent: true)
|
|
79
|
+
end
|
|
71
80
|
end
|
|
72
81
|
|
|
73
82
|
def set_context
|
|
74
|
-
{
|
|
83
|
+
{model: self[:model], compactor: {retention_window: 8, token_threshold: "95%"}}
|
|
75
84
|
end
|
|
76
85
|
|
|
77
86
|
def set_tracer
|
data/app/pages/base.rb
CHANGED
data/app/routes/base.rb
CHANGED
data/app/routes/mcp/base.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Relay::Routes
|
|
|
8
8
|
Relay::Models::MCP.where(id:, user_id: user.id).first || raise(Sequel::NoMatchingRow)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def workspace(selected_id: nil
|
|
11
|
+
def workspace(form:, selected_id: nil)
|
|
12
12
|
partial("fragments/mcp/workspace", locals: {mcps:, selected_id:, form:}) +
|
|
13
13
|
partial("fragments/mcp_settings", locals: {servers: mcps, show_label: false, swap_oob: true})
|
|
14
14
|
end
|
|
@@ -37,9 +37,9 @@ class Relay::Routes::Websocket
|
|
|
37
37
|
# The mutable request params for the current turn
|
|
38
38
|
# @return [void]
|
|
39
39
|
def dispatch(conn, ctx, payload, params)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
if interrupt?(payload)
|
|
41
|
+
interrupt!(conn, ctx)
|
|
42
|
+
elsif request_in_flight?
|
|
43
43
|
write(conn, fragment(:status, status_bar(status: "Busy", ctx:)))
|
|
44
44
|
else
|
|
45
45
|
@task = Async { on_message(conn, ctx, payload, params) }
|
data/app/tools/juke_box.rb
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
hx-swap="outerHTML"
|
|
13
13
|
hx-trigger="change"
|
|
14
14
|
>
|
|
15
|
-
<% Relay.providers.each do |_,
|
|
16
|
-
<% llm = builder.call %>
|
|
15
|
+
<% Relay.providers.each do |_, llm| %>
|
|
17
16
|
<option
|
|
18
17
|
<%= "selected" if llm.name.to_s == provider.to_s %>
|
|
19
18
|
value="<%= llm.name %>">
|
data/bin/relay
CHANGED
|
@@ -13,27 +13,19 @@ end
|
|
|
13
13
|
|
|
14
14
|
def main(argv)
|
|
15
15
|
case argv[0]
|
|
16
|
-
when "setup"
|
|
17
|
-
Process.spawn File.join(libexec,
|
|
18
|
-
Process.wait
|
|
19
|
-
when "download-models"
|
|
20
|
-
Process.spawn File.join(libexec, "download-models"), *argv[1..]
|
|
21
|
-
Process.wait
|
|
22
|
-
when "migrate"
|
|
23
|
-
Process.spawn File.join(libexec, "migrate"), *argv[1..]
|
|
24
|
-
Process.wait
|
|
25
|
-
when "start"
|
|
26
|
-
Process.spawn File.join(libexec, "start"), *argv[1..]
|
|
16
|
+
when "start", "setup", "download-models", "migrate", "bootstrap", "console"
|
|
17
|
+
Process.spawn File.join(libexec, argv[0]), *argv[1..]
|
|
27
18
|
Process.wait
|
|
28
19
|
else
|
|
29
|
-
|
|
20
|
+
require_relative "../lib/relay"
|
|
30
21
|
warn Relay.banner
|
|
31
22
|
warn "Usage: relay [COMMAND] [OPTIONS]\n\n" \
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
"Commands:\n" \
|
|
24
|
+
" setup Setup Relay for the first time\n" \
|
|
25
|
+
" download-models Download provider model catalogs\n" \
|
|
26
|
+
" start Start Relay\n" \
|
|
27
|
+
" migrate Run database migrations\n" \
|
|
28
|
+
" console Start an IRB console with Relay booted\n"
|
|
37
29
|
end
|
|
38
30
|
rescue Interrupt
|
|
39
31
|
wait
|
data/config.ru
CHANGED
|
@@ -4,7 +4,7 @@ ENV["RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT"] ||= (64 * 1024 * 1024).to_s
|
|
|
4
4
|
|
|
5
5
|
require_relative "app/init"
|
|
6
6
|
|
|
7
|
-
use Rack::Static, urls: ["/g"], root: Relay.
|
|
7
|
+
use Rack::Static, urls: ["/g"], root: Relay.public_dir
|
|
8
8
|
use Rack::Static, urls: ["/images", "/stylesheets", "/js"], root: Relay.public_dir
|
|
9
9
|
case Relay.environment
|
|
10
10
|
when "development"
|
data/lib/relay/version.rb
CHANGED
data/lib/relay.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Relay
|
|
4
|
+
require "fileutils"
|
|
5
|
+
gem "llm.rb", "= 8.1.0"
|
|
6
|
+
|
|
4
7
|
require_relative "relay/version"
|
|
5
8
|
require_relative "relay/cache"
|
|
6
9
|
require_relative "relay/attachment"
|
|
@@ -18,7 +21,8 @@ module Relay
|
|
|
18
21
|
"deepseek" => -> { LLM.deepseek(key: ENV["DEEPSEEK_SECRET"]) },
|
|
19
22
|
"google" => -> { LLM.google(key: ENV["GOOGLE_SECRET"]) },
|
|
20
23
|
"openai" => -> { LLM.openai(key: ENV["OPENAI_SECRET"]) },
|
|
21
|
-
"xai" => -> { LLM.xai(key: ENV["XAI_SECRET"]) }
|
|
24
|
+
"xai" => -> { LLM.xai(key: ENV["XAI_SECRET"]) },
|
|
25
|
+
"bedrock" => -> { LLM.bedrock(access_key_id: ENV["AWS_ACCESS_KEY_ID"], secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"]) }
|
|
22
26
|
}.freeze
|
|
23
27
|
private_constant :PROVIDERS
|
|
24
28
|
|
|
@@ -37,7 +41,7 @@ module Relay
|
|
|
37
41
|
# Returns all known providers
|
|
38
42
|
# @return [LLM::Object]
|
|
39
43
|
def self.providers
|
|
40
|
-
@providers ||= LLM::Object.from(PROVIDERS)
|
|
44
|
+
@providers ||= LLM::Object.from(PROVIDERS).transform_values!(&:call)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
##
|
|
@@ -81,7 +85,7 @@ module Relay
|
|
|
81
85
|
# Returns the writable Relay home directory
|
|
82
86
|
# @return [String]
|
|
83
87
|
def self.home
|
|
84
|
-
@home ||= ENV["RELAY_HOME"] || File.join(Dir.home, ".relay")
|
|
88
|
+
@home ||= ENV["RELAY_HOME"] || File.join(Dir.home, ".config", "relay")
|
|
85
89
|
end
|
|
86
90
|
|
|
87
91
|
##
|
|
@@ -91,6 +95,21 @@ module Relay
|
|
|
91
95
|
@env_path ||= File.join(home, "env")
|
|
92
96
|
end
|
|
93
97
|
|
|
98
|
+
##
|
|
99
|
+
# Creates the Relay home layout and copies bundled defaults into it.
|
|
100
|
+
# @return [String]
|
|
101
|
+
def self.bootstrap!
|
|
102
|
+
FileUtils.mkdir_p home
|
|
103
|
+
FileUtils.mkdir_p File.join(home, "db")
|
|
104
|
+
FileUtils.mkdir_p File.join(home, "tools")
|
|
105
|
+
FileUtils.mkdir_p images_dir
|
|
106
|
+
FileUtils.mkdir_p logs_dir
|
|
107
|
+
source = File.join(root, "db", "config.yml")
|
|
108
|
+
destination = File.join(home, "db", "config.yml")
|
|
109
|
+
FileUtils.cp(source, destination) if File.exist?(source) && !File.exist?(destination)
|
|
110
|
+
home
|
|
111
|
+
end
|
|
112
|
+
|
|
94
113
|
##
|
|
95
114
|
# @return [Array<String>]
|
|
96
115
|
# Returns the tools directory
|
|
@@ -109,7 +128,7 @@ module Relay
|
|
|
109
128
|
# Returns the path to generated images
|
|
110
129
|
# @return [String]
|
|
111
130
|
def self.images_dir
|
|
112
|
-
@images_dir ||= File.join(
|
|
131
|
+
@images_dir ||= File.join(public_dir, "g")
|
|
113
132
|
end
|
|
114
133
|
|
|
115
134
|
##
|
|
@@ -170,14 +189,8 @@ module Relay
|
|
|
170
189
|
def self.reload
|
|
171
190
|
LLM::Tool.clear_registry!
|
|
172
191
|
Relay.loader.reload if development?
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
load(_1)
|
|
177
|
-
rescue => ex
|
|
178
|
-
warn "tool error\n"
|
|
179
|
-
"#{ex.class}: #{ex.message}\n"
|
|
180
|
-
"#{ex.backtrace.join("\n")}"
|
|
181
|
-
end
|
|
192
|
+
Relay.user_loader.reload if development?
|
|
193
|
+
Relay.user_loader.eager_load
|
|
194
|
+
Relay.loader.eager_load_dir(tools_dir)
|
|
182
195
|
end
|
|
183
196
|
end
|
data/libexec/relay/bootstrap
CHANGED
data/libexec/relay/configure
CHANGED
|
@@ -1,17 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
require_relative "../../lib/relay"
|
|
5
|
+
require "llm"
|
|
5
6
|
require "fileutils"
|
|
6
7
|
require "io/console"
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
PROVIDERS = [
|
|
10
|
+
LLM::Object.from(
|
|
11
|
+
fields: [
|
|
12
|
+
LLM::Object.from(label: "OpenAI API key", key: "OPENAI_SECRET", aliases: ["OPENAI_API_KEY"], secret: true)
|
|
13
|
+
]
|
|
14
|
+
),
|
|
15
|
+
LLM::Object.from(
|
|
16
|
+
fields: [
|
|
17
|
+
LLM::Object.from(label: "Google API key", key: "GOOGLE_SECRET", aliases: ["GOOGLE_API_KEY"], secret: true)
|
|
18
|
+
]
|
|
19
|
+
),
|
|
20
|
+
LLM::Object.from(
|
|
21
|
+
fields: [
|
|
22
|
+
LLM::Object.from(label: "Anthropic API key", key: "ANTHROPIC_SECRET", aliases: ["ANTHROPIC_API_KEY"], secret: true)
|
|
23
|
+
]
|
|
24
|
+
),
|
|
25
|
+
LLM::Object.from(
|
|
26
|
+
fields: [
|
|
27
|
+
LLM::Object.from(label: "DeepSeek API key", key: "DEEPSEEK_SECRET", aliases: ["DEEPSEEK_API_KEY"], secret: true)
|
|
28
|
+
]
|
|
29
|
+
),
|
|
30
|
+
LLM::Object.from(
|
|
31
|
+
fields: [
|
|
32
|
+
LLM::Object.from(label: "xAI API key", key: "XAI_SECRET", aliases: ["XAI_API_KEY"], secret: true)
|
|
33
|
+
]
|
|
34
|
+
),
|
|
35
|
+
LLM::Object.from(
|
|
36
|
+
fields: [
|
|
37
|
+
LLM::Object.from(label: "AWS access key ID", key: "AWS_ACCESS_KEY_ID", aliases: [], secret: false),
|
|
38
|
+
LLM::Object.from(label: "AWS secret access key", key: "AWS_SECRET_ACCESS_KEY", aliases: [], secret: true)
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
].freeze
|
|
15
42
|
|
|
16
43
|
##
|
|
17
44
|
# utils
|
|
@@ -36,15 +63,24 @@ end
|
|
|
36
63
|
def prompt(label, current: nil, secret: false)
|
|
37
64
|
suffix = current.to_s.empty? ? "" : " [configured]"
|
|
38
65
|
print "#{label}#{suffix}: "
|
|
39
|
-
value = secret ?
|
|
66
|
+
value = secret ? $stdin.noecho(&:gets).to_s.chomp : $stdin.gets.to_s.chomp
|
|
40
67
|
puts if secret
|
|
41
68
|
value.empty? ? current.to_s : value
|
|
42
69
|
end
|
|
43
70
|
|
|
71
|
+
def prompt_provider(env, provider)
|
|
72
|
+
provider.fields.each do |field|
|
|
73
|
+
key = field.key
|
|
74
|
+
aliases = Array(field.aliases)
|
|
75
|
+
current = env[key] || aliases.filter_map { ENV[_1] }.first || ENV[key]
|
|
76
|
+
env[key] = prompt(field.label, current:, secret: field.secret)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
44
80
|
def prompt_required(label)
|
|
45
81
|
loop do
|
|
46
82
|
print "#{label}: "
|
|
47
|
-
value =
|
|
83
|
+
value = $stdin.gets.to_s.strip
|
|
48
84
|
return value unless value.empty?
|
|
49
85
|
warn "#{label} is required."
|
|
50
86
|
end
|
|
@@ -53,10 +89,10 @@ end
|
|
|
53
89
|
def prompt_password
|
|
54
90
|
loop do
|
|
55
91
|
print "Password: "
|
|
56
|
-
password =
|
|
92
|
+
password = $stdin.noecho(&:gets).to_s.chomp
|
|
57
93
|
puts
|
|
58
94
|
print "Confirm password: "
|
|
59
|
-
confirm =
|
|
95
|
+
confirm = $stdin.noecho(&:gets).to_s.chomp
|
|
60
96
|
puts
|
|
61
97
|
return password unless password.empty? || password != confirm
|
|
62
98
|
warn "Passwords must match and cannot be empty."
|
|
@@ -66,15 +102,15 @@ end
|
|
|
66
102
|
##
|
|
67
103
|
# main
|
|
68
104
|
def main
|
|
69
|
-
unless
|
|
105
|
+
unless $stdin.tty? && $stdout.tty?
|
|
70
106
|
warn "relay configure requires an interactive terminal"
|
|
71
107
|
throw(:exit, 1)
|
|
72
108
|
end
|
|
73
109
|
env = load_env
|
|
74
110
|
puts Relay.banner
|
|
75
111
|
puts "Relay configuration\n"
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
PROVIDERS.each do |provider|
|
|
113
|
+
prompt_provider(env, provider)
|
|
78
114
|
end
|
|
79
115
|
name = prompt_required("Name")
|
|
80
116
|
email = prompt_required("Email")
|
data/libexec/relay/migrate
CHANGED
data/libexec/relay/setup
CHANGED
data/libexec/relay/start
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
require_relative "../../lib/relay"
|
|
5
5
|
require "optparse"
|
|
6
|
+
require "rbconfig"
|
|
6
7
|
|
|
7
8
|
DEFAULT_BIND = "http://0.0.0.0:9292"
|
|
8
9
|
|
|
@@ -22,7 +23,7 @@ def main(argv)
|
|
|
22
23
|
option_parser(bind).parse!(argv)
|
|
23
24
|
bind = argv.shift || bind
|
|
24
25
|
Dir.chdir(Relay.root) do
|
|
25
|
-
exec "falcon", "serve", "--bind", bind
|
|
26
|
+
exec RbConfig.ruby, "-S", "falcon", "serve", "--bind", bind
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
|