jvcli 2.0.8__py3-none-any.whl → 2.0.10__py3-none-any.whl
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.
- jvcli/__init__.py +1 -1
- jvcli/api.py +3 -39
- jvcli/client/app.py +0 -2
- jvcli/client/lib/widgets.py +4 -2
- jvcli/commands/auth.py +3 -3
- jvcli/commands/studio.py +210 -48
- jvcli/studio/assets/index-DDV79SDu.js +213 -0
- jvcli/studio/assets/index-DdMMONxd.css +1 -0
- jvcli/studio/index.html +2 -2
- jvcli/studio-auth/assets/index-Bh6lyeXA.js +218 -0
- jvcli/studio-auth/assets/index-DdMMONxd.css +1 -0
- jvcli/studio-auth/index.html +15 -0
- jvcli/studio-auth/jac_logo.png +0 -0
- jvcli/studio-auth/tauri.svg +6 -0
- jvcli/studio-auth/vite.svg +1 -0
- jvcli/templates/2.0.0/project/README.md +3 -3
- jvcli/templates/2.0.0/project/sh/exportenv.sh +12 -0
- jvcli/templates/2.0.0/project/sh/importagent.sh +36 -0
- jvcli/templates/2.0.0/project/sh/initagents.sh +34 -0
- jvcli/templates/2.0.0/project/sh/inituser.sh +50 -0
- jvcli/templates/2.0.0/project/sh/serve.sh +2 -2
- jvcli/templates/2.0.0/project/sh/startclient.sh +7 -0
- {jvcli-2.0.8.dist-info → jvcli-2.0.10.dist-info}/METADATA +1 -1
- {jvcli-2.0.8.dist-info → jvcli-2.0.10.dist-info}/RECORD +28 -19
- jvcli/studio/assets/index-BtFItD2q.js +0 -156
- jvcli/studio/assets/index-CIEsu-TC.css +0 -1
- jvcli/templates/2.0.0/project/sh/import_agent.sh +0 -66
- jvcli/templates/2.0.0/project/sh/init.sh +0 -70
- {jvcli-2.0.8.dist-info → jvcli-2.0.10.dist-info}/LICENSE +0 -0
- {jvcli-2.0.8.dist-info → jvcli-2.0.10.dist-info}/WHEEL +0 -0
- {jvcli-2.0.8.dist-info → jvcli-2.0.10.dist-info}/entry_points.txt +0 -0
- {jvcli-2.0.8.dist-info → jvcli-2.0.10.dist-info}/top_level.txt +0 -0
jvcli/__init__.py
CHANGED
jvcli/api.py
CHANGED
@@ -62,7 +62,7 @@ class RegistryAPI:
|
|
62
62
|
token: Optional[str] = None,
|
63
63
|
api_key: Optional[str] = None,
|
64
64
|
) -> dict:
|
65
|
-
"""Get
|
65
|
+
"""Get package info.yaml content as json"""
|
66
66
|
endpoint = "info"
|
67
67
|
|
68
68
|
try:
|
@@ -83,12 +83,12 @@ class RegistryAPI:
|
|
83
83
|
return response.json()
|
84
84
|
else:
|
85
85
|
click.secho(
|
86
|
-
f"Error retrieving
|
86
|
+
f"Error retrieving package: {response.json()['error']}",
|
87
87
|
fg="red",
|
88
88
|
)
|
89
89
|
return {}
|
90
90
|
except Exception as e:
|
91
|
-
click.secho(f"Error retrieving
|
91
|
+
click.secho(f"Error retrieving package: {e}", fg="red")
|
92
92
|
return {}
|
93
93
|
|
94
94
|
@staticmethod
|
@@ -133,42 +133,6 @@ class RegistryAPI:
|
|
133
133
|
click.secho(f"Error downloading package: {e}", fg="red")
|
134
134
|
return {}
|
135
135
|
|
136
|
-
@staticmethod
|
137
|
-
def get_action_info(
|
138
|
-
name: str,
|
139
|
-
version: str = "",
|
140
|
-
token: Optional[str] = None,
|
141
|
-
api_key: Optional[str] = None,
|
142
|
-
) -> dict:
|
143
|
-
"""Get action info.yaml content as json"""
|
144
|
-
endpoint = "info"
|
145
|
-
|
146
|
-
try:
|
147
|
-
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
148
|
-
|
149
|
-
if api_key:
|
150
|
-
headers["x-api-key"] = api_key
|
151
|
-
|
152
|
-
data = {
|
153
|
-
"name": name,
|
154
|
-
"version": "" if version == "latest" else version,
|
155
|
-
}
|
156
|
-
response = requests.get(
|
157
|
-
RegistryAPI.url + endpoint, params=data, headers=headers
|
158
|
-
)
|
159
|
-
# Check if the response is successful
|
160
|
-
if response.status_code == 200:
|
161
|
-
return response.json()
|
162
|
-
else:
|
163
|
-
click.secho(
|
164
|
-
f"Error retrieving action: {response.json()['error']}",
|
165
|
-
fg="red",
|
166
|
-
)
|
167
|
-
return {}
|
168
|
-
except Exception as e:
|
169
|
-
click.secho(f"Error retrieving action: {e}", fg="red")
|
170
|
-
return {}
|
171
|
-
|
172
136
|
@staticmethod
|
173
137
|
def create_namespace(name: str, token: str) -> dict:
|
174
138
|
"""Create a namespace."""
|
jvcli/client/app.py
CHANGED
@@ -26,8 +26,6 @@ def login_form() -> None:
|
|
26
26
|
"""Render the login form and handle login logic."""
|
27
27
|
login_url = f"{JIVAS_URL}/user/login"
|
28
28
|
|
29
|
-
st.write(os.environ.get("JIVAS_ENVIRONMENT"))
|
30
|
-
|
31
29
|
if os.environ.get("JIVAS_ENVIRONMENT") == "development":
|
32
30
|
email = os.environ.get("JIVAS_USER", "admin@jivas.com")
|
33
31
|
password = os.environ.get("JIVAS_PASSWORD", "password")
|
jvcli/client/lib/widgets.py
CHANGED
@@ -181,7 +181,7 @@ def dynamic_form(
|
|
181
181
|
{"id": 0, "fields": {field["name"]: "" for field in field_definitions}}
|
182
182
|
]
|
183
183
|
|
184
|
-
def add_row() -> None:
|
184
|
+
def add_row() -> None: # pragma: no cover, don't know how to test this yet 😅
|
185
185
|
"""Add a new row to the dynamic form."""
|
186
186
|
new_id = (
|
187
187
|
max((item["id"] for item in st.session_state[session_key]), default=-1) + 1
|
@@ -192,7 +192,9 @@ def dynamic_form(
|
|
192
192
|
}
|
193
193
|
st.session_state[session_key].append(new_row)
|
194
194
|
|
195
|
-
def remove_row(
|
195
|
+
def remove_row(
|
196
|
+
id_to_remove: int,
|
197
|
+
) -> None: # pragma: no cover, don't know how to test this yet 😅
|
196
198
|
"""Remove a row from the dynamic form."""
|
197
199
|
st.session_state[session_key] = [
|
198
200
|
item for item in st.session_state[session_key] if item["id"] != id_to_remove
|
jvcli/commands/auth.py
CHANGED
@@ -26,14 +26,14 @@ def signup(username: str, email: str, password: str) -> None:
|
|
26
26
|
|
27
27
|
@click.command()
|
28
28
|
@click.option(
|
29
|
-
"--
|
29
|
+
"--username",
|
30
30
|
help="Your email address or username.",
|
31
31
|
prompt="Login (username or email)",
|
32
32
|
)
|
33
33
|
@click.option("--password", prompt=True, hide_input=True, help="Your password.")
|
34
|
-
def login(
|
34
|
+
def login(username: str, password: str) -> None:
|
35
35
|
"""Log in to your Jivas Package Repository account."""
|
36
|
-
data = RegistryAPI.login(
|
36
|
+
data = RegistryAPI.login(username, password)
|
37
37
|
if data and "token" in data and "namespaces" in data and "email" in data:
|
38
38
|
save_token(data["token"], data["namespaces"], data["email"])
|
39
39
|
click.secho("Login successful! Token saved.", fg="green", bold=True)
|
jvcli/commands/studio.py
CHANGED
@@ -2,39 +2,44 @@
|
|
2
2
|
|
3
3
|
import json
|
4
4
|
from pathlib import Path
|
5
|
+
from typing import Annotated
|
5
6
|
|
6
7
|
import click
|
7
8
|
import jaclang # noqa: F401
|
8
9
|
from bson import ObjectId
|
10
|
+
from fastapi import Depends, FastAPI, HTTPException
|
11
|
+
from fastapi.middleware.cors import CORSMiddleware
|
12
|
+
from fastapi.responses import JSONResponse
|
13
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
14
|
+
from fastapi.staticfiles import StaticFiles
|
9
15
|
from jac_cloud.core.architype import NodeAnchor
|
16
|
+
from jac_cloud.jaseci.security import decrypt
|
10
17
|
from uvicorn import run
|
11
18
|
|
12
19
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
20
|
+
def get_nodes_and_edges(
|
21
|
+
nid: str,
|
22
|
+
current_depth: int,
|
23
|
+
depth: int,
|
24
|
+
nodes: list,
|
25
|
+
edges: list,
|
26
|
+
node_collection: NodeAnchor.Collection,
|
27
|
+
edge_collection: NodeAnchor.Collection,
|
28
|
+
) -> None:
|
29
|
+
"""Get nodes and edges recursively."""
|
30
|
+
if current_depth >= depth:
|
31
|
+
return
|
17
32
|
|
33
|
+
outgoing_edges = edge_collection.find(
|
34
|
+
{
|
35
|
+
"$or": [
|
36
|
+
{"source": nid},
|
37
|
+
{"source": {"$regex": f"{nid}$"}},
|
38
|
+
]
|
39
|
+
}
|
40
|
+
)
|
18
41
|
|
19
|
-
|
20
|
-
"""Fetches a graph structure from the database."""
|
21
|
-
nodes = []
|
22
|
-
edges = []
|
23
|
-
|
24
|
-
edge_collection = NodeAnchor.Collection.get_collection("edge")
|
25
|
-
node_collection = NodeAnchor.Collection.get_collection("node")
|
26
|
-
node_docs = node_collection.find({"root": ObjectId(root)})
|
27
|
-
edge_docs = edge_collection.find({"root": ObjectId(root)})
|
28
|
-
|
29
|
-
for node in node_docs:
|
30
|
-
nodes.append(
|
31
|
-
{
|
32
|
-
"id": node["_id"],
|
33
|
-
"data": node["architype"],
|
34
|
-
"name": node["name"],
|
35
|
-
}
|
36
|
-
)
|
37
|
-
for edge in edge_docs:
|
42
|
+
for edge in outgoing_edges:
|
38
43
|
edges.append(
|
39
44
|
{
|
40
45
|
"id": edge["_id"],
|
@@ -45,40 +50,181 @@ def get_graph(root: str) -> dict:
|
|
45
50
|
}
|
46
51
|
)
|
47
52
|
|
48
|
-
|
49
|
-
"
|
50
|
-
"edges": json.loads(json.dumps(edges, default=str)),
|
51
|
-
}
|
53
|
+
node_id = edge["target"].split(":")[-1]
|
54
|
+
connected_nodes = node_collection.find({"_id": ObjectId(node_id)})
|
52
55
|
|
56
|
+
for node in connected_nodes:
|
57
|
+
nodes.append(
|
58
|
+
{
|
59
|
+
"id": node["_id"],
|
60
|
+
"data": node["architype"],
|
61
|
+
"name": node["name"],
|
62
|
+
}
|
63
|
+
)
|
53
64
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
65
|
+
get_nodes_and_edges(
|
66
|
+
node["_id"],
|
67
|
+
current_depth + 1,
|
68
|
+
depth,
|
69
|
+
nodes,
|
70
|
+
edges,
|
71
|
+
node_collection,
|
72
|
+
edge_collection,
|
73
|
+
)
|
59
74
|
|
60
|
-
for user in user_docs:
|
61
|
-
users.append(
|
62
|
-
{
|
63
|
-
"id": user["_id"],
|
64
|
-
"root_id": user["root_id"],
|
65
|
-
"email": user["email"],
|
66
|
-
}
|
67
|
-
)
|
68
75
|
|
69
|
-
|
76
|
+
# need this because endpoint annotation differs depending on require auth flag
|
77
|
+
class EndpointFactory:
|
78
|
+
"""Factory for creating endpoints based on require_auth flag."""
|
79
|
+
|
80
|
+
@staticmethod
|
81
|
+
def create_endpoints(require_auth: bool, security: HTTPBearer | None) -> tuple:
|
82
|
+
"""Create endpoints based on require_auth flag."""
|
83
|
+
|
84
|
+
def validate_auth(credentials: HTTPAuthorizationCredentials) -> None:
|
85
|
+
"""Validate authentication token."""
|
86
|
+
token = credentials.credentials
|
87
|
+
if not token or not decrypt(token):
|
88
|
+
raise HTTPException(status_code=401, detail="Invalid token")
|
89
|
+
|
90
|
+
def get_graph_data(root: str) -> dict:
|
91
|
+
"""Get graph nodes and edges data."""
|
92
|
+
edge_collection = NodeAnchor.Collection.get_collection("edge")
|
93
|
+
node_collection = NodeAnchor.Collection.get_collection("node")
|
94
|
+
|
95
|
+
nodes = [
|
96
|
+
{
|
97
|
+
"id": node["_id"],
|
98
|
+
"data": node["architype"],
|
99
|
+
"name": node["name"],
|
100
|
+
}
|
101
|
+
for node in node_collection.find({"root": ObjectId(root)})
|
102
|
+
]
|
103
|
+
|
104
|
+
edges = [
|
105
|
+
{
|
106
|
+
"id": edge["_id"],
|
107
|
+
"name": edge["name"],
|
108
|
+
"source": edge["source"],
|
109
|
+
"target": edge["target"],
|
110
|
+
"data": edge["architype"],
|
111
|
+
}
|
112
|
+
for edge in edge_collection.find({"root": ObjectId(root)})
|
113
|
+
]
|
114
|
+
|
115
|
+
return {"nodes": nodes, "edges": edges}
|
116
|
+
|
117
|
+
def get_users_data() -> list:
|
118
|
+
"""Get users data."""
|
119
|
+
user_collection = NodeAnchor.Collection.get_collection("user")
|
120
|
+
return [
|
121
|
+
{
|
122
|
+
"id": user["_id"],
|
123
|
+
"root_id": user["root_id"],
|
124
|
+
"email": user["email"],
|
125
|
+
}
|
126
|
+
for user in user_collection.find()
|
127
|
+
]
|
128
|
+
|
129
|
+
def get_node_connections(node_id: str, depth: int) -> dict:
|
130
|
+
nid = node_id.split(":")[-1]
|
131
|
+
current_depth = 0
|
132
|
+
nodes: list = []
|
133
|
+
edges: list = []
|
134
|
+
|
135
|
+
edge_collection = NodeAnchor.Collection.get_collection("edge")
|
136
|
+
node_collection = NodeAnchor.Collection.get_collection("node")
|
137
|
+
|
138
|
+
get_nodes_and_edges(
|
139
|
+
nid,
|
140
|
+
current_depth,
|
141
|
+
depth,
|
142
|
+
nodes,
|
143
|
+
edges,
|
144
|
+
node_collection,
|
145
|
+
edge_collection,
|
146
|
+
)
|
147
|
+
|
148
|
+
return {"nodes": nodes, "edges": edges}
|
149
|
+
|
150
|
+
if not require_auth:
|
151
|
+
|
152
|
+
async def graph_endpoint(root: str) -> JSONResponse:
|
153
|
+
return JSONResponse(
|
154
|
+
content=json.loads(json.dumps(get_graph_data(root), default=str))
|
155
|
+
)
|
156
|
+
|
157
|
+
async def users_endpoint() -> JSONResponse:
|
158
|
+
return JSONResponse(
|
159
|
+
content=json.loads(json.dumps(get_users_data(), default=str))
|
160
|
+
)
|
161
|
+
|
162
|
+
async def node_endpoint(node_id: str, depth: int) -> JSONResponse:
|
163
|
+
return JSONResponse(
|
164
|
+
content=json.loads(
|
165
|
+
json.dumps(get_node_connections(node_id, depth), default=str)
|
166
|
+
)
|
167
|
+
)
|
168
|
+
|
169
|
+
return graph_endpoint, users_endpoint, node_endpoint
|
170
|
+
|
171
|
+
else:
|
172
|
+
|
173
|
+
async def guarded_graph_endpoint(
|
174
|
+
root: str,
|
175
|
+
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)],
|
176
|
+
) -> JSONResponse:
|
177
|
+
validate_auth(credentials)
|
178
|
+
return JSONResponse(
|
179
|
+
content=json.loads(json.dumps(get_graph_data(root), default=str))
|
180
|
+
)
|
181
|
+
|
182
|
+
async def guarded_users_endpoint(
|
183
|
+
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)],
|
184
|
+
) -> JSONResponse:
|
185
|
+
validate_auth(credentials)
|
186
|
+
return JSONResponse(
|
187
|
+
content=json.loads(json.dumps(get_users_data(), default=str))
|
188
|
+
)
|
189
|
+
|
190
|
+
async def guarded_node_endpoint(
|
191
|
+
node_id: str,
|
192
|
+
depth: int,
|
193
|
+
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)],
|
194
|
+
) -> JSONResponse:
|
195
|
+
validate_auth(credentials)
|
196
|
+
return JSONResponse(
|
197
|
+
content=json.loads(
|
198
|
+
json.dumps(get_node_connections(node_id, depth), default=str)
|
199
|
+
)
|
200
|
+
)
|
201
|
+
|
202
|
+
return guarded_graph_endpoint, guarded_users_endpoint, guarded_node_endpoint
|
203
|
+
|
204
|
+
|
205
|
+
@click.group()
|
206
|
+
def studio() -> None:
|
207
|
+
"""Group for managing Jivas Studio resources."""
|
208
|
+
pass # pragma: no cover
|
70
209
|
|
71
210
|
|
72
211
|
@studio.command()
|
73
212
|
@click.option("--port", default=8989, help="Port for the studio to launch on.")
|
74
|
-
|
213
|
+
@click.option(
|
214
|
+
"--require-auth", default=False, help="Require authentication for studio api."
|
215
|
+
)
|
216
|
+
def launch(port: int, require_auth: bool) -> None:
|
75
217
|
"""Launch the Jivas Studio on the specified port."""
|
76
218
|
click.echo(f"Launching Jivas Studio on port {port}...")
|
77
|
-
from fastapi import FastAPI
|
78
|
-
from fastapi.middleware.cors import CORSMiddleware
|
79
|
-
from fastapi.staticfiles import StaticFiles
|
80
219
|
|
81
|
-
|
220
|
+
security = HTTPBearer() if require_auth else None
|
221
|
+
|
222
|
+
get_graph, get_users, get_node = EndpointFactory.create_endpoints(
|
223
|
+
require_auth, security
|
224
|
+
)
|
225
|
+
|
226
|
+
app = FastAPI(title="Jivas Studio API")
|
227
|
+
|
82
228
|
app.add_middleware(
|
83
229
|
CORSMiddleware,
|
84
230
|
allow_origins=["*"],
|
@@ -89,8 +235,24 @@ def launch(port: int) -> None:
|
|
89
235
|
|
90
236
|
app.add_api_route("/graph", endpoint=get_graph, methods=["GET"])
|
91
237
|
app.add_api_route("/users", endpoint=get_users, methods=["GET"])
|
238
|
+
app.add_api_route("/graph/node", endpoint=get_node, methods=["GET"])
|
239
|
+
|
240
|
+
client_dir = (
|
241
|
+
Path(__file__)
|
242
|
+
.resolve()
|
243
|
+
.parent.parent.joinpath("studio-auth" if require_auth else "studio")
|
244
|
+
)
|
92
245
|
|
93
|
-
|
94
|
-
|
246
|
+
app.mount(
|
247
|
+
"/",
|
248
|
+
StaticFiles(directory=client_dir, html=True),
|
249
|
+
name="studio",
|
250
|
+
)
|
251
|
+
|
252
|
+
app.mount(
|
253
|
+
"/graph",
|
254
|
+
StaticFiles(directory=client_dir, html=True),
|
255
|
+
name="studio_graph",
|
256
|
+
)
|
95
257
|
|
96
258
|
run(app, host="0.0.0.0", port=port)
|