@hasna/microservices 0.0.10 → 0.0.11

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 (90) hide show
  1. package/bin/index.js +86 -1
  2. package/bin/mcp.js +86 -1
  3. package/dist/index.js +86 -1
  4. package/microservices/microservice-analytics/package.json +27 -0
  5. package/microservices/microservice-analytics/src/cli/index.ts +373 -0
  6. package/microservices/microservice-analytics/src/db/analytics.ts +564 -0
  7. package/microservices/microservice-analytics/src/db/database.ts +93 -0
  8. package/microservices/microservice-analytics/src/db/migrations.ts +50 -0
  9. package/microservices/microservice-analytics/src/index.ts +37 -0
  10. package/microservices/microservice-analytics/src/mcp/index.ts +334 -0
  11. package/microservices/microservice-assets/package.json +27 -0
  12. package/microservices/microservice-assets/src/cli/index.ts +375 -0
  13. package/microservices/microservice-assets/src/db/assets.ts +370 -0
  14. package/microservices/microservice-assets/src/db/database.ts +93 -0
  15. package/microservices/microservice-assets/src/db/migrations.ts +51 -0
  16. package/microservices/microservice-assets/src/index.ts +32 -0
  17. package/microservices/microservice-assets/src/mcp/index.ts +346 -0
  18. package/microservices/microservice-compliance/package.json +27 -0
  19. package/microservices/microservice-compliance/src/cli/index.ts +467 -0
  20. package/microservices/microservice-compliance/src/db/compliance.ts +633 -0
  21. package/microservices/microservice-compliance/src/db/database.ts +93 -0
  22. package/microservices/microservice-compliance/src/db/migrations.ts +63 -0
  23. package/microservices/microservice-compliance/src/index.ts +46 -0
  24. package/microservices/microservice-compliance/src/mcp/index.ts +438 -0
  25. package/microservices/microservice-habits/package.json +27 -0
  26. package/microservices/microservice-habits/src/cli/index.ts +315 -0
  27. package/microservices/microservice-habits/src/db/database.ts +93 -0
  28. package/microservices/microservice-habits/src/db/habits.ts +451 -0
  29. package/microservices/microservice-habits/src/db/migrations.ts +46 -0
  30. package/microservices/microservice-habits/src/index.ts +31 -0
  31. package/microservices/microservice-habits/src/mcp/index.ts +313 -0
  32. package/microservices/microservice-health/package.json +27 -0
  33. package/microservices/microservice-health/src/cli/index.ts +484 -0
  34. package/microservices/microservice-health/src/db/database.ts +93 -0
  35. package/microservices/microservice-health/src/db/health.ts +708 -0
  36. package/microservices/microservice-health/src/db/migrations.ts +70 -0
  37. package/microservices/microservice-health/src/index.ts +63 -0
  38. package/microservices/microservice-health/src/mcp/index.ts +437 -0
  39. package/microservices/microservice-notifications/package.json +27 -0
  40. package/microservices/microservice-notifications/src/cli/index.ts +349 -0
  41. package/microservices/microservice-notifications/src/db/database.ts +93 -0
  42. package/microservices/microservice-notifications/src/db/migrations.ts +62 -0
  43. package/microservices/microservice-notifications/src/db/notifications.ts +509 -0
  44. package/microservices/microservice-notifications/src/index.ts +41 -0
  45. package/microservices/microservice-notifications/src/mcp/index.ts +422 -0
  46. package/microservices/microservice-products/package.json +27 -0
  47. package/microservices/microservice-products/src/cli/index.ts +416 -0
  48. package/microservices/microservice-products/src/db/categories.ts +154 -0
  49. package/microservices/microservice-products/src/db/database.ts +93 -0
  50. package/microservices/microservice-products/src/db/migrations.ts +58 -0
  51. package/microservices/microservice-products/src/db/pricing-tiers.ts +66 -0
  52. package/microservices/microservice-products/src/db/products.ts +452 -0
  53. package/microservices/microservice-products/src/index.ts +53 -0
  54. package/microservices/microservice-products/src/mcp/index.ts +453 -0
  55. package/microservices/microservice-projects/package.json +27 -0
  56. package/microservices/microservice-projects/src/cli/index.ts +480 -0
  57. package/microservices/microservice-projects/src/db/database.ts +93 -0
  58. package/microservices/microservice-projects/src/db/migrations.ts +65 -0
  59. package/microservices/microservice-projects/src/db/projects.ts +715 -0
  60. package/microservices/microservice-projects/src/index.ts +57 -0
  61. package/microservices/microservice-projects/src/mcp/index.ts +501 -0
  62. package/microservices/microservice-proposals/package.json +27 -0
  63. package/microservices/microservice-proposals/src/cli/index.ts +400 -0
  64. package/microservices/microservice-proposals/src/db/database.ts +93 -0
  65. package/microservices/microservice-proposals/src/db/migrations.ts +52 -0
  66. package/microservices/microservice-proposals/src/db/proposals.ts +532 -0
  67. package/microservices/microservice-proposals/src/index.ts +37 -0
  68. package/microservices/microservice-proposals/src/mcp/index.ts +375 -0
  69. package/microservices/microservice-reading/package.json +27 -0
  70. package/microservices/microservice-reading/src/cli/index.ts +464 -0
  71. package/microservices/microservice-reading/src/db/database.ts +93 -0
  72. package/microservices/microservice-reading/src/db/migrations.ts +59 -0
  73. package/microservices/microservice-reading/src/db/reading.ts +524 -0
  74. package/microservices/microservice-reading/src/index.ts +51 -0
  75. package/microservices/microservice-reading/src/mcp/index.ts +368 -0
  76. package/microservices/microservice-travel/package.json +27 -0
  77. package/microservices/microservice-travel/src/cli/index.ts +505 -0
  78. package/microservices/microservice-travel/src/db/database.ts +93 -0
  79. package/microservices/microservice-travel/src/db/migrations.ts +77 -0
  80. package/microservices/microservice-travel/src/db/travel.ts +802 -0
  81. package/microservices/microservice-travel/src/index.ts +60 -0
  82. package/microservices/microservice-travel/src/mcp/index.ts +495 -0
  83. package/microservices/microservice-wiki/package.json +27 -0
  84. package/microservices/microservice-wiki/src/cli/index.ts +345 -0
  85. package/microservices/microservice-wiki/src/db/database.ts +93 -0
  86. package/microservices/microservice-wiki/src/db/migrations.ts +55 -0
  87. package/microservices/microservice-wiki/src/db/wiki.ts +395 -0
  88. package/microservices/microservice-wiki/src/index.ts +32 -0
  89. package/microservices/microservice-wiki/src/mcp/index.ts +344 -0
  90. package/package.json +1 -1
