portacode 0.3.19.dev2__tar.gz → 0.3.19.dev3__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 (45) hide show
  1. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/PKG-INFO +1 -1
  2. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/_version.py +2 -2
  3. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/project_state_handlers.py +164 -71
  4. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode.egg-info/PKG-INFO +1 -1
  5. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/restore.sh +2 -0
  6. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/.claude/agents/communication-manager.md +0 -0
  7. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/.claude/settings.local.json +0 -0
  8. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/.gitignore +0 -0
  9. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/.gitmodules +0 -0
  10. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/LICENSE +0 -0
  11. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/MANIFEST.in +0 -0
  12. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/Makefile +0 -0
  13. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/README.md +0 -0
  14. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/backup.sh +0 -0
  15. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/docker-compose.yaml +0 -0
  16. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/README.md +0 -0
  17. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/__init__.py +0 -0
  18. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/__main__.py +0 -0
  19. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/cli.py +0 -0
  20. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/README.md +0 -0
  21. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/__init__.py +0 -0
  22. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/client.py +0 -0
  23. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/README.md +0 -0
  24. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  25. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/__init__.py +0 -0
  26. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/base.py +0 -0
  27. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/file_handlers.py +0 -0
  28. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/registry.py +0 -0
  29. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/session.py +0 -0
  30. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/system_handlers.py +0 -0
  31. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/tab_factory.py +0 -0
  32. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/handlers/terminal_handlers.py +0 -0
  33. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/multiplex.py +0 -0
  34. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/connection/terminal.py +0 -0
  35. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/data.py +0 -0
  36. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/keypair.py +0 -0
  37. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode/service.py +0 -0
  38. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode.egg-info/SOURCES.txt +0 -0
  39. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode.egg-info/dependency_links.txt +0 -0
  40. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode.egg-info/entry_points.txt +0 -0
  41. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode.egg-info/requires.txt +0 -0
  42. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/portacode.egg-info/top_level.txt +0 -0
  43. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/pyproject.toml +0 -0
  44. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/setup.cfg +0 -0
  45. {portacode-0.3.19.dev2 → portacode-0.3.19.dev3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.19.dev2
3
+ Version: 0.3.19.dev3
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.19.dev2'
21
- __version_tuple__ = version_tuple = (0, 3, 19, 'dev2')
20
+ __version__ = version = '0.3.19.dev3'
21
+ __version_tuple__ = version_tuple = (0, 3, 19, 'dev3')
@@ -401,35 +401,45 @@ class GitManager:
401
401
  return None
402
402
 
403
403
  def _generate_html_diff(self, original_content: str, modified_content: str, file_path: str) -> Optional[str]:
404
- """Generate unified HTML diff with syntax highlighting using Pygments and diff-match-patch."""
405
- if not DIFF_MATCH_PATCH_AVAILABLE or not PYGMENTS_AVAILABLE:
406
- logger.debug("Required libraries not available for HTML diff generation")
404
+ """Generate unified HTML diff with intra-line highlighting using GitPython and diff-match-patch."""
405
+ if not PYGMENTS_AVAILABLE:
406
+ logger.debug("Pygments not available for HTML diff generation")
407
407
  return None
408
408
 
409
409
  try:
410
- # Get diff details
411
- dmp = diff_match_patch()
412
- diffs = dmp.diff_main(original_content, modified_content)
413
- dmp.diff_cleanupSemantic(diffs)
410
+ import difflib
411
+
412
+ # Get line-based diff using Python's difflib (similar to git diff)
413
+ original_lines = original_content.splitlines(keepends=True)
414
+ modified_lines = modified_content.splitlines(keepends=True)
415
+
416
+ # Generate unified diff
417
+ diff_lines = list(difflib.unified_diff(
418
+ original_lines,
419
+ modified_lines,
420
+ fromfile='a/' + os.path.basename(file_path),
421
+ tofile='b/' + os.path.basename(file_path),
422
+ lineterm=''
423
+ ))
424
+
425
+ # Parse the unified diff and add intra-line highlighting
426
+ parsed_diff = self._parse_unified_diff_with_intraline(diff_lines, original_lines, modified_lines)
414
427
 
415
428
  # Get Pygments lexer for syntax highlighting
416
429
  lexer = self._get_pygments_lexer(file_path)
417
430
 
418
- # Convert diffs to line-based unified diff
419
- unified_diff_lines = self._create_unified_diff_lines(diffs, original_content, modified_content)
420
-
421
431
  # Build HTML
422
432
  html_parts = []
423
433
  html_parts.append('<div class="unified-diff-container">')
424
434
 
425
435
  # Add stats header
426
- char_additions = sum(len(text) for op, text in diffs if op == 1)
427
- char_deletions = sum(len(text) for op, text in diffs if op == -1)
436
+ line_additions = sum(1 for line in parsed_diff if line['type'] == 'add')
437
+ line_deletions = sum(1 for line in parsed_diff if line['type'] == 'delete')
428
438
 
429
439
  html_parts.append(f'''
430
440
  <div class="diff-stats">
431
- <span class="additions">+{char_additions}</span>
432
- <span class="deletions">-{char_deletions}</span>
441
+ <span class="additions">+{line_additions}</span>
442
+ <span class="deletions">-{line_deletions}</span>
433
443
  <span class="file-path">{os.path.basename(file_path)}</span>
434
444
  </div>
435
445
  ''')
@@ -438,26 +448,29 @@ class GitManager:
438
448
  html_parts.append('<div class="diff-content">')
439
449
  html_parts.append('<table class="diff-table">')
440
450
 
441
- for line_info in unified_diff_lines:
442
- line_type = line_info['type'] # 'context', 'delete', 'add', 'header'
451
+ for line_info in parsed_diff:
452
+ if line_info['type'] == 'header':
453
+ continue # Skip diff headers
454
+
455
+ line_type = line_info['type']
443
456
  old_line_num = line_info.get('old_line_num', '')
444
457
  new_line_num = line_info.get('new_line_num', '')
445
458
  content = line_info['content']
459
+ intraline_html = line_info.get('intraline_html', '')
446
460
 
447
- # Apply syntax highlighting to the content
448
- if lexer and content.strip() and line_type != 'header':
461
+ # Use intra-line highlighted content if available, otherwise apply syntax highlighting
462
+ if intraline_html:
463
+ final_content = intraline_html
464
+ elif lexer and content.strip():
449
465
  try:
450
- # Remove the +/- prefix for highlighting, then add it back
466
+ # Apply syntax highlighting to the content (without the +/- prefix)
451
467
  clean_content = content[1:] if content and content[0] in '+-' else content
452
468
  highlighted = highlight(clean_content, lexer, HtmlFormatter(nowrap=True, noclasses=False))
453
- # Add the prefix back
454
- if content and content[0] in '+-':
455
- highlighted = content[0] + highlighted[len(clean_content):]
456
- content = highlighted
469
+ final_content = content[0] + highlighted if content and content[0] in '+-' else highlighted
457
470
  except Exception:
458
- content = self._escape_html(content)
471
+ final_content = self._escape_html(content)
459
472
  else:
460
- content = self._escape_html(content)
473
+ final_content = self._escape_html(content)
461
474
 
462
475
  # CSS classes for different line types
463
476
  row_class = f'diff-line diff-{line_type}'
@@ -466,7 +479,7 @@ class GitManager:
466
479
  <tr class="{row_class}">
467
480
  <td class="line-num old-line-num">{old_line_num}</td>
468
481
  <td class="line-num new-line-num">{new_line_num}</td>
469
- <td class="line-content">{content}</td>
482
+ <td class="line-content">{final_content}</td>
470
483
  </tr>
471
484
  ''')
472
485
 
@@ -480,53 +493,133 @@ class GitManager:
480
493
  logger.error("Error generating HTML diff: %s", e)
481
494
  return None
482
495
 
483
- def _create_unified_diff_lines(self, diffs, original_content: str, modified_content: str):
484
- """Create unified diff lines from diff-match-patch output."""
485
- lines = []
486
-
487
- # Split content into lines
488
- original_lines = original_content.splitlines()
489
- modified_lines = modified_content.splitlines()
490
-
491
- old_line_num = 1
492
- new_line_num = 1
493
-
494
- # Process each diff operation
495
- for op, text in diffs:
496
- text_lines = text.splitlines()
497
-
498
- if op == 0: # EQUAL - context lines
499
- for line in text_lines:
500
- lines.append({
501
- 'type': 'context',
502
- 'old_line_num': old_line_num,
503
- 'new_line_num': new_line_num,
504
- 'content': ' ' + line
505
- })
506
- old_line_num += 1
507
- new_line_num += 1
496
+ def _parse_unified_diff_with_intraline(self, diff_lines, original_lines, modified_lines):
497
+ """Parse unified diff and add intra-line character highlighting."""
498
+ parsed = []
499
+ old_line_num = 0
500
+ new_line_num = 0
501
+
502
+ pending_deletes = []
503
+ pending_adds = []
504
+
505
+ def flush_pending():
506
+ """Process pending delete/add pairs for intra-line highlighting."""
507
+ if pending_deletes and pending_adds:
508
+ # Apply intra-line highlighting to delete/add pairs
509
+ for i, (del_line, add_line) in enumerate(zip(pending_deletes, pending_adds)):
510
+ del_content = del_line['content'][1:] # Remove '-' prefix
511
+ add_content = add_line['content'][1:] # Remove '+' prefix
512
+
513
+ del_highlighted, add_highlighted = self._generate_intraline_diff(del_content, add_content)
508
514
 
509
- elif op == -1: # DELETE
510
- for line in text_lines:
511
- lines.append({
512
- 'type': 'delete',
513
- 'old_line_num': old_line_num,
514
- 'new_line_num': '',
515
- 'content': '-' + line
516
- })
517
- old_line_num += 1
515
+ # Update the parsed lines with intra-line highlighting
516
+ del_line['intraline_html'] = '-' + del_highlighted
517
+ add_line['intraline_html'] = '+' + add_highlighted
518
518
 
519
- elif op == 1: # INSERT
520
- for line in text_lines:
521
- lines.append({
522
- 'type': 'add',
523
- 'old_line_num': '',
524
- 'new_line_num': new_line_num,
525
- 'content': '+' + line
526
- })
527
- new_line_num += 1
528
-
529
- return lines
519
+ parsed.append(del_line)
520
+ parsed.append(add_line)
521
+
522
+ # Handle remaining unmatched deletes/adds
523
+ for del_line in pending_deletes[len(pending_adds):]:
524
+ parsed.append(del_line)
525
+ for add_line in pending_adds[len(pending_deletes):]:
526
+ parsed.append(add_line)
527
+ else:
528
+ # No pairs to highlight, just add them as-is
529
+ parsed.extend(pending_deletes)
530
+ parsed.extend(pending_adds)
531
+
532
+ pending_deletes.clear()
533
+ pending_adds.clear()
534
+
535
+ for line in diff_lines:
536
+ if line.startswith('@@'):
537
+ # Flush any pending changes before hunk header
538
+ flush_pending()
539
+
540
+ # Parse hunk header to get line numbers
541
+ import re
542
+ match = re.match(r'@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@', line)
543
+ if match:
544
+ old_line_num = int(match.group(1)) - 1
545
+ new_line_num = int(match.group(2)) - 1
546
+
547
+ parsed.append({
548
+ 'type': 'header',
549
+ 'content': line,
550
+ 'old_line_num': '',
551
+ 'new_line_num': ''
552
+ })
553
+ elif line.startswith('-'):
554
+ pending_deletes.append({
555
+ 'type': 'delete',
556
+ 'old_line_num': old_line_num + 1,
557
+ 'new_line_num': '',
558
+ 'content': line
559
+ })
560
+ old_line_num += 1
561
+ elif line.startswith('+'):
562
+ pending_adds.append({
563
+ 'type': 'add',
564
+ 'old_line_num': '',
565
+ 'new_line_num': new_line_num + 1,
566
+ 'content': line
567
+ })
568
+ new_line_num += 1
569
+ elif line.startswith(' '):
570
+ # Flush pending changes before context line
571
+ flush_pending()
572
+
573
+ old_line_num += 1
574
+ new_line_num += 1
575
+ parsed.append({
576
+ 'type': 'context',
577
+ 'old_line_num': old_line_num,
578
+ 'new_line_num': new_line_num,
579
+ 'content': line
580
+ })
581
+ elif line.startswith('---') or line.startswith('+++'):
582
+ parsed.append({
583
+ 'type': 'header',
584
+ 'content': line,
585
+ 'old_line_num': '',
586
+ 'new_line_num': ''
587
+ })
588
+
589
+ # Flush any remaining pending changes
590
+ flush_pending()
591
+
592
+ return parsed
593
+
594
+ def _generate_intraline_diff(self, old_text: str, new_text: str) -> tuple[str, str]:
595
+ """Generate intra-line character-level diff highlighting."""
596
+ if not DIFF_MATCH_PATCH_AVAILABLE:
597
+ return self._escape_html(old_text), self._escape_html(new_text)
598
+
599
+ try:
600
+ dmp = diff_match_patch()
601
+ diffs = dmp.diff_main(old_text, new_text)
602
+ dmp.diff_cleanupSemantic(diffs)
603
+
604
+ old_parts = []
605
+ new_parts = []
606
+
607
+ for op, text in diffs:
608
+ escaped_text = self._escape_html(text)
609
+
610
+ if op == 0: # EQUAL
611
+ old_parts.append(escaped_text)
612
+ new_parts.append(escaped_text)
613
+ elif op == -1: # DELETE
614
+ old_parts.append(f'<span class="intraline-delete">{escaped_text}</span>')
615
+ elif op == 1: # INSERT
616
+ new_parts.append(f'<span class="intraline-add">{escaped_text}</span>')
617
+
618
+ return ''.join(old_parts), ''.join(new_parts)
619
+
620
+ except Exception as e:
621
+ logger.debug("Error generating intra-line diff: %s", e)
622
+ return self._escape_html(old_text), self._escape_html(new_text)
530
623
 
531
624
  def _escape_html(self, text: str) -> str:
532
625
  """Escape HTML special characters."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.19.dev2
3
+ Version: 0.3.19.dev3
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -8,6 +8,8 @@ BACKUP_DIR="${1:-$PWD/../backups}"
8
8
  VOLUME_NAME="portacode_pgdata"
9
9
  SERVICE="db"
10
10
 
11
+ SDFSD
12
+
11
13
  # ─── PICK A BACKUP ────────────────────────────────────────────────────────
12
14
  mapfile -t BACKUPS < <(
13
15
  find "$BACKUP_DIR" -maxdepth 1 -type f -name "pgdata-*.tar.gz" \
File without changes