spawnio 1.0.5__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 (56) hide show
  1. spawn/__init__.py +6 -0
  2. spawn/cli/__init__.py +0 -0
  3. spawn/cli/app.py +218 -0
  4. spawn/cli/noninteractive.py +204 -0
  5. spawn/cli/prompts.py +267 -0
  6. spawn/core/__init__.py +0 -0
  7. spawn/core/exceptions.py +7 -0
  8. spawn/core/models.py +21 -0
  9. spawn/core/registry.py +152 -0
  10. spawn/generators/__init__.py +0 -0
  11. spawn/generators/custom_structure.py +508 -0
  12. spawn/generators/project_generator.py +115 -0
  13. spawn/github/__init__.py +0 -0
  14. spawn/github/exceptions.py +5 -0
  15. spawn/github/publisher.py +69 -0
  16. spawn/github/validators.py +23 -0
  17. spawn/templates/__init__.py +0 -0
  18. spawn/templates/agent/__init__.py +182 -0
  19. spawn/templates/agent/content.py +374 -0
  20. spawn/templates/automation/__init__.py +100 -0
  21. spawn/templates/automation/content.py +181 -0
  22. spawn/templates/backend_api/__init__.py +243 -0
  23. spawn/templates/backend_api/content.py +509 -0
  24. spawn/templates/base.py +58 -0
  25. spawn/templates/chatbot/__init__.py +243 -0
  26. spawn/templates/chatbot/content.py +668 -0
  27. spawn/templates/cli_application/__init__.py +211 -0
  28. spawn/templates/cli_application/content.py +512 -0
  29. spawn/templates/data_project/__init__.py +125 -0
  30. spawn/templates/data_project/analysis/__init__.py +74 -0
  31. spawn/templates/data_project/analysis/content.py +190 -0
  32. spawn/templates/data_project/dashboard/__init__.py +61 -0
  33. spawn/templates/data_project/dashboard/content.py +135 -0
  34. spawn/templates/data_project/etl/__init__.py +66 -0
  35. spawn/templates/data_project/etl/content.py +239 -0
  36. spawn/templates/data_project/ml/__init__.py +62 -0
  37. spawn/templates/data_project/ml/content.py +239 -0
  38. spawn/templates/mcp_server/__init__.py +83 -0
  39. spawn/templates/mcp_server/content.py +161 -0
  40. spawn/templates/rag/__init__.py +124 -0
  41. spawn/templates/rag/content.py +463 -0
  42. spawn/templates/shared_content.py +89 -0
  43. spawn/utils/__init__.py +0 -0
  44. spawn/utils/banner.py +41 -0
  45. spawn/utils/console.py +3 -0
  46. spawn/utils/doctor.py +594 -0
  47. spawn/utils/git.py +132 -0
  48. spawn/utils/success.py +47 -0
  49. spawn/utils/theme.py +25 -0
  50. spawn/utils/uv.py +61 -0
  51. spawn/utils/validators.py +15 -0
  52. spawnio-1.0.5.dist-info/METADATA +798 -0
  53. spawnio-1.0.5.dist-info/RECORD +56 -0
  54. spawnio-1.0.5.dist-info/WHEEL +4 -0
  55. spawnio-1.0.5.dist-info/entry_points.txt +2 -0
  56. spawnio-1.0.5.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,211 @@
