fantasy-cli 1.2.14 → 1.3.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.
data/lib/gsd/tui/app.rb CHANGED
@@ -8,12 +8,33 @@ require 'gsd/tui/status_bar'
8
8
  require 'gsd/tui/auto_complete'
9
9
  require 'gsd/tui/command_palette'
10
10
  require 'gsd/tui/spinner'
11
+ require 'gsd/tui/agent_panel'
12
+ require 'gsd/tui/session'
13
+ require 'gsd/tui/tab_manager'
14
+ require 'gsd/tui/animations'
15
+ require 'gsd/tui/transitions'
16
+ require 'gsd/tui/mouse'
17
+ require 'gsd/tui/effects'
18
+ require 'gsd/agents/swarm'
19
+ require 'gsd/agents/communication'
11
20
  require 'gsd/ai/config'
21
+ require 'gsd/plugins/registry'
22
+ require 'gsd/plugins/loader'
23
+ require 'gsd/plugins/hooks'
24
+ require 'gsd/plugins/hot_reload'
25
+ require 'gsd/plugins/api'
26
+ require 'gsd/lsp/protocol'
27
+ require 'gsd/lsp/client'
28
+ require 'gsd/lsp/server_manager'
29
+ require 'gsd/lsp/diagnostics'
30
+ require 'gsd/lsp/completion'
31
+ require 'gsd/lsp/hover'
32
+ require 'gsd/lsp/symbols'
12
33
 
13
34
  module Gsd
14
35
  module TUI
15
36
  class App
16
- def initialize(theme: :fantasy, header_style: :pixel)
37
+ def initialize(theme: :cyber_wave, header_style: :pixel)
17
38
  @theme = theme
18
39
  @header_style = header_style
19
40
  @running = false
@@ -26,6 +47,44 @@ module Gsd
26
47
  @command_palette = CommandPalette.new
27
48
  @spinner = Spinner.new
28
49
 
50
+ # Initialize Agent Swarm (v1.3.0)
51
+ @swarm = Gsd::Agents::Swarm.new(max_concurrent: 5, auto_recovery: true)
52
+ @agent_panel = AgentPanel.new(@swarm, width: 60, visible: false)
53
+ @communication = Gsd::Agents::Communication.new(persist_messages: true)
54
+
55
+ # Start swarm orchestration
56
+ @swarm.start
57
+
58
+ # Subscribe to swarm events
59
+ setup_swarm_observers
60
+
61
+ # Initialize Session (v1.3.0)
62
+ @session = Session.new
63
+ @session.start_auto_save
64
+
65
+ # Initialize TabManager (v1.3.0)
66
+ @tab_manager = TabManager.new(max_tabs: 10)
67
+
68
+ # Initialize Advanced UI components (v1.3.0)
69
+ @animation_manager = AnimationManager.new
70
+ @transition_manager = TransitionManager.new
71
+ @mouse_integration = MouseIntegration.new(self)
72
+ @view_transition = ViewTransition.new(self)
73
+ @effects_enabled = true
74
+ @animations_enabled = true
75
+
76
+ # Initialize Plugin System (v1.3.0)
77
+ @plugin_registry = Gsd::Plugins::Registry.instance
78
+ @plugin_loader = Gsd::Plugins::Loader.new(registry: @plugin_registry)
79
+ @plugin_hooks = Gsd::Plugins::Hooks.instance
80
+ @hot_reload = Gsd::Plugins::HotReload.new(registry: @plugin_registry, loader: @plugin_loader)
81
+ setup_plugin_system
82
+
83
+ # Initialize LSP System (v1.3.0)
84
+ @lsp_manager = Gsd::LSP::ServerManager.new
85
+ @lsp_diagnostics = Gsd::LSP::Diagnostics.new
86
+ setup_lsp_system
87
+
29
88
  @agents = ['Code', 'Kilo Auto Free', 'Kilo Gateway']
30
89
  @selected_agent = 1
31
90
 
@@ -37,6 +96,16 @@ module Gsd
37
96
 
38
97
  # Initialize status bar with current agent mode
