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 +4 -4
- data/README.md +0 -16
- data/lib/tidewave/database_adapter.rb +0 -4
- data/lib/tidewave/database_adapters/active_record.rb +0 -4
- data/lib/tidewave/database_adapters/sequel.rb +0 -17
- data/lib/tidewave/railtie.rb +4 -0
- data/lib/tidewave/tool.rb +4 -0
- data/lib/tidewave/tools/create_design_canvas.rb +124 -0
- data/lib/tidewave/tools/get_logs.rb +4 -0
- data/lib/tidewave/version.rb +1 -1
- data/lib/tidewave.rb +13 -2
- metadata +3 -3
- data/lib/tidewave/tools/get_models.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ab18eddab48d627b651d3a52e46a351506e57401015706b088910f11c91e54f
|
|
4
|
+
data.tar.gz: 0740650fc83bb249def8d108beeb3434dfb3313e3a740755ac305be2663202ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
[](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
|
-
[](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.
|
|
@@ -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
|
data/lib/tidewave/railtie.rb
CHANGED
|
@@ -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
|
@@ -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
|
data/lib/tidewave/version.rb
CHANGED
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.
|
|
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-
|
|
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
|