xenfra 0.4.3__tar.gz → 0.4.4__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.
xenfra-0.4.4/PKG-INFO ADDED
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.3
2
+ Name: xenfra
3
+ Version: 0.4.4
4
+ Summary: Xenfra CLI: Hands for AI to deploy on DigitalOcean.
5
+ Author: xenfra-cloud
6
+ Author-email: xenfra-cloud <xenfracloud@gmail.com>
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Software Development :: Build Tools
13
+ Classifier: Topic :: System :: Systems Administration
14
+ Requires-Dist: click>=8.1.7
15
+ Requires-Dist: rich>=14.2.0
16
+ Requires-Dist: sqlmodel>=0.0.16
17
+ Requires-Dist: python-digitalocean>=1.17.0
18
+ Requires-Dist: python-dotenv>=1.2.1
19
+ Requires-Dist: pyyaml>=6.0.1
20
+ Requires-Dist: fabric>=3.2.2
21
+ Requires-Dist: xenfra-sdk>=0.2.2
22
+ Requires-Dist: httpx>=0.27.0
23
+ Requires-Dist: keyring>=25.7.0
24
+ Requires-Dist: keyrings-alt>=5.0.2
25
+ Requires-Dist: tenacity>=8.2.3
26
+ Requires-Dist: cryptography>=43.0.0
27
+ Requires-Dist: toml>=0.10.2
28
+ Requires-Dist: pytest>=8.0.0 ; extra == 'test'
29
+ Requires-Dist: pytest-mock>=3.12.0 ; extra == 'test'
30
+ Requires-Python: >=3.11
31
+ Project-URL: Homepage, https://github.com/xenfra-cloud/xenfra-cli
32
+ Project-URL: Issues, https://github.com/xenfra-cloud/xenfra-cli/issues
33
+ Provides-Extra: test
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Xenfra CLI (The Interface) 🖥️
37
+
38
+ [![PyPI](https://img.shields.io/pypi/v/xenfra)](https://pypi.org/project/xenfra/)
39
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
40
+
41
+ The official command-line interface for **Xenfra** (The Sovereign Cloud OS). It empowers developers to deploy, monitor, and manage applications on their own infrastructure (DigitalOcean) with the ease of Heroku.
42
+
43
+ ## 🚀 Features
44
+
45
+ - **Zero-Config Deployment**: `xenfra deploy` detects your stack (Python, Node.js) and ships it.
46
+ - **Sovereign Auth**: `xenfra auth login` connects securely to your cloud provider.
47
+ - **Live Logs**: `xenfra logs` streams colorized, PII-scrubbed logs from your servers.
48
+ - **Doctor**: `xenfra doctor` runs a battery of health checks on your deployment environment.
49
+ - **Zen Mode**: Automatically applies fix patches when deployments fail.
50
+
51
+ ## 📦 Installation
52
+
53
+ ```bash
54
+ # Recommended: Install via uv
55
+ uv tool install xenfra
56
+
57
+ # Or via pip
58
+ pip install xenfra
59
+ ```
60
+
61
+ ## 🛠️ Quick Start
62
+
63
+ ### 1. Login
64
+
65
+ Authenticate with your cloud provider (DigitalOcean via Xenfra Platform).
66
+
67
+ ```bash
68
+ xenfra auth login
69
+ ```
70
+
71
+ ### 2. Deploy Your App
72
+
73
+ Navigate to your project directory and blast off.
74
+
75
+ ```bash
76
+ cd ~/my-projects/awesome-api
77
+ xenfra deploy
78
+ ```
79
+
80
+ _That's it._ Xenfra handles Dockerfile generation, server provisioning, SSL (Caddy), and database connections.
81
+
82
+ ### 3. Check Status
83
+
84
+ ```bash
85
+ xenfra status
86
+ ```
87
+
88
+ ## 🎛️ Command Reference
89
+
90
+ | Command | Description |
91
+ | :------------------ | :-------------------------------------- |
92
+ | `xenfra auth login` | Start the OAuth flow |
93
+ | `xenfra deploy` | Deploy current directory |
94
+ | `xenfra logs` | Tail logs (Ctrl+C to stop) |
95
+ | `xenfra status` | Show health metrics (CPU/RAM) |
96
+ | `xenfra list` | List all your projects |
97
+ | `xenfra init` | Generate config files without deploying |
98
+
99
+ ## 🔗 The Xenfra Ecosystem
100
+
101
+ This CLI is the "Interface" of the Xenfra Open Core architecture:
102
+
103
+ - **[xenfra-sdk](https://github.com/xenfracloud/xenfra-sdk)**: The Core Engine (Used by this CLI).
104
+ - **[xenfra-mcp](https://github.com/xenfracloud/xenfra-mcp)**: The AI Agent Interface.
105
+ - **xenfra-platform**: The Private SaaS Backend.
106
+
107
+ ## 🤝 Contributing
108
+
109
+ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
110
+
111
+ ## 📄 License
112
+
113
+ MIT © [Xenfra Cloud](https://xenfra.tech)
xenfra-0.4.4/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Xenfra CLI (The Interface) 🖥️
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/xenfra)](https://pypi.org/project/xenfra/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ The official command-line interface for **Xenfra** (The Sovereign Cloud OS). It empowers developers to deploy, monitor, and manage applications on their own infrastructure (DigitalOcean) with the ease of Heroku.
7
+
8
+ ## 🚀 Features
9
+
10
+ - **Zero-Config Deployment**: `xenfra deploy` detects your stack (Python, Node.js) and ships it.
11
+ - **Sovereign Auth**: `xenfra auth login` connects securely to your cloud provider.
12
+ - **Live Logs**: `xenfra logs` streams colorized, PII-scrubbed logs from your servers.
13
+ - **Doctor**: `xenfra doctor` runs a battery of health checks on your deployment environment.
14
+ - **Zen Mode**: Automatically applies fix patches when deployments fail.
15
+
16
+ ## 📦 Installation
17
+
18
+ ```bash
19
+ # Recommended: Install via uv
20
+ uv tool install xenfra
21
+
22
+ # Or via pip
23
+ pip install xenfra
24
+ ```
25
+
26
+ ## 🛠️ Quick Start
27
+
28
+ ### 1. Login
29
+
30
+ Authenticate with your cloud provider (DigitalOcean via Xenfra Platform).
31
+
32
+ ```bash
33
+ xenfra auth login
34
+ ```
35
+
36
+ ### 2. Deploy Your App
37
+
38
+ Navigate to your project directory and blast off.
39
+
40
+ ```bash
41
+ cd ~/my-projects/awesome-api
42
+ xenfra deploy
43
+ ```
44
+
45
+ _That's it._ Xenfra handles Dockerfile generation, server provisioning, SSL (Caddy), and database connections.
46
+
47
+ ### 3. Check Status
48
+
49
+ ```bash
50
+ xenfra status
51
+ ```
52
+
53
+ ## 🎛️ Command Reference
54
+
55
+ | Command | Description |
56
+ | :------------------ | :-------------------------------------- |
57
+ | `xenfra auth login` | Start the OAuth flow |
58
+ | `xenfra deploy` | Deploy current directory |
59
+ | `xenfra logs` | Tail logs (Ctrl+C to stop) |
60
+ | `xenfra status` | Show health metrics (CPU/RAM) |
61
+ | `xenfra list` | List all your projects |
62
+ | `xenfra init` | Generate config files without deploying |
63
+
64
+ ## 🔗 The Xenfra Ecosystem
65
+
66
+ This CLI is the "Interface" of the Xenfra Open Core architecture:
67
+
68
+ - **[xenfra-sdk](https://github.com/xenfracloud/xenfra-sdk)**: The Core Engine (Used by this CLI).
69
+ - **[xenfra-mcp](https://github.com/xenfracloud/xenfra-mcp)**: The AI Agent Interface.
70
+ - **xenfra-platform**: The Private SaaS Backend.
71
+
72
+ ## 🤝 Contributing
73
+
74
+ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
75
+
76
+ ## 📄 License
77
+
78
+ MIT © [Xenfra Cloud](https://xenfra.tech)
@@ -1,56 +1,54 @@
1
- [project]
2
- name = "xenfra"
3
- version = "0.4.3"
4
- description = "A 'Zen Mode' infrastructure engine for Python developers."
5
- readme = "README.md"
6
- authors = [
7
- { name = "xenfra-cloud", email = "xenfracloud@gmail.com" }
8
- ]
9
-
10
- classifiers = [
11
- "Programming Language :: Python :: 3",
12
- "License :: OSI Approved :: MIT License",
13
- "Operating System :: OS Independent",
14
- "Development Status :: 3 - Alpha",
15
- "Intended Audience :: Developers",
16
- "Topic :: Software Development :: Build Tools",
17
- "Topic :: System :: Systems Administration",
18
- ]
19
-
20
- dependencies = [
21
- "click>=8.1.7",
22
- "rich>=14.2.0",
23
- "sqlmodel>=0.0.16",
24
- "python-digitalocean>=1.17.0",
25
- "python-dotenv>=1.2.1",
26
- "pyyaml>=6.0.1",
27
- "fabric>=3.2.2",
28
- "xenfra-sdk",
29
- "httpx>=0.27.0",
30
- "keyring>=25.7.0",
31
- "keyrings.alt>=5.0.2",
32
- "tenacity>=8.2.3", # For retry logic
33
- "cryptography>=43.0.0", # For encrypted file-based token storage
34
- "toml>=0.10.2",
35
- ]
36
- requires-python = ">=3.13"
37
-
38
- [tool.uv.sources]
39
- xenfra-sdk = { workspace = true }
40
-
41
- [project.urls]
42
- Homepage = "https://github.com/xenfra-cloud/xenfra"
43
- Issues = "https://github.com/xenfra-cloud/xenfra/issues"
44
-
45
- [project.optional-dependencies]
46
- test = [
47
- "pytest>=8.0.0",
48
- "pytest-mock>=3.12.0",
49
- ]
50
-
51
- [project.scripts]
52
- xenfra = "xenfra.main:main"
53
-
54
- [build-system]
55
- requires = ["uv_build>=0.9.18,<0.10.0"]
56
- build-backend = "uv_build"
1
+ [project]
2
+ name = "xenfra"
3
+ version = "0.4.4"
4
+ description = "Xenfra CLI: Hands for AI to deploy on DigitalOcean."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "xenfra-cloud", email = "xenfracloud@gmail.com" }
8
+ ]
9
+
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Operating System :: OS Independent",
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "Topic :: Software Development :: Build Tools",
17
+ "Topic :: System :: Systems Administration",
18
+ ]
19
+
20
+ dependencies = [
21
+ "click>=8.1.7",
22
+ "rich>=14.2.0",
23
+ "sqlmodel>=0.0.16",
24
+ "python-digitalocean>=1.17.0",
25
+ "python-dotenv>=1.2.1",
26
+ "pyyaml>=6.0.1",
27
+ "fabric>=3.2.2",
28
+ "xenfra-sdk>=0.2.2",
29
+ "httpx>=0.27.0",
30
+ "keyring>=25.7.0",
31
+ "keyrings.alt>=5.0.2",
32
+ "tenacity>=8.2.3", # For retry logic
33
+ "cryptography>=43.0.0", # For encrypted file-based token storage
34
+ "toml>=0.10.2",
35
+ ]
36
+ requires-python = ">=3.11"
37
+
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/xenfra-cloud/xenfra-cli"
41
+ Issues = "https://github.com/xenfra-cloud/xenfra-cli/issues"
42
+
43
+ [project.optional-dependencies]
44
+ test = [
45
+ "pytest>=8.0.0",
46
+ "pytest-mock>=3.12.0",
47
+ ]
48
+
49
+ [project.scripts]
50
+ xenfra = "xenfra.main:main"
51
+
52
+ [build-system]
53
+ requires = ["uv_build>=0.9.18,<0.10.0"]
54
+ build-backend = "uv_build"
@@ -1,3 +1,3 @@
1
- """
2
- CLI command modules for Xenfra.
3
- """
1
+ """
2
+ CLI command modules for Xenfra.
3
+ """
@@ -1,144 +1,144 @@
1
- """
2
- Authentication commands for Xenfra CLI.
3
- """
4
-
5
- import base64
6
- import hashlib
7
- import secrets
8
- import urllib.parse
9
- import webbrowser
10
- from http.server import HTTPServer
11
-
12
- import click
13
- import httpx
14
- import keyring
15
- from rich.console import Console
16
- from tenacity import (
17
- retry,
18
- retry_if_exception_type,
19
- stop_after_attempt,
20
- wait_exponential,
21
- )
22
-
23
- from ..utils.auth import (
24
- API_BASE_URL,
25
- CLI_CLIENT_ID,
26
- CLI_LOCAL_SERVER_END_PORT,
27
- CLI_LOCAL_SERVER_START_PORT,
28
- CLI_REDIRECT_PATH,
29
- SERVICE_ID,
30
- AuthCallbackHandler,
31
- clear_tokens,
32
- get_auth_token,
33
- )
34
-
35
- console = Console()
36
-
37
- # HTTP request timeout (30 seconds)
38
- HTTP_TIMEOUT = 30.0
39
-
40
-
41
- @click.group()
42
- def auth():
43
- """Authentication commands (login, logout, whoami)."""
44
- pass
45
-
46
-
47
- @retry(
48
- stop=stop_after_attempt(3),
49
- wait=wait_exponential(multiplier=1, min=2, max=10),
50
- retry=retry_if_exception_type((httpx.TimeoutException, httpx.NetworkError)),
51
- reraise=True,
52
- )
53
- def _exchange_code_for_tokens_with_retry(code: str, code_verifier: str, redirect_uri: str) -> dict:
54
- """
55
- Exchange authorization code for tokens with retry logic.
56
-
57
- Returns token data dictionary.
58
- """
59
- with httpx.Client(timeout=HTTP_TIMEOUT) as client:
60
- response = client.post(
61
- f"{API_BASE_URL}/auth/token",
62
- data={
63
- "grant_type": "authorization_code",
64
- "client_id": CLI_CLIENT_ID,
65
- "code": code,
66
- "code_verifier": code_verifier,
67
- "redirect_uri": redirect_uri,
68
- },
69
- headers={"Accept": "application/json"},
70
- )
71
- response.raise_for_status()
72
-
73
- # Safe JSON parsing with content-type check
74
- content_type = response.headers.get("content-type", "")
75
- if "application/json" not in content_type:
76
- raise ValueError(f"Expected JSON response, got {content_type}")
77
-
78
- try:
79
- token_data = response.json()
80
- except (ValueError, TypeError) as e:
81
- raise ValueError(f"Failed to parse JSON response: {e}")
82
-
83
- return token_data
84
-
85
-
86
- @auth.command()
87
- def login():
88
- """Login to Xenfra using Device Authorization Flow (like GitHub CLI, Claude Code)."""
89
- from .auth_device import device_login
90
- device_login()
91
-
92
- # Removed old PKCE flow - now using Device Authorization Flow
93
-
94
-
95
- @auth.command()
96
- def logout():
97
- """Logout and clear stored tokens."""
98
- try:
99
- clear_tokens()
100
- console.print("[bold green]Logged out successfully.[/bold green]")
101
- except Exception as e:
102
- console.print(f"[yellow]Warning: Error during logout: {e}[/yellow]")
103
- console.print("[dim]Tokens may still be stored in keyring.[/dim]")
104
-
105
-
106
- @auth.command()
107
- @click.option("--token", is_flag=True, help="Show access token")
108
- def whoami(token):
109
- """Show current authenticated user."""
110
- access_token = get_auth_token()
111
-
112
- if not access_token:
113
- console.print("[bold red]Not logged in. Run 'xenfra login' first.[/bold red]")
114
- return
115
-
116
- try:
117
- import base64
118
- import json
119
-
120
- # Manually decode JWT payload without verification
121
- # JWT format: header.payload.signature
122
- parts = access_token.split(".")
123
- if len(parts) != 3:
124
- console.print("[bold red]Invalid token format[/bold red]")
125
- return
126
-
127
- # Decode payload (second part)
128
- payload_b64 = parts[1]
129
- # Add padding if needed
130
- padding = 4 - len(payload_b64) % 4
131
- if padding != 4:
132
- payload_b64 += "=" * padding
133
-
134
- payload_bytes = base64.urlsafe_b64decode(payload_b64)
135
- claims = json.loads(payload_bytes)
136
-
137
- console.print("[bold green]Logged in as:[/bold green]")
138
- console.print(f" Email: {claims.get('sub', 'N/A')}")
139
- console.print(f" User ID: {claims.get('user_id', 'N/A')}")
140
-
141
- if token:
142
- console.print(f"\n[dim]Access Token:[/dim]\n{access_token}")
143
- except Exception as e:
144
- console.print(f"[bold red]Failed to decode token: {e}[/bold red]")
1
+ """
2
+ Authentication commands for Xenfra CLI.
3
+ """
4
+
5
+ import base64
6
+ import hashlib
7
+ import secrets
8
+ import urllib.parse
9
+ import webbrowser
10
+ from http.server import HTTPServer
11
+
12
+ import click
13
+ import httpx
14
+ import keyring
15
+ from rich.console import Console
16
+ from tenacity import (
17
+ retry,
18
+ retry_if_exception_type,
19
+ stop_after_attempt,
20
+ wait_exponential,
21
+ )
22
+
23
+ from ..utils.auth import (
24
+ API_BASE_URL,
25
+ CLI_CLIENT_ID,
26
+ CLI_LOCAL_SERVER_END_PORT,
27
+ CLI_LOCAL_SERVER_START_PORT,
28
+ CLI_REDIRECT_PATH,
29
+ SERVICE_ID,
30
+ AuthCallbackHandler,
31
+ clear_tokens,
32
+ get_auth_token,
33
+ )
34
+
35
+ console = Console()
36
+
37
+ # HTTP request timeout (30 seconds)
38
+ HTTP_TIMEOUT = 30.0
39
+
40
+
41
+ @click.group()
42
+ def auth():
43
+ """Authentication commands (login, logout, whoami)."""
44
+ pass
45
+
46
+
47
+ @retry(
48
+ stop=stop_after_attempt(3),
49
+ wait=wait_exponential(multiplier=1, min=2, max=10),
50
+ retry=retry_if_exception_type((httpx.TimeoutException, httpx.NetworkError)),
51
+ reraise=True,
52
+ )
53
+ def _exchange_code_for_tokens_with_retry(code: str, code_verifier: str, redirect_uri: str) -> dict:
54
+ """
55
+ Exchange authorization code for tokens with retry logic.
56
+
57
+ Returns token data dictionary.
58
+ """
59
+ with httpx.Client(timeout=HTTP_TIMEOUT) as client:
60
+ response = client.post(
61
+ f"{API_BASE_URL}/auth/token",
62
+ data={
63
+ "grant_type": "authorization_code",
64
+ "client_id": CLI_CLIENT_ID,
65
+ "code": code,
66
+ "code_verifier": code_verifier,
67
+ "redirect_uri": redirect_uri,
68
+ },
69
+ headers={"Accept": "application/json"},
70
+ )
71
+ response.raise_for_status()
72
+
73
+ # Safe JSON parsing with content-type check
74
+ content_type = response.headers.get("content-type", "")
75
+ if "application/json" not in content_type:
76
+ raise ValueError(f"Expected JSON response, got {content_type}")
77
+
78
+ try:
79
+ token_data = response.json()
80
+ except (ValueError, TypeError) as e:
81
+ raise ValueError(f"Failed to parse JSON response: {e}")
82
+
83
+ return token_data
84
+
85
+
86
+ @auth.command()
87
+ def login():
88
+ """Login to Xenfra using Device Authorization Flow (like GitHub CLI, Claude Code)."""
89
+ from .auth_device import device_login
90
+ device_login()
91
+
92
+ # Removed old PKCE flow - now using Device Authorization Flow
93
+
94
+
95
+ @auth.command()
96
+ def logout():
97
+ """Logout and clear stored tokens."""
98
+ try:
99
+ clear_tokens()
100
+ console.print("[bold green]Logged out successfully.[/bold green]")
101
+ except Exception as e:
102
+ console.print(f"[yellow]Warning: Error during logout: {e}[/yellow]")
103
+ console.print("[dim]Tokens may still be stored in keyring.[/dim]")
104
+
105
+
106
+ @auth.command()
107
+ @click.option("--token", is_flag=True, help="Show access token")
108
+ def whoami(token):
109
+ """Show current authenticated user."""
110
+ access_token = get_auth_token()
111
+
112
+ if not access_token:
113
+ console.print("[bold red]Not logged in. Run 'xenfra login' first.[/bold red]")
114
+ return
115
+
116
+ try:
117
+ import base64
118
+ import json
119
+
120
+ # Manually decode JWT payload without verification
121
+ # JWT format: header.payload.signature
122
+ parts = access_token.split(".")
123
+ if len(parts) != 3:
124
+ console.print("[bold red]Invalid token format[/bold red]")
125
+ return
126
+
127
+ # Decode payload (second part)
128
+ payload_b64 = parts[1]
129
+ # Add padding if needed
130
+ padding = 4 - len(payload_b64) % 4
131
+ if padding != 4:
132
+ payload_b64 += "=" * padding
133
+
134
+ payload_bytes = base64.urlsafe_b64decode(payload_b64)
135
+ claims = json.loads(payload_bytes)
136
+
137
+ console.print("[bold green]Logged in as:[/bold green]")
138
+ console.print(f" Email: {claims.get('sub', 'N/A')}")
139
+ console.print(f" User ID: {claims.get('user_id', 'N/A')}")
140
+
141
+ if token:
142
+ console.print(f"\n[dim]Access Token:[/dim]\n{access_token}")
143
+ except Exception as e:
144
+ console.print(f"[bold red]Failed to decode token: {e}[/bold red]")