appydave-tools 0.78.0 → 0.80.0
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/CHANGELOG.md +19 -0
- data/bin/llm_context.rb +19 -2
- data/bin/query_brain.rb +65 -0
- data/bin/query_omi.rb +71 -0
- data/exe/query_brain +7 -0
- data/exe/query_omi +7 -0
- data/lib/appydave/tools/brain_context/brain_finder.rb +165 -0
- data/lib/appydave/tools/brain_context/omi_finder.rb +153 -0
- data/lib/appydave/tools/brain_context/options.rb +57 -0
- data/lib/appydave/tools/llm_context/file_collector.rb +31 -38
- data/lib/appydave/tools/llm_context/options.rb +4 -0
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools.rb +4 -0
- data/package.json +1 -1
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb0b711e430f2082be55712eb0c3e630726bcc77628dc8ab144d72f27fc05bd7
|
|
4
|
+
data.tar.gz: 8fc9be5413601f6da5cccd649d324578df676efb842b138623998a1f6dd7b676
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7527c25930ba5fb35206e6321fea074f12593a9188ed4b7d51f1f832f01f60a9929336b8670bf48bea0f68eeba0df150ecb542ea244be899334781ef432a7a1
|
|
7
|
+
data.tar.gz: 051f6c24d48fa6caa2ac342436a1105403d7524e871ee248996b772e0e16aafde612cd33386d4b6bba92b5f1f0c2e2f6cf61406e9f76db5a42e55d367031c642
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# [0.79.0](https://github.com/appydave/appydave-tools/compare/v0.78.0...v0.79.0) (2026-04-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add --stdin support to llm_context for piping file paths ([8890cfe](https://github.com/appydave/appydave-tools/commit/8890cfebb80cfda81b679da3e52a185bbb1c9141))
|
|
7
|
+
|
|
8
|
+
# [0.78.0](https://github.com/appydave/appydave-tools/compare/v0.77.7...v0.78.0) (2026-04-03)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* resolve rubocop block length violation in llm_context CLI ([305b65a](https://github.com/appydave/appydave-tools/commit/305b65aca92204b877ed25841a8041e9c5a08683))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* update gpt_gatherer to llm_gatherer ([acfbf1c](https://github.com/appydave/appydave-tools/commit/acfbf1c09d40b45d2ca211d5f3921af1127f068c))
|
|
19
|
+
|
|
1
20
|
## [0.77.7](https://github.com/appydave/appydave-tools/compare/v0.77.6...v0.77.7) (2026-03-20)
|
|
2
21
|
|
|
3
22
|
|
data/bin/llm_context.rb
CHANGED
|
@@ -80,6 +80,10 @@ def setup_options(opts, options)
|
|
|
80
80
|
opts.on('-t', '--tokens', 'Show estimated token count after collecting context') do
|
|
81
81
|
options.show_tokens = true
|
|
82
82
|
end
|
|
83
|
+
|
|
84
|
+
opts.on('--stdin', 'Read file paths from stdin (one per line) instead of using patterns') do
|
|
85
|
+
options.stdin = true
|
|
86
|
+
end
|
|
83
87
|
end
|
|
84
88
|
|
|
85
89
|
def setup_help_sections(opts)
|
|
@@ -96,6 +100,10 @@ def setup_help_sections(opts)
|
|
|
96
100
|
opts.separator ' temp - Write to system temp dir, copy path to clipboard'
|
|
97
101
|
opts.separator ' filename - Write to specified file path'
|
|
98
102
|
opts.separator ''
|
|
103
|
+
opts.separator 'INPUT MODES'
|
|
104
|
+
opts.separator ' Patterns (default): -i <glob> and -e <exclude_glob>'
|
|
105
|
+
opts.separator ' Stdin: --stdin (read file paths from stdin, one per line)'
|
|
106
|
+
opts.separator ''
|
|
99
107
|
opts.separator 'EXAMPLES'
|
|
100
108
|
opts.separator ' # Gather Ruby library code for AI context'
|
|
101
109
|
opts.separator " llm_context -i 'lib/**/*.rb' -e 'spec/**/*' -d"
|
|
@@ -109,6 +117,9 @@ def setup_help_sections(opts)
|
|
|
109
117
|
opts.separator ' # Write to system temp dir and copy path to clipboard'
|
|
110
118
|
opts.separator " llm_context -i 'lib/**/*.rb' -o temp"
|
|
111
119
|
opts.separator ''
|
|
120
|
+
opts.separator ' # Read file paths from stdin'
|
|
121
|
+
opts.separator " find lib -name '*.rb' | llm_context --stdin -o temp"
|
|
122
|
+
opts.separator ''
|
|
112
123
|
opts.separator ' # Generate aider command'
|
|
113
124
|
opts.separator " llm_context -i 'lib/**/*.rb' -f aider -p 'Add logging'"
|
|
114
125
|
opts.separator ''
|
|
@@ -140,10 +151,16 @@ end
|
|
|
140
151
|
|
|
141
152
|
parser.parse!
|
|
142
153
|
|
|
143
|
-
|
|
154
|
+
# Handle stdin file paths
|
|
155
|
+
if options.stdin
|
|
156
|
+
options.file_paths = $stdin.readlines.map(&:chomp).reject(&:empty?)
|
|
157
|
+
options.working_directory = Dir.pwd unless options.working_directory
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if options.include_patterns.empty? && options.exclude_patterns.empty? && options.file_paths.empty?
|
|
144
161
|
script_name = File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME))
|
|
145
162
|
|
|
146
|
-
puts 'No options provided to LLM Context. Please specify patterns to include or exclude.'
|
|
163
|
+
puts 'No options provided to LLM Context. Please specify patterns to include or exclude, or use --stdin.'
|
|
147
164
|
puts "For help, run: #{script_name} --help"
|
|
148
165
|
exit
|
|
149
166
|
end
|
data/bin/query_brain.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
5
|
+
require 'appydave/tools'
|
|
6
|
+
|
|
7
|
+
options = Appydave::Tools::BrainContextOptions.new
|
|
8
|
+
|
|
9
|
+
def setup_options(options)
|
|
10
|
+
OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
|
|
11
|
+
opts.banner = 'Usage: query_brain [options]'
|
|
12
|
+
|
|
13
|
+
opts.on('--brain NAME', 'Query brain by name, alias, or fuzzy match') do |name|
|
|
14
|
+
options.brain_names << name
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
opts.on('--tag TAG', 'Query brains by tag') do |tag|
|
|
18
|
+
options.tags << tag
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
opts.on('--category CAT', 'Query all brains in category') do |cat|
|
|
22
|
+
options.categories << cat
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
opts.on('--activity-min LEVEL', %w[high medium low none],
|
|
26
|
+
'Filter brains by minimum activity level') do |level|
|
|
27
|
+
options.activity_levels << level
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
opts.on('--status STATUS', %w[active stable deprecated],
|
|
31
|
+
'Filter brains by status (default: exclude deprecated)') do |status|
|
|
32
|
+
options.status = status
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
opts.on('--files-only', 'Exclude INDEX.md, only include content files') do
|
|
36
|
+
options.include_index = false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts.on('-d', '--debug [MODE]', %w[none info params debug],
|
|
40
|
+
'Debug output level') do |level|
|
|
41
|
+
options.debug_level = level || 'info'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
opts.on('-v', '--version', 'Show version') do
|
|
45
|
+
puts "query_brain v#{Appydave::Tools::VERSION}"
|
|
46
|
+
exit 0
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
opts.on('-h', '--help', 'Show this message') do
|
|
50
|
+
puts opts
|
|
51
|
+
exit 0
|
|
52
|
+
end
|
|
53
|
+
end.parse!
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
setup_options(options)
|
|
57
|
+
|
|
58
|
+
# Query
|
|
59
|
+
finder = Appydave::Tools::BrainQuery.new(options)
|
|
60
|
+
paths = finder.find
|
|
61
|
+
|
|
62
|
+
# Output file paths, one per line
|
|
63
|
+
paths.each { |p| puts p }
|
|
64
|
+
|
|
65
|
+
exit 0
|
data/bin/query_omi.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
5
|
+
require 'appydave/tools'
|
|
6
|
+
|
|
7
|
+
options = Appydave::Tools::BrainContextOptions.new
|
|
8
|
+
options.omi = true
|
|
9
|
+
|
|
10
|
+
def setup_options(options)
|
|
11
|
+
OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
|
|
12
|
+
opts.banner = 'Usage: query_omi [options]'
|
|
13
|
+
|
|
14
|
+
opts.on('--signal SIGNAL', %w[work life ambient],
|
|
15
|
+
'Filter by signal') do |signal|
|
|
16
|
+
options.omi_signals << signal
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
opts.on('--routing ROUTING',
|
|
20
|
+
'Filter by routing (brain-update, todo-item, personal, til, archive; pipe-delimited)') do |routing|
|
|
21
|
+
options.omi_routings.concat(routing.split('|').map(&:strip))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
opts.on('--activity ACTIVITY',
|
|
25
|
+
'Filter by activity (planning, reviewing, learning, debugging, etc.; pipe-delimited)') do |activity|
|
|
26
|
+
options.omi_activities.concat(activity.split('|').map(&:strip))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
opts.on('--date-from DATE', 'Include files from date (YYYY-MM-DD)') do |date|
|
|
30
|
+
options.date_from = date
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
opts.on('--date-to DATE', 'Include files up to date (YYYY-MM-DD)') do |date|
|
|
34
|
+
options.date_to = date
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
opts.on('--enriched-only', 'Skip raw (non-enriched) transcripts') do
|
|
38
|
+
options.enriched_only = true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
opts.on('--brain NAME', 'Find OMI files mentioning this brain') do |name|
|
|
42
|
+
options.brain_names << name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
opts.on('-d', '--debug [MODE]', %w[none info params debug],
|
|
46
|
+
'Debug output level') do |level|
|
|
47
|
+
options.debug_level = level || 'info'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
opts.on('-v', '--version', 'Show version') do
|
|
51
|
+
puts "query_omi v#{Appydave::Tools::VERSION}"
|
|
52
|
+
exit 0
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
opts.on('-h', '--help', 'Show this message') do
|
|
56
|
+
puts opts
|
|
57
|
+
exit 0
|
|
58
|
+
end
|
|
59
|
+
end.parse!
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
setup_options(options)
|
|
63
|
+
|
|
64
|
+
# Query
|
|
65
|
+
finder = Appydave::Tools::OmiQuery.new(options)
|
|
66
|
+
paths = finder.find
|
|
67
|
+
|
|
68
|
+
# Output file paths, one per line
|
|
69
|
+
paths.each { |p| puts p }
|
|
70
|
+
|
|
71
|
+
exit 0
|
data/exe/query_brain
ADDED
data/exe/query_omi
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Appydave
|
|
6
|
+
module Tools
|
|
7
|
+
# Queries brains-index.json to find brain files
|
|
8
|
+
class BrainQuery
|
|
9
|
+
def initialize(options)
|
|
10
|
+
@options = options
|
|
11
|
+
@index = nil
|
|
12
|
+
@alias_map = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def find
|
|
16
|
+
return [] unless @options.brain_query?
|
|
17
|
+
|
|
18
|
+
load_index!
|
|
19
|
+
paths = []
|
|
20
|
+
|
|
21
|
+
# Handle brain name queries
|
|
22
|
+
@options.brain_names.each do |name|
|
|
23
|
+
paths.concat(find_by_name(name))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Handle tag queries
|
|
27
|
+
@options.tags.each do |tag|
|
|
28
|
+
paths.concat(find_by_tag(tag))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Handle category queries
|
|
32
|
+
@options.categories.each do |category|
|
|
33
|
+
paths.concat(find_by_category(category))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Handle activity level filters
|
|
37
|
+
paths.select! { |p| matches_activity_level?(p) } if @options.activity_levels.any?
|
|
38
|
+
|
|
39
|
+
# Remove duplicates and return
|
|
40
|
+
paths.uniq.sort
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def load_index!
|
|
46
|
+
return if @index
|
|
47
|
+
|
|
48
|
+
index_path = @options.brains_index_path
|
|
49
|
+
unless File.exist?(index_path)
|
|
50
|
+
raise "brains-index.json not found at #{index_path}. Run: python ~/dev/ad/brains/.claude/skills/brain-librarian/scripts/build_brain_index.py build --all ~/dev/ad/brains"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@index = JSON.parse(File.read(index_path))
|
|
54
|
+
build_alias_map!
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def build_alias_map!
|
|
58
|
+
@alias_map = {}
|
|
59
|
+
@index['alias_index']&.each { |alias_name, brain_name| @alias_map[alias_name.downcase] = brain_name }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def find_by_name(name)
|
|
63
|
+
load_index!
|
|
64
|
+
|
|
65
|
+
# Step 1: Exact match on brain key
|
|
66
|
+
brain_entry = find_brain_in_index(name)
|
|
67
|
+
return brain_entries_to_paths([brain_entry]) if brain_entry
|
|
68
|
+
|
|
69
|
+
# Step 2: Alias match
|
|
70
|
+
if @alias_map && @alias_map[name.downcase]
|
|
71
|
+
brain_name = @alias_map[name.downcase]
|
|
72
|
+
brain_entry = find_brain_in_index(brain_name)
|
|
73
|
+
return brain_entries_to_paths([brain_entry]) if brain_entry
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Step 3: Substring match on brain key (case-insensitive)
|
|
77
|
+
matches = []
|
|
78
|
+
@index['categories'].each_value do |category_data|
|
|
79
|
+
category_data['brains'].each do |brain_name, brain_data|
|
|
80
|
+
matches << brain_data if brain_name.downcase.include?(name.downcase)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
return brain_entries_to_paths(matches) if matches.length == 1
|
|
84
|
+
return brain_entries_to_paths(matches) if matches.any?
|
|
85
|
+
|
|
86
|
+
# Not found
|
|
87
|
+
[]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def find_by_tag(tag)
|
|
91
|
+
load_index!
|
|
92
|
+
tag = tag.downcase.gsub('_', '-')
|
|
93
|
+
|
|
94
|
+
brain_names = @index['tag_index']&.[](tag) || []
|
|
95
|
+
brain_entries = []
|
|
96
|
+
|
|
97
|
+
brain_names.each do |brain_name|
|
|
98
|
+
entry = find_brain_in_index(brain_name)
|
|
99
|
+
brain_entries << entry if entry
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
brain_entries_to_paths(brain_entries)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def find_by_category(category)
|
|
106
|
+
load_index!
|
|
107
|
+
category_data = @index['categories'][category] || @index['categories'][category.downcase]
|
|
108
|
+
|
|
109
|
+
return [] unless category_data
|
|
110
|
+
|
|
111
|
+
brain_entries = category_data['brains'].values
|
|
112
|
+
brain_entries_to_paths(brain_entries)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def find_brain_in_index(brain_name)
|
|
116
|
+
@index['categories'].each_value do |category_data|
|
|
117
|
+
return category_data['brains'][brain_name] if category_data['brains'][brain_name]
|
|
118
|
+
end
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def brain_entries_to_paths(brain_entries)
|
|
123
|
+
paths = []
|
|
124
|
+
|
|
125
|
+
brain_entries.each do |entry|
|
|
126
|
+
next unless entry
|
|
127
|
+
|
|
128
|
+
# Add files from files[] array
|
|
129
|
+
entry['files']&.each do |file|
|
|
130
|
+
# File entry is relative to the brain directory
|
|
131
|
+
brain_name = extract_brain_name(entry)
|
|
132
|
+
full_path = File.join(@options.brains_root, brain_name, file)
|
|
133
|
+
paths << full_path if File.exist?(full_path)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Add INDEX.md if requested
|
|
137
|
+
if @options.include_index
|
|
138
|
+
index_path = File.join(@options.brains_root, entry['index_path'])
|
|
139
|
+
paths << index_path if File.exist?(index_path)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
paths
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def extract_brain_name(entry)
|
|
147
|
+
# index_path is like "brain-name/INDEX.md"
|
|
148
|
+
entry['index_path'].split('/')[0]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def matches_activity_level?(file_path)
|
|
152
|
+
# Extract brain name from path
|
|
153
|
+
brain_name = file_path.match(%r{#{Regexp.escape(@options.brains_root)}/([^/]+)})&.[](1)
|
|
154
|
+
return false unless brain_name
|
|
155
|
+
|
|
156
|
+
# Find the brain entry
|
|
157
|
+
entry = find_brain_in_index(brain_name)
|
|
158
|
+
return false unless entry
|
|
159
|
+
|
|
160
|
+
# Check if activity level matches filter
|
|
161
|
+
@options.activity_levels.include?(entry['activity_level'])
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
module Appydave
|
|
6
|
+
module Tools
|
|
7
|
+
# Queries OMI transcript directory by enriched frontmatter
|
|
8
|
+
class OmiQuery
|
|
9
|
+
def initialize(options)
|
|
10
|
+
@options = options
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def find
|
|
14
|
+
return [] unless @options.omi_query?
|
|
15
|
+
|
|
16
|
+
paths = []
|
|
17
|
+
|
|
18
|
+
Dir.glob(File.join(@options.omi_dir, '*.md')).each do |file_path|
|
|
19
|
+
next unless include_file?(file_path)
|
|
20
|
+
|
|
21
|
+
paths << file_path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
paths.sort
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def include_file?(file_path)
|
|
30
|
+
frontmatter = extract_frontmatter(file_path)
|
|
31
|
+
return false unless frontmatter
|
|
32
|
+
|
|
33
|
+
# If enriched-only requested, skip raw files
|
|
34
|
+
return false if @options.enriched_only && (!frontmatter['signal'] || !frontmatter['extraction_summary'])
|
|
35
|
+
|
|
36
|
+
# Apply filters
|
|
37
|
+
return false unless signal_matches?(frontmatter)
|
|
38
|
+
return false unless routing_matches?(frontmatter)
|
|
39
|
+
return false unless activity_matches?(frontmatter)
|
|
40
|
+
return false unless date_matches?(frontmatter)
|
|
41
|
+
return false unless brain_matches?(frontmatter)
|
|
42
|
+
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def extract_frontmatter(file_path)
|
|
47
|
+
content = File.read(file_path, encoding: 'utf-8')
|
|
48
|
+
lines = content.split("\n")
|
|
49
|
+
|
|
50
|
+
return nil unless lines[0] == '---'
|
|
51
|
+
|
|
52
|
+
frontmatter = {}
|
|
53
|
+
i = 1
|
|
54
|
+
while i < lines.length
|
|
55
|
+
line = lines[i]
|
|
56
|
+
break if line == '---'
|
|
57
|
+
|
|
58
|
+
# Parse YAML-like: key: value
|
|
59
|
+
if line.match?(/^[a-z_]+:/)
|
|
60
|
+
key, value = parse_yaml_line(line)
|
|
61
|
+
frontmatter[key] = value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
i += 1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
frontmatter.empty? ? nil : frontmatter
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def parse_yaml_line(line)
|
|
71
|
+
# Handle simple cases: key: value, key: [a, b, c]
|
|
72
|
+
match = line.match(/^([a-z_]+):\s*(.*)$/)
|
|
73
|
+
return [nil, nil] unless match
|
|
74
|
+
|
|
75
|
+
key = match[1]
|
|
76
|
+
value_str = match[2].strip
|
|
77
|
+
|
|
78
|
+
# Parse array [a, b, c]
|
|
79
|
+
if value_str.start_with?('[') && value_str.end_with?(']')
|
|
80
|
+
array_content = value_str[1..-2]
|
|
81
|
+
value = array_content.split(',').map { |v| v.strip.gsub(/^["']|["']$/, '') }
|
|
82
|
+
# Parse quoted string
|
|
83
|
+
elsif (value_str.start_with?('"') && value_str.end_with?('"')) ||
|
|
84
|
+
(value_str.start_with?("'") && value_str.end_with?("'"))
|
|
85
|
+
value = value_str[1..-2]
|
|
86
|
+
# Parse date (YYYY-MM-DD) or other values as-is
|
|
87
|
+
else
|
|
88
|
+
value = value_str
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
[key, value]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def signal_matches?(frontmatter)
|
|
95
|
+
return true if @options.omi_signals.empty?
|
|
96
|
+
|
|
97
|
+
signal = frontmatter['signal']
|
|
98
|
+
return false unless signal
|
|
99
|
+
|
|
100
|
+
@options.omi_signals.include?(signal)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def routing_matches?(frontmatter)
|
|
104
|
+
return true if @options.omi_routings.empty?
|
|
105
|
+
|
|
106
|
+
routing = frontmatter['routing']
|
|
107
|
+
return false unless routing
|
|
108
|
+
|
|
109
|
+
# routing can be pipe-delimited
|
|
110
|
+
routings = routing.split('|').map(&:strip)
|
|
111
|
+
routings.any? { |r| @options.omi_routings.include?(r) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def activity_matches?(frontmatter)
|
|
115
|
+
return true if @options.omi_activities.empty?
|
|
116
|
+
|
|
117
|
+
activity = frontmatter['activity']
|
|
118
|
+
return false unless activity
|
|
119
|
+
|
|
120
|
+
# activity can be pipe-delimited
|
|
121
|
+
activities = activity.split('|').map(&:strip)
|
|
122
|
+
activities.any? { |a| @options.omi_activities.include?(a) }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def date_matches?(frontmatter)
|
|
126
|
+
extracted_at = frontmatter['extracted_at']
|
|
127
|
+
return true if extracted_at.nil? # No date field = include
|
|
128
|
+
|
|
129
|
+
begin
|
|
130
|
+
date = Date.parse(extracted_at)
|
|
131
|
+
rescue StandardError
|
|
132
|
+
return true # Can't parse = include
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
from_ok = @options.date_from.nil? || date >= Date.parse(@options.date_from)
|
|
136
|
+
to_ok = @options.date_to.nil? || date <= Date.parse(@options.date_to)
|
|
137
|
+
|
|
138
|
+
from_ok && to_ok
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def brain_matches?(frontmatter)
|
|
142
|
+
return true if @options.brain_names.empty?
|
|
143
|
+
|
|
144
|
+
matched_brains = frontmatter['matched_brains']
|
|
145
|
+
return false unless matched_brains
|
|
146
|
+
|
|
147
|
+
# matched_brains is an array
|
|
148
|
+
matched_brains_list = matched_brains.is_a?(Array) ? matched_brains : [matched_brains]
|
|
149
|
+
matched_brains_list.any? { |brain| @options.brain_names.include?(brain) }
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appydave
|
|
4
|
+
module Tools
|
|
5
|
+
# Options struct for brain/OMI query tools
|
|
6
|
+
class BrainContextOptions
|
|
7
|
+
attr_accessor :brain_names, :tags, :categories, :activity_levels, :status,
|
|
8
|
+
:omi, :omi_signals, :omi_routings, :omi_activities,
|
|
9
|
+
:date_from, :date_to, :enriched_only,
|
|
10
|
+
:include_index, :output_targets, :formats, :line_limit,
|
|
11
|
+
:debug_level, :dry_run, :tokens, :base_dir, :omi_dir
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@brain_names = []
|
|
15
|
+
@tags = []
|
|
16
|
+
@categories = []
|
|
17
|
+
@activity_levels = []
|
|
18
|
+
@status = 'active' # default: exclude deprecated
|
|
19
|
+
|
|
20
|
+
@omi = false
|
|
21
|
+
@omi_signals = []
|
|
22
|
+
@omi_routings = []
|
|
23
|
+
@omi_activities = []
|
|
24
|
+
@date_from = nil
|
|
25
|
+
@date_to = nil
|
|
26
|
+
@enriched_only = false
|
|
27
|
+
|
|
28
|
+
@include_index = true
|
|
29
|
+
@output_targets = ['clipboard'] # default to clipboard
|
|
30
|
+
@formats = ['content']
|
|
31
|
+
@line_limit = nil
|
|
32
|
+
@debug_level = 'none'
|
|
33
|
+
@dry_run = false
|
|
34
|
+
@tokens = false
|
|
35
|
+
@base_dir = Dir.pwd
|
|
36
|
+
|
|
37
|
+
@omi_dir = File.expand_path('~/dev/raw-intake/omi')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def brains_root
|
|
41
|
+
@brains_root ||= File.expand_path('~/dev/ad/brains')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def brains_index_path
|
|
45
|
+
File.join(brains_root, 'audit', 'brains-index.json')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def brain_query?
|
|
49
|
+
brain_names.any? || tags.any? || categories.any? || activity_levels.any?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def omi_query?
|
|
53
|
+
omi
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -13,6 +13,7 @@ module Appydave
|
|
|
13
13
|
@format = options.format
|
|
14
14
|
@working_directory = File.expand_path(options.working_directory)
|
|
15
15
|
@line_limit = options.line_limit
|
|
16
|
+
@file_paths = options.file_paths
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def build
|
|
@@ -23,6 +24,22 @@ module Appydave
|
|
|
23
24
|
|
|
24
25
|
private
|
|
25
26
|
|
|
27
|
+
def collect_files
|
|
28
|
+
if @file_paths.any?
|
|
29
|
+
# Use file paths directly (from stdin)
|
|
30
|
+
@file_paths.reject { |f| excluded?(f) || File.directory?(f) }
|
|
31
|
+
else
|
|
32
|
+
# Use glob patterns
|
|
33
|
+
files = []
|
|
34
|
+
@include_patterns.each do |pattern|
|
|
35
|
+
Dir.glob(pattern).each do |file_path|
|
|
36
|
+
files << file_path unless excluded?(file_path) || File.directory?(file_path)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
files
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
26
43
|
def build_formats
|
|
27
44
|
@format.split(',').map do |fmt|
|
|
28
45
|
case fmt
|
|
@@ -38,13 +55,9 @@ module Appydave
|
|
|
38
55
|
def build_content
|
|
39
56
|
concatenated_content = []
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
content = "# file: #{file_path}\n\n#{read_file_content(file_path)}"
|
|
46
|
-
concatenated_content << content
|
|
47
|
-
end
|
|
58
|
+
collect_files.each do |file_path|
|
|
59
|
+
content = "# file: #{file_path}\n\n#{read_file_content(file_path)}"
|
|
60
|
+
concatenated_content << content
|
|
48
61
|
end
|
|
49
62
|
|
|
50
63
|
concatenated_content.join("\n\n")
|
|
@@ -60,13 +73,9 @@ module Appydave
|
|
|
60
73
|
def build_tree
|
|
61
74
|
tree_view = {}
|
|
62
75
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
path_parts = file_path.split('/')
|
|
68
|
-
insert_into_tree(tree_view, path_parts)
|
|
69
|
-
end
|
|
76
|
+
collect_files.each do |file_path|
|
|
77
|
+
path_parts = file_path.split('/')
|
|
78
|
+
insert_into_tree(tree_view, path_parts)
|
|
70
79
|
end
|
|
71
80
|
|
|
72
81
|
build_tree_pretty(tree_view).rstrip
|
|
@@ -96,22 +105,14 @@ module Appydave
|
|
|
96
105
|
'content' => []
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
next if excluded?(file_path)
|
|
108
|
+
collect_files.each do |file_path|
|
|
109
|
+
path_parts = file_path.split('/')
|
|
110
|
+
insert_into_tree(json_output['tree'], path_parts)
|
|
103
111
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
next if excluded?(file_path) || File.directory?(file_path)
|
|
109
|
-
|
|
110
|
-
json_output['content'] << {
|
|
111
|
-
'file' => file_path,
|
|
112
|
-
'content' => read_file_content(file_path)
|
|
113
|
-
}
|
|
114
|
-
end
|
|
112
|
+
json_output['content'] << {
|
|
113
|
+
'file' => file_path,
|
|
114
|
+
'content' => read_file_content(file_path)
|
|
115
|
+
}
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
JSON.pretty_generate(json_output)
|
|
@@ -120,15 +121,7 @@ module Appydave
|
|
|
120
121
|
def build_aider
|
|
121
122
|
return '' unless @options.prompt
|
|
122
123
|
|
|
123
|
-
files =
|
|
124
|
-
@include_patterns.each do |pattern|
|
|
125
|
-
Dir.glob(pattern).each do |file_path|
|
|
126
|
-
next if excluded?(file_path) || File.directory?(file_path)
|
|
127
|
-
|
|
128
|
-
files << file_path
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
124
|
+
files = collect_files
|
|
132
125
|
"aider --message \"#{@options.prompt}\" #{files.join(' ')}"
|
|
133
126
|
end
|
|
134
127
|
|
|
@@ -14,6 +14,8 @@ module Appydave
|
|
|
14
14
|
:working_directory,
|
|
15
15
|
:prompt,
|
|
16
16
|
:show_tokens,
|
|
17
|
+
:file_paths,
|
|
18
|
+
:stdin,
|
|
17
19
|
keyword_init: true
|
|
18
20
|
) do
|
|
19
21
|
def initialize(**args)
|
|
@@ -25,6 +27,8 @@ module Appydave
|
|
|
25
27
|
self.output_target ||= []
|
|
26
28
|
self.prompt ||= nil
|
|
27
29
|
self.show_tokens ||= false
|
|
30
|
+
self.file_paths ||= []
|
|
31
|
+
self.stdin ||= false
|
|
28
32
|
end
|
|
29
33
|
end
|
|
30
34
|
end
|
data/lib/appydave/tools.rb
CHANGED
|
@@ -37,6 +37,10 @@ require 'appydave/tools/llm_context/options'
|
|
|
37
37
|
require 'appydave/tools/llm_context/file_collector'
|
|
38
38
|
require 'appydave/tools/llm_context/output_handler'
|
|
39
39
|
|
|
40
|
+
require 'appydave/tools/brain_context/options'
|
|
41
|
+
require 'appydave/tools/brain_context/brain_finder'
|
|
42
|
+
require 'appydave/tools/brain_context/omi_finder'
|
|
43
|
+
|
|
40
44
|
require 'appydave/tools/configuration/openai'
|
|
41
45
|
require 'appydave/tools/configuration/configurable'
|
|
42
46
|
require 'appydave/tools/configuration/config'
|
data/package.json
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: appydave-tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.80.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Cruwys
|
|
@@ -190,6 +190,8 @@ executables:
|
|
|
190
190
|
- jump
|
|
191
191
|
- llm_context
|
|
192
192
|
- prompt_tools
|
|
193
|
+
- query_brain
|
|
194
|
+
- query_omi
|
|
193
195
|
- subtitle_processor
|
|
194
196
|
- youtube_automation
|
|
195
197
|
- youtube_manager
|
|
@@ -229,6 +231,8 @@ files:
|
|
|
229
231
|
- bin/llm_context.rb
|
|
230
232
|
- bin/move_images.rb
|
|
231
233
|
- bin/prompt_tools.rb
|
|
234
|
+
- bin/query_brain.rb
|
|
235
|
+
- bin/query_omi.rb
|
|
232
236
|
- bin/setup
|
|
233
237
|
- bin/subtitle_manager-old.rb
|
|
234
238
|
- bin/subtitle_manager.rb
|
|
@@ -338,12 +342,17 @@ files:
|
|
|
338
342
|
- exe/jump
|
|
339
343
|
- exe/llm_context
|
|
340
344
|
- exe/prompt_tools
|
|
345
|
+
- exe/query_brain
|
|
346
|
+
- exe/query_omi
|
|
341
347
|
- exe/subtitle_processor
|
|
342
348
|
- exe/youtube_automation
|
|
343
349
|
- exe/youtube_manager
|
|
344
350
|
- exe/zsh_history
|
|
345
351
|
- images.log
|
|
346
352
|
- lib/appydave/tools.rb
|
|
353
|
+
- lib/appydave/tools/brain_context/brain_finder.rb
|
|
354
|
+
- lib/appydave/tools/brain_context/omi_finder.rb
|
|
355
|
+
- lib/appydave/tools/brain_context/options.rb
|
|
347
356
|
- lib/appydave/tools/cli_actions/_doc.md
|
|
348
357
|
- lib/appydave/tools/cli_actions/base_action.rb
|
|
349
358
|
- lib/appydave/tools/cli_actions/get_video_action.rb
|