@stithy/mileage 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/dist/store.js ADDED
@@ -0,0 +1,289 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { rateCentsPerMile } from "./rates.js";
3
+ import { VALID_CATEGORIES } from "./db.js";
4
+ function rowToVehicle(r) {
5
+ return { ...r, is_default: !!r.is_default };
6
+ }
7
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
8
+ function assertDate(d) {
9
+ if (!ISO_DATE.test(d)) {
10
+ throw new Error(`date must be ISO yyyy-mm-dd, got '${d}'`);
11
+ }
12
+ }
13
+ function assertCategory(c) {
14
+ if (!VALID_CATEGORIES.includes(c)) {
15
+ throw new Error(`invalid category '${c}'. Valid: ${VALID_CATEGORIES.join(", ")}`);
16
+ }
17
+ }
18
+ export class Store {
19
+ db;
20
+ constructor(db) {
21
+ this.db = db;
22
+ }
23
+ // -- vehicles --
24
+ addVehicle(input) {
25
+ if (input.placed_in_service_date)
26
+ assertDate(input.placed_in_service_date);
27
+ if (input.is_default) {
28
+ this.db.prepare(`UPDATE vehicles SET is_default = 0`).run();
29
+ }
30
+ const id = randomUUID();
31
+ const now = Date.now();
32
+ const placed = input.placed_in_service_date
33
+ ? Date.parse(`${input.placed_in_service_date}T00:00:00Z`)
34
+ : null;
35
+ this.db
36
+ .prepare(`INSERT INTO vehicles (id, name, year, make, model, license_plate, is_default, placed_in_service_date, created_at)
37
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
38
+ .run(id, input.name, input.year ?? null, input.make ?? null, input.model ?? null, input.license_plate ?? null, input.is_default ? 1 : 0, placed, now);
39
+ return this.getVehicle(id);
40
+ }
41
+ getVehicle(id) {
42
+ const r = this.db
43
+ .prepare(`SELECT * FROM vehicles WHERE id = ?`)
44
+ .get(id);
45
+ return r ? rowToVehicle(r) : null;
46
+ }
47
+ listVehicles() {
48
+ const rows = this.db
49
+ .prepare(`SELECT * FROM vehicles ORDER BY is_default DESC, name ASC`)
50
+ .all();
51
+ return rows.map(rowToVehicle);
52
+ }
53
+ setDefaultVehicle(id) {
54
+ const v = this.getVehicle(id);
55
+ if (!v)
56
+ return null;
57
+ this.db.prepare(`UPDATE vehicles SET is_default = 0`).run();
58
+ this.db.prepare(`UPDATE vehicles SET is_default = 1 WHERE id = ?`).run(id);
59
+ return this.getVehicle(id);
60
+ }
61
+ defaultVehicle() {
62
+ const r = this.db
63
+ .prepare(`SELECT * FROM vehicles WHERE is_default = 1 LIMIT 1`)
64
+ .get();
65
+ return r ? rowToVehicle(r) : null;
66
+ }
67
+ removeVehicle(id) {
68
+ // restrict if trips exist
69
+ const trips = this.db
70
+ .prepare(`SELECT COUNT(*) as c FROM trips WHERE vehicle_id = ?`)
71
+ .get(id);
72
+ if ((trips?.c ?? 0) > 0) {
73
+ throw new Error(`vehicle ${id} has ${trips.c} trip(s) recorded — delete or reassign trips first`);
74
+ }
75
+ const r = this.db.prepare(`DELETE FROM vehicles WHERE id = ?`).run(id);
76
+ return r.changes > 0;
77
+ }
78
+ // -- trips --
79
+ addTrip(input) {
80
+ assertDate(input.trip_date);
81
+ assertCategory(input.category);
82
+ if (!Number.isFinite(input.miles) || input.miles <= 0) {
83
+ throw new Error(`miles must be a positive number, got ${input.miles}`);
84
+ }
85
+ if (input.miles > 5000) {
86
+ throw new Error(`single trip > 5000 miles is suspicious — split into multiple trips`);
87
+ }
88
+ let vehicleId = input.vehicle_id;
89
+ if (!vehicleId) {
90
+ const def = this.defaultVehicle();
91
+ if (!def) {
92
+ throw new Error(`no vehicle_id provided and no default vehicle set — call add_vehicle or set_default_vehicle first`);
93
+ }
94
+ vehicleId = def.id;
95
+ }
96
+ else if (!this.getVehicle(vehicleId)) {
97
+ throw new Error(`vehicle ${vehicleId} not found`);
98
+ }
99
+ const id = randomUUID();
100
+ const now = Date.now();
101
+ this.db
102
+ .prepare(`INSERT INTO trips (id, vehicle_id, trip_date, miles, category, purpose, start_location, end_location, notes, created_at)
103
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
104
+ .run(id, vehicleId, input.trip_date, input.miles, input.category, input.purpose, input.start_location ?? null, input.end_location ?? null, input.notes ?? null, now);
105
+ return this.getTrip(id);
106
+ }
107
+ getTrip(id) {
108
+ const r = this.db.prepare(`SELECT * FROM trips WHERE id = ?`).get(id);
109
+ return r ? { ...r, category: r.category } : null;
110
+ }
111
+ updateTrip(id, patch) {
112
+ const existing = this.getTrip(id);
113
+ if (!existing)
114
+ return null;
115
+ if (patch.trip_date !== undefined)
116
+ assertDate(patch.trip_date);
117
+ if (patch.category !== undefined)
118
+ assertCategory(patch.category);
119
+ if (patch.miles !== undefined) {
120
+ if (!Number.isFinite(patch.miles) || patch.miles <= 0) {
121
+ throw new Error(`miles must be a positive number, got ${patch.miles}`);
122
+ }
123
+ }
124
+ if (patch.vehicle_id !== undefined && !this.getVehicle(patch.vehicle_id)) {
125
+ throw new Error(`vehicle ${patch.vehicle_id} not found`);
126
+ }
127
+ const next = {
128
+ ...existing,
129
+ ...patch,
130
+ category: patch.category ?? existing.category,
131
+ };
132
+ this.db
133
+ .prepare(`UPDATE trips SET vehicle_id=?, trip_date=?, miles=?, category=?, purpose=?, start_location=?, end_location=?, notes=? WHERE id=?`)
134
+ .run(next.vehicle_id, next.trip_date, next.miles, next.category, next.purpose, next.start_location, next.end_location, next.notes, id);
135
+ return next;
136
+ }
137
+ deleteTrip(id) {
138
+ const r = this.db.prepare(`DELETE FROM trips WHERE id = ?`).run(id);
139
+ return r.changes > 0;
140
+ }
141
+ listTrips(filter) {
142
+ const where = [];
143
+ const params = [];
144
+ if (filter?.from_date) {
145
+ assertDate(filter.from_date);
146
+ where.push("trip_date >= ?");
147
+ params.push(filter.from_date);
148
+ }
149
+ if (filter?.to_date) {
150
+ assertDate(filter.to_date);
151
+ where.push("trip_date <= ?");
152
+ params.push(filter.to_date);
153
+ }
154
+ if (filter?.category) {
155
+ assertCategory(filter.category);
156
+ where.push("category = ?");
157
+ params.push(filter.category);
158
+ }
159
+ if (filter?.vehicle_id) {
160
+ where.push("vehicle_id = ?");
161
+ params.push(filter.vehicle_id);
162
+ }
163
+ const sql = `SELECT * FROM trips` +
164
+ (where.length ? ` WHERE ${where.join(" AND ")}` : ``) +
165
+ ` ORDER BY trip_date DESC, created_at DESC` +
166
+ ` LIMIT ? OFFSET ?`;
167
+ params.push(filter?.limit ?? 200, filter?.offset ?? 0);
168
+ const rows = this.db.prepare(sql).all(...params);
169
+ return rows.map((r) => ({ ...r, category: r.category }));
170
+ }
171
+ // -- odometer --
172
+ addOdometer(input) {
173
+ assertDate(input.snapshot_date);
174
+ if (!Number.isInteger(input.reading) || input.reading < 0) {
175
+ throw new Error(`reading must be a non-negative integer`);
176
+ }
177
+ if (!this.getVehicle(input.vehicle_id)) {
178
+ throw new Error(`vehicle ${input.vehicle_id} not found`);
179
+ }
180
+ const id = randomUUID();
181
+ const now = Date.now();
182
+ this.db
183
+ .prepare(`INSERT INTO odometer_snapshots (id, vehicle_id, snapshot_date, reading, note, created_at) VALUES (?, ?, ?, ?, ?, ?)`)
184
+ .run(id, input.vehicle_id, input.snapshot_date, input.reading, input.note ?? null, now);
185
+ return {
186
+ id,
187
+ vehicle_id: input.vehicle_id,
188
+ snapshot_date: input.snapshot_date,
189
+ reading: input.reading,
190
+ note: input.note ?? null,
191
+ created_at: now,
192
+ };
193
+ }
194
+ listOdometers(vehicle_id) {
195
+ const rows = this.db
196
+ .prepare(`SELECT * FROM odometer_snapshots WHERE vehicle_id = ? ORDER BY snapshot_date ASC`)
197
+ .all(vehicle_id);
198
+ return rows;
199
+ }
200
+ // -- summaries / tax math --
201
+ /**
202
+ * Sum miles per category for a date range. Use this for monthly check-ins.
203
+ */
204
+ summary(input) {
205
+ assertDate(input.from_date);
206
+ assertDate(input.to_date);
207
+ const trips = this.listTrips({
208
+ from_date: input.from_date,
209
+ to_date: input.to_date,
210
+ vehicle_id: input.vehicle_id,
211
+ limit: 100_000,
212
+ });
213
+ const by_category = {
214
+ business: { miles: 0, trips: 0 },
215
+ medical: { miles: 0, trips: 0 },
216
+ moving: { miles: 0, trips: 0 },
217
+ charitable: { miles: 0, trips: 0 },
218
+ };
219
+ for (const t of trips) {
220
+ by_category[t.category].miles += t.miles;
221
+ by_category[t.category].trips += 1;
222
+ }
223
+ return {
224
+ by_category,
225
+ total_miles: trips.reduce((a, t) => a + t.miles, 0),
226
+ total_trips: trips.length,
227
+ };
228
+ }
229
+ /**
230
+ * Annual deduction for a tax year: miles per category × IRS rate for that year.
231
+ */
232
+ annualDeduction(year, vehicle_id) {
233
+ const sum = this.summary({
234
+ from_date: `${year}-01-01`,
235
+ to_date: `${year}-12-31`,
236
+ vehicle_id,
237
+ });
238
+ const by_category = {};
239
+ let total = 0;
240
+ for (const cat of ["business", "medical", "moving", "charitable"]) {
241
+ const rate = rateCentsPerMile(year, cat);
242
+ const miles = sum.by_category[cat].miles;
243
+ const deduction = +((miles * rate) / 100).toFixed(2);
244
+ by_category[cat] = {
245
+ miles,
246
+ trips: sum.by_category[cat].trips,
247
+ rate_cents_per_mile: rate,
248
+ deduction_dollars: deduction,
249
+ };
250
+ total += deduction;
251
+ }
252
+ return {
253
+ year,
254
+ by_category,
255
+ total_miles: sum.total_miles,
256
+ total_deduction_dollars: +total.toFixed(2),
257
+ };
258
+ }
259
+ /**
260
+ * IRS-audit-ready CSV. One row per trip. Header row matches what an auditor expects:
261
+ * Date | Miles | Category | Purpose | Start | End | Vehicle | Notes
262
+ */
263
+ exportCsv(filter) {
264
+ const trips = this.listTrips({ ...filter, limit: 100_000 });
265
+ const vehicles = new Map(this.listVehicles().map((v) => [v.id, v.name]));
266
+ const header = "Date,Miles,Category,Purpose,Start,End,Vehicle,Notes";
267
+ const rows = trips.map((t) => {
268
+ const vehicle = vehicles.get(t.vehicle_id) ?? t.vehicle_id;
269
+ return [
270
+ t.trip_date,
271
+ t.miles,
272
+ t.category,
273
+ csvCell(t.purpose),
274
+ csvCell(t.start_location ?? ""),
275
+ csvCell(t.end_location ?? ""),
276
+ csvCell(vehicle),
277
+ csvCell(t.notes ?? ""),
278
+ ].join(",");
279
+ });
280
+ return [header, ...rows].join("\n");
281
+ }
282
+ }
283
+ function csvCell(v) {
284
+ if (v.includes(",") || v.includes('"') || v.includes("\n")) {
285
+ return `"${v.replace(/"/g, '""')}"`;
286
+ }
287
+ return v;
288
+ }
289
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@stithy/mileage",
3
+ "version": "0.1.0",
4
+ "description": "IRS-audit-proof mileage tracker MCP server — log trips, manage vehicles, export Schedule C — local-first, no subscription",
5
+ "type": "module",
6
+ "bin": {
7
+ "stithy-mileage": "dist/cli.js"
8
+ },
9
+ "main": "./dist/server.js",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/resolceo-ai/stithy-mcp.git",
18
+ "directory": "servers/mileage"
19
+ },
20
+ "homepage": "https://github.com/resolceo-ai/stithy-mcp/tree/main/servers/mileage",
21
+ "bugs": "https://github.com/resolceo-ai/stithy-mcp/issues",
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "dev": "tsx src/server.ts",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "typecheck": "tsc --noEmit"
28
+ },
29
+ "keywords": [
30
+ "mcp",
31
+ "model-context-protocol",
32
+ "mileage",
33
+ "tax",
34
+ "schedule-c",
35
+ "irs",
36
+ "freelancer",
37
+ "self-employed"
38
+ ],
39
+ "author": "Stithy",
40
+ "license": "BUSL-1.1",
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
44
+ "dependencies": {
45
+ "@mcp_marketplace/license": "^1.1.0",
46
+ "@modelcontextprotocol/sdk": "^1.0.4",
47
+ "better-sqlite3": "^11.5.0",
48
+ "zod": "^3.23.8"
49
+ },
50
+ "devDependencies": {
51
+ "@types/better-sqlite3": "^7.6.11",
52
+ "@types/node": "^22.10.0",
53
+ "tsx": "^4.19.2",
54
+ "typescript": "^5.7.2",
55
+ "vitest": "^2.1.8"
56
+ }
57
+ }