rubyn-code 0.1.0 → 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 +269 -467
- data/db/migrations/009_create_teams.sql +6 -6
- data/db/migrations/011_fix_mailbox_messages_columns.rb +35 -0
- data/db/migrations/012_expand_mailbox_message_types.rb +37 -0
- data/exe/rubyn-code +1 -1
- data/lib/rubyn_code/agent/RUBYN.md +17 -0
- data/lib/rubyn_code/agent/conversation.rb +68 -19
- data/lib/rubyn_code/agent/loop.rb +312 -54
- data/lib/rubyn_code/agent/loop_detector.rb +6 -6
- data/lib/rubyn_code/auth/RUBYN.md +19 -0
- data/lib/rubyn_code/auth/oauth.rb +40 -35
- data/lib/rubyn_code/auth/server.rb +16 -12
- data/lib/rubyn_code/auth/token_store.rb +22 -22
- data/lib/rubyn_code/autonomous/RUBYN.md +14 -0
- data/lib/rubyn_code/autonomous/daemon.rb +115 -79
- data/lib/rubyn_code/autonomous/idle_poller.rb +4 -8
- data/lib/rubyn_code/autonomous/task_claimer.rb +11 -11
- data/lib/rubyn_code/background/RUBYN.md +13 -0
- data/lib/rubyn_code/background/notifier.rb +0 -2
- data/lib/rubyn_code/background/worker.rb +60 -15
- data/lib/rubyn_code/cli/RUBYN.md +30 -0
- data/lib/rubyn_code/cli/app.rb +85 -9
- data/lib/rubyn_code/cli/commands/RUBYN.md +133 -0
- data/lib/rubyn_code/cli/commands/base.rb +53 -0
- data/lib/rubyn_code/cli/commands/budget.rb +24 -0
- data/lib/rubyn_code/cli/commands/clear.rb +16 -0
- data/lib/rubyn_code/cli/commands/compact.rb +21 -0
- data/lib/rubyn_code/cli/commands/context.rb +44 -0
- data/lib/rubyn_code/cli/commands/context_info.rb +56 -0
- data/lib/rubyn_code/cli/commands/cost.rb +23 -0
- data/lib/rubyn_code/cli/commands/diff.rb +30 -0
- data/lib/rubyn_code/cli/commands/doctor.rb +112 -0
- data/lib/rubyn_code/cli/commands/help.rb +41 -0
- data/lib/rubyn_code/cli/commands/model.rb +37 -0
- data/lib/rubyn_code/cli/commands/plan.rb +22 -0
- data/lib/rubyn_code/cli/commands/quit.rb +17 -0
- data/lib/rubyn_code/cli/commands/registry.rb +64 -0
- data/lib/rubyn_code/cli/commands/resume.rb +51 -0
- data/lib/rubyn_code/cli/commands/review.rb +26 -0
- data/lib/rubyn_code/cli/commands/skill.rb +32 -0
- data/lib/rubyn_code/cli/commands/spawn.rb +24 -0
- data/lib/rubyn_code/cli/commands/tasks.rb +32 -0
- data/lib/rubyn_code/cli/commands/tokens.rb +76 -0
- data/lib/rubyn_code/cli/commands/undo.rb +17 -0
- data/lib/rubyn_code/cli/commands/version.rb +16 -0
- data/lib/rubyn_code/cli/daemon_runner.rb +129 -0
- data/lib/rubyn_code/cli/input_handler.rb +20 -23
- data/lib/rubyn_code/cli/renderer.rb +25 -27
- data/lib/rubyn_code/cli/repl.rb +161 -194
- data/lib/rubyn_code/cli/setup.rb +117 -0
- data/lib/rubyn_code/cli/spinner.rb +40 -40
- data/lib/rubyn_code/cli/stream_formatter.rb +29 -28
- data/lib/rubyn_code/cli/version_check.rb +94 -0
- data/lib/rubyn_code/config/RUBYN.md +14 -0
- data/lib/rubyn_code/config/defaults.rb +28 -19
- data/lib/rubyn_code/config/project_config.rb +7 -9
- data/lib/rubyn_code/config/settings.rb +3 -3
- data/lib/rubyn_code/context/RUBYN.md +20 -0
- data/lib/rubyn_code/context/auto_compact.rb +7 -7
- data/lib/rubyn_code/context/compactor.rb +2 -2
- data/lib/rubyn_code/context/context_collapse.rb +45 -0
- data/lib/rubyn_code/context/manager.rb +20 -3
- data/lib/rubyn_code/context/manual_compact.rb +7 -7
- data/lib/rubyn_code/context/micro_compact.rb +12 -12
- data/lib/rubyn_code/db/RUBYN.md +40 -0
- data/lib/rubyn_code/db/connection.rb +13 -13
- data/lib/rubyn_code/db/migrator.rb +67 -27
- data/lib/rubyn_code/db/schema.rb +6 -6
- data/lib/rubyn_code/debug.rb +74 -0
- data/lib/rubyn_code/hooks/RUBYN.md +17 -0
- data/lib/rubyn_code/hooks/built_in.rb +9 -9
- data/lib/rubyn_code/hooks/registry.rb +5 -5
- data/lib/rubyn_code/hooks/runner.rb +1 -1
- data/lib/rubyn_code/hooks/user_hooks.rb +16 -16
- data/lib/rubyn_code/learning/RUBYN.md +16 -0
- data/lib/rubyn_code/learning/extractor.rb +22 -22
- data/lib/rubyn_code/learning/injector.rb +17 -18
- data/lib/rubyn_code/learning/instinct.rb +18 -14
- data/lib/rubyn_code/llm/RUBYN.md +15 -0
- data/lib/rubyn_code/llm/client.rb +121 -55
- data/lib/rubyn_code/llm/message_builder.rb +19 -15
- data/lib/rubyn_code/llm/streaming.rb +80 -50
- data/lib/rubyn_code/mcp/RUBYN.md +21 -0
- data/lib/rubyn_code/mcp/client.rb +25 -24
- data/lib/rubyn_code/mcp/config.rb +7 -7
- data/lib/rubyn_code/mcp/sse_transport.rb +27 -26
- data/lib/rubyn_code/mcp/stdio_transport.rb +22 -19
- data/lib/rubyn_code/mcp/tool_bridge.rb +32 -32
- data/lib/rubyn_code/memory/RUBYN.md +17 -0
- data/lib/rubyn_code/memory/models.rb +3 -3
- data/lib/rubyn_code/memory/search.rb +17 -17
- data/lib/rubyn_code/memory/session_persistence.rb +49 -34
- data/lib/rubyn_code/memory/store.rb +17 -17
- data/lib/rubyn_code/observability/RUBYN.md +19 -0
- data/lib/rubyn_code/observability/budget_enforcer.rb +16 -15
- data/lib/rubyn_code/observability/cost_calculator.rb +3 -3
- data/lib/rubyn_code/observability/token_counter.rb +1 -1
- data/lib/rubyn_code/observability/usage_reporter.rb +35 -35
- data/lib/rubyn_code/output/RUBYN.md +11 -0
- data/lib/rubyn_code/output/diff_renderer.rb +6 -6
- data/lib/rubyn_code/output/formatter.rb +4 -4
- data/lib/rubyn_code/permissions/RUBYN.md +17 -0
- data/lib/rubyn_code/permissions/prompter.rb +8 -8
- data/lib/rubyn_code/protocols/RUBYN.md +14 -0
- data/lib/rubyn_code/protocols/interrupt_handler.rb +1 -1
- data/lib/rubyn_code/protocols/plan_approval.rb +9 -9
- data/lib/rubyn_code/protocols/shutdown_handshake.rb +9 -11
- data/lib/rubyn_code/skills/RUBYN.md +19 -0
- data/lib/rubyn_code/skills/catalog.rb +7 -7
- data/lib/rubyn_code/skills/document.rb +15 -15
- data/lib/rubyn_code/skills/loader.rb +6 -8
- data/lib/rubyn_code/sub_agents/RUBYN.md +12 -0
- data/lib/rubyn_code/sub_agents/runner.rb +15 -15
- data/lib/rubyn_code/sub_agents/summarizer.rb +1 -1
- data/lib/rubyn_code/tasks/RUBYN.md +13 -0
- data/lib/rubyn_code/tasks/dag.rb +12 -16
- data/lib/rubyn_code/tasks/manager.rb +24 -24
- data/lib/rubyn_code/tasks/models.rb +4 -4
- data/lib/rubyn_code/teams/RUBYN.md +14 -0
- data/lib/rubyn_code/teams/mailbox.rb +38 -18
- data/lib/rubyn_code/teams/manager.rb +19 -19
- data/lib/rubyn_code/teams/teammate.rb +3 -4
- data/lib/rubyn_code/tools/RUBYN.md +38 -0
- data/lib/rubyn_code/tools/background_run.rb +9 -11
- data/lib/rubyn_code/tools/base.rb +54 -3
- data/lib/rubyn_code/tools/bash.rb +16 -34
- data/lib/rubyn_code/tools/bundle_add.rb +10 -12
- data/lib/rubyn_code/tools/bundle_install.rb +9 -11
- data/lib/rubyn_code/tools/compact.rb +10 -9
- data/lib/rubyn_code/tools/db_migrate.rb +17 -15
- data/lib/rubyn_code/tools/edit_file.rb +12 -12
- data/lib/rubyn_code/tools/executor.rb +9 -4
- data/lib/rubyn_code/tools/git_commit.rb +29 -34
- data/lib/rubyn_code/tools/git_diff.rb +17 -18
- data/lib/rubyn_code/tools/git_log.rb +17 -19
- data/lib/rubyn_code/tools/git_status.rb +18 -20
- data/lib/rubyn_code/tools/glob.rb +7 -9
- data/lib/rubyn_code/tools/grep.rb +11 -9
- data/lib/rubyn_code/tools/load_skill.rb +7 -7
- data/lib/rubyn_code/tools/memory_search.rb +13 -12
- data/lib/rubyn_code/tools/memory_write.rb +14 -12
- data/lib/rubyn_code/tools/rails_generate.rb +16 -16
- data/lib/rubyn_code/tools/read_file.rb +8 -7
- data/lib/rubyn_code/tools/read_inbox.rb +5 -5
- data/lib/rubyn_code/tools/registry.rb +2 -2
- data/lib/rubyn_code/tools/review_pr.rb +55 -55
- data/lib/rubyn_code/tools/run_specs.rb +20 -19
- data/lib/rubyn_code/tools/schema.rb +9 -11
- data/lib/rubyn_code/tools/send_message.rb +10 -10
- data/lib/rubyn_code/tools/spawn_agent.rb +51 -23
- data/lib/rubyn_code/tools/spawn_teammate.rb +21 -21
- data/lib/rubyn_code/tools/task.rb +28 -28
- data/lib/rubyn_code/tools/web_fetch.rb +46 -31
- data/lib/rubyn_code/tools/web_search.rb +64 -66
- data/lib/rubyn_code/tools/write_file.rb +7 -6
- data/lib/rubyn_code/version.rb +1 -1
- data/lib/rubyn_code.rb +136 -105
- metadata +94 -21
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module RubynCode
|
|
6
|
+
module CLI
|
|
7
|
+
# Boots all dependencies and starts the GOLEM daemon from the CLI.
|
|
8
|
+
# Handles authentication, database setup, and lifecycle output.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# rubyn-code daemon --name golem-1 --max-runs 50 --max-cost 5.0
|
|
12
|
+
class DaemonRunner
|
|
13
|
+
def initialize(options)
|
|
14
|
+
@options = options
|
|
15
|
+
@daemon_opts = options.fetch(:daemon, {})
|
|
16
|
+
@renderer = Renderer.new
|
|
17
|
+
@project_root = Dir.pwd
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run
|
|
21
|
+
ensure_home_dir!
|
|
22
|
+
ensure_auth!
|
|
23
|
+
setup_database!
|
|
24
|
+
display_banner!
|
|
25
|
+
|
|
26
|
+
daemon = build_daemon
|
|
27
|
+
daemon.start!
|
|
28
|
+
|
|
29
|
+
display_shutdown_summary(daemon)
|
|
30
|
+
rescue Interrupt
|
|
31
|
+
@renderer.info("\nShutting down daemon...")
|
|
32
|
+
display_shutdown_summary(daemon) if daemon
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
@renderer.error("Daemon failed: #{e.message}")
|
|
35
|
+
RubynCode::Debug.warn(e.backtrace&.first(5)&.join("\n")) if @options[:debug]
|
|
36
|
+
exit(1)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def build_daemon
|
|
42
|
+
Autonomous::Daemon.new(
|
|
43
|
+
agent_name: @daemon_opts[:agent_name],
|
|
44
|
+
role: @daemon_opts[:role],
|
|
45
|
+
llm_client: @llm_client,
|
|
46
|
+
project_root: @project_root,
|
|
47
|
+
task_manager: @task_manager,
|
|
48
|
+
mailbox: @mailbox,
|
|
49
|
+
max_runs: @daemon_opts[:max_runs],
|
|
50
|
+
max_cost: @daemon_opts[:max_cost],
|
|
51
|
+
poll_interval: @daemon_opts[:poll_interval],
|
|
52
|
+
idle_timeout: @daemon_opts[:idle_timeout],
|
|
53
|
+
on_state_change: method(:on_state_change),
|
|
54
|
+
on_task_complete: method(:on_task_complete),
|
|
55
|
+
on_task_error: method(:on_task_error)
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# ── Callbacks ────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
def on_state_change(old_state, new_state)
|
|
62
|
+
@renderer.info(" [#{@daemon_opts[:agent_name]}] #{old_state} → #{new_state}")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def on_task_complete(task, result)
|
|
66
|
+
summary = result.to_s[0..200]
|
|
67
|
+
@renderer.success(" ✓ Completed: #{task.title} — #{summary}")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def on_task_error(task, error)
|
|
71
|
+
@renderer.error(" ✗ Failed: #{task.title} — #{error.message}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# ── Setup ────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
def ensure_home_dir!
|
|
77
|
+
dir = Config::Defaults::HOME_DIR
|
|
78
|
+
FileUtils.mkdir_p(dir)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def ensure_auth!
|
|
82
|
+
unless Auth::TokenStore.valid?
|
|
83
|
+
@renderer.error('No valid authentication found.')
|
|
84
|
+
@renderer.info('Run `rubyn-code --auth` or set ANTHROPIC_API_KEY first.')
|
|
85
|
+
exit(1)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
@llm_client = LLM::Client.new
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def setup_database!
|
|
92
|
+
@db = DB::Connection.instance
|
|
93
|
+
DB::Migrator.new(@db).migrate!
|
|
94
|
+
@task_manager = Tasks::Manager.new(@db)
|
|
95
|
+
@mailbox = Teams::Mailbox.new(@db)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def display_banner!
|
|
99
|
+
@renderer.info('╔══════════════════════════════════════╗')
|
|
100
|
+
@renderer.info('║ GOLEM Daemon Starting ║')
|
|
101
|
+
@renderer.info('╚══════════════════════════════════════╝')
|
|
102
|
+
@renderer.info(" Agent: #{@daemon_opts[:agent_name]}")
|
|
103
|
+
@renderer.info(" Role: #{@daemon_opts[:role]}")
|
|
104
|
+
@renderer.info(" Project: #{@project_root}")
|
|
105
|
+
@renderer.info(" Max runs: #{@daemon_opts[:max_runs]}")
|
|
106
|
+
@renderer.info(" Max cost: $#{@daemon_opts[:max_cost]}")
|
|
107
|
+
@renderer.info(" Idle timeout: #{@daemon_opts[:idle_timeout]}s")
|
|
108
|
+
@renderer.info(" Poll interval: #{@daemon_opts[:poll_interval]}s")
|
|
109
|
+
@renderer.info('')
|
|
110
|
+
@renderer.info('Waiting for tasks... (Ctrl-C to stop)')
|
|
111
|
+
@renderer.info('Seed tasks via the REPL: /tasks or the task tool.')
|
|
112
|
+
@renderer.info('')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def display_shutdown_summary(daemon)
|
|
116
|
+
return unless daemon
|
|
117
|
+
|
|
118
|
+
status = daemon.status
|
|
119
|
+
@renderer.info('')
|
|
120
|
+
@renderer.info('╔══════════════════════════════════════╗')
|
|
121
|
+
@renderer.info('║ GOLEM Daemon Stopped ║')
|
|
122
|
+
@renderer.info('╚══════════════════════════════════════╝')
|
|
123
|
+
@renderer.info(" Final state: #{status[:state]}")
|
|
124
|
+
@renderer.info(" Tasks completed: #{status[:runs_completed]}")
|
|
125
|
+
@renderer.info(" Total cost: $#{'%.4f' % status[:total_cost]}")
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -3,33 +3,28 @@
|
|
|
3
3
|
module RubynCode
|
|
4
4
|
module CLI
|
|
5
5
|
class InputHandler
|
|
6
|
+
# Legacy mapping for backward compatibility when no registry is provided.
|
|
7
|
+
# New code should use the command registry instead.
|
|
6
8
|
SLASH_COMMANDS = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"/compact" => :compact,
|
|
11
|
-
"/cost" => :cost,
|
|
12
|
-
"/clear" => :clear,
|
|
13
|
-
"/undo" => :undo,
|
|
14
|
-
"/help" => :help,
|
|
15
|
-
"/tasks" => :tasks,
|
|
16
|
-
"/budget" => :budget,
|
|
17
|
-
"/resume" => :resume,
|
|
18
|
-
"/skill" => :skill,
|
|
19
|
-
"/version" => :version,
|
|
20
|
-
"/review" => :review,
|
|
21
|
-
"/spawn" => :spawn_teammate
|
|
9
|
+
'/quit' => :quit,
|
|
10
|
+
'/exit' => :quit,
|
|
11
|
+
'/q' => :quit
|
|
22
12
|
}.freeze
|
|
23
13
|
|
|
24
14
|
Command = Data.define(:action, :args)
|
|
25
15
|
|
|
16
|
+
# @param command_registry [CLI::Commands::Registry, nil]
|
|
17
|
+
def initialize(command_registry: nil)
|
|
18
|
+
@command_registry = command_registry
|
|
19
|
+
end
|
|
20
|
+
|
|
26
21
|
def parse(input)
|
|
27
22
|
return Command.new(action: :quit, args: []) if input.nil?
|
|
28
23
|
|
|
29
24
|
stripped = input.strip
|
|
30
25
|
return Command.new(action: :empty, args: []) if stripped.empty?
|
|
31
26
|
|
|
32
|
-
if stripped.start_with?(
|
|
27
|
+
if stripped.start_with?('/')
|
|
33
28
|
parse_slash_command(stripped)
|
|
34
29
|
else
|
|
35
30
|
Command.new(action: :message, args: [process_file_references(stripped)])
|
|
@@ -37,26 +32,28 @@ module RubynCode
|
|
|
37
32
|
end
|
|
38
33
|
|
|
39
34
|
def multiline?(line)
|
|
40
|
-
line&.end_with?(
|
|
35
|
+
line&.end_with?('\\')
|
|
41
36
|
end
|
|
42
37
|
|
|
43
38
|
def strip_continuation(line)
|
|
44
|
-
line.chomp(
|
|
39
|
+
line.chomp('\\')
|
|
45
40
|
end
|
|
46
41
|
|
|
47
42
|
private
|
|
48
43
|
|
|
49
44
|
def parse_slash_command(input)
|
|
50
|
-
return Command.new(action: :list_commands, args: []) if input.strip ==
|
|
45
|
+
return Command.new(action: :list_commands, args: []) if input.strip == '/'
|
|
51
46
|
|
|
52
47
|
parts = input.split(/\s+/, 2)
|
|
53
48
|
cmd = parts[0].downcase
|
|
54
49
|
args = parts[1]&.split(/\s+/) || []
|
|
55
50
|
|
|
56
|
-
|
|
51
|
+
# Quick exit for /quit and friends
|
|
52
|
+
return Command.new(action: :quit, args: []) if SLASH_COMMANDS[cmd] == :quit
|
|
57
53
|
|
|
58
|
-
if
|
|
59
|
-
|
|
54
|
+
# Dispatch through registry if available
|
|
55
|
+
if @command_registry&.known?(cmd)
|
|
56
|
+
Command.new(action: :slash_command, args: [cmd, *args])
|
|
60
57
|
else
|
|
61
58
|
Command.new(action: :unknown_command, args: [cmd])
|
|
62
59
|
end
|
|
@@ -66,7 +63,7 @@ module RubynCode
|
|
|
66
63
|
input.gsub(/@(\S+)/) do |match|
|
|
67
64
|
path = Regexp.last_match(1)
|
|
68
65
|
if File.exist?(path)
|
|
69
|
-
content = File.read(path, encoding:
|
|
66
|
+
content = File.read(path, encoding: 'utf-8')
|
|
70
67
|
truncated = content.length > 50_000 ? "#{content[0...50_000]}\n[truncated]" : content
|
|
71
68
|
"\n<file path=\"#{path}\">\n#{truncated}\n</file>\n"
|
|
72
69
|
else
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'pastel'
|
|
4
|
+
require 'rouge'
|
|
5
5
|
|
|
6
6
|
module RubynCode
|
|
7
7
|
module CLI
|
|
@@ -33,7 +33,7 @@ module RubynCode
|
|
|
33
33
|
puts @pastel.cyan(" > #{name}: #{format_params(params)}")
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def tool_result(
|
|
36
|
+
def tool_result(_name, output)
|
|
37
37
|
truncated = output.to_s[0...300]
|
|
38
38
|
lines = truncated.lines
|
|
39
39
|
if lines.length > 6
|
|
@@ -69,17 +69,17 @@ module RubynCode
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def welcome
|
|
72
|
-
puts @pastel.bold.cyan(
|
|
72
|
+
puts @pastel.bold.cyan('╔══════════════════════════════════════╗')
|
|
73
73
|
puts @pastel.bold.cyan("║ Rubyn Code v#{RubynCode::VERSION} ║")
|
|
74
|
-
puts @pastel.bold.cyan(
|
|
75
|
-
puts @pastel.bold.cyan(
|
|
74
|
+
puts @pastel.bold.cyan('║ Ruby & Rails Agentic Assistant ║')
|
|
75
|
+
puts @pastel.bold.cyan('╚══════════════════════════════════════╝')
|
|
76
76
|
puts
|
|
77
|
-
puts @pastel.dim(
|
|
77
|
+
puts @pastel.dim('Type /help for commands, /quit to exit')
|
|
78
78
|
puts
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def cost_summary(session_cost:, daily_cost:, tokens:)
|
|
82
|
-
puts @pastel.bold(
|
|
82
|
+
puts @pastel.bold('Cost Summary:')
|
|
83
83
|
puts " Session: $#{'%.4f' % session_cost}"
|
|
84
84
|
puts " Today: $#{'%.4f' % daily_cost}"
|
|
85
85
|
puts " Tokens: #{tokens[:input]} in / #{tokens[:output]} out"
|
|
@@ -87,9 +87,9 @@ module RubynCode
|
|
|
87
87
|
|
|
88
88
|
def prompt
|
|
89
89
|
if @yolo
|
|
90
|
-
@pastel.bold.green(
|
|
90
|
+
@pastel.bold.green('rubyn ') + @pastel.bold.red('YOLO') + @pastel.bold.green(' > ')
|
|
91
91
|
else
|
|
92
|
-
@pastel.bold.green(
|
|
92
|
+
@pastel.bold.green('rubyn > ')
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
|
|
@@ -113,7 +113,7 @@ module RubynCode
|
|
|
113
113
|
else
|
|
114
114
|
in_code_block = true
|
|
115
115
|
code_lang = line.strip.match(/\A```(\w*)/)[1]
|
|
116
|
-
code_lang =
|
|
116
|
+
code_lang = 'ruby' if code_lang.empty?
|
|
117
117
|
end
|
|
118
118
|
elsif in_code_block
|
|
119
119
|
code_buffer << line
|
|
@@ -123,9 +123,7 @@ module RubynCode
|
|
|
123
123
|
end
|
|
124
124
|
|
|
125
125
|
# Flush any unclosed code block
|
|
126
|
-
unless code_buffer.empty?
|
|
127
|
-
result << render_code_block(code_buffer.join, code_lang || "ruby")
|
|
128
|
-
end
|
|
126
|
+
result << render_code_block(code_buffer.join, code_lang || 'ruby') unless code_buffer.empty?
|
|
129
127
|
|
|
130
128
|
result.join
|
|
131
129
|
end
|
|
@@ -133,9 +131,9 @@ module RubynCode
|
|
|
133
131
|
def render_code_block(code, lang)
|
|
134
132
|
lexer = Rouge::Lexer.find(lang) || Rouge::Lexers::PlainText.new
|
|
135
133
|
highlighted = @rouge_formatter.format(lexer.lex(code))
|
|
136
|
-
border = @pastel.dim(
|
|
134
|
+
border = @pastel.dim(' │ ')
|
|
137
135
|
formatted = highlighted.lines.map { |l| "#{border}#{l}" }.join
|
|
138
|
-
"\n#{@pastel.dim(" ┌─ #{lang}")}\n#{formatted}#{@pastel.dim(
|
|
136
|
+
"\n#{@pastel.dim(" ┌─ #{lang}")}\n#{formatted}#{@pastel.dim(' └─')}\n"
|
|
139
137
|
rescue StandardError
|
|
140
138
|
"\n#{code}\n"
|
|
141
139
|
end
|
|
@@ -144,7 +142,7 @@ module RubynCode
|
|
|
144
142
|
# Headers
|
|
145
143
|
if line.match?(/\A\s*\#{1,6}\s/)
|
|
146
144
|
level = line.match(/\A\s*(\#{1,6})\s/)[1].length
|
|
147
|
-
text = line.sub(/\A\s*\#{1,6}\s+/,
|
|
145
|
+
text = line.sub(/\A\s*\#{1,6}\s+/, '').rstrip
|
|
148
146
|
case level
|
|
149
147
|
when 1 then "#{@pastel.bold.underline(text)}\n"
|
|
150
148
|
when 2 then "#{@pastel.bold(text)}\n"
|
|
@@ -153,19 +151,19 @@ module RubynCode
|
|
|
153
151
|
# Bullet lists
|
|
154
152
|
elsif line.match?(/\A\s*[-*]\s/)
|
|
155
153
|
indent = line.match(/\A(\s*)/)[1]
|
|
156
|
-
content = line.sub(/\A\s*[-*]\s+/,
|
|
157
|
-
"#{indent} #{@pastel.cyan(
|
|
154
|
+
content = line.sub(/\A\s*[-*]\s+/, '')
|
|
155
|
+
"#{indent} #{@pastel.cyan('•')} #{render_inline(content)}"
|
|
158
156
|
# Numbered lists
|
|
159
157
|
elsif line.match?(/\A\s*\d+\.\s/)
|
|
160
158
|
indent = line.match(/\A(\s*)/)[1]
|
|
161
159
|
num = line.match(/(\d+)\./)[1]
|
|
162
|
-
content = line.sub(/\A\s*\d+\.\s+/,
|
|
163
|
-
"#{indent} #{@pastel.cyan(num
|
|
160
|
+
content = line.sub(/\A\s*\d+\.\s+/, '')
|
|
161
|
+
"#{indent} #{@pastel.cyan("#{num}.")} #{render_inline(content)}"
|
|
164
162
|
# Horizontal rules
|
|
165
163
|
elsif line.strip.match?(/\A-{3,}\z/)
|
|
166
|
-
"#{@pastel.dim(
|
|
164
|
+
"#{@pastel.dim('─' * [terminal_width - 4, 40].min)}\n"
|
|
167
165
|
# Table rows
|
|
168
|
-
elsif line.include?(
|
|
166
|
+
elsif line.include?('|')
|
|
169
167
|
render_table_row(line)
|
|
170
168
|
else
|
|
171
169
|
render_inline(line)
|
|
@@ -180,16 +178,16 @@ module RubynCode
|
|
|
180
178
|
end
|
|
181
179
|
|
|
182
180
|
def render_table_row(line)
|
|
183
|
-
return
|
|
181
|
+
return '' if line.strip.match?(/\A\|[\s\-:|]+\|\z/) # separator row
|
|
184
182
|
|
|
185
|
-
cells = line.split(
|
|
186
|
-
" #{cells.map { |c| render_inline(c) }.join(
|
|
183
|
+
cells = line.split('|').map(&:strip).reject(&:empty?)
|
|
184
|
+
" #{cells.map { |c| render_inline(c) }.join(' │ ')}\n"
|
|
187
185
|
end
|
|
188
186
|
|
|
189
187
|
def format_params(params)
|
|
190
188
|
case params
|
|
191
189
|
when Hash
|
|
192
|
-
params.map { |k, v| "#{k}=#{v.to_s[0...80]}" }.join(
|
|
190
|
+
params.map { |k, v| "#{k}=#{v.to_s[0...80]}" }.join(', ')
|
|
193
191
|
else
|
|
194
192
|
params.to_s[0...200]
|
|
195
193
|
end
|