@@ -0,0 +1,484 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import {
5
+ createMetric,
6
+ listMetrics,
7
+ getMetricTrend,
8
+ deleteMetric,
9
+ } from "../db/health.js";
10
+ import {
11
+ createMedication,
12
+ getMedication,
13
+ listMedications,
14
+ updateMedication,
15
+ deactivateMedication,
16
+ } from "../db/health.js";
17
+ import {
18
+ createAppointment,
19
+ listAppointments,
20
+ completeAppointment,
21
+ cancelAppointment,
22
+ getUpcomingAppointments,
23
+ } from "../db/health.js";
24
+ import {
25
+ createFitnessLog,
26
+ listFitnessLogs,
27
+ getFitnessStats,
28
+ } from "../db/health.js";
29
+ import { getHealthSummary } from "../db/health.js";
30
+
31
+ const program = new Command();
32
+
33
+ program
34
+ .name("microservice-health")
35
+ .description("Health tracking microservice")
36
+ .version("0.0.1");
37
+
38
+ // --- Metrics ---
39
+
40
+ const metricCmd = program
41
+ .command("metric")
42
+ .description("Health metrics tracking");
43
+
44
+ metricCmd
45
+ .command("record")
46
+ .description("Record a health metric")
47
+ .requiredOption("--type <type>", "Metric type (e.g. weight, blood_pressure, heart_rate)")
48
+ .requiredOption("--value <value>", "Metric value")
49
+ .option("--unit <unit>", "Unit of measurement")
50
+ .option("--notes <notes>", "Notes")
51
+ .option("--recorded-at <datetime>", "When recorded (ISO 8601)")
52
+ .option("--json", "Output as JSON", false)
53
+ .action((opts) => {
54
+ const metric = createMetric({
55
+ type: opts.type,
56
+ value: parseFloat(opts.value),
57
+ unit: opts.unit,
58
+ notes: opts.notes,
59
+ recorded_at: opts.recordedAt,
60
+ });
61
+
62
+ if (opts.json) {
63
+ console.log(JSON.stringify(metric, null, 2));
64
+ } else {
65
+ console.log(`Recorded ${metric.type}: ${metric.value}${metric.unit ? ` ${metric.unit}` : ""} (${metric.id})`);
66
+ }
67
+ });
68
+
69
+ metricCmd
70
+ .command("list")
71
+ .description("List health metrics")
72
+ .option("--type <type>", "Filter by type")
73
+ .option("--limit <n>", "Limit results")
74
+ .option("--json", "Output as JSON", false)
75
+ .action((opts) => {
76
+ const metrics = listMetrics({
77
+ type: opts.type,
78
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
79
+ });
80
+
81
+ if (opts.json) {
82
+ console.log(JSON.stringify(metrics, null, 2));
83
+ } else {
84
+ if (metrics.length === 0) {
85
+ console.log("No metrics found.");
86
+ return;
87
+ }
88
+ for (const m of metrics) {
89
+ const unit = m.unit ? ` ${m.unit}` : "";
90
+ console.log(` ${m.type}: ${m.value}${unit} (${m.recorded_at})`);
91
+ }
92
+ console.log(`\n${metrics.length} metric(s)`);
93
+ }
94
+ });
95
+
96
+ metricCmd
97
+ .command("trend")
98
+ .description("Show metric trend over time")
99
+ .requiredOption("--type <type>", "Metric type")
100
+ .option("--days <n>", "Number of days", "30")
101
+ .option("--json", "Output as JSON", false)
102
+ .action((opts) => {
103
+ const trend = getMetricTrend(opts.type, parseInt(opts.days));
104
+
105
+ if (opts.json) {
106
+ console.log(JSON.stringify(trend, null, 2));
107
+ } else {
108
+ if (trend.length === 0) {
109
+ console.log(`No data for ${opts.type} in the last ${opts.days} days.`);
110
+ return;
111
+ }
112
+ console.log(`Trend for ${opts.type} (last ${opts.days} days):`);
113
+ for (const point of trend) {
114
+ console.log(` ${point.date}: ${point.value}`);
115
+ }
116
+ }
117
+ });
118
+
119
+ // --- Medications ---
120
+
121
+ const medCmd = program
122
+ .command("medication")
123
+ .alias("med")
124
+ .description("Medication management");
125
+
126
+ medCmd
127
+ .command("add")
128
+ .description("Add a medication")
129
+ .requiredOption("--name <name>", "Medication name")
130
+ .option("--dosage <dosage>", "Dosage (e.g. 10mg)")
131
+ .option("--frequency <frequency>", "Frequency (e.g. twice daily)")
132
+ .option("--start-date <date>", "Start date")
133
+ .option("--end-date <date>", "End date")
134
+ .option("--refill-date <date>", "Next refill date")
135
+ .option("--notes <notes>", "Notes")
136
+ .option("--json", "Output as JSON", false)
137
+ .action((opts) => {
138
+ const med = createMedication({
139
+ name: opts.name,
140
+ dosage: opts.dosage,
141
+ frequency: opts.frequency,
142
+ start_date: opts.startDate,
143
+ end_date: opts.endDate,
144
+ refill_date: opts.refillDate,
145
+ notes: opts.notes,
146
+ });
147
+
148
+ if (opts.json) {
149
+ console.log(JSON.stringify(med, null, 2));
150
+ } else {
151
+ console.log(`Added medication: ${med.name}${med.dosage ? ` (${med.dosage})` : ""} (${med.id})`);
152
+ }
153
+ });
154
+
155
+ medCmd
156
+ .command("list")
157
+ .description("List medications")
158
+ .option("--active", "Show only active medications")
159
+ .option("--all", "Show all medications including inactive")
160
+ .option("--search <query>", "Search by name")
161
+ .option("--json", "Output as JSON", false)
162
+ .action((opts) => {
163
+ const meds = listMedications({
164
+ active: opts.all ? undefined : (opts.active !== undefined ? true : undefined),
165
+ search: opts.search,
166
+ });
167
+
168
+ if (opts.json) {
169
+ console.log(JSON.stringify(meds, null, 2));
170
+ } else {
171
+ if (meds.length === 0) {
172
+ console.log("No medications found.");
173
+ return;
174
+ }
175
+ for (const m of meds) {
176
+ const dosage = m.dosage ? ` ${m.dosage}` : "";
177
+ const freq = m.frequency ? ` (${m.frequency})` : "";
178
+ const status = m.active ? "" : " [INACTIVE]";
179
+ console.log(` ${m.name}${dosage}${freq}${status}`);
180
+ }
181
+ console.log(`\n${meds.length} medication(s)`);
182
+ }
183
+ });
184
+
185
+ medCmd
186
+ .command("get")
187
+ .description("Get medication details")
188
+ .argument("<id>", "Medication ID")
189
+ .option("--json", "Output as JSON", false)
190
+ .action((id, opts) => {
191
+ const med = getMedication(id);
192
+ if (!med) {
193
+ console.error(`Medication '${id}' not found.`);
194
+ process.exit(1);
195
+ }
196
+
197
+ if (opts.json) {
198
+ console.log(JSON.stringify(med, null, 2));
199
+ } else {
200
+ console.log(`${med.name}`);
201
+ if (med.dosage) console.log(` Dosage: ${med.dosage}`);
202
+ if (med.frequency) console.log(` Frequency: ${med.frequency}`);
203
+ if (med.start_date) console.log(` Start: ${med.start_date}`);
204
+ if (med.end_date) console.log(` End: ${med.end_date}`);
205
+ if (med.refill_date) console.log(` Refill: ${med.refill_date}`);
206
+ console.log(` Active: ${med.active ? "Yes" : "No"}`);
207
+ if (med.notes) console.log(` Notes: ${med.notes}`);
208
+ }
209
+ });
210
+
211
+ medCmd
212
+ .command("update")
213
+ .description("Update a medication")
214
+ .argument("<id>", "Medication ID")
215
+ .option("--name <name>", "Name")
216
+ .option("--dosage <dosage>", "Dosage")
217
+ .option("--frequency <frequency>", "Frequency")
218
+ .option("--start-date <date>", "Start date")
219
+ .option("--end-date <date>", "End date")
220
+ .option("--refill-date <date>", "Refill date")
221
+ .option("--notes <notes>", "Notes")
222
+ .option("--json", "Output as JSON", false)
223
+ .action((id, opts) => {
224
+ const input: Record<string, unknown> = {};
225
+ if (opts.name !== undefined) input.name = opts.name;
226
+ if (opts.dosage !== undefined) input.dosage = opts.dosage;
227
+ if (opts.frequency !== undefined) input.frequency = opts.frequency;
228
+ if (opts.startDate !== undefined) input.start_date = opts.startDate;
229
+ if (opts.endDate !== undefined) input.end_date = opts.endDate;
230
+ if (opts.refillDate !== undefined) input.refill_date = opts.refillDate;
231
+ if (opts.notes !== undefined) input.notes = opts.notes;
232
+
233
+ const med = updateMedication(id, input);
234
+ if (!med) {
235
+ console.error(`Medication '${id}' not found.`);
236
+ process.exit(1);
237
+ }
238
+
239
+ if (opts.json) {
240
+ console.log(JSON.stringify(med, null, 2));
241
+ } else {
242
+ console.log(`Updated: ${med.name}`);
243
+ }
244
+ });
245
+
246
+ medCmd
247
+ .command("deactivate")
248
+ .description("Deactivate a medication")
249
+ .argument("<id>", "Medication ID")
250
+ .option("--json", "Output as JSON", false)
251
+ .action((id, opts) => {
252
+ const med = deactivateMedication(id);
253
+ if (!med) {
254
+ console.error(`Medication '${id}' not found.`);
255
+ process.exit(1);
256
+ }
257
+
258
+ if (opts.json) {
259
+ console.log(JSON.stringify(med, null, 2));
260
+ } else {
261
+ console.log(`Deactivated: ${med.name}`);
262
+ }
263
+ });
264
+
265
+ // --- Appointments ---
266
+
267
+ const apptCmd = program
268
+ .command("appointment")
269
+ .alias("appt")
270
+ .description("Appointment management");
271
+
272
+ apptCmd
273
+ .command("schedule")
274
+ .description("Schedule an appointment")
275
+ .requiredOption("--provider <provider>", "Provider name")
276
+ .requiredOption("--scheduled-at <datetime>", "Date/time (ISO 8601)")
277
+ .option("--specialty <specialty>", "Specialty")
278
+ .option("--location <location>", "Location")
279
+ .option("--notes <notes>", "Notes")
280
+ .option("--follow-up <date>", "Follow-up date")
281
+ .option("--json", "Output as JSON", false)
282
+ .action((opts) => {
283
+ const appt = createAppointment({
284
+ provider: opts.provider,
285
+ scheduled_at: opts.scheduledAt,
286
+ specialty: opts.specialty,
287
+ location: opts.location,
288
+ notes: opts.notes,
289
+ follow_up_date: opts.followUp,
290
+ });
291
+
292
+ if (opts.json) {
293
+ console.log(JSON.stringify(appt, null, 2));
294
+ } else {
295
+ console.log(`Scheduled: ${appt.provider} on ${appt.scheduled_at} (${appt.id})`);
296
+ }
297
+ });
298
+
299
+ apptCmd
300
+ .command("list")
301
+ .description("List appointments")
302
+ .option("--status <status>", "Filter by status")
303
+ .option("--provider <provider>", "Filter by provider")
304
+ .option("--upcoming <days>", "Show upcoming appointments within N days")
305
+ .option("--limit <n>", "Limit results")
306
+ .option("--json", "Output as JSON", false)
307
+ .action((opts) => {
308
+ let appointments;
309
+ if (opts.upcoming) {
310
+ appointments = getUpcomingAppointments(parseInt(opts.upcoming));
311
+ } else {
312
+ appointments = listAppointments({
313
+ status: opts.status,
314
+ provider: opts.provider,
315
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
316
+ });
317
+ }
318
+
319
+ if (opts.json) {
320
+ console.log(JSON.stringify(appointments, null, 2));
321
+ } else {
322
+ if (appointments.length === 0) {
323
+ console.log("No appointments found.");
324
+ return;
325
+ }
326
+ for (const a of appointments) {
327
+ const specialty = a.specialty ? ` (${a.specialty})` : "";
328
+ const location = a.location ? ` @ ${a.location}` : "";
329
+ console.log(` ${a.provider}${specialty}${location} - ${a.scheduled_at} [${a.status}]`);
330
+ }
331
+ console.log(`\n${appointments.length} appointment(s)`);
332
+ }
333
+ });
334
+
335
+ apptCmd
336
+ .command("complete")
337
+ .description("Mark an appointment as completed")
338
+ .argument("<id>", "Appointment ID")
339
+ .option("--json", "Output as JSON", false)
340
+ .action((id, opts) => {
341
+ const appt = completeAppointment(id);
342
+ if (!appt) {
343
+ console.error(`Appointment '${id}' not found.`);
344
+ process.exit(1);
345
+ }
346
+
347
+ if (opts.json) {
348
+ console.log(JSON.stringify(appt, null, 2));
349
+ } else {
350
+ console.log(`Completed: ${appt.provider} (${appt.scheduled_at})`);
351
+ }
352
+ });
353
+
354
+ apptCmd
355
+ .command("cancel")
356
+ .description("Cancel an appointment")
357
+ .argument("<id>", "Appointment ID")
358
+ .option("--json", "Output as JSON", false)
359
+ .action((id, opts) => {
360
+ const appt = cancelAppointment(id);
361
+ if (!appt) {
362
+ console.error(`Appointment '${id}' not found.`);
363
+ process.exit(1);
364
+ }
365
+
366
+ if (opts.json) {
367
+ console.log(JSON.stringify(appt, null, 2));
368
+ } else {
369
+ console.log(`Cancelled: ${appt.provider} (${appt.scheduled_at})`);
370
+ }
371
+ });
372
+
373
+ // --- Fitness ---
374
+
375
+ const fitnessCmd = program
376
+ .command("fitness")
377
+ .description("Fitness log tracking");
378
+
379
+ fitnessCmd
380
+ .command("log")
381
+ .description("Log a fitness activity")
382
+ .requiredOption("--activity <activity>", "Activity type (e.g. running, swimming)")
383
+ .option("--duration <min>", "Duration in minutes")
384
+ .option("--calories <cal>", "Calories burned")
385
+ .option("--distance <dist>", "Distance")
386
+ .option("--notes <notes>", "Notes")
387
+ .option("--logged-at <datetime>", "When logged (ISO 8601)")
388
+ .option("--json", "Output as JSON", false)
389
+ .action((opts) => {
390
+ const log = createFitnessLog({
391
+ activity: opts.activity,
392
+ duration_min: opts.duration ? parseInt(opts.duration) : undefined,
393
+ calories_burned: opts.calories ? parseInt(opts.calories) : undefined,
394
+ distance: opts.distance ? parseFloat(opts.distance) : undefined,
395
+ notes: opts.notes,
396
+ logged_at: opts.loggedAt,
397
+ });
398
+
399
+ if (opts.json) {
400
+ console.log(JSON.stringify(log, null, 2));
401
+ } else {
402
+ const dur = log.duration_min ? ` ${log.duration_min}min` : "";
403
+ const cal = log.calories_burned ? ` ${log.calories_burned}cal` : "";
404
+ console.log(`Logged: ${log.activity}${dur}${cal} (${log.id})`);
405
+ }
406
+ });
407
+
408
+ fitnessCmd
409
+ .command("list")
410
+ .description("List fitness logs")
411
+ .option("--activity <activity>", "Filter by activity")
412
+ .option("--limit <n>", "Limit results")
413
+ .option("--json", "Output as JSON", false)
414
+ .action((opts) => {
415
+ const logs = listFitnessLogs({
416
+ activity: opts.activity,
417
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
418
+ });
419
+
420
+ if (opts.json) {
421
+ console.log(JSON.stringify(logs, null, 2));
422
+ } else {
423
+ if (logs.length === 0) {
424
+ console.log("No fitness logs found.");
425
+ return;
426
+ }
427
+ for (const l of logs) {
428
+ const dur = l.duration_min ? ` ${l.duration_min}min` : "";
429
+ const cal = l.calories_burned ? ` ${l.calories_burned}cal` : "";
430
+ const dist = l.distance ? ` ${l.distance}` : "";
431
+ console.log(` ${l.activity}${dur}${cal}${dist} (${l.logged_at})`);
432
+ }
433
+ console.log(`\n${logs.length} log(s)`);
434
+ }
435
+ });
436
+
437
+ fitnessCmd
438
+ .command("stats")
439
+ .description("Show fitness statistics")
440
+ .option("--days <n>", "Number of days", "30")
441
+ .option("--json", "Output as JSON", false)
442
+ .action((opts) => {
443
+ const stats = getFitnessStats(parseInt(opts.days));
444
+
445
+ if (opts.json) {
446
+ console.log(JSON.stringify(stats, null, 2));
447
+ } else {
448
+ console.log(`Fitness stats (last ${opts.days} days):`);
449
+ console.log(` Total sessions: ${stats.total_sessions}`);
450
+ console.log(` Total minutes: ${stats.total_minutes}`);
451
+ console.log(` Total calories: ${stats.total_calories}`);
452
+ console.log(` Avg duration: ${stats.avg_duration} min`);
453
+ }
454
+ });
455
+
456
+ // --- Summary ---
457
+
458
+ program
459
+ .command("summary")
460
+ .description("Show overall health summary")
461
+ .option("--json", "Output as JSON", false)
462
+ .action((opts) => {
463
+ const summary = getHealthSummary();
464
+
465
+ if (opts.json) {
466
+ console.log(JSON.stringify(summary, null, 2));
467
+ } else {
468
+ console.log("Health Summary:");
469
+ console.log(`\n Metrics: ${summary.metrics.total} recorded`);
470
+ if (summary.metrics.types.length) {
471
+ console.log(` Types: ${summary.metrics.types.join(", ")}`);
472
+ }
473
+ console.log(`\n Medications: ${summary.medications.total} total, ${summary.medications.active} active`);
474
+ console.log(`\n Appointments: ${summary.appointments.total} total`);
475
+ console.log(` Upcoming: ${summary.appointments.upcoming}`);
476
+ console.log(` Completed: ${summary.appointments.completed}`);
477
+ console.log(`\n Fitness (last 30 days):`);
478
+ console.log(` Sessions: ${summary.fitness.recent_stats.total_sessions}`);
479
+ console.log(` Total minutes: ${summary.fitness.recent_stats.total_minutes}`);
480
+ console.log(` Total calories: ${summary.fitness.recent_stats.total_calories}`);
481
+ }
482
+ });
483
+
484
+ program.parse(process.argv);
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Database connection for microservice-health
3
+ */
4
+
5
+ import { Database } from "bun:sqlite";
6
+ import { existsSync, mkdirSync } from "node:fs";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { MIGRATIONS } from "./migrations.js";
9
+
10
+ let _db: Database | null = null;
11
+
12
+ function getDbPath(): string {
13
+ // Environment variable override
14
+ if (process.env["MICROSERVICES_DIR"]) {
15
+ return join(process.env["MICROSERVICES_DIR"], "microservice-health", "data.db");
16
+ }
17
+
18
+ // Check for .microservices in current or parent directories
19
+ let dir = resolve(process.cwd());
20
+ while (true) {
21
+ const candidate = join(dir, ".microservices", "microservice-health", "data.db");
22
+ const msDir = join(dir, ".microservices");
23
+ if (existsSync(msDir)) return candidate;
24
+ const parent = dirname(dir);
25
+ if (parent === dir) break;
26
+ dir = parent;
27
+ }
28
+
29
+ // Global fallback
30
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
31
+ return join(home, ".microservices", "microservice-health", "data.db");
32
+ }
33
+
34
+ function ensureDir(filePath: string): void {
35
+ const dir = dirname(resolve(filePath));
36
+ if (!existsSync(dir)) {
37
+ mkdirSync(dir, { recursive: true });
38
+ }
39
+ }
40
+
41
+ export function getDatabase(): Database {
42
+ if (_db) return _db;
43
+
44
+ const dbPath = getDbPath();
45
+ ensureDir(dbPath);
46
+
47
+ _db = new Database(dbPath);
48
+ _db.exec("PRAGMA journal_mode = WAL");
49
+ _db.exec("PRAGMA foreign_keys = ON");
50
+
51
+ // Create migrations table
52
+ _db.exec(`
53
+ CREATE TABLE IF NOT EXISTS _migrations (
54
+ id INTEGER PRIMARY KEY,
55
+ name TEXT NOT NULL,
56
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
57
+ )
58
+ `);
59
+
60
+ // Apply pending migrations
61
+ const applied = _db
62
+ .query("SELECT id FROM _migrations ORDER BY id")
63
+ .all() as { id: number }[];
64
+ const appliedIds = new Set(applied.map((r) => r.id));
65
+
66
+ for (const migration of MIGRATIONS) {
67
+ if (appliedIds.has(migration.id)) continue;
68
+
69
+ _db.exec("BEGIN");
70
+ try {
71
+ _db.exec(migration.sql);
72
+ _db.prepare("INSERT INTO _migrations (id, name) VALUES (?, ?)").run(
73
+ migration.id,
74
+ migration.name
75
+ );
76
+ _db.exec("COMMIT");
77
+ } catch (error) {
78
+ _db.exec("ROLLBACK");
79
+ throw new Error(
80
+ `Migration ${migration.id} (${migration.name}) failed: ${error instanceof Error ? error.message : String(error)}`
81
+ );
82
+ }
83
+ }
84
+
85
+ return _db;
86
+ }
87
+
88
+ export function closeDatabase(): void {
89
+ if (_db) {
90
+ _db.close();
91
+ _db = null;
92
+ }
93
+ }