internator 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -3
- data/internator.gemspec +2 -2
- data/lib/internator/cli.rb +36 -67
- data/lib/internator/opencode_service.rb +23 -0
- data/lib/internator/version.rb +1 -1
- data/lib/internator.rb +3 -3
- metadata +5 -7
- data/lib/internator/codex_service.rb +0 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b41c036134848c56e6d552dd9ce8975d62ea52196497c855bc3072188eb5f9c
|
|
4
|
+
data.tar.gz: 6bf30109d8ec851dd0d25b96ddea9360d8c4d85a6a1495090fb99e894bceda4f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb28427b622853467d18b733fce85c7ddfcf36935450982e7799bce8c050de8a9f7927c0d6a1fa5e7fea8d3caabfa99eb548ccc31c8c942f620cad7f765cb739
|
|
7
|
+
data.tar.gz: 2c7c86e87ac17cba9f6469c1ab09dfac0d0ac95118f92785ad5c1058735d7374acd236a54c97188c4e4026f0fa2958eb0255452b91742d1e7675b4a3aa3efe7c
|
data/README.md
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# Internator
|
|
4
4
|
|
|
5
|
-
Internator is a Ruby-based CLI tool that automates iterative pull request improvements using
|
|
5
|
+
Internator is a Ruby-based CLI tool that automates iterative pull request improvements using OpenCode. It cycles through objectives, makes incremental changes, automatically commits and pushes each update, and optionally waits between iterations.
|
|
6
6
|
|
|
7
7
|
## Requirements
|
|
8
8
|
|
|
9
9
|
- Ruby (>= 2.5).
|
|
10
|
-
-
|
|
11
|
-
- Environment variable `OPENAI_API_KEY` set to your OpenAI API key.
|
|
10
|
+
- `opencode` CLI installed.
|
|
12
11
|
|
|
13
12
|
## Installation
|
|
14
13
|
|
data/internator.gemspec
CHANGED
|
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
|
|
|
6
6
|
spec.authors = ["AlexLarra"]
|
|
7
7
|
spec.email = ["clausrybnic@gmail.com"]
|
|
8
8
|
|
|
9
|
-
spec.summary = "CLI tool that automates iterative pull request improvements using
|
|
10
|
-
spec.description = "Internator is a Ruby-based CLI tool that automates iterative pull request improvements using
|
|
9
|
+
spec.summary = "CLI tool that automates iterative pull request improvements using Opencode"
|
|
10
|
+
spec.description = "Internator is a Ruby-based CLI tool that automates iterative pull request improvements using Opencode. It cycles through objectives, makes incremental changes, automatically commits and pushes each update, and optionally waits between iterations."
|
|
11
11
|
spec.homepage = "https://github.com/AlexLarra/internator"
|
|
12
12
|
spec.license = "MIT"
|
|
13
13
|
|
data/lib/internator/cli.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require "json"
|
|
4
|
-
require "tempfile"
|
|
5
|
-
require "yaml"
|
|
1
|
+
require 'tempfile'
|
|
2
|
+
require 'yaml'
|
|
6
3
|
|
|
7
4
|
module Internator
|
|
8
5
|
# Command-line interface for the Internator gem
|
|
@@ -17,7 +14,7 @@ module Internator
|
|
|
17
14
|
else
|
|
18
15
|
{}
|
|
19
16
|
end
|
|
20
|
-
rescue => e
|
|
17
|
+
rescue StandardError => e
|
|
21
18
|
warn "⚠️ Could not parse config file #{CONFIG_FILE}: #{e.message}"
|
|
22
19
|
{}
|
|
23
20
|
end
|
|
@@ -51,18 +48,10 @@ module Internator
|
|
|
51
48
|
end
|
|
52
49
|
|
|
53
50
|
def self.run(args = ARGV)
|
|
54
|
-
unless system(
|
|
55
|
-
abort "❌ 'codex' CLI is not installed or not in PATH. Please install it from https://github.com/openai/codex"
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
if ENV["OPENAI_API_KEY"].to_s.strip.empty?
|
|
59
|
-
abort "❌ OPENAI_API_KEY not set. Please set the environment variable."
|
|
60
|
-
end
|
|
51
|
+
abort "❌ 'opencode' CLI is not installed or not in PATH." unless system('which opencode > /dev/null 2>&1')
|
|
61
52
|
|
|
62
53
|
# Parse arguments: objectives, optional delay (minutes), optional parent_branch
|
|
63
|
-
if args.empty? || args.size > 3
|
|
64
|
-
abort "❌ Usage: internator \"<PR Objectives>\" [delay_mins] [parent_branch]"
|
|
65
|
-
end
|
|
54
|
+
abort '❌ Usage: internator "<PR Objectives>" [delay_mins] [parent_branch]' if args.empty? || args.size > 3
|
|
66
55
|
|
|
67
56
|
objectives = args[0]
|
|
68
57
|
delay_mins = 0
|
|
@@ -76,15 +65,19 @@ module Internator
|
|
|
76
65
|
parent_branch = args[1]
|
|
77
66
|
end
|
|
78
67
|
when 3
|
|
79
|
-
delay_mins =
|
|
68
|
+
delay_mins = begin
|
|
69
|
+
Integer(args[1])
|
|
70
|
+
rescue StandardError
|
|
71
|
+
abort('❌ Invalid delay_mins: must be an integer')
|
|
72
|
+
end
|
|
80
73
|
parent_branch = args[2]
|
|
81
74
|
end
|
|
82
75
|
|
|
83
|
-
remote, default_base = git_detect_default_base&.split(
|
|
76
|
+
remote, default_base = git_detect_default_base&.split('/', 2)
|
|
84
77
|
branch = git_current_branch
|
|
85
78
|
|
|
86
|
-
abort
|
|
87
|
-
abort
|
|
79
|
+
abort '❌ Git remote is not detected.' unless remote
|
|
80
|
+
abort '❌ Git default branch is not detected.' unless default_base
|
|
88
81
|
|
|
89
82
|
if branch == default_base
|
|
90
83
|
abort "❌ You are on the default branch '#{default_base}'. Please create a new branch before running Internator."
|
|
@@ -97,33 +90,29 @@ module Internator
|
|
|
97
90
|
git_upstream(remote, branch)
|
|
98
91
|
|
|
99
92
|
iteration = 1
|
|
100
|
-
Signal.trap(
|
|
93
|
+
Signal.trap('INT') do
|
|
101
94
|
puts "\n🛑 Interrupt received. Exiting cleanly..."
|
|
102
95
|
exit
|
|
103
96
|
end
|
|
104
97
|
|
|
105
98
|
begin
|
|
106
99
|
loop do
|
|
107
|
-
puts "\n🌀 Iteration ##{iteration} - #{Time.now.strftime(
|
|
100
|
+
puts "\n🌀 Iteration ##{iteration} - #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
108
101
|
|
|
109
|
-
exit_code =
|
|
110
|
-
if exit_code != 0
|
|
111
|
-
abort "🚨 Codex process exited with code #{exit_code}. Stopping."
|
|
112
|
-
end
|
|
102
|
+
exit_code = opencode_cycle(objectives, iteration, remote, default_base, branch, parent_branch)
|
|
103
|
+
abort "🚨 Opencode process exited with code #{exit_code}. Stopping." if exit_code != 0
|
|
113
104
|
|
|
114
|
-
if `git status --porcelain`.strip.empty?
|
|
115
|
-
abort "🎉 Objectives completed; no new changes. Exiting loop..."
|
|
116
|
-
end
|
|
105
|
+
abort '🎉 Objectives completed; no new changes. Exiting loop...' if `git status --porcelain`.strip.empty?
|
|
117
106
|
|
|
118
107
|
auto_commit
|
|
119
|
-
puts "⏳ Waiting #{delay_mins} minutes for next iteration... Current time: #{Time.now.strftime(
|
|
108
|
+
puts "⏳ Waiting #{delay_mins} minutes for next iteration... Current time: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
120
109
|
sleep(delay_mins * 60)
|
|
121
110
|
iteration += 1
|
|
122
111
|
end
|
|
123
|
-
rescue => e
|
|
112
|
+
rescue StandardError => e
|
|
124
113
|
puts "🚨 Critical error: #{e.message}"
|
|
125
114
|
ensure
|
|
126
|
-
puts "\n🏁 Process completed - #{Time.now.strftime(
|
|
115
|
+
puts "\n🏁 Process completed - #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
127
116
|
end
|
|
128
117
|
end
|
|
129
118
|
|
|
@@ -135,11 +124,10 @@ module Internator
|
|
|
135
124
|
# Try to resolve remote HEAD via rev-parse
|
|
136
125
|
ref = `git rev-parse --abbrev-ref #{remote}/HEAD 2>/dev/null`.strip
|
|
137
126
|
return ref unless ref.empty?
|
|
127
|
+
|
|
138
128
|
# Fallback to symbolic-ref
|
|
139
129
|
sym = `git symbolic-ref refs/remotes/#{remote}/HEAD 2>/dev/null`.strip
|
|
140
|
-
if sym.start_with?('refs/remotes/')
|
|
141
|
-
return sym.sub('refs/remotes/', '')
|
|
142
|
-
end
|
|
130
|
+
return sym.sub('refs/remotes/', '') if sym.start_with?('refs/remotes/')
|
|
143
131
|
end
|
|
144
132
|
nil
|
|
145
133
|
end
|
|
@@ -161,12 +149,12 @@ module Internator
|
|
|
161
149
|
upstream
|
|
162
150
|
end
|
|
163
151
|
|
|
164
|
-
# Executes one
|
|
165
|
-
def self.
|
|
152
|
+
# Executes one Opencode iteration by diffing against the parent or default branch
|
|
153
|
+
def self.opencode_cycle(objectives, iteration, _remote, default_base, _branch, parent_branch = nil)
|
|
166
154
|
# Determine base branch: user-specified parent or detected default
|
|
167
155
|
base = parent_branch || default_base
|
|
168
156
|
current_diff = `git diff #{base} 2>/dev/null`
|
|
169
|
-
current_diff =
|
|
157
|
+
current_diff = 'No initial changes' if current_diff.strip.empty?
|
|
170
158
|
prompt = <<~PROMPT
|
|
171
159
|
Objectives: #{objectives}
|
|
172
160
|
Iteration: #{iteration}
|
|
@@ -175,35 +163,16 @@ module Internator
|
|
|
175
163
|
#{instructions}
|
|
176
164
|
PROMPT
|
|
177
165
|
|
|
178
|
-
|
|
166
|
+
OpencodeService.new(prompt).call
|
|
179
167
|
end
|
|
180
168
|
|
|
181
|
-
# Generate a concise commit message for the given diff using OpenAI
|
|
182
169
|
def self.generate_commit_message(diff)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
"Authorization" => "Bearer #{api_key}"
|
|
190
|
-
}
|
|
191
|
-
body = {
|
|
192
|
-
model: "gpt-4o-mini",
|
|
193
|
-
messages: [
|
|
194
|
-
{ role: "system", content: "You are a helpful assistant that generates concise git commit messages." },
|
|
195
|
-
{ role: "user", content: "Generate a concise commit message for the following diff:\n\n#{diff}" }
|
|
196
|
-
],
|
|
197
|
-
temperature: 0.3
|
|
198
|
-
}
|
|
199
|
-
response = http.post(uri.request_uri, JSON.generate(body), headers)
|
|
200
|
-
if response.is_a?(Net::HTTPSuccess)
|
|
201
|
-
json = JSON.parse(response.body)
|
|
202
|
-
msg = json.dig("choices", 0, "message", "content")
|
|
203
|
-
return msg.strip if msg
|
|
204
|
-
end
|
|
205
|
-
rescue
|
|
206
|
-
nil
|
|
170
|
+
prompt = "Generate a concise commit message for the following diff:\n\n#{diff}"
|
|
171
|
+
stdout, status = OpencodeService.new(prompt).call(return_output: true)
|
|
172
|
+
msg = stdout.to_s.strip
|
|
173
|
+
return 'Some changes' if status != 0 || msg.empty?
|
|
174
|
+
|
|
175
|
+
msg
|
|
207
176
|
end
|
|
208
177
|
|
|
209
178
|
def self.auto_commit
|
|
@@ -222,14 +191,14 @@ module Internator
|
|
|
222
191
|
first_line = commit_msg.lines.first.to_s.strip
|
|
223
192
|
puts "✅ Commit made: #{first_line}"
|
|
224
193
|
else
|
|
225
|
-
puts
|
|
194
|
+
puts '❌ Error committing'
|
|
226
195
|
end
|
|
227
196
|
end
|
|
228
197
|
|
|
229
198
|
if system('git', 'push')
|
|
230
|
-
puts
|
|
199
|
+
puts '✅ Push successful'
|
|
231
200
|
else
|
|
232
|
-
puts
|
|
201
|
+
puts '❌ Error pushing to remote'
|
|
233
202
|
end
|
|
234
203
|
end
|
|
235
204
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'shellwords'
|
|
2
|
+
require 'open3'
|
|
3
|
+
|
|
4
|
+
module Internator
|
|
5
|
+
class OpencodeService
|
|
6
|
+
def initialize(instruction)
|
|
7
|
+
@instruction = instruction
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Executes `opencode run "<instruction>"`.
|
|
11
|
+
# When return_output is true, returns [stdout, exit_code]; otherwise returns exit code.
|
|
12
|
+
def call(return_output: false)
|
|
13
|
+
command = "opencode run #{Shellwords.escape(@instruction)}"
|
|
14
|
+
if return_output
|
|
15
|
+
stdout, _stderr, status = Open3.capture3(command)
|
|
16
|
+
[stdout, status.exitstatus]
|
|
17
|
+
else
|
|
18
|
+
system(command)
|
|
19
|
+
$?.exitstatus
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/internator/version.rb
CHANGED
data/lib/internator.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: internator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AlexLarra
|
|
@@ -38,9 +38,8 @@ dependencies:
|
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0'
|
|
40
40
|
description: Internator is a Ruby-based CLI tool that automates iterative pull request
|
|
41
|
-
improvements using
|
|
42
|
-
|
|
43
|
-
iterations.
|
|
41
|
+
improvements using Opencode. It cycles through objectives, makes incremental changes,
|
|
42
|
+
automatically commits and pushes each update, and optionally waits between iterations.
|
|
44
43
|
email:
|
|
45
44
|
- clausrybnic@gmail.com
|
|
46
45
|
executables:
|
|
@@ -54,7 +53,7 @@ files:
|
|
|
54
53
|
- internator.gemspec
|
|
55
54
|
- lib/internator.rb
|
|
56
55
|
- lib/internator/cli.rb
|
|
57
|
-
- lib/internator/
|
|
56
|
+
- lib/internator/opencode_service.rb
|
|
58
57
|
- lib/internator/version.rb
|
|
59
58
|
homepage: https://github.com/AlexLarra/internator
|
|
60
59
|
licenses:
|
|
@@ -76,6 +75,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
76
75
|
requirements: []
|
|
77
76
|
rubygems_version: 3.6.9
|
|
78
77
|
specification_version: 4
|
|
79
|
-
summary: CLI tool that automates iterative pull request improvements using
|
|
80
|
-
Codex
|
|
78
|
+
summary: CLI tool that automates iterative pull request improvements using Opencode
|
|
81
79
|
test_files: []
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
module Internator
|
|
2
|
-
# Service for executing the Codex CLI with full-auto mode
|
|
3
|
-
class CodexService
|
|
4
|
-
# @param instruction [String] Text instruction for Codex
|
|
5
|
-
def initialize(instruction)
|
|
6
|
-
@instruction = instruction
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
# Executes `codex --full-auto` with the provided instruction.
|
|
10
|
-
# @return [Integer] Exit code of the Codex process
|
|
11
|
-
def call
|
|
12
|
-
command = [
|
|
13
|
-
"codex",
|
|
14
|
-
"exec",
|
|
15
|
-
"-c",
|
|
16
|
-
"preferred_auth_method=apikey",
|
|
17
|
-
"--full-auto",
|
|
18
|
-
@instruction
|
|
19
|
-
]
|
|
20
|
-
system(*command)
|
|
21
|
-
$?.exitstatus
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|