clawbooks 0.1.2 → 0.1.4

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 (3) hide show
  1. package/README.md +115 -8
  2. package/build/cli.js +155 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
- # clawbooks
1
+ <p align="center">
2
+ <img src="./logo.png" alt="clawbooks logo" width="180" align="center">
3
+ </p>
2
4
 
3
- Accounting by inference, not by engine.
5
+ <h1 align="center">clawbooks</h1>
4
6
 
5
- Financial memory for agents.
7
+ <p align="center"><strong>Financial memory for agents.</strong></p>
8
+
9
+ <p align="center">Append-only ledger • Plain-English policy • Agent-native accounting CLI</p>
6
10
 
7
11
  Clawbooks is an append-only ledger, a plain-English accounting policy, and a CLI.
8
12
  Your agent reads the data, reads the policy, and does the accounting.
@@ -11,6 +15,28 @@ No rules engine. No SDK. No framework.
11
15
 
12
16
  **Two source files. Zero runtime dependencies.**
13
17
 
18
+ Bring CSVs, Stripe exports, exchange fills, receipts, PDFs, or copied transaction text.
19
+ Your agent reads the source, applies `policy.md`, writes normalized ledger events into clawbooks, and produces statements, summaries, and audit packs from the same record.
20
+
21
+ ## The loop
22
+
23
+ ```text
24
+ Raw inputs
25
+ bank CSVs / Stripe exports / receipts / PDFs / exchange fills / copied text
26
+ ->
27
+ Agent ingestion
28
+ reads the source + applies policy.md + writes normalized ledger events
29
+ ->
30
+ Clawbooks ledger
31
+ append-only records + snapshots + verification + context + packs
32
+ ->
33
+ Agent outputs
34
+ P&L / balance sheet / cash flow / tax views / asset register / audit-ready working files
35
+ ->
36
+ Policy improvement
37
+ you refine policy.md and the next ingestion/reporting cycle gets better
38
+ ```
39
+
14
40
  ## Why
15
41
 
16
42
  Most accounting software assumes the product should contain the accounting logic.
@@ -30,6 +56,53 @@ That makes clawbooks useful anywhere an agent can read files and run shell comma
30
56
  - Structured `context` output designed for agent reasoning
31
57
  - Zero runtime dependencies
32
58
 
59
+ ## How ingestion works
60
+
61
+ Clawbooks does not ship source-specific import logic.
62
+ That is deliberate.
63
+
64
+ Your agent is the importer:
65
+
66
+ - bring raw inputs in whatever form you already have
67
+ - the agent reads them and applies `policy.md`
68
+ - the agent converts them into normalized ledger events
69
+ - clawbooks stores the canonical record
70
+
71
+ This keeps ingestion programmable by policy instead of hardcoded per integration.
72
+
73
+ ## What the agent can produce
74
+
75
+ With `context`, `summary`, `verify`, `reconcile`, `assets`, and `pack`, your agent can prepare:
76
+
77
+ - profit and loss statements
78
+ - balance sheets
79
+ - cash flow summaries
80
+ - categorized tax views
81
+ - asset registers and depreciation views
82
+ - audit-ready working packs
83
+
84
+ Clawbooks supplies durable memory, verification, and repeatable tooling.
85
+ The agent does the accounting work on top of that foundation.
86
+
87
+ ## Boundaries
88
+
89
+ You and your agent:
90
+
91
+ - write and refine `policy.md`
92
+ - ingest source documents and convert them into ledger events
93
+ - interpret edge cases
94
+ - review outputs and improve the policy over time
95
+
96
+ clawbooks:
97
+
98
+ - stores append-only financial records
99
+ - preserves snapshots and audit history
100
+ - provides structured context for the agent
101
+ - verifies integrity and reconciliation surfaces
102
+ - packages records for downstream review and reporting
103
+
104
+ As `policy.md` gets better, your ingestion, classification, and reporting get better too.
105
+
33
106
  ## Example
