sqlsaber 0.30.2__py3-none-any.whl → 0.32.0__py3-none-any.whl

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.

Potentially problematic release.


This version of sqlsaber might be problematic. Click here for more details.

sqlsaber/cli/auth.py CHANGED
@@ -11,10 +11,12 @@ from sqlsaber.config.api_keys import APIKeyManager
11
11
  from sqlsaber.config.auth import AuthConfigManager, AuthMethod
12
12
  from sqlsaber.config.oauth_tokens import OAuthTokenManager
13
13
  from sqlsaber.theme.manager import create_console
14
+ from sqlsaber.config.logging import get_logger
14
15
 
15
16
  # Global instances for CLI commands
16
17
  console = create_console()
17
18
  config_manager = AuthConfigManager()
19
+ logger = get_logger(__name__)
18
20
 
19
21
  # Create the authentication management CLI app
20
22
  auth_app = cyclopts.App(
@@ -46,7 +48,9 @@ def setup():
46
48
  )
47
49
  return success, provider
48
50
 
49
- success, _ = asyncio.run(run_setup())
51
+ logger.info("auth.setup.start")
52
+ success, provider = asyncio.run(run_setup())
53
+ logger.info("auth.setup.complete", success=bool(success), provider=str(provider))
50
54
 
51
55
  if not success:
52
56
  console.print("\n[warning]No authentication configured.[/warning]")
@@ -59,6 +63,7 @@ def setup():
59
63
  @auth_app.command
60
64
  def status():
61
65
  """Show current authentication configuration and provider key status."""
66
+ logger.info("auth.status.start")
62
67
  auth_method = config_manager.get_auth_method()
63
68
 
64
69
  console.print("\n[bold blue]Authentication Status[/bold blue]")
@@ -68,6 +73,7 @@ def status():
68
73
  console.print(
69
74
  "Run [primary]saber auth setup[/primary] to configure authentication."
70
75
  )
76
+ logger.info("auth.status.none_configured")
71
77
  return
72
78
 
73
79
  # Show configured method summary
@@ -93,6 +99,7 @@ def status():
93
99
  console.print(f"> {provider}: [green]configured[/green]")
94
100
  else:
95
101
  console.print(f"> {provider}: [warning]not configured[/warning]")
102
+ logger.info("auth.status.complete", method=str(auth_method))
96
103
 
97
104
 
98
105
  @auth_app.command
@@ -108,6 +115,7 @@ def reset():
108
115
 
109
116
  if provider is None:
110
117
  console.print("[warning]Reset cancelled.[/warning]")
118
+ logger.info("auth.reset.cancelled_no_provider")
111
119
  return
112
120
 
113
121
  api_key_manager = APIKeyManager()
@@ -125,6 +133,7 @@ def reset():
125
133
  console.print(
126
134
  f"[warning]No stored credentials found for {provider}. Nothing to reset.[/warning]"
127
135
  )
136
+ logger.info("auth.reset.nothing_to_reset", provider=provider)
128
137
  return
129
138
 
130
139
  # Build confirmation message
@@ -142,6 +151,7 @@ def reset():
142
151
 
143
152
  if not confirmed:
144
153
  console.print("Reset cancelled.")
154
+ logger.info("auth.reset.cancelled_confirm", provider=provider)
145
155
  return
146
156
 
147
157
  # Perform deletions
@@ -151,11 +161,13 @@ def reset():
151
161
  try:
152
162
  keyring.delete_password(service, provider)
153
163
  console.print(f"Removed {provider} API key from keyring", style="green")
164
+ logger.info("auth.reset.api_key_removed", provider=provider)
154
165
  except keyring.errors.PasswordDeleteError:
155
166
  # Already absent; treat as success
156
167
  pass
157
168
  except Exception as e:
158
169
  console.print(f"Warning: Could not remove API key: {e}", style="warning")
170
+ logger.warning("auth.reset.api_key_remove_failed", provider=provider, error=str(e))
159
171
 
160
172
  # Optionally clear global auth method if removing Anthropic OAuth configuration
161
173
  if provider == "anthropic" and oauth_present:
