borealhost 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.

Potentially problematic release.


This version of borealhost might be problematic. Click here for more details.

Files changed (37) hide show
  1. borealhost-0.1.0/.gitignore +36 -0
  2. borealhost-0.1.0/PKG-INFO +96 -0
  3. borealhost-0.1.0/README.md +69 -0
  4. borealhost-0.1.0/borealhost_cli/__init__.py +3 -0
  5. borealhost-0.1.0/borealhost_cli/commands/__init__.py +1 -0
  6. borealhost-0.1.0/borealhost_cli/commands/account.py +51 -0
  7. borealhost-0.1.0/borealhost_cli/commands/alerts.py +46 -0
  8. borealhost-0.1.0/borealhost_cli/commands/apps.py +43 -0
  9. borealhost-0.1.0/borealhost_cli/commands/auth.py +91 -0
  10. borealhost-0.1.0/borealhost_cli/commands/backups.py +35 -0
  11. borealhost-0.1.0/borealhost_cli/commands/cache.py +32 -0
  12. borealhost-0.1.0/borealhost_cli/commands/checkout.py +64 -0
  13. borealhost-0.1.0/borealhost_cli/commands/cloudflare.py +49 -0
  14. borealhost-0.1.0/borealhost_cli/commands/cron.py +37 -0
  15. borealhost-0.1.0/borealhost_cli/commands/db.py +64 -0
  16. borealhost-0.1.0/borealhost_cli/commands/dns.py +28 -0
  17. borealhost-0.1.0/borealhost_cli/commands/domains.py +138 -0
  18. borealhost-0.1.0/borealhost_cli/commands/files.py +90 -0
  19. borealhost-0.1.0/borealhost_cli/commands/firewall.py +37 -0
  20. borealhost-0.1.0/borealhost_cli/commands/ftp.py +50 -0
  21. borealhost-0.1.0/borealhost_cli/commands/keys.py +53 -0
  22. borealhost-0.1.0/borealhost_cli/commands/logs.py +22 -0
  23. borealhost-0.1.0/borealhost_cli/commands/modules.py +25 -0
  24. borealhost-0.1.0/borealhost_cli/commands/php.py +25 -0
  25. borealhost-0.1.0/borealhost_cli/commands/plans.py +30 -0
  26. borealhost-0.1.0/borealhost_cli/commands/sites.py +79 -0
  27. borealhost-0.1.0/borealhost_cli/commands/snapshots.py +91 -0
  28. borealhost-0.1.0/borealhost_cli/commands/ssh.py +45 -0
  29. borealhost-0.1.0/borealhost_cli/commands/ssl.py +21 -0
  30. borealhost-0.1.0/borealhost_cli/commands/system.py +32 -0
  31. borealhost-0.1.0/borealhost_cli/commands/wp.py +70 -0
  32. borealhost-0.1.0/borealhost_cli/config.py +69 -0
  33. borealhost-0.1.0/borealhost_cli/context.py +26 -0
  34. borealhost-0.1.0/borealhost_cli/helpers.py +32 -0
  35. borealhost-0.1.0/borealhost_cli/main.py +143 -0
  36. borealhost-0.1.0/borealhost_cli/output.py +111 -0
  37. borealhost-0.1.0/pyproject.toml +47 -0
