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.
- package/README.md +108 -5
- package/build/cli.js +155 -0
- 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
|
|
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
|
|
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
|
-
<
|
|
115
|
-
{
|
|
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-
|
|
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());
|