@@ -170,8 +182,10 @@ def reset():
170
182
  config["auth_method"] = None
171
183
  config_manager._save_config(config)
172
184
  console.print("Global auth method unset.", style="green")
185
+ logger.info("auth.reset.global_method_unset")
173
186
 
174
187
  console.print("\n[success]✓ Reset complete.[/success]")
188
+ logger.info("auth.reset.complete", provider=provider)
175
189
  console.print(
176
190
  "Environment variables are not modified by this command.", style="dim"
177
191
  )
sqlsaber/cli/commands.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """CLI command definitions and handlers."""
2
2
 
3
3
  import asyncio
4
+ import os
4
5
  import sys
5
6
  from typing import Annotated
6
7
 
@@ -16,6 +17,7 @@ from sqlsaber.cli.threads import create_threads_app
16
17
 
17
18
  # Lazy imports - only import what's needed for CLI parsing
18
19
  from sqlsaber.config.database import DatabaseConfigManager
20
+ from sqlsaber.config.logging import get_logger, setup_logging
19
21
  from sqlsaber.theme.manager import create_console
20
22
 
21
23
 
@@ -42,6 +44,52 @@ app.command(create_threads_app(), name="threads")
42
44
  console = create_console()
43
45
  config_manager = DatabaseConfigManager()
44
46
 
47
+ _MLFLOW_CONFIGURED = False
48
+
49
+
50
+ def _maybe_configure_mlflow(log) -> bool:
51
+ """Enable mlflow autologging when environment variables are present."""
52
+ global _MLFLOW_CONFIGURED
53
+ if _MLFLOW_CONFIGURED:
54
+ return True
55
+
56
+ tracking_uri = os.getenv("MLFLOW_URI")
57
+ experiment = os.getenv("MLFLOW_EXP")
58
+ if not tracking_uri and not experiment:
59
+ return False
60
+
61
+ try:
62
+ import mlflow
63
+ except ModuleNotFoundError:
64
+ log.warning(
65
+ "mlflow.setup.skipped",
66
+ reason="mlflow package not installed",
67
+ uri=tracking_uri,
68
+ experiment=experiment,
69
+ )
70
+ return False
71
+
72
+ try:
73
+ mlflow.pydantic_ai.autolog()
74
+ except Exception:
75
+ log.warning("mlflow.autolog.failed", exc_info=True)
76
+ try:
77
+ if tracking_uri:
78
+ mlflow.set_tracking_uri(tracking_uri)
79
+ if experiment:
80
+ mlflow.set_experiment(experiment)
81
+ except Exception:
82
+ log.warning("mlflow.setup.failed", exc_info=True)
83
+ return False
84
+
85
+ _MLFLOW_CONFIGURED = True
86
+ log.info(
87
+ "mlflow.setup.enabled",
88
+ uri=tracking_uri,
89
+ experiment=experiment,
90
+ )
91
+ return True
92
+
45
93
 
46
94
  @app.meta.default
