portacode 0.3.16.dev4__tar.gz → 0.3.16.dev5__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.
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/PKG-INFO +1 -1
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/_version.py +2 -2
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +2 -4
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/project_state_handlers.py +105 -34
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/terminal.py +0 -1
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/.claude/agents/communication-manager.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/.claude/settings.local.json +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/.gitignore +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/.gitmodules +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/LICENSE +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/MANIFEST.in +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/Makefile +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/README.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/backup.sh +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/docker-compose.yaml +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/README.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/__init__.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/__main__.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/cli.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/README.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/client.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/base.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/registry.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/session.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/data.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/keypair.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/service.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode.egg-info/requires.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/pyproject.toml +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/restore.sh +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/setup.cfg +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/setup.py +0 -0
|
@@ -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.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 3, 16, '
|
|
20
|
+
__version__ = version = '0.3.16.dev5'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 16, 'dev5')
|
{portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md
RENAMED
|
@@ -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
|
-
* `
|
|
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
|
-
* `
|
|
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
|
|
319
|
-
await self.
|
|
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)
|
|
@@ -379,23 +376,95 @@ class ProjectStateManager:
|
|
|
379
376
|
except (OSError, PermissionError) as e:
|
|
380
377
|
logger.error("Error loading directory %s: %s", directory_path, e)
|
|
381
378
|
|
|
382
|
-
async def
|
|
383
|
-
"""
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
379
|
+
async def _build_flattened_items_structure(self, project_state: ProjectState):
|
|
380
|
+
"""Build a flattened items structure including all visible items and one level down from expanded folders."""
|
|
381
|
+
all_items = []
|
|
382
|
+
|
|
383
|
+
# Load root items
|
|
384
|
+
root_items = await self._load_directory_items_list(project_state.project_folder_path, project_state.project_folder_path)
|
|
385
|
+
|
|
386
|
+
for item in root_items:
|
|
387
|
+
all_items.append(item)
|
|
388
|
+
|
|
389
|
+
# Always load one level down from root folders (project root is always "expanded")
|
|
390
|
+
# OR if this folder is explicitly expanded, add its children and one level down
|
|
391
|
+
if item.is_directory and (item.parent_path == project_state.project_folder_path or item.is_expanded):
|
|
392
|
+
children = await self._load_directory_items_list(item.path, item.path)
|
|
393
|
+
for child in children:
|
|
394
|
+
all_items.append(child)
|
|
395
|
+
|
|
396
|
+
# If child is a directory, load one level down
|
|
397
|
+
if child.is_directory:
|
|
398
|
+
grandchildren = await self._load_directory_items_list(child.path, child.path)
|
|
399
|
+
all_items.extend(grandchildren)
|
|
400
|
+
|
|
401
|
+
project_state.items = all_items
|
|
402
|
+
|
|
403
|
+
async def _load_directory_items_list(self, directory_path: str, parent_path: str) -> List[FileItem]:
|
|
404
|
+
"""Load directory items and return as a list with parent_path."""
|
|
405
|
+
git_manager = None
|
|
406
|
+
for manager in self.git_managers.values():
|
|
407
|
+
if directory_path.startswith(manager.project_path):
|
|
408
|
+
git_manager = manager
|
|
409
|
+
break
|
|
410
|
+
|
|
411
|
+
items = []
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
with os.scandir(directory_path) as entries:
|
|
415
|
+
for entry in entries:
|
|
416
|
+
try:
|
|
417
|
+
stat_info = entry.stat()
|
|
418
|
+
is_hidden = entry.name.startswith('.')
|
|
419
|
+
|
|
420
|
+
# Get Git status if available
|
|
421
|
+
git_info = {"is_tracked": False, "status": None}
|
|
422
|
+
if git_manager:
|
|
423
|
+
git_info = git_manager.get_file_status(entry.path)
|
|
424
|
+
|
|
425
|
+
# Check if this directory is expanded by finding it in current items
|
|
426
|
+
is_expanded = False
|
|
427
|
+
if entry.is_dir():
|
|
428
|
+
# Check if this folder is expanded by looking for existing items with this path as parent
|
|
429
|
+
is_expanded = self._is_folder_expanded(entry.path)
|
|
430
|
+
|
|
431
|
+
file_item = FileItem(
|
|
432
|
+
name=entry.name,
|
|
433
|
+
path=entry.path,
|
|
434
|
+
is_directory=entry.is_dir(),
|
|
435
|
+
parent_path=parent_path,
|
|
436
|
+
size=stat_info.st_size if entry.is_file() else None,
|
|
437
|
+
modified_time=stat_info.st_mtime,
|
|
438
|
+
is_git_tracked=git_info["is_tracked"],
|
|
439
|
+
git_status=git_info["status"],
|
|
440
|
+
is_hidden=is_hidden,
|
|
441
|
+
is_expanded=is_expanded,
|
|
442
|
+
is_loaded=not entry.is_dir()
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
items.append(file_item)
|
|
446
|
+
|
|
447
|
+
except (OSError, PermissionError) as e:
|
|
448
|
+
logger.debug("Error reading entry %s: %s", entry.path, e)
|
|
449
|
+
continue
|
|
450
|
+
|
|
451
|
+
# Sort items: directories first, then files, both alphabetically
|
|
452
|
+
items.sort(key=lambda x: (not x.is_directory, x.name.lower()))
|
|
453
|
+
|
|
454
|
+
except (OSError, PermissionError) as e:
|
|
455
|
+
logger.error("Error loading directory %s: %s", directory_path, e)
|
|
456
|
+
|
|
457
|
+
return items
|
|
458
|
+
|
|
459
|
+
def _is_folder_expanded(self, folder_path: str) -> bool:
|
|
460
|
+
"""Check if a folder is expanded by looking at existing items."""
|
|
461
|
+
# During initial load, no folders are expanded
|
|
462
|
+
# During updates, check if any items have this folder as parent_path
|
|
463
|
+
for project_state in self.projects.values():
|
|
464
|
+
for item in project_state.items:
|
|
465
|
+
if item.parent_path == folder_path:
|
|
466
|
+
return True
|
|
467
|
+
return False
|
|
399
468
|
|
|
400
469
|
async def expand_folder(self, project_id: str, folder_path: str) -> bool:
|
|
401
470
|
"""Expand a folder and load its contents."""
|
|
@@ -404,19 +473,18 @@ class ProjectStateManager:
|
|
|
404
473
|
|
|
405
474
|
project_state = self.projects[project_id]
|
|
406
475
|
|
|
407
|
-
# Find the folder item
|
|
476
|
+
# Find the folder item and mark it as expanded
|
|
408
477
|
folder_item = self._find_item_by_path(project_state.items, folder_path)
|
|
409
478
|
if not folder_item or not folder_item.is_directory:
|
|
410
479
|
return False
|
|
411
480
|
|
|
412
481
|
folder_item.is_expanded = True
|
|
413
|
-
project_state.expanded_folders.add(folder_path)
|
|
414
482
|
|
|
415
483
|
# Start watching this folder
|
|
416
484
|
self.file_watcher.start_watching(folder_path)
|
|
417
485
|
|
|
418
|
-
#
|
|
419
|
-
await self.
|
|
486
|
+
# Rebuild the entire flattened structure to include new expanded content
|
|
487
|
+
await self._build_flattened_items_structure(project_state)
|
|
420
488
|
|
|
421
489
|
self._write_debug_state()
|
|
422
490
|
return True
|
|
@@ -428,18 +496,20 @@ class ProjectStateManager:
|
|
|
428
496
|
|
|
429
497
|
project_state = self.projects[project_id]
|
|
430
498
|
|
|
431
|
-
# Find the folder item
|
|
499
|
+
# Find the folder item and mark it as collapsed
|
|
432
500
|
folder_item = self._find_item_by_path(project_state.items, folder_path)
|
|
433
501
|
if not folder_item or not folder_item.is_directory:
|
|
434
502
|
return False
|
|
435
503
|
|
|
436
504
|
folder_item.is_expanded = False
|
|
437
|
-
project_state.expanded_folders.discard(folder_path)
|
|
438
505
|
|
|
439
506
|
# Stop watching collapsed folders (except root)
|
|
440
507
|
if folder_path != project_state.project_folder_path:
|
|
441
508
|
self.file_watcher.stop_watching(folder_path)
|
|
442
509
|
|
|
510
|
+
# Rebuild the flattened structure to remove collapsed content
|
|
511
|
+
await self._build_flattened_items_structure(project_state)
|
|
512
|
+
|
|
443
513
|
self._write_debug_state()
|
|
444
514
|
return True
|
|
445
515
|
|
|
@@ -546,8 +616,8 @@ class ProjectStateManager:
|
|
|
546
616
|
self._write_debug_state()
|
|
547
617
|
|
|
548
618
|
async def _reload_visible_structures(self, project_state: ProjectState):
|
|
549
|
-
"""Reload all visible structures with
|
|
550
|
-
await self.
|
|
619
|
+
"""Reload all visible structures with flattened items."""
|
|
620
|
+
await self._build_flattened_items_structure(project_state)
|
|
551
621
|
|
|
552
622
|
async def _send_project_state_update(self, project_state: ProjectState):
|
|
553
623
|
"""Send project state update to clients."""
|
|
@@ -560,7 +630,6 @@ class ProjectStateManager:
|
|
|
560
630
|
"git_status_summary": project_state.git_status_summary,
|
|
561
631
|
"open_files": list(project_state.open_files),
|
|
562
632
|
"active_file": project_state.active_file,
|
|
563
|
-
"expanded_folders": list(project_state.expanded_folders),
|
|
564
633
|
"items": [self._serialize_file_item(item) for item in project_state.items],
|
|
565
634
|
"timestamp": time.time()
|
|
566
635
|
}
|
|
@@ -575,8 +644,10 @@ class ProjectStateManager:
|
|
|
575
644
|
|
|
576
645
|
# Stop watching all folders for this project
|
|
577
646
|
self.file_watcher.stop_watching(project_state.project_folder_path)
|
|
578
|
-
|
|
579
|
-
|
|
647
|
+
# Stop watching all expanded folders
|
|
648
|
+
for item in project_state.items:
|
|
649
|
+
if item.is_directory and item.is_expanded:
|
|
650
|
+
self.file_watcher.stop_watching(item.path)
|
|
580
651
|
|
|
581
652
|
# Clean up managers
|
|
582
653
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/file_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/system_handlers.py
RENAMED
|
File without changes
|
{portacode-0.3.16.dev4 → portacode-0.3.16.dev5}/portacode/connection/handlers/terminal_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|