internator 0.1.9 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 677bf4f9a06e89a9595db0b00170e3f3f681f10df110796cae33de79e8f1a539
4
- data.tar.gz: d2ada07c647aff576ba3c60550643e2bb729184b55d3c00f0a5d557fa83071a4
3
+ metadata.gz: 5b41c036134848c56e6d552dd9ce8975d62ea52196497c855bc3072188eb5f9c
4
+ data.tar.gz: 6bf30109d8ec851dd0d25b96ddea9360d8c4d85a6a1495090fb99e894bceda4f
5
5
  SHA512:
6
- metadata.gz: 417692ddaf3d8cc9fc8c4ce84c77aa627ceba9c1f3aed744737ef0525a9baab8705bfc6abcce5a15b3ffb6288999d817ff10c0442effe7a5c3332fa9d0f6cd00
7
- data.tar.gz: 198f1b5c098fe81e6e49388e2f557362193ae7851156606c3e777bc8edf9495a7312c938ec84dbdc5d63690ab69e75ed7f768a760866da61649f20e8657f5da1
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 OpenAI's Codex. It cycles through objectives, makes incremental changes, automatically commits and pushes each update, and optionally waits between iterations.
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
- - [Codex CLI](https://github.com/openai/codex) installed (v 0.46.0).
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 OpenAI's Codex"
10
- spec.description = "Internator is a Ruby-based CLI tool that automates iterative pull request improvements using OpenAI's Codex. It cycles through objectives, makes incremental changes, automatically commits and pushes each update, and optionally waits between iterations."
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
 
@@ -1,8 +1,5 @@
1
- require "net/http"
2
- require "uri"
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("which codex > /dev/null 2>&1")
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 = Integer(args[1]) rescue abort("❌ Invalid delay_mins: must be an integer")
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("/", 2)
76
+ remote, default_base = git_detect_default_base&.split('/', 2)
84
77
  branch = git_current_branch
85
78
 
86
- abort "❌ Git remote is not detected." unless remote
87
- abort "❌ Git default branch is not detected." unless default_base
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("INT") do
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("%Y-%m-%d %H:%M:%S")}"
100
+ puts "\n🌀 Iteration ##{iteration} - #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
108
101
 
109
- exit_code = codex_cycle(objectives, iteration, remote, default_base, branch, parent_branch)
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("%Y-%m-%d %H:%M:%S")}"
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("%Y-%m-%d %H:%M:%S")}"
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 Codex iteration by diffing against the parent or default branch
165
- def self.codex_cycle(objectives, iteration, remote, default_base, branch, parent_branch = nil)
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 = "No initial changes" if current_diff.strip.empty?
157
+ current_diff = 'No initial changes' if current_diff.strip.empty?
170
158
  prompt = <<~PROMPT
171
159
  Objectives: #{objectives}
172
160
  Iteration: #{iteration}
@@ -175,34 +163,16 @@ module Internator
175
163
  #{instructions}
176
164
  PROMPT
177
165
 
178
- CodexService.new(prompt).call
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
- uri = URI("https://api.openai.com/v1/chat/completions")
184
- http = Net::HTTP.new(uri.host, uri.port)
185
- http.use_ssl = true
186
- headers = {
187
- "Content-Type" => "application/json",
188
- "Authorization" => "Bearer #{ENV["OPENAI_API_KEY"]}"
189
- }
190
- body = {
191
- model: "gpt-4o-mini",
192
- messages: [
193
- { role: "system", content: "You are a helpful assistant that generates concise git commit messages." },
194
- { role: "user", content: "Generate a concise commit message for the following diff:\n\n#{diff}" }
195
- ],
196
- temperature: 0.3
197
- }
198
- response = http.post(uri.request_uri, JSON.generate(body), headers)
199
- if response.is_a?(Net::HTTPSuccess)
200
- json = JSON.parse(response.body)
201
- msg = json.dig("choices", 0, "message", "content")
202
- return msg.strip if msg
203
- end
204
- rescue
205
- 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
206
176
  end
207
177
 
208
178
  def self.auto_commit
@@ -221,14 +191,14 @@ module Internator
221
191
  first_line = commit_msg.lines.first.to_s.strip
222
192
  puts "✅ Commit made: #{first_line}"
223
193
  else
224
- puts "❌ Error committing"
194
+ puts '❌ Error committing'
225
195
  end
226
196
  end
227
197
 
228
198
  if system('git', 'push')
229
- puts "✅ Push successful"
199
+ puts '✅ Push successful'
230
200
  else
231
- puts "❌ Error pushing to remote"
201
+ puts '❌ Error pushing to remote'
232
202
  end
233
203
  end
234
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
@@ -1,3 +1,3 @@
1
1
  module Internator
2
- VERSION = "0.1.9"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/internator.rb CHANGED
@@ -1,6 +1,6 @@
1
- require "internator/version"
2
- require "internator/codex_service"
3
- require "internator/cli"
1
+ require 'internator/version'
2
+ require 'internator/opencode_service'
3
+ require 'internator/cli'
4
4
 
5
5
  module Internator
6
6
  end
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.1.9
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 OpenAI's Codex. It cycles through objectives, makes incremental
42
- changes, automatically commits and pushes each update, and optionally waits between
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/codex_service.rb
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 OpenAI's
80
- Codex
78
+ summary: CLI tool that automates iterative pull request improvements using Opencode
81
79
  test_files: []
@@ -1,20 +0,0 @@
1
- require 'shellwords'
2
-
3
- module Internator
4
- class CodexService
5
- def initialize(instruction)
6
- @instruction = instruction
7
- end
8
-
9
- # Executes `codex exec --full-auto` with the provided instruction.
10
- # @return [Integer] Exit code of the Codex process
11
- def call
12
- command = <<~CMD
13
- printenv OPENAI_API_KEY | codex login --with-api-key &&
14
- codex exec --full-auto #{Shellwords.escape(@instruction)}
15
- CMD
16
- system(command)
17
- $?.exitstatus
18
- end
19
- end
20
- end