appydave-tools 0.70.0 → 0.71.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/brainstorming-agent.md +227 -0
  3. data/.claude/commands/cli-test.md +251 -0
  4. data/.claude/commands/dev.md +234 -0
  5. data/.claude/commands/po.md +227 -0
  6. data/.claude/commands/progress.md +51 -0
  7. data/.claude/commands/uat.md +321 -0
  8. data/.rubocop.yml +9 -0
  9. data/AGENTS.md +43 -0
  10. data/CHANGELOG.md +12 -0
  11. data/CLAUDE.md +26 -3
  12. data/README.md +15 -0
  13. data/bin/dam +21 -1
  14. data/bin/jump.rb +29 -0
  15. data/bin/subtitle_processor.rb +54 -1
  16. data/bin/zsh_history.rb +846 -0
  17. data/docs/README.md +162 -69
  18. data/docs/architecture/cli/exe-bin-convention.md +434 -0
  19. data/docs/architecture/cli-patterns.md +631 -0
  20. data/docs/architecture/gpt-context/gpt-context-architecture.md +325 -0
  21. data/docs/architecture/gpt-context/gpt-context-implementation-guide.md +419 -0
  22. data/docs/architecture/gpt-context/gpt-context-vision.md +179 -0
  23. data/docs/architecture/testing/testing-patterns.md +762 -0
  24. data/docs/backlog.md +120 -0
  25. data/docs/cli-tests/FR-3-jump-location-tool.md +515 -0
  26. data/docs/specs/fr-002-gpt-context-help-system.md +265 -0
  27. data/docs/specs/fr-003-jump-location-tool.md +779 -0
  28. data/docs/specs/zsh-history-tool.md +820 -0
  29. data/docs/uat/FR-3-jump-location-tool.md +741 -0
  30. data/exe/jump +11 -0
  31. data/exe/{subtitle_manager → subtitle_processor} +1 -1
  32. data/exe/zsh_history +11 -0
  33. data/lib/appydave/tools/configuration/openai.rb +1 -1
  34. data/lib/appydave/tools/dam/file_helper.rb +28 -0
  35. data/lib/appydave/tools/dam/project_listing.rb +4 -30
  36. data/lib/appydave/tools/dam/s3_operations.rb +2 -1
  37. data/lib/appydave/tools/dam/ssd_status.rb +226 -0
  38. data/lib/appydave/tools/dam/status.rb +3 -51
  39. data/lib/appydave/tools/jump/cli.rb +561 -0
  40. data/lib/appydave/tools/jump/commands/add.rb +52 -0
  41. data/lib/appydave/tools/jump/commands/base.rb +43 -0
  42. data/lib/appydave/tools/jump/commands/generate.rb +153 -0
  43. data/lib/appydave/tools/jump/commands/remove.rb +58 -0
  44. data/lib/appydave/tools/jump/commands/report.rb +214 -0
  45. data/lib/appydave/tools/jump/commands/update.rb +42 -0
  46. data/lib/appydave/tools/jump/commands/validate.rb +54 -0
  47. data/lib/appydave/tools/jump/config.rb +233 -0
  48. data/lib/appydave/tools/jump/formatters/base.rb +48 -0
  49. data/lib/appydave/tools/jump/formatters/json_formatter.rb +19 -0
  50. data/lib/appydave/tools/jump/formatters/paths_formatter.rb +21 -0
  51. data/lib/appydave/tools/jump/formatters/table_formatter.rb +183 -0
  52. data/lib/appydave/tools/jump/location.rb +134 -0
  53. data/lib/appydave/tools/jump/path_validator.rb +47 -0
  54. data/lib/appydave/tools/jump/search.rb +230 -0
  55. data/lib/appydave/tools/subtitle_processor/transcript.rb +51 -0
  56. data/lib/appydave/tools/version.rb +1 -1
  57. data/lib/appydave/tools/zsh_history/command.rb +37 -0
  58. data/lib/appydave/tools/zsh_history/config.rb +235 -0
  59. data/lib/appydave/tools/zsh_history/filter.rb +184 -0
  60. data/lib/appydave/tools/zsh_history/formatter.rb +75 -0
  61. data/lib/appydave/tools/zsh_history/parser.rb +101 -0
  62. data/lib/appydave/tools.rb +25 -0
  63. data/package.json +1 -1
  64. metadata +51 -4
