consolle 0.3.8 → 0.4.1

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.
data/lib/consolle/cli.rb CHANGED
@@ -8,9 +8,60 @@ require 'timeout'
8
8
  require 'securerandom'
9
9
  require 'date'
10
10
  require_relative 'constants'
11
+ require_relative 'session_registry'
12
+ require_relative 'history'
11
13
  require_relative 'adapters/rails_console'
12
14
 
13
15
  module Consolle
16
+ # Rails convenience commands subcommand
17
+ class RailsCommands < Thor
18
+ namespace :rails
19
+
20
+ class_option :verbose, type: :boolean, aliases: '-v', desc: 'Verbose output'
21
+ class_option :target, type: :string, aliases: '-t', desc: 'Target session name', default: 'cone'
22
+
23
+ desc 'reload', 'Reload Rails application code (reload!)'
24
+ def reload
25
+ execute_rails_code('reload!')
26
+ end
27
+
28
+ desc 'env', 'Show current Rails environment'
29
+ def env
30
+ execute_rails_code('Rails.env')
31
+ end
32
+
33
+ desc 'db', 'Show database connection information'
34
+ def db
35
+ code = <<~RUBY
36
+ config = ActiveRecord::Base.connection_db_config
37
+ puts "Adapter: \#{config.adapter}"
38
+ puts "Database: \#{config.database}"
39
+ puts "Host: \#{config.host || 'localhost'}" if config.respond_to?(:host)
40
+ puts "Pool: \#{config.pool}" if config.respond_to?(:pool)
41
+ puts "Connected: \#{ActiveRecord::Base.connected?}"
42
+ nil
43
+ RUBY
44
+ execute_rails_code(code)
45
+ end
46
+
47
+ private
48
+
49
+ def execute_rails_code(code)
50
+ # Delegate to main CLI's exec command
51
+ cli = Consolle::CLI.new
52
+ cli.options = {
53
+ target: options[:target] || 'cone',
54
+ verbose: options[:verbose] || false,
55
+ timeout: 60,
56
+ raw: false
57
+ }
58
+ cli.exec(code)
59
+ rescue SystemExit
60
+ # Allow exit from exec
61
+ raise
62
+ end
63
+ end
64
+
14
65
  class CLI < Thor
15
66
  package_name 'Consolle'
16
67
 
@@ -38,7 +89,11 @@ module Consolle
38
89
  shell.say ' cone restart # Restart Rails console'
39
90
  shell.say ' cone status # Show Rails console status'
40
91
  shell.say ' cone exec CODE # Execute Ruby code in Rails console'
41
- shell.say ' cone ls # List active Rails console sessions'
92
+ shell.say ' cone rails SUBCOMMAND # Rails convenience commands'
93
+ shell.say ' cone ls # List active sessions (use -a for all)'
94
+ shell.say ' cone history # Show command history'
95
+ shell.say ' cone rm SESSION # Remove session and its history'
96
+ shell.say ' cone prune # Remove all stopped sessions'
42
97
  shell.say ' cone stop_all # Stop all Rails console sessions'
43
98
  shell.say ' cone rule FILE # Write cone command guide to FILE'
44
99
  shell.say ' cone version # Show version'
@@ -66,6 +121,10 @@ module Consolle
66
121
  class_option :verbose, type: :boolean, aliases: '-v', desc: 'Verbose output'
67
122
  class_option :target, type: :string, aliases: '-t', desc: 'Target session name', default: 'cone'
68
123
 
124
+ # Register rails subcommand
125
+ desc 'rails SUBCOMMAND', 'Rails convenience commands (reload, env, db)'
126
+ subcommand 'rails', RailsCommands
127
+
69
128
  def self.exit_on_failure?
70
129
  true
71
130
  end
@@ -182,18 +241,25 @@ module Consolle
182
241
 
183
242
  begin
184
243
  adapter.start
185
- puts '✓ Rails console started successfully'
244
+
245
+ # Register session in registry
246
+ session = session_registry.create_session(
247
+ target: options[:target],
248
+ socket_path: adapter.socket_path,
249
+ pid: adapter.process_pid,
250
+ rails_env: current_rails_env,
251
+ mode: options[:mode] || 'pty'
252
+ )
253
+
254
+ puts '✓ Rails console started'
255
+ puts " Session ID: #{session['id']} (#{session['short_id']})"
256
+ puts " Target: #{session['target']}"
257
+ puts " Environment: #{current_rails_env}"
186
258
  puts " PID: #{adapter.process_pid}"
