relay.app 0.1.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.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +17 -0
  4. data/README.md +132 -0
  5. data/app/concerns/attachment.rb +12 -0
  6. data/app/concerns/context.rb +147 -0
  7. data/app/concerns/roda.rb +50 -0
  8. data/app/concerns/view.rb +90 -0
  9. data/app/forms/mcp/forgejo.rb +55 -0
  10. data/app/forms/mcp/github.rb +47 -0
  11. data/app/forms/mcp.rb +89 -0
  12. data/app/hooks/require_user.rb +10 -0
  13. data/app/init/database.rb +36 -0
  14. data/app/init/env.rb +21 -0
  15. data/app/init/router.rb +164 -0
  16. data/app/models/context.rb +82 -0
  17. data/app/models/mcp/preset.rb +60 -0
  18. data/app/models/mcp.rb +165 -0
  19. data/app/models/model_record.rb +70 -0
  20. data/app/models/song.rb +11 -0
  21. data/app/models/user.rb +31 -0
  22. data/app/pages/base.rb +25 -0
  23. data/app/pages/chat.rb +18 -0
  24. data/app/pages/mcp.rb +12 -0
  25. data/app/pages/sign_in.rb +14 -0
  26. data/app/prompts/system.md +129 -0
  27. data/app/resources/jukebox.yml +90 -0
  28. data/app/routes/base.rb +36 -0
  29. data/app/routes/clear_attachment.rb +13 -0
  30. data/app/routes/list_chat.rb +11 -0
  31. data/app/routes/list_contexts.rb +17 -0
  32. data/app/routes/list_controls.rb +11 -0
  33. data/app/routes/list_mcp.rb +16 -0
  34. data/app/routes/list_models.rb +14 -0
  35. data/app/routes/list_providers.rb +11 -0
  36. data/app/routes/list_tools.rb +13 -0
  37. data/app/routes/mcp/base.rb +16 -0
  38. data/app/routes/mcp/create.rb +19 -0
  39. data/app/routes/mcp/delete.rb +17 -0
  40. data/app/routes/mcp/form.rb +11 -0
  41. data/app/routes/mcp/new.rb +16 -0
  42. data/app/routes/mcp/show.rb +17 -0
  43. data/app/routes/mcp/toggle.rb +17 -0
  44. data/app/routes/mcp/update.rb +20 -0
  45. data/app/routes/settings/new_context.rb +23 -0
  46. data/app/routes/settings/set_context.rb +26 -0
  47. data/app/routes/settings/set_model.rb +23 -0
  48. data/app/routes/settings/set_provider.rb +38 -0
  49. data/app/routes/sign_in.rb +39 -0
  50. data/app/routes/upload_attachment.rb +35 -0
  51. data/app/routes/websocket/connection.rb +247 -0
  52. data/app/routes/websocket/interrupt.rb +25 -0
  53. data/app/routes/websocket/stream.rb +46 -0
  54. data/app/routes/websocket.rb +62 -0
  55. data/app/tools/add_song.rb +27 -0
  56. data/app/tools/juke_box.rb +41 -0
  57. data/app/tools/relay_knowledge.rb +59 -0
  58. data/app/tools/remove_song.rb +53 -0
  59. data/app/validators/mcp.rb +42 -0
  60. data/app/views/fragments/_append_message.erb +1 -0
  61. data/app/views/fragments/_chat.erb +15 -0
  62. data/app/views/fragments/_contexts.erb +7 -0
  63. data/app/views/fragments/_contexts_body.erb +35 -0
  64. data/app/views/fragments/_controls.erb +15 -0
  65. data/app/views/fragments/_iframe.erb +8 -0
  66. data/app/views/fragments/_input.erb +67 -0
  67. data/app/views/fragments/_mcp_settings.erb +52 -0
  68. data/app/views/fragments/_message.erb +31 -0
  69. data/app/views/fragments/_models.erb +25 -0
  70. data/app/views/fragments/_providers.erb +26 -0
  71. data/app/views/fragments/_remove_empty_state.erb +1 -0
  72. data/app/views/fragments/_replace_last_message.erb +1 -0
  73. data/app/views/fragments/_sidebar_menu.erb +11 -0
  74. data/app/views/fragments/_sidebar_status.erb +21 -0
  75. data/app/views/fragments/_status.erb +40 -0
  76. data/app/views/fragments/_stream.erb +26 -0
  77. data/app/views/fragments/_tools.erb +34 -0
  78. data/app/views/fragments/_tools_panel.erb +4 -0
  79. data/app/views/fragments/mcp/_editor.erb +54 -0
  80. data/app/views/fragments/mcp/_fields_forgejo.erb +16 -0
  81. data/app/views/fragments/mcp/_fields_github.erb +12 -0
  82. data/app/views/fragments/mcp/_list.erb +55 -0
  83. data/app/views/fragments/mcp/_workspace.erb +14 -0
  84. data/app/views/fragments/models/_loading.erb +4 -0
  85. data/app/views/fragments/settings/_chat.erb +1 -0
  86. data/app/views/fragments/settings/_input.erb +1 -0
  87. data/app/views/fragments/settings/_replace_contexts.erb +1 -0
  88. data/app/views/fragments/settings/_workspace.erb +4 -0
  89. data/app/views/layout.erb +19 -0
  90. data/app/views/pages/chat.erb +13 -0
  91. data/app/views/pages/mcps.erb +10 -0
  92. data/app/views/pages/sign_in.erb +45 -0
  93. data/app/views/partials/_sidebar.erb +24 -0
  94. data/bin/relay +38 -0
  95. data/config.ru +21 -0
  96. data/db/migrate/20260319131927_create_users.rb +12 -0
  97. data/db/migrate/20260327000000_create_contexts.rb +20 -0
  98. data/db/migrate/20260426130000_create_mcps.rb +19 -0
  99. data/db/migrate/20260426170000_create_model_infos.rb +20 -0
  100. data/db/migrate/20260503120000_create_songs.rb +17 -0
  101. data/db/migrate/20260503153000_drop_chat_from_model_infos.rb +8 -0
  102. data/db/migrate/20260503160000_rename_model_infos_to_model_records.rb +5 -0
  103. data/db/seeds.rb +13 -0
  104. data/lib/relay/attachment/session.rb +154 -0
  105. data/lib/relay/attachment.rb +55 -0
  106. data/lib/relay/cache/in_memory_cache.rb +60 -0
  107. data/lib/relay/cache.rb +5 -0
  108. data/lib/relay/jukebox.rb +96 -0
  109. data/lib/relay/markdown.rb +45 -0
  110. data/lib/relay/model.rb +12 -0
  111. data/lib/relay/reloader.rb +29 -0
  112. data/lib/relay/task.rb +66 -0
  113. data/lib/relay/task_monitor.rb +80 -0
  114. data/lib/relay/test.rb +11 -0
  115. data/lib/relay/theme.rb +5 -0
  116. data/lib/relay/tool.rb +12 -0
  117. data/lib/relay/version.rb +5 -0
  118. data/lib/relay.rb +183 -0
  119. data/libexec/relay/bootstrap +10 -0
  120. data/libexec/relay/configure +100 -0
  121. data/libexec/relay/migrate +7 -0
  122. data/libexec/relay/setup +10 -0
  123. data/libexec/relay/start +31 -0
  124. data/public/.gitkeep +0 -0
  125. data/public/images/relay.png +0 -0
  126. data/public/js/relay.js +68669 -0
  127. data/public/js/relay.js.map +1 -0
  128. data/public/stylesheets/application.css +2292 -0
  129. data/public/stylesheets/application.css.map +1 -0
  130. metadata +465 -0
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relay
4
+ require "redcarpet"
5
+
6
+ ##
7
+ # Renders markdown to HTML
8
+ # @param [String] text
9
+ # The markdown source
10
+ # @return [String]
11
+ def self.markdown(text)
12
+ renderer.render(text.to_s.gsub(/\r\n?/, "\n"))
13
+ end
14
+
15
+ ##
16
+ # @return [Redcarpet::Markdown]
17
+ # Returns the shared markdown renderer
18
+ def self.renderer
19
+ Redcarpet::Markdown.new(
20
+ Markdown.new(filter_html: false, safe_links_only: true),
21
+ autolink: true,
22
+ fenced_code_blocks: true,
23
+ lax_spacing: true,
24
+ no_intra_emphasis: true,
25
+ tables: true
26
+ )
27
+ end
28
+
29
+ class Markdown < Redcarpet::Render::HTML
30
+ require "erb"
31
+ include ERB::Util
32
+ ##
33
+ # Renders fenced code blocks with a language class for highlight.js
34
+ # @param [String] code
35
+ # The code block contents
36
+ # @param [String, nil] language
37
+ # The fenced code language
38
+ # @return [String]
39
+ def block_code(code, language)
40
+ language = language.to_s.strip
41
+ language = "plaintext" if language.empty?
42
+ %(<pre><code class="language-#{h(language)}">#{h(code)}</code></pre>)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relay
4
+ module Model
5
+ def self.included(model)
6
+ model.plugin :timestamps,
7
+ create: :created_at,
8
+ update: :updated_at,
9
+ update_on_create: true
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relay
4
+ ##
5
+ # Reloads Zeitwerk-managed application code on each request in
6
+ # development before passing control to the downstream Rack app.
7
+ class Reloader
8
+ ##
9
+ # @param [#call] app
10
+ # @return [Relay::Reloader]
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ ##
16
+ # @param [Hash] env
17
+ # @return [Array(Integer, Hash, #each)]
18
+ def call(env)
19
+ reload!
20
+ @app.call(env)
21
+ end
22
+
23
+ private
24
+
25
+ def reload!
26
+ Relay.reload
27
+ end
28
+ end
29
+ end
data/lib/relay/task.rb ADDED
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # {Relay::Task} represents a Rake task that can be run
5
+ # in a separate process. The process becomes a group leader,
6
+ # so that if it spawns any child processes, they will be in
7
+ # the same process group.
8
+ #
9
+ # A task provides its status through a channel, which can be
10
+ # either "success" or "error" depending on whether the task
11
+ # completed successfully or not.
12
+ class Relay::Task
13
+ require "xchan"
14
+
15
+ ##
16
+ # @return [Chan::UNIXSocket]
17
+ attr_reader :ch
18
+
19
+ ##
20
+ # @return [Integer]
21
+ attr_reader :pid
22
+
23
+ ##
24
+ # @param [String] task
25
+ # The name of the Rake task to run
26
+ # @return [void]
27
+ def initialize(task)
28
+ @task = task
29
+ @ch = xchan(:pure)
30
+ @pid = nil
31
+ end
32
+
33
+ ##
34
+ # Call the task in a separate process
35
+ # @return [Integer]
36
+ # The PID of the child process
37
+ def call
38
+ @pid = fork do
39
+ become_group_leader
40
+ invoke_task
41
+ record_success
42
+ rescue
43
+ record_error
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Read the status of a task (non-blocking)
49
+ # @raise [Chan::WaitReadable]
50
+ # When the channel is not ready to be read
51
+ # @return [String]
52
+ # Either "success" or "error"
53
+ def status
54
+ return @status if defined?(@status)
55
+ @status = ch.recv_nonblock
56
+ end
57
+
58
+ private
59
+
60
+ def become_group_leader = Process.setpgrp
61
+ def invoke_task = Rake::Task[@task].invoke
62
+
63
+ def record(status) = @ch.send(status)
64
+ def record_success = record("success")
65
+ def record_error = record("error")
66
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # A task monitor that runs a list of tasks in parallel
5
+ # and monitors them afterwards. When a task fails, the
6
+ # monitor will exit and kill all other tasks.
7
+ #
8
+ # A {Relay::Task} becomes a process group leader, so killing
9
+ # the process group will kill all processes in the group which
10
+ # includes the task and all of its subprocesses.
11
+ class Relay::TaskMonitor
12
+ ##
13
+ # @param [Array<String>] tasks
14
+ # A list of task names
15
+ # @return [Relay::TaskMonitor]
16
+ def initialize(tasks:)
17
+ @tasks = tasks.map { Relay::Task.new(_1) }
18
+ @pids = []
19
+ end
20
+
21
+ ##
22
+ # Assign a block that is run before tasks are forked
23
+ # @return [void]
24
+ def prefork(&block)
25
+ @prefork = block
26
+ end
27
+
28
+ ##
29
+ # Start the task monitor
30
+ # @return [void]
31
+ def monitor
32
+ @prefork&.call
33
+ run
34
+ wait
35
+ rescue Interrupt
36
+ @pids.each { Process.kill("TERM", -_1) }
37
+ wait
38
+ end
39
+
40
+ private
41
+
42
+ ##
43
+ # Run all tasks in parallel and monitor their status
44
+ # @return [void]
45
+ def run
46
+ @pids = @tasks.map(&:call)
47
+ @tasks.each do |task|
48
+ if error?(task.status)
49
+ break(@tasks.size - 1)
50
+ end
51
+ rescue Chan::WaitReadable
52
+ sleep 0.05
53
+ retry
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Wait for all tasks to finish
59
+ # @return [void]
60
+ def wait
61
+ @pids.each { Process.wait(_1) }
62
+ end
63
+
64
+ ##
65
+ # Read the status of a task
66
+ # @param [String] status
67
+ # Either "success" or "error"
68
+ # @return [Boolean]
69
+ def error?(status)
70
+ case status
71
+ when "error"
72
+ @tasks.each do
73
+ Process.kill("TERM", -_1.pid)
74
+ rescue Errno::ESRCH
75
+ end
76
+ true
77
+ when "success" then false
78
+ end
79
+ end
80
+ end
data/lib/relay/test.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relay
4
+ class Test < Test::Unit::TestCase
5
+ include Rack::Test::Methods
6
+
7
+ def app
8
+ Relay::Router.freeze.app
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relay
4
+ THEME = "dark"
5
+ end
data/lib/relay/tool.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relay
4
+ module Tool
5
+ ##
6
+ # Returns the root directory of the project
7
+ # @return [String]
8
+ def root
9
+ File.realpath File.join(__dir__, "..", "..")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relay
4
+ VERSION = "0.1.0"
5
+ end
data/lib/relay.rb ADDED
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relay
4
+ require_relative "relay/version"
5
+ require_relative "relay/cache"
6
+ require_relative "relay/attachment"
7
+ require_relative "relay/jukebox"
8
+ require_relative "relay/markdown"
9
+ require_relative "relay/theme"
10
+ require_relative "relay/task_monitor"
11
+ require_relative "relay/task"
12
+ require_relative "relay/tool"
13
+ require_relative "relay/model"
14
+ require_relative "relay/reloader"
15
+
16
+ PROVIDERS = {
17
+ "anthropic" => -> { LLM.anthropic(key: ENV["ANTHROPIC_SECRET"]) },
18
+ "deepseek" => -> { LLM.deepseek(key: ENV["DEEPSEEK_SECRET"]) },
19
+ "google" => -> { LLM.google(key: ENV["GOOGLE_SECRET"]) },
20
+ "openai" => -> { LLM.openai(key: ENV["OPENAI_SECRET"]) },
21
+ "xai" => -> { LLM.xai(key: ENV["XAI_SECRET"]) }
22
+ }.freeze
23
+ private_constant :PROVIDERS
24
+
25
+ ##
26
+ # @return [String]
27
+ def self.banner
28
+ " ____ _ \n" \
29
+ "| _ \\ ___| | __ _ _ _ \n" \
30
+ "| |_) / _ \\ |/ _` | | | |\n" \
31
+ "| _ < __/ | (_| | |_| |\n" \
32
+ "|_| \\_\\___|_|\\__,_|\\__, |\n" \
33
+ " |___/ \n\n"
34
+ end
35
+
36
+ ##
37
+ # Returns all known providers
38
+ # @return [LLM::Object]
39
+ def self.providers
40
+ @providers ||= LLM::Object.from(PROVIDERS)
41
+ end
42
+
43
+ ##
44
+ # Returns the current Rack environment
45
+ # @return [String]
46
+ def self.environment
47
+ ENV["RACK_ENV"] || "development"
48
+ end
49
+
50
+ ##
51
+ # Returns true when running in development
52
+ # @return [Boolean]
53
+ def self.development?
54
+ environment == "development"
55
+ end
56
+
57
+ ##
58
+ # Returns true when running in production
59
+ # @return [Boolean]
60
+ def self.production?
61
+ environment == "production"
62
+ end
63
+
64
+ ##
65
+ # Returns an object that can be used to store application state
66
+ # that should persist between requests.
67
+ # @return [Relay::InMemoryCache]
68
+ def self.cache
69
+ @cache
70
+ end
71
+ @cache = Cache::InMemoryCache.new
72
+
73
+ ##
74
+ # Returns the root path of the application
75
+ # @return [String]
76
+ def self.root
77
+ @root ||= File.realpath File.join(__dir__, "..")
78
+ end
79
+
80
+ ##
81
+ # Returns the writable Relay home directory
82
+ # @return [String]
83
+ def self.home
84
+ @home ||= ENV["RELAY_HOME"] || File.join(Dir.home, ".relay")
85
+ end
86
+
87
+ ##
88
+ # Returns the path to the Relay env file
89
+ # @return [String]
90
+ def self.env_path
91
+ @env_path ||= File.join(home, "env")
92
+ end
93
+
94
+ ##
95
+ # @return [Array<String>]
96
+ # Returns the tools directory
97
+ def self.tools_dir
98
+ @tools_dir ||= File.join(root, "app", "tools")
99
+ end
100
+
101
+ ##
102
+ # Returns the path to the public/ directory
103
+ # @return [String]
104
+ def self.public_dir
105
+ @public_dir ||= File.join(root, "public")
106
+ end
107
+
108
+ ##
109
+ # Returns the path to generated images
110
+ # @return [String]
111
+ def self.images_dir
112
+ @images_dir ||= File.join(home, "g")
113
+ end
114
+
115
+ ##
116
+ # Returns the path to the app/assets/ directory
117
+ # @return [String]
118
+ def self.assets_dir
119
+ @assets_dir ||= File.join(root, "app", "assets")
120
+ end
121
+
122
+ ##
123
+ # @return [String]
124
+ # Returns the path to the app/views/resources directory
125
+ def self.resources_dir
126
+ @resources_dir ||= File.join(root, "app", "resources")
127
+ end
128
+
129
+ ##
130
+ # Returns the path to the app/views/ directory
131
+ # @return [String]
132
+ def self.views_dir
133
+ @views_dir ||= File.join(root, "app", "views")
134
+ end
135
+
136
+ ##
137
+ # Returns the path to the db/migrate directory
138
+ # @return [String]
139
+ def self.migrations_dir
140
+ @migrations_dir ||= File.join(root, "db", "migrate")
141
+ end
142
+
143
+ ##
144
+ # Returns the path to the app/views/fragments directory
145
+ # @return [String]
146
+ def self.fragments_dir
147
+ @fragments_dir ||= File.join(views_dir, "fragments")
148
+ end
149
+
150
+ ##
151
+ # @return [String]
152
+ def self.logs_dir
153
+ @logs_dir ||= File.join(home, "tmp")
154
+ end
155
+
156
+ ##
157
+ # Renders an erb template
158
+ # @param [String] path
159
+ # @param [Hash] locals
160
+ # @return [String]
161
+ def self.erb(path, locals = {})
162
+ tmpl = File.read File.join(views_dir, path)
163
+ ERB.new(tmpl).result_with_hash(locals)
164
+ end
165
+
166
+ ##
167
+ # Reload Relay (useful in development enviroments)
168
+ # @param [Boolean] reload
169
+ # @return [Array<String>]
170
+ def self.reload
171
+ LLM::Tool.clear_registry!
172
+ Relay.loader.reload
173
+ paths = Dir[File.join(tools_dir, "*.rb")]
174
+ paths.concat Dir[File.join(home, "tools", "*.rb")]
175
+ paths.sort.each do
176
+ load(_1)
177
+ rescue => ex
178
+ warn "tool error\n"
179
+ "#{ex.class}: #{ex.message}\n"
180
+ "#{ex.backtrace.join("\n")}"
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "relay"
5
+ require File.join(Relay.root, "app", "init")
6
+ warn "relay: bootstrap the database"
7
+ Sequel.extension :migration
8
+ Sequel::Migrator.run(Relay::DB, Relay.migrations_dir)
9
+ load File.join(Relay.root, "db", "seeds.rb")
10
+ Relay::Models::ModelRecord.refresh_all
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "relay"
5
+ require "fileutils"
6
+ require "io/console"
7
+
8
+ PROVIDER_KEYS = {
9
+ "OPENAI_SECRET" => "OpenAI API key",
10
+ "GOOGLE_SECRET" => "Google API key",
11
+ "ANTHROPIC_SECRET" => "Anthropic API key",
12
+ "DEEPSEEK_SECRET" => "DeepSeek API key",
13
+ "XAI_SECRET" => "xAI API key"
14
+ }.freeze
15
+
16
+ ##
17
+ # utils
18
+ def load_env
19
+ return {} unless File.readable?(Relay.env_path)
20
+ File.readlines(Relay.env_path, chomp: true).each_with_object({}) do |line, env|
21
+ next if line.empty? || line.start_with?("#")
22
+ key, value = line.split("=", 2)
23
+ env[key] = value.to_s
24
+ end
25
+ end
26
+
27
+ def write_env(env)
28
+ FileUtils.mkdir_p Relay.home
29
+ lines = env.sort.filter_map do |key, value|
30
+ next if value.to_s.empty?
31
+ "#{key}=#{value}"
32
+ end
33
+ File.write(Relay.env_path, "#{lines.join("\n")}\n")
34
+ end
35
+
36
+ def prompt(label, current: nil, secret: false)
37
+ suffix = current.to_s.empty? ? "" : " [configured]"
38
+ print "#{label}#{suffix}: "
39
+ value = secret ? STDIN.noecho(&:gets).to_s.chomp : STDIN.gets.to_s.chomp
40
+ puts if secret
41
+ value.empty? ? current.to_s : value
42
+ end
43
+
44
+ def prompt_required(label)
45
+ loop do
46
+ print "#{label}: "
47
+ value = STDIN.gets.to_s.strip
48
+ return value unless value.empty?
49
+ warn "#{label} is required."
50
+ end
51
+ end
52
+
53
+ def prompt_password
54
+ loop do
55
+ print "Password: "
56
+ password = STDIN.noecho(&:gets).to_s.chomp
57
+ puts
58
+ print "Confirm password: "
59
+ confirm = STDIN.noecho(&:gets).to_s.chomp
60
+ puts
61
+ return password unless password.empty? || password != confirm
62
+ warn "Passwords must match and cannot be empty."
63
+ end
64
+ end
65
+
66
+ ##
67
+ # main
68
+ def main
69
+ unless STDIN.tty? && $stdout.tty?
70
+ warn "relay configure requires an interactive terminal"
71
+ throw(:exit, 1)
72
+ end
73
+ env = load_env
74
+ puts Relay.banner
75
+ puts "Relay configuration\n"
76
+ PROVIDER_KEYS.each do |key, label|
77
+ env[key] = prompt(label, current: env[key], secret: true)
78
+ end
79
+ name = prompt_required("Name")
80
+ email = prompt_required("Email")
81
+ password = prompt_password
82
+ write_env(env)
83
+ require File.join(Relay.root, "app", "init")
84
+ unless Relay::DB.table_exists?(:users)
85
+ warn "Relay has not been bootstrapped"
86
+ throw(:exit, 1)
87
+ end
88
+ user = Relay::Models::User.where(email:).first || Relay::Models::User.new(email:)
89
+ user.name = name
90
+ user.password = password
91
+ user.save
92
+ puts "\nConfigured Relay."
93
+ puts "Email: #{email}"
94
+ end
95
+
96
+ excode = catch(:exit) {
97
+ main
98
+ 0
99
+ }
100
+ exit excode
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "relay"
5
+ require File.join(Relay.root, "app", "init")
6
+ Sequel.extension :migration
7
+ Sequel::Migrator.run(Relay::DB, Relay.migrations_dir)
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "relay"
5
+
6
+ bootstrap = File.join(Relay.root, "libexec", "relay", "bootstrap")
7
+ configure = File.join(Relay.root, "libexec", "relay", "configure")
8
+
9
+ system(bootstrap) || exit(1)
10
+ system(configure) || exit(1)
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "relay"
5
+ require "optparse"
6
+
7
+ DEFAULT_BIND = "http://0.0.0.0:9292"
8
+
9
+ ##
10
+ # utils
11
+ def option_parser(bind)
12
+ OptionParser.new do |o|
13
+ o.banner = "Usage: relay start [OPTIONS] [URL]"
14
+ o.on("--bind URL", "Bind Falcon to a URL") { bind.replace(_1) }
15
+ end
16
+ end
17
+
18
+ ##
19
+ # main
20
+ def main(argv)
21
+ bind = String.new(DEFAULT_BIND)
22
+ option_parser(bind).parse!(argv)
23
+ bind = argv.shift || bind
24
+ exec "falcon", "serve", "--bind", bind
25
+ end
26
+
27
+ excode = catch(:exit) {
28
+ main(ARGV)
29
+ 0
30
+ }
31
+ exit excode
data/public/.gitkeep ADDED
File without changes
Binary file