agentic-team-templates 0.3.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.
- package/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
# CLI User Experience
|
|
2
|
+
|
|
3
|
+
Patterns for creating intuitive, informative, and pleasant command-line interfaces.
|
|
4
|
+
|
|
5
|
+
## Output Formatting
|
|
6
|
+
|
|
7
|
+
### Structured Output
|
|
8
|
+
|
|
9
|
+
Support multiple output formats for different use cases:
|
|
10
|
+
|
|
11
|
+
```go
|
|
12
|
+
// Go: Multi-format output
|
|
13
|
+
type OutputFormat string
|
|
14
|
+
|
|
15
|
+
const (
|
|
16
|
+
FormatTable OutputFormat = "table"
|
|
17
|
+
FormatJSON OutputFormat = "json"
|
|
18
|
+
FormatYAML OutputFormat = "yaml"
|
|
19
|
+
FormatPlain OutputFormat = "plain"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
type Project struct {
|
|
23
|
+
Name string `json:"name" yaml:"name"`
|
|
24
|
+
Status string `json:"status" yaml:"status"`
|
|
25
|
+
Updated string `json:"updated" yaml:"updated"`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func printProjects(projects []Project, format OutputFormat) error {
|
|
29
|
+
switch format {
|
|
30
|
+
case FormatJSON:
|
|
31
|
+
return json.NewEncoder(os.Stdout).Encode(projects)
|
|
32
|
+
|
|
33
|
+
case FormatYAML:
|
|
34
|
+
return yaml.NewEncoder(os.Stdout).Encode(projects)
|
|
35
|
+
|
|
36
|
+
case FormatTable:
|
|
37
|
+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
38
|
+
fmt.Fprintln(w, "NAME\tSTATUS\tUPDATED")
|
|
39
|
+
for _, p := range projects {
|
|
40
|
+
fmt.Fprintf(w, "%s\t%s\t%s\n", p.Name, p.Status, p.Updated)
|
|
41
|
+
}
|
|
42
|
+
return w.Flush()
|
|
43
|
+
|
|
44
|
+
case FormatPlain:
|
|
45
|
+
for _, p := range projects {
|
|
46
|
+
fmt.Println(p.Name)
|
|
47
|
+
}
|
|
48
|
+
return nil
|
|
49
|
+
|
|
50
|
+
default:
|
|
51
|
+
return fmt.Errorf("unknown format: %s", format)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// TypeScript: Multi-format output
|
|
58
|
+
type OutputFormat = 'table' | 'json' | 'yaml' | 'plain';
|
|
59
|
+
|
|
60
|
+
function printProjects(projects: Project[], format: OutputFormat): void {
|
|
61
|
+
switch (format) {
|
|
62
|
+
case 'json':
|
|
63
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
case 'yaml':
|
|
67
|
+
console.log(yaml.dump(projects));
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
case 'table':
|
|
71
|
+
console.table(projects);
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case 'plain':
|
|
75
|
+
projects.forEach(p => console.log(p.name));
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### stdout vs stderr
|
|
82
|
+
|
|
83
|
+
- **stdout**: Data output (for piping)
|
|
84
|
+
- **stderr**: Progress, status, errors (for humans)
|
|
85
|
+
|
|
86
|
+
```go
|
|
87
|
+
// Go: Correct stream usage
|
|
88
|
+
func runBuild(ctx context.Context) error {
|
|
89
|
+
// Progress goes to stderr
|
|
90
|
+
fmt.Fprintln(os.Stderr, "Building project...")
|
|
91
|
+
|
|
92
|
+
result, err := build(ctx)
|
|
93
|
+
if err != nil {
|
|
94
|
+
// Errors go to stderr
|
|
95
|
+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
96
|
+
return err
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Data output goes to stdout (can be piped)
|
|
100
|
+
fmt.Println(result.OutputPath)
|
|
101
|
+
return nil
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// TypeScript: Correct stream usage
|
|
107
|
+
// Progress and errors to stderr
|
|
108
|
+
console.error('Building project...');
|
|
109
|
+
|
|
110
|
+
// Data to stdout
|
|
111
|
+
console.log(result.outputPath);
|
|
112
|
+
|
|
113
|
+
// Or use explicit streams
|
|
114
|
+
process.stderr.write('Building...\n');
|
|
115
|
+
process.stdout.write(JSON.stringify(result));
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Colors and Styling
|
|
119
|
+
|
|
120
|
+
### Color Usage Guidelines
|
|
121
|
+
|
|
122
|
+
| Color | Use For |
|
|
123
|
+
|-------|---------|
|
|
124
|
+
| Green | Success, completion |
|
|
125
|
+
| Red | Errors, warnings |
|
|
126
|
+
| Yellow | Warnings, cautions |
|
|
127
|
+
| Blue | Information, highlights |
|
|
128
|
+
| Cyan | Commands, paths |
|
|
129
|
+
| Dim/Gray | Secondary information |
|
|
130
|
+
| Bold | Emphasis, headers |
|
|
131
|
+
|
|
132
|
+
### Implementing Colors
|
|
133
|
+
|
|
134
|
+
```go
|
|
135
|
+
// Go with fatih/color
|
|
136
|
+
import "github.com/fatih/color"
|
|
137
|
+
|
|
138
|
+
var (
|
|
139
|
+
success = color.New(color.FgGreen).SprintFunc()
|
|
140
|
+
failure = color.New(color.FgRed).SprintFunc()
|
|
141
|
+
warning = color.New(color.FgYellow).SprintFunc()
|
|
142
|
+
info = color.New(color.FgBlue).SprintFunc()
|
|
143
|
+
dim = color.New(color.Faint).SprintFunc()
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
func printStatus(name, status string) {
|
|
147
|
+
switch status {
|
|
148
|
+
case "success":
|
|
149
|
+
fmt.Printf("%s %s\n", success("✓"), name)
|
|
150
|
+
case "failure":
|
|
151
|
+
fmt.Printf("%s %s\n", failure("✗"), name)
|
|
152
|
+
case "pending":
|
|
153
|
+
fmt.Printf("%s %s\n", warning("○"), name)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// TypeScript with chalk
|
|
160
|
+
import chalk from 'chalk';
|
|
161
|
+
|
|
162
|
+
function printStatus(name: string, status: string): void {
|
|
163
|
+
switch (status) {
|
|
164
|
+
case 'success':
|
|
165
|
+
console.log(chalk.green('✓'), name);
|
|
166
|
+
break;
|
|
167
|
+
case 'failure':
|
|
168
|
+
console.log(chalk.red('✗'), name);
|
|
169
|
+
break;
|
|
170
|
+
case 'pending':
|
|
171
|
+
console.log(chalk.yellow('○'), name);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Respecting No-Color
|
|
178
|
+
|
|
179
|
+
Always support disabling colors:
|
|
180
|
+
|
|
181
|
+
```go
|
|
182
|
+
// Go: Check NO_COLOR and --no-color flag
|
|
183
|
+
func init() {
|
|
184
|
+
if os.Getenv("NO_COLOR") != "" || noColorFlag {
|
|
185
|
+
color.NoColor = true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Also disable colors when not a TTY
|
|
189
|
+
if !term.IsTerminal(int(os.Stdout.Fd())) {
|
|
190
|
+
color.NoColor = true
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// TypeScript: Check environment and flag
|
|
197
|
+
import chalk from 'chalk';
|
|
198
|
+
|
|
199
|
+
if (process.env.NO_COLOR || options.noColor || !process.stdout.isTTY) {
|
|
200
|
+
chalk.level = 0;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Progress Indicators
|
|
205
|
+
|
|
206
|
+
### Spinners
|
|
207
|
+
|
|
208
|
+
For operations with unknown duration:
|
|
209
|
+
|
|
210
|
+
```go
|
|
211
|
+
// Go with briandowns/spinner
|
|
212
|
+
import "github.com/briandowns/spinner"
|
|
213
|
+
|
|
214
|
+
func deploy(ctx context.Context) error {
|
|
215
|
+
s := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
|
|
216
|
+
s.Suffix = " Deploying..."
|
|
217
|
+
s.Start()
|
|
218
|
+
defer s.Stop()
|
|
219
|
+
|
|
220
|
+
if err := runDeploy(ctx); err != nil {
|
|
221
|
+
s.FinalMSG = "✗ Deployment failed\n"
|
|
222
|
+
return err
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
s.FinalMSG = "✓ Deployment complete\n"
|
|
226
|
+
return nil
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// TypeScript with ora
|
|
232
|
+
import ora from 'ora';
|
|
233
|
+
|
|
234
|
+
async function deploy(): Promise<void> {
|
|
235
|
+
const spinner = ora('Deploying...').start();
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await runDeploy();
|
|
239
|
+
spinner.succeed('Deployment complete');
|
|
240
|
+
} catch (error) {
|
|
241
|
+
spinner.fail('Deployment failed');
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Progress Bars
|
|
248
|
+
|
|
249
|
+
For operations with known progress:
|
|
250
|
+
|
|
251
|
+
```go
|
|
252
|
+
// Go with schollz/progressbar
|
|
253
|
+
import "github.com/schollz/progressbar/v3"
|
|
254
|
+
|
|
255
|
+
func processFiles(files []string) error {
|
|
256
|
+
bar := progressbar.NewOptions(len(files),
|
|
257
|
+
progressbar.OptionSetDescription("Processing"),
|
|
258
|
+
progressbar.OptionShowCount(),
|
|
259
|
+
progressbar.OptionSetWidth(40),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
for _, file := range files {
|
|
263
|
+
if err := processFile(file); err != nil {
|
|
264
|
+
return err
|
|
265
|
+
}
|
|
266
|
+
bar.Add(1)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return nil
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// TypeScript with cli-progress
|
|
275
|
+
import { SingleBar, Presets } from 'cli-progress';
|
|
276
|
+
|
|
277
|
+
async function processFiles(files: string[]): Promise<void> {
|
|
278
|
+
const bar = new SingleBar({
|
|
279
|
+
format: 'Processing |{bar}| {percentage}% | {value}/{total}',
|
|
280
|
+
}, Presets.shades_classic);
|
|
281
|
+
|
|
282
|
+
bar.start(files.length, 0);
|
|
283
|
+
|
|
284
|
+
for (const file of files) {
|
|
285
|
+
await processFile(file);
|
|
286
|
+
bar.increment();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
bar.stop();
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Progress for Non-Interactive Environments
|
|
294
|
+
|
|
295
|
+
Detect TTY and adapt:
|
|
296
|
+
|
|
297
|
+
```go
|
|
298
|
+
func processFiles(files []string) error {
|
|
299
|
+
if term.IsTerminal(int(os.Stderr.Fd())) {
|
|
300
|
+
// Interactive: Use progress bar
|
|
301
|
+
return processWithProgressBar(files)
|
|
302
|
+
} else {
|
|
303
|
+
// Non-interactive (CI): Use periodic updates
|
|
304
|
+
return processWithPeriodicLog(files)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
func processWithPeriodicLog(files []string) error {
|
|
309
|
+
total := len(files)
|
|
310
|
+
for i, file := range files {
|
|
311
|
+
if err := processFile(file); err != nil {
|
|
312
|
+
return err
|
|
313
|
+
}
|
|
314
|
+
// Log every 10% or every 100 files
|
|
315
|
+
if i%100 == 0 || i == total-1 {
|
|
316
|
+
fmt.Fprintf(os.Stderr, "Processed %d/%d files\n", i+1, total)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return nil
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Interactive Prompts
|
|
324
|
+
|
|
325
|
+
### Confirmation Prompts
|
|
326
|
+
|
|
327
|
+
```go
|
|
328
|
+
// Go with AlecAivazis/survey
|
|
329
|
+
import "github.com/AlecAivazis/survey/v2"
|
|
330
|
+
|
|
331
|
+
func confirmDelete(name string) (bool, error) {
|
|
332
|
+
var confirm bool
|
|
333
|
+
prompt := &survey.Confirm{
|
|
334
|
+
Message: fmt.Sprintf("Delete %q?", name),
|
|
335
|
+
Default: false,
|
|
336
|
+
}
|
|
337
|
+
err := survey.AskOne(prompt, &confirm)
|
|
338
|
+
return confirm, err
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// TypeScript with inquirer
|
|
344
|
+
import inquirer from 'inquirer';
|
|
345
|
+
|
|
346
|
+
async function confirmDelete(name: string): Promise<boolean> {
|
|
347
|
+
const { confirm } = await inquirer.prompt([{
|
|
348
|
+
type: 'confirm',
|
|
349
|
+
name: 'confirm',
|
|
350
|
+
message: `Delete "${name}"?`,
|
|
351
|
+
default: false,
|
|
352
|
+
}]);
|
|
353
|
+
return confirm;
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Selection Prompts
|
|
358
|
+
|
|
359
|
+
```go
|
|
360
|
+
// Go: Select from list
|
|
361
|
+
func selectEnvironment() (string, error) {
|
|
362
|
+
var env string
|
|
363
|
+
prompt := &survey.Select{
|
|
364
|
+
Message: "Select environment:",
|
|
365
|
+
Options: []string{"dev", "staging", "prod"},
|
|
366
|
+
}
|
|
367
|
+
err := survey.AskOne(prompt, &env)
|
|
368
|
+
return env, err
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Text Input
|
|
373
|
+
|
|
374
|
+
```go
|
|
375
|
+
// Go: Get text input
|
|
376
|
+
func askProjectName() (string, error) {
|
|
377
|
+
var name string
|
|
378
|
+
prompt := &survey.Input{
|
|
379
|
+
Message: "Project name:",
|
|
380
|
+
Default: "my-project",
|
|
381
|
+
}
|
|
382
|
+
err := survey.AskOne(prompt, &name, survey.WithValidator(survey.Required))
|
|
383
|
+
return name, err
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Non-Interactive Mode
|
|
388
|
+
|
|
389
|
+
Always support skipping prompts:
|
|
390
|
+
|
|
391
|
+
```go
|
|
392
|
+
func confirmDelete(name string, force bool) (bool, error) {
|
|
393
|
+
// Skip prompt if --force flag or non-interactive
|
|
394
|
+
if force || !term.IsTerminal(int(os.Stdin.Fd())) {
|
|
395
|
+
return true, nil
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
var confirm bool
|
|
399
|
+
prompt := &survey.Confirm{
|
|
400
|
+
Message: fmt.Sprintf("Delete %q?", name),
|
|
401
|
+
}
|
|
402
|
+
err := survey.AskOne(prompt, &confirm)
|
|
403
|
+
return confirm, err
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Status and Logging
|
|
408
|
+
|
|
409
|
+
### Log Levels
|
|
410
|
+
|
|
411
|
+
```go
|
|
412
|
+
type LogLevel int
|
|
413
|
+
|
|
414
|
+
const (
|
|
415
|
+
LevelQuiet LogLevel = iota
|
|
416
|
+
LevelNormal
|
|
417
|
+
LevelVerbose
|
|
418
|
+
LevelDebug
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
type Logger struct {
|
|
422
|
+
level LogLevel
|
|
423
|
+
writer io.Writer
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
func (l *Logger) Debug(msg string, args ...any) {
|
|
427
|
+
if l.level >= LevelDebug {
|
|
428
|
+
fmt.Fprintf(l.writer, "[DEBUG] "+msg+"\n", args...)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
func (l *Logger) Info(msg string, args ...any) {
|
|
433
|
+
if l.level >= LevelNormal {
|
|
434
|
+
fmt.Fprintf(l.writer, msg+"\n", args...)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
func (l *Logger) Success(msg string, args ...any) {
|
|
439
|
+
if l.level >= LevelNormal {
|
|
440
|
+
fmt.Fprintf(l.writer, "✓ "+msg+"\n", args...)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
func (l *Logger) Error(msg string, args ...any) {
|
|
445
|
+
// Errors always print
|
|
446
|
+
fmt.Fprintf(l.writer, "✗ "+msg+"\n", args...)
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Structured Status Output
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
$ mytool deploy
|
|
454
|
+
|
|
455
|
+
Deploying to production...
|
|
456
|
+
|
|
457
|
+
✓ Building application
|
|
458
|
+
✓ Running tests
|
|
459
|
+
✓ Uploading artifacts
|
|
460
|
+
○ Starting services
|
|
461
|
+
├─ api-server ... starting
|
|
462
|
+
├─ worker ... starting
|
|
463
|
+
└─ scheduler ... starting
|
|
464
|
+
○ Running health checks
|
|
465
|
+
|
|
466
|
+
Deployment in progress...
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
```go
|
|
470
|
+
func printDeployStatus(services []Service) {
|
|
471
|
+
fmt.Println(" ○ Starting services")
|
|
472
|
+
for i, svc := range services {
|
|
473
|
+
prefix := "├─"
|
|
474
|
+
if i == len(services)-1 {
|
|
475
|
+
prefix = "└─"
|
|
476
|
+
}
|
|
477
|
+
status := dim("starting")
|
|
478
|
+
if svc.Running {
|
|
479
|
+
status = success("running")
|
|
480
|
+
}
|
|
481
|
+
fmt.Printf(" %s %s ... %s\n", prefix, svc.Name, status)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Responsive Design
|
|
487
|
+
|
|
488
|
+
### Terminal Width
|
|
489
|
+
|
|
490
|
+
Adapt output to terminal size:
|
|
491
|
+
|
|
492
|
+
```go
|
|
493
|
+
import "golang.org/x/term"
|
|
494
|
+
|
|
495
|
+
func getTerminalWidth() int {
|
|
496
|
+
width, _, err := term.GetSize(int(os.Stdout.Fd()))
|
|
497
|
+
if err != nil {
|
|
498
|
+
return 80 // Default fallback
|
|
499
|
+
}
|
|
500
|
+
return width
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
func truncate(s string, maxLen int) string {
|
|
504
|
+
if len(s) <= maxLen {
|
|
505
|
+
return s
|
|
506
|
+
}
|
|
507
|
+
return s[:maxLen-3] + "..."
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
func printTable(rows [][]string) {
|
|
511
|
+
width := getTerminalWidth()
|
|
512
|
+
colWidth := width / len(rows[0])
|
|
513
|
+
|
|
514
|
+
for _, row := range rows {
|
|
515
|
+
for _, col := range row {
|
|
516
|
+
fmt.Printf("%-*s", colWidth, truncate(col, colWidth-1))
|
|
517
|
+
}
|
|
518
|
+
fmt.Println()
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Paging Long Output
|
|
524
|
+
|
|
525
|
+
```go
|
|
526
|
+
// Go: Pipe to pager for long output
|
|
527
|
+
func showLongOutput(content string) error {
|
|
528
|
+
if !term.IsTerminal(int(os.Stdout.Fd())) {
|
|
529
|
+
fmt.Print(content)
|
|
530
|
+
return nil
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
pager := os.Getenv("PAGER")
|
|
534
|
+
if pager == "" {
|
|
535
|
+
pager = "less"
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
cmd := exec.Command(pager)
|
|
539
|
+
cmd.Stdin = strings.NewReader(content)
|
|
540
|
+
cmd.Stdout = os.Stdout
|
|
541
|
+
cmd.Stderr = os.Stderr
|
|
542
|
+
|
|
543
|
+
return cmd.Run()
|
|
544
|
+
}
|
|
545
|
+
```
|