tidewave 0.8.2 → 0.9.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: e88f6c0da8ac82856c1663c96b1c6eabea60f9c4784464743b162fd6bdd27203
4
- data.tar.gz: 758b4b72b27d505a6e9645ecb10dd8db993c3c72336529eeaf2bccd6816b3c50
3
+ metadata.gz: a1267db74de119b4df61724a8842bf73cf8808ca502a36595a7180806c172731
4
+ data.tar.gz: 9a0661b32af7c7f0932930d0948fa7842b9da7037aa8c936707cf7349c043342
5
5
  SHA512:
6
- metadata.gz: 630ee8e8a59f4fe6f83aae684f1dfbe496f90200879453aeed9aaa1aae714ee92b4a5e7cd29f7feb1565387d5823641c0cfec17362797bad1b1a19e2393f2fcf
7
- data.tar.gz: 25925a77d58572342a333ef86b82a87b8f1b43aba22208c4832b077a0fccaf2aa758cf4a53c69d1a1f99159562a3caeec4bb16fe242ad2dd3988281b25c83df6
6
+ metadata.gz: 9b10d2635c19388de314ffeb62f27bfd3fbcde4f3ec3cdbb07476ebd598bc455423e003d04a222d81fd7231b5cee2bf778683fb67589d1cc1278ebeef1e51b29
7
+ data.tar.gz: dc487bfc541140331ca32e28ad7986b8d9265ef73c696a31d8014c7766fc32f8eb1972594b301652937c07d2684ae4df2494cc4c819ab4a63458fd8965044f35
data/README.md CHANGED
@@ -82,14 +82,6 @@ Returns output from your running server's log.
82
82
 
83
83
  Your agent can use it to see what happened after a request. For example, reading the request log and backtrace when something misbehaves, or checking the log after an action to confirm the request came in with the expected params.
84
84
 
85
- ### `get_models`
86
-
87
- Lists all of your app's models and where each one is defined, by file and line.
88
-
89
- [![get_models demo](docs/assets/get_models-poster.png)](https://asciinema.org/a/qeELC7wEgMEI5T7n)
90
-
91
- Your agent can use it to map the data domain and find where each model lives before opening files, rather than grepping around for class definitions.
92
-
93
85
  ### `get_source_location`
94
86
 
95
87
  Returns the file and line where a class, module, or method is defined, across both your app and its dependencies.
@@ -20,9 +20,5 @@ class Tidewave
20
20
  def execute_query(query, arguments = [])
21
21
  raise NotImplementedError, "Subclasses must implement execute_query"
22
22
  end
23
-
24
- def get_models
25
- raise NotImplementedError, "Subclasses must implement get_models"
26
- end
27
23
  end
28
24
  end
@@ -24,10 +24,6 @@ class Tidewave
24
24
  database: conn.pool.db_config.database
25
25
  }
26
26
  end
27
-
28
- def get_models
29
- ::ActiveRecord::Base.descendants
30
- end
31
27
  end
32
28
  end
33
29
  end
@@ -28,23 +28,6 @@ class Tidewave
28
28
  database: db.opts[:database]
29
29
  }
30
30
  end
31
-
32
- def get_models
33
- # Filter out anonymous Sequel models that can't be resolved as constants
34
- descendants_of(::Sequel::Model).reject { |model| model.name&.start_with?("Sequel::_Model(") }
35
- end
36
-
37
- private
38
-
39
- def descendants_of(base)
40
- if base.respond_to?(:descendants)
41
- base.descendants
42
- elsif base.respond_to?(:subclasses)
43
- base.subclasses.flat_map { |subclass| [ subclass ] + descendants_of(subclass) }
44
- else
45
- []
46
- end
47
- end
48
31
  end
49
32
  end
50
33
  end
@@ -68,6 +68,10 @@ class Tidewave
68
68
  script_src_elem << client_origin unless script_src_elem.include?(client_origin)
69
69
  end
70
70
 
71
+ directives["connect-src"].try do |connect_src|
72
+ connect_src << client_origin unless connect_src.include?(client_origin)
73
+ end
74
+
71
75
  directives.delete("frame-ancestors")
72
76
  end
