abstractnote 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- abstractnote/__init__.py +18 -0
- abstractnote/cli.py +240 -0
- abstractnote/flows/__init__.py +2 -0
- abstractnote/flows/build.py +970 -0
- abstractnote/flows/bundle.py +62 -0
- abstractnote/flows/contract.py +88 -0
- abstractnote/flows/graph.py +540 -0
- abstractnote/flows/json/note-capture.json +2280 -0
- abstractnote/flows/json/note-drain.json +2012 -0
- abstractnote/flows/json/note-mine.json +2392 -0
- abstractnote/flows/json/note-process.json +1853 -0
- abstractnote/flows/json/note-recall.json +1876 -0
- abstractnote/flows/json/note-reflect.json +2057 -0
- abstractnote/flows/prompts_fallback.py +119 -0
- abstractnote/flows/runner.py +320 -0
- abstractnote/flows/schemas_fallback.py +175 -0
- abstractnote/gateway/__init__.py +24 -0
- abstractnote/gateway/appspec.py +10 -0
- abstractnote/gateway/router.py +571 -0
- abstractnote/gateway/tools.py +74 -0
- abstractnote/ids.py +177 -0
- abstractnote/importers/__init__.py +142 -0
- abstractnote/importers/__main__.py +3 -0
- abstractnote/importers/agora.py +203 -0
- abstractnote/importers/claude_sessions.py +415 -0
- abstractnote/importers/cli.py +113 -0
- abstractnote/importers/files.py +72 -0
- abstractnote/importers/markdown.py +165 -0
- abstractnote/ingest/__init__.py +7 -0
- abstractnote/ingest/blobs.py +47 -0
- abstractnote/ingest/capture.py +265 -0
- abstractnote/ingest/extract.py +118 -0
- abstractnote/ingest/normalise.py +95 -0
- abstractnote/model.py +229 -0
- abstractnote/process/__init__.py +5 -0
- abstractnote/process/context.py +218 -0
- abstractnote/process/merge.py +60 -0
- abstractnote/process/prompts/__init__.py +39 -0
- abstractnote/process/prompts/extract.md +30 -0
- abstractnote/process/prompts/link.md +32 -0
- abstractnote/process/prompts/mine_dossier.md +13 -0
- abstractnote/process/prompts/mine_findings.md +13 -0
- abstractnote/process/prompts/mine_plan.md +13 -0
- abstractnote/process/prompts/quick_filing.md +23 -0
- abstractnote/process/prompts/reflect.md +10 -0
- abstractnote/process/prompts/synthesize.md +17 -0
- abstractnote/process/resolve.py +96 -0
- abstractnote/process/revise.py +410 -0
- abstractnote/process/schemas.py +377 -0
- abstractnote/process/vocabulary.py +68 -0
- abstractnote/recall/__init__.py +1 -0
- abstractnote/recall/changes.py +191 -0
- abstractnote/recall/expand.py +379 -0
- abstractnote/recall/insights.py +62 -0
- abstractnote/recall/reflect.py +202 -0
- abstractnote/recall/review.py +191 -0
- abstractnote/recall/search.py +205 -0
- abstractnote/recall/shapes.py +52 -0
- abstractnote/recall/then_now.py +100 -0
- abstractnote/store/__init__.py +12 -0
- abstractnote/store/archive.py +166 -0
- abstractnote/store/embeddings.py +275 -0
- abstractnote/store/kg.py +219 -0
- abstractnote/store/notebook.py +829 -0
- abstractnote/store/notes.py +1190 -0
- abstractnote/store/queue.py +159 -0
- abstractnote/store/schema.py +348 -0
- abstractnote/store/vector_index.py +88 -0
- abstractnote/tools/__init__.py +215 -0
- abstractnote/tools/capture.py +121 -0
- abstractnote/tools/context.py +19 -0
- abstractnote/tools/process.py +22 -0
- abstractnote/tools/queue.py +23 -0
- abstractnote/tools/recall.py +64 -0
- abstractnote-0.1.0.dist-info/METADATA +145 -0
- abstractnote-0.1.0.dist-info/RECORD +80 -0
- abstractnote-0.1.0.dist-info/WHEEL +5 -0
- abstractnote-0.1.0.dist-info/entry_points.txt +2 -0
- abstractnote-0.1.0.dist-info/licenses/LICENSE +21 -0
- abstractnote-0.1.0.dist-info/top_level.txt +1 -0
abstractnote/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""AbstractNote: one endpoint for your notes; a temporal semantic knowledge graph of your life.
|
|
2
|
+
|
|
3
|
+
``Notebook.open(dir)`` opens a notebook directory; ``abstractnote.tools.bind(dir)``
|
|
4
|
+
returns the note tools for flows and agents.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def __getattr__(name: str): # lazy: importing abstractnote stays cheap
|
|
11
|
+
if name == "Notebook":
|
|
12
|
+
from .store.notebook import Notebook
|
|
13
|
+
|
|
14
|
+
return Notebook
|
|
15
|
+
raise AttributeError(name)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["Notebook", "__version__"]
|
abstractnote/cli.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""The ``abstractnote`` command line (docs/api.md, "CLI").
|
|
2
|
+
|
|
3
|
+
Every command works on a notebook directory with ``--notebook DIR`` (inside a
|
|
4
|
+
gateway the notebook is ``<gateway data dir>/abstractnote``, or
|
|
5
|
+
``<data dir>/users/<tenant>/<runtime>/abstractnote`` for a user plane).
|
|
6
|
+
``--gateway URL --token T`` client mode is not available yet and says so.
|
|
7
|
+
Output is JSON on stdout. Exit codes: 0 done, 2 bad input, 3 a part of
|
|
8
|
+
AbstractNote that is not installed or not available yet.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
EXIT_OK, EXIT_INPUT, EXIT_GAP = 0, 2, 3
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Gap(RuntimeError):
|
|
23
|
+
"""A piece the command needs is not available (another package part, or client mode)."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _emit(data: Any) -> None:
|
|
27
|
+
print(json.dumps(data, indent=2, ensure_ascii=False, default=str))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _common(p: argparse.ArgumentParser) -> None:
|
|
31
|
+
p.add_argument("--notebook", type=Path, help="notebook directory (created when missing)")
|
|
32
|
+
p.add_argument("--gateway", help="gateway URL (client mode)")
|
|
33
|
+
p.add_argument("--token", help="gateway token, passed directly")
|
|
34
|
+
p.add_argument("--embed-base-url", help="OpenAI-compatible embeddings URL (for example http://127.0.0.1:1234/v1). "
|
|
35
|
+
"A notebook is pinned to its embedding model when it is created (by the first command that "
|
|
36
|
+
"opens it, capture included; without these flags: the built-in hashing embedder); every later "
|
|
37
|
+
"command must give the same model, or run rebuild --reembed to change it")
|
|
38
|
+
p.add_argument("--embed-model", help="embedding model id at that URL (pinned at notebook creation, see --embed-base-url)")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
42
|
+
ap = argparse.ArgumentParser(prog="abstractnote", description="AbstractNote: one endpoint for your notes.")
|
|
43
|
+
sub = ap.add_subparsers(dest="command", required=True)
|
|
44
|
+
|
|
45
|
+
p = sub.add_parser("capture", help="save a note now")
|
|
46
|
+
_common(p)
|
|
47
|
+
p.add_argument("text", nargs="?", default="")
|
|
48
|
+
p.add_argument("--file", action="append", default=[], type=Path, help="attach a file (repeatable)")
|
|
49
|
+
p.add_argument("--at", help="when it happened (ISO 8601; default now)")
|
|
50
|
+
p.add_argument("--title")
|
|
51
|
+
p.add_argument("--priority", type=int, choices=[0, 1, 2, 3])
|
|
52
|
+
p.add_argument("--key", help="idempotency key (a retry with the same key saves nothing new)")
|
|
53
|
+
|
|
54
|
+
p = sub.add_parser("search", help="search the notes")
|
|
55
|
+
_common(p)
|
|
56
|
+
p.add_argument("query")
|
|
57
|
+
p.add_argument("--effort", choices=["quick", "standard", "deep"], default="quick")
|
|
58
|
+
p.add_argument("--since")
|
|
59
|
+
p.add_argument("--until")
|
|
60
|
+
p.add_argument("--as-of")
|
|
61
|
+
p.add_argument("--limit", type=int, default=20)
|
|
62
|
+
p.add_argument("--provider", help="model provider for standard and deep effort")
|
|
63
|
+
p.add_argument("--model", help="model for standard and deep effort")
|
|
64
|
+
|
|
65
|
+
p = sub.add_parser("entity", help="an entity card")
|
|
66
|
+
_common(p)
|
|
67
|
+
p.add_argument("entity_id")
|
|
68
|
+
p.add_argument("--at")
|
|
69
|
+
|
|
70
|
+
p = sub.add_parser("day", help="what happened on a day")
|
|
71
|
+
_common(p)
|
|
72
|
+
p.add_argument("date", help="YYYY-MM-DD")
|
|
73
|
+
p.add_argument("--tz")
|
|
74
|
+
|
|
75
|
+
p = sub.add_parser("status", help="notebook counts, queue and versions")
|
|
76
|
+
_common(p)
|
|
77
|
+
|
|
78
|
+
p = sub.add_parser("changes", help="what changed since a cursor or a time")
|
|
79
|
+
_common(p)
|
|
80
|
+
p.add_argument("--since")
|
|
81
|
+
p.add_argument("--limit", type=int, default=50)
|
|
82
|
+
|
|
83
|
+
p = sub.add_parser("rebuild", help="replay revisions into fresh index and triple stores")
|
|
84
|
+
_common(p)
|
|
85
|
+
p.add_argument("--reembed", action="store_true", help="recompute every vector with the given embedder")
|
|
86
|
+
|
|
87
|
+
p = sub.add_parser("process", help="process queued notes now")
|
|
88
|
+
_common(p)
|
|
89
|
+
p.add_argument("--local", action="store_true", help="run the flows here (headless)")
|
|
90
|
+
p.add_argument("--provider")
|
|
91
|
+
p.add_argument("--model")
|
|
92
|
+
p.add_argument("--max-items", type=int, default=20)
|
|
93
|
+
p.add_argument("--budget-s", type=float, default=90.0)
|
|
94
|
+
|
|
95
|
+
p = sub.add_parser("bundle", help="pack the abstractnote .flow bundle")
|
|
96
|
+
p.add_argument("--out", type=Path, default=Path("."))
|
|
97
|
+
|
|
98
|
+
p = sub.add_parser("processor", help="processor automation")
|
|
99
|
+
_common(p)
|
|
100
|
+
p.add_argument("action", choices=["ensure"])
|
|
101
|
+
|
|
102
|
+
sub.add_parser("import", help="import a corpus (claude-sessions, agora, markdown, files)", add_help=False)
|
|
103
|
+
return ap
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _embedder(args: argparse.Namespace) -> Any:
|
|
107
|
+
if getattr(args, "embed_base_url", None) or getattr(args, "embed_model", None):
|
|
108
|
+
if not (args.embed_base_url and args.embed_model):
|
|
109
|
+
raise ValueError("Give both --embed-base-url and --embed-model.")
|
|
110
|
+
from .store.embeddings import openai_compat_embedder
|
|
111
|
+
|
|
112
|
+
return openai_compat_embedder(args.embed_base_url, args.embed_model)
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _client_mode(args: argparse.Namespace) -> None:
|
|
117
|
+
if getattr(args, "gateway", None):
|
|
118
|
+
raise NotImplementedError(
|
|
119
|
+
"Gateway client mode is not available in this version; run the command on the machine that holds the "
|
|
120
|
+
"notebook with --notebook DIR.")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _open(args: argparse.Namespace) -> Any:
|
|
124
|
+
_client_mode(args)
|
|
125
|
+
if not getattr(args, "notebook", None):
|
|
126
|
+
raise ValueError("Say which notebook with --notebook DIR (a gateway keeps it in <data dir>/abstractnote).")
|
|
127
|
+
from .store.notebook import Notebook
|
|
128
|
+
|
|
129
|
+
return Notebook.open(args.notebook, embedder=_embedder(args))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _cmd_capture(args: argparse.Namespace) -> Any:
|
|
133
|
+
files = []
|
|
134
|
+
for path in args.file:
|
|
135
|
+
if not path.is_file():
|
|
136
|
+
raise ValueError(f"There is no file at {path}.")
|
|
137
|
+
files.append({"data": path.read_bytes(), "filename": path.name})
|
|
138
|
+
with _open(args) as nb:
|
|
139
|
+
return nb.capture(args.text, title=args.title, occurred_at=args.at, priority=args.priority,
|
|
140
|
+
idempotency_key=args.key, attachments=files, source={"client": "cli"}).model_dump()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _runner(args: argparse.Namespace) -> Any:
|
|
144
|
+
try:
|
|
145
|
+
from .flows.runner import NoteRunner
|
|
146
|
+
except ImportError as exc:
|
|
147
|
+
raise Gap(f"The flows are not installed with this AbstractNote ({exc}).") from None
|
|
148
|
+
if not (args.provider and args.model):
|
|
149
|
+
raise ValueError("Give --provider and --model for the model that processes or answers.")
|
|
150
|
+
return NoteRunner.local(args.notebook, provider=args.provider, model=args.model, embedder=_embedder(args))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _cmd_search(args: argparse.Namespace) -> Any:
|
|
154
|
+
if args.effort == "quick":
|
|
155
|
+
with _open(args) as nb:
|
|
156
|
+
return nb.search(args.query, since=args.since, until=args.until, as_of=args.as_of, limit=args.limit)
|
|
157
|
+
_client_mode(args)
|
|
158
|
+
if not args.notebook:
|
|
159
|
+
raise ValueError("Say which notebook with --notebook DIR.")
|
|
160
|
+
return _runner(args).run_recall(args.query, effort=args.effort, since=args.since, until=args.until,
|
|
161
|
+
as_of=args.as_of)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _cmd_process(args: argparse.Namespace) -> Any:
|
|
165
|
+
_client_mode(args)
|
|
166
|
+
if not args.local:
|
|
167
|
+
raise NotImplementedError("Processing through a gateway is not available here; use --local with "
|
|
168
|
+
"--provider and --model, or press Process now in the web app.")
|
|
169
|
+
if not args.notebook:
|
|
170
|
+
raise ValueError("Say which notebook with --notebook DIR.")
|
|
171
|
+
return _runner(args).run_drain(max_items=args.max_items, budget_s=args.budget_s)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _cmd_bundle(args: argparse.Namespace) -> Any:
|
|
175
|
+
try:
|
|
176
|
+
from .flows.bundle import pack
|
|
177
|
+
except ImportError as exc:
|
|
178
|
+
raise Gap(f"The flows are not installed with this AbstractNote ({exc}).") from None
|
|
179
|
+
try:
|
|
180
|
+
return {"bundle": str(pack(args.out, check_fresh=True))}
|
|
181
|
+
except RuntimeError as exc: # stale generated flows: the flows must be rebuilt first
|
|
182
|
+
raise Gap(str(exc)) from None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _cmd_import(argv: List[str]) -> int:
|
|
186
|
+
try:
|
|
187
|
+
from .importers.cli import main as import_main
|
|
188
|
+
except ImportError as exc:
|
|
189
|
+
print(f"The importers are not installed with this AbstractNote ({exc}).", file=sys.stderr)
|
|
190
|
+
return EXIT_GAP
|
|
191
|
+
return int(import_main(argv, prog="abstractnote import") or 0)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
195
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
196
|
+
if argv[:1] == ["import"]:
|
|
197
|
+
return _cmd_import(argv[1:])
|
|
198
|
+
args = build_parser().parse_args(argv)
|
|
199
|
+
try:
|
|
200
|
+
if args.command == "capture":
|
|
201
|
+
out = _cmd_capture(args)
|
|
202
|
+
elif args.command == "search":
|
|
203
|
+
out = _cmd_search(args)
|
|
204
|
+
elif args.command == "entity":
|
|
205
|
+
with _open(args) as nb:
|
|
206
|
+
out = nb.entity(args.entity_id, at=args.at)
|
|
207
|
+
elif args.command == "day":
|
|
208
|
+
with _open(args) as nb:
|
|
209
|
+
out = nb.day(args.date, args.tz)
|
|
210
|
+
elif args.command == "status":
|
|
211
|
+
with _open(args) as nb:
|
|
212
|
+
out = nb.status()
|
|
213
|
+
elif args.command == "changes":
|
|
214
|
+
with _open(args) as nb:
|
|
215
|
+
out = nb.changes(args.since, args.limit)
|
|
216
|
+
elif args.command == "rebuild":
|
|
217
|
+
with _open(args) as nb:
|
|
218
|
+
out = nb.rebuild(reembed=args.reembed, embedder=_embedder(args))
|
|
219
|
+
elif args.command == "process":
|
|
220
|
+
out = _cmd_process(args)
|
|
221
|
+
elif args.command == "bundle":
|
|
222
|
+
out = _cmd_bundle(args)
|
|
223
|
+
elif args.command == "processor":
|
|
224
|
+
_client_mode(args)
|
|
225
|
+
raise NotImplementedError("Creating the processor needs a gateway: open the web app, or use "
|
|
226
|
+
"POST /api/gateway/note/processor/ensure with your token.")
|
|
227
|
+
else: # pragma: no cover - argparse rejects unknown commands
|
|
228
|
+
raise ValueError(f"Unknown command {args.command}.")
|
|
229
|
+
except (NotImplementedError, Gap) as exc:
|
|
230
|
+
print(str(exc), file=sys.stderr)
|
|
231
|
+
return EXIT_GAP
|
|
232
|
+
except ValueError as exc:
|
|
233
|
+
print(str(exc), file=sys.stderr)
|
|
234
|
+
return EXIT_INPUT
|
|
235
|
+
_emit(out)
|
|
236
|
+
return EXIT_OK
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == "__main__": # pragma: no cover
|
|
240
|
+
raise SystemExit(main())
|