47
95
  def meta_handler(
@@ -111,6 +159,14 @@ def query(
111
159
  """
112
160
 
113
161
  async def run_session():
162
+ log = get_logger(__name__)
163
+ log.info(
164
+ "cli.session.start",
165
+ argv=sys.argv[1:],
166
+ database=database,
167
+ has_query=query_text is not None,
168
+ thinking=thinking,
169
+ )
114
170
  # Import heavy dependencies only when actually running a query
115
171
  # This is only done to speed up startup time
116
172
  from sqlsaber.agents import SQLSaberAgent
@@ -134,29 +190,39 @@ def query(
134
190
  # Check if onboarding is needed (only for interactive mode or when no database is configured)
135
191
  if needs_onboarding(database):
136
192
  # Run onboarding flow
193
+ log.debug("cli.onboarding.start")
137
194
  onboarding_success = await run_onboarding()
138
195
  if not onboarding_success:
139
196
  # User cancelled or onboarding failed
140
197
  raise CLIError(
141
198
  "Setup incomplete. Please configure your database and try again."
142
199
  )
200
+ log.info("cli.onboarding.complete", success=True)
143
201
 
144
202
  # Resolve database from CLI input
145
203
  try:
146
204
  resolved = resolve_database(database, config_manager)
147
205
  connection_string = resolved.connection_string
148
206
  db_name = resolved.name
207
+ log.info(
208
+ "db.resolve.success",
209
+ name=db_name,
210
+ )
149
211
  except DatabaseResolutionError as e:
212
+ log.error("db.resolve.error", error=str(e))
150
213
  raise CLIError(str(e))
151
214
 
152
215
  # Create database connection
153
216
  try:
154
217
  db_conn = DatabaseConnection(connection_string)
218
+ log.info("db.connection.created", db_type=type(db_conn).__name__)
155
219
  except Exception as e:
220
+ log.exception("db.connection.error", error=str(e))
156
221
  raise CLIError(f"Error creating database connection: {e}")
157
222
 
158
223
  # Create pydantic-ai agent instance with database name for memory context
159
224
  sqlsaber_agent = SQLSaberAgent(db_conn, db_name, thinking_enabled=thinking)
225
+ _maybe_configure_mlflow(log)
160
226
 
161
227
  try:
162
228
  if actual_query:
@@ -166,9 +232,11 @@ def query(
166
232
  console.print(
167
233
  f"[primary]Connected to:[/primary] {db_name} ({db_type})\n"
168
234
  )
235
+ log.info("query.execute.start", db_name=db_name, db_type=db_type)
169
236
  run = await streaming_handler.execute_streaming_query(
170
237
  actual_query, sqlsaber_agent
171
238
  )
239
+
172
240
  # Persist non-interactive run as a thread snapshot so it can be resumed later
173
241
  try:
174
242
  if run is not None:
@@ -187,8 +255,10 @@ def query(
187
255
  console.print(
188
256
  f"[dim]You can continue this thread using:[/dim] saber threads resume {thread_id}"
189
257
  )
258
+ log.info("thread.save.success", thread_id=thread_id)
190
259
  except Exception:
191
260
  # best-effort persistence; don't fail the CLI on storage errors
261
+ log.warning("thread.save.failed", exc_info=True)
192
262
  pass
193
263
  finally:
194
264
  await threads.prune_threads()
@@ -200,16 +270,20 @@ def query(
200
270
  finally:
201
271
  # Clean up
202
272
  await db_conn.close()
273
+ log.info("db.connection.closed")
203
274
  console.print("\n[success]Goodbye![/success]")
204
275
 
205
276
  # Run the async function with proper error handling
206
277
  try:
207
278
  asyncio.run(run_session())
208
279
  except CLIError as e:
280
+ get_logger(__name__).error("cli.error", error=str(e))
209
281
  console.print(f"[error]Error:[/error] {e}")
210
282
  sys.exit(e.exit_code)
211
283
 
212
284
 
213
285
  def main():
214
286
  """Entry point for the CLI application."""
287
+ setup_logging()
288
+ get_logger(__name__).info("cli.start")
215
289
  app()
sqlsaber/cli/database.py CHANGED
@@ -11,11 +11,13 @@ import questionary
11
11
  from rich.table import Table
12
12
 
13
13
  from sqlsaber.config.database import DatabaseConfig, DatabaseConfigManager
14
+ from sqlsaber.config.logging import get_logger
14
15
  from sqlsaber.theme.manager import create_console
15
16
 
16
17
  # Global instances for CLI commands
17
18
  console = create_console()
18
19
  config_manager = DatabaseConfigManager()
20
+ logger = get_logger(__name__)
19
21
 
20
22
  # Create the database management CLI app
21
23
  db_app = cyclopts.App(
@@ -78,6 +80,13 @@ def add(
78
80
  ] = True,
79
81
  ):
80
82
  """Add a new database connection."""
83
+ logger.info(
84
+ "db.add.start",
85
+ name=name,
86
+ type=type,
87
+ interactive=bool(interactive),
88
+ has_password=False,
89
+ )
81
90
 
82
91
  if interactive:
83
92
  # Interactive mode - prompt for all required fields
@@ -96,6 +105,7 @@ def add(
96
105
 
97
106
  if db_input is None:
98
107
  console.print("[warning]Operation cancelled[/warning]")
108
+ logger.info("db.add.cancelled")
99
109
  return
100
110
 
101
111
  # Extract values from db_input
@@ -116,6 +126,7 @@ def add(
116
126
  console.print(
117
127
  "[bold error]Error:[/bold error] Database file path is required for SQLite"
118
128
  )
129
+ logger.error("db.add.missing_path", db_type="sqlite")
119
130
  sys.exit(1)
120
131
  host = "localhost"
121
132
  port = 0
@@ -126,6 +137,7 @@ def add(
126
137
  console.print(
127
138
  "[bold error]Error:[/bold error] Database file path is required for DuckDB"
128
139
  )
140
+ logger.error("db.add.missing_path", db_type="duckdb")
129
141
  sys.exit(1)
130
142
  database = str(Path(database).expanduser().resolve())
131
143
  host = "localhost"
@@ -137,6 +149,7 @@ def add(
137
149
  console.print(
138
150
  "[bold error]Error:[/bold error] Host, database, and username are required"
139
151
  )
152
+ logger.error("db.add.missing_fields")
140
153
  sys.exit(1)
141
154
 
142
155
  if port is None:
@@ -173,12 +186,15 @@ def add(
173
186
  # Add the configuration
174
187
  config_manager.add_database(db_config, password if password else None)
175
188
  console.print(f"[green]Successfully added database connection '{name}'[/green]")
189
+ logger.info("db.add.success", name=name, type=type)
176
190
 
177
191
  # Set as default if it's the first one
178
192
  if len(config_manager.list_databases()) == 1:
179
193
  console.print(f"[blue]Set '{name}' as default database[/blue]")
194
+ logger.info("db.default.set", name=name)
180
195
 
181
196
  except Exception as e:
197
+ logger.exception("db.add.error", name=name, error=str(e))
182
198
  console.print(f"[bold error]Error adding database:[/bold error] {e}")
183
199
  sys.exit(1)
184
200
 
@@ -186,12 +202,14 @@ def add(
186
202
  @db_app.command
187
203
  def list():
188
204
  """List all configured database connections."""
205
+ logger.info("db.list.start")
189
206
  databases = config_manager.list_databases()
190
207
  default_name = config_manager.get_default_name()
191
208
 
192
209
  if not databases:
193
210
  console.print("[warning]No database connections configured[/warning]")
194
211
  console.print("Use 'sqlsaber db add <name>' to add a database connection")
212
+ logger.info("db.list.empty")
195
213
  return
196
214
 
197
215
  table = Table(title="Database Connections")
@@ -228,6 +246,7 @@ def list():
228
246
  )
229
247
 
230
248
  console.print(table)
249
+ logger.info("db.list.complete", count=len(databases))
231
250
 
232
251
 
233
252
  @db_app.command
@@ -237,10 +256,12 @@ def remove(
237
256
  ],
238
257
  ):
239
258
  """Remove a database connection."""
259
+ logger.info("db.remove.start", name=name)
240
260
  if not config_manager.get_database(name):
241
261
  console.print(
242
262
  f"[bold error]Error:[/bold error] Database connection '{name}' not found"
243
263
  )
264
+ logger.error("db.remove.not_found", name=name)
244
265
  sys.exit(1)
245
266
 
246
267
  if questionary.confirm(
@@ -250,13 +271,16 @@ def remove(
250
271
  console.print(
251
272
  f"[green]Successfully removed database connection '{name}'[/green]"
252
273
  )
274
+ logger.info("db.remove.success", name=name)
253
275
  else:
254
276
  console.print(
255
277
  f"[bold error]Error:[/bold error] Failed to remove database connection '{name}'"
256
278
  )
279
+ logger.error("db.remove.failed", name=name)
257
280
  sys.exit(1)
258
281
  else:
259
282
  console.print("Operation cancelled")
283
+ logger.info("db.remove.cancelled", name=name)
260
284
 
261
285
 
262
286
  @db_app.command
@@ -267,18 +291,22 @@ def set_default(
267
291
  ],
268
292
  ):
269
293
  """Set the default database connection."""
294
+ logger.info("db.default.start", name=name)
270
295
  if not config_manager.get_database(name):
271
296
  console.print(
272
297
  f"[bold error]Error:[/bold error] Database connection '{name}' not found"
273
298
  )
299
+ logger.error("db.default.not_found", name=name)
274
300
  sys.exit(1)
275
301
 
276
302
  if config_manager.set_default_database(name):
277
303
  console.print(f"[green]Successfully set '{name}' as default database[/green]")
304
+ logger.info("db.default.success", name=name)
278
305
  else:
279
306
  console.print(
280
307
  f"[bold error]Error:[/bold error] Failed to set '{name}' as default"
281
308
  )
309
+ logger.error("db.default.failed", name=name)
282
310
  sys.exit(1)
283
311
 
284
312
 
@@ -292,6 +320,7 @@ def test(
292
320
  ] = None,
293
321
  ):
294
322
  """Test a database connection."""
323
+ logger.info("db.test.start")
295
324
 
296
325
  async def test_connection():
297
326
  # Lazy import to keep CLI startup fast
@@ -303,6 +332,7 @@ def test(
303
332
  console.print(
304
333
  f"[bold error]Error:[/bold error] Database connection '{name}' not found"
305
334
  )
335
+ logger.error("db.test.not_found", name=name)
306
336
  sys.exit(1)
307
337
  else:
308
338
  db_config = config_manager.get_default_database()
@@ -313,6 +343,7 @@ def test(
313
343
  console.print(
314
344
  "Use 'sqlsaber db add <name>' to add a database connection"
315
345
  )
346
+ logger.error("db.test.no_default")
316
347
  sys.exit(1)
317
348
 
318
349
  console.print(f"[blue]Testing connection to '{db_config.name}'...[/blue]")
@@ -328,8 +359,16 @@ def test(
328
359
  console.print(
329
360
  f"[green]✓ Connection to '{db_config.name}' successful[/green]"
330
361
  )
362
+ logger.info("db.test.success", name=db_config.name)
331
363
 
332
364
  except Exception as e:
365
+ logger.exception(
366
+ "db.test.failed",
367
+ name=(
368
+ db_config.name if "db_config" in locals() and db_config else name
369
+ ),
370
+ error=str(e),
371
+ )
333
372
  console.print(f"[bold error]✗ Connection failed:[/bold error] {e}")
334
373
  sys.exit(1)
335
374
 
@@ -30,6 +30,7 @@ from sqlsaber.database import (
30
30
  from sqlsaber.database.schema import SchemaManager
31
31
  from sqlsaber.theme.manager import get_theme_manager
32
32
  from sqlsaber.threads import ThreadStorage
33
+ from sqlsaber.config.logging import get_logger
33
34
 
34
35
  if TYPE_CHECKING:
35
36
  from sqlsaber.agents.pydantic_ai_agent import SQLSaberAgent
@@ -66,6 +67,7 @@ class InteractiveSession:
66
67
  self._threads = ThreadStorage()
67
68
  self._thread_id: str | None = initial_thread_id
68
69
  self.first_message = not self._thread_id
70
+ self.log = get_logger(__name__)
69
71
 
70
72
  def _history_path(self) -> Path:
71
73
  """Get the history file path, ensuring directory exists."""
@@ -240,6 +242,7 @@ class InteractiveSession:
240
242
 
241
243
  async def _execute_query_with_cancellation(self, user_query: str):
242
244
  """Execute a query with cancellation support."""
245
+ self.log.info("interactive.query.start", database=self.database_name)
243
246
  # Create cancellation token
244
247
  self.cancellation_token = asyncio.Event()
245
248
 
@@ -276,16 +279,18 @@ class InteractiveSession:
276
279
  model_name=self.sqlsaber_agent.agent.model.model_name,
277
280
  )
278
281
  self.first_message = False
279
- except Exception:
280
- pass
282
+ except Exception as e:
283
+ self.log.warning("interactive.thread.save_failed", error=str(e))
281
284
  finally:
282
285
  await self._threads.prune_threads()
283
286
  finally:
284
287
  self.current_task = None
285
288
  self.cancellation_token = None
289
+ self.log.info("interactive.query.end")
286
290
 
287
291
  async def run(self):
288
292
  """Run the interactive session loop."""
293
+ self.log.info("interactive.start", database=self.database_name)
289
294
  self.show_welcome_message()
290
295
  await self.before_prompt_loop()
291
296
 
@@ -348,3 +353,4 @@ class InteractiveSession:
348
353
  break
349
354
  except Exception as exc:
350
355
  self.console.print(f"[error]Error:[/error] {exc}")
356
+ self.log.exception("interactive.error", error=str(exc))
sqlsaber/cli/memory.py CHANGED
@@ -8,6 +8,7 @@ import questionary
8
8
  from rich.table import Table
9
9
 
10
10
  from sqlsaber.config.database import DatabaseConfigManager
11
+ from sqlsaber.config.logging import get_logger
11
12
  from sqlsaber.memory.manager import MemoryManager
12
13
  from sqlsaber.theme.manager import create_console
13
14
 
@@ -15,6 +16,7 @@ from sqlsaber.theme.manager import create_console
15
16
  console = create_console()
16
17
  config_manager = DatabaseConfigManager()
17
18
  memory_manager = MemoryManager()
19
+ logger = get_logger(__name__)
18
20
 
19
21
  # Create the memory management CLI app
20
22
  memory_app = cyclopts.App(
@@ -31,6 +33,7 @@ def _get_database_name(database: str | None = None) -> str:
31
33
  console.print(
32
34
  f"[bold error]Error:[/bold error] Database connection '{database}' not found."
33
35
  )
36
+ logger.error("memory.db.not_found", database=database)
34
37
  sys.exit(1)
35
38
  return database
36
39
  else:
@@ -40,6 +43,7 @@ def _get_database_name(database: str | None = None) -> str:
40
43
  "[bold error]Error:[/bold error] No database connections configured."
41
44
  )
42
45
  console.print("Use 'sqlsaber db add <name>' to add a database connection.")
46
+ logger.error("memory.db.none_configured")
43
47
  sys.exit(1)
44
48
  return db_config.name
45
49
 
@@ -57,14 +61,17 @@ def add(
57
61
  ):
58
62
  """Add a new memory for the specified database."""
59
63
  database_name = _get_database_name(database)
64
+ logger.info("memory.add.start", database=database_name)
60
65
 
61
66
  try:
62
67
  memory = memory_manager.add_memory(database_name, content)
63
68
  console.print(f"[green]✓ Memory added for database '{database_name}'[/green]")
64
69
  console.print(f"[dim]Memory ID:[/dim] {memory.id}")
65
70
  console.print(f"[dim]Content:[/dim] {memory.content}")
71
+ logger.info("memory.add.success", database=database_name, id=memory.id)
66
72
  except Exception as e:
67
73
  console.print(f"[bold error]Error adding memory:[/bold error] {e}")
74
+ logger.exception("memory.add.error", database=database_name, error=str(e))
68
75
  sys.exit(1)
69
76
 
70
77
 
@@ -80,6 +87,7 @@ def list(
80
87
  ):
81
88
  """List all memories for the specified database."""
82
89
  database_name = _get_database_name(database)
90
+ logger.info("memory.list.start", database=database_name)
83
91
 
84
92
  memories = memory_manager.get_memories(database_name)
85
93
 
@@ -88,6 +96,7 @@ def list(
88
96
  f"[warning]No memories found for database '{database_name}'[/warning]"
89
97
  )
90
98
  console.print("Use 'sqlsaber memory add \"<content>\"' to add memories")
99
+ logger.info("memory.list.empty", database=database_name)
91
100
  return
92
101
 
93
102
  table = Table(title=f"Memories for Database: {database_name}")
@@ -105,6 +114,7 @@ def list(
105
114
 
106
115
  console.print(table)
107
116
  console.print(f"\n[dim]Total memories: {len(memories)}[/dim]")
117
+ logger.info("memory.list.complete", database=database_name, count=len(memories))
108
118
 
109
119
 
110
120
  @memory_app.command
@@ -120,6 +130,7 @@ def show(
120
130
  ):
121
131
  """Show the full content of a specific memory."""
122
132
  database_name = _get_database_name(database)
133
+ logger.info("memory.show.start", database=database_name, id=memory_id)
123
134
 
124
135
  memory = memory_manager.get_memory_by_id(database_name, memory_id)
125
136
 
@@ -127,6 +138,7 @@ def show(
127
138
  console.print(
128
139
  f"[bold error]Error:[/bold error] Memory with ID '{memory_id}' not found for database '{database_name}'"
129
140
  )
141
+ logger.error("memory.show.not_found", database=database_name, id=memory_id)
130
142
  sys.exit(1)
131
143
 
132
144
  console.print(f"[bold]Memory ID:[/bold] {memory.id}")
@@ -149,6 +161,7 @@ def remove(
149
161
  ):
150
162
  """Remove a specific memory by ID."""
151
163
  database_name = _get_database_name(database)
164
+ logger.info("memory.remove.start", database=database_name, id=memory_id)
152
165
 
153
166
  # First check if memory exists
154
167
  memory = memory_manager.get_memory_by_id(database_name, memory_id)
@@ -156,6 +169,7 @@ def remove(
156
169
  console.print(
157
170
  f"[bold error]Error:[/bold error] Memory with ID '{memory_id}' not found for database '{database_name}'"
158
171
  )
172
+ logger.error("memory.remove.not_found", database=database_name, id=memory_id)
159
173
  sys.exit(1)
160
174
 
161
175
  # Show memory content before removal
@@ -166,10 +180,12 @@ def remove(
166
180
  console.print(
167
181
  f"[green]✓ Memory removed from database '{database_name}'[/green]"
168
182
  )
183
+ logger.info("memory.remove.success", database=database_name, id=memory_id)
169
184
  else:
170
185
  console.print(
171
186
  f"[bold error]Error:[/bold error] Failed to remove memory '{memory_id}'"
172
187
  )
188
+ logger.error("memory.remove.failed", database=database_name, id=memory_id)
173
189
  sys.exit(1)
174
190
 
175
191
 
@@ -192,6 +208,7 @@ def clear(
192
208
  ):
193
209
  """Clear all memories for the specified database."""
194
210
  database_name = _get_database_name(database)
211
+ logger.info("memory.clear.start", database=database_name, force=bool(force))
195
212
 
196
213
  # Count memories first
197
214
  memories_count = len(memory_manager.get_memories(database_name))
@@ -200,6 +217,7 @@ def clear(
200
217
  console.print(
201
218
  f"[warning]No memories to clear for database '{database_name}'[/warning]"
202
219
  )
220
+ logger.info("memory.clear.nothing", database=database_name)
203
221
  return
204
222
 
205
223
  if not force:
@@ -210,12 +228,14 @@ def clear(
210
228
 
211
229
  if not questionary.confirm("Are you sure you want to proceed?").ask():
212
230
  console.print("Operation cancelled")
231
+ logger.info("memory.clear.cancelled", database=database_name)
213
232
  return
214
233
 
215
234
  cleared_count = memory_manager.clear_memories(database_name)
216
235
  console.print(
217
236
  f"[green]✓ Cleared {cleared_count} memories for database '{database_name}'[/green]"
218
237
  )
238
+ logger.info("memory.clear.success", database=database_name, deleted=cleared_count)
219
239
 
220
240
 
221
241
  @memory_app.command
@@ -230,6 +250,7 @@ def summary(
230
250
  ):
231
251
  """Show memory summary for the specified database."""
232
252
  database_name = _get_database_name(database)
253
+ logger.info("memory.summary.start", database=database_name)
233
254
 
234
255
  summary = memory_manager.get_memories_summary(database_name)
235
256
 
@@ -240,6 +261,11 @@ def summary(
240
261
  console.print("\n[bold]Recent memories:[/bold]")
241
262
  for memory in summary["memories"][-5:]: # Show last 5 memories
242
263
  console.print(f"[dim]{memory['timestamp']}[/dim] - {memory['content']}")
264
+ logger.info(
265
+ "memory.summary.complete",
266
+ database=database_name,
267
+ total=summary["total_memories"],
268
+ )
243
269
 
244
270
 
245
271
  def create_memory_app() -> cyclopts.App: