Initial sandbox buildout - Structure: projects, docs, inbox, archive, templates, scripts, tools - Tools: search.py, inbox-processor.py, daily-digest.py - Shell aliases and bashrc integration - Templates for projects and notes - MEMORY.md, TASKS.md, STRUCTURE.md - tmux config
This commit is contained in:
81
tools/search.py
Executable file
81
tools/search.py
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple workspace search tool.
|
||||
Searches markdown files and returns ranked results.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
WORKSPACE = Path("/home/wdjones/.openclaw/workspace")
|
||||
EXTENSIONS = {'.md', '.txt', '.sh', '.py', '.json'}
|
||||
|
||||
def search(query: str, max_results: int = 10) -> list:
|
||||
"""Search workspace for query, return list of (file, line_num, line, score)."""
|
||||
results = []
|
||||
query_lower = query.lower()
|
||||
query_words = query_lower.split()
|
||||
|
||||
for root, dirs, files in os.walk(WORKSPACE):
|
||||
# Skip hidden directories
|
||||
dirs[:] = [d for d in dirs if not d.startswith('.')]
|
||||
|
||||
for fname in files:
|
||||
fpath = Path(root) / fname
|
||||
if fpath.suffix not in EXTENSIONS:
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
for i, line in enumerate(f, 1):
|
||||
line_lower = line.lower()
|
||||
|
||||
# Calculate score
|
||||
score = 0
|
||||
for word in query_words:
|
||||
if word in line_lower:
|
||||
score += 1
|
||||
# Bonus for exact phrase
|
||||
if query_lower in line_lower:
|
||||
score += 2
|
||||
# Bonus for word boundaries
|
||||
if re.search(rf'\b{re.escape(word)}\b', line_lower):
|
||||
score += 1
|
||||
|
||||
if score > 0:
|
||||
rel_path = fpath.relative_to(WORKSPACE)
|
||||
results.append((str(rel_path), i, line.strip(), score))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# Sort by score descending
|
||||
results.sort(key=lambda x: -x[3])
|
||||
return results[:max_results]
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: search.py <query> [max_results]")
|
||||
sys.exit(1)
|
||||
|
||||
query = sys.argv[1]
|
||||
max_results = int(sys.argv[2]) if len(sys.argv) > 2 else 10
|
||||
|
||||
results = search(query, max_results)
|
||||
|
||||
if not results:
|
||||
print(f"No results for: {query}")
|
||||
return
|
||||
|
||||
print(f"Results for: {query}\n")
|
||||
for fpath, line_num, line, score in results:
|
||||
# Truncate long lines
|
||||
display = line[:80] + "..." if len(line) > 80 else line
|
||||
print(f"[{fpath}:{line_num}] {display}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user