pwn 0.5.749 → 0.5.751
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/documentation/Blockchain.md +107 -9
- data/documentation/pwn-ai-Agent.md +310 -4
- data/documentation/pwn_silent_help_learn_demo.gif +0 -0
- data/etc/default_skills/pwn/banner/SKILL.md +7 -3
- data/etc/default_skills/pwn/blockchain/btc/SKILL.md +8 -5
- data/etc/default_skills/pwn/blockchain/eth/SKILL.md +12 -3
- data/etc/default_skills/pwn/blockchain/eth/references/urls.md +1 -1
- data/etc/default_skills/pwn/plugins/repl/SKILL.md +5 -0
- data/etc/default_skills/pwn/plugins/repl/ai/SKILL.md +4 -0
- data/etc/default_skills/pwn/plugins/repl/ai/references/urls.md +5 -0
- data/etc/default_skills/pwn/plugins/repl/ai_console/SKILL.md +48 -0
- data/etc/default_skills/pwn/plugins/repl/ai_console_commands/SKILL.md +47 -0
- data/etc/default_skills/pwn/plugins/repl/ai_console_usage/SKILL.md +48 -0
- data/etc/default_skills/pwn/plugins/repl/ai_swarm/SKILL.md +46 -0
- data/lib/pwn/ai/agent/loop.rb +287 -18
- data/lib/pwn/ai/agent/swarm.rb +28 -4
- data/lib/pwn/ai/open_ai.rb +4 -2
- data/lib/pwn/banner.rb +743 -2
- data/lib/pwn/blockchain/btc.rb +494 -196
- data/lib/pwn/blockchain/eth.rb +440 -81
- data/lib/pwn/blockchain.rb +6 -0
- data/lib/pwn/config.rb +22 -0
- data/lib/pwn/migrate.rb +9 -1
- data/lib/pwn/plugins/repl/ai.rb +182 -17
- data/lib/pwn/plugins/repl/ai_console.rb +1581 -0
- data/lib/pwn/plugins/repl/ai_console_commands.rb +282 -0
- data/lib/pwn/plugins/repl/ai_console_usage.rb +144 -0
- data/lib/pwn/plugins/repl/ai_swarm.rb +375 -0
- data/lib/pwn/plugins/repl.rb +25 -3
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/loop_spec.rb +258 -1
- data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +3 -2
- data/spec/lib/pwn/ai/open_ai_spec.rb +5 -0
- data/spec/lib/pwn/banner_spec.rb +643 -0
- data/spec/lib/pwn/blockchain/btc_spec.rb +372 -1
- data/spec/lib/pwn/blockchain/eth_spec.rb +205 -0
- data/spec/lib/pwn/blockchain_spec.rb +6 -0
- data/spec/lib/pwn/config_spec.rb +10 -0
- data/spec/lib/pwn/migrate_spec.rb +40 -3
- data/spec/lib/pwn/plugins/repl/ai_console_commands_spec.rb +65 -0
- data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +1434 -0
- data/spec/lib/pwn/plugins/repl/ai_console_usage_spec.rb +29 -0
- data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +127 -0
- data/spec/lib/pwn/plugins/repl_pwn_vault_spec.rb +5 -3
- data/spec/lib/pwn/plugins/repl_spec.rb +167 -1
- data/third_party/pwn_rdoc.jsonl +229 -10
- metadata +18 -6
- data/etc/default_skills/pwn/blockchain/btc/references/urls.md +0 -3
|
@@ -80,7 +80,7 @@ describe PWN::Migrate do
|
|
|
80
80
|
File.write(File.join(@tmp, '.schema'), JSON.generate(schema: 2))
|
|
81
81
|
expect(described_class.needed?).to be(true)
|
|
82
82
|
result = described_class.run(fix: false, backup: false, io: io)
|
|
83
|
-
expect(result[:applied_migrations]).to eq([3, 4, 5])
|
|
83
|
+
expect(result[:applied_migrations]).to eq([3, 4, 5, 6, 7])
|
|
84
84
|
expected = Marshal.load(Marshal.dump(original))
|
|
85
85
|
expected['custom']['model'] = nil
|
|
86
86
|
expect(YAML.safe_load_file(path)).to eq(expected)
|
|
@@ -189,7 +189,7 @@ describe PWN::Migrate do
|
|
|
189
189
|
File.write(File.join(@tmp, '.schema'), JSON.generate(schema: 3))
|
|
190
190
|
expect(described_class.needed?).to be(true)
|
|
191
191
|
result = described_class.run(fix: true, backup: false, io: io)
|
|
192
|
-
expect(result[:applied_migrations]).to eq([4, 5])
|
|
192
|
+
expect(result[:applied_migrations]).to eq([4, 5, 6, 7])
|
|
193
193
|
creds = YAML.safe_load_file(dec, symbolize_names: true)
|
|
194
194
|
user = PWN::Plugins::Vault.dump(file: yaml, key: creds[:key], iv: creds[:iv])
|
|
195
195
|
expect(user[:ai][:active]).to eq('grok')
|
|
@@ -215,7 +215,7 @@ describe PWN::Migrate do
|
|
|
215
215
|
PWN::Plugins::Vault.create(file: yaml, decryptor_file: dec)
|
|
216
216
|
File.write(File.join(@tmp, '.schema'), JSON.generate(schema: 4))
|
|
217
217
|
result = described_class.run(fix: true, backup: false, io: io)
|
|
218
|
-
expect(result[:applied_migrations]).to eq([5])
|
|
218
|
+
expect(result[:applied_migrations]).to eq([5, 6, 7])
|
|
219
219
|
creds = YAML.safe_load_file(dec, symbolize_names: true)
|
|
220
220
|
user = PWN::Plugins::Vault.dump(file: yaml, key: creds[:key], iv: creds[:iv])
|
|
221
221
|
expect(user.dig(:ai, :agent, :skill_review)).to eq('off')
|
|
@@ -233,6 +233,43 @@ describe PWN::Migrate do
|
|
|
233
233
|
expect(described_class.run(fix: true, backup: false, io: io)[:applied_migrations]).to eq([])
|
|
234
234
|
end
|
|
235
235
|
|
|
236
|
+
it 'upgrades a schema-5 vault with ai.tui.theme without replacing a chosen color' do
|
|
237
|
+
yaml = File.join(@tmp, 'pwn.yaml')
|
|
238
|
+
dec = File.join(@tmp, 'pwn.yaml.decryptor')
|
|
239
|
+
stale = { ai: { active: 'grok', grok: { key: 'sk-KEEP' }, tui: { theme: { assistant: 'blue' } } } }
|
|
240
|
+
File.write(yaml, YAML.dump(stale).gsub(/^(\s*):/, '\1'))
|
|
241
|
+
PWN::Plugins::Vault.create(file: yaml, decryptor_file: dec)
|
|
242
|
+
File.write(File.join(@tmp, '.schema'), JSON.generate(schema: 5))
|
|
243
|
+
expect(described_class.run(fix: true, backup: false, io: io)[:applied_migrations]).to eq([6, 7])
|
|
244
|
+
creds = YAML.safe_load_file(dec, symbolize_names: true)
|
|
245
|
+
user = PWN::Plugins::Vault.dump(file: yaml, key: creds[:key], iv: creds[:iv])
|
|
246
|
+
theme = user.dig(:ai, :tui, :theme)
|
|
247
|
+
expect(theme[:assistant]).to eq('blue')
|
|
248
|
+
expect(theme[:border]).to eq('black')
|
|
249
|
+
expect(theme[:title]).to eq('red')
|
|
250
|
+
expect(theme[:request]).to eq('white')
|
|
251
|
+
expect(theme).to eq(PWN::Config.env_template.dig(:ai, :tui, :theme).merge(assistant: 'blue'))
|
|
252
|
+
expect(user.dig(:ai, :grok, :key)).to eq('sk-KEEP')
|
|
253
|
+
expect(described_class.run(fix: true, backup: false, io: io)[:applied_migrations]).to eq([])
|
|
254
|
+
expect(PWN::Plugins::Vault.dump(file: yaml, key: creds[:key], iv: creds[:iv]).dig(:ai, :tui, :theme)).to eq(theme)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
it 'preserves every configured theme role when backfilling a current-schema vault' do
|
|
258
|
+
yaml = File.join(@tmp, 'pwn.yaml')
|
|
259
|
+
dec = File.join(@tmp, 'pwn.yaml.decryptor')
|
|
260
|
+
chosen = PWN::Config.env_template.dig(:ai, :tui, :theme).transform_values { 'blue' }
|
|
261
|
+
user = PWN::Config.env_template
|
|
262
|
+
user[:ai][:tui][:theme] = chosen
|
|
263
|
+
File.write(yaml, YAML.dump(user))
|
|
264
|
+
PWN::Plugins::Vault.create(file: yaml, decryptor_file: dec)
|
|
265
|
+
File.write(File.join(@tmp, '.schema'), JSON.generate(schema: described_class::SCHEMA_VERSION))
|
|
266
|
+
encrypted = File.binread(yaml)
|
|
267
|
+
expect(described_class.backfill_vault(io: io)[:added]).to eq(0)
|
|
268
|
+
expect(File.binread(yaml)).to eq(encrypted)
|
|
269
|
+
creds = YAML.safe_load_file(dec, symbolize_names: true)
|
|
270
|
+
expect(PWN::Plugins::Vault.dump(file: yaml, key: creds[:key], iv: creds[:iv]).dig(:ai, :tui, :theme)).to eq(chosen)
|
|
271
|
+
end
|
|
272
|
+
|
|
236
273
|
it 'dry_run writes nothing' do
|
|
237
274
|
r = PWN::Migrate.run(fix: true, backup: true, dry_run: true, io: io)
|
|
238
275
|
expect(r[:dry_run]).to be true
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe PWN::Plugins::REPL::AIConsoleCommands do
|
|
6
|
+
it 'completes command names and parameter positions without a provider call' do
|
|
7
|
+
allow(PWN::Plugins::REPL).to receive(:pwn_ai_engines).and_return(%w[openai grok])
|
|
8
|
+
first = described_class.complete(line: '/mo', cursor: 3)
|
|
9
|
+
expect(first[:items].map { |item| item[:label] }).to include('/model')
|
|
10
|
+
expect(described_class.complete(line: '/', cursor: 1)[:items].map { |item| item[:label] }).to include('/system-role')
|
|
11
|
+
expect(described_class.complete(line: '/sys', cursor: 4)[:items].map { |item| item[:label] }).to eq(['/system-role'])
|
|
12
|
+
engines = described_class.complete(line: '/model ', cursor: 7)
|
|
13
|
+
expect(engines[:items].map { |item| item[:label] }).to include('openai', 'list')
|
|
14
|
+
expect(engines[:items].map { |item| item[:text] }).to include('/model openai')
|
|
15
|
+
verbose = described_class.complete(line: '/verbose ', cursor: 9)
|
|
16
|
+
expect(verbose[:items].map { |item| item[:label] }).to eq(%w[on off])
|
|
17
|
+
free = described_class.complete(line: '/steer keep ', cursor: 12)
|
|
18
|
+
expect(free[:items]).to be_empty
|
|
19
|
+
expect(free[:hint]).to include('free text')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'completes PWN constants and filesystem paths as they are typed' do
|
|
23
|
+
hits = described_class.complete(line: 'inspect PWN::Plug', cursor: 16)
|
|
24
|
+
expect(hits[:items].map { |item| item[:label] }).to include('PWN::Plugins::')
|
|
25
|
+
expect(hits[:hint]).to eq('PWN constant')
|
|
26
|
+
Dir.mktmpdir do |dir|
|
|
27
|
+
File.write(File.join(dir, 'sample.txt'), 'x')
|
|
28
|
+
path = described_class.complete(line: "#{dir}/sam", cursor: "#{dir}/sam".length)
|
|
29
|
+
expect(path[:items].map { |item| item[:label] }).to include(File.join(dir, 'sample.txt'))
|
|
30
|
+
expect(path[:hint]).to eq('path')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'does not autoload every constant candidate or fail on a scalar namespace' do
|
|
35
|
+
mod = Module.new
|
|
36
|
+
mod.autoload(:Unloaded, '/not/a/real/dependency.rb')
|
|
37
|
+
mod.const_set(:Scalar, 42)
|
|
38
|
+
stub_const('PWN::CompletionProbe', mod)
|
|
39
|
+
result = described_class.complete(line: 'PWN::CompletionProbe::', cursor: 22)
|
|
40
|
+
expect(result[:items].map { |item| item[:label] }).to include('PWN::CompletionProbe::Unloaded::')
|
|
41
|
+
expect(mod.autoload?(:Unloaded)).not_to be_nil
|
|
42
|
+
line = 'PWN::CompletionProbe::Scalar::'
|
|
43
|
+
expect(described_class.complete(line: line, cursor: line.length)[:items]).to be_empty
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'recognizes absolute paths outside a hardcoded root list without swallowing slash commands' do
|
|
47
|
+
expect(PWN::Plugins::REPL).to receive(:pwn_ai_complete_path).with(target: '/srv/probe', line: '/srv/probe').and_return(['/srv/probe.txt'])
|
|
48
|
+
expect(described_class.complete(line: '/srv/probe', cursor: 10)[:items].first[:label]).to eq('/srv/probe.txt')
|
|
49
|
+
expect(described_class.complete(line: '/mo', cursor: 3)[:items].first[:label]).to eq('/model')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'preserves a Unicode suffix when replacing the token at the cursor' do
|
|
53
|
+
result = described_class.complete(line: '/verbose o café', cursor: 10)
|
|
54
|
+
chosen = result[:items].find { |item| item[:label] == 'on' }
|
|
55
|
+
expect(chosen[:text]).to eq('/verbose on café')
|
|
56
|
+
expect(chosen[:text].scan(/\X/)[chosen[:cursor]]).to eq(' ')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'keeps every command reachable and completes only the prefix before a mid-token cursor' do
|
|
60
|
+
labels = described_class.complete(line: '/', cursor: 1)[:items].map { |item| item[:label] }
|
|
61
|
+
expect(labels).to include('/swarm', '/verbose', '/steer', '/status')
|
|
62
|
+
result = described_class.complete(line: '/verbose ox café', cursor: 10)
|
|
63
|
+
expect(result[:items].map { |item| item[:text] }).to include('/verbose on café')
|
|
64
|
+
end
|
|
65
|
+
end
|