jules-ruby 0.0.67

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.jules/bolt.md +4 -0
  3. data/.rubocop.yml +51 -0
  4. data/AGENTS.md +250 -0
  5. data/CHANGELOG.md +20 -0
  6. data/CONTRIBUTING.md +82 -0
  7. data/LICENSE +21 -0
  8. data/README.md +330 -0
  9. data/Rakefile +70 -0
  10. data/SECURITY.md +41 -0
  11. data/assets/banner.png +0 -0
  12. data/bin/jules-ruby +7 -0
  13. data/jules-ruby.gemspec +43 -0
  14. data/lib/jules-ruby/cli/activities.rb +142 -0
  15. data/lib/jules-ruby/cli/banner.rb +113 -0
  16. data/lib/jules-ruby/cli/base.rb +38 -0
  17. data/lib/jules-ruby/cli/interactive/activity_renderer.rb +81 -0
  18. data/lib/jules-ruby/cli/interactive/session_creator.rb +112 -0
  19. data/lib/jules-ruby/cli/interactive/session_manager.rb +285 -0
  20. data/lib/jules-ruby/cli/interactive/source_manager.rb +65 -0
  21. data/lib/jules-ruby/cli/interactive.rb +48 -0
  22. data/lib/jules-ruby/cli/prompts.rb +184 -0
  23. data/lib/jules-ruby/cli/sessions.rb +185 -0
  24. data/lib/jules-ruby/cli/sources.rb +72 -0
  25. data/lib/jules-ruby/cli.rb +127 -0
  26. data/lib/jules-ruby/client.rb +130 -0
  27. data/lib/jules-ruby/configuration.rb +20 -0
  28. data/lib/jules-ruby/errors.rb +35 -0
  29. data/lib/jules-ruby/models/activity.rb +137 -0
  30. data/lib/jules-ruby/models/artifact.rb +78 -0
  31. data/lib/jules-ruby/models/github_branch.rb +17 -0
  32. data/lib/jules-ruby/models/github_repo.rb +31 -0
  33. data/lib/jules-ruby/models/plan.rb +23 -0
  34. data/lib/jules-ruby/models/plan_step.rb +25 -0
  35. data/lib/jules-ruby/models/pull_request.rb +23 -0
  36. data/lib/jules-ruby/models/session.rb +111 -0
  37. data/lib/jules-ruby/models/source.rb +23 -0
  38. data/lib/jules-ruby/models/source_context.rb +35 -0
  39. data/lib/jules-ruby/resources/activities.rb +76 -0
  40. data/lib/jules-ruby/resources/base.rb +27 -0
  41. data/lib/jules-ruby/resources/sessions.rb +125 -0
  42. data/lib/jules-ruby/resources/sources.rb +61 -0
  43. data/lib/jules-ruby/version.rb +5 -0
  44. data/lib/jules-ruby.rb +43 -0
  45. data/mise.toml +2 -0
  46. metadata +232 -0
