clawbooks 0.1.2 → 0.1.3

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 +108 -5
  2. package/build/cli.js +155 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -11,6 +11,28 @@ No rules engine. No SDK. No framework.
11
11
 
12
12
  **Two source files. Zero runtime dependencies.**
13
13
 
14
+ Bring CSVs, Stripe exports, exchange fills, receipts, PDFs, or copied transaction text.
15
+ 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.
16
+
17
+ ## The loop
18
+
19
+ ```text
20
+ Raw inputs
21
+ bank CSVs / Stripe exports / receipts / PDFs / exchange fills / copied text
22
+ ->
23
+ Agent ingestion
24
+ reads the source + applies policy.md + writes normalized ledger events
25
+ ->
26
+ Clawbooks ledger
27
+ append-only records + snapshots + verification + context + packs
28
+ ->
29
+ Agent outputs
30
+ P&L / balance sheet / cash flow / tax views / asset register / audit-ready working files
31
+ ->
32
+ Policy improvement
33
+ you refine policy.md and the next ingestion/reporting cycle gets better
34
+ ```
35
+
14
36
  ## Why
15
37
 
16
38
  Most accounting software assumes the product should contain the accounting logic.
@@ -30,6 +52,53 @@ That makes clawbooks useful anywhere an agent can read files and run shell comma
30
52
  - Structured `context` output designed for agent reasoning
31
53
  - Zero runtime dependencies
32
54
 
55
+ ## How ingestion works
56
+
57
+ Clawbooks does not ship source-specific import logic.
58
+ That is deliberate.
59
+
60
+ Your agent is the importer:
61
+
62
+ - bring raw inputs in whatever form you already have
63
+ - the agent reads them and applies `policy.md`
64
+ - the agent converts them into normalized ledger events
65
+ - clawbooks stores the canonical record
66
+
67
+ This keeps ingestion programmable by policy instead of hardcoded per integration.
68
+
69
+ ## What the agent can produce
70
+
71
+ With `context`, `summary`, `verify`, `reconcile`, `assets`, and `pack`, your agent can prepare:
72
+
73
+ - profit and loss statements
74
+ - balance sheets
75
+ - cash flow summaries
76
+ - categorized tax views
77
+ - asset registers and depreciation views
78
+ - audit-ready working packs
79
+
80
+ Clawbooks supplies durable memory, verification, and repeatable tooling.
81
+ The agent does the accounting work on top of that foundation.
82
+
83
+ ## Boundaries
84
+
85
+ You and your agent:
86
+
87
+ - write and refine `policy.md`
88
+ - ingest source documents and convert them into ledger events
89
+ - interpret edge cases
90
+ - review outputs and improve the policy over time
91
+
92
+ clawbooks:
93
+
94
+ - stores append-only financial records
95
+ - preserves snapshots and audit history
96
+ - provides structured context for the agent
97
+ - verifies integrity and reconciliation surfaces
98
+ - packages records for downstream review and reporting
99
+
100
+ As `policy.md` gets better, your ingestion, classification, and reporting get better too.
101
+
33
102
  ## Example
34
103
 
35
104
  ```text
@@ -64,7 +133,7 @@ cp policy.md.example policy.md # edit with your own accounting rules
64
133
  ## How it works
65
134
 
66
135
  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.
136
+ 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
137
 
69
138
  ## Commands
70
139
 
@@ -101,25 +170,59 @@ clawbooks policy
101
170
 
102
171
  ## The context command
103
172
 
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.
173
+ This is the core command. It prints a `context` envelope for the requested period:
174
+
175
+ - `metadata` explains the requested and effective window, whether a snapshot was used, and what kinds of records are present
176
+ - `instructions` tells the agent how to interpret snapshot plus events
177
+ - `policy` is your plain-English accounting policy
178
+ - `summary` provides orientation before the raw records
179
+ - `snapshot` is the starting state, when available
180
+ - `events` contains the raw append-only records the agent should reason from
105
181
 
106
182
  ```bash
107
183
  $ clawbooks context 2026-03
108
184
 
185
+ <context schema="clawbooks.context.v2">
186
+ <metadata>
187
+ {
188
+ "requested_window": {"after":"2026-03-01T00:00:00.000Z","before":"2026-03-31T23:59:59.999Z"},
189
+ "effective_window": {"after":"2026-03-01T00:00:00.000Z","before":"2026-03-31T23:59:59.999Z"},
190
+ "snapshot": {"used": true, "ts":"2026-03-01T00:00:00.000Z"},
191
+ "event_count": 47,
192
+ "sources": ["bank", "stripe"],
193
+ "currencies": ["USD"]
194
+ }
195
+ </metadata>
196
+
197
+ <instructions>
198
+ Read the policy first.
199
+ Treat the snapshot as the starting state.
200
+ Apply the events block on top of that snapshot.
201
+ </instructions>
202
+
109
203
  <policy>
110
204
  # Accounting policy
111
205
  Cash basis. Crypto trades are revenue income...
112
206
  </policy>
113
207
 
114
- <snapshot as_of="2026-03-01">
115
- {"balances":{"USDC":45000},"ytd_pnl":18450}
208
+ <summary>
209
+ {
210
+ "by_type": {"income":{"count":12,"total":1700},"fee":{"count":3,"total":-55}},
211
+ "by_currency": {"USD":{"count":15,"total":1645}},
212
+ "cash_flow": {"inflows":1700,"outflows":-55,"net":1645}
213
+ }
214
+ </summary>
215
+
216
+ <snapshot as_of="2026-03-01T00:00:00.000Z">
217
+ {"balances":{"USD":45000},"ytd_pnl":18450}
116
218
  </snapshot>
117
219
 
118
- <events count="47" after="2026-03-01" before="2026-03-31">
220
+ <events count="47" after="2026-03-01T00:00:00.000Z" before="2026-03-31T23:59:59.999Z">
119
221
  {"ts":"...","source":"stripe","type":"payment","data":{"amount":500,...}}
120
222
  {"ts":"...","source":"bank","type":"fee","data":{"amount":-55,...}}
121
223
  ...
122
224
  </events>
225
+ </context>
123
226
  ```
124
227
 
125
228
  ## 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.3",
4
4
  "description": "Accounting by inference, not by engine. Zero dependencies.",
5
5
  "type": "module",
6
6
  "repository": {