pwn 0.5.754 → 0.5.755

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e01b8e88574a241d780acc3e2c4d70a7e5c07fb1e9e17fa709363a4bfcbc7898
4
- data.tar.gz: f50a1f5f35d25f300c396678edb5daa8119f4fca9e1567d71652c7f45bcc1dcb
3
+ metadata.gz: '088caeab6962a895018847c637332f41ca7033f69c7e8a547862d219c58b162c'
4
+ data.tar.gz: b0562e89e6cd08079728c7d5f0b2f640b975f482ed028c1cbe6b27a363361172
5
5
  SHA512:
6
- metadata.gz: c34deaa7b6835b37cacb76a2863cb0759b4dc727301692014702468aa3d4a04ddc1c76fda3893005f58071b9060768ade05401dbbae1cac2fc3dbfc99bcfa9f0
7
- data.tar.gz: de481a34a2b3776fa8003094618af168f6a33341fd236e14daa3247694e8e703e0eddb15ea74556e9bd4814a14a3187d48f51c18be90094f1bd4c83ddcd364b8
6
+ metadata.gz: 2730afce4cd70424a7fb927983bb417c1bb755846cd33113ee522f3b4d1a2e3a227251b7f3e5079b9bf3198224f8d36d5f31bed362acc37b6022fb0da03c417d
7
+ data.tar.gz: 9ac89ddaeb41583bc8b0db2a98faf9696ae78aae7f8ecc2689f4f8519071e1c284d172245aeb586de4a33c4ad7a81e4dc7ee16a7b849c23f4429e4ee9456e8ca
data/Gemfile CHANGED
@@ -89,8 +89,8 @@ gem 'ruby-nmap', '1.0.3'
89
89
  gem 'ruby-saml', '1.18.1'
90
90
  gem 'rvm', '1.11.3.9'
91
91
  gem 'savon', '2.17.5'
92
- gem 'selenium-devtools', '0.153.0'
93
- gem 'selenium-webdriver', '4.49.0'
92
+ gem 'selenium-devtools', '0.154.0'
93
+ gem 'selenium-webdriver', '4.50.0'
94
94
  gem 'slack-ruby-client', '3.2.0'
95
95
  gem 'socksify', '1.8.1'
96
96
  gem 'spreadsheet', '1.3.5'
@@ -115,8 +115,10 @@ changes only default values, not the configuration schema; no migration is neede
115
115
  | --- | --- |
116
116
  | Enter | Submit the mission |
117
117
  | Shift+Enter, or trailing `\\` + Enter | Insert a newline. Terminals that cannot distinguish Shift+Enter need `tmux set -s extended-keys on` or the backslash fallback. |
118
- | Up/Down | Move the completion highlight while the Command menu is visible (wrapping at either end). Otherwise recall requests from `~/.pwn/pwn_history`, including prior runs; Down past the newest restores the draft and cursor. Esc closes the menu to resume history recall. |
119
- | Ctrl+P / Ctrl+N | Alternative completion selection keys; recall history when no menu is visible. Tab still accepts the highlight. |
118
+ | Up/Down | In MISSION CONTROL, move the completion highlight while the Command menu is visible (wrapping at either end). Otherwise recall requests from `~/.pwn/pwn_history`, including prior runs; Down past the newest restores the draft and cursor. In SESSION, move one transcript row. Esc closes the menu to resume history recall. |
119
+ | Home/End | In MISSION CONTROL, move the composer cursor to the start or end of the line. In SESSION, jump to the top of the transcript or back to the live bottom. |
120
+ | Ctrl+X | Toggle the active pane between SESSION and MISSION CONTROL. The active title is reversed and marked `active`. Typing or Enter returns to MISSION CONTROL; Enter does not submit while SESSION is active. |
121
+ | Ctrl+P / Ctrl+N | Alternative completion selection keys. In MISSION CONTROL they recall history when no menu is visible. In SESSION they scroll one row. Tab still accepts the highlight. |
120
122
  | Tab | Accept the highlighted as-you-type parameter |
