tidewave 0.8.2 → 0.9.1

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: 4ab18eddab48d627b651d3a52e46a351506e57401015706b088910f11c91e54f
4
+ data.tar.gz: 0740650fc83bb249def8d108beeb3434dfb3313e3a740755ac305be2663202ac
5
5
  SHA512:
6
- metadata.gz: 630ee8e8a59f4fe6f83aae684f1dfbe496f90200879453aeed9aaa1aae714ee92b4a5e7cd29f7feb1565387d5823641c0cfec17362797bad1b1a19e2393f2fcf
7
- data.tar.gz: 25925a77d58572342a333ef86b82a87b8f1b43aba22208c4832b077a0fccaf2aa758cf4a53c69d1a1f99159562a3caeec4bb16fe242ad2dd3988281b25c83df6
6
+ metadata.gz: 9d7a8ecabbc3139ce103a77d44f8e317044c2b78128914ee52c9bca5b4e60ee157f5506920117fd8df257daa9b2df29901882de6cdd77eb95e3fd1d8a0d72e64
7
+ data.tar.gz: eb30a267b41ffd08940674265c51757511ae8a7598eed87c8f19e9a75e70753c1357d5da7a1ee3cd3d104965bb95c86d5626b7cd8e15c8ed5b21a8155620b813
data/README.md CHANGED
@@ -74,22 +74,6 @@ Looks up the documentation for a class, method, or constant, reading from the ex
74
74
 
75
75
  Your agent can use it when it's unsure how a class or method works, so the code it generates is grounded in the docs for the exact versions of the gems your app uses, rather than training data that may be stale or a generic docs lookup that can't guarantee it matches the version your app depends on.
76
76
 
77
- ### `get_logs`
78
-
79
- Returns output from your running server's log.
80
-
81
- [![get_logs demo](docs/assets/get_logs-poster.png)](https://asciinema.org/a/1260413)
82
-
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
-
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
77
  ### `get_source_location`
94
78
 
95
79
  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
data/lib/tidewave/tool.rb CHANGED
@@ -24,6 +24,10 @@ class Tidewave
24
24
  raise NotImplementedError, "#{self.class} must implement #call"
25
25
  end
26
26
 
27
+ def include_in_tool_list?
28
+ true
29
+ end
30
+
27
31
  def validate_and_call(arguments, context = {})
28
32
  arguments ||= {}
29
33
 
@@ -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
@@ -58,6 +58,10 @@ class Tidewave::Tools::GetLogs < Tidewave::Tool
58
58
  matching_lines.join
59
59
  end
60
60
 
61
+ def include_in_tool_list?
62
+ false
63
+ end
64
+
61
65
  private
62
66
 
63
67
  def tail_lines(file_path)
@@ -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.1"
5
5
  end
data/lib/tidewave.rb CHANGED
@@ -76,6 +76,15 @@ class Tidewave
76
76
  UPLOAD_ROUTE = "upload".freeze
77
77
  WS_ROUTE = "ws".freeze
78
78
  PROTOCOL_VERSION = "2025-03-26".freeze
79
+ INSTRUCTIONS = <<~TEXT
80
+ Tidewave connects you to this Rails application while it is running in development: the same process, database and logs the developer is using. Anything you would run through `rails runner` or `ruby -e` can run inside the already-booted app instead, with no startup cost per call. Questions about the app's actual state (data, loaded code, etc) are answered from the live process rather than inferred from files.
81
+
82
+ What is available and when each is relevant:
83
+ - project_eval: run Ruby inside the running app: models, queries, any expression or snippet. Relevant whenever you would otherwise write a script or start `rails runner` or a console.
84
+ - execute_sql_query: query the development database directly. Relevant when the answer is in the database (counts, rows, what a record looks like).
85
+ - get_source_location: where a class, module or method is defined, across the app and every gem, including Rails itself. Uses runtime information, so it works even across meta-programming. Use when you know the exact name and would otherwise grep
86
+ - get_docs: documentation for a class, module or method, app or dependency, for the exact versions this app has installed.
87
+ TEXT
79
88
  MAX_UPLOAD_SIZE = 10_000_000
80
89
  ALLOWED_UPLOAD_CONTENT_TYPES = [ "image/png", "image/jpeg", "video/webm" ].freeze
81
90
  ALLOWED_UPLOAD_TYPES = [ "screenshot", "recording" ].freeze
@@ -361,6 +370,7 @@ class Tidewave
361
370
  "tidewave" => config_data(request),
362
371
  "root" => @root.to_s,
363
372
  "wsl_distro" => ENV["WSL_DISTRO_NAME"],
373
+ "host_path" => ENV["TIDEWAVE_HOST_PATH"],
364
374
  "framework" => {}
365
375
  }
366
376
 
@@ -572,7 +582,8 @@ class Tidewave
572
582
  "name" => "tidewave",
573
583
  "version" => VERSION
574
584
  },
575
- "tools" => tool_definitions(context)
585
+ "tools" => tool_definitions(context),
586
+ "instructions" => INSTRUCTIONS
576
587
  })
577
588
  end
578
589
 
@@ -616,7 +627,7 @@ class Tidewave
616
627
  end
617
628
 
618
629
  def tool_definitions(context)
619
- context[:tools].values.map(&:definition)
630
+ context[:tools].values.select(&:include_in_tool_list?).map(&:definition)
620
631
  end
621
632
 
622
633
  def tool_error_result(message)
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.1
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-10-25 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