tidewave 0.2.0 → 0.3.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 +15 -15
- data/lib/tidewave/configuration.rb +2 -1
- data/lib/tidewave/middleware.rb +3 -1
- data/lib/tidewave/quiet_requests_middleware.rb +15 -0
- data/lib/tidewave/railtie.rb +6 -0
- data/lib/tidewave/tools/get_logs.rb +10 -3
- data/lib/tidewave/tools/get_models.rb +1 -1
- data/lib/tidewave/tools/get_source_location.rb +22 -3
- data/lib/tidewave/tools/project_eval.rb +1 -1
- data/lib/tidewave/version.rb +1 -1
- metadata +4 -9
- data/lib/tidewave/file_tracker.rb +0 -102
- data/lib/tidewave/tools/edit_project_file.rb +0 -47
- data/lib/tidewave/tools/get_package_location.rb +0 -41
- data/lib/tidewave/tools/list_project_files.rb +0 -26
- data/lib/tidewave/tools/read_project_file.rb +0 -25
- data/lib/tidewave/tools/shell_eval.rb +0 -36
- data/lib/tidewave/tools/write_project_file.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d140360315f54166fc14e330f33fc5dd62e561c49ba3d6cb5044a8e7b5247e41
|
4
|
+
data.tar.gz: c69a1a0363ce0f50a396599f2b551896a602431929731dd9fbf4fbe283823cf1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f3203bbce1c31681fcb2b21575ea62a8714a469f61ec30be8d59ccd22028592b7a33317b4f434e0d7a4051caaf1db1aa82247d395a961d4a09f1a1538cc6cd8
|
7
|
+
data.tar.gz: 968742eed4690b5c2a1f8c0ecf233e4742a19bc2ac8a700d4f64eca2878bea8f70d1d2228cffc472019462790eb122601b7acfc608fd7950c100974f3c9053d4
|
data/README.md
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# Tidewave
|
2
2
|
|
3
|
-
Tidewave
|
4
|
-
how it runs, and what it delivers. Our current release connects your editor's
|
5
|
-
assistant to your web framework runtime via [MCP](https://modelcontextprotocol.io/).
|
3
|
+
Tidewave is the coding agent for full-stack web app development, deeply integrated with Rails, from the database to the UI. [See our website](https://tidewave.ai) for more information.
|
6
4
|
|
7
|
-
|
5
|
+
This project can also be used as a standalone Model Context Protocol server for your editors.
|
8
6
|
|
9
7
|
## Installation
|
10
8
|
|
@@ -14,9 +12,7 @@ You can install Tidewave by adding the `tidewave` gem to the development group i
|
|
14
12
|
gem "tidewave", group: :development
|
15
13
|
```
|
16
14
|
|
17
|
-
|
18
|
-
In particular, the MCP is located by default at http://localhost:3000/tidewave/mcp.
|
19
|
-
[You must configure your editor and AI assistants accordingly](https://hexdocs.pm/tidewave/mcp.html).
|
15
|
+
Now access `/tidewave` route of your web application to enjoy Tidewave Web!
|
20
16
|
|
21
17
|
## Troubleshooting
|
22
18
|
|
@@ -29,22 +25,24 @@ config.hosts << "company.local"
|
|
29
25
|
config.tidewave.allow_remote_access = true
|
30
26
|
```
|
31
27
|
|
32
|
-
If you want to use Docker for development, you either need to enable the configuration above or automatically redirect the relevant ports, as done by [devcontainers](https://code.visualstudio.com/docs/devcontainers/containers). See our [
|
28
|
+
If you want to use Docker for development, you either need to enable the configuration above or automatically redirect the relevant ports, as done by [devcontainers](https://code.visualstudio.com/docs/devcontainers/containers). See our [containers](https://hexdocs.pm/tidewave/containers.html) guide for more information.
|
33
29
|
|
34
30
|
### Content security policy
|
35
31
|
|
36
32
|
If you have enabled Content-Security-Policy, Tidewave will automatically enable "unsafe-eval" under `script-src` in order for contextual browser testing to work correctly.
|
37
33
|
|
34
|
+
### Web server requirements
|
35
|
+
|
36
|
+
At the moment, Tidewave requires all requests to be processed by the same process. In case Tidewave cannot connect to your application, consider starting your Rails application as follows:
|
37
|
+
|
38
|
+
RAILS_MAX_THREADS=1 WEB_CONCURRENCY=1 rails server
|
39
|
+
|
38
40
|
### Production Environment
|
39
41
|
|
40
42
|
Tidewave is a powerful tool that can help you develop your web application faster and more efficiently. However, it is important to note that Tidewave is not meant to be used in a production environment.
|
41
43
|
|
42
44
|
Tidewave will raise an error if it is used in any environment where code reloading is disabled (which typically includes production).
|
43
45
|
|
44
|
-
### Web server requirements
|
45
|
-
|
46
|
-
Tidewave currently requires a threaded web server like Puma.
|
47
|
-
|
48
46
|
## Configuration
|
49
47
|
|
50
48
|
You may configure `tidewave` using the following syntax:
|
@@ -53,11 +51,13 @@ You may configure `tidewave` using the following syntax:
|
|
53
51
|
config.tidewave.allow_remote_access = true
|
54
52
|
```
|
55
53
|
|
56
|
-
The following
|
54
|
+
The following config is available:
|
55
|
+
|
56
|
+
* `allow_remote_access` - Tidewave only allows requests from localhost by default, even if your server listens on other interfaces as well. If you trust your network and need to access Tidewave from a different machine, this configuration can be set to `true`
|
57
57
|
|
58
|
-
*
|
58
|
+
* `preferred_orm` - which ORM to use, either `:active_record` (default) or `:sequel`
|
59
59
|
|
60
|
-
*
|
60
|
+
* `team` - set your team configuration, such as `config.tidewave.team = { id: "my-company }`
|
61
61
|
|
62
62
|
## Acknowledgements
|
63
63
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Tidewave
|
4
4
|
class Configuration
|
5
|
-
attr_accessor :logger, :allow_remote_access, :preferred_orm, :credentials, :client_url
|
5
|
+
attr_accessor :logger, :allow_remote_access, :preferred_orm, :credentials, :client_url, :team
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
@logger = nil
|
@@ -10,6 +10,7 @@ module Tidewave
|
|
10
10
|
@preferred_orm = :active_record
|
11
11
|
@credentials = {}
|
12
12
|
@client_url = "https://tidewave.ai"
|
13
|
+
@team = {}
|
13
14
|
end
|
14
15
|
end
|
15
16
|
end
|
data/lib/tidewave/middleware.rb
CHANGED
@@ -25,6 +25,7 @@ class Tidewave::Middleware
|
|
25
25
|
def initialize(app, config)
|
26
26
|
@allow_remote_access = config.allow_remote_access
|
27
27
|
@client_url = config.client_url
|
28
|
+
@team = config.team
|
28
29
|
@project_name = Rails.application.class.module_parent.name
|
29
30
|
|
30
31
|
@app = FastMcp.rack_middleware(app,
|
@@ -78,7 +79,8 @@ class Tidewave::Middleware
|
|
78
79
|
config = {
|
79
80
|
"project_name" => @project_name,
|
80
81
|
"framework_type" => "rails",
|
81
|
-
"tidewave_version" => Tidewave::VERSION
|
82
|
+
"tidewave_version" => Tidewave::VERSION,
|
83
|
+
"team" => @team
|
82
84
|
}
|
83
85
|
|
84
86
|
html = <<~HTML
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Tidewave::QuietRequestsMiddleware
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
if env["PATH_INFO"].start_with?("/tidewave")
|
10
|
+
Rails.logger.silence { @app.call(env) }
|
11
|
+
else
|
12
|
+
@app.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/tidewave/railtie.rb
CHANGED
@@ -5,6 +5,7 @@ require "fileutils"
|
|
5
5
|
require "tidewave/configuration"
|
6
6
|
require "tidewave/middleware"
|
7
7
|
require "tidewave/exceptions_middleware"
|
8
|
+
require "tidewave/quiet_requests_middleware"
|
8
9
|
|
9
10
|
gem_tools_path = File.expand_path("tools/**/*.rb", __dir__)
|
10
11
|
Dir[gem_tools_path].each { |f| require f }
|
@@ -45,5 +46,10 @@ module Tidewave
|
|
45
46
|
|
46
47
|
app.middleware.insert_before(ActionDispatch::DebugExceptions, Tidewave::ExceptionsMiddleware)
|
47
48
|
end
|
49
|
+
|
50
|
+
initializer "tidewave.logging" do |app|
|
51
|
+
# Do not pollute user logs with tidewave requests.
|
52
|
+
app.middleware.insert_before(Rails::Rack::Logger, Tidewave::QuietRequestsMiddleware)
|
53
|
+
end
|
48
54
|
end
|
49
55
|
end
|
@@ -10,13 +10,20 @@ class Tidewave::Tools::GetLogs < Tidewave::Tools::Base
|
|
10
10
|
|
11
11
|
arguments do
|
12
12
|
required(:tail).filled(:integer).description("The number of log entries to return from the end of the log")
|
13
|
+
optional(:grep).filled(:string).description("Filter logs with the given regular expression (case insensitive). E.g. \"error\" when you want to capture errors in particular")
|
13
14
|
end
|
14
15
|
|
15
|
-
def call(tail:)
|
16
|
+
def call(tail:, grep: nil)
|
16
17
|
log_file = Rails.root.join("log", "#{Rails.env}.log")
|
17
18
|
return "Log file not found" unless File.exist?(log_file)
|
18
19
|
|
19
|
-
logs = File.readlines(log_file)
|
20
|
-
|
20
|
+
logs = File.readlines(log_file)
|
21
|
+
|
22
|
+
if grep
|
23
|
+
regex = Regexp.new(grep, Regexp::IGNORECASE)
|
24
|
+
logs = logs.select { |line| line.match?(regex) }
|
25
|
+
end
|
26
|
+
|
27
|
+
logs.last(tail).join
|
21
28
|
end
|
22
29
|
end
|
@@ -10,10 +10,11 @@ class Tidewave::Tools::GetSourceLocation < Tidewave::Tools::Base
|
|
10
10
|
such as `String`, an instance method, such as `String#gsub`, or class
|
11
11
|
method, such as `File.executable?`
|
12
12
|
|
13
|
-
This works
|
14
|
-
|
15
|
-
This tool only works if you know the specific constant/method being targeted.
|
13
|
+
This tool only works if you know the specific constant/method being targeted,
|
14
|
+
and it works across the current project and all dependencies.
|
16
15
|
If that is the case, prefer this tool over grepping the file system.
|
16
|
+
|
17
|
+
You may also get the root location of a gem, you can use "dep:PACKAGE_NAME".
|
17
18
|
DESCRIPTION
|
18
19
|
|
19
20
|
arguments do
|
@@ -21,6 +22,12 @@ class Tidewave::Tools::GetSourceLocation < Tidewave::Tools::Base
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def call(reference:)
|
25
|
+
# Check if this is a package location request
|
26
|
+
if reference.start_with?("dep:")
|
27
|
+
package_name = reference.gsub("dep:", "")
|
28
|
+
return get_package_location(package_name)
|
29
|
+
end
|
30
|
+
|
24
31
|
file_path, line_number = self.class.get_source_location(reference)
|
25
32
|
|
26
33
|
if file_path
|
@@ -36,6 +43,18 @@ class Tidewave::Tools::GetSourceLocation < Tidewave::Tools::Base
|
|
36
43
|
end
|
37
44
|
end
|
38
45
|
|
46
|
+
def get_package_location(package)
|
47
|
+
raise "dep: prefix only works with projects using Bundler" unless defined?(Bundler)
|
48
|
+
specs = Bundler.load.specs
|
49
|
+
|
50
|
+
spec = specs.find { |s| s.name == package }
|
51
|
+
if spec
|
52
|
+
spec.full_gem_path
|
53
|
+
else
|
54
|
+
raise "Package #{package} not found. Check your Gemfile for available packages."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
39
58
|
def self.get_source_location(reference)
|
40
59
|
constant_path, selector, method_name = reference.rpartition(/\.|#/)
|
41
60
|
|
@@ -20,7 +20,7 @@ class Tidewave::Tools::ProjectEval < Tidewave::Tools::Base
|
|
20
20
|
required(:code).filled(:string).description("The Ruby code to evaluate")
|
21
21
|
optional(:arguments).value(:array).description("The arguments to pass to evaluation. They are available inside the evaluated code as `arguments`.")
|
22
22
|
optional(:timeout).filled(:integer).description("The timeout in milliseconds. If the evaluation takes longer than this, it will be terminated. Defaults to 30000 (30 seconds).")
|
23
|
-
optional(:json).filled(:bool).description("Whether to return the result as JSON with structured output containing result, success, stdout, and stderr fields. Defaults to false.")
|
23
|
+
optional(:json).hidden().filled(:bool).description("Whether to return the result as JSON with structured output containing result, success, stdout, and stderr fields. Defaults to false.")
|
24
24
|
end
|
25
25
|
|
26
26
|
def call(code:, arguments: [], timeout: 30_000, json: false)
|
data/lib/tidewave/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tidewave
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yorick Jacquin
|
8
|
+
- José Valim
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2025-08
|
12
|
+
date: 2025-09-08 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: rails
|
@@ -68,22 +69,16 @@ files:
|
|
68
69
|
- lib/tidewave/database_adapters/active_record.rb
|
69
70
|
- lib/tidewave/database_adapters/sequel.rb
|
70
71
|
- lib/tidewave/exceptions_middleware.rb
|
71
|
-
- lib/tidewave/file_tracker.rb
|
72
72
|
- lib/tidewave/middleware.rb
|
73
|
+
- lib/tidewave/quiet_requests_middleware.rb
|
73
74
|
- lib/tidewave/railtie.rb
|
74
75
|
- lib/tidewave/tools/base.rb
|
75
|
-
- lib/tidewave/tools/edit_project_file.rb
|
76
76
|
- lib/tidewave/tools/execute_sql_query.rb
|
77
77
|
- lib/tidewave/tools/get_docs.rb
|
78
78
|
- lib/tidewave/tools/get_logs.rb
|
79
79
|
- lib/tidewave/tools/get_models.rb
|
80
|
-
- lib/tidewave/tools/get_package_location.rb
|
81
80
|
- lib/tidewave/tools/get_source_location.rb
|
82
|
-
- lib/tidewave/tools/list_project_files.rb
|
83
81
|
- lib/tidewave/tools/project_eval.rb
|
84
|
-
- lib/tidewave/tools/read_project_file.rb
|
85
|
-
- lib/tidewave/tools/shell_eval.rb
|
86
|
-
- lib/tidewave/tools/write_project_file.rb
|
87
82
|
- lib/tidewave/version.rb
|
88
83
|
homepage: https://tidewave.ai/
|
89
84
|
licenses:
|
@@ -1,102 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Tidewave
|
4
|
-
module FileTracker
|
5
|
-
extend self
|
6
|
-
|
7
|
-
def project_files(glob_pattern: nil, include_ignored: false)
|
8
|
-
args = [ "ls-files", "--cached", "--others" ]
|
9
|
-
args << "--exclude-standard" unless include_ignored
|
10
|
-
args << glob_pattern if glob_pattern
|
11
|
-
`git #{args.join(" ")}`.split("\n")
|
12
|
-
end
|
13
|
-
|
14
|
-
def read_file(path, line_offset: 0, count: nil)
|
15
|
-
full_path = file_full_path(path)
|
16
|
-
# Explicitly read the mtime first to avoid race conditions
|
17
|
-
mtime = File.mtime(full_path).to_i
|
18
|
-
content = File.read(full_path)
|
19
|
-
|
20
|
-
if line_offset > 0 || count
|
21
|
-
lines = content.lines
|
22
|
-
start_idx = [ line_offset, 0 ].max
|
23
|
-
count = (count || lines.length)
|
24
|
-
selected_lines = lines[start_idx, count]
|
25
|
-
content = selected_lines ? selected_lines.join : ""
|
26
|
-
end
|
27
|
-
|
28
|
-
[ mtime, content ]
|
29
|
-
end
|
30
|
-
|
31
|
-
def write_file(path, content)
|
32
|
-
validate_ruby_syntax!(content) if ruby_file?(path)
|
33
|
-
full_path = file_full_path(path)
|
34
|
-
|
35
|
-
# Create the directory if it doesn't exist
|
36
|
-
dirname = File.dirname(full_path)
|
37
|
-
FileUtils.mkdir_p(dirname)
|
38
|
-
|
39
|
-
# Write and return the file contents
|
40
|
-
File.write(full_path, content)
|
41
|
-
content
|
42
|
-
end
|
43
|
-
|
44
|
-
def file_full_path(path)
|
45
|
-
File.expand_path(path, Rails.root)
|
46
|
-
end
|
47
|
-
|
48
|
-
def validate_path_access!(path, validate_existence: true)
|
49
|
-
raise ArgumentError, "File path must not contain '..'" if path.include?("..")
|
50
|
-
|
51
|
-
# Ensure the path is within the project
|
52
|
-
full_path = file_full_path(path)
|
53
|
-
|
54
|
-
# Verify the file is within the project directory
|
55
|
-
unless full_path.start_with?(Rails.root.to_s + File::SEPARATOR)
|
56
|
-
raise ArgumentError, "File path must be within the project directory"
|
57
|
-
end
|
58
|
-
|
59
|
-
# Verify the file exists
|
60
|
-
if validate_existence && !File.exist?(full_path)
|
61
|
-
raise ArgumentError, "File not found: #{path}"
|
62
|
-
end
|
63
|
-
|
64
|
-
true
|
65
|
-
end
|
66
|
-
|
67
|
-
def validate_path_is_editable!(path, atime)
|
68
|
-
validate_path_access!(path)
|
69
|
-
validate_path_has_been_read_since_last_write!(path, atime)
|
70
|
-
|
71
|
-
true
|
72
|
-
end
|
73
|
-
|
74
|
-
def validate_path_is_writable!(path, atime)
|
75
|
-
validate_path_access!(path, validate_existence: false)
|
76
|
-
validate_path_has_been_read_since_last_write!(path, atime)
|
77
|
-
|
78
|
-
true
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def ruby_file?(path)
|
84
|
-
[ ".rb", ".rake", ".gemspec" ].include?(File.extname(path)) ||
|
85
|
-
[ "Gemfile" ].include?(File.basename(path))
|
86
|
-
end
|
87
|
-
|
88
|
-
def validate_ruby_syntax!(content)
|
89
|
-
RubyVM::AbstractSyntaxTree.parse(content)
|
90
|
-
rescue SyntaxError => e
|
91
|
-
raise "Invalid Ruby syntax: #{e.message}"
|
92
|
-
end
|
93
|
-
|
94
|
-
def validate_path_has_been_read_since_last_write!(path, atime)
|
95
|
-
if atime && File.mtime(file_full_path(path)).to_i > atime
|
96
|
-
raise ArgumentError, "File has been modified since last read, please read the file again"
|
97
|
-
end
|
98
|
-
|
99
|
-
true
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "tidewave/file_tracker"
|
4
|
-
|
5
|
-
class Tidewave::Tools::EditProjectFile < Tidewave::Tools::Base
|
6
|
-
tags :file_system_tool
|
7
|
-
|
8
|
-
tool_name "edit_project_file"
|
9
|
-
description <<~DESCRIPTION
|
10
|
-
A tool for editing parts of a file. It can find and replace text inside a file.
|
11
|
-
For moving or deleting files, use the shell_eval tool with 'mv' or 'rm' instead.
|
12
|
-
|
13
|
-
For large edits, use the write_project_file tool instead and overwrite the entire file.
|
14
|
-
|
15
|
-
Before editing, ensure to read the source file using the read_project_file tool.
|
16
|
-
|
17
|
-
To use this tool, provide the path to the file, the old_string to search for, and the new_string to replace it with.
|
18
|
-
If the old_string is found multiple times, an error will be returned. To ensure uniqueness, include a couple of lines
|
19
|
-
before and after the edit. All whitespace must be preserved as in the original file.
|
20
|
-
|
21
|
-
This tool can only do a single edit at a time. If you need to make multiple edits, you can create a message with
|
22
|
-
multiple tool calls to this tool, ensuring that each one contains enough context to uniquely identify the edit.
|
23
|
-
DESCRIPTION
|
24
|
-
|
25
|
-
arguments do
|
26
|
-
required(:path).filled(:string).description("The path to the file to edit. It is relative to the project root.")
|
27
|
-
required(:old_string).filled(:string).description("The string to search for")
|
28
|
-
required(:new_string).filled(:string).description("The string to replace the old_string with")
|
29
|
-
optional(:atime).filled(:integer).hidden.description("The Unix timestamp this file was last accessed. Not to be used.")
|
30
|
-
end
|
31
|
-
|
32
|
-
def call(path:, old_string:, new_string:, atime: nil)
|
33
|
-
# Check if the file exists within the project root and has been read
|
34
|
-
Tidewave::FileTracker.validate_path_is_editable!(path, atime)
|
35
|
-
|
36
|
-
_mtime, old_content = Tidewave::FileTracker.read_file(path)
|
37
|
-
|
38
|
-
# Ensure old_string is unique within the file
|
39
|
-
scan_result = old_content.scan(old_string)
|
40
|
-
raise ArgumentError, "old_string is not found" if scan_result.empty?
|
41
|
-
raise ArgumentError, "old_string is not unique" if scan_result.size > 1
|
42
|
-
|
43
|
-
new_content = old_content.sub(old_string, new_string)
|
44
|
-
Tidewave::FileTracker.write_file(path, new_content)
|
45
|
-
"OK"
|
46
|
-
end
|
47
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pathname"
|
4
|
-
|
5
|
-
class Tidewave::Tools::GetPackageLocation < Tidewave::Tools::Base
|
6
|
-
tool_name "get_package_location"
|
7
|
-
|
8
|
-
description <<~DESCRIPTION
|
9
|
-
Returns the location of dependency packages.
|
10
|
-
You can use this tool to get the location of any project dependency. Optionally,
|
11
|
-
a specific dependency name can be provided to only return the location of that dependency.
|
12
|
-
Use the result in combination with shell tools like grep to look for specific
|
13
|
-
code inside dependencies.
|
14
|
-
DESCRIPTION
|
15
|
-
|
16
|
-
arguments do
|
17
|
-
optional(:package).maybe(:string).description(
|
18
|
-
"The name of the package to get the location of. If not provided, the location of all packages will be returned."
|
19
|
-
)
|
20
|
-
end
|
21
|
-
|
22
|
-
def call(package: nil)
|
23
|
-
raise "get_package_location only works with projects using Bundler" unless defined?(Bundler)
|
24
|
-
specs = Bundler.load.specs
|
25
|
-
|
26
|
-
if package
|
27
|
-
spec = specs.find { |s| s.name == package }
|
28
|
-
if spec
|
29
|
-
spec.full_gem_path
|
30
|
-
else
|
31
|
-
raise "Package #{package} not found. Check your Gemfile for available packages."
|
32
|
-
end
|
33
|
-
else
|
34
|
-
# For all packages, return a formatted string with package names and locations
|
35
|
-
specs.map do |spec|
|
36
|
-
relative_path = Pathname.new(spec.full_gem_path).relative_path_from(Pathname.new(Dir.pwd))
|
37
|
-
"#{spec.name}: #{relative_path}"
|
38
|
-
end.join("\n")
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "tidewave/file_tracker"
|
4
|
-
|
5
|
-
class Tidewave::Tools::ListProjectFiles < Tidewave::Tools::Base
|
6
|
-
tags :file_system_tool
|
7
|
-
|
8
|
-
tool_name "list_project_files"
|
9
|
-
description <<~DESC
|
10
|
-
Returns a list of files in the project.
|
11
|
-
|
12
|
-
By default, when no arguments are passed, it returns all files in the project that
|
13
|
-
are not ignored by .gitignore.
|
14
|
-
|
15
|
-
Optionally, a glob_pattern can be passed to filter this list.
|
16
|
-
DESC
|
17
|
-
|
18
|
-
arguments do
|
19
|
-
optional(:glob_pattern).maybe(:string).description("Optional: a glob pattern to filter the listed files")
|
20
|
-
optional(:include_ignored).maybe(:bool).description("Optional: whether to include files that are ignored by .gitignore. Defaults to false. WARNING: Use with targeted glob patterns to avoid listing excessive files from dependencies or build directories.")
|
21
|
-
end
|
22
|
-
|
23
|
-
def call(glob_pattern: nil, include_ignored: false)
|
24
|
-
Tidewave::FileTracker.project_files(glob_pattern: glob_pattern, include_ignored: include_ignored)
|
25
|
-
end
|
26
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "tidewave/file_tracker"
|
4
|
-
|
5
|
-
class Tidewave::Tools::ReadProjectFile < Tidewave::Tools::Base
|
6
|
-
tags :file_system_tool
|
7
|
-
|
8
|
-
tool_name "read_project_file"
|
9
|
-
description <<~DESCRIPTION
|
10
|
-
Returns the contents of the given file.
|
11
|
-
Supports an optional line_offset and count. To read the full file, only the path needs to be passed.
|
12
|
-
DESCRIPTION
|
13
|
-
|
14
|
-
arguments do
|
15
|
-
required(:path).filled(:string).description("The path to the file to read. It is relative to the project root.")
|
16
|
-
optional(:line_offset).filled(:integer).description("Optional: the starting line offset from which to read. Defaults to 0.")
|
17
|
-
optional(:count).filled(:integer).description("Optional: the number of lines to read. Defaults to all.")
|
18
|
-
end
|
19
|
-
|
20
|
-
def call(path:, **keywords)
|
21
|
-
Tidewave::FileTracker.validate_path_access!(path)
|
22
|
-
_meta[:mtime], contents = Tidewave::FileTracker.read_file(path, **keywords)
|
23
|
-
contents
|
24
|
-
end
|
25
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "open3"
|
4
|
-
|
5
|
-
class Tidewave::Tools::ShellEval < Tidewave::Tools::Base
|
6
|
-
tags :file_system_tool
|
7
|
-
class CommandFailedError < StandardError; end
|
8
|
-
|
9
|
-
tool_name "shell_eval"
|
10
|
-
description <<~DESCRIPTION
|
11
|
-
Executes a shell command in the project root directory.
|
12
|
-
|
13
|
-
The operating system is of flavor #{RUBY_PLATFORM}.
|
14
|
-
|
15
|
-
Avoid using this tool for manipulating project files.
|
16
|
-
Instead rely on the tools with the name matching `*_project_files`.
|
17
|
-
|
18
|
-
Do not use this tool to evaluate Ruby code. Use `project_eval` instead.
|
19
|
-
Do not use this tool for commands that run indefinitely,
|
20
|
-
such as servers (like `bin/dev` or `npm run dev`),
|
21
|
-
REPLs (`bin/rails console`) or file watchers.
|
22
|
-
|
23
|
-
Only use this tool if other means are not available.
|
24
|
-
DESCRIPTION
|
25
|
-
|
26
|
-
arguments do
|
27
|
-
required(:command).filled(:string).description("The shell command to execute. Avoid using this for file operations; use dedicated file system tools instead.")
|
28
|
-
end
|
29
|
-
|
30
|
-
def call(command:)
|
31
|
-
stdout, status = Open3.capture2e(command)
|
32
|
-
raise CommandFailedError, "Command failed with status #{status.exitstatus}:\n\n#{stdout}" unless status.exitstatus.zero?
|
33
|
-
|
34
|
-
stdout.strip
|
35
|
-
end
|
36
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "tidewave/file_tracker"
|
4
|
-
|
5
|
-
class Tidewave::Tools::WriteProjectFile < Tidewave::Tools::Base
|
6
|
-
tags :file_system_tool
|
7
|
-
|
8
|
-
tool_name "write_project_file"
|
9
|
-
description <<~DESCRIPTION
|
10
|
-
Writes a file to the file system. If the file already exists, it will be overwritten.
|
11
|
-
|
12
|
-
Note that this tool will fail if the file wasn't previously read with the `read_project_file` tool.
|
13
|
-
DESCRIPTION
|
14
|
-
|
15
|
-
arguments do
|
16
|
-
required(:path).filled(:string).description("The path to the file to write. It is relative to the project root.")
|
17
|
-
required(:content).filled(:string).description("The content to write to the file")
|
18
|
-
optional(:atime).filled(:integer).hidden.description("The Unix timestamp this file was last accessed. Not to be used.")
|
19
|
-
end
|
20
|
-
|
21
|
-
def call(path:, content:, atime: nil)
|
22
|
-
Tidewave::FileTracker.validate_path_is_writable!(path, atime)
|
23
|
-
Tidewave::FileTracker.write_file(path, content)
|
24
|
-
_meta[:mtime] = Time.now.to_i
|
25
|
-
|
26
|
-
"OK"
|
27
|
-
end
|
28
|
-
end
|