data/README.md ADDED
@@ -0,0 +1,330 @@
1
+ # Jules Ruby
2
+
3
+ <p align="center">
4
+ <img src="assets/banner.png" alt="Jules Ruby" width="600">
5
+ </p>
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/jules-ruby.svg)](https://badge.fury.io/rb/jules-ruby)
8
+ [![CI](https://github.com/tweibley/jules-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/tweibley/jules-ruby/actions/workflows/ci.yml)
9
+
10
+ A Ruby gem for interacting with the [Jules API](https://developers.google.com/jules/api) to programmatically create and manage asynchronous coding tasks.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'jules-ruby'
18
+ ```
19
+
20
+ Then execute:
21
+
22
+ ```bash
23
+ bundle install
24
+ ```
25
+
26
+ Or install it yourself:
27
+
28
+ ```bash
29
+ gem install jules-ruby
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ ### Using environment variables (recommended)
35
+
36
+ Create a `.env` file in your project root:
37
+
38
+ ```
39
+ JULES_API_KEY=your_api_key_here
40
+ ```
41
+
42
+ The gem automatically loads from `.env` using dotenv.
43
+
44
+ ### Using Ruby configuration
45
+
46
+ ```ruby
47
+ require 'jules-ruby'
48
+
49
+ JulesRuby.configure do |config|
50
+ config.api_key = 'your_api_key_here'
51
+ config.timeout = 60 # optional, default is 30 seconds
52
+ end
53
+ ```
54
+
55
+ ### Per-client configuration
56
+
57
+ ```ruby
58
+ client = JulesRuby::Client.new(api_key: 'different_api_key')
59
+ ```
60
+
61
+ ## Command-Line Interface
62
+
63
+ The gem includes a CLI for interacting with the Jules API from your terminal.
64
+
65
+ ### Installation
66
+
67
+ After installing the gem, the `jules-ruby` command becomes available:
68
+
69
+ ```bash
70
+ jules-ruby help
71
+ ```
72
+
73
+ ### Commands
74
+
75
+ #### Sources
76
+
77
+ ```bash
78
+ # List all connected repositories
79
+ jules-ruby sources list
80
+ jules-ruby sources list --format=json
81
+
82
+ # Show source details
83
+ jules-ruby sources show sources/github/owner/repo
84
+ ```
85
+
86
+ #### Sessions
87
+
88
+ ```bash
89
+ # List all sessions
90
+ jules-ruby sessions list
91
+ jules-ruby sessions list --format=json
92
+
93
+ # Show session details
94
+ jules-ruby sessions show <session_id>
95
+
96
+ # Create a new session
97
+ jules-ruby sessions create \
98
+ --source=sources/github/owner/repo \
99
+ --branch=main \
100
+ --prompt="Fix the login bug" \
101
+ --title="Fix Login" \
102
+ --auto-pr
103
+
104
+ # Approve a plan
105
+ jules-ruby sessions approve <session_id>
106
+
107
+ # Send a message
108
+ jules-ruby sessions message <session_id> --prompt="Also add unit tests"
109
+
110
+ # Delete a session
111
+ jules-ruby sessions delete <session_id>
112
+ ```
113
+
114
+ #### Activities
115
+
116
+ ```bash
117
+ # List activities for a session
118
+ jules-ruby activities list <session_id>
119
+ jules-ruby activities list <session_id> --format=json
120
+
121
+ # Show activity details
122
+ jules-ruby activities show sessions/<session_id>/activities/<activity_id>
123
+ ```
124
+
125
+ ### Interactive Mode
126
+
127
+ Start the interactive TUI for guided workflows:
128
+
129
+ ```bash
130
+ jules-ruby interactive
131
+ # or
132
+ jules-ruby -i
133
+ ```
134
+
135
+ Interactive mode provides:
136
+ - **Main menu** - Navigate between actions
137
+ - **Session wizard** - Step-by-step session creation with source selection
138
+ - **Session viewer** - View details, approve plans, send messages, delete
139
+ - **Activities viewer** - See session progress and history
140
+
141
+ ### Output Formats
142
+
143
+ All list commands support `--format=table` (default) or `--format=json`.
144
+
145
+ ### Gemini CLI Extension
146
+
147
+ Use jules-ruby directly from [Gemini CLI](https://github.com/google-gemini/gemini-cli) with our extension:
148
+
149
+ 👉 [jules-ruby-gemini-cli-extension](https://github.com/tweibley/jules-ruby-gemini-cli-extension)
150
+
151
+ ## Usage
152
+
153
+ ### Initialize the client
154
+
155
+ ```ruby
156
+ require 'jules-ruby'
157
+
158
+ client = JulesRuby::Client.new
159
+ ```
160
+
161
+ ### Sources
162
+
163
+ List your connected repositories:
164
+
165
+ ```ruby
166
+ # List sources with pagination
167
+ result = client.sources.list(page_size: 10)
168
+ result[:sources].each do |source|
169
+ puts "#{source.name}: #{source.github_repo.full_name}"
170
+ end
171
+
172
+ # Get all sources
173
+ sources = client.sources.all
174
+ ```
175
+
176
+ ### Sessions
177
+
178
+ Create and manage coding sessions:
179
+
180
+ ```ruby
181
+ # Create a new session
182
+ session = client.sessions.create(
183
+ prompt: "Fix the login bug in the authentication module",
184
+ source_context: {
185
+ "source" => "sources/github/myorg/myrepo",
186
+ "githubRepoContext" => { "startingBranch" => "main" }
187
+ },
188
+ title: "Fix Login Bug",
189
+ automation_mode: "AUTO_CREATE_PR" # optional: auto-create PR when done
190
+ )
191
+
192
+ puts "Session created: #{session.url}"
193
+ puts "State: #{session.state}"
194
+
195
+ # List sessions
196
+ result = client.sessions.list(page_size: 10)
197
+ result[:sessions].each { |s| puts "#{s.title}: #{s.state}" }
198
+
199
+ # Get a specific session
200
+ session = client.sessions.find("12345678")
201
+ # or
202
+ session = client.sessions.find("sessions/12345678")
203
+
204
+ # Check session state
205
+ if session.awaiting_plan_approval?
206
+ client.sessions.approve_plan(session.name)
207
+ end
208
+
209
+ # Send a message to the agent
210
+ client.sessions.send_message(session.name, prompt: "Can you also add unit tests?")
211
+
212
+ # Delete a session
213
+ client.sessions.destroy(session.name)
214
+ ```
215
+
216
+ ### Activities
217
+
218
+ Monitor session progress:
219
+
220
+ ```ruby
221
+ # List activities for a session
222
+ result = client.activities.list(session.name, page_size: 30)
223
+
224
+ result[:activities].each do |activity|
225
+ case activity.type
226
+ when :plan_generated
227
+ puts "Plan: #{activity.plan.steps.map(&:title).join(', ')}"
228
+ when :progress_updated
229
+ puts "Progress: #{activity.progress_title}"
230
+ when :session_completed
231
+ puts "Session completed!"
232
+ when :session_failed
233
+ puts "Failed: #{activity.failure_reason}"
234
+ end
235
+ end
236
+
237
+ # Get all activities
238
+ activities = client.activities.all(session.name)
239
+ ```
240
+
241
+ ### Working with models
242
+
243
+ #### Session states
244
+
245
+ ```ruby
246
+ session.queued? # Waiting to start
247
+ session.planning? # Creating a plan
248
+ session.awaiting_plan_approval? # Needs plan approval
249
+ session.awaiting_user_feedback? # Waiting for user input
250
+ session.in_progress? # Working on the task
251
+ session.completed? # Finished successfully
252
+ session.failed? # Failed
253
+ session.active? # Any non-terminal state
254
+ ```
255
+
256
+ #### Activity types
257
+
258
+ ```ruby
259
+ activity.agent_message? # Agent posted a message
260
+ activity.user_message? # User posted a message
261
+ activity.plan_generated? # A plan was created
262
+ activity.plan_approved? # A plan was approved
263
+ activity.progress_update? # Progress update
264
+ activity.session_completed? # Session completed
265
+ activity.session_failed? # Session failed
266
+
267
+ # Get content based on type
268
+ activity.message # For agent/user messages
269
+ activity.plan # For plan_generated (returns Plan object)
270
+ activity.progress_title # For progress updates
271
+ activity.failure_reason # For session_failed
272
+ ```
273
+
274
+ #### Artifacts
275
+
276
+ ```ruby
277
+ activity.artifacts.each do |artifact|
278
+ case artifact.type
279
+ when :change_set
280
+ puts "Changes to: #{artifact.source}"
281
+ puts "Commit message: #{artifact.suggested_commit_message}"
282
+ when :bash_output
283
+ puts "Command: #{artifact.bash_command}"
284
+ puts "Output: #{artifact.bash_output_text}"
285
+ puts "Exit code: #{artifact.bash_exit_code}"
286
+ when :media
287
+ puts "Media type: #{artifact.media_mime_type}"
288
+ end
289
+ end
290
+ ```
291
+
292
+ ## Error Handling
293
+
294
+ ```ruby
295
+ begin
296
+ client.sessions.find("nonexistent")
297
+ rescue JulesRuby::AuthenticationError => e
298
+ puts "Invalid API key"
299
+ rescue JulesRuby::NotFoundError => e
300
+ puts "Session not found"
301
+ rescue JulesRuby::RateLimitError => e
302
+ puts "Rate limit exceeded, try again later"
303
+ rescue JulesRuby::ServerError => e
304
+ puts "Server error: #{e.message}"
305
+ rescue JulesRuby::Error => e
306
+ puts "API error: #{e.message} (status: #{e.status_code})"
307
+ end
308
+ ```
309
+
310
+ ## Requirements
311
+
312
+ - Ruby 3.0+
313
+ - async-http gem
314
+
315
+ ## Development
316
+
317
+ ```bash
318
+ # Install dependencies
319
+ bundle install
320
+
321
+ # Run tests
322
+ bundle exec rspec
323
+
324
+ # Run linter
325
+ bundle exec rubocop
326
+ ```
327
+
328
+ ## License
329
+
330
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec rubocop]
11
+
12
+ namespace :release do
13
+ desc 'Bump patch version, commit, tag, and push'
14
+ task :patch do
15
+ bump_version(:patch)
16
+ end
17
+
18
+ desc 'Bump minor version, commit, tag, and push'
19
+ task :minor do
20
+ bump_version(:minor)
21
+ end
22
+
23
+ desc 'Bump major version, commit, tag, and push'
24
+ task :major do
25
+ bump_version(:major)
26
+ end
27
+ end
28
+
29
+ def bump_version(type)
30
+ gemspec_file = 'jules-ruby.gemspec'
31
+ content = File.read(gemspec_file)
32
+
33
+ # Extract current version
34
+ version_match = content.match(/spec\.version\s*=\s*["'](\d+)\.(\d+)\.(\d+)["']/)
35
+ unless version_match
36
+ puts 'Could not find version in gemspec'
37
+ exit 1
38
+ end
39
+
40
+ major, minor, patch = version_match[1..3].map(&:to_i)
41
+
42
+ # Bump version based on type
43
+ case type
44
+ when :major
45
+ major += 1
46
+ minor = 0
47
+ patch = 0
48
+ when :minor
49
+ minor += 1
50
+ patch = 0
51
+ when :patch
52
+ patch += 1
53
+ end
54
+
55
+ new_version = "#{major}.#{minor}.#{patch}"
56
+ puts "Bumping version to #{new_version}"
57
+
58
+ # Update gemspec
59
+ new_content = content.sub(/spec\.version\s*=\s*["']\d+\.\d+\.\d+["']/, "spec.version = \"#{new_version}\"")
60
+ File.write(gemspec_file, new_content)
61
+
62
+ # Git operations
63
+ sh "git add #{gemspec_file}"
64
+ sh "git commit -m 'Bump version to #{new_version}'"
65
+ sh "git tag v#{new_version}"
66
+ sh 'git push origin main'
67
+ sh "git push origin v#{new_version}"
68
+
69
+ puts "Released v#{new_version}!"
70
+ end
data/SECURITY.md ADDED
@@ -0,0 +1,41 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 0.0.x | :white_check_mark: |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ If you discover a security vulnerability in jules-ruby, please report it responsibly:
12
+
13
+ 1. **Do not** open a public GitHub issue
14
+ 2. Email the maintainers at tweibley@gmail.com with:
15
+ - Description of the vulnerability
16
+ - Steps to reproduce
17
+ - Potential impact
18
+ - Suggested fix (if any)
19
+
20
+ We will acknowledge receipt within 48 hours and work with you to understand and address the issue.
21
+
22
+ ## Security Practices
23
+
24
+ This gem follows RubyGems security best practices:
25
+
26
+ - **MFA Required**: All gem owners must have MFA enabled
27
+ - **Trusted Publishing**: Releases use OIDC authentication (no long-lived API keys)
28
+ - **Checksums**: SHA512 checksums are published with each release
29
+ - **Dependency Scanning**: We use `bundle audit` to check for vulnerable dependencies
30
+
31
+ ## Verifying Releases
32
+
33
+ You can verify the integrity of a release:
34
+
35
+ ```bash
36
+ # Fetch the gem
37
+ gem fetch jules-ruby -v VERSION
38
+
39
+ # Compare checksum with the one published in GitHub Releases
40
+ ruby -rdigest/sha2 -e "puts Digest::SHA512.hexdigest(File.read('jules-ruby-VERSION.gem'))"
41
+ ```
data/assets/banner.png ADDED
Binary file
data/bin/jules-ruby ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/jules-ruby'
5
+ require_relative '../lib/jules-ruby/cli'
6
+
7
+ JulesRuby::CLI.start(ARGV)
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "jules-ruby"
5
+ spec.version = "0.0.67"
6
+ spec.authors = ["Taylor Weibley"]
7
+ spec.email = ["tweibley@gmail.com"]
8
+
9
+ spec.summary = "Ruby CLI for the Jules API"
10
+ spec.description = "A Ruby gem for interacting with the Jules API to programmatically create and manage asynchronous coding tasks."
11
+ spec.homepage = "https://github.com/tweibley/jules-ruby"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = ">= 3.0.0"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = spec.homepage
17
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
18
+ spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
19
+ spec.metadata["documentation_uri"] = "#{spec.homepage}#readme"
20
+ spec.metadata["rubygems_mfa_required"] = "true"
21
+
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (File.expand_path(f) == __FILE__) ||
25
+ f.start_with?(*%w[test/ spec/ features/ .git .github Gemfile])
26
+ end
27
+ end
28
+ spec.bindir = "bin"
29
+ spec.executables = ['jules-ruby']
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "async-http", "~> 0.75"
33
+ spec.add_dependency "dotenv", "~> 3.0"
34
+ spec.add_dependency "thor", "~> 1.3"
35
+ spec.add_dependency "tty-prompt", "~> 0.23"
36
+ spec.add_dependency "tty-spinner", "~> 0.9"
37
+ spec.add_dependency "pastel", "~> 0.8"
38
+
39
+ spec.add_development_dependency "rspec", "~> 3.0"
40
+ spec.add_development_dependency "webmock", "~> 3.0"
41
+ spec.add_development_dependency "rubocop", "~> 1.0"
42
+ spec.add_development_dependency "simplecov", "~> 0.22"
43
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module JulesRuby
6
+ module Commands
7
+ # Activities subcommand
8
+ class Activities < Base
9
+ desc 'list SESSION_ID', 'List activities for a session'
10
+ long_desc <<~LONGDESC
11
+ List all activities for a session (messages, plans, progress updates).
12
+
13
+ Example:
14
+ $ jules-ruby activities list SESSION_ID
15
+ LONGDESC
16
+ format_option
17
+ def list(session_id)
18
+ activities = client.activities.all(session_id)
19
+ if options[:format] == 'json'
20
+ puts JSON.pretty_generate(activities.map(&:to_h))
21
+ else
22
+ print_activities_table(activities)
23
+ end
24
+ rescue JulesRuby::Error => e
25
+ error_exit(e)
26
+ end
27
+
28
+ desc 'show NAME', 'Show details for an activity'
29
+ long_desc <<~LONGDESC
30
+ Show details for a specific activity.
31
+
32
+ Example:
33
+ $ jules-ruby activities show sessions/SESSION_ID/activities/ACTIVITY_ID
34
+ LONGDESC
35
+ format_option
36
+ def show(name)
37
+ activity = client.activities.find(name)
38
+ if options[:format] == 'json'
39
+ puts JSON.pretty_generate(activity.to_h)
40
+ else
41
+ print_activity_details(activity)
42
+ end
43
+ rescue JulesRuby::Error => e
44
+ error_exit(e)
45
+ end
46
+
47
+ private
48
+
49
+ def print_activities_table(activities)
50
+ if activities.empty?
51
+ puts 'No activities found.'
52
+ return
53
+ end
54
+ puts 'ID TYPE FROM DESCRIPTION '
55
+ puts '-' * 95
56
+ activities.each do |a|
57
+ desc = truncate(activity_summary(a), 38)
58
+ puts format('%<id>-20s %<type>-20s %<originator>-10s %<desc>-40s',
59
+ id: a.id, type: a.type, originator: a.originator || 'N/A', desc: desc)
60
+ end
61
+ end
62
+
63
+ def print_activity_details(activity)
64
+ print_activity_header(activity)
65
+ print_activity_content(activity)
66
+ print_activity_artifacts(activity.artifacts) if activity.artifacts&.any?
67
+ end
68
+
69
+ def print_activity_header(activity)
70
+ puts "Name: #{activity.name}"
71
+ puts "ID: #{activity.id}"
72
+ puts "Type: #{activity.type}"
73
+ puts "Originator: #{activity.originator}"
74
+ puts "Created: #{activity.create_time}"
75
+ puts "Description: #{activity.description}" if activity.description
76
+ end
77
+
78
+ def print_activity_content(activity)
79
+ case activity.type
80
+ when :agent_messaged, :user_messaged
81
+ print_message(activity)
82
+ when :plan_generated
83
+ print_plan(activity)
84
+ when :progress_updated
85
+ print_progress(activity)
86
+ when :session_failed
87
+ puts "\nFailure Reason: #{activity.failure_reason}"
88
+ end
89
+ end
90
+
91
+ def print_message(activity)
92
+ puts "\nMessage:"
93
+ puts " #{activity.message}"
94
+ end
95
+
96
+ def print_plan(activity)
97
+ return unless activity.plan
98
+
99
+ puts "\nPlan:"
100
+ activity.plan.steps&.each_with_index do |step, i|
101
+ puts " #{i + 1}. #{step.title}"
102
+ end
103
+ end
104
+
105
+ def print_progress(activity)
106
+ puts "\nProgress: #{activity.progress_title}"
107
+ puts "Details: #{activity.progress_description}" if activity.progress_description
108
+ end
109
+
110
+ def print_activity_artifacts(artifacts)
111
+ puts "\nArtifacts:"
112
+ artifacts.each do |artifact|
113
+ puts " - Type: #{artifact.type}"
114
+ end
115
+ end
116
+
117
+ def activity_summary(activity)
118
+ case activity.type
119
+ when :agent_messaged, :user_messaged
120
+ activity.message || ''
121
+ when :plan_generated
122
+ "Plan with #{activity.plan&.steps&.length || 0} steps"
123
+ when :progress_updated
124
+ activity.progress_title || ''
125
+ else
126
+ summary_for_state(activity)
127
+ end
128
+ end
129
+
130
+ def summary_for_state(activity)
131
+ case activity.type
132
+ when :session_completed
133
+ 'Session completed'
134
+ when :session_failed
135
+ activity.failure_reason || 'Session failed'
136
+ else
137
+ activity.description || ''
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end