relay.app 0.5.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3caa789124f0cee625277b4f1e3472e04c12d1aeeaa193ed6146ba593061d57d
4
- data.tar.gz: 83e22033af1263c50c8746958d56e07836f5570a02399e894be48468de750c74
3
+ metadata.gz: 5bfc3dab37e4f9472c9bc2fc6e384059cd1d75a772407feaccf874d87606ee37
4
+ data.tar.gz: dcafd2c06b26dfbbe84e5f9b700a88cdac835621bedc3a8ffc3b01477028af2d
5
5
  SHA512:
6
- metadata.gz: 848201fc63074254365539657cd709eb60dd3fbe729d7b1421d4076e58bc6dcb82e437058c1fa8f6cb7ce1a887a6ba3583724abc044ce9e10179d76884f46f91
7
- data.tar.gz: 347db524516e78944bce97e8ee6f96944190206fb057f85be65eada7a1fcd3449bb3001be17016ad2125a190600deb6e08f7fcbfff69d4fba77285203860dbb9
6
+ metadata.gz: 0601e7bc7927a746fd219c63f1f048ce6ae284cda78dda87acf0fae8fe2850375b53ea5b98d5ea6c2fa817b4ea36e26e6787c45f8b518d09d24d9b5c4e27be5d
7
+ data.tar.gz: 7cf9df0755a86c7e932ddd9cfac5dc22632ac875ab82350a8205414a7527cde5f1feb45293a0c0ae5e655526b76d3debc2df97a13af9522c442c202042bb7d4e
data/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v0.6.0
6
+
7
+ Changes since v0.5.0.
8
+
9
+ ### Change
10
+
11
+ * **Move the default Relay home to `~/.config/relay`** <br>
12
+ Change Relay's default writable home directory from `~/.relay` to
13
+ `~/.config/relay`, and update the README examples for user-installed
14
+ tools to match the new location.
15
+
16
+ * **Add `relay console`** <br>
17
+ Add a console command that boots Relay and starts IRB in the loaded
18
+ application process for interactive debugging.
19
+
20
+ * **Add AWS Bedrock provider support** <br>
21
+ Add Bedrock to Relay's provider registry and persisted context
22
+ initialization, and extend `relay configure` to prompt for AWS access
23
+ key credentials.
24
+
25
+ * **Prepopulate provider API key prompts from environment** <br>
26
+ Let `relay configure` reuse existing provider secrets from process
27
+ environment variables such as `OPENAI_API_KEY` and
28
+ `DEEPSEEK_API_KEY`, while still writing Relay's canonical `*_SECRET`
29
+ keys to `~/.relay/env`.
30
+
31
+ ### Fix
32
+
33
+ * **Launch Falcon with the current Ruby interpreter** <br>
34
+ Change `relay start` to exec Falcon through `RbConfig.ruby -S` so the
35
+ server process uses the same Ruby and gem environment as Relay instead
36
+ of whichever `falcon` binary appears first on `PATH`.
37
+
38
+ * **Load user-installed tools through Zeitwerk** <br>
39
+ Replace manual loading of `~/.config/relay/tools/*.rb` with a dedicated
40
+ Zeitwerk loader so development reloads unload and recreate user tools
41
+ instead of reopening existing classes.
42
+
43
+ * **Activate the `llm.rb` gem before `require "llm"`** <br>
44
+ Avoid RubyGems loading the unrelated `llm` gem when both gems are
45
+ installed by explicitly activating `llm.rb` before requiring its
46
+ `llm` entrypoint.
47
+
5
48
  ## v0.5.0
6
49
 
7
50
  Model catalog workflow release.
data/README.md CHANGED
@@ -1,68 +1,66 @@
1
1
  ## About
2
2
 
