@ai-content-space/loopx 0.2.8 → 0.2.10
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 +26 -9
- package/README.zh-CN.md +26 -9
- package/docs/loopx/design/loopx-skill-suite-v1-design.md +12 -0
- package/docs/loopx/plans/2026-06-14-loopx-spec-memory-context-loading.md +948 -0
- package/docs/loopx/plans/2026-06-15-support-lens-skills-migration.md +1153 -0
- package/package.json +6 -1
- package/plugins/loopx/.codex-plugin/plugin.json +1 -1
- package/plugins/loopx/skills/api-designer/SKILL.md +232 -0
- package/plugins/loopx/skills/api-designer/references/error-handling.md +541 -0
- package/plugins/loopx/skills/api-designer/references/openapi.md +824 -0
- package/plugins/loopx/skills/api-designer/references/pagination.md +494 -0
- package/plugins/loopx/skills/api-designer/references/rest-patterns.md +335 -0
- package/plugins/loopx/skills/api-designer/references/versioning.md +391 -0
- package/plugins/loopx/skills/architecture-designer/SKILL.md +117 -0
- package/plugins/loopx/skills/architecture-designer/references/adr-template.md +116 -0
- package/plugins/loopx/skills/architecture-designer/references/architecture-patterns.md +346 -0
- package/plugins/loopx/skills/architecture-designer/references/database-selection.md +102 -0
- package/plugins/loopx/skills/architecture-designer/references/nfr-checklist.md +212 -0
- package/plugins/loopx/skills/architecture-designer/references/system-design.md +313 -0
- package/plugins/loopx/skills/clarify/SKILL.md +12 -1
- package/plugins/loopx/skills/cli-developer/SKILL.md +124 -0
- package/plugins/loopx/skills/cli-developer/references/design-patterns.md +221 -0
- package/plugins/loopx/skills/cli-developer/references/go-cli.md +540 -0
- package/plugins/loopx/skills/cli-developer/references/node-cli.md +383 -0
- package/plugins/loopx/skills/cli-developer/references/python-cli.md +422 -0
- package/plugins/loopx/skills/cli-developer/references/ux-patterns.md +448 -0
- package/plugins/loopx/skills/debug/SKILL.md +1 -1
- package/plugins/loopx/skills/doc-readability/SKILL.md +1 -1
- package/plugins/loopx/skills/exec/SKILL.md +1 -1
- package/plugins/loopx/skills/final-review/SKILL.md +1 -1
- package/plugins/loopx/skills/finish/SKILL.md +1 -1
- package/plugins/loopx/skills/fix-review/SKILL.md +1 -1
- package/plugins/loopx/skills/go-style/SKILL.md +1 -1
- package/plugins/loopx/skills/kratos/SKILL.md +2 -1
- package/plugins/loopx/skills/plan-to-exec/SKILL.md +12 -1
- package/plugins/loopx/skills/refactor-plan/SKILL.md +1 -1
- package/plugins/loopx/skills/requirement-analyzer/SKILL.md +161 -0
- package/plugins/loopx/skills/requirement-analyzer/references/example-reports.md +170 -0
- package/plugins/loopx/skills/requirement-analyzer/references/prd-gap-checklist.md +167 -0
- package/plugins/loopx/skills/requirement-analyzer/references/readiness-rubric.md +70 -0
- package/plugins/loopx/skills/requirement-analyzer/references/report-template.md +83 -0
- package/plugins/loopx/skills/review/SKILL.md +1 -1
- package/plugins/loopx/skills/spec/SKILL.md +12 -1
- package/plugins/loopx/skills/sql-style/SKILL.md +108 -0
- package/plugins/loopx/skills/sql-style/references/database-design.md +402 -0
- package/plugins/loopx/skills/sql-style/references/dialect-differences.md +419 -0
- package/plugins/loopx/skills/sql-style/references/optimization.md +384 -0
- package/plugins/loopx/skills/sql-style/references/query-patterns.md +285 -0
- package/plugins/loopx/skills/sql-style/references/window-functions.md +328 -0
- package/plugins/loopx/skills/subagent-exec/SKILL.md +1 -1
- package/plugins/loopx/skills/tdd/SKILL.md +1 -1
- package/plugins/loopx/skills/verify/SKILL.md +1 -1
- package/scripts/verify-skills.mjs +0 -2
- package/skills/RESOLVER.md +8 -1
- package/skills/api-designer/SKILL.md +232 -0
- package/skills/api-designer/references/error-handling.md +541 -0
- package/skills/api-designer/references/openapi.md +824 -0
- package/skills/api-designer/references/pagination.md +494 -0
- package/skills/api-designer/references/rest-patterns.md +335 -0
- package/skills/api-designer/references/versioning.md +391 -0
- package/skills/architecture-designer/SKILL.md +117 -0
- package/skills/architecture-designer/references/adr-template.md +116 -0
- package/skills/architecture-designer/references/architecture-patterns.md +346 -0
- package/skills/architecture-designer/references/database-selection.md +102 -0
- package/skills/architecture-designer/references/nfr-checklist.md +212 -0
- package/skills/architecture-designer/references/system-design.md +313 -0
- package/skills/clarify/SKILL.md +12 -1
- package/skills/cli-developer/SKILL.md +124 -0
- package/skills/cli-developer/references/design-patterns.md +221 -0
- package/skills/cli-developer/references/go-cli.md +540 -0
- package/skills/cli-developer/references/node-cli.md +383 -0
- package/skills/cli-developer/references/python-cli.md +422 -0
- package/skills/cli-developer/references/ux-patterns.md +448 -0
- package/skills/debug/SKILL.md +1 -1
- package/skills/doc-readability/SKILL.md +1 -1
- package/skills/exec/SKILL.md +1 -1
- package/skills/final-review/SKILL.md +1 -1
- package/skills/finish/SKILL.md +1 -1
- package/skills/fix-review/SKILL.md +1 -1
- package/skills/go-style/SKILL.md +1 -1
- package/skills/kratos/SKILL.md +2 -1
- package/skills/plan-to-exec/SKILL.md +12 -1
- package/skills/refactor-plan/SKILL.md +1 -1
- package/skills/requirement-analyzer/SKILL.md +161 -0
- package/skills/requirement-analyzer/references/example-reports.md +170 -0
- package/skills/requirement-analyzer/references/prd-gap-checklist.md +167 -0
- package/skills/requirement-analyzer/references/readiness-rubric.md +70 -0
- package/skills/requirement-analyzer/references/report-template.md +83 -0
- package/skills/review/SKILL.md +1 -1
- package/skills/spec/SKILL.md +12 -1
- package/skills/sql-style/SKILL.md +108 -0
- package/skills/sql-style/references/database-design.md +402 -0
- package/skills/sql-style/references/dialect-differences.md +419 -0
- package/skills/sql-style/references/optimization.md +384 -0
- package/skills/sql-style/references/query-patterns.md +285 -0
- package/skills/sql-style/references/window-functions.md +328 -0
- package/skills/subagent-exec/SKILL.md +1 -1
- package/skills/tdd/SKILL.md +1 -1
- package/skills/verify/SKILL.md +1 -1
- package/src/cli.mjs +4 -1
- package/src/context-manifest.mjs +51 -1
- package/src/install-discovery.mjs +114 -0
- package/src/loopx-context-artifacts.mjs +114 -0
- package/src/project-discovery.mjs +1 -0
- package/src/workflow.mjs +47 -3
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# Python CLI Development
|
|
2
|
+
|
|
3
|
+
## Typer (Recommended - Modern)
|
|
4
|
+
|
|
5
|
+
FastAPI-style CLI framework with automatic help generation.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
#!/usr/bin/env python3
|
|
9
|
+
import typer
|
|
10
|
+
from typing import Optional
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
app = typer.Typer()
|
|
14
|
+
|
|
15
|
+
class Environment(str, Enum):
|
|
16
|
+
dev = "development"
|
|
17
|
+
staging = "staging"
|
|
18
|
+
prod = "production"
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def init(
|
|
22
|
+
name: str = typer.Argument(..., help="Project name"),
|
|
23
|
+
template: str = typer.Option("default", help="Project template"),
|
|
24
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing"),
|
|
25
|
+
):
|
|
26
|
+
"""Initialize a new project"""
|
|
27
|
+
typer.echo(f"Creating {name} from {template}")
|
|
28
|
+
if force:
|
|
29
|
+
typer.echo("Force mode enabled")
|
|
30
|
+
|
|
31
|
+
@app.command()
|
|
32
|
+
def deploy(
|
|
33
|
+
environment: Environment = typer.Argument(..., help="Target environment"),
|
|
34
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Preview only"),
|
|
35
|
+
config: Optional[typer.FileText] = typer.Option(None, help="Config file"),
|
|
36
|
+
):
|
|
37
|
+
"""Deploy to environment"""
|
|
38
|
+
if dry_run:
|
|
39
|
+
typer.echo(f"Would deploy to: {environment.value}")
|
|
40
|
+
else:
|
|
41
|
+
typer.echo(f"Deploying to {environment.value}...")
|
|
42
|
+
|
|
43
|
+
# Nested commands
|
|
44
|
+
config_app = typer.Typer()
|
|
45
|
+
app.add_typer(config_app, name="config", help="Manage configuration")
|
|
46
|
+
|
|
47
|
+
@config_app.command("get")
|
|
48
|
+
def config_get(key: str):
|
|
49
|
+
"""Get config value"""
|
|
50
|
+
typer.echo(f"Value: {get_config(key)}")
|
|
51
|
+
|
|
52
|
+
@config_app.command("set")
|
|
53
|
+
def config_set(key: str, value: str):
|
|
54
|
+
"""Set config value"""
|
|
55
|
+
set_config(key, value)
|
|
56
|
+
typer.echo(f"Set {key} = {value}")
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
app()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Click (Widely Used)
|
|
63
|
+
|
|
64
|
+
Powerful, composable CLI framework.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import click
|
|
68
|
+
|
|
69
|
+
@click.group()
|
|
70
|
+
@click.version_option()
|
|
71
|
+
def cli():
|
|
72
|
+
"""My awesome CLI tool"""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@cli.command()
|
|
76
|
+
@click.argument('name')
|
|
77
|
+
@click.option('--template', default='default', help='Project template')
|
|
78
|
+
@click.option('--force', '-f', is_flag=True, help='Overwrite existing')
|
|
79
|
+
def init(name, template, force):
|
|
80
|
+
"""Initialize a new project"""
|
|
81
|
+
click.echo(f"Creating {name} from {template}")
|
|
82
|
+
|
|
83
|
+
@cli.command()
|
|
84
|
+
@click.argument('environment', type=click.Choice(['dev', 'staging', 'prod']))
|
|
85
|
+
@click.option('--dry-run', is_flag=True, help='Preview only')
|
|
86
|
+
@click.option('--config', type=click.File('r'), help='Config file')
|
|
87
|
+
def deploy(environment, dry_run, config):
|
|
88
|
+
"""Deploy to environment"""
|
|
89
|
+
if dry_run:
|
|
90
|
+
click.secho(f"Would deploy to: {environment}", fg='yellow')
|
|
91
|
+
else:
|
|
92
|
+
click.secho(f"Deploying to {environment}...", fg='green')
|
|
93
|
+
|
|
94
|
+
# Nested groups
|
|
95
|
+
@cli.group()
|
|
96
|
+
def config():
|
|
97
|
+
"""Manage configuration"""
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
@config.command('get')
|
|
101
|
+
@click.argument('key')
|
|
102
|
+
def config_get(key):
|
|
103
|
+
"""Get config value"""
|
|
104
|
+
click.echo(get_config(key))
|
|
105
|
+
|
|
106
|
+
@config.command('set')
|
|
107
|
+
@click.argument('key')
|
|
108
|
+
@click.argument('value')
|
|
109
|
+
def config_set(key, value):
|
|
110
|
+
"""Set config value"""
|
|
111
|
+
set_config(key, value)
|
|
112
|
+
|
|
113
|
+
if __name__ == '__main__':
|
|
114
|
+
cli()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Rich Terminal Output
|
|
118
|
+
|
|
119
|
+
Beautiful terminal formatting and progress indicators.
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from rich.console import Console
|
|
123
|
+
from rich.table import Table
|
|
124
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
125
|
+
from rich.panel import Panel
|
|
126
|
+
from rich.syntax import Syntax
|
|
127
|
+
from rich import print as rprint
|
|
128
|
+
|
|
129
|
+
console = Console()
|
|
130
|
+
|
|
131
|
+
# Styled output
|
|
132
|
+
console.print("[bold blue]Info:[/] Starting deployment...")
|
|
133
|
+
console.print("[bold green]Success:[/] Deployment complete!")
|
|
134
|
+
console.print("[bold yellow]Warning:[/] Deprecated flag used")
|
|
135
|
+
console.print("[bold red]Error:[/] Deployment failed")
|
|
136
|
+
|
|
137
|
+
# Tables
|
|
138
|
+
table = Table(title="Deployments")
|
|
139
|
+
table.add_column("Environment", style="cyan")
|
|
140
|
+
table.add_column("Status", style="magenta")
|
|
141
|
+
table.add_column("Time", style="green")
|
|
142
|
+
|
|
143
|
+
table.add_row("Production", "✓ Success", "2m 34s")
|
|
144
|
+
table.add_row("Staging", "✗ Failed", "1m 12s")
|
|
145
|
+
console.print(table)
|
|
146
|
+
|
|
147
|
+
# Panels
|
|
148
|
+
console.print(Panel.fit(
|
|
149
|
+
"Deploy to production?",
|
|
150
|
+
title="Confirmation",
|
|
151
|
+
border_style="red"
|
|
152
|
+
))
|
|
153
|
+
|
|
154
|
+
# Syntax highlighting
|
|
155
|
+
code = '''
|
|
156
|
+
def deploy(env: str):
|
|
157
|
+
print(f"Deploying to {env}")
|
|
158
|
+
'''
|
|
159
|
+
console.print(Syntax(code, "python", theme="monokai"))
|
|
160
|
+
|
|
161
|
+
# Progress bars
|
|
162
|
+
with Progress() as progress:
|
|
163
|
+
task = progress.add_task("[cyan]Deploying...", total=100)
|
|
164
|
+
for i in range(100):
|
|
165
|
+
do_work()
|
|
166
|
+
progress.update(task, advance=1)
|
|
167
|
+
|
|
168
|
+
# Spinners
|
|
169
|
+
with Progress(
|
|
170
|
+
SpinnerColumn(),
|
|
171
|
+
TextColumn("[progress.description]{task.description}"),
|
|
172
|
+
) as progress:
|
|
173
|
+
task = progress.add_task("Installing dependencies...")
|
|
174
|
+
install_dependencies()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Interactive Prompts (questionary)
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
import questionary
|
|
181
|
+
|
|
182
|
+
# Text input
|
|
183
|
+
name = questionary.text(
|
|
184
|
+
"Project name:",
|
|
185
|
+
default="my-project",
|
|
186
|
+
validate=lambda x: len(x) > 0 or "Name required"
|
|
187
|
+
).ask()
|
|
188
|
+
|
|
189
|
+
# Select from list
|
|
190
|
+
environment = questionary.select(
|
|
191
|
+
"Select environment:",
|
|
192
|
+
choices=["development", "staging", "production"],
|
|
193
|
+
default="development"
|
|
194
|
+
).ask()
|
|
195
|
+
|
|
196
|
+
# Checkbox (multi-select)
|
|
197
|
+
features = questionary.checkbox(
|
|
198
|
+
"Select features:",
|
|
199
|
+
choices=[
|
|
200
|
+
questionary.Choice("TypeScript", checked=True),
|
|
201
|
+
questionary.Choice("ESLint", checked=True),
|
|
202
|
+
questionary.Choice("Prettier", checked=True),
|
|
203
|
+
questionary.Choice("Jest", checked=False),
|
|
204
|
+
]
|
|
205
|
+
).ask()
|
|
206
|
+
|
|
207
|
+
# Confirmation
|
|
208
|
+
confirmed = questionary.confirm(
|
|
209
|
+
"Deploy to production?",
|
|
210
|
+
default=False
|
|
211
|
+
).ask()
|
|
212
|
+
|
|
213
|
+
if confirmed:
|
|
214
|
+
deploy()
|
|
215
|
+
|
|
216
|
+
# Password
|
|
217
|
+
password = questionary.password("Enter password:").ask()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Argparse (Standard Library)
|
|
221
|
+
|
|
222
|
+
Built-in argument parsing (verbose but no dependencies).
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
import argparse
|
|
226
|
+
import sys
|
|
227
|
+
|
|
228
|
+
def main():
|
|
229
|
+
parser = argparse.ArgumentParser(
|
|
230
|
+
prog='mycli',
|
|
231
|
+
description='My awesome CLI tool',
|
|
232
|
+
)
|
|
233
|
+
parser.add_argument('--version', action='version', version='1.0.0')
|
|
234
|
+
|
|
235
|
+
subparsers = parser.add_subparsers(dest='command', required=True)
|
|
236
|
+
|
|
237
|
+
# Init command
|
|
238
|
+
init_parser = subparsers.add_parser('init', help='Initialize project')
|
|
239
|
+
init_parser.add_argument('name', help='Project name')
|
|
240
|
+
init_parser.add_argument('--template', default='default', help='Template')
|
|
241
|
+
init_parser.add_argument('-f', '--force', action='store_true')
|
|
242
|
+
|
|
243
|
+
# Deploy command
|
|
244
|
+
deploy_parser = subparsers.add_parser('deploy', help='Deploy')
|
|
245
|
+
deploy_parser.add_argument(
|
|
246
|
+
'environment',
|
|
247
|
+
choices=['dev', 'staging', 'prod'],
|
|
248
|
+
help='Target environment'
|
|
249
|
+
)
|
|
250
|
+
deploy_parser.add_argument('--dry-run', action='store_true')
|
|
251
|
+
deploy_parser.add_argument('--config', type=argparse.FileType('r'))
|
|
252
|
+
|
|
253
|
+
args = parser.parse_args()
|
|
254
|
+
|
|
255
|
+
if args.command == 'init':
|
|
256
|
+
init(args.name, args.template, args.force)
|
|
257
|
+
elif args.command == 'deploy':
|
|
258
|
+
deploy(args.environment, args.dry_run, args.config)
|
|
259
|
+
|
|
260
|
+
if __name__ == '__main__':
|
|
261
|
+
main()
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Error Handling
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
import typer
|
|
268
|
+
import sys
|
|
269
|
+
from pathlib import Path
|
|
270
|
+
|
|
271
|
+
app = typer.Typer()
|
|
272
|
+
|
|
273
|
+
@app.command()
|
|
274
|
+
def deploy():
|
|
275
|
+
try:
|
|
276
|
+
perform_deploy()
|
|
277
|
+
except PermissionError as e:
|
|
278
|
+
typer.secho("Permission denied", fg=typer.colors.RED, err=True)
|
|
279
|
+
typer.echo("Try running with sudo or check file permissions")
|
|
280
|
+
raise typer.Exit(code=77)
|
|
281
|
+
except FileNotFoundError as e:
|
|
282
|
+
typer.secho(f"File not found: {e.filename}", fg=typer.colors.RED, err=True)
|
|
283
|
+
raise typer.Exit(code=127)
|
|
284
|
+
except Exception as e:
|
|
285
|
+
typer.secho(f"Deployment failed: {e}", fg=typer.colors.RED, err=True)
|
|
286
|
+
if os.getenv('DEBUG'):
|
|
287
|
+
import traceback
|
|
288
|
+
traceback.print_exc()
|
|
289
|
+
raise typer.Exit(code=1)
|
|
290
|
+
|
|
291
|
+
# Handle KeyboardInterrupt (Ctrl+C)
|
|
292
|
+
def main():
|
|
293
|
+
try:
|
|
294
|
+
app()
|
|
295
|
+
except KeyboardInterrupt:
|
|
296
|
+
typer.echo("\nOperation cancelled")
|
|
297
|
+
sys.exit(130)
|
|
298
|
+
|
|
299
|
+
if __name__ == "__main__":
|
|
300
|
+
main()
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Configuration Management
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
from pathlib import Path
|
|
307
|
+
from typing import Any
|
|
308
|
+
import json
|
|
309
|
+
import os
|
|
310
|
+
|
|
311
|
+
class Config:
|
|
312
|
+
def __init__(self):
|
|
313
|
+
self.config_paths = [
|
|
314
|
+
Path("/etc/mycli/config.json"), # System
|
|
315
|
+
Path.home() / ".config" / "mycli" / "config.json", # User
|
|
316
|
+
Path.cwd() / "mycli.json", # Project
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
def load(self) -> dict[str, Any]:
|
|
320
|
+
config = self._defaults()
|
|
321
|
+
|
|
322
|
+
# Load from files (lowest to highest priority)
|
|
323
|
+
for path in self.config_paths:
|
|
324
|
+
if path.exists():
|
|
325
|
+
with path.open() as f:
|
|
326
|
+
config.update(json.load(f))
|
|
327
|
+
|
|
328
|
+
# Override with environment variables
|
|
329
|
+
for key in config.keys():
|
|
330
|
+
env_var = f"MYCLI_{key.upper()}"
|
|
331
|
+
if env_var in os.environ:
|
|
332
|
+
config[key] = os.environ[env_var]
|
|
333
|
+
|
|
334
|
+
return config
|
|
335
|
+
|
|
336
|
+
def _defaults(self) -> dict[str, Any]:
|
|
337
|
+
return {
|
|
338
|
+
"environment": "development",
|
|
339
|
+
"verbose": False,
|
|
340
|
+
"timeout": 30,
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Setup.py / pyproject.toml
|
|
345
|
+
|
|
346
|
+
```toml
|
|
347
|
+
# pyproject.toml
|
|
348
|
+
[build-system]
|
|
349
|
+
requires = ["setuptools>=61.0"]
|
|
350
|
+
build-backend = "setuptools.build_meta"
|
|
351
|
+
|
|
352
|
+
[project]
|
|
353
|
+
name = "mycli"
|
|
354
|
+
version = "1.0.0"
|
|
355
|
+
description = "My awesome CLI tool"
|
|
356
|
+
requires-python = ">=3.10"
|
|
357
|
+
dependencies = [
|
|
358
|
+
"typer[all]>=0.9.0",
|
|
359
|
+
"rich>=13.0.0",
|
|
360
|
+
"questionary>=2.0.0",
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
[project.scripts]
|
|
364
|
+
mycli = "mycli.cli:main"
|
|
365
|
+
|
|
366
|
+
[project.optional-dependencies]
|
|
367
|
+
dev = [
|
|
368
|
+
"pytest>=7.0.0",
|
|
369
|
+
"pytest-cov>=4.0.0",
|
|
370
|
+
]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Testing CLIs
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
from typer.testing import CliRunner
|
|
377
|
+
from mycli.cli import app
|
|
378
|
+
|
|
379
|
+
runner = CliRunner()
|
|
380
|
+
|
|
381
|
+
def test_version():
|
|
382
|
+
result = runner.invoke(app, ["--version"])
|
|
383
|
+
assert result.exit_code == 0
|
|
384
|
+
assert "1.0.0" in result.stdout
|
|
385
|
+
|
|
386
|
+
def test_init():
|
|
387
|
+
result = runner.invoke(app, ["init", "my-project"])
|
|
388
|
+
assert result.exit_code == 0
|
|
389
|
+
assert "Creating my-project" in result.stdout
|
|
390
|
+
|
|
391
|
+
def test_init_with_template():
|
|
392
|
+
result = runner.invoke(app, ["init", "my-project", "--template", "react"])
|
|
393
|
+
assert result.exit_code == 0
|
|
394
|
+
assert "react" in result.stdout
|
|
395
|
+
|
|
396
|
+
def test_invalid_command():
|
|
397
|
+
result = runner.invoke(app, ["invalid"])
|
|
398
|
+
assert result.exit_code != 0
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Progress Bars (tqdm)
|
|
402
|
+
|
|
403
|
+
```python
|
|
404
|
+
from tqdm import tqdm
|
|
405
|
+
import time
|
|
406
|
+
|
|
407
|
+
# Simple progress bar
|
|
408
|
+
for i in tqdm(range(100), desc="Processing"):
|
|
409
|
+
process_item(i)
|
|
410
|
+
|
|
411
|
+
# Custom format
|
|
412
|
+
with tqdm(total=100, desc="Downloading", unit="MB") as pbar:
|
|
413
|
+
for chunk in download_chunks():
|
|
414
|
+
pbar.update(len(chunk))
|
|
415
|
+
|
|
416
|
+
# Multiple progress bars
|
|
417
|
+
from tqdm import trange
|
|
418
|
+
|
|
419
|
+
for epoch in trange(10, desc="Epochs"):
|
|
420
|
+
for batch in trange(100, desc="Batches", leave=False):
|
|
421
|
+
train_batch(batch)
|
|
422
|
+
```
|