@@ -0,0 +1,36 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ venv/
6
+ .pytest_cache/
7
+ db.sqlite3
8
+
9
+ # Node
10
+ node_modules/
11
+ dist/
12
+ bun.lock
13
+
14
+ # Secrets
15
+ .env
16
+ *.key
17
+ *.pem
18
+ secrets/
19
+ .ssh/
20
+
21
+ # Claude Code
22
+ .claude/
23
+ .mcp.json
24
+
25
+ # Deploy artifacts & large files
26
+ *.tar.gz
27
+ *.tar
28
+ *.mo
29
+
30
+ # Backups
31
+ *.bak.*
32
+
33
+ # OS
34
+ .DS_Store
35
+ *.swp
36
+ *~
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: borealhost
3
+ Version: 0.1.0
4
+ Summary: Command-line tool for BorealHost.ai — agent-native web hosting
5
+ Project-URL: Homepage, https://borealhost.ai
6
+ Project-URL: Documentation, https://borealhost.ai/api/v1/docs/
7
+ Project-URL: Repository, https://github.com/alainsvrd/platform
8
+ Author-email: BorealHost <hello@borealhost.ai>
9
+ License: MIT
10
+ Keywords: borealhost,cli,hosting
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Internet :: WWW/HTTP
20
+ Classifier: Topic :: System :: Systems Administration
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: borealhost-sdk>=0.1.0
23
+ Requires-Dist: pyyaml>=6.0
24
+ Requires-Dist: rich>=13.0.0
25
+ Requires-Dist: typer>=0.12.0
26
+ Description-Content-Type: text/markdown
27
+
28
+ # BorealHost CLI (`bh`)
29
+
30
+ Command-line tool for [BorealHost.ai](https://borealhost.ai) — agent-native web hosting.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install borealhost
36
+ ```
37
+
38
+ ## Authenticate
39
+
40
+ ```bash
41
+ bh auth register # new agent account
42
+ bh auth set-key bh_your_existing_key # existing account
43
+ bh auth whoami
44
+ ```
45
+
46
+ Keys are persisted to `~/.borealhost/config.toml` (chmod 600).
47
+
48
+ ## Examples
49
+
50
+ ```bash
51
+ # Buy hosting
52
+ bh plans list
53
+ bh checkout create bh_site_starter_monthly
54
+ bh checkout update <id> --slug my-site --email you@example.com
55
+ bh checkout complete <id>
56
+
57
+ # Manage a site
58
+ bh sites list
59
+ bh sites status my-site
60
+ bh sites deploy my-site
61
+ bh files ls my-site /public_html
62
+ bh wp plugins list my-site
63
+ bh db query my-site wp_my_site "SELECT * FROM wp_users LIMIT 5"
64
+
65
+ # Snapshots
66
+ bh snapshots create my-site --description "before upgrade"
67
+ bh snapshots list my-site
68
+
69
+ # DNS
70
+ bh dns add my-site --type A --value 1.2.3.4 --subdomain www
71
+
72
+ # Domains
73
+ bh domains search example.com
74
+ bh domains register example.com --period 1
75
+ ```
76
+
77
+ ## Output formats
78
+
79
+ All commands accept `--json`, `--yaml`, or `--table` (default for TTY):
80
+
81
+ ```bash
82
+ bh sites list --json | jq '.[] | .slug'
83
+ bh plans list --yaml > plans.yaml
84
+ ```
85
+
86
+ When stdout is not a TTY, JSON is used automatically.
87
+
88
+ ## Environment variables
89
+
90
+ - `BOREALHOST_API_KEY` — API key (overrides config file)
91
+ - `BOREALHOST_BASE_URL` — API base URL (default: `https://borealhost.ai`)
92
+
93
+ ## Related
94
+
95
+ - [`borealhost-sdk`](https://pypi.org/project/borealhost-sdk/) — Python SDK
96
+ - [`borealhost-mcp`](https://pypi.org/project/borealhost-mcp/) — MCP server for AI agents
@@ -0,0 +1,69 @@
1
+ # BorealHost CLI (`bh`)
2
+
3
+ Command-line tool for [BorealHost.ai](https://borealhost.ai) — agent-native web hosting.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install borealhost
9
+ ```
10
+
11
+ ## Authenticate
12
+
13
+ ```bash
14
+ bh auth register # new agent account
15
+ bh auth set-key bh_your_existing_key # existing account
16
+ bh auth whoami
17
+ ```
18
+
19
+ Keys are persisted to `~/.borealhost/config.toml` (chmod 600).
20
+
21
+ ## Examples
22
+
23
+ ```bash
24
+ # Buy hosting
25
+ bh plans list
26
+ bh checkout create bh_site_starter_monthly
27
+ bh checkout update <id> --slug my-site --email you@example.com
28
+ bh checkout complete <id>
29
+
30
+ # Manage a site
31
+ bh sites list
32
+ bh sites status my-site
33
+ bh sites deploy my-site
34
+ bh files ls my-site /public_html
35
+ bh wp plugins list my-site
36
+ bh db query my-site wp_my_site "SELECT * FROM wp_users LIMIT 5"
37
+
38
+ # Snapshots
39
+ bh snapshots create my-site --description "before upgrade"
40
+ bh snapshots list my-site
41
+
42
+ # DNS
43
+ bh dns add my-site --type A --value 1.2.3.4 --subdomain www
44
+
45
+ # Domains
46
+ bh domains search example.com
47
+ bh domains register example.com --period 1
48
+ ```
49
+
50
+ ## Output formats
51
+
52
+ All commands accept `--json`, `--yaml`, or `--table` (default for TTY):
53
+
54
+ ```bash
55
+ bh sites list --json | jq '.[] | .slug'
56
+ bh plans list --yaml > plans.yaml
57
+ ```
58
+
59
+ When stdout is not a TTY, JSON is used automatically.
60
+
61
+ ## Environment variables
62
+
63
+ - `BOREALHOST_API_KEY` — API key (overrides config file)
64
+ - `BOREALHOST_BASE_URL` — API base URL (default: `https://borealhost.ai`)
65
+
66
+ ## Related
67
+
68
+ - [`borealhost-sdk`](https://pypi.org/project/borealhost-sdk/) — Python SDK
69
+ - [`borealhost-mcp`](https://pypi.org/project/borealhost-mcp/) — MCP server for AI agents
@@ -0,0 +1,3 @@
1
+ """BorealHost CLI — `bh` command-line tool."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ """CLI command groups — one module per functional area."""
@@ -0,0 +1,51 @@
1
+ """Account management: update profile, subscriptions, billing portal."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from ..helpers import run
8
+
9
+ app = typer.Typer(no_args_is_help=True)
10
+
11
+
12
+ @app.command("update")
13
+ def update_cmd(
14
+ ctx: typer.Context,
15
+ email: Optional[str] = typer.Option(None, "--email"),
16
+ language: Optional[str] = typer.Option(None, "--language", help="en or fr."),
17
+ first_name: Optional[str] = typer.Option(None, "--first-name"),
18
+ last_name: Optional[str] = typer.Option(None, "--last-name"),
19
+ ) -> None:
20
+ """Update account profile fields."""
21
+ cli = ctx.obj
22
+ run(ctx, cli.client.update_account, email=email, language=language, first_name=first_name, last_name=last_name)
23
+
24
+
25
+ @app.command("delete")
26
+ def delete_cmd(
27
+ ctx: typer.Context,
28
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation."),
29
+ ) -> None:
30
+ """Soft-delete (anonymize) the account. [red]Irreversible.[/red]"""
31
+ if not yes:
32
+ typer.confirm("Really delete this account? This cannot be undone.", abort=True)
33
+ cli = ctx.obj
34
+ run(ctx, cli.client.delete_account)
35
+
36
+
37
+ @app.command("subscriptions")
38
+ def subscriptions_cmd(ctx: typer.Context) -> None:
39
+ """List all subscriptions for the account."""
40
+ cli = ctx.obj
41
+ run(ctx, cli.client.list_subscriptions, title="Subscriptions")
42
+
43
+
44
+ @app.command("billing-portal")
45
+ def billing_portal_cmd(
46
+ ctx: typer.Context,
47
+ flow: Optional[str] = typer.Option(None, "--flow", help="Stripe portal flow type."),
48
+ ) -> None:
49
+ """Get the Stripe billing portal URL."""
50
+ cli = ctx.obj
51
+ run(ctx, cli.client.get_billing_portal, flow=flow)
@@ -0,0 +1,46 @@
1
+ """Alert rules: list, create, delete."""
2
+
3
+ import typer
4
+
5
+ from ..helpers import run
6
+
7
+ app = typer.Typer(no_args_is_help=True)
8
+
9
+
10
+ @app.command("list")
11
+ def list_cmd(ctx: typer.Context, slug: str = typer.Argument(..., help="Site slug.")) -> None:
12
+ """List alert rules for a site."""
13
+ cli = ctx.obj
14
+ run(ctx, cli.client.list_alert_rules, slug)
15
+
16
+
17
+ @app.command("create")
18
+ def create_cmd(
19
+ ctx: typer.Context,
20
+ slug: str = typer.Argument(..., help="Site slug."),
21
+ metric: str = typer.Argument(..., help="Metric: cpu, ram, disk, error_5xx, etc."),
22
+ threshold: float = typer.Argument(..., help="Threshold value."),
23
+ operator: str = typer.Option("gt", "--operator", help="gt, lt, gte, lte, eq."),
24
+ severity: str = typer.Option("warning", "--severity", help="info, warning, critical."),
25
+ cooldown_minutes: int = typer.Option(30, "--cooldown", help="Cooldown between alerts (minutes)."),
26
+ notify_email: bool = typer.Option(True, "--email/--no-email"),
27
+ notify_webhook: str = typer.Option("", "--webhook", help="Webhook URL."),
28
+ ) -> None:
29
+ """Create a new alert rule."""
30
+ cli = ctx.obj
31
+ run(
32
+ ctx, cli.client.create_alert_rule, slug, metric, threshold,
33
+ operator=operator, severity=severity, cooldown_minutes=cooldown_minutes,
34
+ notify_email=notify_email, notify_webhook=notify_webhook,
35
+ )
36
+
37
+
38
+ @app.command("delete")
39
+ def delete_cmd(
40
+ ctx: typer.Context,
41
+ slug: str = typer.Argument(..., help="Site slug."),
42
+ rule_id: str = typer.Argument(..., help="Alert rule ID."),
43
+ ) -> None:
44
+ """Delete an alert rule."""
45
+ cli = ctx.obj
46
+ run(ctx, cli.client.delete_alert_rule, slug, rule_id)
@@ -0,0 +1,43 @@
1
+ """App installation on VPS sites (Ghost, WordPress, etc)."""
2
+
3
+ import typer
4
+
5
+ from ..helpers import run
6
+
7
+ app = typer.Typer(no_args_is_help=True)
8
+
9
+
10
+ @app.command("list")
11
+ def list_cmd(ctx: typer.Context, slug: str = typer.Argument(..., help="Site slug.")) -> None:
12
+ """List installed apps on a VPS site."""
13
+ cli = ctx.obj
14
+ run(ctx, cli.client.list_apps, slug)
15
+
16
+
17
+ @app.command("install")
18
+ def install_cmd(
19
+ ctx: typer.Context,
20
+ slug: str = typer.Argument(..., help="Site slug."),
21
+ template: str = typer.Argument(..., help="Template name (e.g. 'ghost', 'wordpress')."),
22
+ app_name: str = typer.Argument(..., help="Unique app identifier within the site."),
23
+ db_type: str = typer.Option("none", "--db", help="Database: none, mysql, postgres."),
24
+ domain: str = typer.Option("", "--domain", help="Custom domain for this app."),
25
+ display_name: str = typer.Option("", "--display-name", help="Human-readable name."),
26
+ ) -> None:
27
+ """Install an app template on a VPS site."""
28
+ cli = ctx.obj
29
+ run(
30
+ ctx, cli.client.install_app, slug, template, app_name,
31
+ db_type=db_type, domain=domain, display_name=display_name,
32
+ )
33
+
34
+
35
+ @app.command("status")
36
+ def status_cmd(
37
+ ctx: typer.Context,
38
+ slug: str = typer.Argument(..., help="Site slug."),
39
+ app_id: str = typer.Argument(..., help="App ID from `apps list`."),
40
+ ) -> None:
41
+ """Get install status and logs for an app."""
42
+ cli = ctx.obj
43
+ run(ctx, cli.client.get_app, slug, app_id)
@@ -0,0 +1,91 @@
1
+ """Authentication commands: register, set-key, whoami, request-key, claim-key."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from ..config import load, save
8
+ from ..helpers import run
9
+ from ..output import emit, success, info
10
+
11
+ app = typer.Typer(no_args_is_help=True)
12
+
13
+
14
+ @app.command("register")
15
+ def register_cmd(
16
+ ctx: typer.Context,
17
+ name: str = typer.Option("Agent Key", "--name", help="Human-readable key name."),
18
+ email: Optional[str] = typer.Option(None, "--email", help="Email to link to a human account."),
19
+ save_key: bool = typer.Option(True, "--save/--no-save", help="Persist the new key to the config file."),
20
+ ) -> None:
21
+ """Register a new agent account and receive an API key."""
22
+ cli = ctx.obj
23
+ result = cli.client.register(email=email, name=name)
24
+ if result and result.get("api_key"):
25
+ key = result["api_key"]
26
+ cli.client.set_api_key(key)
27
+ if save_key:
28
+ data = load()
29
+ data["api_key"] = key
30
+ save(data)
31
+ success(f"Saved new API key ({key[:10]}...) to ~/.borealhost/config.toml")
32
+ emit(result, cli.output_format)
33
+
34
+
35
+ @app.command("set-key")
36
+ def set_key_cmd(
37
+ ctx: typer.Context,
38
+ api_key: str = typer.Argument(..., help="Your BorealHost API key (bh_...)."),
39
+ ) -> None:
40
+ """Save an existing API key to ~/.borealhost/config.toml."""
41
+ if not api_key.startswith("bh_"):
42
+ typer.echo("error: keys must start with 'bh_'", err=True)
43
+ raise typer.Exit(1)
44
+ data = load()
45
+ data["api_key"] = api_key
46
+ save(data)
47
+ success(f"Saved API key ({api_key[:10]}...) to ~/.borealhost/config.toml")
48
+
49
+
50
+ @app.command("whoami")
51
+ def whoami_cmd(ctx: typer.Context) -> None:
52
+ """Show the account associated with the current API key."""
53
+ cli = ctx.obj
54
+ run(ctx, cli.client.whoami, title="Account")
55
+
56
+
57
+ @app.command("request-key")
58
+ def request_key_cmd(
59
+ ctx: typer.Context,
60
+ site_slug: str = typer.Argument(..., help="Site slug (you must be running on this container)."),
61
+ ) -> None:
62
+ """Request a claim token for challenge-response key provisioning (on-container)."""
63
+ cli = ctx.obj
64
+ run(ctx, cli.client.request_api_key, site_slug)
65
+
66
+
67
+ @app.command("claim-key")
68
+ def claim_key_cmd(
69
+ ctx: typer.Context,
70
+ token: str = typer.Argument(..., help="Claim token from ~/.borealhost/.claim_token on the container."),
71
+ save_key: bool = typer.Option(True, "--save/--no-save", help="Persist the new key to the config file."),
72
+ ) -> None:
73
+ """Claim an API key with a claim token from the container."""
74
+ cli = ctx.obj
75
+ result = cli.client.claim_api_key(token)
76
+ if result and result.get("api_key"):
77
+ key = result["api_key"]
78
+ cli.client.set_api_key(key)
79
+ if save_key:
80
+ data = load()
81
+ data["api_key"] = key
82
+ save(data)
83
+ success(f"Saved claimed API key to ~/.borealhost/config.toml")
84
+ emit(result, cli.output_format)
85
+
86
+
87
+ @app.command("config-path")
88
+ def config_path_cmd() -> None:
89
+ """Print the path to the CLI config file."""
90
+ from ..config import CONFIG_PATH
91
+ info(str(CONFIG_PATH))
@@ -0,0 +1,35 @@
1
+ """VPS backups: list, create, restore."""
2
+
3
+ import typer
4
+
5
+ from ..helpers import run
6
+
7
+ app = typer.Typer(no_args_is_help=True)
8
+
9
+
10
+ @app.command("list")
11
+ def list_cmd(ctx: typer.Context, slug: str = typer.Argument(..., help="Site slug.")) -> None:
12
+ """List backups for a site."""
13
+ cli = ctx.obj
14
+ run(ctx, cli.client.list_backups, slug)
15
+
16
+
17
+ @app.command("create")
18
+ def create_cmd(ctx: typer.Context, slug: str = typer.Argument(..., help="Site slug.")) -> None:
19
+ """Create a new backup."""
20
+ cli = ctx.obj
21
+ run(ctx, cli.client.create_backup, slug)
22
+
23
+
24
+ @app.command("restore")
25
+ def restore_cmd(
26
+ ctx: typer.Context,
27
+ slug: str = typer.Argument(..., help="Site slug."),
28
+ backup_id: str = typer.Argument(..., help="Backup ID."),
29
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation."),
30
+ ) -> None:
31
+ """Restore a backup. [red]Overwrites current state.[/red]"""
32
+ if not yes:
33
+ typer.confirm(f"Restore '{slug}' from backup {backup_id}?", abort=True)
34
+ cli = ctx.obj
35
+ run(ctx, cli.client.restore_backup, slug, backup_id)
@@ -0,0 +1,32 @@
1
+ """Cache management: status, flush, toggle."""
2
+
3
+ import typer
4
+
5
+ from ..helpers import run
6
+
7
+ app = typer.Typer(no_args_is_help=True)
8
+
9
+
10
+ @app.command("status")
11
+ def status_cmd(ctx: typer.Context, slug: str = typer.Argument(..., help="Site slug.")) -> None:
12
+ """Show cache status for a site."""
13
+ cli = ctx.obj
14
+ run(ctx, cli.client.get_cache_status, slug)
15
+
16
+
17
+ @app.command("flush")
18
+ def flush_cmd(ctx: typer.Context, slug: str = typer.Argument(..., help="Site slug.")) -> None:
19
+ """Flush all cache for a site."""
20
+ cli = ctx.obj
21
+ run(ctx, cli.client.flush_cache, slug)
22
+
23
+
24
+ @app.command("toggle")
25
+ def toggle_cmd(
26
+ ctx: typer.Context,
27
+ slug: str = typer.Argument(..., help="Site slug."),
28
+ enable: bool = typer.Argument(..., help="true to enable, false to disable."),
29
+ ) -> None:
30
+ """Enable or disable caching for a site."""
31
+ cli = ctx.obj
32
+ run(ctx, cli.client.toggle_cache, slug, enable)
@@ -0,0 +1,64 @@
1
+ """ACP checkout flow: create, update, complete, status."""
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from ..helpers import run
8
+
9
+ app = typer.Typer(no_args_is_help=True)
10
+
11
+
12
+ @app.command("create")
13
+ def create_cmd(
14
+ ctx: typer.Context,
15
+ sku: str = typer.Argument(..., help="Plan SKU (e.g. 'bh_site_starter_monthly')."),
16
+ ) -> None:
17
+ """Start a checkout session for a plan SKU."""
18
+ cli = ctx.obj
19
+ run(ctx, cli.client.create_checkout, sku, title="Checkout")
20
+
21
+
22
+ @app.command("update")
23
+ def update_cmd(
24
+ ctx: typer.Context,
25
+ checkout_id: str = typer.Argument(..., help="Checkout session ID."),
26
+ slug: Optional[str] = typer.Option(None, "--slug", help="Requested site slug."),
27
+ email: Optional[str] = typer.Option(None, "--email", help="Buyer email."),
28
+ ) -> None:
29
+ """Set buyer email and/or desired site slug on a checkout."""
30
+ cli = ctx.obj
31
+ run(ctx, cli.client.update_checkout, checkout_id, buyer_email=email, requested_slug=slug)
32
+
33
+
34
+ @app.command("complete")
35
+ def complete_cmd(
36
+ ctx: typer.Context,
37
+ checkout_id: str = typer.Argument(..., help="Checkout session ID."),
38
+ payment_method: str = typer.Option(
39
+ "stripe_checkout", "--payment-method",
40
+ help="stripe_checkout (hosted URL) or stripe_payment_method.",
41
+ ),
42
+ payment_method_id: Optional[str] = typer.Option(
43
+ None, "--payment-method-id", help="Stripe pm_... (for stripe_payment_method)."
44
+ ),
45
+ ) -> None:
46
+ """Complete checkout and start provisioning."""
47
+ cli = ctx.obj
48
+ run(
49
+ ctx,
50
+ cli.client.complete_checkout,
51
+ checkout_id,
52
+ payment_method=payment_method,
53
+ payment_method_id=payment_method_id,
54
+ )
55
+
56
+
57
+ @app.command("status")
58
+ def status_cmd(
59
+ ctx: typer.Context,
60
+ checkout_id: str = typer.Argument(..., help="Checkout session ID."),
61
+ ) -> None:
62
+ """Poll the status of a checkout (used after stripe_checkout payment)."""
63
+ cli = ctx.obj
64
+ run(ctx, cli.client.get_checkout, checkout_id, title="Checkout status")
@@ -0,0 +1,49 @@
1
+ """Cloudflare CDN: proxy status, cache purge, analytics."""
2
+
3
+ from typing import List, Optional
4
+
5
+ import typer
6
+
7
+ from ..helpers import run
8
+
9
+ app = typer.Typer(no_args_is_help=True)
10
+
11
+
12
+ @app.command("proxy-status")
13
+ def proxy_status_cmd(ctx: typer.Context, slug: str = typer.Argument(..., help="Site slug.")) -> None:
14
+ """Show Cloudflare proxy status for a site."""
15
+ cli = ctx.obj
16
+ run(ctx, cli.client.cf_proxy_status, slug)
17
+
18
+
19
+ @app.command("proxy-set")
20
+ def proxy_set_cmd(
21
+ ctx: typer.Context,
22
+ slug: str = typer.Argument(..., help="Site slug."),
23
+ proxied: bool = typer.Argument(..., help="true to proxy through CF, false for DNS-only."),
24
+ ) -> None:
25
+ """Toggle Cloudflare proxying on/off for a site."""
26
+ cli = ctx.obj
27
+ run(ctx, cli.client.cf_set_proxy, slug, proxied)
28
+
29
+
30
+ @app.command("cache-purge")
31
+ def cache_purge_cmd(
32
+ ctx: typer.Context,
33
+ slug: str = typer.Argument(..., help="Site slug."),
34
+ url: Optional[List[str]] = typer.Option(None, "--url", help="Specific URLs to purge (repeat). Default: purge all."),
35
+ ) -> None:
36
+ """Purge Cloudflare cache for a site."""
37
+ cli = ctx.obj
38
+ run(ctx, cli.client.cf_cache_purge, slug, urls=list(url) if url else None)
39
+
40
+
41
+ @app.command("analytics")
42
+ def analytics_cmd(
43
+ ctx: typer.Context,
44
+ slug: str = typer.Argument(..., help="Site slug."),
45
+ minutes: int = typer.Option(1440, "--minutes", help="Time window in minutes."),
46
+ ) -> None:
47
+ """Get Cloudflare analytics for a site."""
48
+ cli = ctx.obj
49
+ run(ctx, cli.client.cf_analytics, slug, minutes=minutes)
@@ -0,0 +1,37 @@
1
+ """Cron jobs: list, add, delete."""
2
+
3
+ import typer
4
+
5
+ from ..helpers import run
6
+
7
+ app = typer.Typer(no_args_is_help=True)
8
+
9
+
10
+ @app.command("list")
11
+ def list_cmd(ctx: typer.Context, slug: str = typer.Argument(..., help="Site slug.")) -> None:
12
+ """List cron jobs for a site."""
13
+ cli = ctx.obj
14
+ run(ctx, cli.client.list_cron, slug)
15
+
16
+
17
+ @app.command("add")
18
+ def add_cmd(
19
+ ctx: typer.Context,
20
+ slug: str = typer.Argument(..., help="Site slug."),
21
+ schedule: str = typer.Argument(..., help="Cron schedule, e.g. '0 * * * *'."),
22
+ command: str = typer.Argument(..., help="Command to run."),
23
+ ) -> None:
24
+ """Add a cron job."""
25
+ cli = ctx.obj
26
+ run(ctx, cli.client.add_cron, slug, schedule, command)
27
+
28
+
29
+ @app.command("delete")
30
+ def delete_cmd(
31
+ ctx: typer.Context,
32
+ slug: str = typer.Argument(..., help="Site slug."),
33
+ line_number: int = typer.Argument(..., help="Crontab line number."),
34
+ ) -> None:
35
+ """Delete a cron job by line number."""
36
+ cli = ctx.obj
37
+ run(ctx, cli.client.delete_cron, slug, line_number)