litclaude-ai 0.2.2

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 (156) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/LICENSE +21 -0
  3. package/README.md +369 -0
  4. package/README_ko-KR.md +374 -0
  5. package/RELEASE_CHECKLIST.md +165 -0
  6. package/bin/litclaude-ai.js +643 -0
  7. package/cover.png +0 -0
  8. package/docs/agents.md +67 -0
  9. package/docs/hooks.md +134 -0
  10. package/docs/lsp.md +40 -0
  11. package/docs/migration.md +209 -0
  12. package/docs/workflow-compatibility-audit.md +119 -0
  13. package/generate_cover.py +123 -0
  14. package/package.json +48 -0
  15. package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
  16. package/plugins/litclaude/.lsp.json +13 -0
  17. package/plugins/litclaude/.mcp.json +9 -0
  18. package/plugins/litclaude/agents/boulder-executor.md +12 -0
  19. package/plugins/litclaude/agents/librarian-researcher.md +15 -0
  20. package/plugins/litclaude/agents/oracle-verifier.md +16 -0
  21. package/plugins/litclaude/agents/prometheus-planner.md +13 -0
  22. package/plugins/litclaude/agents/qa-runner.md +16 -0
  23. package/plugins/litclaude/agents/quality-reviewer.md +17 -0
  24. package/plugins/litclaude/bin/litclaude-hook.js +110 -0
  25. package/plugins/litclaude/bin/litclaude-hud.js +271 -0
  26. package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
  27. package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
  28. package/plugins/litclaude/commands/deep-interview.md +21 -0
  29. package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
  30. package/plugins/litclaude/commands/lit-loop.md +40 -0
  31. package/plugins/litclaude/commands/lit-plan.md +35 -0
  32. package/plugins/litclaude/commands/litgoal.md +30 -0
  33. package/plugins/litclaude/commands/review-work.md +35 -0
  34. package/plugins/litclaude/commands/start-work.md +36 -0
  35. package/plugins/litclaude/hooks/hooks.json +54 -0
  36. package/plugins/litclaude/lib/context-pressure.mjs +25 -0
  37. package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
  38. package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
  39. package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
  40. package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
  41. package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
  42. package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
  43. package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
  44. package/plugins/litclaude/lib/workflow-check.mjs +83 -0
  45. package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
  46. package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
  47. package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
  48. package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
  49. package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
  50. package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
  51. package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
  52. package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
  53. package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
  54. package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
  55. package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
  56. package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
  57. package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
  58. package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
  59. package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
  60. package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
  61. package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
  62. package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
  63. package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
  64. package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
  65. package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
  66. package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
  67. package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
  68. package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
  69. package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
  70. package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
  71. package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
  72. package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
  73. package/plugins/litclaude/skills/programming/SKILL.md +106 -0
  74. package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
  75. package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
  76. package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
  77. package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
  78. package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
  79. package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
  80. package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
  81. package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
  82. package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
  83. package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
  84. package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
  85. package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
  86. package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
  87. package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
  88. package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
  89. package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
  90. package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
  91. package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
  92. package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
  93. package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
  94. package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
  95. package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
  96. package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
  97. package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
  98. package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
  99. package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
  100. package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
  101. package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
  102. package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
  103. package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
  104. package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
  105. package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
  106. package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
  107. package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
  108. package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
  109. package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
  110. package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
  111. package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
  112. package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
  113. package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
  114. package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
  115. package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
  116. package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
  117. package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
  118. package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
  119. package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
  120. package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
  121. package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
  122. package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
  123. package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
  124. package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
  125. package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
  126. package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
  127. package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
  128. package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
  129. package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
  130. package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
  131. package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
  132. package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
  133. package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
  134. package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
  135. package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
  136. package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
  137. package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
  138. package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
  139. package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
  140. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
  141. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
  142. package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
  143. package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
  144. package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
  145. package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
  146. package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
  147. package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
  148. package/plugins/litclaude/skills/rules/SKILL.md +66 -0
  149. package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
  150. package/scripts/audit-plan-checkboxes.mjs +37 -0
  151. package/scripts/doctor.mjs +41 -0
  152. package/scripts/inspect-agent-tools.mjs +27 -0
  153. package/scripts/postinstall.mjs +50 -0
  154. package/scripts/qa-claude-plugin-smoke.sh +60 -0
  155. package/scripts/qa-portable-install.sh +136 -0
  156. package/scripts/validate-plugin.mjs +72 -0
