pwn 0.5.751 → 0.5.754

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: 370b89e2788788896b95041a37ef33082bf4e32f753deae04778b27262660d37
4
- data.tar.gz: 397bc93f10ee8a9b9f891c447b47b268b6d6526005762b97fd8cc94102c62053
3
+ metadata.gz: e01b8e88574a241d780acc3e2c4d70a7e5c07fb1e9e17fa709363a4bfcbc7898
4
+ data.tar.gz: f50a1f5f35d25f300c396678edb5daa8119f4fca9e1567d71652c7f45bcc1dcb
5
5
  SHA512:
6
- metadata.gz: 9c92bca139bd4a6b8c6e8ce32a0ce79505ecc495586f9aa36e89a829c1007508f380e1c84603dfd19ddbba7b7c2dffececdedc3232efd93dc87affc3270119db
7
- data.tar.gz: 1ab472b60a58d992e9513d9e8a5d668ce6b543bc83109c8c04a6ac563d71baae399ea47184e5cc145bd5aa01a750d00f2ec273456cc40077f205c996726e513f
6
+ metadata.gz: c34deaa7b6835b37cacb76a2863cb0759b4dc727301692014702468aa3d4a04ddc1c76fda3893005f58071b9060768ade05401dbbae1cac2fc3dbfc99bcfa9f0
7
+ data.tar.gz: de481a34a2b3776fa8003094618af168f6a33341fd236e14daa3247694e8e703e0eddb15ea74556e9bd4814a14a3187d48f51c18be90094f1bd4c83ddcd364b8
data/Gemfile CHANGED
@@ -20,7 +20,7 @@ gem 'base32', '0.3.4'
20
20
  gem 'bitcoin-ruby', '0.0.20'
21
21
  gem 'brakeman', '8.0.6'
22
22
  gem 'bson', '5.2.0'
23
- gem 'bundler', '>=4.0.21'
23
+ gem 'bundler', '>=4.0.22'
24
24
  gem 'bundler-audit', '>=0.9.3'
25
25
  gem 'bunny', '3.4.0'
26
26
  gem 'colorize', '1.1.0'
@@ -57,7 +57,7 @@ gem 'msfrpc-client', '1.1.2'
57
57
  gem 'netaddr', '2.0.6'
58
58
  gem 'net-ldap', '0.20.0'
59
59
  gem 'net-openvpn', '0.8.7'
60
- gem 'net-smtp', '0.5.1'
60
+ gem 'net-smtp', '0.5.2'
61
61
  gem 'nexpose', '7.3.0'
62
62
  gem 'nokogiri', '1.19.4'
63
63
  gem 'nokogiri-diff', '0.3.0'
@@ -31,7 +31,9 @@ its seeded autonomous flight is decorative, not player-controlled.
31
31
  Its framed width in terminal cells equals the complete header's height in rows;
32
32
  the interior canvas is `(header height - 2)` cells on each side. This is a
33
33
  cell-square, not a pixel-square—terminal glyph cells are usually taller than wide.
34
- Falling blocks fill the entire interior below the PWN wordmark, including the
34
+ The animation pane has no wordmark; its former label row belongs to gameplay.
35
+ The main `pwn-ai` header title and full-screen static banners are unchanged.
36
+ Falling blocks fill the entire interior, including the
35
37
  bottom two quadrants. Each logical block occupies one horizontal half-cell from
36
38
  spawn through landing and locking. This deliberately replaces independent
37
39
  quarter-cell blocks: a terminal cell has only a foreground and background, so
@@ -51,11 +53,13 @@ changing the active session selects again. Frames advance at the banner API's
51
53
  blocks/snake) using monotonic time in the existing render loop—no extra animation
52
54
  thread, input reader or provider call. This is decoration, not progress or
53
55
  telemetry. The frame keeps the existing border/title theme roles. Artwork comes
54
- from `PWN::Banner.mini_cells` with dedicated foreground/background curses pairs,
56
+ from `PWN::Banner.mini_cells(branding: false)` with dedicated foreground/background curses pairs,
55
57
  not ANSI output or theme overrides. `NO_COLOR` and terminals with too few color
56
58
  pairs retain occupied geometry in monochrome (two differently colored occupied
57
59
  halves become a full block, not a half-block with its background lost). The `mini_frame` ASCII API
58
- retains its 60-frame, 0.1-second loops for other callers. Colored replays contain
60
+ retains its 60-frame, 0.1-second loops for other callers. Both miniature APIs
61
+ default to their legacy branding; `branding: false` reclaims the label row and
62
+ leaves undersized fallback panes blank. Colored replays contain
59
63
  1800 frames (90 seconds for Pong/Asteroids, 180 for blocks/snake), use a local
60
64
  per-session seed, and share a bounded six-replay cache. Settings
61
65
  wrap in the right-hand region without losing their label colors. A bounded,
@@ -12,7 +12,7 @@ metadata:
12
12
 
13
13
  # PWN::Banner
14
14
 
15
- Static banners and pure, PWN-branded retro ASCII/color loops: falling_blocks, snake, pong, and asteroids. mini_frame returns fresh rows on a borderless canvas; use equal width and height (5..16) for complete square artwork. Dimensions clamp to 0..16 (colored blocks: 0..64), with tiny panes showing a wordmark. The caller samples mini_names once per session and advances the explicit frame index every mini_frame_seconds(name:) (0.05 for Pong/Asteroids, 0.1 otherwise). mini_cells replays 1800 seeded gameplay frames (MINI_FRAME_COUNT); mini_frame retains its legacy 60-frame ASCII loop. These are decorative game simulations, not interactive games or telemetry. mini_cells uses local Random.new with seed 73 by default; callers may keep a different integer seed per session. Six immutable replays are cached. Tetrominoes stack and clear; snake pursues food and grows; Pong rallies include imperfect paddle tracking, misses and score pips between serves. Blocks use horizontal half-cells with independent foreground/background colors, filling the pane below PWN (up to 64 cells). Other games retain quadrant pixels; snake stays centered with food/head color priority. White line-clear flashes and game-over/reset wipes are intentional. Pong uses half-cell motion and one-cell-high paddles without speeding up play. Asteroids uses five-pixel cyan arrowheads on a half-cell grid, sparse small white rocks, yellow shots and red thrust; wrapped collisions split real rocks. Static Banner modules autoload only when needed. For more information, see: http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
15
+ Static banners and pure, PWN-branded retro ASCII/color loops: falling_blocks, snake, pong, and asteroids. mini_frame returns fresh rows on a borderless canvas; use equal width and height (5..16) for complete square artwork. Dimensions clamp to 0..16 (colored blocks: 0..64). Set branding: false for an unbranded pane with the wordmark row reclaimed for play; tiny panes then stay blank. The caller samples mini_names once per session and advances the explicit frame index every mini_frame_seconds(name:) (0.05 for Pong/Asteroids, 0.1 otherwise). mini_cells replays 1800 seeded gameplay frames (MINI_FRAME_COUNT); mini_frame retains its legacy 60-frame ASCII loop. These are decorative game simulations, not interactive games or telemetry. mini_cells uses local Random.new with seed 73 by default; callers may keep a different integer seed per session. Six immutable replays are cached. Tetrominoes stack and clear; snake pursues food and grows; Pong rallies include imperfect paddle tracking, misses and score pips between serves. Blocks use horizontal half-cells with independent foreground/background colors, filling the pane below PWN (up to 64 cells). Other games retain quadrant pixels; snake stays centered with food/head color priority. White line-clear flashes and game-over/reset wipes are intentional. Pong uses half-cell motion and one-cell-high paddles without speeding up play. Asteroids uses five-pixel cyan arrowheads on a half-cell grid, sparse small white rocks, yellow shots and red thrust; wrapped collisions split real rocks. Static Banner modules autoload only when needed. For more information, see: http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
16
16
 
17
17
  ## When to use
18
18
 
@@ -575,7 +575,7 @@ module PWN
575
575
  {
576
576
  name: fn[:name] || fn['name'],
577
577
  description: fn[:description] || fn['description'],
578
- input_schema: fn[:parameters] || fn['parameters'] || { type: 'object', properties: {} }
578
+ input_schema: anthropic_input_schema(schema: fn[:parameters] || fn['parameters'])
579
579
  }
580
580
  end
581
581
  http_body[:tool_choice] = anth_tool_choice(choice: opts[:tool_choice]) if opts[:tool_choice]
@@ -598,6 +598,78 @@ module PWN
598
598
  raise e
599
599
  end
600
600
 
601
+ # Anthropic rejects oneOf, allOf, and anyOf at the root of input_schema.
602
+ # Return a deep copy with those root keys removed and branch properties merged.
603
+ # Nested combinators and the caller's schema stay unchanged.
604
+ private_class_method def self.anthropic_input_schema(opts = {})
605
+ schema = opts[:schema]
606
+ return { type: 'object', properties: {} } unless schema.is_a?(Hash)
607
+
608
+ copy = json_copy(value: schema)
609
+ notes = []
610
+ %w[anyOf oneOf allOf].each do |name|
611
+ key = anthropic_schema_key(schema: copy, name: name)
612
+ next unless key
613
+
614
+ Array(copy.delete(key)).each do |branch|
615
+ next unless branch.is_a?(Hash)
616
+
617
+ anthropic_schema_notes(branch: branch, notes: notes)
618
+ props = branch[:properties] || branch['properties']
619
+ next unless props.is_a?(Hash)
620
+
621
+ dest = copy.key?('properties') ? 'properties' : :properties
622
+ copy[dest] = {} unless copy[dest].is_a?(Hash)
623
+ props.each { |prop, spec| copy[dest][prop] = spec unless copy[dest].key?(prop) }
624
+ end
625
+ end
626
+ copy[:type] = 'object' unless copy.key?(:type) || copy.key?('type')
627
+ copy[:properties] = {} unless copy.key?(:properties) || copy.key?('properties')
628
+ unless notes.empty?
629
+ note = "One of these field sets is required: #{notes.uniq.join('; ')}."
630
+ copy[:description] = [copy[:description] || copy['description'], note].compact.reject { |part| part.to_s.empty? }.join(' ')
631
+ copy.delete('description')
632
+ end
633
+ copy
634
+ end
635
+
636
+ private_class_method def self.anthropic_schema_notes(opts = {})
637
+ branch = opts[:branch]
638
+ notes = opts[:notes]
639
+ return notes unless branch.is_a?(Hash) && notes.is_a?(Array)
640
+
641
+ required = branch[:required] || branch['required']
642
+ notes << required.join('+') if required.is_a?(Array) && required.any?
643
+ %w[anyOf oneOf allOf].each do |name|
644
+ key = anthropic_schema_key(schema: branch, name: name)
645
+ next unless key
646
+
647
+ Array(branch[key]).each { |child| anthropic_schema_notes(branch: child, notes: notes) }
648
+ end
649
+ notes
650
+ end
651
+
652
+ private_class_method def self.anthropic_schema_key(opts = {})
653
+ schema = opts[:schema]
654
+ name = opts[:name]
655
+ return name if schema.is_a?(Hash) && schema.key?(name)
656
+ return name.to_sym if schema.is_a?(Hash) && schema.key?(name.to_sym)
657
+
658
+ nil
659
+ end
660
+
661
+ private_class_method def self.json_copy(opts = {})
662
+ value = opts[:value]
663
+ case value
664
+ when Hash
665
+ value.each_with_object({}) { |(key, item), out| out[key] = json_copy(value: item) }
666
+ when Array
667
+ value.map { |item| json_copy(value: item) }
668
+ else
669
+ value.is_a?(String) ? value.dup : value
670
+ end
671
+ end
672
+
601
673
  # OpenAI messages[] -> [system_string, anthropic messages[]]
602
674
  private_class_method def self.oa_messages_to_anthropic(opts = {})
603
675
  messages = opts[:messages] ||= []
data/lib/pwn/banner.rb CHANGED
@@ -4,7 +4,8 @@ module PWN
4
4
  # Static banners and pure, PWN-branded retro ASCII/color loops: falling_blocks,
5
5
  # snake, pong, and asteroids. mini_frame returns fresh rows on a borderless canvas;
6
6
  # use equal width and height (5..16) for complete square artwork. Dimensions
7
- # clamp to 0..16 (colored blocks: 0..64), with tiny panes showing a wordmark. The caller
7
+ # clamp to 0..16 (colored blocks: 0..64). Set branding: false for an unbranded
8
+ # pane with the wordmark row reclaimed for play; tiny panes then stay blank. The caller
8
9
  # samples mini_names once per session and advances the explicit frame index
9
10
  # every mini_frame_seconds(name:) (0.05 for Pong/Asteroids, 0.1 otherwise).
10
11
  # mini_cells replays 1800 seeded gameplay
@@ -98,7 +99,8 @@ module PWN
98
99
  # name: 'optional - falling_blocks, snake, pong, or asteroids; defaults to falling_blocks',
99
100
  # frame: 'optional - integer frame index, defaults to zero and wraps at 60',
100
101
  # width: 'optional - output columns clamped to zero through 16, defaults to 16',
101
- # height: 'optional - output rows clamped to zero through 16, defaults to 16'
102
+ # height: 'optional - output rows clamped to zero through 16, defaults to 16',
103
+ # branding: 'optional - include the legacy wordmark, defaults to true; false reclaims its row'
102
104
  # )
103
105
 
104
106
  public_class_method def self.mini_frame(opts = {})
@@ -110,12 +112,16 @@ module PWN
110
112
  height = (opts[:height] || MINI_HEIGHT).to_i.clamp(0, MINI_HEIGHT)
111
113
  return [] if width.zero? || height.zero?
112
114
 
115
+ branding = opts[:branding] != false
116
+ return Array.new(height) { ' ' * width } if !branding && (width < 5 || height < 5)
117
+
118
+ height += 1 unless branding
113
119
  rows = Array.new(height) { ' ' * width }
114
120
  rows[0] = 'PWN'[0, width].center(width)
115
121
  return rows if width < 5 || height < 5
116
122
 
117
123
  send("mini_#{name}", rows: rows, phase: phase, width: width, height: height)
118
- rows
124
+ branding ? rows : rows.drop(1)
119
125
  end
120
126
 
121
127
  # Supported Method Parameters::
@@ -150,7 +156,8 @@ module PWN
150
156
  # frame: 'optional - integer index, defaults to zero and wraps at 1800',
151
157
  # width: 'optional - columns clamped to 0..64 for blocks, 0..16 otherwise; defaults to 16',
152
158
  # height: 'optional - rows clamped to 0..64 for blocks, 0..16 otherwise; defaults to 16',
153
- # seed: 'optional - integer local PRNG seed, defaults to 73; retain per session'
159
+ # seed: 'optional - integer local PRNG seed, defaults to 73; retain per session',
160
+ # branding: 'optional - include the legacy wordmark, defaults to true; false reclaims its row'
154
161
  # )
155
162
 
156
163
  public_class_method def self.mini_cells(opts = {})
@@ -163,10 +170,14 @@ module PWN
163
170
  height = (opts[:height] || MINI_HEIGHT).to_i.clamp(0, limit)
164
171
  return [] if width.zero? || height.zero?
165
172
 
173
+ branding = opts[:branding] != false
174
+ # Simulate one extra row before removing the legacy label, so every output
175
+ # row belongs to gameplay without changing the public dimension limits.
166
176
  rows = if width < 5 || height < 5
167
- ['PWN'[0, width].center(width)] + Array.new(height - 1) { ' ' * width }
177
+ [branding ? 'PWN'[0, width].center(width) : ' ' * width] + Array.new(height - 1) { ' ' * width }
168
178
  else
169
- mini_replay(name: name, width: width, height: height, seed: (opts[:seed] || MINI_SEED).to_i)[phase][:rows]
179
+ replay = mini_replay(name: name, width: width, height: height + (branding ? 0 : 1), seed: (opts[:seed] || MINI_SEED).to_i)
180
+ branding ? replay[phase][:rows] : replay[phase][:rows].drop(1)
170
181
  end
171
182
  rows.map do |row|
172
183
  row.chars.map do |pixel|
@@ -823,7 +834,8 @@ module PWN
823
834
  name: 'optional - falling_blocks, snake, pong, or asteroids; defaults to falling_blocks',
824
835
  frame: 'optional - integer frame index, defaults to zero and wraps at 60',
825
836
  width: 'optional - columns clamped to zero through 16; defaults to 16',
826
- height: 'optional - rows clamped to zero through 16; defaults to 16'
837
+ height: 'optional - rows clamped to zero through 16; defaults to 16',
838
+ branding: 'optional - include the legacy wordmark, defaults to true; false reclaims its row'
827
839
  )
828
840
 
829
841
  # Return fresh Unicode block cells: { glyph:, foreground:, background: }.
@@ -838,7 +850,8 @@ module PWN
838
850
  frame: 'optional - integer frame index, defaults to zero and wraps at 1800',
839
851
  width: 'optional - columns clamped to 0..64 for blocks, 0..16 otherwise; defaults to 16',
840
852
  height: 'optional - rows clamped to 0..64 for blocks, 0..16 otherwise; defaults to 16',
841
- seed: 'optional - integer local PRNG seed, defaults to 73; retain per session'
853
+ seed: 'optional - integer local PRNG seed, defaults to 73; retain per session',
854
+ branding: 'optional - include the legacy wordmark, defaults to true; false reclaims its row'
842
855
  )
843
856
 
844
857
  # Run welcome and return its result
@@ -1042,7 +1042,7 @@ module PWN
1042
1042
  put(top + row, left + box_width - 1, '│', color)
1043
1043
  end
1044
1044
  put(top + height - 1, left, "╰#{'─' * (box_width - 2)}╯", color)
1045
- put(top, left + 2, fit(" #{title} ", box_width - 4), tone(:title))
1045
+ put(top, left + 2, fit(" #{title} ", box_width - 4), tone(:title)) unless title.empty?
1046
1046
  end
1047
1047
 
1048
1048
  def seed_request_history
@@ -1070,10 +1070,10 @@ module PWN
1070
1070
  role = cfg[:system_role_content].to_s.gsub(/\s+/, ' ').strip
1071
1071
  parts = []
1072
1072
  parts << "SYSTEM ROLE CONTENT: #{role}" unless role.empty?
1073
- parts << "\nTEMP: #{cfg[:temp]}" unless cfg[:temp].nil?
1074
- parts << " MAX TOKENS: #{cfg[:max_tokens]}" unless cfg[:max_tokens].nil?
1073
+ parts << "\nMAX TOKENS: #{cfg[:max_tokens]}" unless cfg[:max_tokens].nil?
1075
1074
  parts << " MAX PROMPT LENGTH: #{cfg[:max_prompt_length]}" unless cfg[:max_prompt_length].nil?
1076
- parts << "\nREASONING EFFORT: #{cfg[:reasoning_effort]}" unless cfg[:reasoning_effort].to_s.empty?
1075
+ parts << " REASONING EFFORT: #{cfg[:reasoning_effort]}" unless cfg[:reasoning_effort].to_s.empty?
1076
+ parts << " TEMP: #{cfg[:temp]}" unless cfg[:temp].nil?
1077
1077
  parts.join(' · ')
1078
1078
  rescue StandardError
1079
1079
  ''
@@ -1138,9 +1138,9 @@ module PWN
1138
1138
  end
1139
1139
  cadence = cells ? @banner_frame_seconds : PWN::Banner::MINI_FRAME_SECONDS
1140
1140
  frame = ((now - @banner_started) / cadence).floor % PWN::Banner::MINI_FRAME_COUNT
1141
- return PWN::Banner.mini_cells(name: @banner_name, frame: frame, width: size, height: size, seed: @banner_seed) if cells
1141
+ return PWN::Banner.mini_cells(name: @banner_name, frame: frame, width: size, height: size, seed: @banner_seed, branding: false) if cells
1142
1142
 
1143
- art = PWN::Banner.mini_frame(name: @banner_name, frame: frame, width: size, height: size)
1143
+ art = PWN::Banner.mini_frame(name: @banner_name, frame: frame, width: size, height: size, branding: false)
1144
1144
  canvas = Array.new(size) { ' ' * size }
1145
1145
  top = (size - art.length) / 2
1146
1146
  art.each_with_index { |row, index| canvas[top + index] = row.center(size) }
@@ -1208,7 +1208,7 @@ module PWN
1208
1208
  put(1, @header_text_column, fit("#{state} · #{elapsed} · #{scroll_status}", @header_text_width), tone(:status))
1209
1209
  visible.each_with_index { |spans, index| put_spans(2 + index, @header_text_column, spans) }
1210
1210
  if @header_pane_height
1211
- box(0, 0, height, height, 'PWN')
1211
+ box(0, 0, height, height, '')
1212
1212
  draw_banner(height - 2)
1213
1213
  end
1214
1214
  height
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.751'
4
+ VERSION = '0.5.754'
5
5
  end
@@ -58,4 +58,52 @@ describe PWN::AI::Anthropic do
58
58
  expect(src).to include('PromptCache.enabled?')
59
59
  end
60
60
  end
61
+
62
+ it 'drops root combinators from Anthropic input_schema without mutating registry schemas' do
63
+ allow(PWN::Env).to receive(:[]).with(:ai).and_return(
64
+ anthropic: { key: 'test-key', model: 'claude-test', temp: 1, max_tokens: 100 }
65
+ )
66
+ allow(PWN::AI::Agent::PromptCache).to receive(:enabled?).and_return(false) if defined?(PWN::AI::Agent::PromptCache)
67
+ tools = PWN::AI::Agent::Registry.definitions(core_only: true)
68
+ artifact = tools.find { |tool| tool.dig(:function, :name) == 'artifact_read' }
69
+ original = artifact[:function][:parameters]
70
+ nested = {
71
+ type: 'object',
72
+ properties: {
73
+ item: { anyOf: [{ type: 'string' }, { type: 'object', properties: { handle: { type: 'string' } } }] }
74
+ }
75
+ }
76
+ union = {
77
+ 'anyOf' => [
78
+ { 'type' => 'object', 'properties' => { 'handle' => { 'type' => 'string' } }, 'required' => ['handle'] },
79
+ { 'type' => 'object', 'properties' => { 'path' => { 'type' => 'string' } }, 'required' => ['path'] }
80
+ ]
81
+ }
82
+ sent = tools + [
83
+ { type: 'function', function: { name: 'nested_union', description: 'keep nested', parameters: nested } },
84
+ { type: 'function', function: { name: 'root_union', description: 'flatten', parameters: union } }
85
+ ]
86
+ captured = nil
87
+ allow(described_class).to receive(:anthropic_rest_call) do |opts|
88
+ captured = opts[:http_body]
89
+ '{"id":"msg_schema","type":"message","role":"assistant","content":[{"type":"text","text":"ok"}],"stop_reason":"end_turn"}'
90
+ end
91
+
92
+ described_class.chat_with_tools(messages: [{ role: 'user', content: 'hello' }], tools: sent, model: 'claude-test')
93
+
94
+ expect(original).to have_key(:anyOf)
95
+ wire = captured[:tools]
96
+ expect(wire.map { |tool| tool[:name] }[10]).to eq('artifact_read')
97
+ wire.each do |tool|
98
+ schema = tool[:input_schema]
99
+ expect(schema.keys.map(&:to_s)).not_to include('anyOf', 'oneOf', 'allOf')
100
+ end
101
+ read_schema = wire[10][:input_schema]
102
+ expect(read_schema[:properties].keys.map(&:to_s)).to include('handle', 'path', 'ref')
103
+ expect(read_schema[:description]).to include('handle', 'path', 'ref')
104
+ expect(wire.find { |tool| tool[:name] == 'nested_union' }[:input_schema][:properties][:item]).to have_key(:anyOf)
105
+ flat = wire.find { |tool| tool[:name] == 'root_union' }[:input_schema]
106
+ expect(flat[:properties].keys.map(&:to_s)).to include('handle', 'path')
107
+ expect(flat[:description]).to include('handle', 'path')
108
+ end
61
109
  end
@@ -3,6 +3,29 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe PWN::Banner do
6
+ it 'reclaims the wordmark row for unbranded games and leaves tiny panes blank' do
7
+ described_class.mini_names.product([1, 3, 5, 8, 16]).each do |name, size|
8
+ [0, 6, 100, 1799].each do |frame|
9
+ args = { name: name, width: size, height: size, frame: frame, branding: false }
10
+ cells = described_class.mini_cells(**args)
11
+ ascii = described_class.mini_frame(**args)
12
+ expect(cells.length).to eq(size)
13
+ expect(cells.map(&:length)).to all(eq(size))
14
+ expect(cells.flatten.map { |cell| cell[:glyph] }.join).not_to match(/[PWN]/)
15
+ expect(ascii.length).to eq(size)
16
+ expect(ascii.map(&:length)).to all(eq(size))
17
+ expect(ascii.join).not_to match(/[PWN]/)
18
+ next if size < 5
19
+
20
+ replay = described_class.send(:mini_replay, name: name, width: size, height: size + 1, seed: 73)
21
+ expected = replay[frame][:rows].drop(1).map do |row|
22
+ row.chars.map { |pixel| described_class::MINI_PIXELS.fetch(pixel).first }.join
23
+ end
24
+ expect(cells.map { |row| row.map { |cell| cell[:glyph] }.join }).to eq(expected)
25
+ end
26
+ end
27
+ end
28
+
6
29
  def block_colors(rows)
7
30
  rows.flat_map do |row|
8
31
  [0, 2].map do |bit|
@@ -31,9 +54,9 @@ describe PWN::Banner do
31
54
 
32
55
  it 'keeps every active tetromino color beside the stack throughout its fall and lock' do
33
56
  rotations = Hash.new { |hash, key| hash[key] = [] }
34
- [5, 8, 16, 20].product([1, 73, 74]).each do |size, seed|
35
- args = { name: :falling_blocks, width: size, height: size, seed: seed }
36
- frames = described_class.send(:mini_replay, **args)
57
+ [5, 8, 16, 20].product([1, 73, 74], [true, false]).each do |size, seed, branding|
58
+ args = { name: :falling_blocks, width: size, height: size, seed: seed, branding: branding }
59
+ frames = described_class.send(:mini_replay, **args, height: size + (branding ? 0 : 1))
37
60
  frames.each_with_index do |frame, index|
38
61
  next unless frame[:state][:active]
39
62
 
@@ -45,13 +68,17 @@ describe PWN::Banner do
45
68
  points.each { |x, y| expected[y][x] = color }
46
69
  expect(block_colors(frame[:rows].drop(1))).to eq(expected)
47
70
  points.each do |x, y|
48
- cell = cells[1 + (y / 2)][x]
71
+ expect(x).to be_between(0, size - 1)
72
+ expect(y).to be_between(0, ((size - (branding ? 1 : 0)) * 2) - 1)
73
+ cell = cells[(branding ? 1 : 0) + (y / 2)][x]
49
74
  mask = ' ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█'.index(cell[:glyph])
50
75
  actual = mask[(y % 2) * 2] == 1 ? cell[:foreground] : cell[:background]
51
76
  expect(actual).to eq(color)
52
77
  end
53
78
  next unless frames[index + 1][:event] == :lock
54
79
 
80
+ stack = frame[:state][:stack]
81
+ expect(points.any? { |x, y| y == stack.length - 1 || stack[y + 1][x] != ' ' }).to be(true)
55
82
  expect(frames[index + 1][:rows]).to eq(frame[:rows])
56
83
  expect(described_class.mini_cells(**args, frame: index + 1)).to eq(cells)
57
84
  rotations[type] << points.map { |x, y| [x - points.map(&:first).min, y - points.map(&:last).min] }.sort
@@ -425,6 +425,25 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
425
425
  PWN::Env[:ai] = previous
426
426
  end
427
427
 
428
+ it 'draws every game without wordmark glyphs in color, monochrome and tiny panes' do
429
+ PWN::Banner.mini_names.product([1, 3, 5, 8, 16, 20], [true, false]).each do |name, size, colors|
430
+ console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
431
+ allow(PWN::Banner).to receive(:mini_names).and_return([name])
432
+ now = 100.0
433
+ allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC) { now }
434
+ console.instance_variable_set(:@banner_colors, colors)
435
+ console.instance_variable_set(:@banner_two_colors, colors)
436
+ calls = []
437
+ allow(console).to receive(:put) { |*args| calls << args }
438
+ console.draw_banner(size)
439
+ now += 10.0
440
+ console.draw_banner(size)
441
+ expect(calls.map { |_, _, text| text }.join).not_to match(/[PWN]/)
442
+ expect(calls).to all(satisfy { |y, x, text| y.between?(1, size) && x >= 1 && x + text.length <= size + 1 })
443
+ expect(console.banner_frame(size).join).not_to match(/[PWN]/)
444
+ end
445
+ end
446
+
428
447
  it 'selects one mini-banner per session and advances only from monotonic elapsed time' do
429
448
  pry = Pry.new
430
449
  pry.config.pwn_ai_session_id = 'banner-one'
@@ -435,16 +454,16 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
435
454
  now = 100.0
436
455
  allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC) { now }
437
456
  first = console.banner_frame(8)
438
- expect(first).to eq(PWN::Banner.mini_frame(name: choices.first, frame: 0, width: 8, height: 8))
457
+ expect(first).to eq(PWN::Banner.mini_frame(name: choices.first, frame: 0, width: 8, height: 8, branding: false))
439
458
  now += PWN::Banner::MINI_FRAME_SECONDS / 2
440
459
  expect(console.banner_frame(8)).to eq(first)
441
460
  now += PWN::Banner::MINI_FRAME_SECONDS * 0.75
442
- expect(console.banner_frame(8)).to eq(PWN::Banner.mini_frame(name: choices.first, frame: 1, width: 8, height: 8))
461
+ expect(console.banner_frame(8)).to eq(PWN::Banner.mini_frame(name: choices.first, frame: 1, width: 8, height: 8, branding: false))
443
462
  now += PWN::Banner::MINI_FRAME_SECONDS * PWN::Banner::MINI_FRAME_COUNT
444
- expect(console.banner_frame(12)).to eq(PWN::Banner.mini_frame(name: choices.first, frame: 1, width: 12, height: 12))
463
+ expect(console.banner_frame(12)).to eq(PWN::Banner.mini_frame(name: choices.first, frame: 1, width: 12, height: 12, branding: false))
445
464
  expect(choices).to have_received(:sample).once
446
465
  pry.config.pwn_ai_session_id = 'banner-two'
447
- expect(console.banner_frame(8)).to eq(PWN::Banner.mini_frame(name: choices.last, frame: 0, width: 8, height: 8))
466
+ expect(console.banner_frame(8)).to eq(PWN::Banner.mini_frame(name: choices.last, frame: 0, width: 8, height: 8, branding: false))
448
467
  expect(choices).to have_received(:sample).twice
449
468
  end
450
469
 
@@ -454,7 +473,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
454
473
  allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0)
455
474
  size = 24
456
475
  canvas = console.banner_frame(size)
457
- art = PWN::Banner.mini_frame(name: :pong, frame: 0, width: size, height: size)
476
+ art = PWN::Banner.mini_frame(name: :pong, frame: 0, width: size, height: size, branding: false)
458
477
  expect(canvas.length).to eq(size)
459
478
  expect(canvas.map(&:length)).to all(eq(size))
460
479
  top = (size - art.length) / 2
@@ -470,14 +489,14 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
470
489
  allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC) { now }
471
490
  console.banner_frame(20, cells: true)
472
491
  seed = console.instance_variable_get(:@banner_seed)
473
- frames = PWN::Banner.send(:mini_replay, name: :falling_blocks, width: 20, height: 20, seed: seed)
492
+ frames = PWN::Banner.send(:mini_replay, name: :falling_blocks, width: 20, height: 21, seed: seed)
474
493
  tick = frames.index { |frame| frame[:event] == :lock }
475
494
  now += (tick + 0.1) * PWN::Banner::MINI_FRAME_SECONDS
476
495
  calls = []
477
496
  allow(console).to receive(:put) { |*args| calls << args }
478
497
  console.draw_banner(20)
479
498
  expect(calls.select { |y, _x, text| y == 20 && text.strip != '' }).not_to be_empty