73
77
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "net/http"
6
+ require "pathname"
7
+ require "uri"
8
+
9
+ class Tidewave::Tools::CreateDesignCanvas < Tidewave::Tool
10
+ class Error < StandardError; end
11
+
12
+ DESCRIPTION = <<~DESCRIPTION.freeze
13
+ Creates a new design canvas, an HTML file for presenting design explorations.
14
+
15
+ The tool returns the absolute path of the HTML file. The file includes usage
16
+ instructions, read it, then edit it to author the actual design.
17
+ DESCRIPTION
18
+
19
+ FETCH_TIMEOUT_SECONDS = 15
20
+
21
+ def initialize(options = {})
22
+ super
23
+ @client_url = options[:client_url]
24
+ end
25
+
26
+ def browser_tool?
27
+ true
28
+ end
29
+
30
+ def definition
31
+ {
32
+ "name" => "create_design_canvas",
33
+ "description" => DESCRIPTION,
34
+ "inputSchema" => {
35
+ "type" => "object",
36
+ "properties" => {
37
+ "path" => {
38
+ "type" => "string",
39
+ "description" =>
40
+ "The absolute path for the canvas file, with .html file extension. The file must not exist yet."
41
+ }
42
+ },
43
+ "required" => [ "path" ]
44
+ }
45
+ }
46
+ end
47
+
48
+ def call(arguments)
49
+ path = arguments.fetch("path")
50
+
51
+ unless Pathname.new(path).absolute? && path.end_with?(".html")
52
+ return error_result("Invalid path #{path.inspect}. It must be an absolute path with the .html file extension.")
53
+ end
54
+
55
+ html = fetch_canvas_html
56
+ write_canvas(path, html)
57
+
58
+ "Design canvas created at: <path>#{path}</path>. Read the file for usage instructions."
59
+ rescue Error => error
60
+ error_result(error.message)
61
+ end
62
+
63
+ private
64
+
65
+ def write_canvas(path, html)
66
+ begin
67
+ FileUtils.mkdir_p(File.dirname(path))
68
+ rescue SystemCallError => error
69
+ raise Error, "Failed to create the design canvas directory: #{error.message}"
70
+ end
71
+
72
+ File.write(path, html, mode: "wx")
73
+ rescue Errno::EEXIST
74
+ raise Error, "Failed to create the design canvas file, the file already exists."
75
+ rescue SystemCallError => error
76
+ raise Error, "Failed to create the design canvas file: #{error.message}"
77
+ end
78
+
79
+ def fetch_canvas_html
80
+ url = @client_url.to_s.sub(%r{/\z}, "") + "/tc/data/canvas.json"
81
+ uri = URI.parse(url)
82
+
83
+ response =
84
+ Net::HTTP.start(
85
+ uri.host,
86
+ uri.port,
87
+ use_ssl: uri.scheme == "https",
88
+ open_timeout: FETCH_TIMEOUT_SECONDS,
89
+ read_timeout: FETCH_TIMEOUT_SECONDS
90
+ ) { |http| http.request(Net::HTTP::Get.new(uri)) }
91
+
92
+ unless response.is_a?(Net::HTTPOK)
93
+ raise Error, "Failed to fetch the design canvas template, request to #{url} failed with status #{response.code}"
94
+ end
95
+
96
+ parse_canvas_html(response.body, url)
97
+ rescue SystemCallError, SocketError, IOError, Timeout::Error, OpenSSL::SSL::SSLError => error
98
+ raise Error, "Failed to fetch the design canvas template, request to #{url} failed: #{error.message}"
99
+ end
100
+
101
+ def parse_canvas_html(body, url)
102
+ payload =
103
+ begin
104
+ JSON.parse(body)
105
+ rescue JSON::ParserError
106
+ nil
107
+ end
108
+
109
+ html = payload["html"] if payload.is_a?(Hash)
110
+
111
+ unless html.is_a?(String)
112
+ raise Error, "Failed to fetch the design canvas template, unexpected response from #{url}"
113
+ end
114
+
115
+ html
116
+ end
117
+
118
+ def error_result(text)
119
+ {
120
+ "content" => [ { "type" => "text", "text" => text } ],
121
+ "isError" => true
122
+ }
123
+ end
124
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Tidewave
4
- VERSION = "0.8.2"
4
+ VERSION = "0.9.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tidewave
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yorick Jacquin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-08-05 00:00:00.000000000 Z
12
+ date: 2026-08-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -47,10 +47,10 @@ files:
47
47
  - lib/tidewave/railtie.rb
48
48
  - lib/tidewave/tool.rb
49
49
  - lib/tidewave/tools/browser_eval.rb
50
+ - lib/tidewave/tools/create_design_canvas.rb
50
51
  - lib/tidewave/tools/execute_sql_query.rb
51
52
  - lib/tidewave/tools/get_docs.rb
52
53
  - lib/tidewave/tools/get_logs.rb
53
- - lib/tidewave/tools/get_models.rb
54
54
  - lib/tidewave/tools/get_source_location.rb
55
55
  - lib/tidewave/tools/project_eval.rb
56
56
  - lib/tidewave/version.rb
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pathname"
4
-
5
- class Tidewave::Tools::GetModels < Tidewave::Tool
6
- DESCRIPTION = <<~DESCRIPTION.freeze
7
- Returns a list of all database-backed models in the application.
8
- DESCRIPTION
9
-
10
- def initialize(options = {})
11
- @root = options[:root] ? Pathname.new(options[:root].to_s) : Pathname.pwd
12
- @database_adapter = Tidewave::DatabaseAdapter.for(options[:orm_adapter]) if options[:orm_adapter]
13
- @before_reload = options[:before_reload]
14
- end
15
-
16
- def definition
17
- return nil unless @database_adapter
18
-
19
- {
20
- "name" => "get_models",
21
- "description" => DESCRIPTION,
22
- "inputSchema" => {
23
- "type" => "object",
24
- "properties" => {}
25
- }
26
- }
27
- end
28
-
29
- def call(_arguments)
30
- @before_reload&.call
31
-
32
- models = @database_adapter.get_models
33
-
34
- models.map do |model|
35
- display_name = model.name || model.to_s
36
-
37
- if location = get_relative_source_location(model.name)
38
- "* #{display_name} at #{location}"
39
- else
40
- "* #{display_name}"
41
- end
42
- end.join("\n")
43
- end
44
-
45
- private
46
-
47
- def get_relative_source_location(model_name)
48
- return nil if model_name.nil? || model_name.empty?
49
-
50
- source_location = Object.const_source_location(model_name)
51
- return nil unless source_location
52
-
53
- file_path, line_number = source_location
54
- relative_path = Pathname.new(file_path).relative_path_from(@root)
55
- "#{relative_path}:#{line_number}"
56
- rescue ArgumentError
57
- "#{file_path}:#{line_number}"
58
- end
59
- end