cli-memory-os 0.1.2__tar.gz → 0.1.3__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 (66) hide show
  1. {cli_memory_os-0.1.2/cli_memory_os.egg-info → cli_memory_os-0.1.3}/PKG-INFO +1 -1
  2. cli_memory_os-0.1.3/cli/commands/ask.py +67 -0
  3. cli_memory_os-0.1.3/cli/commands/sync.py +70 -0
  4. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/main.py +14 -29
  5. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3/cli_memory_os.egg-info}/PKG-INFO +1 -1
  6. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli_memory_os.egg-info/SOURCES.txt +3 -0
  7. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/pyproject.toml +1 -1
  8. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/tests/test_first_time_user_experience.py +1 -0
  9. cli_memory_os-0.1.3/tests/test_sync_ask_cli.py +111 -0
  10. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/LICENSE +0 -0
  11. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/MANIFEST.in +0 -0
  12. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/README.md +0 -0
  13. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/__init__.py +0 -0
  14. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/__init__.py +0 -0
  15. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/benchmark.py +0 -0
  16. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/config_cmd.py +0 -0
  17. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/doctor.py +0 -0
  18. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/export.py +0 -0
  19. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/import_cmd.py +0 -0
  20. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/init.py +0 -0
  21. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/logs.py +0 -0
  22. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/monitor.py +0 -0
  23. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/plugins.py +0 -0
  24. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/restart.py +0 -0
  25. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/start.py +0 -0
  26. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/status.py +0 -0
  27. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/stop.py +0 -0
  28. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/version.py +0 -0
  29. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/commands/workspace.py +0 -0
  30. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli/parser.py +0 -0
  31. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli_memory_os.egg-info/dependency_links.txt +0 -0
  32. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli_memory_os.egg-info/entry_points.txt +0 -0
  33. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli_memory_os.egg-info/requires.txt +0 -0
  34. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/cli_memory_os.egg-info/top_level.txt +0 -0
  35. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/connectors/__init__.py +0 -0
  36. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/connectors/base.py +0 -0
  37. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/connectors/github.py +0 -0
  38. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/connectors/gmail.py +0 -0
  39. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/connectors/notion.py +0 -0
  40. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/connectors/registry.py +0 -0
  41. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/core/__init__.py +0 -0
  42. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/core/chunker.py +0 -0
  43. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/core/context_builder.py +0 -0
  44. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/core/embedder.py +0 -0
  45. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/core/llm.py +0 -0
  46. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/core/vector_store.py +0 -0
  47. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/infrastructure/__init__.py +0 -0
  48. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/infrastructure/compose.py +0 -0
  49. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/infrastructure/config.py +0 -0
  50. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/infrastructure/docker.py +0 -0
  51. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/infrastructure/health.py +0 -0
  52. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/infrastructure/observability.py +0 -0
  53. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/infrastructure/workspace.py +0 -0
  54. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/models/__init__.py +0 -0
  55. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/models/memory.py +0 -0
  56. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/setup.cfg +0 -0
  57. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/storage/__init__.py +0 -0
  58. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/storage/db.py +0 -0
  59. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/storage/graph.py +0 -0
  60. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/storage/schema.sql +0 -0
  61. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/storage/tech_detector.py +0 -0
  62. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/tests/test_cli_integration.py +0 -0
  63. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/tests/test_commands_phase2.py +0 -0
  64. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/tests/test_compose_manager.py +0 -0
  65. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/tests/test_infrastructure_stabilization.py +0 -0
  66. {cli_memory_os-0.1.2 → cli_memory_os-0.1.3}/tests/test_v1_features.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-memory-os
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: CLI-based Personal Knowledge Operating System
5
5
  Author-email: Anirudh T <anirudh200584@gmail.com>
6
6
  License-Expression: MIT