480
- expect(calls).to include([1, 9, 'P', nil], [1, 10, 'W', nil], [1, 11, 'N', nil])
499
+ expect(calls.map { |_, _, text| text }.join).not_to match(/[PWN]/)
481
500
  expect(calls).to all(satisfy { |y, x, text| y.between?(1, 20) && x >= 1 && x + text.length <= 21 })
482
501
  end
483
502
 
@@ -498,12 +517,13 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
498
517
  end
499
518
  allow(PWN::Banner).to receive(:mini_cells).and_call_original
500
519
  console.draw
501
- expect(rectangles).to include([0, 0, 10, 10, 'PWN'])
520
+ expect(rectangles).to include([0, 0, 10, 10, ''])
502
521
  expect(rectangles).to include([0, 10, 10, 109, "pwn-ai v#{PWN::VERSION}"])
503
522
  expect(console.instance_variable_get(:@header_text_column)).to eq(12)
504
523
  expect(painted).to include([2, 12, 'PROVIDER (ENGINE):', console.tone(:category)])
505
524
  expect(painted.any? { |y, x, text, color| y == 0 && x == 0 && text.include?('╭') && color == console.tone(:border) }).to be(true)
506
- expect(painted.any? { |y, x, text, color| y == 0 && x == 2 && text.include?('PWN') && color == console.tone(:title) }).to be(true)
525
+ expect(painted.any? { |y, x, _text, _color| y == 0 && x == 2 }).to be(false)
526
+ expect(painted.select { |y, x, _text, _color| y < 10 && x < 10 }.map { |_, _, text| text }.join).not_to match(/[PWN]/)
507
527
  expect(painted.select { |y, x, text, _color| y.between?(1, 8) && x == 1 && text == ' ' * 8 }.length).to eq(8)
508
528
  expect(painted.select { |y, _x, _text, _color| y < 10 }).to all(satisfy { |y, x, text, _color| y >= 0 && x >= 0 && x + console.width(text) <= 120 })
509
529
  expect(PWN::Banner).to have_received(:mini_cells).with(hash_including(width: 8, height: 8)).once
@@ -511,14 +531,14 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
511
531
  allow(curses).to receive_messages(cols: columns, lines: rows)
512
532
  rectangles.clear
513
533
  console.draw
514
- expect(rectangles.map(&:last)).not_to include('PWN')
534
+ expect(rectangles.map(&:last)).not_to include('')
515
535
  expect(console.instance_variable_get(:@header_text_column)).to eq(2)
516
536
  end
517
537
  allow(curses).to receive_messages(cols: 120, lines: 26)
518
- PWN::Env[:ai][:ollama][:system_role_content] = 'operator ' * 110
538
+ PWN::Env[:ai][:ollama][:system_role_content] = 'operator ' * 200
519
539
  console.draw
520
540
  expect(console.instance_variable_get(:@header_text_column)).to eq(2)
521
- expect(console.header_lines('ollama', 'fixture', 115).length).to be <= 13
541
+ expect(console.header_lines('ollama', 'fixture', 115).length).to be > 16
522
542
  expect(PWN::Banner).to have_received(:mini_cells).once
523
543
  expect([console.instance_variable_get(:@editor).text, console.instance_variable_get(:@editor).cursor]).to eq(['retained draft', 4])
524
544
  end
@@ -533,7 +553,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
533
553
  allow(PWN::Banner).to receive(:mini_cells).and_call_original
534
554
  height = console.draw_header('ollama', 'fixture')
535
555
  expect(height).to be > 10
536
- expect(console).to have_received(:box).with(0, 0, height, height, 'PWN')
556
+ expect(console).to have_received(:box).with(0, 0, height, height, '')
537
557
  expect(PWN::Banner).to have_received(:mini_cells).with(hash_including(width: height - 2, height: height - 2))
538
558
  expect(console.instance_variable_get(:@header_spans).length + 3).to be <= height
539
559
  expect(console.header_lines('ollama', 'fixture', console.instance_variable_get(:@header_text_width)).join).to include('REASONING EFFORT: high')
@@ -605,10 +625,10 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
605
625
  console.banner_frame(8, cells: true)
606
626
  seed = console.instance_variable_get(:@banner_seed)
607
627
  expect(seed).to be_a(Integer)
608
- expect(console.banner_frame(8, cells: true)).to eq(PWN::Banner.mini_cells(name: :snake, frame: 0, width: 8, height: 8, seed: seed))
628
+ expect(console.banner_frame(8, cells: true)).to eq(PWN::Banner.mini_cells(name: :snake, frame: 0, width: 8, height: 8, seed: seed, branding: false))
609
629
  now += 0.21
610
- expect(console.banner_frame(8, cells: true)).to eq(PWN::Banner.mini_cells(name: :snake, frame: 2, width: 8, height: 8, seed: seed))
611
- expect(console.banner_frame(12, cells: true)).to eq(PWN::Banner.mini_cells(name: :snake, frame: 2, width: 12, height: 12, seed: seed))
630
+ expect(console.banner_frame(8, cells: true)).to eq(PWN::Banner.mini_cells(name: :snake, frame: 2, width: 8, height: 8, seed: seed, branding: false))
631
+ expect(console.banner_frame(12, cells: true)).to eq(PWN::Banner.mini_cells(name: :snake, frame: 2, width: 12, height: 12, seed: seed, branding: false))
612
632
  expect(console.instance_variable_get(:@banner_seed)).to eq(seed)
613
633
  pry.config.pwn_ai_session_id = 'another-colored-session'
614
634
  console.banner_frame(8, cells: true)
@@ -630,7 +650,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
630
650
  console.banner_frame(8, cells: true)
631
651
  seed = console.instance_variable_get(:@banner_seed)
632
652
  now += 1.21
633
- expect(PWN::Banner).to receive(:mini_cells).with(name: name, frame: 24, width: 8, height: 8, seed: seed).twice.and_call_original
653
+ expect(PWN::Banner).to receive(:mini_cells).with(name: name, frame: 24, width: 8, height: 8, seed: seed, branding: false).twice.and_call_original
634
654
  2.times { console.banner_frame(8, cells: true) }
635
655
  end
636
656
  end
@@ -699,13 +719,15 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
699
719
  expect(actual).to eq(lines)
700
720
  expect(header.select { |entry| entry[3] == console.tone(:category) }.map { |entry| entry[2] }.join).to include('SYSTEM ROLE CONTENT:', 'MAX PROMPT LENGTH:', 'REASONING EFFORT:')
701
721
  expect(header.select { |entry| entry[3] == console.tone(:header) }.map { |entry| entry[2] }.join).to include('operator', '1024', 'high')
