superset 0.3.6 → 0.4.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/AGENTS.md +105 -0
- data/CHANGELOG.md +7 -0
- data/CLAUDE.md +1 -0
- data/doc/setting_up_personal_api_credentials.md +22 -2
- data/lib/superset/client.rb +34 -0
- data/lib/superset/dashboard/import.rb +1 -0
- data/lib/superset/file_utilities.rb +3 -2
- data/lib/superset/guest_token.rb +17 -1
- data/lib/superset/services/import_dashboard_across_environment.rb +1 -0
- data/lib/superset/version.rb +1 -1
- data/superset.gemspec +7 -1
- metadata +26 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ec3571934b0c40b84d75e2bfc55fb010fbae561539d65b2a20f6131cfcef323
|
|
4
|
+
data.tar.gz: 9e68d4aa5a36aba31b4b548eb9a7bf131e45c3c168090782207ea9e1238228de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ac4a4a54a6d41d7094c3e0e4220b1af461066d433a8ddb3199644b243b16e9db177d290dfd48b54beea967f930c5eb677d81ca99b68add155bc688e1e6aa062
|
|
7
|
+
data.tar.gz: d169a9b5b1f85cec5f07e25a54619530923ad1e12ad97f7b5c4f5a73abf5c751f8c83392595f0d0806648c83f59617948523e208ee19dd8307b9105c9375b7c9
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Agents
|
|
2
|
+
|
|
3
|
+
This file provides guidance to AI coding agents working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies
|
|
9
|
+
bundle install
|
|
10
|
+
|
|
11
|
+
# Open interactive console (auto-loads .env)
|
|
12
|
+
bin/console
|
|
13
|
+
|
|
14
|
+
# Run full test suite
|
|
15
|
+
rspec
|
|
16
|
+
|
|
17
|
+
# Run a single test file
|
|
18
|
+
rspec spec/path/to/test_spec.rb
|
|
19
|
+
|
|
20
|
+
# Run a single test by line number
|
|
21
|
+
rspec spec/path/to/test_spec.rb:42
|
|
22
|
+
|
|
23
|
+
# Lint
|
|
24
|
+
rubocop
|
|
25
|
+
|
|
26
|
+
# Run specs + rubocop together
|
|
27
|
+
rake
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Docker equivalents: prefix commands with `docker-compose run --rm app`.
|
|
31
|
+
|
|
32
|
+
### Environment Setup
|
|
33
|
+
|
|
34
|
+
Copy `env.sample` to `.env` and fill in:
|
|
35
|
+
- `SUPERSET_HOST` — API base URL (required)
|
|
36
|
+
- `SUPERSET_API_USERNAME` / `SUPERSET_API_PASSWORD` — API credentials (required)
|
|
37
|
+
- `SUPERSET_ENVIRONMENT` — optional; loads `.env-{ENVIRONMENT}` instead
|
|
38
|
+
- `CONSOLE_TIMEOUT` — seconds before auto-logout in console (default 1800)
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
### Request Framework
|
|
43
|
+
|
|
44
|
+
All API interactions inherit from `Superset::Request` (`lib/superset/request.rb`). Subclasses implement:
|
|
45
|
+
- `route` — returns the API path (e.g., `"dashboard/#{id}"`)
|
|
46
|
+
- `filters` — returns a filter query string or `""`
|
|
47
|
+
- `list_attributes` — array of field names used for terminal table display
|
|
48
|
+
|
|
49
|
+
**Patterns by HTTP verb:**
|
|
50
|
+
- **GET/LIST** — inherit `Superset::Request`, implement `route` and `filters`
|
|
51
|
+
- **PUT** — inherit `Superset::BasePutRequest`, takes `target_id:` and `params:`, implement `route`
|
|
52
|
+
- **DELETE/POST** — call `client.delete(route)` or `client.post(route, params)` directly
|
|
53
|
+
|
|
54
|
+
Pagination is built into `Superset::Request` (default page size: 100, max: 1000). List classes accept `page_num:` and `page_size:` kwargs.
|
|
55
|
+
|
|
56
|
+
### Authentication Flow
|
|
57
|
+
|
|
58
|
+
1. `Superset::Client` (extends `Happi::Client`) includes `Credential::ApiUser` and creates a `Superset::Authenticator`
|
|
59
|
+
2. `Authenticator` POSTs to `api/v1/security/login` and extracts `access_token`
|
|
60
|
+
3. `Client#connection` builds a Faraday connection with Bearer token; supports both JSON and multipart modes (`config.use_json`)
|
|
61
|
+
|
|
62
|
+
### Query Filter Syntax
|
|
63
|
+
|
|
64
|
+
Superset uses an ORM-like filter string format:
|
|
65
|
+
```
|
|
66
|
+
filters:!((col:column_name,opr:operation,value:value)),
|
|
67
|
+
```
|
|
68
|
+
Common operations: `ct` (contains), `eq` (equals), `neq` (not equals), `rel_o_m`, `rel_m_m`, `dashboard_tags`.
|
|
69
|
+
|
|
70
|
+
### Service Layer
|
|
71
|
+
|
|
72
|
+
`lib/superset/services/` contains complex multi-step workflows. The main one is `DuplicateDashboard`, which:
|
|
73
|
+
1. Validates source/target IDs, schema existence, and data sovereignty rules (all chart datasets must share the same DB schema)
|
|
74
|
+
2. Copies the dashboard and its charts
|
|
75
|
+
3. Duplicates datasets pointing to the target schema
|
|
76
|
+
4. Rewires chart JSON metadata and filter configs to the new dataset IDs
|
|
77
|
+
5. Optionally sets embedded config, tags, and publishes
|
|
78
|
+
6. **Rolls back all created objects on failure**
|
|
79
|
+
|
|
80
|
+
`ImportDashboardAcrossEnvironment` handles cross-environment migration via export/import ZIP files.
|
|
81
|
+
|
|
82
|
+
### Display Mixin
|
|
83
|
+
|
|
84
|
+
`Superset::Display` (`lib/superset/display.rb`) is included in `Superset::Request` and provides `list`, `rows`, `to_h`, and `ids`. Requires `list_attributes` and `result` to be implemented by the subclass.
|
|
85
|
+
|
|
86
|
+
### Resource–Relationship Model
|
|
87
|
+
|
|
88
|
+
- **Dashboards** contain **Charts**
|
|
89
|
+
- **Charts** reference **Datasets** (datasources)
|
|
90
|
+
- **Dashboard JSON metadata** stores layout, chart positions, and filter configurations
|
|
91
|
+
- **Filters** reference Datasets for filter values
|
|
92
|
+
- Duplication/migration requires updating all these cross-references
|
|
93
|
+
|
|
94
|
+
### Conventions
|
|
95
|
+
|
|
96
|
+
- Namespace: `Superset::<ResourceType>::<Action>` (e.g., `Superset::Dashboard::List`)
|
|
97
|
+
- `perform` — executes a state-changing action, returns `self`
|
|
98
|
+
- `response` — raw (cached) HTTP response
|
|
99
|
+
- `result` — extracts `response['result']`
|
|
100
|
+
- Constructor filter params follow naming: `title_contains:`, `title_equals:`, etc.
|
|
101
|
+
- Logging goes to `log/superset-client.log` via `Superset::Logger`
|
|
102
|
+
|
|
103
|
+
### Console Helpers
|
|
104
|
+
|
|
105
|
+
In `bin/console`: `sshelp` (alias: `superset_class_list`) lists all available `Superset::` classes. On startup, `Superset::Database::List.call` prints available DB connections.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 - 2026-06-05
|
|
4
|
+
|
|
5
|
+
* send X-CSRFToken (and replay the session cookie) on state-changing requests so writes work against a CSRF-protected Superset
|
|
6
|
+
* send a same-origin Referer on state-changing requests to satisfy Flask-WTF WTF_CSRF_SSL_STRICT over HTTPS
|
|
7
|
+
* GuestToken: send X-CSRFToken + Referer (and replay the session cookie) on the guest_token POST — that endpoint is CSRF-protected too, so embedded dashboards broke without it
|
|
8
|
+
* add faraday-cookie_jar dependency
|
|
9
|
+
|
|
3
10
|
## 0.3.6 - 2026-02-26
|
|
4
11
|
|
|
5
12
|
* add dry_run to dashboard bulk delete cascade #74
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
See [AGENTS.md](AGENTS.md) for project context and development instructions.
|
|
@@ -4,6 +4,8 @@ Superset API Credentials are essentially the username, password and host of the
|
|
|
4
4
|
|
|
5
5
|
If you know these already, plug them and your host value into the `.env` and it should all just work. A sample env.sample is provided as a template.
|
|
6
6
|
|
|
7
|
+
Note: `.env` files are for local development only—do not store production credentials in a `.env` file (or commit it to git). In production, inject secrets at runtime via your platform's environment variables or a secret manager.
|
|
8
|
+
|
|
7
9
|
Create your own .env file with
|
|
8
10
|
|
|
9
11
|
```
|
|
@@ -59,9 +61,17 @@ Scroll down to the 'Security Users' area and find the PUT request that will upda
|
|
|
59
61
|
|
|
60
62
|
PUT `/api/v1/security/users/{pk}`
|
|
61
63
|
|
|
62
|
-
Click 'Try it Out' and add your users ID in the PK input box.
|
|
64
|
+
Click 'Try it Out' and add your users ID in the PK input box.
|
|
65
|
+
|
|
66
|
+
Generate a secure random password with your preferred tool ... or in ruby with something like:
|
|
67
|
+
```ruby
|
|
68
|
+
# ruby SecureRandom code to provide 32 random bytes
|
|
69
|
+
require 'securerandom'
|
|
70
|
+
SecureRandom.urlsafe_base64(32)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Edit the params to only consist of only the password field and the value of your new password.
|
|
63
74
|
|
|
64
|
-
Edit the params to only consist of only the password field and the value of your new password.
|
|
65
75
|
|
|
66
76
|
```
|
|
67
77
|
{
|
|
@@ -159,5 +169,15 @@ Superset::GuestToken.new(embedded_dashboard_id: '15').guest_token
|
|
|
159
169
|
=> "eyJ0eXAiOi............VV4mrMfsvg"
|
|
160
170
|
```
|
|
161
171
|
|
|
172
|
+
## Troubleshooting tips
|
|
162
173
|
|
|
174
|
+
### Password or token expired
|
|
175
|
+
|
|
176
|
+
If your Superset password has expired (or your auth token is no longer valid), API calls may fail with an error like:
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
Superset::Dashboard::List.call
|
|
180
|
+
ArgumentError: Can't build an Authorization Bearer header from nil
|
|
181
|
+
```
|
|
163
182
|
|
|
183
|
+
Fix: refresh your credentials via swagger interface then update your credentials in .env and try again.
|
data/lib/superset/client.rb
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
require 'faraday-cookie_jar'
|
|
2
|
+
|
|
1
3
|
module Superset
|
|
2
4
|
class Client < Happi::Client
|
|
3
5
|
include Credential::ApiUser
|
|
4
6
|
|
|
7
|
+
# Superset enforces CSRF on state-changing requests once WTF_CSRF_ENABLED is on;
|
|
8
|
+
# GETs are never CSRF-checked. Bearer-token auth is not sufficient,
|
|
9
|
+
# so these verbs must carry an X-CSRFToken header (see #call / #csrf_token).
|
|
10
|
+
CSRF_PROTECTED_METHODS = %i[post put patch delete].freeze
|
|
11
|
+
|
|
5
12
|
attr_reader :authenticator
|
|
6
13
|
|
|
7
14
|
def initialize
|
|
@@ -17,6 +24,15 @@ module Superset
|
|
|
17
24
|
@superset_host ||= authenticator.superset_host
|
|
18
25
|
end
|
|
19
26
|
|
|
27
|
+
# All verbs funnel through Happi::Client#call. Before any state-changing request,
|
|
28
|
+
# attach a CSRF token; fetching one also sets the Flask session cookie that the
|
|
29
|
+
# token is validated against, which the cookie jar on this connection replays on
|
|
30
|
+
# the write.
|
|
31
|
+
def call(method, url, params = {})
|
|
32
|
+
set_csrf_token if CSRF_PROTECTED_METHODS.include?(method)
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
|
|
20
36
|
# TODO: Happi has not got a put method yet
|
|
21
37
|
def put(resource, params = {})
|
|
22
38
|
call(:put, url(resource), param_check(params))
|
|
@@ -40,9 +56,27 @@ module Superset
|
|
|
40
56
|
|
|
41
57
|
private
|
|
42
58
|
|
|
59
|
+
# Set the CSRF headers for the upcoming write:
|
|
60
|
+
# * X-CSRFToken — session-bound token; GET /api/v1/security/csrf_token/ returns it
|
|
61
|
+
# and sets the Flask session cookie it is validated against (the cookie jar on
|
|
62
|
+
# this connection replays that cookie on the write).
|
|
63
|
+
# * Referer — over HTTPS, Flask-WTF's WTF_CSRF_SSL_STRICT (default True) also
|
|
64
|
+
# requires a Referer matching the Superset host (same-origin check), else the
|
|
65
|
+
# write fails with "400 The referrer header is missing." Browsers send this
|
|
66
|
+
# automatically; an API client must set it explicitly.
|
|
67
|
+
def set_csrf_token
|
|
68
|
+
connection.headers['X-CSRFToken'] = csrf_token
|
|
69
|
+
connection.headers['Referer'] = superset_host
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def csrf_token
|
|
73
|
+
@csrf_token ||= get('security/csrf_token/')['result']
|
|
74
|
+
end
|
|
75
|
+
|
|
43
76
|
def connection
|
|
44
77
|
@connection ||= Faraday.new(superset_host) do |f|
|
|
45
78
|
f.authorization :Bearer, access_token
|
|
79
|
+
f.use :cookie_jar # persist the Flask session cookie across the csrf_token GET and the write
|
|
46
80
|
f.use FaradayMiddleware::ParseJson, content_type: 'application/json'
|
|
47
81
|
|
|
48
82
|
if self.config.use_json
|
|
@@ -78,6 +78,7 @@ module Superset
|
|
|
78
78
|
def source_zip_file
|
|
79
79
|
return source if zip?
|
|
80
80
|
|
|
81
|
+
require 'zip' # lazy: only import/export needs rubyzip
|
|
81
82
|
Zip::File.open(new_zip_file, create: true) do |zipfile|
|
|
82
83
|
Dir[File.join(source, "**", "**")].each do |file|
|
|
83
84
|
next unless File.file?(file)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
require 'zip'
|
|
2
|
-
|
|
3
1
|
module Superset
|
|
4
2
|
module FileUtilities
|
|
3
|
+
# rubyzip is loaded lazily so the gem can be required without it; only
|
|
4
|
+
# consumers that actually import/export need rubyzip installed.
|
|
5
5
|
def unzip_file(zip_file, destination)
|
|
6
|
+
require 'zip'
|
|
6
7
|
entries = []
|
|
7
8
|
Zip::File.open(zip_file) do |zip|
|
|
8
9
|
zip.each do |entry|
|
data/lib/superset/guest_token.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'faraday-cookie_jar'
|
|
2
|
+
|
|
1
3
|
module Superset
|
|
2
4
|
class GuestToken
|
|
3
5
|
include Credential::EmbeddedUser
|
|
@@ -52,13 +54,27 @@ module Superset
|
|
|
52
54
|
'api/v1/security/guest_token/'
|
|
53
55
|
end
|
|
54
56
|
|
|
57
|
+
# The guest_token endpoint is CSRF-protected (it is NOT in Superset's CSRF
|
|
58
|
+
# exempt list), so this POST needs the same treatment as Client writes:
|
|
59
|
+
# an X-CSRFToken bound to the session cookie, plus a same-origin
|
|
60
|
+
# Referer for WTF_CSRF_SSL_STRICT over HTTPS. The csrf_token GET also sets the
|
|
61
|
+
# session cookie that the cookie jar replays on the POST.
|
|
55
62
|
def response
|
|
56
|
-
@response ||=
|
|
63
|
+
@response ||= begin
|
|
64
|
+
connection.headers['X-CSRFToken'] = csrf_token
|
|
65
|
+
connection.headers['Referer'] = authenticator.superset_host
|
|
66
|
+
connection.post(route, params.to_json)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def csrf_token
|
|
71
|
+
@csrf_token ||= connection.get('api/v1/security/csrf_token/').env.body['result']
|
|
57
72
|
end
|
|
58
73
|
|
|
59
74
|
def connection
|
|
60
75
|
@connection ||= Faraday.new(authenticator.superset_host) do |f|
|
|
61
76
|
f.authorization :Bearer, access_token
|
|
77
|
+
f.use :cookie_jar # replay the Flask session cookie from the csrf_token GET on the POST
|
|
62
78
|
f.use FaradayMiddleware::ParseJson, content_type: 'application/json'
|
|
63
79
|
f.request :json
|
|
64
80
|
f.adapter :net_http
|
|
@@ -87,6 +87,7 @@ module Superset
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def create_new_dashboard_zip
|
|
90
|
+
require 'zip' # lazy: only import/export needs rubyzip
|
|
90
91
|
Zip::File.open(new_zip_file, create: true) do |zipfile|
|
|
91
92
|
Dir[File.join(dashboard_export_root_path, '**', '**')].each do |file|
|
|
92
93
|
zipfile.add(file.sub(dashboard_export_root_path + '/', File.basename(dashboard_export_root_path) + '/' ), file) if File.file?(file)
|
data/lib/superset/version.rb
CHANGED
data/superset.gemspec
CHANGED
|
@@ -37,11 +37,17 @@ Gem::Specification.new do |spec|
|
|
|
37
37
|
spec.add_dependency "json", ">= 2.0"
|
|
38
38
|
spec.add_dependency "terminal-table", "~> 4.0"
|
|
39
39
|
spec.add_dependency "require_all", ">= 3.0"
|
|
40
|
-
spec.add_dependency "rubyzip", ">= 3.0"
|
|
41
40
|
spec.add_dependency "faraday", "~> 1.0"
|
|
42
41
|
spec.add_dependency "faraday-multipart", "~> 1.0"
|
|
42
|
+
spec.add_dependency "faraday-cookie_jar", "~> 0.0.7" # replay the Flask session cookie for CSRF
|
|
43
43
|
spec.add_dependency "enumerate_it", ">= 1.7"
|
|
44
44
|
|
|
45
|
+
# rubyzip is only needed by the dashboard import/export feature, which lazily
|
|
46
|
+
# `require 'zip'` at call time. Kept as a dev dependency so those specs run here,
|
|
47
|
+
# but NOT a runtime dependency — consumers that don't import/export (embedders,
|
|
48
|
+
# read/write API clients) shouldn't inherit its rubyzip >= 3 pin.
|
|
49
|
+
# Apps that DO use import/export must declare rubyzip (>= 3.0) themselves.
|
|
50
|
+
spec.add_development_dependency "rubyzip", ">= 3.0"
|
|
45
51
|
spec.add_development_dependency "dotenv", ">= 2.0"
|
|
46
52
|
spec.add_development_dependency "rake", ">= 13.0"
|
|
47
53
|
spec.add_development_dependency "rspec", ">= 3.0"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: superset
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- jbat
|
|
@@ -52,21 +52,21 @@ dependencies:
|
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '3.0'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
|
-
name:
|
|
55
|
+
name: faraday
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
|
-
- - "
|
|
58
|
+
- - "~>"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '
|
|
60
|
+
version: '1.0'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
|
-
- - "
|
|
65
|
+
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '
|
|
67
|
+
version: '1.0'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: faraday
|
|
69
|
+
name: faraday-multipart
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
72
|
- - "~>"
|
|
@@ -80,19 +80,19 @@ dependencies:
|
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '1.0'
|
|
82
82
|
- !ruby/object:Gem::Dependency
|
|
83
|
-
name: faraday-
|
|
83
|
+
name: faraday-cookie_jar
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
85
85
|
requirements:
|
|
86
86
|
- - "~>"
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
|
-
version:
|
|
88
|
+
version: 0.0.7
|
|
89
89
|
type: :runtime
|
|
90
90
|
prerelease: false
|
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
92
|
requirements:
|
|
93
93
|
- - "~>"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
|
-
version:
|
|
95
|
+
version: 0.0.7
|
|
96
96
|
- !ruby/object:Gem::Dependency
|
|
97
97
|
name: enumerate_it
|
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -107,6 +107,20 @@ dependencies:
|
|
|
107
107
|
- - ">="
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
109
|
version: '1.7'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: rubyzip
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '3.0'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '3.0'
|
|
110
124
|
- !ruby/object:Gem::Dependency
|
|
111
125
|
name: dotenv
|
|
112
126
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -201,7 +215,9 @@ files:
|
|
|
201
215
|
- ".rspec"
|
|
202
216
|
- ".rubocop.yml"
|
|
203
217
|
- ".ruby-version"
|
|
218
|
+
- AGENTS.md
|
|
204
219
|
- CHANGELOG.md
|
|
220
|
+
- CLAUDE.md
|
|
205
221
|
- Dockerfile
|
|
206
222
|
- LICENSE
|
|
207
223
|
- README.md
|