Project 6: Personal DevOps Toolkit (Capstone)
Build a unified
devtoolCLI that wraps your log analyzer, CSV pipeline, refactoring toolkit, and audit tool into one cohesive interface.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 3: Advanced |
| Time Estimate | 2-3 weeks |
| Main Programming Language | Shell (bash) |
| Alternative Programming Languages | Python, Go |
| Coolness Level | Level 5: Your Own Toolchain |
| Business Potential | Level 4: Automation Platform |
| Prerequisites | Solid shell scripting, prior projects complete |
| Key Topics | CLI design, modular scripting, config management, portability |
1. Learning Objectives
By completing this project, you will:
- Design a multi-command CLI with consistent behavior across subcommands.
- Build a modular shell codebase with reusable libraries.
- Implement configuration precedence (defaults, config file, env, flags).
- Package and distribute a CLI tool safely.
- Integrate existing tools into a unified workflow.
2. All Theory Needed (Per-Concept Breakdown)
2.1 CLI Architecture and Subcommand Dispatch
Fundamentals
A multi-command CLI groups related workflows under a single tool, using subcommands like devtool logs analyze or devtool data transform. This structure improves discoverability and makes complex tooling feel cohesive. A good CLI architecture defines a command tree, a consistent flag style, and shared behavior for help and errors. Subcommand dispatch is typically implemented by parsing the first argument and routing execution to a handler function. Understanding this pattern is essential for building a maintainable CLI with many features. It also lets you standardize exit codes and output formatting across every command. Users should be able to discover commands without reading the source.
Deep Dive into the concept
CLI architecture starts with information hierarchy. The top-level command (devtool) should provide broad categories (logs, data, code, system), each of which contains specific actions. This mirrors how users think about tasks. A command tree also reduces flag collisions: devtool logs analyze can have flags specific to log analysis without polluting the entire CLI namespace.
Dispatch can be implemented with a case statement on $1 and nested case statements for subcommands. For example, the top-level handler switches on logs|data|code|system, then each category handler switches on its action. This structure is easy to follow in shell scripts and scales as long as the command tree is not too deep. A small helper function can print usage text and exit with code 2 for invalid commands. This mirrors standard Unix behavior.
Consistent flag handling matters. Users expect --help to work everywhere, and errors should point to the correct usage. For a toolkit, implement a global help flag plus per-subcommand help. Also define a consistent set of exit codes: 0 for success, 1 for runtime failure, 2 for invalid arguments. Reuse these across subcommands so the tool behaves predictably.
Another important aspect is shared output style. If each subcommand prints entirely different formats, the CLI feels inconsistent. Define conventions: use INFO: for normal status lines, ERROR: for errors, and a consistent timestamp format for logs. This makes it easier to use the toolkit in scripts and to parse output.
Finally, consider extensibility. A robust CLI architecture allows you to add new subcommands without modifying the core too much. This can be achieved by having each subcommand as a separate script in lib/commands/ and a dispatcher that sources and calls them. This mirrors how many large shell tools (like git’s plumbing commands) are structured.
Good CLI architecture also includes a consistent argument grammar. Decide early whether flags can appear before or after subcommands, and enforce that rule everywhere. For example, some tools allow global flags like --config before the subcommand, while others require subcommand-specific flags after it. Pick one pattern and document it to avoid confusing behavior. You should also provide short aliases for commonly used subcommands or flags if they improve usability, but avoid conflicts. Consistency matters more than cleverness.
Another practical concern is output design. When a command is run interactively, concise status lines are helpful; when run in automation, machine-readable output is more valuable. A common strategy is to provide a --quiet or --json flag at the top level that influences all subcommands. Even if you do not implement JSON output in this project, shaping your output with this possibility in mind will keep the architecture clean. For example, avoid ad-hoc print statements inside deep logic; instead, return structured data that a formatter can print. This separation makes the CLI easier to test and easier to extend.
A final UX detail is error messaging. Always include the exact subcommand and a short usage line in error output so users know what to fix. This reduces frustration and makes the CLI self-teaching. Small details like these are what make tools feel "professional" rather than like a loose collection of scripts.
How this fit on projects
This concept defines how the capstone tool integrates the previous projects into a single interface.
Definitions & key terms
- Subcommand: A secondary command within a CLI (
tool <subcommand>). - Dispatcher: Logic that routes to subcommand handlers.
- Usage text: Help output that documents flags and arguments.
- Exit codes: Numeric status codes for script integration.
Mental model diagram (ASCII)
devtool
|-- logs -> analyze, report
|-- data -> transform, summary
|-- code -> refactor, rollback
|-- system -> audit
How it works (step-by-step, with invariants and failure modes)
- Parse
$1as category. - Parse
$2as action. - Route to the appropriate handler.
- Handler parses flags and executes logic.
Invariant: Unknown commands print usage and exit with code 2. Failure modes: inconsistent flag parsing, missing help output.
Minimal concrete example
case "$1" in
logs) shift; logs_dispatch "$@" ;;
data) shift; data_dispatch "$@" ;;
*) usage; exit 2 ;;
esac
Common misconceptions
- “A single script is enough” -> Modular structure is easier to maintain.
- “Help text is optional” -> It is essential for usability.
- “Exit codes are per subcommand” -> They should be consistent across the CLI.
Check-your-understanding questions
- Why should exit codes be consistent across subcommands?
- What is the advantage of a command tree?
- How would you implement
--helpfor nested commands? - Why should usage text include examples?
Check-your-understanding answers
- Predictability enables scripting and automation.
- It groups related tasks and reduces flag conflicts.
- Each dispatcher checks for
--helpand prints its own usage. - Examples show expected syntax and reduce user error.
Real-world applications
- git, kubectl, and docker CLIs use command trees.
- Internal developer tools with multiple workflows.
Where you’ll apply it
- See §3.2 for CLI functional requirements.
- See §5.2 for project structure and dispatch layout.
- Also used in: P01 Log Analyzer and P04 System Audit for subcommand integration.
References
- “The Unix Programming Environment” (CLI conventions)
getoptsdocumentation
Key insights
A well-structured CLI is a product: consistency and discoverability matter as much as features.
Summary
Subcommand dispatch and consistent help/exit codes turn a collection of scripts into a cohesive tool.
Homework/Exercises to practice the concept
- Sketch a command tree for your top 5 CLI tasks.
- Implement a dispatcher with
casestatements. - Add
--helpfor one subcommand.
Solutions to the homework/exercises
- Group tasks into 3-4 categories with 1-2 actions each.
- Use a top-level
caseon$1and nestedcaseon$2. - If
$1 == --help, print usage and exit 0.
2.2 Configuration, Defaults, and Precedence
Fundamentals
A CLI toolkit must support configuration so users can set defaults. Configuration can come from three sources: built-in defaults, a config file, and environment variables. Flags provided on the command line should override all other sources. This precedence order is a standard pattern and makes behavior predictable. Implementing configuration correctly ensures the toolkit is convenient without being surprising. Without a clear precedence model, users cannot trust which value is actually applied. Defaults should be conservative so that the tool is safe to run without any config. Good defaults reduce the need for constant flag usage. This improves reproducibility across environments.
Deep Dive into the concept
Configuration is about policy and precedence. The typical precedence order is: defaults < config file < environment variables < CLI flags. This allows users to set global defaults in a config file (like ~/.devtoolrc), override them in a CI environment with env vars, and still override both when running a specific command. The key is to implement this order consistently across all subcommands.
A simple config file format can be KEY=VALUE lines. This is easy to parse in shell: read line by line, skip comments, and export variables. You can also support INI-style sections, but that adds complexity. For this project, a single global config is sufficient. The config file might include values like LOG_WINDOW=1m, ERROR_THRESHOLD=2.0, or AUDIT_ROOT=/etc.
Environment variables should be namespaced to avoid collisions, such as DEVTOOL_LOG_WINDOW. This makes it clear which tool they belong to. When parsing, you can map these env vars to the same internal variables used for defaults and config file values.
Command-line flags override everything else. This requires a consistent parsing strategy, usually a while-loop with case statements. Flags that take values should support both --flag value and --flag=value if possible. For a learning project, supporting --flag value is sufficient, but document the accepted format. The important part is that the final value used by the tool is derived from the precedence chain.
Configuration also affects testing. For deterministic tests, you should either unset environment variables or run the tool with explicit flags. In your documentation, note that DEVTOOL_* environment variables will override config file values. This helps users avoid confusion when running tests.
Finally, config should be validated. If a user sets ERROR_THRESHOLD=abc, the tool should detect this and return an error. This is part of being a robust CLI. For each numeric config, implement a simple validation check. Invalid config should exit with code 2 and an error message.
Configuration also raises security and safety concerns. A config file is executable context for your tool, so you should avoid directly source-ing arbitrary files unless you trust them. If you do source a config file, document that it must be trusted and suggest using restrictive file permissions (e.g., chmod 600 ~/.devtoolrc). A safer alternative is to parse the file manually and only accept known keys. Even in a learning project, this distinction is worth calling out so users understand the trade-offs.
Another layer is per-command configuration. You might have general defaults (like a global OUTPUT_FORMAT) and command-specific defaults (like LOG_WINDOW). A clear naming convention and a single configuration loader make this manageable. If you implement caching of the parsed config, make sure it is invalidated when the file changes; otherwise, users can be surprised when edits do not take effect. While caching is optional here, thinking through this behavior helps you design a robust configuration system.
For troubleshooting, add a --print-config or --debug option that shows the final resolved values after precedence rules. This makes it obvious whether a config file or environment variable is overriding a flag, and it saves time when users report "mysterious" behavior.
How this fit on projects
Configuration enables the toolkit to reuse the earlier tools with consistent defaults and user overrides.
Definitions & key terms
- Precedence: The order in which config sources override each other.
- Config file: A persistent file storing default settings.
- Environment variable: A shell variable used for configuration.
- Override: A value that takes priority over defaults.
Mental model diagram (ASCII)
defaults -> config file -> env vars -> CLI flags -> final value
How it works (step-by-step, with invariants and failure modes)
- Load built-in defaults.
- Read config file if present.
- Apply environment variables.
- Parse CLI flags and override.
- Validate final values.
Invariant: CLI flags always override config file and env values. Failure modes: invalid config values, missing config file handling.
Minimal concrete example
# precedence example
LOG_WINDOW=${LOG_WINDOW:-"1m"}
[ -f "$HOME/.devtoolrc" ] && source "$HOME/.devtoolrc"
[ -n "$DEVTOOL_LOG_WINDOW" ] && LOG_WINDOW="$DEVTOOL_LOG_WINDOW"
# CLI flag overrides
Common misconceptions
- “Config file should override flags” -> Flags must win.
- “Env vars are optional” -> They are critical for CI and automation.
- “Invalid config should be ignored” -> It should raise an error.
Check-your-understanding questions
- Why should CLI flags override environment variables?
- What is a good namespace for env vars?
- How do you prevent invalid config values from silently breaking behavior?
- Why is precedence important for testing?
Check-your-understanding answers
- Flags are the most explicit user intent.
- Prefix with
DEVTOOL_to avoid collisions. - Validate values and exit with error on invalid input.
- Tests need deterministic values; explicit flags avoid surprises.
Real-world applications
- CLI tools like
awsandkubectluse layered config. - CI pipelines rely on environment variable overrides.
Where you’ll apply it
- See §3.2 for configuration requirements.
- See §5.10 Phase 2 for implementing config parsing.
- Also used in: P02 CSV Pipeline for schema and thresholds.
References
getoptsdocumentation- “The Linux Command Line” (shell scripting configuration patterns)
Key insights
Configuration is about precedence and validation. Consistency here makes the tool trustworthy.
Summary
A clear precedence chain (defaults < config < env < flags) makes the toolkit predictable and script-friendly. Validate all config values.
Homework/Exercises to practice the concept
- Write a config loader that reads
KEY=VALUEpairs. - Implement env var overrides with a
DEVTOOL_prefix. - Add validation for numeric thresholds.
Solutions to the homework/exercises
- Use
while IFS='=' read -r key val; do ...; done. - Map
DEVTOOL_LOG_WINDOWtoLOG_WINDOW. - Use regex checks and exit 2 on invalid values.
2.3 Portability, Packaging, and Integration
Fundamentals
A toolkit is only useful if it runs reliably on different systems. Portability means handling differences between GNU and BSD utilities, using POSIX-compatible shell features when possible, and documenting required dependencies. Packaging means providing an installer or Makefile that puts the tool on the user’s PATH. Integration means the toolkit can call into the sub-tools you built previously in a consistent way. This concept ensures your capstone tool is actually usable. If users cannot run it from any directory, it will never become a daily tool. Clear PATH instructions avoid silent failures. Compatibility notes should be explicit. Document supported platforms clearly.
Deep Dive into the concept
Portability is one of the biggest challenges in shell scripting. Tools like sed, awk, and grep have GNU and BSD variants with subtle differences. For example, sed -i behaves differently, and awk on macOS may lack GNU extensions like FPAT. A portable toolkit should detect the environment and adjust. For example, check if gawk exists and use it for CSV parsing; otherwise, exit with a clear message. For sed differences, avoid in-place edits or provide platform-specific flags.
Shell portability also matters. If you rely on bash-specific features (arrays, [[), you should specify #!/usr/bin/env bash and document the requirement. If you want POSIX sh compatibility, avoid arrays and certain expansions. For this project, use bash and document it. This keeps the code manageable and allows you to use arrays for argument parsing.
Packaging can be simple: provide an install script that copies devtool to ~/.local/bin and ensures it is on PATH. You can also provide a Makefile with make install and make uninstall. This is sufficient for a personal toolkit. An advanced version could use Homebrew or a package manager, but that is beyond scope.
Integration means consistent interfaces for subcommands. The toolkit should call the underlying scripts (log analyzer, CSV pipeline, refactor tool, audit tool) with a predictable set of flags. If you designed those tools independently, you may need a small adapter layer to translate devtool flags into subcommand flags. This is common in real toolchains: a unified CLI wraps multiple tools with a consistent facade.
Testing portability is essential. At minimum, test on macOS and Linux. Document which versions you tested. If something is unsupported (e.g., BSD awk without gawk), detect it and print a helpful error message. This is part of being a good CLI citizen.
Packaging is also about upgrades and uninstalls. A simple install script should record where it placed the binary and provide a matching uninstall script that removes it. If you add shell completions or config templates, the installer should be explicit about where those files go. This prevents "mystery" files from showing up in a user’s home directory. For reliability, avoid installing into system directories that require sudo unless the user explicitly asks for it.
Integration with the underlying tools benefits from a thin adapter layer. Instead of calling sub-tools directly with complex arguments, normalize the arguments in the adapter and keep sub-tools decoupled. This lets you update one sub-tool without breaking the CLI. For example, if logwatch changes its flag name from --window to --window-size, the adapter can translate old flags to new ones while keeping the CLI stable. This decoupling is a core design pattern in toolchains and is worth learning.
Finally, portability includes documentation. A simple "Compatibility" section listing tested OS versions and required tool versions can prevent many user issues. If you require gawk or sed -E support, write it down. Users are more forgiving of limitations when they are explicit. Version checks should be explicit and fail fast.
How this fit on projects
Portability and packaging are what turn your toolkit into a real product rather than a local script.
Definitions & key terms
- Portability: Ability to run across environments with minimal changes.
- Dependency detection: Checking for required tools at runtime.
- Packaging: Installing to PATH for easy execution.
- Adapter layer: Wrapper that normalizes interfaces across tools.
Mental model diagram (ASCII)
[devtool] -> [subcommand adapter] -> [tool script]
\-> [dependency check] -> ok/fail
How it works (step-by-step, with invariants and failure modes)
- On startup, check required dependencies.
- Determine platform (GNU vs BSD tools).
- Map devtool flags to subcommand flags.
- Execute the subcommand tool.
Invariant: If a required dependency is missing, exit with a clear message. Failure modes: silent fallback to incompatible tools, missing PATH entries.
Minimal concrete example
if command -v gawk >/dev/null; then
AWK="gawk"
else
echo "ERROR: gawk required for CSV parsing" >&2
exit 2
fi
Common misconceptions
- “macOS and Linux tools behave the same” -> They do not.
- “Packaging is optional” -> Users won’t run tools not on PATH.
- “Dependencies can be assumed” -> Detect and report missing ones.
Check-your-understanding questions
- Why might
sed -ibreak on macOS? - How do you ensure users can run
devtoolfrom anywhere? - What is an adapter layer and why is it useful?
- Why is dependency detection important?
Check-your-understanding answers
- BSD sed requires an argument for
-iand uses different flags. - Install to
~/.local/binand ensure it’s on PATH. - It normalizes interfaces across sub-tools.
- Without it, failures are confusing and non-deterministic.
Real-world applications
- Internal CLI toolkits used by SRE and DevOps teams.
- Cross-platform developer tooling.
Where you’ll apply it
- See §3.2 for portability requirements.
- See §5.10 Phase 3 for packaging and install scripts.
- Also used in: P03 Refactoring Toolkit for safe sed usage.
References
- “Portable Shell Programming” by Bruce Blinn
shandbashdocumentation
Key insights
A toolkit is only valuable if it runs reliably. Portability and packaging are part of the product.
Summary
Handle GNU/BSD differences, detect dependencies, and provide an installer. This is how you turn scripts into a toolchain.
Homework/Exercises to practice the concept
- Add a dependency check for
gawkandgrep. - Write an
installscript that copiesdevtoolto~/.local/bin. - Test your toolkit on macOS and Linux (or document limitations).
Solutions to the homework/exercises
- Use
command -vchecks and exit with error if missing. - Copy the script and print instructions to update PATH.
- Note any differences and add platform checks.
3. Project Specification
3.1 What You Will Build
A CLI tool called devtool that:
- Provides subcommands for logs, data, code, and system audits.
- Wraps and reuses the scripts from Projects 1-4.
- Supports config file defaults and env var overrides.
- Includes an installer and uninstall script.
Included:
- Subcommands:
logs analyze,data transform,code refactor,system audit. - Consistent help output and exit codes.
- Basic dependency checks.
Excluded:
- Complex plugin systems.
- UI interfaces beyond CLI.
3.2 Functional Requirements
- Command tree:
devtool <category> <action> [flags]. - Help system:
--helpat any level. - Config precedence: defaults < config < env < flags.
- Integration: delegates to existing tools.
- Packaging:
install.shanduninstall.sh. - Exit codes: 0/1/2 semantics across subcommands.
3.3 Non-Functional Requirements
- Portability: Works on macOS and Linux with documented dependencies.
- Reliability: Clear errors when dependencies are missing.
- Usability: Consistent output formatting.
3.4 Example Usage / Output
$ devtool logs analyze /var/log/nginx/access.log --window 1m --error-threshold 2.0
$ devtool data transform sales.csv --schema schema.json --normalize --summary
$ devtool code refactor --root ./src --from old_func --to new_func --dry-run
$ devtool system audit --root /etc --world-writable --setuid
3.5 Data Formats / Schemas / Protocols
Config file example (~/.devtoolrc):
LOG_WINDOW=1m
ERROR_THRESHOLD=2.0
CSV_SCHEMA=~/schemas/sales.json
3.6 Edge Cases
- Missing dependency (gawk not installed).
- Subcommand invoked with missing arguments.
- Config file contains invalid values.
3.7 Real World Outcome
3.7.1 How to Run (Copy/Paste)
./install.sh
devtool logs analyze ./sample/access.log --window 1m --error-threshold 2.0
3.7.2 Golden Path Demo (Deterministic)
$ devtool logs analyze ./sample/access.log --window 1m --error-threshold 2.0
[2026-01-01 09:21:00] window=2026-01-01 09:20 total=810 errors=17 error_rate=2.10%
[2026-01-01 09:21:00] ALERT: error_rate=2.10% threshold=2.00% window=2026-01-01 09:20
3.7.3 Failure Demo (Deterministic)
$ devtool data transform missing.csv --schema schema.json
ERROR: file not found: missing.csv
exit code: 2
3.7.4 If CLI: exact terminal transcript
$ devtool system audit --root ./sample --world-writable
Scanning ./sample ...
World-writable files: 1
$ echo $?
0
Exit codes:
0: Success.1: Subcommand completed with findings or no matches.2: Invalid arguments or missing dependencies.
4. Solution Architecture
4.1 High-Level Design
+---------------------+
| devtool |
+----------+----------+
|
+-----------------+-----------------+
| | | | |
logs data code system config
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| Dispatcher | Route subcommands | case-based routing |
| Config Loader | Apply precedence rules | defaults < config < env < flags |
| Subcommand Adapters | Call underlying tools | consistent flag mapping |
| Installer | Copy binaries to PATH | ~/.local/bin |
4.3 Data Structures (No Full Code)
config map = {key -> value}
commands[] = list of supported subcommands
4.4 Algorithm Overview
Key Algorithm: Dispatch + Config Merge
- Load defaults into config map.
- Read config file and merge.
- Apply env overrides.
- Parse CLI flags and override.
- Dispatch to subcommand with final config.
Complexity Analysis:
- Time: O(n) for parsing args and config.
- Space: O(k) for config entries.
5. Implementation Guide
5.1 Development Environment Setup
# Ensure bash is available
bash --version
5.2 Project Structure
devtool/
├── devtool
├── lib/
│ ├── commands/
│ │ ├── logs.sh
│ │ ├── data.sh
│ │ ├── code.sh
│ │ └── system.sh
│ ├── config.sh
│ └── utils.sh
├── install.sh
└── uninstall.sh
5.3 The Core Question You’re Answering
“Can I unify my daily DevOps workflows into a single CLI that is consistent and reliable?”
5.4 Concepts You Must Understand First
- Subcommand dispatch
- Config precedence
- GNU vs BSD tool differences
5.5 Questions to Guide Your Design
- What should be the top-level categories?
- Which defaults make sense for your workflow?
- How will you handle missing dependencies?
5.6 Thinking Exercise
Design a devtool command tree for your own daily tasks and compare it with the proposed structure.
5.7 The Interview Questions They’ll Ask
- How do you design a CLI with subcommands?
- Why is configuration precedence important?
- How do you handle cross-platform differences in shell tools?
5.8 Hints in Layers
Hint 1: Start with dispatcher
case "$1" in
logs) shift; logs_dispatch "$@" ;;
*) usage; exit 2 ;;
esac
Hint 2: Add config loader
Read ~/.devtoolrc and override defaults.
Hint 3: Add env overrides
Apply DEVTOOL_* variables.
Hint 4: Add installer
Copy devtool to ~/.local/bin.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| CLI design | “The Linux Command Line” | Scripting chapters |
| Shell reliability | “Effective Shell” | Error handling |
| Portability | “Portable Shell Programming” | portability chapters |
5.10 Implementation Phases
Phase 1: Foundation (3-4 days)
Goals: Basic dispatcher and help output.
Tasks:
- Implement top-level command routing.
- Add
--helpoutput for each subcommand.
Checkpoint: Running devtool --help shows command tree.
Phase 2: Core Integration (5-6 days)
Goals: Wire subcommands to existing tools.
Tasks:
- Implement adapter functions for logs, data, code, system.
- Add config loader with precedence rules.
Checkpoint: Each subcommand runs its respective tool with correct defaults.
Phase 3: Packaging & Robustness (3-4 days)
Goals: Install scripts and dependency checks.
Tasks:
- Implement
install.shanduninstall.sh. - Add dependency checks on startup.
Checkpoint: devtool runs from PATH and reports missing dependencies cleanly.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Config format | KEY=VALUE vs JSON | KEY=VALUE | easy to parse in shell |
| Dispatch style | case vs getopts subcommands | case | clearer in bash |
| Install path | /usr/local/bin vs ~/.local/bin | ~/.local/bin | no sudo needed |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | Config parsing | precedence ordering |
| Integration Tests | Subcommand adapters | logs/data/system commands |
| Edge Case Tests | missing dependencies | gawk not installed |
6.2 Critical Test Cases
- Config precedence yields expected values.
devtool logs analyzepasses correct flags.- Missing dependency exits with code 2.
6.3 Test Data
# .devtoolrc
LOG_WINDOW=1m
ERROR_THRESHOLD=2.0
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Inconsistent flags | subcommands behave differently | standardize interface |
| Missing dependencies | cryptic errors | add checks and messages |
| Config precedence bugs | unexpected values | write unit tests |
7.2 Debugging Strategies
- Add a
--debugflag to print resolved config. - Log which subcommand script is called.
- Test with and without config files.
7.3 Performance Traps
Avoid re-reading config for every subcommand; load once and pass values down.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add
devtool versioncommand. - Add
devtool self-checkto validate dependencies.
8.2 Intermediate Extensions
- Add a global
--jsonoutput mode. - Add
devtool config editcommand.
8.3 Advanced Extensions
- Add plugin system for custom subcommands.
- Add automatic updates from a git repo.
9. Real-World Connections
9.1 Industry Applications
- Internal DevOps toolchains.
- Consistent CLI interfaces for operational tasks.
9.2 Related Open Source Projects
- kubectl: command tree and config precedence.
- aws CLI: profiles and environment overrides.
9.3 Interview Relevance
- CLI design and developer experience.
- Configuration management patterns.
10. Resources
10.1 Essential Reading
- “The Linux Command Line” by William Shotts
- “Effective Shell” by Dave Kerr
10.2 Video Resources
- “Designing CLIs” (talk)
10.3 Tools & Documentation
- bash(1) manual page
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain subcommand dispatch.
- I can explain config precedence.
- I can explain how my tool handles portability.
11.2 Implementation
- Subcommands work and are consistent.
- Config precedence works as expected.
- Installer puts the tool on PATH.
11.3 Growth
- I can describe how to extend the toolkit.
- I can explain trade-offs in portability decisions.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Subcommands for logs, data, code, and system.
- Config precedence implemented.
- Help output for all commands.
Full Completion:
- Installer/uninstaller scripts.
- Dependency checks.
- Deterministic outputs for subcommands.
Excellence (Going Above & Beyond):
- Plugin system for new commands.
- JSON output mode.
- Automated updates.