702
- if columns == 48
703
- labels = header.select { |entry| entry[3] == console.tone(:category) }.map { |entry| entry[2] }
704
- expect(labels.join).to include('MAX PROMPT LENGTH:')
705
- expect(labels).not_to include(a_string_including('MAX PROMPT LENGTH:'))
706
- end
707
722
  expect(console.instance_variable_get(:@page_size)).to be >= 1
708
723
  end
724
+ console.header_lines('ollama', 'fixture', 35)
725
+ labels = []
726
+ console.instance_variable_get(:@header_spans).each do |row|
727
+ row.each { |role, text| labels << text if role == :category }
728
+ end
729
+ expect(labels.join).to include('MAX PROMPT LENGTH:')
730
+ expect(labels).not_to include(a_string_including('MAX PROMPT LENGTH:'))
709
731
  console.handle("\u000f")
710
732
  console.draw
711
733
  console.handle(:end)
@@ -1218,7 +1240,9 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
1218
1240
  if rendered_header.include?('worker-effort')
1219
1241
  expect(rendered_header).to include('worker settings role', '0.42', '4321', '8765')
1220
1242
  expect(painted).to include([a_kind_of(Integer), a_kind_of(Integer), 'MODEL:', 7])
1221
- expect(painted).to include([a_kind_of(Integer), a_kind_of(Integer), ' worker-effort', 6])
1243
+ value = painted.find { |_y, _x, text, color| color == 6 && text.to_s.include?('worker-effort') }
1244
+ expect(value).not_to be_nil
1245
+ expect(value[2]).not_to include('REASONING EFFORT:')
1222
1246
  "\u0004"
1223
1247
  else
1224
1248
  Thread.pass