@@ -0,0 +1,67 @@
1
+ """
2
+ Command: memory-os ask
3
+
4
+ Queries the RAG pipeline with a question.
5
+ """
6
+
7
+ import sys
8
+ import sqlite3
9
+ from core.llm import run_hybrid_rag
10
+ from storage.db import init_db
11
+
12
+
13
+ def run_and_print_ask(query: str):
14
+ """Run RAG query pipeline and print the structured result blocks."""
15
+ rag_res = run_hybrid_rag(query)
16
+
17
+ print("========================================")
18
+ print("ANSWER")
19
+ print("========================================")
20
+ print(rag_res["answer"])
21
+ print("\n========================================")
22
+ print("SOURCES")
23
+ print("========================================")
24
+ if rag_res.get("sources"):
25
+ for s in rag_res["sources"]:
26
+ print(f"- {s}")
27
+ else:
28
+ print("None")
29
+ print("\n========================================")
30
+ print("REPOSITORIES USED")
31
+ print("========================================")
32
+ if rag_res.get("repositories"):
33
+ for r in rag_res["repositories"]:
34
+ print(f"- {r}")
35
+ else:
36
+ print("None")
37
+ print("\n========================================")
38
+ confidence = rag_res.get("confidence", 0.0)
39
+ print(f"Confidence: {confidence:.2f}")
40
+ print("========================================")
41
+
42
+
43
+ def execute(args):
44
+ """Run the ask command parser handler."""
45
+ # Ensure database is initialized
46
+ try:
47
+ init_db()
48
+ except sqlite3.OperationalError as e:
49
+ print("----------------------------------")
50
+ print("Workspace has not been initialized.")
51
+ print("")
52
+ print("Run:")
53
+ print("memory-os init")
54
+ print("----------------------------------")
55
+ sys.exit(1)
56
+
57
+ if not args.question:
58
+ print("Usage: memory-os ask <question>")
59
+ sys.exit(1)
60
+
61
+ query = " ".join(args.question)
62
+
63
+ try:
64
+ run_and_print_ask(query)
65
+ except Exception as e:
66
+ print(f"❌ Error executing query: {e}")
67
+ sys.exit(1)
@@ -0,0 +1,70 @@
1
+ """
2
+ Command: memory-os sync
3
+
4
+ Synchronizes data from connected sources (GitHub, Gmail, Notion),
5
+ chunks documents, generates embeddings, and syncs databases.
6
+ """
7
+
8
+ import sys
9
+ import time
10
+ import logging
11
+ import sqlite3
12
+ from storage.db import init_db, clear_all
13
+ from core.vector_store import run_reindexing, close_qdrant_client
14
+ from storage.graph import GraphStore
15
+ from connectors.github import sync_github
16
+ from connectors.gmail import sync_gmail
17
+ from connectors.notion import sync_notion
18
+
19
+
20
+ def execute(args):
21
+ """Run the synchronization pipeline."""
22
+ # Ensure database is initialized
23
+ try:
24
+ init_db()
25
+ except sqlite3.OperationalError as e:
26
+ print("----------------------------------")
27
+ print("Workspace has not been initialized.")
28
+ print("")
29
+ print("Run:")
30
+ print("memory-os init")
31
+ print("----------------------------------")
32
+ sys.exit(1)
33
+
34
+ start_time = time.perf_counter()
35
+
36
+ rebuild = getattr(args, "rebuild", False)
37
+ source = getattr(args, "source", None)
38
+
39
+ if rebuild:
40
+ print("Performing full rebuild reset...")
41
+ clear_all()
42
+ close_qdrant_client()
43
+ run_reindexing()
44
+ GraphStore().clear_graph()
45
+
46
+ # Sync based on source selection
47
+ if source == "github":
48
+ print("Syncing GitHub source...")
49
+ sync_github()
50
+ elif source == "gmail":
51
+ print("Syncing Gmail source...")
52
+ sync_gmail()
53
+ elif source == "notion":
54
+ print("Syncing Notion source...")
55
+ sync_notion()
56
+ else:
57
+ print("Syncing all sources (GitHub, Gmail, Notion)...")
58
+ sync_github()
59
+ sync_gmail()
60
+ sync_notion()
61
+
62
+ print("Running document chunking and vector reindexing...")
63
+ run_reindexing()
64
+
65
+ print("Extracting and syncing knowledge graph relationships...")
66
+ GraphStore().extract_and_sync_graph()
67
+
68
+ duration = time.perf_counter() - start_time
69
+ print(f"\nSync complete. Total Duration: {duration:.2f}s")
70
+ logging.getLogger("main").info(f"Sync completed in {duration:.2f}s")
@@ -115,7 +115,6 @@ def run_interactive():
115
115
  close_qdrant_client,
116
116
  )
117
117
  from core.embedder import Embedder
118
- from core.llm import run_hybrid_rag
119
118
  from storage.graph import GraphStore
120
119
  from connectors.github import sync_github
121
120
  from connectors.gmail import sync_gmail
@@ -283,30 +282,11 @@ def run_interactive():
283
282
  if not arg:
284
283
  print("Usage: ask <question>")
285
284
  else:
