Module: Pikuri::Os::LocalSearch
- Defined in:
- lib/pikuri/os/local_search.rb
Overview
Backend wrapper over GNOME's localsearch CLI (the desktop file
index, 2024 rename of tracker3 / tracker-miners). Not a
Tool — the shared, stateless seam behind
fileindex_search / fileindex_read, so localsearch-specific
knowledge (argv, parsing, json-ld extraction, binary probe) lives in
one place.
localsearch search returns at most one match snippet per file
from a possibly-stale index. LocalSearch.search returns exactly those
one-per-file hits and the tools tell the LLM to read the file for the
rest — enumerating every per-file hit would need a stemmer (the index
is stemmed, "mouse" matches "mice"), deliberately out of scope.
PDFs and office docs come for free: the index extracted their text at
index time, so LocalSearch.read_text returns it via info -c with no
extractor dependency here.
Defined Under Namespace
Classes: CommandError
Constant Summary collapse
- BINARY =
Returns the localsearch CLI.
'localsearch'- LIMITATIONS =
Plain-English note of this backend's structural blind spots, appended to the fileindex tools' LLM description so the model knows when to fall back to a filesystem text/regex search. Lives on the backend (a different index ships its own). All three are confirmed against GNOME localsearch (measured; see
pikuri-os/DESIGN.md). <<~LIMITS.chomp Not covered by this index (fall back to a text/regex search over the files for these): - Source code and other developer files are not content-indexed — only their names are. Search their text directly. - Nothing inside a git checkout is indexed (any folder containing a .git). Search those files directly. - No folder scoping: it ranks across your whole home at once, so a common word confined to one project may stay buried below the results shown. Prefer distinctive words, or search that folder's text directly. LIMITS
- ANSI_SGR =
Returns one ANSI SGR escape (color/bold), stripped from snippets before they reach the LLM.
/\e\[[0-9;]*m/
Class Method Summary collapse
-
.available? ⇒ Boolean
Non-raising presence probe — lets a caller wire the file-index tools only when localsearch is installed and degrade gracefully otherwise, rather than failing the whole agent.
-
.check_binaries! ⇒ Object
Verify localsearch is reachable; raise loudly otherwise.
-
.extract_plain_text(raw) ⇒ String?
Find
nie:plainTextContentanywhere in the json-ld document (it nests under@graph, a named graph). -
.label ⇒ String
Short human name for this backend, used by the fileindex tools when prefixing an
"Error: ..."observation. -
.limitations ⇒ String
Uniform accessor mirroring Recoll.limitations.
-
.parse_search_output(raw) ⇒ Array<Hash>
Parse
localsearch search -doutput into hits. -
.read_text(path) ⇒ String?
Full stored plain text of an indexed file, via
localsearch info -c -o json-ld <path>. -
.search(query:, limit:) ⇒ Array<Hash>
Run
localsearch search -d --limit <limit> <query>and parse the results.
Class Method Details
.available? ⇒ Boolean
Non-raising presence probe — lets a caller wire the file-index tools only when localsearch is installed and degrade gracefully otherwise, rather than failing the whole agent.
148 149 150 151 152 |
# File 'lib/pikuri/os/local_search.rb', line 148 def self.available? Pikuri::Subprocess.spawn(BINARY, '--version', chdir: '/').wait.status.success? rescue Errno::ENOENT false end |
.check_binaries! ⇒ Object
Verify localsearch is reachable; raise loudly otherwise.
134 135 136 137 138 139 140 141 |
# File 'lib/pikuri/os/local_search.rb', line 134 def self.check_binaries! result = Pikuri::Subprocess.spawn(BINARY, '--version', chdir: '/').wait return if result.status.success? raise install_hint rescue Errno::ENOENT raise install_hint end |
.extract_plain_text(raw) ⇒ String?
Find nie:plainTextContent anywhere in the json-ld document (it
nests under @graph, a named graph). Lenient: if the raw bytes
don't parse as JSON (a stray diagnostic prefix), retry from the
first {.
126 127 128 129 |
# File 'lib/pikuri/os/local_search.rb', line 126 def self.extract_plain_text(raw) data = parse_json_lenient(raw) data && find_plain_text(data) end |
.label ⇒ String
Returns short human name for this backend, used by the
fileindex tools when prefixing an "Error: ..." observation.
29 |
# File 'lib/pikuri/os/local_search.rb', line 29 def self.label = 'localsearch' |
.limitations ⇒ String
Uniform accessor mirroring Recoll.limitations. A constant here (unlike recoll's runtime method) because this backend's blind spots are the same on every host.
36 |
# File 'lib/pikuri/os/local_search.rb', line 36 def self.limitations = LIMITATIONS |
.parse_search_output(raw) ⇒ Array<Hash>
Parse localsearch search -d output into hits. Each result is a
file://<uri> (urn:…) line followed by an indented snippet line;
the path is percent-decoded and the snippet ANSI-stripped.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/pikuri/os/local_search.rb', line 88 def self.parse_search_output(raw) hits = [] current = nil raw.each_line do |line| encoded = line[%r{\Afile://(\S+)}, 1] if encoded current = { path: decode_file_uri(encoded), snippet: nil } hits << current elsif current && current[:snippet].nil? && !line.strip.empty? current[:snippet] = line.gsub(ANSI_SGR, '').strip end end hits.uniq { |hit| hit[:path] } end |
.read_text(path) ⇒ String?
Full stored plain text of an indexed file, via +localsearch info
-c -o json-ld nil when the path isn't in the index or has no
stored text — the caller turns that into an LLM-facing message.
110 111 112 113 114 115 116 117 |
# File 'lib/pikuri/os/local_search.rb', line 110 def self.read_text(path) result = Pikuri::Subprocess.spawn(BINARY, 'info', '-c', '-o', 'json-ld', path, chdir: '/').wait return nil unless result.status.success? text = extract_plain_text(result.output) text && !text.strip.empty? ? text : nil end |
.search(query:, limit:) ⇒ Array<Hash>
Run localsearch search -d --limit <limit> <query> and parse the
results. -d adds the (ignored) URN and, on the line after each
path, a short snippet with the matched word highlighted; we strip
the ANSI and keep the text.
70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/pikuri/os/local_search.rb', line 70 def self.search(query:, limit:) result = Pikuri::Subprocess.spawn(BINARY, 'search', '-d', '--limit', limit.to_s, query, chdir: '/').wait unless result.status.success? stderr = result.output.strip stderr = "exited #{result.status.exitstatus}" if stderr.empty? raise CommandError, stderr end parse_search_output(result.output) end |