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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'prompts'
|
|
4
|
+
require_relative 'interactive/session_creator'
|
|
5
|
+
require_relative 'interactive/session_manager'
|
|
6
|
+
require_relative 'interactive/source_manager'
|
|
7
|
+
|
|
8
|
+
module JulesRuby
|
|
9
|
+
# Interactive mode for jules-ruby CLI
|
|
10
|
+
class Interactive
|
|
11
|
+
def initialize
|
|
12
|
+
@client = JulesRuby::Client.new
|
|
13
|
+
@prompt = Prompts.prompt
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start
|
|
17
|
+
loop do
|
|
18
|
+
Prompts.clear_screen
|
|
19
|
+
Prompts.print_banner
|
|
20
|
+
|
|
21
|
+
choice = main_menu_selection
|
|
22
|
+
|
|
23
|
+
case choice
|
|
24
|
+
when :create_session
|
|
25
|
+
SessionCreator.new(@client, @prompt).run
|
|
26
|
+
when :view_sessions
|
|
27
|
+
SessionManager.new(@client, @prompt).run
|
|
28
|
+
when :browse_sources
|
|
29
|
+
SourceManager.new(@client, @prompt).run
|
|
30
|
+
when :exit
|
|
31
|
+
puts Prompts.rgb_color("\nGoodbye! 👋", :purple)
|
|
32
|
+
break
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def main_menu_selection
|
|
40
|
+
@prompt.select(Prompts.rgb_color('What would you like to do?', :lavender), cycle: true) do |menu|
|
|
41
|
+
menu.choice Prompts.rgb_color('Create new session', :purple), :create_session
|
|
42
|
+
menu.choice Prompts.rgb_color('View sessions', :purple), :view_sessions
|
|
43
|
+
menu.choice Prompts.rgb_color('Browse sources', :purple), :browse_sources
|
|
44
|
+
menu.choice Prompts.rgb_color('Exit', :purple), :exit
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pastel'
|
|
4
|
+
require 'tty-prompt'
|
|
5
|
+
require 'tty-spinner'
|
|
6
|
+
require_relative 'banner'
|
|
7
|
+
|
|
8
|
+
module JulesRuby
|
|
9
|
+
# Helper methods for interactive prompts
|
|
10
|
+
module Prompts
|
|
11
|
+
# Custom true-color RGB theme matching Jules CLI design
|
|
12
|
+
PASTEL = Pastel.new
|
|
13
|
+
|
|
14
|
+
# RGB color values matching the reference CLI screenshot
|
|
15
|
+
COLORS = {
|
|
16
|
+
purple: [147, 112, 219], # Selection highlight #9370DB
|
|
17
|
+
lavender: [196, 181, 253], # Accent text #C4B5FD
|
|
18
|
+
muted: [139, 92, 246], # Muted purple #8B5CF6
|
|
19
|
+
dim: [107, 114, 128] # Dim gray #6B7280
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
# Define custom symbols for the prompt
|
|
23
|
+
PROMPT_SYMBOLS = {
|
|
24
|
+
marker: '❯',
|
|
25
|
+
radio_on: '◉',
|
|
26
|
+
radio_off: '◯'
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
# State emoji indicators
|
|
30
|
+
STATE_EMOJI = {
|
|
31
|
+
'QUEUED' => '⏳',
|
|
32
|
+
'PLANNING' => '🔵',
|
|
33
|
+
'AWAITING_PLAN_APPROVAL' => '🟡',
|
|
34
|
+
'AWAITING_USER_FEEDBACK' => '🟠',
|
|
35
|
+
'IN_PROGRESS' => '🔵',
|
|
36
|
+
'PAUSED' => '⏸️',
|
|
37
|
+
'FAILED' => '🔴',
|
|
38
|
+
'COMPLETED' => '🟢'
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
STATE_LABELS = {
|
|
42
|
+
'QUEUED' => 'Queued',
|
|
43
|
+
'PLANNING' => 'Planning',
|
|
44
|
+
'AWAITING_PLAN_APPROVAL' => 'Needs Approval',
|
|
45
|
+
'AWAITING_USER_FEEDBACK' => 'Needs Feedback',
|
|
46
|
+
'IN_PROGRESS' => 'Working',
|
|
47
|
+
'PAUSED' => 'Paused',
|
|
48
|
+
'FAILED' => 'Failed',
|
|
49
|
+
'COMPLETED' => 'Done'
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
class << self
|
|
53
|
+
# Apply true-color RGB to text using ANSI escape sequences
|
|
54
|
+
def rgb_color(text, color_name)
|
|
55
|
+
r, g, b = COLORS[color_name]
|
|
56
|
+
"\e[38;2;#{r};#{g};#{b}m#{text}\e[0m"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def prompt
|
|
60
|
+
@prompt ||= TTY::Prompt.new(
|
|
61
|
+
interrupt: :exit,
|
|
62
|
+
symbols: PROMPT_SYMBOLS,
|
|
63
|
+
active_color: :magenta,
|
|
64
|
+
help_color: :cyan
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def spinner(message)
|
|
69
|
+
TTY::Spinner.new(
|
|
70
|
+
"[:spinner] #{rgb_color(message, :purple)}",
|
|
71
|
+
format: :dots,
|
|
72
|
+
success_mark: PASTEL.green('✓'),
|
|
73
|
+
error_mark: PASTEL.red('✗')
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def with_spinner(message)
|
|
78
|
+
spin = spinner(message)
|
|
79
|
+
spin.auto_spin
|
|
80
|
+
result = yield
|
|
81
|
+
spin.success(PASTEL.green('done'))
|
|
82
|
+
result
|
|
83
|
+
rescue StandardError => e
|
|
84
|
+
spin.error(PASTEL.red('failed'))
|
|
85
|
+
raise e
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def state_emoji(state)
|
|
89
|
+
# API returns nil for completed sessions
|
|
90
|
+
return '🟢' if state.nil?
|
|
91
|
+
|
|
92
|
+
STATE_EMOJI[state] || '❓'
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def state_label(state)
|
|
96
|
+
# API returns nil for completed sessions
|
|
97
|
+
return 'Completed' if state.nil?
|
|
98
|
+
|
|
99
|
+
STATE_LABELS[state] || state
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def format_session_choice(session)
|
|
103
|
+
emoji = state_emoji(session.state)
|
|
104
|
+
label = state_label(session.state)
|
|
105
|
+
title = session.title || session.prompt&.slice(0, 25) || 'Untitled'
|
|
106
|
+
title = "#{title[0..22]}..." if title.length > 25
|
|
107
|
+
time_ago = time_ago_in_words(session.update_time)
|
|
108
|
+
{
|
|
109
|
+
name: "#{emoji} #{rgb_color(title.ljust(27),
|
|
110
|
+
:purple)} #{rgb_color(label.ljust(15), :lavender)} #{rgb_color(time_ago, :dim)}",
|
|
111
|
+
value: session
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def format_source_choice(source)
|
|
116
|
+
repo_name = source.github_repo&.full_name || source.name
|
|
117
|
+
{
|
|
118
|
+
name: rgb_color(repo_name, :purple),
|
|
119
|
+
value: source
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def time_ago_in_words(time_string)
|
|
124
|
+
return 'N/A' unless time_string
|
|
125
|
+
|
|
126
|
+
time = Time.parse(time_string)
|
|
127
|
+
diff = Time.now - time
|
|
128
|
+
case diff
|
|
129
|
+
when 0..59
|
|
130
|
+
'just now'
|
|
131
|
+
when 60..3599
|
|
132
|
+
"#{(diff / 60).to_i}m ago"
|
|
133
|
+
when 3600..86_399
|
|
134
|
+
"#{(diff / 3600).to_i}h ago"
|
|
135
|
+
else
|
|
136
|
+
"#{(diff / 86_400).to_i}d ago"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def format_datetime(time_string)
|
|
141
|
+
return 'N/A' unless time_string
|
|
142
|
+
|
|
143
|
+
time = Time.parse(time_string)
|
|
144
|
+
today = Time.now.to_date
|
|
145
|
+
date = time.to_date
|
|
146
|
+
|
|
147
|
+
if date == today
|
|
148
|
+
"Today #{time.strftime('%l:%M %p').strip}"
|
|
149
|
+
elsif date == today - 1
|
|
150
|
+
"Yesterday #{time.strftime('%l:%M %p').strip}"
|
|
151
|
+
else
|
|
152
|
+
time.strftime('%b %d, %Y %l:%M %p').strip
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def clear_screen
|
|
157
|
+
print "\e[2J\e[H"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def header(title)
|
|
161
|
+
puts
|
|
162
|
+
puts " 🚀 #{rgb_color(title, :lavender)}"
|
|
163
|
+
puts " #{rgb_color('─' * 50, :muted)}"
|
|
164
|
+
puts
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def divider
|
|
168
|
+
rgb_color('─' * 50, :muted)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def highlight(text)
|
|
172
|
+
rgb_color(text, :lavender)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def muted(text)
|
|
176
|
+
rgb_color(text, :dim)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def print_banner
|
|
180
|
+
Banner.print_banner
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module JulesRuby
|
|
6
|
+
module Commands
|
|
7
|
+
# Sessions subcommand
|
|
8
|
+
class Sessions < Base
|
|
9
|
+
desc 'list', 'List all sessions'
|
|
10
|
+
format_option
|
|
11
|
+
def list
|
|
12
|
+
sessions = client.sessions.all
|
|
13
|
+
if options[:format] == 'json'
|
|
14
|
+
puts JSON.pretty_generate(sessions.map(&:to_h))
|
|
15
|
+
else
|
|
16
|
+
print_sessions_table(sessions)
|
|
17
|
+
end
|
|
18
|
+
rescue JulesRuby::Error => e
|
|
19
|
+
error_exit(e)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc 'show ID', 'Show details for a session'
|
|
23
|
+
format_option
|
|
24
|
+
def show(id)
|
|
25
|
+
session = client.sessions.find(id)
|
|
26
|
+
if options[:format] == 'json'
|
|
27
|
+
puts JSON.pretty_generate(session.to_h)
|
|
28
|
+
else
|
|
29
|
+
print_session_details(session)
|
|
30
|
+
end
|
|
31
|
+
rescue JulesRuby::Error => e
|
|
32
|
+
error_exit(e)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc 'create', 'Create a new session'
|
|
36
|
+
long_desc <<~LONGDESC
|
|
37
|
+
Create a new Jules coding session.
|
|
38
|
+
|
|
39
|
+
You must provide a prompt either inline with --prompt or from a file with --prompt-file.
|
|
40
|
+
If both are provided, --prompt-file takes precedence.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
# Create with inline prompt
|
|
44
|
+
$ jules-ruby sessions create --source=sources/github/owner/repo --prompt="Fix the login bug"
|
|
45
|
+
# Create with prompt from file
|
|
46
|
+
$ jules-ruby sessions create --source=sources/github/owner/repo --prompt-file=./task-instructions.md
|
|
47
|
+
# Create with custom branch and auto-PR
|
|
48
|
+
$ jules-ruby sessions create --source=sources/github/owner/repo --branch=develop --prompt="Add tests" --auto-pr
|
|
49
|
+
LONGDESC
|
|
50
|
+
option :source, required: true, desc: 'Source name (e.g., sources/github/owner/repo)'
|
|
51
|
+
option :branch, default: 'main', desc: 'Starting branch'
|
|
52
|
+
option :prompt, desc: 'Task prompt (inline text)'
|
|
53
|
+
option :prompt_file, desc: 'Path to file containing task prompt'
|
|
54
|
+
option :title, desc: 'Session title'
|
|
55
|
+
option :auto_pr, type: :boolean, default: false, desc: 'Auto-create PR when done'
|
|
56
|
+
def create
|
|
57
|
+
prompt_text = resolve_prompt
|
|
58
|
+
raise Thor::Error, 'You must provide --prompt or --prompt-file' if prompt_text.nil? || prompt_text.strip.empty?
|
|
59
|
+
|
|
60
|
+
session = create_session(prompt_text)
|
|
61
|
+
print_creation_success(session)
|
|
62
|
+
rescue JulesRuby::Error => e
|
|
63
|
+
error_exit(e)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
desc 'approve ID', 'Approve the plan for a session'
|
|
67
|
+
long_desc <<~LONGDESC
|
|
68
|
+
Approve the generated plan for a session.
|
|
69
|
+
Example:
|
|
70
|
+
$ jules-ruby sessions approve SESSION_ID
|
|
71
|
+
LONGDESC
|
|
72
|
+
def approve(id)
|
|
73
|
+
session = client.sessions.approve_plan(id)
|
|
74
|
+
puts "Plan approved for session: #{session.name}"
|
|
75
|
+
puts "State: #{session.state}"
|
|
76
|
+
rescue JulesRuby::Error => e
|
|
77
|
+
error_exit(e)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
desc 'message ID', 'Send a message to a session'
|
|
81
|
+
long_desc <<~LONGDESC
|
|
82
|
+
Send a message to an existing session.
|
|
83
|
+
Examples:
|
|
84
|
+
$ jules-ruby sessions message SESSION_ID --prompt="Please also add unit tests"
|
|
85
|
+
LONGDESC
|
|
86
|
+
option :prompt, required: true, desc: 'Message to send'
|
|
87
|
+
def message(id)
|
|
88
|
+
session = client.sessions.send_message(id, prompt: options[:prompt])
|
|
89
|
+
puts "Message sent to session: #{session.name}"
|
|
90
|
+
puts "State: #{session.state}"
|
|
91
|
+
rescue JulesRuby::Error => e
|
|
92
|
+
error_exit(e)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
desc 'delete ID', 'Delete a session'
|
|
96
|
+
def delete(id)
|
|
97
|
+
client.sessions.destroy(id)
|
|
98
|
+
puts "Session deleted: #{id}"
|
|
99
|
+
rescue JulesRuby::Error => e
|
|
100
|
+
error_exit(e)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def resolve_prompt
|
|
106
|
+
if options[:prompt_file]
|
|
107
|
+
file_path = File.expand_path(options[:prompt_file])
|
|
108
|
+
raise Thor::Error, "Prompt file not found: #{file_path}" unless File.exist?(file_path)
|
|
109
|
+
|
|
110
|
+
File.read(file_path)
|
|
111
|
+
else
|
|
112
|
+
options[:prompt]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def create_session(prompt_text)
|
|
117
|
+
params = build_create_params(prompt_text)
|
|
118
|
+
client.sessions.create(**params)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def build_create_params(prompt_text)
|
|
122
|
+
source_context = {
|
|
123
|
+
'source' => options[:source],
|
|
124
|
+
'githubRepoContext' => { 'startingBranch' => options[:branch] }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
params = {
|
|
128
|
+
prompt: prompt_text,
|
|
129
|
+
source_context: source_context
|
|
130
|
+
}
|
|
131
|
+
params[:title] = options[:title] if options[:title]
|
|
132
|
+
params[:automation_mode] = 'AUTO_CREATE_PR' if options[:auto_pr]
|
|
133
|
+
params
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def print_creation_success(session)
|
|
137
|
+
puts "Session created: #{session.name}"
|
|
138
|
+
puts "URL: #{session.url}"
|
|
139
|
+
puts "State: #{session.state}"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def print_sessions_table(sessions)
|
|
143
|
+
if sessions.empty?
|
|
144
|
+
puts 'No sessions found.'
|
|
145
|
+
return
|
|
146
|
+
end
|
|
147
|
+
puts 'ID TITLE STATE UPDATED '
|
|
148
|
+
puts '-' * 90
|
|
149
|
+
sessions.each do |s|
|
|
150
|
+
title = truncate(s.title || s.prompt, 28)
|
|
151
|
+
updated = s.update_time ? Time.parse(s.update_time).strftime('%Y-%m-%d %H:%M') : 'N/A'
|
|
152
|
+
puts format('%<id>-20s %<title>-30s %<state>-20s %<updated>-15s',
|
|
153
|
+
id: s.id, title: title, state: s.state, updated: updated)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def print_session_details(session)
|
|
158
|
+
print_session_basic_info(session)
|
|
159
|
+
print_session_outputs(session.outputs) if session.outputs&.any?
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def print_session_basic_info(session)
|
|
163
|
+
puts "Name: #{session.name}"
|
|
164
|
+
puts "ID: #{session.id}"
|
|
165
|
+
puts "Title: #{session.title}" if session.title
|
|
166
|
+
puts "Prompt: #{session.prompt}"
|
|
167
|
+
puts "State: #{session.state}"
|
|
168
|
+
puts "URL: #{session.url}" if session.url
|
|
169
|
+
puts "Created: #{session.create_time}"
|
|
170
|
+
puts "Updated: #{session.update_time}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def print_session_outputs(outputs)
|
|
174
|
+
puts "\nOutputs:"
|
|
175
|
+
outputs.each do |output|
|
|
176
|
+
if output.respond_to?(:url)
|
|
177
|
+
puts " - PR: #{output.url}"
|
|
178
|
+
else
|
|
179
|
+
puts " - #{output}"
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module JulesRuby
|
|
6
|
+
module Commands
|
|
7
|
+
# Sources subcommand
|
|
8
|
+
class Sources < Base
|
|
9
|
+
desc 'list', 'List all connected repositories'
|
|
10
|
+
long_desc <<~LONGDESC
|
|
11
|
+
List all GitHub repositories connected to your Jules account.
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
$ jules-ruby sources list
|
|
15
|
+
$ jules-ruby sources list --format=json
|
|
16
|
+
LONGDESC
|
|
17
|
+
format_option
|
|
18
|
+
def list
|
|
19
|
+
sources = client.sources.all
|
|
20
|
+
if options[:format] == 'json'
|
|
21
|
+
puts JSON.pretty_generate(sources.map(&:to_h))
|
|
22
|
+
else
|
|
23
|
+
print_sources_table(sources)
|
|
24
|
+
end
|
|
25
|
+
rescue JulesRuby::Error => e
|
|
26
|
+
error_exit(e)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc 'show NAME', 'Show details for a source'
|
|
30
|
+
long_desc <<~LONGDESC
|
|
31
|
+
Show details for a specific source.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
$ jules-ruby sources show sources/github/owner/repo
|
|
35
|
+
LONGDESC
|
|
36
|
+
format_option
|
|
37
|
+
def show(name)
|
|
38
|
+
source = client.sources.find(name)
|
|
39
|
+
if options[:format] == 'json'
|
|
40
|
+
puts JSON.pretty_generate(source.to_h)
|
|
41
|
+
else
|
|
42
|
+
print_source_details(source)
|
|
43
|
+
end
|
|
44
|
+
rescue JulesRuby::Error => e
|
|
45
|
+
error_exit(e)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def print_sources_table(sources)
|
|
51
|
+
if sources.empty?
|
|
52
|
+
puts 'No sources found.'
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
puts 'NAME REPOSITORY '
|
|
56
|
+
puts '-' * 72
|
|
57
|
+
sources.each do |s|
|
|
58
|
+
puts format('%<name>-50s %<repo>-20s', name: s.name, repo: s.github_repo&.full_name || 'N/A')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def print_source_details(source)
|
|
63
|
+
puts "Name: #{source.name}"
|
|
64
|
+
puts "ID: #{source.id}"
|
|
65
|
+
return unless source.github_repo
|
|
66
|
+
|
|
67
|
+
puts "Repository: #{source.github_repo.full_name}"
|
|
68
|
+
puts "URL: #{source.github_repo.url}" if source.github_repo.respond_to?(:url)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative 'cli/interactive'
|
|
5
|
+
require_relative 'cli/prompts'
|
|
6
|
+
require_relative 'cli/sources'
|
|
7
|
+
require_relative 'cli/sessions'
|
|
8
|
+
require_relative 'cli/activities'
|
|
9
|
+
|
|
10
|
+
module JulesRuby
|
|
11
|
+
# Command-line interface for jules-ruby
|
|
12
|
+
class CLI < Thor
|
|
13
|
+
package_name 'jules-ruby'
|
|
14
|
+
|
|
15
|
+
def self.exit_on_failure?
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.help(shell, subcommand = false)
|
|
20
|
+
Prompts.print_banner
|
|
21
|
+
|
|
22
|
+
shell.say <<~BANNER
|
|
23
|
+
QUICK START EXAMPLES:
|
|
24
|
+
|
|
25
|
+
# Start interactive mode (default)
|
|
26
|
+
$ jules-ruby
|
|
27
|
+
|
|
28
|
+
# List your connected repositories
|
|
29
|
+
$ jules-ruby sources list
|
|
30
|
+
|
|
31
|
+
# Create a new coding session
|
|
32
|
+
$ jules-ruby sessions create --source=sources/github/owner/repo --prompt="Fix the login bug"
|
|
33
|
+
|
|
34
|
+
# Create a session with prompt from file
|
|
35
|
+
$ jules-ruby sessions create --source=sources/github/owner/repo --prompt-file=./task.md
|
|
36
|
+
|
|
37
|
+
# List all sessions
|
|
38
|
+
$ jules-ruby sessions list
|
|
39
|
+
|
|
40
|
+
# View session activities
|
|
41
|
+
$ jules-ruby activities list SESSION_ID
|
|
42
|
+
|
|
43
|
+
# Approve a session's plan
|
|
44
|
+
$ jules-ruby sessions approve SESSION_ID
|
|
45
|
+
|
|
46
|
+
CONFIGURATION:
|
|
47
|
+
|
|
48
|
+
Set your API key via environment variable:
|
|
49
|
+
$ export JULES_API_KEY=your_api_key
|
|
50
|
+
|
|
51
|
+
BANNER
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc 'interactive', 'Start interactive mode'
|
|
56
|
+
def interactive
|
|
57
|
+
JulesRuby::Interactive.new.start
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
map %w[-i] => :interactive
|
|
61
|
+
default_command :interactive
|
|
62
|
+
|
|
63
|
+
desc 'sources SUBCOMMAND', 'Manage sources (connected repositories)'
|
|
64
|
+
long_desc <<~LONGDESC
|
|
65
|
+
Manage connected GitHub repositories (sources).
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
|
|
69
|
+
# List all connected repositories
|
|
70
|
+
$ jules-ruby sources list
|
|
71
|
+
|
|
72
|
+
# Show details for a specific source
|
|
73
|
+
$ jules-ruby sources show sources/github/owner/repo
|
|
74
|
+
|
|
75
|
+
# Output as JSON
|
|
76
|
+
$ jules-ruby sources list --format=json
|
|
77
|
+
LONGDESC
|
|
78
|
+
subcommand 'sources', JulesRuby::Commands::Sources
|
|
79
|
+
|
|
80
|
+
desc 'sessions SUBCOMMAND', 'Manage coding sessions'
|
|
81
|
+
long_desc <<~LONGDESC
|
|
82
|
+
Manage Jules coding sessions.
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
|
|
86
|
+
# List all sessions
|
|
87
|
+
$ jules-ruby sessions list
|
|
88
|
+
|
|
89
|
+
# Show session details
|
|
90
|
+
$ jules-ruby sessions show SESSION_ID
|
|
91
|
+
|
|
92
|
+
# Create a session with inline prompt
|
|
93
|
+
$ jules-ruby sessions create --source=sources/github/owner/repo --prompt="Fix the login bug"
|
|
94
|
+
|
|
95
|
+
# Create a session with prompt from file
|
|
96
|
+
$ jules-ruby sessions create --source=sources/github/owner/repo --prompt-file=./task.md
|
|
97
|
+
|
|
98
|
+
# Create a session with auto-PR
|
|
99
|
+
$ jules-ruby sessions create --source=sources/github/owner/repo --prompt="Add tests" --auto-pr
|
|
100
|
+
LONGDESC
|
|
101
|
+
subcommand 'sessions', JulesRuby::Commands::Sessions
|
|
102
|
+
|
|
103
|
+
desc 'activities SUBCOMMAND', 'View session activities'
|
|
104
|
+
long_desc <<~LONGDESC
|
|
105
|
+
View activities (messages, plans, progress) for Jules sessions.
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
|
|
109
|
+
# List all activities for a session
|
|
110
|
+
$ jules-ruby activities list SESSION_ID
|
|
111
|
+
|
|
112
|
+
# Show details for a specific activity
|
|
113
|
+
$ jules-ruby activities show sessions/SESSION_ID/activities/ACTIVITY_ID
|
|
114
|
+
|
|
115
|
+
# Output as JSON
|
|
116
|
+
$ jules-ruby activities list SESSION_ID --format=json
|
|
117
|
+
LONGDESC
|
|
118
|
+
subcommand 'activities', JulesRuby::Commands::Activities
|
|
119
|
+
|
|
120
|
+
desc 'version', 'Show jules-ruby version'
|
|
121
|
+
def version
|
|
122
|
+
puts "jules-ruby #{JulesRuby::VERSION}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
map %w[-v --version] => :version
|
|
126
|
+
end
|
|
127
|
+
end
|