pwn 0.5.677 → 0.5.679

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/documentation/AI-Integration.md +4 -3
  4. data/documentation/Agent-Tool-Registry.md +27 -5
  5. data/documentation/CLI-Drivers.md +1 -1
  6. data/documentation/Configuration.md +65 -25
  7. data/documentation/Cron.md +12 -2
  8. data/documentation/Home.md +1 -1
  9. data/documentation/How-PWN-Works.md +1 -1
  10. data/documentation/Installation.md +12 -7
  11. data/documentation/Reinforcement-Learning.md +10 -6
  12. data/documentation/Skills-Memory-Learning.md +1 -1
  13. data/documentation/What-is-PWN.md +1 -1
  14. data/documentation/diagrams/agent-tool-registry.svg +156 -147
  15. data/documentation/diagrams/cron-scheduling.svg +149 -126
  16. data/documentation/diagrams/dot/agent-tool-registry.dot +2 -1
  17. data/documentation/diagrams/dot/cron-scheduling.dot +6 -3
  18. data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +1 -1
  19. data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +14 -13
  20. data/documentation/pwn-REPL.md +1 -1
  21. data/documentation/pwn-ai-Agent.md +16 -9
  22. data/lib/pwn/ai/agent/policy.rb +9 -3
  23. data/lib/pwn/ai/agent/registry.rb +95 -6
  24. data/lib/pwn/ai/anthropic.rb +2 -6
  25. data/lib/pwn/ai/gemini.rb +2 -6
  26. data/lib/pwn/ai/grok.rb +7 -8
  27. data/lib/pwn/ai/ollama.rb +2 -6
  28. data/lib/pwn/ai/open_ai.rb +2 -6
  29. data/lib/pwn/ai/open_web_ui.rb +2 -6
  30. data/lib/pwn/blockchain/btc.rb +2 -6
  31. data/lib/pwn/blockchain/eth.rb +2 -6
  32. data/lib/pwn/config.rb +4 -3
  33. data/lib/pwn/plugins/black_duck_binary_analysis.rb +2 -4
  34. data/lib/pwn/plugins/jira_data_center.rb +2 -4
  35. data/lib/pwn/plugins/repl.rb +12 -6
  36. data/lib/pwn/plugins/tty_spinner.rb +75 -0
  37. data/lib/pwn/plugins.rb +1 -0
  38. data/lib/pwn/version.rb +1 -1
  39. data/spec/lib/pwn/ai/agent/registry_spec.rb +6 -0
  40. data/spec/lib/pwn/ai/grok_spec.rb +7 -0
  41. data/spec/lib/pwn/plugins/tty_spinner_spec.rb +61 -0
  42. data/third_party/pwn_rdoc.jsonl +8 -2
  43. metadata +3 -1
@@ -1291,17 +1291,23 @@ module PWN
1291
1291
 
1292
1292
  case engine
1293
1293
  when :anthropic
1294
- response = PWN::AI::Anthropic.chat(**chat_opts)
1294
+ # response = PWN::AI::Anthropic.chat(**chat_opts)
1295
+ response = PWN::AI::Anthropic.chat(chat_opts)
1295
1296
  when :gemini
1296
- response = PWN::AI::Gemini.chat(**chat_opts)
1297
+ # response = PWN::AI::Gemini.chat(**chat_opts)
1298
+ response = PWN::AI::Gemini.chat(chat_opts)
1297
1299
  when :grok
1298
- response = PWN::AI::Grok.chat(**chat_opts)
1300
+ # response = PWN::AI::Grok.chat(**chat_opts)
1301
+ response = PWN::AI::Grok.chat(chat_opts)
1299
1302
  when :ollama
1300
- response = PWN::AI::Ollama.chat(**chat_opts)
1303
+ # response = PWN::AI::Ollama.chat(**chat_opts)
1304
+ response = PWN::AI::Ollama.chat(chat_opts)
1301
1305
  when :openai
1302
- response = PWN::AI::OpenAI.chat(**chat_opts)
1306
+ # response = PWN::AI::OpenAI.chat(**chat_opts)
1307
+ response = PWN::AI::OpenAI.chat(chat_opts)
1303
1308
  when :openwebui
