abstractnote 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- abstractnote-0.1.0/LICENSE +21 -0
- abstractnote-0.1.0/PKG-INFO +145 -0
- abstractnote-0.1.0/README.md +112 -0
- abstractnote-0.1.0/pyproject.toml +60 -0
- abstractnote-0.1.0/setup.cfg +4 -0
- abstractnote-0.1.0/src/abstractnote/__init__.py +18 -0
- abstractnote-0.1.0/src/abstractnote/cli.py +240 -0
- abstractnote-0.1.0/src/abstractnote/flows/__init__.py +2 -0
- abstractnote-0.1.0/src/abstractnote/flows/build.py +970 -0
- abstractnote-0.1.0/src/abstractnote/flows/bundle.py +62 -0
- abstractnote-0.1.0/src/abstractnote/flows/contract.py +88 -0
- abstractnote-0.1.0/src/abstractnote/flows/graph.py +540 -0
- abstractnote-0.1.0/src/abstractnote/flows/json/note-capture.json +2280 -0
- abstractnote-0.1.0/src/abstractnote/flows/json/note-drain.json +2012 -0
- abstractnote-0.1.0/src/abstractnote/flows/json/note-mine.json +2392 -0
- abstractnote-0.1.0/src/abstractnote/flows/json/note-process.json +1853 -0
- abstractnote-0.1.0/src/abstractnote/flows/json/note-recall.json +1876 -0
- abstractnote-0.1.0/src/abstractnote/flows/json/note-reflect.json +2057 -0
- abstractnote-0.1.0/src/abstractnote/flows/prompts_fallback.py +119 -0
- abstractnote-0.1.0/src/abstractnote/flows/runner.py +320 -0
- abstractnote-0.1.0/src/abstractnote/flows/schemas_fallback.py +175 -0
- abstractnote-0.1.0/src/abstractnote/gateway/__init__.py +24 -0
- abstractnote-0.1.0/src/abstractnote/gateway/appspec.py +10 -0
- abstractnote-0.1.0/src/abstractnote/gateway/router.py +571 -0
- abstractnote-0.1.0/src/abstractnote/gateway/tools.py +74 -0
- abstractnote-0.1.0/src/abstractnote/ids.py +177 -0
- abstractnote-0.1.0/src/abstractnote/importers/__init__.py +142 -0
- abstractnote-0.1.0/src/abstractnote/importers/__main__.py +3 -0
- abstractnote-0.1.0/src/abstractnote/importers/agora.py +203 -0
- abstractnote-0.1.0/src/abstractnote/importers/claude_sessions.py +415 -0
- abstractnote-0.1.0/src/abstractnote/importers/cli.py +113 -0
- abstractnote-0.1.0/src/abstractnote/importers/files.py +72 -0
- abstractnote-0.1.0/src/abstractnote/importers/markdown.py +165 -0
- abstractnote-0.1.0/src/abstractnote/ingest/__init__.py +7 -0
- abstractnote-0.1.0/src/abstractnote/ingest/blobs.py +47 -0
- abstractnote-0.1.0/src/abstractnote/ingest/capture.py +265 -0
- abstractnote-0.1.0/src/abstractnote/ingest/extract.py +118 -0
- abstractnote-0.1.0/src/abstractnote/ingest/normalise.py +95 -0
- abstractnote-0.1.0/src/abstractnote/model.py +229 -0
- abstractnote-0.1.0/src/abstractnote/process/__init__.py +5 -0
- abstractnote-0.1.0/src/abstractnote/process/context.py +218 -0
- abstractnote-0.1.0/src/abstractnote/process/merge.py +60 -0
- abstractnote-0.1.0/src/abstractnote/process/prompts/__init__.py +39 -0
- abstractnote-0.1.0/src/abstractnote/process/prompts/extract.md +30 -0
- abstractnote-0.1.0/src/abstractnote/process/prompts/link.md +32 -0
- abstractnote-0.1.0/src/abstractnote/process/prompts/mine_dossier.md +13 -0
- abstractnote-0.1.0/src/abstractnote/process/prompts/mine_findings.md +13 -0
- abstractnote-0.1.0/src/abstractnote/process/prompts/mine_plan.md +13 -0
- abstractnote-0.1.0/src/abstractnote/process/prompts/quick_filing.md +23 -0
- abstractnote-0.1.0/src/abstractnote/process/prompts/reflect.md +10 -0
- abstractnote-0.1.0/src/abstractnote/process/prompts/synthesize.md +17 -0
- abstractnote-0.1.0/src/abstractnote/process/resolve.py +96 -0
- abstractnote-0.1.0/src/abstractnote/process/revise.py +410 -0
- abstractnote-0.1.0/src/abstractnote/process/schemas.py +377 -0
- abstractnote-0.1.0/src/abstractnote/process/vocabulary.py +68 -0
- abstractnote-0.1.0/src/abstractnote/recall/__init__.py +1 -0
- abstractnote-0.1.0/src/abstractnote/recall/changes.py +191 -0
- abstractnote-0.1.0/src/abstractnote/recall/expand.py +379 -0
- abstractnote-0.1.0/src/abstractnote/recall/insights.py +62 -0
- abstractnote-0.1.0/src/abstractnote/recall/reflect.py +202 -0
- abstractnote-0.1.0/src/abstractnote/recall/review.py +191 -0
- abstractnote-0.1.0/src/abstractnote/recall/search.py +205 -0
- abstractnote-0.1.0/src/abstractnote/recall/shapes.py +52 -0
- abstractnote-0.1.0/src/abstractnote/recall/then_now.py +100 -0
- abstractnote-0.1.0/src/abstractnote/store/__init__.py +12 -0
- abstractnote-0.1.0/src/abstractnote/store/archive.py +166 -0
- abstractnote-0.1.0/src/abstractnote/store/embeddings.py +275 -0
- abstractnote-0.1.0/src/abstractnote/store/kg.py +219 -0
- abstractnote-0.1.0/src/abstractnote/store/notebook.py +829 -0
- abstractnote-0.1.0/src/abstractnote/store/notes.py +1190 -0
- abstractnote-0.1.0/src/abstractnote/store/queue.py +159 -0
- abstractnote-0.1.0/src/abstractnote/store/schema.py +348 -0
- abstractnote-0.1.0/src/abstractnote/store/vector_index.py +88 -0
- abstractnote-0.1.0/src/abstractnote/tools/__init__.py +215 -0
- abstractnote-0.1.0/src/abstractnote/tools/capture.py +121 -0
- abstractnote-0.1.0/src/abstractnote/tools/context.py +19 -0
- abstractnote-0.1.0/src/abstractnote/tools/process.py +22 -0
- abstractnote-0.1.0/src/abstractnote/tools/queue.py +23 -0
- abstractnote-0.1.0/src/abstractnote/tools/recall.py +64 -0
- abstractnote-0.1.0/src/abstractnote.egg-info/PKG-INFO +145 -0
- abstractnote-0.1.0/src/abstractnote.egg-info/SOURCES.txt +102 -0
- abstractnote-0.1.0/src/abstractnote.egg-info/dependency_links.txt +1 -0
- abstractnote-0.1.0/src/abstractnote.egg-info/entry_points.txt +2 -0
- abstractnote-0.1.0/src/abstractnote.egg-info/requires.txt +13 -0
- abstractnote-0.1.0/src/abstractnote.egg-info/top_level.txt +1 -0
- abstractnote-0.1.0/tests/test_capture.py +191 -0
- abstractnote-0.1.0/tests/test_cli.py +95 -0
- abstractnote-0.1.0/tests/test_flows_compile.py +152 -0
- abstractnote-0.1.0/tests/test_flows_contract.py +63 -0
- abstractnote-0.1.0/tests/test_flows_headless.py +499 -0
- abstractnote-0.1.0/tests/test_gateway_router.py +300 -0
- abstractnote-0.1.0/tests/test_gateway_seam_guard.py +45 -0
- abstractnote-0.1.0/tests/test_gateway_w10.py +111 -0
- abstractnote-0.1.0/tests/test_importers.py +412 -0
- abstractnote-0.1.0/tests/test_kg_projection.py +103 -0
- abstractnote-0.1.0/tests/test_process.py +373 -0
- abstractnote-0.1.0/tests/test_process_tools.py +64 -0
- abstractnote-0.1.0/tests/test_queue.py +126 -0
- abstractnote-0.1.0/tests/test_rebuild.py +113 -0
- abstractnote-0.1.0/tests/test_recall.py +280 -0
- abstractnote-0.1.0/tests/test_review_and_archive.py +313 -0
- abstractnote-0.1.0/tests/test_store.py +244 -0
- abstractnote-0.1.0/tests/test_store_support.py +58 -0
- abstractnote-0.1.0/tests/test_stub_llm.py +63 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Laurent-Philippe Albou
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abstractnote
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AbstractNote: one endpoint for your notes; a temporal semantic knowledge graph of your life, built on AbstractGateway, AbstractRuntime and AbstractMemory
|
|
5
|
+
Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/lpalbou/abstractnote
|
|
8
|
+
Project-URL: Repository, https://github.com/lpalbou/abstractnote
|
|
9
|
+
Project-URL: Issues, https://github.com/lpalbou/abstractnote/issues
|
|
10
|
+
Keywords: notes,knowledge-graph,memory,temporal,abstractframework
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: abstractmemory>=0.3.1
|
|
22
|
+
Requires-Dist: abstractsemantics>=0.0.5
|
|
23
|
+
Requires-Dist: abstractruntime>=0.10.2
|
|
24
|
+
Requires-Dist: abstractcore>=2.26.3
|
|
25
|
+
Requires-Dist: pydantic>=2.0
|
|
26
|
+
Provides-Extra: gateway
|
|
27
|
+
Requires-Dist: abstractgateway>=0.15.11; extra == "gateway"
|
|
28
|
+
Requires-Dist: fastapi>=0.110; extra == "gateway"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# AbstractNote
|
|
35
|
+
|
|
36
|
+
One endpoint for everything you want to remember. Send a sentence, a document, a photo, a
|
|
37
|
+
recording, a log. AbstractNote files it with its time, place and provenance, links it to what
|
|
38
|
+
you already know, and grows a **temporal semantic knowledge graph** of your ideas, projects,
|
|
39
|
+
people and decisions. You search it by effort (quick, standard, deep) and revisit it through
|
|
40
|
+
time: what you knew, when you learned it, and how your view changed.
|
|
41
|
+
|
|
42
|
+
AbstractNote is part of the AbstractFramework. It runs on **AbstractGateway** (sign-in, one
|
|
43
|
+
runtime and one notebook per user, app hosting), **AbstractRuntime** and **AbstractFlow** (the
|
|
44
|
+
capture and understanding workflows), **AbstractMemory** (temporal facts) and **AbstractCore**
|
|
45
|
+
(models, structured output, embeddings).
|
|
46
|
+
|
|
47
|
+
Status: version 0.1.0, in development. It is not published on PyPI or npm yet; you install it
|
|
48
|
+
from source (see [Install](#install)).
|
|
49
|
+
|
|
50
|
+
## What you get
|
|
51
|
+
|
|
52
|
+
- **Capture anywhere**: the web app, the Apple Watch app, the `abstractnote` command line, and
|
|
53
|
+
agents. Attachments are de-duplicated by content; a retried send never saves a note twice.
|
|
54
|
+
- **Automatic filing**: a small fast model gives each note a title, a kind, topics, an urgency
|
|
55
|
+
and its language, next to the time and place it was made.
|
|
56
|
+
- **Self-organising knowledge**: a model resolves people, projects and ideas against candidates
|
|
57
|
+
that exact lookups, full-text and vectors produce. Every change is a revision you can replay,
|
|
58
|
+
and every claim keeps the note it came from.
|
|
59
|
+
- **Remember by cue and effort**: quick (no model, under a second), standard (one synthesis with
|
|
60
|
+
citations), deep (a background dossier). Then-versus-now for any person, project, idea or link.
|
|
61
|
+
- **Reflection**: open loops, contradictions, things that went quiet and connections you may have
|
|
62
|
+
missed, surfaced as insights.
|
|
63
|
+
- **Imports**: Claude Code sessions, an agora hub database, Markdown trees and plain files, with
|
|
64
|
+
their original dates and provenance.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
AbstractNote needs Python 3.10 or later and the framework packages next to it. Until the
|
|
69
|
+
packages are published, install everything from source checkouts:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
python3 -m venv .venv
|
|
73
|
+
.venv/bin/pip install -e ../abstractsemantics -e ../abstractcore -e ../abstractruntime \
|
|
74
|
+
-e ../abstractmemory # a checkout that has close_assertion, see below
|
|
75
|
+
.venv/bin/pip install -e .
|
|
76
|
+
.venv/bin/abstractnote --help
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Two framework changes that AbstractNote relies on are not in released packages yet:
|
|
80
|
+
|
|
81
|
+
- **AbstractMemory `close_assertion`** (the `close-assertion` branch): closes a superseded fact in
|
|
82
|
+
place. AbstractNote's first release waits for the AbstractMemory release that contains it.
|
|
83
|
+
- **The AbstractGateway integration** (the `abstractnote-seam` branch): binds the note tools to
|
|
84
|
+
each user's notebook, serves the note read API and lists the Note app. You need it only for the
|
|
85
|
+
gateway path; the headless notebook works without a gateway. The gateway must carry this seam
|
|
86
|
+
(`abstractgateway.abstractnote_seam`; the first gateway release with it is named at
|
|
87
|
+
AbstractNote's release): the `[gateway]` extra alone does not guarantee it, and
|
|
88
|
+
`abstractnote.gateway` refuses to import into a gateway without it, with one sentence.
|
|
89
|
+
|
|
90
|
+
To run models locally on Apple silicon, add `pip install "abstractcore[apple]"` (MLX).
|
|
91
|
+
|
|
92
|
+
## Quick start
|
|
93
|
+
|
|
94
|
+
A headless notebook on this machine, no gateway and no model:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
abstractnote capture "Met Dana about the LUT budget; she wants the Pareto by Friday." --notebook ~/notes
|
|
98
|
+
abstractnote search "LUT budget" --notebook ~/notes
|
|
99
|
+
abstractnote status --notebook ~/notes
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Let a model understand the queued notes (entities, claims, tasks), then ask a question:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
abstractnote process --local --notebook ~/notes --provider mlx --model mlx-community/Qwen3.6-35B-A3B-4bit
|
|
106
|
+
abstractnote search "What does Dana want?" --effort standard --notebook ~/notes \
|
|
107
|
+
--provider mlx --model mlx-community/Qwen3.6-35B-A3B-4bit
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
A notebook is pinned to the embedding model that created it. Choose it on the first command
|
|
111
|
+
(`--embed-base-url` and `--embed-model`, for example a local LM Studio embedding model) and give
|
|
112
|
+
the same flags every time, or start with the built-in offline embedder and change later with
|
|
113
|
+
`abstractnote rebuild --reembed`. See [docs/getting-started.md](docs/getting-started.md).
|
|
114
|
+
|
|
115
|
+
Through a gateway, clients start the `note-capture` and `note-recall` flows with the gateway's
|
|
116
|
+
runs API, and `POST /api/gateway/note/processor/ensure` creates the per-user processor that
|
|
117
|
+
understands notes in the background. The getting-started guide walks through a private scratch
|
|
118
|
+
gateway, the web app and the watch app.
|
|
119
|
+
|
|
120
|
+
## Clients
|
|
121
|
+
|
|
122
|
+
| Client | Where | What it does |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| Web app | [`web/`](web/) (`@abstractframework/note`) | Capture (a chat with attachments), Graph (entities and links through time), Remember (since you last looked, cue cards, ask by effort, then and now), Settings |
|
|
125
|
+
| Apple Watch app | [`watch/apple/`](watch/apple/README.md) | Note, Ask, Today, Settings; watch-only, watchOS 26 or later |
|
|
126
|
+
| Command line | `abstractnote` | Headless capture, search, processing, imports, rebuild, JSON output for scripts and agents |
|
|
127
|
+
|
|
128
|
+
An iPhone app is planned ([`phone/iphone/`](phone/iphone/README.md)). Agents use the same note
|
|
129
|
+
tools and flows, with the effort level as a parameter.
|
|
130
|
+
|
|
131
|
+
## Documentation
|
|
132
|
+
|
|
133
|
+
- [docs/README.md](docs/README.md): the documentation index
|
|
134
|
+
- [Getting started](docs/getting-started.md): a headless notebook in five minutes, then the gateway path
|
|
135
|
+
- [Architecture](docs/architecture.md): components, data plane, request lifecycles
|
|
136
|
+
- [API](docs/api.md): runs API usage, note read API, note tools, Python API, CLI
|
|
137
|
+
- [Data model](docs/data-model.md), [Cognition](docs/cognition.md), [Remembering](docs/remembering.md),
|
|
138
|
+
[Clients](docs/clients.md), [Importers](docs/importers.md)
|
|
139
|
+
- [FAQ](docs/faq.md) and [Troubleshooting](docs/troubleshooting.md)
|
|
140
|
+
- [Contributing](CONTRIBUTING.md), [Security](SECURITY.md), [Changelog](CHANGELOG.md),
|
|
141
|
+
[Acknowledgements](ACKNOWLEDGEMENTS.md)
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# AbstractNote
|
|
2
|
+
|
|
3
|
+
One endpoint for everything you want to remember. Send a sentence, a document, a photo, a
|
|
4
|
+
recording, a log. AbstractNote files it with its time, place and provenance, links it to what
|
|
5
|
+
you already know, and grows a **temporal semantic knowledge graph** of your ideas, projects,
|
|
6
|
+
people and decisions. You search it by effort (quick, standard, deep) and revisit it through
|
|
7
|
+
time: what you knew, when you learned it, and how your view changed.
|
|
8
|
+
|
|
9
|
+
AbstractNote is part of the AbstractFramework. It runs on **AbstractGateway** (sign-in, one
|
|
10
|
+
runtime and one notebook per user, app hosting), **AbstractRuntime** and **AbstractFlow** (the
|
|
11
|
+
capture and understanding workflows), **AbstractMemory** (temporal facts) and **AbstractCore**
|
|
12
|
+
(models, structured output, embeddings).
|
|
13
|
+
|
|
14
|
+
Status: version 0.1.0, in development. It is not published on PyPI or npm yet; you install it
|
|
15
|
+
from source (see [Install](#install)).
|
|
16
|
+
|
|
17
|
+
## What you get
|
|
18
|
+
|
|
19
|
+
- **Capture anywhere**: the web app, the Apple Watch app, the `abstractnote` command line, and
|
|
20
|
+
agents. Attachments are de-duplicated by content; a retried send never saves a note twice.
|
|
21
|
+
- **Automatic filing**: a small fast model gives each note a title, a kind, topics, an urgency
|
|
22
|
+
and its language, next to the time and place it was made.
|
|
23
|
+
- **Self-organising knowledge**: a model resolves people, projects and ideas against candidates
|
|
24
|
+
that exact lookups, full-text and vectors produce. Every change is a revision you can replay,
|
|
25
|
+
and every claim keeps the note it came from.
|
|
26
|
+
- **Remember by cue and effort**: quick (no model, under a second), standard (one synthesis with
|
|
27
|
+
citations), deep (a background dossier). Then-versus-now for any person, project, idea or link.
|
|
28
|
+
- **Reflection**: open loops, contradictions, things that went quiet and connections you may have
|
|
29
|
+
missed, surfaced as insights.
|
|
30
|
+
- **Imports**: Claude Code sessions, an agora hub database, Markdown trees and plain files, with
|
|
31
|
+
their original dates and provenance.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
AbstractNote needs Python 3.10 or later and the framework packages next to it. Until the
|
|
36
|
+
packages are published, install everything from source checkouts:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
python3 -m venv .venv
|
|
40
|
+
.venv/bin/pip install -e ../abstractsemantics -e ../abstractcore -e ../abstractruntime \
|
|
41
|
+
-e ../abstractmemory # a checkout that has close_assertion, see below
|
|
42
|
+
.venv/bin/pip install -e .
|
|
43
|
+
.venv/bin/abstractnote --help
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Two framework changes that AbstractNote relies on are not in released packages yet:
|
|
47
|
+
|
|
48
|
+
- **AbstractMemory `close_assertion`** (the `close-assertion` branch): closes a superseded fact in
|
|
49
|
+
place. AbstractNote's first release waits for the AbstractMemory release that contains it.
|
|
50
|
+
- **The AbstractGateway integration** (the `abstractnote-seam` branch): binds the note tools to
|
|
51
|
+
each user's notebook, serves the note read API and lists the Note app. You need it only for the
|
|
52
|
+
gateway path; the headless notebook works without a gateway. The gateway must carry this seam
|
|
53
|
+
(`abstractgateway.abstractnote_seam`; the first gateway release with it is named at
|
|
54
|
+
AbstractNote's release): the `[gateway]` extra alone does not guarantee it, and
|
|
55
|
+
`abstractnote.gateway` refuses to import into a gateway without it, with one sentence.
|
|
56
|
+
|
|
57
|
+
To run models locally on Apple silicon, add `pip install "abstractcore[apple]"` (MLX).
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
A headless notebook on this machine, no gateway and no model:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
abstractnote capture "Met Dana about the LUT budget; she wants the Pareto by Friday." --notebook ~/notes
|
|
65
|
+
abstractnote search "LUT budget" --notebook ~/notes
|
|
66
|
+
abstractnote status --notebook ~/notes
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Let a model understand the queued notes (entities, claims, tasks), then ask a question:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
abstractnote process --local --notebook ~/notes --provider mlx --model mlx-community/Qwen3.6-35B-A3B-4bit
|
|
73
|
+
abstractnote search "What does Dana want?" --effort standard --notebook ~/notes \
|
|
74
|
+
--provider mlx --model mlx-community/Qwen3.6-35B-A3B-4bit
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
A notebook is pinned to the embedding model that created it. Choose it on the first command
|
|
78
|
+
(`--embed-base-url` and `--embed-model`, for example a local LM Studio embedding model) and give
|
|
79
|
+
the same flags every time, or start with the built-in offline embedder and change later with
|
|
80
|
+
`abstractnote rebuild --reembed`. See [docs/getting-started.md](docs/getting-started.md).
|
|
81
|
+
|
|
82
|
+
Through a gateway, clients start the `note-capture` and `note-recall` flows with the gateway's
|
|
83
|
+
runs API, and `POST /api/gateway/note/processor/ensure` creates the per-user processor that
|
|
84
|
+
understands notes in the background. The getting-started guide walks through a private scratch
|
|
85
|
+
gateway, the web app and the watch app.
|
|
86
|
+
|
|
87
|
+
## Clients
|
|
88
|
+
|
|
89
|
+
| Client | Where | What it does |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| Web app | [`web/`](web/) (`@abstractframework/note`) | Capture (a chat with attachments), Graph (entities and links through time), Remember (since you last looked, cue cards, ask by effort, then and now), Settings |
|
|
92
|
+
| Apple Watch app | [`watch/apple/`](watch/apple/README.md) | Note, Ask, Today, Settings; watch-only, watchOS 26 or later |
|
|
93
|
+
| Command line | `abstractnote` | Headless capture, search, processing, imports, rebuild, JSON output for scripts and agents |
|
|
94
|
+
|
|
95
|
+
An iPhone app is planned ([`phone/iphone/`](phone/iphone/README.md)). Agents use the same note
|
|
96
|
+
tools and flows, with the effort level as a parameter.
|
|
97
|
+
|
|
98
|
+
## Documentation
|
|
99
|
+
|
|
100
|
+
- [docs/README.md](docs/README.md): the documentation index
|
|
101
|
+
- [Getting started](docs/getting-started.md): a headless notebook in five minutes, then the gateway path
|
|
102
|
+
- [Architecture](docs/architecture.md): components, data plane, request lifecycles
|
|
103
|
+
- [API](docs/api.md): runs API usage, note read API, note tools, Python API, CLI
|
|
104
|
+
- [Data model](docs/data-model.md), [Cognition](docs/cognition.md), [Remembering](docs/remembering.md),
|
|
105
|
+
[Clients](docs/clients.md), [Importers](docs/importers.md)
|
|
106
|
+
- [FAQ](docs/faq.md) and [Troubleshooting](docs/troubleshooting.md)
|
|
107
|
+
- [Contributing](CONTRIBUTING.md), [Security](SECURITY.md), [Changelog](CHANGELOG.md),
|
|
108
|
+
[Acknowledgements](ACKNOWLEDGEMENTS.md)
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "abstractnote"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AbstractNote: one endpoint for your notes; a temporal semantic knowledge graph of your life, built on AbstractGateway, AbstractRuntime and AbstractMemory"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{name = "Laurent-Philippe Albou", email = "contact@abstractcore.ai"}]
|
|
13
|
+
keywords = ["notes", "knowledge-graph", "memory", "temporal", "abstractframework"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: End Users/Desktop",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"abstractmemory>=0.3.1",
|
|
25
|
+
"abstractsemantics>=0.0.5",
|
|
26
|
+
"abstractruntime>=0.10.2",
|
|
27
|
+
"abstractcore>=2.26.3",
|
|
28
|
+
"pydantic>=2.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
# The gateway must carry the AbstractNote seam (abstractgateway.abstractnote_seam); no released
|
|
33
|
+
# AbstractGateway has it yet, so this floor is raised to the first release that does when
|
|
34
|
+
# AbstractNote is released. Until then abstractnote.gateway refuses to import without the seam.
|
|
35
|
+
gateway = ["abstractgateway>=0.15.11", "fastapi>=0.110"]
|
|
36
|
+
dev = ["pytest>=7.0", "ruff>=0.1"]
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
abstractnote = "abstractnote.cli:main"
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/lpalbou/abstractnote"
|
|
43
|
+
Repository = "https://github.com/lpalbou/abstractnote"
|
|
44
|
+
Issues = "https://github.com/lpalbou/abstractnote/issues"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.package-dir]
|
|
47
|
+
"" = "src"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.packages.find]
|
|
50
|
+
where = ["src"]
|
|
51
|
+
include = ["abstractnote*"]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.package-data]
|
|
54
|
+
abstractnote = ["flows/json/*.json", "process/prompts/*.md"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff]
|
|
57
|
+
line-length = 110
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
testpaths = ["tests"]
|
|
@@ -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__"]
|
|
@@ -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())
|