portacode 0.3.16.dev4__tar.gz → 0.3.16.dev6__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.dev4 → portacode-0.3.16.dev6}/PKG-INFO +1 -1
  2. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/_version.py +2 -2
  3. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +2 -4
  4. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/project_state_handlers.py +113 -34
  5. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/terminal.py +0 -1
  6. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/PKG-INFO +1 -1
  7. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/.claude/agents/communication-manager.md +0 -0
  8. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/.claude/settings.local.json +0 -0
  9. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/.gitignore +0 -0
  10. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/.gitmodules +0 -0
  11. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/LICENSE +0 -0
  12. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/MANIFEST.in +0 -0
  13. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/Makefile +0 -0
  14. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/README.md +0 -0
  15. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/backup.sh +0 -0
  16. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/docker-compose.yaml +0 -0
  17. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/README.md +0 -0
  18. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/__init__.py +0 -0
  19. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/__main__.py +0 -0
  20. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/cli.py +0 -0
  21. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/README.md +0 -0
  22. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/__init__.py +0 -0
  23. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/client.py +0 -0
  24. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/README.md +0 -0
  25. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/__init__.py +0 -0
  26. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/base.py +0 -0
  27. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/file_handlers.py +0 -0
  28. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/registry.py +0 -0
  29. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/session.py +0 -0
  30. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/system_handlers.py +0 -0
  31. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/terminal_handlers.py +0 -0
  32. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/multiplex.py +0 -0
  33. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/data.py +0 -0
  34. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/keypair.py +0 -0
  35. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/service.py +0 -0
  36. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/SOURCES.txt +0 -0
  37. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/dependency_links.txt +0 -0
  38. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/entry_points.txt +0 -0
  39. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/requires.txt +0 -0
  40. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/top_level.txt +0 -0
  41. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/pyproject.toml +0 -0
  42. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/restore.sh +0 -0
  43. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/setup.cfg +0 -0
  44. {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.16.dev4
3
+ Version: 0.3.16.dev6
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.dev4'
21
- __version_tuple__ = version_tuple = (0, 3, 16, 'dev4')
20
+ __version__ = version = '0.3.16.dev6'
21
+ __version_tuple__ = version_tuple = (0, 3, 16, 'dev6')
@@ -545,8 +545,7 @@ Confirms that project state has been successfully initialized for a client sessi
545
545
  * `git_status_summary` (object, optional): Summary of Git status counts (modified, added, deleted, untracked files).
546
546
  * `open_files` (array, mandatory): Array of file paths currently marked as open.
547
547
  * `active_file` (string, optional): Path to the currently active file.
548
- * `expanded_folders` (array, mandatory): Array of folder paths currently expanded.
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.
548
+ * `items` (array, mandatory): Flattened array of all visible file/folder items. Always includes root level items and one level down from the project root (since the project root is treated as expanded by default). Also includes items within explicitly expanded folders and one level down from each expanded folder. Each item has a `parent_path` property indicating which folder it belongs to, and folder items have an `is_expanded` boolean property indicating their expansion state.
550
549
  * `timestamp` (float, mandatory): Unix timestamp of when the state was generated.
551
550
 
552
551
  ### <a name="project_state_update"></a>`project_state_update`
@@ -562,8 +561,7 @@ Sent automatically when project state changes due to file system modifications,
562
561
  * `git_status_summary` (object, optional): Updated summary of Git status counts.
563
562
  * `open_files` (array, mandatory): Updated array of open file paths.
564
563
  * `active_file` (string, optional): Updated active file path.
565
- * `expanded_folders` (array, mandatory): Updated array of expanded folder paths.
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.
564
+ * `items` (array, mandatory): Updated flattened array of all visible file/folder items. Always includes root level items and one level down from the project root (since the project root is treated as expanded by default). Also includes items within explicitly expanded folders and one level down from each expanded folder. Each item has a `parent_path` property indicating which folder it belongs to, and folder items have an `is_expanded` boolean property indicating their expansion state.
567
565
  * `timestamp` (float, mandatory): Unix timestamp of when the update was generated.
568
566
 
569
567
  ### <a name="project_state_folder_expand_response"></a>`project_state_folder_expand_response`
@@ -43,6 +43,7 @@ class FileItem:
43
43
  name: str
44
44
  path: str
45
45
  is_directory: bool
46
+ parent_path: str
46
47
  size: Optional[int] = None
47
48
  modified_time: Optional[float] = None
48
49
  is_git_tracked: Optional[bool] = None
@@ -64,13 +65,10 @@ class ProjectState:
64
65
  git_status_summary: Optional[Dict[str, int]] = None
65
66
  open_files: Set[str] = None
66
67
  active_file: Optional[str] = None
67
- expanded_folders: Set[str] = None
68
68
 
69
69
  def __post_init__(self):
70
70
  if self.open_files is None:
71
71
  self.open_files = set()
72
- if self.expanded_folders is None:
73
- self.expanded_folders = set()
74
72
 
75
73
 
76
74
  class GitManager:
@@ -275,7 +273,6 @@ class ProjectStateManager:
275
273
  "git_status_summary": state.git_status_summary,
276
274
  "open_files": list(state.open_files),
277
275
  "active_file": state.active_file,
278
- "expanded_folders": list(state.expanded_folders),
279
276
  "items": [self._serialize_file_item(item) for item in state.items]
280
277
  }
