pwn 0.5.627 → 0.5.628
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/.rubocop_todo.yml +20 -30
- data/Gemfile +1 -1
- data/README.md +2 -2
- data/lib/pwn/ai/agent/dispatch.rb +76 -4
- data/lib/pwn/ai/agent/learning.rb +124 -1
- data/lib/pwn/ai/agent/loop.rb +104 -12
- data/lib/pwn/ai/agent/metrics.rb +60 -27
- data/lib/pwn/ai/agent/prompt_builder.rb +63 -12
- data/lib/pwn/ai/agent/reflect.rb +41 -7
- data/lib/pwn/ai/agent/registry.rb +69 -4
- data/lib/pwn/ai/ollama.rb +39 -10
- data/lib/pwn/config.rb +11 -0
- data/lib/pwn/ffi/liquid.rb +179 -0
- data/lib/pwn/memory.rb +15 -0
- data/lib/pwn/memory_index.rb +233 -0
- data/lib/pwn/sdr/decoder/bluetooth.rb +141 -75
- data/lib/pwn/sdr/decoder/dect.rb +89 -63
- data/lib/pwn/sdr/decoder/dsp.rb +443 -0
- data/lib/pwn/sdr/decoder/gps.rb +93 -73
- data/lib/pwn/sdr/decoder/gsm.rb +167 -63
- data/lib/pwn/sdr/decoder/lora.rb +118 -65
- data/lib/pwn/sdr/decoder/lte.rb +188 -65
- data/lib/pwn/sdr/decoder/p25.rb +83 -67
- data/lib/pwn/sdr/decoder/zigbee.rb +140 -67
- data/lib/pwn/version.rb +1 -1
- data/lib/pwn.rb +1 -0
- data/spec/lib/pwn/memory_index_spec.rb +15 -0
- data/third_party/pwn_rdoc.jsonl +65 -8
- metadata +6 -4
data/lib/pwn/ffi/liquid.rb
CHANGED
|
@@ -65,6 +65,36 @@ module PWN
|
|
|
65
65
|
attach_function :firfilt_rrrf_execute_block,
|
|
66
66
|
%i[pointer pointer uint pointer],
|
|
67
67
|
:int
|
|
68
|
+
|
|
69
|
+
# ── msresamp_crcf (complex arbitrary resampler) ───────────────
|
|
70
|
+
attach_function :msresamp_crcf_create, %i[float float], :pointer
|
|
71
|
+
attach_function :msresamp_crcf_destroy, [:pointer], :int
|
|
72
|
+
attach_function :msresamp_crcf_execute,
|
|
73
|
+
%i[pointer pointer uint pointer pointer],
|
|
74
|
+
:int
|
|
75
|
+
|
|
76
|
+
# ── nco_crcf (numerically-controlled oscillator / mixer) ──────
|
|
77
|
+
LIQUID_NCO = 0
|
|
78
|
+
LIQUID_VCO = 1
|
|
79
|
+
attach_function :nco_crcf_create, [:int], :pointer
|
|
80
|
+
attach_function :nco_crcf_destroy, [:pointer], :int
|
|
81
|
+
attach_function :nco_crcf_set_frequency, %i[pointer float], :int
|
|
82
|
+
attach_function :nco_crcf_mix_block_down,
|
|
83
|
+
%i[pointer pointer pointer uint],
|
|
84
|
+
:int
|
|
85
|
+
|
|
86
|
+
# ── gmskdem (GMSK demodulator: k samp/sym → bit) ──────────────
|
|
87
|
+
attach_function :gmskdem_create, %i[uint uint float], :pointer
|
|
88
|
+
attach_function :gmskdem_destroy, [:pointer], :int
|
|
89
|
+
attach_function :gmskdem_reset, [:pointer], :int
|
|
90
|
+
attach_function :gmskdem_demodulate,
|
|
91
|
+
%i[pointer pointer pointer],
|
|
92
|
+
:int
|
|
93
|
+
|
|
94
|
+
# ── fskdem (M-FSK demodulator: k samp/sym → symbol) ───────────
|
|
95
|
+
attach_function :fskdem_create, %i[uint uint float uint], :pointer
|
|
96
|
+
attach_function :fskdem_destroy, [:pointer], :int
|
|
97
|
+
attach_function :fskdem_demodulate, %i[pointer pointer], :uint
|
|
68
98
|
end
|
|
69
99
|
|
|
70
100
|
# Supported Method Parameters::
|
|
@@ -210,6 +240,151 @@ module PWN
|
|
|
210
240
|
end
|
|
211
241
|
end
|
|
212
242
|
|
|
243
|
+
# Supported Method Parameters::
|
|
244
|
+
# out = PWN::FFI::Liquid.resample_iq(
|
|
245
|
+
# iq: 'required - interleaved I/Q Array<Float>',
|
|
246
|
+
# rate: 'required - output/input ratio (>0)',
|
|
247
|
+
# as_db: 'optional - stop-band attenuation dB (default 60.0)'
|
|
248
|
+
# )
|
|
249
|
+
# Returns interleaved Array<Float> [I0,Q0,…] resampled by `rate`.
|
|
250
|
+
|
|
251
|
+
public_class_method def self.resample_iq(opts = {})
|
|
252
|
+
raise 'ERROR: libliquid not available' unless available?
|
|
253
|
+
|
|
254
|
+
iq = opts[:iq]
|
|
255
|
+
rate = opts[:rate].to_f
|
|
256
|
+
as_db = (opts[:as_db] || 60.0).to_f
|
|
257
|
+
n = iq.length / 2
|
|
258
|
+
raise 'ERROR: rate must be > 0' unless rate.positive?
|
|
259
|
+
return iq.dup if n.zero?
|
|
260
|
+
|
|
261
|
+
q = msresamp_crcf_create(rate, as_db)
|
|
262
|
+
raise 'ERROR: msresamp_crcf_create failed' if q.null?
|
|
263
|
+
|
|
264
|
+
begin
|
|
265
|
+
ny_max = [1, (2 + (2 * rate * n)).ceil].max
|
|
266
|
+
in_ptr = float_ptr(iq.first(n * 2))
|
|
267
|
+
out_ptr = PubFFI::MemoryPointer.new(:float, ny_max * 2)
|
|
268
|
+
ny_ptr = PubFFI::MemoryPointer.new(:uint, 1)
|
|
269
|
+
rc = msresamp_crcf_execute(q, in_ptr, n, out_ptr, ny_ptr)
|
|
270
|
+
raise "ERROR: msresamp_crcf_execute rc=#{rc}" unless rc.zero?
|
|
271
|
+
|
|
272
|
+
ny = ny_ptr.read_uint
|
|
273
|
+
out_ptr.read_array_of_float(ny * 2)
|
|
274
|
+
ensure
|
|
275
|
+
msresamp_crcf_destroy(q)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Supported Method Parameters::
|
|
280
|
+
# out = PWN::FFI::Liquid.mix_down(
|
|
281
|
+
# iq: 'required - interleaved I/Q Array<Float>',
|
|
282
|
+
# freq: 'required - normalised angular freq (rad/sample, i.e. 2π·f/fs)'
|
|
283
|
+
# )
|
|
284
|
+
# Returns interleaved Array<Float> shifted down by `freq`.
|
|
285
|
+
|
|
286
|
+
public_class_method def self.mix_down(opts = {})
|
|
287
|
+
raise 'ERROR: libliquid not available' unless available?
|
|
288
|
+
|
|
289
|
+
iq = opts[:iq]
|
|
290
|
+
freq = opts[:freq].to_f
|
|
291
|
+
n = iq.length / 2
|
|
292
|
+
return [] if n.zero?
|
|
293
|
+
|
|
294
|
+
q = nco_crcf_create(LIQUID_NCO)
|
|
295
|
+
raise 'ERROR: nco_crcf_create failed' if q.null?
|
|
296
|
+
|
|
297
|
+
begin
|
|
298
|
+
nco_crcf_set_frequency(q, freq)
|
|
299
|
+
in_ptr = float_ptr(iq.first(n * 2))
|
|
300
|
+
out_ptr = PubFFI::MemoryPointer.new(:float, n * 2)
|
|
301
|
+
rc = nco_crcf_mix_block_down(q, in_ptr, out_ptr, n)
|
|
302
|
+
raise "ERROR: nco_crcf_mix_block_down rc=#{rc}" unless rc.zero?
|
|
303
|
+
|
|
304
|
+
out_ptr.read_array_of_float(n * 2)
|
|
305
|
+
ensure
|
|
306
|
+
nco_crcf_destroy(q)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Supported Method Parameters::
|
|
311
|
+
# bits = PWN::FFI::Liquid.gmsk_demod(
|
|
312
|
+
# iq: 'required - interleaved I/Q Array<Float>, length = k·sym·2',
|
|
313
|
+
# sps: 'required - samples per symbol (integer ≥ 2)',
|
|
314
|
+
# m: 'optional - filter delay (default 3)',
|
|
315
|
+
# bt: 'optional - Gaussian BT (default 0.35)'
|
|
316
|
+
# )
|
|
317
|
+
# Returns Array<0|1> (one bit per symbol, floor(n/sps) bits).
|
|
318
|
+
|
|
319
|
+
public_class_method def self.gmsk_demod(opts = {})
|
|
320
|
+
raise 'ERROR: libliquid not available' unless available?
|
|
321
|
+
|
|
322
|
+
iq = opts[:iq]
|
|
323
|
+
k = opts[:sps].to_i
|
|
324
|
+
m = (opts[:m] || 3).to_i
|
|
325
|
+
bt = (opts[:bt] || 0.35).to_f
|
|
326
|
+
raise 'ERROR: sps must be ≥ 2' if k < 2
|
|
327
|
+
|
|
328
|
+
n = iq.length / 2
|
|
329
|
+
nsym = n / k
|
|
330
|
+
return [] if nsym.zero?
|
|
331
|
+
|
|
332
|
+
q = gmskdem_create(k, m, bt)
|
|
333
|
+
raise 'ERROR: gmskdem_create failed' if q.null?
|
|
334
|
+
|
|
335
|
+
begin
|
|
336
|
+
in_ptr = float_ptr(iq.first(nsym * k * 2))
|
|
337
|
+
bit_ptr = PubFFI::MemoryPointer.new(:uint, 1)
|
|
338
|
+
bits = Array.new(nsym)
|
|
339
|
+
i = 0
|
|
340
|
+
while i < nsym
|
|
341
|
+
gmskdem_demodulate(q, in_ptr + (i * k * 2 * 4), bit_ptr)
|
|
342
|
+
bits[i] = bit_ptr.read_uint & 1
|
|
343
|
+
i += 1
|
|
344
|
+
end
|
|
345
|
+
bits
|
|
346
|
+
ensure
|
|
347
|
+
gmskdem_destroy(q)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Supported Method Parameters::
|
|
352
|
+
# syms = PWN::FFI::Liquid.mfsk_demod(
|
|
353
|
+
# iq: 'required - interleaved I/Q Array<Float>',
|
|
354
|
+
# m: 'required - bits per symbol (1=2FSK, 2=4FSK, …)',
|
|
355
|
+
# sps: 'required - samples per symbol (integer ≥ 2^m)',
|
|
356
|
+
# bw: 'optional - normalised bandwidth 0..0.5 (default 0.25)'
|
|
357
|
+
# )
|
|
358
|
+
# Returns Array<Integer> of demodulated symbols (0..2^m-1).
|
|
359
|
+
|
|
360
|
+
public_class_method def self.mfsk_demod(opts = {})
|
|
361
|
+
raise 'ERROR: libliquid not available' unless available?
|
|
362
|
+
|
|
363
|
+
iq = opts[:iq]
|
|
364
|
+
mb = opts[:m].to_i
|
|
365
|
+
k = opts[:sps].to_i
|
|
366
|
+
bw = (opts[:bw] || 0.25).to_f
|
|
367
|
+
n = iq.length / 2
|
|
368
|
+
nsym = n / k
|
|
369
|
+
return [] if nsym.zero?
|
|
370
|
+
|
|
371
|
+
q = fskdem_create(mb, k, bw, 0)
|
|
372
|
+
raise 'ERROR: fskdem_create failed' if q.null?
|
|
373
|
+
|
|
374
|
+
begin
|
|
375
|
+
in_ptr = float_ptr(iq.first(nsym * k * 2))
|
|
376
|
+
syms = Array.new(nsym)
|
|
377
|
+
i = 0
|
|
378
|
+
while i < nsym
|
|
379
|
+
syms[i] = fskdem_demodulate(q, in_ptr + (i * k * 2 * 4))
|
|
380
|
+
i += 1
|
|
381
|
+
end
|
|
382
|
+
syms
|
|
383
|
+
ensure
|
|
384
|
+
fskdem_destroy(q)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
213
388
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
214
389
|
|
|
215
390
|
public_class_method def self.authors
|
|
@@ -225,6 +400,10 @@ module PWN
|
|
|
225
400
|
#{self}.resample(samples:, rate:, as_db: 60.0) # arbitrary rate
|
|
226
401
|
#{self}.fir_kaiser(samples:, length: 51, fc: 0.1, as_db: 60.0)
|
|
227
402
|
#{self}.dc_block(samples:, m: 7, as_db: 60.0)
|
|
403
|
+
#{self}.resample_iq(iq:, rate:, as_db: 60.0) # complex resample
|
|
404
|
+
#{self}.mix_down(iq:, freq:) # NCO mix (rad/samp)
|
|
405
|
+
#{self}.gmsk_demod(iq:, sps:, m: 3, bt: 0.35) # I/Q → bits
|
|
406
|
+
#{self}.mfsk_demod(iq:, m:, sps:, bw: 0.25) # I/Q → symbols
|
|
228
407
|
|
|
229
408
|
#{self}.authors
|
|
230
409
|
"
|
data/lib/pwn/memory.rb
CHANGED
|
@@ -82,6 +82,20 @@ module PWN
|
|
|
82
82
|
results.to_a.first(limit).to_h
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
# Supported Method Parameters::
|
|
86
|
+
# hits = PWN::Memory.recall_semantic(query: 'nmap sweep', limit: 6)
|
|
87
|
+
#
|
|
88
|
+
# Relevance-ranked recall via PWN::MemoryIndex (local Ollama embeddings
|
|
89
|
+
# + cosine over ~/.pwn/memory.idx). Falls back to substring .recall
|
|
90
|
+
# when no embedding backend is configured.
|
|
91
|
+
public_class_method def self.recall_semantic(opts = {})
|
|
92
|
+
return recall(query: opts[:query], limit: opts[:limit]) unless defined?(PWN::MemoryIndex) && PWN::MemoryIndex.available?
|
|
93
|
+
|
|
94
|
+
PWN::MemoryIndex.recall_semantic(query: opts[:query], limit: opts[:limit])
|
|
95
|
+
rescue StandardError
|
|
96
|
+
recall(query: opts[:query], limit: opts[:limit])
|
|
97
|
+
end
|
|
98
|
+
|
|
85
99
|
# Supported Method Parameters::
|
|
86
100
|
# PWN::Memory.forget(key: :some_key)
|
|
87
101
|
public_class_method def self.forget(opts = {}) # rubocop:disable Naming/PredicateMethod
|
|
@@ -127,6 +141,7 @@ module PWN
|
|
|
127
141
|
mem = PWN::Memory.load
|
|
128
142
|
PWN::Memory.remember(key: :user_prefers_ruby, value: 'Always prefer pure Ruby + RestClient patterns', category: :preference)
|
|
129
143
|
facts = PWN::Memory.recall(query: 'recon', category: :fact, limit: 10)
|
|
144
|
+
hits = PWN::Memory.recall_semantic(query: 'recon', limit: 6) # embedding-ranked
|
|
130
145
|
PWN::Memory.forget(key: :some_key)
|
|
131
146
|
PWN::Memory.clear
|
|
132
147
|
context_str = PWN::Memory.to_context
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'digest'
|
|
6
|
+
|
|
7
|
+
module PWN
|
|
8
|
+
# PWN::MemoryIndex is a lightweight local embedding index over
|
|
9
|
+
# PWN::Memory (~/.pwn/memory.json) so PromptBuilder can inject the N
|
|
10
|
+
# MOST-RELEVANT memories for the current request instead of the N
|
|
11
|
+
# newest. Embeddings come from the local Ollama instance
|
|
12
|
+
# (PWN::Env[:ai][:ollama][:embed_model], default 'nomic-embed-text') via
|
|
13
|
+
# its native /api/embed endpoint — everything stays on-box.
|
|
14
|
+
#
|
|
15
|
+
# Index layout (~/.pwn/memory.idx):
|
|
16
|
+
# { "<key>": { "sha": "<sha16 of value>", "vec": [Float,…] }, … }
|
|
17
|
+
#
|
|
18
|
+
# Rebuilds are incremental: only entries whose value-sha changed are
|
|
19
|
+
# (re)embedded, so a warm index costs one embed call (the query).
|
|
20
|
+
module MemoryIndex
|
|
21
|
+
INDEX_FILE = File.join(Dir.home, '.pwn', 'memory.idx')
|
|
22
|
+
DEFAULT_EMBED_MODEL = 'nomic-embed-text'
|
|
23
|
+
|
|
24
|
+
# Supported Method Parameters::
|
|
25
|
+
# bool = PWN::MemoryIndex.available?
|
|
26
|
+
#
|
|
27
|
+
# True when a local Ollama base_uri is configured. All public methods
|
|
28
|
+
# degrade to substring recall when this is false.
|
|
29
|
+
|
|
30
|
+
public_class_method def self.available?
|
|
31
|
+
!ollama_base.nil?
|
|
32
|
+
rescue StandardError
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Supported Method Parameters::
|
|
37
|
+
# hits = PWN::MemoryIndex.recall_semantic(
|
|
38
|
+
# query: 'required - user request / search text',
|
|
39
|
+
# limit: 'optional - top-K by cosine similarity (default 6)'
|
|
40
|
+
# )
|
|
41
|
+
#
|
|
42
|
+
# Returns [{ key:, value:, category:, timestamp:, score: }, …]
|
|
43
|
+
# (newest-first Memory.recall shape + :score) or falls back to
|
|
44
|
+
# PWN::Memory.recall(query:) when embedding is unavailable.
|
|
45
|
+
|
|
46
|
+
public_class_method def self.recall_semantic(opts = {})
|
|
47
|
+
query = opts[:query].to_s
|
|
48
|
+
limit = (opts[:limit] || 6).to_i
|
|
49
|
+
mem = PWN::Memory.load
|
|
50
|
+
return [] if mem.empty? || query.strip.empty?
|
|
51
|
+
|
|
52
|
+
qv = embed(texts: [query]).first
|
|
53
|
+
return fallback(query: query, limit: limit) unless qv
|
|
54
|
+
|
|
55
|
+
idx = refresh(mem: mem)
|
|
56
|
+
scored = mem.map do |k, v|
|
|
57
|
+
vec = idx.dig(k, :vec)
|
|
58
|
+
next unless vec
|
|
59
|
+
|
|
60
|
+
{ key: k, value: v[:value], category: v[:category], timestamp: v[:timestamp], score: cosine(a: qv, b: vec) }
|
|
61
|
+
end
|
|
62
|
+
scored.compact.sort_by { |h| -h[:score] }.first(limit)
|
|
63
|
+
rescue StandardError
|
|
64
|
+
fallback(query: query, limit: limit)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Supported Method Parameters::
|
|
68
|
+
# ctx = PWN::MemoryIndex.to_context(query:, limit: 6)
|
|
69
|
+
#
|
|
70
|
+
# Drop-in replacement for PWN::Memory.to_context that ranks by
|
|
71
|
+
# relevance to +query+ instead of insertion order. Emitted format is
|
|
72
|
+
# identical so PromptBuilder needs no special-casing.
|
|
73
|
+
|
|
74
|
+
public_class_method def self.to_context(opts = {})
|
|
75
|
+
hits = recall_semantic(query: opts[:query], limit: opts[:limit] || 6)
|
|
76
|
+
return PWN::Memory.to_context(limit: opts[:limit] || 6) if hits.empty?
|
|
77
|
+
|
|
78
|
+
ctx = "\n\nPERSISTENT MEMORY (relevance-ranked for this request - use PWN::Memory.remember to store new ones):\n"
|
|
79
|
+
hits.each do |h|
|
|
80
|
+
ctx += "- #{h[:key]} [#{h[:category]} @ #{h[:timestamp]}]: #{h[:value].to_s[0, 300]}\n"
|
|
81
|
+
end
|
|
82
|
+
ctx
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Supported Method Parameters::
|
|
86
|
+
# idx = PWN::MemoryIndex.refresh(mem: 'optional - preloaded PWN::Memory hash')
|
|
87
|
+
#
|
|
88
|
+
# Incrementally (re)embed changed entries and prune deleted keys.
|
|
89
|
+
# Returns the in-memory index Hash keyed by memory key (Symbol).
|
|
90
|
+
|
|
91
|
+
public_class_method def self.refresh(opts = {})
|
|
92
|
+
mem = opts[:mem] || PWN::Memory.load
|
|
93
|
+
idx = load_index
|
|
94
|
+
idx.delete_if { |k, _| !mem.key?(k) }
|
|
95
|
+
|
|
96
|
+
todo = mem.reject { |k, v| idx.dig(k, :sha) == sha(text: v[:value].to_s) }
|
|
97
|
+
unless todo.empty?
|
|
98
|
+
vecs = embed(texts: todo.values.map { |v| "#{v[:category]}: #{v[:value]}" })
|
|
99
|
+
todo.keys.each_with_index do |k, i|
|
|
100
|
+
next unless vecs[i]
|
|
101
|
+
|
|
102
|
+
idx[k] = { sha: sha(text: mem[k][:value].to_s), vec: vecs[i] }
|
|
103
|
+
end
|
|
104
|
+
save_index(idx: idx)
|
|
105
|
+
end
|
|
106
|
+
idx
|
|
107
|
+
rescue StandardError
|
|
108
|
+
idx || {}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Supported Method Parameters::
|
|
112
|
+
# vecs = PWN::MemoryIndex.embed(texts: ['a', 'b'])
|
|
113
|
+
#
|
|
114
|
+
# POST /api/embed on the local Ollama; returns Array<Array<Float>> (one
|
|
115
|
+
# vector per input, nil on per-item failure). Batches of 32.
|
|
116
|
+
|
|
117
|
+
public_class_method def self.embed(opts = {})
|
|
118
|
+
texts = Array(opts[:texts]).map(&:to_s)
|
|
119
|
+
base = ollama_base
|
|
120
|
+
return Array.new(texts.length) unless base && !texts.empty?
|
|
121
|
+
|
|
122
|
+
model = (PWN::Env.dig(:ai, :ollama, :embed_model) if defined?(PWN::Env)) || DEFAULT_EMBED_MODEL
|
|
123
|
+
browser = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
|
|
124
|
+
rest = browser[:browser]::Request
|
|
125
|
+
headers = { content_type: 'application/json; charset=UTF-8' }
|
|
126
|
+
token = PWN::Env.dig(:ai, :ollama, :key) if defined?(PWN::Env)
|
|
127
|
+
headers[:authorization] = "Bearer #{token}" if token
|
|
128
|
+
|
|
129
|
+
out = []
|
|
130
|
+
texts.each_slice(32) do |batch|
|
|
131
|
+
resp = rest.execute(
|
|
132
|
+
method: :post,
|
|
133
|
+
url: "#{base}/ollama/api/embed",
|
|
134
|
+
headers: headers,
|
|
135
|
+
payload: { model: model, input: batch }.to_json,
|
|
136
|
+
verify_ssl: false,
|
|
137
|
+
timeout: 120
|
|
138
|
+
)
|
|
139
|
+
j = JSON.parse(resp, symbolize_names: true)
|
|
140
|
+
out.concat(Array(j[:embeddings]))
|
|
141
|
+
end
|
|
142
|
+
out
|
|
143
|
+
rescue StandardError
|
|
144
|
+
Array.new(texts.length)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Supported Method Parameters::
|
|
148
|
+
# PWN::MemoryIndex.reset
|
|
149
|
+
|
|
150
|
+
public_class_method def self.reset
|
|
151
|
+
FileUtils.rm_f(INDEX_FILE)
|
|
152
|
+
{}
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# -----------------------------------------------------------------
|
|
156
|
+
# privates
|
|
157
|
+
# -----------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
private_class_method def self.load_index
|
|
160
|
+
return {} unless File.exist?(INDEX_FILE)
|
|
161
|
+
|
|
162
|
+
JSON.parse(File.read(INDEX_FILE), symbolize_names: true)
|
|
163
|
+
rescue StandardError
|
|
164
|
+
{}
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private_class_method def self.save_index(opts = {})
|
|
168
|
+
FileUtils.mkdir_p(File.dirname(INDEX_FILE))
|
|
169
|
+
File.write(INDEX_FILE, JSON.generate(opts[:idx] || {}))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private_class_method def self.cosine(opts = {})
|
|
173
|
+
a = opts[:a]
|
|
174
|
+
b = opts[:b]
|
|
175
|
+
return 0.0 unless a && b && a.length == b.length
|
|
176
|
+
|
|
177
|
+
dot = na = nb = 0.0
|
|
178
|
+
a.each_with_index do |x, i|
|
|
179
|
+
y = b[i]
|
|
180
|
+
dot += x * y
|
|
181
|
+
na += x * x
|
|
182
|
+
nb += y * y
|
|
183
|
+
end
|
|
184
|
+
return 0.0 if na.zero? || nb.zero?
|
|
185
|
+
|
|
186
|
+
dot / (Math.sqrt(na) * Math.sqrt(nb))
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private_class_method def self.sha(opts = {})
|
|
190
|
+
Digest::SHA256.hexdigest(opts[:text].to_s)[0, 16]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private_class_method def self.ollama_base
|
|
194
|
+
return nil unless defined?(PWN::Env) && PWN::Env.is_a?(Hash)
|
|
195
|
+
|
|
196
|
+
b = PWN::Env.dig(:ai, :ollama, :base_uri).to_s
|
|
197
|
+
b.start_with?('http') ? b.chomp('/') : nil
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private_class_method def self.fallback(opts = {})
|
|
201
|
+
PWN::Memory.recall(query: opts[:query], limit: opts[:limit]).map do |k, v|
|
|
202
|
+
{ key: k, value: v[:value], category: v[:category], timestamp: v[:timestamp], score: 0.0 }
|
|
203
|
+
end
|
|
204
|
+
rescue StandardError
|
|
205
|
+
[]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
209
|
+
|
|
210
|
+
public_class_method def self.authors
|
|
211
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Display Usage for this Module
|
|
215
|
+
|
|
216
|
+
public_class_method def self.help
|
|
217
|
+
puts <<~USAGE
|
|
218
|
+
USAGE:
|
|
219
|
+
PWN::MemoryIndex.available?
|
|
220
|
+
PWN::MemoryIndex.recall_semantic(query: 'nmap sweep', limit: 6)
|
|
221
|
+
PWN::MemoryIndex.to_context(query: 'nmap sweep', limit: 6) # PromptBuilder drop-in
|
|
222
|
+
PWN::MemoryIndex.refresh # incremental (re)embed
|
|
223
|
+
PWN::MemoryIndex.embed(texts: ['a', 'b']) # raw vectors
|
|
224
|
+
PWN::MemoryIndex.reset
|
|
225
|
+
|
|
226
|
+
Config:
|
|
227
|
+
PWN::Env[:ai][:ollama][:embed_model] = 'nomic-embed-text' # or bge-m3, mxbai-embed-large
|
|
228
|
+
|
|
229
|
+
#{self}.authors
|
|
230
|
+
USAGE
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|