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
data/lib/pwn/plugins/repl/ai.rb
CHANGED
|
@@ -21,9 +21,7 @@ module PWN
|
|
|
21
21
|
pi.config.pwn_ai_agent = true
|
|
22
22
|
pi.config.color = false if pi.config.pwn_ai
|
|
23
23
|
|
|
24
|
-
#
|
|
25
|
-
pi.config.input = PWNMultiLineInput.new(pi)
|
|
26
|
-
PWN::Plugins::REPL.install_pwn_ai_completer!(pry: pi)
|
|
24
|
+
# Terminal ownership belongs to curses, not Reline.
|
|
27
25
|
|
|
28
26
|
# Load and make aware of skills folder (scaled in PWN::Config per user pwn_env_path parent)
|
|
29
27
|
skills_path = begin
|
|
@@ -46,6 +44,21 @@ module PWN
|
|
|
46
44
|
pi.config.pwn_ai_session_id = sess[:id] if sess
|
|
47
45
|
cron_count = (PWN.const_defined?(:Cron) ? PWN::Cron.list.keys.length : 0)
|
|
48
46
|
|
|
47
|
+
begin
|
|
48
|
+
console = PWN::Plugins::REPL::AIConsole.run(pry: pi)
|
|
49
|
+
rescue Exception # rubocop:disable Lint/RescueException -- restore Pry mode even on terminal interrupts
|
|
50
|
+
PWN::Plugins::REPL.leave_special_mode!(pry: pi)
|
|
51
|
+
raise
|
|
52
|
+
end
|
|
53
|
+
if console == :closed
|
|
54
|
+
PWN::Plugins::REPL.leave_special_mode!(pry: pi)
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Non-TTY/dumb terminal fallback keeps the existing line interface.
|
|
59
|
+
pi.config.input = PWNMultiLineInput.new(pi)
|
|
60
|
+
PWN::Plugins::REPL.install_pwn_ai_completer!(pry: pi)
|
|
61
|
+
puts '[*] Fullscreen curses unavailable on this terminal; using the legacy line interface.'
|
|
49
62
|
puts '[*] pwn-ai agent TUI activated (PWN REPL driver w/ memory, sessions, delegation, cron).'
|
|
50
63
|
puts "[*] Memory facts: #{mem_count} | Session: #{pi.config.pwn_ai_session_id} | Cron jobs: #{cron_count} | Skills: #{skills_count}"
|
|
51
64
|
puts '[*] Instruct the AI agent to carry out a task, e.g.:'
|
|
@@ -53,6 +66,7 @@ module PWN
|
|
|
53
66
|
puts " 'Execute CLI nmap -sV target.com and summarize findings using PWN modules.'"
|
|
54
67
|
puts "[*] Skills loaded from #{skills_path} (#{skills_count} available) + memory/sessions/cron to expand autonomous capabilities."
|
|
55
68
|
puts "[*] Type 'back' or CTRL+D to exit pwn-ai mode."
|
|
69
|
+
puts '[*] While busy: /steer <instruction> redirects the request; a running tool finishes first and is not undone. Trace ENTER stepping is suspended.'
|
|
56
70
|
puts '[*] MULTILINE in pwn-ai: SHIFT+ENTER (or ALT+ENTER, or trailing `\\`) inserts a newline; ENTER submits to the AI.'
|
|
57
71
|
puts '[*] TAB menus: leading `/` = commands (/cron /skills /sessions …); `/` later = host paths; otherwise Ruby completion (same as the pwn REPL).'
|
|
58
72
|
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."
|
|
@@ -235,7 +249,7 @@ module PWN
|
|
|
235
249
|
end
|
|
236
250
|
end
|
|
237
251
|
PWN_AI_SLASH_COMMANDS = %w[
|
|
238
|
-
/back /cron /debug /delegate /help /learning /memory /mcp /model /sessions /skills /trace
|
|
252
|
+
/back /cron /debug /delegate /help /learning /memory /mcp /model /sessions /skills /steer /system-role /trace
|
|
239
253
|
].freeze
|
|
240
254
|
|
|
241
255
|
PWN_AI_SLASH_SUBCOMMANDS = {
|
|
@@ -259,6 +273,19 @@ module PWN
|
|
|
259
273
|
# 2. '/' anywhere else → :path (host-native file nav)
|
|
260
274
|
# 3. else → :ruby (same Pry::InputCompleter menu as the pwn REPL)
|
|
261
275
|
class << PWN::Plugins::REPL # rubocop:disable Metrics/ClassLength
|
|
276
|
+
# Foreground Loop plus one scoped terminal reader, not another Pry
|
|
277
|
+
# readline and not a worker thread running tools without their locals.
|
|
278
|
+
def pwn_ai_run_steerable(opts = {})
|
|
279
|
+
previous = Thread.current[:pwn_steering_input]
|
|
280
|
+
input = opts[:input] || $stdin
|
|
281
|
+
output = opts[:output] || $stdout
|
|
282
|
+
control = PWN::AI::Agent::Loop::Steering.new(input: input, output: output)
|
|
283
|
+
Thread.current[:pwn_steering_input] = control
|
|
284
|
+
control.with_reader { PWN::AI::Agent::Loop.run(opts.merge(steering: control)) }
|
|
285
|
+
ensure
|
|
286
|
+
Thread.current[:pwn_steering_input] = previous
|
|
287
|
+
end
|
|
288
|
+
|
|
262
289
|
def pwn_ai_complete_kind(opts = {})
|
|
263
290
|
line = opts[:line].to_s
|
|
264
291
|
return :command if line.start_with?('/')
|
|
@@ -497,6 +524,8 @@ module PWN
|
|
|
497
524
|
args = tokens[1..]
|
|
498
525
|
pi = opts[:pry]
|
|
499
526
|
case cmd
|
|
527
|
+
when '/steer'
|
|
528
|
+
puts(args.empty? ? '[pwn-ai] Usage: /steer <instruction>' : '[pwn-ai] /steer: no active request; submit a normal request instead')
|
|
500
529
|
when '/help'
|
|
501
530
|
puts 'pwn-ai commands:'
|
|
502
531
|
PWN_AI_SLASH_COMMANDS.each do |c|
|
|
@@ -504,6 +533,7 @@ module PWN
|
|
|
504
533
|
puts(subs.empty? ? " #{c}" : " #{c} #{subs.join('|')}")
|
|
505
534
|
end
|
|
506
535
|
puts ' TAB: /… command menu · slash later in the line: path nav · else Ruby completion'
|
|
536
|
+
puts ' /steer <instruction>: redirect an active request; a running tool finishes first (not undone)'
|
|
507
537
|
when '/back'
|
|
508
538
|
if pi.respond_to?(:eval)
|
|
509
539
|
pi.eval('back')
|
|
@@ -535,6 +565,8 @@ module PWN
|
|
|
535
565
|
puts ' Use agent_list / agent_debate from pwn-ai, or pwn-ai-delegate in the pwn REPL.'
|
|
536
566
|
when '/model'
|
|
537
567
|
pwn_ai_run_model(args: args)
|
|
568
|
+
when '/system-role'
|
|
569
|
+
puts '[pwn-ai] /system-role requires the curses console. Open pwn-ai in an interactive terminal.'
|
|
538
570
|
when '/mcp'
|
|
539
571
|
pwn_ai_run_mcp(args: args)
|
|
540
572
|
when '/learning'
|
|
@@ -625,6 +657,8 @@ module PWN
|
|
|
625
657
|
sub = args[0].to_s
|
|
626
658
|
if sub.empty? || %w[show status].include?(sub)
|
|
627
659
|
msg = "active=#{current.empty? ? '(none)' : current} model=#{current_model.empty? ? '(unset)' : current_model}"
|
|
660
|
+
effort = PWN::Env.dig(:ai, current.to_sym, :reasoning_effort)
|
|
661
|
+
msg += " reasoning_effort=#{effort}" unless effort.to_s.empty?
|
|
628
662
|
puts "[*] #{msg}"
|
|
629
663
|
return msg
|
|
630
664
|
end
|
|
@@ -634,6 +668,7 @@ module PWN
|
|
|
634
668
|
puts 'pwn-ai /model — switch provider and model in this session'
|
|
635
669
|
puts " current: #{current} #{current_model}"
|
|
636
670
|
puts ' usage: /model [list] | /model list llms | /model <engine> [model] | /model <model>'
|
|
671
|
+
puts ' Supported models prompt for reasoning effort before applying; Enter accepts, Esc cancels.'
|
|
637
672
|
engines.each do |eng|
|
|
638
673
|
mark = eng == current ? '*' : ' '
|
|
639
674
|
puts " #{mark} #{eng} #{pwn_ai_engine_model(engine: eng)}"
|
|
@@ -654,18 +689,108 @@ module PWN
|
|
|
654
689
|
raise "no active engine — /model <engine> first (#{engines.join(', ')})" if engine.to_s.empty?
|
|
655
690
|
raise "unknown engine #{engine.inspect} — try: #{engines.join(', ')}" unless engines.include?(engine.to_s)
|
|
656
691
|
|
|
692
|
+
selection = pwn_ai_reasoning_selection(engine: engine, model: model || pwn_ai_engine_model(engine: engine))
|
|
693
|
+
prompt = opts[:prompt]
|
|
694
|
+
if selection[:efforts].any?
|
|
695
|
+
effort = prompt.respond_to?(:call) ? prompt.call(selection) : pwn_ai_prompt_reasoning(selection: selection)
|
|
696
|
+
return 'Model selection cancelled.' if effort.nil?
|
|
697
|
+
return selection if effort == :deferred
|
|
698
|
+
|
|
699
|
+
raise ArgumentError, 'unsupported reasoning effort' unless selection[:efforts].include?(effort)
|
|
700
|
+
|
|
701
|
+
selection[:reasoning_effort] = effort
|
|
702
|
+
end
|
|
703
|
+
pwn_ai_apply_model(selection: selection)
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
# Resolve capabilities only on submitted selections, never completion.
|
|
707
|
+
def pwn_ai_reasoning_selection(opts = {})
|
|
708
|
+
engine = opts[:engine].to_s
|
|
709
|
+
model = opts[:model].to_s
|
|
710
|
+
efforts = []
|
|
711
|
+
default = 'medium'
|
|
712
|
+
if engine == 'openai' && PWN::AI::OpenAI.send(:reasoning_model?, model: model)
|
|
713
|
+
begin
|
|
714
|
+
catalog = PWN::AI::OpenAI.get_models
|
|
715
|
+
rows = catalog[:data] || catalog[:models] || []
|
|
716
|
+
row = Array(rows).find { |item| item.is_a?(Hash) && (item[:id] || item[:slug] || item['id'] || item['slug']) == model } || {}
|
|
717
|
+
levels = row[:supported_reasoning_levels] || row['supported_reasoning_levels']
|
|
718
|
+
efforts = Array(levels).filter_map { |level| level.is_a?(Hash) ? (level[:effort] || level['effort']) : nil }.map(&:to_s)
|
|
719
|
+
default = row[:default_reasoning_level] || row['default_reasoning_level'] || default
|
|
720
|
+
rescue StandardError => e
|
|
721
|
+
warn "[pwn-ai] reasoning catalog unavailable: #{e.class}; using documented capabilities."
|
|
722
|
+
end
|
|
723
|
+
# Model-specific API contracts; do not guess future families.
|
|
724
|
+
# https://developers.openai.com/api/docs/models/gpt-6-astra
|
|
725
|
+
# https://developers.openai.com/api/docs/models/gpt-5
|
|
726
|
+
efforts = %w[low medium high xhigh max] if efforts.empty? && model == 'gpt-6-astra'
|
|
727
|
+
efforts = %w[minimal low medium high] if efforts.empty? && model.match?(/\Agpt-5(?:-\d{4}-\d{2}-\d{2})?\z/)
|
|
728
|
+
# Same model pages document these explicit versions (not codex/pro variants).
|
|
729
|
+
efforts = %w[none low medium high] if efforts.empty? && model.match?(/\Agpt-5\.1(?:-\d{4}-\d{2}-\d{2})?\z/)
|
|
730
|
+
efforts = %w[none low medium high xhigh] if efforts.empty? && model.match?(/\Agpt-5\.(?:2|4|5)(?:-\d{4}-\d{2}-\d{2})?\z/)
|
|
731
|
+
elsif engine == 'grok'
|
|
732
|
+
# https://docs.x.ai/developers/model-capabilities/text/reasoning
|
|
733
|
+
efforts = %w[low medium high xhigh] if %w[grok-4.6 grok-4.7].include?(model)
|
|
734
|
+
efforts = %w[low medium high] if model == 'grok-4.5'
|
|
735
|
+
efforts = %w[low high] if %w[grok-3-mini grok-3-mini-fast].include?(model)
|
|
736
|
+
end
|
|
737
|
+
current = PWN::Env.dig(:ai, engine.to_sym, :reasoning_effort).to_s
|
|
738
|
+
efforts.delete('none') if engine == 'openai' && model.start_with?('gpt-6-astra')
|
|
739
|
+
default = current if efforts.include?(current)
|
|
740
|
+
default = efforts.first unless efforts.include?(default)
|
|
741
|
+
{ engine: engine, model: model, efforts: efforts.uniq, default: default }
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
def pwn_ai_prompt_reasoning(opts = {})
|
|
745
|
+
selection = opts[:selection]
|
|
746
|
+
# The legacy line interface alone owns stdin here. Curses supplies a callback.
|
|
747
|
+
return selection[:default] unless $stdin.tty?
|
|
748
|
+
|
|
749
|
+
loop do
|
|
750
|
+
puts "Reasoning effort for #{selection[:engine]}/#{selection[:model]}: #{selection[:efforts].join(', ')}"
|
|
751
|
+
print "Effort [#{selection[:default]}] (Enter accepts; q/Esc cancels): "
|
|
752
|
+
line = $stdin.gets
|
|
753
|
+
return nil if line.nil? || ["\e", 'q', 'cancel'].include?(line.strip)
|
|
754
|
+
|
|
755
|
+
value = line.strip.empty? ? selection[:default] : line.strip
|
|
756
|
+
return value if selection[:efforts].include?(value)
|
|
757
|
+
end
|
|
758
|
+
rescue Interrupt
|
|
759
|
+
nil
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
def pwn_ai_apply_model(opts = {})
|
|
763
|
+
selection = opts[:selection]
|
|
764
|
+
engine = selection[:engine]
|
|
765
|
+
model = selection[:model]
|
|
657
766
|
PWN::Env[:ai] ||= {}
|
|
658
767
|
PWN::Env[:ai][engine.to_sym] ||= {}
|
|
659
768
|
PWN::Env[:ai][:active] = engine.to_s
|
|
660
769
|
PWN::Env[:ai][engine.to_sym][:model] = model unless model.to_s.strip.empty?
|
|
661
|
-
|
|
770
|
+
persistence = { engine: engine, model: PWN::Env[:ai][engine.to_sym][:model] }
|
|
771
|
+
if selection.key?(:reasoning_effort)
|
|
772
|
+
PWN::Env[:ai][engine.to_sym][:reasoning_effort] = selection[:reasoning_effort]
|
|
773
|
+
persistence[:reasoning_effort] = selection[:reasoning_effort]
|
|
774
|
+
end
|
|
775
|
+
persisted = persist_ai_selection(persistence)
|
|
662
776
|
shown = PWN::Env[:ai][engine.to_sym][:model]
|
|
663
777
|
msg = "active=#{engine} model=#{shown.to_s.empty? ? '(unset)' : shown}"
|
|
778
|
+
msg += " reasoning_effort=#{selection[:reasoning_effort]}" if selection.key?(:reasoning_effort)
|
|
664
779
|
msg = "#{msg} (session only)" unless persisted
|
|
665
780
|
puts "[*] #{msg}"
|
|
666
781
|
msg
|
|
667
782
|
end
|
|
668
783
|
|
|
784
|
+
def pwn_ai_apply_system_role(opts = {})
|
|
785
|
+
engine = opts[:engine].to_s
|
|
786
|
+
content = opts[:content].to_s
|
|
787
|
+
raise 'Active engine changed; reopen /system-role.' unless engine == PWN::Env.dig(:ai, :active).to_s && PWN::Env.dig(:ai, engine.to_sym).is_a?(Hash)
|
|
788
|
+
raise 'System role not saved: vault persistence failed (check vault/decryptor permissions).' unless persist_ai_selection(engine: engine, system_role_content: content)
|
|
789
|
+
|
|
790
|
+
PWN::Env[:ai][engine.to_sym][:system_role_content] = content
|
|
791
|
+
:saved
|
|
792
|
+
end
|
|
793
|
+
|
|
669
794
|
def persist_ai_selection(opts = {})
|
|
670
795
|
engine = opts[:engine].to_s
|
|
671
796
|
model = opts[:model]
|
|
@@ -688,26 +813,41 @@ module PWN
|
|
|
688
813
|
iv = decryptor.is_a?(Hash) ? decryptor[:iv] : nil
|
|
689
814
|
return false if key.to_s.strip.empty? || iv.to_s.strip.empty?
|
|
690
815
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
816
|
+
# Never decrypt the live vault in place: failed encryption must leave
|
|
817
|
+
# the previous ciphertext intact. Rename only the encrypted merge.
|
|
818
|
+
require 'tempfile'
|
|
819
|
+
Tempfile.create(['.pwn-ai-selection', '.yaml'], File.dirname(env_path)) do |file|
|
|
820
|
+
file.binmode
|
|
821
|
+
file.write(File.binread(env_path))
|
|
822
|
+
file.flush
|
|
823
|
+
path = file.path
|
|
824
|
+
PWN::Plugins::Vault.decrypt(file: path, key: key, iv: iv)
|
|
825
|
+
cfg = YAML.load_file(path, symbolize_names: true)
|
|
694
826
|
cfg = {} unless cfg.is_a?(Hash)
|
|
695
827
|
cfg[:ai] = {} unless cfg[:ai].is_a?(Hash)
|
|
696
|
-
cfg[:ai][:active] = engine
|
|
828
|
+
cfg[:ai][:active] = engine unless opts.key?(:system_role_content)
|
|
697
829
|
unless model.to_s.strip.empty?
|
|
698
830
|
slot = engine.to_sym
|
|
699
831
|
cfg[:ai][slot] = {} unless cfg[:ai][slot].is_a?(Hash)
|
|
700
832
|
cfg[:ai][slot][:model] = model
|
|
701
833
|
end
|
|
834
|
+
if opts.key?(:reasoning_effort)
|
|
835
|
+
cfg[:ai][engine.to_sym] ||= {}
|
|
836
|
+
cfg[:ai][engine.to_sym][:reasoning_effort] = opts[:reasoning_effort]
|
|
837
|
+
end
|
|
838
|
+
if opts.key?(:system_role_content)
|
|
839
|
+
cfg[:ai][engine.to_sym] ||= {}
|
|
840
|
+
cfg[:ai][engine.to_sym][:system_role_content] = opts[:system_role_content]
|
|
841
|
+
end
|
|
702
842
|
yaml_env = YAML.dump(cfg).gsub(/^(\s*):/, '\1')
|
|
703
|
-
File.write(
|
|
704
|
-
File.chmod(0o600,
|
|
705
|
-
|
|
706
|
-
|
|
843
|
+
File.write(path, yaml_env)
|
|
844
|
+
File.chmod(0o600, path)
|
|
845
|
+
PWN::Plugins::Vault.encrypt(file: path, key: key, iv: iv)
|
|
846
|
+
File.rename(path, env_path)
|
|
707
847
|
end
|
|
708
848
|
true
|
|
709
849
|
rescue StandardError => e
|
|
710
|
-
warn "[pwn-ai]
|
|
850
|
+
warn "[pwn-ai] settings persist failed: #{e.class}: #{e.message}"
|
|
711
851
|
false
|
|
712
852
|
end
|
|
713
853
|
|
|
@@ -1006,13 +1146,38 @@ module PWN
|
|
|
1006
1146
|
|
|
1007
1147
|
# Apply /model arguments to the active engine.
|
|
1008
1148
|
#{self}.pwn_ai_run_model(
|
|
1009
|
-
args: 'optional - Array of slash tokens after /model'
|
|
1149
|
+
args: 'optional - Array of slash tokens after /model',
|
|
1150
|
+
prompt: 'optional - callable receiving selection metadata; return effort, nil to cancel, or :deferred'
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
# Resolve model-specific reasoning options from provider capabilities.
|
|
1154
|
+
#{self}.pwn_ai_reasoning_selection(
|
|
1155
|
+
engine: 'required - provider name',
|
|
1156
|
+
model: 'required - exact model identifier'
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
# Ask for effort in the legacy line interface only.
|
|
1160
|
+
#{self}.pwn_ai_prompt_reasoning(
|
|
1161
|
+
selection: 'required - model selection with efforts and default'
|
|
1162
|
+
)
|
|
1163
|
+
|
|
1164
|
+
# Apply an accepted model selection and merge its vault fields.
|
|
1165
|
+
#{self}.pwn_ai_apply_model(
|
|
1166
|
+
selection: 'required - engine, model and optional reasoning_effort'
|
|
1167
|
+
)
|
|
1168
|
+
|
|
1169
|
+
# Save a role to the vault before updating the active engine.
|
|
1170
|
+
#{self}.pwn_ai_apply_system_role(
|
|
1171
|
+
engine: 'required - active engine name captured when opening the editor',
|
|
1172
|
+
content: 'required - exact multiline system role content, including an empty string'
|
|
1010
1173
|
)
|
|
1011
1174
|
|
|
1012
|
-
# Persist engine
|
|
1175
|
+
# Persist engine settings into the encrypted pwn.yaml vault.
|
|
1013
1176
|
#{self}.persist_ai_selection(
|
|
1014
1177
|
engine: 'required - engine name to store as ai.active',
|
|
1015
|
-
model: '
|
|
1178
|
+
model: 'optional - model id to store for that engine',
|
|
1179
|
+
reasoning_effort: 'optional - accepted effort to merge into that engine only',
|
|
1180
|
+
system_role_content: 'optional - exact accepted role to merge into that engine only'
|
|
1016
1181
|
)
|
|
1017
1182
|
|
|
1018
1183
|
# Run /cron locally without Loop.run.
|