vibecode 0.0.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/.gitignore +33 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +56 -0
- data/LICENSE.txt +21 -0
- data/README.md +245 -0
- data/Rakefile +8 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/exe/vibecode +4 -0
- data/lib/vibecode/agent.rb +365 -0
- data/lib/vibecode/cli.rb +148 -0
- data/lib/vibecode/git.rb +141 -0
- data/lib/vibecode/ollama_client.rb +111 -0
- data/lib/vibecode/version.rb +5 -0
- data/lib/vibecode/workspace.rb +225 -0
- data/lib/vibecode.rb +13 -0
- data/sig/vibecode.rbs +4 -0
- data/test/test_helper.rb +6 -0
- data/test/test_vibecode.rb +13 -0
- data/vibecode.gemspec +39 -0
- metadata +140 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require "httparty"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module Vibecode
|
|
5
|
+
class OllamaClient
|
|
6
|
+
include HTTParty
|
|
7
|
+
base_uri "http://localhost:11434"
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@headers = { "Content-Type" => "application/json" }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# -----------------------------
|
|
14
|
+
# Chat with Model
|
|
15
|
+
# -----------------------------
|
|
16
|
+
def chat(model, system_prompt, user_input)
|
|
17
|
+
body = {
|
|
18
|
+
model: model,
|
|
19
|
+
stream: false,
|
|
20
|
+
messages: [
|
|
21
|
+
{ role: "system", content: system_prompt },
|
|
22
|
+
{ role: "user", content: user_input }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
response = self.class.post("/api/chat", headers: @headers, body: body.to_json)
|
|
27
|
+
|
|
28
|
+
unless response.success?
|
|
29
|
+
return "Error talking to Ollama: #{response.code} #{response.body}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
parsed = JSON.parse(response.body)
|
|
33
|
+
parsed.dig("message", "content") || "(No response from model)"
|
|
34
|
+
rescue Errno::ECONNREFUSED
|
|
35
|
+
"Cannot connect to Ollama. Is it running? Try: `ollama serve`"
|
|
36
|
+
rescue => e
|
|
37
|
+
"Ollama error: #{e.message}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# -----------------------------
|
|
41
|
+
# List Installed Models
|
|
42
|
+
# -----------------------------
|
|
43
|
+
def list_models
|
|
44
|
+
response = self.class.get("/api/tags")
|
|
45
|
+
|
|
46
|
+
return [] unless response.success?
|
|
47
|
+
|
|
48
|
+
parsed = JSON.parse(response.body)
|
|
49
|
+
parsed.fetch("models", []).map { |m| m["name"] }
|
|
50
|
+
rescue
|
|
51
|
+
[]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def model_installed?(model_name)
|
|
55
|
+
list_models.include?(model_name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# -----------------------------
|
|
59
|
+
# Pull Model
|
|
60
|
+
# -----------------------------
|
|
61
|
+
def pull_model(model_name)
|
|
62
|
+
uri = URI("#{self.class.base_uri}/api/pull")
|
|
63
|
+
|
|
64
|
+
req = Net::HTTP::Post.new(uri, @headers)
|
|
65
|
+
req.body = { name: model_name, stream: true }.to_json
|
|
66
|
+
|
|
67
|
+
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
|
68
|
+
http.request(req) do |res|
|
|
69
|
+
unless res.is_a?(Net::HTTPSuccess)
|
|
70
|
+
puts "Failed to start model pull"
|
|
71
|
+
return false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
res.read_body do |chunk|
|
|
75
|
+
begin
|
|
76
|
+
data = JSON.parse(chunk)
|
|
77
|
+
show_pull_progress(data)
|
|
78
|
+
rescue JSON::ParserError
|
|
79
|
+
# ignore incomplete chunks
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
true
|
|
86
|
+
rescue => e
|
|
87
|
+
puts "Error pulling model: #{e.message}"
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def show_pull_progress(data)
|
|
92
|
+
if data["status"]
|
|
93
|
+
print "\r#{data['status'].ljust(60)}"
|
|
94
|
+
elsif data["completed"] && data["total"]
|
|
95
|
+
percent = (data["completed"].to_f / data["total"] * 100).round(1)
|
|
96
|
+
print "\rDownloading... #{percent}%".ljust(60)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# -----------------------------
|
|
101
|
+
# Health Check
|
|
102
|
+
# -----------------------------
|
|
103
|
+
def server_alive?
|
|
104
|
+
response = self.class.get("/")
|
|
105
|
+
response.code == 200 || response.code == 404
|
|
106
|
+
rescue
|
|
107
|
+
false
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "pathname"
|
|
3
|
+
require "tty-prompt"
|
|
4
|
+
require "pastel"
|
|
5
|
+
require "diffy"
|
|
6
|
+
require "open3"
|
|
7
|
+
|
|
8
|
+
module Vibecode
|
|
9
|
+
class Workspace
|
|
10
|
+
attr_reader :root
|
|
11
|
+
|
|
12
|
+
def initialize(root = Dir.pwd)
|
|
13
|
+
@root = File.expand_path(root)
|
|
14
|
+
@prompt = TTY::Prompt.new
|
|
15
|
+
@pastel = Pastel.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# --------------------------------------------------
|
|
19
|
+
# File Reading
|
|
20
|
+
# --------------------------------------------------
|
|
21
|
+
|
|
22
|
+
def read_file(path)
|
|
23
|
+
full_path = safe_path(path)
|
|
24
|
+
return error("File does not exist: #{path}") unless File.exist?(full_path)
|
|
25
|
+
|
|
26
|
+
File.read(full_path)
|
|
27
|
+
rescue => e
|
|
28
|
+
error("Failed to read file: #{e.message}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def file_exists?(path)
|
|
32
|
+
full_path = safe_path(path)
|
|
33
|
+
File.exist?(full_path)
|
|
34
|
+
rescue
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def list_files(limit: 200)
|
|
39
|
+
files = Dir.glob("**/*", base: @root)
|
|
40
|
+
.reject { |f| File.directory?(File.join(@root, f)) }
|
|
41
|
+
.first(limit)
|
|
42
|
+
|
|
43
|
+
files.join("\n")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# --------------------------------------------------
|
|
47
|
+
# File Writing
|
|
48
|
+
# --------------------------------------------------
|
|
49
|
+
|
|
50
|
+
def diff_for(path, new_content)
|
|
51
|
+
full_path = safe_path(path)
|
|
52
|
+
old_content = File.exist?(full_path) ? File.read(full_path) : ""
|
|
53
|
+
Diffy::Diff.new(old_content, new_content, context: 3).to_s(:color)
|
|
54
|
+
rescue => e
|
|
55
|
+
error("Failed to diff file: #{e.message}")
|
|
56
|
+
""
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def write_file(path, new_content, show_diff: true)
|
|
60
|
+
full_path = safe_path(path)
|
|
61
|
+
|
|
62
|
+
if show_diff
|
|
63
|
+
diff = diff_for(path, new_content)
|
|
64
|
+
puts @pastel.yellow("\nProposed changes to #{path}:\n")
|
|
65
|
+
puts diff.empty? ? @pastel.dim("(No changes)") : diff
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
FileUtils.mkdir_p(File.dirname(full_path))
|
|
69
|
+
File.write(full_path, new_content)
|
|
70
|
+
|
|
71
|
+
puts @pastel.green("Updated #{path}")
|
|
72
|
+
true
|
|
73
|
+
rescue => e
|
|
74
|
+
error("Failed to write file: #{e.message}")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# --------------------------------------------------
|
|
78
|
+
# Ruby Execution
|
|
79
|
+
# --------------------------------------------------
|
|
80
|
+
|
|
81
|
+
def ruby_executable_content?(content)
|
|
82
|
+
return false if content.nil? || content.strip.empty?
|
|
83
|
+
|
|
84
|
+
return true if content.match?(/if\s+__FILE__\s*==\s*\$0/)
|
|
85
|
+
return true if content.match?(/\bputs\b/)
|
|
86
|
+
|
|
87
|
+
line = last_significant_line(content)
|
|
88
|
+
return false unless line
|
|
89
|
+
|
|
90
|
+
stripped = line.strip
|
|
91
|
+
return false if stripped.match?(/^(def|class|module|end)\b/)
|
|
92
|
+
|
|
93
|
+
stripped.match?(/[A-Za-z_]\w*(\s*\(|\b)/)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def run_ruby(path)
|
|
97
|
+
full_path = safe_path(path)
|
|
98
|
+
return {
|
|
99
|
+
stdout: "",
|
|
100
|
+
stderr: "File does not exist: #{path}",
|
|
101
|
+
status: nil,
|
|
102
|
+
command: "ruby #{path}",
|
|
103
|
+
skipped: true
|
|
104
|
+
} unless File.exist?(full_path)
|
|
105
|
+
|
|
106
|
+
content = File.read(full_path)
|
|
107
|
+
unless ruby_executable_content?(content)
|
|
108
|
+
return {
|
|
109
|
+
stdout: "",
|
|
110
|
+
stderr: "",
|
|
111
|
+
status: nil,
|
|
112
|
+
command: "ruby #{path}",
|
|
113
|
+
skipped: true
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
stdout, stderr, status = Open3.capture3("ruby #{full_path}", chdir: @root)
|
|
118
|
+
{
|
|
119
|
+
stdout: stdout,
|
|
120
|
+
stderr: stderr,
|
|
121
|
+
status: status,
|
|
122
|
+
command: "ruby #{path}",
|
|
123
|
+
skipped: false
|
|
124
|
+
}
|
|
125
|
+
rescue => e
|
|
126
|
+
{
|
|
127
|
+
stdout: "",
|
|
128
|
+
stderr: e.message,
|
|
129
|
+
status: nil,
|
|
130
|
+
command: "ruby #{path}",
|
|
131
|
+
skipped: false
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# --------------------------------------------------
|
|
136
|
+
# Filename Suggestions
|
|
137
|
+
# --------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def suggest_filename(task_description)
|
|
140
|
+
prompt = task_description.to_s.downcase
|
|
141
|
+
|
|
142
|
+
return "hello_world.rb" if prompt.include?("hello world")
|
|
143
|
+
return "greet.rb" if prompt.match?(/\bgreet\b/)
|
|
144
|
+
|
|
145
|
+
preferred = %w[
|
|
146
|
+
greet hello world user server client parser json api http config file data
|
|
147
|
+
]
|
|
148
|
+
stopwords = %w[
|
|
149
|
+
a an the to for of and in on with from into is are be create build make write
|
|
150
|
+
ruby method function class module script program app code that this
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
words = prompt.scan(/[a-z0-9]+/)
|
|
154
|
+
words = words.reject { |w| stopwords.include?(w) }
|
|
155
|
+
|
|
156
|
+
if words.include?("hello") && words.include?("world")
|
|
157
|
+
return "hello_world.rb"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
picked = []
|
|
161
|
+
preferred.each do |w|
|
|
162
|
+
picked << w if words.include?(w)
|
|
163
|
+
break if picked.size >= 3
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if picked.empty?
|
|
167
|
+
words.each do |w|
|
|
168
|
+
picked << w
|
|
169
|
+
break if picked.size >= 3
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
return "main.rb" if picked.empty?
|
|
174
|
+
|
|
175
|
+
"#{picked.uniq.first(3).join("_")}.rb"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# --------------------------------------------------
|
|
179
|
+
# Directory Tree Snapshot
|
|
180
|
+
# --------------------------------------------------
|
|
181
|
+
|
|
182
|
+
def tree(max_depth: 3)
|
|
183
|
+
output = []
|
|
184
|
+
|
|
185
|
+
Dir.glob("**/*", base: @root).each do |path|
|
|
186
|
+
depth = path.count(File::SEPARATOR)
|
|
187
|
+
next if depth > max_depth
|
|
188
|
+
next if path.start_with?(".git")
|
|
189
|
+
|
|
190
|
+
output << path
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
output.sort.join("\n")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# --------------------------------------------------
|
|
197
|
+
# Safety Helpers
|
|
198
|
+
# --------------------------------------------------
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
def last_significant_line(content)
|
|
203
|
+
content.lines.reverse_each do |line|
|
|
204
|
+
stripped = line.strip
|
|
205
|
+
next if stripped.empty?
|
|
206
|
+
next if stripped.start_with?("#")
|
|
207
|
+
return line
|
|
208
|
+
end
|
|
209
|
+
nil
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def safe_path(path)
|
|
213
|
+
expanded = File.expand_path(path, @root)
|
|
214
|
+
unless expanded.start_with?(@root)
|
|
215
|
+
raise "Access outside project root is not allowed"
|
|
216
|
+
end
|
|
217
|
+
expanded
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def error(message)
|
|
221
|
+
puts @pastel.red(message)
|
|
222
|
+
nil
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
data/lib/vibecode.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "vibecode/version"
|
|
4
|
+
require_relative "vibecode/cli"
|
|
5
|
+
require_relative "vibecode/ollama_client"
|
|
6
|
+
require_relative "vibecode/workspace"
|
|
7
|
+
require_relative "vibecode/git"
|
|
8
|
+
require_relative "vibecode/agent"
|
|
9
|
+
|
|
10
|
+
module Vibecode
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
# Your code goes here...
|
|
13
|
+
end
|
data/sig/vibecode.rbs
ADDED
data/test/test_helper.rb
ADDED
data/vibecode.gemspec
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/vibecode/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "vibecode"
|
|
7
|
+
spec.version = Vibecode::VERSION
|
|
8
|
+
spec.authors = ["hackliteracy"]
|
|
9
|
+
spec.email = ["hackliteracy@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "A local-first “Codex-style” CLI but powered by Ollama"
|
|
12
|
+
spec.description = "Local AI coding agent with abilities like: File editing with diffs, Git command approvals, Model switching, Repo awareness. All on your machine available offline"
|
|
13
|
+
spec.homepage = "https://github.com/ktamulonis/vibecode"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.0"
|
|
16
|
+
|
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org/"
|
|
18
|
+
|
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/ktamulonis/vibecode"
|
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/ktamulonis/vibecode/blob/main/CHANGELOG.md"
|
|
21
|
+
|
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
24
|
+
spec.files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
|
|
25
|
+
spec.bindir = "exe"
|
|
26
|
+
spec.executables = ["vibecode"]
|
|
27
|
+
|
|
28
|
+
spec.require_paths = ["lib"]
|
|
29
|
+
|
|
30
|
+
# Uncomment to register a new dependency of your gem
|
|
31
|
+
spec.add_dependency "tty-prompt", "~> 0.23"
|
|
32
|
+
spec.add_dependency "tty-spinner", "~> 0.9"
|
|
33
|
+
spec.add_dependency "pastel", "~> 0.8"
|
|
34
|
+
spec.add_dependency "httparty", "~> 0.21"
|
|
35
|
+
spec.add_dependency "diffy", "~> 3.4"
|
|
36
|
+
|
|
37
|
+
# For more information and examples about making a new gem, check out our
|
|
38
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
39
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: vibecode
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- hackliteracy
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-02-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: tty-prompt
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.23'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.23'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: tty-spinner
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.9'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.9'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: pastel
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.8'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.8'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: httparty
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0.21'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0.21'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: diffy
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.4'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.4'
|
|
83
|
+
description: 'Local AI coding agent with abilities like: File editing with diffs,
|
|
84
|
+
Git command approvals, Model switching, Repo awareness. All on your machine available
|
|
85
|
+
offline'
|
|
86
|
+
email:
|
|
87
|
+
- hackliteracy@gmail.com
|
|
88
|
+
executables:
|
|
89
|
+
- vibecode
|
|
90
|
+
extensions: []
|
|
91
|
+
extra_rdoc_files: []
|
|
92
|
+
files:
|
|
93
|
+
- ".gitignore"
|
|
94
|
+
- CHANGELOG.md
|
|
95
|
+
- Gemfile
|
|
96
|
+
- Gemfile.lock
|
|
97
|
+
- LICENSE.txt
|
|
98
|
+
- README.md
|
|
99
|
+
- Rakefile
|
|
100
|
+
- bin/console
|
|
101
|
+
- bin/setup
|
|
102
|
+
- exe/vibecode
|
|
103
|
+
- lib/vibecode.rb
|
|
104
|
+
- lib/vibecode/agent.rb
|
|
105
|
+
- lib/vibecode/cli.rb
|
|
106
|
+
- lib/vibecode/git.rb
|
|
107
|
+
- lib/vibecode/ollama_client.rb
|
|
108
|
+
- lib/vibecode/version.rb
|
|
109
|
+
- lib/vibecode/workspace.rb
|
|
110
|
+
- sig/vibecode.rbs
|
|
111
|
+
- test/test_helper.rb
|
|
112
|
+
- test/test_vibecode.rb
|
|
113
|
+
- vibecode.gemspec
|
|
114
|
+
homepage: https://github.com/ktamulonis/vibecode
|
|
115
|
+
licenses:
|
|
116
|
+
- MIT
|
|
117
|
+
metadata:
|
|
118
|
+
allowed_push_host: https://rubygems.org/
|
|
119
|
+
source_code_uri: https://github.com/ktamulonis/vibecode
|
|
120
|
+
changelog_uri: https://github.com/ktamulonis/vibecode/blob/main/CHANGELOG.md
|
|
121
|
+
post_install_message:
|
|
122
|
+
rdoc_options: []
|
|
123
|
+
require_paths:
|
|
124
|
+
- lib
|
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - ">="
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '3.0'
|
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - ">="
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: '0'
|
|
135
|
+
requirements: []
|
|
136
|
+
rubygems_version: 3.5.22
|
|
137
|
+
signing_key:
|
|
138
|
+
specification_version: 4
|
|
139
|
+
summary: A local-first “Codex-style” CLI but powered by Ollama
|
|
140
|
+
test_files: []
|