1
+ from pathlib import Path
2
+
3
+ from spawn.templates.base import BaseTemplate
4
+ from spawn.templates.cli_application.content import (
5
+ INIT_CONTENT,
6
+ TYPER_MAIN_CONTENT,
7
+ TYPER_COMMANDS_INIT_CONTENT,
8
+ TYPER_UTILS_INIT_CONTENT,
9
+ TYPER_TEST_CONTENT,
10
+ TYPER_README_CONTENT,
11
+ CLICK_MAIN_CONTENT,
12
+ CLICK_TEST_CONTENT,
13
+ CLICK_README_CONTENT,
14
+ ARGPARSE_MAIN_CONTENT,
15
+ ARGPARSE_TEST_CONTENT,
16
+ ARGPARSE_README_CONTENT,
17
+ TYPER_INTERACTIVE_MAIN_CONTENT,
18
+ TYPER_INTERACTIVE_TEST_CONTENT,
19
+ CLICK_INTERACTIVE_MAIN_CONTENT,
20
+ CLICK_INTERACTIVE_TEST_CONTENT,
21
+ ARGPARSE_INTERACTIVE_MAIN_CONTENT,
22
+ ARGPARSE_INTERACTIVE_TEST_CONTENT,
23
+ CLI_GITHUB_ACTIONS_CI_BASE,
24
+ CLI_GITHUB_ACTIONS_CI_RUFF_STEP,
25
+ CLI_GITHUB_ACTIONS_CI_PYTEST_STEP,
26
+ TYPER_INTERACTIVE_README_CONTENT,
27
+ CLICK_INTERACTIVE_README_CONTENT,
28
+ ARGPARSE_INTERACTIVE_README_CONTENT,
29
+ )
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Folder structures
33
+ # ---------------------------------------------------------------------------
34
+
35
+ UTILITY_FOLDERS = [
36
+ "src/commands",
37
+ "src/utils",
38
+ "tests",
39
+ ]
40
+
41
+ INTERACTIVE_FOLDERS = [
42
+ "src/commands",
43
+ "src/prompts",
44
+ "src/ui",
45
+ "src/utils",
46
+ "tests",
47
+ ]
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # Utility starter file lists
51
+ # ---------------------------------------------------------------------------
52
+
53
+ TYPER_UTILITY_FILES = [
54
+ ("src/__init__.py", INIT_CONTENT),
55
+ ("src/commands/__init__.py", TYPER_COMMANDS_INIT_CONTENT),
56
+ ("src/utils/__init__.py", TYPER_UTILS_INIT_CONTENT),
57
+ ("src/main.py", TYPER_MAIN_CONTENT),
58
+ ("tests/__init__.py", INIT_CONTENT),
59
+ ("tests/test_cli.py", TYPER_TEST_CONTENT),
60
+ ]
61
+
62
+ CLICK_UTILITY_FILES = [
63
+ ("src/__init__.py", INIT_CONTENT),
64
+ ("src/commands/__init__.py", INIT_CONTENT),
65
+ ("src/utils/__init__.py", INIT_CONTENT),
66
+ ("src/main.py", CLICK_MAIN_CONTENT),
67
+ ("tests/__init__.py", INIT_CONTENT),
68
+ ("tests/test_cli.py", CLICK_TEST_CONTENT),
69
+ ]
70
+
71
+ ARGPARSE_UTILITY_FILES = [
72
+ ("src/__init__.py", INIT_CONTENT),
73
+ ("src/commands/__init__.py", INIT_CONTENT),
74
+ ("src/utils/__init__.py", INIT_CONTENT),
75
+ ("src/main.py", ARGPARSE_MAIN_CONTENT),
76
+ ("tests/__init__.py", INIT_CONTENT),
77
+ ("tests/test_cli.py", ARGPARSE_TEST_CONTENT),
78
+ ]
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # Interactive starter file lists
82
+ # ---------------------------------------------------------------------------
83
+
84
+ TYPER_INTERACTIVE_FILES = [
85
+ ("src/__init__.py", INIT_CONTENT),
86
+ ("src/commands/__init__.py", INIT_CONTENT),
87
+ ("src/prompts/__init__.py", INIT_CONTENT),
88
+ ("src/ui/__init__.py", INIT_CONTENT),
89
+ ("src/utils/__init__.py", INIT_CONTENT),
90
+ ("src/main.py", TYPER_INTERACTIVE_MAIN_CONTENT),
91
+ ("tests/__init__.py", INIT_CONTENT),
92
+ ("tests/test_cli.py", TYPER_INTERACTIVE_TEST_CONTENT),
93
+ ]
94
+
95
+ CLICK_INTERACTIVE_FILES = [
96
+ ("src/__init__.py", INIT_CONTENT),
97
+ ("src/commands/__init__.py", INIT_CONTENT),
98
+ ("src/prompts/__init__.py", INIT_CONTENT),
99
+ ("src/ui/__init__.py", INIT_CONTENT),
100
+ ("src/utils/__init__.py", INIT_CONTENT),
101
+ ("src/main.py", CLICK_INTERACTIVE_MAIN_CONTENT),
102
+ ("tests/__init__.py", INIT_CONTENT),
103
+ ("tests/test_cli.py", CLICK_INTERACTIVE_TEST_CONTENT),
104
+ ]
105
+
106
+ ARGPARSE_INTERACTIVE_FILES = [
107
+ ("src/__init__.py", INIT_CONTENT),
108
+ ("src/commands/__init__.py", INIT_CONTENT),
109
+ ("src/prompts/__init__.py", INIT_CONTENT),
110
+ ("src/ui/__init__.py", INIT_CONTENT),
111
+ ("src/utils/__init__.py", INIT_CONTENT),
112
+ ("src/main.py", ARGPARSE_INTERACTIVE_MAIN_CONTENT),
113
+ ("tests/__init__.py", INIT_CONTENT),
114
+ ("tests/test_cli.py", ARGPARSE_INTERACTIVE_TEST_CONTENT),
115
+ ]
116
+
117
+
118
+ class CLITemplate(BaseTemplate):
119
+ def __init__(
120
+ self,
121
+ framework: str | None = None,
122
+ extras: list[str] | None = None,
123
+ cli_type: str | None = None,
124
+ ) -> None:
125
+ self.framework = framework or "typer"
126
+ self.extras = extras or []
127
+ self.cli_type = cli_type or "utility"
128
+
129
+ is_interactive = self.cli_type == "interactive"
130
+
131
+ if self.framework == "click":
132
+ folders = list(INTERACTIVE_FOLDERS if is_interactive else UTILITY_FOLDERS)
133
+ starter_files = list(
134
+ CLICK_INTERACTIVE_FILES if is_interactive else CLICK_UTILITY_FILES
135
+ )
136
+ elif self.framework == "argparse":
137
+ folders = list(INTERACTIVE_FOLDERS if is_interactive else UTILITY_FOLDERS)
138
+ starter_files = list(
139
+ ARGPARSE_INTERACTIVE_FILES if is_interactive else ARGPARSE_UTILITY_FILES
140
+ )
141
+ else:
142
+ folders = list(INTERACTIVE_FOLDERS if is_interactive else UTILITY_FOLDERS)
143
+ starter_files = list(
144
+ TYPER_INTERACTIVE_FILES if is_interactive else TYPER_UTILITY_FILES
145
+ )
146
+
147
+ super().__init__(
148
+ name="CLI Application",
149
+ folders=folders,
150
+ starter_files=starter_files,
151
+ next_steps=[
152
+ "cd {project_name}",
153
+ "uv run python -m src.main greet"
154
+ if is_interactive
155
+ else "uv run python -m src.main hello",
156
+ ],
157
+ )
158
+
159
+ def get_readme_content(self, context: dict) -> str | None:
160
+ is_interactive = self.cli_type == "interactive"
161
+ if self.framework == "click":
162
+ template = CLICK_INTERACTIVE_README_CONTENT if is_interactive else CLICK_README_CONTENT
163
+ elif self.framework == "argparse":
164
+ template = ARGPARSE_INTERACTIVE_README_CONTENT if is_interactive else ARGPARSE_README_CONTENT
165
+ else:
166
+ template = TYPER_INTERACTIVE_README_CONTENT if is_interactive else TYPER_README_CONTENT
167
+ return template.format_map(context)
168
+
169
+ def get_dependencies(self) -> list[str]:
170
+ if self.framework == "click":
171
+ base: list[str] = ["click"]
172
+ elif self.framework == "argparse":
173
+ base = []
174
+ else:
175
+ base = ["typer"]
176
+
177
+ if "pytest" in self.extras:
178
+ base = base + ["pytest"]
179
+ if "ruff" in self.extras:
180
+ base = base + ["ruff"]
181
+
182
+ return base
183
+
184
+ def post_install(self, project_path: Path) -> None:
185
+ pyproject = project_path / "pyproject.toml"
186
+ current = pyproject.read_text(encoding="utf-8")
187
+ additions = ""
188
+
189
+ if "pytest" in self.extras:
190
+ additions += (
191
+ "\n[tool.pytest.ini_options]\n"
192
+ 'testpaths = ["tests"]\n'
193
+ )
194
+
195
+ if "ruff" in self.extras:
196
+ additions += "\n[tool.ruff]\nline-length = 88\n"
197
+
198
+ if additions:
199
+ pyproject.write_text(current + additions, encoding="utf-8")
200
+
201
+ if "github-actions" in self.extras:
202
+ workflows_path = project_path / ".github" / "workflows"
203
+ workflows_path.mkdir(parents=True, exist_ok=True)
204
+ ci_content = CLI_GITHUB_ACTIONS_CI_BASE
205
+ if "ruff" in self.extras:
206
+ ci_content += CLI_GITHUB_ACTIONS_CI_RUFF_STEP
207
+ if "pytest" in self.extras:
208
+ ci_content += CLI_GITHUB_ACTIONS_CI_PYTEST_STEP
209
+ (workflows_path / "ci.yml").write_text(
210
+ ci_content, encoding="utf-8"
211
+ )
@@ -0,0 +1,512 @@
1
+ INIT_CONTENT = ""
2
+
3
+ TYPER_MAIN_CONTENT = """\
4
+ import typer
5
+
6
+ app = typer.Typer()
7
+
8
+
9
+ @app.command()
10
+ def hello() -> None:
11
+ \"\"\"Say hello.\"\"\"
12
+ typer.echo("Hello from {project_name}!")
13
+
14
+
15
+ @app.command()
16
+ def version() -> None:
17
+ \"\"\"Show the version.\"\"\"
18
+ typer.echo("{project_name} v0.1.0")
19
+
20
+
21
+ def main() -> None:
22
+ app()
23
+
24
+
25
+ if __name__ == "__main__":
26
+ main()
27
+ """
28
+
29
+ TYPER_COMMANDS_INIT_CONTENT = ""
30
+
31
+ TYPER_UTILS_INIT_CONTENT = ""
32
+
33
+ TYPER_TEST_CONTENT = """\
34
+ from typer.testing import CliRunner
35
+
36
+ from src.main import app
37
+
38
+ runner = CliRunner()
39
+
40
+
41
+ def test_hello_command() -> None:
42
+ result = runner.invoke(app, ["hello"])
43
+ assert result.exit_code == 0
44
+ assert "Hello" in result.output
45
+
46
+
47
+ def test_version_command() -> None:
48
+ result = runner.invoke(app, ["version"])
49
+ assert result.exit_code == 0
50
+ """
51
+
52
+ TYPER_README_CONTENT = """\
53
+ # {project_name}
54
+
55
+ A CLI application generated with Spawn.
56
+
57
+ ## Getting Started
58
+
59
+ ```bash
60
+ uv run python -m src.main hello
61
+ ```
62
+
63
+ ## Running Tests
64
+
65
+ ```bash
66
+ uv run pytest
67
+ ```
68
+
69
+ ## Commands
70
+
71
+ | Command | Description |
72
+ |---------|-------------------------|
73
+ | hello | Prints a hello message |
74
+ | version | Shows the version |
75
+
76
+ ## Development
77
+
78
+ ```bash
79
+ uv run python -m src.main --help
80
+ ```
81
+ """
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Click
85
+ # ---------------------------------------------------------------------------
86
+
87
+ CLICK_MAIN_CONTENT = """\
88
+ import click
89
+
90
+
91
+ @click.group()
92
+ def main() -> None:
93
+ \"\"\"CLI tool for {project_name}.\"\"\"
94
+ pass
95
+
96
+
97
+ @main.command()
98
+ def hello() -> None:
99
+ \"\"\"Say hello.\"\"\"
100
+ click.echo("Hello from {project_name}!")
101
+
102
+
103
+ if __name__ == "__main__":
104
+ main()
105
+ """
106
+
107
+ CLICK_TEST_CONTENT = """\
108
+ from click.testing import CliRunner
109
+
110
+ from src.main import main
111
+
112
+ runner = CliRunner()
113
+
114
+
115
+ def test_hello_command() -> None:
116
+ result = runner.invoke(main, ["hello"])
117
+ assert result.exit_code == 0
118
+ assert "Hello" in result.output
119
+ """
120
+
121
+ CLICK_README_CONTENT = """\
122
+ # {project_name}
123
+
124
+ A CLI application generated with Spawn.
125
+
126
+ ## Getting Started
127
+
128
+ ```bash
129
+ uv run python -m src.main hello
130
+ ```
131
+
132
+ ## Running Tests
133
+
134
+ ```bash
135
+ uv run pytest
136
+ ```
137
+
138
+ ## Commands
139
+
140
+ | Command | Description |
141
+ |---------|-------------------------|
142
+ | hello | Prints a hello message |
143
+
144
+ ## Development
145
+
146
+ ```bash
147
+ uv run python -m src.main --help
148
+ ```
149
+
150
+ Built with Click
151
+ """
152
+
153
+ # ---------------------------------------------------------------------------
154
+ # Argparse
155
+ # ---------------------------------------------------------------------------
156
+
157
+ ARGPARSE_MAIN_CONTENT = """\
158
+ import argparse
159
+
160
+
161
+ def hello(args: argparse.Namespace) -> None:
162
+ \"\"\"Say hello.\"\"\"
163
+ print("Hello from {project_name}!")
164
+
165
+
166
+ def main() -> None:
167
+ parser = argparse.ArgumentParser(
168
+ prog="{project_name}",
169
+ description="{project_name} CLI",
170
+ )
171
+
172
+ subparsers = parser.add_subparsers(dest="command")
173
+
174
+ hello_parser = subparsers.add_parser("hello", help="Say hello")
175
+ hello_parser.set_defaults(func=hello)
176
+
177
+ args = parser.parse_args()
178
+
179
+ if hasattr(args, "func"):
180
+ args.func(args)
181
+ else:
182
+ parser.print_help()
183
+
184
+
185
+ if __name__ == "__main__":
186
+ main()
187
+ """
188
+
189
+ ARGPARSE_TEST_CONTENT = """\
190
+ import subprocess
191
+ import sys
192
+
193
+
194
+ def test_hello_command() -> None:
195
+ result = subprocess.run(
196
+ [sys.executable, "-m", "src.main", "hello"],
197
+ capture_output=True,
198
+ text=True,
199
+ )
200
+ assert result.returncode == 0
201
+ assert "Hello" in result.stdout
202
+ """
203
+
204
+ ARGPARSE_README_CONTENT = """\
205
+ # {project_name}
206
+
207
+ A CLI application generated with Spawn.
208
+
209
+ ## Getting Started
210
+
211
+ ```bash
212
+ uv run python -m src.main hello
213
+ ```
214
+
215
+ ## Running Tests
216
+
217
+ ```bash
218
+ uv run pytest
219
+ ```
220
+
221
+ ## Commands
222
+
223
+ | Command | Description |
224
+ |---------|-------------------------|
225
+ | hello | Prints a hello message |
226
+
227
+ ## Development
228
+
229
+ ```bash
230
+ uv run python -m src.main --help
231
+ ```
232
+
233
+ Built with Python's standard argparse library. No external dependencies required.
234
+ """
235
+
236
+ # ---------------------------------------------------------------------------
237
+ # Interactive — Typer
238
+ # ---------------------------------------------------------------------------
239
+
240
+ TYPER_INTERACTIVE_MAIN_CONTENT = """\
241
+ import typer
242
+
243
+ app = typer.Typer()
244
+
245
+
246
+ @app.command()
247
+ def greet() -> None:
248
+ \"\"\"Greet the user interactively.\"\"\"
249
+ name = typer.prompt("What is your name?")
250
+ typer.echo(f"Hello, {{name}}! Welcome to {project_name}.")
251
+
252
+
253
+ @app.command()
254
+ def version() -> None:
255
+ \"\"\"Show the version.\"\"\"
256
+ typer.echo("{project_name} v0.1.0")
257
+
258
+
259
+ def main() -> None:
260
+ app()
261
+
262
+
263
+ if __name__ == "__main__":
264
+ main()
265
+ """
266
+
267
+ TYPER_INTERACTIVE_TEST_CONTENT = """\
268
+ from typer.testing import CliRunner
269
+
270
+ from src.main import app
271
+
272
+ runner = CliRunner()
273
+
274
+
275
+ def test_greet_command() -> None:
276
+ result = runner.invoke(app, ["greet"], input="Alice\\n")
277
+ assert result.exit_code == 0
278
+ assert "Hello, Alice" in result.output
279
+
280
+
281
+ def test_version_command() -> None:
282
+ result = runner.invoke(app, ["version"])
283
+ assert result.exit_code == 0
284
+ """
285
+
286
+ # ---------------------------------------------------------------------------
287
+ # Interactive — Click
288
+ # ---------------------------------------------------------------------------
289
+
290
+ CLICK_INTERACTIVE_MAIN_CONTENT = """\
291
+ import click
292
+
293
+
294
+ @click.group()
295
+ def main() -> None:
296
+ \"\"\"Interactive CLI for {project_name}.\"\"\"
297
+ pass
298
+
299
+
300
+ @main.command()
301
+ def greet() -> None:
302
+ \"\"\"Greet the user interactively.\"\"\"
303
+ name = click.prompt("What is your name?")
304
+ click.echo(f"Hello, {{name}}! Welcome to {project_name}.")
305
+
306
+
307
+ if __name__ == "__main__":
308
+ main()
309
+ """
310
+
311
+ CLICK_INTERACTIVE_TEST_CONTENT = """\
312
+ from click.testing import CliRunner
313
+
314
+ from src.main import main
315
+
316
+ runner = CliRunner()
317
+
318
+
319
+ def test_greet_command() -> None:
320
+ result = runner.invoke(main, ["greet"], input="Alice\\n")
321
+ assert result.exit_code == 0
322
+ assert "Hello, Alice" in result.output
323
+ """
324
+
325
+ # ---------------------------------------------------------------------------
326
+ # Interactive — Argparse
327
+ # ---------------------------------------------------------------------------
328
+
329
+ ARGPARSE_INTERACTIVE_MAIN_CONTENT = """\
330
+ import argparse
331
+
332
+
333
+ def greet(args: argparse.Namespace) -> None:
334
+ \"\"\"Greet the user interactively.\"\"\"
335
+ name = input("What is your name? ")
336
+ print(f"Hello, {{name}}! Welcome to {project_name}.")
337
+
338
+
339
+ def main() -> None:
340
+ parser = argparse.ArgumentParser(
341
+ prog="{project_name}",
342
+ description="{project_name} interactive CLI",
343
+ )
344
+
345
+ subparsers = parser.add_subparsers(dest="command")
346
+
347
+ greet_parser = subparsers.add_parser("greet", help="Greet the user")
348
+ greet_parser.set_defaults(func=greet)
349
+
350
+ args = parser.parse_args()
351
+
352
+ if hasattr(args, "func"):
353
+ args.func(args)
354
+ else:
355
+ parser.print_help()
356
+
357
+
358
+ if __name__ == "__main__":
359
+ main()
360
+ """
361
+
362
+ ARGPARSE_INTERACTIVE_TEST_CONTENT = """\
363
+ import subprocess
364
+ import sys
365
+
366
+
367
+ def test_greet_command() -> None:
368
+ result = subprocess.run(
369
+ [sys.executable, "-m", "src.main", "greet"],
370
+ input="Alice\\n",
371
+ capture_output=True,
372
+ text=True,
373
+ )
374
+ assert result.returncode == 0
375
+ assert "Hello, Alice" in result.stdout
376
+ """
377
+
378
+ # ---------------------------------------------------------------------------
379
+ # GitHub Actions CI
380
+ # ---------------------------------------------------------------------------
381
+
382
+ CLI_GITHUB_ACTIONS_CI_BASE = """\
383
+ name: CI
384
+
385
+ on:
386
+ push:
387
+ branches: [main]
388
+ pull_request:
389
+ branches: [main]
390
+
391
+ jobs:
392
+ ci:
393
+ runs-on: ubuntu-latest
394
+
395
+ steps:
396
+ - uses: actions/checkout@v4
397
+
398
+ - name: Install uv
399
+ uses: astral-sh/setup-uv@v4
400
+
401
+ - name: Install dependencies
402
+ run: uv sync
403
+ """
404
+
405
+ CLI_GITHUB_ACTIONS_CI_RUFF_STEP = """\
406
+ - name: Run Ruff
407
+ run: uv run ruff check .
408
+ """
409
+
410
+ CLI_GITHUB_ACTIONS_CI_PYTEST_STEP = """\
411
+ - name: Run Pytest
412
+ run: uv run pytest
413
+ """
414
+
415
+ # ---------------------------------------------------------------------------
416
+ # Interactive READMEs
417
+ # ---------------------------------------------------------------------------
418
+
419
+ TYPER_INTERACTIVE_README_CONTENT = """\
420
+ # {project_name}
421
+
422
+ A CLI application generated with Spawn.
423
+
424
+ ## Getting Started
425
+
426
+ ```bash
427
+ uv run python -m src.main greet
428
+ ```
429
+
430
+ ## Running Tests
431
+
432
+ ```bash
433
+ uv run pytest
434
+ ```
435
+
436
+ ## Commands
437
+
438
+ | Command | Description |
439
+ |---------|--------------------------------|
440
+ | greet | Greets the user interactively |
441
+ | version | Shows the version |
442
+
443
+ ## Development
444
+
445
+ ```bash
446
+ uv run python -m src.main --help
447
+ ```
448
+ """
449
+
450
+ CLICK_INTERACTIVE_README_CONTENT = """\
451
+ # {project_name}
452
+
453
+ A CLI application generated with Spawn.
454
+
455
+ ## Getting Started
456
+
457
+ ```bash
458
+ uv run python -m src.main greet
459
+ ```
460
+
461
+ ## Running Tests
462
+
463
+ ```bash
464
+ uv run pytest
465
+ ```
466
+
467
+ ## Commands
468
+
469
+ | Command | Description |
470
+ |---------|--------------------------------|
471
+ | greet | Greets the user interactively |
472
+
473
+ ## Development
474
+
475
+ ```bash
476
+ uv run python -m src.main --help
477
+ ```
478
+
479
+ Built with Click
480
+ """
481
+
482
+ ARGPARSE_INTERACTIVE_README_CONTENT = """\
483
+ # {project_name}
484
+
485
+ A CLI application generated with Spawn.
486
+
487
+ ## Getting Started
488
+
489
+ ```bash
490
+ uv run python -m src.main greet
491
+ ```
492
+
493
+ ## Running Tests
494
+
495
+ ```bash
496
+ uv run pytest
497
+ ```
498
+
499
+ ## Commands
500
+
501
+ | Command | Description |
502
+ |---------|--------------------------------|
503
+ | greet | Greets the user interactively |
504
+
505
+ ## Development
506
+
507
+ ```bash
508
+ uv run python -m src.main --help
509
+ ```
510
+
511
+ Built with Python's standard argparse library. No external dependencies required.
512
+ """