3
- Relay is a self-hostable LLM environment with support for OpenAI, DeepSeek,
4
- Anthropic, xAI and zAI out of the box. It is incredibly simple to setup
5
- and get started. The application is distributed as a RubyGem. It has a minimal
6
- set of dependencies - built on Roda, Sequel, Falcon, [llm.rb](https://github.com/llmrb/llm.rb),
3
+ Relay is a self-hostable, hackable LLM web environment that can be extended
4
+ with your own tools and skills that live in your `${HOME}` directory.
5
+ It is for programmers, AI engineers, hackers, and anyone who wants
6
+ their own AI environment with the option to extend it with code.
7
+
8
+
9
+ ## Setup
10
+
11
+ It is simple to setup and get started. The application is
12
+ distributed as a RubyGem. It has a minimal set of dependencies -
13
+ built on Roda, Sequel, Falcon, [llm.rb](https://github.com/llmrb/llm.rb),
7
14
  HTMX and web sockets.
8
15
 
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 environment
11
- with your own functionality. The database uses SQLite3 to keep things simple - the
12
- goal is to have something you can setup in under two minutes.
16
+ ![demo](./demo.gif)
13
17
 
14
- ## Getting started
18
+ ## Appearance
15
19
 
16
- #### Install
20
+ #### Sign-in
17
21
 
18
- Install the gem:
22
+ ![Relay screenshot](./relay3.png)
19
23
 
20
- ```sh
21
- gem install relay.app
22
- ```
24
+ #### Chat
23
25
 
24
- Go through interactive setup, start the server, and visit
25
- http://localhost:9292.
26
+ ![Relay screenshot](./relay1.png)
26
27
 
27
- ```sh
28
- relay setup
29
- relay start
30
- ```
28
+ #### MCP
31
29
 
32
- ## Features
30
+ ![Relay screenshot](./relay2.png)
33
31
 
34
- * Install and setup in 2 minutes
35
- * Localize your chats and mcp settings to your user account
36
- * Connect to multiple providers (OpenAI, xAI, Anthropic, Google, DeepSeek, zAI)
37
- * Connect to MCP servers
38
- * Cancel in-flight requests and tool execution cleanly
39
- * Run tools concurrently
40
- * Make it yours: extend and customize with your own tools and system prompt
41
- * Lightweight architecture
42
32
 
43
- ## Sounds cool, how does it look?
33
+ ## Getting started
44
34
 
45
- **Sign-in**
35
+ #### Install
46
36
 
47
- ![Relay screenshot](./relay3.png)
37
+ Install the gem
48
38
 
49
- **Chat**
39
+ ```sh
40
+ gem install relay.app
41
+ ```
50
42
 
51
- ![Relay screenshot](./relay1.png)
43
+ #### Configure
52
44
 
53
- **MCP**
45
+ Interactive setup
54
46
 
55
- ![Relay screenshot](./relay2.png)
47
+ ```sh
48
+ relay setup
49
+ ```
56
50
 
57
- ## How easy is it to setup?
51
+ #### Serve
58
52
 
59
- Very easy.
53
+ Start the server, and visit http://localhost:9292
60
54
 
61
- ![demo](./demo.gif)
55
+ ```sh
56
+ relay start
57
+ ```
62
58
 
63
- ## How do I add my own tool?
59
+ ## Tools
64
60
 
65
- Before running `relay start` you should add a `~/.relay/tools/<yourtool>.rb`.
61
+ #### How do I add my own tool?
62
+
63
+ Before running `relay start` you should add `~/.config/relay/tools/<yourtool>.rb`.
66
64
  The tool will be automatically made available to the LLM. This is how a tool
67
65
  might look - it is not very useful because it does not emit command output
68
66
  but it serves as a simple example that you can modify and change to meet
@@ -82,18 +80,18 @@ class Shell < LLM::Tool
82
80
  end
83
81
  ```
84
82
 
85
- ## Wait, what is a tool?
83
+ #### Wait, what is a tool?
86
84
 
87
85
  A tool contains a name, a description, and optional parameters. It is attached
88
86
  to a method, and that method that can be called. The model or LLM decides when
89
- and how to call a tool. A tool can do anything you can imagine, and it can extend
90
- the abilities of the LLM. Suddenly a LLM can search the web, run code, and anything
91
- you can think of. They're a powerful way to extend the capabilities of an LLM.
87
+ and how to call a tool. A tool can extend the abilities of the LLM with
88
+ your own code that could can search the web, read documentation, etc.
92
89
 
93
90
  An MCP server can also expose pre-packaged tools, and those can be especially
94
- powerful for talking to GitHub or your own Forgejo instance.
91
+ useful for talking to GitHub, your own Forgejo instance or any other
92
+ kind of MCP server.
95
93
 
96
- ## What are the default tools?
94
+ #### What are the default tools?
97
95
 
98
96
  The `relay-knowledge` tool returns documentation for both Relay
99
97
  and [llm.rb](https://github.com/llmrb/llm.rb) - ask about either
@@ -106,14 +104,15 @@ can be played inline in the chat, and you can also add your own
106
104
  songs or remove existing ones through the same tools. The only
107
105
  requirement is that it is a YouTube URL.
108
106
 
109
- ## What provider is the best value?
107
+ ## Costs
108
+
109
+ #### What provider is the best value?
110
110
 
111
- DeepSeek. I highly recommend it. The context window is 1M. I have been using it
112
- all the time - especially for Relay development, and despite my heavy usage, it
113
- cost only 80 cents overall. It's almost free. I used it **a lot**. I'd estimate
114
- that a 1M context window costs 14 cents or so.
111
+ DeepSeek. <br>
112
+ Hard to beat it on price. <br>
113
+ Recent models have a context window of 1M.
115
114
 
116
- ## What about Ollama and friends?
115
+ #### What about self-hosting with Ollama ?
117
116
 
118
117
  [llm.rb](https://github.com/llmrb/llm.rb#readme) provides support ollama, llama.cpp,
119
118
  and any OpenAI-compatible endpoint. But Relay does not surface it as a feature. I haven't
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Relay::Concerns
2
4
  module Attachment
3
5
  def attachment
@@ -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
- @llms ||= {
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.fetch(self.provider)).name
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
@@ -4,7 +4,7 @@ module Relay::Hooks
4
4
  module RequireUser
5
5
  def call(*args, **kwargs)
6
6
  @user = Relay::Models::User[session["user_id"]]
7
- @user.nil? ? r.redirect("/sign-in") : super(*args, **kwargs)
7
+ @user.nil? ? r.redirect("/sign-in") : super
8
8
  end
9
9
  end
10
10
  end
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(__dir__, "..", "..", "db", "config.yml")))
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
@@ -157,8 +157,6 @@ module Relay
157
157
  Routes::ClearAttachment.new(self).call
158
158
  end
159
159
  end
160
-
161
160
  end
162
-
163
161
  end
164
162
  end
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
- FileUtils.mkdir_p Relay.home
41
- FileUtils.mkdir_p File.join(Relay.home, "db")
42
- FileUtils.mkdir_p Relay.images_dir
43
- FileUtils.mkdir_p Relay.logs_dir
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
@@ -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
- LLM.method(provider).call(key: ENV["#{provider.upcase}_SECRET"], persistent: true)
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
- { model: self[:model], compactor: { retention_window: 8, token_threshold: "95%" } }
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
@@ -20,6 +20,5 @@ module Relay::Pages
20
20
  def page(name, **locals)
21
21
  view(File.join("pages", name), locals:, layout_opts: {locals:})
22
22
  end
23
-
24
23
  end
25
24
  end
data/app/routes/base.rb CHANGED
@@ -31,6 +31,5 @@ module Relay::Routes
31
31
  def htmx?
32
32
  request.env["HTTP_HX_REQUEST"] == "true"
33
33
  end
34
-
35
34
  end
36
35
  end
@@ -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, form:)
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
- case
41
- when interrupt?(payload) then interrupt!(conn, ctx)
42
- when request_in_flight?
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) }
@@ -73,14 +73,14 @@ class Relay::Routes::Websocket
73
73
  file = attachment_from_payload(payload) || attachment.consume
74
74
  prompt = build_prompt(ctx, payload["message"], file)
75
75
  return if prompt.empty?
76
- vars[:messages].concat [{role: :user, content: prompt}, {role: :assistant, content: +""}]
77
- write(conn, fragment(:status, status_bar(status: "Thinking...", ctx:)))
78
- write(conn, fragment(:remove_empty_state)) if vars[:messages].length == 2
79
- write(conn, fragment(:append_message, message: vars[:messages][-2]))
80
- write(conn, fragment(:append_message, message: vars[:messages][-1]))
81
- write(conn, fragment(:input))
82
76
  yield_tools(ctx) do |tools|
83
77
  params[:tools] = tools
78
+ vars[:messages].concat [{role: :user, content: prompt}, {role: :assistant, content: +""}]
79
+ write(conn, fragment(:status, status_bar(status: "Thinking...", ctx:)))
80
+ write(conn, fragment(:remove_empty_state)) if vars[:messages].length == 2
81
+ write(conn, fragment(:append_message, message: vars[:messages][-2]))
82
+ write(conn, fragment(:append_message, message: vars[:messages][-1]))
83
+ write(conn, fragment(:input))
84
84
  wait_with_heartbeat(conn, proc { talk(ctx, prompt, params) })
85
85
  resolve_functions(ctx, conn, params)
86
86
  end
@@ -121,11 +121,11 @@ class Relay::Routes::Websocket
121
121
  # The WebSocket connection object
122
122
  # @return [void]
123
123
  def resolve_functions(ctx, conn, params)
124
- return if ctx.functions.empty?
125
- returns = wait_with_heartbeat(conn, ctx.wait(:task))
126
- wait_with_heartbeat(conn, proc { ctx.talk(returns, params) })
127
- if ctx.functions.any?
128
- resolve_functions(ctx, conn, params)
124
+ while ctx.functions?
125
+ returns = wait_with_heartbeat(conn, proc { ctx.wait(:task) })
126
+ break if returns.empty?
127
+ write(conn, fragment(:status, status_bar(status: tool_status(ctx.functions), ctx:))) if ctx.functions?
128
+ wait_with_heartbeat(conn, proc { ctx.talk(returns, params) })
129
129
  end
130
130
  end
131
131
 
@@ -19,7 +19,7 @@ module Relay::Tools
19
19
  title: entry["title"],
20
20
  track: entry["track"],
21
21
  html: Relay.erb("fragments/_iframe.erb", {entry:}),
22
- directions:,
22
+ directions:
23
23
  }
24
24
  end
25
25
  end
@@ -10,24 +10,26 @@ module Relay::Tools
10
10
  include Relay::Tool
11
11
 
12
12
  name "relay-knowledge"
13
- description "Returns Relay or llm.rb documentation so answers can cite project details"
14
- param :topic, Enum["relay", "llm.rb"], "The knowledge topic", required: true
13
+ description "Returns Relay, llm.rb or mruby-llm documentation"
14
+ parameter :topic, Enum["relay", "llm.rb", "mruby-llm"], "The knowledge topic"
15
+ required %i[topic]
15
16
 
16
17
  ##
17
18
  # Provides the Relay documentation
18
19
  # @return [Hash]
19
20
  def call(topic:)
20
21
  case topic
21
- when "relay" then {directions:, documentation: relay_documentation}
22
- when "llm.rb" then {directions:, documentation: llmrb_documentation}
22
+ when "relay" then {directions:, documentation: fetch(relay_resources)}
23
+ when "llm.rb" then {directions:, documentation: fetch(llmrb_resources)}
24
+ when "mruby-llm" then {directions:, documentation: fetch(mruby_llm_resources)}
23
25
  else {error: "unknown topic: #{topic}"}
24
26
  end
25
27
  end
26
28
 
27
29
  private
28
30
 
29
- def relay_documentation
30
- relay_resources.each_with_object({}) do |(key, url), h|
31
+ def fetch(resources)
32
+ resources.each_with_object({}) do |(key, url), h|
31
33
  res = Net::HTTP.get_response URI.parse(url)
32
34
  h[key] = res.body
33
35
  end
@@ -37,13 +39,6 @@ module Relay::Tools
37
39
  {"readme" => "https://raw.githubusercontent.com/llmrb/relay/refs/heads/main/README.md"}
38
40
  end
39
41
 
40
- def llmrb_documentation
41
- llmrb_resources.each_with_object({}) do |(key, url), h|
42
- res = Net::HTTP.get_response URI.parse(url)
43
- h[key] = res.body
44
- end
45
- end
46
-
47
42
  def llmrb_resources
48
43
  {
49
44
  "readme" => "https://raw.githubusercontent.com/llmrb/llm.rb/refs/heads/main/README.md",
@@ -52,6 +47,12 @@ module Relay::Tools
52
47
  }
53
48
  end
54
49
 
50
+ def mruby_llm_resources
51
+ {
52
+ "readme" => "https://raw.githubusercontent.com/llmrb/mruby-llm/refs/heads/main/README.md"
53
+ }
54
+ end
55
+
55
56
  def directions
56
57
  "Reference links from the associated document in your response"
57
58
  end
@@ -22,6 +22,9 @@
22
22
  <form
23
23
  id="chat-composer"
24
24
  class="flex min-w-0 flex-1 items-end gap-3"
25
+ data-context-id="<%= ctx.id %>"
26
+ data-provider="<%= provider %>"
27
+ data-model="<%= model %>"
25
28
  hx-on::after-settle="this.querySelector('textarea')?.focus()"
26
29
  ws-send
27
30
  >
@@ -12,8 +12,7 @@
12
12
  hx-swap="outerHTML"
13
13
  hx-trigger="change"
14
14
  >
15
- <% Relay.providers.each do |_, builder| %>
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, "setup"), *argv[1..]
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
- require "relay"
20
+ require_relative "../lib/relay"
30
21
  warn Relay.banner
31
22
  warn "Usage: relay [COMMAND] [OPTIONS]\n\n" \
32
- "Commands:\n" \
33
- " setup Setup Relay for the first time\n" \
34
- " download-models Download provider model catalogs\n" \
35
- " start Start Relay\n" \
36
- " migrate Run database migrations\n"
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.home
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/jukebox.rb CHANGED
@@ -63,7 +63,8 @@ module Relay
63
63
  def extract_youtube_id(uri)
64
64
  path = uri.path.to_s
65
65
  return path.split("/").reject(&:empty?).last if path.start_with?("/embed/", "/shorts/")
66
- CGI.parse(uri.query.to_s).fetch("v", []).first
66
+ form = URI.decode_www_form(uri.query.to_s)
67
+ (form.to_h["v"] || []).first
67
68
  end
68
69
 
69
70
  def songs
data/lib/relay/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Relay
4
- VERSION = "0.5.0"
4
+ VERSION = "0.7.0"
5
5
  end