39
98
  update_status_mode
99
+
100
+ # Show session info in welcome message
101
+ add_welcome_message
102
+ end
103
+
104
+ def add_welcome_message
105
+ @output << { type: :system, text: "Welcome to Fantasy CLI v#{Gsd::VERSION}!" }
106
+ @output << { type: :system, text: "Session: #{@session.name}" }
107
+ @output << { type: :system, text: 'Press Ctrl+P for commands, Ctrl+Q to quit.' }
108
+ @output << { type: :system, text: 'Commands: /save, /restore, /api' }
40
109
  end
41
110
 
42
111
  def run
@@ -72,6 +141,7 @@ module Gsd
72
141
 
73
142
  def stop
74
143
  @running = false
144
+ @swarm&.stop
75
145
  end
76
146
 
77
147
  # Add a message to the output
@@ -143,6 +213,9 @@ module Gsd
143
213
  # Header
144
214
  lines << @header.render
145
215
 
216
+ # Tab bar
217
+ lines << @tab_manager.render_tab_bar(width: 80) if @tab_manager.tab_count > 1
218
+
146
219
  if @command_palette.visible?
147
220
  lines << @command_palette.render(width: 58)
148
221
  else
@@ -212,6 +285,10 @@ module Gsd
212
285
  char = "\cP"
213
286
  when "\x11" # Ctrl+Q (17 = 0x11)
214
287
  char = "\cQ"
288
+ when "\x14" # Ctrl+T (20 = 0x14) - New Tab
289
+ char = :new_tab
290
+ when "\x17" # Ctrl+W (23 = 0x17) - Close Tab
291
+ char = :close_tab
215
292
  end
216
293
 
217
294
  if @command_palette.visible?
@@ -236,6 +313,12 @@ module Gsd
236
313
  when "\cP"
237
314
  @command_palette.show
238
315
  full_render
316
+ when :new_tab
317
+ handle_new_tab
318
+ full_render
319
+ when :close_tab
320
+ handle_close_tab
321
+ full_render
239
322
  when "\t"
240
323
  handle_tab
241
324
  full_render
@@ -290,7 +373,7 @@ module Gsd
290
373
  case action
291
374
  when :quit
292
375
  stop
293
- when :theme_fantasy, :theme_kilo, :theme_dark, :theme_light, :theme_nord
376
+ when :theme_fantasy, :theme_kilo, :theme_dark, :theme_light, :theme_nord, :theme_cyber_wave
294
377
  theme_name = action.to_s.split('_').last.to_sym
295
378
  set_theme(theme_name)
296
379
  else
@@ -338,6 +421,16 @@ module Gsd
338
421
  case command
339
422
  when '/api'
340
423
  handle_api_command(args)
424
+ when '/save'
425
+ handle_save_command(args)
426
+ when '/restore'
427
+ handle_restore_command(args)
428
+ when '/tab'
429
+ handle_tab_command(args)
430
+ when '/plugin'
431
+ handle_plugin_command(args)
432
+ when '/lsp'
433
+ handle_lsp_command(args)
341
434
  else
342
435
  @output << { type: :error, text: "Unknown command: #{command}" }
343
436
  end
@@ -393,6 +486,85 @@ module Gsd
393
486
  end
394
487
  end
395
488
 