281
278
 
@@ -315,8 +312,8 @@ class ProjectStateManager:
315
312
  git_status_summary=git_manager.get_status_summary()
316
313
  )
317
314
 
318
- # Load initial file structure with recursive expanded folder loading
319
- await self._load_expanded_folders_recursively(project_state)
315
+ # Load initial file structure with flattened items
316
+ await self._build_flattened_items_structure(project_state)
320
317
 
321
318
  # Start watching the project folder
322
319
  self.file_watcher.start_watching(project_folder_path)
@@ -337,6 +334,10 @@ class ProjectStateManager:
337
334
  with os.scandir(directory_path) as entries:
338
335
  for entry in entries:
339
336
  try:
337
+ # Skip .git folders and their contents
338
+ if entry.name == '.git' and entry.is_dir():
339
+ continue
340
+
340
341
  stat_info = entry.stat()
341
342
  is_hidden = entry.name.startswith('.')
342
343
 
@@ -379,23 +380,99 @@ class ProjectStateManager:
379
380
  except (OSError, PermissionError) as e:
380
381
  logger.error("Error loading directory %s: %s", directory_path, e)
381
382
 
382
- async def _load_expanded_folders_recursively(self, project_state: ProjectState):
383
- """Load one level down for all expanded folders recursively."""
384
- # First load root level
385
- await self._load_directory_items(project_state, project_state.project_folder_path, is_root=True)
386
-
387
- # Then load one level down for all expanded folders
388
- for folder_path in project_state.expanded_folders:
389
- folder_item = self._find_item_by_path(project_state.items, folder_path)
390
- if folder_item and folder_item.is_directory:
391
- # Load the folder's immediate children
392
- await self._load_directory_items(project_state, folder_path, parent_item=folder_item)
393
-
394
- # Load one level down for each subfolder in this expanded folder
395
- if folder_item.children:
396
- for child in folder_item.children:
397
- if child.is_directory and not child.is_loaded:
398
- await self._load_directory_items(project_state, child.path, parent_item=child)
383
+ async def _build_flattened_items_structure(self, project_state: ProjectState):
384
+ """Build a flattened items structure including all visible items and one level down from expanded folders."""
385
+ all_items = []
386
+
387
+ # Load root items
388
+ root_items = await self._load_directory_items_list(project_state.project_folder_path, project_state.project_folder_path)
389
+
390
+ for item in root_items:
391
+ all_items.append(item)
392
+
393
+ # Always load one level down from root folders (project root is always "expanded")
394
+ # OR if this folder is explicitly expanded, add its children and one level down
395
+ if item.is_directory and (item.parent_path == project_state.project_folder_path or item.is_expanded):
396
+ children = await self._load_directory_items_list(item.path, item.path)
397
+ for child in children:
398
+ all_items.append(child)
399
+
400
+ # If child is a directory, load one level down
401
+ if child.is_directory:
402
+ grandchildren = await self._load_directory_items_list(child.path, child.path)
403
+ all_items.extend(grandchildren)
404
+
405
+ project_state.items = all_items
406
+
407
+ async def _load_directory_items_list(self, directory_path: str, parent_path: str) -> List[FileItem]:
408
+ """Load directory items and return as a list with parent_path."""
409
+ git_manager = None
410
+ for manager in self.git_managers.values():
411
+ if directory_path.startswith(manager.project_path):
412
+ git_manager = manager
413
+ break
414
+
415
+ items = []
416
+
417
+ try:
418
+ with os.scandir(directory_path) as entries:
419
+ for entry in entries:
420
+ try:
421
+ # Skip .git folders and their contents
422
+ if entry.name == '.git' and entry.is_dir():
423
+ continue
424
+
425
+ stat_info = entry.stat()
426
+ is_hidden = entry.name.startswith('.')
427
+
428
+ # Get Git status if available
429
+ git_info = {"is_tracked": False, "status": None}
430
+ if git_manager:
431
+ git_info = git_manager.get_file_status(entry.path)
432
+
433
+ # Check if this directory is expanded by finding it in current items
434
+ is_expanded = False
435
+ if entry.is_dir():
436
+ # Check if this folder is expanded by looking for existing items with this path as parent
437
+ is_expanded = self._is_folder_expanded(entry.path)
438
+
439
+ file_item = FileItem(
440
+ name=entry.name,
441
+ path=entry.path,
442
+ is_directory=entry.is_dir(),
443
+ parent_path=parent_path,
444
+ size=stat_info.st_size if entry.is_file() else None,
445
+ modified_time=stat_info.st_mtime,
446
+ is_git_tracked=git_info["is_tracked"],
447
+ git_status=git_info["status"],
448
+ is_hidden=is_hidden,
449
+ is_expanded=is_expanded,
450
+ is_loaded=not entry.is_dir()
451
+ )
452
+
453
+ items.append(file_item)
454
+
455
+ except (OSError, PermissionError) as e:
456
+ logger.debug("Error reading entry %s: %s", entry.path, e)
457
+ continue
458
+
459
+ # Sort items: directories first, then files, both alphabetically
460
+ items.sort(key=lambda x: (not x.is_directory, x.name.lower()))
461
+
462
+ except (OSError, PermissionError) as e:
463
+ logger.error("Error loading directory %s: %s", directory_path, e)
464
+
465
+ return items
466
+
467
+ def _is_folder_expanded(self, folder_path: str) -> bool:
468
+ """Check if a folder is expanded by looking at existing items."""
469
+ # During initial load, no folders are expanded
470
+ # During updates, check if any items have this folder as parent_path
471
+ for project_state in self.projects.values():
472
+ for item in project_state.items:
473
+ if item.parent_path == folder_path:
474
+ return True
475
+ return False
399
476
 