1304
- response = PWN::AI::OpenWebUI.chat(**chat_opts)
1309
+ # response = PWN::AI::OpenWebUI.chat(**chat_opts)
1310
+ response = PWN::AI::OpenWebUI.chat(chat_opts)
1305
1311
  else
1306
1312
  raise "ERROR: Unsupported AI Engine: #{engine}"
1307
1313
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-spinner'
4
+
5
+ module PWN
6
+ module Plugins
7
+ # Shared TTY::Spinner lifecycle for REST / AI calls.
8
+ #
9
+ # TTY::Spinner#stop marks @done and Thread#kills the auto_spin
10
+ # worker but does NOT join it. The worker is typically mid-sleep
11
+ # on its interval, so it stays alive long enough to write another
12
+ # frame (cursor col 1 + glyph) over the HTTP response that the
13
+ # caller is about to print. Creating the spinner with clear:false
14
+ # (the gem default) also reprints the last glyph + newline on
15
+ # stop, so the spinner appears to keep running whenever a
16
+ # response is provided.
17
+ #
18
+ # #start uses clear:true + hide_cursor. #stop joins the worker
19
+ # (bounded) so the glyph is gone before the response is written.
20
+ module TTYSpinner
21
+ JOIN_SECS = 0.5
22
+
23
+ # Supported Method Parameters::
24
+ # spin = PWN::Plugins::TTYSpinner.start(
25
+ # format: 'optional - TTY::Spinner format (defaults to :dots)',
26
+ # output: 'optional - IO to draw on (defaults to $stderr)'
27
+ # )
28
+
29
+ public_class_method def self.start(opts = {})
30
+ args = {
31
+ format: opts[:format] || :dots,
32
+ clear: true,
33
+ hide_cursor: true
34
+ }
35
+ args[:output] = opts[:output] unless opts[:output].nil?
36
+
37
+ spin = TTY::Spinner.new(**args)
38
+ spin.auto_spin
39
+ spin
40
+ end
41
+
42
+ # Supported Method Parameters::
43
+ # PWN::Plugins::TTYSpinner.stop(
44
+ # spin: 'optional - TTY::Spinner from #start (no-op if nil)'
45
+ # )
46
+
47
+ public_class_method def self.stop(opts = {})
48
+ spin = opts.is_a?(Hash) ? opts[:spin] : opts
49
+ return if spin.nil?
50
+
51
+ thread = spin.instance_variable_get(:@thread)
52
+ spin.stop unless spin.respond_to?(:done?) && spin.done?
53
+ thread.join(JOIN_SECS) if thread.respond_to?(:join)
54
+ spin
55
+ rescue StandardError
56
+ spin
57
+ end
58
+
59
+ # Author(s):: 0day Inc. <support@0dayinc.com>
60
+
61
+ public_class_method def self.authors
62
+ "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
63
+ end
64
+
65
+ # Display usage info
66
+
67
+ public_class_method def self.help
68
+ puts "USAGE:
69
+ spin = #{self}.start
70
+ #{self}.stop(spin: spin)
71
+ "
72
+ end
73
+ end
74
+ end
75
+ end
data/lib/pwn/plugins.rb CHANGED
@@ -63,6 +63,7 @@ module PWN
63
63
  autoload :ThreadPool, 'pwn/plugins/thread_pool'
64
64
  autoload :Tor, 'pwn/plugins/tor'
65
65
  autoload :TransparentBrowser, 'pwn/plugins/transparent_browser'
66
+ autoload :TTYSpinner, 'pwn/plugins/tty_spinner'
66
67
  autoload :TwitterAPI, 'pwn/plugins/twitter_api'
67
68
  autoload :URIScheme, 'pwn/plugins/uri_scheme'
68
69
  autoload :Vault, 'pwn/plugins/vault'
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.677'
4
+ VERSION = '0.5.679'
5
5
  end
@@ -12,4 +12,10 @@ describe PWN::AI::Agent::Registry do
12
12
  help_response = PWN::AI::Agent::Registry
13
13
  expect(help_response).to respond_to :help
14
14
  end
15
+
16
+ it 'DEFAULT_PREFERENCE is memory_recall, sessions_view, pwn_eval, shell, mistakes_record, mistakes_resolve, learning_note_outcome, memory_remember' do
17
+ expect(described_class::DEFAULT_PREFERENCE).to eq(
18
+ %w[memory_recall sessions_view pwn_eval shell mistakes_record mistakes_resolve learning_note_outcome memory_remember]
19
+ )
20
+ end
15
21
  end