286
- rag_res = run_hybrid_rag(arg)
287
- print("========================================")
288
- print("ANSWER")
289
- print("========================================")
290
- print(rag_res["answer"])
291
- print("\n========================================")
292
- print("SOURCES")
293
- print("========================================")
294
- if rag_res["sources"]:
295
- for s in rag_res["sources"]:
296
- print(f"- {s}")
297
- else:
298
- print("None")
299
- print("\n========================================")
300
- print("REPOSITORIES USED")
301
- print("========================================")
302
- if rag_res["repositories"]:
303
- for r in rag_res["repositories"]:
304
- print(f"- {r}")
305
- else:
306
- print("None")
307
- print("\n========================================")
308
- print(f"Confidence: {rag_res['confidence']:.2f}")
309
- print("========================================")
285
+ from cli.commands.ask import run_and_print_ask
286
+ try:
287
+ run_and_print_ask(arg)
288
+ except Exception as e:
289
+ print(f"❌ Error executing query: {e}")
310
290
 
311
291
  elif cmd == "repo-info":
312
292
  if not arg:
@@ -441,11 +421,16 @@ def route_command(args):
441
421
  elif command == "logs":
442
422
  from cli.commands.logs import execute
443
423
  execute(args)
424
+ elif command == "sync":
425
+ from cli.commands.sync import execute
426
+ execute(args)
427
+ elif command == "ask":
428
+ from cli.commands.ask import execute
429
+ execute(args)
444
430
  else:
445
- # For commands not yet implemented, show a helpful message
446
- print(f"Command '{command}' is not yet implemented.")
447
- print("Run 'memory-os --help' for available commands.")
448
- print("Run 'memory-os' (no arguments) for the interactive REPL.")
431
+ from cli.parser import build_parser
432
+ build_parser().print_help()
433
+ sys.exit(1)
449
434
 
450
435
 
451
436
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-memory-os
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: CLI-based Personal Knowledge Operating System
5
5
  Author-email: Anirudh T <anirudh200584@gmail.com>
6
6
  License-Expression: MIT
@@ -6,6 +6,7 @@ cli/__init__.py
6
6
  cli/main.py
7
7
  cli/parser.py
8
8
  cli/commands/__init__.py
9
+ cli/commands/ask.py
9
10
  cli/commands/benchmark.py
10
11
  cli/commands/config_cmd.py
11
12
  cli/commands/doctor.py
@@ -19,6 +20,7 @@ cli/commands/restart.py
19
20
  cli/commands/start.py
20
21
  cli/commands/status.py
21
22
  cli/commands/stop.py
23
+ cli/commands/sync.py
22
24
  cli/commands/version.py
23
25
  cli/commands/workspace.py
24
26
  cli_memory_os.egg-info/PKG-INFO
@@ -58,4 +60,5 @@ tests/test_commands_phase2.py
58
60
  tests/test_compose_manager.py
59
61
  tests/test_first_time_user_experience.py
60
62
  tests/test_infrastructure_stabilization.py
63
+ tests/test_sync_ask_cli.py
61
64
  tests/test_v1_features.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cli-memory-os"
3
- version = "0.1.2"
3
+ version = "0.1.3"
4
4
  description = "CLI-based Personal Knowledge Operating System"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -76,6 +76,7 @@ def test_init_missing_docker(capsys):
76
76
  """Test memory-os init when Docker Desktop is not running."""
77
77
  with patch("infrastructure.docker.check_docker_installed", return_value=(True, "Docker version 24.0.7")), \
78
78
  patch("infrastructure.docker.check_docker_compose_installed", return_value=(True, "Docker Compose version 2.23.3")), \
79
+ patch("infrastructure.docker.check_docker_daemon", return_value=(False, "Docker Desktop is not running.")), \
79
80
  patch("infrastructure.docker.check_docker_running", return_value=False):
80
81
 
81
82
  with pytest.raises(SystemExit) as excinfo:
