portacode 0.3.16.dev1__tar.gz → 0.3.16.dev2__tar.gz

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 (44) hide show
  1. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/PKG-INFO +1 -1
  2. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/_version.py +2 -2
  3. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +4 -5
  4. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/project_state_handlers.py +41 -53
  5. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/terminal.py +0 -1
  6. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/PKG-INFO +1 -1
  7. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/.claude/agents/communication-manager.md +0 -0
  8. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/.claude/settings.local.json +0 -0
  9. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/.gitignore +0 -0
  10. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/.gitmodules +0 -0
  11. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/LICENSE +0 -0
  12. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/MANIFEST.in +0 -0
  13. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/Makefile +0 -0
  14. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/README.md +0 -0
  15. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/backup.sh +0 -0
  16. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/docker-compose.yaml +0 -0
  17. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/README.md +0 -0
  18. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/__init__.py +0 -0
  19. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/__main__.py +0 -0
  20. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/cli.py +0 -0
  21. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/README.md +0 -0
  22. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/__init__.py +0 -0
  23. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/client.py +0 -0
  24. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/README.md +0 -0
  25. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/__init__.py +0 -0
  26. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/base.py +0 -0
  27. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/file_handlers.py +0 -0
  28. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/registry.py +0 -0
  29. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/session.py +0 -0
  30. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/system_handlers.py +0 -0
  31. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/terminal_handlers.py +0 -0
  32. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/multiplex.py +0 -0
  33. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/data.py +0 -0
  34. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/keypair.py +0 -0
  35. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/service.py +0 -0
  36. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/SOURCES.txt +0 -0
  37. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/dependency_links.txt +0 -0
  38. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/entry_points.txt +0 -0
  39. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/requires.txt +0 -0
  40. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/top_level.txt +0 -0
  41. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/pyproject.toml +0 -0
  42. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/restore.sh +0 -0
  43. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/setup.cfg +0 -0
  44. {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.16.dev1
3
+ Version: 0.3.16.dev2
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.3.16.dev1'
21
- __version_tuple__ = version_tuple = (0, 3, 16, 'dev1')
20
+ __version__ = version = '0.3.16.dev2'
21
+ __version_tuple__ = version_tuple = (0, 3, 16, 'dev2')
@@ -247,7 +247,7 @@ Project state actions manage the state of project folders, including file struct
247
247
 
248
248
  ### `project_state_folder_expand`
249
249
 
250
- Expands a folder in the project tree, loading its contents and enabling monitoring for that folder level.
250
+ Expands a folder in the project tree, loading its contents and enabling monitoring for that folder level. When a folder is expanded, the system proactively loads one level down for all subdirectories to enable immediate expansion in the UI. This action also scans items in the expanded folder and preloads content for any non-empty subdirectories.
251
251
 
252
252
  **Payload Fields:**
253
253
 
@@ -256,7 +256,7 @@ Expands a folder in the project tree, loading its contents and enabling monitori
256
256
 
257
257
  **Responses:**
258
258
 
259
- * On success, the device will respond with a [`project_state_folder_expand_response`](#project_state_folder_expand_response) event, followed by a [`project_state_update`](#project_state_update) event.
259
+ * On success, the device will respond with a [`project_state_folder_expand_response`](#project_state_folder_expand_response) event, followed by a [`project_state_update`](#project_state_update) event containing the updated file structure with preloaded subdirectory contents.
260
260
  * On error, a generic [`error`](#error) event is sent.
261
261
 
262
262
  ### `project_state_folder_collapse`
@@ -539,7 +539,6 @@ Confirms that project state has been successfully initialized for a client sessi
539
539
 
540
540
  **Event Fields:**
541
541
 
542
- * `project_id` (string, mandatory): The unique ID assigned to this project state.
543
542
  * `project_folder_path` (string, mandatory): The absolute path to the project folder.
544
543
  * `is_git_repo` (boolean, mandatory): Whether the project folder is a Git repository.
545
544
  * `git_branch` (string, optional): The current Git branch name if available.
@@ -547,7 +546,7 @@ Confirms that project state has been successfully initialized for a client sessi
547
546
  * `open_files` (array, mandatory): Array of file paths currently marked as open.
548
547
  * `active_file` (string, optional): Path to the currently active file.
549
548
  * `expanded_folders` (array, mandatory): Array of folder paths currently expanded.
550
- * `root_items` (array, mandatory): Array of file/folder items at the project root level.
549
+ * `items` (array, mandatory): Array of file/folder items including root level and one level down for all folders. Each folder item has an `is_expanded` boolean property indicating its expansion state.
551
550
  * `timestamp` (float, mandatory): Unix timestamp of when the state was generated.
552
551
 
553
552
  ### <a name="project_state_update"></a>`project_state_update`
@@ -564,7 +563,7 @@ Sent automatically when project state changes due to file system modifications,
564
563
  * `open_files` (array, mandatory): Updated array of open file paths.
565
564
  * `active_file` (string, optional): Updated active file path.
566
565
  * `expanded_folders` (array, mandatory): Updated array of expanded folder paths.
567
- * `root_items` (array, mandatory): Updated array of file/folder items at the project root level.
566
+ * `items` (array, mandatory): Updated array of file/folder items including root level and one level down for all folders. Each folder item has an `is_expanded` boolean property indicating its expansion state.
568
567
  * `timestamp` (float, mandatory): Unix timestamp of when the update was generated.
569
568
 
570
569
  ### <a name="project_state_folder_expand_response"></a>`project_state_folder_expand_response`
@@ -58,7 +58,7 @@ class ProjectState:
58
58
  """Represents the complete state of a project."""
59
59
  project_id: str
60
60
  project_folder_path: str
61
- root_items: List[FileItem]
61
+ items: List[FileItem]
62
62
  is_git_repo: bool = False
63
63
  git_branch: Optional[str] = None
64
64
  git_status_summary: Optional[Dict[str, int]] = None
@@ -276,7 +276,7 @@ class ProjectStateManager:
276
276
  "open_files": list(state.open_files),
277
277
  "active_file": state.active_file,
278
278
  "expanded_folders": list(state.expanded_folders),
279
- "root_items": [self._serialize_file_item(item) for item in state.root_items]
279
+ "items": [self._serialize_file_item(item) for item in state.items]
280
280
  }
281
281
 
282
282
  with open(self.debug_file_path, 'w', encoding='utf-8') as f:
@@ -309,7 +309,7 @@ class ProjectStateManager:
309
309
  project_state = ProjectState(
310
310
  project_id=project_id,
311
311
  project_folder_path=project_folder_path,
312
- root_items=[],
312
+ items=[],
313
313
  is_git_repo=git_manager.is_git_repo,
314
314
  git_branch=git_manager.get_branch_name(),
315
315
  git_status_summary=git_manager.get_status_summary()
@@ -326,8 +326,8 @@ class ProjectStateManager:
326
326
 
327
327
  return project_state
328
328
 
329
- async def _load_directory_items(self, project_state: ProjectState, directory_path: str, is_root: bool = False, parent_item: Optional[FileItem] = None, load_one_level_down: bool = True):
330
- """Load directory items with Git metadata and optionally preload one level down."""
329
+ async def _load_directory_items(self, project_state: ProjectState, directory_path: str, is_root: bool = False, parent_item: Optional[FileItem] = None):
330
+ """Load directory items with Git metadata."""
331
331
  git_manager = self.git_managers.get(project_state.project_id)
332
332
 
333
333
  try:
@@ -345,6 +345,9 @@ class ProjectStateManager:
345
345
  if git_manager:
346
346
  git_info = git_manager.get_file_status(entry.path)
347
347
 
348
+ # Check if this directory is expanded
349
+ is_expanded = entry.path in project_state.expanded_folders if entry.is_dir() else False
350
+
348
351
  file_item = FileItem(
349
352
  name=entry.name,
350
353
  path=entry.path,
@@ -354,19 +357,10 @@ class ProjectStateManager:
354
357
  is_git_tracked=git_info["is_tracked"],
355
358
  git_status=git_info["status"],
356
359
  is_hidden=is_hidden,
360
+ is_expanded=is_expanded,
357
361
  is_loaded=not entry.is_dir() # Files are always "loaded", directories need expansion
358
362
  )
359
363
 
360
- # If this is a directory and we should load one level down, do it now
361
- if entry.is_dir() and load_one_level_down:
362
- await self._load_directory_items(
363
- project_state,
364
- entry.path,
365
- is_root=False,
366
- parent_item=file_item,
367
- load_one_level_down=False # Don't go deeper than one level
368
- )
369
-
370
364
  items.append(file_item)
371
365
 
372
366
  except (OSError, PermissionError) as e:
@@ -377,7 +371,11 @@ class ProjectStateManager:
377
371
  items.sort(key=lambda x: (not x.is_directory, x.name.lower()))
378
372
 
379
373
  if is_root:
380
- project_state.root_items = items
374
+ project_state.items = items
375
+ # Load one level down for folders to enable immediate expansion
376
+ for item in items:
377
+ if item.is_directory:
378
+ await self._load_directory_items(project_state, item.path, parent_item=item)
381
379
  elif parent_item:
382
380
  parent_item.children = items
383
381
  parent_item.is_loaded = True
@@ -385,51 +383,36 @@ class ProjectStateManager:
385
383
  except (OSError, PermissionError) as e:
386
384
  logger.error("Error loading directory %s: %s", directory_path, e)
387
385
 
388
- async def expand_folder(self, project_id: str, folder_path: str) -> tuple[bool, bool]:
389
- """Expand a folder and determine if updates are needed.
390
-
391
- Returns:
392
- (success, needs_update): success indicates if expansion worked,
393
- needs_update indicates if client needs a state update
394
- """
386
+ async def expand_folder(self, project_id: str, folder_path: str) -> bool:
387
+ """Expand a folder and load its contents."""
395
388
  if project_id not in self.projects:
396
- return False, False
389
+ return False
397
390
 
398
391
  project_state = self.projects[project_id]
399
392
 
400
393
  # Find the folder item
401
- folder_item = self._find_item_by_path(project_state.root_items, folder_path)
394
+ folder_item = self._find_item_by_path(project_state.items, folder_path)
402
395
  if not folder_item or not folder_item.is_directory:
403
- return False, False
396
+ return False
397
+
398
+ # Load children if not already loaded
399
+ if not folder_item.is_loaded:
400
+ await self._load_directory_items(project_state, folder_path, parent_item=folder_item)
404
401
 
405
- # Mark as expanded and start watching
406
402
  folder_item.is_expanded = True
407
403
  project_state.expanded_folders.add(folder_path)
404
+
405
+ # Start watching this folder
408
406
  self.file_watcher.start_watching(folder_path)
409
407
 
410
- # Check if any subfolders have content that needs to be loaded
411
- needs_update = False
408
+ # Preload one level down for newly expanded folders
412
409
  if folder_item.children:
413
410
  for child in folder_item.children:
414
411
  if child.is_directory and not child.is_loaded:
415
- # Check if this subfolder has content
416
- try:
417
- with os.scandir(child.path) as entries:
418
- # If there are any items in this subfolder, we need to load them
419
- if any(True for _ in entries):
420
- await self._load_directory_items(
421
- project_state,
422
- child.path,
423
- parent_item=child,
424
- load_one_level_down=True
425
- )
426
- needs_update = True
427
- except (OSError, PermissionError):
428
- # If we can't read the directory, assume it's empty
429
- pass
412
+ await self._load_directory_items(project_state, child.path, parent_item=child)
430
413
 
431
414
  self._write_debug_state()
432
- return True, needs_update
415
+ return True
433
416
 
434
417
  async def collapse_folder(self, project_id: str, folder_path: str) -> bool:
435
418
  """Collapse a folder."""
@@ -439,7 +422,7 @@ class ProjectStateManager:
439
422
  project_state = self.projects[project_id]
440
423
 
441
424
  # Find the folder item
442
- folder_item = self._find_item_by_path(project_state.root_items, folder_path)
425
+ folder_item = self._find_item_by_path(project_state.items, folder_path)
443
426
  if not folder_item or not folder_item.is_directory:
444
427
  return False
445
428
 
@@ -562,7 +545,7 @@ class ProjectStateManager:
562
545
 
563
546
  # Reload expanded folders
564
547
  for folder_path in project_state.expanded_folders:
565
- folder_item = self._find_item_by_path(project_state.root_items, folder_path)
548
+ folder_item = self._find_item_by_path(project_state.items, folder_path)
566
549
  if folder_item and folder_item.is_directory:
567
550
  await self._load_directory_items(project_state, folder_path, parent_item=folder_item)
568
551
 
@@ -570,6 +553,7 @@ class ProjectStateManager:
570
553
  """Send project state update to clients."""
571
554
  payload = {
572
555
  "event": "project_state_update",
556
+ "project_id": project_state.project_id,
573
557
  "project_folder_path": project_state.project_folder_path,
574
558
  "is_git_repo": project_state.is_git_repo,
575
559
  "git_branch": project_state.git_branch,
@@ -577,7 +561,7 @@ class ProjectStateManager:
577
561
  "open_files": list(project_state.open_files),
578
562
  "active_file": project_state.active_file,
579
563
  "expanded_folders": list(project_state.expanded_folders),
580
- "root_items": [self._serialize_file_item(item) for item in project_state.root_items],
564
+ "items": [self._serialize_file_item(item) for item in project_state.items],
581
565
  "timestamp": time.time()
582
566
  }
583
567
 
@@ -639,18 +623,18 @@ class ProjectStateFolderExpandHandler(AsyncHandler):
639
623
 
640
624
  manager = _get_or_create_project_state_manager(self.context, self.control_channel)
641
625
 
642
- success, needs_update = await manager.expand_folder(project_id, folder_path)
626
+ success = await manager.expand_folder(project_id, folder_path)
643
627
 
644
- if success and needs_update:
645
- # Only send updated state if there were changes (new subfolder contents loaded)
628
+ if success:
629
+ # Send updated state
646
630
  project_state = manager.projects[project_id]
647
631
  await manager._send_project_state_update(project_state)
648
632
 
649
633
  return {
650
634
  "event": "project_state_folder_expand_response",
635
+ "project_id": project_id,
651
636
  "folder_path": folder_path,
652
- "success": success,
653
- "updated": needs_update
637
+ "success": success
654
638
  }
655
639
 
656
640
 
@@ -682,6 +666,7 @@ class ProjectStateFolderCollapseHandler(AsyncHandler):
682
666
 
683
667
  return {
684
668
  "event": "project_state_folder_collapse_response",
669
+ "project_id": project_id,
685
670
  "folder_path": folder_path,
686
671
  "success": success
687
672
  }
@@ -719,6 +704,7 @@ class ProjectStateFileOpenHandler(AsyncHandler):
719
704
 
720
705
  return {
721
706
  "event": "project_state_file_open_response",
707
+ "project_id": project_id,
722
708
  "file_path": file_path,
723
709
  "success": success,
724
710
  "set_active": set_active
@@ -753,6 +739,7 @@ class ProjectStateFileCloseHandler(AsyncHandler):
753
739
 
754
740
  return {
755
741
  "event": "project_state_file_close_response",
742
+ "project_id": project_id,
756
743
  "file_path": file_path,
757
744
  "success": success
758
745
  }
@@ -784,6 +771,7 @@ class ProjectStateSetActiveFileHandler(AsyncHandler):
784
771
 
785
772
  return {
786
773
  "event": "project_state_set_active_file_response",
774
+ "project_id": project_id,
787
775
  "file_path": file_path,
788
776
  "success": success
789
777
  }
@@ -454,7 +454,6 @@ class TerminalManager:
454
454
  # Send initial project state to the client
455
455
  initial_state_payload = {
456
456
  "event": "project_state_initialized",
457
- "project_id": project_state.project_id,
458
457
  "project_folder_path": project_state.project_folder_path,
459
458
  "is_git_repo": project_state.is_git_repo,
460
459
  "git_branch": project_state.git_branch,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.16.dev1
3
+ Version: 0.3.16.dev2
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
File without changes