griptape-nodes 0.52.1__py3-none-any.whl → 0.54.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.
- griptape_nodes/__init__.py +8 -942
- griptape_nodes/__main__.py +6 -0
- griptape_nodes/app/app.py +48 -86
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +35 -5
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +15 -1
- griptape_nodes/cli/__init__.py +1 -0
- griptape_nodes/cli/commands/__init__.py +1 -0
- griptape_nodes/cli/commands/config.py +74 -0
- griptape_nodes/cli/commands/engine.py +80 -0
- griptape_nodes/cli/commands/init.py +550 -0
- griptape_nodes/cli/commands/libraries.py +96 -0
- griptape_nodes/cli/commands/models.py +504 -0
- griptape_nodes/cli/commands/self.py +120 -0
- griptape_nodes/cli/main.py +56 -0
- griptape_nodes/cli/shared.py +75 -0
- griptape_nodes/common/__init__.py +1 -0
- griptape_nodes/common/directed_graph.py +71 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +40 -20
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +24 -29
- griptape_nodes/drivers/storage/local_storage_driver.py +23 -14
- griptape_nodes/exe_types/core_types.py +60 -2
- griptape_nodes/exe_types/node_types.py +257 -38
- griptape_nodes/exe_types/param_components/__init__.py +1 -0
- griptape_nodes/exe_types/param_components/execution_status_component.py +138 -0
- griptape_nodes/machines/control_flow.py +195 -94
- griptape_nodes/machines/dag_builder.py +207 -0
- griptape_nodes/machines/fsm.py +10 -1
- griptape_nodes/machines/parallel_resolution.py +558 -0
- griptape_nodes/machines/{node_resolution.py → sequential_resolution.py} +30 -57
- griptape_nodes/node_library/library_registry.py +34 -1
- griptape_nodes/retained_mode/events/app_events.py +5 -1
- griptape_nodes/retained_mode/events/base_events.py +9 -9
- griptape_nodes/retained_mode/events/config_events.py +30 -0
- griptape_nodes/retained_mode/events/execution_events.py +2 -2
- griptape_nodes/retained_mode/events/model_events.py +296 -0
- griptape_nodes/retained_mode/events/node_events.py +4 -3
- griptape_nodes/retained_mode/griptape_nodes.py +34 -12
- griptape_nodes/retained_mode/managers/agent_manager.py +23 -5
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +3 -1
- griptape_nodes/retained_mode/managers/config_manager.py +44 -3
- griptape_nodes/retained_mode/managers/context_manager.py +6 -5
- griptape_nodes/retained_mode/managers/event_manager.py +8 -2
- griptape_nodes/retained_mode/managers/flow_manager.py +150 -206
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +1 -1
- griptape_nodes/retained_mode/managers/library_manager.py +35 -25
- griptape_nodes/retained_mode/managers/model_manager.py +1107 -0
- griptape_nodes/retained_mode/managers/node_manager.py +102 -220
- griptape_nodes/retained_mode/managers/object_manager.py +11 -5
- griptape_nodes/retained_mode/managers/os_manager.py +28 -13
- griptape_nodes/retained_mode/managers/secrets_manager.py +8 -4
- griptape_nodes/retained_mode/managers/settings.py +116 -7
- griptape_nodes/retained_mode/managers/static_files_manager.py +85 -12
- griptape_nodes/retained_mode/managers/sync_manager.py +17 -9
- griptape_nodes/retained_mode/managers/workflow_manager.py +186 -192
- griptape_nodes/retained_mode/retained_mode.py +19 -0
- griptape_nodes/servers/__init__.py +1 -0
- griptape_nodes/{mcp_server/server.py → servers/mcp.py} +1 -1
- griptape_nodes/{app/api.py → servers/static.py} +43 -40
- griptape_nodes/traits/add_param_button.py +1 -1
- griptape_nodes/traits/button.py +334 -6
- griptape_nodes/traits/color_picker.py +66 -0
- griptape_nodes/traits/multi_options.py +188 -0
- griptape_nodes/traits/numbers_selector.py +77 -0
- griptape_nodes/traits/options.py +93 -2
- griptape_nodes/traits/traits.json +4 -0
- griptape_nodes/utils/async_utils.py +31 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/METADATA +4 -1
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/RECORD +71 -48
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/WHEEL +1 -1
- /griptape_nodes/{mcp_server → servers}/ws_request_manager.py +0 -0
- {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""Models command for managing AI models."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from griptape_nodes.cli.shared import console
|
|
10
|
+
from griptape_nodes.retained_mode.events.model_events import (
|
|
11
|
+
DeleteModelDownloadRequest,
|
|
12
|
+
DeleteModelDownloadResultFailure,
|
|
13
|
+
DeleteModelDownloadResultSuccess,
|
|
14
|
+
DeleteModelRequest,
|
|
15
|
+
DeleteModelResultFailure,
|
|
16
|
+
DeleteModelResultSuccess,
|
|
17
|
+
ListModelDownloadsRequest,
|
|
18
|
+
ListModelDownloadsResultFailure,
|
|
19
|
+
ListModelDownloadsResultSuccess,
|
|
20
|
+
ListModelsRequest,
|
|
21
|
+
ListModelsResultFailure,
|
|
22
|
+
ListModelsResultSuccess,
|
|
23
|
+
ModelInfo,
|
|
24
|
+
QueryInfo,
|
|
25
|
+
SearchModelsRequest,
|
|
26
|
+
SearchModelsResultFailure,
|
|
27
|
+
SearchModelsResultSuccess,
|
|
28
|
+
)
|
|
29
|
+
from griptape_nodes.retained_mode.retained_mode import GriptapeNodes
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from griptape_nodes.retained_mode.events.model_events import ModelDownloadStatus
|
|
33
|
+
|
|
34
|
+
app = typer.Typer(help="Manage AI models.")
|
|
35
|
+
downloads_app = typer.Typer(help="Manage model download tracking records.")
|
|
36
|
+
|
|
37
|
+
# Add downloads subcommand
|
|
38
|
+
app.add_typer(downloads_app, name="downloads")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@app.command("download")
|
|
42
|
+
def download_command(
|
|
43
|
+
model_id: str = typer.Argument(..., help="Model ID or URL (e.g., 'microsoft/DialoGPT-medium')"),
|
|
44
|
+
local_dir: str | None = typer.Option(None, "--local-dir", help="Local directory to download the model to"),
|
|
45
|
+
revision: str = typer.Option("main", "--revision", help="Git revision to download"),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Download a model from Hugging Face Hub."""
|
|
48
|
+
asyncio.run(_download_model(model_id, local_dir, revision))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command("list")
|
|
52
|
+
def list_command() -> None:
|
|
53
|
+
"""List all downloaded model files in local cache."""
|
|
54
|
+
asyncio.run(_list_models())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command("delete")
|
|
58
|
+
def delete_command(
|
|
59
|
+
model_id: str = typer.Argument(..., help="Model ID to delete (e.g., 'microsoft/DialoGPT-medium')"),
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Delete model files from local cache."""
|
|
62
|
+
asyncio.run(_delete_model(model_id))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@downloads_app.command("status")
|
|
66
|
+
def downloads_status_command(
|
|
67
|
+
model_id: str = typer.Argument(None, help="Optional model ID to check download status for"),
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Show download status for a specific model or all models."""
|
|
70
|
+
asyncio.run(_get_model_status(model_id))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@downloads_app.command("list")
|
|
74
|
+
def downloads_list_command() -> None:
|
|
75
|
+
"""List all model download status records."""
|
|
76
|
+
asyncio.run(_get_model_status(None))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@downloads_app.command("delete")
|
|
80
|
+
def downloads_delete_command(
|
|
81
|
+
model_id: str = typer.Argument(
|
|
82
|
+
..., help="Model ID to delete download status for (e.g., 'microsoft/DialoGPT-medium')"
|
|
83
|
+
),
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Delete download status tracking records for a model."""
|
|
86
|
+
asyncio.run(_delete_model_status(model_id))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.command("search")
|
|
90
|
+
def search_command(
|
|
91
|
+
query: str | None = typer.Argument(None, help="Search query to match against model names"),
|
|
92
|
+
task: str | None = typer.Option(None, "--task", help="Filter by task type"),
|
|
93
|
+
limit: int = typer.Option(20, "--limit", help="Maximum number of results (max: 100)"),
|
|
94
|
+
sort: str = typer.Option("downloads", "--sort", help="Sort results by"),
|
|
95
|
+
direction: str = typer.Option("desc", "--direction", help="Sort direction"),
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Search for models on Hugging Face Hub."""
|
|
98
|
+
asyncio.run(_search_models(query, task, limit, sort, direction))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def _download_model(
|
|
102
|
+
model_id: str,
|
|
103
|
+
local_dir: str | None,
|
|
104
|
+
revision: str,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Download a model from Hugging Face Hub.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
model_id: Model ID or URL to download
|
|
110
|
+
local_dir: Local directory to download the model to
|
|
111
|
+
revision: Git revision to download
|
|
112
|
+
"""
|
|
113
|
+
console.print(f"[bold green]Downloading model: {model_id}[/bold green]")
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
# ModelManager DownloadModelRequest will use this command so it's important that we don't use the request ourselves
|
|
117
|
+
model_manager = GriptapeNodes.ModelManager()
|
|
118
|
+
local_path = model_manager.download_model(
|
|
119
|
+
model_id=model_id,
|
|
120
|
+
local_dir=local_dir,
|
|
121
|
+
revision=revision,
|
|
122
|
+
allow_patterns=None,
|
|
123
|
+
ignore_patterns=None,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Success case
|
|
127
|
+
console.print("[bold green]Model downloaded successfully![/bold green]")
|
|
128
|
+
console.print(f"[green]Downloaded to: {local_path}[/green]")
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
console.print("[bold red]Model download failed:[/bold red]")
|
|
132
|
+
console.print(f"[red]{e}[/red]")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async def _list_models() -> None:
|
|
136
|
+
"""List all downloaded models in the local cache."""
|
|
137
|
+
console.print("[bold green]Listing cached models...[/bold green]")
|
|
138
|
+
|
|
139
|
+
# Create the list request
|
|
140
|
+
request = ListModelsRequest()
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
# Use the ModelManager to handle the listing
|
|
144
|
+
result = await GriptapeNodes.ahandle_request(request)
|
|
145
|
+
if isinstance(result, ListModelsResultSuccess):
|
|
146
|
+
# Success case
|
|
147
|
+
models = result.models
|
|
148
|
+
if models:
|
|
149
|
+
console.print(f"[bold green]Found {len(models)} cached models:[/bold green]")
|
|
150
|
+
|
|
151
|
+
table = Table()
|
|
152
|
+
table.add_column("Model ID", style="green")
|
|
153
|
+
table.add_column("Size (GB)", style="cyan", justify="right")
|
|
154
|
+
|
|
155
|
+
for model in models:
|
|
156
|
+
size_gb = round((model.size_bytes or 0) / (1024**3), 2) if model.size_bytes else 0.0
|
|
157
|
+
table.add_row(model.model_id, str(size_gb))
|
|
158
|
+
console.print(table)
|
|
159
|
+
else:
|
|
160
|
+
console.print("[yellow]No models found in local cache[/yellow]")
|
|
161
|
+
|
|
162
|
+
# Failure case
|
|
163
|
+
|
|
164
|
+
elif isinstance(result, ListModelsResultFailure):
|
|
165
|
+
console.print("[bold red]Model listing failed:[/bold red]")
|
|
166
|
+
if result.result_details:
|
|
167
|
+
console.print(f"[red]{result.result_details}[/red]")
|
|
168
|
+
if result.exception:
|
|
169
|
+
console.print(f"[dim]Error: {result.exception}[/dim]")
|
|
170
|
+
else:
|
|
171
|
+
console.print("[bold red]Model listing failed: Unknown error occurred[/bold red]")
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
console.print("[bold red]Unexpected error during model listing:[/bold red]")
|
|
175
|
+
console.print(f"[red]{e}[/red]")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
async def _delete_model(model_id: str) -> None:
|
|
179
|
+
"""Delete a model from the local cache.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
model_id: Model ID to delete
|
|
183
|
+
"""
|
|
184
|
+
console.print(f"[bold yellow]Deleting model: {model_id}[/bold yellow]")
|
|
185
|
+
|
|
186
|
+
# Create the delete request
|
|
187
|
+
request = DeleteModelRequest(model_id=model_id)
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
# Use the ModelManager to handle the deletion
|
|
191
|
+
result = await GriptapeNodes.ahandle_request(request)
|
|
192
|
+
|
|
193
|
+
if isinstance(result, DeleteModelResultSuccess):
|
|
194
|
+
# Success case
|
|
195
|
+
console.print("[bold green]Model deleted successfully![/bold green]")
|
|
196
|
+
console.print(f"[green]Deleted: {result.deleted_path}[/green]")
|
|
197
|
+
# Failure case
|
|
198
|
+
|
|
199
|
+
elif isinstance(result, DeleteModelResultFailure):
|
|
200
|
+
console.print("[bold red]Model deletion failed:[/bold red]")
|
|
201
|
+
if result.result_details:
|
|
202
|
+
console.print(f"[red]{result.result_details}[/red]")
|
|
203
|
+
if result.exception:
|
|
204
|
+
console.print(f"[dim]Error: {result.exception}[/dim]")
|
|
205
|
+
else:
|
|
206
|
+
console.print("[bold red]Model deletion failed: Unknown error occurred[/bold red]")
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
console.print("[bold red]Unexpected error during model deletion:[/bold red]")
|
|
210
|
+
console.print(f"[red]{e}[/red]")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _format_download_row(download: "ModelDownloadStatus") -> tuple[str, str, str, str, str, str]:
|
|
214
|
+
"""Format a download status object into table row data.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
download: ModelDownloadStatus object
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
tuple: (model_id, status_colored, progress_str, size_str, eta_str, started_str)
|
|
221
|
+
"""
|
|
222
|
+
progress_str = _format_progress(download)
|
|
223
|
+
size_str = _format_size(download)
|
|
224
|
+
eta_str = _format_eta(download)
|
|
225
|
+
started_str = _format_timestamp(download)
|
|
226
|
+
status_colored = _format_status(download)
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
download.model_id,
|
|
230
|
+
status_colored,
|
|
231
|
+
progress_str,
|
|
232
|
+
size_str,
|
|
233
|
+
eta_str,
|
|
234
|
+
started_str,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _format_progress(download: "ModelDownloadStatus") -> str:
|
|
239
|
+
"""Format download progress information."""
|
|
240
|
+
if download.total_files is not None and download.completed_files is not None and download.total_files > 0:
|
|
241
|
+
progress_percent = (download.completed_files / download.total_files) * 100
|
|
242
|
+
return f"{progress_percent:.1f}%"
|
|
243
|
+
return "Unknown"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _format_size(download: "ModelDownloadStatus") -> str:
|
|
247
|
+
"""Format download size information."""
|
|
248
|
+
if download.total_files is not None and download.completed_files is not None:
|
|
249
|
+
return f"{download.completed_files}/{download.total_files} files"
|
|
250
|
+
return "Unknown"
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _format_eta(download: "ModelDownloadStatus") -> str:
|
|
254
|
+
"""Format estimated time of arrival."""
|
|
255
|
+
# ETA is not available in the current ModelDownloadStatus structure
|
|
256
|
+
# For active downloads, we could potentially calculate based on progress
|
|
257
|
+
# but without timing data, we return a status-appropriate message
|
|
258
|
+
if download.status == "downloading":
|
|
259
|
+
return "In progress"
|
|
260
|
+
if download.status == "completed":
|
|
261
|
+
return "Completed"
|
|
262
|
+
if download.status == "failed":
|
|
263
|
+
return "Failed"
|
|
264
|
+
return "Unknown"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _format_timestamp(download: "ModelDownloadStatus") -> str:
|
|
268
|
+
"""Format started timestamp."""
|
|
269
|
+
started_at = download.started_at
|
|
270
|
+
if not started_at:
|
|
271
|
+
return "Unknown"
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
from datetime import datetime
|
|
275
|
+
|
|
276
|
+
dt = datetime.fromisoformat(started_at)
|
|
277
|
+
return dt.strftime("%H:%M:%S")
|
|
278
|
+
except Exception:
|
|
279
|
+
return started_at[:10] # Fallback
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _format_status(download: "ModelDownloadStatus") -> str:
|
|
283
|
+
"""Format status with color coding."""
|
|
284
|
+
status = download.status
|
|
285
|
+
status_colors = {
|
|
286
|
+
"completed": "green",
|
|
287
|
+
"failed": "red",
|
|
288
|
+
"downloading": "yellow",
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if status in status_colors:
|
|
292
|
+
return f"[{status_colors[status]}]{status}[/{status_colors[status]}]"
|
|
293
|
+
return status
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _display_downloads_table(downloads: list["ModelDownloadStatus"]) -> None:
|
|
297
|
+
"""Display downloads in a formatted table.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
downloads: List of ModelDownloadStatus objects
|
|
301
|
+
"""
|
|
302
|
+
console.print(f"[bold green]Found {len(downloads)} download(s):[/bold green]")
|
|
303
|
+
|
|
304
|
+
table = Table()
|
|
305
|
+
table.add_column("Model ID", style="green")
|
|
306
|
+
table.add_column("Status", style="cyan")
|
|
307
|
+
table.add_column("Progress", style="yellow", justify="right")
|
|
308
|
+
table.add_column("Size", style="blue", justify="right")
|
|
309
|
+
table.add_column("ETA", style="magenta", justify="right")
|
|
310
|
+
table.add_column("Started", style="dim")
|
|
311
|
+
|
|
312
|
+
for download in downloads:
|
|
313
|
+
row_data = _format_download_row(download)
|
|
314
|
+
table.add_row(*row_data)
|
|
315
|
+
|
|
316
|
+
console.print(table)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
async def _get_model_status(model_id: str | None) -> None:
|
|
320
|
+
"""Get download status for models.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
model_id: Optional model ID to get status for
|
|
324
|
+
"""
|
|
325
|
+
if model_id:
|
|
326
|
+
console.print(f"[bold green]Getting download status for: {model_id}[/bold green]")
|
|
327
|
+
else:
|
|
328
|
+
console.print("[bold green]Getting download status for all models...[/bold green]")
|
|
329
|
+
|
|
330
|
+
# Create the status request
|
|
331
|
+
request = ListModelDownloadsRequest(model_id=model_id)
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
# Use the ModelManager to handle the status query
|
|
335
|
+
result = await GriptapeNodes.ahandle_request(request)
|
|
336
|
+
|
|
337
|
+
if isinstance(result, ListModelDownloadsResultSuccess):
|
|
338
|
+
# Success case
|
|
339
|
+
downloads = result.downloads
|
|
340
|
+
if downloads:
|
|
341
|
+
_display_downloads_table(downloads)
|
|
342
|
+
elif model_id:
|
|
343
|
+
console.print(f"[yellow]No download found for model: {model_id}[/yellow]")
|
|
344
|
+
else:
|
|
345
|
+
console.print("[yellow]No downloads found[/yellow]")
|
|
346
|
+
|
|
347
|
+
elif isinstance(result, ListModelDownloadsResultFailure):
|
|
348
|
+
console.print("[bold red]Failed to get download status:[/bold red]")
|
|
349
|
+
if result.result_details:
|
|
350
|
+
console.print(f"[red]{result.result_details}[/red]")
|
|
351
|
+
if result.exception:
|
|
352
|
+
console.print(f"[dim]Error: {result.exception}[/dim]")
|
|
353
|
+
else:
|
|
354
|
+
console.print("[bold red]Failed to get download status: Unknown error occurred[/bold red]")
|
|
355
|
+
|
|
356
|
+
except Exception as e:
|
|
357
|
+
console.print("[bold red]Unexpected error getting download status:[/bold red]")
|
|
358
|
+
console.print(f"[red]{e}[/red]")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
async def _search_models(
|
|
362
|
+
query: str | None,
|
|
363
|
+
task: str | None,
|
|
364
|
+
limit: int,
|
|
365
|
+
sort: str,
|
|
366
|
+
direction: str,
|
|
367
|
+
) -> None:
|
|
368
|
+
"""Search for models on Hugging Face Hub.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
query: Search query to match against model names
|
|
372
|
+
task: Filter by task type
|
|
373
|
+
limit: Maximum number of results
|
|
374
|
+
sort: Sort results by
|
|
375
|
+
direction: Sort direction
|
|
376
|
+
"""
|
|
377
|
+
if query:
|
|
378
|
+
console.print(f"[bold green]Searching for models: {query}[/bold green]")
|
|
379
|
+
else:
|
|
380
|
+
console.print("[bold green]Searching for models...[/bold green]")
|
|
381
|
+
|
|
382
|
+
# Create the search request
|
|
383
|
+
request = SearchModelsRequest(
|
|
384
|
+
query=query,
|
|
385
|
+
task=task,
|
|
386
|
+
library=None,
|
|
387
|
+
author=None,
|
|
388
|
+
tags=None,
|
|
389
|
+
limit=limit,
|
|
390
|
+
sort=sort,
|
|
391
|
+
direction=direction,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
try:
|
|
395
|
+
# Use the ModelManager to handle the search
|
|
396
|
+
result = await GriptapeNodes.ahandle_request(request)
|
|
397
|
+
|
|
398
|
+
if isinstance(result, SearchModelsResultSuccess):
|
|
399
|
+
# Success case
|
|
400
|
+
models = result.models
|
|
401
|
+
if models:
|
|
402
|
+
_display_search_results(models, result.query_info)
|
|
403
|
+
else:
|
|
404
|
+
console.print("[yellow]No models found matching the search criteria[/yellow]")
|
|
405
|
+
|
|
406
|
+
elif isinstance(result, SearchModelsResultFailure):
|
|
407
|
+
console.print("[bold red]Model search failed:[/bold red]")
|
|
408
|
+
if result.result_details:
|
|
409
|
+
console.print(f"[red]{result.result_details}[/red]")
|
|
410
|
+
if result.exception:
|
|
411
|
+
console.print(f"[dim]Error: {result.exception}[/dim]")
|
|
412
|
+
else:
|
|
413
|
+
console.print("[bold red]Model search failed: Unknown error occurred[/bold red]")
|
|
414
|
+
|
|
415
|
+
except Exception as e:
|
|
416
|
+
console.print("[bold red]Unexpected error during model search:[/bold red]")
|
|
417
|
+
console.print(f"[red]{e}[/red]")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _display_search_results(models: list[ModelInfo], query_info: QueryInfo) -> None:
|
|
421
|
+
"""Display model search results in a formatted table.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
models: List of model information
|
|
425
|
+
query_info: Information about the search query
|
|
426
|
+
"""
|
|
427
|
+
console.print(f"[bold green]Found {len(models)} models[/bold green]")
|
|
428
|
+
|
|
429
|
+
# Show search parameters if any were used
|
|
430
|
+
params = []
|
|
431
|
+
if query_info.query:
|
|
432
|
+
params.append(f"query: {query_info.query}")
|
|
433
|
+
if query_info.task:
|
|
434
|
+
params.append(f"task: {query_info.task}")
|
|
435
|
+
if query_info.library:
|
|
436
|
+
params.append(f"library: {query_info.library}")
|
|
437
|
+
if query_info.author:
|
|
438
|
+
params.append(f"author: {query_info.author}")
|
|
439
|
+
if query_info.tags:
|
|
440
|
+
params.append(f"tags: {', '.join(query_info.tags)}")
|
|
441
|
+
|
|
442
|
+
if params:
|
|
443
|
+
console.print(f"[dim]Search parameters: {', '.join(params)}[/dim]")
|
|
444
|
+
|
|
445
|
+
table = Table()
|
|
446
|
+
table.add_column("Model ID", style="green")
|
|
447
|
+
table.add_column("Author", style="blue")
|
|
448
|
+
table.add_column("Downloads", style="cyan", justify="right")
|
|
449
|
+
table.add_column("Likes", style="yellow", justify="right")
|
|
450
|
+
table.add_column("Task", style="magenta")
|
|
451
|
+
table.add_column("Library", style="dim")
|
|
452
|
+
|
|
453
|
+
for model in models:
|
|
454
|
+
downloads_str = f"{model.downloads:,}" if model.downloads else "0"
|
|
455
|
+
likes_str = str(model.likes or 0)
|
|
456
|
+
task_str = model.task or ""
|
|
457
|
+
library_str = model.library or ""
|
|
458
|
+
author_str = model.author or ""
|
|
459
|
+
|
|
460
|
+
table.add_row(
|
|
461
|
+
model.model_id,
|
|
462
|
+
author_str,
|
|
463
|
+
downloads_str,
|
|
464
|
+
likes_str,
|
|
465
|
+
task_str,
|
|
466
|
+
library_str,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
console.print(table)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
async def _delete_model_status(model_id: str) -> None:
|
|
473
|
+
"""Delete download status records for a model.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
model_id: Model ID to delete download status for
|
|
477
|
+
"""
|
|
478
|
+
console.print(f"[bold yellow]Deleting download status for: {model_id}[/bold yellow]")
|
|
479
|
+
|
|
480
|
+
# Create the delete request
|
|
481
|
+
request = DeleteModelDownloadRequest(model_id=model_id)
|
|
482
|
+
|
|
483
|
+
try:
|
|
484
|
+
# Use the ModelManager to handle the deletion
|
|
485
|
+
result = await GriptapeNodes.ahandle_request(request)
|
|
486
|
+
|
|
487
|
+
if isinstance(result, DeleteModelDownloadResultSuccess):
|
|
488
|
+
# Success case
|
|
489
|
+
console.print("[bold green]Download status deleted successfully![/bold green]")
|
|
490
|
+
console.print(f"[green]Deleted status file: {result.deleted_path}[/green]")
|
|
491
|
+
# Failure case
|
|
492
|
+
|
|
493
|
+
elif isinstance(result, DeleteModelDownloadResultFailure):
|
|
494
|
+
console.print("[bold red]Download status deletion failed:[/bold red]")
|
|
495
|
+
if result.result_details:
|
|
496
|
+
console.print(f"[red]{result.result_details}[/red]")
|
|
497
|
+
if result.exception:
|
|
498
|
+
console.print(f"[dim]Error: {result.exception}[/dim]")
|
|
499
|
+
else:
|
|
500
|
+
console.print("[bold red]Download status deletion failed: Unknown error occurred[/bold red]")
|
|
501
|
+
|
|
502
|
+
except Exception as e:
|
|
503
|
+
console.print("[bold red]Unexpected error during download status deletion:[/bold red]")
|
|
504
|
+
console.print(f"[red]{e}[/red]")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Self command for Griptape Nodes CLI."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from griptape_nodes.cli.shared import (
|
|
9
|
+
CONFIG_DIR,
|
|
10
|
+
DATA_DIR,
|
|
11
|
+
GITHUB_UPDATE_URL,
|
|
12
|
+
LATEST_TAG,
|
|
13
|
+
PYPI_UPDATE_URL,
|
|
14
|
+
console,
|
|
15
|
+
)
|
|
16
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
17
|
+
from griptape_nodes.utils.uv_utils import find_uv_bin
|
|
18
|
+
from griptape_nodes.utils.version_utils import (
|
|
19
|
+
get_complete_version_string,
|
|
20
|
+
get_current_version,
|
|
21
|
+
get_latest_version_git,
|
|
22
|
+
get_latest_version_pypi,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
config_manager = GriptapeNodes.ConfigManager()
|
|
26
|
+
secrets_manager = GriptapeNodes.SecretsManager()
|
|
27
|
+
os_manager = GriptapeNodes.OSManager()
|
|
28
|
+
|
|
29
|
+
app = typer.Typer(help="Manage this CLI installation.")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.command()
|
|
33
|
+
def update() -> None:
|
|
34
|
+
"""Update the CLI."""
|
|
35
|
+
_update_self()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.command()
|
|
39
|
+
def uninstall() -> None:
|
|
40
|
+
"""Uninstall the CLI."""
|
|
41
|
+
_uninstall_self()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.command()
|
|
45
|
+
def version() -> None:
|
|
46
|
+
"""Print the CLI version."""
|
|
47
|
+
_print_current_version()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_latest_version(package: str, install_source: str) -> str:
|
|
51
|
+
"""Fetches the latest release tag from PyPI.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
package: The name of the package to fetch the latest version for.
|
|
55
|
+
install_source: The source from which the package is installed (e.g., "pypi", "git", "file").
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: Latest release tag (e.g., "v0.31.4")
|
|
59
|
+
"""
|
|
60
|
+
if install_source == "pypi":
|
|
61
|
+
return get_latest_version_pypi(package, PYPI_UPDATE_URL)
|
|
62
|
+
if install_source == "git":
|
|
63
|
+
return get_latest_version_git(package, GITHUB_UPDATE_URL, LATEST_TAG)
|
|
64
|
+
# If the package is installed from a file, just return the current version since the user is likely managing it manually
|
|
65
|
+
return get_current_version()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _update_self() -> None:
|
|
69
|
+
"""Installs the latest release of the CLI *and* refreshes bundled libraries."""
|
|
70
|
+
console.print("[bold green]Starting updater...[/bold green]")
|
|
71
|
+
|
|
72
|
+
os_manager.replace_process([sys.executable, "-m", "griptape_nodes.updater"])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _print_current_version() -> None:
|
|
76
|
+
"""Prints the current version of the script."""
|
|
77
|
+
version_string = get_complete_version_string()
|
|
78
|
+
console.print(f"[bold green]{version_string}[/bold green]")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _uninstall_self() -> None:
|
|
82
|
+
"""Uninstalls itself by removing config/data directories and the executable."""
|
|
83
|
+
console.print("[bold]Uninstalling Griptape Nodes...[/bold]")
|
|
84
|
+
|
|
85
|
+
# Remove config and data directories
|
|
86
|
+
console.print("[bold]Removing config and data directories...[/bold]")
|
|
87
|
+
dirs = [(CONFIG_DIR, "Config Dir"), (DATA_DIR, "Data Dir")]
|
|
88
|
+
caveats = []
|
|
89
|
+
for dir_path, dir_name in dirs:
|
|
90
|
+
if dir_path.exists():
|
|
91
|
+
console.print(f"[bold]Removing {dir_name} '{dir_path}'...[/bold]")
|
|
92
|
+
try:
|
|
93
|
+
shutil.rmtree(dir_path)
|
|
94
|
+
except OSError as exc:
|
|
95
|
+
console.print(f"[red]Error removing {dir_name} '{dir_path}': {exc}[/red]")
|
|
96
|
+
caveats.append(
|
|
97
|
+
f"- [red]Error removing {dir_name} '{dir_path}'. You may want remove this directory manually.[/red]"
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
console.print(f"[yellow]{dir_name} '{dir_path}' does not exist; skipping.[/yellow]")
|
|
101
|
+
|
|
102
|
+
# Handle any remaining config files not removed by design
|
|
103
|
+
remaining_config_files = config_manager.config_files
|
|
104
|
+
if remaining_config_files:
|
|
105
|
+
caveats.append("- Some config files were intentionally not removed:")
|
|
106
|
+
caveats.extend(f"\t[yellow]- {file}[/yellow]" for file in remaining_config_files)
|
|
107
|
+
|
|
108
|
+
# If there were any caveats to the uninstallation process, print them
|
|
109
|
+
if caveats:
|
|
110
|
+
console.print("[bold]Caveats:[/bold]")
|
|
111
|
+
for line in caveats:
|
|
112
|
+
console.print(line)
|
|
113
|
+
|
|
114
|
+
# Remove the executable
|
|
115
|
+
console.print("[bold]Removing the executable...[/bold]")
|
|
116
|
+
console.print("[bold yellow]When done, press Enter to exit.[/bold yellow]")
|
|
117
|
+
|
|
118
|
+
# Remove the tool using UV
|
|
119
|
+
uv_path = find_uv_bin()
|
|
120
|
+
os_manager.replace_process([uv_path, "tool", "uninstall", "griptape-nodes"])
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Main CLI application using typer."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
# Add current directory to path for imports to work
|
|
10
|
+
sys.path.append(str(Path.cwd()))
|
|
11
|
+
|
|
12
|
+
from griptape_nodes.cli.commands import config, engine, init, libraries, models, self
|
|
13
|
+
from griptape_nodes.utils.version_utils import get_complete_version_string
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(
|
|
18
|
+
name="griptape-nodes",
|
|
19
|
+
help="Griptape Nodes Engine CLI",
|
|
20
|
+
no_args_is_help=False,
|
|
21
|
+
rich_markup_mode="rich",
|
|
22
|
+
invoke_without_command=True,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Add subcommands
|
|
26
|
+
app.command("init", help="Initialize engine configuration.")(init.init_command)
|
|
27
|
+
app.add_typer(config.app, name="config")
|
|
28
|
+
app.add_typer(self.app, name="self")
|
|
29
|
+
app.add_typer(libraries.app, name="libraries")
|
|
30
|
+
app.add_typer(models.app, name="models")
|
|
31
|
+
app.command("engine")(engine.engine_command)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.callback()
|
|
35
|
+
def main(
|
|
36
|
+
ctx: typer.Context,
|
|
37
|
+
version: bool = typer.Option( # noqa: FBT001
|
|
38
|
+
False, "--version", help="Show version and exit."
|
|
39
|
+
),
|
|
40
|
+
no_update: bool = typer.Option( # noqa: FBT001
|
|
41
|
+
False, "--no-update", help="Skip the auto-update check."
|
|
42
|
+
),
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Griptape Nodes Engine CLI."""
|
|
45
|
+
if version:
|
|
46
|
+
version_string = get_complete_version_string()
|
|
47
|
+
console.print(f"[bold green]{version_string}[/bold green]")
|
|
48
|
+
raise typer.Exit
|
|
49
|
+
|
|
50
|
+
if ctx.invoked_subcommand is None:
|
|
51
|
+
# Default to engine command when no subcommand is specified
|
|
52
|
+
engine.engine_command(no_update=no_update)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
app()
|