basic-memory 0.14.2__py3-none-any.whl → 0.14.4__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 basic-memory might be problematic. Click here for more details.
- basic_memory/__init__.py +1 -1
- basic_memory/alembic/env.py +3 -1
- basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +53 -0
- basic_memory/api/app.py +4 -1
- basic_memory/api/routers/management_router.py +3 -1
- basic_memory/api/routers/project_router.py +21 -13
- basic_memory/api/routers/resource_router.py +3 -3
- basic_memory/cli/app.py +3 -3
- basic_memory/cli/commands/__init__.py +1 -2
- basic_memory/cli/commands/db.py +5 -5
- basic_memory/cli/commands/import_chatgpt.py +3 -2
- basic_memory/cli/commands/import_claude_conversations.py +3 -1
- basic_memory/cli/commands/import_claude_projects.py +3 -1
- basic_memory/cli/commands/import_memory_json.py +5 -2
- basic_memory/cli/commands/mcp.py +3 -15
- basic_memory/cli/commands/project.py +46 -6
- basic_memory/cli/commands/status.py +4 -1
- basic_memory/cli/commands/sync.py +10 -2
- basic_memory/cli/main.py +0 -1
- basic_memory/config.py +61 -34
- basic_memory/db.py +2 -6
- basic_memory/deps.py +3 -2
- basic_memory/file_utils.py +65 -0
- basic_memory/importers/chatgpt_importer.py +20 -10
- basic_memory/importers/memory_json_importer.py +22 -7
- basic_memory/importers/utils.py +2 -2
- basic_memory/markdown/entity_parser.py +2 -2
- basic_memory/markdown/markdown_processor.py +2 -2
- basic_memory/markdown/plugins.py +42 -26
- basic_memory/markdown/utils.py +1 -1
- basic_memory/mcp/async_client.py +22 -2
- basic_memory/mcp/project_session.py +6 -4
- basic_memory/mcp/prompts/__init__.py +0 -2
- basic_memory/mcp/server.py +8 -71
- basic_memory/mcp/tools/build_context.py +12 -2
- basic_memory/mcp/tools/move_note.py +24 -12
- basic_memory/mcp/tools/project_management.py +22 -7
- basic_memory/mcp/tools/read_content.py +16 -0
- basic_memory/mcp/tools/read_note.py +17 -2
- basic_memory/mcp/tools/sync_status.py +3 -2
- basic_memory/mcp/tools/write_note.py +9 -1
- basic_memory/models/knowledge.py +13 -2
- basic_memory/models/project.py +3 -3
- basic_memory/repository/entity_repository.py +2 -2
- basic_memory/repository/project_repository.py +19 -1
- basic_memory/repository/search_repository.py +7 -3
- basic_memory/schemas/base.py +40 -10
- basic_memory/schemas/importer.py +1 -0
- basic_memory/schemas/memory.py +23 -11
- basic_memory/services/context_service.py +12 -2
- basic_memory/services/directory_service.py +7 -0
- basic_memory/services/entity_service.py +56 -10
- basic_memory/services/initialization.py +0 -75
- basic_memory/services/project_service.py +93 -36
- basic_memory/sync/background_sync.py +4 -3
- basic_memory/sync/sync_service.py +53 -4
- basic_memory/sync/watch_service.py +31 -8
- basic_memory/utils.py +234 -71
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/METADATA +21 -92
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/RECORD +63 -68
- basic_memory/cli/commands/auth.py +0 -136
- basic_memory/mcp/auth_provider.py +0 -270
- basic_memory/mcp/external_auth_provider.py +0 -321
- basic_memory/mcp/prompts/sync_status.py +0 -112
- basic_memory/mcp/supabase_auth_provider.py +0 -463
- basic_memory/services/migration_service.py +0 -168
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/WHEEL +0 -0
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,138 +1,133 @@
|
|
|
1
|
-
basic_memory/__init__.py,sha256=
|
|
2
|
-
basic_memory/config.py,sha256=
|
|
3
|
-
basic_memory/db.py,sha256=
|
|
4
|
-
basic_memory/deps.py,sha256=
|
|
5
|
-
basic_memory/file_utils.py,sha256=
|
|
6
|
-
basic_memory/utils.py,sha256=
|
|
1
|
+
basic_memory/__init__.py,sha256=mDdoiW51bM3-d6fzochHAwurt6I9-U7K-Ar8WifQv4I,256
|
|
2
|
+
basic_memory/config.py,sha256=boVbIb5qgHmvXQo4XlkpaxxqRmhDXWqHUtjbD-_brxE,13046
|
|
3
|
+
basic_memory/db.py,sha256=JeBTsLP56PqCOwX8CZAB7-ijpPbg-Aco5QebpFqkBtg,7428
|
|
4
|
+
basic_memory/deps.py,sha256=vOKM2cpUm4r84LxonMH7LcgM1bkp-FEUqmgruK6CQR0,12144
|
|
5
|
+
basic_memory/file_utils.py,sha256=p1pjbAHNrAR4H3VuMq_Nh4yjJUKkbnwEAygtg5SrMio,8395
|
|
6
|
+
basic_memory/utils.py,sha256=cHcvxLF48lHL2XbekcEFkY6RVJrFwZfJXu28Itepm7o,12704
|
|
7
7
|
basic_memory/alembic/alembic.ini,sha256=IEZsnF8CbbZnkwBr67LzKKNobHuzTaQNUvM8Psop5xc,3733
|
|
8
|
-
basic_memory/alembic/env.py,sha256=
|
|
8
|
+
basic_memory/alembic/env.py,sha256=4kHZh-rfzVARy9ndvsuDPTBt6Hk3dZ2BwI030EppBrA,2838
|
|
9
9
|
basic_memory/alembic/migrations.py,sha256=lriHPXDdBLSNXEW3QTpU0SJKuVd1V-8NrVkpN3qfsUQ,718
|
|
10
10
|
basic_memory/alembic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
|
|
11
11
|
basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py,sha256=lTbWlAnd1es7xU99DoJgfaRe1_Kte8TL98riqeKGV80,4363
|
|
12
12
|
basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py,sha256=k6xYTmYPM9Ros-7CA7BwZBKYwoK_gmVdC-2n8FAjdoE,1840
|
|
13
13
|
basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py,sha256=2CCY9ayjzbraYMcinqSsJi9Sc0nu2e-ehkUJus-sz34,4379
|
|
14
14
|
basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py,sha256=YErFkIpZdicvUil4ZtE6uxSpk5BZCTXZ_TfPE-MgSfo,4210
|
|
15
|
+
basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py,sha256=ItF6VEmSMIKBRZSEse9cUHSQ3xTQpQ1TJBrwournz7g,1884
|
|
15
16
|
basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py,sha256=RsGymQzfRXV1LSNKiyi0lMilTxW1NgwS9jR67ye2apI,1428
|
|
16
17
|
basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py,sha256=kDavR9Qqx9wSu5P3qd4SZq_waIsDG1UMTg2SmDoktMU,3679
|
|
17
18
|
basic_memory/api/__init__.py,sha256=wCpj-21j1D0KzKl9Ql6unLBVFY0K1uGp_FeSZRKtqpk,72
|
|
18
|
-
basic_memory/api/app.py,sha256=
|
|
19
|
+
basic_memory/api/app.py,sha256=rWc6wsCUEK9YMPav3_qiiwBASA2s1S_wF1C4ssBm6M8,2897
|
|
19
20
|
basic_memory/api/template_loader.py,sha256=exbTeXyJRgyLFmSjNeroxjT7X2DWFm2X5qUVa3drbYM,8607
|
|
20
21
|
basic_memory/api/routers/__init__.py,sha256=REO5CKQ96o5vtGWACcsIxIpWybIUSeKXc83RWbWc8BQ,398
|
|
21
22
|
basic_memory/api/routers/directory_router.py,sha256=rBQHvuwffUOk0iKvcEs2QlWclgvr8ur24f_pH3-sVRQ,2054
|
|
22
23
|
basic_memory/api/routers/importer_router.py,sha256=xFUCorkPWo8AF0ya0UrcLmXNf8CjPZdAqddQIH8PO-o,4513
|
|
23
24
|
basic_memory/api/routers/knowledge_router.py,sha256=4dD_tPpcJGWCuoRKEbQXCS3hoXNWe-VbcGC7xV-8VoE,9738
|
|
24
|
-
basic_memory/api/routers/management_router.py,sha256=
|
|
25
|
+
basic_memory/api/routers/management_router.py,sha256=zbzilNzsYUbFbE2uFXRM33cDn9IbI-73y8C1-b-19O4,2730
|
|
25
26
|
basic_memory/api/routers/memory_router.py,sha256=a9Cnx3XgwSkO-2ABFzI3wM3PoMGxuyfJFFp7NfFZapc,3003
|
|
26
|
-
basic_memory/api/routers/project_router.py,sha256=
|
|
27
|
+
basic_memory/api/routers/project_router.py,sha256=3qoto8Fm9pWqg6K0ukdd-VmggMENx7kK4JHvJrOhZ_I,8432
|
|
27
28
|
basic_memory/api/routers/prompt_router.py,sha256=4wxq6-NREgVJM8N9C0YsN1AAUDD8nkTCOzWyzSqTSFw,9948
|
|
28
|
-
basic_memory/api/routers/resource_router.py,sha256=
|
|
29
|
+
basic_memory/api/routers/resource_router.py,sha256=Uko0RLea8DTXl0hPyGjay_YNyYE5852VrBXhlWs8YGc,8097
|
|
29
30
|
basic_memory/api/routers/search_router.py,sha256=GD62jlCQTiL_VNsdibi-b1f6H40KCWo9SX2Cl7YH4QU,1226
|
|
30
31
|
basic_memory/api/routers/utils.py,sha256=nmD1faJOHcnWQjbCanojUwA9xhinf764U8SUqjNXpXw,5159
|
|
31
32
|
basic_memory/cli/__init__.py,sha256=arcKLAWRDhPD7x5t80MlviZeYzwHZ0GZigyy3NKVoGk,33
|
|
32
|
-
basic_memory/cli/app.py,sha256=
|
|
33
|
-
basic_memory/cli/main.py,sha256=
|
|
34
|
-
basic_memory/cli/commands/__init__.py,sha256=
|
|
35
|
-
basic_memory/cli/commands/
|
|
36
|
-
basic_memory/cli/commands/
|
|
37
|
-
basic_memory/cli/commands/
|
|
38
|
-
basic_memory/cli/commands/
|
|
39
|
-
basic_memory/cli/commands/
|
|
40
|
-
basic_memory/cli/commands/
|
|
41
|
-
basic_memory/cli/commands/
|
|
42
|
-
basic_memory/cli/commands/
|
|
43
|
-
basic_memory/cli/commands/
|
|
44
|
-
basic_memory/cli/commands/sync.py,sha256=gOU_onrMj9_IRiIe8FWU_FLEvfjcOt-qhrvvFJuU-ws,8010
|
|
33
|
+
basic_memory/cli/app.py,sha256=9xBnA4DXB8RFitiB96Jbs8XNsrz_WfE583QhJk_o8vg,2268
|
|
34
|
+
basic_memory/cli/main.py,sha256=ekBbEr_5jjGZaFJiOK4VhZ3wNiu6WjhXVi_syke1xw0,465
|
|
35
|
+
basic_memory/cli/commands/__init__.py,sha256=3oojcC-Y-4RPqff9vtwWziT_T4uvBVicL0pSHNilVkU,393
|
|
36
|
+
basic_memory/cli/commands/db.py,sha256=cEoQltgKudEuJH0Cn-YiPpNaDQzu5-YVwCD0anIIKOA,1480
|
|
37
|
+
basic_memory/cli/commands/import_chatgpt.py,sha256=iVfMo6yrY1EzViSlGL3BnZVkh-k9ht0bbCMJ6dWFCuU,2856
|
|
38
|
+
basic_memory/cli/commands/import_claude_conversations.py,sha256=e8l4OHMr8A9PtKgOO6T9-86Jca6FzCrJAsOzo-EQrlc,2946
|
|
39
|
+
basic_memory/cli/commands/import_claude_projects.py,sha256=YyFXcHWAHJmtR6DNwTtao8nKECoFyo8GripRElqMQ7w,2891
|
|
40
|
+
basic_memory/cli/commands/import_memory_json.py,sha256=fqGT9nILJnIpCtFseaFDox2IodVng48Suev5EQp8Azg,3013
|
|
41
|
+
basic_memory/cli/commands/mcp.py,sha256=Phvj0nsY70dDIEt8-KN_vCoakfwWNsRo4IzvzYWXusY,2593
|
|
42
|
+
basic_memory/cli/commands/project.py,sha256=gKnLp4X3AXKunAIQzGib8StFRkO_nWQ6eDEE81Ahxks,13940
|
|
43
|
+
basic_memory/cli/commands/status.py,sha256=ttDqI31xTQVPabJKIFi-unTiJKL6SoClJdYqKqLqLXA,5809
|
|
44
|
+
basic_memory/cli/commands/sync.py,sha256=cDs-c8TDOFDCf29MB6u_XUzl3EP3Br8MQyZLu0X4shM,8213
|
|
45
45
|
basic_memory/cli/commands/tool.py,sha256=my-kALn3khv1W2Avi736NrHsfkpbyP57mDi5LjHwqe0,9540
|
|
46
46
|
basic_memory/importers/__init__.py,sha256=BTcBW97P3thcsWa5w9tQsvOu8ynHDgw2-8tPgkCZoh8,795
|
|
47
47
|
basic_memory/importers/base.py,sha256=awwe_U-CfzSINKoM6iro7ru4QqLlsfXzdHztDvebnxM,2531
|
|
48
|
-
basic_memory/importers/chatgpt_importer.py,sha256=
|
|
48
|
+
basic_memory/importers/chatgpt_importer.py,sha256=3BJZUOVSX0cg9G6WdMTDQTscMoG6eWuf6E-c9Qhi0v4,7687
|
|
49
49
|
basic_memory/importers/claude_conversations_importer.py,sha256=p7yehy9Pgc5fef6d0ab9DwCm8CCiyyZkGEqX8U7rHbw,5725
|
|
50
50
|
basic_memory/importers/claude_projects_importer.py,sha256=pFJnX9m7GOv2TrS9f2nM1-mTtheTEBWjxKtwDWdJOGM,5389
|
|
51
|
-
basic_memory/importers/memory_json_importer.py,sha256=
|
|
52
|
-
basic_memory/importers/utils.py,sha256=
|
|
51
|
+
basic_memory/importers/memory_json_importer.py,sha256=vH0EUpnxftmtXOv_exQjJQ7CihETDkegrEjTq4K96vw,4631
|
|
52
|
+
basic_memory/importers/utils.py,sha256=SRcDrjedYH_PeD9Pu1DenlrDcar7L751ZV3qkzJXRQ4,1818
|
|
53
53
|
basic_memory/markdown/__init__.py,sha256=DdzioCWtDnKaq05BHYLgL_78FawEHLpLXnp-kPSVfIc,501
|
|
54
|
-
basic_memory/markdown/entity_parser.py,sha256=
|
|
55
|
-
basic_memory/markdown/markdown_processor.py,sha256=
|
|
56
|
-
basic_memory/markdown/plugins.py,sha256=
|
|
54
|
+
basic_memory/markdown/entity_parser.py,sha256=1zpvVAtWnCV0uXB4DtmMMNzlxGSE9iulWPUpfJyYWvw,4542
|
|
55
|
+
basic_memory/markdown/markdown_processor.py,sha256=imSVYyyfz0rQe9CyDcZhouB4m6V1haMdq6j3EWdo_wc,4984
|
|
56
|
+
basic_memory/markdown/plugins.py,sha256=9wjnfl82CVHuWPMdLH3Wwhml4MC4vh3zrEiH8qY17yY,7478
|
|
57
57
|
basic_memory/markdown/schemas.py,sha256=eyxYCr1hVyWmImcle0asE5It_DD6ARkqaBZYu1KK5n4,1896
|
|
58
|
-
basic_memory/markdown/utils.py,sha256=
|
|
58
|
+
basic_memory/markdown/utils.py,sha256=cm3h3C1eFz-zklXx5xaNRE-EBv8d-S5tixbTa5WqubQ,3416
|
|
59
59
|
basic_memory/mcp/__init__.py,sha256=dsDOhKqjYeIbCULbHIxfcItTbqudEuEg1Np86eq0GEQ,35
|
|
60
|
-
basic_memory/mcp/async_client.py,sha256=
|
|
61
|
-
basic_memory/mcp/
|
|
62
|
-
basic_memory/mcp/
|
|
63
|
-
basic_memory/mcp/
|
|
64
|
-
basic_memory/mcp/server.py,sha256=T8utX0fTA12rAC_TjtWgsfB1z-Q6pdTWJH4HISw73vg,3764
|
|
65
|
-
basic_memory/mcp/supabase_auth_provider.py,sha256=R_E4jzXSDOyPomoHiIqPVjx-VUhPqJSIUbg84mE2YaQ,16518
|
|
66
|
-
basic_memory/mcp/prompts/__init__.py,sha256=UvaIw5KA8PaXj3Wz1Dr-VjlkEq6T5D8AGtYFVwaHqnA,683
|
|
60
|
+
basic_memory/mcp/async_client.py,sha256=ZTH0OH8wowoyIZf_UdbqyuSyWljGSAlZMbPRFF0dowM,925
|
|
61
|
+
basic_memory/mcp/project_session.py,sha256=TaQD7JPeyQY-64sFJG41AXQNFizHegZ9POVLpD1pCBk,4203
|
|
62
|
+
basic_memory/mcp/server.py,sha256=CpEMrifWHqkj6Q--LUtcfKzzsp0-FW8Lf3eHKrlzx2E,1248
|
|
63
|
+
basic_memory/mcp/prompts/__init__.py,sha256=-Bl9Dgj2TD9PULjzggPqXuvPEjWCRy7S9Yg03h2-U7A,615
|
|
67
64
|
basic_memory/mcp/prompts/ai_assistant_guide.py,sha256=8TI5xObiRVcwv6w9by1xQHlX0whvyE7-LGsiqDMRTFg,821
|
|
68
65
|
basic_memory/mcp/prompts/continue_conversation.py,sha256=rsmlC2V7e7G6DAK0K825vFsPKgsRQ702HFzn6lkHaDM,1998
|
|
69
66
|
basic_memory/mcp/prompts/recent_activity.py,sha256=0v1c3b2SdDDxXVuF8eOjNooYy04uRYel0pdJ0rnggw4,3311
|
|
70
67
|
basic_memory/mcp/prompts/search.py,sha256=nb88MZy9tdW_MmCLUVItiukrLdb3xEHWLv0JVLUlc4o,1692
|
|
71
|
-
basic_memory/mcp/prompts/sync_status.py,sha256=0F6YowgqIbAFmGE3vFFJ-D-q1SrTqzGLKYWECgNWaxw,4495
|
|
72
68
|
basic_memory/mcp/prompts/utils.py,sha256=VacrbqwYtySpIlYIrKHo5s6jtoTMscYJqrFRH3zpC6Q,5431
|
|
73
69
|
basic_memory/mcp/resources/ai_assistant_guide.md,sha256=qnYWDkYlb-JmKuOoZ5llmRas_t4dWDXB_i8LE277Lgs,14777
|
|
74
70
|
basic_memory/mcp/resources/project_info.py,sha256=LcUkTx4iXBfU6Lp4TVch78OqLopbOy4ljyKnfr4VXso,1906
|
|
75
71
|
basic_memory/mcp/tools/__init__.py,sha256=hyt3HdUuw7djZForr24Qpw8EnOMQaDCm0_BTs-CaX-Y,1619
|
|
76
|
-
basic_memory/mcp/tools/build_context.py,sha256=
|
|
72
|
+
basic_memory/mcp/tools/build_context.py,sha256=41JGBL0_Ne0J4q8HXbDPmIZFRNyrwD5NT4EhWE_SlEQ,4975
|
|
77
73
|
basic_memory/mcp/tools/canvas.py,sha256=22F9G9gfPb-l8i1B5ra4Ja_h9zYY83rPY9mDA5C5gkY,3738
|
|
78
74
|
basic_memory/mcp/tools/delete_note.py,sha256=tSyRc_VgBmLyVeenClwX1Sk--LKcGahAMzTX2mK2XIs,7346
|
|
79
75
|
basic_memory/mcp/tools/edit_note.py,sha256=q4x-f7-j_l-wzm17-AVFT1_WGCo0Cq4lI3seYSe21aY,13570
|
|
80
76
|
basic_memory/mcp/tools/list_directory.py,sha256=-FxDsCru5YD02M4qkQDAurEJWyRaC7YI4YR6zg0atR8,5236
|
|
81
|
-
basic_memory/mcp/tools/move_note.py,sha256=
|
|
82
|
-
basic_memory/mcp/tools/project_management.py,sha256=
|
|
83
|
-
basic_memory/mcp/tools/read_content.py,sha256=
|
|
84
|
-
basic_memory/mcp/tools/read_note.py,sha256=
|
|
77
|
+
basic_memory/mcp/tools/move_note.py,sha256=XvJ9m6rrwsVlb8ScR5ik5OQKcLbNnR52HbyuAhutVYw,17541
|
|
78
|
+
basic_memory/mcp/tools/project_management.py,sha256=daaa7t9xrCLno3VLMBpda3adMw7cumIO-_R_ncBjMsc,13624
|
|
79
|
+
basic_memory/mcp/tools/read_content.py,sha256=lqE63axQf9h0OU36Lh5e1xr164nQhywdaMrPnKw_hyQ,9125
|
|
80
|
+
basic_memory/mcp/tools/read_note.py,sha256=_IbyJLCajJoI2ztFhyv_n9rpN-P64BPuw4QGXbR7D4w,8170
|
|
85
81
|
basic_memory/mcp/tools/recent_activity.py,sha256=XVjNJAJnmxvzx9_Ls1A-QOd2yTR7pJlSTTuRxSivmN4,4833
|
|
86
82
|
basic_memory/mcp/tools/search.py,sha256=hRmwBXRoxEUOtUOi9WG80NfLluHOG5XpSOArMJumt8o,15883
|
|
87
|
-
basic_memory/mcp/tools/sync_status.py,sha256=
|
|
83
|
+
basic_memory/mcp/tools/sync_status.py,sha256=c91zsYbq7IuC5rF-OaZ8JwgtJOBlPcTks1cSvYO4CiI,10517
|
|
88
84
|
basic_memory/mcp/tools/utils.py,sha256=qVAEkR4naCLrqIo_7xXFubqGGxypouz-DB4_svTvARY,20892
|
|
89
85
|
basic_memory/mcp/tools/view_note.py,sha256=ddNXxyETsdA5SYflIaQVj_Cbd7I7CLVs3atRRDMbGmg,2499
|
|
90
|
-
basic_memory/mcp/tools/write_note.py,sha256=
|
|
86
|
+
basic_memory/mcp/tools/write_note.py,sha256=dxOW-0nTl_619-NjIdTSM8IsTRtTQUkSyEmVfG6W_IM,6612
|
|
91
87
|
basic_memory/models/__init__.py,sha256=j0C4dtFi-FOEaQKR8dQWEG-dJtdQ15NBTiJg4nbIXNU,333
|
|
92
88
|
basic_memory/models/base.py,sha256=4hAXJ8CE1RnjKhb23lPd-QM7G_FXIdTowMJ9bRixspU,225
|
|
93
|
-
basic_memory/models/knowledge.py,sha256=
|
|
94
|
-
basic_memory/models/project.py,sha256=
|
|
89
|
+
basic_memory/models/knowledge.py,sha256=t493m_jYw_80xtqYRauyJ5pDaa1RlvpMduHw7JCNWuA,7702
|
|
90
|
+
basic_memory/models/project.py,sha256=b1ujg-mM-XqLtKw29RVUtLWMgZb9qOllfHmA5ZS1WuQ,2838
|
|
95
91
|
basic_memory/models/search.py,sha256=PhQ8w4taApSvjh1DpPhB4cH9GTt2E2po-DFZzhnoZkY,1300
|
|
96
92
|
basic_memory/repository/__init__.py,sha256=MWK-o8QikqzOpe5SyPbKQ2ioB5BWA0Upz65tgg-E0DU,327
|
|
97
|
-
basic_memory/repository/entity_repository.py,sha256=
|
|
93
|
+
basic_memory/repository/entity_repository.py,sha256=QaWYJf9kx55illiLXFCAOA7-fXJX5IVf4m8FspdPzY0,10429
|
|
98
94
|
basic_memory/repository/observation_repository.py,sha256=qhMvHLSjaoT3Fa_cQOKsT5jYPj66GXSytEBMwLAgygQ,2943
|
|
99
95
|
basic_memory/repository/project_info_repository.py,sha256=8XLVAYKkBWQ6GbKj1iqA9OK0FGPHdTlOs7ZtfeUf9t8,338
|
|
100
|
-
basic_memory/repository/project_repository.py,sha256=
|
|
96
|
+
basic_memory/repository/project_repository.py,sha256=Fvo_pCylz8okFiv3FD3nv6PNQknLuC7f3S11fJNQkic,3795
|
|
101
97
|
basic_memory/repository/relation_repository.py,sha256=z7Oo5Zz_J-Bj6RvQDpSWR73ZLk2fxG7e7jrMbeFeJvQ,3179
|
|
102
98
|
basic_memory/repository/repository.py,sha256=MJb-cb8QZQbL-Grq_iqv4Kq75aX2yQohLIqh5T4fFxw,15224
|
|
103
|
-
basic_memory/repository/search_repository.py,sha256=
|
|
99
|
+
basic_memory/repository/search_repository.py,sha256=VQfxYZqYupT9-HiWz3_UvhpIRNzpjb4aDrbIdQj8x6E,22171
|
|
104
100
|
basic_memory/schemas/__init__.py,sha256=mEgIFcdTeb-v4y0gkOh_pA5zyqGbZk-9XbXqlSi6WMs,1674
|
|
105
|
-
basic_memory/schemas/base.py,sha256=
|
|
101
|
+
basic_memory/schemas/base.py,sha256=XKrEJyGP8QpFuDOeoAicF8iOh4V-jF1XCdLIs1Byp6k,8270
|
|
106
102
|
basic_memory/schemas/delete.py,sha256=UAR2JK99WMj3gP-yoGWlHD3eZEkvlTSRf8QoYIE-Wfw,1180
|
|
107
103
|
basic_memory/schemas/directory.py,sha256=F9_LrJqRqb_kO08GDKJzXLb2nhbYG2PdVUo5eDD_Kf4,881
|
|
108
|
-
basic_memory/schemas/importer.py,sha256=
|
|
109
|
-
basic_memory/schemas/memory.py,sha256=
|
|
104
|
+
basic_memory/schemas/importer.py,sha256=rDPfQjyjKyjOe26pwp1UH4eDqGwMKfeNs1Fjv5PxOc0,693
|
|
105
|
+
basic_memory/schemas/memory.py,sha256=K9RyK4MS0nzuV9JhRINLKSsO5BpmACI8ldNzBPs4f_s,6388
|
|
110
106
|
basic_memory/schemas/project_info.py,sha256=fcNjUpe25_5uMmKy142ib3p5qEakzs1WJPLkgol5zyw,7047
|
|
111
107
|
basic_memory/schemas/prompt.py,sha256=SpIVfZprQT8E5uP40j3CpBc2nHKflwOo3iZD7BFPIHE,3648
|
|
112
108
|
basic_memory/schemas/request.py,sha256=Mv5EvrLZlFIiPr8dOjo_4QXvkseYhQI7cd_X2zDsxQM,3760
|
|
113
109
|
basic_memory/schemas/response.py,sha256=XupGYKKr5I2D7Qg9HCSD_c-0A-C1BPA8FNIvHK6Gars,6450
|
|
114
110
|
basic_memory/schemas/search.py,sha256=ywMsDGAQK2sO2TT5lc-da_k67OKW1x1TenXormHHWv4,3657
|
|
115
111
|
basic_memory/services/__init__.py,sha256=XGt8WX3fX_0K9L37Msy8HF8nlMZYIG3uQ6mUX6_iJtg,259
|
|
116
|
-
basic_memory/services/context_service.py,sha256=
|
|
117
|
-
basic_memory/services/directory_service.py,sha256=
|
|
118
|
-
basic_memory/services/entity_service.py,sha256=
|
|
112
|
+
basic_memory/services/context_service.py,sha256=DaxfDEC2mOFyfqmOMoKHAwa-GS6cQub8tfwJDaiHJdI,15103
|
|
113
|
+
basic_memory/services/directory_service.py,sha256=_FGX9yunAfmeKCfGG4wKC5oN9eBj2R12v0fwH3GcNxo,6273
|
|
114
|
+
basic_memory/services/entity_service.py,sha256=eYwJYwAG7xGzCeVU5eeLmKJP-oKDqvjPUN0d3L1VbJs,32253
|
|
119
115
|
basic_memory/services/exceptions.py,sha256=oVjQr50XQqnFq1-MNKBilI2ShtHDxypavyDk1UeyHhw,390
|
|
120
116
|
basic_memory/services/file_service.py,sha256=jCrmnEkTQ4t9HF7L_M6BL7tdDqjjzty9hpTo9AzwhvM,10059
|
|
121
|
-
basic_memory/services/initialization.py,sha256=
|
|
117
|
+
basic_memory/services/initialization.py,sha256=Td5Yt5nPSskGMeWZSbVbM1WpO9-Z3w2cJAjAzqZ-EMQ,6715
|
|
122
118
|
basic_memory/services/link_resolver.py,sha256=1-_VFsvqdT5rVBHe8Jrq63U59XQ0hxGezxY8c24Tiow,4594
|
|
123
|
-
basic_memory/services/
|
|
124
|
-
basic_memory/services/project_service.py,sha256=uLIrQB6T1DY3BXrEsLdB2ZlcKnPgjubyn-g6V9vMBzA,27928
|
|
119
|
+
basic_memory/services/project_service.py,sha256=M9tukO5Ph847a7ZWrimcB8b-yNcQz3LKRvhvc7aU8GI,30175
|
|
125
120
|
basic_memory/services/search_service.py,sha256=c5Ky0ufz7YPFgHhVzNRQ4OecF_JUrt7nALzpMjobW4M,12782
|
|
126
121
|
basic_memory/services/service.py,sha256=V-d_8gOV07zGIQDpL-Ksqs3ZN9l3qf3HZOK1f_YNTag,336
|
|
127
122
|
basic_memory/services/sync_status_service.py,sha256=CgJdaJ6OFvFjKHIQSVIQX8kEU389Mrz_WS6x8dx2-7c,7504
|
|
128
123
|
basic_memory/sync/__init__.py,sha256=CVHguYH457h2u2xoM8KvOilJC71XJlZ-qUh8lHcjYj4,156
|
|
129
|
-
basic_memory/sync/background_sync.py,sha256=
|
|
130
|
-
basic_memory/sync/sync_service.py,sha256
|
|
131
|
-
basic_memory/sync/watch_service.py,sha256=
|
|
124
|
+
basic_memory/sync/background_sync.py,sha256=VJr2SukRKLdsbfB-9Re4LehcpK15a-RLXAFB-sAdRRM,726
|
|
125
|
+
basic_memory/sync/sync_service.py,sha256=-cN3fV1VqO_LtnfcJlbsNAMSmM_tcUNVdUAMlh11XVo,26581
|
|
126
|
+
basic_memory/sync/watch_service.py,sha256=1KsgZn7B_izo5ECe_mhwJm_ewi0hYjjVQckhU8EqhX0,16877
|
|
132
127
|
basic_memory/templates/prompts/continue_conversation.hbs,sha256=trrDHSXA5S0JCbInMoUJL04xvCGRB_ku1RHNQHtl6ZI,3076
|
|
133
128
|
basic_memory/templates/prompts/search.hbs,sha256=H1cCIsHKp4VC1GrH2KeUB8pGe5vXFPqb2VPotypmeCA,3098
|
|
134
|
-
basic_memory-0.14.
|
|
135
|
-
basic_memory-0.14.
|
|
136
|
-
basic_memory-0.14.
|
|
137
|
-
basic_memory-0.14.
|
|
138
|
-
basic_memory-0.14.
|
|
129
|
+
basic_memory-0.14.4.dist-info/METADATA,sha256=zoI_IydCEe0CWVkDlOQ8X_DyNhqexppXE6RIWE3lokY,14329
|
|
130
|
+
basic_memory-0.14.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
131
|
+
basic_memory-0.14.4.dist-info/entry_points.txt,sha256=wvE2mRF6-Pg4weIYcfQ-86NOLZD4WJg7F7TIsRVFLb8,90
|
|
132
|
+
basic_memory-0.14.4.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
133
|
+
basic_memory-0.14.4.dist-info/RECORD,,
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
"""OAuth management commands."""
|
|
2
|
-
|
|
3
|
-
import typer
|
|
4
|
-
from typing import Optional
|
|
5
|
-
from pydantic import AnyHttpUrl
|
|
6
|
-
|
|
7
|
-
from basic_memory.cli.app import app
|
|
8
|
-
from basic_memory.mcp.auth_provider import BasicMemoryOAuthProvider
|
|
9
|
-
from mcp.shared.auth import OAuthClientInformationFull
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
auth_app = typer.Typer(help="OAuth client management commands")
|
|
13
|
-
app.add_typer(auth_app, name="auth")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@auth_app.command()
|
|
17
|
-
def register_client(
|
|
18
|
-
client_id: Optional[str] = typer.Option(
|
|
19
|
-
None, help="Client ID (auto-generated if not provided)"
|
|
20
|
-
),
|
|
21
|
-
client_secret: Optional[str] = typer.Option(
|
|
22
|
-
None, help="Client secret (auto-generated if not provided)"
|
|
23
|
-
),
|
|
24
|
-
issuer_url: str = typer.Option("http://localhost:8000", help="OAuth issuer URL"),
|
|
25
|
-
):
|
|
26
|
-
"""Register a new OAuth client for Basic Memory MCP server."""
|
|
27
|
-
|
|
28
|
-
# Create provider instance
|
|
29
|
-
provider = BasicMemoryOAuthProvider(issuer_url=issuer_url)
|
|
30
|
-
|
|
31
|
-
# Create client info with required redirect_uris
|
|
32
|
-
client_info = OAuthClientInformationFull(
|
|
33
|
-
client_id=client_id or "", # Provider will generate if empty
|
|
34
|
-
client_secret=client_secret or "", # Provider will generate if empty
|
|
35
|
-
redirect_uris=[AnyHttpUrl("http://localhost:8000/callback")], # Default redirect URI
|
|
36
|
-
client_name="Basic Memory OAuth Client",
|
|
37
|
-
grant_types=["authorization_code", "refresh_token"],
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
# Register the client
|
|
41
|
-
import asyncio
|
|
42
|
-
|
|
43
|
-
asyncio.run(provider.register_client(client_info))
|
|
44
|
-
|
|
45
|
-
typer.echo("Client registered successfully!")
|
|
46
|
-
typer.echo(f"Client ID: {client_info.client_id}")
|
|
47
|
-
typer.echo(f"Client Secret: {client_info.client_secret}")
|
|
48
|
-
typer.echo("\nSave these credentials securely - the client secret cannot be retrieved later.")
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@auth_app.command()
|
|
52
|
-
def test_auth(
|
|
53
|
-
issuer_url: str = typer.Option("http://localhost:8000", help="OAuth issuer URL"),
|
|
54
|
-
):
|
|
55
|
-
"""Test OAuth authentication flow.
|
|
56
|
-
|
|
57
|
-
IMPORTANT: Use the same FASTMCP_AUTH_SECRET_KEY environment variable
|
|
58
|
-
as your MCP server for tokens to validate correctly.
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
import asyncio
|
|
62
|
-
import secrets
|
|
63
|
-
from mcp.server.auth.provider import AuthorizationParams
|
|
64
|
-
from pydantic import AnyHttpUrl
|
|
65
|
-
|
|
66
|
-
async def test_flow():
|
|
67
|
-
# Create provider with same secret key as server
|
|
68
|
-
provider = BasicMemoryOAuthProvider(issuer_url=issuer_url)
|
|
69
|
-
|
|
70
|
-
# Register a test client
|
|
71
|
-
client_info = OAuthClientInformationFull(
|
|
72
|
-
client_id=secrets.token_urlsafe(16),
|
|
73
|
-
client_secret=secrets.token_urlsafe(32),
|
|
74
|
-
redirect_uris=[AnyHttpUrl("http://localhost:8000/callback")],
|
|
75
|
-
client_name="Test OAuth Client",
|
|
76
|
-
grant_types=["authorization_code", "refresh_token"],
|
|
77
|
-
)
|
|
78
|
-
await provider.register_client(client_info)
|
|
79
|
-
typer.echo(f"Registered test client: {client_info.client_id}")
|
|
80
|
-
|
|
81
|
-
# Get the client
|
|
82
|
-
client = await provider.get_client(client_info.client_id)
|
|
83
|
-
if not client:
|
|
84
|
-
typer.echo("Error: Client not found after registration", err=True)
|
|
85
|
-
return
|
|
86
|
-
|
|
87
|
-
# Create authorization request
|
|
88
|
-
auth_params = AuthorizationParams(
|
|
89
|
-
state="test-state",
|
|
90
|
-
scopes=["read", "write"],
|
|
91
|
-
code_challenge="test-challenge",
|
|
92
|
-
redirect_uri=AnyHttpUrl("http://localhost:8000/callback"),
|
|
93
|
-
redirect_uri_provided_explicitly=True,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
# Get authorization URL
|
|
97
|
-
auth_url = await provider.authorize(client, auth_params)
|
|
98
|
-
typer.echo(f"Authorization URL: {auth_url}")
|
|
99
|
-
|
|
100
|
-
# Extract auth code from URL
|
|
101
|
-
from urllib.parse import urlparse, parse_qs
|
|
102
|
-
|
|
103
|
-
parsed = urlparse(auth_url)
|
|
104
|
-
params = parse_qs(parsed.query)
|
|
105
|
-
auth_code = params.get("code", [None])[0]
|
|
106
|
-
|
|
107
|
-
if not auth_code:
|
|
108
|
-
typer.echo("Error: No authorization code in URL", err=True)
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
# Load the authorization code
|
|
112
|
-
code_obj = await provider.load_authorization_code(client, auth_code)
|
|
113
|
-
if not code_obj:
|
|
114
|
-
typer.echo("Error: Invalid authorization code", err=True)
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
# Exchange for tokens
|
|
118
|
-
token = await provider.exchange_authorization_code(client, code_obj)
|
|
119
|
-
typer.echo(f"Access token: {token.access_token}")
|
|
120
|
-
typer.echo(f"Refresh token: {token.refresh_token}")
|
|
121
|
-
typer.echo(f"Expires in: {token.expires_in} seconds")
|
|
122
|
-
|
|
123
|
-
# Validate access token
|
|
124
|
-
access_token_obj = await provider.load_access_token(token.access_token)
|
|
125
|
-
if access_token_obj:
|
|
126
|
-
typer.echo("Access token validated successfully!")
|
|
127
|
-
typer.echo(f"Client ID: {access_token_obj.client_id}")
|
|
128
|
-
typer.echo(f"Scopes: {access_token_obj.scopes}")
|
|
129
|
-
else:
|
|
130
|
-
typer.echo("Error: Invalid access token", err=True)
|
|
131
|
-
|
|
132
|
-
asyncio.run(test_flow())
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if __name__ == "__main__":
|
|
136
|
-
auth_app()
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
"""OAuth authentication provider for Basic Memory MCP server."""
|
|
2
|
-
|
|
3
|
-
import secrets
|
|
4
|
-
from datetime import datetime, timedelta, timezone
|
|
5
|
-
from typing import Dict, Optional
|
|
6
|
-
|
|
7
|
-
import jwt
|
|
8
|
-
from mcp.server.auth.provider import (
|
|
9
|
-
OAuthAuthorizationServerProvider,
|
|
10
|
-
AuthorizationParams,
|
|
11
|
-
AuthorizationCode,
|
|
12
|
-
RefreshToken,
|
|
13
|
-
AccessToken,
|
|
14
|
-
)
|
|
15
|
-
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
|
|
16
|
-
from loguru import logger
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class BasicMemoryAuthorizationCode(AuthorizationCode):
|
|
20
|
-
"""Extended authorization code with additional metadata."""
|
|
21
|
-
|
|
22
|
-
issuer_state: Optional[str] = None
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class BasicMemoryRefreshToken(RefreshToken):
|
|
26
|
-
"""Extended refresh token with additional metadata."""
|
|
27
|
-
|
|
28
|
-
pass
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class BasicMemoryAccessToken(AccessToken):
|
|
32
|
-
"""Extended access token with additional metadata."""
|
|
33
|
-
|
|
34
|
-
pass
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class BasicMemoryOAuthProvider(
|
|
38
|
-
OAuthAuthorizationServerProvider[
|
|
39
|
-
BasicMemoryAuthorizationCode, BasicMemoryRefreshToken, BasicMemoryAccessToken
|
|
40
|
-
]
|
|
41
|
-
):
|
|
42
|
-
"""OAuth provider for Basic Memory MCP server.
|
|
43
|
-
|
|
44
|
-
This is a simple in-memory implementation that can be extended
|
|
45
|
-
to integrate with external OAuth providers or use persistent storage.
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
def __init__(self, issuer_url: str = "http://localhost:8000", secret_key: Optional[str] = None):
|
|
49
|
-
self.issuer_url = issuer_url
|
|
50
|
-
# Use environment variable for secret key if available, otherwise generate
|
|
51
|
-
import os
|
|
52
|
-
|
|
53
|
-
self.secret_key = (
|
|
54
|
-
secret_key or os.getenv("FASTMCP_AUTH_SECRET_KEY") or secrets.token_urlsafe(32)
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
# In-memory storage - in production, use a proper database
|
|
58
|
-
self.clients: Dict[str, OAuthClientInformationFull] = {}
|
|
59
|
-
self.authorization_codes: Dict[str, BasicMemoryAuthorizationCode] = {}
|
|
60
|
-
self.refresh_tokens: Dict[str, BasicMemoryRefreshToken] = {}
|
|
61
|
-
self.access_tokens: Dict[str, BasicMemoryAccessToken] = {}
|
|
62
|
-
|
|
63
|
-
async def get_client(self, client_id: str) -> Optional[OAuthClientInformationFull]:
|
|
64
|
-
"""Get a client by ID."""
|
|
65
|
-
return self.clients.get(client_id)
|
|
66
|
-
|
|
67
|
-
async def register_client(self, client_info: OAuthClientInformationFull) -> None:
|
|
68
|
-
"""Register a new OAuth client."""
|
|
69
|
-
# Generate client ID if not provided
|
|
70
|
-
if not client_info.client_id:
|
|
71
|
-
client_info.client_id = secrets.token_urlsafe(16)
|
|
72
|
-
|
|
73
|
-
# Generate client secret if not provided
|
|
74
|
-
if not client_info.client_secret:
|
|
75
|
-
client_info.client_secret = secrets.token_urlsafe(32)
|
|
76
|
-
|
|
77
|
-
self.clients[client_info.client_id] = client_info
|
|
78
|
-
logger.info(f"Registered OAuth client: {client_info.client_id}")
|
|
79
|
-
|
|
80
|
-
async def authorize(
|
|
81
|
-
self, client: OAuthClientInformationFull, params: AuthorizationParams
|
|
82
|
-
) -> str:
|
|
83
|
-
"""Create an authorization URL for the OAuth flow.
|
|
84
|
-
|
|
85
|
-
For basic-memory, we'll implement a simple authorization flow.
|
|
86
|
-
In production, this might redirect to an external provider.
|
|
87
|
-
"""
|
|
88
|
-
# Generate authorization code
|
|
89
|
-
auth_code = secrets.token_urlsafe(32)
|
|
90
|
-
|
|
91
|
-
# Store authorization code with metadata
|
|
92
|
-
self.authorization_codes[auth_code] = BasicMemoryAuthorizationCode(
|
|
93
|
-
code=auth_code,
|
|
94
|
-
scopes=params.scopes or [],
|
|
95
|
-
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=10)).timestamp(),
|
|
96
|
-
client_id=client.client_id,
|
|
97
|
-
code_challenge=params.code_challenge,
|
|
98
|
-
redirect_uri=params.redirect_uri,
|
|
99
|
-
redirect_uri_provided_explicitly=params.redirect_uri_provided_explicitly,
|
|
100
|
-
issuer_state=params.state,
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
# In a real implementation, we'd redirect to an authorization page
|
|
104
|
-
# For now, we'll just return the redirect URL with the code
|
|
105
|
-
redirect_uri = str(params.redirect_uri)
|
|
106
|
-
separator = "&" if "?" in redirect_uri else "?"
|
|
107
|
-
|
|
108
|
-
auth_url = f"{redirect_uri}{separator}code={auth_code}"
|
|
109
|
-
if params.state:
|
|
110
|
-
auth_url += f"&state={params.state}"
|
|
111
|
-
|
|
112
|
-
return auth_url
|
|
113
|
-
|
|
114
|
-
async def load_authorization_code(
|
|
115
|
-
self, client: OAuthClientInformationFull, authorization_code: str
|
|
116
|
-
) -> Optional[BasicMemoryAuthorizationCode]:
|
|
117
|
-
"""Load an authorization code."""
|
|
118
|
-
code = self.authorization_codes.get(authorization_code)
|
|
119
|
-
|
|
120
|
-
if code and code.client_id == client.client_id:
|
|
121
|
-
# Check if expired
|
|
122
|
-
if datetime.now(timezone.utc).timestamp() > code.expires_at:
|
|
123
|
-
del self.authorization_codes[authorization_code]
|
|
124
|
-
return None
|
|
125
|
-
return code
|
|
126
|
-
|
|
127
|
-
return None
|
|
128
|
-
|
|
129
|
-
async def exchange_authorization_code(
|
|
130
|
-
self, client: OAuthClientInformationFull, authorization_code: BasicMemoryAuthorizationCode
|
|
131
|
-
) -> OAuthToken:
|
|
132
|
-
"""Exchange an authorization code for tokens."""
|
|
133
|
-
# Generate tokens
|
|
134
|
-
access_token = self._generate_access_token(client.client_id, authorization_code.scopes)
|
|
135
|
-
refresh_token = secrets.token_urlsafe(32)
|
|
136
|
-
|
|
137
|
-
# Store tokens
|
|
138
|
-
expires_at = (datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()
|
|
139
|
-
|
|
140
|
-
self.access_tokens[access_token] = BasicMemoryAccessToken(
|
|
141
|
-
token=access_token,
|
|
142
|
-
client_id=client.client_id,
|
|
143
|
-
scopes=authorization_code.scopes,
|
|
144
|
-
expires_at=int(expires_at),
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
self.refresh_tokens[refresh_token] = BasicMemoryRefreshToken(
|
|
148
|
-
token=refresh_token,
|
|
149
|
-
client_id=client.client_id,
|
|
150
|
-
scopes=authorization_code.scopes,
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
# Remove used authorization code
|
|
154
|
-
del self.authorization_codes[authorization_code.code]
|
|
155
|
-
|
|
156
|
-
return OAuthToken(
|
|
157
|
-
access_token=access_token,
|
|
158
|
-
token_type="bearer",
|
|
159
|
-
expires_in=3600, # 1 hour
|
|
160
|
-
refresh_token=refresh_token,
|
|
161
|
-
scope=" ".join(authorization_code.scopes) if authorization_code.scopes else None,
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
async def load_refresh_token(
|
|
165
|
-
self, client: OAuthClientInformationFull, refresh_token: str
|
|
166
|
-
) -> Optional[BasicMemoryRefreshToken]:
|
|
167
|
-
"""Load a refresh token."""
|
|
168
|
-
token = self.refresh_tokens.get(refresh_token)
|
|
169
|
-
|
|
170
|
-
if token and token.client_id == client.client_id:
|
|
171
|
-
return token
|
|
172
|
-
|
|
173
|
-
return None
|
|
174
|
-
|
|
175
|
-
async def exchange_refresh_token(
|
|
176
|
-
self,
|
|
177
|
-
client: OAuthClientInformationFull,
|
|
178
|
-
refresh_token: BasicMemoryRefreshToken,
|
|
179
|
-
scopes: list[str],
|
|
180
|
-
) -> OAuthToken:
|
|
181
|
-
"""Exchange a refresh token for new tokens."""
|
|
182
|
-
# Use requested scopes or original scopes
|
|
183
|
-
token_scopes = scopes if scopes else refresh_token.scopes
|
|
184
|
-
|
|
185
|
-
# Generate new tokens
|
|
186
|
-
new_access_token = self._generate_access_token(client.client_id, token_scopes)
|
|
187
|
-
new_refresh_token = secrets.token_urlsafe(32)
|
|
188
|
-
|
|
189
|
-
# Store new tokens
|
|
190
|
-
expires_at = (datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()
|
|
191
|
-
|
|
192
|
-
self.access_tokens[new_access_token] = BasicMemoryAccessToken(
|
|
193
|
-
token=new_access_token,
|
|
194
|
-
client_id=client.client_id,
|
|
195
|
-
scopes=token_scopes,
|
|
196
|
-
expires_at=int(expires_at),
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
self.refresh_tokens[new_refresh_token] = BasicMemoryRefreshToken(
|
|
200
|
-
token=new_refresh_token,
|
|
201
|
-
client_id=client.client_id,
|
|
202
|
-
scopes=token_scopes,
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
# Remove old tokens
|
|
206
|
-
del self.refresh_tokens[refresh_token.token]
|
|
207
|
-
|
|
208
|
-
return OAuthToken(
|
|
209
|
-
access_token=new_access_token,
|
|
210
|
-
token_type="bearer",
|
|
211
|
-
expires_in=3600, # 1 hour
|
|
212
|
-
refresh_token=new_refresh_token,
|
|
213
|
-
scope=" ".join(token_scopes) if token_scopes else None,
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
async def load_access_token(self, token: str) -> Optional[BasicMemoryAccessToken]:
|
|
217
|
-
"""Load and validate an access token."""
|
|
218
|
-
logger.debug("Loading access token, checking in-memory store first")
|
|
219
|
-
access_token = self.access_tokens.get(token)
|
|
220
|
-
|
|
221
|
-
if access_token:
|
|
222
|
-
# Check if expired
|
|
223
|
-
if access_token.expires_at and datetime.now(timezone.utc).timestamp() > access_token.expires_at:
|
|
224
|
-
logger.debug("Token found in memory but expired, removing")
|
|
225
|
-
del self.access_tokens[token]
|
|
226
|
-
return None
|
|
227
|
-
logger.debug("Token found in memory and valid")
|
|
228
|
-
return access_token
|
|
229
|
-
|
|
230
|
-
# Try to decode as JWT
|
|
231
|
-
logger.debug("Token not in memory, attempting JWT decode with secret key")
|
|
232
|
-
try:
|
|
233
|
-
# Decode with audience verification - PyJWT expects the audience to match
|
|
234
|
-
payload = jwt.decode(
|
|
235
|
-
token,
|
|
236
|
-
self.secret_key,
|
|
237
|
-
algorithms=["HS256"],
|
|
238
|
-
audience="basic-memory", # Expecting this audience
|
|
239
|
-
issuer=self.issuer_url, # And this issuer
|
|
240
|
-
)
|
|
241
|
-
logger.debug(f"JWT decoded successfully: {payload}")
|
|
242
|
-
return BasicMemoryAccessToken(
|
|
243
|
-
token=token,
|
|
244
|
-
client_id=payload.get("sub", ""),
|
|
245
|
-
scopes=payload.get("scopes", []),
|
|
246
|
-
expires_at=payload.get("exp"),
|
|
247
|
-
)
|
|
248
|
-
except jwt.InvalidTokenError as e:
|
|
249
|
-
logger.error(f"JWT decode failed: {e}")
|
|
250
|
-
return None
|
|
251
|
-
|
|
252
|
-
async def revoke_token(self, token: BasicMemoryAccessToken | BasicMemoryRefreshToken) -> None:
|
|
253
|
-
"""Revoke an access or refresh token."""
|
|
254
|
-
if isinstance(token, BasicMemoryAccessToken):
|
|
255
|
-
self.access_tokens.pop(token.token, None)
|
|
256
|
-
else:
|
|
257
|
-
self.refresh_tokens.pop(token.token, None)
|
|
258
|
-
|
|
259
|
-
def _generate_access_token(self, client_id: str, scopes: list[str]) -> str:
|
|
260
|
-
"""Generate a JWT access token."""
|
|
261
|
-
payload = {
|
|
262
|
-
"iss": self.issuer_url,
|
|
263
|
-
"sub": client_id,
|
|
264
|
-
"aud": "basic-memory",
|
|
265
|
-
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
|
|
266
|
-
"iat": datetime.now(timezone.utc),
|
|
267
|
-
"scopes": scopes,
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return jwt.encode(payload, self.secret_key, algorithm="HS256")
|