121
123
  | Ctrl+O or `/status` | Inspect full status/settings, including overflowing header text; Esc or Ctrl+O closes |
122
124
  | Ctrl+L | Clear only the Session pane, including while a request runs; preserve stored conversation, token totals, and draft |
@@ -279,6 +279,7 @@ module PWN
279
279
  @editor = Editor.new
280
280
  @timeline = []
281
281
  @scroll = nil
282
+ @focus = :mission
282
283
  @event_count = @completed_tools = @unseen = 0
283
284
  @verbose = true
284
285
  @usage = AIConsoleUsage::Tracker.new
@@ -652,6 +653,28 @@ module PWN
652
653
  @menu = nil if @menu.empty?
653
654
  end
654
655
 
656
+ # Live view uses a nil scroll. Up from that view pins one row above the bottom.
657
+ def scroll_session(key)
658
+ bottom = [@total_rows.to_i - @page_size.to_i, 0].max
659
+ case key
660
+ when :up, "\u0010"
661
+ @scroll = [(@scroll || bottom) - 1, 0].max
662
+ when :down, "\u000e"
663
+ return if @scroll.nil?
664
+
665
+ @scroll += 1
666
+ if @scroll >= bottom
667
+ @scroll = nil
668
+ @unseen = 0
669
+ end
670
+ when :home
671
+ @scroll = bottom.zero? ? nil : 0
672
+ when :end
673
+ @scroll = nil
674
+ @unseen = 0
675
+ end
676
+ end
677
+
655
678
  def handle(key)
656
679
  return if key.nil?
657
680
  return handle_system_role(key) if @role_editor && (!@too_small || ["\e", "\u0003", "\u0004", :eof].include?(key))
@@ -681,10 +704,17 @@ module PWN
681
704
  if key == "\u0012"
682
705
  @menu = nil
683
706
  @paste = false
707
+ @focus = :mission
684
708
  @editor.search_history
685
709
  return
686
710
  end
687
711
 
712
+ if key == "\u0018"
713
+ @menu = nil
714
+ @focus = @focus == :session ? :mission : :session
715
+ return
716
+ end
717
+
688
718
  if key == "\t" && @menu
689
719
  accept_completion
690
720
  return
@@ -702,7 +732,9 @@ module PWN
702
732
  when :paste_end then @paste = false
703
733
  when :newline then @editor.insert("\n")
704
734
  when "\r", "\n"
705
- if @paste
735
+ if @focus == :session && !@paste
736
+ @focus = :mission
737
+ elsif @paste
706
738
  @editor.insert("\n")
707
739
  elsif @editor.text.end_with?('\\')
708
740
  @editor.edit("\b")
@@ -718,6 +750,8 @@ module PWN
718
750
  direction = [:up, "\u0010"].include?(key) ? :up : :down
719
751
  if @menu
720
752
  @menu_index = (@menu_index + (direction == :up ? -1 : 1)) % @menu.length
753
+ elsif @focus == :session
754
+ scroll_session(key)
721
755
  else
722
756
  @editor.recall(direction)
723
757
  end
@@ -727,11 +761,21 @@ module PWN
727
761
  @scroll = nil if @scroll >= [@total_rows.to_i - @page_size.to_i, 0].max
728
762
  @unseen = 0 unless @scroll
729
763
  when "\t" then accept_completion
730
- when :left, :right, :home, :end, :delete, "\b", "\u007f", "\u0001", "\u0005", "\u0015"
731
- @editor.edit(key)
732
- refresh_completion
764
+ when :home, :end
765
+ if @focus == :session
766
+ scroll_session(key)
767
+ else
768
+ @editor.edit(key)
769
+ refresh_completion
770
+ end
771
+ when :left, :right, :delete, "\b", "\u007f", "\u0001", "\u0005", "\u0015"
772
+ unless @focus == :session
773
+ @editor.edit(key)
774
+ refresh_completion
775
+ end
733
776
  else
734
777
  if key.is_a?(String) && key.ord >= 32
778
+ @focus = :mission
735
779
  @editor.insert(key)
736
780
  refresh_completion
737
781
  end
@@ -1045,6 +1089,13 @@ module PWN
1045
1089
  put(top, left + 2, fit(" #{title} ", box_width - 4), tone(:title)) unless title.empty?
1046
1090
  end
1047
1091
 
1092
+ def mark_active(top, left, box_width, title)
1093
+ return unless @screen.respond_to?(:attron)
1094
+
1095
+ text = fit(" #{title} ", box_width - 4)
1096
+ @screen.attron(Curses::A_REVERSE) { put(top, left + 2, text, tone(:title)) }
1097
+ end
1098
+
1048
1099
  def seed_request_history
1049
1100
  # REPL configures ~/.pwn/pwn_history and Pry loads it at startup.
1050
1101
  # Reuse that owner rather than loading twice or opening another writer.
@@ -1262,21 +1313,36 @@ module PWN
1262
1313
  @total_rows = rows.length
1263
1314
  @scroll = @scroll.clamp(0, [rows.length - @page_size, 0].max) if @scroll
1264
1315
  session_id = @pry.config.pwn_ai_session_id.to_s
1265
- box(header_height, 0, timeline_height, pane_width, "SESSION #{session_id}")
1316
+ session_title = "SESSION #{session_id}"
1317
+ session_title += ' · active' if @focus == :session
1318
+ box(header_height, 0, timeline_height, pane_width, session_title)
1319
+ mark_active(header_height, 0, pane_width, session_title) if @focus == :session
1266
1320
  start = @scroll || [rows.length - @page_size, 0].max
1267
1321
  rows.slice(start, @page_size).to_a.each_with_index { |(color, row), index| put(header_height + 1 + index, 2, row, color) }
1268
1322
  draw_sidebar(header_height, pane_width + 1, timeline_height) if sidebar.positive?
1269
1323
  compose_y = @height - compose_height - 1
1270
- box(compose_y, 0, compose_height, @width - 1, 'MISSION CONTROL')
1324
+ mission_title = @focus == :session ? 'MISSION CONTROL' : 'MISSION CONTROL · active'
1325
+ box(compose_y, 0, compose_height, @width - 1, mission_title)
1326
+ mark_active(compose_y, 0, @width - 1, mission_title) if @focus != :session
1271
1327
  draw_composer(compose_y)
1272
- hint = @menu ? '↑↓/^P/^N select · Tab accept · Esc close' : '↑↓ history · PgUp/PgDn scroll'
1328
+ hint = if @menu
1329
+ '↑↓/^P/^N select · Tab accept · Esc close'
1330
+ elsif @focus == :session
1331
+ '↑↓/Home/End scroll · ^X mission'
1332
+ else
1333
+ '↑↓ history · ^X session'
1334
+ end
1273
1335
  put(@height - 1, 1, "^C cancel ^D back · #{hint} · ^G swarm ^L clear ^R search ^O status · Enter send", tone(:footer))
1274
1336
  draw_menu if @menu
1275
1337
  draw_model_prompt if @model_prompt
1276
1338
  draw_details(engine, model) if @details
1277
1339
  draw_swarm if @workspace
1278
1340
  draw_system_role if @role_editor
1279
- @screen.setpos(*@cursor_position) if @role_editor || !(@details || @workspace)
1341
+ if @focus == :session && !(@details || @workspace || @role_editor)
1342
+ @screen.setpos(header_height + 1, 2)
1343
+ elsif @role_editor || !(@details || @workspace)
1344
+ @screen.setpos(*@cursor_position)
1345
+ end
1280
1346
  @screen.refresh
1281
1347
  end