400
477
  async def expand_folder(self, project_id: str, folder_path: str) -> bool:
401
478
  """Expand a folder and load its contents."""
@@ -404,19 +481,18 @@ class ProjectStateManager:
404
481
 
405
482
  project_state = self.projects[project_id]
406
483
 
407
- # Find the folder item
484
+ # Find the folder item and mark it as expanded
408
485
  folder_item = self._find_item_by_path(project_state.items, folder_path)
409
486
  if not folder_item or not folder_item.is_directory:
410
487
  return False
411
488
 
412
489
  folder_item.is_expanded = True
413
- project_state.expanded_folders.add(folder_path)
414
490
 
415
491
  # Start watching this folder
416
492
  self.file_watcher.start_watching(folder_path)
417
493
 
418
- # Reload the entire structure to ensure consistency with recursive loading
419
- await self._load_expanded_folders_recursively(project_state)
494
+ # Rebuild the entire flattened structure to include new expanded content
495
+ await self._build_flattened_items_structure(project_state)
420
496
 
421
497
  self._write_debug_state()
422
498
  return True
@@ -428,18 +504,20 @@ class ProjectStateManager:
428
504
 
429
505
  project_state = self.projects[project_id]
430
506
 
431
- # Find the folder item
507
+ # Find the folder item and mark it as collapsed
432
508
  folder_item = self._find_item_by_path(project_state.items, folder_path)
433
509
  if not folder_item or not folder_item.is_directory:
434
510
  return False
435
511
 
436
512
  folder_item.is_expanded = False
437
- project_state.expanded_folders.discard(folder_path)
438
513
 
439
514
  # Stop watching collapsed folders (except root)
440
515
  if folder_path != project_state.project_folder_path:
441
516
  self.file_watcher.stop_watching(folder_path)
442
517
 
518
+ # Rebuild the flattened structure to remove collapsed content
519
+ await self._build_flattened_items_structure(project_state)
520
+
443
521
  self._write_debug_state()
444
522
  return True
445
523
 
@@ -546,8 +624,8 @@ class ProjectStateManager:
546
624
  self._write_debug_state()
547
625
 
548
626
  async def _reload_visible_structures(self, project_state: ProjectState):
549
- """Reload all visible structures with recursive expanded folder loading."""
550
- await self._load_expanded_folders_recursively(project_state)
627
+ """Reload all visible structures with flattened items."""
628
+ await self._build_flattened_items_structure(project_state)
551
629
 
552
630
  async def _send_project_state_update(self, project_state: ProjectState):
553
631
  """Send project state update to clients."""
@@ -560,7 +638,6 @@ class ProjectStateManager:
560
638
  "git_status_summary": project_state.git_status_summary,
561
639
  "open_files": list(project_state.open_files),
562
640
  "active_file": project_state.active_file,
563
- "expanded_folders": list(project_state.expanded_folders),
564
641
  "items": [self._serialize_file_item(item) for item in project_state.items],
565
642
  "timestamp": time.time()
566
643
  }
@@ -575,8 +652,10 @@ class ProjectStateManager:
575
652
 
576
653
  # Stop watching all folders for this project
577
654
  self.file_watcher.stop_watching(project_state.project_folder_path)
578
- for folder_path in project_state.expanded_folders:
579
- self.file_watcher.stop_watching(folder_path)
655
+ # Stop watching all expanded folders
656
+ for item in project_state.items:
657
+ if item.is_directory and item.is_expanded:
658
+ self.file_watcher.stop_watching(item.path)
580
659
 
581
660
  # Clean up managers
582
661
  self.git_managers.pop(project_id, None)
@@ -460,7 +460,6 @@ class TerminalManager:
460
460
  "git_status_summary": project_state.git_status_summary,
461
461
  "open_files": list(project_state.open_files),
462
462
  "active_file": project_state.active_file,
463
- "expanded_folders": list(project_state.expanded_folders),
464
463
  "items": [manager._serialize_file_item(item) for item in project_state.items],
465
464
  "timestamp": time.time(),
466
465
  "client_sessions": [session_name] # Target this specific session
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.16.dev4
3
+ Version: 0.3.16.dev6
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