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.
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/PKG-INFO +1 -1
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/_version.py +2 -2
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +2 -4
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/project_state_handlers.py +113 -34
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/terminal.py +0 -1
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/.claude/agents/communication-manager.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/.claude/settings.local.json +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/.gitignore +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/.gitmodules +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/LICENSE +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/MANIFEST.in +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/Makefile +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/README.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/backup.sh +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/docker-compose.yaml +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/README.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/__init__.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/__main__.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/cli.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/README.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/client.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/base.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/registry.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/session.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/data.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/keypair.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode/service.py +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/requires.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/pyproject.toml +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/restore.sh +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/setup.cfg +0 -0
- {portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/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.dev6'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 16, 'dev6')
|
{portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/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)
|
|
@@ -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
|
|
383
|
-
"""
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
#
|
|
419
|
-
await self.
|
|
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
|
|
550
|
-
await self.
|
|
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
|
-
|
|
579
|
-
|
|
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
|
|
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.dev6}/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.dev6}/portacode/connection/handlers/system_handlers.py
RENAMED
|
File without changes
|
{portacode-0.3.16.dev4 → portacode-0.3.16.dev6}/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
|