sscli 0.1.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.
sscli-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: sscli
3
+ Version: 0.1.0
4
+ Summary: Seed & Source CLI
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer[all]
8
+ Requires-Dist: rich
9
+ Requires-Dist: questionary
10
+ Requires-Dist: requests
11
+ Requires-Dist: pathlib
12
+ Requires-Dist: python-dotenv
13
+
14
+ # Seed & Source CLI
15
+ Unified interface for Stack Foundry templates.
sscli-0.1.0/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # Seed & Source CLI
2
+ Unified interface for Stack Foundry templates.
File without changes
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Alpha Package Expiration Check (Refactored)
4
+ """
5
+
6
+ import sys
7
+ from .alpha import AlphaInfo, ExpirationStatus, AlphaExpirationManager
8
+
9
+
10
+ def get_expiration_manager() -> AlphaExpirationManager:
11
+ """Factory function to get the global expiration manager."""
12
+ return AlphaExpirationManager()
13
+
14
+
15
+ def main():
16
+ """Debug/test CLI for expiration checks."""
17
+ import argparse
18
+
19
+ parser = argparse.ArgumentParser(description="Alpha Package Expiration Check")
20
+ parser.add_argument(
21
+ "--status", action="store_true", help="Show alpha status (if available)"
22
+ )
23
+ parser.add_argument(
24
+ "--check", type=str, help="Check if action is permitted (e.g., 'generate')"
25
+ )
26
+
27
+ args = parser.parse_args()
28
+ manager = AlphaExpirationManager()
29
+
30
+ if args.status:
31
+ manager.show_status()
32
+ elif args.check:
33
+ allowed = manager.check_before_action(args.check)
34
+ sys.exit(0 if allowed else 1)
35
+ else:
36
+ if manager.is_alpha():
37
+ manager.show_status()
38
+ else:
39
+ from rich.console import Console
40
+
41
+ Console().print("[dim]Not running in alpha mode[/dim]")
42
+
43
+
44
+ if __name__ == "__main__":
45
+ main()
46
+
@@ -0,0 +1,190 @@
1
+ from foundry.constants import console
2
+ from foundry.actions.documentation import run_view_docs
3
+ from foundry.actions.explore import run_explore
4
+ from foundry.actions.generator import run_generator
5
+ from foundry.actions.validation import run_smoke_tests
6
+ from foundry.interactive_utils import manage_config_menu, pause
7
+ from foundry.utils import is_internal_dev
8
+
9
+ import questionary
10
+
11
+ from typing import cast, List
12
+ from foundry.animations.base import Animation
13
+
14
+ # Animation Imports - moved inside function to avoid unbound variables
15
+ # try:
16
+ # from foundry.animations import (AnimationSequence,
17
+ # InteractiveAnimationEngine)
18
+ # from foundry.animations.scenes import (LogoDisperseAnimation,
19
+ # LogoRevealAnimation,
20
+ # LogoStaticAnimation,
21
+ # TranquilityAnimation)
22
+
23
+ # HAS_ANIMATIONS = True
24
+ # except Exception:
25
+ # HAS_ANIMATIONS = False
26
+
27
+
28
+ def run_animated_experience():
29
+ """Plays the full robust animation sequence and handles user choice loop."""
30
+ # Import animations here to avoid unbound variables
31
+ try:
32
+ from foundry.animations import (AnimationSequence,
33
+ InteractiveAnimationEngine)
34
+ from foundry.animations.scenes import (LogoDisperseAnimation,
35
+ LogoRevealAnimation,
36
+ LogoStaticAnimation,
37
+ TranquilityAnimation,
38
+ ForgeAnimation)
39
+ except Exception:
40
+ console.print("[bold red]Animation system not fully loaded.[/]")
41
+ return
42
+
43
+ # Internal Mode selection for Developers
44
+ if is_internal_dev():
45
+ mode = questionary.select(
46
+ "Select Your Experience:",
47
+ choices=[
48
+ "Seed & Source (Client Side)",
49
+ "Foundry Ops (Internal Side)",
50
+ "Exit",
51
+ ],
52
+ style=questionary.Style([("highlighted", "fg:#00ffff bold")]),
53
+ ).ask()
54
+
55
+ if mode == "Exit" or mode is None:
56
+ console.print("[yellow]Goodbye![/yellow]")
57
+ return
58
+
59
+ if mode == "Foundry Ops (Internal Side)":
60
+ run_internal_animated_experience()
61
+ return
62
+
63
+ engine = InteractiveAnimationEngine(fps=60, console=console)
64
+
65
+ # 1. Play Intro Sequence once
66
+ try:
67
+ intro = AnimationSequence(
68
+ cast(List[Animation], [
69
+ LogoRevealAnimation(duration=1.2),
70
+ LogoStaticAnimation(duration=1.0),
71
+ LogoDisperseAnimation(duration=2.0),
72
+ ])
73
+ )
74
+ engine.play(intro)
75
+ except KeyboardInterrupt:
76
+ console.print("[yellow]Goodbye![/yellow]")
77
+ return
78
+ except Exception as e:
79
+ console.print(f"[red]Error during intro: {e}[/]")
80
+
81
+ # 2. Main Menu Loop
82
+ while True:
83
+ try:
84
+ # Play Tranquility with menu until a choice is made
85
+ choice = engine.play(TranquilityAnimation())
86
+
87
+ if not choice or choice == "Exit":
88
+ console.print("[yellow]Goodbye![/yellow]")
89
+ break
90
+
91
+ # Dispatch the choice
92
+ if choice == "Explore Templates":
93
+ run_explore()
94
+ pause()
95
+ elif choice == "Generate Project":
96
+ run_generator(interactive=True)
97
+ pause()
98
+ elif choice == "Manage Config":
99
+ manage_config_menu()
100
+ elif choice == "System Validation":
101
+ run_smoke_tests()
102
+ pause()
103
+ elif choice == "View Docs":
104
+ run_view_docs(interactive=True)
105
+ pause()
106
+
107
+ except KeyboardInterrupt:
108
+ console.print("[yellow]Goodbye![/yellow]")
109
+ break
110
+ except Exception as e:
111
+ console.print(f"[red]Error during menu: {e}[/]")
112
+ break
113
+
114
+
115
+ def run_internal_animated_experience():
116
+ """Plays the internal development animation sequence with industrial theme."""
117
+ # Import animations here to avoid unbound variables
118
+ try:
119
+ from foundry.animations import (AnimationSequence,
120
+ InteractiveAnimationEngine)
121
+ from foundry.animations.scenes import (LogoDisperseAnimation,
122
+ LogoRevealAnimation,
123
+ LogoStaticAnimation,
124
+ ForgeAnimation)
125
+ except Exception:
126
+ console.print("[bold red]Animation system not fully loaded.[/]")
127
+ return
128
+
129
+ engine = InteractiveAnimationEngine(fps=60, console=console)
130
+
131
+ # 1. Play Intro Sequence once (industrial theme)
132
+ try:
133
+ intro = AnimationSequence(
134
+ [
135
+ LogoRevealAnimation(duration=1.2),
136
+ LogoStaticAnimation(duration=1.0),
137
+ LogoDisperseAnimation(duration=2.0),
138
+ ]
139
+ )
140
+ engine.play(intro)
141
+ except KeyboardInterrupt:
142
+ console.print("[yellow]Goodbye![/yellow]")
143
+ return
144
+ except Exception as e:
145
+ console.print(f"[red]Error during intro: {e}[/]")
146
+
147
+ # 2. Main Menu Loop with Forge theme
148
+ while True:
149
+ try:
150
+ # Play Forge animation with industrial menu
151
+ choice = engine.play(ForgeAnimation())
152
+
153
+ if not choice or choice == "Back to Main":
154
+ console.print("[yellow]Goodbye![/yellow]")
155
+ break
156
+
157
+ # Dispatch the choice for internal ops
158
+ try:
159
+ from foundry.ops.manager import InternalManager
160
+ from foundry.ops.runner import run_verification_script
161
+ manager = InternalManager()
162
+
163
+ if choice == "Verify Builds":
164
+ manager.verify_builds()
165
+ pause()
166
+ elif choice == "Hygiene Check":
167
+ run_verification_script(manager.root_dir, "verify_hygiene", "Code Hygiene (File Lengths)")
168
+ pause()
169
+ elif choice == "Full Suite":
170
+ manager.verify_full_suite()
171
+ pause()
172
+ elif choice == "Clean Reports":
173
+ manager.clean_reports()
174
+ pause()
175
+ elif choice == "View Docs":
176
+ run_view_docs(interactive=True)
177
+ pause()
178
+ elif choice == "Back to Main":
179
+ break
180
+
181
+ except ImportError:
182
+ console.print("[red]Ops manager not available in this build.[/]")
183
+ break
184
+
185
+ except KeyboardInterrupt:
186
+ console.print("[yellow]Goodbye![/yellow]")
187
+ break
188
+ except Exception as e:
189
+ console.print(f"[red]Error during menu: {e}[/]")
190
+ break
@@ -0,0 +1,120 @@
1
+ import os
2
+ import json
3
+ import time
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Optional
6
+
7
+ import requests
8
+ from foundry.constants import console, API_BASE_URL
9
+
10
+ # Configuration
11
+ # The base URL is managed in foundry/constants.py (configurable via environment variables)
12
+ LICENSE_SERVER = API_BASE_URL
13
+ CREDENTIALS_FILE = Path.home() / ".config" / "sscli" / "credentials.json"
14
+
15
+
16
+ def get_access_token() -> Optional[str]:
17
+ """Retrieve the stored access token if it exists."""
18
+ if not CREDENTIALS_FILE.exists():
19
+ return None
20
+ try:
21
+ with open(CREDENTIALS_FILE, "r") as f:
22
+ data = json.load(f)
23
+ return data.get("access_token")
24
+ except Exception:
25
+ return None
26
+
27
+
28
+ def save_credentials(data: Dict[str, Any]) -> None:
29
+ """Save user credentials to the home directory."""
30
+ CREDENTIALS_FILE.parent.mkdir(parents=True, exist_ok=True)
31
+ with open(CREDENTIALS_FILE, "w") as f:
32
+ json.dump(data, f, indent=2)
33
+
34
+
35
+ def login_flow() -> None:
36
+ """Execute the GitHub Device Flow via the License Server."""
37
+ console.print("Initiating login with GitHub...")
38
+
39
+ try:
40
+ response = requests.post(f"{LICENSE_SERVER}/auth/device/code")
41
+ response.raise_for_status()
42
+ except Exception as e:
43
+ console.print(f"Error connecting to License Server at {LICENSE_SERVER}: {e}")
44
+ return
45
+
46
+ data = response.json()
47
+ device_code = data["device_code"]
48
+ user_code = data["user_code"]
49
+ verification_uri = data["verification_uri"]
50
+ interval = data.get("interval", 5)
51
+
52
+ console.print(f"\n1. Go to: {verification_uri}")
53
+ console.print(f"2. Enter code: {user_code}\n")
54
+ console.print("Waiting for authorization...")
55
+
56
+ while True:
57
+ try:
58
+ token_resp = requests.get(
59
+ f"{LICENSE_SERVER}/auth/device/token",
60
+ params={"device_code": device_code},
61
+ )
62
+
63
+ if token_resp.status_code == 200:
64
+ credentials = token_resp.json()
65
+ save_credentials(credentials)
66
+ console.print(
67
+ f"\nSuccessfully logged in as {credentials.get('email', 'User')}!"
68
+ )
69
+ console.print(
70
+ f"License Tier: {credentials.get('tier', 'free').upper()}"
71
+ )
72
+ break
73
+
74
+ if token_resp.status_code == 428: # Pending
75
+ time.sleep(interval)
76
+ continue
77
+
78
+ # If we get here, something went wrong
79
+ console.print(
80
+ f"Login failed: {token_resp.json().get('detail', 'Unknown error')}"
81
+ )
82
+ break
83
+
84
+ except Exception as e:
85
+ console.print(f"Polling error: {e}")
86
+ break
87
+
88
+
89
+ def get_current_license_info() -> Dict[str, Any]:
90
+ """Read the current license/tier info from local storage.
91
+
92
+ Returns a dict with at least `tier` and `authorized` keys when no
93
+ credentials are present or when reading fails.
94
+ """
95
+ if not CREDENTIALS_FILE.exists():
96
+ return {"tier": "free", "authorized": False}
97
+ try:
98
+ with open(CREDENTIALS_FILE, "r") as f:
99
+ data = json.load(f)
100
+ if isinstance(data, dict):
101
+ return data
102
+ # If stored data is malformed, return a sensible default
103
+ return {"tier": "free", "authorized": False}
104
+ except Exception:
105
+ return {"tier": "free", "authorized": False}
106
+
107
+
108
+ def logout() -> None:
109
+ """Clear local stored credentials (logout).
110
+
111
+ Removes the `credentials.json` file if present and reports status to the console.
112
+ """
113
+ try:
114
+ if CREDENTIALS_FILE.exists():
115
+ CREDENTIALS_FILE.unlink()
116
+ console.print("[green]Logged out:[/] removed local credentials.")
117
+ else:
118
+ console.print("[yellow]No credentials found to remove.[/yellow]")
119
+ except Exception as e:
120
+ console.print(f"[red]Error clearing credentials:[/] {e}")
@@ -0,0 +1,117 @@
1
+ from typing import Optional
2
+ from foundry.constants import console
3
+ import typer
4
+ from foundry.actions.documentation import run_view_docs
5
+ from foundry.actions.explore import run_explore
6
+ from foundry.actions.generator import run_generator
7
+ from foundry.actions.health import run_health_check
8
+ from foundry.actions.setup import run_setup
9
+ from foundry.actions.validation import run_smoke_tests
10
+ from foundry.actions.verify import run_verification
11
+ from foundry.auth import get_current_license_info
12
+ from foundry.ops.cli import ops_app
13
+ from foundry.telemetry import log_event
14
+ from foundry.commands.auth import auth_app, run_whoami
15
+ from foundry.commands.interactive import interactive_app
16
+
17
+ app = typer.Typer(help="Stack Foundry CLI Tool")
18
+
19
+ # Add sub-apps from separate modules
20
+ app.add_typer(auth_app, name="auth")
21
+ app.add_typer(interactive_app, name="interactive")
22
+ app.add_typer(ops_app, name="ops", help="Internal operations and maintenance.")
23
+
24
+
25
+ @app.command("whoami")
26
+ def whoami():
27
+ run_whoami()
28
+
29
+
30
+
31
+ @app.command("explore")
32
+ def explore():
33
+ """List available templates."""
34
+ log_event("COMMAND", "explore")
35
+ run_explore()
36
+
37
+
38
+ @app.command("new")
39
+ def new(
40
+ template: str = typer.Option(..., help="Name of the template to use"),
41
+ name: str = typer.Option(..., help="Name of the application"),
42
+ target: Optional[str] = typer.Option(
43
+ None, help="Target directory (defaults to name)"
44
+ ),
45
+ no_lint: bool = typer.Option(False, "--no-lint", help="Disable default linters"),
46
+ secrets: str = typer.Option(
47
+ "Dotenv (Standard .env files)", help="Secrets management strategy"
48
+ ),
49
+ ):
50
+ """Generate a new project from a template."""
51
+ log_event("COMMAND", f"new --template {template} --name {name}")
52
+ run_generator(
53
+ template_name=template,
54
+ app_name=name,
55
+ target_dir_name=target,
56
+ use_linters=not no_lint,
57
+ interactive=False,
58
+ secrets_strategy=secrets,
59
+ )
60
+
61
+
62
+ @app.command("setup")
63
+ def setup(
64
+ verbose: bool = typer.Option(
65
+ False, "--verbose", "-v", help="Enable detailed logging"
66
+ ),
67
+ force: bool = typer.Option(
68
+ False, "--force", help="Force setup even if already configured"
69
+ ),
70
+ skip_deps: bool = typer.Option(
71
+ False, "--skip-deps", help="Skip dependency installation"
72
+ ),
73
+ env: str = typer.Option("dev", "--env", help="Environment to setup (default: dev)"),
74
+ strategy: Optional[str] = typer.Option(
75
+ None, "--strategy", help="Setup strategy (docker, etc)"
76
+ ),
77
+ ):
78
+ """Run setup for templates."""
79
+ log_event("COMMAND", f"setup --env {env}")
80
+ run_setup(
81
+ verbose=verbose, force=force, skip_deps=skip_deps, env=env, strategy=strategy
82
+ )
83
+
84
+
85
+ @app.command("validate")
86
+ def validate():
87
+ """Run smoke tests."""
88
+ log_event("COMMAND", "validate")
89
+ run_smoke_tests()
90
+
91
+
92
+ @app.command("health")
93
+ def health():
94
+ """Check configuration and health of templates."""
95
+ log_event("COMMAND", "health")
96
+ run_health_check()
97
+
98
+
99
+ @app.command("verify")
100
+ def verify(
101
+ skip_docker: bool = typer.Option(
102
+ False, "--skip-docker", help="Skip Docker builds (faster, less thorough)"
103
+ ),
104
+ no_reports: bool = typer.Option(
105
+ False, "--no-reports", help="Skip generating CSV/MD reports"
106
+ ),
107
+ ):
108
+ """Verify template integrity and buildability."""
109
+ log_event("COMMAND", "verify")
110
+ run_verification(include_docker=not skip_docker, output_reports=not no_reports)
111
+
112
+
113
+ @app.command("docs")
114
+ def docs():
115
+ """List available documentation."""
116
+ log_event("COMMAND", "docs")
117
+ run_view_docs(interactive=False)
@@ -0,0 +1,128 @@
1
+ from pathlib import Path
2
+ import os
3
+
4
+ from rich.console import Console
5
+
6
+ TEMPLATES_DIR = Path(__file__).resolve().parent.parent.parent
7
+
8
+ # Configurable base URLs - can be overridden via environment variables
9
+ # For Render Free Tier, you can set LICENSE_SERVER_URL directly
10
+ SEED_SOURCE_DOMAIN = os.getenv("SEED_SOURCE_DOMAIN", "seedandsource.dev")
11
+ # DEFAULT_API_URL updated for current Alpha deployment on Render
12
+ DEFAULT_API_URL = "https://license-server-53oj.onrender.com/api/v1"
13
+ API_BASE_URL = os.getenv("LICENSE_SERVER_URL", DEFAULT_API_URL)
14
+ FORMS_BASE_URL = os.getenv("FORMS_BASE_URL", f"https://forms.{SEED_SOURCE_DOMAIN}")
15
+ PRICING_URL = os.getenv("PRICING_URL", f"https://{SEED_SOURCE_DOMAIN}/pricing")
16
+ SALES_EMAIL = f"sales@{SEED_SOURCE_DOMAIN}"
17
+ ALPHA_SUPPORT_EMAIL = f"alpha-support@{SEED_SOURCE_DOMAIN}"
18
+ ALPHA_FEEDBACK_FORM = f"{FORMS_BASE_URL}/alpha"
19
+
20
+ # Metadata-based template registry for PyPI distribution
21
+ # This allows the CLI to know about templates without them being on disk
22
+ TEMPLATE_REGISTRY = {
23
+ "python-saas": {
24
+ "name": "Python SaaS Boilerplate",
25
+ "description": "Hexagonal Architecture (FastAPI + SQLAlchemy)",
26
+ "tier": "free",
27
+ "repo": "https://github.com/seed-source/python-saas.git",
28
+ },
29
+ "rails-api": {
30
+ "name": "Rails API Suite",
31
+ "description": "JSON API with Devise, RSpec, and Docker",
32
+ "tier": "free",
33
+ "repo": "https://github.com/seed-source/rails-api.git",
34
+ },
35
+ "react-client": {
36
+ "name": "React Vite Client",
37
+ "description": "Modern frontend with Tailwind and React Query",
38
+ "tier": "free",
39
+ "repo": "https://github.com/seed-source/react-client.git",
40
+ },
41
+ "rails-ui-kit": {
42
+ "name": "Rails UI Kit",
43
+ "description": "Full-stack Rails with ViewComponent and Tailwind",
44
+ "tier": "alpha",
45
+ "repo": "https://github.com/seed-source/rails-ui-kit-private.git",
46
+ },
47
+ "data-pipeline": {
48
+ "name": "Data Pipeline (dbt)",
49
+ "description": "Modern data stack with dbt and Python",
50
+ "tier": "alpha",
51
+ "repo": "https://github.com/seed-source/data-pipeline-private.git",
52
+ },
53
+ "mobile-android": {
54
+ "name": "Mobile Android",
55
+ "description": "Kotlin-based Android application",
56
+ "tier": "alpha",
57
+ "repo": "https://github.com/seed-source/mobile-android-private.git",
58
+ },
59
+ "mobile-ios": {
60
+ "name": "Mobile iOS",
61
+ "description": "SwiftUI-based iOS application",
62
+ "tier": "alpha",
63
+ "repo": "https://github.com/seed-source/mobile-ios-private.git",
64
+ },
65
+ "terraform-infra": {
66
+ "name": "Terraform Infrastructure",
67
+ "description": "Multi-cloud IaC modules",
68
+ "tier": "alpha",
69
+ "repo": "https://github.com/seed-source/terraform-infra-private.git",
70
+ },
71
+ "wiring": {
72
+ "name": "Docker Wiring",
73
+ "description": "Orchestration for multi-service stacks",
74
+ "tier": "free",
75
+ "repo": "https://github.com/seed-source/wiring.git",
76
+ },
77
+ }
78
+
79
+ TIER_HIERARCHY = {"free": 0, "alpha": 1, "pro": 2, "enterprise": 3}
80
+
81
+ console = Console()
82
+
83
+ FOUNDRY_BANNER = """
84
+ [bold cyan]
85
+ ╔════════════════════════════════════════════════════════════════════════╗
86
+ ║ ║
87
+ ║ ███████╗████████╗ █████╗ ██████╗██╗ ██╗ ║
88
+ ║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝ ║
89
+ ║ ███████╗ ██║ ███████║██║ █████╔╝ ║
90
+ ║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗ ║
91
+ ║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗ ║
92
+ ║ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ║
93
+ ║ ║
94
+ ║ ███████╗ ██████╗ ██╗ ██╗███╗ ██╗██████╗ ██████╗ ██╗ ██╗ ║
95
+ ║ ██╔════╝██╔═══██╗██║ ██║████╗ ██║██╔══██╗██╔══██╗╚██╗ ██╔╝ ║
96
+ ║ █████╗ ██║ ██║██║ ██║██╔██╗ ██║██║ ██║██████╔╝ ╚████╔╝ ║
97
+ ║ ██╔══╝ ██║ ██║██║ ██║██║╚██╗██║██║ ██║██╔══██╗ ╚██╔╝ ║
98
+ ║ ██║ ╚██████╔╝╚██████╔╝██║ ╚████║██████╔╝██║ ██║ ██║ ║
99
+ ║ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ║
100
+ ║ ║
101
+ ╟────────────────────────────────────────────────────────────────────────╢
102
+ ║ ║
103
+ ║ ⬢ Template Management & Deployment System ║
104
+ ║ ║
105
+ ║ ├─ 📦 9 Production-Ready Templates ║
106
+ ║ ├─ 🏗️ Hexagonal Architecture Patterns ║
107
+ ║ ├─ 🐳 Docker-First Deployment ║
108
+ ║ ├─ 🔧 Automated Configuration & Setup ║
109
+ ║ └─ ✅ Built-in Validation & Smoke Tests ║
110
+ ║ ║
111
+ ╟────────────────────────────────────────────────────────────────────────╢
112
+ ║ ║
113
+ ║ STATUS: [green]●[/green] OPERATIONAL │ PATTERN: PORTS & ADAPTERS │ YEAR: 2026 ║
114
+ ║ ║
115
+ ╚════════════════════════════════════════════════════════════════════════╝
116
+ [/bold cyan]
117
+ [dim] The Foundry Overseer - Stack Management CLI[/dim]
118
+ """
119
+
120
+ # Menu configuration
121
+ ANIMATED_MENU_ITEMS = [
122
+ "Explore Templates",
123
+ "Generate Project",
124
+ "Manage Config",
125
+ "System Validation",
126
+ "View Docs",
127
+ "Exit",
128
+ ]
@@ -0,0 +1,151 @@
1
+ import questionary
2
+ from foundry.actions.documentation import run_view_docs
3
+ from foundry.actions.explore import run_explore
4
+ from foundry.actions.generator import run_generator
5
+ from foundry.actions.validation import run_smoke_tests
6
+ from foundry.alpha_expiration import get_expiration_manager
7
+ from foundry.alpha.view import show_warning_message
8
+ from foundry.auth import get_current_license_info, login_flow
9
+ from foundry.constants import FOUNDRY_BANNER, console
10
+ from foundry.utils import is_internal_dev
11
+ from foundry.interactive_utils import manage_config_menu, pause, internal_ops_menu
12
+
13
+
14
+ # Animation Imports - Standard Menu only needs to know if they exist for banner display
15
+ try:
16
+ from foundry.animations import InteractiveAnimationEngine
17
+ HAS_ANIMATIONS = True
18
+ except Exception:
19
+ HAS_ANIMATIONS = False
20
+
21
+
22
+ def run_animated_experience():
23
+ """Proxy to the actual animated experience implementation."""
24
+ from foundry.animated_experience import run_animated_experience as _run
25
+ _run()
26
+
27
+
28
+ def interactive_menu():
29
+ if HAS_ANIMATIONS:
30
+ try:
31
+ # No intro sequence for standard menu - jumping straight to banner
32
+ console.print(FOUNDRY_BANNER)
33
+ except Exception:
34
+ console.print(FOUNDRY_BANNER)
35
+ else:
36
+ console.print(FOUNDRY_BANNER)
37
+
38
+ # Check authentication status
39
+ auth_info = get_current_license_info()
40
+ if auth_info.get("tier") == "free":
41
+ console.print("\n[bold yellow]🔐 Authentication Required[/bold yellow]")
42
+ console.print("You need to authenticate to access PRO and ALPHA features.\n")
43
+
44
+ auth_choice = questionary.select(
45
+ "Would you like to authenticate now?",
46
+ choices=[
47
+ "Login with GitHub (Recommended)",
48
+ "Continue as Free User",
49
+ "Exit",
50
+ ],
51
+ ).ask()
52
+
53
+ if auth_choice == "Exit" or auth_choice is None:
54
+ console.print("[yellow]Goodbye![/yellow]")
55
+ return
56
+ elif auth_choice == "Login with GitHub (Recommended)":
57
+ try:
58
+ login_flow()
59
+ # Refresh auth info after login
60
+ auth_info = get_current_license_info()
61
+ console.print("\n[green]✓ Authentication successful![/green]\n")
62
+ except Exception as e:
63
+ console.print(f"\n[red]✗ Authentication failed:[/red] {e}\n")
64
+ console.print("Continuing as free user...\n")
65
+
66
+ # Internal Mode selection for Developers
67
+ if is_internal_dev():
68
+ mode = questionary.select(
69
+ "Select Your Experience:",
70
+ choices=[
71
+ "Seed & Source (Client Side)",
72
+ "Foundry Ops (Internal Side)",
73
+ "Exit",
74
+ ],
75
+ style=questionary.Style([("highlighted", "fg:#00ffff bold")]),
76
+ ).ask()
77
+
78
+ if mode == "Exit" or mode is None:
79
+ console.print("[yellow]Goodbye![/yellow]")
80
+ return
81
+
82
+ if mode == "Foundry Ops (Internal Side)":
83
+ internal_ops_menu()
84
+ return
85
+
86
+ # Check for alpha package expiration
87
+ alpha_mgr = get_expiration_manager()
88
+ if alpha_mgr.is_alpha():
89
+ alpha_mgr.show_status()
90
+ if alpha_mgr.is_near_expiration():
91
+ show_warning_message(alpha_mgr.alpha_info)
92
+
93
+ while True:
94
+ # Build menu choices based on auth status
95
+ base_choices = [
96
+ "Explore Templates",
97
+ "Generate New Project",
98
+ "Manage Stack Configuration",
99
+ "Run System Validation (Smoke Tests)",
100
+ "View Documentation",
101
+ ]
102
+
103
+ # Add auth option if not authenticated
104
+ if auth_info.get("tier") == "free":
105
+ base_choices.insert(0, "🔐 Login to Unlock PRO Features")
106
+ else:
107
+ base_choices.insert(0, f"👤 Account ({auth_info.get('tier', 'free').upper()})")
108
+
109
+ base_choices.append("Exit")
110
+
111
+ choice = questionary.select(
112
+ "What would you like to do?",
113
+ choices=base_choices,
114
+ ).ask()
115
+
116
+ if choice == "🔐 Login to Unlock PRO Features":
117
+ try:
118
+ login_flow()
119
+ # Refresh auth info after login
120
+ auth_info = get_current_license_info()
121
+ console.print("\n[green]✓ Authentication successful![/green]\n")
122
+ except Exception as e:
123
+ console.print(f"\n[red]✗ Authentication failed:[/red] {e}\n")
124
+ elif choice == f"👤 Account ({auth_info.get('tier', 'free').upper()})":
125
+ # Show account info
126
+ console.print(f"\n[bold]Account Information:[/bold]")
127
+ console.print(f"Email: {auth_info.get('email', 'Not available')}")
128
+ console.print(f"Tier: {auth_info.get('tier', 'free').upper()}")
129
+ console.print(f"Expires: {auth_info.get('expires', 'N/A')}")
130
+ console.print(f"User ID: {auth_info.get('user_id', 'N/A')}\n")
131
+ pause()
132
+ elif choice == "Explore Templates":
133
+ run_explore()
134
+ pause()
135
+ elif choice == "Generate New Project":
136
+ # Check expiration before generation
137
+ if not alpha_mgr.check_before_action("Generate New Project"):
138
+ continue
139
+ run_generator(interactive=True)
140
+ pause()
141
+ elif choice == "Manage Stack Configuration":
142
+ manage_config_menu()
143
+ elif choice == "Run System Validation (Smoke Tests)":
144
+ run_smoke_tests()
145
+ pause()
146
+ elif choice == "View Documentation":
147
+ run_view_docs(interactive=True)
148
+ pause()
149
+ else:
150
+ console.print("[yellow]Goodbye![/yellow]")
151
+ break
@@ -0,0 +1,81 @@
1
+ import questionary
2
+ from foundry.constants import console
3
+ from foundry.actions.health import run_health_check
4
+ from foundry.actions.setup import run_setup
5
+
6
+
7
+ def manage_config_menu():
8
+ while True:
9
+ action = questionary.select(
10
+ "Configuration Management:",
11
+ choices=[
12
+ "View Validation Status",
13
+ "Run Template Setup (Full Suite)",
14
+ "Back",
15
+ ],
16
+ ).ask()
17
+
18
+ if action == "Back":
19
+ break
20
+
21
+ if action == "View Validation Status":
22
+ run_health_check()
23
+ pause()
24
+
25
+ if action == "Run Template Setup (Full Suite)":
26
+ run_setup()
27
+ pause()
28
+
29
+
30
+ def pause():
31
+ input("\nPress Enter to return...")
32
+
33
+
34
+ def internal_ops_menu():
35
+ """Menu for internal development and ops tasks."""
36
+ try:
37
+ from foundry.ops.manager import InternalManager
38
+ except ImportError:
39
+ console.print("[red]Ops manager not available in this build.[/]")
40
+ return
41
+
42
+ # Show auth status for internal devs too
43
+ from foundry.auth import get_current_license_info
44
+ auth_info = get_current_license_info()
45
+ if auth_info.get("tier") != "free":
46
+ console.print(f"[green]✓ Authenticated as {auth_info.get('tier', 'user').upper()}[/green]")
47
+ else:
48
+ console.print("[yellow]⚠️ Not authenticated - limited access[/yellow]")
49
+
50
+ manager = InternalManager()
51
+
52
+ while True:
53
+ choice = questionary.select(
54
+ "Foundry Ops - Internal Development:",
55
+ choices=[
56
+ "Verify All Builds (Docker)",
57
+ "Run Hygiene Checks (Repo Health)",
58
+ "Full Verification Suite",
59
+ "Clean Verification Reports",
60
+ "Back to Main",
61
+ ],
62
+ style=questionary.Style([("highlighted", "fg:#ff00ff bold")]),
63
+ ).ask()
64
+
65
+ if not choice or choice == "Back to Main":
66
+ break
67
+
68
+ if choice == "Verify All Builds (Docker)":
69
+ manager.verify_builds()
70
+ pause()
71
+ elif choice == "Run Hygiene Checks (Repo Health)":
72
+ manager.run_verification_script(
73
+ "verify_hygiene", "Code Hygiene (File Lengths)"
74
+ )
75
+ pause()
76
+ elif choice == "Full Verification Suite":
77
+ manager.verify_full_suite()
78
+ pause()
79
+ elif choice == "Clean Verification Reports":
80
+ manager.clean_reports()
81
+ pause()
@@ -0,0 +1,9 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from foundry.cli import app
4
+
5
+ # Load environment variables from .env file if it exists
6
+ load_dotenv()
7
+
8
+ if __name__ == "__main__":
9
+ app()
@@ -0,0 +1,23 @@
1
+ import datetime
2
+ from pathlib import Path
3
+ from foundry.constants import ALPHA_FEEDBACK_FORM
4
+
5
+ LOG_FILE = Path(".session.log")
6
+
7
+
8
+ def log_event(event_type: str, details: str = ""):
9
+ """Log an event to the local session log."""
10
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
11
+ log_entry = f"[{timestamp}] {event_type.upper()}: {details}\n"
12
+
13
+ try:
14
+ with open(LOG_FILE, "a", encoding="utf-8") as f:
15
+ f.write(log_entry)
16
+ except Exception:
17
+ # Fallback to silent failure to not break the CLI experience
18
+ pass
19
+
20
+
21
+ def get_feedback_link() -> str:
22
+ """Returns the feedback form link."""
23
+ return ALPHA_FEEDBACK_FORM
@@ -0,0 +1,66 @@
1
+ from pathlib import Path
2
+ import os
3
+ from typing import Any, Dict, List
4
+
5
+ from foundry.constants import TEMPLATE_REGISTRY, TEMPLATES_DIR
6
+
7
+
8
+ def is_valid_template(path: Path) -> bool:
9
+ """Check if a local directory is a valid template."""
10
+ if not path.is_dir() or path.name.startswith("."):
11
+ return False
12
+
13
+ # Check if it's in our registry
14
+ if path.name in TEMPLATE_REGISTRY:
15
+ return True
16
+
17
+ # Check for legacy project markers (fallback)
18
+ markers = [
19
+ "Dockerfile",
20
+ "package.json",
21
+ "main.tf",
22
+ "pyproject.toml",
23
+ "Gemfile",
24
+ "build.gradle.kts",
25
+ "StackApp",
26
+ ]
27
+
28
+ return any((path / marker).exists() for marker in markers)
29
+
30
+
31
+ def is_internal_dev() -> bool:
32
+ """
33
+ Check if the CLI is running in the internal development mono-repo context.
34
+ Looks for markers like .github, ops/, and scripts/ at the root.
35
+ Can be explicitly disabled with FOUNDRY_INTERNAL_DEV=0
36
+ """
37
+ if os.getenv("FOUNDRY_INTERNAL_DEV") == "0":
38
+ return False
39
+
40
+ internal_markers = [".github", "ops", "scripts", "wiring"]
41
+ return all((TEMPLATES_DIR / marker).exists() for marker in internal_markers)
42
+
43
+
44
+ def get_templates() -> List[Path]:
45
+ """
46
+ Get list of templates.
47
+ In development mode, returns local directories.
48
+ In PyPI mode, returns virtual paths based on registry.
49
+ """
50
+ # 1. Try local scanning (Development/Monorepo mode)
51
+ if TEMPLATES_DIR.exists():
52
+ local_templates = [d for d in TEMPLATES_DIR.iterdir() if is_valid_template(d)]
53
+ if local_templates:
54
+ return sorted(local_templates, key=lambda x: x.name)
55
+
56
+ # 2. Fallback to Registry (PyPI mode)
57
+ # We return dummy Path objects where the name corresponds to the registry key
58
+ return [Path(name) for name in TEMPLATE_REGISTRY.keys()]
59
+
60
+
61
+ def get_template_info(template_name: str) -> Dict[str, Any]:
62
+ """Get metadata for a specific template."""
63
+ return TEMPLATE_REGISTRY.get(
64
+ template_name,
65
+ {"name": template_name, "description": "Custom Template", "tier": "free"},
66
+ )
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sscli"
7
+ version = "0.1.0"
8
+ description = "Seed & Source CLI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ dependencies = [
12
+ "typer[all]",
13
+ "rich",
14
+ "questionary",
15
+ "requests",
16
+ "pathlib",
17
+ "python-dotenv"
18
+ ]
19
+
20
+ [project.scripts]
21
+ stack-cli = "foundry.manage:app"
22
+
23
+ [tool.setuptools]
24
+ packages = ["foundry"]
25
+
26
+ [tool.setuptools.package-data]
27
+ "*" = ["*.md", "*.json"]
28
+
29
+ [tool.setuptools.exclude-package-data]
30
+ "*" = ["venv*", ".env*", "*.log", ".pytest_cache*", "tests*"]
sscli-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: sscli
3
+ Version: 0.1.0
4
+ Summary: Seed & Source CLI
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer[all]
8
+ Requires-Dist: rich
9
+ Requires-Dist: questionary
10
+ Requires-Dist: requests
11
+ Requires-Dist: pathlib
12
+ Requires-Dist: python-dotenv
13
+
14
+ # Seed & Source CLI
15
+ Unified interface for Stack Foundry templates.
@@ -0,0 +1,19 @@
1
+ README.md
2
+ pyproject.toml
3
+ foundry/__init__.py
4
+ foundry/alpha_expiration.py
5
+ foundry/animated_experience.py
6
+ foundry/auth.py
7
+ foundry/cli.py
8
+ foundry/constants.py
9
+ foundry/interactive.py
10
+ foundry/interactive_utils.py
11
+ foundry/manage.py
12
+ foundry/telemetry.py
13
+ foundry/utils.py
14
+ sscli.egg-info/PKG-INFO
15
+ sscli.egg-info/SOURCES.txt
16
+ sscli.egg-info/dependency_links.txt
17
+ sscli.egg-info/entry_points.txt
18
+ sscli.egg-info/requires.txt
19
+ sscli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ stack-cli = foundry.manage:app
@@ -0,0 +1,6 @@
1
+ typer[all]
2
+ rich
3
+ questionary
4
+ requests
5
+ pathlib
6
+ python-dotenv
@@ -0,0 +1 @@
1
+ foundry