pomera-ai-commander 1.2.1 → 1.2.3
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.
- package/core/backup_recovery_manager.py +169 -3
- package/core/data_directory.py +549 -0
- package/core/diff_utils.py +239 -0
- package/core/efficient_line_numbers.py +30 -0
- package/core/mcp/find_replace_diff.py +334 -0
- package/core/mcp/tool_registry.py +369 -9
- package/core/memento.py +275 -0
- package/mcp.json +1 -1
- package/migrate_data.py +127 -0
- package/package.json +5 -2
- package/pomera.py +408 -10
- package/pomera_mcp_server.py +2 -2
- package/requirements.txt +1 -0
- package/scripts/Dockerfile.alpine +43 -0
- package/scripts/Dockerfile.gui-test +54 -0
- package/scripts/Dockerfile.linux +43 -0
- package/scripts/Dockerfile.test-linux +80 -0
- package/scripts/Dockerfile.ubuntu +39 -0
- package/scripts/README.md +53 -0
- package/scripts/build-all.bat +113 -0
- package/scripts/build-docker.bat +53 -0
- package/scripts/build-docker.sh +55 -0
- package/scripts/build-optimized.bat +101 -0
- package/scripts/build.sh +78 -0
- package/scripts/docker-compose.test.yml +27 -0
- package/scripts/docker-compose.yml +32 -0
- package/scripts/postinstall.js +62 -0
- package/scripts/requirements-minimal.txt +33 -0
- package/scripts/test-linux-simple.bat +28 -0
- package/scripts/validate-release-workflow.py +450 -0
- package/tools/diff_viewer.py +797 -52
- package/tools/find_replace.py +551 -12
- package/tools/notes_widget.py +48 -8
- package/tools/regex_extractor.py +5 -5
|
@@ -421,6 +421,8 @@ class BackupRecoveryManager:
|
|
|
421
421
|
"""
|
|
422
422
|
Export settings to a file.
|
|
423
423
|
|
|
424
|
+
Also exports notes from notes.db if available.
|
|
425
|
+
|
|
424
426
|
Args:
|
|
425
427
|
settings_data: Settings data to export
|
|
426
428
|
export_path: Path to export file
|
|
@@ -445,18 +447,25 @@ class BackupRecoveryManager:
|
|
|
445
447
|
export_file.parent.mkdir(parents=True, exist_ok=True)
|
|
446
448
|
self.logger.debug(f"Export directory created/verified: {export_file.parent}")
|
|
447
449
|
|
|
450
|
+
# Include notes data from notes.db
|
|
451
|
+
notes_data = self._export_notes_data()
|
|
452
|
+
if notes_data:
|
|
453
|
+
settings_data['notes'] = notes_data
|
|
454
|
+
self.logger.info(f"Including {len(notes_data)} notes in export")
|
|
455
|
+
|
|
448
456
|
# Count items being exported for logging
|
|
449
457
|
tool_count = len(settings_data.get("tool_settings", {}))
|
|
458
|
+
notes_count = len(settings_data.get("notes", []))
|
|
450
459
|
total_keys = len(settings_data.keys())
|
|
451
460
|
|
|
452
461
|
if format_type == "compressed":
|
|
453
462
|
with gzip.open(export_path, 'wt', encoding='utf-8') as f:
|
|
454
463
|
json.dump(settings_data, f, indent=2, ensure_ascii=False)
|
|
455
|
-
self.logger.info(f"Settings exported (compressed) to: {export_path} - {total_keys} keys, {tool_count} tools")
|
|
464
|
+
self.logger.info(f"Settings exported (compressed) to: {export_path} - {total_keys} keys, {tool_count} tools, {notes_count} notes")
|
|
456
465
|
else:
|
|
457
466
|
with open(export_path, 'w', encoding='utf-8') as f:
|
|
458
467
|
json.dump(settings_data, f, indent=2, ensure_ascii=False)
|
|
459
|
-
self.logger.info(f"Settings exported to: {export_path} - {total_keys} keys, {tool_count} tools")
|
|
468
|
+
self.logger.info(f"Settings exported to: {export_path} - {total_keys} keys, {tool_count} tools, {notes_count} notes")
|
|
460
469
|
|
|
461
470
|
# Verify file was created and has content
|
|
462
471
|
if export_file.exists():
|
|
@@ -481,10 +490,159 @@ class BackupRecoveryManager:
|
|
|
481
490
|
self.logger.error(f"Export failed with unexpected error: {e}", exc_info=True)
|
|
482
491
|
return False
|
|
483
492
|
|
|
493
|
+
def _export_notes_data(self) -> Optional[List[Dict[str, Any]]]:
|
|
494
|
+
"""
|
|
495
|
+
Export notes from notes.db.
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
List of note dictionaries, or None if notes.db not available
|
|
499
|
+
"""
|
|
500
|
+
try:
|
|
501
|
+
# Get notes database path
|
|
502
|
+
try:
|
|
503
|
+
from core.data_directory import get_database_path
|
|
504
|
+
notes_db_path = get_database_path('notes.db')
|
|
505
|
+
except ImportError:
|
|
506
|
+
# Fallback to backup directory parent
|
|
507
|
+
notes_db_path = str(self.backup_dir.parent / 'notes.db')
|
|
508
|
+
|
|
509
|
+
if not os.path.exists(notes_db_path):
|
|
510
|
+
self.logger.debug(f"Notes database not found: {notes_db_path}")
|
|
511
|
+
return None
|
|
512
|
+
|
|
513
|
+
import sqlite3
|
|
514
|
+
conn = sqlite3.connect(notes_db_path, timeout=10.0)
|
|
515
|
+
conn.row_factory = sqlite3.Row
|
|
516
|
+
|
|
517
|
+
try:
|
|
518
|
+
cursor = conn.execute('''
|
|
519
|
+
SELECT id, Created, Modified, Title, Input, Output
|
|
520
|
+
FROM notes ORDER BY id
|
|
521
|
+
''')
|
|
522
|
+
notes = []
|
|
523
|
+
for row in cursor.fetchall():
|
|
524
|
+
notes.append({
|
|
525
|
+
'id': row['id'],
|
|
526
|
+
'Created': row['Created'],
|
|
527
|
+
'Modified': row['Modified'],
|
|
528
|
+
'Title': row['Title'],
|
|
529
|
+
'Input': row['Input'],
|
|
530
|
+
'Output': row['Output']
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
self.logger.debug(f"Exported {len(notes)} notes from notes.db")
|
|
534
|
+
return notes
|
|
535
|
+
|
|
536
|
+
finally:
|
|
537
|
+
conn.close()
|
|
538
|
+
|
|
539
|
+
except Exception as e:
|
|
540
|
+
self.logger.warning(f"Failed to export notes data: {e}")
|
|
541
|
+
return None
|
|
542
|
+
|
|
543
|
+
def _import_notes_data(self, notes_data: List[Dict[str, Any]]) -> int:
|
|
544
|
+
"""
|
|
545
|
+
Import notes to notes.db.
|
|
546
|
+
|
|
547
|
+
Notes are imported with their original IDs if no conflict exists,
|
|
548
|
+
otherwise they are inserted with new IDs.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
notes_data: List of note dictionaries to import
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
Number of notes successfully imported
|
|
555
|
+
"""
|
|
556
|
+
if not notes_data:
|
|
557
|
+
return 0
|
|
558
|
+
|
|
559
|
+
try:
|
|
560
|
+
# Get notes database path
|
|
561
|
+
try:
|
|
562
|
+
from core.data_directory import get_database_path
|
|
563
|
+
notes_db_path = get_database_path('notes.db')
|
|
564
|
+
except ImportError:
|
|
565
|
+
# Fallback to backup directory parent
|
|
566
|
+
notes_db_path = str(self.backup_dir.parent / 'notes.db')
|
|
567
|
+
|
|
568
|
+
import sqlite3
|
|
569
|
+
conn = sqlite3.connect(notes_db_path, timeout=10.0)
|
|
570
|
+
|
|
571
|
+
try:
|
|
572
|
+
# Ensure tables exist
|
|
573
|
+
conn.execute('''
|
|
574
|
+
CREATE TABLE IF NOT EXISTS notes (
|
|
575
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
576
|
+
Created DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
577
|
+
Modified DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
578
|
+
Title TEXT(255),
|
|
579
|
+
Input TEXT,
|
|
580
|
+
Output TEXT
|
|
581
|
+
)
|
|
582
|
+
''')
|
|
583
|
+
|
|
584
|
+
# Check if FTS table exists
|
|
585
|
+
fts_exists = conn.execute(
|
|
586
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='notes_fts'"
|
|
587
|
+
).fetchone() is not None
|
|
588
|
+
|
|
589
|
+
imported_count = 0
|
|
590
|
+
for note in notes_data:
|
|
591
|
+
try:
|
|
592
|
+
# Check if note with this ID already exists
|
|
593
|
+
existing = conn.execute(
|
|
594
|
+
'SELECT id FROM notes WHERE id = ?',
|
|
595
|
+
(note.get('id'),)
|
|
596
|
+
).fetchone()
|
|
597
|
+
|
|
598
|
+
if existing:
|
|
599
|
+
# Skip notes that already exist
|
|
600
|
+
self.logger.debug(f"Skipping existing note ID {note.get('id')}")
|
|
601
|
+
continue
|
|
602
|
+
|
|
603
|
+
# Insert with original ID if possible
|
|
604
|
+
conn.execute('''
|
|
605
|
+
INSERT INTO notes (id, Created, Modified, Title, Input, Output)
|
|
606
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
607
|
+
''', (
|
|
608
|
+
note.get('id'),
|
|
609
|
+
note.get('Created'),
|
|
610
|
+
note.get('Modified'),
|
|
611
|
+
note.get('Title'),
|
|
612
|
+
note.get('Input'),
|
|
613
|
+
note.get('Output')
|
|
614
|
+
))
|
|
615
|
+
imported_count += 1
|
|
616
|
+
|
|
617
|
+
except Exception as e:
|
|
618
|
+
self.logger.debug(f"Failed to import note {note.get('id')}: {e}")
|
|
619
|
+
|
|
620
|
+
conn.commit()
|
|
621
|
+
|
|
622
|
+
# Rebuild FTS index if table exists
|
|
623
|
+
if fts_exists:
|
|
624
|
+
try:
|
|
625
|
+
conn.execute('INSERT INTO notes_fts(notes_fts) VALUES("rebuild")')
|
|
626
|
+
conn.commit()
|
|
627
|
+
except Exception as e:
|
|
628
|
+
self.logger.debug(f"FTS rebuild skipped: {e}")
|
|
629
|
+
|
|
630
|
+
self.logger.debug(f"Imported {imported_count} notes to notes.db")
|
|
631
|
+
return imported_count
|
|
632
|
+
|
|
633
|
+
finally:
|
|
634
|
+
conn.close()
|
|
635
|
+
|
|
636
|
+
except Exception as e:
|
|
637
|
+
self.logger.warning(f"Failed to import notes data: {e}")
|
|
638
|
+
return 0
|
|
639
|
+
|
|
484
640
|
def import_settings(self, import_path: str) -> Optional[Dict[str, Any]]:
|
|
485
641
|
"""
|
|
486
642
|
Import settings from a file.
|
|
487
643
|
|
|
644
|
+
Also imports notes to notes.db if present in the import file.
|
|
645
|
+
|
|
488
646
|
Args:
|
|
489
647
|
import_path: Path to import file
|
|
490
648
|
|
|
@@ -524,6 +682,13 @@ class BackupRecoveryManager:
|
|
|
524
682
|
self.logger.error(f"Import failed: Invalid data format - expected dict, got {type(settings_data)}")
|
|
525
683
|
return None
|
|
526
684
|
|
|
685
|
+
# Import notes if present
|
|
686
|
+
if 'notes' in settings_data:
|
|
687
|
+
notes_data = settings_data.pop('notes') # Remove from settings_data
|
|
688
|
+
if notes_data:
|
|
689
|
+
imported_count = self._import_notes_data(notes_data)
|
|
690
|
+
self.logger.info(f"Imported {imported_count} notes to notes.db")
|
|
691
|
+
|
|
527
692
|
# Count imported items for logging
|
|
528
693
|
tool_count = len(settings_data.get("tool_settings", {}))
|
|
529
694
|
total_keys = len(settings_data.keys())
|
|
@@ -865,7 +1030,8 @@ class BackupRecoveryManager:
|
|
|
865
1030
|
# Get table counts
|
|
866
1031
|
table_counts = {}
|
|
867
1032
|
tables = ['core_settings', 'tool_settings', 'tab_content',
|
|
868
|
-
'performance_settings', 'font_settings', 'dialog_settings'
|
|
1033
|
+
'performance_settings', 'font_settings', 'dialog_settings',
|
|
1034
|
+
'notes', 'notes_fts']
|
|
869
1035
|
|
|
870
1036
|
for table in tables:
|
|
871
1037
|
try:
|