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.
- checksums.yaml +7 -0
- data/.jules/bolt.md +4 -0
- data/.rubocop.yml +51 -0
- data/AGENTS.md +250 -0
- data/CHANGELOG.md +20 -0
- data/CONTRIBUTING.md +82 -0
- data/LICENSE +21 -0
- data/README.md +330 -0
- data/Rakefile +70 -0
- data/SECURITY.md +41 -0
- data/assets/banner.png +0 -0
- data/bin/jules-ruby +7 -0
- data/jules-ruby.gemspec +43 -0
- data/lib/jules-ruby/cli/activities.rb +142 -0
- data/lib/jules-ruby/cli/banner.rb +113 -0
- data/lib/jules-ruby/cli/base.rb +38 -0
- data/lib/jules-ruby/cli/interactive/activity_renderer.rb +81 -0
- data/lib/jules-ruby/cli/interactive/session_creator.rb +112 -0
- data/lib/jules-ruby/cli/interactive/session_manager.rb +285 -0
- data/lib/jules-ruby/cli/interactive/source_manager.rb +65 -0
- data/lib/jules-ruby/cli/interactive.rb +48 -0
- data/lib/jules-ruby/cli/prompts.rb +184 -0
- data/lib/jules-ruby/cli/sessions.rb +185 -0
- data/lib/jules-ruby/cli/sources.rb +72 -0
- data/lib/jules-ruby/cli.rb +127 -0
- data/lib/jules-ruby/client.rb +130 -0
- data/lib/jules-ruby/configuration.rb +20 -0
- data/lib/jules-ruby/errors.rb +35 -0
- data/lib/jules-ruby/models/activity.rb +137 -0
- data/lib/jules-ruby/models/artifact.rb +78 -0
- data/lib/jules-ruby/models/github_branch.rb +17 -0
- data/lib/jules-ruby/models/github_repo.rb +31 -0
- data/lib/jules-ruby/models/plan.rb +23 -0
- data/lib/jules-ruby/models/plan_step.rb +25 -0
- data/lib/jules-ruby/models/pull_request.rb +23 -0
- data/lib/jules-ruby/models/session.rb +111 -0
- data/lib/jules-ruby/models/source.rb +23 -0
- data/lib/jules-ruby/models/source_context.rb +35 -0
- data/lib/jules-ruby/resources/activities.rb +76 -0
- data/lib/jules-ruby/resources/base.rb +27 -0
- data/lib/jules-ruby/resources/sessions.rb +125 -0
- data/lib/jules-ruby/resources/sources.rb +61 -0
- data/lib/jules-ruby/version.rb +5 -0
- data/lib/jules-ruby.rb +43 -0
- data/mise.toml +2 -0
- 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
|
+
[](https://badge.fury.io/rb/jules-ruby)
|
|
8
|
+
[](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
data/jules-ruby.gemspec
ADDED
|
@@ -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
|