1282
1348
 
@@ -1291,7 +1357,7 @@ module PWN
1291
1357
  first = [cursor_row - 2, 0].max
1292
1358
  put(top + 1, 2, '›', tone(:prompt)) if first.zero?
1293
1359
  rows.slice(first, 3).to_a.each_with_index { |row, index| put(top + 1 + index, 4, row, tone(:composer)) }
1294
- hint = @menu ? 'Tab accept · ↑↓/^P/^N select · Esc close' : '^G swarm · Shift+Enter newline · / commands · ^O status'
1360
+ hint = @menu ? 'Tab accept · ↑↓/^P/^N select · Esc close' : '^X pane · ^G swarm · Shift+Enter newline · / commands · ^O status'
1295
1361
  put(top + 4, 4, fit(hint, @width - 7), tone(:footer))
1296
1362
  @cursor_position = [top + 1 + cursor_row - first, [4 + width(prefix.last), @width - 3].min]
1297
1363
  end
@@ -1557,7 +1623,8 @@ module PWN
1557
1623
  public_class_method def self.help
1558
1624
  puts "USAGE:
1559
1625
  # Ctrl+L clears only the session pane, including while busy.
1560
- # Up/Down select in menus, otherwise recall ~/.pwn/pwn_history; Ctrl+R searches history.
1626
+ # Ctrl+X switches the active pane between SESSION and MISSION CONTROL.
1627
+ # Up/Down select in menus, recall ~/.pwn/pwn_history in the mission pane, and scroll the session pane when it is active.
1561
1628
  # Search: Enter accepts without sending; Esc restores the draft.
1562
1629
  # Launch the single-owner curses console; non-terminals return unavailable.
1563
1630
  #{self}.run(
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.754'
4
+ VERSION = '0.5.755'
5
5
  end
@@ -947,6 +947,62 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
947
947
  expect(console.instance_variable_get(:@scroll)).to be_nil
948
948
  end
949
949
 
950
+ it 'toggles the active pane so session arrows, Home and End scroll without changing the draft' do
951
+ console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
952
+ editor = console.instance_variable_get(:@editor)
953
+ editor.place('kept draft', 4)
954
+ console.instance_variable_set(:@total_rows, 100)
955
+ console.instance_variable_set(:@page_size, 20)
956
+ console.instance_variable_set(:@unseen, 3)
957
+ console.handle(:up)
958
+ expect(editor.text).to eq('kept draft')
959
+ expect(console.instance_variable_get(:@scroll)).to be_nil
960
+ console.handle("\u0018")
961
+ expect(console.instance_variable_get(:@focus)).to eq(:session)
962
+ console.handle(:up)
963
+ expect(console.instance_variable_get(:@scroll)).to eq(79)
964
+ console.handle("\u0010")
965
+ expect(console.instance_variable_get(:@scroll)).to eq(78)
966
+ console.handle(:home)
967
+ expect(console.instance_variable_get(:@scroll)).to eq(0)
968
+ console.handle(:end)
969
+ expect(console.instance_variable_get(:@scroll)).to be_nil
970
+ expect(console.instance_variable_get(:@unseen)).to eq(0)
971
+ console.handle(:up)
972
+ console.handle(:down)
973
+ expect(console.instance_variable_get(:@scroll)).to be_nil
974
+ expect([editor.text, editor.cursor]).to eq(['kept draft', 4])
975
+ console.handle("\n")
976
+ expect(console.instance_variable_get(:@focus)).to eq(:mission)
977
+ expect(editor.text).to eq('kept draft')
978
+ console.handle("\u0018")
979
+ console.handle('x')
980
+ expect(console.instance_variable_get(:@focus)).to eq(:mission)
981
+ expect(editor.text).to eq('keptx draft')
982
+ console.handle(:up)
983
+ expect(console.instance_variable_get(:@scroll)).to be_nil
984
+ expect(console.instance_variable_get(:@focus)).to eq(:mission)
985
+ end
986
+
987
+ it 'marks the active pane and parks the cursor in the session when that pane is selected' do
988
+ screen = double(erase: nil, refresh: nil, setpos: nil, addstr: nil, attron: nil)
989
+ allow(screen).to receive(:attron).and_yield
990
+ curses = double(lines: 36, cols: 120)
991
+ console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: curses, getch: nil)
992
+ console.instance_variable_set(:@screen, screen)
993
+ painted = []
994
+ allow(console).to receive(:put) { |*args| painted << args }
995
+ console.draw
996
+ expect(painted.map { |row| row[2] }.join).to include('MISSION CONTROL · active')
997
+ expect(painted.map { |row| row[2] }.join).not_to include('SESSION · active')
998
+ expect(screen).to have_received(:attron).with(Curses::A_REVERSE).at_least(:once)
999
+ console.handle("\u0018")
1000
+ painted.clear
1001
+ console.draw
1002
+ expect(painted.map { |row| row[2] }.join).to include('SESSION · active')
1003
+ expect(screen).to have_received(:setpos).with(a_kind_of(Integer), 2)
1004
+ end
1005
+
950
1006
  it 'wraps at words without discarding code whitespace or splitting graphemes' do
