diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6e2dc8b --- /dev/null +++ b/.env.example @@ -0,0 +1,27 @@ +# ============================================================ +# clawbot-sql-memory — Memory & Storage Configuration +# ============================================================ +# SECURITY: This is a TEMPLATE ONLY. Never commit actual .env files. +# Copy this to .env and fill in your local values. + +# SQL Server — Local Development +SQL_LOCAL_SERVER=10.0.0.110 +SQL_LOCAL_PORT=1433 +SQL_LOCAL_DATABASE=Oblio_Memories +SQL_LOCAL_USER=your_sql_user +SQL_LOCAL_PASSWORD=your_sql_password + +# SQL Server — Cloud Backup +SQL_CLOUD_SERVER=SQL5112.site4now.net +SQL_CLOUD_PORT=1433 +SQL_CLOUD_DATABASE=db_99ba1f_memory4oblio +SQL_CLOUD_USER=db_99ba1f_memory4oblio_admin +SQL_CLOUD_PASSWORD=your_cloud_password + +# Memory Configuration +MEMORY_STORE=sql # Options: sql, file (sql is recommended) +MEMORY_RETENTION_DAYS=365 +SEMANTIC_SEARCH_ENABLED=true + +# Logging +LOG_LEVEL=INFO diff --git a/.github/workflows/test-on-pr.yml b/.github/workflows/test-on-pr.yml index b12053d..b7fc325 100644 --- a/.github/workflows/test-on-pr.yml +++ b/.github/workflows/test-on-pr.yml @@ -7,6 +7,20 @@ on: - develop jobs: + secret-scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: TruffleHog Secret Scan + uses: trufflesecurity/trufflehog@main + with: + path: ./ + base: ${{ github.event.repository.default_branch }} + head: HEAD + test: runs-on: ubuntu-latest strategy: @@ -34,3 +48,11 @@ jobs: - name: Build run: npm run build --if-present + + - name: Check for .env files + run: | + if [ -f .env ]; then + echo "ERROR: .env file should not be committed" + exit 1 + fi + echo "OK: No .env file tracked" diff --git a/sql-memory/sql_memory.py b/sql-memory/sql_memory.py index 44551ab..75fdfaa 100644 --- a/sql-memory/sql_memory.py +++ b/sql-memory/sql_memory.py @@ -6,12 +6,12 @@ SQLConnector v2 (pymssql, parameterised, sealed API) — no subprocess/sqlcmd. Supports two backends: - - 'cloud' → site4now hosted (SQL5112.site4now.net) — default - - 'local' → SQL Server on DEAUS (10.0.0.110) + - 'local' → SQL Server on DEAUS (10.0.0.110) — default + - 'cloud' → site4now hosted (SQL5112.site4now.net) Backward-compatible with v1.x callers: - - SQLMemory('cloud') — works as before - - get_memory('cloud') — singleton factory preserved + - SQLMemory('local') — works as before + - get_memory('local') — singleton factory preserved - mem.remember / recall / search / queue_task / log_event — all preserved - mem.execute(raw_sql) — preserved as legacy passthrough (returns bool) - mem.execute_scalar(sql) — preserved, returns Any @@ -25,7 +25,7 @@ Usage: from sql_memory import SQLMemory, get_memory - mem = get_memory('cloud') + mem = get_memory('local') mem.remember('facts', 'sky_color', 'The sky is blue', importance=3) result = mem.recall('facts', 'sky_color') """ @@ -105,10 +105,10 @@ class SQLMemory: Wraps SQLConnector — all queries are parameterised, no string interpolation. Args: - backend: 'cloud' (default) or 'local' + backend: 'local' (default) or 'cloud' """ - def __init__(self, backend: str = 'cloud') -> None: + def __init__(self, backend: str = 'local') -> None: self.backend = backend self._db = get_connector(backend) _log.info(f"SQLMemory v2.0 initialized (backend={backend})") @@ -490,7 +490,7 @@ def ensure_schema(self) -> bool: _instances: Dict[str, SQLMemory] = {} -def get_memory(backend: str = 'cloud') -> SQLMemory: +def get_memory(backend: str = 'local') -> SQLMemory: """Get or create a SQLMemory instance. Singleton per backend.""" if backend not in _instances: _instances[backend] = SQLMemory(backend) @@ -513,7 +513,7 @@ def t(name, fn): print(f" ❌ {name}: {e}") failed += 1 - mem = get_memory('cloud') + mem = get_memory('local') t("ping", mem.ping) t("remember", lambda: mem.remember('test', '_v2_test', 'v2 self-test', importance=1, tags='test')) t("recall", lambda: mem.recall('test', '_v2_test')) diff --git a/tests/test_queue_daemon.py b/tests/test_queue_daemon.py index 8521075..30f38dc 100644 --- a/tests/test_queue_daemon.py +++ b/tests/test_queue_daemon.py @@ -62,7 +62,7 @@ class TestFetchParsing(unittest.TestCase): def test_fetch_returns_none_or_dict(self): from sql_memory import get_memory from queue_daemon import fetch_next_task - mem = get_memory('cloud') + mem = get_memory('local') result = fetch_next_task(mem) self.assertTrue(result is None or isinstance(result, dict)) diff --git a/tests/test_sql_memory.py b/tests/test_sql_memory.py index 087bffc..a119d6f 100644 --- a/tests/test_sql_memory.py +++ b/tests/test_sql_memory.py @@ -41,16 +41,16 @@ class TestSQLMemoryConnection(unittest.TestCase): """Test connection and ping.""" def test_get_memory_cloud(self): - mem = get_memory('cloud') + mem = get_memory('local') self.assertIsInstance(mem, SQLMemory) def test_ping(self): - mem = get_memory('cloud') + mem = get_memory('local') result = mem.ping() self.assertIsInstance(result, bool) def test_ping_succeeds_with_valid_creds(self): - mem = get_memory('cloud') + mem = get_memory('local') self.assertTrue(mem.ping(), "Ping should succeed with valid credentials") @@ -58,7 +58,7 @@ class TestMemoryCRUD(unittest.TestCase): """Test remember/recall/search/forget cycle.""" def setUp(self): - self.mem = get_memory('cloud') + self.mem = get_memory('local') self.test_key = f"test_{datetime.now().strftime('%Y%m%d%H%M%S')}" def test_remember_and_recall(self): @@ -90,7 +90,7 @@ class TestTaskQueue(unittest.TestCase): """Test task queue operations.""" def setUp(self): - self.mem = get_memory('cloud') + self.mem = get_memory('local') self.test_task_type = f"test_task_{datetime.now().strftime('%H%M%S')}" def test_queue_and_claim(self): @@ -137,7 +137,7 @@ class TestActivityLog(unittest.TestCase): """Test event logging.""" def setUp(self): - self.mem = get_memory('cloud') + self.mem = get_memory('local') def test_log_event(self): self.mem.log_event('test_event', 'test_agent', 'unit test log entry') @@ -149,7 +149,7 @@ class TestKnowledge(unittest.TestCase): """Test knowledge store operations.""" def setUp(self): - self.mem = get_memory('cloud') + self.mem = get_memory('local') self.test_topic = f"test_topic_{datetime.now().strftime('%H%M%S')}" def test_store_and_search_knowledge(self): @@ -167,13 +167,13 @@ class TestEdgeCases(unittest.TestCase): """Test error handling and edge cases.""" def test_execute_invalid_sql(self): - mem = get_memory('cloud') + mem = get_memory('local') result = mem.execute("SELECT * FROM nonexistent_table_xyz") # Should not crash, may return error text self.assertIsInstance(result, str) def test_remember_with_special_chars(self): - mem = get_memory('cloud') + mem = get_memory('local') key = f"special_test_{datetime.now().strftime('%H%M%S')}" mem.remember('test', key, "Content with 'quotes' and \"doubles\" and ", importance=1) result = mem.recall('test', key) @@ -181,7 +181,7 @@ def test_remember_with_special_chars(self): mem.execute(f"DELETE FROM memory.Memories WHERE key_name='{key}'") def test_very_long_content(self): - mem = get_memory('cloud') + mem = get_memory('local') key = f"long_test_{datetime.now().strftime('%H%M%S')}" long_content = "x" * 5000 mem.remember('test', key, long_content, importance=1)