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.
- checksums.yaml +4 -4
- data/.claude/commands/brainstorming-agent.md +227 -0
- data/.claude/commands/cli-test.md +251 -0
- data/.claude/commands/dev.md +234 -0
- data/.claude/commands/po.md +227 -0
- data/.claude/commands/progress.md +51 -0
- data/.claude/commands/uat.md +321 -0
- data/.rubocop.yml +9 -0
- data/AGENTS.md +43 -0
- data/CHANGELOG.md +12 -0
- data/CLAUDE.md +26 -3
- data/README.md +15 -0
- data/bin/dam +21 -1
- data/bin/jump.rb +29 -0
- data/bin/subtitle_processor.rb +54 -1
- data/bin/zsh_history.rb +846 -0
- data/docs/README.md +162 -69
- data/docs/architecture/cli/exe-bin-convention.md +434 -0
- data/docs/architecture/cli-patterns.md +631 -0
- data/docs/architecture/gpt-context/gpt-context-architecture.md +325 -0
- data/docs/architecture/gpt-context/gpt-context-implementation-guide.md +419 -0
- data/docs/architecture/gpt-context/gpt-context-vision.md +179 -0
- data/docs/architecture/testing/testing-patterns.md +762 -0
- data/docs/backlog.md +120 -0
- data/docs/cli-tests/FR-3-jump-location-tool.md +515 -0
- data/docs/specs/fr-002-gpt-context-help-system.md +265 -0
- data/docs/specs/fr-003-jump-location-tool.md +779 -0
- data/docs/specs/zsh-history-tool.md +820 -0
- data/docs/uat/FR-3-jump-location-tool.md +741 -0
- data/exe/jump +11 -0
- data/exe/{subtitle_manager → subtitle_processor} +1 -1
- data/exe/zsh_history +11 -0
- data/lib/appydave/tools/configuration/openai.rb +1 -1
- data/lib/appydave/tools/dam/file_helper.rb +28 -0
- data/lib/appydave/tools/dam/project_listing.rb +4 -30
- data/lib/appydave/tools/dam/s3_operations.rb +2 -1
- data/lib/appydave/tools/dam/ssd_status.rb +226 -0
- data/lib/appydave/tools/dam/status.rb +3 -51
- data/lib/appydave/tools/jump/cli.rb +561 -0
- data/lib/appydave/tools/jump/commands/add.rb +52 -0
- data/lib/appydave/tools/jump/commands/base.rb +43 -0
- data/lib/appydave/tools/jump/commands/generate.rb +153 -0
- data/lib/appydave/tools/jump/commands/remove.rb +58 -0
- data/lib/appydave/tools/jump/commands/report.rb +214 -0
- data/lib/appydave/tools/jump/commands/update.rb +42 -0
- data/lib/appydave/tools/jump/commands/validate.rb +54 -0
- data/lib/appydave/tools/jump/config.rb +233 -0
- data/lib/appydave/tools/jump/formatters/base.rb +48 -0
- data/lib/appydave/tools/jump/formatters/json_formatter.rb +19 -0
- data/lib/appydave/tools/jump/formatters/paths_formatter.rb +21 -0
- data/lib/appydave/tools/jump/formatters/table_formatter.rb +183 -0
- data/lib/appydave/tools/jump/location.rb +134 -0
- data/lib/appydave/tools/jump/path_validator.rb +47 -0
- data/lib/appydave/tools/jump/search.rb +230 -0
- data/lib/appydave/tools/subtitle_processor/transcript.rb +51 -0
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools/zsh_history/command.rb +37 -0
- data/lib/appydave/tools/zsh_history/config.rb +235 -0
- data/lib/appydave/tools/zsh_history/filter.rb +184 -0
- data/lib/appydave/tools/zsh_history/formatter.rb +75 -0
- data/lib/appydave/tools/zsh_history/parser.rb +101 -0
- data/lib/appydave/tools.rb +25 -0
- data/package.json +1 -1
- metadata +51 -4
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
# CLI Patterns Guide
|
|
2
|
+
|
|
3
|
+
This document analyzes the CLI patterns used across appydave-tools and provides guidance on which pattern to use for different scenarios.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The codebase uses **4 distinct CLI patterns**, each suited to different complexity levels:
|
|
8
|
+
|
|
9
|
+
| Pattern | Example Tools | Best For |
|
|
10
|
+
|---------|---------------|----------|
|
|
11
|
+
| [Simple Procedural](#pattern-1-simple-procedural) | GPT Context, Configuration | Single-purpose tools, < 5 options |
|
|
12
|
+
| [Action Class Dispatch](#pattern-2-action-class-dispatch) | YouTube Manager | Medium complexity, reusable actions |
|
|
13
|
+
| [Method Dispatch (Light)](#pattern-3-method-dispatch-light) | Subtitle Processor | Multi-command with per-command options |
|
|
14
|
+
| [Method Dispatch (Full)](#pattern-4-method-dispatch-full) | DAM | Complex tools, hierarchical help, 10+ commands |
|
|
15
|
+
|
|
16
|
+
## Pattern Decision Flowchart
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
How many commands/subcommands?
|
|
20
|
+
│
|
|
21
|
+
├─ 1 (single purpose) ──────────────────────► Pattern 1: Simple Procedural
|
|
22
|
+
│
|
|
23
|
+
├─ 2-5 commands
|
|
24
|
+
│ │
|
|
25
|
+
│ ├─ Actions reusable elsewhere? ─── Yes ─► Pattern 2: Action Class Dispatch
|
|
26
|
+
│ │
|
|
27
|
+
│ └─ Self-contained tool? ─────────── Yes ─► Pattern 3: Method Dispatch (Light)
|
|
28
|
+
│
|
|
29
|
+
└─ 6+ commands
|
|
30
|
+
│
|
|
31
|
+
├─ Need hierarchical help? ──────── Yes ─► Pattern 4: Method Dispatch (Full)
|
|
32
|
+
│
|
|
33
|
+
└─ Simple help is fine? ─────────── Yes ─► Pattern 3: Method Dispatch (Light)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Pattern 1: Simple Procedural
|
|
39
|
+
|
|
40
|
+
**Used by:** `gpt_context.rb`, `configuration.rb`
|
|
41
|
+
|
|
42
|
+
**Characteristics:**
|
|
43
|
+
- Direct OptionParser usage at script level
|
|
44
|
+
- No wrapper class (or minimal)
|
|
45
|
+
- Options collected into hash or struct
|
|
46
|
+
- `case/when` for command dispatch (if needed)
|
|
47
|
+
- Delegates to library classes for business logic
|
|
48
|
+
|
|
49
|
+
**When to use:**
|
|
50
|
+
- Single-purpose tools
|
|
51
|
+
- Fewer than 5 options/flags
|
|
52
|
+
- No subcommands, or very few (< 3)
|
|
53
|
+
- Quick scripts that don't need extensibility
|
|
54
|
+
|
|
55
|
+
### Example: GPT Context (Single Purpose)
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
#!/usr/bin/env ruby
|
|
59
|
+
# frozen_string_literal: true
|
|
60
|
+
|
|
61
|
+
require 'appydave/tools'
|
|
62
|
+
|
|
63
|
+
options = Appydave::Tools::GptContext::Options.new(
|
|
64
|
+
working_directory: nil
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
OptionParser.new do |opts|
|
|
68
|
+
opts.banner = 'Usage: gpt_context [options]'
|
|
69
|
+
|
|
70
|
+
opts.on('-i', '--include PATTERN', 'Pattern to include') do |pattern|
|
|
71
|
+
options.include_patterns << pattern
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
opts.on('-e', '--exclude PATTERN', 'Pattern to exclude') do |pattern|
|
|
75
|
+
options.exclude_patterns << pattern
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
opts.on('-f', '--format FORMAT', 'Output format') do |format|
|
|
79
|
+
options.format = format
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
opts.on('-d', '--debug', 'Enable debug mode') do
|
|
83
|
+
options.debug = true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
|
87
|
+
puts opts
|
|
88
|
+
exit
|
|
89
|
+
end
|
|
90
|
+
end.parse!
|
|
91
|
+
|
|
92
|
+
# Delegate to library classes
|
|
93
|
+
gatherer = Appydave::Tools::GptContext::FileCollector.new(options)
|
|
94
|
+
content = gatherer.build
|
|
95
|
+
|
|
96
|
+
output_handler = Appydave::Tools::GptContext::OutputHandler.new(content, options)
|
|
97
|
+
output_handler.execute
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Example: Configuration (Flag-based Commands)
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
#!/usr/bin/env ruby
|
|
104
|
+
# frozen_string_literal: true
|
|
105
|
+
|
|
106
|
+
require 'appydave/tools'
|
|
107
|
+
|
|
108
|
+
options = { keys: [] }
|
|
109
|
+
|
|
110
|
+
OptionParser.new do |opts|
|
|
111
|
+
opts.banner = 'Usage: ad_config [options]'
|
|
112
|
+
|
|
113
|
+
opts.on('-e', '--edit', 'Edit configuration') do
|
|
114
|
+
options[:command] = :edit
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
opts.on('-l', '--list', 'List configurations') do
|
|
118
|
+
options[:command] = :list
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
opts.on('-c', '--create', 'Create missing configs') do
|
|
122
|
+
options[:command] = :create
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
opts.on('-p', '--print [KEYS]', Array, 'Print config values') do |keys|
|
|
126
|
+
options[:command] = :print
|
|
127
|
+
options[:keys] = keys
|
|
128
|
+
end
|
|
129
|
+
end.parse!
|
|
130
|
+
|
|
131
|
+
# Simple case/when dispatch
|
|
132
|
+
case options[:command]
|
|
133
|
+
when :edit
|
|
134
|
+
Appydave::Tools::Configuration::Config.edit
|
|
135
|
+
when :list
|
|
136
|
+
# ... list logic
|
|
137
|
+
when :create
|
|
138
|
+
# ... create logic
|
|
139
|
+
when :print
|
|
140
|
+
Appydave::Tools::Configuration::Config.print(*options[:keys])
|
|
141
|
+
else
|
|
142
|
+
puts 'No command provided. Use --help for usage.'
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Pros:**
|
|
147
|
+
- Minimal boilerplate
|
|
148
|
+
- Easy to understand
|
|
149
|
+
- Fast to implement
|
|
150
|
+
- Standard Ruby pattern
|
|
151
|
+
|
|
152
|
+
**Cons:**
|
|
153
|
+
- Doesn't scale well beyond 5 options
|
|
154
|
+
- No help hierarchy
|
|
155
|
+
- Hard to test CLI parsing separately
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Pattern 2: Action Class Dispatch
|
|
160
|
+
|
|
161
|
+
**Used by:** `youtube_manager.rb`
|
|
162
|
+
|
|
163
|
+
**Characteristics:**
|
|
164
|
+
- CLI class routes commands to Action objects
|
|
165
|
+
- Each Action is a separate class with `.action(args)` method
|
|
166
|
+
- Actions handle their own OptionParser
|
|
167
|
+
- Actions can be reused by other code (not just CLI)
|
|
168
|
+
|
|
169
|
+
**When to use:**
|
|
170
|
+
- Actions need to be callable from multiple places (CLI, tests, other code)
|
|
171
|
+
- Medium complexity (2-5 commands)
|
|
172
|
+
- Each command has distinct option sets
|
|
173
|
+
- Want clean separation between routing and execution
|
|
174
|
+
|
|
175
|
+
### Example: YouTube Manager
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
#!/usr/bin/env ruby
|
|
179
|
+
# frozen_string_literal: true
|
|
180
|
+
|
|
181
|
+
require 'appydave/tools'
|
|
182
|
+
|
|
183
|
+
class YouTubeVideoManagerCLI
|
|
184
|
+
def initialize
|
|
185
|
+
@commands = {
|
|
186
|
+
'get' => Appydave::Tools::CliActions::GetVideoAction.new,
|
|
187
|
+
'update' => Appydave::Tools::CliActions::UpdateVideoAction.new
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def run
|
|
192
|
+
command, *args = ARGV
|
|
193
|
+
if @commands.key?(command)
|
|
194
|
+
@commands[command].action(args)
|
|
195
|
+
else
|
|
196
|
+
puts "Unknown command: #{command}"
|
|
197
|
+
print_help
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def print_help
|
|
204
|
+
puts 'Usage: youtube_manager [command] [options]'
|
|
205
|
+
puts 'Commands:'
|
|
206
|
+
puts ' get Get video details'
|
|
207
|
+
puts ' update Update video metadata'
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
YouTubeVideoManagerCLI.new.run
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Action Class Structure:**
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
# lib/appydave/tools/cli_actions/get_video_action.rb
|
|
218
|
+
module Appydave::Tools::CliActions
|
|
219
|
+
class GetVideoAction
|
|
220
|
+
def action(args)
|
|
221
|
+
options = parse_options(args)
|
|
222
|
+
|
|
223
|
+
video = YouTubeManager::GetVideo.new
|
|
224
|
+
video.get(options[:video_id])
|
|
225
|
+
# ... display results
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
private
|
|
229
|
+
|
|
230
|
+
def parse_options(args)
|
|
231
|
+
options = {}
|
|
232
|
+
OptionParser.new do |opts|
|
|
233
|
+
opts.on('-v', '--video-id ID', 'Video ID') { |v| options[:video_id] = v }
|
|
234
|
+
opts.on('-h', '--help', 'Show help') { puts opts; exit }
|
|
235
|
+
end.parse!(args)
|
|
236
|
+
options
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Pros:**
|
|
243
|
+
- Actions are testable independently
|
|
244
|
+
- Clean separation of concerns
|
|
245
|
+
- Actions reusable outside CLI context
|
|
246
|
+
- Easy to add new commands
|
|
247
|
+
|
|
248
|
+
**Cons:**
|
|
249
|
+
- More files to manage
|
|
250
|
+
- Slight overhead for simple cases
|
|
251
|
+
- Requires Action class convention
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Pattern 3: Method Dispatch (Light)
|
|
256
|
+
|
|
257
|
+
**Used by:** `subtitle_processor.rb`
|
|
258
|
+
|
|
259
|
+
**Characteristics:**
|
|
260
|
+
- CLI class with command map to `method(:name)`
|
|
261
|
+
- Each command method handles its own OptionParser
|
|
262
|
+
- Per-command help via `-h` flag
|
|
263
|
+
- Self-contained in single file
|
|
264
|
+
|
|
265
|
+
**When to use:**
|
|
266
|
+
- 3-8 commands with distinct option sets
|
|
267
|
+
- Commands are tool-specific (not reusable elsewhere)
|
|
268
|
+
- Want subcommand-style interface: `tool command --options`
|
|
269
|
+
- Moderate complexity, single-file preferred
|
|
270
|
+
|
|
271
|
+
### Example: Subtitle Processor
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
#!/usr/bin/env ruby
|
|
275
|
+
# frozen_string_literal: true
|
|
276
|
+
|
|
277
|
+
require 'appydave/tools'
|
|
278
|
+
|
|
279
|
+
class SubtitleProcessorCLI
|
|
280
|
+
def initialize
|
|
281
|
+
@commands = {
|
|
282
|
+
'clean' => method(:clean_subtitles),
|
|
283
|
+
'join' => method(:join_subtitles),
|
|
284
|
+
'transcript' => method(:transcript_subtitles)
|
|
285
|
+
}
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def run
|
|
289
|
+
command, *args = ARGV
|
|
290
|
+
if command.nil?
|
|
291
|
+
print_help
|
|
292
|
+
exit
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
if @commands.key?(command)
|
|
296
|
+
@commands[command].call(args)
|
|
297
|
+
else
|
|
298
|
+
puts "Unknown command: #{command}"
|
|
299
|
+
print_help
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
private
|
|
304
|
+
|
|
305
|
+
def clean_subtitles(args)
|
|
306
|
+
options = { file: nil, output: nil }
|
|
307
|
+
|
|
308
|
+
parser = OptionParser.new do |opts|
|
|
309
|
+
opts.banner = 'Usage: subtitle_processor clean [options]'
|
|
310
|
+
opts.on('-f', '--file FILE', 'Input SRT file') { |v| options[:file] = v }
|
|
311
|
+
opts.on('-o', '--output FILE', 'Output file') { |v| options[:output] = v }
|
|
312
|
+
opts.on('-h', '--help', 'Show help') { puts opts; exit }
|
|
313
|
+
end
|
|
314
|
+
parser.parse!(args)
|
|
315
|
+
|
|
316
|
+
validate_required!(options, [:file, :output], parser)
|
|
317
|
+
|
|
318
|
+
cleaner = Appydave::Tools::SubtitleProcessor::Clean.new(file_path: options[:file])
|
|
319
|
+
cleaner.clean
|
|
320
|
+
cleaner.write(options[:output])
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def join_subtitles(args)
|
|
324
|
+
options = { folder: './', files: '*.srt', output: 'merged.srt' }
|
|
325
|
+
|
|
326
|
+
parser = OptionParser.new do |opts|
|
|
327
|
+
opts.banner = 'Usage: subtitle_processor join [options]'
|
|
328
|
+
opts.on('-d', '--directory DIR', 'Directory') { |v| options[:folder] = v }
|
|
329
|
+
opts.on('-f', '--files PATTERN', 'File pattern') { |v| options[:files] = v }
|
|
330
|
+
opts.on('-o', '--output FILE', 'Output file') { |v| options[:output] = v }
|
|
331
|
+
opts.on('-h', '--help', 'Show help') { puts opts; exit }
|
|
332
|
+
end
|
|
333
|
+
parser.parse!(args)
|
|
334
|
+
|
|
335
|
+
joiner = Appydave::Tools::SubtitleProcessor::Join.new(**options)
|
|
336
|
+
joiner.join
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def print_help
|
|
340
|
+
puts 'Usage: subtitle_processor [command] [options]'
|
|
341
|
+
puts 'Commands:'
|
|
342
|
+
puts ' clean Clean SRT files'
|
|
343
|
+
puts ' join Join multiple SRT files'
|
|
344
|
+
puts ' transcript Convert to plain text'
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def validate_required!(options, required, parser)
|
|
348
|
+
missing = required.select { |k| options[k].nil? }
|
|
349
|
+
return if missing.empty?
|
|
350
|
+
|
|
351
|
+
puts "Error: Missing required options: #{missing.join(', ')}"
|
|
352
|
+
puts parser
|
|
353
|
+
exit 1
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
SubtitleProcessorCLI.new.run
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Pros:**
|
|
361
|
+
- Clean subcommand interface
|
|
362
|
+
- Per-command help works naturally
|
|
363
|
+
- Single file, easy to navigate
|
|
364
|
+
- Scales to ~10 commands comfortably
|
|
365
|
+
|
|
366
|
+
**Cons:**
|
|
367
|
+
- File gets long with many commands
|
|
368
|
+
- No hierarchical help topics
|
|
369
|
+
- Command methods not reusable outside CLI
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Pattern 4: Method Dispatch (Full)
|
|
374
|
+
|
|
375
|
+
**Used by:** `dam` (bin/dam)
|
|
376
|
+
|
|
377
|
+
**Characteristics:**
|
|
378
|
+
- Large CLI class (1000+ lines)
|
|
379
|
+
- Command map to `method(:name)`
|
|
380
|
+
- **Hierarchical help system**: `tool help <topic>`
|
|
381
|
+
- Custom argument parsing per command (not just OptionParser)
|
|
382
|
+
- Deprecated command aliases
|
|
383
|
+
- Auto-detection features (e.g., detect brand from PWD)
|
|
384
|
+
|
|
385
|
+
**When to use:**
|
|
386
|
+
- 10+ commands
|
|
387
|
+
- Complex help needs (topics, workflows, configuration)
|
|
388
|
+
- Advanced features (auto-detection, patterns, shortcuts)
|
|
389
|
+
- Tool is central to workflow, worth the investment
|
|
390
|
+
|
|
391
|
+
### Example: DAM Structure
|
|
392
|
+
|
|
393
|
+
```ruby
|
|
394
|
+
#!/usr/bin/env ruby
|
|
395
|
+
# frozen_string_literal: true
|
|
396
|
+
|
|
397
|
+
require 'appydave/tools'
|
|
398
|
+
|
|
399
|
+
class VatCLI
|
|
400
|
+
def initialize
|
|
401
|
+
@commands = {
|
|
402
|
+
'help' => method(:help_command),
|
|
403
|
+
'list' => method(:list_command),
|
|
404
|
+
's3-up' => method(:s3_up_command),
|
|
405
|
+
's3-down' => method(:s3_down_command),
|
|
406
|
+
's3-status' => method(:s3_status_command),
|
|
407
|
+
# ... 15+ more commands
|
|
408
|
+
|
|
409
|
+
# Deprecated aliases (backward compatibility)
|
|
410
|
+
's3-cleanup' => method(:s3_cleanup_remote_command)
|
|
411
|
+
}
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def run
|
|
415
|
+
command, *args = ARGV
|
|
416
|
+
|
|
417
|
+
# Version flag handling
|
|
418
|
+
if ['--version', '-v'].include?(command)
|
|
419
|
+
puts "DAM v#{Appydave::Tools::VERSION}"
|
|
420
|
+
exit
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# No command = show quick usage
|
|
424
|
+
if command.nil?
|
|
425
|
+
puts 'DAM - Video Asset Tools'
|
|
426
|
+
puts 'Usage: dam [command] [options]'
|
|
427
|
+
puts "Run 'dam help' for more information."
|
|
428
|
+
exit
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Dispatch or error with helpful message
|
|
432
|
+
if @commands.key?(command)
|
|
433
|
+
@commands[command].call(args)
|
|
434
|
+
else
|
|
435
|
+
puts "Unknown command: #{command}"
|
|
436
|
+
puts 'Available: list, s3-up, s3-down, s3-status, ...'
|
|
437
|
+
exit 1
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
private
|
|
442
|
+
|
|
443
|
+
# ─────────────────────────────────────────────────────────────────
|
|
444
|
+
# HIERARCHICAL HELP SYSTEM
|
|
445
|
+
# ─────────────────────────────────────────────────────────────────
|
|
446
|
+
|
|
447
|
+
def help_command(args)
|
|
448
|
+
topic = args[0]
|
|
449
|
+
|
|
450
|
+
case topic
|
|
451
|
+
when 'brands'
|
|
452
|
+
show_brands_help
|
|
453
|
+
when 'workflows'
|
|
454
|
+
show_workflows_help
|
|
455
|
+
when 'config'
|
|
456
|
+
show_config_help
|
|
457
|
+
when 's3-up', 's3-down', 's3-status'
|
|
458
|
+
show_s3_help(topic)
|
|
459
|
+
when 's3-cleanup' # Deprecated
|
|
460
|
+
puts "⚠️ 's3-cleanup' is deprecated. Use 's3-cleanup-remote'."
|
|
461
|
+
show_s3_help('s3-cleanup-remote')
|
|
462
|
+
when nil
|
|
463
|
+
show_main_help
|
|
464
|
+
else
|
|
465
|
+
puts "Unknown help topic: #{topic}"
|
|
466
|
+
show_help_topics
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def show_main_help
|
|
471
|
+
puts <<~HELP
|
|
472
|
+
DAM - Digital Asset Management for Video Projects
|
|
473
|
+
|
|
474
|
+
Usage: dam [command] [options]
|
|
475
|
+
|
|
476
|
+
Commands:
|
|
477
|
+
list List brands and projects
|
|
478
|
+
s3-up Upload project to S3
|
|
479
|
+
s3-down Download project from S3
|
|
480
|
+
...
|
|
481
|
+
|
|
482
|
+
Help Topics:
|
|
483
|
+
dam help brands Brand shortcuts and configuration
|
|
484
|
+
dam help workflows FliVideo and Storyline workflows
|
|
485
|
+
dam help config Configuration setup
|
|
486
|
+
HELP
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def show_brands_help
|
|
490
|
+
puts <<~HELP
|
|
491
|
+
Available Brands
|
|
492
|
+
|
|
493
|
+
DAM supports multi-tenant video project management:
|
|
494
|
+
|
|
495
|
+
appydave → v-appydave (AppyDave brand)
|
|
496
|
+
voz → v-voz (VOZ client)
|
|
497
|
+
aitldr → v-aitldr (AITLDR brand)
|
|
498
|
+
...
|
|
499
|
+
|
|
500
|
+
Examples:
|
|
501
|
+
dam list appydave
|
|
502
|
+
dam s3-up voz boy-baker
|
|
503
|
+
HELP
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# ─────────────────────────────────────────────────────────────────
|
|
507
|
+
# COMMAND IMPLEMENTATIONS
|
|
508
|
+
# ─────────────────────────────────────────────────────────────────
|
|
509
|
+
|
|
510
|
+
def s3_up_command(args)
|
|
511
|
+
options = parse_s3_args(args, 's3-up')
|
|
512
|
+
s3_ops = Appydave::Tools::Dam::S3Operations.new(options[:brand], options[:project])
|
|
513
|
+
s3_ops.upload(dry_run: options[:dry_run])
|
|
514
|
+
rescue StandardError => e
|
|
515
|
+
puts "Error: #{e.message}"
|
|
516
|
+
exit 1
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# ─────────────────────────────────────────────────────────────────
|
|
520
|
+
# CUSTOM ARGUMENT PARSING
|
|
521
|
+
# ─────────────────────────────────────────────────────────────────
|
|
522
|
+
|
|
523
|
+
def parse_s3_args(args, command)
|
|
524
|
+
dry_run = args.include?('--dry-run')
|
|
525
|
+
force = args.include?('--force')
|
|
526
|
+
args = args.reject { |arg| arg.start_with?('--') }
|
|
527
|
+
|
|
528
|
+
brand_arg = args[0]
|
|
529
|
+
project_arg = args[1]
|
|
530
|
+
|
|
531
|
+
# Auto-detect from current directory if no args
|
|
532
|
+
if brand_arg.nil?
|
|
533
|
+
brand, project_id = Appydave::Tools::Dam::ProjectResolver.detect_from_pwd
|
|
534
|
+
if brand.nil?
|
|
535
|
+
puts "Could not auto-detect. Usage: dam #{command} <brand> <project>"
|
|
536
|
+
exit 1
|
|
537
|
+
end
|
|
538
|
+
else
|
|
539
|
+
# Validate and resolve
|
|
540
|
+
validate_brand!(brand_arg)
|
|
541
|
+
project_id = Appydave::Tools::Dam::ProjectResolver.resolve(brand_arg, project_arg)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
{ brand: brand_arg, project: project_id, dry_run: dry_run, force: force }
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
VatCLI.new.run
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Pros:**
|
|
552
|
+
- Scales to 20+ commands
|
|
553
|
+
- Rich, hierarchical help system
|
|
554
|
+
- Supports deprecated aliases gracefully
|
|
555
|
+
- Advanced features (auto-detection, patterns)
|
|
556
|
+
- Professional CLI experience
|
|
557
|
+
|
|
558
|
+
**Cons:**
|
|
559
|
+
- Large file (1000+ lines)
|
|
560
|
+
- Complex to maintain
|
|
561
|
+
- Significant upfront investment
|
|
562
|
+
- Custom parsing logic to test
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## Pattern Comparison Summary
|
|
567
|
+
|
|
568
|
+
| Aspect | Pattern 1 | Pattern 2 | Pattern 3 | Pattern 4 |
|
|
569
|
+
|--------|-----------|-----------|-----------|-----------|
|
|
570
|
+
| **Commands** | 1 | 2-5 | 3-10 | 10+ |
|
|
571
|
+
| **File Count** | 1 | Multiple | 1 | 1 (large) |
|
|
572
|
+
| **Help System** | `-h` flag | Per-command `-h` | Per-command `-h` | Hierarchical topics |
|
|
573
|
+
| **Reusability** | Low | High (Actions) | Medium | Low |
|
|
574
|
+
| **Testability** | Medium | High | Medium | Medium |
|
|
575
|
+
| **Boilerplate** | Minimal | Medium | Medium | High |
|
|
576
|
+
| **Learning Curve** | Low | Medium | Low | Medium |
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Recommendations by Scenario
|
|
581
|
+
|
|
582
|
+
### New single-purpose tool
|
|
583
|
+
→ **Pattern 1: Simple Procedural**
|
|
584
|
+
|
|
585
|
+
Example: A tool to format JSON files
|
|
586
|
+
```bash
|
|
587
|
+
json_formatter -i input.json -o output.json --pretty
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Tool with actions used elsewhere
|
|
591
|
+
→ **Pattern 2: Action Class Dispatch**
|
|
592
|
+
|
|
593
|
+
Example: A build tool where actions are also called by CI scripts
|
|
594
|
+
```bash
|
|
595
|
+
build_tool compile # Also called by BuildAction.new.action([])
|
|
596
|
+
build_tool test
|
|
597
|
+
build_tool deploy
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Medium complexity, self-contained
|
|
601
|
+
→ **Pattern 3: Method Dispatch (Light)**
|
|
602
|
+
|
|
603
|
+
Example: A subtitle/media processing tool
|
|
604
|
+
```bash
|
|
605
|
+
media_tool convert -i video.mp4 -o video.webm
|
|
606
|
+
media_tool thumbnail -i video.mp4 -t 5
|
|
607
|
+
media_tool info -i video.mp4
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Complex workflow tool
|
|
611
|
+
→ **Pattern 4: Method Dispatch (Full)**
|
|
612
|
+
|
|
613
|
+
Example: A project management CLI
|
|
614
|
+
```bash
|
|
615
|
+
project help topics
|
|
616
|
+
project init --template react
|
|
617
|
+
project deploy staging --dry-run
|
|
618
|
+
project status --all
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## Migration Path
|
|
624
|
+
|
|
625
|
+
If a tool outgrows its pattern:
|
|
626
|
+
|
|
627
|
+
1. **Pattern 1 → Pattern 3**: Extract command logic into methods, add command dispatch
|
|
628
|
+
2. **Pattern 3 → Pattern 4**: Add hierarchical help, enhance argument parsing
|
|
629
|
+
3. **Pattern 3 → Pattern 2**: Extract methods into Action classes if reuse needed
|
|
630
|
+
|
|
631
|
+
The patterns are designed to be progressive - start simple, add complexity only when needed.
|