diarium 0.1.0

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.
package/FLOW.md ADDED
@@ -0,0 +1,209 @@
1
+ # The flow
2
+
3
+ Every command output below is real, copied from a live run against a repo with a bd tracker and a GitHub tracker — not
4
+ an illustration.
5
+
6
+ ## Install
7
+
8
+ ```
9
+ npm i -D diarium
10
+ ```
11
+
12
+ First run creates a `diarium/` folder next to your code and generates a signing key into `.env`:
13
+
14
+ ```
15
+ diarium/
16
+ rules.md prose. The agent reads it before every entry. Yours to rewrite — we do not care how you word it.
17
+ settings.json structure. Code reads it: which trackers to watch, where the cursors are.
18
+ .pending/ closures that owe an entry
19
+ *.ust.json one sealed entry per file
20
+ ```
21
+
22
+ The key lives in `.env` as `DIARIUM_SEED` and is never printed — not even by the tool that made it. Lose it and the
23
+ chain can no longer be extended, though every existing entry still verifies. Leak it and someone else can write entries
24
+ as you, and every one of those will verify. So the tool checks, on every run, whether git actually ignores `.env`:
25
+
26
+ ```
27
+ · generated a signing key into .env — gitignore it. The seed is never printed, not even here.
28
+ ! .env is NOT gitignored — add it now. The seed in there is the identity of this store;
29
+ anyone who has it can write entries as you, and every one of them will verify.
30
+ ```
31
+
32
+ The key is generated at install rather than at the first entry, so that warning arrives while you are setting up — not
33
+ half an hour later, mid-flow, when the store already exists.
34
+
35
+ ## Wire it to your agent
36
+
37
+ There is no plugin, no MCP server, no GitHub Action. Your agent already runs shell commands, so one paragraph in
38
+ whatever file it reads as standing instructions — `CLAUDE.md`, `AGENTS.md`, a Cursor rule — is the whole integration.
39
+ The suggested wording is in `INTEGRATE.md`.
40
+
41
+ ## The loop
42
+
43
+ ### 1. Start of a session — what did I learn last time
44
+
45
+ ```
46
+ $ diarium read --depth 3
47
+ walking back 3 hop(s) from the head of 9 entries
48
+
49
+ ── ust:20260725.154843 · task diarium-ixr · 20260725.154843_diarium-ixr_be8ef642.ust.json
50
+ Nothing here to learn: this task existed only to prove the trigger fires on a real closure. Recording that
51
+ plainly rather than inventing an insight.
52
+ ```
53
+
54
+ It walks the chain N hops back from the head rather than loading the whole corpus, so a store with a thousand entries
55
+ costs the same to read as one with three.
56
+
57
+ ### 2. Work. Close a task — in whichever tracker you use
58
+
59
+ ```
60
+ $ bd close UST-l0a
61
+ ```
62
+ or
63
+ ```
64
+ $ gh issue close 90
65
+ ```
66
+
67
+ Nothing special: close it the way you always do.
68
+
69
+ ### 3. `scan` — what closed since last time
70
+
71
+ ```
72
+ $ diarium scan
73
+ bd:..: 1 closure(s) since 2026-07-25T15:56
74
+ github:thelabmd/UST-Protocol: 0 closure(s) since 2026-07-25T15:56
75
+
76
+ 1 new obligation(s) — run status
77
+ ```
78
+
79
+ Both trackers answer the same question — *what closed after this cursor* — so adding a tracker is an adapter, not an
80
+ integration. A tracker that is unreachable says so and its cursor is left untouched, so nothing is skipped quietly.
81
+
82
+ **The very first scan sets a baseline instead of a backlog:**
83
+
84
+ ```
85
+ bd:..: BASELINE set — 79 historical closure(s) skipped (a memory you did not live is an invention, not a memory)
86
+ ```
87
+
88
+ Asking an agent to write seventy-nine entries about work it does not remember would manufacture recollection. What is
89
+ skipped is printed, not dropped in silence.
90
+
91
+ ### 4. `status` — what owes an entry
92
+
93
+ ```
94
+ $ diarium status
95
+ store: 9 entries · cap 560 · diarium/
96
+ ✗ 1 closure(s) owe an entry — a closure without an entry is what this catches:
97
+ UST-l0a done 2026-07-25T16:09 flow doc demo — a real closure in this repo tracker
98
+ ```
99
+
100
+ Exit code is 1 while anything is owed, so it can gate a commit if you want it to.
101
+
102
+ This is the load-bearing part of the design: **the agent does not choose when to write.** A closure creates the
103
+ obligation; only the agent can discharge it. It cannot skip a closure it would rather not record, and it cannot bury
104
+ one entry under ten others.
105
+
106
+ ### 5. The agent writes the recap
107
+
108
+ What it should write: **what it understood and what it learned** — not what it did. Git already keeps what it did, and
109
+ keeps it better.
110
+
111
+ If it no longer holds the task, the honest entry is exactly that, and it is a first-class record:
112
+
113
+ ```
114
+ $ diarium write UST-l0a recap.md --nothing-learned
115
+ ```
116
+
117
+ Absence of a result is a result. It is recorded structurally rather than left inside a sentence, so a corpus that is
118
+ mostly *nothing learned* can be counted — and that count tells you something about the work, or about the trigger.
119
+
120
+ ```
121
+ $ diarium write UST-l0a recap.md
122
+ ✓ entry sealed + stored
123
+ file : 20260725.160942_UST-l0a_6e96103b.ust.json
124
+ task : UST-l0a (tracker-local)
125
+ content_hash: sha256:6e96103b582a92dc7312ccea9d86d03adc186c818f76bde8da51569beddaf975
126
+ prev : sha256:be8ef64235d1919de4bd88f2d78c32b27c2373328fbd83c8fa8e44d705d4dcae
127
+ ```
128
+
129
+ The entry is verified **before** it is stored, chained to the head of the chain (not to the last filename), and the
130
+ obligation is discharged. The cap declared in `rules.md` is enforced here; everything else in that file the tool can
131
+ only ask for, which is honest as long as asking is not dressed up as enforcement.
132
+
133
+ ```
134
+ $ diarium status
135
+ store: 10 entries · cap 560 · diarium/
136
+ ✓ no closure is waiting for an entry
137
+ ```
138
+
139
+ ### 6. `verify` — nobody rewrote anything
140
+
141
+ ```
142
+ $ diarium verify
143
+ ✓ 10 entries: every seal verifies, one genesis, one head, no fork, no orphan
144
+ (order from the chain — filenames are cosmetic)
145
+ ```
146
+
147
+ This is the only reason the entries are UST documents rather than lines in a text file. It catches, with the sealed
148
+ bytes alone:
149
+
150
+ | what happened | what verify says |
151
+ | --- | --- |
152
+ | an entry's text was edited | that seal does not verify |
153
+ | an entry was deleted from the middle | no genesis, and a `prev` that resolves to nothing |
154
+ | entries were reordered by renaming | nothing — names carry no meaning, and that is by design |
155
+ | an entry was duplicated | a fork: two entries claim the same `prev` |
156
+ | the whole folder was moved | nothing — a seal is not bound to where it is stored |
157
+
158
+ An entry cannot be rewritten afterwards **including by the agent that wrote it**, and anyone can check that offline
159
+ without trusting you. Hand someone a single file and they can verify it with the public web verifier or `npm i
160
+ ust-protocol` — no server, no account, no call back to us.
161
+
162
+ ## What a stored entry actually is
163
+
164
+ A plain UST document. Not a diarium format:
165
+
166
+ ```json
167
+ {
168
+ "ust": "1.0",
169
+ "state": {
170
+ "id": { "domain_shard": "sha256:9075…", "ust_id": "ust:20260725.160942",
171
+ "key_id": "sha256:9075…", "class": "observation" },
172
+ "time": { "generated_at": "2026-07-25T16:09:42Z", "valid_from": "…", "valid_to": "…" },
173
+ "data": { "entry": { "kind": "captured", "value": {
174
+ "text": "…the recap…",
175
+ "task": { "ref": "thelabmd/UST-Protocol#91", "id": "4975299807",
176
+ "node_id": "I_kwDO…", "source": "github",
177
+ "closed_at": "2026-07-25T16:09:12Z" } } } },
178
+ "hashes": { "entry": "sha256:aaa7…" },
179
+ "provenance": { "prev": "sha256:be8e…" }
180
+ },
181
+ "sig": { "alg": "Ed25519", "key_id": "sha256:9075…", "pub": "56tM…", "sig": "pIsn…" }
182
+ }
183
+ ```
184
+
185
+ Two details worth knowing:
186
+
187
+ **The task reference carries a durable id.** `owner/repo#N` is a path, and paths go stale — rename or transfer the repo
188
+ and the reference inside an already-sealed entry becomes unresolvable, with no way to fix it. So the seal carries the
189
+ readable ref *and* the identifier that survives a rename. Where no global id exists — bd, any local tracker — that is
190
+ recorded as `source: tracker-local` rather than implied, and nothing is invented when a lookup fails.
191
+
192
+ **Lived and reconstructed are told apart by evidence, not by claim.** The seal carries the closure time next to the
193
+ write time, so the interval is observable: an entry written three minutes after the closure was lived, one written
194
+ three days later was reconstructed. Nobody declares which — declaring your own reliability is exactly the self-asserted
195
+ assurance this design refuses everywhere else.
196
+
197
+ ## Filenames
198
+
199
+ ```
200
+ 20260725.160942_UST-l0a_6e96103b.ust.json
201
+ └── address ──┘ └ task ┘ └ hash ┘
202
+ ```
203
+
204
+ Underscore separates fields and hyphen lives inside them, because repo names carry hyphens (`UST-Protocol`) and a
205
+ separator that can also appear in the data is not a separator. The address has no `ust:` prefix because a colon cannot
206
+ be checked out on Windows.
207
+
208
+ None of this carries meaning. Order comes from the chain, identity from the `content_hash` — rename every file and
209
+ `verify` stays green. The name is there so a human can find something by eye.
package/INTEGRATE.md ADDED
@@ -0,0 +1,13 @@
1
+ # Wire it to your agent
2
+
3
+ There is no plugin, no MCP server, no GitHub Action. Your agent already runs shell commands, so one instruction is
4
+ enough. Paste this into whatever file your agent reads as standing instructions — `CLAUDE.md`, `AGENTS.md`, a Cursor
5
+ rule, a system prompt. We suggest the wording; how you wire it is yours.
6
+
7
+ > **Diary.** At the start of a session run `diarium scan` — it lists tasks that closed since you last looked. Each one
8
+ > owes an entry. Write what you UNDERSTOOD and what you LEARNED, not what you did, and seal it with
9
+ > `diarium write <task-ref> <file>`. If you no longer hold the task, say exactly that and pass `--nothing-learned` —
10
+ > absence of a result is a result. Do not read the issue and the diff to reconstruct an insight; git already keeps
11
+ > summaries better than you will. Before starting work, `diarium read --depth 3` to see what you last learned.
12
+
13
+ That is the whole integration.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # diarium
2
+
3
+ Agent memory as a verifiable UST stream.
4
+
5
+ ```
6
+ npm i -D diarium
7
+ ```
8
+
9
+ A task closes in your tracker. The agent writes what it understood and what it learned from it — a few sentences, its own voice. That text is sealed as a UST `observation`, `prev`-chained to the entry before it, and appended to `diarium/`. The agent reads it back later as its own memory, walking the chain instead of loading the whole corpus.
10
+
11
+ Nothing here proves the work was done well. A sealed entry proves **the agent said this, then**, and that nobody — including the agent — rewrote it afterwards. Fixation, not truth. Ordering and non-repudiation are the product; correctness is not on offer.
12
+
13
+ The full walkthrough, with real command output, is in [FLOW.md](./FLOW.md). Wiring it to your agent is one paragraph: [INTEGRATE.md](./INTEGRATE.md).
14
+
15
+ ## Why it is not just a log file
16
+
17
+ A log can be edited. A tracker comment can be edited. A closed issue can be reopened and its history rewritten. A `prev`-chained stream of signed entries cannot: change one entry and every later `prev` stops resolving, so the tampering is visible to anyone holding a later entry — no server, no trust in the author.
18
+
19
+ Hand someone a single file and they can check it offline with `npm i ust-protocol` or the public web verifier. Nothing calls back to us; there is no us to call.
20
+
21
+ ## What holds it together
22
+
23
+ Nobody reviews an entry before it lands. That is deliberate, so the discipline cannot be review — it is the **trigger**: the agent does not choose when to write. A task closes, an entry is owed. It cannot skip a closure it would rather not record, and it cannot bury one entry under ten others. One closure, one entry, and a closure without an entry is detectable — that is what `status` is, and why it exits non-zero while anything is owed.
24
+
25
+ The rest is mechanical, and split honestly between what code can enforce and what only prose can ask for:
26
+
27
+ | enforced by code | asked for by the rules |
28
+ | --- | --- |
29
+ | character cap | write the failures too, not just the wins |
30
+ | `prev` chain, signature | one entry, one moment |
31
+ | one entry per closure | no performed feelings |
32
+ | a broken store cannot be extended | do not reconstruct a task you no longer hold |
33
+
34
+ The rules live in `diarium/rules.md` as prose, and the agent reads them before writing. Rewrite that file and the agent's behaviour changes — configuration by prose. The cap is yours to set. The append-only chain is not: without it this is a text file.
35
+
36
+ `"Nothing to learn"` is a first-class entry, recorded structurally rather than buried in a sentence, so a corpus that is mostly *nothing learned* can be counted. Absence of a result is a result.
37
+
38
+ ## Commands
39
+
40
+ ```
41
+ diarium scan what closed since last time (first run sets a baseline, not a backlog)
42
+ diarium status closures owing an entry — exit 1 while any is owed, so it can gate
43
+ diarium write <ref> <file> seal + chain + store [--nothing-learned]
44
+ diarium read [--depth N] walk the chain N hops back from the head
45
+ diarium verify every seal, one genesis, one head, no fork, no orphan
46
+ diarium render markdown to stdout, derived, never stored
47
+ ```
48
+
49
+ Trackers are detected on first run — a `.beads/` directory, a GitHub remote — and written into `diarium/settings.json` for you to edit. Both answer one question, *what closed after this cursor*, so another tracker is an adapter, not an integration.
50
+
51
+ ## Keys
52
+
53
+ A signing key is generated into `.env` as `DIARIUM_SEED` on first run and is never printed, not even by the tool that made it. Every run checks that git actually ignores it. Lose it and the chain can no longer be extended, though every existing entry still verifies. Leak it and someone else can write entries as you — and every one of those will verify too, which is the point of checking.
54
+
55
+ ## Tier
56
+
57
+ `VALID:LIGHT`, key-form identity, and that is the right ceiling. Everyone keeps their own memories, so there is no third party to convince — no domain-bound identity to prove *who*, no external clock to prove *when*. Adding those would be selling assurance nobody asked for.
58
+
59
+ ## Status
60
+
61
+ `0.1.0`, and the version is deliberate: everything else in this repository is protocol surface on the `1.0.0-rc` line, while this is a product built on top of it. Treat the CLI surface as unsettled and the sealed entries as permanent — an entry written today verifies under any later version, because what makes it verifiable is the protocol, not this tool.
62
+
63
+ Built on [`ust-protocol`](../ust-protocol), which is its only dependency, and which has none of its own.
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // diarium — a task closes, the agent writes what it learned, the entry is sealed as a UST and chained into diarium/.
4
+ //
5
+ // The crypto is not here: it is `ust-protocol`, and this tool calls five functions from it. What lives here is the
6
+ // DISCIPLINE the library cannot hold — prev taken from the head of the chain rather than the last filename, the entry
7
+ // verified before it is stored, the cap enforced, one closure one entry, and a refusal to extend a broken store. Those
8
+ // are exactly the invariants an agent re-deriving them each session gets wrong.
9
+ //
10
+ // Everything is relative to the directory you run it in, so the store lands next to your code and the tool itself can
11
+ // live in node_modules.
12
+ import * as P from 'ust-protocol';
13
+ import { createPrivateKey, createPublicKey, randomBytes } from 'node:crypto';
14
+ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'node:fs';
15
+ import { execSync } from 'node:child_process';
16
+ import { join } from 'node:path';
17
+
18
+ const CWD = process.cwd();
19
+ const STORE = join(CWD, 'diarium');
20
+ const PENDING = join(STORE, '.pending');
21
+ const RULES = join(STORE, 'rules.md');
22
+ const SETTINGS = join(STORE, 'settings.json');
23
+ const ENV = join(CWD, '.env');
24
+
25
+ // rules.md is PROSE and it is the agent's prompt — yours to rewrite, and the tool does not care how you word it.
26
+ // Only `cap` is parsed and enforced; the rest it can ask for, which is honest as long as asking is not presented as
27
+ // enforcement. settings.json is STRUCTURE, read by code: a typo there fails loudly instead of being ignored.
28
+ const DEFAULT_RULES = `# Rules
29
+
30
+ Yours to rewrite. The tool reads this file before every entry and enforces \`cap\`; the rest it can only ask for.
31
+
32
+ cap: 560
33
+
34
+ - Write what you UNDERSTOOD and what you LEARNED from the task that just closed. Not what you did — git already keeps that.
35
+ - Write the failures too. A task that went wrong, a fix that did not hold, a call you got wrong. Never dress a loss up as a win.
36
+ - One entry, one thing. If it does not fit the cap, cut it; do not split it.
37
+ - No performed feelings. If it reads like marketing, delete it and write what actually happened.
38
+ - Do NOT reconstruct. If you no longer hold the task, say so and stop — reading the issue and the diff to manufacture an
39
+ insight produces a summary, and git already keeps summaries better than you will. Read enough to RECOGNISE whether you
40
+ remember it; never enough to invent it.
41
+ - "Nothing to learn" is a real entry. Absence of a result is a result. Write it plainly and pass --nothing-learned.
42
+ - Nobody reviews this before it lands. The discipline is the trigger, not review: you do not choose when to write.
43
+ `;
44
+
45
+ const args = process.argv.slice(2);
46
+ const cmd = args[0];
47
+ const flag = (n, d) => { const i = args.indexOf('--' + n); return i > 0 && args[i + 1] ? args[i + 1] : d; };
48
+ const die = (m) => { console.error('✗ ' + m); process.exit(1); };
49
+
50
+ // First run detects the trackers this repo actually has rather than asking you to describe them.
51
+ function detectSources() {
52
+ const out = [];
53
+ if (existsSync(join(CWD, '.beads'))) out.push({ type: 'bd', cwd: '.' });
54
+ try {
55
+ const url = execSync('git remote get-url origin', { cwd: CWD, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
56
+ const m = /[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?$/.exec(url);
57
+ if (m) out.push({ type: 'github', repo: `${m[1]}/${m[2]}` });
58
+ } catch { /* no remote: leave it out rather than guess */ }
59
+ return out;
60
+ }
61
+
62
+ // The seed is generated at INIT, not lazily on the first entry: the developer must see the "gitignore this" warning
63
+ // while installing, not in the middle of writing. A leaked seed lets someone else write entries as you.
64
+ function ignored() {
65
+ try { execSync('git check-ignore -q .env', { cwd: CWD, stdio: 'ignore' }); return true; } catch { return false; }
66
+ }
67
+
68
+ function ensure() {
69
+ mkdirSync(PENDING, { recursive: true });
70
+ if (!existsSync(RULES)) writeFileSync(RULES, DEFAULT_RULES);
71
+ if (!existsSync(SETTINGS)) {
72
+ const sources = detectSources();
73
+ writeFileSync(SETTINGS, JSON.stringify({ sources, cursors: {} }, null, 2) + '\n');
74
+ console.log(` · created ${STORE.replace(CWD + '/', '')}/ with ${sources.length} detected tracker(s): ${sources.map((s) => s.type).join(', ') || 'none — add them to settings.json'}`);
75
+ }
76
+ key();
77
+ if (!ignored()) console.log(' ! .env is NOT gitignored — add it now. The seed in there is the identity of this store;\n anyone who has it can write entries as you, and every one of them will verify.');
78
+ }
79
+ const cap = () => { const m = (existsSync(RULES) ? readFileSync(RULES, 'utf8') : DEFAULT_RULES).match(/^cap:\s*(\d+)/m); return m ? Number(m[1]) : 560; };
80
+ const files = () => (existsSync(STORE) ? readdirSync(STORE).filter((f) => f.endsWith('.ust.json')).sort() : []);
81
+ const load = (f) => JSON.parse(readFileSync(join(STORE, f), 'utf8'));
82
+
83
+ // Order comes from the CHAIN, never from the filesystem: filenames are cosmetic and renaming every file must change
84
+ // nothing. Deriving order from `prev` also sees forks and orphans, which a filename sort never can.
85
+ function chain() {
86
+ const docs = files().map((f) => ({ f, d: load(f) }));
87
+ const problems = [];
88
+ if (!docs.length) return { ordered: [], problems };
89
+ const hashOf = (x) => P.contentHash(x.d);
90
+ const prevOf = (x) => x.d.state.provenance?.prev;
91
+ const byHash = new Map(docs.map((x) => [hashOf(x), x]));
92
+
93
+ const genesis = docs.filter((x) => !prevOf(x));
94
+ if (!genesis.length) problems.push('no genesis: every entry carries a prev, so the oldest one is missing');
95
+ if (genesis.length > 1) problems.push(`${genesis.length} entries carry no prev — a stream has exactly one genesis: ${genesis.map((x) => x.f).join(', ')}`);
96
+
97
+ const claimed = new Map();
98
+ for (const x of docs) {
99
+ const p = prevOf(x); if (!p) continue;
100
+ if (!byHash.has(p)) problems.push(`${x.f}: prev ${p.slice(0, 20)}… resolves to no entry here — a gap, or the chain leaves this store`);
101
+ claimed.set(p, (claimed.get(p) || []).concat(x.f));
102
+ }
103
+ for (const [p, fs2] of claimed) if (fs2.length > 1) problems.push(`FORK: ${fs2.length} entries claim the same prev ${p.slice(0, 20)}… — ${fs2.join(', ')}`);
104
+
105
+ const heads = docs.filter((x) => !claimed.has(hashOf(x)));
106
+ if (heads.length > 1) problems.push(`${heads.length} heads — nothing follows any of: ${heads.map((x) => x.f).join(', ')}`);
107
+
108
+ const ordered = []; const seen = new Set();
109
+ let cur = heads[0], guard = docs.length + 1;
110
+ while (cur && guard-- > 0) {
111
+ if (seen.has(cur.f)) { problems.push(`cycle detected at ${cur.f}`); break; }
112
+ seen.add(cur.f); ordered.unshift(cur);
113
+ const p = prevOf(cur); cur = p ? byHash.get(p) : null;
114
+ }
115
+ for (const x of docs) if (!seen.has(x.f)) problems.push(`${x.f}: unreachable from the head — an orphan the chain does not include`);
116
+ return { ordered, problems };
117
+ }
118
+
119
+ function key() {
120
+ const env = existsSync(ENV) ? readFileSync(ENV, 'utf8') : '';
121
+ let m = env.match(/^DIARIUM_SEED=(\S+)/m);
122
+ if (!m) {
123
+ const seed = randomBytes(32).toString('base64url');
124
+ appendFileSync(ENV, `${env.endsWith('\n') || !env ? '' : '\n'}DIARIUM_SEED=${seed}\n`);
125
+ m = [null, seed];
126
+ console.log(' · generated a signing key into .env — gitignore it. The seed is never printed, not even here.');
127
+ }
128
+ const seed = Buffer.from(m[1], 'base64url');
129
+ const priv = createPrivateKey({ key: Buffer.concat([Buffer.from('302e020100300506032b657004220420', 'hex'), seed]), format: 'der', type: 'pkcs8' });
130
+ const pub = createPublicKey(priv).export({ format: 'der', type: 'spki' }).slice(-32).toString('base64url');
131
+ return { priv, pub, kid: P.keyId(pub) };
132
+ }
133
+
134
+ // A task reference is a PATH and paths go stale: rename or transfer the repo and the reference inside an already-sealed
135
+ // entry is unresolvable, unfixable because the seal is immutable. So the seal carries the readable ref AND the id that
136
+ // survives a rename. Where no global id exists that is RECORDED, never implied, and nothing is invented on failure.
137
+ function resolveTask(ref) {
138
+ const m = /^(?:([\w.-]+)\/([\w.-]+))?#?(\d+)$/.exec(String(ref).trim());
139
+ if (!m) return { ref, source: /^[A-Za-z][\w.-]*-[a-z0-9]+$/.test(ref) ? 'tracker-local' : 'raw' };
140
+ let [, owner, repo, num] = m;
141
+ if (!owner || !repo) {
142
+ try {
143
+ const url = execSync('git remote get-url origin', { cwd: CWD, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
144
+ const rm = /[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?$/.exec(url);
145
+ if (rm) { owner = rm[1]; repo = rm[2]; }
146
+ } catch { /* stay unqualified rather than guess */ }
147
+ }
148
+ if (!owner || !repo) return { ref, source: 'raw' };
149
+ const qualified = `${owner}/${repo}#${num}`;
150
+ try {
151
+ const j = JSON.parse(execSync(`gh api repos/${owner}/${repo}/issues/${num} --jq '{id:.id,node_id:.node_id}'`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
152
+ return { ref: qualified, id: String(j.id), node_id: j.node_id, source: 'github' };
153
+ } catch { return { ref: qualified, source: 'github-unresolved' }; }
154
+ }
155
+ const field = (s) => String(s).replace(/_/g, '-').replace(/[^\w.-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
156
+ const slug = (t) => { const m = /^(?:[\w.-]+\/)?([\w.-]+)#(\d+)$/.exec(t.ref); return m ? `${field(m[1])}_${field(m[2])}` : field(t.ref); };
157
+ const pendingPath = (ref) => join(PENDING, String(ref).replace(/[^\w.-]/g, '_') + '.json');
158
+
159
+ // ── scan ─────────────────────────────────────────────────────────────────────────────────────────────────────────
160
+ // Every source answers ONE question — what closed after this cursor — so a new tracker is an adapter, not an
161
+ // integration. Polling is what makes the trigger real: measured on a live day, it caught every closure while a
162
+ // commit-keyword hook caught none, because the closures went through `gh issue close` and the commits said "Refs #90".
163
+ if (cmd === 'scan') {
164
+ ensure();
165
+ let cfg; try { cfg = JSON.parse(readFileSync(SETTINGS, 'utf8')); } catch (e) { die(`settings.json does not parse — a typo there fails loudly on purpose: ${e.message}`); }
166
+ cfg.cursors = cfg.cursors || {};
167
+ let found = 0;
168
+ for (const s of (cfg.sources || []).filter((x) => x.enabled !== false)) {
169
+ const id = `${s.type}:${s.repo || s.cwd || '.'}`;
170
+ const cold = !cfg.cursors[id];
171
+ const since = cfg.cursors[id] || '1970-01-01T00:00:00Z';
172
+ let rows = [];
173
+ try {
174
+ if (s.type === 'github') {
175
+ rows = JSON.parse(execSync(`gh issue list -R ${s.repo} --state closed --limit 100 --json number,closedAt,title`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }))
176
+ .filter((x) => x.closedAt > since).map((x) => ({ ref: `${s.repo}#${x.number}`, at: x.closedAt, title: x.title }));
177
+ } else if (s.type === 'bd') {
178
+ rows = JSON.parse(execSync('bd list --status=closed --limit 400 --json', { cwd: join(CWD, s.cwd || '.'), encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }))
179
+ .filter((x) => (x.closed_at || '') > since).map((x) => ({ ref: x.id, at: x.closed_at, title: x.title }));
180
+ } else { console.log(` ? ${id}: unknown source type — skipped, never guessed`); continue; }
181
+ } catch (e) {
182
+ // The CAUSE lives in stderr; e.message only repeats the command back, which is the least informative line there is.
183
+ const why = (String(e.stderr || '').split('\n').filter(Boolean)[0] || String(e.message).split('\n')[1] || 'no output').trim();
184
+ console.log(` ! ${id}: unreachable — cursor untouched, so nothing is skipped quietly\n ${why.slice(0, 120)}`); continue;
185
+ }
186
+
187
+ // A cold start sets a BASELINE, not a backlog. Asking an agent to write dozens of entries about work it does not
188
+ // remember manufactures recollection, and a corpus of invented memories is worse than an empty one.
189
+ if (cold) {
190
+ cfg.cursors[id] = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
191
+ console.log(` ${id}: BASELINE set — ${rows.length} historical closure(s) skipped (a memory you did not live is an invention, not a memory)`);
192
+ continue;
193
+ }
194
+ for (const r of rows.sort((a, b) => a.at.localeCompare(b.at))) {
195
+ if (!existsSync(pendingPath(r.ref))) { writeFileSync(pendingPath(r.ref), JSON.stringify({ ref: r.ref, kind: 'done', title: r.title || '', at: r.at }, null, 2) + '\n'); found++; }
196
+ if (r.at > (cfg.cursors[id] || '')) cfg.cursors[id] = r.at;
197
+ }
198
+ console.log(` ${id}: ${rows.length} closure(s) since ${since.slice(0, 16)}`);
199
+ }
200
+ writeFileSync(SETTINGS, JSON.stringify(cfg, null, 2) + '\n');
201
+ console.log(found ? `\n ${found} new obligation(s) — run: diarium status` : '\n nothing new');
202
+ process.exit(0);
203
+ }
204
+
205
+ if (cmd === 'closed') {
206
+ const ref = args[1]; if (!ref) die('usage: diarium closed <ref> [--kind done|cancelled] [--title "..."]');
207
+ ensure();
208
+ if (existsSync(pendingPath(ref))) { console.log(`· ${ref} already owes an entry`); process.exit(0); }
209
+ writeFileSync(pendingPath(ref), JSON.stringify({ ref, kind: flag('kind', 'done'), title: flag('title', ''), at: new Date().toISOString() }, null, 2) + '\n');
210
+ console.log(`· ${ref} closed — an entry is owed: diarium write ${ref} <file>`);
211
+ process.exit(0);
212
+ }
213
+
214
+ // ── status: the gate. A closure without an entry is the thing this catches, so it exits non-zero while any is owed.
215
+ if (cmd === 'status') {
216
+ ensure();
217
+ const owed = readdirSync(PENDING).filter((f) => f.endsWith('.json')).map((f) => JSON.parse(readFileSync(join(PENDING, f), 'utf8')));
218
+ console.log(` store: ${files().length} entries · cap ${cap()} · diarium/`);
219
+ if (!owed.length) { console.log(' ✓ no closure is waiting for an entry'); process.exit(0); }
220
+ console.log(` ✗ ${owed.length} closure(s) owe an entry:`);
221
+ for (const o of owed.sort((a, b) => a.at.localeCompare(b.at))) console.log(` ${String(o.ref).padEnd(16)} ${o.kind.padEnd(9)} ${o.at.slice(0, 16)} ${(o.title || '').slice(0, 60)}`);
222
+ process.exit(1);
223
+ }
224
+
225
+ if (cmd === 'write') {
226
+ const ref = args[1], bodyPath = args[2];
227
+ if (!ref || !bodyPath || !existsSync(bodyPath)) die('usage: diarium write <task-ref> <file> [--nothing-learned]');
228
+ ensure();
229
+ const body = readFileSync(bodyPath, 'utf8').trim();
230
+ if (!body) die('empty entry');
231
+ const limit = cap();
232
+ if (body.length > limit) die(`entry is ${body.length} characters — the cap declared in diarium/rules.md is ${limit}. Cut it, do not split it.`);
233
+ // "Nothing to learn" is first-class and recorded STRUCTURALLY so it can be counted: a corpus that is mostly this is
234
+ // telling you something, and that signal is lost if the fact hides inside a sentence.
235
+ const nothing = args.includes('--nothing-learned');
236
+
237
+ const task = resolveTask(ref);
238
+ // The closure time travels into the seal so the closure→entry INTERVAL is observable: an entry written minutes after
239
+ // the closure was lived, one written days later was reconstructed. Nobody declares which — declaring your own
240
+ // reliability is the self-asserted assurance this design refuses everywhere else.
241
+ if (existsSync(pendingPath(ref))) { try { task.closed_at = JSON.parse(readFileSync(pendingPath(ref), 'utf8')).at; } catch { /* keep going without it */ } }
242
+
243
+ const { ordered, problems } = chain();
244
+ if (problems.length) { console.error('✗ refusing to extend a broken store — fix it first:'); for (const p of problems) console.error(' • ' + p); process.exit(1); }
245
+ const prev = ordered.length ? P.contentHash(ordered[ordered.length - 1].d) : undefined;
246
+
247
+ const { priv, pub, kid } = key();
248
+ const now = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
249
+ const ust_id = `ust:${now.slice(0, 4)}${now.slice(5, 7)}${now.slice(8, 10)}.${now.slice(11, 13)}${now.slice(14, 16)}${now.slice(17, 19)}`;
250
+ const state = P.buildState({ domain_shard: kid, ust_id, key_id: kid, class: 'observation' },
251
+ { generated_at: now, valid_from: now, valid_to: now },
252
+ { entry: { kind: 'captured', value: nothing ? { text: body, task, learned: 'none' } : { text: body, task } } },
253
+ prev ? { prev } : undefined);
254
+ const doc = P.seal(state, priv, pub);
255
+ const v = P.verify(doc, { context: 'data' });
256
+ if (v.result !== 'VALID:LIGHT') die(`entry did not seal VALID:LIGHT: ${v.result || v.error}`);
257
+
258
+ // Underscore separates fields, hyphen lives inside them: repo names carry hyphens, and a separator that can also
259
+ // appear in the data is not a separator. No `ust:` prefix — a colon cannot be checked out on Windows. The name
260
+ // carries no meaning; order is the chain's and identity is the content_hash.
261
+ const name = `${ust_id.slice(4)}_${slug(task)}_${P.contentHash(doc).slice(7, 15)}.ust.json`;
262
+ writeFileSync(join(STORE, name), JSON.stringify(doc) + '\n');
263
+ if (existsSync(pendingPath(ref))) unlinkSync(pendingPath(ref));
264
+ console.log('✓ entry sealed + stored');
265
+ console.log(' file :', name);
266
+ if (nothing) console.log(' learned : none (recorded as such, and countable)');
267
+ console.log(' task :', task.ref, `(${task.source}${task.id ? ', id ' + task.id : ''})`);
268
+ console.log(' content_hash:', P.contentHash(doc));
269
+ console.log(' prev :', prev || '(genesis)');
270
+ process.exit(0);
271
+ }
272
+
273
+ if (cmd === 'read') {
274
+ const depth = Number(flag('depth', '3'));
275
+ const { ordered, problems } = chain();
276
+ if (!ordered.length) { console.log(' (no entries yet)'); process.exit(0); }
277
+ if (problems.length) console.log(` ! ${problems.length} structural problem(s) — run: diarium verify\n`);
278
+ console.log(` walking back ${depth} hop(s) from the head of ${ordered.length} entries\n`);
279
+ for (const { f, d } of ordered.slice(-depth).reverse()) {
280
+ const st = d.state, v = st.data.entry?.value ?? {};
281
+ console.log(` ── ${st.id.ust_id} · task ${v.task?.ref ?? v.task ?? '?'}${v.learned === 'none' ? ' · learned: none' : ''}`);
282
+ console.log(' ' + String(v.text ?? '').split('\n').join('\n ') + '\n');
283
+ }
284
+ process.exit(0);
285
+ }
286
+
287
+ if (cmd === 'verify') {
288
+ const { ordered, problems } = chain();
289
+ const present = files();
290
+ if (!present.length) { console.log(' (no entries yet)'); process.exit(0); }
291
+ // Every file PRESENT, not just the chain-reachable ones: a tampered entry falls out of the chain, and verifying only
292
+ // what is reachable would leave its broken seal unreported.
293
+ const broken = [];
294
+ for (const f of present) {
295
+ let d; try { d = load(f); } catch { broken.push(`${f}: not parseable JSON`); continue; }
296
+ const v = P.verify(d, { context: 'data' });
297
+ if (v.result !== 'VALID:LIGHT') broken.push(`${f}: seal does NOT verify — ${v.result || v.error}`);
298
+ }
299
+ // Broken seals lead. A tampered entry is the FINDING; the orphans and extra heads it produces are consequences, and
300
+ // printing them first buries the one line that says what actually happened.
301
+ const fails = [...broken, ...problems];
302
+ if (fails.length) {
303
+ console.error(`✗ verify FAILED (${ordered.length} in chain, ${present.length} file(s) present):`);
304
+ for (const x of broken) console.error(' • ' + x);
305
+ if (broken.length && problems.length) console.error(' — and the structural consequences of that:');
306
+ for (const x of problems) console.error(' • ' + x);
307
+ process.exit(1);
308
+ }
309
+ console.log(`✓ ${ordered.length} entries: every seal verifies, one genesis, one head, no fork, no orphan (order from the chain — filenames are cosmetic)`);
310
+ process.exit(0);
311
+ }
312
+
313
+ if (cmd === 'render') {
314
+ for (const { f, d } of chain().ordered) {
315
+ const st = d.state, v = st.data.entry?.value ?? {};
316
+ console.log(`${v.text}\n\n<sub>${f} · task ${v.task?.ref ?? v.task} · ${st.time.generated_at}</sub>\n\n---\n`);
317
+ }
318
+ process.exit(0);
319
+ }
320
+
321
+ console.log(`diarium — a task closes, you write what you learned, it is sealed and chained
322
+
323
+ diarium scan what closed since last time (first run sets a baseline)
324
+ diarium status closures owing an entry (exit 1 while any is owed)
325
+ diarium write <ref> <file> seal + chain + store [--nothing-learned]
326
+ diarium read [--depth N] walk the chain N hops back from the head
327
+ diarium verify every seal, one genesis, one head, no fork, no orphan
328
+ diarium render markdown to stdout, derived, never stored
329
+
330
+ The store is ./diarium/ — rules.md is prose you may rewrite, settings.json is structure.`);
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "diarium",
3
+ "version": "0.1.0",
4
+ "description": "Agent memory as a verifiable UST stream — a task closes, the agent writes what it learned, the entry is sealed and prev-chained. Tamper-evident, offline-verifiable, no server.",
5
+ "type": "module",
6
+ "bin": {
7
+ "diarium": "./bin/diarium.mjs"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "LICENSE",
12
+ "README.md",
13
+ "FLOW.md",
14
+ "INTEGRATE.md"
15
+ ],
16
+ "keywords": [
17
+ "ust",
18
+ "agent",
19
+ "agent-memory",
20
+ "journal",
21
+ "tamper-evident",
22
+ "ed25519",
23
+ "provenance"
24
+ ],
25
+ "license": "Apache-2.0",
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "dependencies": {
30
+ "ust-protocol": "^1.0.0-rc.32"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/thelabmd/UST-Protocol.git",
35
+ "directory": "packages/diarium"
36
+ },
37
+ "homepage": "https://github.com/thelabmd/UST-Protocol/tree/main/packages/diarium#readme",
38
+ "scripts": {
39
+ "test": "node --test"
40
+ }
41
+ }