Module: PWN::Plugins::REPL::AI

Defined in:
lib/pwn/plugins/repl/ai.rb

Overview

pwn-ai REPL mode.

Constant Summary collapse

PWN_AI_SLASH_COMMANDS =
%w[
  /back /cron /debug /delegate /help /learning /memory /mcp /model /sessions /skills /steer /system-role /trace
].freeze
PWN_AI_SLASH_SUBCOMMANDS =
{
  '/cron' => %w[list create run remove],
  '/debug' => [],
  '/trace' => [],
  '/delegate' => [],
  '/help' => [],
  '/memory' => %w[list recall remember forget clear],
  '/mcp' => %w[list backends use current connect disconnect ping tools call status help],
  '/model' => %w[list],
  '/sessions' => %w[list resume delete stats],
  '/skills' => %w[list recall],
  '/learning' => %w[list requeue]
}.freeze

Class Method Summary collapse

Class Method Details

.add_commands ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/pwn/plugins/repl/ai.rb', line 14

def self.add_commands
  Pry::Commands.create_command 'pwn-ai' do
    description 'Initiate pwn.ai autonomous agent TUI (instruct tasks using PWN modules + CLI tools; memory/sessions/agents/cron/skills-aware from PWN::Config/PWN::Memory etc).'

    def process
      pi = pry_instance
      pi.config.pwn_ai = true
      pi.config.pwn_ai_agent = true
      pi.config.color = false if pi.config.pwn_ai

      # Terminal ownership belongs to curses, not Reline.

      # Load and make aware of skills folder (scaled in PWN::Config per user pwn_env_path parent)
      skills_path = begin
        PWN::Config.pwn_skills_path
      rescue StandardError
        "#{Dir.home}/.pwn/skills"
      end
      PWN::ModuleSkills.install(pwn_skills_path: skills_path) if defined?(PWN::ModuleSkills) && PWN::ModuleSkills.respond_to?(:install)
      PWN::Config.load_skills(pwn_skills_path: skills_path)
      skills_count = (PWN.const_defined?(:Skills) ? PWN::Skills.keys.length : 0)

      # pwn-ai activation: initialise memory/sessions/cron stores
      PWN::Config.load_memory
      mem_count = (PWN.const_defined?(:Memory) ? PWN::Memory.load.keys.length : 0)
      sess = begin
        PWN::Plugins::REPL.pwn_ai_activation_session(pry: pi)
      rescue StandardError
        nil
      end
      pi.config.pwn_ai_session_id = sess[:id] if sess
      cron_count = (PWN.const_defined?(:Cron) ? PWN::Cron.list.keys.length : 0)

      begin
        console = PWN::Plugins::REPL::AIConsole.run(pry: pi)
      rescue Exception # rubocop:disable Lint/RescueException -- restore Pry mode even on terminal interrupts
        PWN::Plugins::REPL.leave_special_mode!(pry: pi)
        raise
      end
      if console == :closed
        PWN::Plugins::REPL.leave_special_mode!(pry: pi)
        return
      end

      # Non-TTY/dumb terminal fallback keeps the existing line interface.
      pi.config.input = PWNMultiLineInput.new(pi)
      PWN::Plugins::REPL.install_pwn_ai_completer!(pry: pi)
      puts '[*] Fullscreen curses unavailable on this terminal; using the legacy line interface.'
      puts '[*] pwn-ai agent TUI activated (PWN REPL driver w/ memory, sessions, delegation, cron).'
      puts "[*] Memory facts: #{mem_count} | Session: #{pi.config.pwn_ai_session_id} | Cron jobs: #{cron_count} | Skills: #{skills_count}"
      puts '[*] Instruct the AI agent to carry out a task, e.g.:'
      puts "    'Use NmapIt to port scan target.com then use TransparentBrowser to spider and SAST::TestCaseEngine to analyze code if cloned. Generate report with PWN::Reports.'"
      puts "    'Execute CLI nmap -sV target.com and summarize findings using PWN modules.'"
      puts "[*] Skills loaded from #{skills_path} (#{skills_count} available) + memory/sessions/cron to expand autonomous capabilities."
      puts "[*] Type 'back' or CTRL+D to exit pwn-ai mode."
      puts '[*] While busy: /steer <instruction> redirects the request; a running tool finishes first and is not undone. Trace ENTER stepping is suspended.'
      puts '[*] MULTILINE in pwn-ai: SHIFT+ENTER (or ALT+ENTER, or trailing `\\`) inserts a newline; ENTER submits to the AI.'
      puts '[*] TAB menus: leading `/` = commands (/cron /skills /sessions …); `/` later = host paths; otherwise Ruby completion (same as the pwn REPL).'
      puts "[*] tmux + terminator users: Ensure ~/.tmux.conf has 'set -s extended-keys on' and 'set -g xterm-keys on', then restart tmux. Use TERM=xterm-256color."
      tag = pi.config.pwn_ai_session_id.to_s.empty? ? '<SESSION_ID>' : pi.config.pwn_ai_session_id

      dbg_lvl = ''
      dbg_lvl = 'trace' if pi.config.pwn_ai_trace
      dbg_lvl = 'debug' if pi.config.pwn_ai_debug && !pi.config.pwn_ai_trace

      puts "\n\n\npwn-ai #{dbg_lvl} ON → ~/.pwn/logs/pwn-ai-DEBUG-#{tag}-R<REQUEST_NUMBER>.log" unless dbg_lvl.empty?
    end
  end

  Pry::Commands.create_command 'ai.profile' do
    description 'Select a session routing profile: ai.profile NAME (no args lists names).'

    def process
      PWN::Plugins::REPL.pwn_ai_profile_command(pry: pry_instance, args: args, output: output)
    end
  end

  Pry::Commands.create_command 'ai.memory' do
    description 'View/edit pinned engagement memory: ai.memory [view|edit TEXT|clear].'

    def process
      PWN::Plugins::REPL.pwn_ai_memory_command(pry: pry_instance, args: args, output: output)
    end
  end

  Pry::Commands.create_command 'pwn-ai-memory' do
    description 'Manage pwn-ai persistent memory.'

    def process
      cmd = args[0]
      case cmd
      when 'list', 'recall', nil
        q = args[1]
        res = PWN::Memory.recall(query: q)
        puts res.inspect
      when 'remember'
        key = args[1]
        val = args[2..].join(' ')
        PWN::Memory.remember(key: key, value: val)
        puts "Remembered #{key}"
      when 'forget'
        PWN::Memory.forget(key: args[1])
        puts "Forgot #{args[1]}"
      when 'clear'
        PWN::Memory.clear(force: true)
        puts 'Memory cleared'
      else
        puts PWN::Memory.help
      end
    end
  end

  Pry::Commands.create_command 'pwn-ai-sessions' do
    description 'List/resume/delete pwn-ai sessions.'

    def process
      cmd = args[0]
      case cmd
      when 'list', nil
        puts PWN::Sessions.list.inspect
      when 'resume'
        sid = args[1]
        hist = PWN::Sessions.to_response_history(session_id: sid)
        puts "Loaded session #{sid} with #{hist[:choices].size} entries (set manually into response_history if needed)"
      when 'delete'
        PWN::Sessions.delete(session_id: args[1], force: true)
        puts "Deleted #{args[1]}"
      when 'stats'
        puts PWN::Sessions.stats
      else
        puts PWN::Sessions.help
      end
    end
  end

  Pry::Commands.create_command 'pwn-ai-cron' do
    description 'Manage scheduled pwn-ai / cron jobs.'

    def process
      cmd = args[0]
      case cmd
      when 'list', nil
        puts PWN::Cron.list.inspect
      when 'create'
        # simplistic: pwn-ai-cron create '0 * * * *' 'prompt here'
        sched = args[1]
        pr = args[2..].join(' ')
        job = PWN::Cron.create(schedule: sched, prompt: pr)
        puts "Created #{job}"
      when 'run'
        res = PWN::Cron.run(id: args[1])
        puts res
      when 'remove'
        PWN::Cron.remove(id: args[1])
        puts 'Removed'
      else
        puts PWN::Cron.help
      end
    end
  end

  Pry::Commands.create_command 'pwn-ai-delegate' do
    description 'Delegate sub-task to a PWN::AI::Agent or simple sub-chat.'

    def process
      goal = args.join(' ')
      puts "[*] Delegating: #{goal}"
      # Simple delegation: use a specialized agent if matches, else another chat turn
      if goal =~ /sast|code|scan/i
        res = PWN::AI::Agent::SAST.analyze(request: goal)
      elsif goal =~ /vuln|report/i
        res = PWN::AI::Agent::VulnGen.analyze(request: goal)
      else
        # fallback sub call to active engine (no full loop here)
        engine = PWN::Env[:ai][:active].to_s.downcase.to_sym
        case engine
        when :anthropic then res = PWN::AI::Anthropic.chat(request: goal)
        when :gemini then res = PWN::AI::Gemini.chat(request: goal)
        when :grok then res = PWN::AI::Grok.chat(request: goal)
        else res = PWN::AI::Ollama.chat(request: goal)
        end
      end
      puts res
    end
  end
  Pry::Commands.create_command 'toggle-debug' do
    description 'Stream pwn-ai stage log to the TUI and ~/.pwn/logs/pwn-ai-DEBUG-<SESSION_ID>-RN.log'

    def process
      pi = pry_instance
      if pi.config.pwn_ai_debug
        path = PWN::Plugins::Log.stop_debug
        pi.config.pwn_ai_debug = false
        pi.config.pwn_ai_trace = false
        if path
          output.puts "pwn-ai debug OFF (was #{path})"
        else
          output.puts 'pwn-ai debug OFF'
        end
      else
        sid = pi.config.pwn_ai_session_id
        PWN::Plugins::Log.start_debug(tee: output, session_id: sid)
        pi.config.pwn_ai_debug = true
        output.puts 'pwn-ai debug ON.'
      end
    end
  end

  Pry::Commands.create_command 'toggle-trace' do
    description 'toggle-debug plus TracePoint; ENTER after each Loop step. Stored on Pry.config like toggle-debug, not Env.'

    def process
      pi = pry_instance
      if pi.config.pwn_ai_trace
        PWN::Plugins::Log.stop_debug
        pi.config.pwn_ai_debug = false
        pi.config.pwn_ai_trace = false
        output.puts 'pwn-ai trace OFF (debug OFF)'
      else
        sid = pi.config.pwn_ai_session_id
        PWN::Plugins::Log.start_debug(tee: output, session_id: sid, trace: true)
        pi.config.pwn_ai_debug = true
        pi.config.pwn_ai_trace = true
        output.puts 'pwn-ai trace ON (debug ON, TracePoint, ENTER each loop step)'
      end
    end
  end

  Pry::Commands.create_command 'toggle-pwn-ai-speaks' do
    description 'Use speech capabilities within pwn.ai to speak answers.'

    def process
      pi = pry_instance
      pi.config.pwn_ai_speak ? pi.config.pwn_ai_speak = false : pi.config.pwn_ai_speak = true
    end
  end
