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.
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/PKG-INFO +1 -1
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/_version.py +2 -2
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +4 -5
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/project_state_handlers.py +41 -53
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/terminal.py +0 -1
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/.claude/agents/communication-manager.md +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/.claude/settings.local.json +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/.gitignore +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/.gitmodules +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/LICENSE +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/MANIFEST.in +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/Makefile +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/README.md +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/backup.sh +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/docker-compose.yaml +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/README.md +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/__init__.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/__main__.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/cli.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/README.md +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/client.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/base.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/registry.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/session.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/data.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/keypair.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/service.py +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/requires.txt +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/pyproject.toml +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/restore.sh +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/setup.cfg +0 -0
- {portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/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.dev2'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 16, 'dev2')
|
{portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md
RENAMED
|
@@ -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
|
-
* `
|
|
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
|
-
* `
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
|
330
|
-
"""Load directory items with Git metadata
|
|
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.
|
|
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) ->
|
|
389
|
-
"""Expand a folder and
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
|
626
|
+
success = await manager.expand_folder(project_id, folder_path)
|
|
643
627
|
|
|
644
|
-
if success
|
|
645
|
-
#
|
|
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,
|
|
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.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/file_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/portacode/connection/handlers/system_handlers.py
RENAMED
|
File without changes
|
{portacode-0.3.16.dev1 → portacode-0.3.16.dev2}/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
|