@@ -0,0 +1,409 @@
1
+ # CLI Stack — clap + color-eyre + tracing + indicatif + dialoguer
2
+
3
+ The default for any new CLI tool. Strict typing on arguments, beautiful errors, progress feedback, interactive prompts when needed.
4
+
5
+ ## Cargo.toml
6
+
7
+ ```toml
8
+ [package]
9
+ name = "mytool"
10
+ version = "0.1.0"
11
+ edition = "2024"
12
+
13
+ [dependencies]
14
+ clap = { version = "4", features = ["derive", "env", "wrap_help", "color", "unicode"] }
15
+ clap_complete = "4"
16
+ color-eyre = "0.6"
17
+ tracing = "0.1"
18
+ tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
19
+ anyhow = "1"
20
+ indicatif = { version = "0.17", features = ["tokio"] }
21
+ dialoguer = { version = "0.11", features = ["fuzzy-select"] }
22
+ console = "0.15"
23
+ tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs", "process", "signal"] }
24
+
25
+ [profile.release]
26
+ opt-level = 3
27
+ lto = "fat"
28
+ codegen-units = 1
29
+ strip = "symbols"
30
+ panic = "abort"
31
+ ```
32
+
33
+ ## Command structure
34
+
35
+ ```rust
36
+ // src/cli.rs
37
+ use clap::{Parser, Subcommand, ValueEnum};
38
+ use std::path::PathBuf;
39
+
40
+ #[derive(Debug, Parser)]
41
+ #[command(
42
+ name = "mytool",
43
+ author,
44
+ version,
45
+ about = "A short description",
46
+ long_about = "A longer description that appears in --help",
47
+ arg_required_else_help = true,
48
+ )]
49
+ pub struct Cli {
50
+ /// Configuration file path
51
+ #[arg(short, long, env = "MYTOOL_CONFIG", default_value = "config.toml", global = true)]
52
+ pub config: PathBuf,
53
+
54
+ /// Increase verbosity (-v info, -vv debug, -vvv trace)
55
+ #[arg(short, long, action = clap::ArgAction::Count, global = true)]
56
+ pub verbose: u8,
57
+
58
+ /// Suppress all non-error output
59
+ #[arg(short, long, global = true, conflicts_with = "verbose")]
60
+ pub quiet: bool,
61
+
62
+ /// Force colored output even when stdout is not a terminal
63
+ #[arg(long, global = true, value_enum, default_value_t = ColorChoice::Auto)]
64
+ pub color: ColorChoice,
65
+
66
+ /// Output format
67
+ #[arg(short, long, global = true, value_enum, default_value_t = OutputFormat::Pretty)]
68
+ pub format: OutputFormat,
69
+
70
+ #[command(subcommand)]
71
+ pub command: Command,
72
+ }
73
+
74
+ #[derive(Debug, Clone, ValueEnum)]
75
+ pub enum ColorChoice { Auto, Always, Never }
76
+
77
+ #[derive(Debug, Clone, ValueEnum)]
78
+ pub enum OutputFormat { Pretty, Json, Plain }
79
+
80
+ #[derive(Debug, Subcommand)]
81
+ pub enum Command {
82
+ /// Build the thing
83
+ Build(BuildArgs),
84
+ /// Watch and rebuild
85
+ Watch(WatchArgs),
86
+ /// Generate shell completions
87
+ Completions { #[arg(value_enum)] shell: clap_complete::Shell },
88
+ }
89
+
90
+ #[derive(Debug, clap::Args)]
91
+ pub struct BuildArgs {
92
+ /// Target directory
93
+ #[arg(short, long, default_value = "target")]
94
+ pub target: PathBuf,
95
+ /// Build mode
96
+ #[arg(short, long, value_enum, default_value_t = Mode::Release)]
97
+ pub mode: Mode,
98
+ /// Specific files to build (default: all)
99
+ pub files: Vec<PathBuf>,
100
+ }
101
+
102
+ #[derive(Debug, clap::Args)]
103
+ pub struct WatchArgs {
104
+ /// Glob pattern to watch
105
+ #[arg(short, long, default_value = "**/*.rs")]
106
+ pub pattern: String,
107
+ }
108
+
109
+ #[derive(Debug, Clone, ValueEnum)]
110
+ pub enum Mode { Debug, Release }
111
+ ```
112
+
113
+ Key clap derive patterns:
114
+
115
+ - `env = "VAR"` — falls back to env var if flag not given.
116
+ - `global = true` — flag inherits to subcommands.
117
+ - `arg_required_else_help = true` — running with no args prints help instead of erroring.
118
+ - `value_enum` on an enum — case-insensitive parsing + auto-completion.
119
+ - `action = clap::ArgAction::Count` — `-v` is 1, `-vv` is 2, etc.
120
+ - `conflicts_with` — incompatible flags.
121
+
122
+ ## Main + tracing init
123
+
124
+ ```rust
125
+ // src/main.rs
126
+ use clap::Parser;
127
+ use mytool::cli::{Cli, Command, ColorChoice};
128
+ use tracing::Level;
129
+ use tracing_subscriber::EnvFilter;
130
+
131
+ fn main() -> color_eyre::Result<()> {
132
+ color_eyre::install()?;
133
+ let cli = Cli::parse();
134
+ init_tracing(&cli);
135
+
136
+ if matches!(cli.color, ColorChoice::Always) {
137
+ console::set_colors_enabled(true);
138
+ } else if matches!(cli.color, ColorChoice::Never) {
139
+ console::set_colors_enabled(false);
140
+ }
141
+
142
+ match cli.command {
143
+ Command::Build(args) => mytool::commands::build::run(&cli, args),
144
+ Command::Watch(args) => mytool::commands::watch::run(&cli, args),
145
+ Command::Completions { shell } => {
146
+ let mut cmd = <Cli as clap::CommandFactory>::command();
147
+ clap_complete::generate(shell, &mut cmd, "mytool", &mut std::io::stdout());
148
+ Ok(())
149
+ }
150
+ }
151
+ }
152
+
153
+ fn init_tracing(cli: &Cli) {
154
+ let level = if cli.quiet {
155
+ Level::ERROR
156
+ } else {
157
+ match cli.verbose {
158
+ 0 => Level::WARN,
159
+ 1 => Level::INFO,
160
+ 2 => Level::DEBUG,
161
+ _ => Level::TRACE,
162
+ }
163
+ };
164
+ let filter = EnvFilter::try_from_default_env()
165
+ .unwrap_or_else(|_| EnvFilter::new(format!("mytool={level}")));
166
+ tracing_subscriber::fmt()
167
+ .with_env_filter(filter)
168
+ .with_target(false)
169
+ .without_time()
170
+ .compact()
171
+ .with_writer(std::io::stderr)
172
+ .init();
173
+ }
174
+ ```
175
+
176
+ Tracing on a CLI:
177
+ - **Write to stderr.** stdout is for the tool's actual output (which the user might pipe). Logs and progress bars go to stderr.
178
+ - **Verbosity from `-v`, not from `RUST_LOG`.** Users expect `-v` on a CLI; `RUST_LOG` is a developer escape hatch (kept, but secondary).
179
+
180
+ ## Progress bars — `indicatif`
181
+
182
+ ```rust
183
+ use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
184
+ use std::time::Duration;
185
+
186
+ let mp = MultiProgress::new();
187
+ let pb = mp.add(ProgressBar::new(files.len() as u64));
188
+ pb.set_style(
189
+ ProgressStyle::with_template(
190
+ "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({eta}) {msg}"
191
+ )?
192
+ .progress_chars("=>-")
193
+ );
194
+
195
+ for file in files {
196
+ pb.set_message(file.display().to_string());
197
+ process(&file)?;
198
+ pb.inc(1);
199
+ }
200
+ pb.finish_with_message("done");
201
+ ```
202
+
203
+ For unbounded operations:
204
+
205
+ ```rust
206
+ let spinner = ProgressBar::new_spinner();
207
+ spinner.enable_steady_tick(Duration::from_millis(80));
208
+ spinner.set_message("connecting…");
209
+ let result = connect().await?;
210
+ spinner.finish_and_clear();
211
+ ```
212
+
213
+ With multiple parallel tasks:
214
+
215
+ ```rust
216
+ let mp = MultiProgress::new();
217
+ let bars: Vec<_> = (0..workers).map(|i| {
218
+ let pb = mp.add(ProgressBar::new(unit));
219
+ pb.set_style(ProgressStyle::with_template("worker {prefix}: {pos}/{len}")?);
220
+ pb.set_prefix(i.to_string());
221
+ pb
222
+ }).collect();
223
+ ```
224
+
225
+ `MultiProgress` keeps bars stacked and redraws cleanly even with concurrent updates from multiple tasks.
226
+
227
+ When stdout is not a terminal, indicatif silently disables animation. Force on/off with `pb.set_draw_target(ProgressDrawTarget::stdout())` / `hidden()`.
228
+
229
+ ## Interactive prompts — `dialoguer`
230
+
231
+ ```rust
232
+ use dialoguer::{theme::ColorfulTheme, Confirm, Input, Password, Select, FuzzySelect, MultiSelect};
233
+
234
+ let name: String = Input::with_theme(&ColorfulTheme::default())
235
+ .with_prompt("Project name")
236
+ .validate_with(|input: &String| -> Result<(), &str> {
237
+ if input.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
238
+ Ok(())
239
+ } else {
240
+ Err("alphanumeric, dash, underscore only")
241
+ }
242
+ })
243
+ .interact_text()?;
244
+
245
+ let secret = Password::with_theme(&ColorfulTheme::default())
246
+ .with_prompt("API key")
247
+ .with_confirmation("Repeat", "passwords don't match")
248
+ .interact()?;
249
+
250
+ let go: bool = Confirm::with_theme(&ColorfulTheme::default())
251
+ .with_prompt(format!("Delete {}? This cannot be undone.", path.display()))
252
+ .default(false)
253
+ .interact()?;
254
+ if !go { return Ok(()); }
255
+
256
+ let items = ["yes", "no", "maybe"];
257
+ let idx = Select::with_theme(&ColorfulTheme::default())
258
+ .with_prompt("Pick one")
259
+ .items(&items)
260
+ .default(0)
261
+ .interact()?;
262
+
263
+ let picks = MultiSelect::with_theme(&ColorfulTheme::default())
264
+ .with_prompt("Toggle features")
265
+ .items(&["alpha", "beta", "gamma"])
266
+ .defaults(&[true, false, false])
267
+ .interact()?;
268
+ ```
269
+
270
+ Detect non-TTY before prompting:
271
+
272
+ ```rust
273
+ if !console::user_attended() {
274
+ return Err(anyhow::anyhow!("input required but stdin is not a terminal"));
275
+ }
276
+ ```
277
+
278
+ For automated tests, expose a `--non-interactive` flag and gate all prompts behind it.
279
+
280
+ ## Structured output
281
+
282
+ ```rust
283
+ match cli.format {
284
+ OutputFormat::Json => {
285
+ serde_json::to_writer(std::io::stdout().lock(), &result)?;
286
+ println!();
287
+ }
288
+ OutputFormat::Plain => {
289
+ for row in &result.rows {
290
+ println!("{}\t{}\t{}", row.a, row.b, row.c);
291
+ }
292
+ }
293
+ OutputFormat::Pretty => {
294
+ use console::{style, Term};
295
+ let term = Term::stdout();
296
+ for row in &result.rows {
297
+ term.write_line(&format!(
298
+ "{} {} {}",
299
+ style(&row.a).green(),
300
+ style(&row.b).yellow(),
301
+ style(&row.c).dim(),
302
+ ))?;
303
+ }
304
+ }
305
+ }
306
+ ```
307
+
308
+ Always offer `--format json` for piping into `jq`, scripts, and other tools.
309
+
310
+ ## Shell completions
311
+
312
+ Already shown in the `Completions` subcommand above. Distribute completions by adding to the install script:
313
+
314
+ ```bash
315
+ mytool completions bash > /etc/bash_completion.d/mytool
316
+ mytool completions fish > ~/.config/fish/completions/mytool.fish
317
+ mytool completions zsh > "${fpath[1]}/_mytool"
318
+ ```
319
+
320
+ ## Signal handling
321
+
322
+ ```rust
323
+ // In an async CLI command
324
+ use tokio::signal::ctrl_c;
325
+
326
+ tokio::select! {
327
+ _ = ctrl_c() => {
328
+ tracing::warn!("interrupted, cleaning up");
329
+ cleanup().await?;
330
+ std::process::exit(130); // standard exit code for SIGINT
331
+ }
332
+ result = long_running_task() => {
333
+ result
334
+ }
335
+ }
336
+ ```
337
+
338
+ For sync CLIs, install a one-shot handler with `ctrlc` crate:
339
+
340
+ ```rust
341
+ let interrupted = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
342
+ let i = interrupted.clone();
343
+ ctrlc::set_handler(move || i.store(true, std::sync::atomic::Ordering::SeqCst))?;
344
+
345
+ while !interrupted.load(std::sync::atomic::Ordering::Relaxed) {
346
+ do_step()?;
347
+ }
348
+ ```
349
+
350
+ ## Error reporting with color-eyre
351
+
352
+ ```rust
353
+ fn main() -> color_eyre::Result<()> {
354
+ color_eyre::config::HookBuilder::default()
355
+ .display_env_section(false) // hide SPANTRACE/BACKTRACE env hints by default
356
+ .display_location_section(false) // hide file:line section
357
+ .panic_section("If this is a bug, please report at https://github.com/me/mytool/issues")
358
+ .install()?;
359
+ real_main()
360
+ }
361
+ ```
362
+
363
+ Errors with `.wrap_err("...")` from `eyre::WrapErr` (compatible with anyhow's `.context`) show as a numbered chain. `RUST_BACKTRACE=1` shows the full trace; `RUST_SPANTRACE=1` shows tracing spans where the error fired.
364
+
365
+ ## Distribution
366
+
367
+ - Add `cargo dist init` for prebuilt binary release pipeline (cross-platform tarballs + installers).
368
+ - Publish to Homebrew tap, AUR, scoop, Chocolatey via dist.
369
+ - Sign Linux binaries with `cosign` if your audience is enterprise.
370
+ - Build single static binary on Linux with `--target x86_64-unknown-linux-musl` (or `aarch64-unknown-linux-musl`).
371
+ - For wasm-runnable CLIs (`wasi-cli`), add `--target wasm32-wasip1`.
372
+
373
+ ## Common mistakes
374
+
375
+ 1. **Mixing stdout and stderr.** Tool output goes to stdout; logs and progress go to stderr.
376
+ 2. **No `--non-interactive` flag.** Interactive prompts block automation.
377
+ 3. **Printing colored output unconditionally.** Honor `NO_COLOR` env var, detect TTY with `console::user_attended()`.
378
+ 4. **`println!` for errors.** Use `tracing::error!` so logs go to stderr automatically and respect verbosity.
379
+ 5. **`unwrap()` on `Cli::parse()`.** clap returns clean errors with `--help` text; `parse()` exits on its own.
380
+ 6. **Long subcommand handlers in `main.rs`.** Split into `src/commands/<name>.rs` per command.
381
+ 7. **Missing exit code semantics.** Use `std::process::exit(1)` (general error), `2` (usage), `130` (SIGINT) appropriately. Or return `Result` and let main map.
382
+
383
+ ## Testing CLIs
384
+
385
+ ```rust
386
+ // tests/cli.rs
387
+ use assert_cmd::Command;
388
+ use predicates::prelude::*;
389
+
390
+ #[test]
391
+ fn shows_help() {
392
+ Command::cargo_bin("mytool").unwrap()
393
+ .arg("--help")
394
+ .assert()
395
+ .success()
396
+ .stdout(predicate::str::contains("Usage:"));
397
+ }
398
+
399
+ #[test]
400
+ fn rejects_unknown_subcommand() {
401
+ Command::cargo_bin("mytool").unwrap()
402
+ .arg("nope")
403
+ .assert()
404
+ .failure()
405
+ .stderr(predicate::str::contains("unrecognized subcommand"));
406
+ }
407
+ ```
408
+
409
+ `assert_cmd` builds the binary once per test run and gives a fluent assertion API.