ruby_coded 0.1.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 +7 -0
- data/.github/workflows/ci.yml +76 -0
- data/.github/workflows/release.yml +24 -0
- data/.rubocop_todo.yml +122 -0
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/Rakefile +12 -0
- data/exe/ruby_coded +6 -0
- data/lib/ruby_coded/auth/auth_manager.rb +145 -0
- data/lib/ruby_coded/auth/callback_servlet.rb +41 -0
- data/lib/ruby_coded/auth/credentials_store.rb +35 -0
- data/lib/ruby_coded/auth/oauth_callback_server.rb +38 -0
- data/lib/ruby_coded/auth/pkce.rb +19 -0
- data/lib/ruby_coded/auth/providers/anthropic.rb +32 -0
- data/lib/ruby_coded/auth/providers/openai.rb +55 -0
- data/lib/ruby_coded/chat/app/event_dispatch.rb +78 -0
- data/lib/ruby_coded/chat/app.rb +104 -0
- data/lib/ruby_coded/chat/command_handler/agent_commands.rb +53 -0
- data/lib/ruby_coded/chat/command_handler/history_commands.rb +38 -0
- data/lib/ruby_coded/chat/command_handler/model_commands.rb +91 -0
- data/lib/ruby_coded/chat/command_handler/plan_commands.rb +112 -0
- data/lib/ruby_coded/chat/command_handler/token_commands.rb +128 -0
- data/lib/ruby_coded/chat/command_handler/token_formatting.rb +26 -0
- data/lib/ruby_coded/chat/command_handler.rb +89 -0
- data/lib/ruby_coded/chat/help.txt +28 -0
- data/lib/ruby_coded/chat/input_handler/modal_inputs.rb +102 -0
- data/lib/ruby_coded/chat/input_handler/normal_mode_input.rb +116 -0
- data/lib/ruby_coded/chat/input_handler.rb +39 -0
- data/lib/ruby_coded/chat/llm_bridge/plan_mode.rb +73 -0
- data/lib/ruby_coded/chat/llm_bridge/streaming_retries.rb +86 -0
- data/lib/ruby_coded/chat/llm_bridge/tool_call_handling.rb +129 -0
- data/lib/ruby_coded/chat/llm_bridge.rb +131 -0
- data/lib/ruby_coded/chat/model_filter.rb +115 -0
- data/lib/ruby_coded/chat/plan_clarification_parser.rb +38 -0
- data/lib/ruby_coded/chat/renderer/chat_panel.rb +128 -0
- data/lib/ruby_coded/chat/renderer/chat_panel_input.rb +56 -0
- data/lib/ruby_coded/chat/renderer/chat_panel_thinking.rb +124 -0
- data/lib/ruby_coded/chat/renderer/model_selector.rb +96 -0
- data/lib/ruby_coded/chat/renderer/plan_clarifier.rb +112 -0
- data/lib/ruby_coded/chat/renderer/plan_clarifier_layout.rb +42 -0
- data/lib/ruby_coded/chat/renderer/status_bar.rb +47 -0
- data/lib/ruby_coded/chat/renderer.rb +64 -0
- data/lib/ruby_coded/chat/state/message_assistant.rb +77 -0
- data/lib/ruby_coded/chat/state/message_token_tracking.rb +57 -0
- data/lib/ruby_coded/chat/state/messages.rb +70 -0
- data/lib/ruby_coded/chat/state/model_selection.rb +79 -0
- data/lib/ruby_coded/chat/state/plan_tracking.rb +140 -0
- data/lib/ruby_coded/chat/state/scrollable.rb +42 -0
- data/lib/ruby_coded/chat/state/token_cost.rb +128 -0
- data/lib/ruby_coded/chat/state/tool_confirmation.rb +129 -0
- data/lib/ruby_coded/chat/state.rb +205 -0
- data/lib/ruby_coded/config/user_config.rb +110 -0
- data/lib/ruby_coded/errors/auth_error.rb +12 -0
- data/lib/ruby_coded/initializer/cover.rb +29 -0
- data/lib/ruby_coded/initializer.rb +52 -0
- data/lib/ruby_coded/plugins/base.rb +44 -0
- data/lib/ruby_coded/plugins/command_completion/input_extension.rb +30 -0
- data/lib/ruby_coded/plugins/command_completion/plugin.rb +27 -0
- data/lib/ruby_coded/plugins/command_completion/renderer_extension.rb +54 -0
- data/lib/ruby_coded/plugins/command_completion/state_extension.rb +90 -0
- data/lib/ruby_coded/plugins/registry.rb +88 -0
- data/lib/ruby_coded/plugins.rb +21 -0
- data/lib/ruby_coded/strategies/api_key_strategy.rb +39 -0
- data/lib/ruby_coded/strategies/base.rb +37 -0
- data/lib/ruby_coded/strategies/oauth_strategy.rb +106 -0
- data/lib/ruby_coded/tools/agent_cancelled_error.rb +7 -0
- data/lib/ruby_coded/tools/agent_iteration_limit_error.rb +7 -0
- data/lib/ruby_coded/tools/base_tool.rb +50 -0
- data/lib/ruby_coded/tools/create_directory_tool.rb +34 -0
- data/lib/ruby_coded/tools/delete_path_tool.rb +50 -0
- data/lib/ruby_coded/tools/edit_file_tool.rb +40 -0
- data/lib/ruby_coded/tools/list_directory_tool.rb +53 -0
- data/lib/ruby_coded/tools/plan_system_prompt.rb +72 -0
- data/lib/ruby_coded/tools/read_file_tool.rb +54 -0
- data/lib/ruby_coded/tools/registry.rb +66 -0
- data/lib/ruby_coded/tools/run_command_tool.rb +75 -0
- data/lib/ruby_coded/tools/system_prompt.rb +32 -0
- data/lib/ruby_coded/tools/tool_rejected_error.rb +7 -0
- data/lib/ruby_coded/tools/write_file_tool.rb +31 -0
- data/lib/ruby_coded/version.rb +10 -0
- data/lib/ruby_coded.rb +16 -0
- data/sig/ruby_coded.rbs +4 -0
- metadata +206 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f81632e5f552b3eb5e4e85d15ae4ecda0767c2f530606ccc083c9c1adcf9c92f
|
|
4
|
+
data.tar.gz: 33759f15b1568c1f0e2c88fbc32d9e97ca3772662aeaa0112acceb1819ab7a9a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d004abb1a5c5197cffc6b28fffb44172ed6b4cb22b62cf3438941d8c67b2869d7244df4d7c60cc63d1a28ce0dbd6dd20e5ab6fadff6b3e3f4bc89780d7c54d7a
|
|
7
|
+
data.tar.gz: ca3ff79da79a9fba4b2166deedb5eaa52626aaad0a3429dcb9ef75134fd831c852f4e1e3ff66d8d71cc234dc23e1a0c60b90f8dcab7166deaa5675e806052294
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test (Ruby ${{ matrix.ruby }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
ruby: ["3.3", "3.4"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- name: Remove Gemfile.lock for CI compatibility
|
|
20
|
+
run: rm -f Gemfile.lock
|
|
21
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
22
|
+
- uses: ruby/setup-ruby@v1
|
|
23
|
+
with:
|
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
|
25
|
+
bundler-cache: true
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: bundle exec rake test
|
|
28
|
+
|
|
29
|
+
test-head:
|
|
30
|
+
name: Test (Ruby head)
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
continue-on-error: true
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- name: Remove Gemfile.lock for CI compatibility
|
|
36
|
+
run: rm -f Gemfile.lock
|
|
37
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
38
|
+
- uses: ruby/setup-ruby@v1
|
|
39
|
+
with:
|
|
40
|
+
ruby-version: head
|
|
41
|
+
bundler-cache: true
|
|
42
|
+
- name: Run tests
|
|
43
|
+
run: bundle exec rake test
|
|
44
|
+
|
|
45
|
+
lint:
|
|
46
|
+
name: RuboCop
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
- name: Remove Gemfile.lock for CI compatibility
|
|
51
|
+
run: rm -f Gemfile.lock
|
|
52
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
53
|
+
- uses: ruby/setup-ruby@v1
|
|
54
|
+
with:
|
|
55
|
+
ruby-version: "3.3"
|
|
56
|
+
bundler-cache: true
|
|
57
|
+
- name: Run RuboCop
|
|
58
|
+
run: bundle exec rake rubocop
|
|
59
|
+
|
|
60
|
+
build:
|
|
61
|
+
name: Build Gem
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
needs: [test, lint]
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
- name: Remove Gemfile.lock for CI compatibility
|
|
67
|
+
run: rm -f Gemfile.lock
|
|
68
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
69
|
+
- uses: ruby/setup-ruby@v1
|
|
70
|
+
with:
|
|
71
|
+
ruby-version: "3.3"
|
|
72
|
+
bundler-cache: true
|
|
73
|
+
- name: Build gem
|
|
74
|
+
run: gem build ruby_coded.gemspec
|
|
75
|
+
- name: Verify gem was built
|
|
76
|
+
run: ls ruby_coded-*.gem
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
name: Publish to RubyGems
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
id-token: write
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
ref: main
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
20
|
+
- uses: ruby/setup-ruby@v1
|
|
21
|
+
with:
|
|
22
|
+
ruby-version: "3.3"
|
|
23
|
+
bundler-cache: true
|
|
24
|
+
- uses: rubygems/release-gem@v1
|
data/.rubocop_todo.yml
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# This configuration was generated by
|
|
2
|
+
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 999`
|
|
3
|
+
# on 2026-04-15 07:58:11 UTC using RuboCop version 1.86.1.
|
|
4
|
+
# The point is for the user to remove these configuration records
|
|
5
|
+
# one by one as the offenses are removed from the code base.
|
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
8
|
+
|
|
9
|
+
# Offense count: 2
|
|
10
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
11
|
+
# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines.
|
|
12
|
+
Layout/EmptyLineBetweenDefs:
|
|
13
|
+
Exclude:
|
|
14
|
+
- 'test/test_agent_commands.rb'
|
|
15
|
+
- 'test/test_renderer_chat_panel.rb'
|
|
16
|
+
|
|
17
|
+
# Offense count: 5
|
|
18
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
19
|
+
Layout/EmptyLines:
|
|
20
|
+
Exclude:
|
|
21
|
+
- 'test/test_agent_commands.rb'
|
|
22
|
+
- 'test/test_model_commands.rb'
|
|
23
|
+
- 'test/test_renderer_chat_panel.rb'
|
|
24
|
+
- 'test/test_renderer_model_selector.rb'
|
|
25
|
+
- 'test/test_state_model_selection.rb'
|
|
26
|
+
|
|
27
|
+
# Offense count: 1
|
|
28
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
29
|
+
# Configuration parameters: EnforcedStyle.
|
|
30
|
+
# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only
|
|
31
|
+
Layout/EmptyLinesAroundClassBody:
|
|
32
|
+
Exclude:
|
|
33
|
+
- 'lib/ruby_coded/chat/app.rb'
|
|
34
|
+
|
|
35
|
+
# Offense count: 2
|
|
36
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
37
|
+
# Configuration parameters: EnforcedStyle.
|
|
38
|
+
# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines
|
|
39
|
+
Layout/EmptyLinesAroundModuleBody:
|
|
40
|
+
Exclude:
|
|
41
|
+
- 'lib/ruby_coded/chat/command_handler/token_commands.rb'
|
|
42
|
+
- 'lib/ruby_coded/chat/renderer/plan_clarifier.rb'
|
|
43
|
+
|
|
44
|
+
# Offense count: 1
|
|
45
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
46
|
+
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
|
47
|
+
# URISchemes: http, https
|
|
48
|
+
Layout/LineLength:
|
|
49
|
+
Exclude:
|
|
50
|
+
- 'lib/ruby_coded/chat/llm_bridge/streaming_retries.rb'
|
|
51
|
+
|
|
52
|
+
# Offense count: 1
|
|
53
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
|
54
|
+
Metrics/AbcSize:
|
|
55
|
+
Exclude:
|
|
56
|
+
- 'lib/ruby_coded/chat/state.rb'
|
|
57
|
+
|
|
58
|
+
# Offense count: 1
|
|
59
|
+
# Configuration parameters: CountComments, Max, CountAsOne.
|
|
60
|
+
Metrics/ClassLength:
|
|
61
|
+
Exclude:
|
|
62
|
+
- 'lib/ruby_coded/chat/state.rb'
|
|
63
|
+
|
|
64
|
+
# Offense count: 2
|
|
65
|
+
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
66
|
+
Metrics/MethodLength:
|
|
67
|
+
Exclude:
|
|
68
|
+
- 'lib/ruby_coded/chat/state.rb'
|
|
69
|
+
- 'lib/ruby_coded/strategies/oauth_strategy.rb'
|
|
70
|
+
|
|
71
|
+
# Offense count: 2
|
|
72
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
73
|
+
# Configuration parameters: EnforcedStyle, BlockForwardingName.
|
|
74
|
+
# SupportedStyles: anonymous, explicit
|
|
75
|
+
Naming/BlockForwarding:
|
|
76
|
+
Exclude:
|
|
77
|
+
- 'test/test_initializer.rb'
|
|
78
|
+
|
|
79
|
+
# Offense count: 2
|
|
80
|
+
# Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
|
|
81
|
+
# AllowedMethods: call
|
|
82
|
+
# WaywardPredicates: infinite?, nonzero?
|
|
83
|
+
Naming/PredicateMethod:
|
|
84
|
+
Exclude:
|
|
85
|
+
- 'lib/ruby_coded/strategies/api_key_strategy.rb'
|
|
86
|
+
- 'lib/ruby_coded/strategies/oauth_strategy.rb'
|
|
87
|
+
|
|
88
|
+
# Offense count: 1
|
|
89
|
+
# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs.
|
|
90
|
+
# NamePrefix: is_, has_, have_, does_
|
|
91
|
+
# ForbiddenPrefixes: is_, has_, have_, does_
|
|
92
|
+
# AllowedMethods: is_a?
|
|
93
|
+
# MethodDefinitionMacros: define_method, define_singleton_method
|
|
94
|
+
Naming/PredicatePrefix:
|
|
95
|
+
Exclude:
|
|
96
|
+
- 'lib/ruby_coded/chat/state/plan_tracking.rb'
|
|
97
|
+
|
|
98
|
+
# Offense count: 2
|
|
99
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
100
|
+
# Configuration parameters: AllowOnlyRestArgument, UseAnonymousForwarding, RedundantRestArgumentNames, RedundantKeywordRestArgumentNames, RedundantBlockArgumentNames.
|
|
101
|
+
# RedundantRestArgumentNames: args, arguments
|
|
102
|
+
# RedundantKeywordRestArgumentNames: kwargs, options, opts
|
|
103
|
+
# RedundantBlockArgumentNames: blk, block, proc
|
|
104
|
+
Style/ArgumentsForwarding:
|
|
105
|
+
Exclude:
|
|
106
|
+
- 'test/test_initializer.rb'
|
|
107
|
+
|
|
108
|
+
# Offense count: 1
|
|
109
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
110
|
+
# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns.
|
|
111
|
+
# SupportedStyles: predicate, comparison
|
|
112
|
+
Style/NumericPredicate:
|
|
113
|
+
Exclude:
|
|
114
|
+
- 'lib/ruby_coded/chat/state.rb'
|
|
115
|
+
|
|
116
|
+
# Offense count: 3
|
|
117
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
118
|
+
Style/SuperArguments:
|
|
119
|
+
Exclude:
|
|
120
|
+
- 'lib/ruby_coded/errors/auth_error.rb'
|
|
121
|
+
- 'test/test_renderer_chat_panel.rb'
|
|
122
|
+
- 'test/test_renderer_status_bar.rb'
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"ruby_coded" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["cesar.rodriguez.lara54@gmail.com"](mailto:"cesar.rodriguez.lara54@gmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cesar Rodriguez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# RubyCoded
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
/\
|
|
5
|
+
/ \
|
|
6
|
+
/ \ ____ _ ____ _
|
|
7
|
+
/------\ | _ \ _ _| |__ _ _ / ___|___ __| | ___ __| |
|
|
8
|
+
/ \ / \ | |_) | | | | '_ \| | | | | | / _ \ / _` |/ _ \ / _` |
|
|
9
|
+
/ \/ \ | _ <| |_| | |_) | |_| | | |__| (_) | (_| | __/ (_| |
|
|
10
|
+
\ /\ / |_| \_\\__,_|_.__/ \__, | \____\___/ \__,_|\___| \__,_|
|
|
11
|
+
\ / \ / |___/
|
|
12
|
+
\/ \/
|
|
13
|
+
\ /
|
|
14
|
+
\ /
|
|
15
|
+
\/
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
An AI-powered terminal coding assistant built in Ruby. Chat with LLMs, let an agent edit your project files, or plan tasks — all from your terminal.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Chat mode** — Talk to an LLM directly in a full terminal UI (TUI) built with [ratatui](https://github.com/nicholasgasior/ratatui-ruby)
|
|
23
|
+
- **Agent mode** — The model can read, write, edit, and delete files in your project, create directories, and run shell commands with user confirmation
|
|
24
|
+
- **Plan mode** — Generate structured plans before implementing, with interactive clarification questions and auto-switch to agent mode when ready
|
|
25
|
+
- **Multi-provider support** — Works with OpenAI and Anthropic out of the box (OAuth and API key authentication)
|
|
26
|
+
- **Tool confirmation** — Write and dangerous operations require explicit approval; safe operations (read, list) run automatically
|
|
27
|
+
- **Token & cost tracking** — Live status bar showing token usage and estimated session cost
|
|
28
|
+
- **Plugin system** — Extend the chat with custom state, input handlers, renderer overlays, and commands
|
|
29
|
+
- **Slash commands** — `/agent`, `/plan`, `/model`, `/history`, `/tokens`, `/help`, and more
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- Ruby >= 3.3.0
|
|
34
|
+
- An OpenAI or Anthropic account (API key or OAuth)
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
gem install ruby_coded
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
Navigate to any project directory and run:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
ruby_coded
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
On first launch you'll be asked to authenticate with a provider. After that, you're dropped into chat mode.
|
|
51
|
+
|
|
52
|
+
### Modes
|
|
53
|
+
|
|
54
|
+
| Command | Description |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `/agent on` | Enable agent mode (file tools + shell access) |
|
|
57
|
+
| `/agent off` | Disable agent mode |
|
|
58
|
+
| `/plan on` | Enable plan mode (read-only tools, structured planning) |
|
|
59
|
+
| `/plan off` | Disable plan mode |
|
|
60
|
+
| `/plan save` | Save the current plan to a file |
|
|
61
|
+
| `/model` | Switch to a different model |
|
|
62
|
+
| `/tokens` | Show detailed token usage breakdown |
|
|
63
|
+
| `/history` | Show conversation history |
|
|
64
|
+
| `/clear` | Clear the conversation |
|
|
65
|
+
| `/help` | Show all available commands |
|
|
66
|
+
|
|
67
|
+
### Agent mode
|
|
68
|
+
|
|
69
|
+
When agent mode is active, the model has access to these tools:
|
|
70
|
+
|
|
71
|
+
| Tool | Risk level | Description |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `read_file` | Safe | Read file contents |
|
|
74
|
+
| `list_directory` | Safe | List directory contents |
|
|
75
|
+
| `write_file` | Confirm | Write a new file |
|
|
76
|
+
| `edit_file` | Confirm | Search and replace in a file |
|
|
77
|
+
| `create_directory` | Confirm | Create a new directory |
|
|
78
|
+
| `delete_path` | Dangerous | Delete a file or directory |
|
|
79
|
+
| `run_command` | Dangerous | Execute a shell command |
|
|
80
|
+
|
|
81
|
+
Safe tools run without asking. Confirm and dangerous tools show the operation details and wait for your approval (`y` to approve, `n` to reject, `a` to approve all remaining).
|
|
82
|
+
|
|
83
|
+
### Plan mode
|
|
84
|
+
|
|
85
|
+
Plan mode restricts the model to read-only tools and a planning-oriented system prompt. The model will analyze your project and propose a structured plan. If the model needs clarification, it presents interactive options you can select or answer with custom text.
|
|
86
|
+
|
|
87
|
+
## Keyboard shortcuts
|
|
88
|
+
|
|
89
|
+
| Key | Action |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `Enter` | Send message |
|
|
92
|
+
| `Esc` | Cancel streaming / clear input |
|
|
93
|
+
| `Ctrl+C` | Quit |
|
|
94
|
+
| `Up/Down` | Scroll chat history |
|
|
95
|
+
| `Left/Right` | Move cursor in input |
|
|
96
|
+
| `Home/End` | Jump to start/end of input |
|
|
97
|
+
| `Tab` | Autocomplete commands |
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git clone https://github.com/MrCesar107/ruby_code.git
|
|
103
|
+
cd ruby_code
|
|
104
|
+
bundle install
|
|
105
|
+
bundle exec rake test
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
To run the application locally:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
bundle exec exe/ruby_coded
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## What's next
|
|
115
|
+
|
|
116
|
+
- Find a way to update the autocomplete plugin when a new command is added []
|
|
117
|
+
- Display context window size (depending on the model) []
|
|
118
|
+
- UI element to indicate the AI is performing a task []
|
|
119
|
+
- Add the possibility to create custom commands []
|
|
120
|
+
- Skills implementation []
|
|
121
|
+
- Implement Google Auth for Gemini []
|
|
122
|
+
- Session recovery system by ID []
|
|
123
|
+
|
|
124
|
+
## Contributing
|
|
125
|
+
|
|
126
|
+
Contributions are welcome! To get started:
|
|
127
|
+
|
|
128
|
+
1. Fork the repository
|
|
129
|
+
2. Create a new branch for your feature or fix (`git checkout -b my-feature`)
|
|
130
|
+
3. Make your changes and add tests if applicable
|
|
131
|
+
4. Make sure all tests pass (`bundle exec rake test`)
|
|
132
|
+
5. Commit your changes (`git commit -m "Add my feature"`)
|
|
133
|
+
6. Push to your fork (`git push origin my-feature`)
|
|
134
|
+
7. Open a Pull Request against the `main` branch of this repository
|
|
135
|
+
|
|
136
|
+
Your PR will be reviewed and merged if everything looks good. If you're unsure about a change, feel free to open an issue first to discuss it.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/exe/ruby_coded
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "time"
|
|
5
|
+
require "ruby_llm"
|
|
6
|
+
require "tty-prompt"
|
|
7
|
+
|
|
8
|
+
require_relative "providers/openai"
|
|
9
|
+
require_relative "providers/anthropic"
|
|
10
|
+
require_relative "../strategies/oauth_strategy"
|
|
11
|
+
require_relative "../strategies/api_key_strategy"
|
|
12
|
+
require_relative "credentials_store"
|
|
13
|
+
|
|
14
|
+
module RubyCoded
|
|
15
|
+
module Auth
|
|
16
|
+
# This class is used to manage the authentication process for the different
|
|
17
|
+
# AI providers
|
|
18
|
+
class AuthManager
|
|
19
|
+
PROVIDERS = {
|
|
20
|
+
openai: Providers::OpenAI,
|
|
21
|
+
anthropic: Providers::Anthropic
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
def initialize(config_path: UserConfig::CONFIG_PATH)
|
|
25
|
+
@config_path = config_path
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def login(provider_name)
|
|
29
|
+
provider = PROVIDERS.fetch(provider_name)
|
|
30
|
+
strategy = strategy_for(provider)
|
|
31
|
+
credentials = strategy.authenticate
|
|
32
|
+
credential_store.store(provider_name, credentials)
|
|
33
|
+
configure_ruby_llm!
|
|
34
|
+
print_api_credits_notice(provider)
|
|
35
|
+
credentials
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def logout(provider_name)
|
|
39
|
+
credential_store.remove(provider_name)
|
|
40
|
+
configure_ruby_llm!
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def configured_providers
|
|
44
|
+
PROVIDERS.keys
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def authenticated_provider_names
|
|
48
|
+
PROVIDERS.keys.select { |name| credential_store.retrieve(name) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def check_authentication
|
|
52
|
+
return if configured_providers.any? { |name| credential_store.retrieve(name) }
|
|
53
|
+
|
|
54
|
+
provider_name = choose_provider
|
|
55
|
+
login(provider_name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def login_prompt
|
|
59
|
+
provider_name = choose_provider
|
|
60
|
+
login(provider_name)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def configure_ruby_llm!
|
|
64
|
+
RubyLLM.configure do |config|
|
|
65
|
+
config.max_retries = 1
|
|
66
|
+
|
|
67
|
+
PROVIDERS.each do |name, provider|
|
|
68
|
+
credentials = credential_store.retrieve(name)
|
|
69
|
+
next unless credentials
|
|
70
|
+
|
|
71
|
+
credentials = refresh_if_expired(name, provider, credentials)
|
|
72
|
+
key = extract_api_key(credentials)
|
|
73
|
+
config.send("#{provider.ruby_llm_key}=", key)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def prompt
|
|
81
|
+
@prompt ||= TTY::Prompt.new
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def choose_provider
|
|
85
|
+
prompt.select("Please select the AI provider you want to log in:",
|
|
86
|
+
configured_providers,
|
|
87
|
+
per_page: 10)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def strategy_for(provider)
|
|
91
|
+
method = choose_auth_method(provider)
|
|
92
|
+
case method
|
|
93
|
+
when :oauth then Strategies::OAuthStrategy.new(provider)
|
|
94
|
+
when :api_key then Strategies::APIKeyStrategy.new(provider)
|
|
95
|
+
else
|
|
96
|
+
raise "Invalid authentication method: #{method}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def credential_store
|
|
101
|
+
@credential_store ||= CredentialsStore.new(config_path: @config_path)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def extract_api_key(credentials)
|
|
105
|
+
case credentials["auth_method"]
|
|
106
|
+
when "oauth" then credentials["access_token"]
|
|
107
|
+
when "api_key" then credentials["key"]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def refresh_if_expired(provider_name, provider, credentials)
|
|
112
|
+
return credentials unless credentials["auth_method"] == "oauth"
|
|
113
|
+
return credentials unless token_expired?(credentials)
|
|
114
|
+
|
|
115
|
+
strategy = Strategies::OAuthStrategy.new(provider)
|
|
116
|
+
refreshed = strategy.refresh(credentials)
|
|
117
|
+
credential_store.store(provider_name, refreshed)
|
|
118
|
+
refreshed
|
|
119
|
+
rescue StandardError
|
|
120
|
+
credentials
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def token_expired?(credentials)
|
|
124
|
+
expires_at = credentials["expires_at"]
|
|
125
|
+
return false unless expires_at
|
|
126
|
+
|
|
127
|
+
Time.parse(expires_at) <= Time.now + 60
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def print_api_credits_notice(provider)
|
|
131
|
+
console = provider.respond_to?(:console_url) ? provider.console_url : nil
|
|
132
|
+
billing_hint = console ? " Check your balance at #{console}." : ""
|
|
133
|
+
puts "\nNote: API usage consumes credits from your #{provider.display_name} account.#{billing_hint}\n\n"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def choose_auth_method(provider)
|
|
137
|
+
methods = provider.auth_methods
|
|
138
|
+
return methods.first[:key] if methods.size == 1
|
|
139
|
+
|
|
140
|
+
choices = methods.map { |m| { name: m[:label], value: m[:key] } }
|
|
141
|
+
prompt.select("How would you like to authenticate with #{provider.display_name}?", choices)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyCoded
|
|
4
|
+
module Auth
|
|
5
|
+
# This class creates a callback servlet for the OAuth authentication
|
|
6
|
+
class CallbackServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
7
|
+
SUCCESS_HTML = <<~HTML
|
|
8
|
+
<html>
|
|
9
|
+
<body>
|
|
10
|
+
<h2>You are now logged</h2>
|
|
11
|
+
<p>You can close this window now.</p>
|
|
12
|
+
<script>window.close();</script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
15
|
+
HTML
|
|
16
|
+
|
|
17
|
+
def initialize(server, result_queue)
|
|
18
|
+
super(server)
|
|
19
|
+
@result_queue = result_queue
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def do_GET(request, response) # rubocop:disable Naming/MethodName
|
|
23
|
+
process_callback(request)
|
|
24
|
+
response.status = 200
|
|
25
|
+
response.content_type = "text/html"
|
|
26
|
+
response.body = SUCCESS_HTML
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def process_callback(request)
|
|
32
|
+
error = request.query["error"]
|
|
33
|
+
if error
|
|
34
|
+
@result_queue.push({ error: error })
|
|
35
|
+
else
|
|
36
|
+
@result_queue.push({ code: request.query["code"], state: request.query["state"] })
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
require_relative "../config/user_config"
|
|
6
|
+
|
|
7
|
+
module RubyCoded
|
|
8
|
+
module Auth
|
|
9
|
+
# This class is used to manage the credentials in the config file
|
|
10
|
+
class CredentialsStore
|
|
11
|
+
def initialize(config_path: UserConfig::CONFIG_PATH)
|
|
12
|
+
@config = UserConfig.new(config_path: config_path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def store(provider_name, credentials)
|
|
16
|
+
cfg = @config.full_config
|
|
17
|
+
cfg["providers"] ||= {}
|
|
18
|
+
cfg["providers"][provider_name.to_s] = credentials
|
|
19
|
+
@config.save
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def retrieve(provider_name)
|
|
23
|
+
@config.full_config.dig("providers", provider_name.to_s)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def remove(provider_name)
|
|
27
|
+
providers = @config.full_config["providers"]
|
|
28
|
+
return unless providers
|
|
29
|
+
|
|
30
|
+
providers.delete(provider_name.to_s)
|
|
31
|
+
@config.save
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|