fluxhive-cli 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.
@@ -0,0 +1,23 @@
1
+ FluxHive License
2
+
3
+ Copyright (c) 2025 FluxHive
4
+
5
+ This repository contains multiple components with different licenses:
6
+
7
+ 1. Agent Component (app-agent/)
8
+ Licensed under the FluxHive Agent Non-Commercial Copyleft License v1.0.
9
+ - Open source: You may view, modify, and distribute the source code
10
+ - Non-commercial: Commercial use is prohibited (separate commercial license required)
11
+ - Copyleft: Modifications and derivative works must remain open source
12
+ See app-agent/LICENSE for full details.
13
+
14
+ 2. Control Server (app-control/)
15
+ Proprietary and confidential.
16
+ Unauthorized copying or distribution is strictly prohibited.
17
+
18
+ 3. Web Client (app-web/)
19
+ Proprietary and confidential.
20
+ Unauthorized copying or distribution is strictly prohibited.
21
+
22
+ For commercial licensing or enterprise support, please contact:
23
+ [dramwig@gmail.com]
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: fluxhive-cli
3
+ Version: 0.1.0
4
+ Summary: FluxHive control-plane command line client
5
+ Author: FluxHive Team
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Dynamic: license-file
10
+
11
+ # @fluxhive/cli
12
+
13
+ Node-installable launcher and Python implementation for the FluxHive control-plane CLI.
14
+
15
+ The CLI talks to Control Server HTTP APIs and does not implement scheduling or agent runtime logic locally.
16
+
17
+ The package exposes two equivalent commands:
18
+
19
+ ```bash
20
+ fluxhive
21
+ fluxhivectl
22
+ ```
23
+
24
+ ## npm Install
25
+
26
+ ```bash
27
+ npm install -g @fluxhive/cli
28
+ ```
29
+
30
+ ## PyPI Install
31
+
32
+ ```bash
33
+ pipx install fluxhive-cli
34
+ ```
35
+
36
+ The compatibility package `fluxhivectl` also installs the same CLI:
37
+
38
+ ```bash
39
+ pipx install fluxhivectl
40
+ ```
41
+
42
+ Or with pip:
43
+
44
+ ```bash
45
+ python -m pip install fluxhive-cli
46
+ ```
47
+
48
+ Python 3.10 or newer must be available on `PATH`. You can also point the launcher at a specific Python executable:
49
+
50
+ ```bash
51
+ FLUXHIVE_PYTHON=/path/to/python fluxhive --version
52
+ ```
53
+
54
+ On Windows PowerShell:
55
+
56
+ ```powershell
57
+ $env:FLUXHIVE_PYTHON = "C:\Python312\python.exe"
58
+ fluxhive --version
59
+ ```
60
+
61
+ ## Development Install
62
+
63
+ ```bash
64
+ python -m pip install -e .
65
+ npm test
66
+ ```
67
+
68
+ ## Quick Start
69
+
70
+ ```bash
71
+ fluxhive config base_url http://127.0.0.1:8001
72
+ fluxhive auth login --username <username>
73
+
74
+ fluxhive jobs list
75
+ fluxhive agents list
76
+ fluxhive runs list --limit 20
77
+ ```
78
+
79
+ You can also use environment variables:
80
+
81
+ ```bash
82
+ FLUXHIVE_CONTROL_URL=http://127.0.0.1:8001
83
+ FLUXHIVE_ACCESS_TOKEN=<access-token>
84
+ FLUXHIVE_REFRESH_TOKEN=<refresh-token>
85
+ ```
86
+
87
+ ## Command Groups
88
+
89
+ - `auth`: login, refresh tokens, inspect current user, clear local tokens
90
+ - `config`: read and write local CLI configuration
91
+ - `jobs`: create, list, inspect, publish, run, cancel, clone, and delete jobs
92
+ - `runs`: list run attempts, inspect run attempts, print stored run logs
93
+ - `agents`: list agents, inspect GPUs, inspect queues, rename agents
94
+ - `nodes`: alias for `agents`
@@ -0,0 +1,84 @@
1
+ # @fluxhive/cli
2
+
3
+ Node-installable launcher and Python implementation for the FluxHive control-plane CLI.
4
+
5
+ The CLI talks to Control Server HTTP APIs and does not implement scheduling or agent runtime logic locally.
6
+
7
+ The package exposes two equivalent commands:
8
+
9
+ ```bash
10
+ fluxhive
11
+ fluxhivectl
12
+ ```
13
+
14
+ ## npm Install
15
+
16
+ ```bash
17
+ npm install -g @fluxhive/cli
18
+ ```
19
+
20
+ ## PyPI Install
21
+
22
+ ```bash
23
+ pipx install fluxhive-cli
24
+ ```
25
+
26
+ The compatibility package `fluxhivectl` also installs the same CLI:
27
+
28
+ ```bash
29
+ pipx install fluxhivectl
30
+ ```
31
+
32
+ Or with pip:
33
+
34
+ ```bash
35
+ python -m pip install fluxhive-cli
36
+ ```
37
+
38
+ Python 3.10 or newer must be available on `PATH`. You can also point the launcher at a specific Python executable:
39
+
40
+ ```bash
41
+ FLUXHIVE_PYTHON=/path/to/python fluxhive --version
42
+ ```
43
+
44
+ On Windows PowerShell:
45
+
46
+ ```powershell
47
+ $env:FLUXHIVE_PYTHON = "C:\Python312\python.exe"
48
+ fluxhive --version
49
+ ```
50
+
51
+ ## Development Install
52
+
53
+ ```bash
54
+ python -m pip install -e .
55
+ npm test
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ```bash
61
+ fluxhive config base_url http://127.0.0.1:8001
62
+ fluxhive auth login --username <username>
63
+
64
+ fluxhive jobs list
65
+ fluxhive agents list
66
+ fluxhive runs list --limit 20
67
+ ```
68
+
69
+ You can also use environment variables:
70
+
71
+ ```bash
72
+ FLUXHIVE_CONTROL_URL=http://127.0.0.1:8001
73
+ FLUXHIVE_ACCESS_TOKEN=<access-token>
74
+ FLUXHIVE_REFRESH_TOKEN=<refresh-token>
75
+ ```
76
+
77
+ ## Command Groups
78
+
79
+ - `auth`: login, refresh tokens, inspect current user, clear local tokens
80
+ - `config`: read and write local CLI configuration
81
+ - `jobs`: create, list, inspect, publish, run, cancel, clone, and delete jobs
82
+ - `runs`: list run attempts, inspect run attempts, print stored run logs
83
+ - `agents`: list agents, inspect GPUs, inspect queues, rename agents
84
+ - `nodes`: alias for `agents`
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: fluxhive-cli
3
+ Version: 0.1.0
4
+ Summary: FluxHive control-plane command line client
5
+ Author: FluxHive Team
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Dynamic: license-file
10
+
11
+ # @fluxhive/cli
12
+
13
+ Node-installable launcher and Python implementation for the FluxHive control-plane CLI.
14
+
15
+ The CLI talks to Control Server HTTP APIs and does not implement scheduling or agent runtime logic locally.
16
+
17
+ The package exposes two equivalent commands:
18
+
19
+ ```bash
20
+ fluxhive
21
+ fluxhivectl
22
+ ```
23
+
24
+ ## npm Install
25
+
26
+ ```bash
27
+ npm install -g @fluxhive/cli
28
+ ```
29
+
30
+ ## PyPI Install
31
+
32
+ ```bash
33
+ pipx install fluxhive-cli
34
+ ```
35
+
36
+ The compatibility package `fluxhivectl` also installs the same CLI:
37
+
38
+ ```bash
39
+ pipx install fluxhivectl
40
+ ```
41
+
42
+ Or with pip:
43
+
44
+ ```bash
45
+ python -m pip install fluxhive-cli
46
+ ```
47
+
48
+ Python 3.10 or newer must be available on `PATH`. You can also point the launcher at a specific Python executable:
49
+
50
+ ```bash
51
+ FLUXHIVE_PYTHON=/path/to/python fluxhive --version
52
+ ```
53
+
54
+ On Windows PowerShell:
55
+
56
+ ```powershell
57
+ $env:FLUXHIVE_PYTHON = "C:\Python312\python.exe"
58
+ fluxhive --version
59
+ ```
60
+
61
+ ## Development Install
62
+
63
+ ```bash
64
+ python -m pip install -e .
65
+ npm test
66
+ ```
67
+
68
+ ## Quick Start
69
+
70
+ ```bash
71
+ fluxhive config base_url http://127.0.0.1:8001
72
+ fluxhive auth login --username <username>
73
+
74
+ fluxhive jobs list
75
+ fluxhive agents list
76
+ fluxhive runs list --limit 20
77
+ ```
78
+
79
+ You can also use environment variables:
80
+
81
+ ```bash
82
+ FLUXHIVE_CONTROL_URL=http://127.0.0.1:8001
83
+ FLUXHIVE_ACCESS_TOKEN=<access-token>
84
+ FLUXHIVE_REFRESH_TOKEN=<refresh-token>
85
+ ```
86
+
87
+ ## Command Groups
88
+
89
+ - `auth`: login, refresh tokens, inspect current user, clear local tokens
90
+ - `config`: read and write local CLI configuration
91
+ - `jobs`: create, list, inspect, publish, run, cancel, clone, and delete jobs
92
+ - `runs`: list run attempts, inspect run attempts, print stored run logs
93
+ - `agents`: list agents, inspect GPUs, inspect queues, rename agents
94
+ - `nodes`: alias for `agents`
@@ -0,0 +1,23 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ fluxhive_cli.egg-info/PKG-INFO
5
+ fluxhive_cli.egg-info/SOURCES.txt
6
+ fluxhive_cli.egg-info/dependency_links.txt
7
+ fluxhive_cli.egg-info/entry_points.txt
8
+ fluxhive_cli.egg-info/top_level.txt
9
+ fluxhivectl/__init__.py
10
+ fluxhivectl/client.py
11
+ fluxhivectl/config.py
12
+ fluxhivectl/context.py
13
+ fluxhivectl/formatting.py
14
+ fluxhivectl/main.py
15
+ fluxhivectl/commands/__init__.py
16
+ fluxhivectl/commands/agents.py
17
+ fluxhivectl/commands/auth.py
18
+ fluxhivectl/commands/config.py
19
+ fluxhivectl/commands/jobs.py
20
+ fluxhivectl/commands/runs.py
21
+ tests/test_client.py
22
+ tests/test_config.py
23
+ tests/test_main.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ fluxhive = fluxhivectl.main:main
3
+ fluxhivectl = fluxhivectl.main:main
@@ -0,0 +1 @@
1
+ fluxhivectl
@@ -0,0 +1,5 @@
1
+ """FluxHive control-plane CLI package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,175 @@
1
+ """HTTP client for FluxHive Control Server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import urllib.error
7
+ import urllib.parse
8
+ import urllib.request
9
+ from dataclasses import replace
10
+ from typing import Any
11
+
12
+ from . import __version__
13
+ from .config import CLIConfig, save_values
14
+
15
+
16
+ class ApiError(RuntimeError):
17
+ def __init__(self, message: str, *, status: int | None = None, code: str | None = None):
18
+ super().__init__(message)
19
+ self.status = status
20
+ self.code = code
21
+
22
+ def __str__(self) -> str:
23
+ parts = []
24
+ if self.status is not None:
25
+ parts.append(str(self.status))
26
+ if self.code:
27
+ parts.append(self.code)
28
+ if parts:
29
+ return f"[{', '.join(parts)}] {super().__str__()}"
30
+ return super().__str__()
31
+
32
+
33
+ class ApiClient:
34
+ def __init__(self, config: CLIConfig, *, timeout: float = 30.0):
35
+ self.config = config
36
+ self.timeout = timeout
37
+
38
+ def get(self, path: str, *, query: dict[str, Any] | None = None, auth: bool = True) -> Any:
39
+ return self.request("GET", path, query=query, auth=auth)
40
+
41
+ def post(
42
+ self,
43
+ path: str,
44
+ *,
45
+ body: dict[str, Any] | None = None,
46
+ query: dict[str, Any] | None = None,
47
+ auth: bool = True,
48
+ ) -> Any:
49
+ return self.request("POST", path, body=body, query=query, auth=auth)
50
+
51
+ def put(self, path: str, *, body: dict[str, Any] | None = None, auth: bool = True) -> Any:
52
+ return self.request("PUT", path, body=body, auth=auth)
53
+
54
+ def patch(self, path: str, *, body: dict[str, Any] | None = None, auth: bool = True) -> Any:
55
+ return self.request("PATCH", path, body=body, auth=auth)
56
+
57
+ def delete(self, path: str, *, auth: bool = True) -> Any:
58
+ return self.request("DELETE", path, auth=auth)
59
+
60
+ def request(
61
+ self,
62
+ method: str,
63
+ path: str,
64
+ *,
65
+ body: dict[str, Any] | None = None,
66
+ query: dict[str, Any] | None = None,
67
+ auth: bool = True,
68
+ _retried_after_refresh: bool = False,
69
+ ) -> Any:
70
+ url = self._build_url(path, query=query)
71
+ payload = json.dumps(body).encode("utf-8") if body is not None else None
72
+ headers = {
73
+ "Accept": "application/json",
74
+ "User-Agent": f"fluxhivectl/{__version__}",
75
+ }
76
+ if body is not None:
77
+ headers["Content-Type"] = "application/json"
78
+ if auth and self.config.access_token:
79
+ headers["Authorization"] = f"Bearer {self.config.access_token}"
80
+
81
+ request = urllib.request.Request(url, data=payload, headers=headers, method=method.upper())
82
+ try:
83
+ with urllib.request.urlopen(request, timeout=self.timeout) as response:
84
+ response_body = response.read()
85
+ return self._parse_response(response.status, response_body)
86
+ except urllib.error.HTTPError as exc:
87
+ response_body = exc.read()
88
+ if (
89
+ exc.code == 401
90
+ and auth
91
+ and self.config.refresh_token
92
+ and not _retried_after_refresh
93
+ ):
94
+ self._refresh_token()
95
+ return self.request(
96
+ method,
97
+ path,
98
+ body=body,
99
+ query=query,
100
+ auth=auth,
101
+ _retried_after_refresh=True,
102
+ )
103
+ self._raise_api_error(exc.code, response_body)
104
+ except urllib.error.URLError as exc:
105
+ raise ApiError(f"Failed to connect to Control Server: {exc.reason}") from exc
106
+
107
+ def _build_url(self, path: str, *, query: dict[str, Any] | None = None) -> str:
108
+ if path.startswith("http://") or path.startswith("https://"):
109
+ url = path
110
+ else:
111
+ url = f"{self.config.api_base_url}/{path.lstrip('/')}"
112
+ if query:
113
+ clean_query = {key: value for key, value in query.items() if value is not None}
114
+ if clean_query:
115
+ url = f"{url}?{urllib.parse.urlencode(clean_query, doseq=True)}"
116
+ return url
117
+
118
+ def _refresh_token(self) -> None:
119
+ data = self.request(
120
+ "POST",
121
+ "/auth/refresh",
122
+ body={"refresh_token": self.config.refresh_token},
123
+ auth=False,
124
+ )
125
+ access_token = data.get("access_token")
126
+ refresh_token = data.get("refresh_token")
127
+ if not access_token or not refresh_token:
128
+ raise ApiError("Refresh response did not include tokens", status=401)
129
+ save_values({"access_token": access_token, "refresh_token": refresh_token})
130
+ self.config = replace(
131
+ self.config,
132
+ access_token=access_token,
133
+ refresh_token=refresh_token,
134
+ )
135
+
136
+ def _parse_response(self, status: int, response_body: bytes) -> Any:
137
+ if not response_body:
138
+ return None
139
+ try:
140
+ payload = json.loads(response_body.decode("utf-8"))
141
+ except json.JSONDecodeError as exc:
142
+ raise ApiError("Control Server returned invalid JSON", status=status) from exc
143
+
144
+ if isinstance(payload, dict) and "success" in payload and "data" in payload:
145
+ if not payload.get("success"):
146
+ error = payload.get("error") or {}
147
+ meta = payload.get("meta") or {}
148
+ message = error.get("message") or meta.get("message") or "Request failed"
149
+ code = error.get("code") or meta.get("code")
150
+ raise ApiError(message, status=status, code=code)
151
+ return payload.get("data")
152
+ return payload
153
+
154
+ def _raise_api_error(self, status: int, response_body: bytes) -> None:
155
+ if not response_body:
156
+ raise ApiError("Request failed", status=status)
157
+ try:
158
+ payload = json.loads(response_body.decode("utf-8"))
159
+ except json.JSONDecodeError as exc:
160
+ raise ApiError(response_body.decode("utf-8", errors="replace"), status=status) from exc
161
+
162
+ if isinstance(payload, dict):
163
+ error = payload.get("error") or {}
164
+ meta = payload.get("meta") or {}
165
+ detail = payload.get("detail")
166
+ message = (
167
+ error.get("message")
168
+ or meta.get("message")
169
+ or (detail if isinstance(detail, str) else None)
170
+ or "Request failed"
171
+ )
172
+ code = error.get("code") or meta.get("code")
173
+ raise ApiError(message, status=status, code=code)
174
+
175
+ raise ApiError("Request failed", status=status)
@@ -0,0 +1,16 @@
1
+ """Command group registration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from . import agents, auth, config, jobs, runs
8
+
9
+
10
+ def register_commands(subparsers: argparse._SubParsersAction) -> None:
11
+ config.register(subparsers)
12
+ auth.register(subparsers)
13
+ jobs.register(subparsers)
14
+ runs.register(subparsers)
15
+ agents.register(subparsers, name="agents")
16
+ agents.register(subparsers, name="nodes")