sscli 2.1.0__tar.gz → 2.2.0__tar.gz

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 (97) hide show
  1. {sscli-2.1.0 → sscli-2.2.0}/PKG-INFO +37 -27
  2. {sscli-2.1.0 → sscli-2.2.0}/README.md +36 -26
  3. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/development.py +101 -5
  4. sscli-2.2.0/foundry/actions/explore.py +227 -0
  5. sscli-2.2.0/foundry/actions/features.py +302 -0
  6. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/generation_utils.py +1 -0
  7. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/generator.py +25 -1
  8. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/interactive_config.py +21 -1
  9. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/metadata.py +31 -0
  10. {sscli-2.1.0 → sscli-2.2.0}/foundry/cli.py +46 -1
  11. {sscli-2.1.0 → sscli-2.2.0}/foundry/constants.py +27 -12
  12. sscli-2.2.0/foundry/ops/cli.py +120 -0
  13. sscli-2.2.0/foundry/ops/version_manager.py +187 -0
  14. {sscli-2.1.0 → sscli-2.2.0}/foundry/utils.py +2 -2
  15. {sscli-2.1.0 → sscli-2.2.0}/pyproject.toml +1 -1
  16. {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/PKG-INFO +37 -27
  17. {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/SOURCES.txt +1 -0
  18. sscli-2.1.0/foundry/actions/explore.py +0 -139
  19. sscli-2.1.0/foundry/actions/features.py +0 -111
  20. sscli-2.1.0/foundry/ops/cli.py +0 -42
  21. {sscli-2.1.0 → sscli-2.2.0}/foundry/__init__.py +0 -0
  22. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/__init__.py +0 -0
  23. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/account_info.py +0 -0
  24. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/documentation.py +0 -0
  25. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/health.py +0 -0
  26. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/repo_utils.py +0 -0
  27. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/setup.py +0 -0
  28. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/validation.py +0 -0
  29. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verification/__init__.py +0 -0
  30. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verification/models.py +0 -0
  31. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verification/reports.py +0 -0
  32. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verification/verifier.py +0 -0
  33. {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verify.py +0 -0
  34. {sscli-2.1.0 → sscli-2.2.0}/foundry/admin_tools.py +0 -0
  35. {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/__init__.py +0 -0
  36. {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/access_manager.py +0 -0
  37. {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/manager.py +0 -0
  38. {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/models.py +0 -0
  39. {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/view.py +0 -0
  40. {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha_expiration.py +0 -0
  41. {sscli-2.1.0 → sscli-2.2.0}/foundry/animated_experience.py +0 -0
  42. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/__init__.py +0 -0
  43. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/animation.py +0 -0
  44. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/assets.py +0 -0
  45. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/base.py +0 -0
  46. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/canvas.py +0 -0
  47. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/components.py +0 -0
  48. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/composites.py +0 -0
  49. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/core.py +0 -0
  50. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/__init__.py +0 -0
  51. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/animation_engine.py +0 -0
  52. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/clock.py +0 -0
  53. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/frame_buffer.py +0 -0
  54. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/state.py +0 -0
  55. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/rendering.py +0 -0
  56. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/__init__.py +0 -0
  57. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/entrance.py +0 -0
  58. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/forge.py +0 -0
  59. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/info_slide.py +0 -0
  60. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/logo_disperse.py +0 -0
  61. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/logo_dissolve.py +0 -0
  62. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/logo_reveal.py +0 -0
  63. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/logo_slide.py +0 -0
  64. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/rain.py +0 -0
  65. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/tranquility.py +0 -0
  66. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/sections/__init__.py +0 -0
  67. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/ui/__init__.py +0 -0
  68. {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/utils.py +0 -0
  69. {sscli-2.1.0 → sscli-2.2.0}/foundry/auth.py +0 -0
  70. {sscli-2.1.0 → sscli-2.2.0}/foundry/client_access_manager.py +0 -0
  71. {sscli-2.1.0 → sscli-2.2.0}/foundry/commands/__init__.py +0 -0
  72. {sscli-2.1.0 → sscli-2.2.0}/foundry/commands/auth.py +0 -0
  73. {sscli-2.1.0 → sscli-2.2.0}/foundry/commands/interactive.py +0 -0
  74. {sscli-2.1.0 → sscli-2.2.0}/foundry/development_experience.py +0 -0
  75. {sscli-2.1.0 → sscli-2.2.0}/foundry/gate.py +0 -0
  76. {sscli-2.1.0 → sscli-2.2.0}/foundry/interactive.py +0 -0
  77. {sscli-2.1.0 → sscli-2.2.0}/foundry/interactive_utils.py +0 -0
  78. {sscli-2.1.0 → sscli-2.2.0}/foundry/internal_experience.py +0 -0
  79. {sscli-2.1.0 → sscli-2.2.0}/foundry/manage.py +0 -0
  80. {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/__init__.py +0 -0
  81. {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/licenses.py +0 -0
  82. {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/manager.py +0 -0
  83. {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/menus.py +0 -0
  84. {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/reporter.py +0 -0
  85. {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/runner.py +0 -0
  86. {sscli-2.1.0 → sscli-2.2.0}/foundry/patch.py +0 -0
  87. {sscli-2.1.0 → sscli-2.2.0}/foundry/telemetry.py +0 -0
  88. {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/__init__.py +0 -0
  89. {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/client/__init__.py +0 -0
  90. {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/development/__init__.py +0 -0
  91. {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/development/build_tools.py +0 -0
  92. {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/internal/__init__.py +0 -0
  93. {sscli-2.1.0 → sscli-2.2.0}/setup.cfg +0 -0
  94. {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/dependency_links.txt +0 -0
  95. {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/entry_points.txt +0 -0
  96. {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/requires.txt +0 -0
  97. {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sscli
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Seed & Source CLI - Multi-tenant SaaS scaffolding with Seed & Source Core
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -13,50 +13,49 @@ Requires-Dist: python-dotenv
13
13
  Requires-Dist: pydantic>=2.0.0
14
14
  Requires-Dist: httpx>=0.27.0
15
15
 
16
- # Seed & Source CLI v1.1.0
16
+ # Seed & Source CLI v2.2.0
17
17
 
18
- Unified interface for Seed & Source templates. Now with **Seed & Source Core** production-ready multi-tenant SaaS APIs.
18
+ Unified interface for Seed & Source templates. Now with **Seed & Source Core** and **The Central Vault**.
19
19
 
20
20
  ## 🎯 What is sscli?
21
21
 
22
- `sscli` scaffolds complete, enterprise-grade SaaS stacks with:
23
- - ✅ Multi-tenant isolation (database-level RLS)
24
- - ✅ Third-party integrations (Shopify, Stripe, WooCommerce)
25
- - ✅ Hexagonal Architecture (clean, testable code)
26
- - ✅ Security by default (HMAC validation, RLS policies)
27
- - ✅ Ready-to-deploy applications
22
+ `sscli` scaffolds complete, enterprise-grade SaaS stacks. As part of the **Great Decoupling**, our templates are now "Lean Cores", with high-value proprietary features (Commerce, Admin, Tunnels) managed via a central vault and injected at setup time.
23
+
24
+ - ✅ **Lean Cores**: Free, high-performance base templates for Rails, Python, and Astro.
25
+ - ✅ **The Vault**: Proprietary high-value features (Commerce, Admin, Tunnels) injected on-demand.
26
+ - ✅ **Multi-tenant isolation**: Pre-configured database-level security.
27
+ - ✅ **Hexagonal Architecture**: Clean separation between domain logic and infrastructure.
28
28
 
29
29
  ## 📦 Quick Install
30
30
 
31
31
  ```bash
32
32
  # Via pip
33
- pip install sscli==1.1.0
33
+ pip install sscli==2.1.0
34
34
 
35
35
  # Via pipx (recommended)
36
- pipx install sscli==1.1.0
36
+ pipx install sscli==2.1.0
37
37
 
38
38
  # Verify
39
39
  sscli --version
40
- # Output: sscli 1.1.0
40
+ # Output: sscli 2.1.0
41
41
  ```
42
42
 
43
43
  ## 🚀 Quick Start
44
44
 
45
- ### Create a Multi-Tenant Rails API
45
+ ### Create a Multi-Tenant Rails API (Lean Core + Commerce Vault)
46
46
 
47
47
  ```bash
48
48
  sscli new \
49
49
  --template rails-api \
50
50
  --name my-saas-api \
51
- --with-commerce \
52
- --with-shopify
51
+ --with-commerce
53
52
  ```
54
53
 
55
54
  This scaffolds:
56
- - Rails 8.0 API with PostgreSQL
57
- - Multi-tenant core (Tenant Integration CommercialAgreement → SecurityToken)
55
+ - Rails 8.0 API with PostgreSQL (Lean Core)
56
+ - **Vault Injection**: Pluggable Commerce adapters and and webhooks.
57
+ - Multi-tenant core (Tenants, API Keys, etc.)
58
58
  - HMAC webhook validation
59
- - Shopify adapter (ready for orders/create webhooks)
60
59
  - Row-Level Security (RLS) enforcement
61
60
 
62
61
  ### Create a Python SaaS Backend
@@ -88,16 +87,27 @@ This scaffolds:
88
87
  - Tailwind CSS
89
88
  - TypeScript
90
89
 
91
- ## 📋 Available Templates
90
+ ## 📋 Available Templates (Lean Cores)
91
+
92
+ | Template | Tier | Description |
93
+ |----------|------|-------------|
94
+ | `rails-api` | **FREE** | Multi-tenant Rails API |
95
+ | `python-saas` | **FREE** | Python SaaS Boilerplate |
96
+ | `static-landing` | **FREE** | Astro Static Landing Page |
97
+ | `react-client` | ALPHA | React + Vite frontend |
98
+ | `data-pipeline` | ALPHA | dbt + Airflow pipeline |
99
+
100
+ ## 💎 High-Value Feature Vault (Premium)
101
+
102
+ These modules are managed in the central private vault and injected on-demand into the Lean Cores:
92
103
 
93
- | Template | Description | Use Case |
94
- |----------|-------------|----------|
95
- | `rails-api` | Multi-tenant Rails API (v1.1.0) | SaaS backends, microservices |
96
- | `python-saas` | Python FastAPI backend | Data processing, ML services |
97
- | `react-client` | React + Vite frontend | Web UIs, dashboards |
98
- | `data-pipeline` | dbt + Airflow pipeline | Analytics, ETL |
99
- | `mobile-android` | Android native app | Mobile apps |
100
- | `mobile-ios` | iOS Swift app | Mobile apps |
104
+ | Feature | Tier | Description |
105
+ |---------|------|-------------|
106
+ | `commerce` | **PRO** | Shopify/Stripe adapters, webhooks, and commerce models |
107
+ | `admin` | **PRO** | Standalone NiceGUI-based admin dashboard |
108
+ | `tunnel` | **PRO** | Automated ngrok config for local webhook testing |
109
+ | `sqlite` | ALPHA | Local persistence adapter (SQLAlchemy/Alembic) |
110
+ | `ingestor` | **PRO** | Data Ingestor adapter for raw normalization |
101
111
 
102
112
  ## 🔧 CLI Commands
103
113
 
@@ -1,47 +1,46 @@
1
- # Seed & Source CLI v1.1.0
1
+ # Seed & Source CLI v2.2.0
2
2
 
3
- Unified interface for Seed & Source templates. Now with **Seed & Source Core** production-ready multi-tenant SaaS APIs.
3
+ Unified interface for Seed & Source templates. Now with **Seed & Source Core** and **The Central Vault**.
4
4
 
5
5
  ## 🎯 What is sscli?
6
6
 
7
- `sscli` scaffolds complete, enterprise-grade SaaS stacks with:
8
- - ✅ Multi-tenant isolation (database-level RLS)
9
- - ✅ Third-party integrations (Shopify, Stripe, WooCommerce)
10
- - ✅ Hexagonal Architecture (clean, testable code)
11
- - ✅ Security by default (HMAC validation, RLS policies)
12
- - ✅ Ready-to-deploy applications
7
+ `sscli` scaffolds complete, enterprise-grade SaaS stacks. As part of the **Great Decoupling**, our templates are now "Lean Cores", with high-value proprietary features (Commerce, Admin, Tunnels) managed via a central vault and injected at setup time.
8
+
9
+ - ✅ **Lean Cores**: Free, high-performance base templates for Rails, Python, and Astro.
10
+ - ✅ **The Vault**: Proprietary high-value features (Commerce, Admin, Tunnels) injected on-demand.
11
+ - ✅ **Multi-tenant isolation**: Pre-configured database-level security.
12
+ - ✅ **Hexagonal Architecture**: Clean separation between domain logic and infrastructure.
13
13
 
14
14
  ## 📦 Quick Install
15
15
 
16
16
  ```bash
17
17
  # Via pip
18
- pip install sscli==1.1.0
18
+ pip install sscli==2.1.0
19
19
 
20
20
  # Via pipx (recommended)
21
- pipx install sscli==1.1.0
21
+ pipx install sscli==2.1.0
22
22
 
23
23
  # Verify
24
24
  sscli --version
25
- # Output: sscli 1.1.0
25
+ # Output: sscli 2.1.0
26
26
  ```
27
27
 
28
28
  ## 🚀 Quick Start
29
29
 
30
- ### Create a Multi-Tenant Rails API
30
+ ### Create a Multi-Tenant Rails API (Lean Core + Commerce Vault)
31
31
 
32
32
  ```bash
33
33
  sscli new \
34
34
  --template rails-api \
35
35
  --name my-saas-api \
36
- --with-commerce \
37
- --with-shopify
36
+ --with-commerce
38
37
  ```
39
38
 
40
39
  This scaffolds:
41
- - Rails 8.0 API with PostgreSQL
42
- - Multi-tenant core (Tenant Integration CommercialAgreement → SecurityToken)
40
+ - Rails 8.0 API with PostgreSQL (Lean Core)
41
+ - **Vault Injection**: Pluggable Commerce adapters and and webhooks.
42
+ - Multi-tenant core (Tenants, API Keys, etc.)
43
43
  - HMAC webhook validation
44
- - Shopify adapter (ready for orders/create webhooks)
45
44
  - Row-Level Security (RLS) enforcement
46
45
 
47
46
  ### Create a Python SaaS Backend
@@ -73,16 +72,27 @@ This scaffolds:
73
72
  - Tailwind CSS
74
73
  - TypeScript
75
74
 
76
- ## 📋 Available Templates
75
+ ## 📋 Available Templates (Lean Cores)
76
+
77
+ | Template | Tier | Description |
78
+ |----------|------|-------------|
79
+ | `rails-api` | **FREE** | Multi-tenant Rails API |
80
+ | `python-saas` | **FREE** | Python SaaS Boilerplate |
81
+ | `static-landing` | **FREE** | Astro Static Landing Page |
82
+ | `react-client` | ALPHA | React + Vite frontend |
83
+ | `data-pipeline` | ALPHA | dbt + Airflow pipeline |
84
+
85
+ ## 💎 High-Value Feature Vault (Premium)
86
+
87
+ These modules are managed in the central private vault and injected on-demand into the Lean Cores:
77
88
 
78
- | Template | Description | Use Case |
79
- |----------|-------------|----------|
80
- | `rails-api` | Multi-tenant Rails API (v1.1.0) | SaaS backends, microservices |
81
- | `python-saas` | Python FastAPI backend | Data processing, ML services |
82
- | `react-client` | React + Vite frontend | Web UIs, dashboards |
83
- | `data-pipeline` | dbt + Airflow pipeline | Analytics, ETL |
84
- | `mobile-android` | Android native app | Mobile apps |
85
- | `mobile-ios` | iOS Swift app | Mobile apps |
89
+ | Feature | Tier | Description |
90
+ |---------|------|-------------|
91
+ | `commerce` | **PRO** | Shopify/Stripe adapters, webhooks, and commerce models |
92
+ | `admin` | **PRO** | Standalone NiceGUI-based admin dashboard |
93
+ | `tunnel` | **PRO** | Automated ngrok config for local webhook testing |
94
+ | `sqlite` | ALPHA | Local persistence adapter (SQLAlchemy/Alembic) |
95
+ | `ingestor` | **PRO** | Data Ingestor adapter for raw normalization |
86
96
 
87
97
  ## 🔧 CLI Commands
88
98
 
@@ -281,16 +281,18 @@ def run_swap(target=None):
281
281
  wheel = wheels[0]
282
282
  console.print(f" Installing from [cyan]{wheel.name}[/cyan]...")
283
283
 
284
+ import os
285
+ python_exe = venv_path / 'bin' / 'python'
286
+
284
287
  result = subprocess.run(
285
- [str(venv_bin / 'pip'), 'install', '--force-reinstall', str(wheel)],
288
+ [str(python_exe), '-m', 'pip', 'install', '--force-reinstall', str(wheel)],
286
289
  capture_output=True,
287
290
  text=True
288
291
  )
289
292
 
290
293
  if result.returncode != 0:
291
294
  console.print("[red]✗ Installation failed[/red]")
292
- if "--force-reinstall" in result.stderr:
293
- console.print(result.stderr)
295
+ console.print(f"[dim]Error details: {result.stderr[:200]}[/dim]")
294
296
  return False
295
297
 
296
298
  console.print("[green]✓ Switched to local build[/green]")
@@ -303,14 +305,18 @@ def run_swap(target=None):
303
305
 
304
306
  console.print(f" Installing sscli=={pip_version} from PyPI...")
305
307
 
308
+ import os
309
+ python_exe = venv_path / 'bin' / 'python'
310
+
306
311
  result = subprocess.run(
307
- [str(venv_bin / 'pip'), 'install', '--force-reinstall', f'sscli=={pip_version}'],
312
+ [str(python_exe), '-m', 'pip', 'install', '--force-reinstall', f'sscli=={pip_version}'],
308
313
  capture_output=True,
309
314
  text=True
310
315
  )
311
316
 
312
317
  if result.returncode != 0:
313
318
  console.print("[red]✗ Installation failed[/red]")
319
+ console.print(f"[dim]Error details: {result.stderr[:200]}[/dim]")
314
320
  return False
315
321
 
316
322
  console.print("[green]✓ Switched to pip package[/green]")
@@ -322,10 +328,93 @@ def run_swap(target=None):
322
328
 
323
329
  return False
324
330
 
331
+ def run_bump_version(bump_type=None, auto_confirm=False):
332
+ """Run the bump version script interactively or with specified type."""
333
+ stack_cli_root = get_stack_cli_root()
334
+ bin_dir = stack_cli_root / 'bin'
335
+ bump_script = bin_dir / 'sscli-bump-version'
336
+
337
+ if not bump_script.exists():
338
+ console.print("[red]✗ Bump script not found at[/red]", bump_script)
339
+ return False
340
+
341
+ # If bump_type is not provided, ask interactively
342
+ if bump_type is None:
343
+ console.print("")
344
+ console.print("[bold cyan]Version Bump Utility[/bold cyan]")
345
+ console.print("")
346
+
347
+ # Ask which version to bump
348
+ bump_type = questionary.select(
349
+ "Select version bump type:",
350
+ choices=[
351
+ questionary.Choice("Major (X.0.0)", value="major"),
352
+ questionary.Choice("Minor (x.Y.0)", value="minor"),
353
+ questionary.Choice("Patch (x.y.Z)", value="patch"),
354
+ questionary.Choice("Cancel", value="cancel"),
355
+ ],
356
+ ).ask()
357
+
358
+ if not bump_type or bump_type == "cancel":
359
+ console.print("[yellow]Cancelled[/yellow]")
360
+ return False
361
+
362
+ # In interactive mode through the dev menu, let the script ask for confirmation
363
+ auto_confirm = False
364
+
365
+ console.print("")
366
+ try:
367
+ # Prepare environment for the bash script
368
+ import os
369
+ env = os.environ.copy()
370
+
371
+ # Build command
372
+ cmd = [str(bump_script), bump_type]
373
+ stdin_input = None
374
+
375
+ if auto_confirm:
376
+ # For non-interactive CLI usage, auto-confirm by feeding 'y' to stdin
377
+ stdin_input = "y\n"
378
+
379
+ result = subprocess.run(
380
+ cmd,
381
+ input=stdin_input,
382
+ capture_output=True,
383
+ text=True,
384
+ env=env
385
+ )
386
+
387
+ # Print script output
388
+ if result.stdout:
389
+ console.print(result.stdout)
390
+
391
+ if result.returncode != 0:
392
+ if result.stderr:
393
+ console.print("[red]Error:[/red]")
394
+ console.print(result.stderr)
395
+ return False
396
+
397
+ return True
398
+
399
+ except Exception as e:
400
+ console.print(f"[red]✗ Bump failed: {e}[/red]")
401
+ return False
402
+
325
403
  def dev_tools_menu():
326
404
  """Interactive menu for development tools."""
327
405
  # Check if running in development context
328
- if not is_internal_dev():
406
+ # Check environment variable first (takes precedence for CLI wrapper scripts)
407
+ import os
408
+ from pathlib import Path
409
+ foundry_root = os.getenv("FOUNDRY_ROOT")
410
+ if foundry_root:
411
+ root_path = Path(foundry_root)
412
+ markers = [".github", "docs", "tooling", "wiring"]
413
+ is_dev = all((root_path / marker).exists() for marker in markers)
414
+ else:
415
+ is_dev = is_internal_dev()
416
+
417
+ if not is_dev:
329
418
  console.print("[yellow]⚠️ Development tools are only available in development mode.[/yellow]")
330
419
  return
331
420
 
@@ -340,6 +429,7 @@ def dev_tools_menu():
340
429
  questionary.Choice("📊 Show Development Status", value="status"),
341
430
  questionary.Choice("🔨 Build Distribution Package", value="build"),
342
431
  questionary.Choice("🔄 Switch Build Source (Local ↔ Pip)", value="swap"),
432
+ questionary.Choice("📌 Bump Version (major/minor/patch)", value="bump"),
343
433
  questionary.Choice("← Back to Main Menu", value="back"),
344
434
  ]
345
435
 
@@ -392,6 +482,12 @@ def dev_tools_menu():
392
482
  else:
393
483
  console.print("[red]✗[/red] Swap failed")
394
484
 
485
+ elif selection == "bump":
486
+ if run_bump_version():
487
+ console.print("[green]✓[/green] Version bump complete")
488
+ else:
489
+ console.print("[red]✗[/red] Version bump failed")
490
+
395
491
  console.print("")
396
492
  input("[dim]Press Enter to continue...[/dim]")
397
493
  console.clear()
@@ -0,0 +1,227 @@
1
+ from foundry.constants import console, TIER_HIERARCHY, QUESTIONARY_STYLE, TEMPLATE_REGISTRY
2
+ from foundry.utils import get_templates, get_template_info
3
+ from foundry.auth import get_current_license_info
4
+ from rich.panel import Panel
5
+ from rich.tree import Tree
6
+ import questionary
7
+ from questionary import Choice
8
+ from prompt_toolkit.key_binding import KeyBindings
9
+ import re
10
+
11
+
12
+ def _get_template_tech(name):
13
+ """Get technology stack for a template by name."""
14
+ # Map template names to tech descriptions
15
+ tech_map = {
16
+ "python-saas": "Python (FastAPI/SQLAlchemy)",
17
+ "rails-api": "Ruby on Rails",
18
+ "react-client": "React/Vite",
19
+ "rails-ui-kit": "Rails + ViewComponent",
20
+ "static-landing": "Astro (Static Site)",
21
+ "mobile-android": "Android (Kotlin/Compose)",
22
+ "mobile-ios": "iOS (Swift/SwiftUI)",
23
+ "data-pipeline": "Data Stack (dbt + Python)",
24
+ "terraform-infra": "Terraform (Multi-cloud)",
25
+ "wiring": "Docker Orchestration",
26
+ }
27
+ return tech_map.get(name, "Multi-tech Stack")
28
+
29
+
30
+ def _strip_markup(text):
31
+ """Remove Rich markup tags from text."""
32
+ return re.sub(r'\[/?[^\]]+\]', '', text)
33
+
34
+
35
+ def _render_templates_tree(buckets):
36
+ """Render a tree view with all categories expanded showing available templates."""
37
+ tree = Tree("[bold green]Seed & Source Inventory[/bold green]")
38
+
39
+ for category, items in buckets.items():
40
+ if not items:
41
+ continue
42
+
43
+ count = len(items)
44
+ cat_node = tree.add(f"[bold yellow]▼ {category} ({count})[/bold yellow]")
45
+
46
+ for item in items:
47
+ node = cat_node.add(f"[bold cyan]{item['name']}[/bold cyan]")
48
+ # Show tier badge in tree
49
+ if item['tier'] != "free":
50
+ tier_display = f"(🔐 {item['tier'].upper()})" if item['locked'] else f"(✅ {item['tier'].upper()})"
51
+ node.add(f"[yellow]{tier_display}[/yellow]")
52
+ # Show tech
53
+ tech = _get_template_tech(item['name'])
54
+ node.add(f"[dim]{tech}[/dim]")
55
+
56
+ console.print(Panel(tree, title="Available Templates", border_style="green"))
57
+
58
+
59
+ def _show_template_info(name):
60
+ """Show detailed information about a template."""
61
+ info = get_template_info(name)
62
+ tech = _get_template_tech(name)
63
+
64
+ content = f"[bold cyan]Name:[/bold cyan] {name}\n"
65
+ content += f"[bold cyan]Tech:[/bold cyan] {tech}\n"
66
+ content += f"[bold cyan]Tier:[/bold cyan] {info.get('tier', 'free').upper()}\n"
67
+ content += f"[bold cyan]Repo:[/bold cyan] {info.get('repo', 'N/A')}\n\n"
68
+ content += f"[italic]{info.get('description', 'No detailed description available.')}[/italic]"
69
+
70
+ console.print(Panel(content, title=f"Template Details: {name}", border_style="cyan", expand=False))
71
+ questionary.press_any_key_to_continue().ask()
72
+
73
+
74
+ def run_explore():
75
+ """Interactive template explorer with collapsible tree and selectable templates."""
76
+ # Get user info for tier checking
77
+ user_info = get_current_license_info()
78
+ user_tier = user_info.get("tier", "free")
79
+ user_rank = TIER_HIERARCHY.get(user_tier, 0)
80
+
81
+ # Group templates by category
82
+ buckets = {
83
+ "Backend Services": [],
84
+ "Frontend & UI": [],
85
+ "Mobile App": [],
86
+ "Data Engineering": [],
87
+ "Infrastructure": [],
88
+ "Other": [],
89
+ }
90
+
91
+ templates = get_templates()
92
+
93
+ for tmpl in templates:
94
+ name = tmpl.name
95
+ # Fetch metadata to get the tier
96
+ info = get_template_info(name)
97
+ tmpl_tier = info.get("tier", "free").lower()
98
+ tmpl_rank = TIER_HIERARCHY.get(tmpl_tier, 0)
99
+
100
+ # Check if locked
101
+ locked = tmpl_rank > user_rank
102
+
103
+ # Store template data for processing
104
+ tmpl_data = {
105
+ "name": name,
106
+ "tier": tmpl_tier,
107
+ "locked": locked,
108
+ }
109
+
110
+ if name in ["python-saas", "rails-api"]:
111
+ buckets["Backend Services"].append(tmpl_data)
112
+ elif name in ["react-client", "rails-ui-kit", "static-landing"]:
113
+ buckets["Frontend & UI"].append(tmpl_data)
114
+ elif name.startswith("mobile-"):
115
+ buckets["Mobile App"].append(tmpl_data)
116
+ elif name == "data-pipeline":
117
+ buckets["Data Engineering"].append(tmpl_data)
118
+ elif name == "terraform-infra":
119
+ buckets["Infrastructure"].append(tmpl_data)
120
+ else:
121
+ buckets["Other"].append(tmpl_data)
122
+
123
+ # Remove empty categories
124
+ buckets = {k: v for k, v in buckets.items() if v}
125
+
126
+ # State for collapsible sections - start collapsed as requested
127
+ expanded_categories = set()
128
+ last_selected_value = f"toggle:{list(buckets.keys())[0]}" if buckets else None
129
+
130
+ # Custom Keybindings for Left/Right arrows
131
+ kb = KeyBindings()
132
+ @kb.add("left")
133
+ @kb.add("right")
134
+ def _(event):
135
+ # Map left/right arrows to Enter (submit) to trigger the toggle/select loop
136
+ event.app.key_processor.feed_text("\n")
137
+
138
+ # ANSI Colors for visual richness
139
+ YELLOW = "\033[33m"
140
+ BLUE = "\033[34m"
141
+ CYAN = "\033[36m"
142
+ GREEN = "\033[32m"
143
+ RED = "\033[31m"
144
+ MAGENTA = "\033[35m"
145
+ BOLD = "\033[1m"
146
+ RESET = "\033[0m"
147
+
148
+ while True:
149
+ choices = []
150
+ for category, items in buckets.items():
151
+ is_expanded = category in expanded_categories
152
+ symbol = "▼" if is_expanded else "▶"
153
+ count = len(items)
154
+
155
+ # Category choice - Base Yellow
156
+ choices.append(Choice(
157
+ title=f"{YELLOW}{symbol} {category} ({count}){RESET}",
158
+ value=f"toggle:{category}"
159
+ ))
160
+
161
+ if is_expanded:
162
+ for i, item in enumerate(items):
163
+ is_last = (i == len(items) - 1)
164
+ connector = " └─ " if is_last else " ├─ "
165
+
166
+ # Blue template names
167
+ label = f"{connector}{BLUE}{item['name']}{RESET}"
168
+
169
+ # Colored badges
170
+ if item['locked']:
171
+ label += f" {RED}🔐 LOCKED{RESET}"
172
+ else:
173
+ tier = item['tier']
174
+ if tier == "alpha":
175
+ label += f" {YELLOW}✅ ALPHA{RESET}"
176
+ elif tier == "pro":
177
+ label += f" {MAGENTA}✅ PRO{RESET}"
178
+ elif tier == "free":
179
+ label += f" {GREEN}✅ FREE{RESET}"
180
+
181
+ # Tech description for the gray 3rd row (via patch)
182
+ tech = _get_template_tech(item['name'])
183
+ description = f" Description: {tech}"
184
+
185
+ choices.append(Choice(
186
+ title=label,
187
+ value=f"tmpl:{item['name']}",
188
+ description=description
189
+ ))
190
+
191
+ choices.append(Choice("← Back to Main Menu", value="back"))
192
+
193
+ console.clear()
194
+ # Green frame that encompasses the header and instructions
195
+ console.print(Panel(
196
+ f"[bold green]Seed & Source Inventory[/bold green]\n"
197
+ f"[dim]Navigate with arrows. Use {BOLD}Enter/Right{RESET} [dim]to expand/select, {BOLD}Left{RESET} [dim]to collapse.[/dim]",
198
+ border_style="green",
199
+ padding=(1, 2)
200
+ ))
201
+
202
+ selected = questionary.select(
203
+ "Select a template to view details:",
204
+ choices=choices,
205
+ style=questionary.Style(QUESTIONARY_STYLE),
206
+ use_indicator=True,
207
+ default=last_selected_value,
208
+ key_bindings=kb
209
+ ).ask()
210
+
211
+ if selected is None or selected == "back":
212
+ break
213
+
214
+ last_selected_value = selected
215
+
216
+ if selected.startswith("toggle:"):
217
+ cat = selected.split(":", 1)[1]
218
+ if cat in expanded_categories:
219
+ expanded_categories.remove(cat)
220
+ else:
221
+ expanded_categories.add(cat)
222
+ continue
223
+
224
+ if selected.startswith("tmpl:"):
225
+ tmpl_name = selected.split(":", 1)[1]
226
+ _show_template_info(tmpl_name)
227
+