34
107
 
35
108
  ```text
@@ -64,7 +137,7 @@ cp policy.md.example policy.md # edit with your own accounting rules
64
137
  ## How it works
65
138
 
66
139
  Clawbooks stores financial events and outputs accounting context.
67
- The important command is `clawbooks context`: it prints your policy, the latest snapshot, and the relevant events in XML-style blocks so an agent can read and reason over them.
140
+ The important command is `clawbooks context`: it prints a structured context envelope with metadata, instructions, policy, summary, snapshot, and raw events so an agent can reason from both overview and detail.
68
141
 
69
142
  ## Commands
70
143
 
@@ -101,25 +174,59 @@ clawbooks policy
101
174
 
102
175
  ## The context command
103
176
 
104
- This is the core command. It prints your accounting policy, the latest snapshot, and the events for a period, wrapped in XML tags so the agent can read and reason over them.
177
+ This is the core command. It prints a `context` envelope for the requested period:
178
+
179
+ - `metadata` explains the requested and effective window, whether a snapshot was used, and what kinds of records are present
180
+ - `instructions` tells the agent how to interpret snapshot plus events
181
+ - `policy` is your plain-English accounting policy
182
+ - `summary` provides orientation before the raw records
183
+ - `snapshot` is the starting state, when available
184
+ - `events` contains the raw append-only records the agent should reason from
105
185
 
106
186
  ```bash
107
187
  $ clawbooks context 2026-03
108
188
 
189
+ <context schema="clawbooks.context.v2">
190
+ <metadata>
191
+ {
192
+ "requested_window": {"after":"2026-03-01T00:00:00.000Z","before":"2026-03-31T23:59:59.999Z"},
193
+ "effective_window": {"after":"2026-03-01T00:00:00.000Z","before":"2026-03-31T23:59:59.999Z"},
194
+ "snapshot": {"used": true, "ts":"2026-03-01T00:00:00.000Z"},
195
+ "event_count": 47,
196
+ "sources": ["bank", "stripe"],
197
+ "currencies": ["USD"]
198
+ }
199
+ </metadata>
200
+
201
+ <instructions>
202
+ Read the policy first.
203
+ Treat the snapshot as the starting state.
204
+ Apply the events block on top of that snapshot.
205
+ </instructions>
206
+
109
207
  <policy>
110
208
  # Accounting policy
111
209
  Cash basis. Crypto trades are revenue income...
112
210
  </policy>
113
211
 
114
- <snapshot as_of="2026-03-01">
115
- {"balances":{"USDC":45000},"ytd_pnl":18450}
212
+ <summary>
213
+ {
214
+ "by_type": {"income":{"count":12,"total":1700},"fee":{"count":3,"total":-55}},
215
+ "by_currency": {"USD":{"count":15,"total":1645}},
216
+ "cash_flow": {"inflows":1700,"outflows":-55,"net":1645}
217
+ }
218
+ </summary>
219
+
220
+ <snapshot as_of="2026-03-01T00:00:00.000Z">
221
+ {"balances":{"USD":45000},"ytd_pnl":18450}
116
222
  </snapshot>
117
223
 
118
- <events count="47" after="2026-03-01" before="2026-03-31">
224
+ <events count="47" after="2026-03-01T00:00:00.000Z" before="2026-03-31T23:59:59.999Z">
119
225
  {"ts":"...","source":"stripe","type":"payment","data":{"amount":500,...}}
120
226
  {"ts":"...","source":"bank","type":"fee","data":{"amount":-55,...}}
121
227
  ...
122
228
  </events>
