keep-skill 0.3.0__py3-none-any.whl → 0.4.2__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.
- keep/__init__.py +1 -1
- keep/api.py +82 -1
- keep/cli.py +286 -125
- keep/config.py +1 -7
- keep/store.py +20 -9
- {keep_skill-0.3.0.dist-info → keep_skill-0.4.2.dist-info}/METADATA +13 -8
- {keep_skill-0.3.0.dist-info → keep_skill-0.4.2.dist-info}/RECORD +10 -10
- {keep_skill-0.3.0.dist-info → keep_skill-0.4.2.dist-info}/WHEEL +0 -0
- {keep_skill-0.3.0.dist-info → keep_skill-0.4.2.dist-info}/entry_points.txt +0 -0
- {keep_skill-0.3.0.dist-info → keep_skill-0.4.2.dist-info}/licenses/LICENSE +0 -0
keep/__init__.py
CHANGED
keep/api.py
CHANGED
|
@@ -902,7 +902,88 @@ class Keeper:
|
|
|
902
902
|
items = _filter_by_date(items, since)
|
|
903
903
|
|
|
904
904
|
return items[:limit]
|
|
905
|
-
|
|
905
|
+
|
|
906
|
+
def get_similar_for_display(
|
|
907
|
+
self,
|
|
908
|
+
id: str,
|
|
909
|
+
*,
|
|
910
|
+
limit: int = 3,
|
|
911
|
+
collection: Optional[str] = None
|
|
912
|
+
) -> list[Item]:
|
|
913
|
+
"""
|
|
914
|
+
Find similar items for frontmatter display using stored embedding.
|
|
915
|
+
|
|
916
|
+
Optimized for display: uses stored embedding (no re-embedding),
|
|
917
|
+
filters to distinct base documents, excludes source document versions.
|
|
918
|
+
|
|
919
|
+
Args:
|
|
920
|
+
id: ID of item to find similar items for
|
|
921
|
+
limit: Maximum results to return
|
|
922
|
+
collection: Target collection
|
|
923
|
+
|
|
924
|
+
Returns:
|
|
925
|
+
List of similar items, one per unique base document
|
|
926
|
+
"""
|
|
927
|
+
coll = self._resolve_collection(collection)
|
|
928
|
+
|
|
929
|
+
# Get the stored embedding (no re-embedding)
|
|
930
|
+
embedding = self._store.get_embedding(coll, id)
|
|
931
|
+
if embedding is None:
|
|
932
|
+
return []
|
|
933
|
+
|
|
934
|
+
# Fetch more than needed to account for version filtering
|
|
935
|
+
fetch_limit = limit * 3
|
|
936
|
+
results = self._store.query_embedding(coll, embedding, limit=fetch_limit)
|
|
937
|
+
|
|
938
|
+
# Convert to Items
|
|
939
|
+
items = [r.to_item() for r in results]
|
|
940
|
+
|
|
941
|
+
# Extract base ID of source document
|
|
942
|
+
source_base_id = id.split("@v")[0] if "@v" in id else id
|
|
943
|
+
|
|
944
|
+
# Filter to distinct base IDs, excluding source document
|
|
945
|
+
seen_base_ids: set[str] = set()
|
|
946
|
+
filtered: list[Item] = []
|
|
947
|
+
for item in items:
|
|
948
|
+
# Get base ID from tags or parse from ID
|
|
949
|
+
base_id = item.tags.get("_base_id", item.id.split("@v")[0] if "@v" in item.id else item.id)
|
|
950
|
+
|
|
951
|
+
# Skip versions of source document
|
|
952
|
+
if base_id == source_base_id:
|
|
953
|
+
continue
|
|
954
|
+
|
|
955
|
+
# Keep only first version of each document
|
|
956
|
+
if base_id not in seen_base_ids:
|
|
957
|
+
seen_base_ids.add(base_id)
|
|
958
|
+
filtered.append(item)
|
|
959
|
+
|
|
960
|
+
if len(filtered) >= limit:
|
|
961
|
+
break
|
|
962
|
+
|
|
963
|
+
return filtered
|
|
964
|
+
|
|
965
|
+
def get_version_offset(self, item: Item, collection: Optional[str] = None) -> int:
|
|
966
|
+
"""
|
|
967
|
+
Get version offset (0=current, 1=previous, ...) for an item.
|
|
968
|
+
|
|
969
|
+
Converts the internal version number (1=oldest, 2=next...) to the
|
|
970
|
+
user-visible offset format (0=current, 1=previous, 2=two-ago...).
|
|
971
|
+
|
|
972
|
+
Args:
|
|
973
|
+
item: Item to get version offset for
|
|
974
|
+
collection: Target collection
|
|
975
|
+
|
|
976
|
+
Returns:
|
|
977
|
+
Version offset (0 for current version)
|
|
978
|
+
"""
|
|
979
|
+
version_tag = item.tags.get("_version")
|
|
980
|
+
if not version_tag:
|
|
981
|
+
return 0 # Current version
|
|
982
|
+
base_id = item.tags.get("_base_id", item.id)
|
|
983
|
+
coll = self._resolve_collection(collection)
|
|
984
|
+
version_count = self._document_store.version_count(coll, base_id)
|
|
985
|
+
return version_count - int(version_tag) + 1
|
|
986
|
+
|
|
906
987
|
def query_fulltext(
|
|
907
988
|
self,
|
|
908
989
|
query: str,
|
keep/cli.py
CHANGED
|
@@ -9,6 +9,7 @@ Usage:
|
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
|
+
import re
|
|
12
13
|
import sys
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Optional
|
|
@@ -16,6 +17,9 @@ from typing import Optional
|
|
|
16
17
|
import typer
|
|
17
18
|
from typing_extensions import Annotated
|
|
18
19
|
|
|
20
|
+
# Pattern for version identifier suffix: @V{N} where N is digits only
|
|
21
|
+
VERSION_SUFFIX_PATTERN = re.compile(r'@V\{(\d+)\}$')
|
|
22
|
+
|
|
19
23
|
from .api import Keeper, _text_content_id
|
|
20
24
|
from .document_store import VersionInfo
|
|
21
25
|
from .types import Item
|
|
@@ -38,6 +42,7 @@ def _verbose_callback(value: bool):
|
|
|
38
42
|
# Global state for CLI options
|
|
39
43
|
_json_output = False
|
|
40
44
|
_ids_output = False
|
|
45
|
+
_full_output = False
|
|
41
46
|
|
|
42
47
|
|
|
43
48
|
def _json_callback(value: bool):
|
|
@@ -58,18 +63,30 @@ def _get_ids_output() -> bool:
|
|
|
58
63
|
return _ids_output
|
|
59
64
|
|
|
60
65
|
|
|
66
|
+
def _full_callback(value: bool):
|
|
67
|
+
global _full_output
|
|
68
|
+
_full_output = value
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _get_full_output() -> bool:
|
|
72
|
+
return _full_output
|
|
73
|
+
|
|
74
|
+
|
|
61
75
|
app = typer.Typer(
|
|
62
76
|
name="keep",
|
|
63
77
|
help="Associative memory with semantic search.",
|
|
64
78
|
no_args_is_help=False,
|
|
65
79
|
invoke_without_command=True,
|
|
80
|
+
rich_markup_mode=None,
|
|
66
81
|
)
|
|
67
82
|
|
|
68
83
|
|
|
69
84
|
def _format_yaml_frontmatter(
|
|
70
85
|
item: Item,
|
|
71
86
|
version_nav: Optional[dict[str, list[VersionInfo]]] = None,
|
|
72
|
-
|
|
87
|
+
viewing_offset: Optional[int] = None,
|
|
88
|
+
similar_items: Optional[list[Item]] = None,
|
|
89
|
+
similar_offsets: Optional[dict[str, int]] = None,
|
|
73
90
|
) -> str:
|
|
74
91
|
"""
|
|
75
92
|
Format item as YAML frontmatter with summary as content.
|
|
@@ -77,41 +94,63 @@ def _format_yaml_frontmatter(
|
|
|
77
94
|
Args:
|
|
78
95
|
item: The item to format
|
|
79
96
|
version_nav: Optional version navigation info (prev/next lists)
|
|
80
|
-
|
|
97
|
+
viewing_offset: If viewing an old version, the offset (1=previous, 2=two ago)
|
|
98
|
+
similar_items: Optional list of similar items to display
|
|
99
|
+
similar_offsets: Version offsets for similar items (item.id -> offset)
|
|
100
|
+
|
|
101
|
+
Note: Offset computation (v1, v2, etc.) assumes version_nav lists
|
|
102
|
+
are ordered newest-first, matching list_versions() ordering.
|
|
103
|
+
Changing that ordering would break the vN = -V N correspondence.
|
|
81
104
|
"""
|
|
82
105
|
lines = ["---", f"id: {item.id}"]
|
|
83
|
-
if
|
|
84
|
-
lines.append(f"version: {
|
|
106
|
+
if viewing_offset is not None:
|
|
107
|
+
lines.append(f"version: {viewing_offset}")
|
|
85
108
|
if item.tags:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
lines.append(f" {k}: {v}")
|
|
109
|
+
tag_items = ", ".join(f"{k}: {v}" for k, v in sorted(item.tags.items()))
|
|
110
|
+
lines.append(f"tags: {{{tag_items}}}")
|
|
89
111
|
if item.score is not None:
|
|
90
112
|
lines.append(f"score: {item.score:.3f}")
|
|
91
113
|
|
|
92
|
-
# Add
|
|
114
|
+
# Add similar items if available (version-scoped IDs with date and summary)
|
|
115
|
+
if similar_items:
|
|
116
|
+
lines.append("similar:")
|
|
117
|
+
for sim_item in similar_items:
|
|
118
|
+
base_id = sim_item.tags.get("_base_id", sim_item.id)
|
|
119
|
+
offset = (similar_offsets or {}).get(sim_item.id, 0)
|
|
120
|
+
score_str = f"({sim_item.score:.2f})" if sim_item.score else ""
|
|
121
|
+
date_part = sim_item.tags.get("_updated", sim_item.tags.get("_created", ""))[:10]
|
|
122
|
+
summary_preview = sim_item.summary[:40].replace("\n", " ")
|
|
123
|
+
if len(sim_item.summary) > 40:
|
|
124
|
+
summary_preview += "..."
|
|
125
|
+
lines.append(f" - {base_id}@V{{{offset}}} {score_str} {date_part} {summary_preview}")
|
|
126
|
+
|
|
127
|
+
# Add version navigation (just @V{N} since ID is shown at top, with date + summary)
|
|
93
128
|
if version_nav:
|
|
129
|
+
# Current offset (0 if viewing current)
|
|
130
|
+
current_offset = viewing_offset if viewing_offset is not None else 0
|
|
131
|
+
|
|
94
132
|
if version_nav.get("prev"):
|
|
95
133
|
lines.append("prev:")
|
|
96
|
-
for v in version_nav["prev"]:
|
|
97
|
-
|
|
98
|
-
date_part = v.created_at[:10] if v.created_at else "
|
|
134
|
+
for i, v in enumerate(version_nav["prev"]):
|
|
135
|
+
prev_offset = current_offset + i + 1
|
|
136
|
+
date_part = v.created_at[:10] if v.created_at else ""
|
|
99
137
|
summary_preview = v.summary[:40].replace("\n", " ")
|
|
100
138
|
if len(v.summary) > 40:
|
|
101
139
|
summary_preview += "..."
|
|
102
|
-
lines.append(f" - {
|
|
140
|
+
lines.append(f" - @V{{{prev_offset}}} {date_part} {summary_preview}")
|
|
103
141
|
if version_nav.get("next"):
|
|
104
142
|
lines.append("next:")
|
|
105
|
-
for v in version_nav["next"]:
|
|
106
|
-
|
|
143
|
+
for i, v in enumerate(version_nav["next"]):
|
|
144
|
+
next_offset = current_offset - i - 1
|
|
145
|
+
date_part = v.created_at[:10] if v.created_at else ""
|
|
107
146
|
summary_preview = v.summary[:40].replace("\n", " ")
|
|
108
147
|
if len(v.summary) > 40:
|
|
109
148
|
summary_preview += "..."
|
|
110
|
-
lines.append(f" - {
|
|
111
|
-
elif
|
|
149
|
+
lines.append(f" - @V{{{next_offset}}} {date_part} {summary_preview}")
|
|
150
|
+
elif viewing_offset is not None:
|
|
112
151
|
# Viewing old version and next is empty means current is next
|
|
113
152
|
lines.append("next:")
|
|
114
|
-
lines.append(" -
|
|
153
|
+
lines.append(f" - @V{{0}}")
|
|
115
154
|
|
|
116
155
|
lines.append("---")
|
|
117
156
|
lines.append(item.summary) # Summary IS the content
|
|
@@ -139,15 +178,29 @@ def main_callback(
|
|
|
139
178
|
callback=_ids_callback,
|
|
140
179
|
is_eager=True,
|
|
141
180
|
)] = False,
|
|
181
|
+
full_output: Annotated[bool, typer.Option(
|
|
182
|
+
"--full", "-F",
|
|
183
|
+
help="Output full items (overrides --ids)",
|
|
184
|
+
callback=_full_callback,
|
|
185
|
+
is_eager=True,
|
|
186
|
+
)] = False,
|
|
142
187
|
):
|
|
143
188
|
"""Associative memory with semantic search."""
|
|
144
189
|
# If no subcommand provided, show the current context (now)
|
|
145
190
|
if ctx.invoked_subcommand is None:
|
|
191
|
+
from .api import NOWDOC_ID
|
|
146
192
|
kp = _get_keeper(None, "default")
|
|
147
193
|
item = kp.get_now()
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
194
|
+
version_nav = kp.get_version_nav(NOWDOC_ID, None, collection="default")
|
|
195
|
+
similar_items = kp.get_similar_for_display(NOWDOC_ID, limit=3, collection="default")
|
|
196
|
+
similar_offsets = {s.id: kp.get_version_offset(s) for s in similar_items}
|
|
197
|
+
typer.echo(_format_item(
|
|
198
|
+
item,
|
|
199
|
+
as_json=_get_json_output(),
|
|
200
|
+
version_nav=version_nav,
|
|
201
|
+
similar_items=similar_items,
|
|
202
|
+
similar_offsets=similar_offsets,
|
|
203
|
+
))
|
|
151
204
|
|
|
152
205
|
|
|
153
206
|
# -----------------------------------------------------------------------------
|
|
@@ -197,13 +250,23 @@ def _format_item(
|
|
|
197
250
|
item: Item,
|
|
198
251
|
as_json: bool = False,
|
|
199
252
|
version_nav: Optional[dict[str, list[VersionInfo]]] = None,
|
|
200
|
-
|
|
253
|
+
viewing_offset: Optional[int] = None,
|
|
254
|
+
similar_items: Optional[list[Item]] = None,
|
|
255
|
+
similar_offsets: Optional[dict[str, int]] = None,
|
|
201
256
|
) -> str:
|
|
202
257
|
"""
|
|
203
258
|
Format an item for display.
|
|
204
259
|
|
|
205
260
|
Text format: YAML frontmatter (matches docs/system format)
|
|
206
261
|
With --ids: just the ID (for piping)
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
item: The item to format
|
|
265
|
+
as_json: Output as JSON
|
|
266
|
+
version_nav: Optional version navigation info (prev/next lists)
|
|
267
|
+
viewing_offset: If viewing an old version, the offset (1=previous, 2=two ago)
|
|
268
|
+
similar_items: Optional list of similar items to display
|
|
269
|
+
similar_offsets: Version offsets for similar items (item.id -> offset)
|
|
207
270
|
"""
|
|
208
271
|
if _get_ids_output():
|
|
209
272
|
return json.dumps(item.id) if as_json else item.id
|
|
@@ -215,17 +278,47 @@ def _format_item(
|
|
|
215
278
|
"tags": item.tags,
|
|
216
279
|
"score": item.score,
|
|
217
280
|
}
|
|
218
|
-
if
|
|
219
|
-
result["version"] =
|
|
281
|
+
if viewing_offset is not None:
|
|
282
|
+
result["version"] = viewing_offset
|
|
283
|
+
result["vid"] = f"{item.id}@V{{{viewing_offset}}}"
|
|
284
|
+
if similar_items:
|
|
285
|
+
result["similar"] = [
|
|
286
|
+
{
|
|
287
|
+
"id": f"{s.tags.get('_base_id', s.id)}@V{{{(similar_offsets or {}).get(s.id, 0)}}}",
|
|
288
|
+
"score": s.score,
|
|
289
|
+
"date": s.tags.get("_updated", s.tags.get("_created", ""))[:10],
|
|
290
|
+
"summary": s.summary[:60],
|
|
291
|
+
}
|
|
292
|
+
for s in similar_items
|
|
293
|
+
]
|
|
220
294
|
if version_nav:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
295
|
+
current_offset = viewing_offset if viewing_offset is not None else 0
|
|
296
|
+
result["version_nav"] = {}
|
|
297
|
+
if version_nav.get("prev"):
|
|
298
|
+
result["version_nav"]["prev"] = [
|
|
299
|
+
{
|
|
300
|
+
"offset": current_offset + i + 1,
|
|
301
|
+
"vid": f"{item.id}@V{{{current_offset + i + 1}}}",
|
|
302
|
+
"created_at": v.created_at,
|
|
303
|
+
"summary": v.summary[:60],
|
|
304
|
+
}
|
|
305
|
+
for i, v in enumerate(version_nav["prev"])
|
|
306
|
+
]
|
|
307
|
+
if version_nav.get("next"):
|
|
308
|
+
result["version_nav"]["next"] = [
|
|
309
|
+
{
|
|
310
|
+
"offset": current_offset - i - 1,
|
|
311
|
+
"vid": f"{item.id}@V{{{current_offset - i - 1}}}",
|
|
312
|
+
"created_at": v.created_at,
|
|
313
|
+
"summary": v.summary[:60],
|
|
314
|
+
}
|
|
315
|
+
for i, v in enumerate(version_nav["next"])
|
|
316
|
+
]
|
|
317
|
+
elif viewing_offset is not None:
|
|
318
|
+
result["version_nav"]["next"] = [{"offset": 0, "vid": f"{item.id}@V{{0}}", "label": "current"}]
|
|
226
319
|
return json.dumps(result)
|
|
227
320
|
|
|
228
|
-
return _format_yaml_frontmatter(item, version_nav,
|
|
321
|
+
return _format_yaml_frontmatter(item, version_nav, viewing_offset, similar_items, similar_offsets)
|
|
229
322
|
|
|
230
323
|
|
|
231
324
|
def _format_items(items: list[Item], as_json: bool = False) -> str:
|
|
@@ -338,7 +431,7 @@ def find(
|
|
|
338
431
|
|
|
339
432
|
@app.command()
|
|
340
433
|
def search(
|
|
341
|
-
query: Annotated[str, typer.Argument(help="Full-text search query")],
|
|
434
|
+
query: Annotated[str, typer.Argument(default=..., help="Full-text search query")],
|
|
342
435
|
store: StoreOption = None,
|
|
343
436
|
collection: CollectionOption = "default",
|
|
344
437
|
limit: LimitOption = 10,
|
|
@@ -364,11 +457,22 @@ def list_recent(
|
|
|
364
457
|
"""
|
|
365
458
|
List recent items by update time.
|
|
366
459
|
|
|
367
|
-
Shows
|
|
460
|
+
Shows IDs by default (composable). Use --full for detailed output.
|
|
368
461
|
"""
|
|
369
462
|
kp = _get_keeper(store, collection)
|
|
370
463
|
results = kp.list_recent(limit=limit)
|
|
371
|
-
|
|
464
|
+
|
|
465
|
+
# Determine output mode: --full > --ids > command default (IDs for list)
|
|
466
|
+
if _get_json_output():
|
|
467
|
+
# JSON always outputs full items
|
|
468
|
+
typer.echo(_format_items(results, as_json=True))
|
|
469
|
+
elif _get_full_output():
|
|
470
|
+
# --full flag: full YAML output
|
|
471
|
+
typer.echo(_format_items(results, as_json=False))
|
|
472
|
+
else:
|
|
473
|
+
# Default for list: IDs only (composable)
|
|
474
|
+
for item in results:
|
|
475
|
+
typer.echo(item.id)
|
|
372
476
|
|
|
373
477
|
|
|
374
478
|
@app.command()
|
|
@@ -429,7 +533,7 @@ def tag(
|
|
|
429
533
|
|
|
430
534
|
@app.command("tag-update")
|
|
431
535
|
def tag_update(
|
|
432
|
-
ids: Annotated[list[str], typer.Argument(help="Document IDs to tag")],
|
|
536
|
+
ids: Annotated[list[str], typer.Argument(default=..., help="Document IDs to tag")],
|
|
433
537
|
tags: Annotated[Optional[list[str]], typer.Option(
|
|
434
538
|
"--tag", "-t",
|
|
435
539
|
help="Tag as key=value (empty value removes: key=)"
|
|
@@ -605,34 +709,46 @@ def now(
|
|
|
605
709
|
versions = kp.list_versions(NOWDOC_ID, limit=50, collection=collection)
|
|
606
710
|
current = kp.get(NOWDOC_ID, collection=collection)
|
|
607
711
|
|
|
608
|
-
if
|
|
712
|
+
if _get_ids_output():
|
|
713
|
+
# Output version identifiers, one per line
|
|
714
|
+
if current:
|
|
715
|
+
typer.echo(f"{NOWDOC_ID}@V{{0}}")
|
|
716
|
+
for i in range(1, len(versions) + 1):
|
|
717
|
+
typer.echo(f"{NOWDOC_ID}@V{{{i}}}")
|
|
718
|
+
elif _get_json_output():
|
|
609
719
|
result = {
|
|
610
720
|
"id": NOWDOC_ID,
|
|
611
721
|
"current": {
|
|
612
722
|
"summary": current.summary if current else None,
|
|
723
|
+
"offset": 0,
|
|
724
|
+
"vid": f"{NOWDOC_ID}@V{{0}}",
|
|
613
725
|
} if current else None,
|
|
614
726
|
"versions": [
|
|
615
727
|
{
|
|
728
|
+
"offset": i + 1,
|
|
729
|
+
"vid": f"{NOWDOC_ID}@V{{{i + 1}}}",
|
|
616
730
|
"version": v.version,
|
|
617
731
|
"summary": v.summary[:60],
|
|
618
732
|
"created_at": v.created_at,
|
|
619
733
|
}
|
|
620
|
-
for v in versions
|
|
734
|
+
for i, v in enumerate(versions)
|
|
621
735
|
],
|
|
622
736
|
}
|
|
623
737
|
typer.echo(json.dumps(result, indent=2))
|
|
624
738
|
else:
|
|
625
739
|
if current:
|
|
626
740
|
summary_preview = current.summary[:60].replace("\n", " ")
|
|
627
|
-
|
|
741
|
+
if len(current.summary) > 60:
|
|
742
|
+
summary_preview += "..."
|
|
743
|
+
typer.echo(f"v0 (current): {summary_preview}")
|
|
628
744
|
if versions:
|
|
629
|
-
typer.echo(f"\
|
|
630
|
-
for v in versions:
|
|
745
|
+
typer.echo(f"\nArchived:")
|
|
746
|
+
for i, v in enumerate(versions, start=1):
|
|
631
747
|
date_part = v.created_at[:10] if v.created_at else "unknown"
|
|
632
748
|
summary_preview = v.summary[:50].replace("\n", " ")
|
|
633
749
|
if len(v.summary) > 50:
|
|
634
750
|
summary_preview += "..."
|
|
635
|
-
typer.echo(f" v{
|
|
751
|
+
typer.echo(f" v{i} ({date_part}): {summary_preview}")
|
|
636
752
|
else:
|
|
637
753
|
typer.echo("No version history.")
|
|
638
754
|
return
|
|
@@ -642,25 +758,26 @@ def now(
|
|
|
642
758
|
offset = version
|
|
643
759
|
if offset == 0:
|
|
644
760
|
item = kp.get_now()
|
|
645
|
-
|
|
761
|
+
internal_version = None
|
|
646
762
|
else:
|
|
647
763
|
item = kp.get_version(NOWDOC_ID, offset, collection=collection)
|
|
764
|
+
# Get internal version number for API call
|
|
648
765
|
versions = kp.list_versions(NOWDOC_ID, limit=1, collection=collection)
|
|
649
766
|
if versions:
|
|
650
|
-
|
|
767
|
+
internal_version = versions[0].version - (offset - 1)
|
|
651
768
|
else:
|
|
652
|
-
|
|
769
|
+
internal_version = None
|
|
653
770
|
|
|
654
771
|
if item is None:
|
|
655
772
|
typer.echo(f"Version not found (offset {offset})", err=True)
|
|
656
773
|
raise typer.Exit(1)
|
|
657
774
|
|
|
658
|
-
version_nav = kp.get_version_nav(NOWDOC_ID,
|
|
775
|
+
version_nav = kp.get_version_nav(NOWDOC_ID, internal_version, collection=collection)
|
|
659
776
|
typer.echo(_format_item(
|
|
660
777
|
item,
|
|
661
778
|
as_json=_get_json_output(),
|
|
662
779
|
version_nav=version_nav,
|
|
663
|
-
|
|
780
|
+
viewing_offset=offset if offset > 0 else None,
|
|
664
781
|
))
|
|
665
782
|
return
|
|
666
783
|
|
|
@@ -700,19 +817,23 @@ def now(
|
|
|
700
817
|
item = kp.set_now(new_content, tags=parsed_tags or None)
|
|
701
818
|
typer.echo(_format_item(item, as_json=_get_json_output()))
|
|
702
819
|
else:
|
|
703
|
-
# Get current context with version navigation
|
|
820
|
+
# Get current context with version navigation and similar items
|
|
704
821
|
item = kp.get_now()
|
|
705
822
|
version_nav = kp.get_version_nav(NOWDOC_ID, None, collection=collection)
|
|
823
|
+
similar_items = kp.get_similar_for_display(NOWDOC_ID, limit=3, collection=collection)
|
|
824
|
+
similar_offsets = {s.id: kp.get_version_offset(s) for s in similar_items}
|
|
706
825
|
typer.echo(_format_item(
|
|
707
826
|
item,
|
|
708
827
|
as_json=_get_json_output(),
|
|
709
828
|
version_nav=version_nav,
|
|
829
|
+
similar_items=similar_items,
|
|
830
|
+
similar_offsets=similar_offsets,
|
|
710
831
|
))
|
|
711
832
|
|
|
712
833
|
|
|
713
834
|
@app.command()
|
|
714
835
|
def get(
|
|
715
|
-
id: Annotated[str, typer.Argument(help="URI of item
|
|
836
|
+
id: Annotated[str, typer.Argument(default=..., help="URI of item (append @V{N} for version)")],
|
|
716
837
|
version: Annotated[Optional[int], typer.Option(
|
|
717
838
|
"--version", "-V",
|
|
718
839
|
help="Get specific version (0=current, 1=previous, etc.)"
|
|
@@ -721,108 +842,187 @@ def get(
|
|
|
721
842
|
"--history", "-H",
|
|
722
843
|
help="List all versions"
|
|
723
844
|
)] = False,
|
|
845
|
+
similar: Annotated[bool, typer.Option(
|
|
846
|
+
"--similar", "-S",
|
|
847
|
+
help="List similar items"
|
|
848
|
+
)] = False,
|
|
849
|
+
no_similar: Annotated[bool, typer.Option(
|
|
850
|
+
"--no-similar",
|
|
851
|
+
help="Suppress similar items in output"
|
|
852
|
+
)] = False,
|
|
853
|
+
limit: Annotated[int, typer.Option(
|
|
854
|
+
"--limit", "-n",
|
|
855
|
+
help="Max items for --history or --similar (default: 10)"
|
|
856
|
+
)] = 10,
|
|
724
857
|
store: StoreOption = None,
|
|
725
858
|
collection: CollectionOption = "default",
|
|
726
859
|
):
|
|
727
860
|
"""
|
|
728
861
|
Retrieve a specific item by ID.
|
|
729
862
|
|
|
863
|
+
Version identifiers: Append @V{N} to get a specific version.
|
|
864
|
+
|
|
730
865
|
Examples:
|
|
731
|
-
keep get doc:1 # Current version with
|
|
866
|
+
keep get doc:1 # Current version with similar items
|
|
732
867
|
keep get doc:1 -V 1 # Previous version with prev/next nav
|
|
868
|
+
keep get "doc:1@V{1}" # Same as -V 1
|
|
733
869
|
keep get doc:1 --history # List all versions
|
|
870
|
+
keep get doc:1 --similar # List similar items
|
|
871
|
+
keep get doc:1 --no-similar # Suppress similar items
|
|
734
872
|
"""
|
|
735
873
|
kp = _get_keeper(store, collection)
|
|
736
874
|
|
|
875
|
+
# Parse @V{N} version identifier from ID (security: check literal first)
|
|
876
|
+
actual_id = id
|
|
877
|
+
version_from_id = None
|
|
878
|
+
|
|
879
|
+
if kp.exists(id, collection=collection):
|
|
880
|
+
# Literal ID exists - use it directly (prevents confusion attacks)
|
|
881
|
+
actual_id = id
|
|
882
|
+
else:
|
|
883
|
+
# Try parsing @V{N} suffix
|
|
884
|
+
match = VERSION_SUFFIX_PATTERN.search(id)
|
|
885
|
+
if match:
|
|
886
|
+
version_from_id = int(match.group(1))
|
|
887
|
+
actual_id = id[:match.start()]
|
|
888
|
+
|
|
889
|
+
# Version from ID only applies if --version not explicitly provided
|
|
890
|
+
if version is None and version_from_id is not None:
|
|
891
|
+
version = version_from_id
|
|
892
|
+
|
|
737
893
|
if history:
|
|
738
894
|
# List all versions
|
|
739
|
-
versions = kp.list_versions(
|
|
740
|
-
current = kp.get(
|
|
895
|
+
versions = kp.list_versions(actual_id, limit=limit, collection=collection)
|
|
896
|
+
current = kp.get(actual_id, collection=collection)
|
|
741
897
|
|
|
742
|
-
if
|
|
898
|
+
if _get_ids_output():
|
|
899
|
+
# Output version identifiers, one per line
|
|
900
|
+
if current:
|
|
901
|
+
typer.echo(f"{actual_id}@V{{0}}")
|
|
902
|
+
for i in range(1, len(versions) + 1):
|
|
903
|
+
typer.echo(f"{actual_id}@V{{{i}}}")
|
|
904
|
+
elif _get_json_output():
|
|
743
905
|
result = {
|
|
744
|
-
"id":
|
|
906
|
+
"id": actual_id,
|
|
745
907
|
"current": {
|
|
746
908
|
"summary": current.summary if current else None,
|
|
747
909
|
"tags": current.tags if current else {},
|
|
910
|
+
"offset": 0,
|
|
911
|
+
"vid": f"{actual_id}@V{{0}}",
|
|
748
912
|
} if current else None,
|
|
749
913
|
"versions": [
|
|
750
914
|
{
|
|
915
|
+
"offset": i + 1,
|
|
916
|
+
"vid": f"{actual_id}@V{{{i + 1}}}",
|
|
751
917
|
"version": v.version,
|
|
752
918
|
"summary": v.summary,
|
|
753
919
|
"created_at": v.created_at,
|
|
754
920
|
}
|
|
755
|
-
for v in versions
|
|
921
|
+
for i, v in enumerate(versions)
|
|
756
922
|
],
|
|
757
923
|
}
|
|
758
924
|
typer.echo(json.dumps(result, indent=2))
|
|
759
925
|
else:
|
|
760
926
|
if current:
|
|
761
|
-
|
|
927
|
+
summary_preview = current.summary[:60].replace("\n", " ")
|
|
928
|
+
if len(current.summary) > 60:
|
|
929
|
+
summary_preview += "..."
|
|
930
|
+
typer.echo(f"v0 (current): {summary_preview}")
|
|
762
931
|
if versions:
|
|
763
|
-
typer.echo(f"\
|
|
764
|
-
for v in versions:
|
|
932
|
+
typer.echo(f"\nArchived:")
|
|
933
|
+
for i, v in enumerate(versions, start=1):
|
|
765
934
|
date_part = v.created_at[:10] if v.created_at else "unknown"
|
|
766
935
|
summary_preview = v.summary[:50].replace("\n", " ")
|
|
767
936
|
if len(v.summary) > 50:
|
|
768
937
|
summary_preview += "..."
|
|
769
|
-
typer.echo(f" v{
|
|
938
|
+
typer.echo(f" v{i} ({date_part}): {summary_preview}")
|
|
770
939
|
else:
|
|
771
940
|
typer.echo("No version history.")
|
|
772
941
|
return
|
|
773
942
|
|
|
943
|
+
if similar:
|
|
944
|
+
# List similar items
|
|
945
|
+
similar_items = kp.get_similar_for_display(actual_id, limit=limit, collection=collection)
|
|
946
|
+
similar_offsets = {s.id: kp.get_version_offset(s) for s in similar_items}
|
|
947
|
+
|
|
948
|
+
if _get_ids_output():
|
|
949
|
+
# Output version-scoped IDs one per line
|
|
950
|
+
for item in similar_items:
|
|
951
|
+
base_id = item.tags.get("_base_id", item.id)
|
|
952
|
+
offset = similar_offsets.get(item.id, 0)
|
|
953
|
+
typer.echo(f"{base_id}@V{{{offset}}}")
|
|
954
|
+
elif _get_json_output():
|
|
955
|
+
result = {
|
|
956
|
+
"id": actual_id,
|
|
957
|
+
"similar": [
|
|
958
|
+
{
|
|
959
|
+
"id": f"{item.tags.get('_base_id', item.id)}@V{{{similar_offsets.get(item.id, 0)}}}",
|
|
960
|
+
"score": item.score,
|
|
961
|
+
"date": item.tags.get("_updated", item.tags.get("_created", ""))[:10],
|
|
962
|
+
"summary": item.summary[:60],
|
|
963
|
+
}
|
|
964
|
+
for item in similar_items
|
|
965
|
+
],
|
|
966
|
+
}
|
|
967
|
+
typer.echo(json.dumps(result, indent=2))
|
|
968
|
+
else:
|
|
969
|
+
typer.echo(f"Similar to {actual_id}:")
|
|
970
|
+
if similar_items:
|
|
971
|
+
for item in similar_items:
|
|
972
|
+
base_id = item.tags.get("_base_id", item.id)
|
|
973
|
+
offset = similar_offsets.get(item.id, 0)
|
|
974
|
+
score_str = f"({item.score:.2f})" if item.score else ""
|
|
975
|
+
date_part = item.tags.get("_updated", item.tags.get("_created", ""))[:10]
|
|
976
|
+
summary_preview = item.summary[:50].replace("\n", " ")
|
|
977
|
+
if len(item.summary) > 50:
|
|
978
|
+
summary_preview += "..."
|
|
979
|
+
typer.echo(f" {base_id}@V{{{offset}}} {score_str} {date_part} {summary_preview}")
|
|
980
|
+
else:
|
|
981
|
+
typer.echo(" No similar items found.")
|
|
982
|
+
return
|
|
983
|
+
|
|
774
984
|
# Get specific version or current
|
|
775
985
|
offset = version if version is not None else 0
|
|
776
986
|
|
|
777
987
|
if offset == 0:
|
|
778
|
-
item = kp.get(
|
|
779
|
-
|
|
988
|
+
item = kp.get(actual_id, collection=collection)
|
|
989
|
+
internal_version = None
|
|
780
990
|
else:
|
|
781
|
-
item = kp.get_version(
|
|
782
|
-
# Calculate
|
|
783
|
-
versions = kp.list_versions(
|
|
991
|
+
item = kp.get_version(actual_id, offset, collection=collection)
|
|
992
|
+
# Calculate internal version number for API call
|
|
993
|
+
versions = kp.list_versions(actual_id, limit=1, collection=collection)
|
|
784
994
|
if versions:
|
|
785
|
-
|
|
995
|
+
internal_version = versions[0].version - (offset - 1)
|
|
786
996
|
else:
|
|
787
|
-
|
|
997
|
+
internal_version = None
|
|
788
998
|
|
|
789
999
|
if item is None:
|
|
790
1000
|
if offset > 0:
|
|
791
|
-
typer.echo(f"Version not found: {
|
|
1001
|
+
typer.echo(f"Version not found: {actual_id} (offset {offset})", err=True)
|
|
792
1002
|
else:
|
|
793
|
-
typer.echo(f"Not found: {
|
|
1003
|
+
typer.echo(f"Not found: {actual_id}", err=True)
|
|
794
1004
|
raise typer.Exit(1)
|
|
795
1005
|
|
|
796
1006
|
# Get version navigation
|
|
797
|
-
version_nav = kp.get_version_nav(
|
|
1007
|
+
version_nav = kp.get_version_nav(actual_id, internal_version, collection=collection)
|
|
1008
|
+
|
|
1009
|
+
# Get similar items (unless suppressed or viewing old version)
|
|
1010
|
+
similar_items = None
|
|
1011
|
+
similar_offsets = None
|
|
1012
|
+
if not no_similar and offset == 0:
|
|
1013
|
+
similar_items = kp.get_similar_for_display(actual_id, limit=3, collection=collection)
|
|
1014
|
+
similar_offsets = {s.id: kp.get_version_offset(s) for s in similar_items}
|
|
798
1015
|
|
|
799
1016
|
typer.echo(_format_item(
|
|
800
1017
|
item,
|
|
801
1018
|
as_json=_get_json_output(),
|
|
802
1019
|
version_nav=version_nav,
|
|
803
|
-
|
|
1020
|
+
viewing_offset=offset if offset > 0 else None,
|
|
1021
|
+
similar_items=similar_items,
|
|
1022
|
+
similar_offsets=similar_offsets,
|
|
804
1023
|
))
|
|
805
1024
|
|
|
806
1025
|
|
|
807
|
-
@app.command()
|
|
808
|
-
def exists(
|
|
809
|
-
id: Annotated[str, typer.Argument(help="URI to check")],
|
|
810
|
-
store: StoreOption = None,
|
|
811
|
-
collection: CollectionOption = "default",
|
|
812
|
-
):
|
|
813
|
-
"""
|
|
814
|
-
Check if an item exists in the store.
|
|
815
|
-
"""
|
|
816
|
-
kp = _get_keeper(store, collection)
|
|
817
|
-
found = kp.exists(id)
|
|
818
|
-
|
|
819
|
-
if found:
|
|
820
|
-
typer.echo(f"Exists: {id}")
|
|
821
|
-
else:
|
|
822
|
-
typer.echo(f"Not found: {id}")
|
|
823
|
-
raise typer.Exit(1)
|
|
824
|
-
|
|
825
|
-
|
|
826
1026
|
@app.command("collections")
|
|
827
1027
|
def list_collections(
|
|
828
1028
|
store: StoreOption = None,
|
|
@@ -910,45 +1110,6 @@ def config(
|
|
|
910
1110
|
typer.echo(f" Summarization: {cfg.summarization.name}")
|
|
911
1111
|
|
|
912
1112
|
|
|
913
|
-
@app.command("system")
|
|
914
|
-
def list_system(
|
|
915
|
-
store: StoreOption = None,
|
|
916
|
-
):
|
|
917
|
-
"""
|
|
918
|
-
List the system documents.
|
|
919
|
-
|
|
920
|
-
Shows ID and summary for each. Use `keep get ID` for full details.
|
|
921
|
-
"""
|
|
922
|
-
kp = _get_keeper(store, "default")
|
|
923
|
-
docs = kp.list_system_documents()
|
|
924
|
-
|
|
925
|
-
# Use --ids flag for pipe-friendly output
|
|
926
|
-
if _get_ids_output():
|
|
927
|
-
ids = [doc.id for doc in docs]
|
|
928
|
-
if _get_json_output():
|
|
929
|
-
typer.echo(json.dumps(ids))
|
|
930
|
-
else:
|
|
931
|
-
for doc_id in ids:
|
|
932
|
-
typer.echo(doc_id)
|
|
933
|
-
return
|
|
934
|
-
|
|
935
|
-
if _get_json_output():
|
|
936
|
-
typer.echo(json.dumps([
|
|
937
|
-
{"id": doc.id, "summary": doc.summary}
|
|
938
|
-
for doc in docs
|
|
939
|
-
], indent=2))
|
|
940
|
-
else:
|
|
941
|
-
if not docs:
|
|
942
|
-
typer.echo("No system documents.")
|
|
943
|
-
else:
|
|
944
|
-
for doc in docs:
|
|
945
|
-
# Compact summary: collapse whitespace, truncate to 70 chars
|
|
946
|
-
summary = " ".join(doc.summary.split())[:70]
|
|
947
|
-
if len(doc.summary) > 70:
|
|
948
|
-
summary += "..."
|
|
949
|
-
typer.echo(f"{doc.id}: {summary}")
|
|
950
|
-
|
|
951
|
-
|
|
952
1113
|
@app.command("process-pending")
|
|
953
1114
|
def process_pending(
|
|
954
1115
|
store: StoreOption = None,
|
keep/config.py
CHANGED
|
@@ -14,10 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
from typing import Any, Optional
|
|
15
15
|
|
|
16
16
|
# tomli_w for writing TOML (tomllib is read-only)
|
|
17
|
-
|
|
18
|
-
import tomli_w
|
|
19
|
-
except ImportError:
|
|
20
|
-
tomli_w = None # type: ignore
|
|
17
|
+
import tomli_w
|
|
21
18
|
|
|
22
19
|
|
|
23
20
|
CONFIG_FILENAME = "keep.toml"
|
|
@@ -387,9 +384,6 @@ def save_config(config: StoreConfig) -> None:
|
|
|
387
384
|
|
|
388
385
|
Creates the directory if it doesn't exist.
|
|
389
386
|
"""
|
|
390
|
-
if tomli_w is None:
|
|
391
|
-
raise RuntimeError("tomli_w is required to save config. Install with: pip install tomli-w")
|
|
392
|
-
|
|
393
387
|
# Ensure config directory exists
|
|
394
388
|
config_location = config.config_dir if config.config_dir else config.path
|
|
395
389
|
config_location.mkdir(parents=True, exist_ok=True)
|
keep/store.py
CHANGED
|
@@ -58,15 +58,9 @@ class ChromaStore:
|
|
|
58
58
|
embedding_dimension: Expected dimension of embeddings (for validation).
|
|
59
59
|
Can be None for read-only access; will be set on first write.
|
|
60
60
|
"""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
except ImportError:
|
|
65
|
-
raise RuntimeError(
|
|
66
|
-
"ChromaStore requires 'chromadb' library. "
|
|
67
|
-
"Install with: pip install chromadb"
|
|
68
|
-
)
|
|
69
|
-
|
|
61
|
+
import chromadb
|
|
62
|
+
from chromadb.config import Settings
|
|
63
|
+
|
|
70
64
|
self._store_path = store_path
|
|
71
65
|
self._embedding_dimension = embedding_dimension
|
|
72
66
|
|
|
@@ -382,6 +376,23 @@ class ChromaStore:
|
|
|
382
376
|
result = coll.get(ids=[id], include=[])
|
|
383
377
|
return bool(result["ids"])
|
|
384
378
|
|
|
379
|
+
def get_embedding(self, collection: str, id: str) -> list[float] | None:
|
|
380
|
+
"""
|
|
381
|
+
Retrieve the stored embedding for a document.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
collection: Collection name
|
|
385
|
+
id: Item identifier
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Embedding vector if found, None otherwise
|
|
389
|
+
"""
|
|
390
|
+
coll = self._get_collection(collection)
|
|
391
|
+
result = coll.get(ids=[id], include=["embeddings"])
|
|
392
|
+
if not result["ids"] or result["embeddings"] is None or len(result["embeddings"]) == 0:
|
|
393
|
+
return None
|
|
394
|
+
return list(result["embeddings"][0])
|
|
395
|
+
|
|
385
396
|
def list_ids(self, collection: str) -> list[str]:
|
|
386
397
|
"""
|
|
387
398
|
List all document IDs in a collection.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: keep-skill
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Semantic memory - remember and search documents by meaning
|
|
5
5
|
Project-URL: Homepage, https://github.com/hughpyle/keep
|
|
6
6
|
Project-URL: Repository, https://github.com/hughpyle/keep
|
|
@@ -56,7 +56,7 @@ Description-Content-Type: text/markdown
|
|
|
56
56
|
Index documents and notes. Search by meaning. Track changes over time.
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
59
|
+
uv tool install 'keep-skill[local]'
|
|
60
60
|
keep init
|
|
61
61
|
|
|
62
62
|
# Index content
|
|
@@ -90,16 +90,20 @@ Backed by ChromaDB for vectors, SQLite for metadata and versions.
|
|
|
90
90
|
**Python 3.11–3.13 required.**
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
|
-
# Recommended:
|
|
94
|
-
pip install 'keep-skill[local]'
|
|
95
|
-
|
|
96
|
-
# Or with uv (faster):
|
|
93
|
+
# Recommended: uv (isolated environment, fast)
|
|
97
94
|
uv tool install 'keep-skill[local]'
|
|
98
95
|
|
|
99
|
-
#
|
|
100
|
-
|
|
96
|
+
# Alternative: pip in a virtual environment
|
|
97
|
+
python -m venv .venv && source .venv/bin/activate
|
|
98
|
+
pip install 'keep-skill[local]'
|
|
99
|
+
|
|
100
|
+
# API-based (requires OPENAI_API_KEY)
|
|
101
|
+
uv tool install 'keep-skill[openai]'
|
|
101
102
|
```
|
|
102
103
|
|
|
104
|
+
> **Note:** Always use an isolated environment (uv or venv). Installing with system pip
|
|
105
|
+
> may cause version conflicts with dependencies like typer.
|
|
106
|
+
|
|
103
107
|
First run downloads embedding models (~3-5 minutes).
|
|
104
108
|
|
|
105
109
|
---
|
|
@@ -120,6 +124,7 @@ keep find "auth" --since P7D # Last 7 days
|
|
|
120
124
|
# Retrieve
|
|
121
125
|
keep get file:///path/to/doc.md
|
|
122
126
|
keep get ID -V 1 # Previous version
|
|
127
|
+
keep get "ID@V{1}" # Same as -V 1 (version identifier)
|
|
123
128
|
keep get ID --history # All versions
|
|
124
129
|
|
|
125
130
|
# Tags
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
keep/__init__.py,sha256=
|
|
1
|
+
keep/__init__.py,sha256=1WVkySoomQuf9-o3pIKs1CC2OwIyfkiaCxn-mO6nhd8,1581
|
|
2
2
|
keep/__main__.py,sha256=3Uu70IhIDIjh8OW6jp9jQQ3dF2lKdJWi_3FtRIQMiMY,104
|
|
3
|
-
keep/api.py,sha256=
|
|
3
|
+
keep/api.py,sha256=7oa6jdmeE55dATOEVw18sXGVBVndjiD_7tMDcP_xl_0,59746
|
|
4
4
|
keep/chunking.py,sha256=neAXOLSvVwbUxapbqq7nZrbSNSzMXuhxj-ODoOSodsU,11830
|
|
5
|
-
keep/cli.py,sha256=
|
|
6
|
-
keep/config.py,sha256=
|
|
5
|
+
keep/cli.py,sha256=DzZ5Dy4U25q-Mnbv5WbfRsZaN-ped24GDk34fVddmFA,42440
|
|
6
|
+
keep/config.py,sha256=xhsTS_55HSzYxFNGt2z0q_0Ne-s9L9_3om8Uf_8gHB4,15643
|
|
7
7
|
keep/context.py,sha256=CNpjmrv6eW2kV1E0MO6qAQfhYKRlfzAL--6v4Mj1nFY,71
|
|
8
8
|
keep/document_store.py,sha256=UswqKIGSc5E-r7Tg9k0g5-byYnuar3e9FieQ7WNod9k,29109
|
|
9
9
|
keep/errors.py,sha256=G9e5FbdfeugyfHOuL_SPZlM5jgWWnwsX4hM7IzanBZc,857
|
|
@@ -11,7 +11,7 @@ keep/indexing.py,sha256=dpPYo3WXnIhFDWinz5ZBZVk7_qumeNpP4EpOIY0zMbs,6063
|
|
|
11
11
|
keep/logging_config.py,sha256=IGwkgIyg-TfYaT4MnoCXfmjeHAe_wsB_XQ1QhVT_ro8,3503
|
|
12
12
|
keep/paths.py,sha256=Dv7pM6oo2QgjL6sj5wPjhuMOK2wqUkfd4Kz08TwJ1ps,3331
|
|
13
13
|
keep/pending_summaries.py,sha256=_irGe7P1Lmog2c5cEgx-BElpq4YJW-tEmF5A3IUZQbQ,5727
|
|
14
|
-
keep/store.py,sha256=
|
|
14
|
+
keep/store.py,sha256=SBc2QdTyApdDDVjm2uZQI6tGbV5Hurfetgj7dyTO65o,17881
|
|
15
15
|
keep/types.py,sha256=f6uOSYsYt6mj1ulKn2iRkooi__dWCiOQFPD6he2eID4,2149
|
|
16
16
|
keep/providers/__init__.py,sha256=GFX_12g9OdjmpFUkTekOQBOWvcRW2Ae6yidfVVW2SiI,1095
|
|
17
17
|
keep/providers/base.py,sha256=7Ug4Kj9fK2Dq4zDcZjn-GKsoZBOAlB9b-FMk969ER-g,14590
|
|
@@ -21,8 +21,8 @@ keep/providers/embeddings.py,sha256=zi8GyitKexdbCJyU1nLrUhGt_zzPn3udYrrPZ5Ak8Wo,
|
|
|
21
21
|
keep/providers/llm.py,sha256=BxROKOklKbkGsHcSADPNNgWQExgSN6Bg4KPQIxVuB3U,12441
|
|
22
22
|
keep/providers/mlx.py,sha256=aNl00r9tGi5tCGj2ArYH7CmDHtL1jLjVzb1rofU1DAo,9050
|
|
23
23
|
keep/providers/summarization.py,sha256=MlVTcYipaqp2lT-QYnznp0AMuPVG36QfcTQnvY7Gb-Q,3409
|
|
24
|
-
keep_skill-0.
|
|
25
|
-
keep_skill-0.
|
|
26
|
-
keep_skill-0.
|
|
27
|
-
keep_skill-0.
|
|
28
|
-
keep_skill-0.
|
|
24
|
+
keep_skill-0.4.2.dist-info/METADATA,sha256=GKkPrekD30dauxLoogAmSaoD2zhIxltJ4RDkg7yPvzk,6606
|
|
25
|
+
keep_skill-0.4.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
26
|
+
keep_skill-0.4.2.dist-info/entry_points.txt,sha256=W8yiI4kNeW0IC8ji4EHRWrvdhFxzaqTIePUhJAJAMOo,39
|
|
27
|
+
keep_skill-0.4.2.dist-info/licenses/LICENSE,sha256=zsm0tpvtyUkevcjn5BIvs9jAho8iwxq3Ax9647AaOSg,1086
|
|
28
|
+
keep_skill-0.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|