ruby_coded 0.2.2 → 0.3.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/CHANGELOG.md +30 -0
- data/README.md +88 -3
- data/lib/ruby_coded/auth/jwt_decoder.rb +14 -0
- data/lib/ruby_coded/chat/app.rb +23 -4
- data/lib/ruby_coded/chat/codex_bridge/error_handling.rb +68 -10
- data/lib/ruby_coded/chat/codex_bridge/sse_parser.rb +20 -0
- data/lib/ruby_coded/chat/codex_models.rb +36 -7
- data/lib/ruby_coded/chat/command_handler/custom_commands.rb +91 -0
- data/lib/ruby_coded/chat/command_handler/model_commands.rb +8 -1
- data/lib/ruby_coded/chat/command_handler.rb +64 -36
- data/lib/ruby_coded/chat/help.txt +0 -20
- data/lib/ruby_coded/chat/renderer/model_selector.rb +4 -1
- data/lib/ruby_coded/chat/renderer/status_bar.rb +7 -0
- data/lib/ruby_coded/chat/state/context_window.rb +59 -0
- data/lib/ruby_coded/chat/state/message_token_tracking.rb +16 -0
- data/lib/ruby_coded/chat/state.rb +19 -3
- data/lib/ruby_coded/commands/catalog.rb +170 -0
- data/lib/ruby_coded/commands/command_definition.rb +30 -0
- data/lib/ruby_coded/commands/core_provider.rb +94 -0
- data/lib/ruby_coded/commands/markdown_loader.rb +101 -0
- data/lib/ruby_coded/commands/markdown_provider.rb +45 -0
- data/lib/ruby_coded/commands/plugin_provider.rb +38 -0
- data/lib/ruby_coded/commands.rb +8 -0
- data/lib/ruby_coded/plugins/command_completion/state_extension.rb +5 -18
- data/lib/ruby_coded/tools/git_add_tool.rb +55 -0
- data/lib/ruby_coded/tools/git_base_tool.rb +67 -0
- data/lib/ruby_coded/tools/git_commit_tool.rb +45 -0
- data/lib/ruby_coded/tools/git_diff_tool.rb +23 -0
- data/lib/ruby_coded/tools/git_status_tool.rb +17 -0
- data/lib/ruby_coded/tools/registry.rb +12 -2
- data/lib/ruby_coded/tools/run_command_tool.rb +8 -1
- data/lib/ruby_coded/tools/system_prompt.rb +3 -0
- data/lib/ruby_coded/version.rb +1 -1
- data/lib/ruby_coded.rb +1 -0
- metadata +16 -2
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module RubyCoded
|
|
6
|
+
module Commands
|
|
7
|
+
# Loads project-local markdown command files.
|
|
8
|
+
class MarkdownLoader
|
|
9
|
+
def initialize(project_root:)
|
|
10
|
+
@project_root = project_root
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def load_files
|
|
14
|
+
load_report[:entries]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def load_report
|
|
18
|
+
return empty_report unless Dir.exist?(commands_dir)
|
|
19
|
+
|
|
20
|
+
build_report(command_paths)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def empty_report
|
|
26
|
+
{ entries: [], invalid_count: 0, invalid_files: [] }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_report(paths)
|
|
30
|
+
entries, invalid_files = paths.each_with_object([[], []]) do |path, memo|
|
|
31
|
+
collect_report_entry(path, *memo)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
entries: entries,
|
|
36
|
+
invalid_count: invalid_files.size,
|
|
37
|
+
invalid_files: invalid_files
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def collect_report_entry(path, entries, invalid_files)
|
|
42
|
+
parsed = parse_file(path)
|
|
43
|
+
parsed ? entries << parsed : invalid_files << File.basename(path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def command_paths
|
|
47
|
+
Dir.glob(File.join(commands_dir, "*.md"))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def commands_dir
|
|
51
|
+
File.join(@project_root, ".ruby_coded", "commands")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def parse_file(path)
|
|
55
|
+
frontmatter, body = extract_frontmatter(File.read(path))
|
|
56
|
+
return nil unless frontmatter
|
|
57
|
+
|
|
58
|
+
build_entry(path, extract_attributes(frontmatter, body))
|
|
59
|
+
rescue StandardError
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def extract_attributes(frontmatter, body)
|
|
64
|
+
data = YAML.safe_load(frontmatter) || {}
|
|
65
|
+
{
|
|
66
|
+
command: data["command"]&.strip,
|
|
67
|
+
description: data["description"]&.strip,
|
|
68
|
+
usage: data["usage"]&.strip,
|
|
69
|
+
content: body.to_s.strip
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def build_entry(path, attrs)
|
|
74
|
+
return nil unless valid_entry?(attrs)
|
|
75
|
+
|
|
76
|
+
attrs.merge(path: path)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def valid_entry?(attrs)
|
|
80
|
+
valid_command_name?(attrs[:command]) &&
|
|
81
|
+
!attrs[:description].to_s.empty? &&
|
|
82
|
+
!attrs[:content].to_s.empty?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def extract_frontmatter(raw)
|
|
86
|
+
match = raw.match(/\A---\s*\n(.*?)\n---\s*\n?(.*)\z/m)
|
|
87
|
+
return [nil, nil] unless match
|
|
88
|
+
|
|
89
|
+
[match[1], match[2]]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def valid_command_name?(name)
|
|
93
|
+
return false if name.to_s.empty?
|
|
94
|
+
return false unless name.start_with?("/")
|
|
95
|
+
return false if name.include?(" ")
|
|
96
|
+
|
|
97
|
+
true
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "command_definition"
|
|
4
|
+
require_relative "markdown_loader"
|
|
5
|
+
|
|
6
|
+
module RubyCoded
|
|
7
|
+
module Commands
|
|
8
|
+
# Converts markdown command files into command definitions.
|
|
9
|
+
class MarkdownProvider
|
|
10
|
+
def initialize(project_root:)
|
|
11
|
+
@loader = MarkdownLoader.new(project_root: project_root)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def definitions
|
|
15
|
+
load_report[:definitions]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def load_report
|
|
19
|
+
report = @loader.load_report
|
|
20
|
+
{
|
|
21
|
+
definitions: build_definitions(report[:entries]),
|
|
22
|
+
invalid_count: report[:invalid_count],
|
|
23
|
+
invalid_files: report[:invalid_files]
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def build_definitions(entries)
|
|
30
|
+
entries.map { |entry| build_definition(entry) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def build_definition(entry)
|
|
34
|
+
CommandDefinition.new(
|
|
35
|
+
name: entry[:command],
|
|
36
|
+
description: entry[:description],
|
|
37
|
+
source: :markdown,
|
|
38
|
+
usage: entry[:usage] || entry[:command],
|
|
39
|
+
content: entry[:content],
|
|
40
|
+
path: entry[:path]
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "command_definition"
|
|
4
|
+
|
|
5
|
+
module RubyCoded
|
|
6
|
+
module Commands
|
|
7
|
+
# Adapts plugin-registered commands to the unified command catalog.
|
|
8
|
+
class PluginProvider
|
|
9
|
+
def initialize(registry:)
|
|
10
|
+
@registry = registry
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def definitions
|
|
14
|
+
commands.map { |name, handler| build_definition(name, handler) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def commands
|
|
20
|
+
@registry.all_commands
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def descriptions
|
|
24
|
+
@registry.all_command_descriptions
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def build_definition(name, handler)
|
|
28
|
+
CommandDefinition.new(
|
|
29
|
+
name: name,
|
|
30
|
+
description: descriptions[name] || "Plugin command",
|
|
31
|
+
handler: handler,
|
|
32
|
+
source: :plugin,
|
|
33
|
+
usage: name
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "commands/command_definition"
|
|
4
|
+
require_relative "commands/core_provider"
|
|
5
|
+
require_relative "commands/plugin_provider"
|
|
6
|
+
require_relative "commands/markdown_loader"
|
|
7
|
+
require_relative "commands/markdown_provider"
|
|
8
|
+
require_relative "commands/catalog"
|
|
@@ -5,19 +5,6 @@ module RubyCoded
|
|
|
5
5
|
module CommandCompletion
|
|
6
6
|
# Mixed into Chat::State to add command-completion tracking.
|
|
7
7
|
module StateExtension
|
|
8
|
-
COMMAND_INFO = {
|
|
9
|
-
"/help" => "Show help message",
|
|
10
|
-
"/model" => "Select or switch model",
|
|
11
|
-
"/clear" => "Clear conversation history",
|
|
12
|
-
"/history" => "Show conversation summary",
|
|
13
|
-
"/tokens" => "Show detailed token usage and cost",
|
|
14
|
-
"/agent" => "Toggle agent mode (on/off)",
|
|
15
|
-
"/plan" => "Toggle plan mode (on/off/save)",
|
|
16
|
-
"/login" => "Authenticate with an AI provider",
|
|
17
|
-
"/exit" => "Exit the chat",
|
|
18
|
-
"/quit" => "Exit the chat"
|
|
19
|
-
}.freeze
|
|
20
|
-
|
|
21
8
|
def self.included(base)
|
|
22
9
|
base.attr_reader :command_completion_index
|
|
23
10
|
end
|
|
@@ -36,8 +23,8 @@ module RubyCoded
|
|
|
36
23
|
def command_suggestions
|
|
37
24
|
prefix = @input_buffer.downcase
|
|
38
25
|
all_descriptions = merged_command_descriptions
|
|
39
|
-
all_descriptions.select { |cmd, _| cmd.start_with?(prefix) }
|
|
40
|
-
.sort_by { |cmd, _| cmd }
|
|
26
|
+
all_descriptions.select { |cmd, _| cmd.downcase.start_with?(prefix) }
|
|
27
|
+
.sort_by { |cmd, _| cmd.downcase }
|
|
41
28
|
end
|
|
42
29
|
|
|
43
30
|
def current_command_suggestion
|
|
@@ -81,9 +68,9 @@ module RubyCoded
|
|
|
81
68
|
private
|
|
82
69
|
|
|
83
70
|
def merged_command_descriptions
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
return {} unless respond_to?(:command_catalog) && command_catalog
|
|
72
|
+
|
|
73
|
+
command_catalog.command_descriptions
|
|
87
74
|
end
|
|
88
75
|
end
|
|
89
76
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "git_base_tool"
|
|
4
|
+
|
|
5
|
+
module RubyCoded
|
|
6
|
+
module Tools
|
|
7
|
+
# Stage specific files or all current changes in the git repository.
|
|
8
|
+
class GitAddTool < GitBaseTool
|
|
9
|
+
description "Stage files in the git repository. Provide paths or set all to true to stage everything."
|
|
10
|
+
risk :confirm
|
|
11
|
+
|
|
12
|
+
params do
|
|
13
|
+
array :paths, of: :string,
|
|
14
|
+
description: "Relative file paths to stage",
|
|
15
|
+
required: false
|
|
16
|
+
boolean :all, description: "Stage all tracked and untracked changes", required: false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def execute(paths: nil, all: false)
|
|
20
|
+
return stage_all if all
|
|
21
|
+
|
|
22
|
+
stage_paths(paths)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def stage_all
|
|
28
|
+
result = run_git_command("add", "--all")
|
|
29
|
+
return result if result.is_a?(Hash)
|
|
30
|
+
|
|
31
|
+
"Staged all changes.\n#{result}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def stage_paths(paths)
|
|
35
|
+
normalized_paths = Array(paths).map(&:to_s).reject(&:empty?)
|
|
36
|
+
return { error: "Provide at least one path or set all to true." } if normalized_paths.empty?
|
|
37
|
+
|
|
38
|
+
invalid = invalid_paths(normalized_paths)
|
|
39
|
+
return { error: "Paths are outside the project directory: #{invalid.join(", ")}" } unless invalid.empty?
|
|
40
|
+
|
|
41
|
+
result = run_git_command("add", *normalized_paths)
|
|
42
|
+
return result if result.is_a?(Hash)
|
|
43
|
+
|
|
44
|
+
"Staged paths: #{normalized_paths.join(", ")}\n#{result}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def invalid_paths(normalized_paths)
|
|
48
|
+
normalized_paths.filter_map do |path|
|
|
49
|
+
validated = validate_path!(path)
|
|
50
|
+
path if validated.is_a?(Hash)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
require_relative "base_tool"
|
|
6
|
+
|
|
7
|
+
module RubyCoded
|
|
8
|
+
module Tools
|
|
9
|
+
# Shared helpers for git-specific tools.
|
|
10
|
+
class GitBaseTool < BaseTool
|
|
11
|
+
GIT_ENV = {
|
|
12
|
+
"GIT_EDITOR" => "true",
|
|
13
|
+
"EDITOR" => "true",
|
|
14
|
+
"VISUAL" => "true",
|
|
15
|
+
"GIT_PAGER" => "cat",
|
|
16
|
+
"PAGER" => "cat"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
MAX_OUTPUT_CHARS = 5000
|
|
20
|
+
|
|
21
|
+
def git_repo?
|
|
22
|
+
Dir.exist?(File.join(@project_root, ".git"))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def ensure_git_repo!
|
|
26
|
+
return nil if git_repo?
|
|
27
|
+
|
|
28
|
+
{ error: "Not a git repository: #{@project_root}" }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def run_git_command(*)
|
|
32
|
+
repo_error = ensure_git_repo!
|
|
33
|
+
return repo_error if repo_error
|
|
34
|
+
|
|
35
|
+
stdout, stderr, status = Open3.capture3(GIT_ENV, "git", *, chdir: @project_root)
|
|
36
|
+
format_git_result(stdout, stderr, status)
|
|
37
|
+
rescue Errno::ENOENT => e
|
|
38
|
+
{ error: "Git executable not found: #{e.message}" }
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
{ error: "Git command failed: #{e.message}" }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def format_git_result(stdout, stderr, status)
|
|
44
|
+
output = String.new
|
|
45
|
+
output << stdout unless stdout.empty?
|
|
46
|
+
output << "\nSTDERR:\n#{stderr}" unless stderr.empty?
|
|
47
|
+
output = output.strip
|
|
48
|
+
output = "(no output)" if output.empty?
|
|
49
|
+
output = truncate_output(output)
|
|
50
|
+
|
|
51
|
+
return output if status.success?
|
|
52
|
+
|
|
53
|
+
{ error: output }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def truncate_output(output)
|
|
57
|
+
return output if output.length <= MAX_OUTPUT_CHARS
|
|
58
|
+
|
|
59
|
+
"#{output[0, MAX_OUTPUT_CHARS]}...(truncated, #{output.length} total characters)"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def shell_join(parts)
|
|
63
|
+
parts.map { |part| Shellwords.escape(part.to_s) }.join(" ")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "git_base_tool"
|
|
4
|
+
|
|
5
|
+
module RubyCoded
|
|
6
|
+
module Tools
|
|
7
|
+
# Create a non-interactive git commit.
|
|
8
|
+
class GitCommitTool < GitBaseTool
|
|
9
|
+
description "Create a git commit with a message. Supports staging all changes first if requested."
|
|
10
|
+
risk :confirm
|
|
11
|
+
|
|
12
|
+
params do
|
|
13
|
+
string :message, description: "Commit message"
|
|
14
|
+
boolean :add_all, description: "Stage all changes before committing", required: false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def execute(message:, add_all: false)
|
|
18
|
+
msg = message.to_s.strip
|
|
19
|
+
return { error: "Commit message cannot be empty." } if msg.empty?
|
|
20
|
+
|
|
21
|
+
if add_all
|
|
22
|
+
add_result = run_git_command("add", "--all")
|
|
23
|
+
return add_result if add_result.is_a?(Hash)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
result = run_git_command("commit", "-m", msg)
|
|
27
|
+
return enhance_commit_error(result) if result.is_a?(Hash)
|
|
28
|
+
|
|
29
|
+
prefix = add_all ? "Staged all changes and created commit." : "Created commit."
|
|
30
|
+
"#{prefix}\n#{result}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def enhance_commit_error(result)
|
|
36
|
+
message = result[:error].to_s
|
|
37
|
+
|
|
38
|
+
return { error: "Nothing to commit. Working tree clean or no staged changes." } if message.include?("nothing to commit")
|
|
39
|
+
return { error: "Git user identity is not configured. Set user.name and user.email before committing." } if message.include?("Author identity unknown")
|
|
40
|
+
|
|
41
|
+
result
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "git_base_tool"
|
|
4
|
+
|
|
5
|
+
module RubyCoded
|
|
6
|
+
module Tools
|
|
7
|
+
# Show git diff output for the project repository.
|
|
8
|
+
class GitDiffTool < GitBaseTool
|
|
9
|
+
description "Show git diff output for the project repository. By default shows unstaged changes."
|
|
10
|
+
risk :safe
|
|
11
|
+
|
|
12
|
+
params do
|
|
13
|
+
boolean :staged, description: "Show staged changes instead of unstaged changes", required: false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute(staged: false)
|
|
17
|
+
args = ["diff"]
|
|
18
|
+
args << "--cached" if staged
|
|
19
|
+
run_git_command(*args)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "git_base_tool"
|
|
4
|
+
|
|
5
|
+
module RubyCoded
|
|
6
|
+
module Tools
|
|
7
|
+
# Show the current git working tree status.
|
|
8
|
+
class GitStatusTool < GitBaseTool
|
|
9
|
+
description "Show the current git working tree status for the project repository"
|
|
10
|
+
risk :safe
|
|
11
|
+
|
|
12
|
+
def execute
|
|
13
|
+
run_git_command("status", "--short", "--branch")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -7,6 +7,10 @@ require_relative "edit_file_tool"
|
|
|
7
7
|
require_relative "create_directory_tool"
|
|
8
8
|
require_relative "delete_path_tool"
|
|
9
9
|
require_relative "run_command_tool"
|
|
10
|
+
require_relative "git_status_tool"
|
|
11
|
+
require_relative "git_diff_tool"
|
|
12
|
+
require_relative "git_add_tool"
|
|
13
|
+
require_relative "git_commit_tool"
|
|
10
14
|
|
|
11
15
|
module RubyCoded
|
|
12
16
|
module Tools
|
|
@@ -14,7 +18,9 @@ module RubyCoded
|
|
|
14
18
|
class Registry
|
|
15
19
|
READONLY_TOOL_CLASSES = [
|
|
16
20
|
ReadFileTool,
|
|
17
|
-
ListDirectoryTool
|
|
21
|
+
ListDirectoryTool,
|
|
22
|
+
GitStatusTool,
|
|
23
|
+
GitDiffTool
|
|
18
24
|
].freeze
|
|
19
25
|
|
|
20
26
|
TOOL_CLASSES = [
|
|
@@ -23,7 +29,11 @@ module RubyCoded
|
|
|
23
29
|
EditFileTool,
|
|
24
30
|
CreateDirectoryTool,
|
|
25
31
|
DeletePathTool,
|
|
26
|
-
RunCommandTool
|
|
32
|
+
RunCommandTool,
|
|
33
|
+
GitStatusTool,
|
|
34
|
+
GitDiffTool,
|
|
35
|
+
GitAddTool,
|
|
36
|
+
GitCommitTool
|
|
27
37
|
].freeze
|
|
28
38
|
|
|
29
39
|
def initialize(project_root:)
|
|
@@ -12,6 +12,13 @@ module RubyCoded
|
|
|
12
12
|
|
|
13
13
|
TIMEOUT_SECONDS = 30
|
|
14
14
|
MAX_OUTPUT_CHARS = 5000
|
|
15
|
+
COMMAND_ENV = {
|
|
16
|
+
"GIT_EDITOR" => "true",
|
|
17
|
+
"EDITOR" => "true",
|
|
18
|
+
"VISUAL" => "true",
|
|
19
|
+
"GIT_PAGER" => "cat",
|
|
20
|
+
"PAGER" => "cat"
|
|
21
|
+
}.freeze
|
|
15
22
|
|
|
16
23
|
params do
|
|
17
24
|
string :command, description: "The shell command to execute"
|
|
@@ -35,7 +42,7 @@ module RubyCoded
|
|
|
35
42
|
private
|
|
36
43
|
|
|
37
44
|
def run_with_timeout(command)
|
|
38
|
-
stdin, stdout_io, stderr_io, wait_thr = Open3.popen3(command, chdir: @project_root)
|
|
45
|
+
stdin, stdout_io, stderr_io, wait_thr = Open3.popen3(COMMAND_ENV, command, chdir: @project_root)
|
|
39
46
|
stdin.close
|
|
40
47
|
|
|
41
48
|
unless wait_thr.join(TIMEOUT_SECONDS)
|
|
@@ -15,6 +15,9 @@ module RubyCoded
|
|
|
15
15
|
- The user will be asked to confirm destructive operations (write, edit, delete).
|
|
16
16
|
- When listing directories, start with the project root to orient yourself.
|
|
17
17
|
- Be concise in your explanations but thorough in your actions.
|
|
18
|
+
- Git workflows are allowed when the user asks for them. Prefer dedicated git tools for status, diff, add, and commit.
|
|
19
|
+
- For git commits, always use a non-interactive commit message and never rely on opening an editor.
|
|
20
|
+
- Do not push, pull, fetch, rebase, or perform other remote/history-rewriting git actions unless the user explicitly asks.
|
|
18
21
|
|
|
19
22
|
Efficiency:
|
|
20
23
|
- You have a budget of %<max_write_rounds>d write/edit/delete tool calls that auto-resets when reached, and a hard limit of %<max_total_rounds>d total tool calls per request.
|
data/lib/ruby_coded/version.rb
CHANGED
data/lib/ruby_coded.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "ruby_coded/config/user_config"
|
|
|
5
5
|
require_relative "ruby_coded/auth/auth_manager"
|
|
6
6
|
require_relative "ruby_coded/initializer"
|
|
7
7
|
require_relative "ruby_coded/plugins"
|
|
8
|
+
require_relative "ruby_coded/commands"
|
|
8
9
|
|
|
9
10
|
raise "This gem requires Ruby 3.3.0 or higher" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.3.0")
|
|
10
11
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_coded
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cesar Rodriguez
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -137,6 +137,7 @@ files:
|
|
|
137
137
|
- lib/ruby_coded/chat/codex_models.rb
|
|
138
138
|
- lib/ruby_coded/chat/command_handler.rb
|
|
139
139
|
- lib/ruby_coded/chat/command_handler/agent_commands.rb
|
|
140
|
+
- lib/ruby_coded/chat/command_handler/custom_commands.rb
|
|
140
141
|
- lib/ruby_coded/chat/command_handler/history_commands.rb
|
|
141
142
|
- lib/ruby_coded/chat/command_handler/login_commands.rb
|
|
142
143
|
- lib/ruby_coded/chat/command_handler/model_commands.rb
|
|
@@ -165,6 +166,7 @@ files:
|
|
|
165
166
|
- lib/ruby_coded/chat/renderer/plan_clarifier_layout.rb
|
|
166
167
|
- lib/ruby_coded/chat/renderer/status_bar.rb
|
|
167
168
|
- lib/ruby_coded/chat/state.rb
|
|
169
|
+
- lib/ruby_coded/chat/state/context_window.rb
|
|
168
170
|
- lib/ruby_coded/chat/state/login_flow.rb
|
|
169
171
|
- lib/ruby_coded/chat/state/login_flow_steps.rb
|
|
170
172
|
- lib/ruby_coded/chat/state/message_assistant.rb
|
|
@@ -175,6 +177,13 @@ files:
|
|
|
175
177
|
- lib/ruby_coded/chat/state/scrollable.rb
|
|
176
178
|
- lib/ruby_coded/chat/state/token_cost.rb
|
|
177
179
|
- lib/ruby_coded/chat/state/tool_confirmation.rb
|
|
180
|
+
- lib/ruby_coded/commands.rb
|
|
181
|
+
- lib/ruby_coded/commands/catalog.rb
|
|
182
|
+
- lib/ruby_coded/commands/command_definition.rb
|
|
183
|
+
- lib/ruby_coded/commands/core_provider.rb
|
|
184
|
+
- lib/ruby_coded/commands/markdown_loader.rb
|
|
185
|
+
- lib/ruby_coded/commands/markdown_provider.rb
|
|
186
|
+
- lib/ruby_coded/commands/plugin_provider.rb
|
|
178
187
|
- lib/ruby_coded/config/user_config.rb
|
|
179
188
|
- lib/ruby_coded/errors/auth_error.rb
|
|
180
189
|
- lib/ruby_coded/initializer.rb
|
|
@@ -195,6 +204,11 @@ files:
|
|
|
195
204
|
- lib/ruby_coded/tools/create_directory_tool.rb
|
|
196
205
|
- lib/ruby_coded/tools/delete_path_tool.rb
|
|
197
206
|
- lib/ruby_coded/tools/edit_file_tool.rb
|
|
207
|
+
- lib/ruby_coded/tools/git_add_tool.rb
|
|
208
|
+
- lib/ruby_coded/tools/git_base_tool.rb
|
|
209
|
+
- lib/ruby_coded/tools/git_commit_tool.rb
|
|
210
|
+
- lib/ruby_coded/tools/git_diff_tool.rb
|
|
211
|
+
- lib/ruby_coded/tools/git_status_tool.rb
|
|
198
212
|
- lib/ruby_coded/tools/list_directory_tool.rb
|
|
199
213
|
- lib/ruby_coded/tools/plan_system_prompt.rb
|
|
200
214
|
- lib/ruby_coded/tools/read_file_tool.rb
|