CLI Commands API Reference¶
This page provides comprehensive documentation for all SFAI CLI commands, including arguments, options, and usage examples.
sfai app
- Application Management¶
init_cmd
¶
Initialize a new SFAI application in the current directory.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
force | bool | bool Whether to force initialization if app is already initialized | Option(False, '--force', help='Force initialize') |
template | str | str Template to use for scaffolding the app (eg: fastapi_hello or flask_hello) | Option('fastapi_hello', '--template', help='Template to use (eg: fastapi_hello or flask_hello)') |
Returns:
Type | Description |
---|---|
None | None |
Source code in sfai/cli/app/init.py
@app.callback(invoke_without_command=True, help="Initialize your app")
def init_cmd(
force: bool = typer.Option(False, "--force", help="Force initialize"),
template: str = typer.Option(
"fastapi_hello",
"--template",
help="Template to use (eg: fastapi_hello or flask_hello)",
),
) -> None:
"""
Initialize a new SFAI application in the current directory.
Args:
force: bool
Whether to force initialization if app is already initialized
template: str
Template to use for scaffolding the app (eg: fastapi_hello or flask_hello)
Returns:
None
"""
# Call the Python API function
result = init(template=template, force=force)
if not result.success:
console.print(
f"{WARNING_EMOJI} [{WARNING_COLOR}] App {result.app_name} "
f"already initialized use --force to reinitialize[/]"
)
return
app_name = result.app_name
console.print(f"{SUCCESS_EMOJI} App initialized!")
console.print(f"{APP_NAME_EMOJI} App name: {app_name}")
console.print(f"{TEMPLATE_EMOJI} Template: {template}")
console.print(
f"\n{ROCKET_EMOJI} Next: Run `[cyan]sfai app deploy[/]` to build "
f"and deploy your app.\n"
f" Run `[cyan]sfai platform init[/]` to initialize a cloud "
f"environment(heroku, aws).\n"
f"{LIGHT_BULB_EMOJI} Tip: Run `[cyan]sfai app open[/]` to access "
f"your app locally."
)
Example:
# Initialize with default template
sfai app init
# Initialize with specific template
sfai app init --template fastapi_hello
# Force reinitialize existing app
sfai app init --force --template flask_hello
deploy_cmd
¶
Deploy the current application to the specified environment.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
platform | Optional[str] | Optional[str] Platform to deploy to | Option(None, help='Platform to deploy to') |
environment | Optional[str] | str Environment to deploy to (defaults to "default") | Option(None, help='Environment to deploy to') |
path | str | str Path to the app folder | Option('.', help='Path to the app folder (default: current directory)') |
values_path | Optional[str] | Optional[str] Path to Helm values file | Option(None, help='Path to Helm values file') |
set_values | Optional[str] | Optional[str] Additional values to set for Helm | Option(None, help='Additional values to set for Helm') |
commit_message | Optional[str] | Optional[str] Commit message | Option(None, help='Commit message') |
branch | Optional[str] | Optional[str] Branch name | Option(None, help='Branch name') |
Returns: None
Source code in sfai/cli/app/deploy.py
@app.callback(
invoke_without_command=True,
help="Deploy an application to the configured environment.",
)
def deploy_cmd(
# common options
platform: Optional[str] = typer.Option(None, help="Platform to deploy to"),
environment: Optional[str] = typer.Option(None, help="Environment to deploy to"),
path: str = typer.Option(
".", help="Path to the app folder (default: current directory)"
),
# k8s options
values_path: Optional[str] = typer.Option(None, help="Path to Helm values file"),
set_values: Optional[str] = typer.Option(
None, help="Additional values to set for Helm"
),
# heroku options
commit_message: Optional[str] = typer.Option(None, help="Commit message"),
branch: Optional[str] = typer.Option(None, help="Branch name"),
) -> None:
"""
Deploy the current application to the specified environment.
Args:
platform: Optional[str]
Platform to deploy to
environment: str
Environment to deploy to (defaults to "default")
path: str
Path to the app folder
values_path: Optional[str]
Path to Helm values file
set_values: Optional[str]
Additional values to set for Helm
commit_message: Optional[str]
Commit message
branch: Optional[str]
Branch name
Returns:
None
"""
console.print(f"{ROCKET_EMOJI} Deploying application...")
result = deploy(
platform=platform,
environment=environment,
path=path,
values_path=values_path,
set_values=set_values,
commit_message=commit_message,
branch=branch,
)
if result.success:
console.print(f"{CELEBRATE_EMOJI} {result.message}")
else:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]{result.error}[/]")
Examples:
# Deploy to current platform
sfai app deploy
# Deploy to specific platform
sfai app deploy --platform heroku
# Deploy with custom Helm values
sfai app deploy --values-path ./values.yaml --set-values "image.tag=v1.2.3"
# Deploy to Heroku with commit message
sfai app deploy --platform heroku --commit-message "Feature: Add new API endpoint"
status_cmd
¶
Show the status of the current app from context.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
platform | Optional[str] | Optional[str] Platform to show status from | Option(None, help='Platform to show status from') |
environment | Optional[str] | str Environment to show status from (defaults to "default") | Option(None, help='Environment to show status from') |
Source code in sfai/cli/app/status.py
@app.callback(invoke_without_command=True, help="Show status of the current app")
def status_cmd(
platform: Optional[str] = typer.Option(None, help="Platform to show status from"),
environment: Optional[str] = typer.Option(
None, help="Environment to show status from"
),
) -> None:
"""
Show the status of the current app from context.
Args:
platform: Optional[str]
Platform to show status from
environment: str
Environment to show status from (defaults to "default")
"""
result = status(platform=platform, environment=environment)
if not result.success:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]{result.error}[/]")
return
Examples:
# Show status from current platform
sfai app status
# Show status from specific platform
sfai app status --platform heroku
logs_cmd
¶
Show logs for the current app.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
platform | Optional[str] | Optional[str] Platform to show logs from | Option(None, help='Platform to show logs from') |
environment | Optional[str] | str Environment to show logs from (defaults to "default") | Option(None, help='Environment to show logs from') |
Source code in sfai/cli/app/logs.py
@app.callback(invoke_without_command=True, help="Show logs for the current app")
def logs_cmd(
platform: Optional[str] = typer.Option(None, help="Platform to show logs from"),
environment: Optional[str] = typer.Option(
None, help="Environment to show logs from"
),
) -> None:
"""
Show logs for the current app.
Args:
platform: Optional[str]
Platform to show logs from
environment: str
Environment to show logs from (defaults to "default")
"""
result = logs(platform=platform, environment=environment)
if not result.success:
console.print(
f"{ERROR_EMOJI} [{ERROR_COLOR}] unable to fetch logs: {result.error}[/]"
)
return
Examples:
# Show logs from current platform
sfai app logs
# Show logs from specific platform
sfai app logs --platform eks
delete_cmd
¶
Delete the current app deployment
Parameters:
Name | Type | Description | Default |
---|---|---|---|
platform | Optional[str] | Optional[str] Platform to delete from | Option(None, help='Platform to delete from') |
environment | Optional[str] | str Environment to delete from (defaults to "default") | Option(None, help='Environment to delete from') |
Source code in sfai/cli/app/delete.py
@app.callback(invoke_without_command=True, help="Delete the current app")
def delete_cmd(
platform: Optional[str] = typer.Option(None, help="Platform to delete from"),
environment: Optional[str] = typer.Option(None, help="Environment to delete from"),
):
"""
Delete the current app deployment
Args:
platform: Optional[str]
Platform to delete from
environment: str
Environment to delete from (defaults to "default")
"""
result = delete(platform=platform, environment=environment)
if not result.success:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]{result.error}[/]")
raise typer.Exit(code=1)
name = result.app_name
console.print(f"{SUCCESS_EMOJI} Deleted app '{name}'...")
Examples:
# Delete from current platform
sfai app delete
# Delete from specific platform
sfai app delete --platform heroku
open_cmd
¶
Open the current app in your browser.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
platform | Optional[str] | Optional[str] Platform to open | Option(None, help='Platform to open') |
environment | Optional[str] | str Environment to open (defaults to "default") | Option(None, help='Environment to open') |
path | str | str The path to open default: /docs | Option('/docs', help='API path to open') |
port | int | int The port to open default: 8080 | Option(8080, help='Local port') |
tunnel | bool | bool Whether to use a tunnel default: False | Option(False, help='Use Cloudflare tunnel') |
url | str | str The URL to open | Option(None, help='URL to open') |
Returns: dict[str, Any]
Source code in sfai/cli/app/open.py
@app.callback(invoke_without_command=True, help="Open the current app in your browser")
def open_cmd(
platform: Optional[str] = typer.Option(None, help="Platform to open"),
environment: Optional[str] = typer.Option(None, help="Environment to open"),
path: str = typer.Option("/docs", help="API path to open"),
port: int = typer.Option(8080, help="Local port"),
tunnel: bool = typer.Option(False, help="Use Cloudflare tunnel"),
url: str = typer.Option(None, help="URL to open"),
):
"""
Open the current app in your browser.
Args:
platform: Optional[str]
Platform to open
environment: str
Environment to open (defaults to "default")
path: str
The path to open
default: /docs
port: int
The port to open
default: 8080
tunnel: bool
Whether to use a tunnel
default: False
url: str
The URL to open
Returns:
dict[str, Any]
"""
result = open(
platform=platform,
environment=environment,
path=path,
port=port,
tunnel=tunnel,
url=url,
)
if not result.success:
console.print(f"{ERROR_EMOJI} [red]{result.error}[/]")
raise typer.Exit(1)
if result.url:
console.print(
f"{PORT_EMOJI} Opening {result.platform} app: [bold blue]{result.url}[/]"
)
webbrowser.open(result.url)
return
# Port-forward confirmed
app_name = result.app_name
console.print(
f"{PORT_EMOJI} Port-forwarding svc/{app_name}-service → http://localhost:{port}"
)
time.sleep(3)
if result.pf_proc.poll() is not None:
console.print(f"{ERROR_EMOJI} Port-forwarding failed. Check service status.")
return
console.print(f"{SUCCESS_EMOJI} Port forwarding established.")
tunnel_proc = None
if tunnel:
try:
url, tunnel_proc = start_cloudflare_tunnel(port, path)
console.print(f"{LINK_EMOJI} Tunnel ready: [bold blue]{url}[/]")
webbrowser.open(url)
console.print(f"{TUNNEL_EMOJI} Press Ctrl+C to stop...")
tunnel_proc.wait()
finally:
if tunnel_proc and tunnel_proc.poll() is None:
tunnel_proc.terminate()
elif result["platform"] != "local" and result["tunnel"]:
console.print(f"{ERROR_EMOJI} Tunnel is only supported for local apps")
else:
url = f"http://localhost:{port}{path}"
console.print(f"{WEB_EMOJI} Opening [bold blue]{url}[/]")
webbrowser.open(url)
result["pf_proc"].wait()
if result["pf_proc"].poll() is None:
result["pf_proc"].terminate()
start_cloudflare_tunnel
¶
Start a Cloudflare tunnel for a service.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
port | int | int The port to forward | required |
path | str | str The path to open | required |
Returns: tuple[str, subprocess.Popen]
Source code in sfai/cli/app/open.py
def start_cloudflare_tunnel(port: int, path: str) -> tuple[str, subprocess.Popen]:
"""
Start a Cloudflare tunnel for a service.
Args:
port: int
The port to forward
path: str
The path to open
Returns:
tuple[str, subprocess.Popen]
"""
proc = subprocess.Popen(
["cloudflared", "tunnel", "--url", f"http://localhost:{port}"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
url_holder = {"url": None}
def parse():
for line in iter(proc.stdout.readline, ""):
match = re.search(r"https://[^\s]+\.trycloudflare\.com", line)
if match:
candidate = match.group(0).rstrip("/") + path
try:
if requests.get(candidate, timeout=5).status_code == 200:
url_holder["url"] = candidate
break
except requests.RequestException:
continue
threading.Thread(target=parse, daemon=True).start()
with Status(f"{UPDATE_EMOJI} Finding tunnel URL...", console=console):
for _ in range(MAX_RETRIES):
if url_holder["url"] or proc.poll() is not None:
break
time.sleep(RETRY_DELAY)
if not url_holder["url"]:
proc.terminate()
raise RuntimeError(f"{ERROR_EMOJI} Tunnel URL failed after retries.")
return url_holder["url"], proc
Examples:
# Open app with default settings (opens /docs on port 8080)
sfai app open
# Open specific path
sfai app open --path /api/v1/health
# Open on custom port
sfai app open --port 3000
# Open specific platform
sfai app open --platform heroku --path /api/status
Examples:
Examples:
# Publish with interactive mode
sfai app publish --interactive
# Publish with specific parameters
sfai app publish --name my-api --version 1.0.0 --description "My API" --profile production
# Publish with tags
sfai app publish --name my-api --tags api --tags production --tags v1
# Publish with OpenAPI spec
sfai app publish --name my-api --oas-file ./openapi.yaml --endpoint-uri https://api.example.com
sfai config
- Service Configuration¶
init_cmd
¶
Configure service connection settings.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
service | str | str Name of the service to configure credentials for | Option(None, help='service name, e.g. mulesoft, heroku, aws') |
profile_name | str | str Name of the profile to store credentials under | Option(None, help='profile name to store credentials under') |
interactive | bool | bool Run in interactive mode | Option(False, '-i', '--interactive', help='Run in interactive mode') |
org_id | str | str MuleSoft organization ID | Option(None, help='MuleSoft organization ID') |
environment_id | str | str MuleSoft environment ID | Option(None, help='MuleSoft environment ID') |
client_id | str | str MuleSoft client ID | Option(None, help='MuleSoft client ID') |
client_secret | str | str MuleSoft client secret | Option(None, help='MuleSoft client secret') |
Returns: None
Source code in sfai/cli/config/init.py
@app.callback(invoke_without_command=True)
def init_cmd(
service: str = typer.Option(None, help="service name, e.g. mulesoft, heroku, aws"),
profile_name: str = typer.Option(
None, help="profile name to store credentials under"
),
interactive: bool = typer.Option(
False, "-i", "--interactive", help="Run in interactive mode"
),
# credentials flags
org_id: str = typer.Option(None, help="MuleSoft organization ID"),
environment_id: str = typer.Option(None, help="MuleSoft environment ID"),
client_id: str = typer.Option(None, help="MuleSoft client ID"),
client_secret: str = typer.Option(None, help="MuleSoft client secret"),
) -> None:
"""
Configure service connection settings.
Args:
service: str
Name of the service to configure credentials for
profile_name: str
Name of the profile to store credentials under
interactive: bool
Run in interactive mode
org_id: str
MuleSoft organization ID
environment_id: str
MuleSoft environment ID
client_id: str
MuleSoft client ID
client_secret: str
MuleSoft client secret
Returns:
None
"""
# Prompt for service if missing
if not service:
service = Prompt.ask(
"Which service to configure (e.g., mulesoft)", default="mulesoft"
)
# Get schema fields and validate service
schema_fields = _get_schema_fields(service)
if not schema_fields:
console.print(
f"{ERROR_EMOJI} [{ERROR_COLOR}]Invalid or missing service: {service}[/]"
)
return
# Define profile name
profile_name = profile_name or "default"
if interactive:
profile_name = Prompt.ask(
f"Enter {service} profile name for storing credentials", default="default"
)
# all CLI values captured
all_cli_args = {
"org_id": org_id,
"environment_id": environment_id,
"client_id": client_id,
"client_secret": client_secret,
}
credentials = {}
# Collect credentials
if interactive:
for field_name, description in schema_fields.items():
val = Prompt.ask(
f"{SECURITY_EMOJI} {description}",
password=("secret" in field_name.lower()),
)
if not val:
console.print(
f"{ERROR_EMOJI} [{ERROR_COLOR}]Missing required field: "
f"{description}[/]"
)
return
credentials[field_name] = val
else:
for field_name in schema_fields:
val = all_cli_args.get(field_name)
if val is not None:
credentials[field_name] = val
if not credentials:
for field_name, description in schema_fields.items():
val = Prompt.ask(
f"{SECURITY_EMOJI} {description}",
password=("secret" in field_name.lower()),
)
if not val:
console.print(
f"{ERROR_EMOJI} [{ERROR_COLOR}]Missing required field: "
f"{description}[/]"
)
return
credentials[field_name] = val
# warn if irrelevant keys were passed
invalid_keys = [
k
for k in all_cli_args
if k not in schema_fields and all_cli_args[k] is not None
]
if invalid_keys:
console.print(
f"{WARNING_EMOJI} Ignored keys not part of {service}: "
f"{', '.join(invalid_keys)}"
)
# Use the API function
result = init(service=service, config=credentials, profile_name=profile_name)
if result.success:
console.print(
f"{SUCCESS_EMOJI} [{SUCCESS_COLOR}]{service.capitalize()}[/] "
f"credentials saved under profile [bold]{profile_name}[/]."
)
else:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]{result.error}[/]")
Examples:
# Interactive configuration
sfai config init --service mulesoft --interactive
# Direct configuration
sfai config init --service mulesoft --profile-name production \
--org-id abc123 --environment-id def456 --client-id ghi789 --client-secret secret123
# Use default profile
sfai config init --service mulesoft --org-id abc123 --client-id ghi789 --client-secret secret123
update_cmd
¶
Update specific values in an existing service profile
Parameters:
Name | Type | Description | Default |
---|---|---|---|
service | str | str Service name | Option(..., help='Service name') |
profile_name | Optional[str] | Optional[str] Profile to update | Option('default', help='Profile to update') |
interactive | bool | bool Run in interactive mode | Option(False, '--interactive', '-i') |
org_id | Optional[str] | Optional[str] MuleSoft organization ID | None |
environment_id | Optional[str] | Optional[str] MuleSoft environment ID | None |
client_id | Optional[str] | Optional[str] MuleSoft client ID | None |
client_secret | Optional[str] | Optional[str] MuleSoft client secret | None |
Source code in sfai/cli/config/update.py
@app.callback(invoke_without_command=True)
def update_cmd(
service: str = typer.Option(..., help="Service name"),
profile_name: Optional[str] = typer.Option("default", help="Profile to update"),
interactive: bool = typer.Option(False, "--interactive", "-i"),
# full list of possible fields
org_id: Optional[str] = None,
environment_id: Optional[str] = None,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
):
"""Update specific values in an existing service profile
Args:
service: str
Service name
profile_name: Optional[str]
Profile to update
interactive: bool
Run in interactive mode
org_id: Optional[str]
MuleSoft organization ID
environment_id: Optional[str]
MuleSoft environment ID
client_id: Optional[str]
MuleSoft client ID
client_secret: Optional[str]
MuleSoft client secret
"""
schema = _get_service_schema(service)
if not schema:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]Invalid service: {service}[/]")
return
# Check if profile exists BEFORE prompting for updates
exists, error = profile_exists(service, profile_name)
if not exists:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]{error}[/]")
return
schema_keys = set(schema.keys())
all_cli_args = {
"org_id": org_id,
"environment_id": environment_id,
"client_id": client_id,
"client_secret": client_secret,
}
updates = {}
if interactive or not any(all_cli_args.get(key) for key in schema_keys):
# prompt user for each field
for key, label in schema.items():
val = Prompt.ask(
f"{UPDATE_EMOJI} New value for {label} (leave blank to skip)"
)
if val:
updates[key] = val
else:
# use only explicitly provided cli args
for key in schema_keys:
val = all_cli_args.get(key)
if val:
updates[key] = val
# Don't update if no changes
if not updates:
console.print(f"{SUCCESS_EMOJI} No updates provided. Profile unchanged.")
return
# Call the API
result = update(service, updates, profile_name)
if result.success:
console.print(
f"{SUCCESS_EMOJI} Updated [{SUCCESS_COLOR}]{service}[/] profile "
f"[bold]{profile_name}[/] with new values."
)
else:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]{result.error}[/]")
Examples:
# Update specific fields
sfai config update --service mulesoft --profile-name production --client-secret new-secret
# Interactive update
sfai config update --service mulesoft --interactive
# Update multiple fields
sfai config update --service mulesoft --org-id new-org --environment-id new-env
list_cmd
¶
List service profiles
Parameters:
Name | Type | Description | Default |
---|---|---|---|
service | Optional[str] | Optional[str] Filter by specific service name | Option(None, help='Filter by specific service name') |
Source code in sfai/cli/config/list.py
@app.callback(invoke_without_command=True)
def list_cmd(
service: Optional[str] = typer.Option(None, help="Filter by specific service name"),
):
"""List service profiles
Args:
service: Optional[str]
Filter by specific service name
"""
result = list(service)
if not result.success:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]{result.error}[/]")
return
profiles = result.profiles
if not profiles:
console.print(f"[{WARNING_COLOR}]No service profiles found.[/]")
return
if service:
# Single service display
console.print(f"[{SUCCESS_COLOR}]{service.capitalize()} Profiles[/]:")
for name in profiles:
console.print(f"- {name}")
else:
# All services display
for svc, svc_profiles in profiles.items():
console.print(f"\n[{SUCCESS_COLOR}]{svc.capitalize()} Profiles[/]:")
for name in svc_profiles:
console.print(f"- {name}")
Examples:
# List all profiles
sfai config list
# List profiles for specific service
sfai config list --service mulesoft
view_cmd
¶
View details of a service profile
Parameters:
Name | Type | Description | Default |
---|---|---|---|
service | str | str Service name | Option(..., help='Service name') |
profile_name | Optional[str] | Optional[str] Profile to view | Option('default', help='Profile to view') |
Source code in sfai/cli/config/view.py
@app.callback(invoke_without_command=True)
def view_cmd(
service: str = typer.Option(..., help="Service name"),
profile_name: Optional[str] = typer.Option("default", help="Profile to view"),
):
"""View details of a service profile
Args:
service: str
Service name
profile_name: Optional[str]
Profile to view
"""
# Get schema to validate service
schema = _get_service_schema(service)
if not schema:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]Invalid service: {service}[/]")
return
# View the profile
result = view(service, profile_name)
if not result.success:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]{result.error}[/]")
return
# Display profile information in a table
table = Table(title=f"{service.capitalize()} Profile: {profile_name}")
table.add_column("Key", style="cyan")
table.add_column("Value", style="green")
profile = result.data
for key, value in profile.items():
table.add_row(key, str(value))
console.print(table)
Examples:
# View default profile
sfai config view --service mulesoft
# View specific profile
sfai config view --service mulesoft --profile-name production
delete_cmd
¶
Delete a service profile
Parameters:
Name | Type | Description | Default |
---|---|---|---|
service | str | str Service name | Option(..., help='Service name') |
profile_name | Optional[str] | Optional[str] Profile to delete | Option('default', help='Profile to delete') |
force | bool | bool Delete without confirmation | Option(False, '--force', '-f', help='Delete without confirmation') |
Returns:
Type | Description |
---|---|
None |
Source code in sfai/cli/config/delete.py
@app.callback(invoke_without_command=True)
def delete_cmd(
service: str = typer.Option(..., help="Service name"),
profile_name: Optional[str] = typer.Option("default", help="Profile to delete"),
force: bool = typer.Option(
False, "--force", "-f", help="Delete without confirmation"
),
):
"""Delete a service profile
Args:
service: str
Service name
profile_name: Optional[str]
Profile to delete
force: bool
Delete without confirmation
Returns:
None
"""
# Get schema to validate service
schema = _get_service_schema(service)
if not schema:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]Invalid service: {service}[/]")
return
# Ask for confirmation unless force is specified
if not force:
confirmed = Confirm.ask(
f"{QUESTION_EMOJI} Are you sure you want to delete the profile "
f"'{profile_name}' for service '{service}'?",
default=False,
)
if not confirmed:
console.print("Operation cancelled.")
return
# Delete the profile
result = delete(service, profile_name)
if result.success:
console.print(f"{SUCCESS_EMOJI} [{SUCCESS_COLOR}]{result.message}[/]")
else:
console.print(f"{ERROR_EMOJI} [{ERROR_COLOR}]{result.error}[/]")
Examples:
# Delete with confirmation
sfai config delete --service mulesoft --profile-name old-profile
# Force delete without confirmation
sfai config delete --service mulesoft --profile-name old-profile --force
sfai platform
- Platform Management¶
init_platform_cmd
¶
Initialize or update cloud/local environment in context
Parameters:
Name | Type | Description | Default |
---|---|---|---|
platform | Optional[str] | str Platform type (local, eks, gcp, heroku) | Option(None, help='Platform type (local, eks, gcp, heroku)') |
environment | str | str Environment name (defaults to "default") | Option('default', help='Environment name') |
app_name | Optional[str] | Optional[str] Heroku app name (for heroku) | Option(None, help='Heroku app name (for heroku)') |
team_name | Optional[str] | Optional[str] Heroku team name (for heroku) | Option(None, help='Heroku team name (for heroku)') |
private_space | Optional[str] | Optional[str] Heroku private space name (for heroku) | Option(None, help='Heroku private space name (for heroku)') |
deployment_type | Optional[str] | Optional[str] Deployment type (for heroku) | Option(None, help='Deployment type (for heroku)') |
routing | Optional[str] | Optional[str] Routing type (for heroku) | Option(None, help='Routing type (for heroku)') |
cluster_name | Optional[str] | Optional[str] EKS cluster name (for eks) | Option(None, help='EKS cluster name (for eks)') |
region | Optional[str] | Optional[str] AWS region (for eks) | Option(None, help='AWS region (for eks)') |
profile | Optional[str] | Optional[str] AWS profile (for eks) | Option(None, help='AWS profile (for eks)') |
namespace | Optional[str] | Optional[str] namespace (for eks) | Option(None, help='namespace (for eks)') |
ecr_repo | Optional[str] | Optional[str] ECR repository (for eks) | Option(None, help='ECR repository (for eks)') |
interactive | bool | bool Interactive mode | Option(False, '-i', '--interactive', help='Interactive mode') |
skip_confirm | bool | bool Skip confirmation of default values | Option(False, '-y', '--yes', help='Skip confirmation of default values') |
Source code in sfai/cli/platform/init.py
@app.callback(
invoke_without_command=True,
help="Initialize or update cloud/local environment in context",
)
def init_platform_cmd(
platform: Optional[str] = typer.Option(
None, help="Platform type (local, eks, gcp, heroku)"
),
environment: str = typer.Option("default", help="Environment name"),
# heroku options
app_name: Optional[str] = typer.Option(None, help="Heroku app name (for heroku)"),
team_name: Optional[str] = typer.Option(None, help="Heroku team name (for heroku)"),
private_space: Optional[str] = typer.Option(
None, help="Heroku private space name (for heroku)"
),
deployment_type: Optional[str] = typer.Option(
None, help="Deployment type (for heroku)"
),
routing: Optional[str] = typer.Option(None, help="Routing type (for heroku)"),
# eks options
cluster_name: Optional[str] = typer.Option(None, help="EKS cluster name (for eks)"),
region: Optional[str] = typer.Option(None, help="AWS region (for eks)"),
profile: Optional[str] = typer.Option(None, help="AWS profile (for eks)"),
namespace: Optional[str] = typer.Option(None, help="namespace (for eks)"),
ecr_repo: Optional[str] = typer.Option(None, help="ECR repository (for eks)"),
# interactive options
interactive: bool = typer.Option(
False, "-i", "--interactive", help="Interactive mode"
),
skip_confirm: bool = typer.Option(
False, "-y", "--yes", help="Skip confirmation of default values"
),
) -> None:
"""
Initialize or update cloud/local environment in context
Args:
platform: str
Platform type (local, eks, gcp, heroku)
environment: str
Environment name (defaults to "default")
app_name: Optional[str]
Heroku app name (for heroku)
team_name: Optional[str]
Heroku team name (for heroku)
private_space: Optional[str]
Heroku private space name (for heroku)
deployment_type: Optional[str]
Deployment type (for heroku)
routing: Optional[str]
Routing type (for heroku)
cluster_name: Optional[str]
EKS cluster name (for eks)
region: Optional[str]
AWS region (for eks)
profile: Optional[str]
AWS profile (for eks)
namespace: Optional[str]
namespace (for eks)
ecr_repo: Optional[str]
ECR repository (for eks)
interactive: bool
Interactive mode
skip_confirm: bool
Skip confirmation of default values
"""
context = ctx_mgr.read_context()
if not context:
console.print(f"{ERROR_EMOJI} No app context found. Run `sfai init app` first.")
return
current_env = context.get("active_env", {})
if not platform:
platform = Prompt.ask(
"Enter the platform type",
choices=["eks", "heroku", "minikube"],
default=current_env or "heroku",
)
environment = Prompt.ask(
"Enter the environment",
default=environment or context.get("environment", "default"),
)
if platform == "heroku":
if interactive:
team_name = Prompt.ask(
"Enter the team name", default=team_name or context.get("team_name", "")
)
if team_name:
private_space = Prompt.ask(
"Enter the private space name",
default=private_space or context.get("private_space", ""),
)
else:
private_space = ""
deployment_type = Prompt.ask(
"Enter the deployment type",
choices=["container", "buildpack"],
default=deployment_type or context.get("deployment_type", "buildpack"),
)
routing = Prompt.ask(
"Enter the routing type",
choices=["internal", "public"],
default=routing or context.get("routing", "public"),
)
else:
app_name = app_name or context.get("app_name", "").lower()
team_name = team_name or context.get("team_name", "")
private_space = private_space or context.get("private_space", "")
deployment_type = deployment_type or context.get(
"deployment_type", "buildpack"
)
routing = routing or context.get("routing", "public")
if not skip_confirm:
console.print(
f"Initializing [cyan]{platform}[/] environment with the "
f"following values:"
)
console.print(f" App Name: [cyan]{app_name}[/]")
console.print(f" Team Name: [cyan]{team_name or ''}[/]")
console.print(f" Private Space: [cyan]{private_space or ''}[/]")
console.print(f" Deployment Type: [cyan]{deployment_type}[/]")
console.print(f" Routing Type: [cyan]{routing}[/]")
confirm = Prompt.ask(
"Are you sure you want to continue?",
choices=["y", "n"],
default="y",
)
if confirm.lower() not in ["y", "yes"]:
console.print(
f"{ERROR_EMOJI} Environment initialization cancelled."
)
return
if platform == "eks":
if interactive:
cluster_name = Prompt.ask(
"Enter the EKS cluster name",
default=cluster_name or context.get("cluster_name"),
)
region = Prompt.ask(
"Enter the region", default=region or context.get("region", "us-west-2")
)
profile = Prompt.ask(
"Enter the profile",
default=profile or context.get("profile", "default"),
)
namespace = Prompt.ask(
"Enter the namespace",
default=namespace or context.get("namespace", "default"),
)
ecr_repo = Prompt.ask(
"Enter the ECR repository",
default=ecr_repo or context.get("ecr_repo", f"{app_name}"),
)
else:
cluster_name = cluster_name or context.get("cluster_name")
region = region or context.get("region", "us-east-1")
profile = profile or context.get("profile", "default")
namespace = namespace or context.get("namespace", "default")
ecr_repo = ecr_repo or context.get("ecr_repo", f"{app_name}")
if not skip_confirm:
console.print(
f"Initializing [cyan]{platform}[/] environment with the "
f"following values:"
)
console.print(f" Cluster Name: [cyan]{cluster_name}[/]")
console.print(f" Region: [cyan]{region}[/]")
console.print(f" Profile: [cyan]{profile}[/]")
console.print(f" Namespace: [cyan]{namespace}[/]")
console.print(f" ECR Repository: [cyan]{ecr_repo}[/]")
confirm = Prompt.ask(
"Are you sure you want to continue?",
choices=["y", "n"],
default="y",
)
if confirm.lower() not in ["y", "yes"]:
console.print(
f"{ERROR_EMOJI} Environment initialization cancelled."
)
return
result = init(
platform=platform,
environment=environment,
# heroku options
app_name=app_name,
team_name=team_name,
private_space=private_space,
deployment_type=deployment_type,
routing=routing,
# eks options
cluster_name=cluster_name,
region=region,
profile=profile,
namespace=namespace,
ecr_repo=ecr_repo,
# interactive options
interactive=interactive,
skip_confirm=skip_confirm,
)
if result.success:
# Get the environment-specific app name from result data if available
environment_app_name = result.data.get("app_name") if result.data else None
app_name = environment_app_name or context.get("app_name")
console.print(
f"{SUCCESS_EMOJI} {platform} environment initialized → app_name: {app_name}"
)
if team_name:
console.print(f"🔗 Team: {team_name}")
if private_space:
console.print(f"🔗 Private Space: {private_space}")
console.print(
f"{ROCKET_EMOJI} Next: Run `sfai app deploy` to deploy your app to "
f"{platform}."
)
else:
console.print(f"{ERROR_EMOJI} {result.error}")
Examples:
# Interactive platform initialization
sfai platform init --interactive
# Initialize Heroku platform
sfai platform init --cloud heroku --app-name my-app --team-name my-team
# Initialize with specific deployment type
sfai platform init --cloud heroku --deployment-type container --routing internal
# Initialize EKS platform
sfai platform init --cloud eks --cluster-name my-cluster --region us-west-2 --namespace production
switch_platform_cmd
¶
Switch to a different platform and environment.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
platform | str | str Platform to switch to (local, aws, gcp, heroku) | Argument(..., help='Platform to switch to (local, aws, gcp, heroku)') |
environment | str | str Environment to switch to (defaults to "default") | Option('default', help='Environment to switch to') |
Returns:
Type | Description |
---|---|
None | None |
Source code in sfai/cli/platform/switch.py
@app.command(help="Switch between different configurations")
def switch_platform_cmd(
platform: str = typer.Argument(
..., help="Platform to switch to (local, aws, gcp, heroku)"
),
environment: str = typer.Option("default", help="Environment to switch to"),
) -> None:
"""
Switch to a different platform and environment.
Args:
platform: str
Platform to switch to (local, aws, gcp, heroku)
environment: str
Environment to switch to (defaults to "default")
Returns:
None
"""
result = switch(platform, environment)
if result.success:
console.print(
f"{SUCCESS_EMOJI} Switched to {platform} platform, {environment} "
f"environment"
)
else:
console.print(
f"{ERROR_EMOJI} Platform '{platform}' environment '{environment}' "
f"is not initialized yet."
)
console.print(
f"{LIGHT_BULB_EMOJI} Run: sfai platform init {platform} "
f"--environment {environment}"
)
Examples:
# Switch to Heroku platform
sfai platform switch heroku
# Switch to local platform
sfai platform switch local
# Switch to EKS platform
sfai platform switch aws
Global Options¶
These options can be used with most commands:
Option | Description |
---|---|
--help | Show help message and exit |
--version | Show version information |
Best Practices¶
Context Management¶
- Use
sfai app context
to verify current app and platform settings - Switch platforms with
sfai platform switch
before deploying - Delete old contexts with
sfai app context --delete
when switching projects
Profile Management¶
- Use descriptive profile names (e.g.,
production
,staging
,dev
) - Store different environment credentials in separate profiles
- Use
sfai config list
to see all configured profiles
Deployment Workflow¶
- Initialize app:
sfai app init
- Set up platform:
sfai platform init --cloud heroku
- Deploy:
sfai app deploy
- Check status:
sfai app status
- View logs:
sfai app logs
- Open app:
sfai app open
Interactive Mode¶
- Use
--interactive
or-i
for guided setup - Especially useful for first-time configuration
- Provides validation and helpful prompts
Notes¶
- Interactive Mode: Many commands support
--interactive
or-i
flag for guided setup - Profile Management: Config commands support multiple profiles per service for different environments
- Platform Context: App commands respect the current platform context but can be overridden with
--platform
- Required Arguments: Arguments marked as Required must be provided
- Default Values: Default values are used when options are not specified
- Cloudflare Tunnel: The
--tunnel
option insfai app open
requirescloudflared
to be installed
For more examples and workflows, see the CLI Examples page.