951
1007
  console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
952
1008
  expect(console.wrap('some evidence', 12)).to eq(['some ', 'evidence'])
@@ -3324,6 +3324,7 @@
3324
3324
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.handle_system_role Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.handle_system_role`: "}]}
3325
3325
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.header_layout Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.header_layout`: "}]}
3326
3326
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.header_lines Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.header_lines`: "}]}
3327
+ {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.mark_active Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.mark_active`: "}]}
3327
3328
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.move_role_cursor Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.move_role_cursor`: "}]}
3328
3329
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.open_swarm Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.open_swarm`: "}]}
3329
3330
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.open_system_role Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.open_system_role`: "}]}
@@ -3336,6 +3337,7 @@
3336
3337
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.restore_streams Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.restore_streams`: "}]}
3337
3338
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.run Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.run`: "}]}
3338
3339
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.run_swarm Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.run_swarm`: "}]}
3340
+ {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.scroll_session Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.scroll_session`: "}]}
3339
3341
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.scroll_status Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.scroll_status`: "}]}
3340
3342
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.seed_request_history Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.seed_request_history`: "}]}
3341
3343
  {"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.send_tool_input Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.send_tool_input`: "}]}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.754
4
+ version: 0.5.755
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -1051,28 +1051,28 @@ dependencies:
1051
1051
  requirements:
1052
1052
  - - '='
1053
1053
  - !ruby/object:Gem::Version
1054
- version: 0.153.0
1054
+ version: 0.154.0
1055
1055
  type: :runtime
1056
1056
  prerelease: false
1057
1057
  version_requirements: !ruby/object:Gem::Requirement
1058
1058
  requirements:
1059
1059
  - - '='
1060
1060
  - !ruby/object:Gem::Version
1061
- version: 0.153.0
1061
+ version: 0.154.0
1062
1062
  - !ruby/object:Gem::Dependency
1063
1063
  name: selenium-webdriver
1064
1064
  requirement: !ruby/object:Gem::Requirement
1065
1065
  requirements:
1066
1066
  - - '='
1067
1067
  - !ruby/object:Gem::Version
1068
- version: 4.49.0
1068
+ version: 4.50.0
1069
1069
  type: :runtime
1070
1070
  prerelease: false
1071
1071
  version_requirements: !ruby/object:Gem::Requirement
1072
1072
  requirements:
1073
1073
  - - '='
1074
1074
  - !ruby/object:Gem::Version
1075
- version: 4.49.0
1075
+ version: 4.50.0
1076
1076
  - !ruby/object:Gem::Dependency
1077
1077
  name: slack-ruby-client
1078
1078
  requirement: !ruby/object:Gem::Requirement