griptape-nodes 0.37.0__py3-none-any.whl → 0.38.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.
Files changed (38) hide show
  1. griptape_nodes/__init__.py +292 -132
  2. griptape_nodes/app/__init__.py +1 -6
  3. griptape_nodes/app/app.py +108 -76
  4. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +80 -5
  5. griptape_nodes/drivers/storage/local_storage_driver.py +5 -1
  6. griptape_nodes/exe_types/core_types.py +84 -3
  7. griptape_nodes/exe_types/node_types.py +260 -50
  8. griptape_nodes/machines/node_resolution.py +2 -14
  9. griptape_nodes/retained_mode/events/agent_events.py +7 -0
  10. griptape_nodes/retained_mode/events/base_events.py +16 -0
  11. griptape_nodes/retained_mode/events/library_events.py +26 -0
  12. griptape_nodes/retained_mode/events/parameter_events.py +31 -0
  13. griptape_nodes/retained_mode/griptape_nodes.py +32 -0
  14. griptape_nodes/retained_mode/managers/agent_manager.py +25 -12
  15. griptape_nodes/retained_mode/managers/config_manager.py +37 -4
  16. griptape_nodes/retained_mode/managers/event_manager.py +15 -0
  17. griptape_nodes/retained_mode/managers/flow_manager.py +64 -61
  18. griptape_nodes/retained_mode/managers/library_manager.py +215 -45
  19. griptape_nodes/retained_mode/managers/node_manager.py +344 -147
  20. griptape_nodes/retained_mode/managers/operation_manager.py +6 -0
  21. griptape_nodes/retained_mode/managers/os_manager.py +6 -1
  22. griptape_nodes/retained_mode/managers/secrets_manager.py +7 -2
  23. griptape_nodes/retained_mode/managers/settings.py +2 -11
  24. griptape_nodes/retained_mode/managers/static_files_manager.py +12 -3
  25. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +105 -0
  26. griptape_nodes/retained_mode/managers/workflow_manager.py +4 -4
  27. griptape_nodes/updater/__init__.py +14 -8
  28. griptape_nodes/version_compatibility/__init__.py +1 -0
  29. griptape_nodes/version_compatibility/versions/__init__.py +1 -0
  30. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +1 -0
  31. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +77 -0
  32. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/METADATA +4 -1
  33. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/RECORD +36 -33
  34. griptape_nodes/app/app_websocket.py +0 -481
  35. griptape_nodes/app/nodes_api_socket_manager.py +0 -117
  36. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/WHEEL +0 -0
  37. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/entry_points.txt +0 -0
  38. {griptape_nodes-0.37.0.dist-info → griptape_nodes-0.38.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,4 @@
1
1
  """Griptape Nodes package."""
2
- # ruff: noqa: S603, S607
3
2
 
4
3
  from rich.console import Console
5
4
 
@@ -14,6 +13,7 @@ with console.status("Loading Griptape Nodes...") as status:
14
13
  import sys
15
14
  import tarfile
16
15
  import tempfile
16
+ from dataclasses import dataclass
17
17
  from pathlib import Path
18
18
  from typing import Any, Literal
19
19
 
@@ -27,7 +27,8 @@ with console.status("Loading Griptape Nodes...") as status:
27
27
  from xdg_base_dirs import xdg_config_home, xdg_data_home
28
28
 
29
29
  from griptape_nodes.app import start_app
30
- from griptape_nodes.retained_mode.griptape_nodes import engine_version
30
+ from griptape_nodes.drivers.storage.griptape_cloud_storage_driver import GriptapeCloudStorageDriver
31
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes, engine_version
31
32
  from griptape_nodes.retained_mode.managers.config_manager import ConfigManager
32
33
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
33
34
  from griptape_nodes.retained_mode.managers.secrets_manager import SecretsManager
@@ -48,12 +49,31 @@ GT_CLOUD_BASE_URL = os.getenv("GT_CLOUD_BASE_URL", "https://cloud.griptape.ai")
48
49
  ENV_WORKSPACE_DIRECTORY = os.getenv("GTN_WORKSPACE_DIRECTORY")
49
50
  ENV_API_KEY = os.getenv("GTN_API_KEY")
50
51
  ENV_STORAGE_BACKEND = os.getenv("GTN_STORAGE_BACKEND")
51
- ENV_STORAGE_BACKEND_BUCKET_ID = os.getenv("GTN_STORAGE_BACKEND_BUCKET_ID")
52
52
  ENV_REGISTER_ADVANCED_LIBRARY = (
53
53
  os.getenv("GTN_REGISTER_ADVANCED_LIBRARY", "false").lower() == "true"
54
54
  if os.getenv("GTN_REGISTER_ADVANCED_LIBRARY") is not None
55
55
  else None
56
56
  )
57
+ ENV_LIBRARIES_SYNC = (
58
+ os.getenv("GTN_LIBRARIES_SYNC", "false").lower() == "true" if os.getenv("GTN_LIBRARIES_SYNC") is not None else None
59
+ )
60
+ ENV_GTN_BUCKET_NAME = os.getenv("GTN_BUCKET_NAME")
61
+ ENV_LIBRARIES_BASE_DIR = os.getenv("GTN_LIBRARIES_BASE_DIR", str(DATA_DIR / "libraries"))
62
+
63
+
64
+ @dataclass
65
+ class InitConfig:
66
+ """Configuration for initialization."""
67
+
68
+ interactive: bool = True
69
+ workspace_directory: str | None = None
70
+ api_key: str | None = None
71
+ storage_backend: str | None = None
72
+ register_advanced_library: bool | None = None
73
+ config_values: dict[str, Any] | None = None
74
+ secret_values: dict[str, str] | None = None
75
+ libraries_sync: bool | None = None
76
+ bucket_name: str | None = None
57
77
 
58
78
 
59
79
  config_manager = ConfigManager()
@@ -75,60 +95,92 @@ def main() -> None:
75
95
  _process_args(args)
76
96
 
77
97
 
78
- def _run_init( # noqa: PLR0913, C901
79
- *,
80
- interactive: bool = True,
81
- workspace_directory: str | None = None,
82
- api_key: str | None = None,
83
- storage_backend: str | None = None,
84
- storage_backend_bucket_id: str | None = None,
85
- register_advanced_library: bool | None = None,
86
- config_values: dict[str, Any] | None = None,
87
- secret_values: dict[str, str] | None = None,
88
- ) -> None:
98
+ def _run_init(config: InitConfig) -> None:
89
99
  """Runs through the engine init steps.
90
100
 
91
101
  Args:
92
- interactive (bool): If True, prompts the user for input; otherwise uses provided values.
93
- workspace_directory (str | None): The workspace directory to set.
94
- api_key (str | None): The API key to set.
95
- storage_backend (str | None): The storage backend to set.
96
- storage_backend_bucket_id (str | None): The storage backend bucket ID to set.
97
- register_advanced_library (bool | None): Whether to register the advanced library.
98
- config_values (dict[str, any] | None): Arbitrary config key-value pairs to set.
99
- secret_values (dict[str, str] | None): Arbitrary secret key-value pairs to set.
102
+ config: Initialization configuration.
100
103
  """
101
104
  __init_system_config()
102
105
 
103
- if interactive:
104
- workspace_directory = _prompt_for_workspace(default_workspace_directory=workspace_directory)
106
+ # Run configuration flow
107
+ _run_init_configuration(config)
108
+
109
+ # Sync libraries
110
+ if config.libraries_sync is not False:
111
+ _sync_libraries()
112
+
113
+ console.print("[bold green]Initialization complete![/bold green]")
114
+
115
+
116
+ def _handle_api_key_config(config: InitConfig) -> str | None:
117
+ """Handle API key configuration step."""
118
+ api_key = config.api_key
119
+
120
+ if config.interactive:
105
121
  api_key = _prompt_for_api_key(default_api_key=api_key)
106
- storage_backend = _prompt_for_storage_backend(default_storage_backend=storage_backend)
107
- if storage_backend == "gtc":
108
- storage_backend_bucket_id = _prompt_for_storage_backend_bucket_id(
109
- default_storage_backend_bucket_id=storage_backend_bucket_id
110
- )
111
- register_advanced_library = _prompt_for_advanced_media_library(
112
- default_prompt_for_advanced_media_library=register_advanced_library
113
- )
114
- libraries_to_register = __build_libraries_list(register_advanced_library=register_advanced_library)
122
+
123
+ if api_key is not None:
124
+ secrets_manager.set_secret("GT_CLOUD_API_KEY", api_key)
125
+ console.print("[bold green]Griptape API Key set")
126
+
127
+ return api_key
128
+
129
+
130
+ def _handle_workspace_config(config: InitConfig) -> str | None:
131
+ """Handle workspace directory configuration step."""
132
+ workspace_directory = config.workspace_directory
133
+
134
+ if config.interactive:
135
+ workspace_directory = _prompt_for_workspace(default_workspace_directory=workspace_directory)
115
136
 
116
137
  if workspace_directory is not None:
117
138
  config_manager.set_config_value("workspace_directory", workspace_directory)
118
139
  console.print(f"[bold green]Workspace directory set to: {workspace_directory}[/bold green]")
119
140
 
120
- if api_key is not None:
121
- secrets_manager.set_secret("GT_CLOUD_API_KEY", api_key)
122
- console.print("[bold green]Griptape API Key set")
141
+ return workspace_directory
142
+
143
+
144
+ def _handle_storage_backend_config(config: InitConfig) -> str | None:
145
+ """Handle storage backend configuration step."""
146
+ storage_backend = config.storage_backend
147
+
148
+ if config.interactive:
149
+ storage_backend = _prompt_for_storage_backend(default_storage_backend=storage_backend)
123
150
 
124
151
  if storage_backend is not None:
125
152
  config_manager.set_config_value("storage_backend", storage_backend)
126
153
  console.print(f"[bold green]Storage backend set to: {storage_backend}")
127
154
 
155
+ return storage_backend
156
+
157
+
158
+ def _handle_bucket_config(config: InitConfig, storage_backend: str | None) -> str | None:
159
+ """Handle bucket configuration step (depends on API key and storage backend)."""
160
+ storage_backend_bucket_id = None
161
+
162
+ if storage_backend == "gtc":
163
+ if config.interactive:
164
+ storage_backend_bucket_id = _prompt_for_gtc_bucket_name(default_bucket_name=config.bucket_name)
165
+ elif config.bucket_name is not None:
166
+ storage_backend_bucket_id = _get_or_create_bucket_id(config.bucket_name)
167
+
128
168
  if storage_backend_bucket_id is not None:
129
169
  secrets_manager.set_secret("GT_CLOUD_BUCKET_ID", storage_backend_bucket_id)
130
170
  console.print(f"[bold green]Storage backend bucket ID set to: {storage_backend_bucket_id}[/bold green]")
131
171
 
172
+ return storage_backend_bucket_id
173
+
174
+
175
+ def _handle_advanced_library_config(config: InitConfig) -> bool | None:
176
+ """Handle advanced library configuration step."""
177
+ register_advanced_library = config.register_advanced_library
178
+
179
+ if config.interactive:
180
+ register_advanced_library = _prompt_for_advanced_media_library(
181
+ default_prompt_for_advanced_media_library=register_advanced_library
182
+ )
183
+
132
184
  if register_advanced_library is not None:
133
185
  libraries_to_register = __build_libraries_list(register_advanced_library=register_advanced_library)
134
186
  config_manager.set_config_value(
@@ -136,20 +188,37 @@ def _run_init( # noqa: PLR0913, C901
136
188
  )
137
189
  console.print(f"[bold green]Libraries to register set to: {', '.join(libraries_to_register)}[/bold green]")
138
190
 
191
+ return register_advanced_library
192
+
193
+
194
+ def _handle_arbitrary_configs(config: InitConfig) -> None:
195
+ """Handle arbitrary config and secret values."""
139
196
  # Set arbitrary config values
140
- if config_values:
141
- for key, value in config_values.items():
197
+ if config.config_values:
198
+ for key, value in config.config_values.items():
142
199
  config_manager.set_config_value(key, value)
143
200
  console.print(f"[bold green]Config '{key}' set to: {value}[/bold green]")
144
201
 
145
202
  # Set arbitrary secret values
146
- if secret_values:
147
- for key, value in secret_values.items():
203
+ if config.secret_values:
204
+ for key, value in config.secret_values.items():
148
205
  secrets_manager.set_secret(key, value)
149
206
  console.print(f"[bold green]Secret '{key}' set[/bold green]")
150
207
 
151
- _sync_assets()
152
- console.print("[bold green]Initialization complete![/bold green]")
208
+
209
+ def _run_init_configuration(config: InitConfig) -> None:
210
+ """Handle initialization with proper dependency ordering."""
211
+ _handle_api_key_config(config)
212
+
213
+ _handle_workspace_config(config)
214
+
215
+ storage_backend = _handle_storage_backend_config(config)
216
+
217
+ _handle_bucket_config(config, storage_backend)
218
+
219
+ _handle_advanced_library_config(config)
220
+
221
+ _handle_arbitrary_configs(config)
153
222
 
154
223
 
155
224
  def _start_engine(*, no_update: bool = False) -> None:
@@ -162,14 +231,17 @@ def _start_engine(*, no_update: bool = False) -> None:
162
231
  # Default init flow if there is no config directory
163
232
  console.print("[bold green]Config directory not found. Initializing...[/bold green]")
164
233
  _run_init(
165
- workspace_directory=ENV_WORKSPACE_DIRECTORY,
166
- api_key=ENV_API_KEY,
167
- storage_backend=ENV_STORAGE_BACKEND,
168
- storage_backend_bucket_id=ENV_STORAGE_BACKEND_BUCKET_ID,
169
- register_advanced_library=ENV_REGISTER_ADVANCED_LIBRARY,
170
- interactive=True,
171
- config_values=None,
172
- secret_values=None,
234
+ InitConfig(
235
+ workspace_directory=ENV_WORKSPACE_DIRECTORY,
236
+ api_key=ENV_API_KEY,
237
+ storage_backend=ENV_STORAGE_BACKEND,
238
+ register_advanced_library=ENV_REGISTER_ADVANCED_LIBRARY,
239
+ interactive=True,
240
+ config_values=None,
241
+ secret_values=None,
242
+ libraries_sync=ENV_LIBRARIES_SYNC,
243
+ bucket_name=ENV_GTN_BUCKET_NAME,
244
+ )
173
245
  )
174
246
 
175
247
  # Confusing double negation -- If `no_update` is set, we want to skip the update
@@ -218,15 +290,20 @@ def _get_args() -> argparse.Namespace:
218
290
  default=ENV_STORAGE_BACKEND,
219
291
  )
220
292
  init_parser.add_argument(
221
- "--storage-backend-bucket-id",
222
- help="Set the Griptape Cloud bucket ID (only used with 'gtc' storage backend).",
223
- default=ENV_STORAGE_BACKEND_BUCKET_ID,
293
+ "--bucket-name",
294
+ help="Name for the bucket (existing or new) when using 'gtc' storage backend.",
295
+ default=ENV_GTN_BUCKET_NAME,
224
296
  )
225
297
  init_parser.add_argument(
226
298
  "--register-advanced-library",
227
299
  help="Install the Griptape Nodes Advanced Image Library.",
228
300
  default=ENV_REGISTER_ADVANCED_LIBRARY,
229
301
  )
302
+ init_parser.add_argument(
303
+ "--libraries-sync",
304
+ help="Sync the Griptape Nodes libraries.",
305
+ default=ENV_LIBRARIES_SYNC,
306
+ )
230
307
  init_parser.add_argument(
231
308
  "--no-interactive",
232
309
  action="store_true",
@@ -270,14 +347,14 @@ def _get_args() -> argparse.Namespace:
270
347
  self_subparsers.add_parser("uninstall", help="Uninstall the CLI.")
271
348
  self_subparsers.add_parser("version", help="Print the CLI version.")
272
349
 
273
- # assets
274
- assets_parser = subparsers.add_parser("assets", help="Manage local assets (libraries, workflows, etc.).")
275
- assets_subparsers = assets_parser.add_subparsers(
350
+ # libraries
351
+ libraries_parser = subparsers.add_parser("libraries", help="Manage local libraries.")
352
+ libraries_subparsers = libraries_parser.add_subparsers(
276
353
  dest="subcommand",
277
354
  metavar="SUBCOMMAND",
278
355
  required=True,
279
356
  )
280
- assets_subparsers.add_parser("sync", help="Sync assets with your current engine version.")
357
+ libraries_subparsers.add_parser("sync", help="Sync libraries with your current engine version.")
281
358
 
282
359
  args = parser.parse_args()
283
360
 
@@ -364,66 +441,95 @@ Enter 'gtc' to use Griptape Cloud Bucket Storage, or press Return to accept the
364
441
  return storage_backend
365
442
 
366
443
 
367
- def _get_griptape_cloud_bucket_ids_and_display_table() -> tuple[list[str], Table]:
368
- """Fetches the list of Griptape Cloud Bucket IDs from the API."""
369
- url = f"{GT_CLOUD_BASE_URL}/api/buckets"
370
- headers = {
371
- "Authorization": f"Bearer {secrets_manager.get_secret('GT_CLOUD_API_KEY')}",
372
- }
373
- bucket_ids: list[str] = []
444
+ def _get_griptape_cloud_buckets_and_display_table() -> tuple[list[str], dict[str, str], Table]:
445
+ """Fetches the list of Griptape Cloud Buckets from the API.
446
+
447
+ Returns:
448
+ tuple: (bucket_names, name_to_id_mapping, display_table)
449
+ """
450
+ api_key = secrets_manager.get_secret("GT_CLOUD_API_KEY")
451
+ bucket_names: list[str] = []
452
+ name_to_id: dict[str, str] = {}
453
+
454
+ if api_key is None:
455
+ msg = "Griptape Cloud API Key not found."
456
+ raise RuntimeError(msg)
374
457
 
375
458
  table = Table(show_header=True, box=HEAVY_EDGE, show_lines=True, expand=True)
376
459
  table.add_column("Bucket Name", style="green")
377
460
  table.add_column("Bucket ID", style="green")
378
461
 
379
- with httpx.Client() as client:
380
- response = client.get(url, headers=headers)
381
- try:
382
- response.raise_for_status()
383
- data = response.json()
384
- for bucket in data["buckets"]:
385
- bucket_ids.append(bucket["bucket_id"])
386
- table.add_row(bucket["name"], bucket["bucket_id"])
387
-
388
- except httpx.HTTPStatusError as e:
389
- console.print(f"[red]Error fetching bucket IDs: {e}[/red]")
462
+ try:
463
+ buckets = GriptapeCloudStorageDriver.list_buckets(base_url=GT_CLOUD_BASE_URL, api_key=api_key)
464
+ for bucket in buckets:
465
+ bucket_name = bucket["name"]
466
+ bucket_id = bucket["bucket_id"]
467
+ bucket_names.append(bucket_name)
468
+ name_to_id[bucket_name] = bucket_id
469
+ table.add_row(bucket_name, bucket_id)
470
+ except RuntimeError as e:
471
+ console.print(f"[red]Error fetching buckets: {e}[/red]")
390
472
 
391
- return bucket_ids, table
473
+ return bucket_names, name_to_id, table
392
474
 
393
475
 
394
- def _prompt_for_storage_backend_bucket_id(*, default_storage_backend_bucket_id: str | None = None) -> str:
395
- """Prompts the user for their storage backend bucket ID."""
396
- if default_storage_backend_bucket_id is None:
397
- default_storage_backend_bucket_id = secrets_manager.get_secret(
398
- "GT_CLOUD_BUCKET_ID", should_error_on_not_found=False
399
- )
400
- explainer = """[bold cyan]Storage Backend Bucket ID[/bold cyan]
401
- Enter the Griptape Cloud Bucket ID to use for Griptape Cloud Storage. This is the location where Griptape Nodes will store your static files."""
476
+ def _prompt_for_gtc_bucket_name(default_bucket_name: str | None = None) -> str:
477
+ """Prompts the user for a GTC bucket and returns the bucket ID."""
478
+ explainer = """[bold cyan]Storage Backend Bucket Selection[/bold cyan]
479
+ Select a Griptape Cloud Bucket to use for storage. This is the location where Griptape Nodes will store your static files."""
402
480
  console.print(Panel(explainer, expand=False))
403
481
 
404
- choices, table = _get_griptape_cloud_bucket_ids_and_display_table()
482
+ # Fetch existing buckets
483
+ bucket_names, name_to_id, table = _get_griptape_cloud_buckets_and_display_table()
484
+ if default_bucket_name is None:
485
+ # Default to "default" bucket if it exists
486
+ default_bucket_name = "default" if "default" in name_to_id else None
405
487
 
406
- # This should not be possible
407
- if len(choices) < 1:
408
- msg = "No Griptape Cloud Buckets found!"
409
- raise RuntimeError(msg)
410
-
411
- console.print(table)
488
+ # Display existing buckets if any
489
+ if len(bucket_names) > 0:
490
+ console.print(table)
491
+ console.print("\n[dim]You can enter an existing bucket by name, or enter a new name to create one.[/dim]")
412
492
 
413
493
  while True:
414
- try:
415
- storage_backend_bucket_id = Prompt.ask(
416
- "Storage Backend Bucket ID",
417
- default=default_storage_backend_bucket_id,
418
- show_default=True,
419
- choices=choices,
494
+ # Prompt user for bucket name
495
+ selected_bucket_name = Prompt.ask(
496
+ "Enter bucket name",
497
+ default=default_bucket_name,
498
+ show_default=bool(default_bucket_name),
499
+ )
500
+
501
+ if selected_bucket_name:
502
+ # Check if it's an existing bucket
503
+ if selected_bucket_name in name_to_id:
504
+ return name_to_id[selected_bucket_name]
505
+ # It's a new bucket name, confirm creation
506
+ create_bucket = Confirm.ask(
507
+ f"Bucket '{selected_bucket_name}' doesn't exist. Create it?",
508
+ default=True,
420
509
  )
421
- if storage_backend_bucket_id:
422
- break
423
- except json.JSONDecodeError as e:
424
- console.print(f"[bold red]Error reading config file: {e}[/bold red]")
510
+ if create_bucket:
511
+ return __create_new_bucket(selected_bucket_name)
512
+ # If they don't want to create, continue the loop to ask again
425
513
 
426
- return storage_backend_bucket_id
514
+
515
+ def _get_or_create_bucket_id(bucket_name: str) -> str:
516
+ """Gets the bucket ID for an existing bucket or creates a new one.
517
+
518
+ Args:
519
+ bucket_name: Name of the bucket to lookup or create
520
+
521
+ Returns:
522
+ The bucket ID
523
+ """
524
+ # Fetch existing buckets to check if bucket_name exists
525
+ _, name_to_id, _ = _get_griptape_cloud_buckets_and_display_table()
526
+
527
+ # Check if bucket already exists
528
+ if bucket_name in name_to_id:
529
+ return name_to_id[bucket_name]
530
+
531
+ # Create the bucket
532
+ return __create_new_bucket(bucket_name)
427
533
 
428
534
 
429
535
  def _prompt_for_advanced_media_library(*, default_prompt_for_advanced_media_library: bool | None = None) -> bool:
@@ -446,29 +552,40 @@ def __build_libraries_list(*, register_advanced_library: bool) -> list[str]:
446
552
  """Builds the list of libraries to register based on the advanced library setting."""
447
553
  # TODO: https://github.com/griptape-ai/griptape-nodes/issues/929
448
554
  libraries_key = "app_events.on_app_initialization_complete.libraries_to_register"
449
- library_base_dir = xdg_data_home() / "griptape_nodes/libraries"
555
+ library_base_dir = Path(ENV_LIBRARIES_BASE_DIR)
450
556
 
451
557
  current_libraries = config_manager.get_config_value(
452
558
  libraries_key,
453
559
  config_source="user_config",
454
560
  default=config_manager.get_config_value(libraries_key, config_source="default_config", default=[]),
455
561
  )
456
- new_libraries = current_libraries
562
+ new_libraries = current_libraries.copy()
563
+
564
+ def _get_library_identifier(library_path: str) -> str:
565
+ """Get the unique identifier for a library based on parent/filename."""
566
+ path = Path(library_path)
567
+ return f"{path.parent.name}/{path.name}"
568
+
569
+ # Create a set of current library identifiers for fast lookup
570
+ current_identifiers = {_get_library_identifier(lib) for lib in current_libraries}
457
571
 
458
572
  default_library = str(library_base_dir / "griptape_nodes_library/griptape_nodes_library.json")
573
+ default_identifier = _get_library_identifier(default_library)
459
574
  # If somehow the user removed the default library, add it back
460
- if default_library not in current_libraries:
461
- current_libraries.append(default_library)
575
+ if default_identifier not in current_identifiers:
576
+ new_libraries.append(default_library)
462
577
 
463
578
  advanced_media_library = str(library_base_dir / "griptape_nodes_advanced_media_library/griptape_nodes_library.json")
579
+ advanced_identifier = _get_library_identifier(advanced_media_library)
464
580
  if register_advanced_library:
465
581
  # If the advanced media library is not registered, add it
466
- if advanced_media_library not in current_libraries:
582
+ if advanced_identifier not in current_identifiers:
467
583
  new_libraries.append(advanced_media_library)
468
- else: # noqa: PLR5501 easier to reason about this way
584
+ else:
469
585
  # If the advanced media library is registered, remove it
470
- if advanced_media_library in current_libraries:
471
- new_libraries.remove(advanced_media_library)
586
+ libraries_to_remove = [lib for lib in new_libraries if _get_library_identifier(lib) == advanced_identifier]
587
+ for lib in libraries_to_remove:
588
+ new_libraries.remove(lib)
472
589
 
473
590
  return new_libraries
474
591
 
@@ -540,26 +657,26 @@ def _auto_update_self() -> None:
540
657
 
541
658
 
542
659
  def _update_self() -> None:
543
- """Installs the latest release of the CLI *and* refreshes bundled assets."""
660
+ """Installs the latest release of the CLI *and* refreshes bundled libraries."""
544
661
  console.print("[bold green]Starting updater...[/bold green]")
545
662
 
546
663
  os_manager.replace_process([sys.executable, "-m", "griptape_nodes.updater"])
547
664
 
548
665
 
549
- def _sync_assets() -> None:
550
- """Download and fully replace the Griptape Nodes assets directory."""
666
+ def _sync_libraries() -> None:
667
+ """Download and sync Griptape Nodes libraries, copying only directories from synced libraries."""
551
668
  install_source, _ = __get_install_source()
552
- # Unless we're installed from PyPi, grab assets from the 'latest' tag
669
+ # Unless we're installed from PyPi, grab libraries from the 'latest' tag
553
670
  if install_source == "pypi":
554
671
  version = __get_current_version()
555
672
  else:
556
673
  version = LATEST_TAG
557
674
 
558
- console.print(f"[bold cyan]Fetching Griptape Nodes assets ({version})...[/bold cyan]")
675
+ console.print(f"[bold cyan]Fetching Griptape Nodes libraries ({version})...[/bold cyan]")
559
676
 
560
677
  tar_url = NODES_TARBALL_URL.format(tag=version)
561
678
  console.print(f"[green]Downloading from {tar_url}[/green]")
562
- dest_nodes = DATA_DIR / "libraries"
679
+ dest_nodes = Path(ENV_LIBRARIES_BASE_DIR)
563
680
 
564
681
  with tempfile.TemporaryDirectory() as tmp:
565
682
  tar_path = Path(tmp) / "nodes.tar.gz"
@@ -569,7 +686,7 @@ def _sync_assets() -> None:
569
686
  try:
570
687
  r.raise_for_status()
571
688
  except httpx.HTTPStatusError as e:
572
- console.print(f"[red]Error fetching assets: {e}[/red]")
689
+ console.print(f"[red]Error fetching libraries: {e}[/red]")
573
690
  return
574
691
  task = progress.add_task("[green]Downloading...", total=int(r.headers.get("Content-Length", 0)))
575
692
  with tar_path.open("wb") as f:
@@ -585,12 +702,26 @@ def _sync_assets() -> None:
585
702
  extracted_root = next(Path(tmp).glob("griptape-nodes-*"))
586
703
  extracted_libs = extracted_root / "libraries"
587
704
 
588
- # Fully replace the destination directory
589
- if dest_nodes.exists():
590
- shutil.rmtree(dest_nodes)
591
- shutil.copytree(extracted_libs, dest_nodes)
705
+ # Copy directories from synced libraries without removing existing content
706
+ dest_nodes.mkdir(parents=True, exist_ok=True)
707
+
708
+ for library_dir in extracted_libs.iterdir():
709
+ if library_dir.is_dir():
710
+ dest_library_dir = dest_nodes / library_dir.name
711
+ if dest_library_dir.exists():
712
+ shutil.rmtree(dest_library_dir)
713
+ shutil.copytree(library_dir, dest_library_dir)
714
+ console.print(f"[green]Synced library: {library_dir.name}[/green]")
715
+
716
+ # Re-initialize all libraries from config
717
+ console.print("[bold cyan]Initializing libraries...[/bold cyan]")
718
+ try:
719
+ GriptapeNodes.LibraryManager().load_all_libraries_from_config()
720
+ console.print("[bold green]Libraries Initialized successfully.[/bold green]")
721
+ except Exception as e:
722
+ console.print(f"[red]Error initializing libraries: {e}[/red]")
592
723
 
593
- console.print("[bold green]Node Libraries updated.[/bold green]")
724
+ console.print("[bold green]Libraries synced.[/bold green]")
594
725
 
595
726
 
596
727
  def _print_current_version() -> None:
@@ -702,14 +833,17 @@ def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
702
833
  secret_values = _parse_key_value_pairs(getattr(args, "secret", None))
703
834
 
704
835
  _run_init(
705
- interactive=not args.no_interactive,
706
- workspace_directory=args.workspace_directory,
707
- api_key=args.api_key,
708
- storage_backend=args.storage_backend,
709
- storage_backend_bucket_id=args.storage_backend_bucket_id,
710
- register_advanced_library=args.register_advanced_library,
711
- config_values=config_values,
712
- secret_values=secret_values,
836
+ InitConfig(
837
+ interactive=not args.no_interactive,
838
+ workspace_directory=args.workspace_directory,
839
+ api_key=args.api_key,
840
+ storage_backend=args.storage_backend,
841
+ register_advanced_library=args.register_advanced_library,
842
+ config_values=config_values,
843
+ secret_values=secret_values,
844
+ libraries_sync=args.libraries_sync,
845
+ bucket_name=args.bucket_name,
846
+ )
713
847
  )
714
848
  elif args.command == "engine":
715
849
  _start_engine(no_update=args.no_update)
@@ -727,9 +861,9 @@ def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
727
861
  _uninstall_self()
728
862
  elif args.subcommand == "version":
729
863
  _print_current_version()
730
- elif args.command == "assets":
864
+ elif args.command == "libraries":
731
865
  if args.subcommand == "sync":
732
- _sync_assets()
866
+ _sync_libraries()
733
867
  else:
734
868
  msg = f"Unknown command: {args.command}"
735
869
  raise ValueError(msg)
@@ -779,5 +913,31 @@ def __init_system_config() -> None:
779
913
  file.write(file_name[1])
780
914
 
781
915
 
916
+ def __create_new_bucket(bucket_name: str) -> str:
917
+ """Create a new Griptape Cloud bucket.
918
+
919
+ Args:
920
+ bucket_name: Name for the bucket
921
+
922
+ Returns:
923
+ The bucket ID of the created bucket.
924
+ """
925
+ api_key = secrets_manager.get_secret("GT_CLOUD_API_KEY")
926
+ if api_key is None:
927
+ msg = "GT_CLOUD_API_KEY secret is required to create a bucket."
928
+ raise ValueError(msg)
929
+
930
+ try:
931
+ bucket_id = GriptapeCloudStorageDriver.create_bucket(
932
+ bucket_name=bucket_name, base_url=GT_CLOUD_BASE_URL, api_key=api_key
933
+ )
934
+ except Exception as e:
935
+ console.print(f"[bold red]Failed to create bucket: {e}[/bold red]")
936
+ raise
937
+ else:
938
+ console.print(f"[bold green]Successfully created bucket '{bucket_name}' with ID: {bucket_id}[/bold green]")
939
+ return bucket_id
940
+
941
+
782
942
  if __name__ == "__main__":
783
943
  main()
@@ -1,10 +1,5 @@
1
1
  """App package."""
2
2
 
3
- import os
4
-
5
- if os.getenv("GTN_USE_WEBSOCKETS", "true").lower() == "true":
6
- from griptape_nodes.app.app_websocket import start_app
7
- else:
8
- from griptape_nodes.app.app import start_app
3
+ from griptape_nodes.app.app import start_app
9
4
 
10
5
  __all__ = ["start_app"]