187
259
  puts " Socket: #{adapter.socket_path}"
188
260
 
189
- # Save session info
190
- save_session_info(adapter)
191
-
192
- # Log session start
193
- log_session_event(adapter.process_pid, 'session_start', {
194
- rails_env: current_rails_env,
195
- socket_path: adapter.socket_path
196
- })
261
+ # Also save to legacy sessions.json for backward compatibility
262
+ save_session_info(adapter, session['id'])
197
263
  rescue StandardError => e
198
264
  puts "✗ Failed to start Rails console: #{e.message}"
199
265
  exit 1
@@ -205,9 +271,11 @@ module Consolle
205
271
  ensure_rails_project!
206
272
  validate_session_name!(options[:target])
207
273
 
274
+ # Try to find session in registry first
275
+ session = session_registry.find_running_session(target: options[:target])
208
276
  session_info = load_session_info
209
277
 
210
- if session_info.nil?
278
+ if session_info.nil? && session.nil?
211
279
  puts 'No active Rails console session found'
212
280
  return
213
281
  end
@@ -224,85 +292,141 @@ module Consolle
224
292
  if process_running
225
293
  rails_env = server_status['rails_env'] || 'unknown'
226
294
  console_pid = server_status['pid'] || 'unknown'
295
+ uptime = session_info&.dig(:started_at) ? format_uptime(Time.now - Time.at(session_info[:started_at])) : 'unknown'
296
+ command_count = session ? session['command_count'] : 0
227
297
 
228
298
  puts '✓ Rails console is running'
229
- puts " PID: #{console_pid}"
299
+ if session
300
+ puts " Session ID: #{session['id']} (#{session['short_id']})"
301
+ end
302
+ puts " Target: #{options[:target]}"
230
303
  puts " Environment: #{rails_env}"
231
- puts " Session: #{session_info[:socket_path]}"
232
- puts ' Ready for input: Yes'
304
+ puts " PID: #{console_pid}"
305
+ puts " Uptime: #{uptime}"
306
+ puts " Commands: #{command_count}"
307
+ puts " Socket: #{session_info&.dig(:socket_path) || session&.dig('socket_path')}"
233
308
  else
234
309
  puts '✗ Rails console is not running'
310
+ # Mark session as stopped in registry
311
+ session_registry.stop_session(target: options[:target], reason: 'process_died') if session
235
312
  clear_session_info
236
313
  end
237
314
  end
238
315
 
239
- desc 'ls', 'List active Rails console sessions'
316
+ desc 'ls', 'List Rails console sessions'
240
317
  long_desc <<-LONGDESC
241
- Lists all active Rails console sessions in the current project.
318
+ Lists Rails console sessions in the current project.
319
+
320
+ By default, shows only active (running) sessions.
321
+ Use -a/--all to include stopped sessions.
242
322
 
243
323
  Shows information about each session including:
244
- - Session name (target)
245
- - Process ID (PID)
324
+ - Session ID (short)
325
+ - Target name
246
326
  - Rails environment
247
327
  - Status (running/stopped)
328
+ - Uptime or stop time
329
+ - Command count
248
330
 
249
331
  Example output:
250
- Active sessions:
251
- - cone (default) [PID: 12345, ENV: development, STATUS: running]
252
- - api [PID: 12346, ENV: production, STATUS: running]
253
- - worker [PID: 12347, ENV: development, STATUS: stopped]
332
+ ID TARGET ENV STATUS UPTIME COMMANDS
333
+ a1b2 cone development running 2h 15m 42
334
+ e5f6 api production running 1h 30m 15
254
335
  LONGDESC
336
+ method_option :all, type: :boolean, aliases: '-a', desc: 'Include stopped sessions'
255
337
  def ls
256
338
  ensure_rails_project!
257
339
 
258
- sessions = load_sessions
340
+ include_stopped = options[:all]
341
+ sessions = session_registry.list_sessions(include_stopped: include_stopped)
259
342
 
