up-cli 0.1.1__py3-none-any.whl → 0.5.0__py3-none-any.whl

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 (55) hide show
  1. up/__init__.py +1 -1
  2. up/ai_cli.py +229 -0
  3. up/cli.py +75 -4
  4. up/commands/agent.py +521 -0
  5. up/commands/bisect.py +343 -0
  6. up/commands/branch.py +350 -0
  7. up/commands/dashboard.py +248 -0
  8. up/commands/init.py +195 -6
  9. up/commands/learn.py +1741 -0
  10. up/commands/memory.py +545 -0
  11. up/commands/new.py +108 -10
  12. up/commands/provenance.py +267 -0
  13. up/commands/review.py +239 -0
  14. up/commands/start.py +1124 -0
  15. up/commands/status.py +360 -0
  16. up/commands/summarize.py +122 -0
  17. up/commands/sync.py +317 -0
  18. up/commands/vibe.py +304 -0
  19. up/context.py +421 -0
  20. up/core/__init__.py +69 -0
  21. up/core/checkpoint.py +479 -0
  22. up/core/provenance.py +364 -0
  23. up/core/state.py +678 -0
  24. up/events.py +512 -0
  25. up/git/__init__.py +37 -0
  26. up/git/utils.py +270 -0
  27. up/git/worktree.py +331 -0
  28. up/learn/__init__.py +155 -0
  29. up/learn/analyzer.py +227 -0
  30. up/learn/plan.py +374 -0
  31. up/learn/research.py +511 -0
  32. up/learn/utils.py +117 -0
  33. up/memory.py +1096 -0
  34. up/parallel.py +551 -0
  35. up/summarizer.py +407 -0
  36. up/templates/__init__.py +70 -2
  37. up/templates/config/__init__.py +502 -20
  38. up/templates/docs/SKILL.md +28 -0
  39. up/templates/docs/__init__.py +341 -0
  40. up/templates/docs/standards/HEADERS.md +24 -0
  41. up/templates/docs/standards/STRUCTURE.md +18 -0
  42. up/templates/docs/standards/TEMPLATES.md +19 -0
  43. up/templates/learn/__init__.py +567 -14
  44. up/templates/loop/__init__.py +546 -27
  45. up/templates/mcp/__init__.py +474 -0
  46. up/templates/projects/__init__.py +786 -0
  47. up/ui/__init__.py +14 -0
  48. up/ui/loop_display.py +650 -0
  49. up/ui/theme.py +137 -0
  50. up_cli-0.5.0.dist-info/METADATA +519 -0
  51. up_cli-0.5.0.dist-info/RECORD +55 -0
  52. up_cli-0.1.1.dist-info/METADATA +0 -186
  53. up_cli-0.1.1.dist-info/RECORD +0 -14
  54. {up_cli-0.1.1.dist-info → up_cli-0.5.0.dist-info}/WHEEL +0 -0
  55. {up_cli-0.1.1.dist-info → up_cli-0.5.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,786 @@
1
+ """Project type templates for different tech stacks.
2
+
3
+ Templates available:
4
+ - fastapi: FastAPI backend with SQLAlchemy
5
+ - nextjs: Next.js frontend with TypeScript
6
+ - python-lib: Python library with packaging
7
+ - minimal: Minimal project structure
8
+ """
9
+
10
+ from pathlib import Path
11
+ from datetime import date
12
+
13
+
14
+ # Template registry
15
+ TEMPLATES = {
16
+ "minimal": "Minimal project structure",
17
+ "fastapi": "FastAPI backend with SQLAlchemy, pytest, and Docker",
18
+ "nextjs": "Next.js 14+ frontend with TypeScript and Tailwind",
19
+ "python-lib": "Python library with pyproject.toml and pytest",
20
+ }
21
+
22
+
23
+ def get_available_templates() -> dict:
24
+ """Get dictionary of available templates."""
25
+ return TEMPLATES.copy()
26
+
27
+
28
+ def create_project_from_template(
29
+ target_dir: Path,
30
+ template: str,
31
+ project_name: str,
32
+ force: bool = False
33
+ ) -> None:
34
+ """Create project from specified template."""
35
+ if template not in TEMPLATES:
36
+ raise ValueError(f"Unknown template: {template}. Available: {list(TEMPLATES.keys())}")
37
+
38
+ if template == "minimal":
39
+ _create_minimal_template(target_dir, project_name, force)
40
+ elif template == "fastapi":
41
+ _create_fastapi_template(target_dir, project_name, force)
42
+ elif template == "nextjs":
43
+ _create_nextjs_template(target_dir, project_name, force)
44
+ elif template == "python-lib":
45
+ _create_python_lib_template(target_dir, project_name, force)
46
+
47
+
48
+ def _write_file(path: Path, content: str, force: bool) -> None:
49
+ """Write file if it doesn't exist or force is True."""
50
+ if path.exists() and not force:
51
+ return
52
+ path.parent.mkdir(parents=True, exist_ok=True)
53
+ path.write_text(content)
54
+
55
+
56
+ # =============================================================================
57
+ # Minimal Template
58
+ # =============================================================================
59
+
60
+ def _create_minimal_template(target_dir: Path, project_name: str, force: bool) -> None:
61
+ """Create minimal project structure."""
62
+ # Create directories
63
+ (target_dir / "src").mkdir(parents=True, exist_ok=True)
64
+ (target_dir / "tests").mkdir(parents=True, exist_ok=True)
65
+
66
+ # Create README
67
+ readme = f"""# {project_name}
68
+
69
+ > Add project description here
70
+
71
+ ## Getting Started
72
+
73
+ ```bash
74
+ # Install dependencies
75
+ pip install -e .
76
+
77
+ # Run tests
78
+ pytest
79
+ ```
80
+
81
+ ## Structure
82
+
83
+ ```
84
+ {project_name}/
85
+ ├── src/ # Source code
86
+ ├── tests/ # Tests
87
+ └── docs/ # Documentation
88
+ ```
89
+ """
90
+ _write_file(target_dir / "README.md", readme, force)
91
+
92
+ # Create .gitignore
93
+ gitignore = """# Python
94
+ __pycache__/
95
+ *.py[cod]
96
+ *$py.class
97
+ *.so
98
+ .Python
99
+ build/
100
+ dist/
101
+ *.egg-info/
102
+ .venv/
103
+ venv/
104
+
105
+ # IDE
106
+ .idea/
107
+ .vscode/
108
+ *.swp
109
+ *.swo
110
+
111
+ # Testing
112
+ .pytest_cache/
113
+ .coverage
114
+ htmlcov/
115
+
116
+ # Up CLI
117
+ .loop_state.json
118
+ .circuit_*.json
119
+ """
120
+ _write_file(target_dir / ".gitignore", gitignore, force)
121
+
122
+
123
+ # =============================================================================
124
+ # FastAPI Template
125
+ # =============================================================================
126
+
127
+ def _create_fastapi_template(target_dir: Path, project_name: str, force: bool) -> None:
128
+ """Create FastAPI project template."""
129
+ safe_name = project_name.replace("-", "_").lower()
130
+
131
+ # Create directories
132
+ dirs = [
133
+ f"src/{safe_name}",
134
+ f"src/{safe_name}/api",
135
+ f"src/{safe_name}/models",
136
+ f"src/{safe_name}/services",
137
+ "tests",
138
+ "alembic/versions",
139
+ ]
140
+ for d in dirs:
141
+ (target_dir / d).mkdir(parents=True, exist_ok=True)
142
+
143
+ # pyproject.toml
144
+ pyproject = f'''[build-system]
145
+ requires = ["hatchling"]
146
+ build-backend = "hatchling.build"
147
+
148
+ [project]
149
+ name = "{project_name}"
150
+ version = "0.1.0"
151
+ description = "FastAPI application"
152
+ readme = "README.md"
153
+ requires-python = ">=3.10"
154
+ dependencies = [
155
+ "fastapi>=0.100.0",
156
+ "uvicorn[standard]>=0.23.0",
157
+ "sqlalchemy>=2.0.0",
158
+ "alembic>=1.12.0",
159
+ "pydantic>=2.0.0",
160
+ "pydantic-settings>=2.0.0",
161
+ ]
162
+
163
+ [project.optional-dependencies]
164
+ dev = [
165
+ "pytest>=7.0",
166
+ "pytest-asyncio>=0.21.0",
167
+ "httpx>=0.24.0",
168
+ "ruff>=0.1.0",
169
+ "mypy>=1.5.0",
170
+ ]
171
+
172
+ [tool.hatch.build.targets.wheel]
173
+ packages = ["src/{safe_name}"]
174
+
175
+ [tool.pytest.ini_options]
176
+ asyncio_mode = "auto"
177
+ testpaths = ["tests"]
178
+
179
+ [tool.ruff]
180
+ line-length = 100
181
+ target-version = "py310"
182
+
183
+ [tool.mypy]
184
+ python_version = "3.10"
185
+ strict = true
186
+ '''
187
+ _write_file(target_dir / "pyproject.toml", pyproject, force)
188
+
189
+ # Main app
190
+ main_app = f'''"""FastAPI application."""
191
+
192
+ from fastapi import FastAPI
193
+ from fastapi.middleware.cors import CORSMiddleware
194
+
195
+ app = FastAPI(
196
+ title="{project_name}",
197
+ description="API description",
198
+ version="0.1.0",
199
+ )
200
+
201
+ # CORS middleware
202
+ app.add_middleware(
203
+ CORSMiddleware,
204
+ allow_origins=["*"], # Configure in production
205
+ allow_credentials=True,
206
+ allow_methods=["*"],
207
+ allow_headers=["*"],
208
+ )
209
+
210
+
211
+ @app.get("/")
212
+ async def root():
213
+ """Root endpoint."""
214
+ return {{"message": "Welcome to {project_name}"}}
215
+
216
+
217
+ @app.get("/health")
218
+ async def health():
219
+ """Health check endpoint."""
220
+ return {{"status": "healthy"}}
221
+
222
+
223
+ # Import and include routers
224
+ # from {safe_name}.api import router
225
+ # app.include_router(router)
226
+ '''
227
+ _write_file(target_dir / f"src/{safe_name}/main.py", main_app, force)
228
+
229
+ # __init__.py
230
+ _write_file(target_dir / f"src/{safe_name}/__init__.py", '"""Package."""\n', force)
231
+ _write_file(target_dir / f"src/{safe_name}/api/__init__.py", '"""API module."""\n', force)
232
+ _write_file(target_dir / f"src/{safe_name}/models/__init__.py", '"""Models module."""\n', force)
233
+ _write_file(target_dir / f"src/{safe_name}/services/__init__.py", '"""Services module."""\n', force)
234
+
235
+ # Config
236
+ config = f'''"""Application configuration."""
237
+
238
+ from pydantic_settings import BaseSettings
239
+
240
+
241
+ class Settings(BaseSettings):
242
+ """Application settings."""
243
+
244
+ app_name: str = "{project_name}"
245
+ debug: bool = False
246
+ database_url: str = "sqlite:///./app.db"
247
+
248
+ class Config:
249
+ env_file = ".env"
250
+
251
+
252
+ settings = Settings()
253
+ '''
254
+ _write_file(target_dir / f"src/{safe_name}/config.py", config, force)
255
+
256
+ # Test file
257
+ test_main = f'''"""Test main endpoints."""
258
+
259
+ import pytest
260
+ from httpx import AsyncClient
261
+ from {safe_name}.main import app
262
+
263
+
264
+ @pytest.fixture
265
+ async def client():
266
+ """Create test client."""
267
+ async with AsyncClient(app=app, base_url="http://test") as client:
268
+ yield client
269
+
270
+
271
+ @pytest.mark.asyncio
272
+ async def test_root(client: AsyncClient):
273
+ """Test root endpoint."""
274
+ response = await client.get("/")
275
+ assert response.status_code == 200
276
+ assert "message" in response.json()
277
+
278
+
279
+ @pytest.mark.asyncio
280
+ async def test_health(client: AsyncClient):
281
+ """Test health endpoint."""
282
+ response = await client.get("/health")
283
+ assert response.status_code == 200
284
+ assert response.json()["status"] == "healthy"
285
+ '''
286
+ _write_file(target_dir / "tests/test_main.py", test_main, force)
287
+ _write_file(target_dir / "tests/__init__.py", "", force)
288
+
289
+ # Dockerfile
290
+ dockerfile = f'''FROM python:3.11-slim
291
+
292
+ WORKDIR /app
293
+
294
+ # Install dependencies
295
+ COPY pyproject.toml .
296
+ RUN pip install --no-cache-dir -e .
297
+
298
+ # Copy source
299
+ COPY src/ src/
300
+
301
+ # Run
302
+ EXPOSE 8000
303
+ CMD ["uvicorn", "{safe_name}.main:app", "--host", "0.0.0.0", "--port", "8000"]
304
+ '''
305
+ _write_file(target_dir / "Dockerfile", dockerfile, force)
306
+
307
+ # README
308
+ readme = f'''# {project_name}
309
+
310
+ FastAPI application.
311
+
312
+ ## Quick Start
313
+
314
+ ```bash
315
+ # Install
316
+ pip install -e ".[dev]"
317
+
318
+ # Run
319
+ uvicorn {safe_name}.main:app --reload
320
+
321
+ # Test
322
+ pytest
323
+ ```
324
+
325
+ ## API Docs
326
+
327
+ - Swagger UI: http://localhost:8000/docs
328
+ - ReDoc: http://localhost:8000/redoc
329
+
330
+ ## Docker
331
+
332
+ ```bash
333
+ docker build -t {project_name} .
334
+ docker run -p 8000:8000 {project_name}
335
+ ```
336
+ '''
337
+ _write_file(target_dir / "README.md", readme, force)
338
+
339
+ # .env.example
340
+ env_example = """# Environment variables
341
+ DEBUG=false
342
+ DATABASE_URL=sqlite:///./app.db
343
+ """
344
+ _write_file(target_dir / ".env.example", env_example, force)
345
+
346
+ # .gitignore
347
+ gitignore = """# Python
348
+ __pycache__/
349
+ *.py[cod]
350
+ *.egg-info/
351
+ .venv/
352
+ venv/
353
+
354
+ # IDE
355
+ .idea/
356
+ .vscode/
357
+
358
+ # Testing
359
+ .pytest_cache/
360
+ .coverage
361
+
362
+ # Environment
363
+ .env
364
+ *.db
365
+
366
+ # Up CLI
367
+ .loop_state.json
368
+ """
369
+ _write_file(target_dir / ".gitignore", gitignore, force)
370
+
371
+
372
+ # =============================================================================
373
+ # Next.js Template
374
+ # =============================================================================
375
+
376
+ def _create_nextjs_template(target_dir: Path, project_name: str, force: bool) -> None:
377
+ """Create Next.js project template."""
378
+ # Create directories
379
+ dirs = [
380
+ "src/app",
381
+ "src/components",
382
+ "src/lib",
383
+ "src/types",
384
+ "public",
385
+ ]
386
+ for d in dirs:
387
+ (target_dir / d).mkdir(parents=True, exist_ok=True)
388
+
389
+ # package.json
390
+ package_json = f'''{{
391
+ "name": "{project_name}",
392
+ "version": "0.1.0",
393
+ "private": true,
394
+ "scripts": {{
395
+ "dev": "next dev",
396
+ "build": "next build",
397
+ "start": "next start",
398
+ "lint": "next lint",
399
+ "test": "jest"
400
+ }},
401
+ "dependencies": {{
402
+ "next": "14.x",
403
+ "react": "18.x",
404
+ "react-dom": "18.x"
405
+ }},
406
+ "devDependencies": {{
407
+ "@types/node": "20.x",
408
+ "@types/react": "18.x",
409
+ "@types/react-dom": "18.x",
410
+ "typescript": "5.x",
411
+ "tailwindcss": "3.x",
412
+ "postcss": "8.x",
413
+ "autoprefixer": "10.x",
414
+ "eslint": "8.x",
415
+ "eslint-config-next": "14.x",
416
+ "jest": "29.x",
417
+ "@testing-library/react": "14.x",
418
+ "@testing-library/jest-dom": "6.x"
419
+ }}
420
+ }}
421
+ '''
422
+ _write_file(target_dir / "package.json", package_json, force)
423
+
424
+ # tsconfig.json
425
+ tsconfig = '''{
426
+ "compilerOptions": {
427
+ "lib": ["dom", "dom.iterable", "esnext"],
428
+ "allowJs": true,
429
+ "skipLibCheck": true,
430
+ "strict": true,
431
+ "noEmit": true,
432
+ "esModuleInterop": true,
433
+ "module": "esnext",
434
+ "moduleResolution": "bundler",
435
+ "resolveJsonModule": true,
436
+ "isolatedModules": true,
437
+ "jsx": "preserve",
438
+ "incremental": true,
439
+ "plugins": [{ "name": "next" }],
440
+ "paths": {
441
+ "@/*": ["./src/*"]
442
+ }
443
+ },
444
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
445
+ "exclude": ["node_modules"]
446
+ }
447
+ '''
448
+ _write_file(target_dir / "tsconfig.json", tsconfig, force)
449
+
450
+ # tailwind.config.js
451
+ tailwind = '''/** @type {import('tailwindcss').Config} */
452
+ module.exports = {
453
+ content: [
454
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
455
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
456
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
457
+ ],
458
+ theme: {
459
+ extend: {},
460
+ },
461
+ plugins: [],
462
+ }
463
+ '''
464
+ _write_file(target_dir / "tailwind.config.js", tailwind, force)
465
+
466
+ # postcss.config.js
467
+ postcss = '''module.exports = {
468
+ plugins: {
469
+ tailwindcss: {},
470
+ autoprefixer: {},
471
+ },
472
+ }
473
+ '''
474
+ _write_file(target_dir / "postcss.config.js", postcss, force)
475
+
476
+ # next.config.js
477
+ next_config = '''/** @type {import('next').NextConfig} */
478
+ const nextConfig = {
479
+ reactStrictMode: true,
480
+ }
481
+
482
+ module.exports = nextConfig
483
+ '''
484
+ _write_file(target_dir / "next.config.js", next_config, force)
485
+
486
+ # src/app/layout.tsx
487
+ layout = f'''import type {{ Metadata }} from 'next'
488
+ import './globals.css'
489
+
490
+ export const metadata: Metadata = {{
491
+ title: '{project_name}',
492
+ description: 'Generated by up-cli',
493
+ }}
494
+
495
+ export default function RootLayout({{
496
+ children,
497
+ }}: {{
498
+ children: React.ReactNode
499
+ }}) {{
500
+ return (
501
+ <html lang="en">
502
+ <body>{{children}}</body>
503
+ </html>
504
+ )
505
+ }}
506
+ '''
507
+ _write_file(target_dir / "src/app/layout.tsx", layout, force)
508
+
509
+ # src/app/page.tsx
510
+ page = f'''export default function Home() {{
511
+ return (
512
+ <main className="flex min-h-screen flex-col items-center justify-center p-24">
513
+ <h1 className="text-4xl font-bold">{project_name}</h1>
514
+ <p className="mt-4 text-gray-600">
515
+ Edit <code className="bg-gray-100 px-2 py-1 rounded">src/app/page.tsx</code> to get started.
516
+ </p>
517
+ </main>
518
+ )
519
+ }}
520
+ '''
521
+ _write_file(target_dir / "src/app/page.tsx", page, force)
522
+
523
+ # src/app/globals.css
524
+ globals_css = '''@tailwind base;
525
+ @tailwind components;
526
+ @tailwind utilities;
527
+ '''
528
+ _write_file(target_dir / "src/app/globals.css", globals_css, force)
529
+
530
+ # README
531
+ readme = f'''# {project_name}
532
+
533
+ Next.js application with TypeScript and Tailwind CSS.
534
+
535
+ ## Quick Start
536
+
537
+ ```bash
538
+ npm install
539
+ npm run dev
540
+ ```
541
+
542
+ Open [http://localhost:3000](http://localhost:3000).
543
+
544
+ ## Scripts
545
+
546
+ - `npm run dev` - Development server
547
+ - `npm run build` - Production build
548
+ - `npm run start` - Start production server
549
+ - `npm run lint` - Run linter
550
+ - `npm run test` - Run tests
551
+ '''
552
+ _write_file(target_dir / "README.md", readme, force)
553
+
554
+ # .gitignore
555
+ gitignore = """# Dependencies
556
+ node_modules/
557
+ .pnp/
558
+
559
+ # Build
560
+ .next/
561
+ out/
562
+ build/
563
+ dist/
564
+
565
+ # Testing
566
+ coverage/
567
+
568
+ # IDE
569
+ .idea/
570
+ .vscode/
571
+
572
+ # Env
573
+ .env*.local
574
+
575
+ # Up CLI
576
+ .loop_state.json
577
+ """
578
+ _write_file(target_dir / ".gitignore", gitignore, force)
579
+
580
+
581
+ # =============================================================================
582
+ # Python Library Template
583
+ # =============================================================================
584
+
585
+ def _create_python_lib_template(target_dir: Path, project_name: str, force: bool) -> None:
586
+ """Create Python library template."""
587
+ safe_name = project_name.replace("-", "_").lower()
588
+
589
+ # Create directories
590
+ dirs = [
591
+ f"src/{safe_name}",
592
+ "tests",
593
+ ]
594
+ for d in dirs:
595
+ (target_dir / d).mkdir(parents=True, exist_ok=True)
596
+
597
+ # pyproject.toml
598
+ pyproject = f'''[build-system]
599
+ requires = ["hatchling"]
600
+ build-backend = "hatchling.build"
601
+
602
+ [project]
603
+ name = "{project_name}"
604
+ version = "0.1.0"
605
+ description = "A Python library"
606
+ readme = "README.md"
607
+ license = "MIT"
608
+ requires-python = ">=3.10"
609
+ authors = [
610
+ {{ name = "Your Name", email = "you@example.com" }}
611
+ ]
612
+ classifiers = [
613
+ "Development Status :: 3 - Alpha",
614
+ "Intended Audience :: Developers",
615
+ "License :: OSI Approved :: MIT License",
616
+ "Programming Language :: Python :: 3",
617
+ "Programming Language :: Python :: 3.10",
618
+ "Programming Language :: Python :: 3.11",
619
+ "Programming Language :: Python :: 3.12",
620
+ ]
621
+ dependencies = []
622
+
623
+ [project.optional-dependencies]
624
+ dev = [
625
+ "pytest>=7.0",
626
+ "pytest-cov>=4.0",
627
+ "ruff>=0.1.0",
628
+ "mypy>=1.5.0",
629
+ ]
630
+
631
+ [project.urls]
632
+ Homepage = "https://github.com/yourusername/{project_name}"
633
+ Documentation = "https://github.com/yourusername/{project_name}#readme"
634
+ Repository = "https://github.com/yourusername/{project_name}"
635
+
636
+ [tool.hatch.build.targets.wheel]
637
+ packages = ["src/{safe_name}"]
638
+
639
+ [tool.pytest.ini_options]
640
+ testpaths = ["tests"]
641
+ addopts = "--cov={safe_name} --cov-report=term-missing"
642
+
643
+ [tool.ruff]
644
+ line-length = 100
645
+ target-version = "py310"
646
+ select = ["E", "F", "I", "N", "W", "UP"]
647
+
648
+ [tool.mypy]
649
+ python_version = "3.10"
650
+ strict = true
651
+ '''
652
+ _write_file(target_dir / "pyproject.toml", pyproject, force)
653
+
654
+ # Main module
655
+ init_py = f'''"""{project_name} - A Python library.
656
+
657
+ Example:
658
+ >>> from {safe_name} import hello
659
+ >>> hello("World")
660
+ 'Hello, World!'
661
+ """
662
+
663
+ __version__ = "0.1.0"
664
+
665
+
666
+ def hello(name: str) -> str:
667
+ """Say hello.
668
+
669
+ Args:
670
+ name: Name to greet
671
+
672
+ Returns:
673
+ Greeting message
674
+ """
675
+ return f"Hello, {{name}}!"
676
+ '''
677
+ _write_file(target_dir / f"src/{safe_name}/__init__.py", init_py, force)
678
+
679
+ # Tests
680
+ test_main = f'''"""Tests for {project_name}."""
681
+
682
+ from {safe_name} import hello, __version__
683
+
684
+
685
+ def test_version():
686
+ """Test version is set."""
687
+ assert __version__ == "0.1.0"
688
+
689
+
690
+ def test_hello():
691
+ """Test hello function."""
692
+ assert hello("World") == "Hello, World!"
693
+ assert hello("Python") == "Hello, Python!"
694
+ '''
695
+ _write_file(target_dir / "tests/test_main.py", test_main, force)
696
+ _write_file(target_dir / "tests/__init__.py", "", force)
697
+
698
+ # README
699
+ readme = f'''# {project_name}
700
+
701
+ A Python library.
702
+
703
+ ## Installation
704
+
705
+ ```bash
706
+ pip install {project_name}
707
+ ```
708
+
709
+ ## Usage
710
+
711
+ ```python
712
+ from {safe_name} import hello
713
+
714
+ print(hello("World")) # Hello, World!
715
+ ```
716
+
717
+ ## Development
718
+
719
+ ```bash
720
+ # Install with dev dependencies
721
+ pip install -e ".[dev]"
722
+
723
+ # Run tests
724
+ pytest
725
+
726
+ # Lint
727
+ ruff check src/
728
+
729
+ # Type check
730
+ mypy src/
731
+ ```
732
+
733
+ ## License
734
+
735
+ MIT
736
+ '''
737
+ _write_file(target_dir / "README.md", readme, force)
738
+
739
+ # LICENSE
740
+ license_text = f'''MIT License
741
+
742
+ Copyright (c) {date.today().year} Your Name
743
+
744
+ Permission is hereby granted, free of charge, to any person obtaining a copy
745
+ of this software and associated documentation files (the "Software"), to deal
746
+ in the Software without restriction, including without limitation the rights
747
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
748
+ copies of the Software, and to permit persons to whom the Software is
749
+ furnished to do so, subject to the following conditions:
750
+
751
+ The above copyright notice and this permission notice shall be included in all
752
+ copies or substantial portions of the Software.
753
+
754
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
755
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
756
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
757
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
758
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
759
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
760
+ SOFTWARE.
761
+ '''
762
+ _write_file(target_dir / "LICENSE", license_text, force)
763
+
764
+ # .gitignore
765
+ gitignore = """# Python
766
+ __pycache__/
767
+ *.py[cod]
768
+ *.egg-info/
769
+ .venv/
770
+ venv/
771
+ dist/
772
+ build/
773
+
774
+ # IDE
775
+ .idea/
776
+ .vscode/
777
+
778
+ # Testing
779
+ .pytest_cache/
780
+ .coverage
781
+ htmlcov/
782
+
783
+ # Up CLI
784
+ .loop_state.json
785
+ """
786
+ _write_file(target_dir / ".gitignore", gitignore, force)