llamactl 0.3.0a12__py3-none-any.whl → 0.3.0a14__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.
@@ -1,217 +0,0 @@
1
- import click
2
- from llama_deploy.cli.client import get_control_plane_client
3
- from rich import print as rprint
4
- from rich.table import Table
5
-
6
- from ..app import app, console
7
- from ..config import config_manager
8
- from ..interactive_prompts.utils import (
9
- select_profile,
10
- )
11
- from ..options import global_options
12
- from ..textual.profile_form import create_profile_form, edit_profile_form
13
-
14
-
15
- # Create sub-applications for organizing commands
16
- @app.group(
17
- help="Login to manage deployments and switch between projects",
18
- no_args_is_help=True,
19
- )
20
- @global_options
21
- def profiles() -> None:
22
- """Manage profiles"""
23
- pass
24
-
25
-
26
- # Profile commands
27
- @profiles.command("create")
28
- @global_options
29
- @click.option("--name", help="Profile name")
30
- @click.option("--api-url", help="API server URL")
31
- @click.option("--project-id", help="Default project ID")
32
- def create_profile(
33
- name: str | None, api_url: str | None, project_id: str | None
34
- ) -> None:
35
- """Create a new profile"""
36
- try:
37
- # If all required args are provided via CLI, skip interactive mode
38
- if name and api_url:
39
- # Use CLI args directly
40
- profile = config_manager.create_profile(name, api_url, project_id)
41
- rprint(f"[green]Created profile '{profile.name}'[/green]")
42
-
43
- # Automatically switch to the new profile
44
- config_manager.set_current_profile(name)
45
- rprint(f"[green]Switched to profile '{name}'[/green]")
46
- return
47
-
48
- # Use interactive creation
49
- profile = create_profile_form()
50
- if profile is None:
51
- rprint("[yellow]Cancelled[/yellow]")
52
- return
53
-
54
- try:
55
- rprint(f"[green]Created profile '{profile.name}'[/green]")
56
-
57
- # Automatically switch to the new profile
58
- config_manager.set_current_profile(profile.name)
59
- rprint(f"[green]Switched to profile '{profile.name}'[/green]")
60
- except Exception as e:
61
- rprint(f"[red]Error creating profile: {e}[/red]")
62
- raise click.Abort()
63
-
64
- except ValueError as e:
65
- rprint(f"[red]Error: {e}[/red]")
66
- raise click.Abort()
67
- except Exception as e:
68
- rprint(f"[red]Error: {e}[/red]")
69
- raise click.Abort()
70
-
71
-
72
- @profiles.command("list")
73
- @global_options
74
- def list_profiles() -> None:
75
- """List all profiles"""
76
- try:
77
- profiles = config_manager.list_profiles()
78
- current_name = config_manager.get_current_profile_name()
79
-
80
- if not profiles:
81
- rprint("[yellow]No profiles found[/yellow]")
82
- rprint("Create one with: [cyan]llamactl profile create[/cyan]")
83
- return
84
-
85
- table = Table(title="Profiles")
86
- table.add_column("Name", style="cyan")
87
- table.add_column("API URL", style="green")
88
- table.add_column("Active Project", style="yellow")
89
- table.add_column("Current", style="magenta")
90
-
91
- for profile in profiles:
92
- is_current = "✓" if profile.name == current_name else ""
93
- active_project = profile.active_project_id or "-"
94
- table.add_row(profile.name, profile.api_url, active_project, is_current)
95
-
96
- console.print(table)
97
-
98
- except Exception as e:
99
- rprint(f"[red]Error: {e}[/red]")
100
- raise click.Abort()
101
-
102
-
103
- @profiles.command("switch")
104
- @global_options
105
- @click.argument("name", required=False)
106
- def switch_profile(name: str | None) -> None:
107
- """Switch to a different profile"""
108
- try:
109
- name = select_profile(name)
110
- if not name:
111
- rprint("[yellow]No profile selected[/yellow]")
112
- return
113
-
114
- profile = config_manager.get_profile(name)
115
- if not profile:
116
- rprint(f"[red]Profile '{name}' not found[/red]")
117
- raise click.Abort()
118
-
119
- config_manager.set_current_profile(name)
120
- rprint(f"[green]Switched to profile '{name}'[/green]")
121
-
122
- except Exception as e:
123
- rprint(f"[red]Error: {e}[/red]")
124
- raise click.Abort()
125
-
126
-
127
- @profiles.command("delete")
128
- @global_options
129
- @click.argument("name", required=False)
130
- def delete_profile(name: str | None) -> None:
131
- """Delete a profile"""
132
- try:
133
- name = select_profile(name)
134
- if not name:
135
- rprint("[yellow]No profile selected[/yellow]")
136
- return
137
-
138
- profile = config_manager.get_profile(name)
139
- if not profile:
140
- rprint(f"[red]Profile '{name}' not found[/red]")
141
- raise click.Abort()
142
-
143
- if config_manager.delete_profile(name):
144
- rprint(f"[green]Deleted profile '{name}'[/green]")
145
- else:
146
- rprint(f"[red]Profile '{name}' not found[/red]")
147
-
148
- except Exception as e:
149
- rprint(f"[red]Error: {e}[/red]")
150
- raise click.Abort()
151
-
152
-
153
- @profiles.command("edit")
154
- @global_options
155
- @click.argument("name", required=False)
156
- def edit_profile(name: str | None) -> None:
157
- """Edit a profile"""
158
- try:
159
- name = select_profile(name)
160
- if not name:
161
- rprint("[yellow]No profile selected[/yellow]")
162
- return
163
-
164
- # Get current profile
165
- maybe_profile = config_manager.get_profile(name)
166
- if not maybe_profile:
167
- rprint(f"[red]Profile '{name}' not found[/red]")
168
- raise click.Abort()
169
- profile = maybe_profile
170
-
171
- # Use the interactive edit menu
172
- updated = edit_profile_form(profile)
173
- if updated is None:
174
- rprint("[yellow]Cancelled[/yellow]")
175
- return
176
-
177
- try:
178
- current_profile = config_manager.get_current_profile()
179
- if not current_profile or current_profile.name != updated.name:
180
- config_manager.set_current_profile(updated.name)
181
- rprint(f"[green]Updated profile '{profile.name}'[/green]")
182
- except Exception as e:
183
- rprint(f"[red]Error updating profile: {e}[/red]")
184
- raise click.Abort()
185
-
186
- except Exception as e:
187
- rprint(f"[red]Error: {e}[/red]")
188
- raise click.Abort()
189
-
190
-
191
- # Projects commands
192
- @profiles.command("list-projects")
193
- @global_options
194
- def list_projects() -> None:
195
- """List all projects with deployment counts"""
196
- try:
197
- client = get_control_plane_client()
198
- projects = client.list_projects()
199
-
200
- if not projects:
201
- rprint("[yellow]No projects found[/yellow]")
202
- return
203
-
204
- table = Table(title="Projects")
205
- table.add_column("Project ID", style="cyan")
206
- table.add_column("Deployments", style="green")
207
-
208
- for project in projects:
209
- project_id = project.project_id
210
- deployment_count = project.deployment_count
211
- table.add_row(project_id, str(deployment_count))
212
-
213
- console.print(table)
214
-
215
- except Exception as e:
216
- rprint(f"[red]Error: {e}[/red]")
217
- raise click.Abort()
@@ -1,173 +0,0 @@
1
- """Configuration and profile management for llamactl"""
2
-
3
- import os
4
- import sqlite3
5
- from dataclasses import dataclass
6
- from pathlib import Path
7
- from typing import List
8
-
9
-
10
- @dataclass
11
- class Profile:
12
- """Profile configuration"""
13
-
14
- name: str
15
- api_url: str
16
- active_project_id: str | None = 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: str | None = 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) -> Profile | None:
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: str | None):
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) -> str | None:
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) -> Profile | None:
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: str | None) -> 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) -> str | None:
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,170 +0,0 @@
1
- """Textual-based forms for CLI interactions"""
2
-
3
- from dataclasses import dataclass
4
- from pathlib import Path
5
-
6
- from textual import events
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, config_manager
16
-
17
-
18
- @dataclass
19
- class ProfileForm:
20
- """Form data for profile editing/creation"""
21
-
22
- name: str = ""
23
- api_url: str = ""
24
- active_project_id: str = ""
25
- existing_name: str | None = None
26
-
27
- @classmethod
28
- def from_profile(cls, profile: Profile) -> "ProfileForm":
29
- """Create form from existing profile"""
30
- return cls(
31
- name=profile.name,
32
- api_url=profile.api_url,
33
- active_project_id=profile.active_project_id or "",
34
- )
35
-
36
-
37
- class ProfileEditApp(App[ProfileForm | None]):
38
- """Textual app for editing profiles"""
39
-
40
- CSS_PATH = Path(__file__).parent / "styles.tcss"
41
-
42
- def __init__(self, initial_data: ProfileForm):
43
- super().__init__()
44
- self.form_data = initial_data
45
-
46
- def on_mount(self) -> None:
47
- self.theme = "tokyo-night"
48
-
49
- def on_key(self, event: events.Key) -> None:
50
- """Handle key events, including Ctrl+C"""
51
- if event.key == "ctrl+c":
52
- self.exit(None)
53
-
54
- def compose(self) -> ComposeResult:
55
- with Container(classes="form-container"):
56
- title = "Edit Profile" if self.form_data.existing_name else "Create Profile"
57
- yield Static(title, classes="primary-message")
58
- yield Static("", id="error-message", classes="error-message hidden")
59
- with Static(classes="two-column-form-grid"):
60
- yield Label(
61
- "Profile Name: *", classes="required form-label", shrink=True
62
- )
63
- yield Input(
64
- value=self.form_data.name,
65
- placeholder="Enter profile name",
66
- validators=[Length(minimum=1)],
67
- id="name",
68
- compact=True,
69
- )
70
- yield Label("API URL: *", classes="required form-label", shrink=True)
71
- yield Input(
72
- value=self.form_data.api_url,
73
- placeholder="http://prod-cloud-llama-deploy",
74
- validators=[Length(minimum=1)],
75
- id="api_url",
76
- compact=True,
77
- )
78
- yield Label("Project ID:", classes="form-label", shrink=True)
79
- yield Input(
80
- value=self.form_data.active_project_id,
81
- placeholder="Optional project ID",
82
- id="project_id",
83
- compact=True,
84
- )
85
- with HorizontalGroup(classes="button-row"):
86
- yield Button("Save", variant="primary", id="save", compact=True)
87
- yield Button("Cancel", variant="default", id="cancel", compact=True)
88
-
89
- def on_button_pressed(self, event: Button.Pressed) -> None:
90
- if event.button.id == "save":
91
- if self._validate_form():
92
- result = self._get_form_data()
93
- try:
94
- if result.existing_name:
95
- config_manager.delete_profile(result.existing_name)
96
- profile = config_manager.create_profile(
97
- result.name,
98
- result.api_url,
99
- result.active_project_id,
100
- )
101
- self.exit(profile)
102
- except Exception as e:
103
- self._handle_error(e)
104
-
105
- elif event.button.id == "cancel":
106
- self.exit(None)
107
-
108
- def _handle_error(self, error: Exception) -> None:
109
- error_message = self.query_one("#error-message", Static)
110
- error_message.update(f"Error creating profile: {error}")
111
- error_message.add_class("visible")
112
-
113
- def _validate_form(self) -> bool:
114
- """Validate required fields"""
115
- name_input = self.query_one("#name", Input)
116
- api_url_input = self.query_one("#api_url", Input)
117
- error_message = self.query_one("#error-message", Static)
118
-
119
- errors = []
120
-
121
- # Clear previous error state
122
- name_input.remove_class("error")
123
- api_url_input.remove_class("error")
124
-
125
- if not name_input.value.strip():
126
- name_input.add_class("error")
127
- errors.append("Profile name is required")
128
-
129
- if not api_url_input.value.strip():
130
- api_url_input.add_class("error")
131
- errors.append("API URL is required")
132
-
133
- if errors:
134
- error_message.update("; ".join(errors))
135
- error_message.add_class("visible")
136
- return False
137
- else:
138
- error_message.update("")
139
- error_message.remove_class("visible")
140
- return True
141
-
142
- def _get_form_data(self) -> ProfileForm:
143
- """Extract form data from inputs"""
144
- name_input = self.query_one("#name", Input)
145
- api_url_input = self.query_one("#api_url", Input)
146
- project_id_input = self.query_one("#project_id", Input)
147
-
148
- return ProfileForm(
149
- name=name_input.value.strip(),
150
- api_url=api_url_input.value.strip(),
151
- active_project_id=project_id_input.value.strip(),
152
- existing_name=self.form_data.existing_name,
153
- )
154
-
155
-
156
- def edit_profile_form(profile: Profile) -> ProfileForm | None:
157
- """Launch profile edit form and return result"""
158
- initial_data = ProfileForm.from_profile(profile)
159
- initial_data.existing_name = profile.name or None
160
- app = ProfileEditApp(initial_data)
161
- return app.run()
162
-
163
-
164
- def create_profile_form() -> ProfileForm | None:
165
- """Launch profile creation form and return result"""
166
- return edit_profile_form(
167
- Profile(
168
- name="", api_url="http://prod-cloud-llama-deploy", active_project_id=None
169
- )
170
- )
@@ -1,27 +0,0 @@
1
- llama_deploy/cli/__init__.py,sha256=274c45e48048bf60668ab564ae8e7c5e6daf1d7779005f87d07ce9fa7d04936c,422
2
- llama_deploy/cli/app.py,sha256=5200b4ac01b0ad0c405ce841fc01a12ed32f7b6474472f00a7d6c75fe274ea45,2324
3
- llama_deploy/cli/client.py,sha256=f88cc30cf6df39fa68fb0aefe668634e4fd7216895d524b9af6f7c5727ba9da4,1510
4
- llama_deploy/cli/commands/aliased_group.py,sha256=bc41007c97b7b93981217dbd4d4591df2b6c9412a2d9ed045b0ec5655ed285f2,1066
5
- llama_deploy/cli/commands/deployment.py,sha256=7874f4a499ce1bfd6ae14833410cc75c4c954463d96064cfd045421358479d4c,8810
6
- llama_deploy/cli/commands/init.py,sha256=51b2de1e35ff34bc15c9dfec72fbad08aaf528c334df168896d36458a4e9401c,6307
7
- llama_deploy/cli/commands/profile.py,sha256=933d7a434c2684c7b47bfbd7340a09e4b34d56d20624886e15fdb4e0af97ce0b,6765
8
- llama_deploy/cli/commands/serve.py,sha256=4d47850397ba172944df56a934a51bedb52403cbd3f9b000b1ced90a31c75049,2721
9
- llama_deploy/cli/config.py,sha256=ebec8cf9e2112378ee6ecd626166711f3fba8cfa27cd1c931fe899c0b2a047b3,6241
10
- llama_deploy/cli/debug.py,sha256=e85a72d473bbe1645eb31772f7349bde703d45704166f767385895c440afc762,496
11
- llama_deploy/cli/env.py,sha256=6ebc24579815b3787829c81fd5bb9f31698a06e62c0128a788559f962b33a7af,1016
12
- llama_deploy/cli/interactive_prompts/utils.py,sha256=db78eba78bf347738feb89ac3eeb77a1d11f4003980f81cf3c13842f8d41afeb,2463
13
- llama_deploy/cli/options.py,sha256=e71d1a306e9e302b92ab55ace75f3a9273be267ae92a71226aac84c14271619d,823
14
- llama_deploy/cli/py.typed,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
15
- llama_deploy/cli/textual/deployment_form.py,sha256=33d4f3f0741aeaf0072f5f91368b3c0b0456c72370b72964363240d790b3fa06,20929
16
- llama_deploy/cli/textual/deployment_help.py,sha256=d43e9ff29db71a842cf8b491545763d581ede3132b8af518c73af85a40950046,2464
17
- llama_deploy/cli/textual/deployment_monitor.py,sha256=a2c4e48c5494f63a2b8dcc9ab1b11b4ce50d78ef94c0f54095448ce9d01fa59b,16782
18
- llama_deploy/cli/textual/git_validation.py,sha256=fcbe5477c99e8e669b31c563572d4894f61475ef7e968a59d9f172642d390cf7,13329
19
- llama_deploy/cli/textual/github_callback_server.py,sha256=dc74c510f8a98ef6ffaab0f6d11c7ea86ee77ca5adbc7725a2a29112bae24191,7556
20
- llama_deploy/cli/textual/llama_loader.py,sha256=33cb32a46dd40bcf889c553e44f2672c410e26bd1d4b17aa6cca6d0a5d59c2c4,1468
21
- llama_deploy/cli/textual/profile_form.py,sha256=747644895774e7416620d2071f6f054b06ec8e398ac0e7649386caa2a83fe2aa,5995
22
- llama_deploy/cli/textual/secrets_form.py,sha256=a43fbd81aad034d0d60906bfd917c107f9ace414648b0f63ac0b29eeba4050db,7061
23
- llama_deploy/cli/textual/styles.tcss,sha256=536cec7627d2a16dd03bf25bb9b6e4d53f1e0d18272b07ec0dc3bf76b0a7c2e0,3056
24
- llamactl-0.3.0a12.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
25
- llamactl-0.3.0a12.dist-info/entry_points.txt,sha256=b67e1eb64305058751a651a80f2d2268b5f7046732268421e796f64d4697f83c,52
26
- llamactl-0.3.0a12.dist-info/METADATA,sha256=9211df91592467022d69271234445d3cfe20593a0dc2961f7a47fb8fdbc19185,3177
27
- llamactl-0.3.0a12.dist-info/RECORD,,