pwn 0.5.613 → 0.5.615
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 +10 -10
- data/README.md +110 -24
- data/documentation/AI-Integration.md +36 -0
- data/documentation/BurpSuite.md +26 -0
- data/documentation/Contributing.md +31 -0
- data/documentation/Diagrams.md +57 -0
- data/documentation/Drivers.md +25 -0
- data/documentation/General-PWN-Usage.md +40 -0
- data/documentation/Home.md +51 -0
- data/documentation/How-PWN-Works.md +60 -0
- data/documentation/Installation.md +50 -0
- data/documentation/NmapIt.md +18 -0
- data/documentation/Plugins.md +59 -0
- data/documentation/Reporting.md +28 -0
- data/documentation/SAST.md +32 -0
- data/documentation/Skills-Memory-Learning.md +44 -0
- data/documentation/Transparent-Browser.md +32 -0
- data/documentation/Troubleshooting.md +38 -0
- data/documentation/What-is-PWN.md +34 -0
- data/documentation/Why-PWN.md +18 -0
- data/documentation/diagrams/ai-integration-tool-calling.svg +134 -0
- data/documentation/diagrams/burp-vs-zap-preference.svg +84 -0
- data/documentation/diagrams/code-scanning-sast.svg +112 -0
- data/documentation/diagrams/dot/ai-integration-tool-calling.dot +23 -0
- data/documentation/diagrams/dot/burp-vs-zap-preference.dot +15 -0
- data/documentation/diagrams/dot/code-scanning-sast.dot +21 -0
- data/documentation/diagrams/dot/driver-framework.dot +17 -0
- data/documentation/diagrams/dot/fuzzing-workflow.dot +27 -0
- data/documentation/diagrams/dot/history-to-drivers.dot +20 -0
- data/documentation/diagrams/dot/memory-skills-detailed.dot +28 -0
- data/documentation/diagrams/dot/network-infra-testing.dot +22 -0
- data/documentation/diagrams/dot/overall-pwn-architecture.dot +49 -0
- data/documentation/diagrams/dot/penetration-testing-workflow.dot +29 -0
- data/documentation/diagrams/dot/plugin-ecosystem.dot +31 -0
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +57 -0
- data/documentation/diagrams/dot/pwn-repl-prototyping.dot +37 -0
- data/documentation/diagrams/dot/reporting-pipeline.dot +17 -0
- data/documentation/diagrams/dot/reverse-engineering-flow.dot +21 -0
- data/documentation/diagrams/dot/sessions-cron-automation.dot +17 -0
- data/documentation/diagrams/dot/web-application-testing.dot +27 -0
- data/documentation/diagrams/dot/zero-day-research-flow.dot +27 -0
- data/documentation/diagrams/driver-framework.svg +90 -0
- data/documentation/diagrams/fuzzing-workflow.svg +147 -0
- data/documentation/diagrams/history-to-drivers.svg +117 -0
- data/documentation/diagrams/memory-skills-detailed.svg +154 -0
- data/documentation/diagrams/network-infra-testing.svg +121 -0
- data/documentation/diagrams/overall-pwn-architecture.svg +234 -0
- data/documentation/diagrams/penetration-testing-workflow.svg +148 -0
- data/documentation/diagrams/plugin-ecosystem.svg +172 -0
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +234 -0
- data/documentation/diagrams/pwn-repl-prototyping.svg +179 -0
- data/documentation/diagrams/reporting-pipeline.svg +91 -0
- data/documentation/diagrams/reverse-engineering-flow.svg +114 -0
- data/documentation/diagrams/sessions-cron-automation.svg +92 -0
- data/documentation/diagrams/web-application-testing.svg +137 -0
- data/documentation/diagrams/zero-day-research-flow.svg +149 -0
- data/documentation/pwn-REPL.md +33 -0
- data/documentation/pwn-ai-Agent.md +43 -0
- data/lib/pwn/ai/agent/learning.rb +377 -0
- data/lib/pwn/ai/agent/loop.rb +18 -3
- data/lib/pwn/ai/agent/metrics.rb +155 -0
- data/lib/pwn/ai/agent/prompt_builder.rb +23 -3
- data/lib/pwn/ai/agent/tools/learning.rb +113 -0
- data/lib/pwn/ai/agent/tools/skills.rb +94 -8
- data/lib/pwn/ai/agent.rb +2 -0
- data/lib/pwn/ai/grok.rb +249 -106
- data/lib/pwn/config.rb +86 -13
- data/lib/pwn/plugins/monkey_patch.rb +2 -2
- data/lib/pwn/plugins/repl.rb +68 -3
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/learning_spec.rb +68 -0
- data/spec/lib/pwn/ai/agent/metrics_spec.rb +27 -0
- data/spec/lib/pwn/ai/agent/tools/learning_spec.rb +30 -0
- metadata +84 -34
- data/documentation/PWN.png +0 -0
- data/documentation/PWN_Contributors_and_Users.png +0 -0
- data/documentation/fax-spectrogram.png +0 -0
- data/documentation/fax-waveform.png +0 -0
- data/documentation/frozen_string_literal_files_changed_from_false_to_true.txt +0 -54
- data/documentation/lifecycle_authz_replay.example.yaml +0 -27
- data/documentation/pwn_android_war_dialer_session.png +0 -0
- data/documentation/pwn_install.png +0 -0
- data/documentation/pwn_wallpaper.jpg +0 -0
- data/documentation/ringing-spectrogram.png +0 -0
- data/documentation/ringing-waveform.png +0 -0
- data/documentation/vulnerability_report_template.md +0 -37
|
@@ -31,7 +31,7 @@ module PWN
|
|
|
31
31
|
pwn : #{pwn_version}
|
|
32
32
|
session_id : #{session_id || '(none)'}
|
|
33
33
|
|
|
34
|
-
#{memory_block}#{skills_block}TOOL USE
|
|
34
|
+
#{memory_block}#{skills_block}#{learning_block}#{metrics_block}TOOL USE
|
|
35
35
|
Use the provided function tools to act on the host. A reply with
|
|
36
36
|
no tool_calls is treated as your FINAL answer to the user.
|
|
37
37
|
Prefer `pwn_eval` for anything in the PWN:: namespace and `shell`
|
|
@@ -70,15 +70,35 @@ module PWN
|
|
|
70
70
|
return '' unless defined?(PWN::Skills) && PWN::Skills.is_a?(Hash) && !PWN::Skills.empty?
|
|
71
71
|
|
|
72
72
|
lines = PWN::Skills.map do |name, meta|
|
|
73
|
-
first = meta[:content].to_s.lines.first.to_s.strip
|
|
73
|
+
first = meta[:content].to_s.lines.reject { |l| l.strip.empty? || l.start_with?('---') }.first.to_s.strip
|
|
74
74
|
first = first[0, 100]
|
|
75
|
-
|
|
75
|
+
rc = Array(meta[:references]).length
|
|
76
|
+
ref_tag = rc.positive? ? " [#{rc} refs]" : ''
|
|
77
|
+
" - #{name}: #{first}#{ref_tag}"
|
|
76
78
|
end
|
|
77
79
|
"SKILLS (call skill_view to read full body)\n#{lines.join("\n")}\n\n"
|
|
78
80
|
rescue StandardError
|
|
79
81
|
''
|
|
80
82
|
end
|
|
81
83
|
|
|
84
|
+
private_class_method def self.learning_block
|
|
85
|
+
return '' unless defined?(PWN::AI::Agent::Learning)
|
|
86
|
+
|
|
87
|
+
ctx = PWN::AI::Agent::Learning.to_context(limit: 5).to_s
|
|
88
|
+
ctx.strip.empty? ? '' : "LEARNING\n#{ctx}"
|
|
89
|
+
rescue StandardError
|
|
90
|
+
''
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private_class_method def self.metrics_block
|
|
94
|
+
return '' unless defined?(PWN::AI::Agent::Metrics)
|
|
95
|
+
|
|
96
|
+
ctx = PWN::AI::Agent::Metrics.to_context(limit: 8).to_s
|
|
97
|
+
ctx.strip.empty? ? '' : ctx
|
|
98
|
+
rescue StandardError
|
|
99
|
+
''
|
|
100
|
+
end
|
|
101
|
+
|
|
82
102
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
83
103
|
|
|
84
104
|
public_class_method def self.authors
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pwn/ai/agent/registry'
|
|
4
|
+
require 'pwn/ai/agent/learning'
|
|
5
|
+
require 'pwn/ai/agent/metrics'
|
|
6
|
+
|
|
7
|
+
# Expose the self-improvement engine to the model so it can actively
|
|
8
|
+
# participate in the learning loop: log outcomes, trigger reflection,
|
|
9
|
+
# promote successful workflows into reusable skills, and inspect its own
|
|
10
|
+
# effectiveness metrics.
|
|
11
|
+
|
|
12
|
+
PWN::AI::Agent::Registry.register(
|
|
13
|
+
name: 'learning_note_outcome',
|
|
14
|
+
toolset: 'learning',
|
|
15
|
+
schema: {
|
|
16
|
+
name: 'learning_note_outcome',
|
|
17
|
+
description: 'Record the outcome of a task attempt so future pwn-ai ' \
|
|
18
|
+
'runs learn from it. Successes reinforce approaches; ' \
|
|
19
|
+
'failures become avoidance lessons injected into every ' \
|
|
20
|
+
'subsequent system prompt.',
|
|
21
|
+
parameters: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
task: { type: 'string', description: 'Short description of what was attempted.' },
|
|
25
|
+
success: { type: 'boolean', description: 'Did the attempt achieve its goal?' },
|
|
26
|
+
details: { type: 'string', description: 'Evidence, error text, or notes.' },
|
|
27
|
+
tags: { type: 'array', items: { type: 'string' } }
|
|
28
|
+
},
|
|
29
|
+
required: %w[task success]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
check: -> { defined?(PWN::AI::Agent::Learning) },
|
|
33
|
+
handler: lambda { |args|
|
|
34
|
+
PWN::AI::Agent::Learning.note_outcome(
|
|
35
|
+
task: args[:task],
|
|
36
|
+
success: args[:success],
|
|
37
|
+
details: args[:details],
|
|
38
|
+
tags: args[:tags]
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
PWN::AI::Agent::Registry.register(
|
|
44
|
+
name: 'learning_reflect',
|
|
45
|
+
toolset: 'learning',
|
|
46
|
+
schema: {
|
|
47
|
+
name: 'learning_reflect',
|
|
48
|
+
description: 'Analyse a completed session transcript and persist up to ' \
|
|
49
|
+
'5 durable lessons into PWN::Memory. Uses LLM introspection ' \
|
|
50
|
+
'when enabled, otherwise a heuristic extractor.',
|
|
51
|
+
parameters: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
session_id: { type: 'string', description: 'PWN::Sessions id to analyse.' },
|
|
55
|
+
dry_run: { type: 'boolean', default: false }
|
|
56
|
+
},
|
|
57
|
+
required: %w[session_id]
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
check: -> { defined?(PWN::AI::Agent::Learning) },
|
|
61
|
+
handler: lambda { |args|
|
|
62
|
+
PWN::AI::Agent::Learning.reflect(
|
|
63
|
+
session_id: args[:session_id],
|
|
64
|
+
dry_run: args[:dry_run]
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
PWN::AI::Agent::Registry.register(
|
|
70
|
+
name: 'learning_distill_skill',
|
|
71
|
+
toolset: 'learning',
|
|
72
|
+
schema: {
|
|
73
|
+
name: 'learning_distill_skill',
|
|
74
|
+
description: 'Promote a successful workflow into a reusable skill under ' \
|
|
75
|
+
'~/.pwn/skills so it appears in every future system prompt.',
|
|
76
|
+
parameters: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
name: { type: 'string', description: 'snake_case skill name.' },
|
|
80
|
+
session_id: { type: 'string', description: 'Session to mine for the procedure.' },
|
|
81
|
+
content: { type: 'string', description: 'Explicit markdown body (overrides session mining).' },
|
|
82
|
+
references: { type: 'array', items: { type: 'string' }, description: 'Optional URLs / CWE / CVE / ATT&CK / NIST references.' }
|
|
83
|
+
},
|
|
84
|
+
required: %w[name]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
check: -> { defined?(PWN::AI::Agent::Learning) },
|
|
88
|
+
handler: lambda { |args|
|
|
89
|
+
PWN::AI::Agent::Learning.distill_skill(
|
|
90
|
+
name: args[:name],
|
|
91
|
+
session_id: args[:session_id],
|
|
92
|
+
content: args[:content],
|
|
93
|
+
references: args[:references]
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
PWN::AI::Agent::Registry.register(
|
|
99
|
+
name: 'learning_stats',
|
|
100
|
+
toolset: 'learning',
|
|
101
|
+
schema: {
|
|
102
|
+
name: 'learning_stats',
|
|
103
|
+
description: 'Return self-effectiveness metrics: outcome success rate, ' \
|
|
104
|
+
'skills known, memory size, and per-tool telemetry.',
|
|
105
|
+
parameters: { type: 'object', properties: {}, required: [] }
|
|
106
|
+
},
|
|
107
|
+
check: -> { defined?(PWN::AI::Agent::Learning) },
|
|
108
|
+
handler: lambda { |_args|
|
|
109
|
+
PWN::AI::Agent::Learning.stats.merge(
|
|
110
|
+
tool_metrics: PWN::AI::Agent::Metrics.summary(limit: 10)
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
)
|
|
@@ -6,19 +6,30 @@ require 'pwn/ai/agent/registry'
|
|
|
6
6
|
# Thin wrappers around the PWN::Skills store (~/.pwn/skills/*.md) so the
|
|
7
7
|
# model can list/read/write reusable procedures. PromptBuilder injects
|
|
8
8
|
# the index (name + first line); skill_view loads the full body on demand.
|
|
9
|
+
#
|
|
10
|
+
# Each skill may carry a :references Array (URLs, CWE/CVE/ATT&CK ids,
|
|
11
|
+
# NIST 800-53 controls, docs) parsed from YAML front-matter or a
|
|
12
|
+
# "## References" markdown section. References let the agent cite
|
|
13
|
+
# authoritative sources when applying a skill and mirror the
|
|
14
|
+
# PWN::SAST::*.security_references convention.
|
|
9
15
|
|
|
10
16
|
PWN::AI::Agent::Registry.register(
|
|
11
17
|
name: 'skill_list',
|
|
12
18
|
toolset: 'skills',
|
|
13
19
|
schema: {
|
|
14
20
|
name: 'skill_list',
|
|
15
|
-
description: 'List available pwn-ai skills (name + first line
|
|
21
|
+
description: 'List available pwn-ai skills (name + first line + reference count).',
|
|
16
22
|
parameters: { type: 'object', properties: {}, required: [] }
|
|
17
23
|
},
|
|
18
24
|
check: -> { defined?(PWN::Skills) && PWN::Skills.is_a?(Hash) },
|
|
19
25
|
handler: lambda { |_args|
|
|
20
26
|
PWN::Skills.map do |name, meta|
|
|
21
|
-
{
|
|
27
|
+
{
|
|
28
|
+
name: name,
|
|
29
|
+
type: meta[:type],
|
|
30
|
+
summary: meta[:content].to_s.lines.first.to_s.strip[0, 120],
|
|
31
|
+
references: Array(meta[:references]).length
|
|
32
|
+
}
|
|
22
33
|
end
|
|
23
34
|
}
|
|
24
35
|
)
|
|
@@ -28,7 +39,7 @@ PWN::AI::Agent::Registry.register(
|
|
|
28
39
|
toolset: 'skills',
|
|
29
40
|
schema: {
|
|
30
41
|
name: 'skill_view',
|
|
31
|
-
description: 'Read the full content of a named skill.',
|
|
42
|
+
description: 'Read the full content of a named skill, including its references.',
|
|
32
43
|
parameters: {
|
|
33
44
|
type: 'object',
|
|
34
45
|
properties: { name: { type: 'string' } },
|
|
@@ -41,7 +52,13 @@ PWN::AI::Agent::Registry.register(
|
|
|
41
52
|
meta = PWN::Skills[key]
|
|
42
53
|
raise ArgumentError, "no such skill: #{key}" unless meta
|
|
43
54
|
|
|
44
|
-
{
|
|
55
|
+
{
|
|
56
|
+
name: key,
|
|
57
|
+
type: meta[:type],
|
|
58
|
+
path: meta[:path],
|
|
59
|
+
references: Array(meta[:references]),
|
|
60
|
+
content: meta[:content]
|
|
61
|
+
}
|
|
45
62
|
}
|
|
46
63
|
)
|
|
47
64
|
|
|
@@ -51,12 +68,20 @@ PWN::AI::Agent::Registry.register(
|
|
|
51
68
|
schema: {
|
|
52
69
|
name: 'skill_create',
|
|
53
70
|
description: 'Save a new reusable skill (markdown procedure) to ~/.pwn/skills/. ' \
|
|
54
|
-
'It will be listed in every future pwn-ai system prompt.'
|
|
71
|
+
'It will be listed in every future pwn-ai system prompt. Optionally ' \
|
|
72
|
+
'attach references (URLs, CWE/CVE ids, MITRE ATT&CK, NIST 800-53, docs).',
|
|
55
73
|
parameters: {
|
|
56
74
|
type: 'object',
|
|
57
75
|
properties: {
|
|
58
76
|
name: { type: 'string', description: 'lowercase, hyphens/underscores only' },
|
|
59
|
-
content: { type: 'string', description: 'Full markdown body of the skill.' }
|
|
77
|
+
content: { type: 'string', description: 'Full markdown body of the skill.' },
|
|
78
|
+
references: {
|
|
79
|
+
type: 'array',
|
|
80
|
+
items: { type: 'string' },
|
|
81
|
+
description: 'Optional list of reference URLs / identifiers (CWE-79, T1059, ' \
|
|
82
|
+
'https://..., NIST SI-3, etc.). Written as YAML front-matter and ' \
|
|
83
|
+
'a "## References" section.'
|
|
84
|
+
}
|
|
60
85
|
},
|
|
61
86
|
required: %w[name content]
|
|
62
87
|
}
|
|
@@ -66,11 +91,72 @@ PWN::AI::Agent::Registry.register(
|
|
|
66
91
|
name = args[:name].to_s.gsub(/[^a-z0-9_-]/i, '_')
|
|
67
92
|
raise ArgumentError, 'name is required' if name.empty?
|
|
68
93
|
|
|
94
|
+
body = args[:content].to_s
|
|
95
|
+
refs = Array(args[:references]).map(&:to_s).map(&:strip).reject(&:empty?).uniq
|
|
96
|
+
|
|
97
|
+
unless refs.empty?
|
|
98
|
+
# YAML front-matter (only if body doesn't already have one)
|
|
99
|
+
unless body.start_with?("---\n")
|
|
100
|
+
fm = "---\nreferences:\n#{refs.map { |r| " - #{r}" }.join("\n")}\n---\n"
|
|
101
|
+
body = fm + body
|
|
102
|
+
end
|
|
103
|
+
# ## References section (only if not already present)
|
|
104
|
+
body = "#{body.rstrip}\n\n## References\n#{refs.map { |r| "- #{r}" }.join("\n")}\n" unless body =~ /^\#{1,3}\s*References\s*$/i
|
|
105
|
+
end
|
|
106
|
+
|
|
69
107
|
dir = PWN::Config.pwn_skills_path
|
|
70
108
|
FileUtils.mkdir_p(dir)
|
|
71
109
|
path = File.join(dir, "#{name}.md")
|
|
72
|
-
File.write(path,
|
|
110
|
+
File.write(path, body)
|
|
73
111
|
PWN::Config.load_skills(pwn_skills_path: dir) # refresh PWN::Skills const
|
|
74
|
-
{ saved: true, path: path, total: PWN::Skills.keys.length }
|
|
112
|
+
{ saved: true, path: path, references: refs, total: PWN::Skills.keys.length }
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
PWN::AI::Agent::Registry.register(
|
|
117
|
+
name: 'skill_add_reference',
|
|
118
|
+
toolset: 'skills',
|
|
119
|
+
schema: {
|
|
120
|
+
name: 'skill_add_reference',
|
|
121
|
+
description: 'Append one or more references (URL, CWE/CVE id, MITRE ATT&CK ' \
|
|
122
|
+
'technique, NIST control, doc link) to an existing skill.',
|
|
123
|
+
parameters: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
name: { type: 'string', description: 'Existing skill name.' },
|
|
127
|
+
references: { type: 'array', items: { type: 'string' } }
|
|
128
|
+
},
|
|
129
|
+
required: %w[name references]
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
check: -> { defined?(PWN::Skills) && PWN::Skills.is_a?(Hash) },
|
|
133
|
+
handler: lambda { |args|
|
|
134
|
+
key = args[:name].to_s.to_sym
|
|
135
|
+
meta = PWN::Skills[key]
|
|
136
|
+
raise ArgumentError, "no such skill: #{key}" unless meta
|
|
137
|
+
|
|
138
|
+
new_refs = Array(args[:references]).map(&:to_s).map(&:strip).reject(&:empty?)
|
|
139
|
+
raise ArgumentError, 'references must be a non-empty array' if new_refs.empty?
|
|
140
|
+
|
|
141
|
+
body = File.read(meta[:path])
|
|
142
|
+
existing = Array(meta[:references])
|
|
143
|
+
add = (new_refs - existing)
|
|
144
|
+
|
|
145
|
+
if body =~ /^(\#{1,3}\s*References\s*)$/i
|
|
146
|
+
# Append bullets right after the References heading block (end of file safest)
|
|
147
|
+
body = "#{body.rstrip}\n#{add.map { |r| "- #{r}" }.join("\n")}\n" unless add.empty?
|
|
148
|
+
else
|
|
149
|
+
body = "#{body.rstrip}\n\n## References\n#{(existing + add).uniq.map { |r| "- #{r}" }.join("\n")}\n"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
File.write(meta[:path], body)
|
|
153
|
+
PWN::Config.load_skills(pwn_skills_path: File.dirname(meta[:path]))
|
|
154
|
+
{
|
|
155
|
+
saved: true,
|
|
156
|
+
name: key,
|
|
157
|
+
path: meta[:path],
|
|
158
|
+
added: add,
|
|
159
|
+
references: Array(PWN::Skills[key][:references])
|
|
160
|
+
}
|
|
75
161
|
}
|
|
76
162
|
)
|
data/lib/pwn/ai/agent.rb
CHANGED
|
@@ -23,6 +23,8 @@ module PWN
|
|
|
23
23
|
autoload :Result, 'pwn/ai/agent/result'
|
|
24
24
|
autoload :PromptBuilder, 'pwn/ai/agent/prompt_builder'
|
|
25
25
|
autoload :Loop, 'pwn/ai/agent/loop'
|
|
26
|
+
autoload :Metrics, 'pwn/ai/agent/metrics'
|
|
27
|
+
autoload :Learning, 'pwn/ai/agent/learning'
|
|
26
28
|
|
|
27
29
|
# Display a List of Every PWN::AI Module
|
|
28
30
|
|