@@ -1058,13 +1058,17 @@
1058
1058
  {"messages":[{"role":"user","content":"PWN::AI::Agent::VulnGen.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::VulnGen.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
1059
1059
  {"messages":[{"role":"user","content":"PWN::AI::Agent::VulnGen.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::VulnGen.help`: "}]}
1060
1060
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anth_tool_choice Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anth_tool_choice`: "}]}
1061
+ {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_input_schema Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_input_schema`: "}]}
1061
1062
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_resp_to_oa Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_resp_to_oa`: "}]}
1062
1063
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_rest_call Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_rest_call`: Supported Method Parameters\n\nanthropic_rest_call(\n\ntoken: 'required - anthropic api key',\nhttp_method: 'optional HTTP method (defaults to GET)',\nbase_uri: 'optional base anthropic api URI (defaults to https://api.anthropic.com/v1)',\nrest_call: 'required rest call to make per the schema',\nparams: 'optional params passed in the URI or HTTP Headers',\nhttp_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST',\ntimeout: 'optional timeout in seconds (defaults to 900)',\nspinner: 'optional - display spinner (defaults to false)'\n\n)\n"}]}
1064
+ {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_schema_key Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_schema_key`: "}]}
1065
+ {"messages":[{"role":"user","content":"PWN::AI::Anthropic.anthropic_schema_notes Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.anthropic_schema_notes`: "}]}
1063
1066
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.authors Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
1064
1067
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.chat Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.chat`: Supported Method Parameters\n\nresponse = PWN::AI::Anthropic.chat(\n\nrequest: 'required - message to Anthropic',\nmodel: 'optional - model to use for text generation (defaults to PWN::Env[:ai][:anthropic][:model])',\ntemp: 'optional - creative response float (defaults to PWN::Env[:ai][:anthropic][:temp])',\nsystem_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ai][:anthropic][:system_role_content])',\nresponse_history: 'optional - pass response back in to have a conversation',\nspeak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)',\ntimeout: 'optional timeout in seconds (defaults to 900)',\nspinner: 'optional - display spinner (defaults to false)'\n\n)\n"}]}
1065
1068
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.chat_with_tools Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.chat_with_tools`: Supported Method Parameters\n\nresponse = PWN::AI::Anthropic.chat_with_tools(\n\nmessages: 'required - OpenAI-format messages array (system/user/assistant/tool)',\ntools: 'optional - OpenAI tools array [{type:\"function\", function:{...}}]',\ntool_choice: 'optional - \"auto\" | \"none\" | \"required\" | {type:\"function\", function:{name:..}}',\nmodel: 'optional - overrides PWN::Env[:ai][:anthropic][:model]',\ntemp: 'optional - temperature (defaults to PWN::Env[:ai][:anthropic][:temp] || 1)',\nmax_tokens: 'optional - defaults to PWN::Env[:ai][:anthropic][:max_tokens] || 128_000',\ntimeout: 'optional - seconds (default 900)',\nspinner: 'optional - display spinner (default false)'\n\n)\n"}]}
1066
1069
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.get_models Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.get_models`: Supported Method Parameters\n\nmodels = PWN::AI::Anthropic.get_models\n"}]}
1067
1070
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.help Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.help`: "}]}
1071
+ {"messages":[{"role":"user","content":"PWN::AI::Anthropic.json_copy Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.json_copy`: "}]}
1068
1072
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.jwt_exp Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.jwt_exp`: "}]}
1069
1073
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.oa_messages_to_anthropic Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.oa_messages_to_anthropic`: "}]}
1070
1074
  {"messages":[{"role":"user","content":"PWN::AI::Anthropic.oauth_token_expiring? Usage"},{"role":"assistant","content":"`PWN::AI::Anthropic.oauth_token_expiring?`: "}]}
@@ -1708,12 +1712,13 @@
1708
1712
  {"messages":[{"role":"user","content":"PWN::Banner.help Usage"},{"role":"assistant","content":"`PWN::Banner.help`: "}]}
1709
1713
  {"messages":[{"role":"user","content":"PWN::Banner.mini_asteroids Usage"},{"role":"assistant","content":"`PWN::Banner.mini_asteroids`: "}]}
1710
1714
  {"messages":[{"role":"user","content":"PWN::Banner.mini_asteroids_snapshot Usage"},{"role":"assistant","content":"`PWN::Banner.mini_asteroids_snapshot`: "}]}
1711
- {"messages":[{"role":"user","content":"PWN::Banner.mini_cells Usage"},{"role":"assistant","content":"`PWN::Banner.mini_cells`: Supported Method Parameters\n\nReturn rows of single-column Unicode block cells, with named terminal colors. Each fresh cell has glyph, foreground and background keys; no ANSI or IO. Square means terminal-cell dimensions, not physical pixels. Caller owns borders. Use mini_frame_seconds for cadence; 1800 frames span 90 or 180 seconds. Colored blocks fill the canvas with quarter-cell pieces; other games cap at 16. PWN::Banner.mini_cells(\n\nname: 'optional - falling_blocks, snake, pong, or asteroids; defaults to falling_blocks',\nframe: 'optional - integer index, defaults to zero and wraps at 1800',\nwidth: 'optional - columns clamped to 0..64 for blocks, 0..16 otherwise; defaults to 16',\nheight: 'optional - rows clamped to 0..64 for blocks, 0..16 otherwise; defaults to 16',\nseed: 'optional - integer local PRNG seed, defaults to 73; retain per session'\n\n)\n"}]}
1715
+ {"messages":[{"role":"user","content":"PWN::Banner.mini_block_board Usage"},{"role":"assistant","content":"`PWN::Banner.mini_block_board`: "}]}
1716
+ {"messages":[{"role":"user","content":"PWN::Banner.mini_cells Usage"},{"role":"assistant","content":"`PWN::Banner.mini_cells`: Supported Method Parameters\n\nReturn rows of single-column Unicode block cells, with named terminal colors. Each fresh cell has glyph, foreground and background keys; no ANSI or IO. Square means terminal-cell dimensions, not physical pixels. Caller owns borders. Use mini_frame_seconds for cadence; 1800 frames span 90 or 180 seconds. Colored blocks fill the canvas with half-cell pieces; other games cap at 16. PWN::Banner.mini_cells(\n\nname: 'optional - falling_blocks, snake, pong, or asteroids; defaults to falling_blocks',\nframe: 'optional - integer index, defaults to zero and wraps at 1800',\nwidth: 'optional - columns clamped to 0..64 for blocks, 0..16 otherwise; defaults to 16',\nheight: 'optional - rows clamped to 0..64 for blocks, 0..16 otherwise; defaults to 16',\nseed: 'optional - integer local PRNG seed, defaults to 73; retain per session',\nbranding: 'optional - include the legacy wordmark, defaults to true; false reclaims its row'\n\n)\n"}]}
1712
1717
  {"messages":[{"role":"user","content":"PWN::Banner.mini_delta Usage"},{"role":"assistant","content":"`PWN::Banner.mini_delta`: "}]}
1713
1718
  {"messages":[{"role":"user","content":"PWN::Banner.mini_emit Usage"},{"role":"assistant","content":"`PWN::Banner.mini_emit`: "}]}
1714
1719
  {"messages":[{"role":"user","content":"PWN::Banner.mini_falling_blocks Usage"},{"role":"assistant","content":"`PWN::Banner.mini_falling_blocks`: "}]}
1715
1720
  {"messages":[{"role":"user","content":"PWN::Banner.mini_fits? Usage"},{"role":"assistant","content":"`PWN::Banner.mini_fits?`: "}]}
1716
- {"messages":[{"role":"user","content":"PWN::Banner.mini_frame Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frame`: Supported Method Parameters\n\nReturn fresh ASCII rows for decorative retro loops, not interactive games. Use equal width and height for a square; 5..16 retains the complete artwork. Smaller panes degrade to a wordmark. Caller owns randomness, clock and IO. PWN::Banner.mini_frame(\n\nname: 'optional - falling_blocks, snake, pong, or asteroids; defaults to falling_blocks',\nframe: 'optional - integer frame index, defaults to zero and wraps at 60',\nwidth: 'optional - output columns clamped to zero through 16, defaults to 16',\nheight: 'optional - output rows clamped to zero through 16, defaults to 16'\n\n)\n"}]}
1721
+ {"messages":[{"role":"user","content":"PWN::Banner.mini_frame Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frame`: Supported Method Parameters\n\nReturn fresh ASCII rows for decorative retro loops, not interactive games. Use equal width and height for a square; 5..16 retains the complete artwork. Smaller panes degrade to a wordmark. Caller owns randomness, clock and IO. PWN::Banner.mini_frame(\n\nname: 'optional - falling_blocks, snake, pong, or asteroids; defaults to falling_blocks',\nframe: 'optional - integer frame index, defaults to zero and wraps at 60',\nwidth: 'optional - output columns clamped to zero through 16, defaults to 16',\nheight: 'optional - output rows clamped to zero through 16, defaults to 16',\nbranding: 'optional - include the legacy wordmark, defaults to true; false reclaims its row'\n\n)\n"}]}
1717
1722
  {"messages":[{"role":"user","content":"PWN::Banner.mini_frame_seconds Usage"},{"role":"assistant","content":"`PWN::Banner.mini_frame_seconds`: Supported Method Parameters\n\nReturn the presentation interval; simulations preserve their gameplay speed. PWN::Banner.mini_frame_seconds(\n\nname: 'optional - animation name, defaults to falling_blocks'\n\n)\n"}]}
1718
1723
  {"messages":[{"role":"user","content":"PWN::Banner.mini_freeze Usage"},{"role":"assistant","content":"`PWN::Banner.mini_freeze`: "}]}
1719
1724
  {"messages":[{"role":"user","content":"PWN::Banner.mini_landing Usage"},{"role":"assistant","content":"`PWN::Banner.mini_landing`: "}]}
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.751
4
+ version: 0.5.754
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -141,14 +141,14 @@ dependencies:
141
141
  requirements:
142
142
  - - ">="
143
143
  - !ruby/object:Gem::Version
144
- version: 4.0.21
144
+ version: 4.0.22
145
145
  type: :development
146
146
  prerelease: false
147
147
  version_requirements: !ruby/object:Gem::Requirement
148
148
  requirements:
149
149
  - - ">="
150
150
  - !ruby/object:Gem::Version
151
- version: 4.0.21
151
+ version: 4.0.22
152
152
  - !ruby/object:Gem::Dependency
153
153
  name: bundler-audit
154
154
  requirement: !ruby/object:Gem::Requirement
@@ -617,14 +617,14 @@ dependencies:
617
617
  requirements:
618
618
  - - '='
619
619
  - !ruby/object:Gem::Version
620
- version: 0.5.1
620
+ version: 0.5.2
621
621
  type: :runtime
622
622
  prerelease: false
623
623
  version_requirements: !ruby/object:Gem::Requirement
624
624
  requirements:
625
625
  - - '='
626
626
  - !ruby/object:Gem::Version
627
- version: 0.5.1
627
+ version: 0.5.2
628
628
  - !ruby/object:Gem::Dependency
629
629
  name: nexpose
630
630
  requirement: !ruby/object:Gem::Requirement
@@ -7228,7 +7228,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
7228
7228
  - !ruby/object:Gem::Version
7229
7229
  version: '0'
7230
7230
  requirements: []
7231
- rubygems_version: 4.0.21
7231
+ rubygems_version: 4.0.22
7232
7232
  specification_version: 4
7233
7233
  summary: Automated Security Testing for CI/CD Pipelines & Beyond
7234
7234
  test_files: []