@@ -38,4 +38,11 @@ describe PWN::AI::Grok do
38
38
  expect(src).to include('x-grok-conv-id')
39
39
  expect(src).to include('enabled?(engine: :grok)')
40
40
  end
41
+
42
+ it 'grok_rest_call stops the spinner via TTYSpinner (joins worker after response)' do
43
+ src = File.read(described_class.method(:chat_with_tools).source_location.first)
44
+ expect(src).to include('PWN::Plugins::TTYSpinner.start')
45
+ expect(src).to include('PWN::Plugins::TTYSpinner.stop(spin: spin)')
46
+ expect(src).not_to match(/spin\.stop if spinner/)
47
+ end
41
48
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe PWN::Plugins::TTYSpinner do
6
+ it 'should display information for authors' do
7
+ expect(described_class).to respond_to :authors
8
+ end
9
+
10
+ it 'should display information for existing help method' do
11
+ expect(described_class).to respond_to :help
12
+ end
13
+
14
+ # StringIO is never a TTY so TTY::Spinner#write is a no-op, but the
15
+ # auto_spin worker thread still starts. That is enough to prove #stop
16
+ # actually joins the worker (the bug: ensure spin.stop left it sleeping
17
+ # so a following response could still be overwritten).
18
+ def run_rest_like(opts = {})
19
+ provide_response = opts.fetch(:provide_response, true)
20
+ spinner = true
21
+ spin = described_class.start(output: StringIO.new) if spinner
22
+ response = nil
23
+ begin
24
+ sleep 0.15
25
+ response = provide_response ? 'HTTP_RESPONSE' : nil
26
+ response
27
+ ensure
28
+ described_class.stop(spin: spin)
29
+ end
30
+ end
31
+
32
+ it 'joins the auto_spin thread when a response is provided' do
33
+ before = Thread.list.size
34
+ result = run_rest_like(provide_response: true)
35
+ expect(result).to eq('HTTP_RESPONSE')
36
+ sleep 0.05
37
+ expect(Thread.list.size).to eq(before)
38
+ end
39
+
40
+ it 'joins the auto_spin thread when no response is provided' do
41
+ before = Thread.list.size
42
+ result = run_rest_like(provide_response: false)
43
+ expect(result).to be_nil
44
+ sleep 0.05
45
+ expect(Thread.list.size).to eq(before)
46
+ end
47
+
48
+ it 'is a no-op when spin is nil' do
49
+ expect { described_class.stop(spin: nil) }.not_to raise_error
50
+ end
51
+
52
+ it 'marks the spinner done and dead after stop' do
53
+ spin = described_class.start(output: StringIO.new)
54
+ thread = spin.instance_variable_get(:@thread)
55
+ expect(thread).not_to be_nil
56
+ described_class.stop(spin: spin)
57
+ expect(spin.done?).to be true
58
+ expect(spin.spinning?).to be false
59
+ expect(thread.status).to eq(false).or(be_nil)
60
+ end
61
+ end
@@ -500,13 +500,15 @@
500
500
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Reflect.on Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Reflect.on`: Supported Method Parameters\n\nresponse = PWN::AI::Agent::Reflect.on(\n\nrequest: 'required - String - What you want the AI to reflect on',\nsystem_role_content: 'optional - context to set up the model behavior for reflection',\nengine: 'optional - override engine for THIS reflection only (Symbol/String); defaults to PWN::Env[:ai][:reflect_engine] || :active',\nmodel: 'optional - override model on the reflection engine for THIS call only; defaults to PWN::Env[:ai][:reflect_model]',\nspinner: 'optional - Boolean - Display spinner during operation (default: false)',\nsuppress_pii_warning: 'optional - Boolean - Suppress PII Warnings (default: false)'\n\n)\n"}]}
501
501
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Reflect.with_engine Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Reflect.with_engine`: "}]}
502
502
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.all Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.all`: Supported Method Parameters\n\nentries = PWN::AI::Agent::Registry.all\n"}]}
503
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.apply_preference Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.apply_preference`: Supported Method Parameters\n\nsorted = PWN::AI::Agent::Registry.apply_preference(\n\nitems: 'required - Array of Entry objects or OpenAI tool hashes',\norder: 'optional - forwarded to preference_order only when the key is set',\npreference: 'optional - alias of :order; same key-present rule'\n\n)\n\nStable sort: preferred names first, original index as the tie-break so equal-preference items keep their incoming order. Empty preference (explicit [] / nil) leaves items unsorted.\n"}]}
503
504
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.authors Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
504
- {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.definitions Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.definitions`: Supported Method Parameters\n\ntools = PWN::AI::Agent::Registry.definitions(\n\nenabled: 'optional - Array of toolset names to include; nil = all whose check passes',\nrelevance: 'optional - user request; when set AND :tool_router is enabled, slim to CORE + top-K keyword matches',\ntop_k: 'optional - keyword-ranked tools to keep beyond CORE (default 10)'\n\n)\n"}]}
505
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.definitions Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.definitions`: Supported Method Parameters\n\ntools = PWN::AI::Agent::Registry.definitions(\n\nenabled: 'optional - Array of toolset names to include; nil = all whose check passes',\nrelevance: 'optional - user request; when set AND :tool_router is enabled, slim to CORE + top-K keyword matches',\ntop_k: 'optional - keyword-ranked tools to keep beyond CORE (default 10)',\norder: 'optional - preference list forwarded only when this key is present',\npreference: 'optional - alias of :order; same key-present rule'\n\n)\n"}]}
505
506
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.discover Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.discover`: Supported Method Parameters\n\nnames = PWN::AI::Agent::Registry.discover(\n\nforce: 'optional - re-require tool files even if already discovered (default false)'\n\n)\n"}]}
506
507
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.help Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.help`: "}]}
507
508
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.lookup Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.lookup`: Supported Method Parameters\n\nentry = PWN::AI::Agent::Registry.lookup(\n\nname: 'required - registered tool name'\n\n)\n"}]}
508
509
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.metrics_rates Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.metrics_rates`: "}]}
509
- {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.rank Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.rank`: Supported Method Parameters\n\nranked = PWN::AI::Agent::Registry.rank(\n\nquery: 'required - user request text',\nentries: 'optional - Entry pool to rank (default .all)'\n\n)\n\nC1 advantage-weighted router: score = α·keyword_sim + β·(tool rolling success_rate − global rate) + γ·UCB1(tool). Untried tools get an exploration bonus; tools that outperform the fleet get an exploitation bonus. Thompson sampling is available via Metrics.thompson for stochastic routing.\n"}]}
510
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.preference_order Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.preference_order`: Supported Method Parameters\n\norder = PWN::AI::Agent::Registry.preference_order(\n\norder: 'optional - Array of tool names; explicit nil/[] disables',\npreference: 'optional - alias of :order. Explicit key disables Env/DEFAULT fallback'\n\n)\n"}]}
511
+ {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.rank Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.rank`: Supported Method Parameters\n\nranked = PWN::AI::Agent::Registry.rank(\n\nquery: 'required - user request text',\nentries: 'optional - Entry pool to rank (default .all)',\norder: 'optional - preference list forwarded to preference_order'\n\n)\n\nC1 advantage-weighted router: score = α·keyword_sim + β·(tool rolling success_rate − global rate) + γ·UCB1(tool). Untried tools get an exploration bonus; tools that outperform the fleet get an exploitation bonus. Thompson sampling is available via Metrics.thompson for stochastic routing.\n"}]}
510
512
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.register Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.register`: Supported Method Parameters\n\nPWN::AI::Agent::Registry.register(\n\nname: 'required - tool name exposed to the model',\ntoolset: 'required - grouping for enable/disable (terminal, file, pwn, memory…)',\nschema: 'required - OpenAI function schema {name:, description:, parameters:}',\nhandler: 'required - ->(args_hash) { ... } returning a JSON-serialisable object',\ncheck: 'optional - -> { bool } gate; tool only advertised when truthy',\nmax_chars: 'optional - cap on serialised result (default 24_000)'\n\n)\n"}]}
511
513
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.router_enabled? Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.router_enabled?`: "}]}
512
514
  {"messages":[{"role":"user","content":"PWN::AI::Agent::Registry.safe_check Usage"},{"role":"assistant","content":"`PWN::AI::Agent::Registry.safe_check`: "}]}
@@ -2096,6 +2098,10 @@
2096
2098
  {"messages":[{"role":"user","content":"PWN::Plugins::Spider.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::Spider.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
2097
2099
  {"messages":[{"role":"user","content":"PWN::Plugins::Spider.crawl Usage"},{"role":"assistant","content":"`PWN::Plugins::Spider.crawl`: Supported Method Parameters\n\nPWN::Plugins::Spider.crawl(\n\ntarget_fqdn: 'required - target fqdn to spider',\nresults_path: 'required - path to save spider results',\nproxy: 'optional - proxy to spider through e.g. http://127.0.0.1:8080'\n\n)\n"}]}
2098
2100
  {"messages":[{"role":"user","content":"PWN::Plugins::Spider.help Usage"},{"role":"assistant","content":"`PWN::Plugins::Spider.help`: "}]}
2101
+ {"messages":[{"role":"user","content":"PWN::Plugins::TTYSpinner.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::TTYSpinner.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
2102
+ {"messages":[{"role":"user","content":"PWN::Plugins::TTYSpinner.help Usage"},{"role":"assistant","content":"`PWN::Plugins::TTYSpinner.help`: "}]}
2103
+ {"messages":[{"role":"user","content":"PWN::Plugins::TTYSpinner.start Usage"},{"role":"assistant","content":"`PWN::Plugins::TTYSpinner.start`: Supported Method Parameters\n\nspin = PWN::Plugins::TTYSpinner.start(\n\nformat: 'optional - TTY::Spinner format (defaults to :dots)',\noutput: 'optional - IO to draw on (defaults to $stderr)'\n\n)\n"}]}
2104
+ {"messages":[{"role":"user","content":"PWN::Plugins::TTYSpinner.stop Usage"},{"role":"assistant","content":"`PWN::Plugins::TTYSpinner.stop`: Supported Method Parameters\n\nPWN::Plugins::TTYSpinner.stop(\n\nspin: 'optional - TTY::Spinner from #start (no-op if nil)'\n\n)\n"}]}
2099
2105
  {"messages":[{"role":"user","content":"PWN::Plugins::ThreadPool.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::ThreadPool.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
2100
2106
  {"messages":[{"role":"user","content":"PWN::Plugins::ThreadPool.fill Usage"},{"role":"assistant","content":"`PWN::Plugins::ThreadPool.fill`: Supported Method Parameters\n\nPWN::Plugins::ThreadPool.fill(\n\nenumerable_array: 'required array for proper thread pool assignment',\nmax_threads: 'optional number of threads in the thread pool (defaults to 9)',\ndetach: 'optional boolean to detach threads (defaults to false)'\n\n)\n\nExample: arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] mutex = Mutex.new PWN::Plugins::ThreadPool.fill(enumerable_array: arr, max_threads: 9) do |integer|\n\nmutex.synchronize do\n puts integer\nend\n\n\nend\n"}]}
2101
2107
  {"messages":[{"role":"user","content":"PWN::Plugins::ThreadPool.help Usage"},{"role":"assistant","content":"`PWN::Plugins::ThreadPool.help`: "}]}
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.677
4
+ version: 0.5.679
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -2082,6 +2082,7 @@ files:
2082
2082
  - lib/pwn/plugins/thread_pool.rb
2083
2083
  - lib/pwn/plugins/tor.rb
2084
2084
  - lib/pwn/plugins/transparent_browser.rb
2085
+ - lib/pwn/plugins/tty_spinner.rb
2085
2086
  - lib/pwn/plugins/twitter_api.rb
2086
2087
  - lib/pwn/plugins/uri_scheme.rb
2087
2088
  - lib/pwn/plugins/vault.rb
@@ -2554,6 +2555,7 @@ files:
2554
2555
  - spec/lib/pwn/plugins/thread_pool_spec.rb
2555
2556
  - spec/lib/pwn/plugins/tor_spec.rb
2556
2557
  - spec/lib/pwn/plugins/transparent_browser_spec.rb
2558
+ - spec/lib/pwn/plugins/tty_spinner_spec.rb
2557
2559
  - spec/lib/pwn/plugins/twitter_api_spec.rb
2558
2560
  - spec/lib/pwn/plugins/uri_scheme_spec.rb
2559
2561
  - spec/lib/pwn/plugins/vault_spec.rb