end

.authors ⇒ Object

Author(s)

0day Inc. [email protected]



1052
1053
1054
1055
1056
# File 'lib/pwn/plugins/repl/ai.rb', line 1052

public_class_method def self.authors
  "AUTHOR(S):
    0day Inc. <[email protected]>
  "
end

.help ⇒ Object

Display usage for this module.



1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
# File 'lib/pwn/plugins/repl/ai.rb', line 1060

public_class_method def self.help
  puts "USAGE:
    # Register pwn-ai Pry commands and debug toggles.
    #{self}.add_commands

    # Classify pwn-ai TAB completion as command, path, or Ruby.
    #{self}.pwn_ai_complete_kind(
      line: 'optional - full line buffer used to classify completion'
    )

    # TAB hits for pwn-ai slash commands, host paths, or Ruby.
    #{self}.pwn_ai_complete(
      target: 'required - token Reline is completing',
      line: 'optional - full line buffer',
      pry: 'optional - Pry instance for Ruby completion'
    )

    # TAB hits for leading-slash pwn-ai commands.
    #{self}.pwn_ai_complete_command(
      line: 'optional - full line buffer',
      target: 'required - token Reline is completing'
    )

    # TAB hits for host paths when slash is not first.
    #{self}.pwn_ai_complete_path(
      target: 'required - path prefix to complete',
      line: 'optional - full line buffer'
    )

    # TAB hits for Ruby constants and methods in pwn-ai.
    #{self}.pwn_ai_complete_ruby(
      target: 'optional - token Reline is completing',
      pry: 'optional - Pry instance (defaults to Thread.current[:pwn_ai_completer_pry])'
    )

    # Install Reline dropdown for pwn-ai (commands / paths / Ruby).
    #{self}.install_pwn_ai_completer!(
      pry: 'optional - Pry instance stored for pwn-ai TAB completion'
    )

    # Restore Pry Ruby completion after leaving pwn-ai.
    #{self}.restore_pwn_ai_completer!

    # Consume a prepared CLI session or create a new interactive session.
    #{self}.pwn_ai_activation_session(pry: 'required - Pry instance')

    # Validate/select a named model profile without changing provider defaults.
    #{self}.pwn_ai_profile_command(
      pry: 'required - Pry instance', args: ['profile-name'],
      env: 'optional - configuration hash', output: 'optional - output IO'
    )

    # View/edit the current session pinned engagement notes.
    #{self}.pwn_ai_memory_command(
      pry: 'required - Pry instance', args: ['edit', 'evidence notes'],
      root: 'optional - artifacts root', output: 'optional - output IO'
    )

    # Run a leading-slash pwn-ai command locally. Returns true when handled.
    #{self}.pwn_ai_dispatch_slash!(
      request: 'optional - full line such as /skills list',
      pry: 'optional - Pry instance used by /back'
    )

    # List configured pwn-ai engine names.
    #{self}.pwn_ai_engines

    # Resolve the provider class for an engine name.
    #{self}.pwn_ai_provider_class(
      engine: 'optional - engine name such as ollama or openai'
    )

    # Normalize provider catalog hashes into model id strings.
    #{self}.pwn_ai_model_ids(
      models: 'optional - catalog payload from a provider list call'
    )

    # List LLM ids for an engine including Codex slug catalogs.
    #{self}.pwn_ai_list_llms(
      engine: 'required - engine name such as openai or ollama'
    )

    # Return the currently selected model string for an engine.
    #{self}.pwn_ai_engine_model(
      engine: 'required - engine name such as ollama'
    )

    # Apply /model arguments to the active engine.
    #{self}.pwn_ai_run_model(
      args: 'optional - Array of slash tokens after /model',
      prompt: 'optional - callable receiving selection metadata; return effort, nil to cancel, or :deferred'
    )

    # Resolve model-specific reasoning options from provider capabilities.
    #{self}.pwn_ai_reasoning_selection(
      engine: 'required - provider name',
      model: 'required - exact model identifier'
    )

    # Ask for effort in the legacy line interface only.
    #{self}.pwn_ai_prompt_reasoning(
      selection: 'required - model selection with efforts and default'
    )

    # Apply an accepted model selection and merge its vault fields.
    #{self}.pwn_ai_apply_model(
      selection: 'required - engine, model and optional reasoning_effort'
    )

    # Save a role to the vault before updating the active engine.
    #{self}.pwn_ai_apply_system_role(
      engine: 'required - active engine name captured when opening the editor',
      content: 'required - exact multiline system role content, including an empty string'
    )

    # Persist engine settings into the encrypted pwn.yaml vault.
    #{self}.persist_ai_selection(
      engine: 'required - engine name to store as ai.active',
      model: 'optional - model id to store for that engine',
      reasoning_effort: 'optional - accepted effort to merge into that engine only',
      system_role_content: 'optional - exact accepted role to merge into that engine only'
    )

    # Run /cron locally without Loop.run.
    #{self}.pwn_ai_run_cron(
      args: 'optional - Array of slash tokens after /cron'
    )

    # Run /sessions locally without Loop.run.
    #{self}.pwn_ai_run_sessions(
      args: 'optional - Array of slash tokens after /sessions'
    )

    # Run /memory locally without Loop.run.
    #{self}.pwn_ai_run_memory(
      args: 'optional - Array of slash tokens after /memory'
    )

    # List or requeue conflicted learning outcomes.
    #{self}.pwn_ai_run_learning(
      args: 'optional - list [--conflicted] or requeue'
    )

    # Run /skills locally without Loop.run.
    #{self}.pwn_ai_run_skills(
      args: 'optional - Array of slash tokens after /skills'
    )

    # Run /mcp locally without Loop.run.
    #{self}.pwn_ai_run_mcp(
      args: 'optional - slash tokens after /mcp such as list tools'
    )

    # Print the AUTHOR(S) string for this module.
    #{self}.authors
  "
  constants.sort
end