489
+ # Handle /save subcommands
490
+ def handle_save_command(args)
491
+ subcommand = args[0]
492
+
493
+ case subcommand
494
+ when 'checkpoint', nil
495
+ checkpoint_name = args[1] || "checkpoint-#{Time.now.to_i}"
496
+ checkpoint_id = @session.checkpoint(app: self, name: checkpoint_name)
497
+ @output << { type: :system, text: "💾 Checkpoint created: #{checkpoint_name}" }
498
+ @output << { type: :system, text: " ID: #{checkpoint_id}" }
499
+ when 'export'
500
+ format = args[1] || 'markdown'
501
+ content = @session.export(format: format.to_sym)
502
+ if content
503
+ filename = "#{@session.name.gsub(/[^a-zA-Z0-9]/, '_')}.#{format}"
504
+ File.write(filename, content)
505
+ @output << { type: :system, text: "📤 Exported to: #{filename}" }
506
+ else
507
+ @output << { type: :error, text: "Failed to export session" }
508
+ end
509
+ when 'info'
510
+ info = @session.info
511
+ @output << { type: :system, text: "📊 Session Info:" }
512
+ @output << { type: :system, text: " Name: #{info[:name]}" }
513
+ @output << { type: :system, text: " ID: #{info[:id]}" }
514
+ @output << { type: :system, text: " Created: #{info[:created_at]}" }
515
+ @output << { type: :system, text: " Last saved: #{info[:last_saved_at] || 'Never'}" }
516
+ else
517
+ @output << { type: :error, text: "Unknown /save subcommand: #{subcommand}" }
518
+ @output << { type: :system, text: "Usage: /save [checkpoint [name]|export [format]|info]" }
519
+ end
520
+ end
521
+
522
+ # Handle /restore subcommands
523
+ def handle_restore_command(args)
524
+ subcommand = args[0]
525
+
526
+ case subcommand
527
+ when 'session', nil
528
+ if args[1]
529
+ session = Session.load(session_id: args[1])
530
+ if session
531
+ @session.stop_auto_save
532
+ @session = session
533
+ @session.restore(app: self)
534
+ @session.start_auto_save
535
+ @output << { type: :system, text: "📂 Restored session: #{@session.name}" }
536
+ else
537
+ @output << { type: :error, text: "Session not found: #{args[1]}" }
538
+ end
539
+ else
540
+ # List available sessions
541
+ sessions = Session.list_all
542
+ @output << { type: :system, text: "📂 Available sessions:" }
543
+ sessions.first(5).each do |s|
544
+ @output << { type: :system, text: " #{s[:id]} - #{s[:name]} (#{s[:message_count]} msgs)" }
545
+ end
546
+ end
547
+ when 'checkpoint'
548
+ if args[1]
549
+ if @session.restore_checkpoint(app: self, checkpoint_id: args[1])
550
+ @output << { type: :system, text: "⏪ Restored checkpoint: #{args[1]}" }
551
+ else
552
+ @output << { type: :error, text: "Checkpoint not found: #{args[1]}" }
553
+ end
554
+ else
555
+ # List checkpoints
556
+ checkpoints = @session.checkpoints
557
+ @output << { type: :system, text: "📋 Checkpoints:" }
558
+ checkpoints.first(5).each do |c|
559
+ @output << { type: :system, text: " #{c[:id]} - #{c[:name]}" }
560
+ end
561
+ end
562
+ else
563
+ @output << { type: :error, text: "Unknown /restore subcommand: #{subcommand}" }
564
+ @output << { type: :system, text: "Usage: /restore [session [id]|checkpoint [id]]" }
565
+ end
566
+ end
567
+
396
568
  def handle_up_arrow
397
569
  return if @history.empty?
398
570
 
@@ -443,6 +615,274 @@ module Gsd
443
615
  'NORMAL'
444
616
  end
445
617
  end