229
+ </context>
123
230
  ```
124
231
 
125
232
  ## Importing data
package/build/cli.js CHANGED
@@ -106,6 +106,109 @@ function periodFromArgs(args) {
106
106
  function round2(n) {
107
107
  return Math.round(n * 100) / 100;
108
108
  }
109
+ function buildReclassifyMap(events) {
110
+ const reclassifyMap = {};
111
+ for (const e of events) {
112
+ if (e.type === "reclassify" && e.data.original_id && e.data.new_category) {
113
+ reclassifyMap[String(e.data.original_id)] = String(e.data.new_category);
114
+ }
115
+ }
116
+ return reclassifyMap;
117
+ }
118
+ function reviewCounts(events, all) {
119
+ const reclassified = new Set(all.filter((e) => e.type === "reclassify").map((e) => String(e.data.original_id)));
120
+ const counts = { unclear: 0, inferred: 0, unset: 0, clear: 0 };
121
+ for (const e of events) {
122
+ if (e.type === "reclassify" || e.type === "snapshot" || reclassified.has(e.id))
123
+ continue;
124
+ const confidence = String(e.data.confidence ?? "unset");
125
+ if (confidence === "clear")
126
+ counts.clear++;
127
+ else if (confidence === "unclear")
128
+ counts.unclear++;
129
+ else if (confidence === "inferred")
130
+ counts.inferred++;
131
+ else
132
+ counts.unset++;
133
+ }
134
+ return counts;
135
+ }
136
+ function buildContextSummary(events, all) {
137
+ const reclassifyMap = buildReclassifyMap(all);
138
+ const byType = {};
139
+ const bySource = {};
140
+ const byCurrency = {};
141
+ const byCategory = {};
142
+ const eventTypes = new Set();
143
+ const sources = new Set();
144
+ const currencies = new Set();
145
+ let inflows = 0;
146
+ let outflows = 0;
147
+ let nonMetaEvents = 0;
148
+ let rawReclassifications = 0;
149
+ for (const e of events) {
150
+ eventTypes.add(e.type);
151
+ sources.add(e.source);
152
+ if (e.type === "reclassify")
153
+ rawReclassifications++;
154
+ if (META_TYPES.has(e.type))
155
+ continue;
156
+ nonMetaEvents++;
157
+ const amount = Number(e.data.amount);
158
+ const currency = String(e.data.currency ?? "UNKNOWN");
159
+ const category = reclassifyMap[e.id] ?? String(e.data.category ?? e.type);
160
+ currencies.add(currency);
161
+ if (!byType[e.type])
162
+ byType[e.type] = { count: 0, total: 0 };
163
+ byType[e.type].count++;
164
+ if (!bySource[e.source])
165
+ bySource[e.source] = { count: 0, total: 0 };
166
+ bySource[e.source].count++;
167
+ if (!byCurrency[currency])
168
+ byCurrency[currency] = { count: 0, total: 0 };
169
+ byCurrency[currency].count++;
170
+ if (!byCategory[category])
171
+ byCategory[category] = { count: 0, total: 0 };
172
+ byCategory[category].count++;
173
+ if (isNaN(amount))
174
+ continue;
175
+ byType[e.type].total = round2(byType[e.type].total + amount);
176
+ bySource[e.source].total = round2(bySource[e.source].total + amount);
177
+ byCurrency[currency].total = round2(byCurrency[currency].total + amount);
178
+ byCategory[category].total = round2(byCategory[category].total + amount);
179
+ if (amount > 0)
180
+ inflows = round2(inflows + amount);
181
+ else
182
+ outflows = round2(outflows + amount);
183
+ }
184
+ const confidence = reviewCounts(events, all);
185
+ const needsReview = confidence.unclear + confidence.inferred + confidence.unset;
186
+ const reclassifiedEventCount = events.filter((e) => reclassifyMap[e.id] !== undefined).length;
187
+ return {
188
+ event_count: events.length,
189
+ non_meta_event_count: nonMetaEvents,
190
+ event_types: [...eventTypes].sort(),
191
+ sources: [...sources].sort(),
192
+ currencies: [...currencies].sort(),
193
+ by_type: byType,
194
+ by_source: bySource,
195
+ by_currency: byCurrency,
196
+ by_category: byCategory,
197
+ cash_flow: {
198
+ inflows: round2(inflows),
199
+ outflows: round2(outflows),
200
+ net: round2(inflows + outflows),
201
+ },
202
+ reclassifications: {
203
+ raw_events_in_window: rawReclassifications,
204
+ applied_to_events_in_window: reclassifiedEventCount,
205
+ },
206
+ review: {
207
+ needs_review: needsReview,
208
+ by_confidence: confidence,
209
+ },
210
+ };
211
+ }
109
212
  function enforceSign(type, data) {
110
213
  if (data.amount === undefined)
111
214
  return;
@@ -228,11 +331,62 @@ function cmdContext(args) {
228
331
  const snapshot = latestSnapshot(all, after);
229
332
  const effectiveAfter = snapshot?.ts ?? after;
230
333
  const events = filter(all, { after: effectiveAfter, before }).filter((e) => e.type !== "snapshot");
334
+ const summary = buildContextSummary(events, all);
335
+ const metadata = {
336
+ schema_version: "clawbooks.context.v2",
337
+ generated_at: new Date().toISOString(),
338
+ ledger_path: LEDGER,
339
+ policy_path: POLICY,
340
+ requested_window: {
341
+ after: after ?? "all",
342
+ before: before ?? "now",
343
+ },
344
+ effective_window: {
345
+ after: effectiveAfter ?? "all",
346
+ before: before ?? "now",
347
+ },
348
+ snapshot: snapshot ? {
349
+ used: true,
350
+ ts: snapshot.ts,
351
+ source: snapshot.source,
352
+ id: snapshot.id,
353
+ event_count: Number(snapshot.data.event_count ?? 0),
354
+ } : {
355
+ used: false,
356
+ },
357
+ event_count: events.length,
358
+ sources: summary.sources,
359
+ event_types: summary.event_types,
360
+ currencies: summary.currencies,
361
+ };
231
362
  // Output structured context for the agent
363
+ console.log(`<context schema="clawbooks.context.v2">`);
364
+ console.log(`<metadata>`);
365
+ console.log(JSON.stringify(metadata, null, 2));
366
+ console.log(`</metadata>`);
367
+ console.log();
368
+ console.log(`<instructions>`);
369
+ console.log(`Read the policy first.`);
370
+ if (snapshot) {
371
+ console.log(`Treat the snapshot as the starting state up to its as_of timestamp.`);
372
+ console.log(`Apply the events block on top of that snapshot to answer the user's question.`);
373
+ }
374
+ else {
375
+ console.log(`No snapshot is present for this window, so reason directly from the events block.`);
376
+ }
377
+ console.log(`Prefer the summary block for orientation, but use raw events for final reasoning and edge cases.`);
378
+ console.log(`Reclassify events are append-only corrections; use them when interpreting categories.`);
379
+ console.log(`Amounts are signed: inflows are positive, outflows are negative for known flow types.`);
380
+ console.log(`</instructions>`);
381
+ console.log();
232
382
  console.log(`<policy>`);
233
383
  console.log(policyText());
234
384
  console.log(`</policy>`);
235
385
  console.log();
386
+ console.log(`<summary>`);
387
+ console.log(JSON.stringify(summary, null, 2));
388
+ console.log(`</summary>`);
389
+ console.log();
236
390
  if (snapshot) {
237
391
  console.log(`<snapshot as_of="${snapshot.ts}">`);
238
392
  console.log(JSON.stringify(snapshot.data, null, 2));
@@ -243,6 +397,7 @@ function cmdContext(args) {
243
397
  for (const e of events)
244
398
  console.log(JSON.stringify(e));
245
399
  console.log(`</events>`);
400
+ console.log(`</context>`);
246
401
  }
247
402
  function cmdPolicy() {
248
403
  console.log(policyText());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawbooks",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Accounting by inference, not by engine. Zero dependencies.",
5
5
  "type": "module",
6
6
  "repository": {