llamactl 0.2.7a1__py3-none-any.whl → 0.3.0__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.
- llama_deploy/cli/__init__.py +9 -22
- llama_deploy/cli/app.py +69 -0
- llama_deploy/cli/auth/client.py +362 -0
- llama_deploy/cli/client.py +47 -170
- llama_deploy/cli/commands/aliased_group.py +33 -0
- llama_deploy/cli/commands/auth.py +696 -0
- llama_deploy/cli/commands/deployment.py +300 -0
- llama_deploy/cli/commands/env.py +211 -0
- llama_deploy/cli/commands/init.py +313 -0
- llama_deploy/cli/commands/serve.py +239 -0
- llama_deploy/cli/config/_config.py +390 -0
- llama_deploy/cli/config/_migrations.py +65 -0
- llama_deploy/cli/config/auth_service.py +130 -0
- llama_deploy/cli/config/env_service.py +67 -0
- llama_deploy/cli/config/migrations/0001_init.sql +35 -0
- llama_deploy/cli/config/migrations/0002_add_auth_fields.sql +24 -0
- llama_deploy/cli/config/migrations/__init__.py +7 -0
- llama_deploy/cli/config/schema.py +61 -0
- llama_deploy/cli/env.py +5 -3
- llama_deploy/cli/interactive_prompts/session_utils.py +37 -0
- llama_deploy/cli/interactive_prompts/utils.py +6 -72
- llama_deploy/cli/options.py +27 -5
- llama_deploy/cli/py.typed +0 -0
- llama_deploy/cli/styles.py +10 -0
- llama_deploy/cli/textual/deployment_form.py +263 -36
- llama_deploy/cli/textual/deployment_help.py +53 -0
- llama_deploy/cli/textual/deployment_monitor.py +466 -0
- llama_deploy/cli/textual/git_validation.py +20 -21
- llama_deploy/cli/textual/github_callback_server.py +17 -14
- llama_deploy/cli/textual/llama_loader.py +13 -1
- llama_deploy/cli/textual/secrets_form.py +28 -8
- llama_deploy/cli/textual/styles.tcss +49 -8
- llama_deploy/cli/utils/env_inject.py +23 -0
- {llamactl-0.2.7a1.dist-info → llamactl-0.3.0.dist-info}/METADATA +9 -6
- llamactl-0.3.0.dist-info/RECORD +38 -0
- {llamactl-0.2.7a1.dist-info → llamactl-0.3.0.dist-info}/WHEEL +1 -1
- llama_deploy/cli/commands.py +0 -549
- llama_deploy/cli/config.py +0 -173
- llama_deploy/cli/textual/profile_form.py +0 -171
- llamactl-0.2.7a1.dist-info/RECORD +0 -19
- {llamactl-0.2.7a1.dist-info → llamactl-0.3.0.dist-info}/entry_points.txt +0 -0
llama_deploy/cli/config.py
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
"""Configuration and profile management for llamactl"""
|
|
2
|
-
|
|
3
|
-
import sqlite3
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Optional, List
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
import os
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@dataclass
|
|
11
|
-
class Profile:
|
|
12
|
-
"""Profile configuration"""
|
|
13
|
-
|
|
14
|
-
name: str
|
|
15
|
-
api_url: str
|
|
16
|
-
active_project_id: Optional[str] = None
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ConfigManager:
|
|
20
|
-
"""Manages profiles and configuration using SQLite"""
|
|
21
|
-
|
|
22
|
-
def __init__(self):
|
|
23
|
-
self.config_dir = self._get_config_dir()
|
|
24
|
-
self.db_path = self.config_dir / "profiles.db"
|
|
25
|
-
self._ensure_config_dir()
|
|
26
|
-
self._init_database()
|
|
27
|
-
|
|
28
|
-
def _get_config_dir(self) -> Path:
|
|
29
|
-
"""Get the configuration directory path based on OS"""
|
|
30
|
-
if os.name == "nt": # Windows
|
|
31
|
-
config_dir = Path(os.environ.get("APPDATA", "~")) / "llamactl"
|
|
32
|
-
else: # Unix-like (Linux, macOS)
|
|
33
|
-
config_dir = Path.home() / ".config" / "llamactl"
|
|
34
|
-
return config_dir.expanduser()
|
|
35
|
-
|
|
36
|
-
def _ensure_config_dir(self):
|
|
37
|
-
"""Create configuration directory if it doesn't exist"""
|
|
38
|
-
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
39
|
-
|
|
40
|
-
def _init_database(self):
|
|
41
|
-
"""Initialize SQLite database with required tables"""
|
|
42
|
-
with sqlite3.connect(self.db_path) as conn:
|
|
43
|
-
# Check if we need to migrate from old schema
|
|
44
|
-
cursor = conn.execute("PRAGMA table_info(profiles)")
|
|
45
|
-
columns = [row[1] for row in cursor.fetchall()]
|
|
46
|
-
|
|
47
|
-
if "project_id" in columns and "active_project_id" not in columns:
|
|
48
|
-
# Migrate old schema to new schema
|
|
49
|
-
conn.execute("""
|
|
50
|
-
ALTER TABLE profiles RENAME COLUMN project_id TO active_project_id
|
|
51
|
-
""")
|
|
52
|
-
|
|
53
|
-
# Create tables with new schema
|
|
54
|
-
conn.execute("""
|
|
55
|
-
CREATE TABLE IF NOT EXISTS profiles (
|
|
56
|
-
name TEXT PRIMARY KEY,
|
|
57
|
-
api_url TEXT NOT NULL,
|
|
58
|
-
active_project_id TEXT
|
|
59
|
-
)
|
|
60
|
-
""")
|
|
61
|
-
|
|
62
|
-
conn.execute("""
|
|
63
|
-
CREATE TABLE IF NOT EXISTS settings (
|
|
64
|
-
key TEXT PRIMARY KEY,
|
|
65
|
-
value TEXT NOT NULL
|
|
66
|
-
)
|
|
67
|
-
""")
|
|
68
|
-
|
|
69
|
-
conn.commit()
|
|
70
|
-
|
|
71
|
-
def create_profile(
|
|
72
|
-
self, name: str, api_url: str, active_project_id: Optional[str] = None
|
|
73
|
-
) -> Profile:
|
|
74
|
-
"""Create a new profile"""
|
|
75
|
-
profile = Profile(
|
|
76
|
-
name=name, api_url=api_url, active_project_id=active_project_id
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
with sqlite3.connect(self.db_path) as conn:
|
|
80
|
-
try:
|
|
81
|
-
conn.execute(
|
|
82
|
-
"INSERT INTO profiles (name, api_url, active_project_id) VALUES (?, ?, ?)",
|
|
83
|
-
(profile.name, profile.api_url, profile.active_project_id),
|
|
84
|
-
)
|
|
85
|
-
conn.commit()
|
|
86
|
-
except sqlite3.IntegrityError:
|
|
87
|
-
raise ValueError(f"Profile '{name}' already exists")
|
|
88
|
-
|
|
89
|
-
return profile
|
|
90
|
-
|
|
91
|
-
def get_profile(self, name: str) -> Optional[Profile]:
|
|
92
|
-
"""Get a profile by name"""
|
|
93
|
-
with sqlite3.connect(self.db_path) as conn:
|
|
94
|
-
cursor = conn.execute(
|
|
95
|
-
"SELECT name, api_url, active_project_id FROM profiles WHERE name = ?",
|
|
96
|
-
(name,),
|
|
97
|
-
)
|
|
98
|
-
row = cursor.fetchone()
|
|
99
|
-
if row:
|
|
100
|
-
return Profile(name=row[0], api_url=row[1], active_project_id=row[2])
|
|
101
|
-
return None
|
|
102
|
-
|
|
103
|
-
def list_profiles(self) -> List[Profile]:
|
|
104
|
-
"""List all profiles"""
|
|
105
|
-
profiles = []
|
|
106
|
-
with sqlite3.connect(self.db_path) as conn:
|
|
107
|
-
cursor = conn.execute(
|
|
108
|
-
"SELECT name, api_url, active_project_id FROM profiles ORDER BY name"
|
|
109
|
-
)
|
|
110
|
-
for row in cursor.fetchall():
|
|
111
|
-
profiles.append(
|
|
112
|
-
Profile(name=row[0], api_url=row[1], active_project_id=row[2])
|
|
113
|
-
)
|
|
114
|
-
return profiles
|
|
115
|
-
|
|
116
|
-
def delete_profile(self, name: str) -> bool:
|
|
117
|
-
"""Delete a profile by name. Returns True if deleted, False if not found."""
|
|
118
|
-
with sqlite3.connect(self.db_path) as conn:
|
|
119
|
-
cursor = conn.execute("DELETE FROM profiles WHERE name = ?", (name,))
|
|
120
|
-
conn.commit()
|
|
121
|
-
|
|
122
|
-
# If this was the active profile, clear it
|
|
123
|
-
if self.get_current_profile_name() == name:
|
|
124
|
-
self.set_current_profile(None)
|
|
125
|
-
|
|
126
|
-
return cursor.rowcount > 0
|
|
127
|
-
|
|
128
|
-
def set_current_profile(self, name: Optional[str]):
|
|
129
|
-
"""Set the current active profile"""
|
|
130
|
-
with sqlite3.connect(self.db_path) as conn:
|
|
131
|
-
if name is None:
|
|
132
|
-
conn.execute("DELETE FROM settings WHERE key = 'current_profile'")
|
|
133
|
-
else:
|
|
134
|
-
conn.execute(
|
|
135
|
-
"INSERT OR REPLACE INTO settings (key, value) VALUES ('current_profile', ?)",
|
|
136
|
-
(name,),
|
|
137
|
-
)
|
|
138
|
-
conn.commit()
|
|
139
|
-
|
|
140
|
-
def get_current_profile_name(self) -> Optional[str]:
|
|
141
|
-
"""Get the name of the current active profile"""
|
|
142
|
-
with sqlite3.connect(self.db_path) as conn:
|
|
143
|
-
cursor = conn.execute(
|
|
144
|
-
"SELECT value FROM settings WHERE key = 'current_profile'"
|
|
145
|
-
)
|
|
146
|
-
row = cursor.fetchone()
|
|
147
|
-
return row[0] if row else None
|
|
148
|
-
|
|
149
|
-
def get_current_profile(self) -> Optional[Profile]:
|
|
150
|
-
"""Get the current active profile"""
|
|
151
|
-
current_name = self.get_current_profile_name()
|
|
152
|
-
if current_name:
|
|
153
|
-
return self.get_profile(current_name)
|
|
154
|
-
return None
|
|
155
|
-
|
|
156
|
-
def set_active_project(self, profile_name: str, project_id: Optional[str]) -> bool:
|
|
157
|
-
"""Set the active project for a profile. Returns True if profile exists."""
|
|
158
|
-
with sqlite3.connect(self.db_path) as conn:
|
|
159
|
-
cursor = conn.execute(
|
|
160
|
-
"UPDATE profiles SET active_project_id = ? WHERE name = ?",
|
|
161
|
-
(project_id, profile_name),
|
|
162
|
-
)
|
|
163
|
-
conn.commit()
|
|
164
|
-
return cursor.rowcount > 0
|
|
165
|
-
|
|
166
|
-
def get_active_project(self, profile_name: str) -> Optional[str]:
|
|
167
|
-
"""Get the active project for a profile"""
|
|
168
|
-
profile = self.get_profile(profile_name)
|
|
169
|
-
return profile.active_project_id if profile else None
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
# Global config manager instance
|
|
173
|
-
config_manager = ConfigManager()
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"""Textual-based forms for CLI interactions"""
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
from textual.app import App, ComposeResult
|
|
8
|
-
from textual.containers import (
|
|
9
|
-
Container,
|
|
10
|
-
HorizontalGroup,
|
|
11
|
-
)
|
|
12
|
-
from textual.validation import Length
|
|
13
|
-
from textual.widgets import Button, Input, Label, Static
|
|
14
|
-
|
|
15
|
-
from ..config import Profile
|
|
16
|
-
from ..config import config_manager
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@dataclass
|
|
20
|
-
class ProfileForm:
|
|
21
|
-
"""Form data for profile editing/creation"""
|
|
22
|
-
|
|
23
|
-
name: str = ""
|
|
24
|
-
api_url: str = ""
|
|
25
|
-
active_project_id: str = ""
|
|
26
|
-
existing_name: Optional[str] = None
|
|
27
|
-
|
|
28
|
-
@classmethod
|
|
29
|
-
def from_profile(cls, profile: Profile) -> "ProfileForm":
|
|
30
|
-
"""Create form from existing profile"""
|
|
31
|
-
return cls(
|
|
32
|
-
name=profile.name,
|
|
33
|
-
api_url=profile.api_url,
|
|
34
|
-
active_project_id=profile.active_project_id or "",
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class ProfileEditApp(App[Optional[ProfileForm]]):
|
|
39
|
-
"""Textual app for editing profiles"""
|
|
40
|
-
|
|
41
|
-
CSS_PATH = Path(__file__).parent / "styles.tcss"
|
|
42
|
-
|
|
43
|
-
def __init__(self, initial_data: ProfileForm):
|
|
44
|
-
super().__init__()
|
|
45
|
-
self.form_data = initial_data
|
|
46
|
-
|
|
47
|
-
def on_mount(self) -> None:
|
|
48
|
-
self.theme = "tokyo-night"
|
|
49
|
-
|
|
50
|
-
def on_key(self, event) -> None:
|
|
51
|
-
"""Handle key events, including Ctrl+C"""
|
|
52
|
-
if event.key == "ctrl+c":
|
|
53
|
-
self.exit(None)
|
|
54
|
-
|
|
55
|
-
def compose(self) -> ComposeResult:
|
|
56
|
-
with Container(classes="form-container"):
|
|
57
|
-
title = "Edit Profile" if self.form_data.existing_name else "Create Profile"
|
|
58
|
-
yield Static(title, classes="primary-message")
|
|
59
|
-
yield Static("", id="error-message", classes="error-message hidden")
|
|
60
|
-
with Static(classes="two-column-form-grid"):
|
|
61
|
-
yield Label(
|
|
62
|
-
"Profile Name: *", classes="required form-label", shrink=True
|
|
63
|
-
)
|
|
64
|
-
yield Input(
|
|
65
|
-
value=self.form_data.name,
|
|
66
|
-
placeholder="Enter profile name",
|
|
67
|
-
validators=[Length(minimum=1)],
|
|
68
|
-
id="name",
|
|
69
|
-
compact=True,
|
|
70
|
-
)
|
|
71
|
-
yield Label("API URL: *", classes="required form-label", shrink=True)
|
|
72
|
-
yield Input(
|
|
73
|
-
value=self.form_data.api_url,
|
|
74
|
-
placeholder="https://prod-cloud-llama-deploy",
|
|
75
|
-
validators=[Length(minimum=1)],
|
|
76
|
-
id="api_url",
|
|
77
|
-
compact=True,
|
|
78
|
-
)
|
|
79
|
-
yield Label("Project ID:", classes="form-label", shrink=True)
|
|
80
|
-
yield Input(
|
|
81
|
-
value=self.form_data.active_project_id,
|
|
82
|
-
placeholder="Optional project ID",
|
|
83
|
-
id="project_id",
|
|
84
|
-
compact=True,
|
|
85
|
-
)
|
|
86
|
-
with HorizontalGroup(classes="button-row"):
|
|
87
|
-
yield Button("Save", variant="primary", id="save", compact=True)
|
|
88
|
-
yield Button("Cancel", variant="default", id="cancel", compact=True)
|
|
89
|
-
|
|
90
|
-
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
91
|
-
if event.button.id == "save":
|
|
92
|
-
if self._validate_form():
|
|
93
|
-
result = self._get_form_data()
|
|
94
|
-
try:
|
|
95
|
-
if result.existing_name:
|
|
96
|
-
config_manager.delete_profile(result.existing_name)
|
|
97
|
-
profile = config_manager.create_profile(
|
|
98
|
-
result.name,
|
|
99
|
-
result.api_url,
|
|
100
|
-
result.active_project_id,
|
|
101
|
-
)
|
|
102
|
-
self.exit(profile)
|
|
103
|
-
except Exception as e:
|
|
104
|
-
self._handle_error(e)
|
|
105
|
-
|
|
106
|
-
elif event.button.id == "cancel":
|
|
107
|
-
self.exit(None)
|
|
108
|
-
|
|
109
|
-
def _handle_error(self, error: Exception) -> None:
|
|
110
|
-
error_message = self.query_one("#error-message", Static)
|
|
111
|
-
error_message.update(f"Error creating profile: {error}")
|
|
112
|
-
error_message.add_class("visible")
|
|
113
|
-
|
|
114
|
-
def _validate_form(self) -> bool:
|
|
115
|
-
"""Validate required fields"""
|
|
116
|
-
name_input = self.query_one("#name", Input)
|
|
117
|
-
api_url_input = self.query_one("#api_url", Input)
|
|
118
|
-
error_message = self.query_one("#error-message", Static)
|
|
119
|
-
|
|
120
|
-
errors = []
|
|
121
|
-
|
|
122
|
-
# Clear previous error state
|
|
123
|
-
name_input.remove_class("error")
|
|
124
|
-
api_url_input.remove_class("error")
|
|
125
|
-
|
|
126
|
-
if not name_input.value.strip():
|
|
127
|
-
name_input.add_class("error")
|
|
128
|
-
errors.append("Profile name is required")
|
|
129
|
-
|
|
130
|
-
if not api_url_input.value.strip():
|
|
131
|
-
api_url_input.add_class("error")
|
|
132
|
-
errors.append("API URL is required")
|
|
133
|
-
|
|
134
|
-
if errors:
|
|
135
|
-
error_message.update("; ".join(errors))
|
|
136
|
-
error_message.add_class("visible")
|
|
137
|
-
return False
|
|
138
|
-
else:
|
|
139
|
-
error_message.update("")
|
|
140
|
-
error_message.remove_class("visible")
|
|
141
|
-
return True
|
|
142
|
-
|
|
143
|
-
def _get_form_data(self) -> ProfileForm:
|
|
144
|
-
"""Extract form data from inputs"""
|
|
145
|
-
name_input = self.query_one("#name", Input)
|
|
146
|
-
api_url_input = self.query_one("#api_url", Input)
|
|
147
|
-
project_id_input = self.query_one("#project_id", Input)
|
|
148
|
-
|
|
149
|
-
return ProfileForm(
|
|
150
|
-
name=name_input.value.strip(),
|
|
151
|
-
api_url=api_url_input.value.strip(),
|
|
152
|
-
active_project_id=project_id_input.value.strip(),
|
|
153
|
-
existing_name=self.form_data.existing_name,
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def edit_profile_form(profile: Profile) -> ProfileForm | None:
|
|
158
|
-
"""Launch profile edit form and return result"""
|
|
159
|
-
initial_data = ProfileForm.from_profile(profile)
|
|
160
|
-
initial_data.existing_name = profile.name or None
|
|
161
|
-
app = ProfileEditApp(initial_data)
|
|
162
|
-
return app.run()
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def create_profile_form() -> Optional[ProfileForm]:
|
|
166
|
-
"""Launch profile creation form and return result"""
|
|
167
|
-
return edit_profile_form(
|
|
168
|
-
Profile(
|
|
169
|
-
name="", api_url="https://prod-cloud-llama-deploy", active_project_id=None
|
|
170
|
-
)
|
|
171
|
-
)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
llama_deploy/cli/__init__.py,sha256=d0cda44bc0c9b76a5bff53f216c558541485910be36e94bd306dd0eeee8048c5,740
|
|
2
|
-
llama_deploy/cli/client.py,sha256=1518e395291356538e2c7c63b1ad424c5484161b921fc0f54d7f6ad5cdcd0ccc,6385
|
|
3
|
-
llama_deploy/cli/commands.py,sha256=d8ce617e18d0a7220d9c7f467f71db54bc6cfc49c3447fd5ae81c84bc95afe8f,17586
|
|
4
|
-
llama_deploy/cli/config.py,sha256=b339d95fceb7a15a183663032396aaeb2afffe1ddf06494416a6a0183a6658ca,6275
|
|
5
|
-
llama_deploy/cli/debug.py,sha256=e85a72d473bbe1645eb31772f7349bde703d45704166f767385895c440afc762,496
|
|
6
|
-
llama_deploy/cli/env.py,sha256=bb1dcde428c779796ad2b39b58d84f08df75a15031afca577aca0db5ce9a9ea0,1015
|
|
7
|
-
llama_deploy/cli/interactive_prompts/utils.py,sha256=59bd7cab8fe359d5a52e1805e8c9555b5cdf16f3699d7fa3be4d189503c4617f,2482
|
|
8
|
-
llama_deploy/cli/options.py,sha256=78b6e36e39fa88f0587146995e2cb66418b67d16f945f0b7570dab37cf5fc673,576
|
|
9
|
-
llama_deploy/cli/textual/deployment_form.py,sha256=7e7d0e40ae451ca7bf202be746378ae54af30b94b85a3c524ad02933c66cc182,15107
|
|
10
|
-
llama_deploy/cli/textual/git_validation.py,sha256=a00ab16b104b9f2f6c989243a65fb19f4b177562798a1174027e70b596fcb5c9,13378
|
|
11
|
-
llama_deploy/cli/textual/github_callback_server.py,sha256=a74b1f5741bdaa682086771fd73a145e1e22359601f16f036f72a87e64b0a152,7444
|
|
12
|
-
llama_deploy/cli/textual/llama_loader.py,sha256=dfef7118eb42d0fec033731b3f3b16ed4dbf4c551f4059c36e290e73c9aa5d13,1244
|
|
13
|
-
llama_deploy/cli/textual/profile_form.py,sha256=2c6ca4690c22b499cc327b117c97e7914d4243b73faa92c0f5ac9cfdcf59b3d7,6015
|
|
14
|
-
llama_deploy/cli/textual/secrets_form.py,sha256=1fd47a5a5ee9dfa0fd2a86f5888894820897c55fbb0cd30e60d6bc08570288b5,6303
|
|
15
|
-
llama_deploy/cli/textual/styles.tcss,sha256=72338c5634bae0547384669382c4e06deec1380ef7bbc31099b1dca8ce49b2d0,2711
|
|
16
|
-
llamactl-0.2.7a1.dist-info/WHEEL,sha256=cc8ae5806c5874a696cde0fcf78fdf73db4982e7c824f3ceab35e2b65182fa1a,79
|
|
17
|
-
llamactl-0.2.7a1.dist-info/entry_points.txt,sha256=b67e1eb64305058751a651a80f2d2268b5f7046732268421e796f64d4697f83c,52
|
|
18
|
-
llamactl-0.2.7a1.dist-info/METADATA,sha256=b74e8a9886a9823e5b65476b5b0712a17d73d3ef6117e058c86bd290a70fac8a,3137
|
|
19
|
-
llamactl-0.2.7a1.dist-info/RECORD,,
|
|
File without changes
|