nexinal 1.0.2__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.
- nexinal-1.0.2/PKG-INFO +133 -0
- nexinal-1.0.2/README.md +94 -0
- nexinal-1.0.2/nexinal/__init__.py +4 -0
- nexinal-1.0.2/nexinal/__main__.py +6 -0
- nexinal-1.0.2/nexinal/api.py +149 -0
- nexinal-1.0.2/nexinal/cli.py +359 -0
- nexinal-1.0.2/nexinal/config.py +75 -0
- nexinal-1.0.2/nexinal/display.py +187 -0
- nexinal-1.0.2/nexinal/runner.py +135 -0
- nexinal-1.0.2/nexinal/yaml_parser.py +79 -0
- nexinal-1.0.2/nexinal.egg-info/PKG-INFO +133 -0
- nexinal-1.0.2/nexinal.egg-info/SOURCES.txt +18 -0
- nexinal-1.0.2/nexinal.egg-info/dependency_links.txt +1 -0
- nexinal-1.0.2/nexinal.egg-info/entry_points.txt +2 -0
- nexinal-1.0.2/nexinal.egg-info/requires.txt +8 -0
- nexinal-1.0.2/nexinal.egg-info/top_level.txt +1 -0
- nexinal-1.0.2/pyproject.toml +54 -0
- nexinal-1.0.2/setup.cfg +4 -0
- nexinal-1.0.2/setup.py +51 -0
- nexinal-1.0.2/tests/test_cli.py +170 -0
nexinal-1.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nexinal
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: CLI client for Nexuss Bash remote execution API
|
|
5
|
+
Home-page: https://github.com/nexuss0781/Nexuss-Bash
|
|
6
|
+
Author: Nexuss
|
|
7
|
+
Author-email: Nexuss <nexuss@proton.me>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/nexuss0781/Nexuss-Bash
|
|
10
|
+
Project-URL: Bug Reports, https://github.com/nexuss0781/Nexuss-Bash/issues
|
|
11
|
+
Project-URL: Source, https://github.com/nexuss0781/Nexuss-Bash
|
|
12
|
+
Keywords: cli,bash,remote,execution,api,nexuss,nexinal
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration
|
|
25
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
26
|
+
Classifier: Topic :: Utilities
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
Requires-Dist: click>=8.0
|
|
30
|
+
Requires-Dist: requests>=2.28
|
|
31
|
+
Requires-Dist: pyyaml>=6.0
|
|
32
|
+
Requires-Dist: rich>=13.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
36
|
+
Dynamic: author
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: requires-python
|
|
39
|
+
|
|
40
|
+
# nexinal
|
|
41
|
+
|
|
42
|
+
CLI client for the **Nexuss Bash** remote execution API. Run shell commands on a remote server directly from your terminal.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install nexinal
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or from source:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/nexuss0781/Nexuss-Bash
|
|
54
|
+
cd Nexuss-Bash/cli
|
|
55
|
+
pip install -e .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Authenticate
|
|
62
|
+
nexinal auth YOUR_API_TOKEN
|
|
63
|
+
|
|
64
|
+
# Run commands
|
|
65
|
+
nexinal run "echo hello" "whoami" "ls /workspace"
|
|
66
|
+
|
|
67
|
+
# Execute a YAML file
|
|
68
|
+
nexinal execute runbook.yaml
|
|
69
|
+
|
|
70
|
+
# Check status
|
|
71
|
+
nexinal status
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Commands
|
|
75
|
+
|
|
76
|
+
| Command | Description |
|
|
77
|
+
|---------|-------------|
|
|
78
|
+
| `nexinal auth <token>` | Save authentication token |
|
|
79
|
+
| `nexinal run <commands...>` | Run shell commands sequentially |
|
|
80
|
+
| `nexinal execute <yaml_file>` | Execute commands from a YAML file |
|
|
81
|
+
| `nexinal status` | Show connection and token info |
|
|
82
|
+
| `nexinal history` | List past runs |
|
|
83
|
+
| `nexinal health` | Quick health check |
|
|
84
|
+
| `nexinal sessions` | List active sessions |
|
|
85
|
+
| `nexinal packages list` | List installed packages |
|
|
86
|
+
| `nexinal packages install <name>` | Install a package |
|
|
87
|
+
| `nexinal config` | Show or set API URL |
|
|
88
|
+
| `nexinal logout` | Remove saved token |
|
|
89
|
+
|
|
90
|
+
## YAML File Format
|
|
91
|
+
|
|
92
|
+
### Simple format
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
commands:
|
|
96
|
+
- "echo hello"
|
|
97
|
+
- "whoami"
|
|
98
|
+
- "ls -la /workspace"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Detailed format
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
commands:
|
|
105
|
+
- name: "Print hello"
|
|
106
|
+
command: "echo hello"
|
|
107
|
+
timeout: 30
|
|
108
|
+
stop_on_fail: true
|
|
109
|
+
- name: "List files"
|
|
110
|
+
command: "ls -la"
|
|
111
|
+
timeout: 15
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Configuration
|
|
115
|
+
|
|
116
|
+
Config is stored at `~/.nexinal/config.json`.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Set custom API URL
|
|
120
|
+
nexinal config set https://my-server.example.com
|
|
121
|
+
|
|
122
|
+
# Show current config
|
|
123
|
+
nexinal config
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Environment
|
|
127
|
+
|
|
128
|
+
- Python 3.8+
|
|
129
|
+
- Works on Linux, macOS, Windows
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
nexinal-1.0.2/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# nexinal
|
|
2
|
+
|
|
3
|
+
CLI client for the **Nexuss Bash** remote execution API. Run shell commands on a remote server directly from your terminal.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install nexinal
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or from source:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
git clone https://github.com/nexuss0781/Nexuss-Bash
|
|
15
|
+
cd Nexuss-Bash/cli
|
|
16
|
+
pip install -e .
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Authenticate
|
|
23
|
+
nexinal auth YOUR_API_TOKEN
|
|
24
|
+
|
|
25
|
+
# Run commands
|
|
26
|
+
nexinal run "echo hello" "whoami" "ls /workspace"
|
|
27
|
+
|
|
28
|
+
# Execute a YAML file
|
|
29
|
+
nexinal execute runbook.yaml
|
|
30
|
+
|
|
31
|
+
# Check status
|
|
32
|
+
nexinal status
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
| Command | Description |
|
|
38
|
+
|---------|-------------|
|
|
39
|
+
| `nexinal auth <token>` | Save authentication token |
|
|
40
|
+
| `nexinal run <commands...>` | Run shell commands sequentially |
|
|
41
|
+
| `nexinal execute <yaml_file>` | Execute commands from a YAML file |
|
|
42
|
+
| `nexinal status` | Show connection and token info |
|
|
43
|
+
| `nexinal history` | List past runs |
|
|
44
|
+
| `nexinal health` | Quick health check |
|
|
45
|
+
| `nexinal sessions` | List active sessions |
|
|
46
|
+
| `nexinal packages list` | List installed packages |
|
|
47
|
+
| `nexinal packages install <name>` | Install a package |
|
|
48
|
+
| `nexinal config` | Show or set API URL |
|
|
49
|
+
| `nexinal logout` | Remove saved token |
|
|
50
|
+
|
|
51
|
+
## YAML File Format
|
|
52
|
+
|
|
53
|
+
### Simple format
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
commands:
|
|
57
|
+
- "echo hello"
|
|
58
|
+
- "whoami"
|
|
59
|
+
- "ls -la /workspace"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Detailed format
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
commands:
|
|
66
|
+
- name: "Print hello"
|
|
67
|
+
command: "echo hello"
|
|
68
|
+
timeout: 30
|
|
69
|
+
stop_on_fail: true
|
|
70
|
+
- name: "List files"
|
|
71
|
+
command: "ls -la"
|
|
72
|
+
timeout: 15
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
Config is stored at `~/.nexinal/config.json`.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Set custom API URL
|
|
81
|
+
nexinal config set https://my-server.example.com
|
|
82
|
+
|
|
83
|
+
# Show current config
|
|
84
|
+
nexinal config
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Environment
|
|
88
|
+
|
|
89
|
+
- Python 3.8+
|
|
90
|
+
- Works on Linux, macOS, Windows
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""API client for Nexuss Bash."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional, Any
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from requests.exceptions import ConnectionError, HTTPError, RequestException, Timeout
|
|
8
|
+
|
|
9
|
+
from nexinal.config import get_api_url, get_token
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class APIError(Exception):
|
|
13
|
+
def __init__(self, message: str, status_code: Optional[int] = None):
|
|
14
|
+
self.message = message
|
|
15
|
+
self.status_code = status_code
|
|
16
|
+
super().__init__(self.message)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AuthError(APIError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConnectionError_(APIError):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _build_headers() -> dict:
|
|
28
|
+
headers = {"Content-Type": "application/json"}
|
|
29
|
+
token = get_token()
|
|
30
|
+
if token:
|
|
31
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
32
|
+
return headers
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _build_url(path: str) -> str:
|
|
36
|
+
base = get_api_url().rstrip("/")
|
|
37
|
+
path = path.lstrip("/")
|
|
38
|
+
return f"{base}/{path}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def api_get(path: str) -> dict:
|
|
42
|
+
url = _build_url(path)
|
|
43
|
+
try:
|
|
44
|
+
resp = requests.get(url, headers=_build_headers(), timeout=30)
|
|
45
|
+
resp.raise_for_status()
|
|
46
|
+
return resp.json()
|
|
47
|
+
except HTTPError as e:
|
|
48
|
+
status = e.response.status_code if e.response is not None else None
|
|
49
|
+
if status == 401:
|
|
50
|
+
raise AuthError("Authentication failed. Run: nexinal auth <token>", status)
|
|
51
|
+
if status == 404:
|
|
52
|
+
raise APIError(f"Not found: {path}", status)
|
|
53
|
+
raise APIError(f"HTTP {status}: {e}", status)
|
|
54
|
+
except ConnectionError:
|
|
55
|
+
raise ConnectionError_(f"Cannot connect to {url}. Check your network and API URL.")
|
|
56
|
+
except Timeout:
|
|
57
|
+
raise APIError(f"Request timed out: {url}")
|
|
58
|
+
except RequestException as e:
|
|
59
|
+
raise APIError(f"Request failed: {e}")
|
|
60
|
+
except json.JSONDecodeError:
|
|
61
|
+
raise APIError("Invalid JSON response from server")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def api_post(path: str, data: Optional[dict] = None) -> dict:
|
|
65
|
+
url = _build_url(path)
|
|
66
|
+
try:
|
|
67
|
+
resp = requests.post(url, headers=_build_headers(), json=data or {}, timeout=30)
|
|
68
|
+
resp.raise_for_status()
|
|
69
|
+
return resp.json()
|
|
70
|
+
except HTTPError as e:
|
|
71
|
+
status = e.response.status_code if e.response is not None else None
|
|
72
|
+
if status == 401:
|
|
73
|
+
raise AuthError("Authentication failed. Run: nexinal auth <token>", status)
|
|
74
|
+
if status == 404:
|
|
75
|
+
raise APIError(f"Not found: {path}", status)
|
|
76
|
+
body = ""
|
|
77
|
+
if e.response is not None:
|
|
78
|
+
try:
|
|
79
|
+
body = e.response.json().get("detail", e.response.text)
|
|
80
|
+
except Exception:
|
|
81
|
+
body = e.response.text
|
|
82
|
+
raise APIError(f"HTTP {status}: {body}", status)
|
|
83
|
+
except ConnectionError:
|
|
84
|
+
raise ConnectionError_(f"Cannot connect to {url}. Check your network and API URL.")
|
|
85
|
+
except Timeout:
|
|
86
|
+
raise APIError(f"Request timed out: {url}")
|
|
87
|
+
except RequestException as e:
|
|
88
|
+
raise APIError(f"Request failed: {e}")
|
|
89
|
+
except json.JSONDecodeError:
|
|
90
|
+
raise APIError("Invalid JSON response from server")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def api_post_file(path: str, file_path: str) -> dict:
|
|
94
|
+
url = _build_url(path)
|
|
95
|
+
token = get_token()
|
|
96
|
+
headers = {}
|
|
97
|
+
if token:
|
|
98
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
99
|
+
try:
|
|
100
|
+
with open(file_path, "rb") as f:
|
|
101
|
+
files = {"file": (file_path.split("/")[-1], f)}
|
|
102
|
+
resp = requests.post(url, headers=headers, files=files, timeout=60)
|
|
103
|
+
resp.raise_for_status()
|
|
104
|
+
return resp.json()
|
|
105
|
+
except HTTPError as e:
|
|
106
|
+
status = e.response.status_code if e.response is not None else None
|
|
107
|
+
if status == 401:
|
|
108
|
+
raise AuthError("Authentication failed. Run: nexinal auth <token>", status)
|
|
109
|
+
raise APIError(f"HTTP {status}: {e}", status)
|
|
110
|
+
except ConnectionError:
|
|
111
|
+
raise ConnectionError_(f"Cannot connect to {url}. Check your network and API URL.")
|
|
112
|
+
except Timeout:
|
|
113
|
+
raise APIError(f"Request timed out: {url}")
|
|
114
|
+
except RequestException as e:
|
|
115
|
+
raise APIError(f"Request failed: {e}")
|
|
116
|
+
except FileNotFoundError:
|
|
117
|
+
raise APIError(f"File not found: {file_path}")
|
|
118
|
+
except json.JSONDecodeError:
|
|
119
|
+
raise APIError("Invalid JSON response from server")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def api_delete(path: str) -> dict:
|
|
123
|
+
url = _build_url(path)
|
|
124
|
+
try:
|
|
125
|
+
resp = requests.delete(url, headers=_build_headers(), timeout=30)
|
|
126
|
+
resp.raise_for_status()
|
|
127
|
+
return resp.json()
|
|
128
|
+
except HTTPError as e:
|
|
129
|
+
status = e.response.status_code if e.response is not None else None
|
|
130
|
+
if status == 401:
|
|
131
|
+
raise AuthError("Authentication failed. Run: nexinal auth <token>", status)
|
|
132
|
+
raise APIError(f"HTTP {status}: {e}", status)
|
|
133
|
+
except ConnectionError:
|
|
134
|
+
raise ConnectionError_(f"Cannot connect to {url}. Check your network and API URL.")
|
|
135
|
+
except Timeout:
|
|
136
|
+
raise APIError(f"Request timed out: {url}")
|
|
137
|
+
except RequestException as e:
|
|
138
|
+
raise APIError(f"Request failed: {e}")
|
|
139
|
+
except json.JSONDecodeError:
|
|
140
|
+
raise APIError("Invalid JSON response from server")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_connection() -> bool:
|
|
144
|
+
try:
|
|
145
|
+
url = _build_url("/health")
|
|
146
|
+
resp = requests.get(url, timeout=10)
|
|
147
|
+
return resp.status_code == 200
|
|
148
|
+
except Exception:
|
|
149
|
+
return False
|