@@ -0,0 +1,846 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # ZSH History Tool - Parse, filter, and clean ZSH history
5
+
6
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
7
+
8
+ require 'appydave/tools'
9
+
10
+ # ZSH History CLI - Parse, filter, and clean ZSH history
11
+ class ZshHistoryCLI
12
+ def initialize
13
+ @commands = {
14
+ 'help' => method(:help_command),
15
+ 'show' => method(:show_command),
16
+ 'stats' => method(:stats_command),
17
+ 'search' => method(:search_command),
18
+ 'purge' => method(:purge_command),
19
+ 'config' => method(:config_command)
20
+ }
21
+ end
22
+
23
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
24
+ def run
25
+ command = ARGV[0]
26
+
27
+ # Handle --version and -v flags
28
+ if ['--version', '-v'].include?(command)
29
+ puts "zsh_history v#{Appydave::Tools::VERSION}"
30
+ puts 'Part of appydave-tools gem'
31
+ exit
32
+ end
33
+
34
+ # Handle --help and -h flags
35
+ if ['--help', '-h'].include?(command)
36
+ show_main_help
37
+ exit
38
+ end
39
+
40
+ if command.nil?
41
+ puts 'ZSH History - Parse, filter, and manage shell history'
42
+ puts "Version: #{Appydave::Tools::VERSION}"
43
+ puts ''
44
+ puts 'Usage: zsh_history [command] [options]'
45
+ puts ''
46
+ puts 'Commands:'
47
+ puts ' zsh_history show # Display wanted commands'
48
+ puts ' zsh_history stats # Show statistics'
49
+ puts ' zsh_history search <pattern> # Search history'
50
+ puts ' zsh_history purge # Rewrite history file (careful!)'
51
+ puts ' zsh_history config # Manage configuration/profiles'
52
+ puts ''
53
+ puts "Run 'zsh_history help' for more information."
54
+ exit
55
+ end
56
+
57
+ if @commands.key?(command)
58
+ @commands[command].call(ARGV[1..])
59
+ else
60
+ puts "Unknown command: #{command}"
61
+ puts ''
62
+ puts 'Available commands: show, stats, search, purge, config, help'
63
+ puts ''
64
+ puts "Run 'zsh_history help' for detailed information."
65
+ exit 1
66
+ end
67
+ end
68
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
69
+
70
+ private
71
+
72
+ # ============================================================
73
+ # HELP COMMAND
74
+ # ============================================================
75
+
76
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
77
+ def help_command(args)
78
+ topic = args[0]
79
+
80
+ case topic
81
+ when 'show'
82
+ show_show_help
83
+ when 'stats'
84
+ show_stats_help
85
+ when 'search'
86
+ show_search_help
87
+ when 'purge'
88
+ show_purge_help
89
+ when 'config'
90
+ show_config_help
91
+ when 'profiles'
92
+ show_profiles_help
93
+ when 'patterns'
94
+ show_patterns_help
95
+ when 'workflow'
96
+ show_workflow_help
97
+ when nil
98
+ show_main_help
99
+ else
100
+ puts "Unknown help topic: #{topic}"
101
+ puts ''
102
+ puts 'Available help topics:'
103
+ puts ' zsh_history help show # Display filtered commands'
104
+ puts ' zsh_history help stats # Show statistics'
105
+ puts ' zsh_history help search # Search history'
106
+ puts ' zsh_history help purge # Rewrite history file'
107
+ puts ' zsh_history help config # Configuration management'
108
+ puts ' zsh_history help profiles # Profile system'
109
+ puts ' zsh_history help patterns # Include/exclude pattern system'
110
+ puts ' zsh_history help workflow # Typical usage workflow'
111
+ end
112
+ end
113
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
114
+
115
+ # rubocop:disable Metrics/MethodLength
116
+ def show_main_help
117
+ puts <<~HELP
118
+ ZSH History - Parse, filter, and manage shell history
119
+
120
+ Usage: zsh_history [command] [options]
121
+
122
+ Commands:
123
+ show Display wanted commands (read-only)
124
+ stats Show statistics about your history
125
+ search <pattern> Search history with regex pattern
126
+ purge Rewrite history file (removes unwanted)
127
+ config Manage configuration and profiles
128
+ help [topic] Show help information
129
+
130
+ Quick Examples:
131
+ zsh_history show # Display wanted commands
132
+ zsh_history show --days 7 # Last 7 days only
133
+ zsh_history show --profile crash-recovery # Use specific profile
134
+ zsh_history stats # Show statistics
135
+ zsh_history search docker # Find docker commands
136
+ zsh_history config init # Create config files
137
+
138
+ Global Options:
139
+ -p, --profile NAME Use named profile for patterns
140
+ -d, --days N Only show last N days
141
+
142
+ Help Topics:
143
+ zsh_history help show # Display options
144
+ zsh_history help stats # Statistics options
145
+ zsh_history help search # Search options
146
+ zsh_history help purge # Rewrite history file
147
+ zsh_history help config # Configuration management
148
+ zsh_history help profiles # Profile system
149
+ zsh_history help patterns # How patterns work
150
+ zsh_history help workflow # Typical usage workflow
151
+
152
+ How It Works:
153
+ Commands are categorized into three groups:
154
+ - WANTED: Useful commands to keep (git commit, docker, rake, etc.)
155
+ - UNWANTED: Noise to remove (ls, cd, typos, output lines)
156
+ - UNSURE: Commands that don't match any pattern
157
+
158
+ Patterns can be customized via profiles. See 'zsh_history help profiles'.
159
+
160
+ For more information: https://github.com/appydave/appydave-tools
161
+ HELP
162
+ end
163
+
164
+ def show_show_help
165
+ puts <<~HELP
166
+ Show Command - Display filtered commands (read-only)
167
+
168
+ Usage: zsh_history show [options]
169
+
170
+ Options:
171
+ -d, --days N Only show last N days
172
+ -u, --unsure Show only unsure commands (for debugging patterns)
173
+ -a, --all Show ALL commands (no filtering)
174
+ -v, --verbose Show which pattern matched each command
175
+ -p, --profile NAME Use named profile for patterns
176
+ -o, --output PATH Write output to file instead of stdout
177
+ -f, --file PATH Use different history file (default: ~/.zsh_history)
178
+
179
+ Examples:
180
+ zsh_history show # Show wanted commands
181
+ zsh_history show --days 7 # Last 7 days
182
+ zsh_history show --unsure # Show only unsure commands
183
+ zsh_history show --verbose # Show matching patterns
184
+ zsh_history show --all # Show everything unfiltered
185
+ zsh_history show -o history.txt # Write to file
186
+ zsh_history show --profile file-exploration # Use different profile
187
+
188
+ Output Format:
189
+ 2025-12-13 10:30:45 git commit -m "Add feature"
190
+ 2025-12-13 10:31:02 docker build .
191
+
192
+ Verbose Output:
193
+ 2025-12-13 10:30:45 [WANTED: ^git commit] git commit -m "Add feature"
194
+ 2025-12-13 10:31:02 [WANTED: ^docker ] docker build .
195
+
196
+ See Also:
197
+ zsh_history help patterns # Understand the filtering system
198
+ zsh_history help purge # Actually rewrite history file
199
+ HELP
200
+ end
201
+
202
+ def show_stats_help
203
+ puts <<~HELP
204
+ Stats Command - Show history statistics
205
+
206
+ Usage: zsh_history stats [options]
207
+
208
+ Options:
209
+ -d, --days N Only analyze last N days
210
+ -f, --file PATH Use different history file
211
+
212
+ Examples:
213
+ zsh_history stats # Full history stats
214
+ zsh_history stats --days 7 # Last 7 days only
215
+ zsh_history stats --days 30 # Last 30 days
216
+
217
+ Output:
218
+ ZSH History Statistics
219
+ ==================================================
220
+ Total commands: 8265
221
+ Wanted: 2693 (32.6%)
222
+ Unwanted: 2610 (31.6%)
223
+ Unsure: 2962 (35.8%)
224
+
225
+ Date range: 2025-08-24 to 2025-12-13 (111 days)
226
+
227
+ Use Cases:
228
+ - See how much noise is in your history
229
+ - Track history size over time
230
+ - Decide if you need to clean up
231
+ HELP
232
+ end
233
+
234
+ def show_search_help
235
+ puts <<~HELP
236
+ Search Command - Search history with regex
237
+
238
+ Usage: zsh_history search <pattern> [options]
239
+
240
+ Options:
241
+ -d, --days N Only search last N days
242
+ -a, --all Search ALL commands (not just wanted)
243
+ -f, --file PATH Use different history file
244
+
245
+ Pattern:
246
+ Uses Ruby regex (case-insensitive by default)
247
+
248
+ Examples:
249
+ zsh_history search docker # Find docker commands
250
+ zsh_history search "git.*main" # Git commands with 'main'
251
+ zsh_history search dam # Find DAM tool usage
252
+ zsh_history search claude # Find Claude CLI usage
253
+ zsh_history search "npm|yarn|bun" # Any JS package manager
254
+
255
+ With Date Filter:
256
+ zsh_history search docker --days 7 # Docker commands this week
257
+
258
+ Search All (including unwanted):
259
+ zsh_history search ls --all # Even though 'ls' is unwanted
260
+ HELP
261
+ end
262
+
263
+ def show_patterns_help
264
+ puts <<~HELP
265
+ Pattern System - How commands are categorized
266
+
267
+ Commands are matched against regex patterns in this order:
268
+ 1. EXCLUDE patterns checked first (marks as UNWANTED)
269
+ 2. INCLUDE patterns checked second (marks as WANTED)
270
+ 3. No match = UNSURE
271
+
272
+ EXCLUDE Patterns (noise removal):
273
+ ^[a-z]$ Single letter typos
274
+ ^[a-z]{2}$ Two letter typos
275
+ ^ls$, ^ls - Directory listings
276
+ ^pwd$, ^clear$ Basic navigation
277
+ ^cd$, ^cd ..$ Directory changes
278
+ ^git status$ Quick git checks (not commits)
279
+ ^git diff$, ^git log$
280
+ ^gs$, ^gd$, ^gl$ Git aliases
281
+ ^history History commands
282
+ ^which, ^type Command lookups
283
+ ^cat, ^head, ^tail File viewing
284
+ ^zsh: command not found Error messages
285
+
286
+ INCLUDE Patterns (valuable commands):
287
+ ^git commit Commits (not status/diff)
288
+ ^git push, ^git add
289
+ ^docker Docker commands
290
+ ^claude Claude CLI
291
+ ^dam DAM tool
292
+ ^rake, ^bundle Ruby development
293
+ ^bun, ^npm run JS development
294
+ ^brew install Package installation
295
+ ^j[a-z] Jump aliases (jad, jfli, etc.)
296
+
297
+ UNSURE:
298
+ Commands matching neither list need manual review.
299
+ Use --unsure flag to include them in output.
300
+
301
+ Examples:
302
+ 'vim file.txt' -> UNSURE (not in either list)
303
+ 'git status' -> UNWANTED (in exclude list)
304
+ 'git commit -m "x"' -> WANTED (in include list)
305
+ 'ls -la' -> UNWANTED (matches ^ls -)
306
+ 'docker build .' -> WANTED (matches ^docker )
307
+ HELP
308
+ end
309
+
310
+ def show_workflow_help
311
+ puts <<~HELP
312
+ Typical Workflow - How to use this tool
313
+
314
+ 1. CHECK STATS FIRST
315
+ See how much noise is in your history:
316
+
317
+ $ zsh_history stats
318
+ Total: 8265, Wanted: 32.6%, Unwanted: 31.6%, Unsure: 35.8%
319
+
320
+ 2. REVIEW WHAT WOULD BE KEPT
321
+ Preview the filtered output:
322
+
323
+ $ zsh_history show --days 30
324
+
325
+ 3. CHECK UNSURE COMMANDS
326
+ See what's not being categorized:
327
+
328
+ $ zsh_history show --unsure --days 7
329
+
330
+ 4. SEARCH FOR SPECIFIC COMMANDS
331
+ Find commands you need:
332
+
333
+ $ zsh_history search docker
334
+ $ zsh_history search "git.*feature"
335
+
336
+ 5. DRY RUN FIRST
337
+ Preview what purge would do:
338
+
339
+ $ zsh_history purge --days 90 --dry-run
340
+
341
+ 6. PURGE HISTORY (CAREFUL!)
342
+ Only when you're confident:
343
+
344
+ $ zsh_history purge --days 90
345
+
346
+ This creates a backup first:
347
+ ~/.zsh_history.backup.20251213-103045
348
+
349
+ Use Cases:
350
+ - After a terminal crash, find what you were doing
351
+ - Clean up history before sharing screen
352
+ - Remove noise accumulated over months
353
+ - Find that docker command from last week
354
+ HELP
355
+ end
356
+
357
+ def show_purge_help
358
+ puts <<~HELP
359
+ Purge Command - Rewrite history file (DANGEROUS!)
360
+
361
+ Removes unwanted commands from your history file permanently.
362
+
363
+ Usage: zsh_history purge [options]
364
+
365
+ Options:
366
+ -d, --days N Only keep last N days
367
+ -u, --unsure Include unsure commands (keep more)
368
+ --dry-run Preview what would be removed
369
+ -f, --file PATH Use different history file
370
+
371
+ Safety Features:
372
+ 1. Creates timestamped backup before writing:
373
+ ~/.zsh_history.backup.20251213-103045
374
+
375
+ 2. Refuses if keeping < 10% of commands:
376
+ "This seems too aggressive"
377
+
378
+ 3. Shows count before/after
379
+
380
+ Examples:
381
+ # Preview first with show
382
+ zsh_history show --days 90
383
+
384
+ # Then purge
385
+ zsh_history purge --days 90
386
+
387
+ # Keep unsure commands too
388
+ zsh_history purge --days 90 --unsure
389
+
390
+ # Dry run (preview only)
391
+ zsh_history purge --days 90 --dry-run
392
+
393
+ Recovery:
394
+ If something goes wrong, restore from backup:
395
+
396
+ $ cp ~/.zsh_history.backup.20251213-103045 ~/.zsh_history
397
+ $ exec zsh # Reload shell
398
+
399
+ WARNING:
400
+ - This permanently modifies your history file
401
+ - The backup is your only recovery option
402
+ - Test with 'show' first to preview
403
+ - Consider keeping unsure commands (--unsure)
404
+ HELP
405
+ end
406
+
407
+ def show_config_help
408
+ puts <<~HELP
409
+ Config Command - Manage configuration and profiles
410
+
411
+ Usage: zsh_history config [subcommand]
412
+
413
+ Subcommands:
414
+ (none) Show configuration status
415
+ init Create default config files
416
+ list List available profiles
417
+ path Show config directory path
418
+
419
+ Examples:
420
+ zsh_history config # Show status
421
+ zsh_history config init # Create default config
422
+ zsh_history config list # List profiles
423
+
424
+ Config Location:
425
+ ~/.config/appydave/zsh_history/
426
+
427
+ Config Structure:
428
+ config.txt - Settings (default_profile=crash-recovery)
429
+ base_exclude.txt - Patterns always excluded (typos, output)
430
+ profiles/
431
+ crash-recovery/
432
+ exclude.txt - Profile-specific excludes
433
+ include.txt - Profile-specific includes
434
+
435
+ See Also:
436
+ zsh_history help profiles # How profiles work
437
+ zsh_history help patterns # Pattern format
438
+ HELP
439
+ end
440
+
441
+ def show_profiles_help
442
+ puts <<~HELP
443
+ Profile System - Scenario-specific pattern sets
444
+
445
+ Profiles allow different include/exclude patterns for different use cases.
446
+ The same command might be "wanted" in one scenario but "noise" in another.
447
+
448
+ How It Works:
449
+ 1. base_exclude.txt is ALWAYS applied (typos, output lines, errors)
450
+ 2. Profile adds additional exclude/include patterns
451
+ 3. Patterns are simple regex, one per line
452
+
453
+ Built-in Profile: crash-recovery
454
+ Use case: Find what you were working on when terminal crashed
455
+ Excludes: ls, cd, git status (navigation noise)
456
+ Includes: git commit, docker, rake (actual work)
457
+
458
+ Using Profiles:
459
+ zsh_history show --profile crash-recovery
460
+ zsh_history stats --profile crash-recovery
461
+ zsh_history purge --profile crash-recovery
462
+
463
+ Default Profile:
464
+ Set in ~/.config/appydave/zsh_history/config.txt:
465
+ default_profile=crash-recovery
466
+
467
+ When set, profile is used automatically without --profile flag.
468
+
469
+ Creating Custom Profiles:
470
+ 1. Create directory: ~/.config/appydave/zsh_history/profiles/my-profile/
471
+ 2. Add exclude.txt with patterns to exclude
472
+ 3. Add include.txt with patterns to include
473
+ 4. Use: zsh_history show --profile my-profile
474
+
475
+ Example Profile: file-exploration
476
+ exclude.txt:
477
+ ^git
478
+ ^docker
479
+ ^rake
480
+ include.txt:
481
+ ^cat
482
+ ^head
483
+ ^tail
484
+ ^less
485
+ ^vim
486
+ ^nano
487
+
488
+ Pattern Format:
489
+ - One regex per line
490
+ - Lines starting with # are comments
491
+ - Blank lines ignored
492
+ - Case-insensitive matching
493
+ - ^ anchors to start of command
494
+ HELP
495
+ end
496
+ # rubocop:enable Metrics/MethodLength
497
+
498
+ # ============================================================
499
+ # SHOW COMMAND
500
+ # ============================================================
501
+
502
+ # rubocop:disable Metrics/AbcSize
503
+ def show_command(args)
504
+ options = parse_common_options(args)
505
+
506
+ parser_instance = Appydave::Tools::ZshHistory::Parser.new(options[:history_path])
507
+ commands = parser_instance.parse
508
+
509
+ if commands.empty?
510
+ puts "No commands found in #{options[:history_path]}"
511
+ exit
512
+ end
513
+
514
+ filter = Appydave::Tools::ZshHistory::Filter.new(profile: options[:profile])
515
+ result = filter.apply(commands, days: options[:days])
516
+
517
+ formatter = Appydave::Tools::ZshHistory::Formatter.new
518
+
519
+ output = if options[:all]
520
+ formatter.format_commands(commands, verbose: options[:verbose])
521
+ elsif options[:unsure]
522
+ formatter.format_commands(result.unsure.sort_by(&:timestamp), verbose: options[:verbose])
523
+ else
524
+ formatter.format_commands(result.wanted.sort_by(&:timestamp), verbose: options[:verbose])
525
+ end
526
+
527
+ write_output(output, options[:output])
528
+ end
529
+ # rubocop:enable Metrics/AbcSize
530
+
531
+ # ============================================================
532
+ # PURGE COMMAND
533
+ # ============================================================
534
+
535
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
536
+ def purge_command(args)
537
+ options = parse_common_options(args)
538
+
539
+ parser_instance = Appydave::Tools::ZshHistory::Parser.new(options[:history_path])
540
+ commands = parser_instance.parse
541
+
542
+ if commands.empty?
543
+ puts "No commands found in #{options[:history_path]}"
544
+ exit
545
+ end
546
+
547
+ filter = Appydave::Tools::ZshHistory::Filter.new(profile: options[:profile])
548
+ result = filter.apply(commands, days: options[:days])
549
+
550
+ commands_to_write = result.wanted
551
+ commands_to_write += result.unsure if options[:unsure]
552
+
553
+ if commands_to_write.size < commands.size * 0.1
554
+ puts "Warning: Would only keep #{commands_to_write.size} of #{commands.size} commands (< 10%)"
555
+ puts 'This seems too aggressive. Use --days to be more selective or check your patterns.'
556
+ exit 1
557
+ end
558
+
559
+ if options[:dry_run]
560
+ puts "DRY RUN - Would keep #{commands_to_write.size} of #{commands.size} commands"
561
+ puts " Wanted: #{result.wanted.size}"
562
+ puts " Unsure: #{result.unsure.size}#{options[:unsure] ? ' (included)' : ' (excluded)'}"
563
+ puts " Unwanted: #{result.unwanted.size} (removed)"
564
+ puts ''
565
+ puts "Run without --dry-run to actually rewrite #{options[:history_path]}"
566
+ else
567
+ formatter = Appydave::Tools::ZshHistory::Formatter.new
568
+ formatter.write_history(commands_to_write, options[:history_path])
569
+ end
570
+ end
571
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
572
+
573
+ # ============================================================
574
+ # STATS COMMAND
575
+ # ============================================================
576
+
577
+ # rubocop:disable Metrics/AbcSize
578
+ def stats_command(args)
579
+ options = parse_common_options(args)
580
+
581
+ parser_instance = Appydave::Tools::ZshHistory::Parser.new(options[:history_path])
582
+ commands = parser_instance.parse
583
+
584
+ if commands.empty?
585
+ puts "No commands found in #{options[:history_path]}"
586
+ exit
587
+ end
588
+
589
+ filter = Appydave::Tools::ZshHistory::Filter.new(profile: options[:profile])
590
+ result = filter.apply(commands, days: options[:days])
591
+
592
+ date_range = nil
593
+ unless commands.empty?
594
+ sorted = commands.sort_by(&:timestamp)
595
+ date_range = {
596
+ from: sorted.first.formatted_datetime('%Y-%m-%d'),
597
+ to: sorted.last.formatted_datetime('%Y-%m-%d'),
598
+ days: ((sorted.last.timestamp - sorted.first.timestamp) / (24 * 60 * 60)).round
599
+ }
600
+ end
601
+
602
+ formatter = Appydave::Tools::ZshHistory::Formatter.new
603
+ puts formatter.format_stats(result.stats, date_range: date_range)
604
+ end
605
+ # rubocop:enable Metrics/AbcSize
606
+
607
+ # ============================================================
608
+ # SEARCH COMMAND
609
+ # ============================================================
610
+
611
+ def search_command(args)
612
+ pattern = args.shift
613
+ show_search_usage_and_exit if pattern.nil? || pattern.start_with?('-')
614
+
615
+ options = parse_common_options(args)
616
+ commands = load_and_filter_commands(options)
617
+ matches = find_matches(commands, pattern, options)
618
+
619
+ exit_with_no_matches(pattern) if matches.empty?
620
+
621
+ formatter = Appydave::Tools::ZshHistory::Formatter.new
622
+ puts formatter.format_commands(matches.sort_by(&:timestamp), verbose: options[:verbose])
623
+ end
624
+
625
+ def show_search_usage_and_exit
626
+ puts 'Usage: zsh_history search <pattern> [options]'
627
+ puts ''
628
+ puts 'Examples:'
629
+ puts ' zsh_history search docker'
630
+ puts ' zsh_history search "git.*main"'
631
+ puts ' zsh_history search claude --days 7'
632
+ exit 1
633
+ end
634
+
635
+ def load_and_filter_commands(options)
636
+ parser_instance = Appydave::Tools::ZshHistory::Parser.new(options[:history_path])
637
+ commands = parser_instance.parse
638
+ if commands.empty?
639
+ puts "No commands found in #{options[:history_path]}"
640
+ exit
641
+ end
642
+ { commands: commands, result: Appydave::Tools::ZshHistory::Filter.new(profile: options[:profile]).apply(commands, days: options[:days]) }
643
+ end
644
+
645
+ def find_matches(data, pattern, options)
646
+ grep_pattern = Regexp.new(pattern, Regexp::IGNORECASE)
647
+ if options[:all]
648
+ data[:commands].select { |cmd| cmd.text.match?(grep_pattern) }
649
+ else
650
+ data[:result].wanted.select { |cmd| cmd.text.match?(grep_pattern) } +
651
+ data[:result].unsure.select { |cmd| cmd.text.match?(grep_pattern) }
652
+ end
653
+ end
654
+
655
+ def exit_with_no_matches(pattern)
656
+ puts "No matches found for: #{pattern}"
657
+ exit
658
+ end
659
+
660
+ # ============================================================
661
+ # CONFIG COMMAND
662
+ # ============================================================
663
+
664
+ def config_command(args)
665
+ subcommand = args[0]
666
+
667
+ case subcommand
668
+ when 'init'
669
+ config_init
670
+ when 'list'
671
+ config_list
672
+ when 'path'
673
+ config_path
674
+ when nil, 'status'
675
+ config_status
676
+ else
677
+ puts "Unknown config subcommand: #{subcommand}"
678
+ puts ''
679
+ puts 'Usage: zsh_history config [subcommand]'
680
+ puts ''
681
+ puts 'Subcommands:'
682
+ puts ' zsh_history config # Show config status'
683
+ puts ' zsh_history config init # Create default config files'
684
+ puts ' zsh_history config list # List available profiles'
685
+ puts ' zsh_history config path # Show config directory path'
686
+ end
687
+ end
688
+
689
+ def config_status
690
+ config = Appydave::Tools::ZshHistory::Config.new
691
+
692
+ puts 'ZSH History Configuration'
693
+ puts '=' * 50
694
+ puts ''
695
+ puts "Config path: #{config.config_path}"
696
+ puts "Configured: #{config.configured? ? 'Yes' : 'No'}"
697
+ puts ''
698
+
699
+ if config.configured?
700
+ display_profiles(config)
701
+ else
702
+ puts "Run 'zsh_history config init' to create default configuration."
703
+ end
704
+ end
705
+
706
+ def display_profiles(config)
707
+ puts 'Profiles:'
708
+ profiles = config.available_profiles
709
+ if profiles.empty?
710
+ puts ' (none)'
711
+ else
712
+ profiles.each do |profile|
713
+ default_marker = profile == config.default_profile ? ' (default)' : ''
714
+ puts " - #{profile}#{default_marker}"
715
+ end
716
+ end
717
+
718
+ puts ''
719
+ puts "Default profile: #{config.default_profile || '(none)'}"
720
+ end
721
+
722
+ def config_init
723
+ config_path = Appydave::Tools::ZshHistory::Config.create_default_config
724
+ puts "Created default configuration at: #{config_path}"
725
+ puts ''
726
+ puts 'Files created:'
727
+ puts ' config.txt - Default profile setting'
728
+ puts ' base_exclude.txt - Patterns always excluded'
729
+ puts ' profiles/'
730
+ puts ' crash-recovery/'
731
+ puts ' exclude.txt - Profile-specific excludes'
732
+ puts ' include.txt - Profile-specific includes'
733
+ puts ''
734
+ puts 'Edit these files to customize your patterns.'
735
+ end
736
+
737
+ def config_list
738
+ config = Appydave::Tools::ZshHistory::Config.new
739
+
740
+ unless config.configured?
741
+ puts 'No configuration found.'
742
+ puts "Run 'zsh_history config init' to create default configuration."
743
+ return
744
+ end
745
+
746
+ profiles = config.available_profiles
747
+ if profiles.empty?
748
+ puts 'No profiles found.'
749
+ else
750
+ puts 'Available profiles:'
751
+ puts ''
752
+ profiles.each do |profile|
753
+ default_marker = profile == config.default_profile ? ' (default)' : ''
754
+ puts " #{profile}#{default_marker}"
755
+ description = load_profile_description(config.config_path, profile)
756
+ puts " #{description}" if description
757
+ puts ''
758
+ end
759
+ end
760
+ end
761
+
762
+ def load_profile_description(config_path, profile)
763
+ desc_file = File.join(config_path, 'profiles', profile, 'description.txt')
764
+ return nil unless File.exist?(desc_file)
765
+
766
+ lines = File.readlines(desc_file).map(&:strip).reject(&:empty?)
767
+ # Return first line after the title (the "Use when:" line)
768
+ lines[1] if lines.size > 1
769
+ end
770
+
771
+ def config_path
772
+ puts Appydave::Tools::ZshHistory::Config::DEFAULT_CONFIG_PATH
773
+ end
774
+
775
+ # ============================================================
776
+ # OUTPUT HELPERS
777
+ # ============================================================
778
+
779
+ def write_output(content, output_path)
780
+ if output_path
781
+ File.write(output_path, content)
782
+ puts "Written to: #{output_path}"
783
+ else
784
+ puts content
785
+ end
786
+ end
787
+
788
+ # ============================================================
789
+ # OPTION PARSING
790
+ # ============================================================
791
+
792
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
793
+ def parse_common_options(args)
794
+ options = {
795
+ days: nil,
796
+ unsure: false,
797
+ verbose: false,
798
+ all: false,
799
+ dry_run: false,
800
+ profile: nil,
801
+ output: nil,
802
+ history_path: File.expand_path('~/.zsh_history')
803
+ }
804
+
805
+ parser = OptionParser.new do |opts|
806
+ opts.on('-d', '--days N', Integer, 'Only show last N days') do |days|
807
+ options[:days] = days
808
+ end
809
+
810
+ opts.on('-u', '--unsure', 'Include unsure commands') do
811
+ options[:unsure] = true
812
+ end
813
+
814
+ opts.on('-a', '--all', 'Show all commands (no filtering)') do
815
+ options[:all] = true
816
+ end
817
+
818
+ opts.on('-v', '--verbose', 'Show which pattern matched') do
819
+ options[:verbose] = true
820
+ end
821
+
822
+ opts.on('--dry-run', 'Preview changes without writing') do
823
+ options[:dry_run] = true
824
+ end
825
+
826
+ opts.on('-p', '--profile NAME', 'Use named profile for patterns') do |name|
827
+ options[:profile] = name
828
+ end
829
+
830
+ opts.on('-o', '--output PATH', 'Write output to file') do |path|
831
+ options[:output] = File.expand_path(path)
832
+ end
833
+
834
+ opts.on('-f', '--file PATH', 'Path to history file') do |path|
835
+ options[:history_path] = File.expand_path(path)
836
+ end
837
+ end
838
+
839
+ parser.parse!(args)
840
+ options
841
+ end
842
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
843
+ end
844
+
845
+ # Run CLI
846
+ ZshHistoryCLI.new.run if $PROGRAM_NAME == __FILE__