deacon-rails 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b9b67729a41edf00bed6786b7b452d46280d312c38add74d0a8dba996c9110b2
4
+ data.tar.gz: 2424fcf30dc687f72f2438a837db3b90560eda33c6f8a89a22ace6c756a4e682
5
+ SHA512:
6
+ metadata.gz: d58bd949062b18345c26f8a706486d5deea5e9d5e956b36861113f9c8525fc5e9326bbb4199affd8a4b8ba0b4b61b03e87184648ae5db5f78b45fb799d0f1ed4
7
+ data.tar.gz: 992cd18343847745069d576eefc7443854c6d8117edfc568fd1a5a3fa08fa3632200d6626fd313c47b8984e35219bf0ea1c2a795e8e3defe79431e72e38d1b6e
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Deacon
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,99 @@
1
+ # deacon-rails
2
+
3
+ [Deacon](https://heydeacon.com) is an AI support agent that answers questions from your own docs and content, and says when it doesn't know. This gem adds its chat widget to every page of a Rails app.
4
+
5
+ You need a Deacon account, and there's a free plan.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bundle add deacon-rails
11
+ ```
12
+
13
+ ## Set it up
14
+
15
+ Put the helper in the `<head>` of `app/views/layouts/application.html.erb`:
16
+
17
+ ```erb
18
+ <head>
19
+ <%# ...the rest of your head %>
20
+ <%= javascript_importmap_tags %>
21
+ <%= deacon_widget_tag %>
22
+ </head>
23
+ ```
24
+
25
+ Then give it your key, in the `DEACON_KEY` environment variable:
26
+
27
+ ```sh
28
+ DEACON_KEY=pk_your_widget_key
29
+ ```
30
+
31
+ Or in an initializer, such as `config/initializers/deacon.rb`, which wins over `DEACON_KEY`:
32
+
33
+ ```ruby
34
+ Rails.application.config.deacon.key = "pk_your_widget_key"
35
+ ```
36
+
37
+ To find your key, open Install from the button at the top of the Deacon dashboard. The snippet there has it in `data-key`, and it starts with `pk_`. It isn't a secret: every visitor's browser reads it. If the key is missing, the helper prints nothing and logs a warning that says why, so your pages still render. An option of the wrong type raises an `ArgumentError` that says why.
38
+
39
+ Deacon only runs on the sites listed on Install. A listed domain covers its subdomains, so `example.com` covers `app.example.com`. If your app lives somewhere else, such as `example.fly.dev`, add that address on Install too.
40
+
41
+ When you run `bin/rails server`, Install notices the widget on `localhost:3000` and lets you add it in one click. Chats on localhost don't count towards your plan.
42
+
43
+ If you tell Deacon who is signed in, with the `window.deaconUser` script from [Deacon's install guide](https://heydeacon.com/agent/install.md), put that script above `<%= deacon_widget_tag %>` in the head, because the widget reads it as it starts.
44
+
45
+ ## Options
46
+
47
+ Set them on `config.deacon`, in an initializer or in `config/application.rb`.
48
+
49
+ | Option | Type | Default | What it does |
50
+ | ------------ | ------- | ------------ | ------------ |
51
+ | `key` | string | `DEACON_KEY` | Your Deacon key. |
52
+ | `context` | hash | none | Details Deacon takes into account when it answers, the same on every page, such as `{ product: "api", docs_version: "3.2" }`. Up to 12 keys, each value a string, number or boolean. Anyone can read them in the page's HTML, so never put a secret here. |
53
+ | `page_views` | boolean | `true` | The same script counts visits for Deacon's website analytics. Set this to `false` to send no page views or JavaScript errors from your app. Chat still works. |
54
+
55
+ ```ruby
56
+ Rails.application.config.deacon.key = "pk_your_widget_key"
57
+ Rails.application.config.deacon.context = { product: "api", docs_version: "3.2" }
58
+ Rails.application.config.deacon.page_views = false
59
+ ```
60
+
61
+ ## Turbo
62
+
63
+ Turbo Drive swaps the `<body>` on each visit and keeps the `<head>`, so in the head the tag runs once. Deacon puts its chat on `<html>`, outside the body, so the widget stays in place as people move between pages.
64
+
65
+ ## Content Security Policy
66
+
67
+ If your app gives each request a nonce, with `config.content_security_policy_nonce_generator`, the tag carries it. The policy also needs Deacon's address, `https://heydeacon.com`, in `script-src`, `connect-src` and `frame-src`, and `'unsafe-inline'` in `style-src`, because the widget writes its styles where a nonce can't reach. Keep the nonce to `script-src`, since a nonce in `style-src` makes browsers ignore `'unsafe-inline'`:
68
+
69
+ ```ruby
70
+ Rails.application.configure do
71
+ config.content_security_policy do |policy|
72
+ policy.script_src :self, "https://heydeacon.com"
73
+ policy.style_src :self, :unsafe_inline
74
+ policy.connect_src :self, "https://heydeacon.com"
75
+ policy.frame_src "https://heydeacon.com"
76
+ end
77
+
78
+ config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
79
+ config.content_security_policy_nonce_directives = %w(script-src)
80
+ end
81
+ ```
82
+
83
+ ## Opening the chat from your own button
84
+
85
+ The widget comes with its own button. To use one of yours instead, hide Deacon's in Appearance in the dashboard and call `window.Deacon.open()` from your button or link. `Deacon.close()` and `Deacon.toggle()` work too.
86
+
87
+ ## What it adds
88
+
89
+ The helper prints the same tag Install gives you:
90
+
91
+ ```html
92
+ <script src="https://heydeacon.com/widget/v1/loader.js" data-key="pk_your_widget_key" async></script>
93
+ ```
94
+
95
+ It loads in the background without holding up the page. Works with Rails 8.0 and later, on Ruby 3.3 and later.
96
+
97
+ ## License
98
+
99
+ MIT
@@ -0,0 +1,82 @@
1
+ require "erb"
2
+ require "json"
3
+
4
+ module Deacon
5
+ module Rails
6
+ # The view helper the layout calls once, in its head.
7
+ module Helper
8
+ LOADER_SRC = "https://heydeacon.com/widget/v1/loader.js".freeze
9
+
10
+ MISSING_KEY = "deacon-rails: the key is missing, so the page has no Deacon widget. Set " \
11
+ "DEACON_KEY or config.deacon.key. In Deacon, open Install (the button at the " \
12
+ "top of the dashboard) and copy the data-key value from the snippet. It " \
13
+ "starts with pk_.".freeze
14
+
15
+ # The tag Install gives, with the key from config.deacon.key, or from DEACON_KEY when
16
+ # the app sets none. With no key it prints nothing and logs a warning, so a missing
17
+ # setting never takes a page down.
18
+ def deacon_widget_tag
19
+ key = (deacon_config.key || ENV["DEACON_KEY"]).to_s.strip
20
+
21
+ if key.empty?
22
+ ::Rails.logger&.warn(MISSING_KEY)
23
+ return
24
+ end
25
+
26
+ context = deacon_config.context
27
+ page_views = deacon_config.page_views
28
+ deacon_check_context(context)
29
+ deacon_check_page_views(page_views)
30
+
31
+ attributes = { "data-key" => key }
32
+ attributes["data-context"] = JSON.generate(context) unless context.nil?
33
+ attributes["data-no-page-views"] = true if page_views == false
34
+
35
+ deacon_loader_tag(attributes, deacon_csp_nonce)
36
+ end
37
+
38
+ private
39
+
40
+ def deacon_config
41
+ ::Rails.application.config.deacon
42
+ end
43
+
44
+ # The request's Content Security Policy nonce, when the app has a nonce generator and the
45
+ # page was rendered for a request.
46
+ def deacon_csp_nonce
47
+ content_security_policy_nonce if respond_to?(:content_security_policy_nonce)
48
+ end
49
+
50
+ # A wrong type is a mistake in the app's code, so it raises, as any view error does.
51
+ def deacon_check_context(context)
52
+ return if context.nil?
53
+ return if context.is_a?(Hash) && context.each_value.all? { |value| deacon_scalar?(value) }
54
+
55
+ raise ArgumentError, "deacon-rails: config.deacon.context must be a hash whose values " \
56
+ "are strings, numbers or booleans."
57
+ end
58
+
59
+ def deacon_check_page_views(page_views)
60
+ return if page_views.nil? || page_views == true || page_views == false
61
+
62
+ raise ArgumentError, "deacon-rails: config.deacon.page_views must be true or false."
63
+ end
64
+
65
+ def deacon_scalar?(value)
66
+ value.is_a?(String) || value.is_a?(Integer) || value.is_a?(Float) || value == true || value == false
67
+ end
68
+
69
+ # The tag Install gives, with any of its other attributes between the key and async, and
70
+ # the nonce after them. An attribute that is true is written with no value, as async is.
71
+ def deacon_loader_tag(attributes, nonce)
72
+ rendered = attributes.map do |name, value|
73
+ value == true ? " #{name}" : %( #{name}="#{ERB::Util.html_escape(value)}")
74
+ end.join
75
+
76
+ nonce_attribute = nonce.nil? ? "" : %( nonce="#{ERB::Util.html_escape(nonce)}")
77
+
78
+ %(<script src="#{LOADER_SRC}"#{rendered} async#{nonce_attribute}></script>).html_safe
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,16 @@
1
+ module Deacon
2
+ module Rails
3
+ # What config.deacon holds. A Struct rather than ordered options, so a misspelt setting
4
+ # fails loudly at boot instead of being ignored.
5
+ Configuration = Struct.new(:key, :context, :page_views)
6
+
7
+ # Adds config.deacon to the app and deacon_widget_tag to its views.
8
+ class Railtie < ::Rails::Railtie
9
+ config.deacon = Configuration.new
10
+
11
+ initializer "deacon.view_helper" do
12
+ ActiveSupport.on_load(:action_view) { include Deacon::Rails::Helper }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module Deacon
2
+ module Rails
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ require "deacon/rails/version"
2
+ require "deacon/rails/helper"
3
+ require "deacon/rails/railtie" if defined?(::Rails::Railtie)
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deacon-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Deacon
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: actionview
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: railties
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '8.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '8.0'
40
+ description: A view helper, deacon_widget_tag, that puts the Deacon chat widget on
41
+ every page of a Rails app from one line in the layout. Deacon is an AI support agent
42
+ that answers questions from your own docs and content, and says when it doesn't
43
+ know.
44
+ email:
45
+ - hello@heydeacon.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - LICENSE
51
+ - README.md
52
+ - lib/deacon/rails.rb
53
+ - lib/deacon/rails/helper.rb
54
+ - lib/deacon/rails/railtie.rb
55
+ - lib/deacon/rails/version.rb
56
+ homepage: https://heydeacon.com
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ homepage_uri: https://heydeacon.com
61
+ source_code_uri: https://github.com/heydeacon/deacon-rails
62
+ bug_tracker_uri: https://github.com/heydeacon/deacon-rails/issues
63
+ rubygems_mfa_required: 'true'
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '3.3'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.6.9
79
+ specification_version: 4
80
+ summary: Adds Deacon, an AI support chat that answers from your docs, to every page
81
+ of a Rails app.
82
+ test_files: []