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.
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.1.2
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-05-02 00:00:00.000000000 Z
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.3.0
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.3.0
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