260
- if sessions.empty? || sessions.size == 1 && sessions.key?('_schema')
261
- puts 'No active sessions'
343
+ # Also check legacy sessions.json for backward compatibility
344
+ legacy_sessions = load_sessions
345
+ legacy_sessions.each do |name, info|
346
+ next if name == '_schema'
347
+ next unless info['process_pid'] && process_alive?(info['process_pid'])
348
+
349
+ # Check if already in registry
350
+ existing = sessions.find { |s| s['target'] == name && s['status'] == 'running' }
351
+ next if existing
352
+
353
+ # Add legacy session (will be migrated on next start)
354
+ sessions << {
355
+ 'short_id' => '----',
356
+ 'target' => name,
357
+ 'rails_env' => 'development',
358
+ 'status' => 'running',
359
+ 'pid' => info['process_pid'],
360
+ 'created_at' => info['started_at'] ? Time.at(info['started_at']).iso8601 : Time.now.iso8601,
361
+ 'command_count' => 0,
362
+ '_legacy' => true
363
+ }
364
+ end
365
+
366
+ if sessions.empty?
367
+ if include_stopped
368
+ puts 'No sessions found'
369
+ else
370
+ puts 'No active sessions'
371
+ puts "Use 'cone ls -a' to see stopped sessions"
372
+ end
262
373
  return
263
374
  end
264
375
 
265
- active_sessions = []
266
- stale_sessions = []
376
+ # Verify running sessions are actually running
377
+ sessions.each do |session|
378
+ next unless session['status'] == 'running'
379
+ next if session['_legacy']
380
+
381
+ unless session['pid'] && process_alive?(session['pid'])
382
+ session_registry.stop_session(session_id: session['id'], reason: 'process_died')
383
+ session['status'] = 'stopped'
384
+ end
385
+ end
267
386
 
268
- sessions.each do |name, info|
269
- next if name == '_schema' # Skip schema field
387
+ # Re-filter if needed
388
+ sessions = sessions.select { |s| s['status'] == 'running' } unless include_stopped
270
389
 
271
- # Check if process is alive
272
- if info['process_pid'] && process_alive?(info['process_pid'])
273
- # Try to get server status
274
- adapter = create_rails_adapter(current_rails_env, name)
275
- server_status = begin
276
- adapter.get_status
277
- rescue StandardError
278
- nil
279
- end
390
+ if sessions.empty?
391
+ puts 'No active sessions'
392
+ puts "Use 'cone ls -a' to see stopped sessions"
393
+ return
394
+ end
280
395
 
281
- if server_status && server_status['success'] && server_status['running']
282
- rails_env = server_status['rails_env'] || 'development'
283
- console_pid = server_status['pid'] || info['process_pid']
284
- active_sessions << "#{name} (#{rails_env}) - PID: #{console_pid}"
285
- else
286
- stale_sessions << name
287
- end
396
+ # Display header
397
+ if include_stopped
398
+ puts 'ALL SESSIONS:'
399
+ else
400
+ puts 'ACTIVE SESSIONS:'
401
+ end
402
+ puts
403
+ puts format(' %-8s %-12s %-12s %-9s %-10s %s', 'ID', 'TARGET', 'ENV', 'STATUS', 'UPTIME', 'COMMANDS')
404
+
405
+ sessions.each do |session|
406
+ short_id = session['short_id'] || session['id']&.[](0, 4) || '----'
407
+ target = session['target'] || 'unknown'
408
+ env = session['rails_env'] || 'dev'
409
+ status = session['status'] || 'unknown'
410
+ commands = session['command_count'] || 0
411
+
412
+ if session['status'] == 'running'
413
+ started = session['started_at'] || session['created_at']
414
+ uptime = started ? format_uptime(Time.now - Time.parse(started)) : '---'
288
415
  else
289
- stale_sessions << name
416
+ stopped = session['stopped_at']
417
+ uptime = stopped ? format_time_ago(Time.now - Time.parse(stopped)) : '---'
290
418
  end
291
- end
292
419
 
293
- # Clean up stale sessions
294
- if stale_sessions.any?
295
- with_sessions_lock do
296
- sessions = load_sessions
297
- stale_sessions.each { |name| sessions.delete(name) }
298
- save_sessions(sessions)
299
- end
420
+ puts format(' %-8s %-12s %-12s %-9s %-10s %d', short_id, target, env, status, uptime, commands)
300
421
  end
301
422
 
302
- if active_sessions.empty?
303
- puts 'No active sessions'
423
+ puts
424
+ if include_stopped
425
+ puts "Use 'cone history --session ID' to view session history"
426
+ puts "Use 'cone rm ID' to remove session and history"
304
427
  else
305
- active_sessions.each { |session| puts session }
428
+ puts 'Usage: cone exec -t TARGET CODE'
429
+ puts ' cone exec --session ID CODE'
306
430
  end
307
431
  end
308
432
 
@@ -319,18 +443,15 @@ module Consolle
319
443
  if adapter.stop
320
444
  puts '✓ Rails console stopped'
321
445
 
322
- # Log session stop
323
- session_info = load_session_info
324
- if session_info && session_info[:process_pid]
325
- log_session_event(session_info[:process_pid], 'session_stop', {
326
- reason: 'user_requested'
327
- })
328
- end
446
+ # Mark session as stopped in registry (preserves history)
447
+ session_registry.stop_session(target: options[:target], reason: 'user_requested')
329
448
  else
330
449
  puts '✗ Failed to stop Rails console'
331
450
  end
332
451
  else
333
452
  puts 'Rails console is not running'
453
+ # Mark as stopped anyway in case registry is out of sync
454
+ session_registry.stop_session(target: options[:target], reason: 'not_running')
334
455
  end
335
456
 
336
457
  clear_session_info
@@ -341,27 +462,31 @@ module Consolle
341
462
  def stop_all
342
463
  ensure_rails_project!
343
464
 
344
- sessions = load_sessions
345
- active_sessions = []
465
+ # Get running sessions from registry
466
+ running_sessions = session_registry.list_sessions(include_stopped: false)
346
467
 
347
- # Filter active sessions (excluding schema)
348
- sessions.each do |name, info|
468
+ # Also check legacy sessions
469
+ legacy_sessions = load_sessions
470
+ legacy_sessions.each do |name, info|
349
471
  next if name == '_schema'
472
+ next unless info['process_pid'] && process_alive?(info['process_pid'])
473
+
474
+ existing = running_sessions.find { |s| s['target'] == name }
475
+ next if existing
350
476
 
351
- active_sessions << { name: name, info: info } if info['process_pid'] && process_alive?(info['process_pid'])
477
+ running_sessions << { 'target' => name, 'pid' => info['process_pid'], '_legacy' => true }
352
478
  end
353
479
 
354
- if active_sessions.empty?
480
+ if running_sessions.empty?
355
481
  puts 'No active sessions to stop'
356
482
  return
357
483
  end
358
484
 
359
- puts "Found #{active_sessions.size} active session(s)"
485
+ puts "Found #{running_sessions.size} active session(s)"
360
486
 
361
487
  # Stop each active session
362
- active_sessions.each do |session|
363
- name = session[:name]
364
- info = session[:info]
488
+ running_sessions.each do |session|
489
+ name = session['target']
365
490
 
366
491
  puts "\nStopping session '#{name}'..."
367
492
 
@@ -370,14 +495,10 @@ module Consolle
370
495
  if adapter.stop
371
496
  puts "✓ Session '#{name}' stopped"
372
497
 
373
- # Log session stop
374
- if info['process_pid']
375
- log_session_event(info['process_pid'], 'session_stop', {
376
- reason: 'stop_all_requested'
377
- })
378
- end
498
+ # Mark session as stopped in registry
499
+ session_registry.stop_session(target: name, reason: 'stop_all_requested') unless session['_legacy']
379
500
 
380
- # Clear session info
501
+ # Clear from legacy sessions.json
381
502
  with_sessions_lock do
382
503
  sessions = load_sessions
383
504
  sessions.delete(name)
@@ -598,6 +719,211 @@ module Consolle
598
719
  end
599
720
  end
600
721
 
722
+ desc 'rm SESSION_ID', 'Remove session and its history'
723
+ long_desc <<-LONGDESC
724
+ Removes a stopped session and all its history.
725
+
726
+ The SESSION_ID can be:
727
+ - Full session ID (8 characters, e.g., a1b2c3d4)
728
+ - Short session ID (4 characters, e.g., a1b2)
729
+ - Target name (e.g., cone, api)
730
+
731
+ Running sessions cannot be removed. Stop them first with 'cone stop -t TARGET'.
732
+
733
+ Use -f/--force to skip confirmation prompt.
734
+ Use -f/--force with a running session to stop and remove it.
735
+
736
+ Examples:
737
+ cone rm a1b2 # Remove by short ID
738
+ cone rm a1b2c3d4 # Remove by full ID
739
+ cone rm -f a1b2 # Remove without confirmation
740
+ LONGDESC
741
+ method_option :force, type: :boolean, aliases: '-f', desc: 'Skip confirmation (or force stop running session)'
742
+ def rm(session_id)
743
+ ensure_rails_project!
744
+
745
+ # Try to find session
746
+ session = session_registry.find_session(session_id: session_id) ||
747
+ session_registry.find_session(target: session_id)
748
+
749
+ unless session
750
+ puts "✗ Session not found: #{session_id}"
751
+ puts "Use 'cone ls -a' to see all sessions"
752
+ exit 1
753
+ end
754
+
755
+ # Check if running
756
+ if session['status'] == 'running'
757
+ if options[:force]
758
+ # Force stop first
759
+ puts "Stopping running session '#{session['target']}'..."
760
+ adapter = create_rails_adapter('development', session['target'])
761
+ adapter.stop
762
+ session_registry.stop_session(session_id: session['id'], reason: 'force_remove')
763
+ clear_session_info if options[:target] == session['target']
764
+ else
765
+ puts "✗ Session #{session['short_id']} (#{session['target']}) is still running"
766
+ puts " Use 'cone stop -t #{session['target']}' first, or 'cone rm -f #{session_id}' to force"
767
+ exit 1
768
+ end
769
+ end
770
+
771
+ # Confirm deletion
772
+ unless options[:force]
773
+ command_count = session['command_count'] || 0
774
+ print "Remove session #{session['id']} (#{session['target']}, #{command_count} commands)?\n"
775
+ print 'This will permanently delete all history. [y/N]: '
776
+ response = $stdin.gets&.strip&.downcase
777
+ unless response == 'y' || response == 'yes'
778
+ puts 'Cancelled'
779
+ return
780
+ end
781
+ end
782
+
783
+ # Remove session
784
+ result = session_registry.remove_session(session_id: session['id'])
785
+
786
+ if result && !result.is_a?(Hash)
787
+ puts "✓ Session #{session['id']} removed"
788
+ else
789
+ puts "✗ Failed to remove session"
790
+ exit 1
791
+ end
792
+ end
793
+
794
+ desc 'prune', 'Remove all stopped sessions'
795
+ long_desc <<-LONGDESC
796
+ Removes all stopped sessions and their history.
797
+
798
+ By default, only removes sessions from the current project.
799
+
800
+ Use --yes to skip confirmation prompt.
801
+
802
+ Examples:
803
+ cone prune # Remove stopped sessions (with confirmation)
804
+ cone prune --yes # Remove without confirmation
805
+ LONGDESC
806
+ method_option :yes, type: :boolean, aliases: '-y', desc: 'Skip confirmation'
807
+ def prune
808
+ ensure_rails_project!
809
+
810
+ stopped = session_registry.list_stopped_sessions
811
+
812
+ if stopped.empty?
813
+ puts 'No stopped sessions to remove'
814
+ return
815
+ end
816
+
817
+ # Show what will be removed
818
+ total_commands = stopped.sum { |s| s['command_count'] || 0 }
819
+
820
+ puts "Found #{stopped.size} stopped session(s):"
821
+ stopped.each do |session|
822
+ stopped_at = session['stopped_at'] ? Time.parse(session['stopped_at']).strftime('%Y-%m-%d') : '---'
823
+ commands = session['command_count'] || 0
824
+ puts " #{session['short_id']} #{session['target'].ljust(12)} stopped #{stopped_at} #{commands} commands"
825
+ end
826
+ puts
827
+
828
+ # Confirm
829
+ unless options[:yes]
830
+ print "Remove all stopped sessions and their history? [y/N]: "
831
+ response = $stdin.gets&.strip&.downcase
832
+ unless response == 'y' || response == 'yes'
833
+ puts 'Cancelled'
834
+ return
835
+ end
836
+ end
837
+
838
+ # Remove all stopped sessions
839
+ removed = session_registry.prune_sessions
840
+
841
+ puts "✓ Removed #{removed.size} sessions (#{total_commands} commands)"
842
+ end
843
+
844
+ desc 'history', 'Show command history'
845
+ long_desc <<-LONGDESC
846
+ Shows command history for sessions.
847
+
848
+ By default, shows history from the current active session (target).
849
+
850
+ Options:
851
+ --session ID Show history for specific session (by ID or short ID)
852
+ -t, --target Show history for specific target name
853
+ -n, --limit Limit number of entries shown
854
+ --today Show only today's commands
855
+ --date DATE Show commands from specific date (YYYY-MM-DD)
856
+ --success Show only successful commands
857
+ --failed Show only failed commands
858
+ --grep PATTERN Filter by code or result matching pattern
859
+ --all Include history from stopped sessions with same target
860
+ -v, --verbose Show detailed output
861
+ --json Output as JSON
862
+
863
+ Examples:
864
+ cone history # Current session history
865
+ cone history -t api # History for 'api' target
866
+ cone history --session a1b2 # History for specific session
867
+ cone history -n 10 # Last 10 commands
868
+ cone history --today # Today's commands only
869
+ cone history --failed # Failed commands only
870
+ cone history --grep User # Filter by pattern
871
+ LONGDESC
872
+ method_option :session, type: :string, aliases: '-s', desc: 'Session ID or short ID'
873
+ method_option :limit, type: :numeric, aliases: '-n', desc: 'Limit number of entries'
874
+ method_option :today, type: :boolean, desc: 'Show only today'
875
+ method_option :date, type: :string, desc: 'Show specific date (YYYY-MM-DD)'
876
+ method_option :success, type: :boolean, desc: 'Show only successful commands'
877
+ method_option :failed, type: :boolean, desc: 'Show only failed commands'
878
+ method_option :grep, type: :string, aliases: '-g', desc: 'Filter by pattern'
879
+ method_option :all, type: :boolean, desc: 'Include stopped sessions'
880
+ method_option :json, type: :boolean, desc: 'Output as JSON'
881
+ def history
882
+ ensure_rails_project!
883
+
884
+ history_manager = Consolle::History.new
885
+
886
+ entries = history_manager.query(
887
+ session_id: options[:session],
888
+ target: options[:target],
889
+ limit: options[:limit],
890
+ today: options[:today],
891
+ date: options[:date],
892
+ success_only: options[:success],
893
+ failed_only: options[:failed],
894
+ grep: options[:grep],
895
+ all_sessions: options[:all]
896
+ )
897
+
898
+ if entries.empty?
899
+ puts 'No history found'
900
+ if options[:session] || options[:target]
901
+ puts "Try 'cone history' without filters to see all history"
902
+ else
903
+ puts "Execute some commands first with 'cone exec'"
904
+ end
905
+ return
906
+ end
907
+
908
+ if options[:json]
909
+ puts history_manager.format_json(entries)
910
+ elsif options[:verbose]
911
+ entries.each do |entry|
912
+ puts history_manager.format_entry_verbose(entry)
913
+ puts
914
+ end
915
+ else
916
+ entries.each do |entry|
917
+ puts history_manager.format_entry(entry)
918
+ puts
919
+ end
920
+ end
921
+
922
+ unless options[:json]
923
+ puts "Showing #{entries.size} entries"
924
+ end
925
+ end
926
+
601
927
  private
602
928
 
603
929
  def current_rails_env
@@ -734,7 +1060,7 @@ module Consolle
734
1060
  )
735
1061
  end
736
1062
 
737
- def save_session_info(adapter)
1063
+ def save_session_info(adapter, session_id = nil)
738
1064
  target = options[:target]
739
1065
 
740
1066
  with_sessions_lock do
@@ -746,7 +1072,8 @@ module Consolle
746
1072
  'pid_path' => project_pid_path(target),
747
1073
  'log_path' => project_log_path(target),
748
1074
  'started_at' => Time.now.to_f,
749
- 'rails_root' => Dir.pwd
1075
+ 'rails_root' => Dir.pwd,
1076
+ 'session_id' => session_id
750
1077
  }
751
1078
 
752
1079
  save_sessions(sessions)
@@ -767,7 +1094,8 @@ module Consolle
767
1094
  socket_path: session['socket_path'],
768
1095
  process_pid: session['process_pid'],
769
1096
  started_at: session['started_at'],
770
- rails_root: session['rails_root']
1097
+ rails_root: session['rails_root'],
1098
+ session_id: session['session_id']
771
1099
  }
772
1100
  end
773
1101
 
@@ -782,47 +1110,44 @@ module Consolle
782
1110
  end
783
1111
 
784
1112
  def log_session_activity(process_pid, code, result)
785
- # Create log filename based on date and PID
786
- log_file = File.join(project_session_dir, "session_#{Date.today.strftime('%Y%m%d')}_pid#{process_pid}.log")
787
-
788
- # Create log entry
789
- log_entry = {
790
- timestamp: Time.now.iso8601,
791
- request_id: result['request_id'],
792
- code: code,
793
- success: result['success'],
794
- result: result['result'],
795
- error: result['error'],
796
- message: result['message'],
797
- execution_time: result['execution_time']
798
- }
1113
+ # Try to use new History class if session_id is available
1114
+ session_info = load_session_info
1115
+ if session_info&.dig(:session_id)
1116
+ history_manager = Consolle::History.new
1117
+ history_manager.log_command(
1118
+ session_id: session_info[:session_id],
1119
+ target: options[:target],
1120
+ code: code,
1121
+ result: result
1122
+ )
1123
+ else
1124
+ # Fallback to legacy logging
1125
+ log_file = File.join(project_session_dir, "session_#{Date.today.strftime('%Y%m%d')}_pid#{process_pid}.log")
1126
+
1127
+ log_entry = {
1128
+ timestamp: Time.now.iso8601,
1129
+ target: options[:target],
1130
+ request_id: result['request_id'],
1131
+ code: code,
1132
+ success: result['success'],
1133
+ result: result['result'],
1134
+ error: result['error'],
1135
+ message: result['message'],
1136
+ execution_time: result['execution_time']
1137
+ }
799
1138
 
800
- # Append to log file
801
- File.open(log_file, 'a') do |f|
802
- f.puts JSON.generate(log_entry)
1139
+ File.open(log_file, 'a') do |f|
1140
+ f.puts JSON.generate(log_entry)
1141
+ end
803
1142
  end
804
1143
  rescue StandardError => e
805
1144
  # Log errors should not crash the command
806
1145
  puts "Warning: Failed to log session activity: #{e.message}" if options[:verbose]
807
1146
  end
808
1147
 
809
- def log_session_event(process_pid, event_type, details = {})
810
- # Create log filename based on date and PID
811
- log_file = File.join(project_session_dir, "session_#{Date.today.strftime('%Y%m%d')}_pid#{process_pid}.log")
812
-
813
- # Create log entry
814
- log_entry = {
815
- timestamp: Time.now.iso8601,
816
- event: event_type
817
- }.merge(details)
818
-
819
- # Append to log file
820
- File.open(log_file, 'a') do |f|
821
- f.puts JSON.generate(log_entry)
822
- end
823
- rescue StandardError => e
824
- # Log errors should not crash the command
825
- puts "Warning: Failed to log session event: #{e.message}" if options[:verbose]
1148
+ def log_session_event(_process_pid, _event_type, _details = {})
1149
+ # Legacy method kept for backward compatibility with tests
1150
+ # Session events are now tracked in registry metadata
826
1151
  end
827
1152
 
828
1153
  def load_sessions
@@ -903,6 +1228,41 @@ module Consolle
903
1228
  end
904
1229
  end
905
1230
 
1231
+ def session_registry
1232
+ @session_registry ||= Consolle::SessionRegistry.new
1233
+ end
1234
+
1235
+ def format_uptime(seconds)
1236
+ seconds = seconds.to_i
1237
+ if seconds < 60
1238
+ "#{seconds}s"
1239
+ elsif seconds < 3600
1240
+ "#{seconds / 60}m #{seconds % 60}s"
1241
+ elsif seconds < 86400
1242
+ hours = seconds / 3600
1243
+ mins = (seconds % 3600) / 60
1244
+ "#{hours}h #{mins}m"
1245
+ else
1246
+ days = seconds / 86400
1247
+ hours = (seconds % 86400) / 3600
1248
+ "#{days}d #{hours}h"
1249
+ end
1250
+ end
1251
+
1252
+ def format_time_ago(seconds)
1253
+ seconds = seconds.to_i
1254
+ if seconds < 60
1255
+ 'just now'
1256
+ elsif seconds < 3600
1257
+ "#{seconds / 60}m ago"
1258
+ elsif seconds < 86400
1259
+ "#{seconds / 3600}h ago"
1260
+ else
1261
+ days = seconds / 86400
1262
+ "#{days}d ago"
1263
+ end
1264
+ end
1265
+
906
1266
  def process_alive?(pid)
907
1267
  return false unless pid
908
1268