appydave-tools 0.81.0 → 0.83.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09f48a7490c924291dff958e3df8ba53c28c2dae599cae0ab8581ac3e4ad3ee2'
4
- data.tar.gz: f1e55198dd31bb3b2b61b61d4f929fc3bda799ecd4cedb96e5f810c994edae4a
3
+ metadata.gz: 7b1472f59045694d5411b4112d3c66c04dfa724197ff9a6caa6fe8dde4437a6a
4
+ data.tar.gz: 9088a3cc358367481e06602912df404694af78d5271d2b90458e08ee63dc272c
5
5
  SHA512:
6
- metadata.gz: 9d4f2bc474af95d2ff538d5360d9ec659b559f155b611c9b7c13f9c475118311a1fc99a40b1f3fc6655b20bee568a3c5fcb4466f709eef948cd400d4efee6709
7
- data.tar.gz: 84af7abbcc284e85742f1db64737622e8c929b57815988908a2aba716d970adcfd0f52e29f7a432fb2d544fd8be950fd27254d7ec61fdf705802c126b34e6d84
6
+ metadata.gz: 9dd76f56f7e9886dbd88b8ab9a2a4e026abd1ddaad935046c61a9fc7a9b72cbf316edb8fcb3d81889a55833cca273c3e09abee6d066bad035b7337afa823aab5
7
+ data.tar.gz: 1c32c5827d8b838d9c4f723ecfc0c4695c6d5189fc0f19401b365ca9c9888f3e57de8a167fdc9ba67edb4dc56d3519207e19df95c3382f9f7d50b3af5eef85c7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # [0.82.0](https://github.com/appydave/appydave-tools/compare/v0.81.0...v0.82.0) (2026-04-04)
2
+
3
+
4
+ ### Features
5
+
6
+ * add random_context tool — randomly surfaces brain/OMI content for discovery; config at ~/.config/appydave/random-queries.yml bootstrapped from bundled seed ([1a98813](https://github.com/appydave/appydave-tools/commit/1a98813951283db7e777b3e7d6a48d60aaa840ad))
7
+
8
+ # [0.81.0](https://github.com/appydave/appydave-tools/compare/v0.80.0...v0.81.0) (2026-04-04)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * 'cops' ([7378a1e](https://github.com/appydave/appydave-tools/commit/7378a1e86e7658eb31924254390abfd967f915ae))
14
+
15
+
16
+ ### Features
17
+
18
+ * add --meta flag to query_brain and query_omi returning rich JSON metadata instead of file paths ([3320c5d](https://github.com/appydave/appydave-tools/commit/3320c5dac93ad60591a82e255b5d45ff53396d1d))
19
+ * simplify query_brain and query_omi APIs with unified --find, --active, --days, --limit flags ([12c80e6](https://github.com/appydave/appydave-tools/commit/12c80e6dcee4bb6bbe801985bf75aaeb897f57b9))
20
+
1
21
  # [0.80.0](https://github.com/appydave/appydave-tools/compare/v0.79.0...v0.80.0) (2026-04-03)
2
22
 
3
23
 
data/bin/llm_context.rb CHANGED
@@ -81,6 +81,16 @@ def setup_options(opts, options)
81
81
  options.show_tokens = true
82
82
  end
83
83
 
84
+ opts.on('-s', '--smart', 'Auto-route: clipboard if ≤ threshold tokens, else temp file',
85
+ 'Mutually exclusive with explicit -o clipboard or -o temp') do
86
+ options.smart = true
87
+ end
88
+
89
+ opts.on('--smart-limit N', Integer,
90
+ 'Token threshold for --smart (default: 100000)') do |n|
91
+ options.smart_limit = n
92
+ end
93
+
84
94
  opts.on('--stdin', 'Read file paths from stdin (one per line) instead of using patterns') do
85
95
  options.stdin = true
86
96
  end
@@ -99,6 +109,7 @@ def setup_help_sections(opts)
99
109
  opts.separator ' clipboard - Copy to system clipboard (default)'
100
110
  opts.separator ' temp - Write to system temp dir, copy path to clipboard'
101
111
  opts.separator ' filename - Write to specified file path'
112
+ opts.separator ' --smart - Auto-route: clipboard if ≤ 100k tokens, else temp file'
102
113
  opts.separator ''
103
114
  opts.separator 'INPUT MODES'
104
115
  opts.separator ' Patterns (default): -i <glob> and -e <exclude_glob>'
@@ -165,7 +176,12 @@ if options.include_patterns.empty? && options.exclude_patterns.empty? && options
165
176
  exit
166
177
  end
167
178
 
168
- if options.output_target.empty?
179
+ if options.smart && (options.output_target & %w[clipboard temp]).any?
180
+ warn 'Error: --smart (-s) cannot be combined with explicit -o clipboard or -o temp'
181
+ exit 1
182
+ end
183
+
184
+ if options.output_target.empty? && !options.smart
169
185
  puts 'No output target provided. Will default to `clipboard`. You can set the output target using -o'
170
186
  options.output_target << 'clipboard'
171
187
  end
@@ -0,0 +1,37 @@
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
+ require 'optparse'
7
+
8
+ options = { meta: false, config: nil }
9
+
10
+ OptionParser.new do |opts|
11
+ opts.banner = 'Usage: random_context [options]'
12
+
13
+ opts.on('--meta', 'Return metadata as JSON instead of file paths') do
14
+ options[:meta] = true
15
+ end
16
+
17
+ opts.on('--config PATH', 'Path to custom random-queries.yml config') do |path|
18
+ options[:config] = path
19
+ end
20
+
21
+ opts.on('-v', '--version', 'Show version') do
22
+ puts "random_context v#{Appydave::Tools::VERSION}"
23
+ exit 0
24
+ end
25
+
26
+ opts.on('-h', '--help', 'Show this message') do
27
+ puts opts
28
+ exit 0
29
+ end
30
+ end.parse!
31
+
32
+ randomizer_opts = { meta: options[:meta] }
33
+ randomizer_opts[:config_path] = options[:config] if options[:config]
34
+
35
+ Appydave::Tools::RandomContext::Randomizer.new(**randomizer_opts).run
36
+
37
+ exit 0
@@ -0,0 +1,104 @@
1
+ # Random context discovery queries
2
+ # Each entry runs a query tool command and checks result count.
3
+ # Entries with result counts in [min_results, max_results] are eligible for random selection.
4
+ #
5
+ # Format:
6
+ # label: Natural language question shown to the user
7
+ # command: Shell command to run (query_brain or query_omi)
8
+ # min_results: Minimum results to be considered a "good" match (default: 1)
9
+ # max_results: Maximum results to be considered a "good" match (default: 15)
10
+ #
11
+ # The OMI skill can extend this file by appending new entries under queries:.
12
+
13
+ queries:
14
+ # --- Brain queries ---
15
+ - label: "What do I know about paperclip?"
16
+ command: "query_brain --find paperclip"
17
+ min_results: 1
18
+ max_results: 20
19
+
20
+ - label: "What do I know about OMI?"
21
+ command: "query_brain --find omi"
22
+ min_results: 1
23
+ max_results: 20
24
+
25
+ - label: "What do I know about BMAD method?"
26
+ command: "query_brain --find bmad"
27
+ min_results: 1
28
+ max_results: 20
29
+
30
+ - label: "What do I know about agentic engineering?"
31
+ command: "query_brain --find agentic-engineering"
32
+ min_results: 1
33
+ max_results: 30
34
+
35
+ - label: "What are my active high-priority brains?"
36
+ command: "query_brain --active"
37
+ min_results: 5
38
+ max_results: 50
39
+
40
+ - label: "What do I know about Claude / Anthropic?"
41
+ command: "query_brain --find anthropic"
42
+ min_results: 1
43
+ max_results: 20
44
+
45
+ - label: "Browse my agent systems knowledge"
46
+ command: "query_brain --category agent-systems"
47
+ min_results: 1
48
+ max_results: 30
49
+
50
+ - label: "Browse my agent frameworks knowledge"
51
+ command: "query_brain --category agent-frameworks"
52
+ min_results: 1
53
+ max_results: 30
54
+
55
+ - label: "What do I know about context engineering?"
56
+ command: "query_brain --find context-engineering"
57
+ min_results: 1
58
+ max_results: 20
59
+
60
+ - label: "What do I know about FliVideo?"
61
+ command: "query_brain --find flivideo"
62
+ min_results: 1
63
+ max_results: 20
64
+
65
+ # --- OMI queries ---
66
+ - label: "Recent things I learned (TIL)"
67
+ command: "query_omi --routing til --limit 5"
68
+ min_results: 1
69
+ max_results: 5
70
+
71
+ - label: "One brain-update session to process"
72
+ command: "query_omi --routing brain-update --limit 1"
73
+ min_results: 1
74
+ max_results: 1
75
+
76
+ - label: "Recent brain-update sessions"
77
+ command: "query_omi --routing brain-update --limit 5"
78
+ min_results: 1
79
+ max_results: 5
80
+
81
+ - label: "What todo items came up recently?"
82
+ command: "query_omi --routing todo-item --days 14"
83
+ min_results: 1
84
+ max_results: 10
85
+
86
+ - label: "Recent OMI conversations about paperclip"
87
+ command: "query_omi --brain paperclip --limit 3"
88
+ min_results: 1
89
+ max_results: 3
90
+
91
+ - label: "Recent OMI conversations about BMAD"
92
+ command: "query_omi --brain bmad --limit 3"
93
+ min_results: 1
94
+ max_results: 3
95
+
96
+ - label: "What was I planning recently?"
97
+ command: "query_omi --activity planning --days 14 --limit 5"
98
+ min_results: 1
99
+ max_results: 5
100
+
101
+ - label: "What was I learning recently?"
102
+ command: "query_omi --activity learning --days 14 --limit 5"
103
+ min_results: 1
104
+ max_results: 5
@@ -25,12 +25,12 @@ The tools are **composable**: query tools select files, `llm_context` loads them
25
25
  ## What Is Being Queried
26
26
 
27
27
  ### query_brain
28
- Reads `~/dev/ad/brains/audit/brains-index.json` — a pre-built index of all brain folders. Does **not** scan brain files at query time. The index contains per-brain metadata: name, category, activity_level, tags, status, file_count.
28
+ Reads `/Users/davidcruwys/dev/ad/brains/audit/brains-index.json` — a pre-built index of all brain folders. Does **not** scan brain files at query time. The index contains per-brain metadata: name, category, activity_level, tags, status, file_count.
29
29
 
30
- Brain folders live at `~/dev/ad/brains/<brain-name>/`. Each has an `INDEX.md` and optional content files.
30
+ Brain folders live at `/Users/davidcruwys/dev/ad/brains/<brain-name>/`. Each has an `INDEX.md` and optional content files.
31
31
 
32
32
  ### query_omi
33
- Scans `~/dev/raw-intake/omi/*.md` and reads YAML frontmatter from each file. Enriched files (processed by Gemini extraction) have rich frontmatter. Raw files (unprocessed transcripts) have minimal frontmatter. Default behaviour: **enriched files only**.
33
+ Scans `/Users/davidcruwys/dev/raw-intake/omi/*.md` and reads YAML frontmatter from each file. Enriched files (processed by Gemini extraction) have rich frontmatter. Raw files (unprocessed transcripts) have minimal frontmatter. Default behaviour: **enriched files only**.
34
34
 
35
35
  ### llm_context
36
36
  Takes file paths (from stdin or arguments) and assembles content for LLM consumption. Two formats: `tree` (file listing) and `content` (file listing + full file content).
@@ -16,6 +16,8 @@ module Appydave
16
16
  :show_tokens,
17
17
  :file_paths,
18
18
  :stdin,
19
+ :smart,
20
+ :smart_limit,
19
21
  keyword_init: true
20
22
  ) do
21
23
  def initialize(**args)
@@ -29,6 +31,8 @@ module Appydave
29
31
  self.show_tokens ||= false
30
32
  self.file_paths ||= []
31
33
  self.stdin ||= false
34
+ self.smart ||= false
35
+ self.smart_limit ||= 100_000
32
36
  end
33
37
  end
34
38
  end
@@ -9,18 +9,15 @@ module Appydave
9
9
  @content = content
10
10
  @output_targets = options.output_target
11
11
  @working_directory = options.working_directory
12
+ @smart = options.smart
13
+ @smart_limit = options.smart_limit
12
14
  end
13
15
 
14
16
  def execute
15
- @output_targets.each do |target|
16
- case target
17
- when 'clipboard'
18
- Clipboard.copy(@content)
19
- when 'temp'
20
- write_to_temp
21
- when /^.+$/
22
- write_to_file(target)
23
- end
17
+ if @smart
18
+ execute_smart
19
+ else
20
+ @output_targets.each { |target| route(target) }
24
21
  end
25
22
  end
26
23
 
@@ -28,23 +25,51 @@ module Appydave
28
25
 
29
26
  attr_reader :content, :output_targets, :working_directory
30
27
 
28
+ def execute_smart
29
+ token_estimate = (@content.length / 4.0).ceil
30
+ if token_estimate <= @smart_limit
31
+ Clipboard.copy(@content)
32
+ warn "→ clipboard (#{format_tokens(token_estimate)})"
33
+ else
34
+ file_path = write_to_temp_file
35
+ Clipboard.copy(file_path)
36
+ warn "→ temp file: #{file_path} (#{format_tokens(token_estimate)})"
37
+ warn ' Path copied to clipboard.'
38
+ end
39
+ end
40
+
41
+ def format_tokens(count)
42
+ "#{(count / 1000.0).round.to_i}k tokens"
43
+ end
44
+
45
+ def route(target)
46
+ case target
47
+ when 'clipboard'
48
+ Clipboard.copy(@content)
49
+ when 'temp'
50
+ write_to_temp
51
+ when /^.+$/
52
+ write_to_file(target)
53
+ end
54
+ end
55
+
31
56
  def write_to_file(target)
32
57
  resolved_path = Pathname.new(target).absolute? ? target : File.join(working_directory, target)
33
58
  File.write(resolved_path, content)
34
59
  end
35
60
 
36
61
  def write_to_temp
37
- # Create in system temp directory with descriptive name
62
+ file_path = write_to_temp_file
63
+ Clipboard.copy(file_path)
64
+ warn "Context saved to: #{file_path}"
65
+ end
66
+
67
+ def write_to_temp_file
38
68
  tmp_dir = Dir.tmpdir
39
69
  timestamp = Time.now.strftime('%Y%m%d-%H%M%S-%N')[0..18] # millisecond precision
40
70
  file_path = File.join(tmp_dir, "llm_context-#{timestamp}.txt")
41
-
42
- # Write content to file
43
71
  File.write(file_path, @content)
44
-
45
- # Copy path to clipboard
46
- Clipboard.copy(file_path)
47
- warn "Context saved to: #{file_path}"
72
+ file_path
48
73
  end
49
74
  end
50
75
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module RandomContext
6
+ # Represents one entry in the random-queries.yml config file
7
+ class QueryEntry
8
+ attr_reader :label, :command, :min_results, :max_results
9
+
10
+ def initialize(data)
11
+ @label = data['label']
12
+ @command = data['command']
13
+ @min_results = data.fetch('min_results', 1)
14
+ @max_results = data.fetch('max_results', 15)
15
+ end
16
+
17
+ def good_count?(count)
18
+ count.between?(min_results, max_results)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'open3'
5
+ require 'fileutils'
6
+
7
+ module Appydave
8
+ module Tools
9
+ module RandomContext
10
+ # Loads query library, runs each query to check result count,
11
+ # filters to entries with "good" result counts, and picks one randomly.
12
+ class Randomizer
13
+ BUNDLED_CONFIG_PATH = File.expand_path('../../../../config/random-queries.yml', __dir__)
14
+ USER_CONFIG_PATH = File.expand_path('~/.config/appydave/random-queries.yml')
15
+
16
+ def initialize(config_path: USER_CONFIG_PATH, meta: false, executor: nil,
17
+ bundled_config_path: BUNDLED_CONFIG_PATH,
18
+ bootstrap: config_path == USER_CONFIG_PATH)
19
+ @config_path = config_path
20
+ @meta = meta
21
+ @executor = executor || method(:shell_execute)
22
+ @bundled_config_path = bundled_config_path
23
+ @bootstrap = bootstrap
24
+ end
25
+
26
+ # Returns [QueryEntry, results_array] for the picked entry, or nil if no candidates.
27
+ def pick
28
+ candidates = []
29
+
30
+ load_entries.each do |entry|
31
+ results = @executor.call(entry.command)
32
+ candidates << [entry, results] if entry.good_count?(results.size)
33
+ end
34
+
35
+ candidates.sample
36
+ end
37
+
38
+ # Picks a random candidate and prints label + results to stdout.
39
+ def run
40
+ picked = pick
41
+ unless picked
42
+ puts 'No matching queries found'
43
+ return
44
+ end
45
+
46
+ entry, results = picked
47
+ puts "Question: \"#{entry.label}\""
48
+
49
+ if @meta
50
+ meta_results = @executor.call("#{entry.command} --meta")
51
+ meta_results.each { |line| puts line }
52
+ else
53
+ results.each { |line| puts line }
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def load_entries
60
+ resolved = resolve_config!
61
+ data = YAML.safe_load(File.read(resolved))
62
+ (data['queries'] || []).map { |q| QueryEntry.new(q) }
63
+ end
64
+
65
+ def resolve_config!
66
+ return @config_path if File.exist?(@config_path)
67
+ raise "Config not found: #{@config_path}" unless @bootstrap
68
+
69
+ FileUtils.mkdir_p(File.dirname(@config_path))
70
+ FileUtils.cp(@bundled_config_path, @config_path)
71
+ @config_path
72
+ end
73
+
74
+ def shell_execute(command)
75
+ stdout, _stderr, _status = Open3.capture2(command)
76
+ stdout.strip.split("\n").reject(&:empty?)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.81.0'
5
+ VERSION = '0.83.0'
6
6
  end
7
7
  end
@@ -41,6 +41,9 @@ require 'appydave/tools/brain_context/options'
41
41
  require 'appydave/tools/brain_context/brain_finder'
42
42
  require 'appydave/tools/brain_context/omi_finder'
43
43
 
44
+ require 'appydave/tools/random_context/query_entry'
45
+ require 'appydave/tools/random_context/randomizer'
46
+
44
47
  require 'appydave/tools/configuration/openai'
45
48
  require 'appydave/tools/configuration/configurable'
46
49
  require 'appydave/tools/configuration/config'
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.81.0",
3
+ "version": "0.83.0",
4
4
  "description": "AppyDave YouTube Automation Tools",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
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.81.0
4
+ version: 0.83.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
@@ -233,6 +233,7 @@ files:
233
233
  - bin/prompt_tools.rb
234
234
  - bin/query_brain.rb
235
235
  - bin/query_omi.rb
236
+ - bin/random_context.rb
236
237
  - bin/setup
237
238
  - bin/subtitle_manager-old.rb
238
239
  - bin/subtitle_manager.rb
@@ -241,6 +242,7 @@ files:
241
242
  - bin/youtube_automation.rb
242
243
  - bin/youtube_manager.rb
243
244
  - bin/zsh_history.rb
245
+ - config/random-queries.yml
244
246
  - docs/README.md
245
247
  - docs/ai-instructions/behavioral-regression-audit.md
246
248
  - docs/ai-instructions/code-quality-retrospective.md
@@ -424,6 +426,8 @@ files:
424
426
  - lib/appydave/tools/name_manager/project_name.rb
425
427
  - lib/appydave/tools/prompt_tools/_doc.md
426
428
  - lib/appydave/tools/prompt_tools/prompt_completion.rb
429
+ - lib/appydave/tools/random_context/query_entry.rb
430
+ - lib/appydave/tools/random_context/randomizer.rb
427
431
  - lib/appydave/tools/subtitle_processor/_doc-clean.md
428
432
  - lib/appydave/tools/subtitle_processor/_doc-join.md
429
433
  - lib/appydave/tools/subtitle_processor/_doc-todo.md