618
+
619
+ # Tab handling methods
620
+ def handle_new_tab
621
+ @tab_manager.create_tab(title: "Tab #{@tab_manager.tab_count + 1}")
622
+ @output << { type: :system, text: "📂 New tab created" }
623
+ switch_to_tab_state(@tab_manager.current_tab)
624
+ end
625
+
626
+ def handle_close_tab
627
+ if @tab_manager.close_current_tab
628
+ @output << { type: :system, text: "📂 Tab closed" }
629
+ switch_to_tab_state(@tab_manager.current_tab)
630
+ else
631
+ @output << { type: :error, text: "Cannot close last tab" }
632
+ end
633
+ end
634
+
635
+ def handle_next_tab
636
+ @tab_manager.next_tab
637
+ @output << { type: :system, text: "📂 Switched to tab: #{@tab_manager.current_tab.title}" }
638
+ switch_to_tab_state(@tab_manager.current_tab)
639
+ end
640
+
641
+ def handle_tab_command(args)
642
+ subcommand = args[0]
643
+
644
+ case subcommand
645
+ when 'new', nil
646
+ handle_new_tab
647
+ when 'close'
648
+ handle_close_tab
649
+ when 'next'
650
+ handle_next_tab
651
+ when 'list'
652
+ tabs = @tab_manager.list_tabs
653
+ @output << { type: :system, text: "📂 Tabs:" }
654
+ tabs.each do |t|
655
+ prefix = t[:is_active] ? '>' : ' '
656
+ @output << { type: :system, text: " #{prefix} [#{t[:index]}] #{t[:title]} (#{t[:message_count]} msgs)" }
657
+ end
658
+ when 'switch'
659
+ index = args[1]&.to_i
660
+ if index && @tab_manager.switch_to_tab(index)
661
+ @output << { type: :system, text: "📂 Switched to tab: #{@tab_manager.current_tab.title}" }
662
+ switch_to_tab_state(@tab_manager.current_tab)
663
+ else
664
+ @output << { type: :error, text: "Usage: /tab switch <index>" }
665
+ end
666
+ else
667
+ @output << { type: :error, text: "Unknown /tab subcommand: #{subcommand}" }
668
+ @output << { type: :system, text: "Usage: /tab {new|close|next|list|switch <index>}" }
669
+ end
670
+ end
671
+
672
+ def switch_to_tab_state(tab)
673
+ @output = tab.output.dup
674
+ @history = tab.history.dup
675
+ @history_index = tab.history_index
676
+ @input_box.clear
677
+ @input_box.add_char(tab.input_text) unless tab.input_text.empty?
678
+ @selected_agent = tab.selected_agent
679
+ end
680
+
681
+ # Setup observers for swarm events
682
+ def setup_swarm_observers
683
+ @swarm.add_observer do |event, *args|
684
+ case event
685
+ when :agent_spawned
686
+ agent_id, type = args
687
+ add_message(:system, "🚀 Agent #{type} spawned (#{agent_id[0..12]})")
688
+ when :agent_terminated
689
+ agent_id = args.first
690
+ add_message(:system, "💀 Agent terminated (#{agent_id[0..12]})")
691
+ when :agent_crashed
692
+ agent_id = args.first
693
+ add_message(:error, "💥 Agent crashed (#{agent_id[0..12]})")
694
+ when :agent_restarted
695
+ agent_id = args.first
696
+ add_message(:system, "🔄 Agent restarted (#{agent_id[0..12]})")
697
+ when :task_completed
698
+ agent_id, task = args
699
+ add_message(:system, "✅ Task completed by #{agent_id[0..12]}")
700
+ when :task_queued
701
+ task = args.first
702
+ add_message(:system, "📋 Task queued: #{task[:type]}")
703
+ end
704
+ end
705
+ end
706
+
707
+ # Plugin System setup
708
+ def setup_plugin_system
709
+ # Load all discovered plugins
710
+ @plugin_loader.load_all
711
+
712
+ # Enable all loaded plugins
713
+ @plugin_registry.all.each do |plugin|
714
+ begin
715
+ plugin.enable!
716
+ @plugin_hooks.trigger("plugin.enabled", plugin.name)
717
+ rescue StandardError => e
718
+ warn "[Plugins] Failed to enable '#{plugin.name}': #{e.message}"
719
+ end
720
+ end
721
+
722
+ # Start hot-reload watcher
723
+ @hot_reload.start
724
+
725
+ # Register hook for TUI messages from plugins
726
+ @plugin_hooks.on('tui.message') do |data|
727
+ if data && data[:text]
728
+ add_message(data[:type] || :system, "[#{data[:plugin]}] #{data[:text]}")
729
+ end
730
+ end
731
+
732
+ @plugin_hooks.trigger('plugins.loaded', @plugin_registry.all.map(&:name))
733
+ end
734
+
735
+ # Handle /plugin subcommands
736
+ def handle_plugin_command(args)
737
+ subcommand = args[0]
738
+
739
+ case subcommand
740
+ when 'list', nil
741
+ plugins = @plugin_registry.all
742
+ @output << { type: :system, text: "📦 Plugins (#{plugins.length}):" }
743
+ plugins.each do |p|
744
+ status = p.enabled ? '✅' : '⭕'
745
+ @output << { type: :system, text: " #{status} #{p.name} v#{p.version}" }
746
+ end
747
+ when 'enable'
748
+ name = args[1]
749
+ if name
750
+ begin
751
+ @plugin_registry.enable(name)
752
+ @output << { type: :system, text: "✅ Plugin '#{name}' enabled" }
753
+ rescue StandardError => e
754
+ @output << { type: :error, text: "❌ #{e.message}" }
755
+ end
756
+ else
757
+ @output << { type: :error, text: "Usage: /plugin enable <name>" }
758
+ end
759
+ when 'disable'
760
+ name = args[1]
761
+ if name
762
+ begin
763
+ @plugin_registry.disable(name)
764
+ @output << { type: :system, text: "⭕ Plugin '#{name}' disabled" }
765
+ rescue StandardError => e
766
+ @output << { type: :error, text: "❌ #{e.message}" }
767
+ end
768
+ else
769
+ @output << { type: :error, text: "Usage: /plugin disable <name>" }
770
+ end
771
+ when 'reload'
772
+ name = args[1]
773
+ if name
774
+ if @hot_reload.reload_plugin(name)
775
+ @output << { type: :system, text: "🔄 Plugin '#{name}' reloaded" }
776
+ else
777
+ @output << { type: :error, text: "❌ Failed to reload '#{name}'" }
778
+ end
779
+ else
780
+ @output << { type: :error, text: "Usage: /plugin reload <name>" }
781
+ end
782
+ when 'info'
783
+ name = args[1]
784
+ if name
785
+ info = @plugin_registry.plugin_info(name)
786
+ if info
787
+ @output << { type: :system, text: "📦 #{info[:name]}:" }
788
+ @output << { type: :system, text: " Version: #{info[:version]}" }
789
+ @output << { type: :system, text: " Author: #{info[:author]}" }
790
+ @output << { type: :system, text: " Status: #{info[:enabled] ? 'enabled' : 'disabled'}" }
791
+ @output << { type: :system, text: " Path: #{info[:path]}" }
792
+ else
793
+ @output << { type: :error, text: "Plugin '#{name}' not found" }
794
+ end
795
+ else
796
+ @output << { type: :error, text: "Usage: /plugin info <name>" }
797
+ end
798
+ when 'status'
799
+ status = @hot_reload.status
800
+ @output << { type: :system, text: "🔌 Plugin System Status:" }
801
+ @output << { type: :system, text: " Watching: #{status[:watching]}" }
802
+ @output << { type: :system, text: " Files watched: #{status[:files_watched]}" }
803
+ @output << { type: :system, text: " Paused: #{status[:paused]}" }
804
+ else
805
+ @output << { type: :error, text: "Unknown /plugin subcommand: #{subcommand}" }
806
+ @output << { type: :system, text: "Usage: /plugin {list|enable|disable|reload|info|status} [args]" }
807
+ end
808
+ end
809
+
810
+ # Setup LSP system and register hooks
811
+ def setup_lsp_system
812
+ # Register hook for LSP diagnostics
813
+ @plugin_hooks.on('lsp.diagnostics') do |uri, diagnostics|
814
+ @lsp_diagnostics.update(uri, diagnostics)
815
+ counts = @lsp_diagnostics.count_by_severity
816
+ if counts[:error] > 0 || counts[:warning] > 0
817
+ @status_bar.update_lsp_status("LSP E:#{counts[:error]} W:#{counts[:warning]}")
818
+ end
819
+ end
820
+ end
821
+
822
+ # Handle /lsp subcommands
823
+ def handle_lsp_command(args)
824
+ subcommand = args[0]
825
+
826
+ case subcommand
827
+ when 'start', nil
828
+ language = args[1]&.to_sym || :ruby
829
+ client = @lsp_manager.client_for_language(language)
830
+ if client
831
+ @output << { type: :system, text: "🔌 LSP server for #{language} started" }
832
+ else
833
+ @output << { type: :error, text: "Failed to start LSP server for #{language}" }
834
+ end
835
+ when 'stop'
836
+ language = args[1]&.to_sym
837
+ if language
838
+ @lsp_manager.stop_language(language)
839
+ @output << { type: :system, text: "🔌 LSP server for #{language} stopped" }
840
+ else
841
+ @lsp_manager.stop_all
842
+ @output << { type: :system, text: "🔌 All LSP servers stopped" }
843
+ end
844
+ when 'status'
845
+ running = @lsp_manager.running
846
+ available = @lsp_manager.available_servers
847
+ @output << { type: :system, text: "🔌 LSP Status:" }
848
+ @output << { type: :system, text: " Running: #{running.empty? ? 'none' : running.join(', ')}" }
849
+ @output << { type: :system, text: " Available: #{available.empty? ? 'none' : available.join(', ')}" }
850
+ when 'info'
851
+ language = args[1]&.to_sym || :ruby
852
+ info = @lsp_manager.server_info(language)
853
+ if info
854
+ @output << { type: :system, text: "📦 #{info[:name]}:" }
855
+ @output << { type: :system, text: " Running: #{info[:running]}" }
856
+ @output << { type: :system, text: " State: #{info[:state]}" }
857
+ @output << { type: :system, text: " Server: #{info[:server_info]&.[]('name') || 'unknown'}" }
858
+ else
859
+ @output << { type: :error, text: "No LSP server for #{language}" }
860
+ end
861
+ when 'diagnostics'
862
+ file_path = args[1] || Dir.pwd
863
+ diagnostics = @lsp_diagnostics.for_file(file_path)
864
+ if diagnostics.empty?
865
+ @output << { type: :system, text: "✅ No diagnostics for #{file_path}" }
866
+ else
867
+ @output << { type: :system, text: "📋 Diagnostics for #{file_path} (#{diagnostics.length}):" }
868
+ diagnostics.first(10).each do |d|
869
+ line = d.range.start.line + 1
870
+ severity = d.severity == 1 ? '🔴' : '🟡'
871
+ @output << { type: :system, text: " #{severity} L#{line}: #{d.message.lines.first.chomp}" }
872
+ end
873
+ end
874
+ when 'list'
875
+ languages = @lsp_manager.configured_languages
876
+ @output << { type: :system, text: "📋 Configured LSP servers:" }
877
+ languages.each do |lang|
878
+ running = @lsp_manager.running?(lang) ? '✅' : '⭕'
879
+ @output << { type: :system, text: " #{running} #{lang}" }
880
+ end
881
+ else
882
+ @output << { type: :error, text: "Unknown /lsp subcommand: #{subcommand}" }
883
+ @output << { type: :system, text: "Usage: /lsp {start|stop|status|info|diagnostics|list} [args]" }
884
+ end
885
+ end
446
886
  end
447
887
  end
448
888
  end
@@ -95,6 +95,20 @@ module Gsd
95
95
  error: BRIGHT_RED,
96
96
  bg: BG_BLACK,
97
97
  bg_accent: BG_BLUE
98
+ },
99
+ cyber_wave: {
100
+ name: 'Cyber Wave',
101
+ accent: BRIGHT_CYAN, # Ciano neon
102
+ accent2: BRIGHT_MAGENTA, # Roxo neon
103
+ accent3: BRIGHT_RED, # Rosa neon (usando RED brilhante)
104
+ text: WHITE,
105
+ dim: BRIGHT_BLACK,
106
+ success: BRIGHT_GREEN,
107
+ warning: BRIGHT_YELLOW,
108
+ error: BRIGHT_RED,
109
+ bg: BG_BLACK,
110
+ bg_accent: BG_CYAN,
111
+ bg_accent2: BG_MAGENTA
98
112
  }
99
113
  }.freeze
100
114
 
@@ -109,3 +123,4 @@ module Gsd
109
123
  end
110
124
  end
111
125
  end
126
+