@@ -0,0 +1,111 @@
1
+ """
2
+ Unit and integration tests for sync, ask commands, and the interactive REPL.
3
+ """
4
+
5
+ import sys
6
+ import os
7
+ import unittest
8
+ from unittest.mock import patch, MagicMock
9
+
10
+ # Ensure project root is in path
11
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
12
+
13
+
14
+ class DummyArgs:
15
+ def __init__(self, **kwargs):
16
+ for k, v in kwargs.items():
17
+ setattr(self, k, v)
18
+
19
+
20
+ class TestSyncAskCLI(unittest.TestCase):
21
+ """Test suite verifying sync and ask CLI command routing and logic."""
22
+
23
+ @patch("cli.commands.sync.init_db")
24
+ @patch("cli.commands.sync.sync_github")
25
+ @patch("cli.commands.sync.sync_gmail")
26
+ @patch("cli.commands.sync.sync_notion")
27
+ @patch("cli.commands.sync.run_reindexing")
28
+ @patch("cli.commands.sync.GraphStore")
29
+ def test_sync_command_all_sources(
30
+ self, mock_graph_store_class, mock_run_reindexing, mock_sync_notion, mock_sync_gmail, mock_sync_github, mock_init_db
31
+ ):
32
+ """Test memory-os sync command runs sync for all sources."""
33
+ mock_graph_inst = MagicMock()
34
+ mock_graph_store_class.return_value = mock_graph_inst
35
+
36
+ from cli.commands.sync import execute as execute_sync
37
+
38
+ args = DummyArgs(rebuild=False, source=None)
39
+ execute_sync(args)
40
+
41
+ mock_init_db.assert_called_once()
42
+ mock_sync_github.assert_called_once()
43
+ mock_sync_gmail.assert_called_once()
44
+ mock_sync_notion.assert_called_once()
45
+ mock_run_reindexing.assert_called_once()
46
+ mock_graph_inst.extract_and_sync_graph.assert_called_once()
47
+
48
+ @patch("cli.commands.sync.init_db")
49
+ @patch("cli.commands.sync.sync_github")
50
+ @patch("cli.commands.sync.run_reindexing")
51
+ @patch("cli.commands.sync.GraphStore")
52
+ def test_sync_command_single_source(
53
+ self, mock_graph_store_class, mock_run_reindexing, mock_sync_github, mock_init_db
54
+ ):
55
+ """Test memory-os sync command can sync only a single source."""
56
+ mock_graph_inst = MagicMock()
57
+ mock_graph_store_class.return_value = mock_graph_inst
58
+
59
+ from cli.commands.sync import execute as execute_sync
60
+
61
+ args = DummyArgs(rebuild=False, source="github")
62
+ execute_sync(args)
63
+
64
+ mock_init_db.assert_called_once()
65
+ mock_sync_github.assert_called_once()
66
+ mock_run_reindexing.assert_called_once()
67
+ mock_graph_inst.extract_and_sync_graph.assert_called_once()
68
+
69
+ @patch("cli.commands.ask.init_db")
70
+ @patch("cli.commands.ask.run_hybrid_rag")
71
+ def test_ask_command(self, mock_run_hybrid_rag, mock_init_db):
72
+ """Test memory-os ask command query execution."""
73
+ mock_run_hybrid_rag.return_value = {
74
+ "answer": "Python is used in core services.",
75
+ "sources": ["source_1.txt"],
76
+ "repositories": ["repo-a"],
77
+ "confidence": 0.95
78
+ }
79
+
80
+ from cli.commands.ask import execute as execute_ask
81
+
82
+ args = DummyArgs(question=["What", "projects", "use", "Python?"])
83
+ execute_ask(args)
84
+
85
+ mock_init_db.assert_called_once()
86
+ mock_run_hybrid_rag.assert_called_once_with("What projects use Python?")
87
+
88
+ @patch("storage.db.init_db")
89
+ @patch("core.embedder.Embedder")
90
+ @patch("cli.main.input")
91
+ @patch("cli.commands.ask.run_and_print_ask")
92
+ def test_interactive_repl_ask(self, mock_run_and_print_ask, mock_input, mock_embedder_class, mock_init_db):
93
+ """Test interactive REPL triggers the ask command routing for natural language query."""
94
+ # Mock REPL sequence: 1. A question, 2. 'exit' to terminate loop
95
+ mock_input.side_effect = ["What projects use Python?", "exit"]
96
+
97
+ # Mock embedder instantiation
98
+ mock_embedder_inst = MagicMock()
99
+ mock_embedder_class.return_value = mock_embedder_inst
100
+
101
+ from cli.main import run_interactive
102
+
103
+ with patch("core.vector_store.close_qdrant_client"), patch("storage.graph.GraphStore"):
104
+ run_interactive()
105
+
106
+ # Verify that natural language query gets routed to ask and calls run_and_print_ask
107
+ mock_run_and_print_ask.assert_called_once_with("What projects use Python?")
108
+
109
+
110
+ if __name__ == "__main__":
111
+ unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes