tidewave 0.1.2 → 0.2.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/README.md +25 -26
- data/lib/tidewave/configuration.rb +6 -5
- data/lib/tidewave/database_adapter.rb +33 -0
- data/lib/tidewave/database_adapters/active_record.rb +39 -0
- data/lib/tidewave/database_adapters/sequel.rb +37 -0
- data/lib/tidewave/exceptions_middleware.rb +88 -0
- data/lib/tidewave/file_tracker.rb +46 -67
- data/lib/tidewave/middleware.rb +182 -0
- data/lib/tidewave/railtie.rb +36 -27
- data/lib/tidewave/tools/base.rb +0 -7
- data/lib/tidewave/tools/edit_project_file.rb +6 -5
- data/lib/tidewave/tools/execute_sql_query.rb +2 -20
- data/lib/tidewave/tools/get_docs.rb +64 -0
- data/lib/tidewave/tools/get_models.rb +21 -10
- data/lib/tidewave/tools/get_package_location.rb +41 -0
- data/lib/tidewave/tools/get_source_location.rb +38 -35
- data/lib/tidewave/tools/list_project_files.rb +16 -4
- data/lib/tidewave/tools/project_eval.rb +49 -9
- data/lib/tidewave/tools/read_project_file.rb +9 -4
- data/lib/tidewave/tools/shell_eval.rb +1 -1
- data/lib/tidewave/tools/write_project_file.rb +7 -4
- data/lib/tidewave/version.rb +1 -1
- data/lib/tidewave.rb +6 -3
- metadata +11 -8
- data/lib/tidewave/tool_resolver.rb +0 -72
- data/lib/tidewave/tools/glob_project_files.rb +0 -18
- data/lib/tidewave/tools/grep_project_files.rb +0 -108
- data/lib/tidewave/tools/package_search.rb +0 -36
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tidewave
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yorick Jacquin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.
|
33
|
+
version: 1.5.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.
|
40
|
+
version: 1.5.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rack
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -64,19 +64,22 @@ files:
|
|
64
64
|
- config/database.yml
|
65
65
|
- lib/tidewave.rb
|
66
66
|
- lib/tidewave/configuration.rb
|
67
|
+
- lib/tidewave/database_adapter.rb
|
68
|
+
- lib/tidewave/database_adapters/active_record.rb
|
69
|
+
- lib/tidewave/database_adapters/sequel.rb
|
70
|
+
- lib/tidewave/exceptions_middleware.rb
|
67
71
|
- lib/tidewave/file_tracker.rb
|
72
|
+
- lib/tidewave/middleware.rb
|
68
73
|
- lib/tidewave/railtie.rb
|
69
|
-
- lib/tidewave/tool_resolver.rb
|
70
74
|
- lib/tidewave/tools/base.rb
|
71
75
|
- lib/tidewave/tools/edit_project_file.rb
|
72
76
|
- lib/tidewave/tools/execute_sql_query.rb
|
77
|
+
- lib/tidewave/tools/get_docs.rb
|
73
78
|
- lib/tidewave/tools/get_logs.rb
|
74
79
|
- lib/tidewave/tools/get_models.rb
|
80
|
+
- lib/tidewave/tools/get_package_location.rb
|
75
81
|
- lib/tidewave/tools/get_source_location.rb
|
76
|
-
- lib/tidewave/tools/glob_project_files.rb
|
77
|
-
- lib/tidewave/tools/grep_project_files.rb
|
78
82
|
- lib/tidewave/tools/list_project_files.rb
|
79
|
-
- lib/tidewave/tools/package_search.rb
|
80
83
|
- lib/tidewave/tools/project_eval.rb
|
81
84
|
- lib/tidewave/tools/read_project_file.rb
|
82
85
|
- lib/tidewave/tools/shell_eval.rb
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "rack"
|
4
|
-
require "json"
|
5
|
-
require "active_support/core_ext/class"
|
6
|
-
|
7
|
-
gem_tools_path = File.expand_path("tools/**/*.rb", __dir__)
|
8
|
-
Dir[gem_tools_path].each { |f| require f }
|
9
|
-
|
10
|
-
module Tidewave
|
11
|
-
class ToolResolver
|
12
|
-
ALL_TOOLS = Tidewave::Tools::Base.descendants
|
13
|
-
NON_FILE_SYSTEM_TOOLS = ALL_TOOLS.reject(&:file_system_tool?)
|
14
|
-
MESSAGES_PATH = "/tidewave/messages".freeze
|
15
|
-
TOOLS_LIST_METHOD = "tools/list".freeze
|
16
|
-
INCLUDE_FS_TOOLS_PARAM = "include_fs_tools".freeze
|
17
|
-
|
18
|
-
def initialize(app, server)
|
19
|
-
@app = app
|
20
|
-
@server = server
|
21
|
-
end
|
22
|
-
|
23
|
-
def call(env)
|
24
|
-
request = Rack::Request.new(env)
|
25
|
-
request_path = request.path
|
26
|
-
request_body = extract_request_body(request)
|
27
|
-
request_params = request.params
|
28
|
-
|
29
|
-
# Override tools list response if requested
|
30
|
-
return override_tools_list_response(env) if overriding_tools_list_request?(request_path, request_params, request_body)
|
31
|
-
|
32
|
-
# Forward the request to the underlying app (RackTransport)
|
33
|
-
@app.call(env)
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def extract_request_body(request)
|
39
|
-
JSON.parse(request.body.read)
|
40
|
-
rescue JSON::ParserError => e
|
41
|
-
{}
|
42
|
-
ensure
|
43
|
-
request.body.rewind
|
44
|
-
end
|
45
|
-
|
46
|
-
# When we want to exclude file system tools, we need to handle the request differently to prevent from listing them
|
47
|
-
def overriding_tools_list_request?(request_path, request_params, request_body)
|
48
|
-
request_path == MESSAGES_PATH && request_body["method"] == TOOLS_LIST_METHOD && request_params[INCLUDE_FS_TOOLS_PARAM] != "true"
|
49
|
-
end
|
50
|
-
|
51
|
-
RESPONSE_HEADERS = { "Content-Type" => "application/json" }
|
52
|
-
|
53
|
-
def override_tools_list_response(env)
|
54
|
-
register_non_file_system_tools
|
55
|
-
@app.call(env).tap { register_all_tools }
|
56
|
-
end
|
57
|
-
|
58
|
-
def register_non_file_system_tools
|
59
|
-
reset_server_tools
|
60
|
-
@server.register_tools(*NON_FILE_SYSTEM_TOOLS)
|
61
|
-
end
|
62
|
-
|
63
|
-
def register_all_tools
|
64
|
-
reset_server_tools
|
65
|
-
@server.register_tools(*ALL_TOOLS)
|
66
|
-
end
|
67
|
-
|
68
|
-
def reset_server_tools
|
69
|
-
@server.instance_variable_set(:@tools, {})
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "tidewave/file_tracker"
|
4
|
-
|
5
|
-
class Tidewave::Tools::GlobProjectFiles < Tidewave::Tools::Base
|
6
|
-
file_system_tool
|
7
|
-
|
8
|
-
tool_name "glob_project_files"
|
9
|
-
description "Searches for files matching the given glob pattern."
|
10
|
-
|
11
|
-
arguments do
|
12
|
-
required(:pattern).filled(:string).description('The glob pattern to match files against, e.g., \"**/*.ex\"')
|
13
|
-
end
|
14
|
-
|
15
|
-
def call(pattern:)
|
16
|
-
Dir.glob(pattern, base: Tidewave::FileTracker.git_root)
|
17
|
-
end
|
18
|
-
end
|
@@ -1,108 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Tidewave::Tools::GrepProjectFiles < Tidewave::Tools::Base
|
4
|
-
file_system_tool
|
5
|
-
|
6
|
-
def self.ripgrep_executable
|
7
|
-
@ripgrep_executable ||= `which rg`.strip
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.ripgrep_available?
|
11
|
-
ripgrep_executable.present?
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.description
|
15
|
-
"Searches for text patterns in files using #{ripgrep_available? ? 'ripgrep' : 'a grep variant'}."
|
16
|
-
end
|
17
|
-
tool_name "grep_project_files"
|
18
|
-
|
19
|
-
arguments do
|
20
|
-
required(:pattern).filled(:string).description("The pattern to search for")
|
21
|
-
optional(:glob).filled(:string).description(
|
22
|
-
'Optional glob pattern to filter which files to search in, e.g., \"**/*.ex\". Note that if a glob pattern is used, the .gitignore file will be ignored.'
|
23
|
-
)
|
24
|
-
optional(:case_sensitive).filled(:bool).description("Whether the search should be case-sensitive. Defaults to false.")
|
25
|
-
optional(:max_results).filled(:integer).description("Maximum number of results to return. Defaults to 100.")
|
26
|
-
end
|
27
|
-
|
28
|
-
def call(pattern:, glob: "**/*", case_sensitive: false, max_results: 100)
|
29
|
-
if self.class.ripgrep_available?
|
30
|
-
execute_ripgrep(pattern, glob, case_sensitive, max_results)
|
31
|
-
else
|
32
|
-
execute_grep(pattern, glob, case_sensitive, max_results)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def execute_ripgrep(pattern, glob, case_sensitive, max_results)
|
39
|
-
command = [ self.class.ripgrep_executable ]
|
40
|
-
command << "--no-require-git" # ignore gitignored files
|
41
|
-
command << "--json" # formatted as json
|
42
|
-
command << "--max-count=#{max_results}"
|
43
|
-
command << "--ignore-case" unless case_sensitive
|
44
|
-
command << "--glob=#{glob}" if glob.present?
|
45
|
-
command << pattern
|
46
|
-
command << "." # Search in current directory
|
47
|
-
|
48
|
-
results = `#{command.join(" ")} 2>&1`
|
49
|
-
|
50
|
-
# Process the results as needed
|
51
|
-
format_ripgrep_results(results)
|
52
|
-
end
|
53
|
-
|
54
|
-
def execute_grep(pattern, glob, case_sensitive, max_results)
|
55
|
-
files = Tidewave::Tools::GlobProjectFiles.new.call(pattern: glob)
|
56
|
-
results = []
|
57
|
-
files.each do |file|
|
58
|
-
next unless File.file?(file)
|
59
|
-
|
60
|
-
begin
|
61
|
-
file_matches = 0
|
62
|
-
line_number = 0
|
63
|
-
|
64
|
-
File.foreach(file) do |line|
|
65
|
-
line_number += 1
|
66
|
-
|
67
|
-
# Check if line matches pattern with proper case sensitivity
|
68
|
-
if case_sensitive
|
69
|
-
next unless line.include?(pattern)
|
70
|
-
else
|
71
|
-
next unless line.downcase.include?(pattern.downcase)
|
72
|
-
end
|
73
|
-
|
74
|
-
results << {
|
75
|
-
"path" => file,
|
76
|
-
"line_number" => line_number,
|
77
|
-
"content" => line.strip
|
78
|
-
}
|
79
|
-
|
80
|
-
file_matches += 1
|
81
|
-
# Stop processing this file if we've reached max results for it
|
82
|
-
break if file_matches >= max_results
|
83
|
-
end
|
84
|
-
rescue => e
|
85
|
-
# Skip files that can't be read (e.g., binary files)
|
86
|
-
next
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
results.to_json
|
91
|
-
end
|
92
|
-
|
93
|
-
def format_ripgrep_results(results)
|
94
|
-
parsed_results = results.split("\n").map(&:strip).reject(&:empty?).map do |line|
|
95
|
-
JSON.parse(line)
|
96
|
-
end
|
97
|
-
|
98
|
-
parsed_results.map do |result|
|
99
|
-
next if result["type"] != "match"
|
100
|
-
|
101
|
-
{
|
102
|
-
"path" => result.dig("data", "path", "text"),
|
103
|
-
"line_number" => result.dig("data", "line_number"),
|
104
|
-
"content" => result.dig("data", "lines", "text").strip
|
105
|
-
}
|
106
|
-
end.compact.to_json
|
107
|
-
end
|
108
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "net/http"
|
4
|
-
require "uri"
|
5
|
-
require "json"
|
6
|
-
|
7
|
-
class Tidewave::Tools::PackageSearch < Tidewave::Tools::Base
|
8
|
-
tool_name "package_search"
|
9
|
-
description <<~DESCRIPTION
|
10
|
-
Searches for packages on RubyGems.
|
11
|
-
|
12
|
-
Use this tool if you need to find new packages to add to the project. Before using this tool,
|
13
|
-
get an overview of the existing dependencies by using the `project_eval` tool and executing
|
14
|
-
`Gem::Specification.map { |gem| [gem.name, gem.version] }`.
|
15
|
-
|
16
|
-
The results are paginated, with 30 packages per page. Use the `page` parameter to fetch a specific page.
|
17
|
-
DESCRIPTION
|
18
|
-
|
19
|
-
arguments do
|
20
|
-
required(:search).filled(:string).description("The search term")
|
21
|
-
optional(:page).filled(:integer, gt?: 0).description("The page number to fetch. Must be greater than 0. Defaults to 1.")
|
22
|
-
end
|
23
|
-
|
24
|
-
def call(search:, page: 1)
|
25
|
-
uri = URI("https://rubygems.org/api/v1/search.json")
|
26
|
-
uri.query = URI.encode_www_form(query: search, page: page)
|
27
|
-
|
28
|
-
response = Net::HTTP.get_response(uri)
|
29
|
-
|
30
|
-
if response.is_a?(Net::HTTPSuccess)
|
31
|
-
JSON.parse(response.body)
|
32
|
-
else
|
33
|
-
raise "RubyGems API request failed with status code: #{response.code}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|