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.
Files changed (104) hide show
  1. abstractnote-0.1.0/LICENSE +21 -0
  2. abstractnote-0.1.0/PKG-INFO +145 -0
  3. abstractnote-0.1.0/README.md +112 -0
  4. abstractnote-0.1.0/pyproject.toml +60 -0
  5. abstractnote-0.1.0/setup.cfg +4 -0
  6. abstractnote-0.1.0/src/abstractnote/__init__.py +18 -0
  7. abstractnote-0.1.0/src/abstractnote/cli.py +240 -0
  8. abstractnote-0.1.0/src/abstractnote/flows/__init__.py +2 -0
  9. abstractnote-0.1.0/src/abstractnote/flows/build.py +970 -0
  10. abstractnote-0.1.0/src/abstractnote/flows/bundle.py +62 -0
  11. abstractnote-0.1.0/src/abstractnote/flows/contract.py +88 -0
  12. abstractnote-0.1.0/src/abstractnote/flows/graph.py +540 -0
  13. abstractnote-0.1.0/src/abstractnote/flows/json/note-capture.json +2280 -0
  14. abstractnote-0.1.0/src/abstractnote/flows/json/note-drain.json +2012 -0
  15. abstractnote-0.1.0/src/abstractnote/flows/json/note-mine.json +2392 -0
  16. abstractnote-0.1.0/src/abstractnote/flows/json/note-process.json +1853 -0
  17. abstractnote-0.1.0/src/abstractnote/flows/json/note-recall.json +1876 -0
  18. abstractnote-0.1.0/src/abstractnote/flows/json/note-reflect.json +2057 -0
  19. abstractnote-0.1.0/src/abstractnote/flows/prompts_fallback.py +119 -0
  20. abstractnote-0.1.0/src/abstractnote/flows/runner.py +320 -0
  21. abstractnote-0.1.0/src/abstractnote/flows/schemas_fallback.py +175 -0
  22. abstractnote-0.1.0/src/abstractnote/gateway/__init__.py +24 -0
  23. abstractnote-0.1.0/src/abstractnote/gateway/appspec.py +10 -0
  24. abstractnote-0.1.0/src/abstractnote/gateway/router.py +571 -0
  25. abstractnote-0.1.0/src/abstractnote/gateway/tools.py +74 -0
  26. abstractnote-0.1.0/src/abstractnote/ids.py +177 -0
  27. abstractnote-0.1.0/src/abstractnote/importers/__init__.py +142 -0
  28. abstractnote-0.1.0/src/abstractnote/importers/__main__.py +3 -0
  29. abstractnote-0.1.0/src/abstractnote/importers/agora.py +203 -0
  30. abstractnote-0.1.0/src/abstractnote/importers/claude_sessions.py +415 -0
  31. abstractnote-0.1.0/src/abstractnote/importers/cli.py +113 -0
  32. abstractnote-0.1.0/src/abstractnote/importers/files.py +72 -0
  33. abstractnote-0.1.0/src/abstractnote/importers/markdown.py +165 -0
  34. abstractnote-0.1.0/src/abstractnote/ingest/__init__.py +7 -0
  35. abstractnote-0.1.0/src/abstractnote/ingest/blobs.py +47 -0
  36. abstractnote-0.1.0/src/abstractnote/ingest/capture.py +265 -0
  37. abstractnote-0.1.0/src/abstractnote/ingest/extract.py +118 -0
  38. abstractnote-0.1.0/src/abstractnote/ingest/normalise.py +95 -0
  39. abstractnote-0.1.0/src/abstractnote/model.py +229 -0
  40. abstractnote-0.1.0/src/abstractnote/process/__init__.py +5 -0
  41. abstractnote-0.1.0/src/abstractnote/process/context.py +218 -0
  42. abstractnote-0.1.0/src/abstractnote/process/merge.py +60 -0
  43. abstractnote-0.1.0/src/abstractnote/process/prompts/__init__.py +39 -0
  44. abstractnote-0.1.0/src/abstractnote/process/prompts/extract.md +30 -0
  45. abstractnote-0.1.0/src/abstractnote/process/prompts/link.md +32 -0
  46. abstractnote-0.1.0/src/abstractnote/process/prompts/mine_dossier.md +13 -0
  47. abstractnote-0.1.0/src/abstractnote/process/prompts/mine_findings.md +13 -0
  48. abstractnote-0.1.0/src/abstractnote/process/prompts/mine_plan.md +13 -0
  49. abstractnote-0.1.0/src/abstractnote/process/prompts/quick_filing.md +23 -0
  50. abstractnote-0.1.0/src/abstractnote/process/prompts/reflect.md +10 -0
  51. abstractnote-0.1.0/src/abstractnote/process/prompts/synthesize.md +17 -0
  52. abstractnote-0.1.0/src/abstractnote/process/resolve.py +96 -0
  53. abstractnote-0.1.0/src/abstractnote/process/revise.py +410 -0
  54. abstractnote-0.1.0/src/abstractnote/process/schemas.py +377 -0
  55. abstractnote-0.1.0/src/abstractnote/process/vocabulary.py +68 -0
  56. abstractnote-0.1.0/src/abstractnote/recall/__init__.py +1 -0
  57. abstractnote-0.1.0/src/abstractnote/recall/changes.py +191 -0
  58. abstractnote-0.1.0/src/abstractnote/recall/expand.py +379 -0
  59. abstractnote-0.1.0/src/abstractnote/recall/insights.py +62 -0
  60. abstractnote-0.1.0/src/abstractnote/recall/reflect.py +202 -0
  61. abstractnote-0.1.0/src/abstractnote/recall/review.py +191 -0
  62. abstractnote-0.1.0/src/abstractnote/recall/search.py +205 -0
  63. abstractnote-0.1.0/src/abstractnote/recall/shapes.py +52 -0
  64. abstractnote-0.1.0/src/abstractnote/recall/then_now.py +100 -0
  65. abstractnote-0.1.0/src/abstractnote/store/__init__.py +12 -0
  66. abstractnote-0.1.0/src/abstractnote/store/archive.py +166 -0
  67. abstractnote-0.1.0/src/abstractnote/store/embeddings.py +275 -0
  68. abstractnote-0.1.0/src/abstractnote/store/kg.py +219 -0
  69. abstractnote-0.1.0/src/abstractnote/store/notebook.py +829 -0
  70. abstractnote-0.1.0/src/abstractnote/store/notes.py +1190 -0
  71. abstractnote-0.1.0/src/abstractnote/store/queue.py +159 -0
  72. abstractnote-0.1.0/src/abstractnote/store/schema.py +348 -0
  73. abstractnote-0.1.0/src/abstractnote/store/vector_index.py +88 -0
  74. abstractnote-0.1.0/src/abstractnote/tools/__init__.py +215 -0
  75. abstractnote-0.1.0/src/abstractnote/tools/capture.py +121 -0
  76. abstractnote-0.1.0/src/abstractnote/tools/context.py +19 -0
  77. abstractnote-0.1.0/src/abstractnote/tools/process.py +22 -0
  78. abstractnote-0.1.0/src/abstractnote/tools/queue.py +23 -0
  79. abstractnote-0.1.0/src/abstractnote/tools/recall.py +64 -0
  80. abstractnote-0.1.0/src/abstractnote.egg-info/PKG-INFO +145 -0
  81. abstractnote-0.1.0/src/abstractnote.egg-info/SOURCES.txt +102 -0
  82. abstractnote-0.1.0/src/abstractnote.egg-info/dependency_links.txt +1 -0
  83. abstractnote-0.1.0/src/abstractnote.egg-info/entry_points.txt +2 -0
  84. abstractnote-0.1.0/src/abstractnote.egg-info/requires.txt +13 -0
  85. abstractnote-0.1.0/src/abstractnote.egg-info/top_level.txt +1 -0
  86. abstractnote-0.1.0/tests/test_capture.py +191 -0
  87. abstractnote-0.1.0/tests/test_cli.py +95 -0
  88. abstractnote-0.1.0/tests/test_flows_compile.py +152 -0
  89. abstractnote-0.1.0/tests/test_flows_contract.py +63 -0
  90. abstractnote-0.1.0/tests/test_flows_headless.py +499 -0
  91. abstractnote-0.1.0/tests/test_gateway_router.py +300 -0
  92. abstractnote-0.1.0/tests/test_gateway_seam_guard.py +45 -0
  93. abstractnote-0.1.0/tests/test_gateway_w10.py +111 -0
  94. abstractnote-0.1.0/tests/test_importers.py +412 -0
  95. abstractnote-0.1.0/tests/test_kg_projection.py +103 -0
  96. abstractnote-0.1.0/tests/test_process.py +373 -0
  97. abstractnote-0.1.0/tests/test_process_tools.py +64 -0
  98. abstractnote-0.1.0/tests/test_queue.py +126 -0
  99. abstractnote-0.1.0/tests/test_rebuild.py +113 -0
  100. abstractnote-0.1.0/tests/test_recall.py +280 -0
  101. abstractnote-0.1.0/tests/test_review_and_archive.py +313 -0
  102. abstractnote-0.1.0/tests/test_store.py +244 -0
  103. abstractnote-0.1.0/tests/test_store_support.py +58 -0
  104. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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())
@@ -0,0 +1,2 @@
1
+ """AbstractNote flows: the VisualFlow generator (``build``), the bundle packer (``bundle``)
2
+ and the headless runner (``runner``)."""